2011/07/31

Third version for seamless replacement of textareas

Sometime ago I wrote a script that allows to use CKEditor, and at the same time keep using older scripts existing on the page that relied on reading the textarea.value (or even writing it), without having to modify them to use the CKEditor API.

The initial version worked with Firefox and Internet Explorer 8, and shortly after I added some adjustments so that Opera was also supported.

Recently a comment in that second post stated that the script gave errors in Chrome, although it tries to detect if the API that I was using is supported, but of course, webkit guys decided to implement the API but not make it available for native properties. Their statement is that treating native properties as overridable would have a negative effect on performance, and as we all know it's much more important to have a fast browser than a browser that allows the developers to do new things; unless you're in their team then you can write a new API for whatever you need and everyone else should use this API because it will change the world, you'll no longer have to use Flash because now you have this API to overcome other problems that we didn't want to fix. Besides the bug tickets commented previously, here's another one: bug 36423

Ok, enough ranting.

As I said, I've worked to find out the problems and the funny fact about this new version is that it works in Chrome but it still fails in Safari 5.1. So you can use it in your site if can restrict the browser used by your users to IE8+, Firefox 3.5+ , Opera 10+, and Chrome 12+ (I don't really know the oldest version of Firefox, Opera and Chrome where this will work, but I wouldn't expect anyone to use old versions of those browsers)


// Modify the default methods in CKEDITOR.dom.element to use .nativeValue if it's available
CKEDITOR.dom.element.prototype.getValue = function()
{
    if (typeof(this.$.nativeValue) == "undefined")
        return this.$.value;

    return this.$.nativeValue;
}

CKEDITOR.dom.element.prototype.setValue = function( value )
{
    if (typeof(this.$.nativeValue) == "undefined")
        this.$.value = value;
    else
        this.$.nativeValue = value;

    return this;
}

// Hook each textarea with its editor
CKEDITOR.on('instanceCreated', function(e) {
 if (e.editor.element.getName()=="textarea")
 {
  var node = e.editor.element.$;

  // If the .nativeValue hasn't been set for the textarea try to do it now
  if (typeof node.nativeValue == "undefined")
  {
   // for Opera & Firefox
   if (!DefineNativeValue(node))
   {
    // IE8 & Webkit
    if (!DefineValueProperty(node))
    {
     alert("Your browser is buggy. You should upgrade to something newer")
     return;
    }
   }
  }

  node.editor = e.editor;

  // House keeping.
  e.editor.on('destroy', function(e) {
   if (node.editor)
    delete node.editor;
  });
 }
});

// This function alters the behavior of the .value property to work with CKEditor
// It also provides a new property .nativeValue that reflects the original .value
// It can be used with HTMLTextAreaElement.prototype for Firefox, but Opera needs to call it on a textarea instance
function DefineNativeValue(node)
{
 if (!node.__lookupGetter__)
  return false;

    var originalGetter = node.__lookupGetter__("value");
    var originalSetter = node.__lookupSetter__("value");
    if (originalGetter && originalSetter)
    {
        node.__defineGetter__("value", function() {
                // if there's an editor, return its value
                if (this.editor)
                    return this.editor.getData();
                // else return the native value
                return originalGetter.call(this);
                }
            );
        node.__defineSetter__("value", function(data) {
                // If there's an editor, set its value
                if (this.editor) this.editor.setData(data);
                // always set the native value
                originalSetter.call(this, data)
                }
            );

        node.__defineGetter__("nativeValue", function() {
                return originalGetter.call(this);
                }
            );
        node.__defineSetter__("nativeValue", function(data) {
                originalSetter.call(this, data)
                }
            );
        return true
    }
    return false;
}

function DefineValueProperty(node)
{
    var originalValuepropDesc = Object.getOwnPropertyDescriptor(node, "value");

 if (!originalValuepropDesc)
  return false;

 // Safari doesn't allow to overwrite the property (but Chrome does)
 if (!originalValuepropDesc.configurable)
  return false;

    Object.defineProperty(node, "nativeValue",
            {
                get: function() {
                    return ( originalValuepropDesc.get ? originalValuepropDesc.get.call(this) : originalValuepropDesc.value );
                },
                set: function(data) {
                    originalValuepropDesc.set ? originalValuepropDesc.set.call(this, data) : originalValuepropDesc.value = data;
                }
   }
        );

    Object.defineProperty(node, "value",
            {
                get: function() {
                    // if there's an editor, return its value
                    if (this.editor)
                        return this.editor.getData();
                    // else return the native value
                    return this.nativeValue;
                },
                set: function(data) {
                    // If there's an editor, set its value
                    if (this.editor) this.editor.setData(data);
                    // always set the native value
                    this.nativeValue = data;
                }
            }
        );
 return true;
}

