Bug 801280 - Update pdf.js to version 0.6.39. r=dtownsend
authorRyan VanderMeulen <ryanvm@gmail.com>
Wed, 17 Oct 2012 15:33:58 -0400
changeset 110582 af98d67916ada9ff478f4f321a2a8460d31f2d7e
parent 110581 5a94cbed4b7a1a733307c195083f6337272b0bb8
child 110583 357778ffa80165c04d0b71ab284fa300c870df51
push id23700
push userryanvm@gmail.com
push dateThu, 18 Oct 2012 02:10:26 +0000
treeherdermozilla-central@5142bbd4da12 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdtownsend
bugs801280
milestone19.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 801280 - Update pdf.js to version 0.6.39. r=dtownsend
browser/extensions/pdfjs/README.mozilla
browser/extensions/pdfjs/components/PdfStreamConverter.js
browser/extensions/pdfjs/content/PdfJs.jsm
browser/extensions/pdfjs/content/web/images/findbarButton-next-rtl.png
browser/extensions/pdfjs/content/web/images/findbarButton-next.png
browser/extensions/pdfjs/content/web/images/findbarButton-previous-rtl.png
browser/extensions/pdfjs/content/web/images/findbarButton-previous.png
browser/extensions/pdfjs/content/web/images/loading-small.png
browser/extensions/pdfjs/content/web/images/toolbarButton-search.png
browser/extensions/pdfjs/content/web/viewer.css
browser/extensions/pdfjs/content/web/viewer.html
browser/extensions/pdfjs/content/web/viewer.js
browser/extensions/pdfjs/extension-files
browser/locales/en-US/pdfviewer/chrome.properties
browser/locales/en-US/pdfviewer/viewer.properties
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,4 +1,4 @@
 This is the pdf.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 0.5.184
+Current extension version is: 0.6.39
 
--- a/browser/extensions/pdfjs/components/PdfStreamConverter.js
+++ b/browser/extensions/pdfjs/components/PdfStreamConverter.js
@@ -26,40 +26,46 @@ const Cu = Components.utils;
 // True only if this is the version of pdf.js that is included with firefox.
 const MOZ_CENTRAL = true;
 const PDFJS_EVENT_ID = 'pdf.js.message';
 const PDF_CONTENT_TYPE = 'application/pdf';
 const PREF_PREFIX = 'pdfjs';
 const PDF_VIEWER_WEB_PAGE = 'resource://pdf.js/web/viewer.html';
 const MAX_DATABASE_LENGTH = 4096;
 const FIREFOX_ID = '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}';
-const SEAMONKEY_ID = '{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}';
-const METRO_ID = '{99bceaaa-e3c6-48c1-b981-ef9b46b67d60}';
 
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.import('resource://gre/modules/Services.jsm');
 Cu.import('resource://gre/modules/NetUtil.jsm');
 
 
 let appInfo = Cc['@mozilla.org/xre/app-info;1']
                   .getService(Ci.nsIXULAppInfo);
-let privateBrowsing, inPrivateBrowsing;
 let Svc = {};
 XPCOMUtils.defineLazyServiceGetter(Svc, 'mime',
                                    '@mozilla.org/mime;1',
                                    'nsIMIMEService');
 
+let isInPrivateBrowsing;
 if (appInfo.ID === FIREFOX_ID) {
-  privateBrowsing = Cc['@mozilla.org/privatebrowsing;1']
-                          .getService(Ci.nsIPrivateBrowsingService);
-  inPrivateBrowsing = privateBrowsing.privateBrowsingEnabled;
-} else if (appInfo.ID === SEAMONKEY_ID ||
-           appInfo.ID === METRO_ID) {
-  privateBrowsing = null;
-  inPrivateBrowsing = false;
+  let privateBrowsing = Cc['@mozilla.org/privatebrowsing;1']
+                            .getService(Ci.nsIPrivateBrowsingService);
+  isInPrivateBrowsing = function getInPrivateBrowsing() {
+    return privateBrowsing.privateBrowsingEnabled;
+  };
+} else {
+  isInPrivateBrowsing = function() { return false; };
+}
+
+function getChromeWindow(domWindow) {
+  var containingBrowser = domWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                                   .getInterface(Ci.nsIWebNavigation)
+                                   .QueryInterface(Ci.nsIDocShell)
+                                   .chromeEventHandler;
+  return containingBrowser.ownerDocument.defaultView;
 }
 
 function getBoolPref(pref, def) {
   try {
     return Services.prefs.getBoolPref(pref);
   } catch (ex) {
     return def;
   }
@@ -268,25 +274,25 @@ ChromeActions.prototype = {
                                            aOffset, aCount);
         }
       };
 
       channel.asyncOpen(listener, null);
     });
   },
   setDatabase: function(data) {
-    if (inPrivateBrowsing)
+    if (isInPrivateBrowsing())
       return;
     // Protect against something sending tons of data to setDatabase.
     if (data.length > MAX_DATABASE_LENGTH)
       return;
     setStringPref(PREF_PREFIX + '.database', data);
   },
   getDatabase: function() {
-    if (inPrivateBrowsing)
+    if (isInPrivateBrowsing())
       return '{}';
     return getStringPref(PREF_PREFIX + '.database', '{}');
   },
   getLocale: function() {
     return getStringPref('general.useragent.locale', 'en-US');
   },
   getLoadingType: function() {
     return this.dataListener ? 'passive' : 'active';
@@ -331,18 +337,21 @@ ChromeActions.prototype = {
     } catch (e) {
       log('Unable to retrive localized strings: ' + e);
       return 'null';
     }
   },
   pdfBugEnabled: function() {
     return getBoolPref(PREF_PREFIX + '.pdfBugEnabled', false);
   },
-  searchEnabled: function() {
-    return getBoolPref(PREF_PREFIX + '.searchEnabled', false);
+  supportsIntegratedFind: function() {
+    // Integrated find is only supported when we're not in a frame and when the
+    // new find events code exists.
+    return this.domWindow.frameElement === null &&
+           'updateControlState' in getChromeWindow(this.domWindow).gFindBar;
   },
   fallback: function(url, sendResponse) {
     var self = this;
     var domWindow = this.domWindow;
     var strings = getLocalizedStrings('chrome.properties');
     var message = getLocalizedString(strings, 'unsupported_feature');
 
     var notificationBox = null;
@@ -386,16 +395,30 @@ ChromeActions.prototype = {
       // added in the future we still only care about removed at the moment.
       if (eventType !== 'removed')
         return;
       // Don't send a response again if we already responded when the button was
       // clicked.
       if (!sentResponse)
         sendResponse(false);
     });
+  },
+  updateFindControlState: function(data) {
+    if (!this.supportsIntegratedFind())
+      return;
+    // Verify what we're sending to the findbar.
+    var result = data.result;
+    var findPrevious = data.findPrevious;
+    var findPreviousType = typeof findPrevious;
+    if ((typeof result !== 'number' || result < 0 || result > 3) ||
+        (findPreviousType !== 'undefined' && findPreviousType !== 'boolean')) {
+      return;
+    }
+    getChromeWindow(this.domWindow).gFindBar
+                                   .updateControlState(result, findPrevious);
   }
 };
 
 // Event listener to trigger chrome privedged code.
 function RequestListener(actions) {
   this.actions = actions;
 }
 // Receive an event and synchronously or asynchronously responds.
@@ -426,16 +449,67 @@ RequestListener.prototype.receive = func
         listener.initEvent('pdf.js.response', true, false);
         return message.dispatchEvent(listener);
       }
     }
     actions[action].call(this.actions, data, response);
   }
 };
 
+// Forwards events from the eventElement to the contentWindow only if the
+// content window matches the currently selected browser window.
+function FindEventManager(eventElement, contentWindow, chromeWindow) {
+  this.types = ['find',
+                'findagain',
+                'findhighlightallchange',
+                'findcasesensitivitychange'];
+  this.chromeWindow = chromeWindow;
+  this.contentWindow = contentWindow;
+  this.eventElement = eventElement;
+}
+
+FindEventManager.prototype.bind = function() {
+  var unload = function(e) {
+    this.unbind();
+    this.contentWindow.removeEventListener(e.type, unload);
+  }.bind(this);
+  this.contentWindow.addEventListener('unload', unload);
+
+  for (var i = 0; i < this.types.length; i++) {
+    var type = this.types[i];
+    this.eventElement.addEventListener(type, this, true);
+  }
+};
+
+FindEventManager.prototype.handleEvent = function(e) {
+  var chromeWindow = this.chromeWindow;
+  var contentWindow = this.contentWindow;
+  // Only forward the events if they are for our dom window.
+  if (chromeWindow.gBrowser.selectedBrowser.contentWindow === contentWindow) {
+    var detail = e.detail;
+    detail.__exposedProps__ = {
+      query: 'r',
+      caseSensitive: 'r',
+      highlightAll: 'r',
+      findPrevious: 'r'
+    };
+    var forward = contentWindow.document.createEvent('CustomEvent');
+    forward.initCustomEvent(e.type, true, true, detail);
+    contentWindow.dispatchEvent(forward);
+    e.preventDefault();
+  }
+};
+
+FindEventManager.prototype.unbind = function() {
+  for (var i = 0; i < this.types.length; i++) {
+    var type = this.types[i];
+    this.eventElement.removeEventListener(type, this, true);
+  }
+};
+
 function PdfStreamConverter() {
 }
 
 PdfStreamConverter.prototype = {
 
   // properties required for XPCOM registration:
   classID: Components.ID('{d0c5195d-e798-49d4-b1d3-9324328b2291}'),
   classDescription: 'pdf.js Component',
@@ -538,31 +612,39 @@ PdfStreamConverter.prototype = {
         var domWindow = getDOMWindow(channel);
         // Double check the url is still the correct one.
         if (domWindow.document.documentURIObject.equals(aRequest.URI)) {
           let actions = new ChromeActions(domWindow, dataListener);
           let requestListener = new RequestListener(actions);
           domWindow.addEventListener(PDFJS_EVENT_ID, function(event) {
             requestListener.receive(event);
           }, false, true);
+          if (actions.supportsIntegratedFind()) {
+            var chromeWindow = getChromeWindow(domWindow);
+            var findEventManager = new FindEventManager(chromeWindow.gFindBar,
+                                                        domWindow,
+                                                        chromeWindow);
+            findEventManager.bind();
+          }
         }
         listener.onStopRequest.apply(listener, arguments);
       }
     };
 
     // Keep the URL the same so the browser sees it as the same.
     channel.originalURI = aRequest.URI;
     channel.asyncOpen(proxy, aContext);
     if (useFetchByChrome) {
       // We can use resource principal when data is fetched by the chrome
       // e.g. useful for NoScript
       var securityManager = Cc['@mozilla.org/scriptsecuritymanager;1']
                             .getService(Ci.nsIScriptSecurityManager);
       var uri = ioService.newURI(PDF_VIEWER_WEB_PAGE, null, null);
-      // FF16 and below had getCodebasePrincipal (bug 774585)
+      // FF16 and below had getCodebasePrincipal, it was replaced by
+      // getNoAppCodebasePrincipal (bug 758258).
       var resourcePrincipal = 'getNoAppCodebasePrincipal' in securityManager ?
                               securityManager.getNoAppCodebasePrincipal(uri) :
                               securityManager.getCodebasePrincipal(uri);
       channel.owner = resourcePrincipal;
     }
   },
 
   // nsIRequestObserver::onStopRequest
--- a/browser/extensions/pdfjs/content/PdfJs.jsm
+++ b/browser/extensions/pdfjs/content/PdfJs.jsm
@@ -18,20 +18,22 @@ var EXPORTED_SYMBOLS = ["PdfJs"];
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 const Cm = Components.manager;
 const Cu = Components.utils;
 
 const PREF_PREFIX = 'pdfjs';
 const PREF_DISABLED = PREF_PREFIX + '.disabled';
-const PREF_FIRST_RUN = PREF_PREFIX + '.firstRun';
+const PREF_MIGRATION_VERSION = PREF_PREFIX + '.migrationVersion';
 const PREF_PREVIOUS_ACTION = PREF_PREFIX + '.previousHandler.preferredAction';
 const PREF_PREVIOUS_ASK = PREF_PREFIX + '.previousHandler.alwaysAskBeforeHandling';
+const PREF_DISABLED_PLUGIN_TYPES = 'plugin.disable_full_page_plugin_for_types';
 const TOPIC_PDFJS_HANDLER_CHANGED = 'pdfjs:handlerChanged';
