My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.
Showing posts with label Internet Epxlorer. Show all posts
Showing posts with label Internet Epxlorer. Show all posts

Friday, April 11, 2014

All IE Objects Are Kinda Broken

Update III
Microsoft landed a patch to supported browsers, including IE9 up to IE11 Desktop.
It's not clear what's going on with Windows Phone IE, but I'd expect at least version 11 to be patched since AFAIK Surface devices have been already fixed as promised before.
I personally strongly doubt IE9 Mobile in WP7 will be patched too but finger crossed for at least WP8 hoping with the 8.1 update this problem will dissolve as well as on Desktop!
Update II there is a better solution
Update
I had a chance to talk with @jdalton and even before that, @bterlson confirmed me they are already working on a fix for the next version of IE and considering patching previous versions: I've also added a third alternative solution to the problem itself, suggested by the same John David Dalton ... I've talked about this option later on so you can check what is this about.
I've posted out of surprise and gut because I really appreciate all progress IE is doing since version 9 so let's hope this is some sort of balsamic vinegar stain that it'll be washed away soon in as many IE versions as possible.
Now, down to the bug ...
Thanks to @bga_ that forwarded to me the following tweet, things are even worst than expected (but keep reading for possible solutions):

Bug Deatils

All IEs that support Object.create seem to be affected. If there are only numeric properties in the created object and no new keyword has been used to inizialize the same, hasOwnProperty check as well as isPropertyEnumerable will miserably fail.
// a generic "class"
function OhComeOn() {}

var a = Object.create(OhComeOn.prototype);
a[1] = 1;
a.hasOwnProperty(1);       // false
a.propertyIsEnumerable(1); // false
for (var k in a) {
  // EVEN IF APPARENTLY NOT ENUMERABLE
  console.log(k); // will be the string "1"
}

var b = new OhComeOn;
b[1] = 1;
b.hasOwnProperty(1);       // true
b.propertyIsEnumerable(1); // true
for (var k in b) {
  console.log(k); // will be the string "1"
}

It does not matter if I use a number or a string as property name or value, it's exactly the same. The only way to make IE believe there is some numeric property to consider too is the following one:
var a = Object.create(OhComeOn.prototype);
a[1] = 1;

// could be anything that is not numeric
a._ = 1;

a.hasOwnProperty(1);       // true
a.propertyIsEnumerable(1); // true
for (var k in a) {
  console.log(k); // will be the string "1", then "_"
}

Fixed If A Descriptor Is Used Instead

This is where the fun begins, as soon as a descriptor is used, everything is awesome.
var a = Object.create(
  OhComeOn.prototype,
  {1:{enumerable:true}}
);

a.hasOwnProperty(1);       // true
a.propertyIsEnumerable(1); // true
Even if not specified as propertyIsEnumerable, the hasOwnProperty check will be trustable once descriptors are used instead of direct access.
var a = Object.create(
  OhComeOn.prototype
);

Object.defineProperty(a, 1, {});

a.hasOwnProperty(1);       // true
a.propertyIsEnumerable(1); // false

Fixing Only These Cases

Accordingly with above behavior, we can say that if there is such bug, and no descriptor is used, most likely we are safe simply polyfilling the behavior:
// note: NOT suitable (yet!) for dictionaries!
var create = Object.create;
if (!function(o){
  o[1] = 1;
  return o.hasOwnProperty(1);
}(create({}))) {
  create = (function(create){
    function Class() {
      // never forget to free the
      // reference counter
      // to the external proto!
      Class.prototype = null;
    }
    return function (p, d) {
      return d ?
        create(p, d) :
        new Class(Class.prototype = p)
    };
  }(create));
}
With above function, previous examples should work as expected:
var a = create(
  OhComeOn.prototype
);

a[1] = 1;

a.hasOwnProperty(1);       // true
a.propertyIsEnumerable(1); // true
Unfortunately, this is not the only problem we gonna have with such bug ...

Corrupted And Doomed Dictionaries

What we cannot do is to use above snippet to create dictionaries, since this does not work as we expect:
function Dictionary() {}
// this is not the equivalent of null objects
Dictionary.prototype = null;


// here the proof
var d = new Dictionary;
d.toString; // function {} [native code]

d instanceof Object // true indeed

Setting null as prototype value never worked like that so we need to find a solution ... but how ...
  1. adding and deleting a non numeric property won't work (with latest updated we'll see it works with numeric properties though as hack to fix the problem)
  2. using Object.defineProperty per each property would be boring, verbose, and slow
  3. descriptors, if empty, do not solve the problem
There must be a non numeric property in order to have reliable dictionaries, otherwise these won't be reliable as dictionaries using the most common check we all know since ever: hasOwnProperty.