// Detection, not really needed, but it can help troubleshoting.
if (Object.defineProperty)
{
    // IE 8 and updated webkits
 // Detect Safari
 if (document.head)
 {
  var test = Object.getOwnPropertyDescriptor(document.head, "innerHTML");
  // IE9
  if (!test)
  {
   if (!DefineValueProperty(HTMLTextAreaElement.prototype))
    alert("Unable to define property override on the prototype");
  }
  else
   if (!test.configurable)
    alert("Safari doesn't allow to overwrite native properties");
 }
}
    else if (document.__defineGetter__)
{
    // FF 3.5 and Opera 10
 // We try to get the innerHTML getter for the body, if it works then getting the value for each textarea will work
 // Detect old webkits
 if (!document.body.__lookupGetter__("innerHTML"))
  alert("Old webkits don't allow to read the originalGetter and Setter for the textarea value");
}
    else
{
    // detect IE8 in compatibility mode...
    if (document.documentMode)
        alert("The page is running in Compatibility Mode (" + document.documentMode + "). Fix that")
    else
        alert("Your version of IE is too old");
}

 

2011/07/13

Too many permissions for simple apps

Yesterday I was badly surprised when Reto Meier added a post pointing to an Android game activated by the voice; that sounded fun (although it's clear that it can't be played in too many places), but I wanted to check it out.

The bad surprise came when the market showed me the required permissions for the App:

  • Record sound (obviously, it needs as that's the differential point)
  • Full internet access (ok, some ads as it's a free app)
  • Read and write the contents of the SD card. This isn't too nice, but some apps and games have extra downloads to keep the app itself smaller and they download the data to the SD. We have to accept it until Android provides a better option (check below for my proposal)
  • Read identity and state of my phone. No, this isn't OK. An app doesn't really require to know anything about my phone. Usually this is enough for me to not install an app.
  • Read SMS. What??? Read my SMS? for a game?, are you joking?. So this app requires full internet access, read all the content of my SD card as well as my identity and my SMS? They can ask for my bank account at the same time.
  • Send SMS. Ok, this is enough. What's the difference between this game and those trojans that have been found claiming to be legit Android Apps? How can a user be able to find out that the developer is really a good person and it's requiring those permissions really based on real requirements instead of being used to send premium SMSs and hide them as those trojans do?

Whenever such application with those huge permissions is mentioned by someone that it's otherwise respected, we can expect that people will download it, and by doing it people will be used to accept that apps might require extra permissions. This will lead to "permission blindness" just like people is now blind to the Ads that appear in some webs, people will get used very soon to the fact that any app might want to read your SMS, your contacts and anything else.

So this will destroy any effectivenes of the permissions system, the typical statement "be careful about what you install" and "it's easy to spot a trojan due to its permissions" will be void. Sum it with the lack of review system in the Android Market and you get a big problem just waiting to happen.

According to some comments in that post, the developers answered in Twitter claiming that the SMS is to upgrade the app to a paid version. So... what's wrong with the Android Market? why don't you offer the full version there instead of using SMSs? Does premium SMS work across countries? how much it's gonna cost some random user in Spain like me a premium SMS sent to the USA? If it's just an internal switch in the app, why isn't this done using the existing internet connection? People pay it in your site and then the app is upgraded to full version. Hell, it can even be done showing the user an unlock code and without internet on the phone app itself!

The requirement to read/write the SD card could be fixed if Android had a better system for apps to store their own data:
Currently those apps create random folders anywhere on the SD card and they remain there after the app has been removed. If instead of getting full access to the SD card they had access to just a custom unique folder, then this could be much better:

  1. Any app can read/write to his own folder in the SD, ex. /Android/data/com.google.android.apps.maps/
  2. That folder is only accessed by that app (or other apps like file managers that really request full SD access), there's no need to state it in the permission list
  3. When the app is removed, the uninstaller also cleans up that folder, so that the app has been really uninstalled and nothing remains. If the user wanted to keep the data he can do a backup of the folder before performing the removal.
  4. In summary: the app itself doesn't really need to know the location of that folder, it just knows that it can safely read and write there, and due to that safety it doesn't require an extra permission at install time. Better for the developer, better for the users.

The Android system really needs an improvement in the way that permissions are granted, the user shouldn't have to worry because an app is requesting weird permissions, also, the Android Market should be much more careful and force a review of any app that requires permissions like send SMS, or perform a combination of read SD and get internet access. When an app just requires internet to show ads it's OK, but if that app is also able to read all the data in your SD card (photos, Titanium backups, etc...) then it should be reviewed to provide a warranty that it's a good app and not something evil.

Enough of bad comments, do you want to know a nice game? (no, this post isn't sponsored and I'm not related to the developer)
Test Trap!, it ask for all the permissions that you can expect from a nice game: none, and it can keep you playing it for a while trying to beat yourself.