Perfection kills

Exploring Javascript by example

Named function expressions demystified

June 15th, 2009 by kangax

After a couple of months, I have finally finished an article on named function expressions. It’s meant to demystify some of the common misconceptions I’ve seen on the web, take an in-depth look at cross-browser quirks and explain how to safely “work around” them. Quite obviously, it also explains what named function expressions are good for and how to “take advantage” of them in your applications.

If you have suggestions or find any mistakes, please let me know. Hope you like it.

http://yura.thinkweb2.com/named-function-expressions/

Categories: Uncategorized 7 Comments »

Those tricky functions!

June 1st, 2009 by kangax

Are you still trying to write a regex matching decompiled function in Javascript? Perhaps, trying to detect a native method? Here’s a short but fun list of what you’ll be facing in various browsers. This is, of course, all very similar to userAgent adventures; Non-standard feature that is as reliable as my old 28K modem.

It’s probably better to abandon this idea altogether. Or at least not rely on it, and maybe use only for debugging purposes. Or, if you do like adventures after all, at least know what you’re dealing with:

Safari 2.0.4

Does not match function expression syntax. Missing identifier in function expressions.

  Object; // (Internal Function)
  document.getElementById; // [function]
  (function foo(){ return a+b; }); // function(){ return a+b; }

Chrome 1.x

Missing identifier

  window.focus; // function () { [native code] }

Blackberry

Unique representation of function body. Source code is not available for anything but “small” functions

  window.XMLHttpRequest;
  // function XMLHttpRequest() { [native code for XMLHttpRequest.XMLHttpRequest, arity=1] }
 
  function foo(){ /* ~10 lines of code */ };
  // function foo() { \\Source code is not available. [1035593284ED8CD9D0734E9B14EF4F3FF6BE9686] }

Opera 10a

Missing identifier

  Array.prototype.push; // function () { [native code] }

Opera 10a Turbo Edition

Unique representation of function body

  document.getElementById; // function getElementById() { /* source code not available */ }

Caja

Unique representation of function body; Augmented identifier

  function f(){}; // function f() { [cajoled code] }
  var f = function(){}; // function f$_var() { [cajoled code] }

Do you have any other examples of strange-looking functions? Please, share. Perhaps, PPK will tell us how other mobile browsers handle function decompilation.

Categories: don'ts 1 Comment »

Feature testing CSS properties

May 11th, 2009 by kangax

Contrary to many beliefs, detecting CSS properties support with Javascript is not very complicated. One of the CSS-related tests in Common Feature Tests suite is IS_CSS_BORDER_RADIUS_SUPPORTED which looks as simple as this:

var IS_CSS_BORDER_RADIUS_SUPPORTED = (function() {
  var docEl = document.documentElement, s;
  if (docEl && (s = docEl.style)) {
      return typeof s.borderRadius == "string" 
        || typeof s.MozBorderRadius == "string" 
        || typeof s.WebkitBorderRadius == "string" 
        || typeof s.KhtmlBorderRadius == "string";
  }
  return null;
})();

As you can see, testing a CSS property boils down to an inference drawn from the presence of the same named property in a style of an arbitrary element. If you wanted to test support for a “marginLeft” property, you would do it like so:

var el = document.createElement('div');
typeof el.style.marginLeft == 'string'; // true
typeof el.style.marginLeft2 == 'string'; // false

An alternative (and less verbose) way involves replacing typeof operator with in operator:

var el = document.createElement('div');
'marginLeft' in el.style; // true
'marginLeft2' in el.style; // false

in was actually used in earlier versions of CFT but I find such inference too weak. in doesn’t check type of a property; it merely determines property existence, and would return true even if it had undefined (or any other, non-string) value.

IS_CSS_BORDER_RADIUS_SUPPORTED doesn’t just test CSS3 “borderRadius” property. It also tries some of the known proprietary, vendor-specific variations - “MozBorderRadius”, “WebkitBorderRadius” and “KhtmlBorderRadius”. While tweaking this test a couple of days ago, I realized that if I wanted to test CSS3 “boxShadow” property, I would need to follow the same logic and “iterate” over the very same prefixes - “MozBoxShadow”, “WebkitBoxShadow” and so on. Such duplication was clearly not the way to go and a more generic function seemed like a better solution.