+const PDF_CONTENT_TYPE = 'application/pdf';
 
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.import('resource://gre/modules/Services.jsm');
 Cu.import('resource://pdf.js.components/PdfStreamConverter.js');
 
 let Svc = {};
 XPCOMUtils.defineLazyServiceGetter(Svc, 'mime',
                                    '@mozilla.org/mime;1',
@@ -40,16 +42,24 @@ XPCOMUtils.defineLazyServiceGetter(Svc, 
 function getBoolPref(aPref, aDefaultValue) {
   try {
     return Services.prefs.getBoolPref(aPref);
   } catch (ex) {
     return aDefaultValue;
   }
 }
 
+function getIntPref(aPref, aDefaultValue) {
+  try {
+    return Services.prefs.getIntPref(aPref);
+  } catch (ex) {
+    return aDefaultValue;
+  }
+}
+
 // Register/unregister a constructor as a component.
 let Factory = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory]),
   _targetConstructor: null,
 
   register: function register(targetConstructor) {
     this._targetConstructor = targetConstructor;
     var proto = targetConstructor.prototype;
@@ -79,47 +89,80 @@ let Factory = {
   }
 };
 
 let PdfJs = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
   _registered: false,
 
   init: function init() {
-    // On first run make pdf.js the default handler.
-    if (!getBoolPref(PREF_DISABLED, true) && getBoolPref(PREF_FIRST_RUN, false)) {
-      Services.prefs.setBoolPref(PREF_FIRST_RUN, false);
-
-      let handlerInfo = Svc.mime.getFromTypeAndExtension('application/pdf', 'pdf');
-      // Store the previous settings of preferredAction and
-      // alwaysAskBeforeHandling in case we need to revert them in a hotfix that
-      // would turn pdf.js off.
-      Services.prefs.setIntPref(PREF_PREVIOUS_ACTION, handlerInfo.preferredAction);
-      Services.prefs.setBoolPref(PREF_PREVIOUS_ASK, handlerInfo.alwaysAskBeforeHandling);
-
-      let handlerService = Cc['@mozilla.org/uriloader/handler-service;1'].
-                           getService(Ci.nsIHandlerService);
-
-      // Change and save mime handler settings.
-      handlerInfo.alwaysAskBeforeHandling = false;
-      handlerInfo.preferredAction = Ci.nsIHandlerInfo.handleInternally;
-      handlerService.store(handlerInfo);
+    if (!getBoolPref(PREF_DISABLED, true)) {
+      this._migrate();
     }
 
     if (this.enabled)
       this._ensureRegistered();
     else
       this._ensureUnregistered();
 
     // Listen for when pdf.js is completely disabled or a different pdf handler
     // is chosen.
     Services.prefs.addObserver(PREF_DISABLED, this, false);
     Services.obs.addObserver(this, TOPIC_PDFJS_HANDLER_CHANGED, false);
   },
 
+  _migrate: function migrate() {
+    const VERSION = 1;
+    var currentVersion = getIntPref(PREF_MIGRATION_VERSION, 0);
+    if (currentVersion >= VERSION) {
+      return;
+    }
+    // Make pdf.js the default pdf viewer on the first migration.
+    if (currentVersion < 2) {
+      this._becomeHandler();
+    }
+    Services.prefs.setIntPref(PREF_MIGRATION_VERSION, VERSION);
+  },
+
+  _becomeHandler: function _becomeHandler() {
+    let handlerInfo = Svc.mime.getFromTypeAndExtension(PDF_CONTENT_TYPE, 'pdf');
+    let prefs = Services.prefs;
+    if (handlerInfo.preferredAction !== Ci.nsIHandlerInfo.handleInternally &&
+        handlerInfo.preferredAction !== false) {
+      // Store the previous settings of preferredAction and
+      // alwaysAskBeforeHandling in case we need to revert them in a hotfix that
+      // would turn pdf.js off.
+      prefs.setIntPref(PREF_PREVIOUS_ACTION, handlerInfo.preferredAction);
+      prefs.setBoolPref(PREF_PREVIOUS_ASK, handlerInfo.alwaysAskBeforeHandling);
+    }
+
+    let handlerService = Cc['@mozilla.org/uriloader/handler-service;1'].
+                         getService(Ci.nsIHandlerService);
+
+    // Change and save mime handler settings.
+    handlerInfo.alwaysAskBeforeHandling = false;
+    handlerInfo.preferredAction = Ci.nsIHandlerInfo.handleInternally;
+    handlerService.store(handlerInfo);
+
+    // Also disable any plugins for pdfs.
+    var stringTypes = '';
+    var types = [];
+    if (prefs.prefHasUserValue(PREF_DISABLED_PLUGIN_TYPES)) {
+      stringTypes = prefs.getCharPref(PREF_DISABLED_PLUGIN_TYPES);
+    }
+    if (stringTypes !== '') {
+      types = stringTypes.split(',');
+    }
+
+    if (types.indexOf(PDF_CONTENT_TYPE) === -1) {
+      types.push(PDF_CONTENT_TYPE);
+    }
+    prefs.setCharPref(PREF_DISABLED_PLUGIN_TYPES, types.join(','));
+  },
+
   // nsIObserver
   observe: function observe(aSubject, aTopic, aData) {
     if (this.enabled)
       this._ensureRegistered();
     else
       this._ensureUnregistered();
   },
   
new file mode 100644
index 0000000000000000000000000000000000000000..08a2c25327d7df3c77186cc3048bf02b7e72f393
GIT binary patch
literal 371
zc%17D@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6n3BBRT^Rni_n+Ah<Z%{wL>4nJ
za0`PlBg3pY5<o%r5>H=O_B&jxVj>1dW-W_hU|?kNba4#PIA1#9tk+=&k=FgQ+pdT`
zUbe$HMe!GtU6WGBI$aLYZm~5FlsUCLM9rJ>x)ki1v&;2jM441q`dLYetDQYE-*|TQ
z^E<_9?^Y%rUfkJzY)ZIaD_<MC&cmBh$CtgYp8dL|p1-2*N5P)WO~<yom=<t<b<MX{
zV0F4SV~dnB5HBwOxorLKx2`PB3<(U3Mv^-!Sehqq>wl!)(o;G8S#tSeKda!GbIv?G
zbmhsbOZ!$BA5pw%wO?3d{?EV_k<*RN+E-q_-@A4@_jI3mOCvtN%FsWiAoSU(>6p5s
zu;<LjPZQOdDxY?~D4*1|{qN@6tRl%q{~C{f`Y<K**{Z}4_g5KqIsN)|hXdZr_a9ya
P3?c?kS3j3^P6<r_kMx(q
new file mode 100644
index 0000000000000000000000000000000000000000..beef8ccea48c932571c9c9009df916a61c850a1c
GIT binary patch
literal 381
zc$@)!0fPRCP)<h;3K|Lk000e1NJLTq000mG000mO1ONa4wfZ;e00002VoOIv0RM-N
z%)bBt010qNS#tmY3ljhU3ljkVnw%H_000McNliru+zJI29sn4uetG}^0S-w-K~yNu
zb&)+w13?gmpWQt!_hI5eD#4IKO<|GNA0TLBqfH`0&_WQwe<O&XAP9<DC}<P>Cn>#B
z0z$y#_7d0P?!t27TP(BlKC{g1C~Qf5JYZ(0Y`HA=4{6~34>Y$nUyp4>0?NXx%y!n^
zPO8lU5v<pjhGYCX!!bT>vjOn@v3E&~2En%3M*;>A02l=3;j?=|jFv2*(IokqkYu+`
z?f#Sot_0GOokeN4@_t0%AeiK{n7pfbJT>4jCFQRS;-#{C!?(j16Xd+w6vSiLIVVO7
zT0|%UMkoT>Cd44_!h@@9OoxH>aKs|3a2xe<yZqHZjL=WD-gRM@<<aV&v0^Vvl-BK`
bNbKrg3mRK+IzR8P00000NkvXXu0mjfaG9N{
new file mode 100644
index 0000000000000000000000000000000000000000..beef8ccea48c932571c9c9009df916a61c850a1c
GIT binary patch
literal 381
zc$@)!0fPRCP)<h;3K|Lk000e1NJLTq000mG000mO1ONa4wfZ;e00002VoOIv0RM-N
z%)bBt010qNS#tmY3ljhU3ljkVnw%H_000McNliru+zJI29sn4uetG}^0S-w-K~yNu
zb&)+w13?gmpWQt!_hI5eD#4IKO<|GNA0TLBqfH`0&_WQwe<O&XAP9<DC}<P>Cn>#B
z0z$y#_7d0P?!t27TP(BlKC{g1C~Qf5JYZ(0Y`HA=4{6~34>Y$nUyp4>0?NXx%y!n^
zPO8lU5v<pjhGYCX!!bT>vjOn@v3E&~2En%3M*;>A02l=3;j?=|jFv2*(IokqkYu+`
z?f#Sot_0GOokeN4@_t0%AeiK{n7pfbJT>4jCFQRS;-#{C!?(j16Xd+w6vSiLIVVO7
zT0|%UMkoT>Cd44_!h@@9OoxH>aKs|3a2xe<yZqHZjL=WD-gRM@<<aV&v0^Vvl-BK`
bNbKrg3mRK+IzR8P00000NkvXXu0mjfaG9N{
new file mode 100644
index 0000000000000000000000000000000000000000..08a2c25327d7df3c77186cc3048bf02b7e72f393
GIT binary patch
literal 371
zc%17D@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6n3BBRT^Rni_n+Ah<Z%{wL>4nJ
za0`PlBg3pY5<o%r5>H=O_B&jxVj>1dW-W_hU|?kNba4#PIA1#9tk+=&k=FgQ+pdT`
zUbe$HMe!GtU6WGBI$aLYZm~5FlsUCLM9rJ>x)ki1v&;2jM441q`dLYetDQYE-*|TQ
z^E<_9?^Y%rUfkJzY)ZIaD_<MC&cmBh$CtgYp8dL|p1-2*N5P)WO~<yom=<t<b<MX{
zV0F4SV~dnB5HBwOxorLKx2`PB3<(U3Mv^-!Sehqq>wl!)(o;G8S#tSeKda!GbIv?G
zbmhsbOZ!$BA5pw%wO?3d{?EV_k<*RN+E-q_-@A4@_jI3mOCvtN%FsWiAoSU(>6p5s
zu;<LjPZQOdDxY?~D4*1|{qN@6tRl%q{~C{f`Y<K**{Z}4_g5KqIsN)|hXdZr_a9ya
P3?c?kS3j3^P6<r_kMx(q
new file mode 100644
index 0000000000000000000000000000000000000000..1b2df8093d630ca5e4b7c9127fd4fc855655599b
GIT binary patch
literal 9025
zc${^ac{G%N`^RUDeczJoO@%B&_UzRp$(Cj!yOMoN_FW<p5|TYx=N7W>OxY5WG<MBM
zl6{LI+war!e1GTk$Mc<Y#(mB(Gu-#<dS9>ih%wZ^!bHzQ4}-y&u4<!=z>yW)*=VW3
z*DvJ~9pFG~a|fdfj*Q?A(`}Jt0|y?v|2_u$|0lry|7MH$35Q#})S`jGC~~i&FJP47
zf}g#;cE_HxGjxQE-t3d6)0#PL^f`Yt?3=Boh|HaZQ8C$4={$9+vj+^1FCHH*{iH`_
zY<KExJ^Rwd^wOzX@p*lQ=SX2m3$)`!`((}B5A(O#xx1=;z1so@eCy{X8S3(u<ANx5
zs69+cZsG7(cw_utoUg`H*YC?r?>a)+C8VSR@zINoIf^>`cn!Rs5}!UIF(oDC`4Boh
zI$v8j$sfMY%}A}GU0`HIO%a}RG51rjb%>|uMn%5%opf4j)TbYfEA}HJBUfejwtxQo
z*+)JgMny$sOG-#2_s(wk78uPtwpf;zm6snm*)dB`E9sB)_xF>vE0>p-MJy~VtY9iV
zwi}C{4XQptL7B;%PdSS*Vlg@e8=uGrXYs4c%fXFxb&9AS*i+7wWD7I1Ky?j`HcuZP
zAF^xQvlSN?mp&IaH~p(0bRH|{^`(v7k&RtmS*a}=nin*}eJQD<>sxa2@K~F>m}^)!
zl1M4@5s$~`nU-=<H+v}Ysj8_R6x=L1AbfC?b#7{I4qDycZ|48Wed5IWQ#pzGnEdr1
z^&=skIA-|pW1&Rt(;K_{h^sSk8twg-G}81z<5N=y6dq=59W(1!@iYPyZwqOL$HsPP
z{p<q*ev@xk_<i%Yj2apF3HJ>P8S%cqx3?E6At{*>78pMhL|c8T2mBH0D6UwJXEJ$t
zdBI^hy23PJd-yA3F13PdcPdOwjee}HdAv3h?UO*El>3iGeyx8qpPij;zZIZ1{B$cO
zKXBbIi17ttMdz_Lz5E$#JE{KHjg5`tEM6|It}Pp?s{>kX%iq7}`*?U5(i~=IWd)D)
zL3Z04&#xLA8><~ZethWJZ`h9?Kf3NWOfp(O`wbBmFgVQPTABL0ayM1?Ltli#HBP__
z#F;zx{z4l5zme`5dOZY?uC%kgfB}zD{DZYGYqv%cIXk`XSRKz)hpK#(TMG4;$2UiW
zr-ogY>?Av_k3}9F4`2`v6jvLq5=fTt8;f|UU^z9Et@cZZ^ZT(_QzZAf-tmW;O~d*u
zT2JM-84#NpX+-JGEUC$dbLmH%JJT(rrJXQ1={zgc+!Qt-7Q(c~&fQS+c5*V4YDaHq
zzl=S=xFS@tH-KK1=fsI~C3>DL4u)7O39eX#)WYJTV0BH+!RY8{{ST#&!K4IDp|vFF
zH;iEoew#v|w^AS{Cnr=CSx(;Gj<>hB9{^`pE2+m8l{3)hZ9$%JQ7|(xHT``H%}C0%
z;r2zLP?ot+Wo_-Y|D{Xqa%->a>d1TG%G!*0t6&KwkR>?!`uMzxK_jVjYpbh+S{fU9
zu-J2#(bz5W{QP|AkGVObfPld77R%ML8Kq8Vae-o|lL85$=M)sSeU-CN@JGm30xz$i
zS7M`Enwwp!>*|g^fBAy>t`@C*mOW-ay~p;0XN>eG`(I@8mH31NXZR^jdct?lh6r<%
zG1OHZ?f1Cz+m+p*uM|!+)YLARnk(2g4B_YR-@irp`T4WLFwU&>kr!iQV{;&<>W!i~
zF?%?h^}D&vU<9VcTbg^H{>67Ab3uH;)BLhB6up-Sd(4oF!d`~!M6fyZiFyIvE!RKV
z)urWTXJ;o!%N=!&-lQ0M{rdH3MMXtH$cacIy%c=G_^4eF(V`jT;P4(UY-VQGgspK8
z3=C9C>Um<Ir*{HYyfEcVe@#wNQ3I9QTZ$K2Lt_zdtv$H4EzaJ=u3DCcMQ6rnpUl!%
z(OiD_?p;o;YtGG$?5U|KMetg8N#!vyG5Tz0qu-A)J<};L+U)7MdJ%g7!(qgDVmrIw
z*Ri3whJMv;ZK35nZ?V_U-y1k0?9=FB5zMuVUekXuj`-h<i(d{^2549O!#L$1#;<9c
z`Ez!KiYT(^5#{ywiM(=J4$4x_5ralaj=UlfdQFCCkLxx!WvY%hGIbujbnA*_$Q8eQ
zPb3+MvS`3{)H3wTeZ!_-R5>X<K0MT!FO>K3R@Ml4J&Pt&Zs6R}$+f*uPkRMK5JHvW
zT}?ez{msn*_zlek-3LMHt5jvb4-NxszFZ-(DsAirC4}*+sj0ONB|i+i5Al4M^_;@r
zxzks-?zgXar%$lfwL2U;Q7c&DM7u`#5_5cwk|I2VhK6Pfxp6x%aIYOCcM%%7e#70}
z-P_)NZVON$b?w?U0uG0pLwX+bqp%G_VX!xijgz`w$VtEGv5hhxSr+4pErm?Wp{=q-
zEpV0H>uPG$;D?nGi(~KKzb^-*%^YLH5)_-7ng++l##)SUUY)DtHo30-V3&?pQ0sj@
zk&(&C{kabxKF}L-CZ1wh%Zu(nqr1Ah6P}!t;^pCqkASl#>R4G3hvMRxjL{NoF>wBy
z$i$tu-ctcWtTAX57k#8+ff3Ggys&+GZLYn&T^zt<DfDS<>;xJ+f!!*D3PHfNIy*W#
zQhk?u;2_77m6_R$gK#{B2hRO8K`g%>UCezHMwX-KzPAF8&^+!Ta2R|VS5k5fK2kNd
zG!YpYY5dmu4I&zGfkeT&9?&wsySw{&ZEcOqTB7#@OdW#-u&+p=P+zYT;%#NdC>gtM
zS2*LDUe+}=sZcPE*9Kc)RfdO$-Si9$G`M+qDq)6%-fne?2@pBPr8ixL=)PkJO7Zs@
zxM3)qlQvc=lpoIKFNK_~sHmW2eevSO4l5%yad;v7V(yxozkdV${t%H!6hq!+^AEj!
zHASc3UTCOB38V)@AP_h&<pUTi)>SOESHs=aHPQe!P&pnmaa2CXz$*fOyRyY9^%vq)
z{}ST~Oqu}k)_)ME{!fe>BssEng<jel+rSlHD<O_Nw?0XTn(B1B)F~<|8rPXFK^4PV
z`G~1bPhkuZO0f^;>xn{`pJt^!Eu5)}kKv$UiDO4}QPVrh>zqvdA{{uIz{FdU)ZEjz
z*QaZj?N4$}x^c))GJXkN6u`hjU@Hi9k!^%`ycN?vm0WPu+h4z`d8Jz{-TQWx)%{Va
z;!Kl3MeN1(z9BwBkh_CJF`XYfEoB*qqoTODxKrNV-f?(hqV{)R>=wLGHmyEfETMa|
zcX04}O?30*__(qH<BYBnU+dK1;602er~2#i@;}KPa)OsP@GR0eTq8@kEd~Ne>hyxd
zQp_2!T3J~5oR*em-e8V0d0s>Frl~1#mPEqq7Y*US8|t@9e`RE0X?r8Fm}Z8?V2R61
zOXgYW>Er#go~h{XHLeqTMK^;=pw&}}@24E(e!Bn``TfL^-B|np^0N?9Mp-$)Wn#Nz
ze0+SU69$r8AVI^*!eT30TNf=rF;(WLdU!)Ur<__f*RWVPqrbIx*0VrI7mTUZD3N%S
zXO60aNy^L1ZxuULK|aM!6H7}jH-dwMUmV5GYsl(VH350bgIx9XHtFnO8Wd{kIjiSL
zWnYB28GR^%@p>e_eeu(PYgbLVG+i#RfUGhKPaw)vij-L%PP+N-O-`D-SzB9kys<!y
z1miWacRf8T18r=kv!O2bn1@uq<mKed5pFORQ!}$mnpQ%r(U236kLBTx4s>H%8<8pu
zvh?D#gI#SS<|wPGGQ#LciO~34>ou;Ya8xm5fR)OA9TpwLnfUGfdu`gaCkY9mGI~`c
z5_ZgG`(R^Z<K$XHQIUuQ@-o}*&atZnMm=qHb*UIEgHTdWbxmDe5s35>6A2$zH#bWh
z+T-$l{mK27%upEhCWQcIMmy#&-f8|N(ocNF0p8#K!#geDJ;_Sj%$KuUqx%PHUD$c(
z46Y&lc1dCxq5h25{c7bU>HHF2hgcJ~jus~BI}cpS)uRm%>#pSs?H$HcR>!#!#2?)E
zB6A7Xo<-ToPhB4EOssnJ_4-8NfqGUbQLyd)M>4+T>5XkHsq0$B@lU^=9Wk%bv2O2I
zX?9c{;DbpYoCQs8-CBp+iE;)EFc4aJm?AGKhV{<O%xLB1pu=B6S|TDM<#4jHii*?^
z&jvFbW+B#ACJolwZ!42i;>1Y{a}C=G69%W-$D-~QI%I-KEa#1yHYgfW1Yv(v<J#2P
z)U>z$<42*po7+dOL@tsuiEk~d82Y}syU4-85$@R#WaHqVJu*DJl$4azz3$gcVOT;y
z)tB)O%z6s0EG;#=r~jDl>+36zvT@l#19f?W5wkG0v<$JG*!E}hx4tz@EHYI!B#3}z
zG>;KW0W1f@R5=p^IG(a6GP5Ou0ByW`D-?}P9I}O>JLXqb4ju2@$-9h>y8NA&)GM2&
zTUca{QbZi0k@sp1Mft{Sm*vvEa2R^!`1>Q?Q`{cv>@l>^cnK5%jkmS7hLpCn9LSWj
z=}U9>>FMbOL(qXh5&0++?u9rt=<rJxKN2PtWwTGaPw`~k+(lc6wVrCS*RD!&b2li}
zF@40pP>I8j_a|kmmAQG}>7<_WFk-l1Sl!2BsJgbcRR#0|FB_Y;f3m^T!{h0~&!5G7
z{QNaA3ov0Rw=3RnZ*O0%Fxi*oV|J&Z2)FC4458M<wr9U%6ylBTbQ2U5tfLTw`C<r8
z&dz(Z+*I!%9xOrHuTBUl&xZT>@uM*8==0~#H-_W{19uBS#DlXwd~R(3EYCA+xX0Sd
zDQC~ebCh7jZeimPPvYaZSr{1^5wD<iry6A-e<PnAE-_7))|2(*kKl8^Xjn*xBRqd0
zPxmho@3gx0SH#o*LEb>y+!$C)ck{@-E)DO6q_jER!6(TbFLc)i#O30@(CNE;&i`V<
zQOuox?Rc5F?NC-d-&0Lm+6PF^$p#6pT*G<c60Rs_s^z}0@`$14)9q%o-{ybRoVNSS
z!aTVdOg3LpBQ~s|zQ=iMc+BvTxzaz*3c?k`<AvN=lwce?d_RL%;Tahj)U1x6xCj{9
zUjU1`h0%n8fk7)T!$dO{GOenrG7g(>ZEZ~e{8ybib&3cw*9?E)qM!mnTx)Pvnhsbx
zsHP;Kd?x4+HC0trSv^@fB_$<gb@h;iz1CY|DQ-(kOK4Dgj#v{FNaF3%%K8QdIj^9~
zrl!TwS~Y5216|$8a)ML*Vq*)$bCd99&R?x;F$7q3E0q8+o8Z)eId>eyjeG@}sw*om
zOpT82lo;XAm-8Jawigw%O^gV~<7e{uy3hXD+&x7yU>g)BrAOzZ(a%#;2V5o&TdJxy
z<>!QuTjAm1b3j)GAHRPStqK#c1YXd%(CEfZdnT!88}!sNjM$b|mb$t+b6~db5c$C8
zrCOpkcd?Txwi_#7)zT7NP*gOBTs@Kb{Q2`qaO21JQ_9bW%5!kTT5Ba1QJ>yB(>-c0
z!4Kfcnj0Fq4GrfRbmu%923uNM43LTMX+7woDMN^e?9$TG0?<hX;TqI~K^L$EAQ(3G
z_xDRc`Tq?DJQnDhWQq<}Z6Fq2ce}!bKtAXOKdzC|I~xI(k{L45;|A`_eHf<Z)C*~j
z_h9b^Pw#~&lK$|n0M^YKgEbB+fcRPWso4C_B5Mi?3Qn_{f;2c5gN{eE;0#6gTUuKW
z5!U%b^8@I7VWbps-Uw75=w``zz`L%V-rcUQu2xDkcBb?8`u5wm7hWe`Vq!DNJZw`t
zg4w0eZPmgGHvi(C;a?(tUzHE=j{k>u#y?79pq=5%-K~*uF79ge&yA>UZ`}BJ@gkRs
zeAa8z^y;gU)t_!J^u(&X*UuKN%-}??85(l?ts8bG30*X}qoO;|lzj10Bs(vnRc~)t
zj!w*nVE?Gl+I3t?buF-UVAbF6FlhSz`$V!Ye)i6!NU_q7P=q<c>DplrI}M4lj6%;!
z;?0{khID>VNr}|+;`V2b78Vw~)=!>1QTpJ08)k`>r>3Eig2e!Hl?6F7=%Ex;UQ*Hv
z`<*xFkO1)%xG`#qh0~|@5-Qr-R=E;IczJmr0U03-4i0j}pueszEh!Dn|C$AtaeRG!
z-J9(m%$T3~5t&3H8T`lKrP2!hskA$8SA<N|O5J)+$cNmp-T)9S5MMf{+0O~taCemg
zUyp`#3Zjc4H#b*T`Tl`{wmw@KZ)Bxgy502qXh-ano1<e)LR{Q|qP+ZFgcKSr9`-ak
zlY=eo#S0}lz0I_NIdKn0q!A-^L;{$(%V=G;d3c!$*ju4*Bzl?ZDZ=qRwlWQAJ+_Z$
z6<X@*=K5wmvp{1&)JMD}(P3&Vu&1E$rs`_1+=_~I2`g58J-z+`Ao%C7%aXIzp-;|-
zuZA~8*-TQ4^?r{XSv+8Rfe4|}e@k=nDi({C`oxvcZ6zfoRUW9CEf^jm1k0p)g^vIo
zyAAY&QzYb?ipRTL$OtqG1v$CooXkuKW=2MrqM`YxHr%T|zP_t4Meqx@MP8gvt*NQ$
z2Lst$Q{xTlwCmo%VG*p@X_+mu^C09%Z=$x1>JTOXLG$Z=UE!f&n~)RuW;YvaVjc2!
zLVP?mY^1MG_v_qdi`cPbTbW%uH@6ij{KCqLx(Kp1y7|E6TIcP-oPN6*D1hf>Ztire
zV>*XsB$WNpu4)}4<_%3oet6~eZM*znS1@Xp$@9XJuJm8TGyO}#E9lz+;=BJLp7~G0
z>uH<a;_MFX{w^;ZLdrl7883?-vv(<t7mU$%GLsBn`Y_se`|&k<oY$#(F=YL!rw29V
znR-@g<8KoAqiI<g&$+5CijydVOs&|>*t|Yv-s<CZaLp7nub&GYXvhrw*sykHqvueV
zwdrNB))>{EI0<ezGfpU@wuX_z3$IDN$jKp7gafHwHOKu#$?H`eqXp{huXC53LJT7>
zr>Yvnpsk>&Xk~DkJp!ovqwMT#)-?wYkF|IxYSQImm|wU|&cKLwK)}NB#1R!0mCNnY
zL11Wnsi~<y#wR4ic`5UonVLdrnVCT%NC8r7_Y6)+L*s}an*uIh@!ea#;kK#*q2HDC
zztk=ZW4B76HbYUl@!F{jUEvwc%Ca(4#hW*u%As$6>T2K0irWx~RZE2o`ODw4?%opA
z@V{+}-2^sa34nP7X6h74ifvHlr3Kn8I$GDj;J9RO052o8h9{iuJlOPsva+)GC5}81
zKYw}o`VwnhCs=lPcz7U11qB)4wZ)hM1AYDG2J<r6)ZP<eY%yqxVy8zCbFtIWw{PD{
zLGgC4xAk~8?Xd?Wx((WHI>&#MbwZPOt;L4B4R*-Bemng~0-kb>RT{1umYkYO1m~ma
z>%_z!8FYBTl9G};czJ80_x|wWmb!j{Q7Guh`@_RGn{2q3L5>y}W7D#-Bo+0BPVn&j
zW@UaP9_9yxeKR5=q8r~20yk%9-fIyUh?aT|(ZbTwk~lfZSXy45gLnzauRO#eoWnJ(
zKi|s?Ae~*yfK0)#X~5ektV%$6b;chsAU#Eq`Vw4=l*~nig_e{NZ^`&$q(5a!Jw>O5
zVxz+3^;I+<+xh)hI-sa~r|AmkLdI}YQ`2md=Ua?x#S4UkgM*#zZJ&C~Bb{%x^)p97
zhwx?j6J_pIK3#u-&+;!(|1v89fZz2G_^f}T{=y%%iE5K|&rUj5IM{kT=@I2!VL3gQ
zLM$==S-#dIx(Ly;w)7VtKInMBJFlhlvny_Kr8Ck2{~G-{4t_j6PUxF3(=#EZa}V6V
zq$;K1juiwnzNf{dZAUPW_vbv7$Qm^2m(?4s;BJ|p4lk3?`{$p+#A4U&FEimO-4NbK
zx2%JLRB4lqF_<8W<oL%|N+76}a_R6eBo8B^;9-hgURhZw(NyM%v(zmxaxGgt{Pg9E
zErJZ%pc@2PEOk+zrlkZ<&<>tqg3h*=Njo(pcZrFK>CXpVM_;F=<bgA~fnAAZW@c_>
zXG7Gxa(B&xoGdDa`uh7j`MY<SwMCM80$W$R)dos#5Z*vhzCJ!qY6DH#`b9~ajg5`U
z^5@RoyM{d@iUf69K|x^wKv223xQIn=e7Kjnf5poD2EiBffP#jG-2*&c6YeL8<bjpN
zXrs}#4h}!x-^)yWes}hKcr@q2;^GmvfWX%j7Ic{V6Yw(Sssjv&lM}UBV4IYhqvldQ
zP0<v)L5D_Qw8Eb*HqM3@6r8cbvS7B#(a4I~y~CQM88y3*kV6*(1B07bGG@#A-o1O9
zO1{*7w6?lKnF#$9F~b`-l3<_l-Y>tID29pPb>w&s9Bpk+b1_m2Qjp;c#nh<Ol$5EQ
z?CjP_0)apq<j&n?ftSOiEH5u_0DtVx%gYnb%F22hq<&O&_tpR~_%fI;&>{vRxh*R#
zZOqcdM9IP3o#<;p#whVw!ZoqtvAjOi+=_aeiK~0^cmzoi>+Lep%CJw7X^b_)il0}w
zhW12$^Y!uyf|~%xYBPIY)A=Zc2_+@fmX(=lPICw<f6U`(DP)6zt{5l%oJ^Xo4ps^V
z7O<eRvvVjF1r%U&|GsQPV`D&T^4l}l2``|o!_|I`9X_^FpNZ|Gzqn`n-`scSisb<A
zoBrXR{lDC2*mHM>8ngy=Nm(giWQMF&WLw|(Tr!Wa(lPCqJ9SF?t&VIOsX*FRWDCh?
zknMZS_EAMFjTE!0X#KTEMbh+YUNkne%DvKbdWBbabogrqnD-tI7w)w)tZs%9$pf4F
zM^ep9{;o%K9&_wuYKqha0TRQ@-f|-OJdA^zihY&S#LSF=Vp{w3Mw&s(Mk?5<MA)nQ
z_wQ>p>Awmqg$C2o(hS01%_xn~M_&BsJyjkR74-^#0IJ_>=z3d*sL%x=R{BUIdwcum
z(fc8OemfuJ`VGZWcmzNfoA=N5_ww?xzkk2V+24Pch3#C}^>*o{&CN|KL+seplwSAD
zx`bih+$Pz%#<bBKC4wOsVHdxD&u=qF5yiM-{a=nwOqiKiSqWap(mjrb7KT8<?%3aN
zURhmLiPM_Nol%M#(NZ-cv;mQt1C6wzzJ8SAkduvShk1u?XRz{h3J!-G^u1hLQ?q3w
zlh({+8#aO0JH-_n7m$4%{-xHn%CuD7t;RI8hJ!UxvuaHDrr0qK+J-V-Ei4VzTNir;
zsC$&_1j;;~Oc4XnudIA#L|{AWCv}bm;_LA3@$vCF;f5;V09ZalN;h~SgE}mTCHeQ>
z-f?4NWB2sJU08i@s757?Hg&{X7aT?hRGFf%z$hDCSYa+y>|`LE<X;VnvBT}#-*W7#
zcD*II0>Alxl^0r8`ZyHOa<~r4XO$6-lTT1k<Gw}RQ041jxFG>0B#ImeFZlRTe_`u4
zORZ}Y9*doN(mgXGo}#0;RsyBtud$tH+F8kdtnKRKvn|GIYGSe@$;Yg**)0p^XaR_*
z2tKx_rzhc$&KJ3-eQ~N$FTPn~1uYy4b5RKNwYDbl3cZAkooY-ENhG1+kEGhQQv&;K
zDn}Gc1$1(iX5#n$f}P`k!!D+{>jPkK{s(qW0DGc=_J96?s<?VN_*_qMJ}x5dV@CSt
z;m9nULH|yXEROkt7{~G=G8W}DU-+Q@o``6&&sbc^3O$N*QdIkD?x)DuPg%V8MBDQp
z=L_rdWgSPzN^kpYKEx@jc}zFW?X~tc%;E38cb;Pj4`-MRWv%~I2hX993=!NqA136%
z@`k8n?e9NEd2!lf=Ty$`ZYft+*8^B|e_tQB@!4JO`dp|2NP{|ld1<LRvB&mv{7lf`
z&=3bg-JobF0^->ubimF9AG3nzAvdP}d{|sz67;lp771LT+OnL6OeWI|kBpdcf#%8y
z0^XimYZK&Yc`@YT;==f_ecC<zqM6x*JJ5g#uvF{*!}`w7&X4J7o1DeQyfEH`Zdq}0
z@k@|cA<%hs9Kp#R)ah9eh!fB6E|1n8?AIJ4W4CU(yRVAiFblhrljcmL9DsG|9r%tq
zPaF!kH&gT3%KrX--)BAFErh5hsoZ$i{Z!4$M~H|rd3pKBYXzSLv*!+i2{1M~zwA_r
z)ZPm(2k&LFLgS?n8SCv35~2=TAGK6hAoE1+GH8M-kn;DVdG$61I0xR~`Q3w`-ClJ3
z{QQo1s^4L2=LMzp?lbQ+l$0zk0)Hz5o8(E`osRN{j_K`@7f+`3&aS|2wMz?7T)2dF
zba1Fwmyy|U@b=zHTx={#(Ybp%QG0*Z-%`aIoH;>$KECFhfw=_w$Oq>Y6t;o62=`VB
zFoZ$1u)^p}3M(xn+u79KHaSJbHU>1-TP0C@QX)lXB%Fwqr)_3WObT0FiHXJ(6%}!4
zI@s7uUlrvHpl_z2XliP@vK!RO-2CFji~2K2EZg}8TcgsOfh9&bRYQVL7#M=w>FMbK
z*mc6TQ;o8z1Q)UO14(@P%H%<)`ymzDlhS{Iuw?lc?Z^Hl+7DtG0quGJ(9ZQI+WW6z
zY&g3@%YD7lGl=H)eJWo>Ik*ySzh+jSSc(dMA(@%>aF^zRwrk!ev4@({7UwhYcVF@z
zM?DlC*K&Im$$L)sdZD4Ci!jZ}<36TdA{_|=e$$!c%sG#b{>*%lKKNGJYQ(||RoG+Z
zT{tQ35~U~&#NWjI)59}fN?Jyyk|x<Pef!<+?`2gS1U16A5eNj0%KjrELlGp1G=99X
z#YmGx?_;y)TW<;qI$^hKU5Tu0?ekr*<lgyjCk%^{18PA1<%;c8-#Iv>0p2Od)IMrv
zW#yiel~u|oAaM9P`EwYv-jM$4>T26%^q0}mQU1D#19rdFt$;tBu1-=?k`Nnx1+5AO
zWU{ME%UM-mmGYZqIeF#RFE11HA<7sXgk)a;Pp@&^+Xe2~Iep@CzKfSvbzo3X^9}Sx
z<Tvq-+jtJrJ-|#Jh}e>DNjhs#%v_|oT@YmmSNySlx&Ed<m$z6{QnD26vAyKpS()x*
zhC0W<3{xs9D#pU=?%q;Hem;#<ynH%SS2!bBop@JYDN|F3Ra-czAIa{cq!;;uHyRc(
zJ3DK@pB9go)8maslxm)he|Y)KnKLb#qm{3vZrr%Bsw!Y0`-%3a<_VH-gLxuYieAV>
z6RM96={^ji!)Qv25#CpW4gsE?8)nGFokaa2DS0_Lm%CNB1U=M29R-!O3nNyerlGOA
zu&^**C9#;?J#!1CjqxYFa~t{cWpZL_suWQMCtv>l#4&v&T-5L+GAbIwrKNc`<%Z5(
zuygmr0>P3fDqAEnoI;^cZbe2o3z9MLuNEV*yyOr3w~W+t%E!jX8zqr7ut5hoz8G{P
s{0y=NY{mQD-UQ!=e(ZcP8HcHxG}a?9+`P23$RBpEUe-sKU9^7ie*^@)R{#J2
new file mode 100644
index 0000000000000000000000000000000000000000..604e652e5ec48052efa7b342c41c33729dbd287c
GIT binary patch
literal 503
zc$@+E0SNwyP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0005LNkl<Zc-obb
zy-Px27{$$?$UZCyA`$~HWJC>7L`x76v;_%@mM9iB^r1sVf!&a+LRUjYsGy;SXlXDS
zqS4Xa%|ApGMeLk-iHe)y13!4)=bZC?+^cI*oxlhzV3LU5D72Q<MRPnJ-!GL)kJW1R
zov`PO8a=H}9?#`+$!4?p242AnVb2*gdj7h}U?dVbV4z;FKgVLRw8diCHJi-=_MB0p
zhhD7>ti@vS4yLDQG<t>g5*Q=w6NyBM8a-G`S{uxk%jE}{?yXkqCe|j<DHz6ViyA%j
zYHctN@fxOENs>0OHhj|zoKb`4hF+}=rVE9F3R6Z^Rhdr$-2y3-$y`vQ2kR-VKQN;x
z3QcOQR^uk8Hk)mQu*WP-&F}XI;T`=Q9`X5ne*E)Gm=u8uQbhDhGMNm+I|q7xguPy`
zr&6iJU^;I!8e!bzJfF{p@!n6^yWQ?Xco)GC==chIJf1CtWrR-<K6W~tN0_gHRj@8t
z1~xDN^g2;}fk0p#;WLCo2nQVwM@W2O6Lnw|^nxyNvM89c+3YsL`v~vZ?e;_QzJ6i+
tw+vuGmgQZS%eB*Pn57q2od8n1;SVuD34zFH^ql|z002ovPDHLkV1l!1+}HpB
--- a/browser/extensions/pdfjs/content/web/viewer.css
+++ b/browser/extensions/pdfjs/content/web/viewer.css
@@ -39,48 +39,62 @@ select {
   display: none;
 }
 [hidden] {
   display: none !important;
 }
 
 #viewerContainer:-webkit-full-screen {
   top: 0px;
-  border-top: 5px solid transparent;
+  border-top: 2px solid transparent;
   background-color: #404040;
   background-image: url(images/texture.png);
   width: 100%;
   height: 100%;
   overflow: hidden;
+  cursor: none;
 }
 
 #viewerContainer:-moz-full-screen {
   top: 0px;
-  border-top: 5px solid transparent;
+  border-top: 2px solid transparent;
   background-color: #404040;
   background-image: url(images/texture.png);
   width: 100%;
   height: 100%;
   overflow: hidden;
-}
-
-:-webkit-full-screen .page:last-child {
-  margin-bottom: 40px;
+  cursor: none;
 }
 
-:-moz-full-screen .page:last-child {
-  margin-bottom: 40px;
-}
-
-#viewerContainer:full-screen {
+#viewerContainer:fullscreen {
   top: 0px;
+  border-top: 2px solid transparent;
   background-color: #404040;
   background-image: url(images/texture.png);
   width: 100%;
   height: 100%;
+  overflow: hidden;
+  cursor: none;
+}
+
+
+:-webkit-full-screen .page {
+  margin-bottom: 100%;
+}
+
+:-moz-full-screen .page {
+  margin-bottom: 100%;
+}
+
+:fullscreen .page {
+  margin-bottom: 100%;
+}
+
+#viewerContainer.presentationControls {
+  cursor: default;
 }
 
 /* outer/inner center provides horizontal center */
 html[dir='ltr'] .outerCenter {
   float: right;
   position: relative;
   right: 50%;
 }
@@ -199,17 +213,16 @@ html[dir='ltr'] #sidebarContent {
 }
 html[dir='rtl'] #sidebarContent {
   right: 0;
 }
 
 #viewerContainer {
   overflow: auto;
   box-shadow: inset 1px 0 0 hsla(0,0%,100%,.05);
-  padding-top: 30px;
   position: absolute;
   top: 32px;
   right: 0;
   bottom: 0;
   left: 0;
 }
 
 .toolbar {
@@ -240,17 +253,17 @@ html[dir='rtl'] #sidebarContent {
                     linear-gradient(hsla(0,0%,30%,.99), hsla(0,0%,25%,.95));
   box-shadow: inset -2px 0 0 hsla(0,0%,100%,.08),
               inset 0 1px 1px hsla(0,0%,0%,.15),
               inset 0 -1px 0 hsla(0,0%,100%,.05),
               0 1px 0 hsla(0,0%,0%,.15),
               0 1px 1px hsla(0,0%,0%,.1);
 }
 
-#toolbarViewer {
+#toolbarViewer, .findbar {
   position: relative;
   height: 32px;
   background-image: url(images/texture.png),
                     -webkit-linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95));
   background-image: url(images/texture.png),
                     -moz-linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95));
   background-image: url(images/texture.png),
                     -ms-linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95));
@@ -260,16 +273,104 @@ html[dir='rtl'] #sidebarContent {
                     linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95));
   border-left: 1px solid hsla(0,0%,0%,.5);
   box-shadow: inset 1px 0 0 hsla(0,0%,100%,.08),
               inset 0 1px 1px hsla(0,0%,0%,.15),
               inset 0 -1px 0 hsla(0,0%,100%,.05),
               0 1px 0 hsla(0,0%,0%,.15),
               0 1px 1px hsla(0,0%,0%,.1);
 }
+
+.findbar {
+  top: 32px;
+  position: absolute;
+  z-index: 10000;
+  height: 32px;
+
+  min-width: 16px;
+  padding: 0px 6px 0px 6px;
+  margin: 4px 2px 4px 2px;
+  color: hsl(0,0%,85%);
+  font-size: 12px;
+  line-height: 14px;
+  text-align: left;
+  cursor: default;
+}
+
+html[dir='ltr'] .findbar {
+  left: 68px;
+}
+
+html[dir='rtl'] .findbar {
+  right: 68px;
+}
+
+.findbar label {
+  -webkit-user-select:none;
+  -moz-user-select:none;
+}
+
+#findInput[data-status="pending"] {
+  background-image: url(images/loading-small.png);
+  background-repeat: no-repeat;
+  background-position: right;
+}
+
+.doorHanger {
+  border: 1px solid hsla(0,0%,0%,.5);
+  border-radius: 2px;
+  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
+}
+.doorHanger:after, .doorHanger:before {
+  bottom: 100%;
+  border: solid transparent;
+  content: " ";
+  height: 0;
+  width: 0;
+  position: absolute;
+  pointer-events: none;
+}
+.doorHanger:after {
+  border-bottom-color: hsla(0,0%,32%,.99);
+  border-width: 8px;
+}
+.doorHanger:before {
+  border-bottom-color: hsla(0,0%,0%,.5);
+  border-width: 9px;
+}
+
+html[dir='ltr'] .doorHanger:after {
+  left: 16px;
+  margin-left: -8px;
+}
+
+html[dir='ltr'] .doorHanger:before {
+  left: 16px;
+  margin-left: -9px;
+}
+
+html[dir='rtl'] .doorHanger:after {
+  right: 16px;
+  margin-right: -8px;
+}
+
+html[dir='rtl'] .doorHanger:before {
+  right: 16px;
+  margin-right: -9px;
+}
+
+#findMsg {
+  font-style: italic;
+  color: #A6B7D0;
+}
+
+.notFound {
+  background-color: rgb(255, 137, 153);
+}
+
 html[dir='ltr'] #toolbarViewerLeft {
   margin-left: -1px;
 }
 html[dir='rtl'] #toolbarViewerRight {
   margin-left: -1px;
 }
 
 
