leaverou.me
  @leaverou   
me@leaverou.me

pol•y•fill |ˈpälēfil|

A polyfill is a piece of code (or plugin) that provides the technology that you, the developer, expect the browser to provide natively.

Remy Sharp

A shim that mimics a future API providing fallback functionality to older browsers.

Paul Irish

if (!String.prototype.trim) {
  String.prototype.trim = function() {
    return this.replace(/^\s+|\s+$/g,'');
  };
}
'  JSConf EU     '.trim(); // 'JSConf EU'

No new API to learn

Can be removed later

Making your own

  1. Step 1 Feature detection
  2. Step 2 Implementation
  3. Step 3 Distribution

Step 1 Feature detection

Core ideas:

  1. Check if something exists
    • Better for performance
    • Less, simpler code
  2. Try something, check if it worked
    • More reliable
  3. Combination: Try something, check if something else got created

JavaScript APIs

var supports = { 
  localStorage: 'localStorage' in window, 
  geolocation: 'geolocation' in navigator, 
  querySelector: 'querySelector' in document,
  HistoryAPI: 'pushState' in history,
  /* etc */ 
}; 

CSS properties

supports.property = function(property){
  var dummy = document.createElement('_');
  return property in dummy.style;
};
// Test it:
supports.property('textShadow');

CSS3 properties

var prefixes = ['', '-moz-', '-ms-', '-o-', '-webkit-'];
supports.property = function(property) {
  for (var i=0; i<prefixes.length; i++) {
    var prefixed = prefixes[i] + property;
    dummy.style.cssText = prefixed + ':inherit;';
    if (dummy.style.getPropertyValue(prefixed))
      return property;
  }};

CSS values

supports.RGBA = (function(){
    var elem = document.createElement('_'),
        style = elem.style;
    try { style.color = 'rgba(0,0,0,.5)'; }
    catch(e) { return false; }
    return style.color.indexOf('rgba') > -1;
})();

CSS selectors

supports.selector = function(selector) {
    var style = document.createElement('style');
    style.innerHTML = selector + '{}';
    document.body.appendChild(style);
    var ret = !!style.sheet.cssRules[0];
    document.body.removeChild(style);
    return ret;
};

HTML elements

Optimistic thought:

supports.element = function(tag) {
    var el = document.createElement(tag);
    return el instanceof HTMLUnknownElement;
};
FFFFFFUUUUUUUUUU

HTML elements

More pragmatic:

var supports = {
  canvas: 'getContext' in document.createElement('canvas'),
  video: 'play' in document.createElement('video'),
  progress: 'position' in document.createElement('progress')
};

HTML attributes

var input = document.createElement('input');
supports.placeholder = 'placeholder' in input;
input.type = 'file';
supports.multipleUpload = 'multiple' in input;

Attribute values

var input = document.createElement('input');
input.setAttribute('type', 'number');
supports.spinner = input.type === 'number';

Events

supports.mouseenter = (function() {
  var el = document.createElement('_');
  el.setAttribute('onmouseenter', 'return;');
  return typeof el.onmouseenter === 'function';
})();

http://perfectionkills.com/detecting-event-support-without-browser-sniffing/

Not always that simple

Step 2 Implementation

Follow the spec

…but know when to stop

A polyfill for document.head:

if(!document.head) {
  document.head = document.getElementsByTagName('head')[0];
}

And then…

myIframe.contentDocument.head.appendChild(script);

TypeError: Cannot call method 'appendChild' of undefined

Don't be intrusive

Don’t:

  • Replace the target element with another
  • Manipulate unrelated elements (e.g. body)
  • Add extra elements
  • Manipulate existing attributes
  • Create more than one global variable
  • Add leaky CSS

Unless… You really have to

No strings attached

Code like this:

