HomeBlogExternalInterface is unreliable

ExternalInterface is unreliable

“What are you supposed to do if you're the chef at Les Halles
and your fishmonger is giving you smelly fish?”

Joel Spolsky

So while playing with ExternalInterface I found quite a big problem with it.  It's serious enough to call it “unreliable” because it mangles your data in strange and undocumented ways.  Here's a quick Flash applet to demonstrate that:

Now, if you write some text in the first textarea (from the yellow area, which is Flash) and press “Send to JS”, the second textarea should fill with that data.  If you type into the second textarea and press “Send to Flash”, the Flash textarea should fill with data coming from JS.

The problem

Now try to send the following text: “&”.  From JS to Flash it works as expected; if you try from Flash to JS, however, you'll only get “&”.  Why in the world does it decode HTML entities is beyond me.

Next problem: try to send one backslash (“\”) character from Flash to JS and enjoy this fabulous JS error in your JS console:

Error: syntax error
Source Code:
try { __flash__toXML(getFromFlash("\")) ; } catch (e) { "<undefined/>"; }

Note that all problems only happen one way (that is, when you try to send from Flash to JS).  The other way around all works as expected.

I spent a few hours investigating this.  There's no elegant solution (but though I imagined one, see below).  The root of the problem seems to be the fact that, ExternalInterface is actually cheating. :)  From the official documentation:

ExternalInterface is similar in functionality to the fscommand(), CallFrame() and CallLabel() methods, but is more flexible and more generally applicable. Use of ExternalInterface is recommended for JavaScript-ActionScript communication.

From ActionScript, you can call any JavaScript function on the HTML page, passing any number of arguments of any data type, and receive a return value from the call.

From JavaScript on the HTML page, you can call an ActionScript function in Flash Player. The ActionScript function can return a value, and JavaScript receives it immediately as the return value of the call.

What my research concluded is that things doesn't stand quite so.  In fact, Flash-JS communication is done via pure strings.  Therefore, if you try to send an object, Flash will actually serialize it to a string, and make sure to deserialize it on the other side, so it's all transparent to the developer.  For this reason you can't pass true object references (let alone functions).  An object might contain methods, but as you can guess, they are lost on the other side.  (In fact I think that if you try to pass an object that contains functions, you'll get an error right away).

This serialization is handled by a few functions that Flash injects into the browser's global object (window).  They are prefixed with __flash__.  Google for __flash__toXML to find more information.  The problem is that serialization is done poorly (and for some reason it converts objects to XML!  how dumb is that?!).

One other thing I don't understand is why does it try to serialize a string to, umm, a string?!  I mean, a string containing one backslash is properly encoded in memory—it could have been sent “as is”.  But no, what it does in fact is something like this:

eval('"' + string + '"')
// which turns out to
eval('"\"')
// which errors out as the string is not terminated

All this over engineering is beyond me...

The code

Here's the code in my Flash applet, though I doubt it has any part of the blame:

import flash.external.ExternalInterface;

function onBtnClicked() {
	ExternalInterface.call("getFromFlash", txtArea.text);
};

function getFromJS(data) {
	txtArea.text = data;
};

sendBtn.addEventListener("click", onBtnClicked);

if (ExternalInterface.addCallback("send", null, getFromJS)) {
	txtArea.text = "ExternalInterface initialized";
} else {
	txtArea.text = "ExternalInterface unavailable!";
}

The solution

Beware, this is very ugly. :-)  The solution is to take the problem in our own hands and therefore encode dangerous characters ourselves, before sending them to JS.  I determined the following characters to be problematic: backslash (\), quote (") and ampersand (&).  Here's my method of dealing with it:

data = data.split("%").join("%25")
           .split("\\").join("%5c")
           .split("\"").join("%22")
           .split("&").join("%26");

// safe to send data now
ExternalInterface.call("alert", data);

So I chosen to encode special chars using the “%XX” notation.  I'm encoding “%” first, so we can safely use it for other characters.  Then backslash, then quotes, then ampersand.

Note how complex it has to be because Flash (8 at least) doesn't support RegExp-s, nor does it have a string.replace function.

On the JS side, when I receive data I'm doing the reverse transformation:

data = data.replace(/%22/g, "\"")
           .replace(/%5c/g, "\\")
           .replace(/%26/g, "&")
           .replace(/%25/g, "%");

A different solution I found on some sites was to use escape() in ActionScript, and unescape() in JavaScript.  This should be theoretically faster since it's one function call (and there's a good chance that this function is implemented in low level C); however, Unicode characters get mangled in translation (curiously, this is a documented bug).

FABridge

My friends over InterAKT pointed out a new solution that seems to give us The Right Thing: FABridge.  Using it you can even pass functions and object references back and forth, which is definitely cool.  It requires Flex and Flash 9 player, which for me at this point are pretty heavy requirements; but it's cool nonetheless and I'll probably switch to it in the future.

Comments

  • By: brio1337Feb 24 (01:57) 2009RE: ExternalInterface is unreliable §

    Thanks for posting this. I just encountered the same problem trying to send backslashes in a string from Flash to JS, and noticed they were getting corrupted. I'll try your idea of escaping and unescaping these characters manually.

    A while ago I noticed another similar problem with ExternalInterface. If the string you are sending ends with a space character, you may lose that space on the other side. I solved this by adding a known character at the beginning and end of the string before sending it, and stripping the character on the other side. This way, the white space in my string didn't get silently dropped. You may want to add this functionality to your string munging functions! :)

  • By: Filipe GiustiMar 31 (06:03) 2009RE: ExternalInterface is unreliable §

    I've just implemented the communication over FABridge, and it has the same problem...

  • By: thepoxJun 21 (09:12) 2009RE: ExternalInterface is unreliable §

    in flash i encode a string using Base64Encoder.
    I send the encoded string to js.
    I expect to see: aVZCT1J3...
    Instead i get: iVBORw0KG...
    A encoded string has no special characters.

  • By: LeO29Jul 15 (19:39) 2009RE: ExternalInterface is unreliable §

    it's pretty easy actually....if you use htmlText instead of .text then the html characters will be returned....but this also requires you to have CData tage around the information you're passing to flash.

  • By: ThomasOct 14 (14:30) 2010RE: ExternalInterface is unreliable §

    Glad to see that I'm not the only one who ran into this issue.
    It looks like there's two problems here:
    1. unescaped backslashes are passed to __flash__toXML
    2. __flash__toXML does not process ampersands.

    So the solution might be as simple as

      data = data.split("\\").join("\\\\")
                 .split("&").join("&amp;")

    on the flash-side with no action required on the JS side.

Page info
Created:
2007/11/21 12:44
Modified:
2007/11/21 13:47
Author:
Mihai Bazon
Comments:
7
Tags:
flash, javascript, programming, web design
See also