First Possible Work Around: The in Operator

Take that JSLint, the most secure way to have dictionaries that behaves as dictionaries is to use the in operator that does not suffer from this bug.
var n = Object.create(null);

n[1] = 1;

if (1 in n) {
  alert('hooraaaay');
}
For dictionaries only, in seems to be the preferred choice also because these properties will show up in for/in loops, but this will break like a charm:
var n = Object.create(null);

n[1] = 1;

if (var k in n) {
  // the infamous check
  // that will FAIL in IE
  if (Object.prototype.hasOwnProperty.call(n, k)) {
    alert(k); // will never happen!
  }
}
The problem here is that most developers are still not ready to abandon JSLint ES3 era hints and embrace dictionaries in a way that is hybrid with these platform, so here I am with the only possible extra solution.

Second Work Around: The Falsy Property

There is no way we deal on daily basis with empty strings as property, unless we are implementing some other sort of hack. I am talking about a solution based on a property that will NOT enumerate, but will FIX properties check.
For unobtrusivity sake, this property should be also configurable and writable.
var n = Object.create(null, {'':{
  configurable: true,
  writable: true
}});

n[1] = 1;

{}.hasOwnProperty.call(n, 1);       // true
{}.propertyIsEnumerable.call(n, 1); // true

for (var k in n) {
  console.log(k); // will be **only** '1'
  // since '' is not enumerable
}

Above solution seems to better fit in current JS world where properties are usually filtered as if (key) {obj[key] = value} plus all own properties will be most likely checked over for/in loops.
More On The Choice Of An Empty String
If you are wondering what's special about the empty string here the answer: when you use JSON.parse(str, revivalCallback) the empty string or the empty key means that the JSON object is fully ready and that would be the last iteration. Since empty property has such strong meaning for JSON, I believe nobody really use empty strings anywhere and for no reason in any library. I also believe nobody ever wrote an empty property name in JSON but in any case this is not a problem because the fix I've proposed will never show up in JSON.parse since it's both not enumerable plus JSON has nothing to do with Object.create so it's the safest property I could think about.

Update II - Actually Not Needed!

Thanks again to Dmitry Korobkin and his further researches: We have a proper solution that won't require any empty string hack.
TL;DR if we set a configurable number and we delete it the object will behave as expected later on ... so ...

Wrapping Up A Solution

In order to have above behavior and a solution for all normal objects in place, here the modified version of initial script:
var create = Object.create;
if (!function(o){
  o[1] = 1;
  return o.hasOwnProperty(1);
}(create({}))) {
  create = (function(create, configurable, define){
    return function (p, d) {
      var r = d ? create(p, d) : create(p);
      // if already own property, nothing to do
      return configurable.hasOwnProperty.call(r, 0) ?
        r : (
        // adding and deleting the numeric property
        delete define(r, 0, configurable)[0],
        // so that now the object is "fixed"
        r
      )
    };
  }(
    create,
    {configurable:true},
    Object.defineProperty
  ));
}
Above snippet is the less obtrusive when it comes to Object.getOwnPropertyNames since the empty property will never show up, no matter if it's a dictionary or not.
While this is in my opinion the best fix for the problem, there might some performance implication since the internal class of each object won't be shared anymore and this can cause de-optimizations per each created object.
Putting together a solution that fixes the problem and is not obtrusive is AFAIK not possible so here yet another snippet based on both new chain and the empty property.
var create = Object.create;
if (!function(o){
  o[1] = 1;
  return o.hasOwnProperty(1);
}(create({}))) {
  create = (function(create, empty){
    function Class() {
      Class.prototype = null;
    }
    return function (p, d) {
      // if p is null
      // or there is a descriptor
      return p === null || d ?
        // crete via descriptor or empty
        create(p, d || empty) :
        // chain via good old prototype
        new Class(Class.prototype = p)
      ;
    };
  }(create, {'':{
    enumerable:   false,
    writable:     true,
    configurable: true
  }}));
}
The main particular caveat, a part from the empty property that will show up in Object.getOwnPropertyNames when it comes to Dictionaries, although hopefully we don't need that so much over Dictionaries like instances, is that passing an empty object as descriptor might confuse the initialization so ... just, don't.
var obj = create(
  Array.prototype,
  {} // <==== DON'T DO THIS!
);
If we want to be extra sure that with Dictionaries the Object.getOwnPropertyNames won't expose our initial hack, we can tweak that too via:
Object.defineProperty(
  Object,
  'getOwnPropertyNames',
  function(original){
    var gOPN = original.value;
    function notTheEmptyOne(k) {
      return !!k;
    }
    original.value = function (o) {
      return gOPN(o).filter(notTheEmptyOne);
    };
    return original;
  }(
    Object.getOwnPropertyDescriptor(
      Object,
      'getOwnPropertyNames'
    )
  )
);
Above hack might be improved accordingly, but that's just a hint I believe not so necessary anyway.

