I stumbled across an odd bug the other day that caused input boxes to go crazy when their value was being populated with a string containing HTML links in it. For example, <a href="particletree.com">particletree</a> would produce the following result:

safari-inputs

As you can see in the picture, the <b> tags worked fine. Since only the link broke things, and links have double quotes in them, surely it had to be an escaping issue. But to blame escaping seemed kind of odd because this problem only happens in Safari. So to track it down, I set up a few tests.

Is innerHTML the Problem

The first test aims to find out if the problem is related to innerHTML. The idea is to place a value inside of a text input in three different ways: in the markup, in JavaScript using the value attribute, and by dynamically creating an input and placing it inside of the innerHTML of a div.

Here is the markup:

<div><input type="text" value="<a href=&quot;http://particletree.com&quot;>particletree</a>" /></div><div><input id="builder" type="text" /></div><div id="inner"></div>

And then the JavaScript to populate the inputs:

build = document.getElementById('builder');
build.value = '<a href="particletree.com">particletree</a>';inhtml = document.getElementById('inner');
inhtml.innerHTML = '<input type="text" value="<a href=&quot;particletree.com&quot;>particletree</a>" />';

So after this test is created and run, everything checks out alright. The page loads with 3 text inputs all populated with <a href="particletree.com">particletree</a>.

Multiple Inputs

As the original screenshot shows, there are multiple inputs being created and modified. Maybe placing 3 inputs inside of a div using innerHTML will create a different effect than placing 1. The test was the same as above except with 3 inputs per method, and the result was the same: everything worked just fine.

What is Wrong with my Code

Since the problem seemingly wasn’t related to JavaScript or the browser, I went to look for discrepancies between my project code and the tests. The project code loops a variable amount of times creating an input on each iteration. The difference was in how I was placing the inputs in the DOM. The project code looks like this:

for(var i = 0; i < count; i++) {
    document.getElementById('inner').innerHTML += '<input type="text" value="<a href=&quot;particletree.com&quot;>particletree</a>" />;
}

Instead of looping through and building a giant string to insert, I was inserting on every iteration using +=. This is what I should have been doing:

var choices = '';
for(var i = 0; i < count; i++) {
    choices += '<input type="text" value="<a href=&quot;particletree.com&quot;>particletree</a>" />;
}
document.getElementById('inner').innerHTML = choices;

Obviously, the first approach was poor in many aspects. Worst of all, it had to access the innerHTML property every iteration causing poor performance in the code. But regardless of the bad coding decision, why did the approach break in Safari? I did an alert after every iteration to find out that Safari edits the string when using the += operator. Here is what was being placed on the page:

value="<a href=" particletree.com"="">particletree"

Notice the addition of a space before the “p” in the first “particletree,” the =”” and the missing closing a tag. I’m glad this problem happened to alert me to replace poor code in the first place, but it was definitely an interesting and aggravating one.

HTML Form Builder
Ryan Campbell

Setting Input Values through innerHTML in Safari by Ryan Campbell

This entry was posted 4 years ago and was filed under Notebooks.
Comments are currently closed.

· 7 Comments! ·

  1. Chris Heilmann · 4 years ago

    It is generally a bad idea to concatenate strings with += because of MSIE’s bad memory handling of strings. The safer (and a lot quicker for large strings - with 10000 characters MSIE might lock up for up to 15 minutes) way is to push the data into an array and join the array before writing out the HTML.

  2. Ryan Campbell · 4 years ago

    Good call. I just ran some quick speed tests and noticed a 1 to 3 millisecond improvement on strings with roughly 250 characters in Safari. I’ll test some more in IE later to see what I can come up with. For larger strings, the improvement is deifnitely worth it.

  3. George · 4 years ago

    @Chris

    It’s funny… += to build a string is commonplace, but because MSIE doesn’t handle it well, we are “recommended” to change our code to suit their inefficiencies? I’m all for optimizing code, and making it clean, but sometimes tweaking to enhance the MSIE experience seems over gracious for a browser that doesn’t do much to help the developers.

    The correct answer, is that Microsoft should fix their code, to make += concatenation work properly, without huge memory issues. String manipulation is something that should just zip along in browsers made in 2005+. Creating an Array (temp object), then calling dozens of methods on it to push the content size… then another to join it all back into one string is just silly.

  4. Chris Heilmann · 4 years ago

    @George,

    MSIE is only a symptom, the cause is that string concatenation is always pricy. If you search for string concatenation you find workarounds for the += problem in almost any language: Java, C#, Python, JavaScript, even Pascal :). You are running into doors if you tell me that we shouldn’t hack around MSIE’s inefficiencies, but this is a general programming problem of the memory allocation strings get in comparison to adding to an Array. Firefox has the same problem, not to the same extend, but it has.

  5. Bramus! · 4 years ago

    Couldn’t pinpoint the problem either … great find and writeup!

  6. Nephilim · 4 years ago

    Using += is not the problem. You’d have the same problem with, say, i = i + j.

    The problem is that when Safari passes back the innerHTML, they give you the string with the html entities inside the tag properties decoded. In other words, when you query the innerHTML, those quote entities come back as quote literals.

    If you want to re-submit them to Safari’s DOM, then, you’ll need to re-escape them using regular expressions or something. Otherwise, you get semi-unpredictable results, as you saw, as Safari tries to parse what you mean by having quote marks in the wrong places.

  7. Mark Rowe · 4 years ago

    Note that this particular issue is fix in the newest versions of WebKit, Safari’s HTML rendering engine. You can grab a nightly build of WebKit and verify that your original code works as you would expect.