getStyleProperty

What it all led to was a very simple getStyleProperty helper:

var getStyleProperty = (function(){
 
  var prefixes = ['Moz', 'Webkit', 'Khtml', 'O', 'Ms'];
 
  function getStyleProperty(propName, element) {
    element = element || document.documentElement;
    var style = element.style,
        prefixed;
 
    // test standard property first
    if (typeof style[propName] == 'string') return propName;
 
    // capitalize
    propName = propName.charAt(0).toUpperCase() + propName.slice(1);
 
    // test vendor specific properties
    for (var i=0, l=prefixes.length; i<l; i++) {
      prefixed = prefixes[i] + propName;
      if (typeof style[prefixed] == 'string') return prefixed;
    }
  }
 
  return getStyleProperty;
})();

getStyleProperty follows the same logic and returns a first found CSS property on an arbitrary (or optionally specified) element. If you run getStyleProperty('borderRadius') in Mozilla-based browser, it would return “MozBorderRadius”; if it was a Webkit-based client, a “WebkitBorderRadius” would be returned. Finally, if no property was found, methods would exit with undefined.

Simple as that.

The way you would test a property is by comparing returned result’s type to “string”:

if (typeof getStyleProperty('borderRadius') == 'string') {
  // property is supported
}

I didn’t want to recreate a prefixes array every time function is called and stored it in a closure. I also used function declaration inside that closure, rather than returning an anonymous function expression, so that getStyleProperty had a descriptive identifier. There’s a document.documentElement used as a generic element (in case none is provided as a second argument), but I could as well have created a new one with document.createElement.

You’re obviously free to improvise with these subtleties as you find appropriate, as long as the actual testing mechanism is left intact.

Performance considerations

I don’t consider execution speed very crucial for a method such asgetStyleProperty, since it would most likely be executed once or twice and probably during the load time. Nevertheless, two basic optimizations come to mind.

Caching a property is one of them. We can simply create a “private” object that maps original (passed into function) property to an actual (possibly prefixed) one and use that object as a cache:

var getStyleProperty = (function(){
 
  var prefixes = ['Moz', 'Webkit', 'Khtml', 'O', 'Ms'];
  var _cache = { };
 
  function getStyleProperty(propName, element) {
    element = element || document.documentElement;
    var style = element.style,
        prefixed,
        uPropName;
 
    // check cache only when no element is given
    if (arguments.length == 1 && typeof _cache[propName] == 'string') {
      return _cache[propName];
    }
    // test standard property first
    if (typeof style[propName] == 'string') {
      return (_cache[propName] = propName);
    }
 
    // capitalize
    uPropName = propName.charAt(0).toUpperCase() + propName.slice(1);
 
    // test vendor specific properties
    for (var i=0, l=prefixes.length; i<l; i++) {
      prefixed = prefixes[i] + uPropName;
      if (typeof style[prefixed] == 'string') {
        return (_cache[propName] = prefixed);
      }
    }
  }
 
  return getStyleProperty;
})();

Another optimization is to cache vendor prefix, such as “Moz” or “Webkit” rather than a specific property. An assumption we’re making here is that if one prefixed property is found in an element’s style, than that prefix can safely be prepended to any other property resulting in a “proper” string. I’m not sure if such inference is a good thing, since clients are obviously not limited to implementing only one type of vendor-specific properties; I can imagine Khtml-based clients implementing both - Khtml- and Webkit- properties.

It might be safer to just stick to “regular” caching mechanism (if any at all), as in the previous snippet.

Inference downsides

Theoretically speaking, the inference we are relying on for getStyleProperty is not all that strong. A mere existence of a CSS property doesn’t tell us about an actual implementation and its conformance to a specification. A browser might have “borderRadius” property with a proper string value; it could allow to assign to that property and even set its value to a specified one after assignment; yet, it could never make borders rounded. The problem is that many CSS3 declarations affect document in such way that it is impossible to detect their effect on a DOM. Border’s radius and box’s shadow, text’s stroke and text overflow (ending with an ellipsis) are all purely visual aspects. If “marginLeft” conformance can be checked by testing element’s offset (as it is represented in a DOM), most of the CSS3 properties don’t provide such luxury.