@@ -282,22 +383,24 @@ html[dir='rtl'] #toolbarViewerRight {
 html[dir='ltr'] #toolbarViewerRight,
 html[dir='rtl'] #toolbarViewerLeft {
   position: absolute;
   top: 0;
   right: 0;
 }
 html[dir='ltr'] #toolbarViewerLeft > *,
 html[dir='ltr'] #toolbarViewerMiddle > *,
-html[dir='ltr'] #toolbarViewerRight > * {
+html[dir='ltr'] #toolbarViewerRight > *,
+html[dir='ltr'] .findbar > * {
   float: left;
 }
 html[dir='rtl'] #toolbarViewerLeft > *,
 html[dir='rtl'] #toolbarViewerMiddle > *,
-html[dir='rtl'] #toolbarViewerRight > * {
+html[dir='rtl'] #toolbarViewerRight > *,
+html[dir='rtl'] .findbar > * {
   float: right;
 }
 
 html[dir='ltr'] .splitToolbarButton {
   margin: 3px 2px 4px 0;
   display: inline-block;
 }
 html[dir='rtl'] .splitToolbarButton {
@@ -625,16 +728,36 @@ html[dir='rtl'] .toolbarButton:first-chi
   min-width: 30px;
 }
 
 .toolbarButton#sidebarToggle::before {
   display: inline-block;
   content: url(images/toolbarButton-sidebarToggle.png);
 }
 