The John-David's Alternative: Don't Object.create(null)

Since using a Dictionary is somehow a doomed choice due some V8 and old WebKit __proto__ implementation, it might make sense to use something like Set/Map from ES6 or a simple HashMap like object instead:
function HashMap() {
  // (C) Andrea Giammarchi - Mit Style
  Object.defineProperty(this, '_', {value:
    Object.create(null)
  });
}
Object.defineProperties(
  HashMap.prototype,
  {
    // clear all the values
    // returns the HashMap
    clear: {value: function () {
      var keys = this.keys(),
          i = keys.length;
      while (i--) delete this._['@' + keys[i]];
      return this;
    }},
    // delete a key, returns true/false
    // accordingly if successful
    del: {value: function (key) {
      return delete this._['@' + key];
    }},
    // get the value or undefined
    get: {value: function (key) {
      return this._['@' + key];
    }},
    // returns true if present, false otherwise
    has: {value: function (key) {
      return ('@' + key) in this._;
    }},
    // all stored keys
    keys: {value: function () {
      var r = [], k;
      for (k in this._) r.push(k.slice(1));
      return r;
    }},
    // set a value and returns it
    set: {value: function (key, value) {
      return this._['@' + key] = value;
    }}
  }
);
With above like utility we can have a universally safe ES5 Dictionary that will never perform like an Object.create(null) but will surely work as expected.

Who Fixed The Initial Problem Already?

Not so many libraries I know have a fix for such issue, at least jsCore and my latest prototypal one has been updated using the empty string solution for dictionaries.
The purpose of prototypal is indeed to bring inheritance in every browser, included those not even compatible with Object.create so, if the test is green, you are safe from problems and ready to go.

WTF IE !

Well, I have no other words to describe this absurd bug with plain JavaScript objects ... all I know is that old IE always had problems with plain objects too, i.e.
// IE < 9 [[DontEnum]] bug
for (var key in {toString:1}) {
  alert('old IE has never been here');
}

Today we also have this Object.create(proto) bug which is a very problematic one and specially for dictionaries, where knowing that some number has been set as own property to a null object might not even be such an edge case, surely less edge ... although it took years to realize there is such problem!

Please Fix it ASAP IE/Chakra Team, thank You!

Wednesday, April 29, 2009

Drip under control via another IE memory leak tentative ...

Apparently, using this strategy I can obtain a flat line in Drip monitor:

// function used to remove a node, every attached
// attribute and every nested node via the same procedure
var destroy = (function(destroy){
// WebReflection IE leaks attemp!
function $destroy(node){
while(node.lastChild)
destroy(node.lastChild);
if(node.parentNode)
node.parentNode.removeChild(node);
};
return destroy = destroy.clearAttributes ?
function(node){
if(node.clearAttributes) // Hedger suggestion
node.clearAttributes();
$destroy(node);
} :
$destroy
;
})(document.createElement("script"));


// used to remove everything
function destroyAll(){
destroy(document.documentElement);
};

// used to avoid leaks when the page is refreshed
// or the url is changed
if(this.attachEvent)
attachEvent("onunload", destroyAll);


reasonable performances and apparently a reliable solution.
tested via this code:

attachEvent("onload", function(){
detachEvent("onload", arguments.callee);
for(var i = 0; i < 1000; i++){
a.push(document.body.appendChild(document.createElement("div")));
var node = a[i];
node.innerText = i;
node.obj = a[i];
node.attachEvent("onmouseover", function(){
node.obj.other1 = node;
});
node.onclick = function(){
this.obj.other2 = node;
};
};
});

The procedure is based on assumptions I did in this post about div[expando] and div.removeAttribute(expando)

Friday, April 24, 2009

div[expando] = null OR div.removeAttribute(expando)?

Today we had an interesting conversation in the jQuery developer mailing list, and "surprise", it was about Internet Explorer and memory leaks.
I instantly started some test to understand what the hell is going on there ... and I encountered a dilemma which is not simple at all!

div.prop = 1 IS NOT div.setAttribute("prop", 1)

Some developer thinks that the usage of setProperty and getProperty is basically the same of a manual assignment as generic DOM Element property ... well, they are wrong.
First of all, if we are working with XML we all know that it is not possible to assign a property in this way:

xmlNode.prop = 123; // Error!!!
xmlNode.setAttribute("prop", 123); // OK, but be careful!