This is an important thing to remember.

CFT

getStyleProperty is now part of CFT suite (source of which you can find on github). There’s also a simple test page with some of the common CSS3 properties tested.

As always, I’d love to hear any suggestions/corrections you have.

Categories: sniff busting, cft, Uncategorized 6 Comments »

Detecting event support without browser sniffing

April 1st, 2009 by kangax

One of the pain points of feature testing in client-side scripting is that for event support. DOM doesn’t really specify any means to detect exactly which events browser understands and can work with. If you’d like to know if a browser supports, say, “dblclick” event, you’re pretty much out of luck. This is probably the reason why so many scripts on the web employ unreliable browser sniffing in such cases. One of the most common events that people sniff for are IE’s proprietary mouseenter/mouseleave, Opera’s impotent contextmenu, and input-related onbeforepaste, onbeforecut, etc. which are present in IE and WebKit, but not in Mozilla-based browsers.

Since browser sniffing is completely unreliable (as well as unmaintainable and fragile), we need a better way to detect events.

An obvious solution might be to employ an actual testing - create an element, attach an event listener, fire an event from that element and check if event listener gets executed. This solution is unfortunately quite brittle and is often too cumbersome. Simulating key events, for example, is currently hardly supported across browsers. Moreover, many events can be considered too obtrusive and interfere with user experience. It is also possible that events such as “scroll” and “resize” are prevented by popup-blockers and so can not be reliably tested.

It’s not widely known, but there actually is a quite robust way to detect most of the DOM L2 events. The trick is that many modern browsers report property corresponding to an event name as being existent in an element:

  'onclick' in document.documentElement; // true
  'onclick2' in document.documentElement; // false

Unfortunately, this is not the case with Firefox. Besides, browsers that do support this, sometimes don’t allow to test an arbitrary event on an arbitrary element. An event must be checked on an element that could actually originate that event:

  'onreset' in document.documentElement; // false
  'onreset' in document.createElement('input'); // true

To work around Firefox, we can employ a slightly different strategy (recommended by David Mark). The workaround is based on the fact that some of the browsers, including Firefox, actually create methods on an element when an attribute with the name corresponding to a “known” event is set on that element:

  var el = document.createElement('div');
 
  el.setAttribute('onclick', 'return;');
  typeof el.onclick; // "function"
 
  el.setAttribute('onclick2', 'return;');
  typeof el.onclick2; // "undefined"

Combining these two approaches, we can create a somewhat robust way to detect an event support. A generic isEventSupported function would look like:

  var isEventSupported = (function(){
    var TAGNAMES = {
      'select':'input','change':'input',
      'submit':'form','reset':'form',
      'error':'img','load':'img','abort':'img'
    }
    function isEventSupported(eventName) {
      var el = document.createElement(TAGNAMES[eventName] || 'div');
      eventName = 'on' + eventName;
      var isSupported = (eventName in el);
      if (!isSupported) {
        el.setAttribute(eventName, 'return;');
        isSupported = typeof el[eventName] == 'function';
      }
      el = null;
      return isSupported;
    }
    return isEventSupported;
  })();

You can now check for “contextmenu” support with - isEventSupported("contextmenu") - instead of an inferior - navigator.userAgent.indexOf('Opera') > -1. If Opera “fixes” “contextmenu” event in its future versions, there’s a big chance that isEventSupported will just evaluate to true and you won’t need to change a single line of code (to make whatever relies on “contextmenu” event - work)

You can also use a stripped down version of this function, for detecting, say, only Mouse events:

  function isMouseEventSupported(eventName) {
    var el = document.createElement('div');
    eventName = 'on' + eventName;
    var isSupported = (eventName in el);
    if (!isSupported) {
      el.setAttribute(eventName, 'return;');
      isSupported = typeof el[eventName] == 'function';
    }
    el = null;
    return isSupported;
  }

And then use - isMouseEventSupported("mouseenter") instead of a horrendous - (!!window.attachEvent && !window.opera) :)