+html[dir='ltr'] .toolbarButton.findPrevious::before {
+  display: inline-block;
+  content: url(images/findbarButton-previous.png);
+}
+
+html[dir='rtl'] .toolbarButton.findPrevious::before {
+  display: inline-block;
+  content: url(images/findbarButton-previous-rtl.png);
+}
+
+html[dir='ltr'] .toolbarButton.findNext::before {
+  display: inline-block;
+  content: url(images/findbarButton-next.png);
+}
+
+html[dir='rtl'] .toolbarButton.findNext::before {
+  display: inline-block;
+  content: url(images/findbarButton-next-rtl.png);
+}
+
 html[dir='ltr'] .toolbarButton.pageUp::before {
   display: inline-block;
   content: url(images/toolbarButton-pageUp.png);
 }
 
 html[dir='rtl'] .toolbarButton.pageUp::before {
   display: inline-block;
   content: url(images/toolbarButton-pageUp-rtl.png);
@@ -697,17 +820,17 @@ html[dir='rtl'] .toolbarButton.pageDown:
   content: url(images/toolbarButton-viewThumbnail.png);
 }
 
 #viewOutline.toolbarButton::before {
   display: inline-block;
   content: url(images/toolbarButton-viewOutline.png);
 }
 
-#viewSearch.toolbarButton::before {
+#viewFind.toolbarButton::before {
   display: inline-block;
   content: url(images/toolbarButton-search.png);
 }
 
 
 .toolbarField {
   padding: 3px 6px;
   margin: 4px 0 4px 0;
@@ -724,16 +847,21 @@ html[dir='rtl'] .toolbarButton.pageDown:
   font-size: 12px;
   line-height: 14px;
   outline-style: none;
   -moz-transition-property: background-color, border-color, box-shadow;
   -moz-transition-duration: 150ms;
   -moz-transition-timing-function: ease;
 }
 
+.toolbarField[type=checkbox] {
+  display: inline-block;
+  margin: 8px 0px;
+}
+
 .toolbarField.pageNumber {
   min-width: 16px;
   text-align: right;
   width: 40px;
 }
 
 .toolbarField.pageNumber::-webkit-inner-spin-button,
 .toolbarField.pageNumber::-webkit-outer-spin-button {
@@ -839,35 +967,33 @@ a:focus > .thumbnail > .thumbnailSelecti
   -webkit-user-select:none;
   -moz-user-select:none;
 }
 
 .outlineItem > .outlineItems {
   margin-left: 20px;
 }
 
-.outlineItem > a,
-#searchResults > a {
+.outlineItem > a {
   text-decoration: none;
   display: inline-block;
   min-width: 95%;
   height: 20px;
   padding: 2px 0 0 10px;
   margin-bottom: 1px;
   border-radius: 2px;
   color: hsla(0,0%,100%,.8);
   font-size: 13px;
   line-height: 15px;
   -moz-user-select:none;
   cursor: default;
   white-space: nowrap;
 }
 
-.outlineItem > a:hover,
-#searchResults > a:hover {
+.outlineItem > a:hover {
   background-color: hsla(0,0%,100%,.02);
   background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
   background-clip: padding-box;
   box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
               0 0 1px hsla(0,0%,100%,.2) inset,
               0 0 1px hsla(0,0%,0%,.2);
   color: hsla(0,0%,100%,.9);
 }
@@ -884,54 +1010,24 @@ a:focus > .thumbnail > .thumbnailSelecti
 
 .noOutline,
 .noResults {
   font-size: 12px;
   color: hsla(0,0%,100%,.8);
   font-style: italic;
 }
 
-#searchScrollView {
+#findScrollView {
   position: absolute;
   top: 10px;
   bottom: 10px;
   left: 10px;
   width: 280px;
 }
 
-#searchToolbar {
-  padding-left: 0px;
-  right: 0px;
-  padding-top: 0px;
-  padding-bottom: 5px;
-}
-
-#searchToolbar > input {
-  margin-left: 4px;
-  width: 124px;
-}
-
-#searchToolbar button {
-  width: auto;
-  margin: 0;
-  padding: 0 6px;
-  height: 22px;
-}
-
-#searchResults {
-  overflow: auto;
-  position: absolute;
-  top: 30px;
-  bottom: 0px;
-  left: 0px;
-  right: 0;
-  padding: 4px 4px 0;
-  font-size: smaller;
-}
-
 #sidebarControls {
   position:absolute;
   width: 180px;
   height: 32px;
   left: 15px;
   bottom: 35px;
 }
 
@@ -1064,16 +1160,40 @@ canvas {
 
 .textLayer > div {
   color: transparent;
   position: absolute;
   line-height:1.3;
   white-space:pre;
 }
 
+.textLayer .highlight {
+  margin: -1px;
+  padding: 1px;
+
+  background-color: rgba(180, 0, 170, 0.2);
+  border-radius: 4px;
+}
+
+.textLayer .highlight.begin {
+  border-radius: 4px 0px 0px 4px;
+}
+
+.textLayer .highlight.end {
+  border-radius: 0px 4px 4px 0px;
+}
+
+.textLayer .highlight.middle {
+  border-radius: 0px;
+}
+
+.textLayer .highlight.selected {
+  background-color: rgba(0, 100, 0, 0.2);
+}
+
 /* TODO: file FF bug to support ::-moz-selection:window-inactive
    so we can override the opaque grey background when the window is inactive;
    see https://bugzilla.mozilla.org/show_bug.cgi?id=706209 */
 ::selection { background:rgba(0,0,255,0.3); }
 ::-moz-selection { background:rgba(0,0,255,0.3); }
 
 .annotComment > div {
   position: absolute;
@@ -1286,17 +1406,17 @@ canvas {
   }
   html[dir='rtl'] .outerCenter {
     float: right;
     right: 180px;
   }
 }
 
 @media all and (max-width: 600px) {
-  #toolbarViewerRight {
+  #toolbarViewerRight, #findbar, #viewFind {
     display: none;
   }
 }
 
 @media all and (max-width: 500px) {
   #scaleSelectContainer, #pageNumberLabel {
     display: none;
   }