The main difference between manual property assignment and the usage of setAttribute/getAttribute is that we are changing HTML or XML properties of the node without control.
The reason is simple, how manual properties are assigned/retrieved is browser dependent, and the only way to be sure we are not modifying a node only internally is to use the setProperty method.
Obviously, it is not that simple in any case still because of browsers implementations. Here a couple of examples:

var div = document.createElement("div");
div.__expando__ = 123;

// FireFox and other browsers
div.getAttribute("__expando__"); // null

// Internet Explorer
div.getAttribute("__expando__"); // 123

Accordingly, the only browser that manages direct assignment as setAttribute shortcut is Internet Explorer.

Moreover, cases are really differents between IE and other browsers ...

While everybody uses setAttribute as a method to change the node in its HTML or XML nature, Internet Explorer uses this method to assign whatever value. Example:

var div = document.createElement("div");

// set an object as an atribute
div.setAttribute("__expando__", {a:"b"});

// FireFox and others will obviously convert the second argument as a string
// to make node representation possible as HTML or XML

// Internet Explorer will set that object as an hidden attribute
// which means that that property will not exists in the
// HTML or XML representation of that object, but will be a property

// this call ...
div.getAttribute("__expando__");

// will generate [object Object] in FireFox and others
// not because we are dealing with an object
// but because the object has been converted into a string
// during setAttribute assignment

// In internet Explorer that cal will retrieve the original
// Object {a:"b"} and this call will produce: "b"
div.getAttribute("__expando__").a; // b in IE, undefined in other browsers


Another interesting thing is that we cannot use delete div.__expando__ in internet explorer for the reason I said before: we cannot delete an attribute via delete, we need to use the removeAttribute method.


var div = document.createElement("div");
div.__expando__ = 123;

delete div.__expando__; // Error in IE

But since IE set attributes of any type, we should bear in mind that everything that is not instanceof Object will be present in the HTML representation of that Element.

var div = document.createElement("div");
div.n = null;
div.obj = {a:"b"};

alert(div.outerHTML);
// <DIV n="null"></DIV>

Accordingly, whatever reason we have behind leaks, we should bear in mind that if that node has been stored somewhere, it will bring with itself every property = notAnObject; included numbers, null, whatever.

To create a memory leak we can simply do something like this:

var div = document.createEement("div");
div.__expando__ = div;

That's it, even if we remove that div and we delete its related variable (div), IE will not remove that node (if present in the DOM) from its used RAM.
Since some developer know it, they obviously try to avoid leaks like this using a syntax which makes sense but which leave traces of "their operate":

var div = document.createEement("div");
div.__expando__ = {iama:"complex object", orwhatever:"value"};

// when div is removed/destroyed
div.__expando__ = null;

// well, as we have seen before:
div.outerHTML;
// <DIV __expando__="null"></DIV>


To completely remove properties then, all we need to do is to use removeAttribute:

var div = document.createEement("div");
div.__expando__ = {iama:"complex object", orwhatever:"value"};

// when div is removed/destroyed
div.removeAttribute("__expando__");

// well, as we have seen before:
div.outerHTML;
// <DIV></DIV>


If you test with a memory leaks detector, you will realize that the result is exactly the same while the final node, leaked or not, will be as clear as possible from other libraries and conflicts will be reduced as well (getAttribute("__expando__") is setted as null will return the string "null" rather than the null value usually returned by getAttribte when it is not there).

What's next? Understand how much performances will be affected and when it is worthy to use removeAttribute rather than = null but at least now we have proven that setAttribute, getAttribute, and direct property assignment are not the same thing.

Thursday, April 23, 2009

vice-versa project, a philosophy rather than a library


Array.forEach(document.all, function(node, i, all){
// vice-versa project
});

well, there are few things I can say here, and first one is: please, if interested, help me!
The second one is my vision, a web where rather than criticisms, there is more collaboration to obtain good overall performances improving code quality.
This is not my old JSL project, this is a new groove, a new project, a non library, something I hope you will all appreciate.
But, if not, I am here to discuss about it :)
vice-versa project
Have fun with Web Development!


Update
Implemented a TaskSpeed test file for vice-versa project. Here first results ordered by average.



browser Pure vice Moo qoox dojo YUI AVG
Dom versa Tools doo
--------------------------------------------------------------------
Safari 4 198 114 392 205 246 410 260.83
Chrome 1 251 273 589 279 336 692 403.33
Opera 10 215 206 764 267 311 659 403.67
FireFox 3 404 670 2962 3557 1197 792 1597.00
IE 8 876 1031 10312 1470 2267 1749 2950.83
IE 7 795 1767 8174 1734 7796 3329 3932.50
IE 6 3941 4686 67590 16611 22388 24591 23301.17