The only oddity I noticed with this method was IE reporting false for “unload” event. “unload” can still be easily checked in a global window object - "unload" in window - returns true in all versions of IE that I tested (6-8). That expression can, of course, produce false positives if there’s a global “unload” variable, but, as a workaround, you can always try deleting the variable and see if in still returns true.

A minor downside to this test is that it doesn’t allow to detect Mutation Events. Fortunately, detecting those is not very complex. You can find an example of a perfect feature test for DOMAttrModified in Diego Perini’s NWMatcher.

I made a simple test case, listing all of the events specified in DOM L2 and corresponding results of running isEventSupported on them.

Give it a try, and enjoy a feature testing!

Categories: sniff busting 15 Comments »

DOMLint - resolving name conflicts

March 9th, 2009 by kangax

DOMLint test suite

One stupid aspect of DOM is the way it allows to access certain elements as properties of their containing “parent” element. Form controls, for example, can be accessed by their “name” through property access of <form> element containing them:

<form>
  <input type="text" name="foo">
</form>
...
document.forms[0].foo; // "[object HTMLInputElement]"

At first, this might seem like a great convenience - surely, it’s easier to write formElement.foo rather than a more verbose formElement.elements.foo. The joy of convenience, unfortunately, starts to fade out once you realize how badly DOM handles conflicts between these “magic” properties and actual properties of a parent element defined as part of its standard DOM interface. Form elements, for example, implement HTMLFormElement interface and that interface consists of 8 properties - elements, length, name, acceptCharset, action, enctype, method, target and 2 methods - submit and reset. The form doesn’t just have these properties. DOM says that HTMLFormElement should inherit from HTMLElement, which in its turn inherits from Element which inherits from Node and so on…

What this means is that a very simple form element happens to have a bunch of very useful methods inherited through one of its numerous interfaces.

What happens if we try to “magically” access an element whose name matches one of those “interface” properties? Do we get an element or a property/method? The horrible truth is that we usually get an element. Magical properties turn out to be too magical and shamelessly shadow all of those useful methods we might need to use:

<form>
  <input type="text" name="submit" value="Submit!">
  <input type="text" name="style">
</form>
...
<script type="text/javascript">
  document.forms[0].submit; // "[object HTMLInputElement]"
  document.forms[0].style; // "[object HTMLInputElement]"
</script>

Just like that, having 2 simple elements with somewhat unfortunate names, we end up not being able to use a very crucial `submit` method, and a not less useful `style` property.

Even creepier examples demonstrate that names on form elements themselves can cause the same mess:

<form name="getElementById">
  ...
</form>
<form name="body">
  ...
</form>
...
<script type="text/javascript">
  document.getElementById; // "[object HTMLFormElement]"
  document.body; // "[object HTMLFormElement]"
</script>

and -

<form name="window" onclick="console.log(window, setInterval)">
  <input name="setInterval">
</form>
// when clicked, logs [object HTMLFormElement], [object HTMLInputElement]

The latter example actually shows another annoyance - scopes of intrinsic event handlers being augmented with element object (as well as a document that an element is contained within).

When working with legacy documents, such unsafe names can lead to quite nightmarish debugging sessions. This is why I created a simple test suite to catch the offenders. It’s called DOMLint and is powered by a wonderful YQL. It is also hosted on github.

DOMLint is a work in progress. It doesn’t yet catch as many of the unsafe names as it could. Finding all of the names is impossible, since many browsers implement proprietary members on global object, document or elements. It nevertheless covers many of the most common ones. Currently, the project page performs 6 tests including the one for Prototype.js conflicts. Each test is thoroughly explained and is accompanied by an example.

I’d like to thank Garrett Smith, whose Unsafe names article made me aware of these issues and has eventually inspired to create a DOMLint. Garrett goes into much more details on the subject and covers even more failing cases. I highly recommend reading it.

I haven’t had time to find out whether YQL allows to send documents as text, so for now the tests are performed against a URL. If you have any suggestions or comments, I will be more than happy to hear them out.

Categories: DOMLint, annoyances 7 Comments »

« Previous Entries