diff --git a/external/webL10n/l10n.js b/external/webL10n/l10n.js
index 543563165..f13c8d60b 100644
--- a/external/webL10n/l10n.js
+++ b/external/webL10n/l10n.js
@@ -1,30 +1,31 @@
-/** Copyright (c) 2011-2012 Fabien Cazenave, Mozilla.
-  *
-  * Permission is hereby granted, free of charge, to any person obtaining a copy
-  * of this software and associated documentation files (the "Software"), to
-  * deal in the Software without restriction, including without limitation the
-  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
-  * sell copies of the Software, and to permit persons to whom the Software is
-  * furnished to do so, subject to the following conditions:
-  *
-  * The above copyright notice and this permission notice shall be included in
-  * all copies or substantial portions of the Software.
-  *
-  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-  * IN THE SOFTWARE.
-  */
+/**
+ * Copyright (c) 2011-2013 Fabien Cazenave, Mozilla.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
 /*
   Additional modifications for PDF.js project:
     - Disables language initialization on page loading;
-    - Adds fallback argument to the getL10nData;
-    - Removes consoleLog and simplifies consoleWarn;
+    - Removes consoleWarn and consoleLog and use console.log/warn directly.
     - Removes window._ assignment.
 */
+
 /*jshint browser: true, devel: true, es5: true, globalstrict: true */
 'use strict';
 