if(!('open' in document.createElement('details')) {
  var details = document.getElementsByTagName('details');
  for(var i=details.length; i--;) {
    Details.init(details[i]);
  }
}

belongs to your polyfill, NOT the user’s code

Be flexible

  • Attributes & properties might change
  • New elements might get added
  • CSS can be very different
  • There might not be a DOM
  • It might not be HTML

In cases like that… Don't break!

Adapting to change

attributes

Our only tools:

Mutation events

They bubble, so we can use event delegation!

document.addEventListener('DOMAttrModified', function(e) {
    var node = e.target, attr = e.attrName;
    if(/^input$/i.test(node.nodeName) &&
      (attr === 'placeholder' || attr === 'value'))
        Placeholder.update(node);
  }, false);

Adapting to change

properties

  • Accessors (getters and setters)
  • onpropertychange for IE < 7
  • Object#watch() for Firefox

Accessors

The standard ES5 way

Object.defineProperty(obj, 'placeholder', {
  get: function() { return obj.getAttribute('placeholder') },
  set: function(value) { Placeholder.update(obj, value); },
  enumerable: true,
  configurable: true
});

Support: IE8, FF4, Chrome, Safari 5, Opera 12

Accessors

The early nonstandard way

obj.__defineGetter__('placeholder', function() {
		return obj.getAttribute('placeholder') 
	});
obj.__defineSetter__('placeholder', function(value) {
		Placeholder.update(obj, value);
	});

Support: FF3.5, Chrome, Safari 4, Opera 10.10

Combine them: github.com/eligrey/Xccessors

Accessors

IE8 horror stories

IE < 9 thinks that attributes are properties, so:

the previous getter would result in infinite recursion

Accessors

IE8 horror stories

When enumerable: true IE8 throws an exception. Catch it with:

try { /* ... */ } catch(e) {
  if(e.number === -0x7FF5EC54) {
    /* ... */
  }
}

Adapting to change

new elements

  • Element.prototype is not evil in this case
  • If you can’t, use DOMNodeInserted to cater for new elements

Minimize dependencies

Use built-in functionality

matchMedia

window.matchMedia || (window.matchMedia = (function(){
  var div = document.createElement('div');
  div.id = 'mq-test-1';
  div.style.cssText = 'position:absolute;top:-100em';
  return function(q){
    div.innerHTML = '<style media="'+q+'">#mq-test-1{ width:42px; }</style>';
    document.body.appendChild(div);
    var ret = div.offsetWidth == 42;
     document.body.removeChild(div);
    return { matches: ret, media: q };
   };
})());

Simplified version of https://github.com/paulirish/matchMedia.js

outerHTML

if(!('outerHTML' in document.createElement('_'))
Object.defineProperty(Element.prototype, 'outerHTML', {get: function(){
  if (doc.xmlVersion) {
    return (new XMLSerializer).serializeToString(node);
  } else {
    var x = document.createElement('_');
    x.appendChild(this.cloneNode(false));
    return x.innerHTML.replace('><', '>' + this.innerHTML + '<');
  }
}, enumerable: true});

Simplified version of https://gist.github.com/1044128 by Eli Grey

Don’t forget accessibility!

Accessibility

  • WAI-ARIA
  • Make it keyboard accessible
    • Focusable with tabindex="0"
    • Support expected keyboard shortcuts

CSS polyfills:
A pain in the ass

CSS polyfills

Everything is hard!

  • Parsing
  • Implementation
  • Reacting to changes
  • Unavoidable obtrusiveness

Step 3 Distribution

Upload to github

github.com/repositories/new

The people

Remy Sharp

remysharp.com

Paul Irish

paulirish.com

Eli Grey

eligrey.com
  • Brad Neuberg
  • Dmitry Baranovskiy
  • Juriy Zaytsev
  • Erik arvidsson
  • Andrea giammarchi
  • Brian Leroux
  • Jonathan Neal
  • Joe Bartlett
  • John Dyer
  • Kroc Camen
  • Ryan Seddon
  • Christopher Giffard
  • Daniel Davis
  • Weston Ruter
  • Eric Hamilton
  • Diego Perini
  • Zoltan Hawryluk
  • Brian McAllister
  • Frank Yan
  • Denis Ciccale
  • Mathias Bynens
  • Mike Taylor
  • Guillermo Rauch
  • Alexander Farkas
  • Manuel Bieh
  • Benjamin Lupton
  • Chris Wanstrath
  • Ben Alman
  • Miller Medeiros
  • Давид Мзареулян
  • Rick Waldron
  • Devon Govett
  • Zoltan Hawryluk
  • Rodney Rehm
  • Andrew Dodson
  • Jeremy Keith
  • David Chambers
  • Grant Galitz
  • Ben Firshman
  • Jussi Kalliokoski
  • Kyle Simpson
  • Sean Kinsey
  • Tim Down
  • Kris Kowal
  • Irakli Gozalishvili
  • Michael Ficarra
  • Sasha Koss
  • Kit Cambridge
  • Domenic Denicola
  • Quildreen Motta
  • Bryan Forbes
  • Florian Schäfer
  • Gerard Paapu
  • Brendan Molloy
  • Sami Samhuri
  • Dan Beam
  • Alexey Zakharov
  • Tom Robinson
  • Douglas Crockford
  • David de Rosier
  • Oliver Nightingale
  • Keith Clark
  • Paul Bakaus
  • Zoltan Hawryluk
  • Jason Johnston
  • Alexis Deveria
  • Simon Madine
  • Cedric Savarese
  • Scott Jehl
  • Micky Hulse
  • Robin Ricard
  • Andrew Kononykhin
  • Nicholas Zakas
  • Philip Jägenstedt
  • Egor Nikolaev
  • Samy Kamkar
  • James Brumond
  • Mathias Nater
  • Addy Osmani
  • Dean Edwards
  • You?

Help yourself

Help everyone

those ____s!