Print unescaped HTML

I’m converting an element of an already built webapp to use Elm. I have all the functionallity needed except printing unescaped HTML in a paragraph but I haven’t found a way to do it.

The source of the data I want to print is trustworthy so no worries for js injection and whatnot.

So my question is how should I achieve this?

There’s a discussion about this at elm#172.

So, the official answer here is to use Web Components.

Please take everything of the following with a grain of salt, since it technically breaks guarantees and security features of Elm. It might even stop working at the next patch release of elm/virtual-dom.


There is a really simple but hackish way to avoid Web Components (and their polyfills and dependencies), if all you want to do is render a static HTML string from your Elm app.

Elm specifically checks for the property name innerHTML to prevent you from defining something like

innerHTML : String -> Attribute msg
innerHTML = Json.Encode.string >> Html.Attributes.property "innerHTML"

(see here and in the Kernel Code)

There is a very simple way to get around this limitation, if we could just use a different property other than “innerHTML” though!

Using Object.defineProperty, you can provide an arbitrary getter function instead of having a real backing field. We can use this to define a proxy-property for innerHTML on the HTMLElement “class” with a different name:

Object.defineProperty(HTMLElement.prototype, "mcHammerInnerHTML", {
    get () {
        return this.innerHTML
    },
    set (value) {
        this.innerHTML = value
    }
})

With this we can work-around the protective measures (against XSS attacks) the Elm Virtual DOM enforces:

innerHTML = Json.Encode.string >> Html.Attributes.property "mcHammerInnerHTML"

-- [...]

div [ innerHTML "<b>Can</b> touch this!" ] []

And there you go! You have your innerHTML property back. Your mileage may vary, and if you are not careful, the Virtual DOM may override your stuff, or break entirely. You have been warned :slight_smile:

https://ellie-app.com/78LvFwt5xWpa1

4 Likes

You could use hecrj/html-parser:

module Main exposing (main)

import Html exposing (Html)
import Html.Parser
import Html.Parser.Util


main : Html msg
main =
    Html.div [] <|
        case Html.Parser.run "<h1>Header</h1><p>A paragraph with <b>bold</b> text.</p>" of
            Ok html ->
                Html.Parser.Util.toVirtualDom html

            Err err ->
                [ Html.text (Debug.toString err) ]

https://ellie-app.com/78VdLqGMSWda1

If you really trust the input (as it will not be sanitized) and the size overhead is acceptable (or you already use it in the application), you could also use elm-explorations/markdown with sanitize = False, because HTML is valid markdown:

main : Html msg
main =
    Markdown.toHtmlWith
        { githubFlavored = Nothing
        , defaultHighlighting = Nothing
        , sanitize = False
        , smartypants = False
        }
        []
        "<h1>Header</h1><p>A paragraph with <b>bold</b> text.</p>"

https://ellie-app.com/78VfdLnfbw5a1

I would use a web component if performance was critical and I would most likely use something like DomPurify or sanitize-html anyway (trusted input can become untrusted later or because of a vulnerability).

4 Likes

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.