@@ -36,13 +37,21 @@ document.webL10n = (function(window, document, undefined) {
   var gMacros = {};
   var gReadyState = 'loading';
 
-  // read-only setting -- we recommend to load l10n resources synchronously
-  var gAsyncResourceLoading = true;
 
-  // debug helpers
-  function consoleWarn(message) {
-    console.log('[l10n] ' + message);
-  };
+  /**
+   * Synchronously loading l10n resources significantly minimizes flickering
+   * from displaying the app with non-localized strings and then updating the
+   * strings. Although this will block all script execution on this page, we
+   * expect that the l10n resources are available locally on flash-storage.
+   *
+   * As synchronous XHR is generally considered as a bad idea, we're still
+   * loading l10n resources asynchronously -- but we keep this in a setting,
+   * just in case... and applications using this library should hide their
+   * content until the `localized' event happens.
+   */
+
+  var gAsyncResourceLoading = true; // read-only
+
 
   /**
    * DOM helpers for the so-called "HTML API".
@@ -55,6 +64,12 @@ document.webL10n = (function(window, document, undefined) {
     return document.querySelectorAll('link[type="application/l10n"]');
   }
 
+  function getL10nDictionary() {
+    var script = document.querySelector('script[type="application/l10n"]');
+    // TODO: support multiple and external JSON dictionaries
+    return script ? JSON.parse(script.innerHTML) : null;
+  }
+
   function getTranslatableChildren(element) {
     return element ? element.querySelectorAll('*[data-l10n-id]') : [];
   }
@@ -70,7 +85,7 @@ document.webL10n = (function(window, document, undefined) {
       try {
         args = JSON.parse(l10nArgs);
       } catch (e) {
-        consoleWarn('could not parse arguments for #' + l10nId);
+        console.warn('could not parse arguments for #' + l10nId);
       }
     }
     return { id: l10nId, args: args };
@@ -78,9 +93,41 @@ document.webL10n = (function(window, document, undefined) {
 
   function fireL10nReadyEvent(lang) {
     var evtObject = document.createEvent('Event');
-    evtObject.initEvent('localized', false, false);
+    evtObject.initEvent('localized', true, false);
     evtObject.language = lang;
-    window.dispatchEvent(evtObject);
+    document.dispatchEvent(evtObject);
+  }
+
+  function xhrLoadText(url, onSuccess, onFailure, asynchronous) {
+    onSuccess = onSuccess || function _onSuccess(data) {};
+    onFailure = onFailure || function _onFailure() {
+      console.warn(url + ' not found.');
+    };
+
+    var xhr = new XMLHttpRequest();
+    xhr.open('GET', url, asynchronous);
+    if (xhr.overrideMimeType) {
+      xhr.overrideMimeType('text/plain; charset=utf-8');
+    }
+    xhr.onreadystatechange = function() {
+      if (xhr.readyState == 4) {
+        if (xhr.status == 200 || xhr.status === 0) {
+          onSuccess(xhr.responseText);
+        } else {
+          onFailure();
+        }
+      }
+    };
+    xhr.onerror = onFailure;
+    xhr.ontimeout = onFailure;
+
+    // in Firefox OS with the app:// protocol, trying to XHR a non-existing
+    // URL will raise an exception here -- hence this ugly try...catch.
+    try {
+      xhr.send(null);
+    } catch (e) {
+      onFailure();
+    }
   }
 
 
@@ -108,7 +155,7 @@ document.webL10n = (function(window, document, undefined) {
    */
 
   function parseResource(href, lang, successCallback, failureCallback) {
-    var baseURL = href.replace(/\/[^\/]*$/, '/');
+    var baseURL = href.replace(/[^\/]*$/, '') || './';
 
     // handle escaped characters (backslashes) in a string
     function evalString(text) {
@@ -171,16 +218,17 @@ document.webL10n = (function(window, document, undefined) {
 
           // key-value pair
           var tmp = line.match(reSplit);
-          if (tmp && tmp.length == 3)
+          if (tmp && tmp.length == 3) {
             dictionary[tmp[1]] = evalString(tmp[2]);
+          }
         }
       }
 
       // import another *.properties file
       function loadImport(url) {
-        loadResource(url, function(content) {
+        xhrLoadText(url, function(content) {
           parseRawLines(content, false); // don't allow recursive imports
-        }, false, false); // load synchronously
+        }, null, false); // load synchronously
       }
 
       // fill the dictionary
@@ -188,29 +236,8 @@ document.webL10n = (function(window, document, undefined) {
       return dictionary;
     }
 
-    // load the specified resource file
-    function loadResource(url, onSuccess, onFailure, asynchronous) {
-      var xhr = new XMLHttpRequest();
-      xhr.open('GET', url, asynchronous);
-      if (xhr.overrideMimeType) {
-        xhr.overrideMimeType('text/plain; charset=utf-8');
-      }
-      xhr.onreadystatechange = function() {
-        if (xhr.readyState == 4) {
-          if (xhr.status == 200 || xhr.status === 0) {
-            if (onSuccess)
-              onSuccess(xhr.responseText);
-          } else {
-            if (onFailure)
-              onFailure();
-          }
-        }
-      };
-      xhr.send(null);
-    }
-
     // load and parse l10n data (warning: global variables are used here)
-    loadResource(href, function(response) {
+    xhrLoadText(href, function(response) {
       gTextData += response; // mostly for debug
 
       // parse *.properties text data into an l10n dictionary
@@ -233,13 +260,16 @@ document.webL10n = (function(window, document, undefined) {
       }
 
       // trigger callback
-      if (successCallback)
+      if (successCallback) {
         successCallback();
+      }
     }, failureCallback, gAsyncResourceLoading);
-  };
+  }
 
   // load and parse all resources for the specified locale
   function loadLocale(lang, callback) {
+    callback = callback || function _callback() {};
+
     clear();
     gLanguage = lang;
 
@@ -247,8 +277,17 @@ document.webL10n = (function(window, document, undefined) {
     // and load the resource files
     var langLinks = getL10nResourceLinks();
     var langCount = langLinks.length;
-    if (langCount == 0) {
-      consoleWarn('no resource to load, early way out');
+    if (langCount === 0) {
+      // we might have a pre-compiled dictionary instead
+      var dict = getL10nDictionary();
+      if (dict && dict.locales && dict.default_locale) {
+        console.log('using the embedded JSON directory, early way out');
+        gL10nData = dict.locales[lang] || dict.locales[dict.default_locale];
+        callback();
+      } else {
+        console.log('no resource to load, early way out');
+      }
+      // early way out
       fireL10nReadyEvent(lang);
       gReadyState = 'complete';
       return;
@@ -260,21 +299,20 @@ document.webL10n = (function(window, document, undefined) {
     onResourceLoaded = function() {
       gResourceCount++;
       if (gResourceCount >= langCount) {
-        if (callback) // execute the [optional] callback
-          callback();
+        callback();
         fireL10nReadyEvent(lang);
         gReadyState = 'complete';
       }
     };
 
     // load all resource files
-    function l10nResourceLink(link) {
+    function L10nResourceLink(link) {
       var href = link.href;
       var type = link.type;
       this.load = function(lang, callback) {
         var applied = lang;
         parseResource(href, lang, callback, function() {
-          consoleWarn(href + ' not found.');
+          console.warn(href + ' not found.');
           applied = '';
         });
         return applied; // return lang if found, an empty string if not found
@@ -282,10 +320,10 @@ document.webL10n = (function(window, document, undefined) {
     }
 
     for (var i = 0; i < langCount; i++) {
-      var resource = new l10nResourceLink(langLinks[i]);
+      var resource = new L10nResourceLink(langLinks[i]);
       var rv = resource.load(lang, onResourceLoaded);
       if (rv != lang) { // lang not found, used default resource instead
-        consoleWarn('"' + lang + '" resource not found');
+        console.warn('"' + lang + '" resource not found');
         gLanguage = '';
       }
     }
@@ -706,7 +744,7 @@ document.webL10n = (function(window, document, undefined) {
     // return a function that gives the plural form name for a given integer
     var index = locales2rules[lang.replace(/-.*$/, '')];
     if (!(index in pluralRules)) {
-      consoleWarn('plural form unknown for [' + lang + ']');
+      console.warn('plural form unknown for [' + lang + ']');
       return function() { return 'other'; };
     }
     return pluralRules[index];
@@ -723,8 +761,9 @@ document.webL10n = (function(window, document, undefined) {
       return str;
 
     // initialize _pluralRules
-    if (!gMacros._pluralRules)
+    if (!gMacros._pluralRules) {
       gMacros._pluralRules = getPluralRules(gLanguage);
+    }
     var index = '[' + gMacros._pluralRules(n) + ']';
 
     // try to find a [zero|one|two] key if it's defined
@@ -736,6 +775,8 @@ document.webL10n = (function(window, document, undefined) {
       str = gL10nData[key + '[two]'][prop];
     } else if ((key + index) in gL10nData) {
       str = gL10nData[key + index][prop];
+    } else if ((key + '[other]') in gL10nData) {
+      str = gL10nData[key + '[other]'][prop];
     }
 
     return str;
@@ -750,7 +791,7 @@ document.webL10n = (function(window, document, undefined) {
   function getL10nData(key, args, fallback) {
     var data = gL10nData[key];
     if (!data) {
-      consoleWarn('#' + key + ' missing for [' + gLanguage + ']');
+      console.warn('#' + key + ' is undefined.');
       if (!fallback) {
         return null;
       }
@@ -766,7 +807,7 @@ document.webL10n = (function(window, document, undefined) {
     for (var prop in data) {
       var str = data[prop];
       str = substIndexes(str, args, key, prop);
-      str = substArguments(str, args);
+      str = substArguments(str, args, key);
       rv[prop] = str;
     }
     return rv;
@@ -799,8 +840,8 @@ document.webL10n = (function(window, document, undefined) {
   }
 
   // replace {{arguments}} with their values
-  function substArguments(str, args) {
-    var reArgs = /\{\{\s*([a-zA-Z\.]+)\s*\}\}/;
+  function substArguments(str, args, key) {
+    var reArgs = /\{\{\s*(.+?)\s*\}\}/;
     var match = reArgs.exec(str);
     while (match) {
       if (!match || match.length < 2)
@@ -808,12 +849,12 @@ document.webL10n = (function(window, document, undefined) {
 
       var arg = match[1];
       var sub = '';
-      if (arg in args) {
+      if (args && arg in args) {
         sub = args[arg];
       } else if (arg in gL10nData) {
         sub = gL10nData[arg][gTextProp];
       } else {
-        consoleWarn('could not find argument {{' + arg + '}}');
+        console.log('argument {{' + arg + '}} for #' + key + ' is undefined.');
         return str;
       }
 
@@ -833,23 +874,21 @@ document.webL10n = (function(window, document, undefined) {
     // get the related l10n object
     var data = getL10nData(l10n.id, l10n.args);
     if (!data) {
-      consoleWarn('#' + l10n.id + ' missing for [' + gLanguage + ']');
+      console.warn('#' + l10n.id + ' is undefined.');
       return;
     }
 
     // translate element (TODO: security checks?)
-    // for the node content, replace the content of the first child textNode
-    // and clear other child textNodes
     if (data[gTextProp]) { // XXX
-      if (element.children.length === 0) {
+      if (getChildElementCount(element) === 0) {
         element[gTextProp] = data[gTextProp];
       } else {
-        var children = element.childNodes,
-            found = false;
+        // this element has element children: replace the content of the first
+        // (non-empty) child textNode and clear other child textNodes
+        var children = element.childNodes;
+        var found = false;
         for (var i = 0, l = children.length; i < l; i++) {
-          if (children[i].nodeType === 3 &&
-              /\S/.test(children[i].textContent)) { // XXX
-            // using nodeValue seems cross-browser
+          if (children[i].nodeType === 3 && /\S/.test(children[i].nodeValue)) {
             if (found) {
               children[i].nodeValue = '';
             } else {
@@ -858,8 +897,11 @@ document.webL10n = (function(window, document, undefined) {
             }
           }
         }
+        // if no (non-empty) textNode is found, insert a textNode before the
+        // first element child.
         if (!found) {
-          consoleWarn('unexpected error, could not translate element content');
+          var textNode = document.createTextNode(data[gTextProp]);
+          element.insertBefore(textNode, element.firstChild);
         }
       }
       delete data[gTextProp];
@@ -870,6 +912,21 @@ document.webL10n = (function(window, document, undefined) {
     }
   }
 
+  // webkit browsers don't currently support 'children' on SVG elements...
+  function getChildElementCount(element) {
+    if (element.children) {
+      return element.children.length;
+    }
+    if (typeof element.childElementCount !== 'undefined') {
+      return element.childElementCount;
+    }
+    var count = 0;
+    for (var i = 0; i < element.childNodes.length; i++) {
+      count += element.nodeType === 1 ? 1 : 0;
+    }
+    return count;
+  }
+
   // translate an HTML subtree
   function translateFragment(element) {
     element = element || document.documentElement;
@@ -888,10 +945,21 @@ document.webL10n = (function(window, document, undefined) {
   // cross-browser API (sorry, oldIE doesn't support getters & setters)
   return {
     // get a localized string
-    get: function(key, args, fallback) {
-      var data = getL10nData(key, args, {textContent: fallback});
-      if (data) { // XXX double-check this
-        return 'textContent' in data ? data.textContent : '';
+    get: function(key, args, fallbackString) {
+      var index = key.lastIndexOf('.');
+      var prop = gTextProp;
+      if (index > 0) { // An attribute has been specified
+        prop = key.substr(index + 1);
+        key = key.substring(0, index);
+      }
+      var fallback;
+      if (fallbackString) {
+        fallback = {};
+        fallback[prop] = fallbackString;
+      }
+      var data = getL10nData(key, args, fallback);
+      if (data && prop in data) {
+        return data[prop];
       }
       return '{{' + key + '}}';
     },
@@ -916,7 +984,21 @@ document.webL10n = (function(window, document, undefined) {
     translate: translateFragment,
 
     // this can be used to prevent race conditions
-    getReadyState: function() { return gReadyState; }
+    getReadyState: function() { return gReadyState; },
+    ready: function(callback) {
+      if (!callback) {
+        return;
+      } else if (gReadyState == 'complete' || gReadyState == 'interactive') {
+        window.setTimeout(callback);
+      } else if (document.addEventListener) {
+        document.addEventListener('localized', callback);
+      } else if (document.attachEvent) {
+        document.documentElement.attachEvent('onpropertychange', function(e) {
+          if (e.propertyName === 'localized') {
+            callback();
+          }
+        });
+      }
+    }
   };
-
 }) (window, document);