--- a/browser/extensions/pdfjs/content/web/viewer.html
+++ b/browser/extensions/pdfjs/content/web/viewer.html
@@ -46,17 +46,17 @@ limitations under the License.
 
 var PDFJS = {};
 
 (function pdfjsWrapper() {
   // Use strict in our context only - users might not want it
   'use strict';
 
   PDFJS.build =
-'e98eba1';
+'c8cf445';
 
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 /* Copyright 2012 Mozilla Foundation
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
@@ -352,18 +352,27 @@ var Page = (function PageClosure() {
                   // entry in the dictionary.
                   if (!isValidUrl(url))
                     url = '';
                   item.url = url;
                   break;
                 case 'GoTo':
                   item.dest = a.get('D');
                   break;
+                case 'GoToR':
+                  var url = a.get('F');
+                  // TODO: pdf reference says that GoToR
+                  // can also have 'NewWindow' attribute
+                  if (!isValidUrl(url))
+                    url = '';
+                  item.url = url;
+                  item.dest = a.get('D');
+                  break;
                 default:
-                  TODO('other link types');
+                  TODO('unrecognized link type: ' + a.get('S').name);
               }
             } else if (annotation.has('Dest')) {
               // simple destination link
               var dest = annotation.get('Dest');
               item.dest = isName(dest) ? dest.name : dest;
             }
             break;
           case 'Widget':
@@ -633,18 +642,16 @@ var PDFDocument = (function PDFDocumentC
 
 
 
 // Use only for debugging purposes. This should not be used in any code that is
 // in mozilla master.
 var log = (function() {
   if ('console' in globalScope && 'log' in globalScope['console']) {
     return globalScope['console']['log'].bind(globalScope['console']);
-  } else if ('print' in globalScope) {
-    return globalScope['print'].bind(globalScope);
   } else {
     return function nop() {
     };
   }
 })();
 
 // A notice for devs that will not trigger the fallback UI.  These are good
 // for things that are helpful to devs, such as warning that Workers were
@@ -897,17 +904,17 @@ var Util = PDFJS.Util = (function UtilCl
 
 var PageViewport = PDFJS.PageViewport = (function PageViewportClosure() {
   function PageViewport(viewBox, scale, rotate, offsetX, offsetY) {
     // creating transform to convert pdf coordinate system to the normal
     // canvas like coordinates taking in account scale and rotation
     var centerX = (viewBox[2] + viewBox[0]) / 2;
     var centerY = (viewBox[3] + viewBox[1]) / 2;
     var rotateA, rotateB, rotateC, rotateD;
-    switch (rotate) {
+    switch (rotate % 360) {
       case -180:
       case 180:
         rotateA = -1; rotateB = 0; rotateC = 0; rotateD = 1;
         break;
       case -270:
       case 90:
         rotateA = 0; rotateB = 1; rotateC = 1; rotateD = 0;
         break;
@@ -1950,17 +1957,18 @@ var WorkerTransport = (function WorkerTr
 var TextRenderingMode = {
   FILL: 0,
   STROKE: 1,
   FILL_STROKE: 2,
   INVISIBLE: 3,
   FILL_ADD_TO_PATH: 4,
   STROKE_ADD_TO_PATH: 5,
   FILL_STROKE_ADD_TO_PATH: 6,
-  ADD_TO_PATH: 7
+  ADD_TO_PATH: 7,
+  ADD_TO_PATH_FLAG: 4
 };
 
 // Minimal font size that would be used during canvas fillText operations.
 var MIN_FONT_SIZE = 1;
 
 function createScratchCanvas(width, height) {
   var canvas = document.createElement('canvas');
   canvas.width = width;
@@ -2336,16 +2344,20 @@ var CanvasGraphics = (function CanvasGra
     },
     save: function CanvasGraphics_save() {
       this.ctx.save();
       var old = this.current;
       this.stateStack.push(old);
       this.current = old.clone();
     },
     restore: function CanvasGraphics_restore() {
+      if ('textClipLayers' in this) {
+        this.completeTextClipping();
+      }
+
       var prev = this.stateStack.pop();
       if (prev) {
         this.current = prev;
         this.ctx.restore();
       }
     },
     transform: function CanvasGraphics_transform(a, b, c, d, e, f) {
       this.ctx.transform(a, b, c, d, e, f);
@@ -2465,16 +2477,74 @@ var CanvasGraphics = (function CanvasGra
 
     // Text
     beginText: function CanvasGraphics_beginText() {
       this.current.textMatrix = IDENTITY_MATRIX;
       this.current.x = this.current.lineX = 0;
       this.current.y = this.current.lineY = 0;
     },
     endText: function CanvasGraphics_endText() {
+      if ('textClipLayers' in this) {
+        this.swapImageForTextClipping();
+      }
+    },
+    getCurrentTextClipping: function CanvasGraphics_getCurrentTextClipping() {
+      var ctx = this.ctx;
+      var transform = ctx.mozCurrentTransform;
+      if ('textClipLayers' in this) {
+        // we need to reset only font and transform
+        var maskCtx = this.textClipLayers.maskCtx;
+        maskCtx.setTransform.apply(maskCtx, transform);
+        maskCtx.font = ctx.font;
+        return maskCtx;
+      }
+
+      var canvasWidth = ctx.canvas.width;
+      var canvasHeight = ctx.canvas.height;
+      // keeping track of the text clipping of the separate canvas
+      var maskCanvas = createScratchCanvas(canvasWidth, canvasHeight);
+      var maskCtx = maskCanvas.getContext('2d');
+      maskCtx.setTransform.apply(maskCtx, transform);
+      maskCtx.font = ctx.font;
+      var textClipLayers = {
+        maskCanvas: maskCanvas,
+        maskCtx: maskCtx
+      };
+      this.textClipLayers = textClipLayers;
+      return maskCtx;
+    },
+    swapImageForTextClipping:
+      function CanvasGraphics_swapImageForTextClipping() {
+      var ctx = this.ctx;
+      var canvasWidth = ctx.canvas.width;
+      var canvasHeight = ctx.canvas.height;
+      // saving current image content and clearing whole canvas
+      ctx.save();
+      ctx.setTransform(1, 0, 0, 1, 0, 0);
+      var data = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
+      this.textClipLayers.imageData = data;
+      ctx.clearRect(0, 0, canvasWidth, canvasHeight);
+      ctx.restore();
+    },
+    completeTextClipping: function CanvasGraphics_completeTextClipping() {
+      var ctx = this.ctx;
+      // applying mask to the image (result is saved in maskCanvas)
+      var maskCtx = this.textClipLayers.maskCtx;
+      maskCtx.setTransform(1, 0, 0, 1, 0, 0);
+      maskCtx.globalCompositeOperation = 'source-in';
+      maskCtx.drawImage(ctx.canvas, 0, 0);
+
+      // restoring image data and applying the result of masked drawing
+      ctx.save();
+      ctx.setTransform(1, 0, 0, 1, 0, 0);
+      ctx.putImageData(this.textClipLayers.imageData, 0, 0);
+      ctx.drawImage(this.textClipLayers.maskCanvas, 0, 0);
+      ctx.restore();
+
+      delete this.textClipLayers;
     },
     setCharSpacing: function CanvasGraphics_setCharSpacing(spacing) {
       this.current.charSpacing = spacing;
     },
     setWordSpacing: function CanvasGraphics_setWordSpacing(spacing) {
       this.current.wordSpacing = spacing;
     },
     setHScale: function CanvasGraphics_setHScale(scale) {
@@ -2532,18 +2602,16 @@ var CanvasGraphics = (function CanvasGra
       var browserFontSize = size >= MIN_FONT_SIZE ? size : MIN_FONT_SIZE;
       this.current.fontSizeScale = browserFontSize != MIN_FONT_SIZE ? 1.0 :
                                    size / MIN_FONT_SIZE;
 
       var rule = italic + ' ' + bold + ' ' + browserFontSize + 'px ' + typeface;
       this.ctx.font = rule;
     },
     setTextRenderingMode: function CanvasGraphics_setTextRenderingMode(mode) {
-      if (mode >= TextRenderingMode.FILL_ADD_TO_PATH)
-        TODO('unsupported text rendering mode: ' + mode);
       this.current.textRenderingMode = mode;
     },
     setTextRise: function CanvasGraphics_setTextRise(rise) {
       this.current.textRise = rise;
     },
     moveText: function CanvasGraphics_moveText(x, y) {
       this.current.x = this.current.lineX += x;
       this.current.y = this.current.lineY += y;
@@ -2568,30 +2636,33 @@ var CanvasGraphics = (function CanvasGra
       var fontMatrix = current.fontMatrix || IDENTITY_MATRIX;
 
       ctx.transform.apply(ctx, current.textMatrix);
       ctx.scale(1, -1);
       ctx.translate(current.x, -current.y - current.textRise);
       ctx.transform.apply(ctx, fontMatrix);
       ctx.scale(textHScale, 1);
     },
-    getTextGeometry: function CanvasGraphics_getTextGeometry() {
+    createTextGeometry: function CanvasGraphics_createTextGeometry() {
       var geometry = {};
       var ctx = this.ctx;
       var font = this.current.font;
       var ctxMatrix = ctx.mozCurrentTransform;
       if (ctxMatrix) {
         var bl = Util.applyTransform([0, 0], ctxMatrix);
         var tr = Util.applyTransform([1, 1], ctxMatrix);
         geometry.x = bl[0];
         geometry.y = bl[1];
         geometry.hScale = tr[0] - bl[0];
         geometry.vScale = tr[1] - bl[1];
       }
       geometry.spaceWidth = font.spaceWidth;
+      geometry.fontName = font.loadedName;
+      geometry.fontFamily = font.fallbackName;
+      geometry.fontSize = this.current.fontSize;
       return geometry;
     },
 
     showText: function CanvasGraphics_showText(str, skipTextSelection) {
       var ctx = this.ctx;
       var current = this.current;
       var font = current.font;
       var glyphs = font.charsToGlyphs(str);
@@ -2615,17 +2686,17 @@ var CanvasGraphics = (function CanvasGra
         ctx.transform.apply(ctx, current.textMatrix);
         ctx.translate(current.x, current.y);
 
         ctx.scale(textHScale, 1);
 
         if (textSelection) {
           this.save();
           ctx.scale(1, -1);
-          geom = this.getTextGeometry();
+          geom = this.createTextGeometry();
           this.restore();
         }
         for (var i = 0; i < glyphsLength; ++i) {
 
           var glyph = glyphs[i];
           if (glyph === null) {
             // word break
             this.ctx.translate(wordSpacing, 0);
@@ -2656,17 +2727,17 @@ var CanvasGraphics = (function CanvasGra
         var lineWidth = current.lineWidth;
         var scale = Math.abs(current.textMatrix[0] * fontMatrix[0]);
         if (scale == 0 || lineWidth == 0)
           lineWidth = this.getSinglePixelWidth();
         else
           lineWidth /= scale;
 
         if (textSelection)
-          geom = this.getTextGeometry();
+          geom = this.createTextGeometry();
 
         if (fontSizeScale != 1.0) {
           ctx.scale(fontSizeScale, fontSizeScale);
           lineWidth /= fontSizeScale;
         }
 
         ctx.lineWidth = lineWidth;
 
@@ -2696,35 +2767,40 @@ var CanvasGraphics = (function CanvasGra
                 ctx.strokeText(character, scaledX, 0);
                 break;
               case TextRenderingMode.FILL_STROKE:
               case TextRenderingMode.FILL_STROKE_ADD_TO_PATH:
                 ctx.fillText(character, scaledX, 0);
                 ctx.strokeText(character, scaledX, 0);
                 break;
               case TextRenderingMode.INVISIBLE:
+              case TextRenderingMode.ADD_TO_PATH:
                 break;
             }
+            if (textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG) {
+              var clipCtx = this.getCurrentTextClipping();
+              clipCtx.fillText(character, scaledX, 0);
+            }
           }
 
           x += charWidth;
 
           var glyphUnicode = glyph.unicode === ' ' ? '\u00A0' : glyph.unicode;
           if (glyphUnicode in NormalizedUnicodes)
             glyphUnicode = NormalizedUnicodes[glyphUnicode];
 
           canvasWidth += charWidth;
         }
         current.x += x * textHScale2;
         ctx.restore();
       }
 
       if (textSelection) {
         geom.canvasWidth = canvasWidth;
-        this.textLayer.appendText(font.fallbackName, fontSize, geom);
+        this.textLayer.appendText(geom);
       }
 
       return canvasWidth;
     },
     showSpacedText: function CanvasGraphics_showSpacedText(arr) {
       var ctx = this.ctx;
       var current = this.current;
       var font = current.font;
@@ -2743,17 +2819,17 @@ var CanvasGraphics = (function CanvasGra
         // Type3 fonts - each glyph is a "mini-PDF" (see also showText)
         if (font.coded) {
           ctx.transform.apply(ctx, current.textMatrix);
           ctx.scale(1, -1);
           ctx.translate(current.x, -1 * current.y);
           ctx.scale(textHScale, 1);
         } else
           this.applyTextTransforms();
-        geom = this.getTextGeometry();
+        geom = this.createTextGeometry();
         ctx.restore();
       }
 
       for (var i = 0; i < arrLength; ++i) {
         var e = arr[i];
         if (isNum(e)) {
           var spacingLength = -e * 0.001 * fontSize * textHScale;
           current.x += spacingLength;
@@ -2767,17 +2843,17 @@ var CanvasGraphics = (function CanvasGra
             canvasWidth += shownCanvasWidth;
         } else {
           error('TJ array element ' + e + ' is not string or num');
         }
       }
 
       if (textSelection) {
         geom.canvasWidth = canvasWidth;
-        this.textLayer.appendText(font.fallbackName, fontSize, geom);
+        this.textLayer.appendText(geom);
       }
     },
     nextLineShowText: function CanvasGraphics_nextLineShowText(text) {
       this.nextLine();
       this.showText(text);
     },
     nextLineSetSpacingShowText:
       function CanvasGraphics_nextLineSetSpacingShowText(wordSpacing,
@@ -3108,18 +3184,32 @@ var CanvasGraphics = (function CanvasGra
       var tmpCanvas = createScratchCanvas(w, h);
       var tmpCtx = tmpCanvas.getContext('2d');
       this.putBinaryImageData(tmpCtx, imgData, w, h);
 
       ctx.drawImage(tmpCanvas, 0, -h);
       this.restore();
     },
 
-    putBinaryImageData: function CanvasGraphics_putBinaryImageData() {
-      //
+    putBinaryImageData: function CanvasGraphics_putBinaryImageData(ctx, imgData,
+                                                                   w, h) {
+      var tmpImgData = 'createImageData' in ctx ? ctx.createImageData(w, h) :
+        ctx.getImageData(0, 0, w, h);
+
+      var tmpImgDataPixels = tmpImgData.data;
+      var data = imgData.data;
+      if ('set' in tmpImgDataPixels)
+        tmpImgDataPixels.set(data);
+      else {
+        // Copy over the imageData pixel by pixel.
+        for (var i = 0, ii = tmpImgDataPixels.length; i < ii; i++)
+          tmpImgDataPixels[i] = data[i];
+      }
+
+      ctx.putImageData(tmpImgData, 0, 0);
     },
 
     // Marked content
 
     markPoint: function CanvasGraphics_markPoint(tag) {
       // TODO Marked content.
     },
     markPointProps: function CanvasGraphics_markPointProps(tag, properties) {
@@ -3176,57 +3266,16 @@ var CanvasGraphics = (function CanvasGra
       var inverse = this.ctx.mozCurrentTransformInverse;
       return Math.abs(inverse[0] + inverse[2]);
     }
   };
 
   return CanvasGraphics;
 })();
 
-function checkPutBinaryImageDataCompatibility() {
-  // Feature detection if the browser can use an Uint8Array directly as imgData.
-  var canvas = document.createElement('canvas');
-  canvas.width = 1;
-  canvas.height = 1;
-  var ctx = canvas.getContext('2d');
-
-  try {
-    ctx.putImageData({
-      width: 1,
-      height: 1,
-      data: new Uint8Array(4)
-    }, 0, 0);
-
-    CanvasGraphics.prototype.putBinaryImageData =
-      function CanvasGraphicsPutBinaryImageDataNative(ctx, imgData) {
-        ctx.putImageData(imgData, 0, 0);
-      };
-  } catch (e) {
-    CanvasGraphics.prototype.putBinaryImageData =
-      function CanvasGraphicsPutBinaryImageDataShim(ctx, imgData, w, h) {
-        var tmpImgData = 'createImageData' in ctx ? ctx.createImageData(w, h) :
-          ctx.getImageData(0, 0, w, h);
-
-        var tmpImgDataPixels = tmpImgData.data;
-        var data = imgData.data;
-        if ('set' in tmpImgDataPixels)
-          tmpImgDataPixels.set(data);
-        else {
-          // Copy over the imageData pixel by pixel.
-          for (var i = 0, ii = tmpImgDataPixels.length; i < ii; i++)
-            tmpImgDataPixels[i] = data[i];
-        }
-
-        ctx.putImageData(tmpImgData, 0, 0);
-      };
-  }
-}
-if (!isWorker) {
-  checkPutBinaryImageDataCompatibility();
-}
 
 
 var Name = (function NameClosure() {
   function Name(name) {
     this.name = name;
   }
 
   Name.prototype = {};
@@ -13265,33 +13314,39 @@ var PartialEvaluator = (function Partial
 
   PartialEvaluator.prototype = {
     loadFont: function PartialEvaluator_loadFont(fontName, font, xref,
                                                  resources, dependency) {
       var fontRes = resources.get('Font');
 
       assert(fontRes, 'fontRes not available');
 
+      ++this.fontIdCounter;
+
       font = xref.fetchIfRef(font) || fontRes.get(fontName);
-      assertWellFormed(isDict(font));
-
-      ++this.fontIdCounter;
+      if (!isDict(font)) {
+        return {
+          translated: new ErrorFont('Font ' + fontName + ' is not available'),
+          loadedName: 'font_' + this.uniquePrefix + this.fontIdCounter
+        };
+      }
+
       var loadedName = font.loadedName;
       if (!loadedName) {
         // keep track of each font we translated so the caller can
         // load them asynchronously before calling display on a page
         loadedName = 'font_' + this.uniquePrefix + this.fontIdCounter;
         font.loadedName = loadedName;
 
         var translated;
         try {
           translated = this.translateFont(font, xref, resources,
                                           dependency);
         } catch (e) {
-          translated = { error: e };
+          translated = new ErrorFont(e instanceof Error ? e.message : e);
         }
         font.translated = translated;
 
         var data = translated;
         if (data.loadCharProcs) {
           delete data.loadCharProcs;
 
           var charProcs = font.get('CharProcs').getAll();
@@ -13329,20 +13384,17 @@ var PartialEvaluator = (function Partial
         }
       }
 
       function handleSetFont(fontName, font) {
         font = self.loadFont(fontName, font, xref, resources, dependency);
 
         var loadedName = font.loadedName;
         if (!font.sent) {
-          var data = font.translated;
-
-          if (data instanceof Font)
-            data = data.exportData();
+          var data = font.translated.exportData();
 
           handler.send('obj', [
               loadedName,
               'Font',
               data
           ]);
           font.sent = true;
         }
@@ -13630,16 +13682,18 @@ var PartialEvaluator = (function Partial
       }
 
       return queue;
     },
 
     getTextContent: function partialEvaluatorGetIRQueue(
                                                     stream, resources, state) {
       var bidiTexts;
+      var kSpaceFactor = 0.35;
+      var kMultipleSpaceFactor = 1.5;
 
       if (!state) {
         bidiTexts = [];
         state = {
           bidiTexts: bidiTexts
         };
       } else {
         bidiTexts = state.bidiTexts;
@@ -13671,18 +13725,23 @@ var PartialEvaluator = (function Partial
               font = handleSetFont(args[0].name).translated;
               break;
             case 'TJ':
               var items = args[0];
               for (var j = 0, jj = items.length; j < jj; j++) {
                 if (typeof items[j] === 'string') {
                   chunk += fontCharsToUnicode(items[j], font);
                 } else if (items[j] < 0 && font.spaceWidth > 0) {
-                  var numFakeSpaces = Math.round(-items[j] / font.spaceWidth);
-                  if (numFakeSpaces > 0) {
+                  var fakeSpaces = -items[j] / font.spaceWidth;
+                  if (fakeSpaces > kMultipleSpaceFactor) {
+                    fakeSpaces = Math.round(fakeSpaces);
+                    while (fakeSpaces--) {
+                      chunk += ' ';
+                    }
+                  } else if (fakeSpaces > kSpaceFactor) {
                     chunk += ' ';
                   }
                 }
               }
               break;
             case 'Tj':
               chunk += fontCharsToUnicode(args[0], font);
               break;
@@ -16504,65 +16563,68 @@ var Font = (function FontClosure() {
 
     var numTables = 1;
     var cmap = '\x00\x00' + // version
                string16(numTables) +  // numTables
                '\x00\x03' + // platformID
                '\x00\x01' + // encodingID
                string32(4 + numTables * 8); // start of the table record
 
-    var segCount = ranges.length + 1;
+    var trailingRangesCount = ranges[ranges.length - 1][1] < 0xFFFF ? 1 : 0;
+    var segCount = ranges.length + trailingRangesCount;
     var segCount2 = segCount * 2;
     var searchRange = getMaxPower2(segCount) * 2;
     var searchEntry = Math.log(segCount) / Math.log(2);
     var rangeShift = 2 * segCount - searchRange;
 
     // Fill up the 4 parallel arrays describing the segments.
     var startCount = '';
     var endCount = '';
     var idDeltas = '';
     var idRangeOffsets = '';
     var glyphsIds = '';
     var bias = 0;
 
     if (deltas) {
-      for (var i = 0; i < segCount - 1; i++) {
+      for (var i = 0, ii = ranges.length; i < ii; i++) {
         var range = ranges[i];
         var start = range[0];
         var end = range[1];
         var offset = (segCount - i) * 2 + bias * 2;
         bias += (end - start + 1);
 
         startCount += string16(start);
         endCount += string16(end);
         idDeltas += string16(0);
         idRangeOffsets += string16(offset);
 
         var codes = range[2];
         for (var j = 0, jj = codes.length; j < jj; ++j)
           glyphsIds += string16(deltas[codes[j]]);
       }
     } else {
-      for (var i = 0; i < segCount - 1; i++) {
+      for (var i = 0, ii = ranges.length; i < ii; i++) {
         var range = ranges[i];
         var start = range[0];
         var end = range[1];
         var startCode = range[2][0];
 
         startCount += string16(start);
         endCount += string16(end);
         idDeltas += string16((startCode - start + 1) & 0xFFFF);
         idRangeOffsets += string16(0);
       }
     }
 
-    endCount += '\xFF\xFF';
-    startCount += '\xFF\xFF';
-    idDeltas += '\x00\x01';
-    idRangeOffsets += '\x00\x00';
+    if (trailingRangesCount > 0) {
+      endCount += '\xFF\xFF';
+      startCount += '\xFF\xFF';
+      idDeltas += '\x00\x01';
+      idRangeOffsets += '\x00\x00';
+    }
 
     var format314 = '\x00\x00' + // language
                     string16(segCount2) +
                     string16(searchRange) +
                     string16(searchEntry) +
                     string16(rangeShift) +
                     endCount + '\x00\x00' + startCount +
                     idDeltas + idRangeOffsets + glyphsIds;
@@ -17233,16 +17295,19 @@ var Font = (function FontClosure() {
 
         return true;
       }
 
       // Check that required tables are present
       var requiredTables = ['OS/2', 'cmap', 'head', 'hhea',
                              'hmtx', 'maxp', 'name', 'post'];
 
+      var optionalTables = ['cvt ', 'fpgm', 'glyf', 'loca', 'prep',
+                            'CFF ', 'VORG', 'vhea', 'vmtx'];
+
       var header = readOpenTypeHeader(font);
       var numTables = header.numTables;
 
       var cmap, post, maxp, hhea, hmtx, vhea, vmtx, head, loca, glyf, os2;
       var tables = [];
       for (var i = 0; i < numTables; i++) {
         var table = readTableEntry(font);
         var index = requiredTables.indexOf(table.tag);
@@ -17258,16 +17323,19 @@ var Font = (function FontClosure() {
           else if (table.tag == 'hmtx')
             hmtx = table;
           else if (table.tag == 'head')
             head = table;
           else if (table.tag == 'OS/2')
             os2 = table;
 
           requiredTables.splice(index, 1);
+        } else if (optionalTables.indexOf(table.tag) < 0) {
+          // skipping table if it's not a required or optional table
+          continue;
         } else {
           if (table.tag == 'vmtx')
             vmtx = table;
           else if (table.tag == 'vhea')
             vhea = table;
           else if (table.tag == 'loca')
             loca = table;
           else if (table.tag == 'glyf')
@@ -17563,16 +17631,22 @@ var Font = (function FontClosure() {
           }
           this.useToFontChar = true;
         }
 
         createGlyphNameMap(glyphs, ids, properties);
         this.glyphNameMap = properties.glyphNameMap;
       }
 
+      if (glyphs.length === 0) {
+        // defines at least one glyph
+        glyphs.push({ unicode: 0xF000, code: 0xF000, glyph: '.notdef' });
+        ids.push(0);
+      }
+
       // Converting glyphs and ids into font's cmap table
       cmap.data = createCMapTable(glyphs, ids);
       var unicodeIsEnabled = [];
       for (var i = 0, ii = glyphs.length; i < ii; i++) {
         unicodeIsEnabled[glyphs[i].unicode] = true;
       }
       this.unicodeIsEnabled = unicodeIsEnabled;
 
@@ -18075,16 +18149,19 @@ var Font = (function FontClosure() {
 var ErrorFont = (function ErrorFontClosure() {
   function ErrorFont(error) {
     this.error = error;
   }
 
   ErrorFont.prototype = {
     charsToGlyphs: function ErrorFont_charsToGlyphs() {
       return [];
+    },
+    exportData: function ErrorFont_exportData() {
+      return {error: this.error};
     }
   };
 
   return ErrorFont;
 })();
 
 var CallothersubrCmd = (function CallothersubrCmdClosure() {
   function CallothersubrCmd(index) {
@@ -18298,16 +18375,31 @@ var Type1Parser = function type1Parser()
               charstring.push(3);
               i++;
               continue;
             }
 
             assert(argc == 0, 'callothersubr with arguments is not supported');
             charstring.push(new CallothersubrCmd(index));
             continue;
+          } else if (escape == 7) { // sbw
+            var args = breakUpArgs(charstring, 4);
+            var arg0 = args[0];
+            var arg1 = args[1];
+            var arg2 = args[2];
+            lsb = arg0.value;
+            width = arg2.value;
+            // To convert to type2 we have to move the width value to the first
+            // part of the charstring and then use rmoveto with (dx, dy).
+            // The height argument will not be used for vmtx and vhea tables
+            // reconstruction -- ignoring it.
+            charstring = arg2.arg;
+            charstring = charstring.concat(arg0.arg, arg1.arg);
+            charstring.push('rmoveto');
+            continue;
           } else if (escape == 17 || escape == 33) {
             // pop or setcurrentpoint commands can be ignored
             // since we are not doing callothersubr
             continue;
           } else if (escape == 6) {
             // seac is like type 2's special endchar but it doesn't use the
             // first argument asb, so remove it.
             var args = breakUpArgs(charstring, 5);
@@ -19105,20 +19197,19 @@ var CFFFont = (function CFFFontClosure()
       var unassignedUnicodeItems = [];
       var inverseEncoding = [];
       // CID fonts don't have an encoding.
       if (encoding !== null)
         for (var charcode in encoding)
           inverseEncoding[encoding[charcode]] = charcode | 0;
       else
         inverseEncoding = charsets;
-      for (var i = 0, ii = charsets.length; i < ii; i++) {
+      var i = charsets[0] == '.notdef' ? 1 : 0;
+      for (var ii = charsets.length; i < ii; i++) {
         var glyph = charsets[i];
-        if (glyph == '.notdef')
-          continue;
 
         var code = inverseEncoding[i];
         if (!code || isSpecialUnicode(code)) {
           unassignedUnicodeItems.push(i);
           continue;
         }
         charstrings.push({
           unicode: code,
@@ -31240,16 +31331,17 @@ function MessageHandler(name, comObj) {
   // If there's no console available, console_error in the
   // action handler will do nothing.
   if ('console' in globalScope) {
     ah['console_error'] = [function ahConsoleError(data) {
       globalScope['console'].error.apply(null, data);
     }];
   } else {
     ah['console_error'] = [function ahConsoleError(data) {
+      log.apply(null, data);
     }];
   }
   ah['_warn'] = [function ah_Warn(data) {
     warn(data);
   }];
 
   comObj.onmessage = function messageHandlerComObjOnMessage(event) {
     var data = event.data;
@@ -35955,45 +36047,55 @@ var JpegImage = (function jpegImage() {
       <div id="sidebarContainer">
         <div id="toolbarSidebar" class="splitToolbarButton toggled">
           <button id="viewThumbnail" class="toolbarButton group toggled" title="Show Thumbnails" tabindex="1" data-l10n-id="thumbs">
              <span data-l10n-id="thumbs_label">Thumbnails</span>
           </button>
           <button id="viewOutline" class="toolbarButton group" title="Show Document Outline" tabindex="2" data-l10n-id="outline">
              <span data-l10n-id="outline_label">Document Outline</span>
           </button>
-          <button id="viewSearch" class="toolbarButton group hidden" title="Search Document" tabindex="3" data-l10n-id="search_panel">
-             <span data-l10n-id="search_panel_label">Search Document</span>
-          </button>
         </div>
         <div id="sidebarContent">
           <div id="thumbnailView">
           </div>
           <div id="outlineView" class="hidden">
           </div>
-          <div id="searchView" class="hidden">
-            <div id="searchToolbar">
-              <input id="searchTermsInput" class="toolbarField">
-              <button id="searchButton" class="textButton toolbarButton" data-l10n-id="search">Find</button>
-            </div>
-            <div id="searchResults"></div>
-          </div>
         </div>
       </div>  <!-- sidebarContainer -->
 
       <div id="mainContainer">
+        <div class="findbar hidden doorHanger" id="findbar">
+          <label for="findInput" class="toolbarLabel" data-l10n-id="find_label">Find:</label>
+          <input id="findInput" class="toolbarField" tabindex="20">
+          <div class="splitToolbarButton">
+            <button class="toolbarButton findPrevious" title="" id="findPrevious" tabindex="21" data-l10n-id="find_previous">
+              <span data-l10n-id="find_previous_label">Previous</span>
+            </button>
+            <div class="splitToolbarButtonSeparator"></div>
+            <button class="toolbarButton findNext" title="" id="findNext" tabindex="22" data-l10n-id="find_next">
+              <span data-l10n-id="find_next_label">Next</span>
+            </button>
+          </div>
+          <input type="checkbox" id="findHighlightAll" class="toolbarField">
+          <label for="findHighlightAll" class="toolbarLabel" tabindex="23" data-l10n-id="find_highlight">Highlight all</label>
+          <input type="checkbox" id="findMatchCase" class="toolbarField">
+          <label for="findMatchCase" class="toolbarLabel" tabindex="24" data-l10n-id="find_match_case_label">Match case</label>
+          <span id="findMsg" class="toolbarLabel"></span>
+        </div>
         <div class="toolbar">
           <div id="toolbarContainer">
-
             <div id="toolbarViewer">
               <div id="toolbarViewerLeft">
-                <button id="sidebarToggle" class="toolbarButton" title="Toggle Sidebar" tabindex="4" data-l10n-id="toggle_slider">
+                <button id="sidebarToggle" class="toolbarButton" title="Toggle Sidebar" tabindex="3" data-l10n-id="toggle_slider">
                   <span data-l10n-id="toggle_slider_label">Toggle Sidebar</span>
                 </button>
                 <div class="toolbarButtonSpacer"></div>
+                <button id="viewFind" class="toolbarButton group" title="Find in Document" tabindex="4" data-l10n-id="findbar">
+                   <span data-l10n-id="findbar_label">Find</span>
+                </button>
                 <div class="splitToolbarButton">
                   <button class="toolbarButton pageUp" title="Previous Page" id="previous" tabindex="5" data-l10n-id="previous">
                     <span data-l10n-id="previous_label">Previous</span>
                   </button>
                   <div class="splitToolbarButtonSeparator"></div>
                   <button class="toolbarButton pageDown" title="Next Page" id="next" tabindex="6" data-l10n-id="next">
                     <span data-l10n-id="next_label">Next</span>
                   </button>
@@ -36053,16 +36155,20 @@ var JpegImage = (function jpegImage() {
                   </span>
                 </div>
               </div>
             </div>
           </div>
         </div>
 
         <menu type="context" id="viewerContextMenu">
+          <menuitem label="First Page" id="first_page"
+                    data-l10n-id="first_page" ></menuitem>
+          <menuitem label="Last Page" id="last_page"
+                    data-l10n-id="last_page" ></menuitem>
           <menuitem label="Rotate Counter-Clockwise" id="page_rotate_ccw"
                     data-l10n-id="page_rotate_ccw" ></menuitem>
           <menuitem label="Rotate Clockwise" id="page_rotate_cw"
                     data-l10n-id="page_rotate_cw" ></menuitem>
         </menu>
 
         <div id="viewerContainer">
           <div id="viewer" contextmenu="viewerContextMenu"></div>
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -19,26 +19,33 @@
 
 var kDefaultURL = 'compressed.tracemonkey-pldi-09.pdf';
 var kDefaultScale = 'auto';
 var kDefaultScaleDelta = 1.1;
 var kUnknownScale = 0;
 var kCacheSize = 20;
 var kCssUnits = 96.0 / 72.0;
 var kScrollbarPadding = 40;
+var kVerticalPadding = 5;
 var kMinScale = 0.25;
 var kMaxScale = 4.0;
 var kImageDirectory = './images/';
 var kSettingsMemory = 20;
 var RenderingStates = {
   INITIAL: 0,
   RUNNING: 1,
   PAUSED: 2,
   FINISHED: 3
 };
+var FindStates = {
+  FIND_FOUND: 0,
+  FIND_NOTFOUND: 1,
+  FIND_WRAPPED: 2,
+  FIND_PENDING: 3
+};
 
 
 var mozL10n = document.mozL10n || document.webL10n;
 
 function getFileName(url) {
   var anchor = url.indexOf('#');
   var query = url.indexOf('?');
   var end = Math.min(
@@ -200,77 +207,479 @@ var FirefoxCom = (function FirefoxComClo
     }
   };
 })();
 
 
 // Settings Manager - This is a utility for saving settings
 // First we see if localStorage is available
 // If not, we use FUEL in FF
+// Use asyncStorage for B2G
 var Settings = (function SettingsClosure() {
   var isLocalStorageEnabled = (function localStorageEnabledTest() {
     // Feature test as per http://diveintohtml5.info/storage.html
     // The additional localStorage call is to get around a FF quirk, see
     // bug #495747 in bugzilla
     try {
       return 'localStorage' in window && window['localStorage'] !== null &&
           localStorage;
     } catch (e) {
       return false;
     }
   })();
 
   function Settings(fingerprint) {
-    var database = null;
-    var index;
-    database = FirefoxCom.requestSync('getDatabase', null) || '{}';
-
-    database = JSON.parse(database);
-    if (!('files' in database))
-      database.files = [];
-    if (database.files.length >= kSettingsMemory)
-      database.files.shift();
-    for (var i = 0, length = database.files.length; i < length; i++) {
-      var branch = database.files[i];
-      if (branch.fingerprint == fingerprint) {
-        index = i;
-        break;
-      }
-    }
-    if (typeof index != 'number')
-      index = database.files.push({fingerprint: fingerprint}) - 1;
-    this.file = database.files[index];
-    this.database = database;
+    this.fingerprint = fingerprint;
+    this.initializedPromise = new PDFJS.Promise();
+
+    var resolvePromise = (function settingsResolvePromise(db) {
+      this.initialize(db || '{}');
+      this.initializedPromise.resolve();
+    }).bind(this);
+
+
+    resolvePromise(FirefoxCom.requestSync('getDatabase', null));
+
   }
 
   Settings.prototype = {
+    initialize: function settingsInitialize(database) {
+      database = JSON.parse(database);
+      if (!('files' in database))
+        database.files = [];
+      if (database.files.length >= kSettingsMemory)
+        database.files.shift();
+      var index;
+      for (var i = 0, length = database.files.length; i < length; i++) {
+        var branch = database.files[i];
+        if (branch.fingerprint == this.fingerprint) {
+          index = i;
+          break;
+        }
+      }
+      if (typeof index != 'number')
+        index = database.files.push({fingerprint: this.fingerprint}) - 1;
+      this.file = database.files[index];
+      this.database = database;
+    },
+
     set: function settingsSet(name, val) {
-      if (!('file' in this))
-        return false;
+      if (!this.initializedPromise.isResolved)
+        return;
 
       var file = this.file;
       file[name] = val;
       var database = JSON.stringify(this.database);
       FirefoxCom.requestSync('setDatabase', database);
     },
 
     get: function settingsGet(name, defaultValue) {
-      if (!('file' in this))
+      if (!this.initializedPromise.isResolved)
         return defaultValue;
 
       return this.file[name] || defaultValue;
     }
   };
 
   return Settings;
 })();
 
 var cache = new Cache(kCacheSize);
 var currentPageNumber = 1;
 
+var PDFFindController = {
+  extractTextPromise: null,
+
+  // If active, find results will be highlighted.
+  active: false,
+
+  // Stores the text for each page.
+  pageContents: [],
+
+  pageMatches: [],
+
+  selected: {
+    pageIdx: 0,
+    matchIdx: 0
+  },
+
+  state: null,
+
+  dirtyMatch: false,
+
+  findTimeout: null,
+
+  initialize: function() {
+    var events = [
+      'find',
+      'findagain',
+      'findhighlightallchange',
+      'findcasesensitivitychange'
+    ];
+
+    this.handleEvent = this.handleEvent.bind(this);
+
+    for (var i = 0; i < events.length; i++) {
+      window.addEventListener(events[i], this.handleEvent);
+    }
+  },
+
+  calcFindMatch: function(pageContent) {
+    var query = this.state.query;
+    var caseSensitive = this.state.caseSensitive;
+    var queryLen = query.length;
+
+    if (queryLen === 0)
+      return [];
+
+    if (!caseSensitive) {
+      pageContent = pageContent.toLowerCase();
+      query = query.toLowerCase();
+    }
+
+    var matches = [];
+
+    var matchIdx = -queryLen;
+    while (true) {
+      matchIdx = pageContent.indexOf(query, matchIdx + queryLen);
+      if (matchIdx === -1) {
+        break;
+      }
+
+      matches.push(matchIdx);
+    }
+    return matches;
+  },
+
+  extractText: function() {
+    if (this.extractTextPromise) {
+      return this.extractTextPromise;
+    }
+    this.extractTextPromise = new PDFJS.Promise();
+
+    var self = this;
+    function extractPageText(pageIndex) {
+      PDFView.pages[pageIndex].getTextContent().then(
+        function textContentResolved(data) {
+          // Build the find string.
+          var bidiTexts = data.bidiTexts;
+          var str = '';
+
+          for (var i = 0; i < bidiTexts.length; i++) {
+            str += bidiTexts[i].str;
+          }
+
+          // Store the pageContent as a string.
+          self.pageContents.push(str);
+          // Ensure there is a empty array of matches.
+          self.pageMatches.push([]);
+
+          if ((pageIndex + 1) < PDFView.pages.length)
+            extractPageText(pageIndex + 1);
+          else
+            self.extractTextPromise.resolve();
+        }
+      );
+    }
+    extractPageText(0);
+    return this.extractTextPromise;
+  },
+
+  handleEvent: function(e) {
+    if (this.state === null || e.type !== 'findagain') {
+      this.dirtyMatch = true;
+    }
+    this.state = e.detail;
+    this.updateUIState(FindStates.FIND_PENDING);
+
+    var promise = this.extractText();
+
+    clearTimeout(this.findTimeout);
+    if (e.type === 'find') {
+      // Only trigger the find action after 250ms of silence.
+      this.findTimeout = setTimeout(function() {
+        promise.then(this.performFind.bind(this));
+      }.bind(this), 250);
+    } else {
+      promise.then(this.performFind.bind(this));
+    }
+  },
+
+  updatePage: function(idx) {
+    var page = PDFView.pages[idx];
+
+    if (this.selected.pageIdx === idx) {
+      // If the page is selected, scroll the page into view, which triggers
+      // rendering the page, which adds the textLayer. Once the textLayer is
+      // build, it will scroll onto the selected match.
+      page.scrollIntoView();
+    }
+
+    if (page.textLayer) {
+      page.textLayer.updateMatches();
+    }
+  },
+
+  performFind: function() {
+    // Recalculate all the matches.
+    // TODO: Make one match show up as the current match
+
+    var pages = PDFView.pages;
+    var pageContents = this.pageContents;
+    var pageMatches = this.pageMatches;
+
+    this.active = true;
+
+    if (this.dirtyMatch) {
+      // Need to recalculate the matches.
+      this.dirtyMatch = false;
+
+      this.selected = {
+        pageIdx: -1,
+        matchIdx: -1
+      };
+
+      // TODO: Make this way more lasily (aka. efficient) - e.g. calculate only
+      // the matches for the current visible pages.
+      var firstMatch = true;
+      for (var i = 0; i < pageContents.length; i++) {
+        var matches = pageMatches[i] = this.calcFindMatch(pageContents[i]);
+        if (firstMatch && matches.length !== 0) {
+          firstMatch = false;
+          this.selected = {
+            pageIdx: i,
+            matchIdx: 0
+          };
+        }
+        this.updatePage(i, true);
+      }
+      if (!firstMatch || !this.state.query) {
+        this.updateUIState(FindStates.FIND_FOUND);
+      } else {
+        this.updateUIState(FindStates.FIND_NOTFOUND);
+      }
+    } else {
+      // If there is NO selection, then there is no match at all -> no sense to
+      // handle previous/next action.
+      if (this.selected.pageIdx === -1) {
+        this.updateUIState(FindStates.FIND_NOTFOUND);
+        return;
+      }
+
+      // Handle findAgain case.
+      var previous = this.state.findPrevious;
+      var sPageIdx = this.selected.pageIdx;
+      var sMatchIdx = this.selected.matchIdx;
+      var findState = FindStates.FIND_FOUND;
+
+      if (previous) {
+        // Select previous match.
+
+        if (sMatchIdx !== 0) {
+          this.selected.matchIdx -= 1;
+        } else {
+          var len = pageMatches.length;
+          for (var i = sPageIdx - 1; i != sPageIdx; i--) {
+            if (i < 0)
+              i += len;
+
+            if (pageMatches[i].length !== 0) {
+              this.selected = {
+                pageIdx: i,
+                matchIdx: pageMatches[i].length - 1
+              };
+              break;
+            }
+          }
+          // If pageIdx stayed the same, select last match on the page.
+          if (this.selected.pageIdx === sPageIdx) {
+            this.selected.matchIdx = pageMatches[sPageIdx].length - 1;
+            findState = FindStates.FIND_WRAPPED;
+          } else if (this.selected.pageIdx > sPageIdx) {
+            findState = FindStates.FIND_WRAPPED;
+          }
+        }
+      } else {
+        // Select next match.
+
+        if (pageMatches[sPageIdx].length !== sMatchIdx + 1) {
+          this.selected.matchIdx += 1;
+        } else {
+          var len = pageMatches.length;
+          for (var i = sPageIdx + 1; i < len + sPageIdx; i++) {
+            if (pageMatches[i % len].length !== 0) {
+              this.selected = {
+                pageIdx: i % len,
+                matchIdx: 0
+              };
+              break;
+            }
+          }
+
+          // If pageIdx stayed the same, select first match on the page.
+          if (this.selected.pageIdx === sPageIdx) {
+            this.selected.matchIdx = 0;
+            findState = FindStates.FIND_WRAPPED;
+          } else if (this.selected.pageIdx < sPageIdx) {
+            findState = FindStates.FIND_WRAPPED;
+          }
+        }
+      }
+
+      this.updateUIState(findState, previous);
+      this.updatePage(sPageIdx, sPageIdx === this.selected.pageIdx);
+      if (sPageIdx !== this.selected.pageIdx) {
+        this.updatePage(this.selected.pageIdx, true);
+      }
+    }
+  },
+
+  updateUIState: function(state, previous) {
+    if (PDFView.supportsIntegratedFind) {
+      FirefoxCom.request('updateFindControlState',
+                         {result: state, findPrevious: previous});
+      return;
+    }
+    PDFFindBar.updateUIState(state, previous);
+  }
+};
+
+var PDFFindBar = {
+  // TODO: Enable the FindBar *AFTER* the pagesPromise in the load function
+  // got resolved
+
+  opened: false,
+
+  initialize: function() {
+    this.bar = document.getElementById('findbar');
+    this.toggleButton = document.getElementById('viewFind');
+    this.findField = document.getElementById('findInput');
+    this.highlightAll = document.getElementById('findHighlightAll');
+    this.caseSensitive = document.getElementById('findMatchCase');
+    this.findMsg = document.getElementById('findMsg');
+    this.findStatusIcon = document.getElementById('findStatusIcon');
+
+    var self = this;
+    this.toggleButton.addEventListener('click', function() {
+      self.toggle();
+    });
+
+    this.findField.addEventListener('input', function() {
+      self.dispatchEvent('');
+    });
+
+    this.bar.addEventListener('keydown', function(evt) {
+      switch (evt.keyCode) {
+        case 13: // Enter
+          if (evt.target === self.findField) {
+            self.dispatchEvent('again', evt.shiftKey);
+          }
+          break;
+        case 27: // Escape
+          self.close();
+          break;
+      }
+    });
+
+    document.getElementById('findPrevious').addEventListener('click',
+      function() { self.dispatchEvent('again', true); }
+    );
+
+    document.getElementById('findNext').addEventListener('click', function() {
+      self.dispatchEvent('again', false);
+    });
+
+    this.highlightAll.addEventListener('click', function() {
+      self.dispatchEvent('highlightallchange');
+    });
+
+    this.caseSensitive.addEventListener('click', function() {
+      self.dispatchEvent('casesensitivitychange');
+    });
+  },
+
+  dispatchEvent: function(aType, aFindPrevious) {
+    var event = document.createEvent('CustomEvent');
+    event.initCustomEvent('find' + aType, true, true, {
+      query: this.findField.value,
+      caseSensitive: this.caseSensitive.checked,
+      highlightAll: this.highlightAll.checked,
+      findPrevious: aFindPrevious
+    });
+    return window.dispatchEvent(event);
+  },
+
+  updateUIState: function(state, previous) {
+    var notFound = false;
+    var findMsg = '';
+    var status = '';
+
+    switch (state) {
+      case FindStates.FIND_FOUND:
+        break;
+
+      case FindStates.FIND_PENDING:
+        status = 'pending';
+        break;
+
+      case FindStates.FIND_NOTFOUND:
+        findMsg = mozL10n.get('find_not_found', null, 'Phrase not found');
+        notFound = true;
+        break;
+
+      case FindStates.FIND_WRAPPED:
+        if (previous) {
+          findMsg = mozL10n.get('find_wrapped_to_bottom', null,
+                                'Reached end of page, continued from bottom');
+        } else {
+          findMsg = mozL10n.get('find_wrapped_to_top', null,
+                                'Reached end of page, continued from top');
+        }
+        break;
+    }
+
+    if (notFound) {
+      this.findField.classList.add('notFound');
+    } else {
+      this.findField.classList.remove('notFound');
+    }
+
+    this.findField.setAttribute('data-status', status);
+    this.findMsg.textContent = findMsg;
+  },
+
+  open: function() {
+    if (this.opened) return;
+
+    this.opened = true;
+    this.toggleButton.classList.add('toggled');
+    this.bar.classList.remove('hidden');
+    this.findField.select();
+    this.findField.focus();
+  },
+
+  close: function() {
+    if (!this.opened) return;
+
+    this.opened = false;
+    this.toggleButton.classList.remove('toggled');
+    this.bar.classList.add('hidden');
+
+    PDFFindController.active = false;
+  },
+
+  toggle: function() {
+    if (this.opened) {
+      this.close();
+    } else {
+      this.open();
+    }
+  }
+};
+
 var PDFView = {
   pages: [],
   thumbnails: [],
   currentScale: kUnknownScale,
   currentScaleValue: null,
   initialBookmark: document.location.hash.substring(1),
   startedTextExtraction: false,
   pageText: [],
@@ -280,31 +689,36 @@ var PDFView = {
   fellback: false,
   pdfDocument: null,
   sidebarOpen: false,
   pageViewScroll: null,
   thumbnailViewScroll: null,
   isFullscreen: false,
   previousScale: null,
   pageRotation: 0,
+  mouseScrollTimeStamp: 0,
+  mouseScrollDelta: 0,
   lastScroll: 0,
 
   // called once when the document is loaded
   initialize: function pdfViewInitialize() {
     var self = this;
     var container = this.container = document.getElementById('viewerContainer');
     this.pageViewScroll = {};
     this.watchScroll(container, this.pageViewScroll, updateViewarea);
 
     var thumbnailContainer = this.thumbnailContainer =
                              document.getElementById('thumbnailView');
     this.thumbnailViewScroll = {};
     this.watchScroll(thumbnailContainer, this.thumbnailViewScroll,
                      this.renderHighestPriority.bind(this));
 
+    PDFFindBar.initialize();
+    PDFFindController.initialize();
+
     this.initialized = true;
     container.addEventListener('scroll', function() {
       self.lastScroll = Date.now();
     }, false);
   },
 
   // Helper function to keep track whether a div was scrolled up or down and
   // then call a callback.
@@ -351,20 +765,23 @@ var PDFView = {
     this.currentScaleValue = value;
     if (scale) {
       this.setScale(scale, true, noScroll);
       return;
     }
 
     var container = this.container;
     var currentPage = this.pages[this.page - 1];
+    if (!currentPage) {
+      return;
+    }
 
     var pageWidthScale = (container.clientWidth - kScrollbarPadding) /
                           currentPage.width * currentPage.scale / kCssUnits;
-    var pageHeightScale = (container.clientHeight - kScrollbarPadding) /
+    var pageHeightScale = (container.clientHeight - kVerticalPadding) /
                            currentPage.height * currentPage.scale / kCssUnits;
     switch (value) {
       case 'page-actual':
         scale = 1;
         break;
       case 'page-width':
         scale = pageWidthScale;
         break;
@@ -445,16 +862,26 @@ var PDFView = {
                   doc.webkitRequestFullScreen;
     Object.defineProperty(this, 'supportsFullScreen', { value: support,
                                                         enumerable: true,
                                                         configurable: true,
                                                         writable: false });
     return support;
   },
 
+  get supportsIntegratedFind() {
+    var support = false;
+    support = FirefoxCom.requestSync('supportsIntegratedFind');
+    Object.defineProperty(this, 'supportsIntegratedFind', { value: support,
+                                                            enumerable: true,
+                                                            configurable: true,
+                                                            writable: false });
+    return support;
+  },
+
   initPassiveLoading: function pdfViewInitPassiveLoading() {
     if (!PDFView.loadingBar) {
       PDFView.loadingBar = new ProgressBar('#loadingBar', {});
     }
 
     window.addEventListener('message', function window_message(e) {
       var args = e.data;
 
@@ -705,30 +1132,23 @@ var PDFView = {
       clearInterval(thumbsView._loadingInterval);
 
     var container = document.getElementById('viewer');
     while (container.hasChildNodes())
       container.removeChild(container.lastChild);
 
     var pagesCount = pdfDocument.numPages;
     var id = pdfDocument.fingerprint;
-    var storedHash = null;
     document.getElementById('numPages').textContent =
       mozL10n.get('page_of', {pageCount: pagesCount}, 'of {{pageCount}}');
     document.getElementById('pageNumber').max = pagesCount;
+
     PDFView.documentFingerprint = id;
     var store = PDFView.store = new Settings(id);
-    if (store.get('exists', false)) {
-      var page = store.get('page', '1');
-      var zoom = store.get('zoom', PDFView.currentScale);
-      var left = store.get('scrollLeft', '0');
-      var top = store.get('scrollTop', '0');
-
-      storedHash = 'page=' + page + '&zoom=' + zoom + ',' + left + ',' + top;
-    }
+    var storePromise = store.initializedPromise;
 
     this.pageRotation = 0;
 
     var pages = this.pages = [];
     this.pageText = [];
     this.startedTextExtraction = false;
     var pagesRefMap = {};
     var thumbnails = this.thumbnails = [];
@@ -755,21 +1175,32 @@ var PDFView = {
     });
 
     var destinationsPromise = pdfDocument.getDestinations();
     destinationsPromise.then(function(destinations) {
       self.destinations = destinations;
     });
 
     // outline and initial view depends on destinations and pagesRefMap
-    PDFJS.Promise.all([pagesPromise, destinationsPromise]).then(function() {
+    var promises = [pagesPromise, destinationsPromise, storePromise];
+    PDFJS.Promise.all(promises).then(function() {
       pdfDocument.getOutline().then(function(outline) {
         self.outline = new DocumentOutlineView(outline);
       });
 
+      var storedHash = null;
+      if (store.get('exists', false)) {
+        var page = store.get('page', '1');
+        var zoom = store.get('zoom', PDFView.currentScale);
+        var left = store.get('scrollLeft', '0');
+        var top = store.get('scrollTop', '0');
+
+        storedHash = 'page=' + page + '&zoom=' + zoom + ',' + left + ',' + top;
+      }
+
       self.setInitialView(storedHash, scale);
     });
 
     pdfDocument.getMetadata().then(function(data) {
       var info = data.info, metadata = data.metadata;
       self.documentInfo = info;
       self.metadata = metadata;
 
@@ -887,82 +1318,16 @@ var PDFView = {
       case RenderingStates.INITIAL:
         PDFView.highestPriorityPage = type + view.id;
         view.draw(this.renderHighestPriority.bind(this));
         break;
     }
     return true;
   },
 
-  search: function pdfViewStartSearch() {
-    // Limit this function to run every <SEARCH_TIMEOUT>ms.
-    var SEARCH_TIMEOUT = 250;
-    var lastSearch = this.lastSearch;
-    var now = Date.now();
-    if (lastSearch && (now - lastSearch) < SEARCH_TIMEOUT) {
-      if (!this.searchTimer) {
-        this.searchTimer = setTimeout(function resumeSearch() {
-            PDFView.search();
-          },
-          SEARCH_TIMEOUT - (now - lastSearch)
-        );
-      }
-      return;
-    }
-    this.searchTimer = null;
-    this.lastSearch = now;
-
-    function bindLink(link, pageNumber) {
-      link.href = '#' + pageNumber;
-      link.onclick = function searchBindLink() {
-        PDFView.page = pageNumber;
-        return false;
-      };
-    }
-
-    var searchResults = document.getElementById('searchResults');
-
-    var searchTermsInput = document.getElementById('searchTermsInput');
-    searchResults.removeAttribute('hidden');
-    searchResults.textContent = '';
-
-    var terms = searchTermsInput.value;
-
-    if (!terms)
-      return;
-
-    // simple search: removing spaces and hyphens, then scanning every
-    terms = terms.replace(/\s-/g, '').toLowerCase();
-    var index = PDFView.pageText;
-    var pageFound = false;
-    for (var i = 0, ii = index.length; i < ii; i++) {
-      var pageText = index[i].replace(/\s-/g, '').toLowerCase();
-      var j = pageText.indexOf(terms);
-      if (j < 0)
-        continue;
-
-      var pageNumber = i + 1;
-      var textSample = index[i].substr(j, 50);
-      var link = document.createElement('a');
-      bindLink(link, pageNumber);
-      link.textContent = 'Page ' + pageNumber + ': ' + textSample;
-      searchResults.appendChild(link);
-
-      pageFound = true;
-    }
-    if (!pageFound) {
-      searchResults.textContent = '';
-      var noResults = document.createElement('div');
-      noResults.classList.add('noResults');
-      noResults.textContent = mozL10n.get('search_terms_not_found', null,
-                                              '(Not found)');
-      searchResults.appendChild(noResults);
-    }
-  },
-
   setHash: function pdfViewSetHash(hash) {
     if (!hash)
       return;
 
     if (hash.indexOf('=') >= 0) {
       var params = PDFView.parseQueryString(hash);
       // borrowing syntax from "Parameters for Opening PDF Files"
       if ('nameddest' in params) {
@@ -994,80 +1359,42 @@ var PDFView = {
       this.page = hash;
     else // named destination
       PDFView.navigateTo(unescape(hash));
   },
 
   switchSidebarView: function pdfViewSwitchSidebarView(view) {
     var thumbsView = document.getElementById('thumbnailView');
     var outlineView = document.getElementById('outlineView');
-    var searchView = document.getElementById('searchView');
 
     var thumbsButton = document.getElementById('viewThumbnail');
     var outlineButton = document.getElementById('viewOutline');
-    var searchButton = document.getElementById('viewSearch');
 
     switch (view) {
       case 'thumbs':
         thumbsButton.classList.add('toggled');
         outlineButton.classList.remove('toggled');
-        searchButton.classList.remove('toggled');
         thumbsView.classList.remove('hidden');
         outlineView.classList.add('hidden');
-        searchView.classList.add('hidden');
 
         PDFView.renderHighestPriority();
         break;
 
       case 'outline':
         thumbsButton.classList.remove('toggled');
         outlineButton.classList.add('toggled');
-        searchButton.classList.remove('toggled');
         thumbsView.classList.add('hidden');
         outlineView.classList.remove('hidden');
-        searchView.classList.add('hidden');
 
         if (outlineButton.getAttribute('disabled'))
           return;
         break;
-
-      case 'search':
-        thumbsButton.classList.remove('toggled');
-        outlineButton.classList.remove('toggled');
-        searchButton.classList.add('toggled');
-        thumbsView.classList.add('hidden');
-        outlineView.classList.add('hidden');
-        searchView.classList.remove('hidden');
-
-        var searchTermsInput = document.getElementById('searchTermsInput');
-        searchTermsInput.focus();
-        // Start text extraction as soon as the search gets displayed.
-        this.extractText();
-        break;
     }
   },
 
-  extractText: function() {
-    if (this.startedTextExtraction)
-      return;
-    this.startedTextExtraction = true;
-    var self = this;
-    function extractPageText(pageIndex) {
-      self.pages[pageIndex].pdfPage.getTextContent().then(
-        function textContentResolved(textContent) {
-          self.pageText[pageIndex] = textContent.join('');
-          self.search();
-          if ((pageIndex + 1) < self.pages.length)
-            extractPageText(pageIndex + 1);
-        }
-      );
-    }
-    extractPageText(0);
-  },
-
   getVisiblePages: function pdfViewGetVisiblePages() {
     return this.getVisibleElements(this.container,
                                    this.pages, true);
   },
 
   getVisibleThumbs: function pdfViewGetVisibleThumbs() {
     return this.getVisibleElements(this.thumbnailContainer,
                                    this.thumbnails);
@@ -1188,23 +1515,51 @@ var PDFView = {
     this.previousScale = this.currentScaleValue;
     this.parseScale('page-fit', true);
 
     // Wait for fullscreen to take effect
     setTimeout(function() {
       currentPage.scrollIntoView();
     }, 0);
 
+    this.showPresentationControls();
     return true;
   },
 
   exitFullscreen: function pdfViewExitFullscreen() {
     this.isFullscreen = false;
     this.parseScale(this.previousScale);
     this.page = this.page;
+    this.clearMouseScrollState();
+    this.hidePresentationControls();
+  },
+
+  showPresentationControls: function pdfViewShowPresentationControls() {
+    var DELAY_BEFORE_HIDING_CONTROLS = 3000;
+    var wrapper = document.getElementById('viewerContainer');
+    if (this.presentationControlsTimeout) {
+      clearTimeout(this.presentationControlsTimeout);
+    } else {
+      wrapper.classList.add('presentationControls');
+    }
+    this.presentationControlsTimeout = setTimeout(function hideControls() {
+      wrapper.classList.remove('presentationControls');
+      delete PDFView.presentationControlsTimeout;
+    }, DELAY_BEFORE_HIDING_CONTROLS);
+  },
+
+  hidePresentationControls: function pdfViewShowPresentationControls() {
+    if (!this.presentationControlsTimeout) {
+      return;
+    }
+    clearTimeout(this.presentationControlsTimeout);
+    delete this.presentationControlsTimeout;
+
+    var wrapper = document.getElementById('viewerContainer');
+    wrapper.classList.remove('presentationControls');
   },
 
   rotatePages: function pdfViewPageRotation(delta) {
 
     this.pageRotation = (this.pageRotation + 360 + delta) % 360;
 
     for (var i = 0, l = this.pages.length; i < l; i++) {
       var page = this.pages[i];
@@ -1221,41 +1576,107 @@ var PDFView = {
     this.parseScale(this.currentScaleValue, true);
 
     this.renderHighestPriority();
 
     // Wait for fullscreen to take effect
     setTimeout(function() {
       currentPage.scrollIntoView();
     }, 0);
+  },
+
+  /**
+   * This function flips the page in presentation mode if the user scrolls up
+   * or down with large enough motion and prevents page flipping too often.
+   *
+   * @this {PDFView}
+   * @param {number} mouseScrollDelta The delta value from the mouse event.
+   */
+  mouseScroll: function pdfViewMouseScroll(mouseScrollDelta) {
+    var MOUSE_SCROLL_COOLDOWN_TIME = 50;
+
+    var currentTime = (new Date()).getTime();
+    var storedTime = this.mouseScrollTimeStamp;
+
+    // In case one page has already been flipped there is a cooldown time
+    // which has to expire before next page can be scrolled on to.
+    if (currentTime > storedTime &&
+        currentTime - storedTime < MOUSE_SCROLL_COOLDOWN_TIME)
+      return;
+
+    // In case the user decides to scroll to the opposite direction than before
+    // clear the accumulated delta.
+    if ((this.mouseScrollDelta > 0 && mouseScrollDelta < 0) ||
+        (this.mouseScrollDelta < 0 && mouseScrollDelta > 0))
+      this.clearMouseScrollState();
+
+    this.mouseScrollDelta += mouseScrollDelta;
+
+    var PAGE_FLIP_THRESHOLD = 120;
+    if (Math.abs(this.mouseScrollDelta) >= PAGE_FLIP_THRESHOLD) {
+
+      var PageFlipDirection = {
+        UP: -1,
+        DOWN: 1
+      };
+
+      // In fullscreen mode scroll one page at a time.
+      var pageFlipDirection = (this.mouseScrollDelta > 0) ?
+                                PageFlipDirection.UP :
+                                PageFlipDirection.DOWN;
+      this.clearMouseScrollState();
+      var currentPage = this.page;
+
+      // In case we are already on the first or the last page there is no need
+      // to do anything.
+      if ((currentPage == 1 && pageFlipDirection == PageFlipDirection.UP) ||
+          (currentPage == this.pages.length &&
+           pageFlipDirection == PageFlipDirection.DOWN))
+        return;
+
+      this.page += pageFlipDirection;
+      this.mouseScrollTimeStamp = currentTime;
+    }
+  },
+
+  /**
+   * This function clears the member attributes used with mouse scrolling in
+   * presentation mode.
+   *
+   * @this {PDFView}
+   */
+  clearMouseScrollState: function pdfViewClearMouseScrollState() {
+    this.mouseScrollTimeStamp = 0;
+    this.mouseScrollDelta = 0;
   }
 };
 
 var PageView = function pageView(container, pdfPage, id, scale,
                                  stats, navigateTo) {
   this.id = id;
   this.pdfPage = pdfPage;
 
   this.rotation = 0;
   this.scale = scale || 1.0;
   this.viewport = this.pdfPage.getViewport(this.scale, this.pdfPage.rotate);
 
   this.renderingState = RenderingStates.INITIAL;
   this.resume = null;
 
   this.textContent = null;
+  this.textLayer = null;
 
   var anchor = document.createElement('a');
   anchor.name = '' + this.id;
 
   var div = this.el = document.createElement('div');
   div.id = 'pageContainer' + this.id;
   div.className = 'page';
-  div.style.width = this.viewport.width + 'px';
-  div.style.height = this.viewport.height + 'px';
+  div.style.width = Math.floor(this.viewport.width) + 'px';
+  div.style.height = Math.floor(this.viewport.height) + 'px';
 
   container.appendChild(anchor);
   container.appendChild(div);
 
   this.destroy = function pageViewDestroy() {
     this.update();
     this.pdfPage.destroy();
   };
@@ -1269,18 +1690,18 @@ var PageView = function pageView(contain
     }
 
     this.scale = scale || this.scale;
 
     var totalRotation = (this.rotation + this.pdfPage.rotate) % 360;
     var viewport = this.pdfPage.getViewport(this.scale, totalRotation);
 
     this.viewport = viewport;
-    div.style.width = viewport.width + 'px';
-    div.style.height = viewport.height + 'px';
+    div.style.width = Math.floor(viewport.width) + 'px';
+    div.style.height = Math.floor(viewport.height) + 'px';
 
     while (div.hasChildNodes())
       div.removeChild(div.lastChild);
     div.removeAttribute('data-loaded');
 
     delete this.canvas;
 
     this.loadingIconDiv = document.createElement('div');
@@ -1483,21 +1904,22 @@ var PageView = function pageView(contain
     this.canvas = canvas;
 
     var textLayerDiv = null;
     if (!PDFJS.disableTextLayer) {
       textLayerDiv = document.createElement('div');
       textLayerDiv.className = 'textLayer';
       div.appendChild(textLayerDiv);
     }
-    var textLayer = textLayerDiv ? new TextLayerBuilder(textLayerDiv) : null;
+    var textLayer = this.textLayer =
+          textLayerDiv ? new TextLayerBuilder(textLayerDiv, this.id - 1) : null;
 
     var scale = this.scale, viewport = this.viewport;
-    canvas.width = viewport.width;
-    canvas.height = viewport.height;
+    canvas.width = Math.floor(viewport.width);
+    canvas.height = Math.floor(viewport.height);
 
     var ctx = canvas.getContext('2d');
     ctx.save();
     ctx.fillStyle = 'rgb(255, 255, 255)';
     ctx.fillRect(0, 0, canvas.width, canvas.height);
     ctx.restore();
 
     // Rendering area
@@ -1842,60 +2264,70 @@ var CustomStyle = (function CustomStyleC
     var prop = this.getProp(propName);
     if (prop != 'undefined')
       element.style[prop] = str;
   };
 
   return CustomStyle;
 })();
 
-var TextLayerBuilder = function textLayerBuilder(textLayerDiv) {
+var TextLayerBuilder = function textLayerBuilder(textLayerDiv, pageIdx) {
   var textLayerFrag = document.createDocumentFragment();
+
   this.textLayerDiv = textLayerDiv;
   this.layoutDone = false;
   this.divContentDone = false;
+  this.pageIdx = pageIdx;
+  this.matches = [];
 
   this.beginLayout = function textLayerBuilderBeginLayout() {
     this.textDivs = [];
     this.textLayerQueue = [];
+    this.renderingDone = false;
   };
 
   this.endLayout = function textLayerBuilderEndLayout() {
     this.layoutDone = true;
     this.insertDivContent();
-  },
+  };
 
   this.renderLayer = function textLayerBuilderRenderLayer() {
     var self = this;
     var textDivs = this.textDivs;
     var textLayerDiv = this.textLayerDiv;
     var canvas = document.createElement('canvas');
     var ctx = canvas.getContext('2d');
 
     // No point in rendering so many divs as it'd make the browser unusable
     // even after the divs are rendered
-    if (textDivs.length > 100000)
+    var MAX_TEXT_DIVS_TO_RENDER = 100000;
+    if (textDivs.length > MAX_TEXT_DIVS_TO_RENDER)
       return;
 
-    while (textDivs.length > 0) {
-      var textDiv = textDivs.shift();
+    for (var i = 0, ii = textDivs.length; i < ii; i++) {
+      var textDiv = textDivs[i];
       textLayerFrag.appendChild(textDiv);
 
       ctx.font = textDiv.style.fontSize + ' ' + textDiv.style.fontFamily;
       var width = ctx.measureText(textDiv.textContent).width;
 
       if (width > 0) {
         var textScale = textDiv.dataset.canvasWidth / width;
 
         CustomStyle.setProp('transform' , textDiv,
           'scale(' + textScale + ', 1)');
         CustomStyle.setProp('transformOrigin' , textDiv, '0% 0%');
+
+        textLayerDiv.appendChild(textDiv);
       }
     }
 
+    this.renderingDone = true;
+    this.updateMatches();
+
     textLayerDiv.appendChild(textLayerFrag);
   };
 
   this.setupRenderLayoutTimer = function textLayerSetupRenderLayoutTimer() {
     // Schedule renderLayout() if user has been scrolling, otherwise
     // run it right away
     var kRenderDelay = 200; // in ms
     var self = this;
@@ -1907,27 +2339,26 @@ var TextLayerBuilder = function textLaye
       if (this.renderTimer)
         clearTimeout(this.renderTimer);
       this.renderTimer = setTimeout(function() {
         self.setupRenderLayoutTimer();
       }, kRenderDelay);
     }
   };
 
-  this.appendText = function textLayerBuilderAppendText(fontName, fontSize,
-                                                        geom) {
+  this.appendText = function textLayerBuilderAppendText(geom) {
     var textDiv = document.createElement('div');
 
     // vScale and hScale already contain the scaling to pixel units
-    var fontHeight = fontSize * geom.vScale;
+    var fontHeight = geom.fontSize * geom.vScale;
     textDiv.dataset.canvasWidth = geom.canvasWidth * geom.hScale;
-    textDiv.dataset.fontName = fontName;
+    textDiv.dataset.fontName = geom.fontName;
 
     textDiv.style.fontSize = fontHeight + 'px';
-    textDiv.style.fontFamily = fontName;
+    textDiv.style.fontFamily = geom.fontFamily;
     textDiv.style.left = geom.x + 'px';
     textDiv.style.top = (geom.y - fontHeight) + 'px';
 
     // The content of the div is set in the `setTextContent` function.
 
     this.textDivs.push(textDiv);
   };
 
@@ -1952,16 +2383,211 @@ var TextLayerBuilder = function textLaye
 
     this.setupRenderLayoutTimer();
   };
 
   this.setTextContent = function textLayerBuilderSetTextContent(textContent) {
     this.textContent = textContent;
     this.insertDivContent();
   };
+
+  this.convertMatches = function textLayerBuilderConvertMatches(matches) {
+    var i = 0;
+    var iIndex = 0;
+    var bidiTexts = this.textContent.bidiTexts;
+    var end = bidiTexts.length - 1;
+    var queryLen = PDFFindController.state.query.length;
+
+    var lastDivIdx = -1;
+    var pos;
+
+    var ret = [];
+
+    // Loop over all the matches.
+    for (var m = 0; m < matches.length; m++) {
+      var matchIdx = matches[m];
+      // # Calculate the begin position.
+
+      // Loop over the divIdxs.
+      while (i !== end && matchIdx >= (iIndex + bidiTexts[i].str.length)) {
+        iIndex += bidiTexts[i].str.length;
+        i++;
+      }
+
+      // TODO: Do proper handling here if something goes wrong.
+      if (i == bidiTexts.length) {
+        console.error('Could not find matching mapping');
+      }
+
+      var match = {
+        begin: {
+          divIdx: i,
+          offset: matchIdx - iIndex
+        }
+      };
+
+      // # Calculate the end position.
+      matchIdx += queryLen;
+
+      // Somewhat same array as above, but use a > instead of >= to get the end
+      // position right.
+      while (i !== end && matchIdx > (iIndex + bidiTexts[i].str.length)) {
+        iIndex += bidiTexts[i].str.length;
+        i++;
+      }
+
+      match.end = {
+        divIdx: i,
+        offset: matchIdx - iIndex
+      };
+      ret.push(match);
+    }
+
+    return ret;
+  };
+
+  this.renderMatches = function textLayerBuilder_renderMatches(matches) {
+    // Early exit if there is nothing to render.
+    if (matches.length === 0) {
+      return;
+    }
+
+    var bidiTexts = this.textContent.bidiTexts;
+    var textDivs = this.textDivs;
+    var prevEnd = null;
+    var isSelectedPage = this.pageIdx === PDFFindController.selected.pageIdx;
+    var selectedMatchIdx = PDFFindController.selected.matchIdx;
+    var highlightAll = PDFFindController.state.highlightAll;
+
+    var infty = {
+      divIdx: -1,
+      offset: undefined
+    };
+
+    function beginText(begin, className) {
+      var divIdx = begin.divIdx;
+      var div = textDivs[divIdx];
+      div.innerHTML = '';
+
+      var content = bidiTexts[divIdx].str.substring(0, begin.offset);
+      var node = document.createTextNode(content);
+      if (className) {
+        var isSelected = isSelectedPage &&
+                          divIdx === selectedMatchIdx;
+        var span = document.createElement('span');
+        span.className = className + (isSelected ? ' selected' : '');
+        span.appendChild(node);
+        div.appendChild(span);
+        return;
+      }
+      div.appendChild(node);
+    }
+
+    function appendText(from, to, className) {
+      var divIdx = from.divIdx;
+      var div = textDivs[divIdx];
+
+      var content = bidiTexts[divIdx].str.substring(from.offset, to.offset);
+      var node = document.createTextNode(content);
+      if (className) {
+        var span = document.createElement('span');
+        span.className = className;
+        span.appendChild(node);
+        div.appendChild(span);
+        return;
+      }
+      div.appendChild(node);
+    }
+
+    function highlightDiv(divIdx, className) {
+      textDivs[divIdx].className = className;
+    }
+
+    var i0 = selectedMatchIdx, i1 = i0 + 1, i;
+
+    if (highlightAll) {
+      i0 = 0;
+      i1 = matches.length;
+    } else if (!isSelectedPage) {
+      // Not highlighting all and this isn't the selected page, so do nothing.
+      return;
+    }
+
+    for (i = i0; i < i1; i++) {
+      var match = matches[i];
+      var begin = match.begin;
+      var end = match.end;
+
+      var isSelected = isSelectedPage && i === selectedMatchIdx;
+      var highlightSuffix = (isSelected ? ' selected' : '');
+      if (isSelected)
+        scrollIntoView(textDivs[begin.divIdx], {top: -50});
+
+      // Match inside new div.
+      if (!prevEnd || begin.divIdx !== prevEnd.divIdx) {
+        // If there was a previous div, then add the text at the end
+        if (prevEnd !== null) {
+          appendText(prevEnd, infty);
+        }
+        // clears the divs and set the content until the begin point.
+        beginText(begin);
+      } else {
+        appendText(prevEnd, begin);
+      }
+
+      if (begin.divIdx === end.divIdx) {
+        appendText(begin, end, 'highlight' + highlightSuffix);
+      } else {
+        appendText(begin, infty, 'highlight begin' + highlightSuffix);
+        for (var n = begin.divIdx + 1; n < end.divIdx; n++) {
+          highlightDiv(n, 'highlight middle' + highlightSuffix);
+        }
+        beginText(end, 'highlight end' + highlightSuffix);
+      }
+      prevEnd = end;
+    }
+
+    if (prevEnd) {
+      appendText(prevEnd, infty);
+    }
+  };
+
+  this.updateMatches = function textLayerUpdateMatches() {
+    // Only show matches, once all rendering is done.
+    if (!this.renderingDone)
+      return;
+
+    // Clear out all matches.
+    var matches = this.matches;
+    var textDivs = this.textDivs;
+    var bidiTexts = this.textContent.bidiTexts;
+    var clearedUntilDivIdx = -1;
+
+    // Clear out all current matches.
+    for (var i = 0; i < matches.length; i++) {
+      var match = matches[i];
+      var begin = Math.max(clearedUntilDivIdx, match.begin.divIdx);
+      for (var n = begin; n <= match.end.divIdx; n++) {
+        var div = textDivs[n];
+        div.textContent = bidiTexts[n].str;
+        div.className = '';
+      }
+      clearedUntilDivIdx = match.end.divIdx + 1;
+    }
+
+    if (!PDFFindController.active)
+      return;
+
+    // Convert the matches on the page controller into the match format used
+    // for the textLayer.
+    this.matches = matches =
+      this.convertMatches(PDFFindController.pageMatches[this.pageIdx] || []);
+
+    this.renderMatches(this.matches);
+  };
 };
 
 document.addEventListener('DOMContentLoaded', function webViewerLoad(evt) {
   PDFView.initialize();
   var params = PDFView.parseQueryString(document.location.search.substring(1));
 
   var file = window.location.toString()
 
@@ -1992,28 +2618,28 @@ document.addEventListener('DOMContentLoa
   if ('pdfBug' in hashParams && FirefoxCom.requestSync('pdfBugEnabled')) {
     PDFJS.pdfBug = true;
     var pdfBug = hashParams['pdfBug'];
     var enabled = pdfBug.split(',');
     PDFBug.enable(enabled);
     PDFBug.init();
   }
 
-  if (FirefoxCom.requestSync('searchEnabled')) {
-    document.querySelector('#viewSearch').classList.remove('hidden');
-  }
-
   if (!PDFView.supportsPrinting) {
     document.getElementById('print').classList.add('hidden');
   }
 
   if (!PDFView.supportsFullscreen) {
     document.getElementById('fullscreen').classList.add('hidden');
   }
 
+  if (PDFView.supportsIntegratedFind) {
+    document.querySelector('#viewFind').classList.add('hidden');
+  }
+
   // Listen for warnings to trigger the fallback UI.  Errors should be caught
   // and call PDFView.error() so we don't need to listen for those.
   PDFJS.LogManager.addLogger({
     warn: function() {
       PDFView.fallback();
     }
   });
 
@@ -2042,26 +2668,16 @@ document.addEventListener('DOMContentLoa
       PDFView.switchSidebarView('thumbs');
     });
 
   document.getElementById('viewOutline').addEventListener('click',
     function() {
       PDFView.switchSidebarView('outline');
     });
 
-  document.getElementById('viewSearch').addEventListener('click',
-    function() {
-      PDFView.switchSidebarView('search');
-    });
-
-  document.getElementById('searchButton').addEventListener('click',
-    function() {
-      PDFView.search();
-    });
-
   document.getElementById('previous').addEventListener('click',
     function() {
       PDFView.page--;
     });
 
   document.getElementById('next').addEventListener('click',
     function() {
       PDFView.page++;
@@ -2092,58 +2708,64 @@ document.addEventListener('DOMContentLoa
       window.print();
     });
 
   document.getElementById('download').addEventListener('click',
     function() {
       PDFView.download();
     });
 
-  document.getElementById('searchTermsInput').addEventListener('keydown',
-    function(event) {
-      if (event.keyCode == 13) {
-        PDFView.search();
-      }
-    });
-
   document.getElementById('pageNumber').addEventListener('change',
     function() {
       PDFView.page = this.value;
     });
 
   document.getElementById('scaleSelect').addEventListener('change',
     function() {
       PDFView.parseScale(this.value);
     });
 
+  document.getElementById('first_page').addEventListener('click',
+    function() {
+      PDFView.page = 1;
+    });
+
+  document.getElementById('last_page').addEventListener('click',
+    function() {
+      PDFView.page = PDFView.pdfDocument.numPages;
+    });
+
   document.getElementById('page_rotate_ccw').addEventListener('click',
-      function() {
-        PDFView.rotatePages(-90);
-      });
+    function() {
+      PDFView.rotatePages(-90);
+    });
 
   document.getElementById('page_rotate_cw').addEventListener('click',
-      function() {
-        PDFView.rotatePages(90);
-      });
+    function() {
+      PDFView.rotatePages(90);
+    });
 
   if (FirefoxCom.requestSync('getLoadingType') == 'passive') {
     PDFView.setTitleUsingUrl(file);
     PDFView.initPassiveLoading();
     return;
   }
 
   PDFView.open(file, 0);
 }, true);
 
 function updateViewarea() {
 
   if (!PDFView.initialized)
     return;
   var visible = PDFView.getVisiblePages();
   var visiblePages = visible.views;
+  if (visiblePages.length === 0) {
+    return;
+  }
 
   PDFView.renderHighestPriority();
 
   var currentId = PDFView.page;
   var firstPage = visible.first;
 
   for (var i = 0, ii = visiblePages.length, stillFullyVisible = false;
        i < ii; ++i) {
@@ -2177,21 +2799,23 @@ function updateViewarea() {
   var pdfOpenParams = '#page=' + pageNumber;
   pdfOpenParams += '&zoom=' + normalizedScaleValue;
   var currentPage = PDFView.pages[pageNumber - 1];
   var topLeft = currentPage.getPagePoint(PDFView.container.scrollLeft,
     (PDFView.container.scrollTop - firstPage.y));
   pdfOpenParams += ',' + Math.round(topLeft[0]) + ',' + Math.round(topLeft[1]);
 
   var store = PDFView.store;
-  store.set('exists', true);
-  store.set('page', pageNumber);
-  store.set('zoom', normalizedScaleValue);
-  store.set('scrollLeft', Math.round(topLeft[0]));
-  store.set('scrollTop', Math.round(topLeft[1]));
+  store.initializedPromise.then(function() {
+    store.set('exists', true);
+    store.set('page', pageNumber);
+    store.set('zoom', normalizedScaleValue);
+    store.set('scrollLeft', Math.round(topLeft[0]));
+    store.set('scrollTop', Math.round(topLeft[1]));
+  });
   var href = PDFView.getAnchorUrl(pdfOpenParams);
   document.getElementById('viewBookmark').href = href;
 }
 
 window.addEventListener('resize', function webViewerResize(evt) {
   if (PDFView.initialized &&
       (document.getElementById('pageWidthOption').selected ||
       document.getElementById('pageFitOption').selected ||
@@ -2296,30 +2920,54 @@ window.addEventListener('pagechange', fu
 window.addEventListener('DOMMouseScroll', function(evt) {
   if (evt.ctrlKey) {
     evt.preventDefault();
 
     var ticks = evt.detail;
     var direction = (ticks > 0) ? 'zoomOut' : 'zoomIn';
     for (var i = 0, length = Math.abs(ticks); i < length; i++)
       PDFView[direction]();
+  } else if (PDFView.isFullscreen) {
+    var FIREFOX_DELTA_FACTOR = -40;
+    PDFView.mouseScroll(evt.detail * FIREFOX_DELTA_FACTOR);
+  }
+}, false);
+
+window.addEventListener('mousemove', function keydown(evt) {
+  if (PDFView.isFullscreen) {
+    PDFView.showPresentationControls();
+  }
+}, false);
+
+window.addEventListener('mousedown', function mousedown(evt) {
+  if (PDFView.isFullscreen && evt.button === 0) {
+    // Mouse click in fullmode advances a page
+    evt.preventDefault();
+
+    PDFView.page++;
   }
 }, false);
 
 window.addEventListener('keydown', function keydown(evt) {
   var handled = false;
   var cmd = (evt.ctrlKey ? 1 : 0) |
             (evt.altKey ? 2 : 0) |
             (evt.shiftKey ? 4 : 0) |
             (evt.metaKey ? 8 : 0);
 
   // First, handle the key bindings that are independent whether an input
   // control is selected or not.
   if (cmd == 1 || cmd == 8) { // either CTRL or META key.
     switch (evt.keyCode) {
+      case 70:
+        if (!PDFView.supportsIntegratedFind) {
+          PDFFindBar.toggle();
+          handled = true;
+        }
+        break;
       case 61: // FF/Mac '='
       case 107: // FF '+' and '='
       case 187: // Chrome '+'
         PDFView.zoomIn();
         handled = true;
         break;
       case 173: // FF/Mac '-'
       case 109: // FF '-'
@@ -2329,51 +2977,85 @@ window.addEventListener('keydown', funct
         break;
       case 48: // '0'
         PDFView.parseScale(kDefaultScale, true);
         handled = true;
         break;
     }
   }
 
+  // CTRL or META with or without SHIFT.
+  if (cmd == 1 || cmd == 8 || cmd == 5 || cmd == 12) {
+    switch (evt.keyCode) {
+      case 71: // g
+        if (!PDFView.supportsIntegratedFind) {
+          PDFFindBar.dispatchEvent('again', cmd == 5 || cmd == 12);
+          handled = true;
+        }
+        break;
+    }
+  }
+
   if (handled) {
     evt.preventDefault();
     return;
   }
 
   // Some shortcuts should not get handled if a control/input element
   // is selected.
   var curElement = document.activeElement;
-  if (curElement && curElement.tagName == 'INPUT')
+  if (curElement && (curElement.tagName == 'INPUT' ||
+                     curElement.tagName == 'SELECT')) {
     return;
-  var controlsElement = document.getElementById('controls');
+  }
+  var controlsElement = document.getElementById('toolbar');
   while (curElement) {
     if (curElement === controlsElement && !PDFView.isFullscreen)
-      return; // ignoring if the 'controls' element is focused
+      return; // ignoring if the 'toolbar' element is focused
     curElement = curElement.parentNode;
   }
 
   if (cmd == 0) { // no control key pressed at all.
     switch (evt.keyCode) {
+      case 38: // up arrow
+      case 33: // pg up
+      case 8: // backspace
+        if (!PDFView.isFullscreen) {
+          break;
+        }
+        //  in fullscreen mode falls throw here
       case 37: // left arrow
       case 75: // 'k'
       case 80: // 'p'
         PDFView.page--;
         handled = true;
         break;
+      case 40: // down arrow
+      case 34: // pg down
+      case 32: // spacebar
+        if (!PDFView.isFullscreen) {
+          break;
+        }
+        //  in fullscreen mode falls throw here
       case 39: // right arrow
       case 74: // 'j'
       case 78: // 'n'
         PDFView.page++;
         handled = true;
         break;
 
-      case 32: // spacebar
+      case 36: // home
         if (PDFView.isFullscreen) {
-          PDFView.page++;
+          PDFView.page = 1;
+          handled = true;
+        }
+        break;
+      case 35: // end
+        if (PDFView.isFullscreen) {
+          PDFView.page = PDFView.pdfDocument.numPages;
           handled = true;
         }
         break;
 
       case 82: // 'r'
         PDFView.rotatePages(90);
         break;
     }
@@ -2384,16 +3066,17 @@ window.addEventListener('keydown', funct
       case 82: // 'r'
         PDFView.rotatePages(-90);
         break;
     }
   }
 
   if (handled) {
     evt.preventDefault();
+    PDFView.clearMouseScrollState();
   }
 });
 
 window.addEventListener('beforeprint', function beforePrint(evt) {
   PDFView.beforePrint();
 });
 
 window.addEventListener('afterprint', function afterPrint(evt) {
--- a/browser/extensions/pdfjs/extension-files
+++ b/browser/extensions/pdfjs/extension-files
@@ -1,16 +1,21 @@
 chrome.manifest
 components/PdfStreamConverter.js
 content/PdfJs.jsm
 content/web/debugger.js
 content/web/images/annotation-check.svg
 content/web/images/annotation-comment.svg
 content/web/images/annotation-text.svg
+content/web/images/findbarButton-next-rtl.png
+content/web/images/findbarButton-next.png
+content/web/images/findbarButton-previous-rtl.png
+content/web/images/findbarButton-previous.png
 content/web/images/loading-icon.gif
+content/web/images/loading-small.png
 content/web/images/texture.png
 content/web/images/toolbarButton-bookmark.png
 content/web/images/toolbarButton-download.png
 content/web/images/toolbarButton-fullscreen.png
 content/web/images/toolbarButton-menuArrows.png
 content/web/images/toolbarButton-openFile.png
 content/web/images/toolbarButton-pageDown-rtl.png
 content/web/images/toolbarButton-pageDown.png
--- a/browser/locales/en-US/pdfviewer/chrome.properties
+++ b/browser/locales/en-US/pdfviewer/chrome.properties
@@ -1,8 +1,4 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
 # Chrome notification bar messages and buttons
 unsupported_feature=This PDF document might not be displayed correctly.
 open_with_different_viewer=Open With Different Viewer
 open_with_different_viewer.accessKey=o
--- a/browser/locales/en-US/pdfviewer/viewer.properties
+++ b/browser/locales/en-US/pdfviewer/viewer.properties
@@ -1,12 +1,8 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
 # Main toolbar buttons (tooltips and alt text for images)
 previous.title=Previous Page
 previous_label=Previous
 next.title=Next Page
 next_label=Next
 
 # LOCALIZATION NOTE (page_label, page_of):
 # These strings are concatenated to form the "Page: X of Y" string.
@@ -35,37 +31,47 @@ bookmark_label=Current View
 # (the _label strings are alt text for the buttons, the .title strings are
 # tooltips)
 toggle_slider.title=Toggle Slider
 toggle_slider_label=Toggle Slider
 outline.title=Show Document Outline
 outline_label=Document Outline
 thumbs.title=Show Thumbnails
 thumbs_label=Thumbnails
-search_panel.title=Search Document
-search_panel_label=Search
+findbar.title=Find in Document
+findbar_label=Find
 
 # Document outline messages
 no_outline=No Outline Available
 
 # Thumbnails panel item (tooltip and alt text for images)
 # LOCALIZATION NOTE (thumb_page_title): "{{page}}" will be replaced by the page
 # number.
 thumb_page_title=Page {{page}}
 # LOCALIZATION NOTE (thumb_page_canvas): "{{page}}" will be replaced by the page
 # number.
 thumb_page_canvas=Thumbnail of Page {{page}}
 
 # Context menu
+first_page.label=Go to First Page
+last_page.label=Go to Last Page
 page_rotate_cw.label=Rotate Clockwise
-page_rotate_ccw.label=Rotate Counter-Clockwise
+page_rotate_ccw.label=Rotate Counterclockwise
 
-# Search panel button title and messages
-search=Find
-search_terms_not_found=(Not found)
+# Find panel button title and messages
+find_label=Find:
+find_previous.title=Find the previous occurrence of the phrase
+find_previous_label=Previous
+find_next.title=Find the next occurrence of the phrase
+find_next_label=Next
+find_highlight=Highlight all
+find_match_case_label=Match case
+find_wrapped_to_bottom=Reached end of page, continued from bottom
+find_wrapped_to_top=Reached end of page, continued from top
+find_not_found=Phrase not found
 
 # Error panel labels
 error_more_info=More Information
 error_less_info=Less Information
 error_close=Close
 # LOCALIZATION NOTE (error_build): "{{build}}" will be replaced by the PDF.JS
 # build ID.
 error_build=PDF.JS Build: {{build}}