Does the Compose HTML library support anything sim...
# compose-web
f
Does the Compose HTML library support anything similar to
Element.innerHTML()
? https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML
i
For anyone interested, this question came from another slack discussion and resulted in this as a solution which works at the cost of a wrapping span tag.
Copy code
@OptIn(ComposeWebInternalApi::class)
 @Composable
 fun HtmlText(rawHtml: String) {
     val element = document.createElement("span")
     element.innerHTML = rawHtml
 
     ComposeNode<DomNodeWrapper, DomApplier>(
         factory = { DomNodeWrapper(element) },
         update = {
             set(rawHtml) { value -> (node as Element).innerHTML = value }
         },
     )
 }
The general goal here was to allow properly displaying localized strings that have embedded markup such as inline bold or italicized text.
d
In general, there's a lot of useful functionality which you can use once you get access to the raw element backing a Compose HTML widget. You can accomplish this with the
attrs.ref { ... }
method Here's another way to write the above code, which is both fewer lines and does not require internal API access:
Copy code
@Composable
fun HtmlText(rawHtml: String) {
  Span(attrs = {
    ref { 
      element -> element.innerHTML = rawHTML 
      onDispose {}
    }
  }
}
πŸ‘ 1
πŸ‘πŸ» 1
Just a warning however -- you should only be reaching to
innerHTML
as a last resort. @CLOVIS has more experience with the anti-pattern than I do, but it's code that at best needs to be audited carefully, because you can end up introducing unexpected security issues in your site when using it.
c
Thanks for the ping :) Yes,
innerHtml
is the most common entry point for an attacker. If you use it on your app, it's almost guaranteed that's where an attack will come from, because it's one of the only ways an attacker can remotely inject code without physical access. So you must audit all usages to be 100% it's impossible for an attacker to control the source of data. ...but the only real way to be 100% sure the attacker cannot impact the source of data, is if the data is entirely produced by the app without any network access, and in that case, using regular components is just simpler
In the React world, it's called
dangerouslySetInnerHtml
so it's easy to CTRL+F for, and I've seen multiple apps fail a pentest just because it was present in the codebase.
i
If there is a better way to properly render translations I'm all ears. Trivial example
Welcome, <b>Ian</b>
in a simple string sure that's easy to work around breaking it up but in more complex templates this isn't easy to work around without actually parsing the html. Fwiw as well, these strings are pre packaged as part of a localization flow so im not very worried about security risks and it's an app for "Smart" TVs so it isn't necessarily prone to the same attack vectors as a web browser.
c
Well, just be aware your translators (or anyone who can temper with the saved files) can easily remotely execute code on all your customers' TVs, if they want to.
i
Yeah our translations are bundled, they go through PR checks but I get your point. Again if there is a better way to accomplish this I'd be happy to take that approach.
d
I haven't found a lot of standard localization solution best practices on the web, but I wonder if clean way would be to create and host translation files on your server (e.g.
translations.json
,
translations.fr.json
), download that on page startup and parse them into a structure that maps keys to values.
c
What I've seen done in multiple translated apps is to have a custom simplified markdown-like syntax, e.g.
**
for bold and
__
for italic. I'm not sure how much formatting you need, but I feel like that should be enough for almost all use-cases.
πŸ‘ 1
d
A subset of markdown formatting would work great as CLOVIS suggests.
All that said, I mean, you know your space. If you're auditing / testing the text coming in and that's the only way you're using innerHTML, it's probably a subset of situations where it's OK?
πŸ‘ 1
Curious how well Compose HTML performs on a Smart TV, if there's anything you can share. Any performance issues?
πŸ‘€ 1
i
@David Herman actually already do that using kotlin poet and a custom gradle task. We have a simple localization file that looks like
Copy code
{
  "TopLevelId": {
    "Hello": "World"
  }
  "AnotherPage": {
    ....
  }
}
Using gradle I combine all the possible keys, spit out warnings for locale files missing keys and back fill them the base english translations. Finally rewrite that as a js file that is dynamically imported based on the user locale if one exists and also always import english. Lastly, I use poet to generate files like this which provide key mappings + a localization function to walk the user locale lookup with a redundant backup to fall back to english for each localization if somehow that didn't happen properly in the gradle task:
Copy code
public class R {
  public class TopLevelId {
    public companion object {
       public inline val Hello: String
          get() = "TopLevelId.Hello".localized()
    }
  }
}
Copy code
locales["en"] = {"TopLevelId":{"Hello":"World"}}
Copy code
locales["es-ES"] = {"TopLevelId":{"Hello":"Mundo"}}
πŸ‘ 1
Curious how well Compose HTML performs on a Smart TV, if there's anything you can share.
bad
πŸ˜‚ 1
πŸ˜… 1
yes black 1
d
Sorry to hear that. I had some friends working in the Smart TV space ~10 years ago. It was a struggle for sure.
i
tbf that isn't kotlins fault though, they generally have terrible performance
d
Yeah, not blaming Compose HTML here as much as the fact it generates a fairly large JS file (I mean, it includes the Kotlin runtime on it, which is awesome and bananas), and I can imagine Smart TV hardware engineers assumed tiny single or double digit KB files, as everything is stripped down as much as possible to reduce costs.
i
Yeah that really only impacts the load time which is not great on old tvs but seems to be fine on newer ones. You can get around that a bit using their preloading feature. It's the rest of it that is bad, simply parsing json is just plain slow. Animations are generally impossible if they're larger than a button. Transparencies, lol not unless you want to limit yourself to a few FPS. Video playback as well has various complete failures like it seems a lot of these tvs are unable to play vertical orientation video in various formats like hls and mp4. Literally all of that is tolerable though, the biggest issues are that releasing on the Samsung store is the worst experience you can imagine and the TV's all run chromium from 50 years ago.
K 1
😒 2
c
How do you actually run the software day-to-day during development? Do you have some kind of emulator? Or do you have to deploy to an actual TV on each build?
i
I run it locally. I have a module in our project that injects itself into the task graph based on flags allowing us to optionally run a ktor server as a proxy for the various endpoints we need to hit (getting around CORS) and we just inject the correct domains into the build using
buildConfig
like most people already use.