Merge fx-team to m-c.
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 24 Jan 2014 17:00:09 -0500
changeset 181136 a12c7d7ac590402510356d33fc6acd4f5e9c1b1a
parent 181105 fdc82b2c5584323dfd7deaaa0601e596d6725578 (current diff)
parent 181135 166b7998065e295f4abf9f00bfc387291cfa1975 (diff)
child 181168 765019ac7236420a6b26ecef7f8fb0fea8efc732
push id3343
push userffxbld
push dateMon, 17 Mar 2014 21:55:32 +0000
treeherdermozilla-beta@2f7d3415f79f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone29.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
Merge fx-team to m-c.
browser/components/sessionstore/src/Messenger.jsm
browser/components/sessionstore/src/SessionSaver.jsm
browser/components/sessionstore/test/browser_625257.js
browser/components/sessionstore/test/browser_pageshow.js
browser/components/sessionstore/test/browser_tabStateCache.js
browser/devtools/webconsole/test/head.js
browser/devtools/webconsole/webconsole.js
browser/extensions/pdfjs/content/build/pdf.worker.js
browser/extensions/pdfjs/extension-files
browser/extensions/pdfjs/icon.png
browser/extensions/pdfjs/icon64.png
toolkit/components/telemetry/Histograms.json
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -1100,16 +1100,17 @@ let RemoteDebugger = {
         if ("nsIProfiler" in Ci) {
           DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/profiler.js");
         }
         DebuggerServer.registerModule("devtools/server/actors/inspector");
         DebuggerServer.registerModule("devtools/server/actors/styleeditor");
         DebuggerServer.registerModule("devtools/server/actors/stylesheets");
         DebuggerServer.registerModule("devtools/server/actors/tracer");
         DebuggerServer.registerModule("devtools/server/actors/webgl");
+        DebuggerServer.registerModule("devtools/server/actors/memory");
       }
       DebuggerServer.addActors('chrome://browser/content/dbg-browser-actors.js');
       DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/webapps.js");
       DebuggerServer.registerModule("devtools/server/actors/device");
 
 #ifdef MOZ_WIDGET_GONK
       DebuggerServer.onConnectionChange = function(what) {
         AdbController.updateState();
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -866,18 +866,16 @@ pref("browser.sessionstore.restore_hidde
 // If restore_on_demand is set, pinned tabs are restored on startup by default.
 // When set to true, this pref overrides that behavior, and pinned tabs will only
 // be restored when they are focused.
 pref("browser.sessionstore.restore_pinned_tabs_on_demand", false);
 // The version at which we performed the latest upgrade backup
 pref("browser.sessionstore.upgradeBackup.latestBuildID", "");
 // End-users should not run sessionstore in debug mode
 pref("browser.sessionstore.debug", false);
-// Enable asynchronous data collection by default.
-pref("browser.sessionstore.async", true);
 
 // allow META refresh by default
 pref("accessibility.blockautorefresh", false);
 
 // Whether history is enabled or not.
 pref("places.history.enabled", true);
 
 // the (maximum) number of the recent visits to sample
@@ -1332,12 +1330,17 @@ pref("geo.wifi.uri", "https://www.google
 // currently irrelevant for desktop e10s
 pref("network.disable.ipc.security", true);
 
 // CustomizableUI debug logging.
 pref("browser.uiCustomization.debug", false);
 
 // The URL where remote content that composes the UI for Firefox Accounts should
 // be fetched. Must use HTTPS.
-pref("identity.fxaccounts.remote.uri", "https://accounts.dev.lcip.org/?service=sync");
+pref("identity.fxaccounts.remote.uri", "https://accounts.firefox.com/?service=sync&context=fx_desktop_v1");
+
+// The URL we take the user to when they opt to "manage" their Firefox Account.
+// Note that this will always need to be in the same TLD as the
+// "identity.fxaccounts.remote.uri" pref.
+pref("identity.fxaccounts.settings.uri", "https://accounts.firefox.com/settings");
 
 // The URL of the Firefox Accounts auth server backend
-pref("identity.fxaccounts.auth.uri", "https://api-accounts.dev.lcip.org/v1");
+pref("identity.fxaccounts.auth.uri", "https://api.accounts.firefox.com/v1");
--- a/browser/base/content/aboutaccounts/aboutaccounts.css
+++ b/browser/base/content/aboutaccounts/aboutaccounts.css
@@ -13,9 +13,38 @@ html, body {
   border: 0;
   display: flex;
 }
 
 #remote {
   width: 100%;
   height: 100%;
   border: 0;
+  display: none;
 }
+
+#manage, #intro, #stage {
+  display: none;
+}
+
+#stage {
+  opacity: 1;
+}
+
+.graphic-sync-intro {
+  background-image: url(images/graphic_sync_intro.png);
+}
+
+.description, .button-row {
+  margin-top: 30px;
+}
+
+/* Retina */
+@media
+only screen and (   min--moz-device-pixel-ratio: 2),
+only screen and (   -moz-min-device-pixel-ratio: 2),
+only screen and (        min-device-pixel-ratio: 2),
+only screen and (                min-resolution: 192dpi),
+only screen and (                min-resolution: 2dppx) {
+  .graphic-sync-intro {
+    background-image: url(images/graphic_sync_intro@2x.png);
+  }
+}
--- a/browser/base/content/aboutaccounts/aboutaccounts.js
+++ b/browser/base/content/aboutaccounts/aboutaccounts.js
@@ -124,10 +124,58 @@ let wrapper = {
     let data = {
       type: type,
       content: content
     };
     this.iframe.contentWindow.postMessage(data, authUrl);
   },
 };
 
-wrapper.init();
+
+// Button onclick handlers
+function handleOldSync() {
+  // we just want to navigate the current tab to the new location...
+  window.location = "https://services.mozilla.com/legacysync";
+}
+
+function getStarted() {
+  hide("intro");
+  hide("stage");
+  show("remote");
+}
+
+function openPrefs() {
+  window.openPreferences("paneSync");
+}
+
+function init() {
+  let signinQuery = window.location.href.match(/signin=true$/);
 
+  if (signinQuery) {
+    show("remote");
+    wrapper.init();
+  } else {
+    // Check if we have a local account
+    fxAccounts.getSignedInUser().then(user => {
+      if (user) {
+        show("stage");
+        show("manage");
+      } else {
+        show("stage");
+        show("intro");
+        // load the remote frame in the background
+        wrapper.init();
+      }
+    });
+  }
+}
+
+function show(id) {
+  document.getElementById(id).style.display = 'block';
+}
+function hide(id) {
+  document.getElementById(id).style.display = 'none';
+}
+
+document.addEventListener("DOMContentLoaded", function onload() {
+  document.removeEventListener("DOMContentLoaded", onload, true);
+  init();
+}, true);
--- a/browser/base/content/aboutaccounts/aboutaccounts.xhtml
+++ b/browser/base/content/aboutaccounts/aboutaccounts.xhtml
@@ -4,25 +4,84 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 <!DOCTYPE html [
   <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
   %htmlDTD;
   <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
   %brandDTD;
   <!ENTITY % aboutAccountsDTD SYSTEM "chrome://browser/locale/aboutAccounts.dtd">
   %aboutAccountsDTD;
+  <!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
+  %syncBrandDTD;
 ]>
 
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
    <title>&aboutAccounts.pageTitle;</title>
+   <meta name="viewport" content="width=device-width"/>
+
+
    <link rel="icon" type="image/png" id="favicon"
          href="chrome://branding/content/icon32.png"/>
    <link rel="stylesheet"
+     href="chrome://browser/content/aboutaccounts/fonts.css"
+     type="text/css" />
+   <link rel="stylesheet"
+     href="chrome://browser/content/aboutaccounts/normalize.css"
+     type="text/css" />
+   <link rel="stylesheet"
+     href="chrome://browser/content/aboutaccounts/main.css"
+     type="text/css" />
+   <link rel="stylesheet"
      href="chrome://browser/content/aboutaccounts/aboutaccounts.css"
      type="text/css" />
   </head>
   <body>
+    <div id="stage">
+
+      <div id="manage">
+        <header>
+          <h1>&aboutAccounts.pageTitle;</h1>
+
+          <h2>&syncBrand.shortName.label;</h2>
+        </header>
+
+        <section>
+            <div class="graphic graphic-sync-intro"> </div>
+
+            <div class="button-row">
+              <a class="button" href="#" onclick="openPrefs()">Manage</a>
+            </div>
+        </section>
+      </div>
+
+      <div id="intro">
+        <header>
+          <h1>&aboutAccounts.pageTitle;</h1>
+
+          <h2>&syncBrand.shortName.label;</h2>
+        </header>
+
+        <section>
+            <div class="graphic graphic-sync-intro"> </div>
+
+            <div class="description">&aboutAccountsSetup.description;</div>
+
+            <div class="button-row">
+              <a class="button" href="#" onclick="getStarted()">&aboutAccountsSetup.startButton.label;</a>
+            </div>
+
+            <div class="links">
+              <a id="oldsync" href="#" onclick="handleOldSync();">&aboutAccountsSetup.useOldSync.label;</a>
+            </div>
+        </section>
+      </div>
+
+    </div>
+
     <iframe mozframetype="content" id="remote" />
+
+    <script type="application/javascript;version=1.8"
+      src="chrome://browser/content/utilityOverlay.js"/>
     <script type="text/javascript;version=1.8"
       src="chrome://browser/content/aboutaccounts/aboutaccounts.js" />
   </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/browser/base/content/aboutaccounts/fonts.css
@@ -0,0 +1,31 @@
+@font-face {
+  font-family: 'Fira Sans';
+  font-style: normal;
+  font-weight: 400;
+  src: local('Fira Sans'),
+       local('FiraSans'),
+       url('fonts/firasans-regular.woff') format('woff'),
+       /*url('/fonts/latin/firasans-regular.ttf') format('truetype'),*/
+       /*url('/fonts/latin/firasans-regular.svg#Fira Sans') format('svg');*/
+}
+@font-face {
+  font-family: 'Fira Sans';
+  font-style: normal;
+  font-weight: 300;
+  src: local('Fira Sans Light'),
+       local('FiraSansLight'),
+       url('fonts/firasans-light.woff') format('woff'),
+       /*url('/fonts/latin/firasans-light.ttf') format('truetype'),*/
+       /*url('/fonts/latin/firasans-light.svg#Fira Sans') format('svg');*/
+}
+@font-face {
+  font-family: 'Clear Sans';
+  font-style: normal;
+  font-weight: 400;
+  src: local('Clear Sans'),
+       local('ClearSans'),
+       url('fonts/clearsans-regular.woff') format('woff'),
+       /*url('/fonts/latin/clearsans-regular.ttf') format('truetype'),*/
+       /*url('/fonts/latin/clearsans-regular.svg#Clear Sans') format('svg');*/
+}
+
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..00c279d2e9f4b95f230b58ef897dfe689e6c36cb
GIT binary patch
literal 25376
zc${>%V{|6X_XYaIwr$(CZQGgHwr$(CZ6^~<oJ^dFZRh6w{qLu{)~#N9Ri9nwoT}<w
z-CgCbATAC70{j#cLIC1_b)y5||EGwHtIGZOf&Sd${s)U+4qyr@49oxk(C&|I_#d7S
z10hUojec@~%Kq>kKbZRZx@9zRb0Gu(U|oKE*nhacFdn$K<_;FN007v?52yeDD8<wP
ze}ILN^G~et%a0%I{{R7iu(0to2LRxI^0VCn04k7=7Ip8IW=5tzd2D`snEuNN5*DUm
z`9uD21wS^y4@eO#z^p87U4F&@EcgQ`006|WI?mM5+RVug01#CL0MMra01jGxa>EcC
zdy}77*qWdCrvK2Gp8aKO<nc2Wpn;!Ug#Q8f4M@+<$kq%1(D|{!6#)PQ;W>=dIR|^^
zpLzW^XP~g3et?y1vK$<o%zkX>n;$Ol2P<oImI@vlCWfYlhJbyXKll9O_nSUu3CD(j
zK<DwJtDuGNrw^KcyT%NlPF$E7f<=HpRk*{SPX8}vH{<!4HlgLS{n&s90s}-56znem
zkbMol2l$_MYXBht*bhJfz&{!?0Mvfy20$eM<fjz`)Bq^qrxgH}1;+Bj6Z~HdvH&m#
z{s{$!0RsU61_lNJEdT&tD*`;;e@Mvx)Rt3_R{#hC;E2({7Csk%vLN$-SqK3@`8%sW
z+G2rO{_m*(d2HAwGg+c2;g0q9Jo~$IfSzU*a&qAoa)KrnhXj~Xj!M~4I*A}bDMONp
zBp`B|QW6VELq?<!YfFdW1W7`{>y>b#I#TWv5|<W7<06j;#q4<7GFpKM^toSu?sUKV
z@5lr+=R<#2P#a5J8(M-_NQD_878!YYRG>31-LH|iKky?jj^|bDiT*x?0$#C~Orx2=
z$4Bh`25%MGz;&-YN7u1$w2(~%uKGgPCKr_QGc*-;k;SicVNA-HJNGHG3eB=>B0u;b
zi|L85wk93(_4KME=y*b}zJo1X-+h53yJo4ngI+C(RqT>{K*PPuzE<A6gVG6_bf-GL
zb3$?ToO$-LRL({&tkkCLa>S?v_Nrq1b>pqV7M{Khv*M)BuIFoY?t)awr{g=KR(E*@
zOXeIAg@4dBBj<Mp#K&t+;7H$i*2DX}-OEvIf%RVC-xju`n9khyiq_(a5B09q<X!XZ
z7H4hnNS)WwiM}E^qc2}7G1qUnkgGG6aZiHBluknXzUd)v>=fU-RC|rF*E=5cd*~k{
zh_j?FIy6$J?NtHu?>&Jx4~g0hT60G&MwZh>j`Mdg4+zz3+HfsY^75H|B-i{Nw=V_f
z7YO%$_H~Zt7Z~$B=Hk1WrTF>V<IeIJleczZyaZ*zGWV6OM-CQ`lL{2tM6(!wJWFp4
zX5F^$@SVr0Inn=jtr{Ad2N+_3i-RL_0v|F3z0$(c1hilW11F4vo8B@nF@0FDn!=iM
zkGVvj#sx(Lr3B>!B?YAhMGHp`VUsO<e-D!10K3uxs$hbuP}CC?6M(9kMU}@tzR$lY
zz7f9jANo%ShIiWicX-#H4DW;G5LN^-UJZwWM<BGH;f1zDKn+cUJ%DD+fstiTpfNEu
zHP_d;I6FH$JzihHKtn^sL`O%+NJ~r1O#dCCp{Ay&sV*<Ev9`9jx;#F>!N$hO$^M?9
zqphv6wYj{&!_Cdn)7|>Z$J^WE>+|^m0vsG9B0M}qLR?&AVtlZVf}D)G!PDhcHj~qD
zdi&?!w0ezpv)5%8+&PEcMw9LNQ!dZb)p~n=AtxK%x$(cEA>6P#o3Pn+STa~xEHuN_
ze=E#2f45SS5R8wPnMgU>e{(uL99B9XE99?_Iz;SEwaTOiGOJlIFt$9kJ-8}la8~+n
zxP<*U{O0nx5JdtRzO^b~1!T_q_j+u^sw>!|NTBBHQIL|t>y1Nk!rEH!bd=f**iu~z
zgV;3di|ms}p^#jb@zo3ybMZThSEb|$X8T0=g|z47<oTQwrbdlaT7-CkLOE7=$?Rn7
z@yJUrOLsTh`(5{DKJU1xXP?vTETyMk{jKYaHsEQu+tz$Jdt-Z_W~lS?``4@>P^sI;
zYOnY4G9*EeHaCqRd;8^mop)eyENvk_H;Y;hGsaV1c9)DoF+MII6L;tm``em~va@eB
zkc%Uvr$$;t(C_h48f=Y~!%RrG5;UEe3bQo;?VPdsh+?J@gwqHQLn~Bw-NU5l4jKbL
zCgnD9x)zeY=Dm#7?-;}{fZJi^Q7$=Hf6M|(>xH&m6m$%GpIjl4%+Q*@cPki!J!pm<
zM0eHKqrM+5s2!_wZcXSqBePoDhV+%I9omN6=oMqYT<W6rcL3c07z`X!f^vByv?YtZ
zAWaZ^cMv&2Z_rZSrhQ}}hHD9it%+#y4P#mTVnh}(`fZm%M^FiK7koeKhpE$0>eVL{
zXa=w*GgIUQDUioVu;}#85^CXb?|J0xSUIE5qG_FkJw(nHB9zu>2T{4l#3}y6Q<xU)
z@|!ACsWQoK2#iT3k}*yWdV^l#%=3P4-C&zA4Z&o^V=h7`Iy5P*#?yuzr-s3%C}jN*
zhYgIhyBb{z?_R4|jCT^8W2&0;8xpV84nn1Di=uj>`=3XGOg{L<m&XFbLDVQYzK`Hz
z<N6jlOPuj|(2s~Cct<(yMo4K(uM4RjID~ud=#-=>Lfb0rbmCN8i*_<Yi|XVRed~II
zZ*v>LF9nq5;8!8J(u`1MXdY9iaxptEJf!Pwjk+tU)dv00)yh9IQJS9@^j7w7`Z`|W
z7)1w?hg0LOawtc%$}uMKvNmFtzE;f4@R#LzyJ=xh9lNZ{;cRN~H{#`LaLkk_You)Z
zA<Kk}i#^4ZF{uQx`f0>vDC1L%CRj&v6X$wa_4V)isWjI@?w>gI(jRDl9$SHGQWeST
zdiH<CoZ&QWOI8<b84Q_k47?HEQ)W$8tK_G>sFYYYp^rJrMQP)X)m{zGrlE$#B>V9D
z?S=}w_JzGDbmLli17RBJbjL}Rc~#f07OuUd{ZT==ROhC(lUh77Cpwv~`NZKlXQ}b1
z@jh!_^N4Kw=Sb|RRDAnqv~pmup;B%kshj_4kWa1IYVd1qV3^BQnx4{o6Gtnc0lPV<
zuyb`nk#~f(GTf~jesc)!g7w3NVGZ4XC?_hk6DD%;+}ZpZ*`j<7t0ElPS*^<cLqZN=
z)mb-TabH_()F5^f(o62)mH}t%l6FgMb<UBm*d>p|YG~-T6<|SID#<iRg@vQcn{24b
zc=pEEhX`35a3Cj=$%F0+C%<5Erx9yTk<BEi1Gr?}gSDu{KJ8>-XNDLh>EWEIvR<pb
zfFQK$-{MGNBJCPic^zsYMetW4q03(Shy94mp%VEIAiuyHroyls^yIXP!RgzegA;|A
zXQ|R86r#ok4$Kehj*u6whV5<19C=ey<0vQM-LmrKA9`&V`rUxh(%3cF8G#>B?NNJ{
zR+cX|d8CE0peJ)UrSwG@<(ug*-oDM-gu|_Idm(hd;aj2g>HO!@HX6bH8rOv3ah(&d
z?-=1!L)aoZatWUUS;D)g@$?!eCURWvJF@Q7t^<?wIPv%zW(+s<ECg8+M+irK^`?d5
ztt0999o~c|il+$B+P}J-F*Np`F?Qwph|2Po5w|88#RaY6kNpMC%u)XSzkgI{oQ#aH
z65{xrQnQ_2uj_q=SMv-GIsR!OA3Mk>orb%~nXt1;Lhp`(m!J@2D`k-7LVJeW6Wj)R
zYn>TEaiiLN5lH{e%r`6u%kAUs>Gg?^H$`sFTWs>;?ljFKM0m*~-JcEeU#`RUHFi0d
zAx}@fMb3_)Ee*0&w68H!%C0=9e;)}Hz`f%l!oBIQBi^agHAb);g)mKs8L3Ai=EFnN
z(5<8?MTYn&b3lR~qc!t6w#&&%IH+1HPA}P7>+I}HbQZNYRZBa?Tshb)<u!t*Iz-q!
z)=(^AJw>g<CEQ<mr6dJ$jpt#MA_Oro5dlElmIVPgIRk{*^bSk5<`}d#$Ex?}Wu<z~
z;!YV!?9}T{w~6=J$e%?AO=DU>dg*KsXueKmx;3|kzPfCAn8&b}>u0V~Q;f5luf%!b
zG8t7I?R&`0Ol+}Xzb5^+O?cA=Ma|)pGuJD3K;#?p;Xyd%!@t&5&oC^U(_-bgh;_yi
zr1mn0D8ZIn`hXOe9IXCtpYVC}O#}jB4IWH83`|Hh8vv|Higk#2#KQRD54#_zDm>v|
z3abi&t2i^n2wedK7?Fhy|Nk6D=mF}0BOptlLtr1^G~f~7I}lM22apVqI*=KVBT!OM
zNze$;6EJ)*JFqdZBXDi-7VskoQV3}XJBT!hGspnQE+|+iO{h4i4ybKtW@tm`DCi#O
z2N+lwaTo`f2ACsQQP>#R3pjW<VK_^;3b-qHTzFA<Q}_V*BKRrzZTJ@iZG;Mh3q)o_
zAH)tMWF$!>C!_|X6J!9gI&u{95DGYoGD;B21}ZwLJgN(76Y2vRH<~e84cZ7gBD(y~
z<%0ph(8V~y6vQ;eyu%{Ls=x-t7R5Hiw#2@`0mk9Pal)y?g~cVsHN;K8t;0RTgU1ub
z3&2ajE5K{O2gZlT$HpheXU6Bpm&VT^KqVj~pe5iW5G7C~&?T@Wa3L5Vm?2mrI3&0s
z_#lKN)FfOZJS4m!{2+oPLM2ipsv>G58X}q{+8{b2x+9Jw0VhEw!6%_6VJ8tLktfk6
zF(=t2xgdEV1tmo!#U-UARVV#Jx=ngU`a}jy22Z9=W=!TrHbpK-Zcd&;K1IGu{zO4Y
zAxYst(MJhOsZHrcnMXN9xj_X><wF(npVqZLFg~iwt<T%XTyry-EOVxJS+Yq)BaEcs
zQjkE*J*dVpn2QmtVk3w{GBKDUF^OfJNF*TvhJx5k+EFPuRK&!TML?jnPJz)>VpD1*
z=mxE6>N0^`uPfUY<On70pM5(L88fDHGk!I#Pul1GPv5vB1+#rJ9l7=5v`qyiim+qm
zO6HnrLYU5Bf(m{G5|cuqp?q9{7l~`*)a>26GExX}Q)*5*9#0z~#j`>4bnBid<i~q%
zPcx}Bq3d9Sje~^35kfp`kuw}E_DH3nHN5DU>ho_a(ZJ)$eg94%|LpnLg5DW!YvnmA
z*M=F)i+R0}kmo*Vgd!S1etIe*zp0(b%ZH=Lnl}%I?VI2wEnA_$HcgK;<!Me@Mu|=Q
zrr^q@2XfBI#y~;Q3nJm)u4|uY`khd)xc)enftekM0h+ya(A2%9pNx-QqnRq`7cZ<~
zmwI12)&D9pD-kMPD4bq=c&J&-U?^9!x@YFg{s!rFDq=J#66jVRdp|qG-RkbQ$ot5#
zEVt|n>1|Q%Kdg11eRQxpRCeaTX~I6+onEWjjlIPER~jGA!;M70a3xuycq7yzxJRp{
zxIU2PKu3`K*9p0cKP3YZ$!}9+n?^^+TX=~kS{&OChqQlSx%&2~Hpbl8dAo+OznG_Q
zsnEBJqD2}TNglX0JCPN=YF-<{1mk5K|ABfb8UbtoB_Ksmmeq^ctYAaTmq8~tU@D)U
z^69Ri;4qUg)N!I?WM%KQk(e;>-HV|Tg9Ham8g3tg$#3Z^sb~!O>vucf>n`q_LAz(&
zU9(rv2Hg4_AahzTH$4(JD{%Ga3VE#l%5OPv_H)}eFK_9iXIAv?;##N8=B<8944La`
z259M=%Nv`1Nj3CrGhj%B_{$G?D9?AflHsxwU`~qjmxT{5%~)G0GmcIA*W&EKd*qoc
zn7*=M!zC6E$~_B2QG<!ddTxGxE?b8XXhY1C!&R9g1r*+-`8KINT=L-~CSvhM4D<aL
zz_VBg)%bzFRHl`j6dat?Tj|Kbcu%6VsFl{YNx@qh>WyKwt6x?6{A;n-*Xy0pmw8SW
zUEi~lsRCcO%l<RDT8=v@JSr+O$v6BTZ==a)s6753((Ek%?vr001@L&jO10U)634O6
z$6MXp1qKg!zy8Fb9S~Ie*Vyqc_*(CPt>Ms70+|&kokY%)@>(r9X!!6tjg5{u{c$K~
z(YeEk&B2&l&-qQ`cuTJ#d`20g6tj4(Ajt+#fFF#EY+K31WL!f>L{IM?Qn3z%6hRJm
zjvz+>3MwFi4U(vFGl75lI36IV99%qhz{?mFQ6ZX>eT*z>44>7ad%zTDv>Td~oIU<K
zPtDcvmSD-c_<U79FEssYKb386Ux(%+H?%cet*ysrg|BoFY<fvRV<-8mngU<@z`*If
z1HquL+w(MDx~uFU2(DFTyGKH=?F8d1TA+7g87*ao7G@<JdSm%)(mho!>$|zgv;jrd
z2Ss1rH<`zD04aXld8I;t8{JX-9}<CRU$a!L+4gb2n0X2!Gf8|vf)-<R(ddA<8!S_X
z@QX3GN$Mf57@-2axVz!;EJ!#!9?xx?_B#@P7~WcNeBNd~_j2?Zf$AKN3xDanJIf_b
z596s0D(DC{EILb3?jpqHRvL{C*;qa2WU{8$%jc>$LSN^(077*u-wUvtNk~86S0cA|
zZ^4%o-aFQ5chF-4OMyKNHH=w+PC6;yw00s{*y1S{NmQd|!ZLMajea@EF<}NAA$&G2
zQm8OH-_}dR=AHl)cqkuG7exV*OU$ATKhE{skSA|-!!LRk^W>#|Mg+#QQYKEJUPf@$
zcR+aU>ls+iK${)-vH4ln;yRZ%3wyQy7O#a0#f&42^g-W0s|2K15p{7>(UM~il5z~j
z;S9Nj&O^dWD#4<}WeYF^=(Ho(2IcOv;p2^43Ro1+Lh|<HR`)6kM;>ml1-OSA3o{sD
zU~6Px3UA4D!#+aAwf2Ps@fc?X?E=BcY(^&kECW8C{mvr8!IO_k6zpx7gs-X_{j&5*
z(C$QiG0Yq);yxgxaYGm{Jhd6*p7w4WrQAPj&?Y#Eo*b~TArk?=f=w*FS^Z|QAaif@
zA|P!*PGZDn6YyCgh(z`(umW5+d^Fb^Q$78&H)y;NW7}hWk2_00oG|A?d5MyPV_Q$e
zP=p(s5I>C(un1&gI5OADIJC^k#?poLMJSKb^wsMtSNp#4oi$&+=EE?$*zGR7j6j-Y
zY*h>f&^)L<R4a6r*)K-PACumgK6HneO#3kR>9PUuQVJIs<k5eN2${V?V!(@flA)RO
z&jfl4T5o0ZjLoW+)AKd8lGcXutpCL3+z+J2He8`WI%e*U?<=ZL7_nzQti}HIl5$D@
zZQeYZJ(GBuKro-90PmT~rwkQmQ8|nz`t2dhhBa(hZ}QK*-@y4Dg$+gnlwI!gA@jKp
zyY&#xRlxaUJ~Fxg#dEYr!UApQ`6Q{>n>x?ms{d`7y=bQo1~=&T^CV4CS{u6rEi#A_
zdreb8H-|^brKCj45r2{-Lj`dPk*!l)B#hi>jpPC2HtTgn{u0-6Gd~@&IVfZI5iC{G
zX5Kt2B65Mu7{2ms)z;w9PTD$qxl^4_Rc*V^bu<a&`P1hkT`!)X#>UqAYK+EPfvz$m
z*<hpT?OG3xP0cwkv)M!GG^~@R+b1kH3r-gFl`Q}tc2`O;NOIhn9OZx=I>>N7(}+b&
zR#&hPdvY@H^a#}|QrbnpXzdi9@2TnCJye2)qOHHQExg~zw5DRbp()z7udN~aH#9>j
zyEaHu)37F#r7-2>QJWNgydFy0G<}DjTlX$2Nebzvji};l);?>eWz}Lx%A!aqDQp^Y
zB7BKV0-=(b(^GYN_bk=ii~8)Rr^V``0z;lB%_0teN~Tu&&O4ajXN<OndZtESYwvAk
z<;*C?a=7l*P9QC|p`XxvDNJd>N5XhgP7dL85YFfs9C$M!_Jh~TfkTOQ(xCrwLg!?b
zl2Dh5WUzjm=sw>%J|u9uOvrBDjChUA%`h(+!&>&Cskz=KE=6%)cy~EHou*T^7crl|
z#IjDl-l?xssaNCSx}i64A4d9tbdT|$k*5#f<$R{whw*&>o#j>_@4KF}=N&6=KeCfL
z)I+L-m!K`+X{Z>&6V0aU)QPX(fsnh8Mh-sk=KD-z?w9o!N_EGhc1a+#fsaRnUR$i!
zndo<?Im6-aKDX;OHa%h?t9OvOa}*;}QVD2f(~w{iu$VUq0EK0ducZ6?VszY;KjMr^
z3|PT~#_N(V@*{}-wh3=9#Y{QVD)l+<PdquV^EDp=em+n4jj7Qb?fJic{nE*6_HZ=G
zc6a$b=jY|W*Cm&G*>Sh4bA2&B261=Y({H(X-Ypcwl0wy;8KfnXK0A@CUP37|d_Fcu
zMFW|puAtk{lexyJWdW{dL5+<e4Okt}Hh%OX;@=+PHc6%<cIuW3!X?PRVo@qpE{>*;
zDyd!2m1k+^O)6IO&!N@1gvBf(_|x7aNl#G;21l0NeF?^M<^A65?-SAq6YulAL9nu~
zVZVtxW3TyexG`-4P4AP>RQUX1kv_<E_1XRQCl5__%<?*00^hGs<WqJtofX}oafw5O
z)JfA+JsxzBbWs!=jCGusl2OwhleB2=qD}E>%}~hhs`cMO!=PnjT-=@z6_HF$Y@87i
z>EtgKuGWO~#rrvN6yM<e-GPY3k9!q~Bo9uH`A|eS37bq|l4A32oq5iP9hLz%Um_R?
zpuj-cATz*~BPGB?QYh>1<Pk+7a&WSv&0bVJ-w^+72KMc(yld;mwv<^bn0JT$x>poV
zvaoqv_yk2$gozd(MgUttV$xNL<^1SPKwaI{QB)?wN{y29#%6uCinydXXx=en2K4w4
zW<#xLRyB371-K(AL&Z2_C*AHTcIUohZU?x)TBSKhlBe8N_lR5Y78jepF)OC%iK1*=
z@+jtPq%IWY;DlCfCAbNz_&*6jQKD91I%Ol8K`0RtG;}<X0M~!xIy%@GY;Ww8*LHlj
z&pdhCxhwHaI1N3!8fy3W)N~mtjSs)m&Fw!<dnJ~mgPkD2m#eR%=`+fM#tN$x+oH`_
zkfB|;jsA8lT_`o>ST~lKzb`JTupl>ey7Uv6L8-rY_$+d&sN)KUZr2bsYuwc$wR1K&
z-htRJ9tXyTNMR>lg_9wkKum_vRA^SxKn7jQ?qNbW7A8D+C#ztcQ*x<EiSs_<CnY6Q
zbn$zb8P$GP!cDjReH4(u_qFskzu697K3?{{uB^WveDpXo*uJ?^>Aw&;D<=5DukQ=d
z*yGzf3d1_b>Cfwqo7Cvnn2XQvdnk-o_lt%r9duX;8OU6{)q4qCUDms<d|xuty38zh
zpfUf7(a6<+G3fHuX!o)VB&PxMv154AMIt_0Qw%CtB$}C&WH(G(b>;>EfrNscD?t%8
zrr=8aTO2MKD*QDR0g5V1jiHb3&=)l46FrBkjsCT}hxfAU+4nXju3KwoN_qqqwfH=c
zLC0tg8!H=_u$W@lejk}*bD)(hb{$qjwVXHcONc6eqIbtttcU4PQM(R$z)RJOye+68
z5~QpiV9;K%qpvc)CwKN57PUfmw)%Zry^YXTN0r@Dbzl9-!+~)Sw~gQDb@`O4FuK{c
zTMTohOoLG|cK0q`(KjG$E5H#L&j-Vf8s8*Bk4j(<l42PB&xReuA~G^Ohhc$A+*o2{
zDALs$Jr8~OB?$>G3lVz+=@X0Gu)7&W=b5$Qy3)b{d$vwsKBxP(EHgeem-E5xdYGq7
zSG&Q3H7^`v_qD_8;=`Ld+HP98Us*t-U!CFWP)N>TQ^*aEwcjxTcXetyX#g9^N$a(c
zt1fq8WMp!maL|Rd+P|;HZ7)h@?enD&tKZXgv5Zf`g1KAmahT%uEcML)`(T2?xn4i#
zohcD$0R_SYOX^X2{9ZHO6uwy;$;pwg5j-gBO$~$`AUT5Ld0`|o1e0m_;(PR~?@i2g
zfql81?hl1`?9rtDO)_S5P)vb9FaCYd3dt#BQ>CRrLMqn{GC9lN?)cu!^T4{Hgxy}u
zg#~ZadyFvT+60>Mz@+^LHyvPD<nd9+`6FQ{D5;=ofnQfdq11E|c&dv7q)TG1qDp8X
zBN5QK^zxH_g^!1W-~9Z&u3cTp3#&dKDQ|UeM6*Mgo8Nvs$C;gwtS&-)<@!?hdzB1f
z-7w|*)bo3v9#>6jNL+ShPBXAvm`aBp3B@%Qp6U=;EYY+~uGnQjBoFqK3B)ycrqBpa
z%{R&KPfQ5cCTGJmFK0LWN9ckusPTbHE`R!1gxS|>oANL;C%hWraWvZqxU|^@j|RRk
z60tCIk`aQyUFKZE{O?zEWUqAWT^%#$_`O>(-c;XaS>6j3B2V-R06XSJP=xW3iM5IQ
z7+^zL+YrZfyPJiJpWX6kH3;(Z`e}aSF%P{RU!NvWv%i&xPRh_O7+OBXOz*gJ-SsC-
z28txjfM2rN3}?<BB<)$nNm15Kwe`WjCt#Gd2Z>V=Duo-z)eKl5EjI{;`;DYCt6Z(n
zB~R^+MsNH>E*EY!Z$u*`{@|le2IwNyQYbc)g>_pH+!Gr3vbEIu`}wf=Y=b7!>(4a-
zhnot4XF3sATWAJHq9H~tC>4VRPH6v5SrKG|OTyA_j4y!BC@}sfUnwWEoR+I`gZ!~e
zR=1Zm#jG(16Gk^<K-@*jye{`Ft}SV91~f?=O{9AcHaNj@5s)~52t650WjZ**KO3^?
zZUn<(htdAe3XHZmoQ5=)xdQ%af@_WBGh@ws!I@)ZWTG0&hZ#F4a;_3%S)eMv%D@QA
z&>q+g-{<sZFWt|#!GgjgHuVgh7XrNna!$<vpqmq70>H?J(EZ@BDfo)2sSJ3d;V~6Q
zrjTklpd)tFl8=;T;D+L|lF=v7Qg2d;@o;m{0W+vfpSu{&W_*7gWaHsqdt8bVb{kKP
zUzY8AYtOIu<?Rvh=hCX~O6O6yjf+2|nD}?MfmNN}?irH<(_;3|Gna>)oKA^j7rsWC
zlxgJ-g&D+Z<}`+l7)g97>5xd7OW-;Y<l4-~ABe~3cjKDxQIPL;QHfSx8{IX5kEh1m
zX21H}Th|63z*G80nHR?R)e3TCu3U~XlBo%$0d2f`r}De_OaGIH{c-vi%ETmWGXdUR
z24=RLGYa$E*ohqGZsWF1Tr1oKq_w|Osb`FjF(T4J;(W#U=bef7((zXl!u7xmMQyf2
z?pt|k_LtXfVcNoo!2L2roSncv+_j;%&T-|_h2%#zoEr;>sV9mM=8Dm9(jdZ#mX43G
zuoLZC{M;!jG9hhTL<%4!@Dz5}Ul|bl2A%~Si#_-v(V!hCu|O)O5JQvr&-}snc`S94
z$EsiFx%w{_LR|gEy6QuD{j)!_k>1tZxrU2rDw}+MtFI@L9p}sDx2Be7kI`O7lTSV>
z4y|=w`}4!p=rYUkk{Woq`TIqqCyPe%t3?3sxurgcW>O;3cr!7?jY0i6gQGGnR%H=a
z##OIX0?nRR9XfE1kJ4uR&q1enJ{)8;d<K2i+~Se8X<b)bk5HM9CMHC?NgNzEe|h^F
zq_bGfVu$PF^sg_<CV@_$`4GSPKG@?_lSIRn3H#>;hxazs_rf`ZCIy~E<cJGUX*l6P
z&=?jwG?5D}i($>Hsc0y$;l4_SbNX@;{s*D{8@bIZu^qw#n1kq==;z9aVieFcF$K<G
zCG&jzY(RrP1b^zHkdp;#rg4bmtfA{dNn3b{YnC^WUrbE#_=e(5J_CQbzFK{b((~mg
zEV{fg2)h2isXDjeH3o;~I=lTULm$b(PV=)>d$`g-npNkbi4&qi(r9EOOLQFA0x*3F
zNb@wK7)os{r7+-|h~m{6N@lPOI3Wr?t6MA|7<#_CZCNV<p5@CKd~=N#|BMs|Ef3~o
zH2%_c2l0};0uhc-+}%1Za9rx~q$FyJGZ*l@ZuyplmSKzK!|qeK))Ugapy+t!x*H5x
zSRNK1gnQo0Zs4waWdk^UxM)P&0t>!fY7!c&0EPlsQC%`g8uYdE3d7kDd-HsF3Z`^7
zrxgr;&&OulmVOZ{y5?K~s3&Y3=DP_IWfE;YvTnZpV%x;OXNY|jSA$|!wVDfe@pyC(
z@2OS{-rU*@VxY&3yeu91(C33TZkp?3Jqpxi=>m(UOMrvf!h@tm1<Z5^h2W9ESY}4A
zc^Q^sa7<5xnSwSkO54sJus2H!=}u#-{z{@qu#W-byx<P7mFWf$V)8Vxp`yIo#Lf%#
z9mQw6k@Nhqb0SlYuxzd4R))!(x4vtUoWgqO6=N?jBgmz3OW#Z|C~Qt|f~u+_=W%j7
zhGxB+q2r7CL00g3&Vt&tG13bEFE5OCj5!SsMS<>)Yu@hMX%T!@*ezV?!Qrr1U8pgS
zBuZ|doJG54Df&rs4!HX@v5+u|XqW2b8~%zeEz9MkD#;@Bm?ed=gPxYciIu6SLjY{f
zjt6<XQhC4P@@+H$-#4te`VyTc_9pvXZaF{S-`-0Bjo(@S80fS1ZTmX+Tc5-(-KXeF
z{lt~bU-bmm@3I~6P10%aud>y$5;8H$jQ}??`tzBfnGB{Bgy!7~|NgA=)hyP6u+p72
zq7{-B>nw>8WxH7^N#5y2lJ|Zlq>=`zGwW#4SVXF0UvXY%ikRtvZEb<+Tywo_*X8pw
z_mT+oo1BSMV|Bl|Kei_~ProMb#ox;;2~_NSKKlW3dr2s8!O50_d=LVBOsMRlVp{|9
z8#%Sg#TYVU8WA7)nr`MCRajvZ@FU>0FZdFA4>F?-{JyBnukG*W7<L+bF7CL@V1OA?
z=*H+JWlb+UyM6v1HuIgg%~0k@nu;FXvQ10`?I$2djXF98Wa#EBl1eoRF`KC5fE9X#
zV^+p5*sx0!g?qmKY>iqSu8FbKQEP@xp<&u)z3%(@x$bnQWf)r9miT>3A5MGDK9D8j
zKw}(dMPz4E0oiKzvr?|VelaqqCljLZXzDSd!bGx0^WsOcac(7<x^vDT>Y(sW;7Wzn
zyn-Y}uE@3|3#|_1<Up+{h>;q9pIO-JC-i#RJa;iL<n`YSLTqM~%E7_8`Mi!ygP~Z=
z^kB%Wg(Wv$R`>tbUszdRk$Uzehdf1nfuX?0a>~R7M=C%OC0T*ThvUj+Dl%mio6QpB
zStx1{79q#u0HT3UvV94HhC87*ml=ZH(M#@sDe_({d)i`Sc`X?Ae4eXX(XBSoSyjdJ
z7*()sSFbheFMCRiDhp9294bB0jHhB-PBeDxprdzEWq{&k=vGc#$jBp=KtrvS3yzC0
zE%95DQb=wAHNnL%X(=EUm)A(m;e9$!PN#PZQNPey)on0yo3rQVtD&m*95sor)4Jq#
z^_iX0g0qDy5XCuS9GXN(_1C)}wC?w`1)G3o<ah^LOUfOwXqFF$h1XAt!AuBUl$Au`
zO0+2`SBw<osCBCXN~xg1OlS1mAIUA<+FFF4UGK=!ns?lZn5r~FxG28*#m4Zd;lZ3Z
zr>!Utw*!5<C`=qUY1G!>6+Ic!f&!}CMT@K5WeNpeHAzQ_%T;Cgx1xBf$KJ-=&W(}B
z!HfpO`;plA&1y;eujqRW8RY`?v|IEz+$V4biYz_GnLw_h<+teSYAh3>iKMNAii3@j
zAQfi4$I5q;llW0f60_NS!y`l&vIfP{_S`?%q+FzbVf(B(<6JK2_}?@c<$ocM)|WM`
zy1H+lRL*F7F!JqG+wUmqe-9Z{4c^ex<qe|bfgLnX06ESB1p^Wm<62KonSi{zyIzwu
zfH#hf{DfiGOt7gT$w*IJ&`le7Ov6JJ(r6u(gSHdQ0c`Jj?A`+N(fB)vt~7TDsQi+T
z@Ks(KhbhxjbB^(N9A_5y-CHJOu~!|<-QIU?>o93N((M{^ew682^IE42Stw78Fe@4s
z+rAcS!=rBKe2f-*dI&qWGun)C5ww*AQczNcA>74a7dKusCHGyk;CP?x8JwO&h&VMf
z#O#nFVseW28abc`4<_Pgz4jh)8}SiE7DNvZB5!9Oul8RbljR0iR@OS*R$>zG(qjc*
zTA#Q)4S4fOc!d8}`}sU7p4SqGS@SAeb1=r5=T#KSPxf-<InF^@5`QOw+HLHjjs!}I
ziz$2vnu33Qp-t6@YRFRT@qN!WE6l$<jz#KeNb}s``xmu=6s1?;*Q+JJ(A^Vx_=0n~
zvO(#yg+siOLyaa^f!^)Dyl@7uTeXfm&SFO6?ThouQY^4hS|wZ}M^<jc)CBvmQUNzB
ziNoFslREW04qi)|`kt14l$Wox@jY2?YOm$_gUua`nAeOCQ<$Y2RPH{3llM8_9e$x9
z;h2HZgB*f<K@6djaC_v2GRaIBGg&4@2CP|vXY-5R(jWPJRBUcspI+s?{q05hxIkW{
zdk$ggT)SMf!ew<2c~O;k1{m^Dk&)#Y^HCJaaS2^Zv1bcrD1Cj))JOoUqMnO+(nrGT
zxbI&cp^)VFe6Av_z|y?0<IMHEJDs0r)|TQ-omZt_co_pf)p+0bWtvT2Q+H7@45xSA
zHl}W(qouQ<D)f^bVlnlm&2Y@S=dBONgXoGo!G?=inB;&5kCL9zHsoP0l%#vmx&3k?
zB8{_bxlt|`FG^FR(NG>`^rk*`sl7Nk83TXxHWR-MDY5O&+-Kud`~1vuxW0QosI2z@
zI&{g44s{F<wb4u)?APQhm$OeaTJuwt1WHLG7GI1&>L9>5I!ZJp=KQ>jdi@QyC6!De
z8?d#;<-2z|*UDrcmLK-X`HX%PzuU&9J8>0$OWZL(sDnA(9|{xQUTys>v=+ZUI;!uT
zjtA$9mgI&0U?yCs*aXBQI%~AVG=I<^4~=F9N&O&*Y_HN$R>!{-o~Ksd*K{t;vu4F{
z-t7h-#y3Zv2%#mRZ;B`GL>>)<6#)(suNfZKjw!l830T=nRI*q)|Bd(xW!gbRtlE-+
zNTiMhg-)70Z*tV!ugeuevt(ONug70wH;C4uS8D`1R>mKp5&n(z1<p9iwzetiuAcOG
zZivcPG9eoyVUI-HBVW$plp`dFi0}v=OlN%J0`GETsTh^>k^t2)U<b{V%JG}~&*vA^
zzeh9v)>GGu(Y`IKlQaxJbj{sQ&b<rSr({OZl4=}%ZMtm8Rj5NHaOX&42?#@p>Ha`G
z87zW=d4aGw=0S6H%|(yP?pAF$%#}8PQzvQ@!M=-EZ%Pa=J5=ceRLzC4T-g#Q-alSS
zm#?XW#gvo>#}-9Y)uh@69hin-a(6eO!<q(@J$rUr_v^)KTlNc{UhAkoe;ew+x^c45
zTAyzET)W+B$5Rz9uRVJ@OW)Uh1HG4a+}l~(17g(!RJvtvDI<sR1uaAl7%R2+qvQt~
z;L>%%YJ{LVUGdvDp$%1=f*9PeV|(c7>Cwn;vEc+4R9QUG+6aGtm9!vnc3~$G$>@`H
zVH+%09D!fhX|2xlo*m`6H!7rQ2>4nd6li8}$@fw;Tx(^#9$a7eN?+>sTd#KchLaq3
z&A?viq}@1v5Ya?Mvz(i*Ul6CdHgoaE<&0TTG%7`5m#PSLSV&?~&LIsCU0FnMY%8y4
z^3_#V8oBIVow{q@M_ZW1+v_c0W~_Pg(8wZ~VjPlM$el5pnUb5u|L${*ax;f#wkda9
z3qlq#Pad~1e`Goev`M%dbOK(b2w@)$+bk~scWk^$-XJj!PGmMs-=RE*rhmC`cVm44
zqz?hfjMo}MuBoD|3<NY$xjT_F+u@=0<kdKA$atzEHfh?5jiUVE1(I}d5DT?-&PFH`
zGnf&8I3gC1H;mi~bY~etL$#AYE2YPzMo6^sPm#Q~$d=MZ#v+K-c}_(Xnfb#;;OI7*
z78D_y05sC#h;Q-ZwS00iu*>kEq%Ee#Rtf;R2_PsVrP+j$ZkJhsn_jlw<!d}=1Par=
z&SyTK9w*c7R_AQ&v0-`!v$pi&iC$*1pbCdrX$Z0x;vY&*C0wHLB77S)WJtPX(hZs`
zUZl9AC$SghuO=;p(r36(+z+Cx6y_mPcdB{))TNDGtYvD-ARNK!AFDZ$XrV8M*@wSi
zs4q5`Zme$}c1ZTZK5ns_(ssX&a&xB*##v4+ZEsfLHnGan+sXOVy_5-`o6XVQR@LF{
z-dOlJe|F}Ps>MNbbk#*NrN*+fJuy$;vWv06B;leC$f260Jab{16gP<hX^A8XjE!y{
zku-1KzH*wp<yMM;@IN@<lufyE;Y2p&Vv(+oX2vIhwKGF3R4ayteG72C>0a@>&N-e7
zRvWg*5DN38UpQ@urcX>V`s~PO2YKUDbG%8Tve)R*dtFRh=VRHSr1o<@DhwJ0o3=P`
zK1ZkbaQgTo6}^i7n^*buBq}qUdYhWwt9ie6oleFYewH@@$sY!u&d+OjC=y|-C+|va
z=yPLGgM_0B((Ke@D7L+MLh2?C=%XOFl`EINdO3tN@}J4T!f?*tE6(I${5_0c)?s8K
z{2YqmF^cOD8QTT?3rJj9uwKD_FJcFP>~N5?WGIi^E{}RI0l%s0wgXw$M<`wb2JR`{
zy1A@=SZ%AcEu0z)e8_que8Cw)0Vx^>-hHGaR(C2VY>u!QLIV>Y;j@C*-?Q>=RF#=}
zW)t?xtIDfQ;{$OL1&ty1jA3TIQRBtWNda}wrDbK_T=7Mq$m>idA%6i)(8I`bW1_`8
zYJ&VbFS^fw)z176KGTCE(6`sCl|UDTl0!ZNC)Q(`XO$?`ytEgx#U2HjcTwqh;?@RI
zUi{|qGi>VV*$d_GMw?f@Ir`eX?x)w(MI8*)8INPgxJoU*RfakGO5e(ZBU=;e)!CZb
z`aGUy@Ne6zseP%(Lb&tQxw<TM+@AN9?ljhIr7a~)FORb*jFl~(=gJ@~6pK{>ZRVbb
zI-a{U*(q<+hm*$UR+f92zxr)4_R;%T>T_DSxX2-z$+&cf-#>2{f?I{zx(N&m2-pFL
zmF)xCz}7&<g`whPG;i=2_8WAFNC60pv~aPKBPAQgMsji@kIBnT&4WvB$X{;y3?5#S
zlDYA9XBOEqrdt7roD|EV#+DSwNl_xw9#zDeoT@{Irr}wKuN+k_&M2uzB&$fhqq`#K
zOLrF1TCkl-w@-cB_sj5(t77MlvUUwyo_FY3|9@(ae0OWTF!5jY2-_Rqe#2!kT_IRz
zH0Z1Be2&(7Uq|lp2OG_YFa%Y#{XFITuSSd29y8YUdrUXAB=vn|eeW9_76M;~e|_80
zs74uFK8u>o&MvUBYG~F-&B@kSDz9as$qtrO>n|c%Fik^x5Wlo0=4+dn*woRHq!oJl
zx};a~6d2ei3Im{Bkm>4<(D#K(76wa~|MH4Ms)kmjv9{UMceavSJ1YBLcCDmwp*c$&
zLztU3t*d-Wf7YB~PhR9@=`kx&<?U2advpn<vygE*#VBMytD+vJHDegQV=ZJT_E-k+
zsu{YF{u<=&H5?%>=D0(AhFh2h23HlIVh(pk#ULgA^^h_o<KJALETgtf)IU_ZF&%!J
zEaZcoug<a*bK*=oHy}ee=Rq*m-apm=i?C@hzZ6fb8d0!1^}CNflV(ij2EuwsXV==-
zSBbw3wv&d{LBxg;=3mjD1b+{wCf18GBob1+y47l|y_=YroED=@lBOh<WG?TlNmg#F
zh%AtaLPt4lM)AuKrOoH>DfD$puJI!i-$o>U>!bd*5zn=@z0jy6hPIqvc3JMzEm{g6
z)-)sLZ7zPBNf3Hg6nS-5BiCWlWwE7qA7R}S^2g~zbuCv!ypNPhd`?MwFJYO(db+Jq
zXMQB!0iFmpE1-!a^rkLO$VLdooY`vzSM$)upS+W}PMli?*M{3Pucs|t1}=2oC+V(e
z$>r*QS<IZk+{<2We{H7m-|REs=kZ#*S^nvLwsHfvI_UeyV=2!;eQ3_vEQL<BzGHkS
zD~>|;p;PKN<ezem5!fK|>pSFyL-3R0Lx5f6aX)_Z_Pc`Wa#dc!BLF7DO?~$}Ea#*w
zVat;7gt6&-W+E;jF`e`opvNHWtkMEHhCU<r`_bCFF8}e&m6mSk$jeTAu;?_N;pOwq
zV7twhf`6h#fa|Pyxg9k|Px)@>Zi9etqp=8UjLrl*lfh6|z>wZP3u51axm04@o40Xt
z;#8*DYrpn3X#KfgaS}u+?O_*K#JFDx?kI`HB@QtugW<`pb|Y<Jzj7j9jlbL2T*~v`
z15_(VJM8;{NxOTk4p(K4t*w@lvgV;mnS8hB-#bO2IwynM6X2aYtm>ev(T(*#o)K8n
zBo;CSiGvyhpeL<N?4epsUaPBzmXS{BK>fNvvKA6tg_^+<7VOi=*QTZy4O%hiF{Yl1
ziE(9U6i_}&mJ1W-_tuC>>fDoOcQw%7X4OU)cTGC!^4pCh&dV(pX_qxL^wP>pksoQ+
z7{P^XG16mniq@TWUg)5j%BG+Q`^JD*mKYn-3urNCB2XD>qyr*>l+ui<fJdJ#*9z<P
zM2Lnj*5)*ZFNrXRHv+62*81aed*2n|`aQ!lM;;!Oi{%Gsqs2eS5A%v#1F2?xm`-rx
zJ4@O}z)3>nBqH+jJ*I|yO`)pDxe33bg9vpjnKMbmKsS>K=oW8+S>^sf2Y?o=!qmF9
z*E&%{Ay7UFyxqQlHM(!Pb@`;tss3_Eg~g%c&$-Mg&)sCXl*RYQKZUdchGPaUs{31T
z-H&<@@~2o&7zH#197}DSLJnpt_NKQe^l!<XHP4BDZIGt;tfb^Nz;AF+`{AUE`Q)()
zb01xvZ?*B4^XeE}wg#%&*UK!AzpmCu>&4&uje7U~r?J_ejpT1R8G`ZyRH2Js*b#i(
zB*Q3aNQGa9xeEmrjryiBy!PltDI(QL{=sVp(fElp8J!>Soyy35de%I$`gmVLhYS1T
z_rBvJl|JQ%5(G`ZAf--E7V0-hksh749oTvdz^~6+o->m--m@p)TX9k9f}X1(eEZ?H
zpBL@brGUNGw#D!NXt$*suc5MXqgqHyNjdT4zLj7iRJ-#e>>}1sk*iqsxKf&NI&9sp
z5?Z75BsQ-U7+?0A$DRJp=KS@rHdMKe$ybrb`M7$DSAXGQre`E`us@dK_u}n(c02rD
z)mBr*U*p$psC8+}hOongz9PugU>uth7Xd2DiuHSJ(SBY({DZ?rAcz{G)%$I82k$R^
z&bhq-=iz<x`)0f_D+5@j84+m;NL(W!j%nhr7}{*lWc@kkCag(rnmO&5Lt--;hBI69
zJdp_@ctzq!)i`o7>y{u@&WNLvRxd3Ae8OMKLsz1$hL<`q!Oh|Aj7)nnvZJl_8hXPD
zyZ@|EjO^6yRX#U6@*LDcFkrsIZj3=rC<<w#ML#{$QkZPk<bEa5%^pZOIiWMjB-u3d
z%CRftK*2?eU}3FOwOJP71fpv%cm%{sAz)W_ho}A>04mweP=ukX1EVm0Qnl8blwPfP
z!QRLIv$S|-!9l#nui{jbOT~b7!DhE`K+Ss`7PC2Z<P|OG1AP+ZWK=oJLd?#bdUo^n
z#EtkTb|b#P5#(|Sotd~TL2Q!-GCtXlU*^2up4*n<W7ZwKb@g#ecJscl*In^FvCnFQ
zA7$ft@vHSIv;@zl3UQ22fonBo;i3rzI3_#Y7JTKjjWC6B<I%3xs_t_5>n_X9k!9G-
zP@C<U+_{Oe)Z2F{jFRX2gZh!=(c9Hl4^KbFGpF-ymta|;`}Xo|dP{=8YH4D0<_~*W
zmmJ?Mo_ucwdRygSC&t^quI#JKoQb?&W#<JwVqN@X#MikTHuWOQ|B0#&Z}t4p-p|vK
zJ0%r)0<<yM7hi~cg?D9sqagrq)&vW}2)Hd8Bs_8D)<=wqaLB@xM|GuT#(pGanm>Ik
zG0j8iRb*xCI27AN`@QxHCe#^#^5K<JK2Cpg*S!3C7DU}6lxq;ma}(Bi2K-lv`C+c}
zkR*jiptC-9F2KWH1piP3uCS4LEGgl{$un0<s!>080kRHbRGPhao5n#rcXo!HEBCf;
zMAVx%&1}Uk&O)n%0|)jOJL}<od7$>zu2-(x<1@m}H2!H*=_20gl!_*WXX-x2Eu{ds
zKdni3bdC^H@^c&F#>p$19A5YtWMYfECkYP|BEM2wrG*8jBP>cz<KvBtOrsf3Uw(q*
zwZGoyh1uh3h=$j-b<OwEoprf%BhdTT(#VloReD4(qCEbxv)>4Po7MYS*Aw@W$M6$L
zg>^yhnSel%e{HVQ*vauY+{LjFuR4n9f))`3#dMJ6^0}B0y4C=+U9aq9jZ#xAZ-kCK
zW^IVsfuh~OuPygH2klLNIj-87QyPM5pU2Z3zrLf}o9x%=$#sGm7AU6#R#vh@v67Tg
zEZ*DOoUQ!_r-?IZ8Duzo1gA*zW&zs-VKnY9!PuOkUrt$usYR==<i_Ek(lFxj=RQJr
zvVSnnpAf#R-y_Q;+%HtCqw%}0C8jab1)gPBH<BL_9KdJtum84LMQ{1l>9~^jCNw&G
z%bj<`!o}LVUv$02rl}~#rS5`Z$pc|QTi&JOw6EgzI=&||fVbXXKezY4f9^iDeLaN+
z3owj4v=e+nFYtj}3S%7-W)_?yR>7f}f&!m%-4-SguQ`j}l2;=%CAl2SO>oVUX&D>G
zs8b~*?oHpuG>#vKK(dJA4mVNp?~L%e%`ad&#x8W5KClcEr@M+<g}r``i}C(80Dm#+
z+=gTt$PDNWUgJ9r0R7-<2_pY<0WP7*pRMtEH!w-*XoHWxePeF^G>0SagGg_GNm;F|
z9>2A+;=+5KSMNN<kuLRS8_%cA89r@lrG2IYD^+i|_L4+0M77CRc#)P=|Hb~k<(q=b
zdXUFc#hxmsc%@H{)$GsPS(LK(#wC2no8!>gG=HHOFy=*IdXt$Xz~|ec;M8Sea*&%O
zHWMTMuGtP%+MT#VAo=!O<ZE;o6@wG<{(y5gP_)3$NV?(w(oKLkyurMAUWf1?elf7`
zs@iy0M9m^$1hR(k@0Su!PT6|x>DZ;Kh5cVYQS2r8OBHK_iq_{`h(h495M0lu4VfJ-
zQ2_)`mSm~T^QEi6r@pOu{@f>j$$LHO*i7F;!1#Fk;4wzkcyWIG9s7XBNTG`=3N{`J
zj<q9Ct@>o@R8StRok`KQv4|Z;Q&8+?41M~hZM-XY;uf(~(3UyKO+=Vy=-}$q1BTBj
zmE6OhwFxPSy#i!M0h|#14|d>pvH3y+$E1A$Y-#w{yF~#X*Q?%f^i6`!N5z}gUurt#
z<+XG6Bg@_DwOalAldjboMryq^eqbBOL!6#(A)jIhDQfAVz`x8&RhDC`9<n)}@CEKx
z&a-OR{E%*uO0(i2pUwl4D=w~<T&Q+Bdas&s+6km^mgL`8H+Jy;Y_II}G0NSu=_ryv
z)0J=ChwE~XIrhYjd-3%Kp*V(+3R>kph{I!NMl5lnX-^T&%_ME@k=Jqd_;7T4KijpI
z^y>Xy>(x2$uCnJyA63)Gc$fN1aVx-%{N9_SQXh{-iv2%gN8Z@KXZ`D&=IOXp=RN+c
zCMASqqa}mkMjJjsXZc|_U%=CE@1bo)%piA%_gb9{#6Gjf@{*dIL9Vym!RUQzpF^in
z^hB_o*Tu9Jn;f(-QYPb$t~q1f1pd)34{JNiH)AU6j=sX~YQ^PL<mVgQ?Q-x$@+EYs
zYN7ouTuTs(BPj5VQk%b(XnhIEKs7}48@{u+hctMYD4PuLi-?6vn5Z`|+w`;lkUSDC
zMT;jRhm}!*+ut@1rvL1?Vb6&kotE@S?QkG%qErrh-PW7K3@GW5^-&M;3B;|rm~;pS
zoPqEh88bV^v^miAl(XBEd2~WbYMWD^2Lr2PfMEzPj?t1>W2_ULX!3!QblT)uovmVg
zxjBU7?!>nS(C`QMi)LVoVfxJf`ADa~mO-Xq6?QBBS|(y>nlJOrU`b(FVX5DIh}zH|
zs+;JFlqe~82VI(@Tgl6-O@tyUXCc$p@r_rxOhj6my?gPKQwzOO9s`#Mp~+Gk|1|$4
zVUv{$Jjj;0Q=db6Icmf>R@Hj27(7nNiEH=`PNmv;W6aK=(671Wi)YAI$4ydc+tTMd
z*mfpZ4(|669vEC|$Z1;>o`KuNj+BVCf<nr*I-B!K4nsf5#pX&~n1!F3P{#k$+<8DX
zm2LlDMS7DbRoWl|BM?A(XDk$@sr24^?}1Pzbm@rn0HPox0YVGCBOua2IwXWpqyz{^
z=;VhwGw=Dv|L_0S`v2crv-WrG{W<&Q-jkervd_I)D~X$2)|Q4RXu2zuf%Ju0TP33x
zK3o=dms}rg_Ia9?*(+eXrAR?$pV)O<0{PfiCd|$q&`-!gbV>z`PI_Y7n|hx_s=&UP
zcjvxskz@>H__kDk#I(*HpX~rNS4V0U0b1Qr6k!m!omXMs-v8l4y>+gUMK0^<$@{xy
zCCVzq8V_tMM$Fx%9^*L-gMS6W@6txgMm*bPuOXvMeZ5`hAGl5t)LiP<@fqvS^oXdZ
zd2A|6j2HT_`MH?1)4HTOH<|ngri|SX-$bSex%kA<(Hfk=JRWD@F>EI@eIo!SLH>57
z!d4C{k#tW%g{SgYj*ea}%knU7iH9~-LZIMB8sLuHp}ss(k9K0KDEw-;dBKqQrz*w9
z)&%<%{RENd_roC}Iq#*`hC$?NdqW2{P6xCLPdY*1><04Q(yQDiA+2mxKbs-*lz``}
zXQ>q4%17_YZN<aNq)3+|7M)2+tx*;H!sMX~S1$vt(2E6EdzolLZQabd--SKcy5rug
za=3ABE24r0yfDdmE~5Jc8vGNizWKF?UmSS?E3b@jt++U+r*10y?vzv#`|6{g$dyVs
z63|T^;*D~HTwRK3ZsU%|OWrX%Bh`Hm%NPVh=8?`y$*nWcI31<VrjNxNHhD+ThuXO7
z#2a9QYUserfarco?{==br4TpQf*mD~bNt@iBceA-+s&dcwc@gN7}P{g#5TRwbaXXc
zDGIPj36%3QYJ1)5gv=5G`qjBduL98B>s_qO@E{>N!b~bdH*UZ5Hi~<)omET6b>I^q
zA=T;uyWL`nf^74xGupcf%nk~O#}q?JPC5qtJ57voyd#jo<WqZ(8|aLUm=SCF_45|1
ze#@4WT)5C7u^r&%)#qh^5)Wtxdy#KSZ*^7hxomUKP<fKtHr9%ESfRdM^D|DK%FKNM
zC*NNqFdo(FVpNu^2lC(s?K#23grA`_nXz~GDJyFVTFB>o%Lgnz<qng)a7}n-k>hat
z*Vde+UCR3fAJ_IcH6eiR7rB=3tcW`ACoBp5;984ysUz>pFS1qFD<<a|9fWcU*k=x(
z8<^$(BRneZ#rdP6p8^y+B-^>LFxS#)S%-O(RWt4S-q=$@uTfX5Mbz;EH2#RW>H|CP
z^s%R`h#cTlp0fzW=eF6pJrYx9+pSYunP5y|`q&4#`Fs%-<=2KxB3Imkagkr+8^&MG
zM~r)Z{eB-{_MtP&WM&(7&_Xz4<emEsse!xFS7Um(m1xb4UNvOh@hmUxs<BLV>{TCC
zy2Q!D`Rm~2K$X(xwV9WjoZh~{#hco@zD&1`mA<u3iQ}-F_Zjq%gatE_9HR3-uWuY9
z@H*pASPa|{V+WsU$NzS=X~ump>DYLFgmhAXVdfta9AIQw8J&1CEF-1p!Hu7hg?NE8
zzqM9XyvgzQXh`}wUKdn|TWyP&@iu1wffFMn_rNE<xT_k||CoJl9>cVvzc87<iiS7C
zc?b?r+R;0}(h4tbp^mlTJ?u=ZObE4XobnFi;KD=a?KyHtr+)QSt$v}ND?`WoPfv|*
z&UMQG8PeUE^xWwt-B~5vuWzHnayr35JqjP$B5;EDee=fGcT4+f1&|Ga8~=Y{UU(ib
zHqSBU*yk-dGoRpF`+6Q@?<ey+vL@`Ax+Ic-9!zm|u+iY6%az0BZf??8{GLM040Sfm
z8_h-}M$8=$F>4Y{ct~uun{D$G%F_tX7-hE=VGSUa+Z>qg!PGXD)3qE0V)@p+pLZ1T
zAjZUnJCODiV8E)y!Uo)ou*Ig2Nj8doQTgyraA@A*Dv(WZL9r>kkh6YMrEEc`DRL4^
zgm`C!Yl!#T5fDHLmo4y?p`=|GxLwgfCAJ+1q3gU<aqEvk;pp;Z2^6n~pgS3}1q>OU
zTq4;lLc&jwM-s$FC@$1t%UH{EFRzt24b0&?b#7fZsI4Z{Md8JNC9hbnWe*1SDJcVL
z680Tj;c+*UtSJiJC_dw7qrm2owd%t|pl5*@%$kI)<7u71tBI(-<EBv9Rta%#8xvjz
z8uYZWFz8YBSOP{21oxm?J=+dFhoOytL{>d{-)4xEoWi@r9jnu;3SC3kN@PQ*9`=T5
z4>Rz(+gb?o_JWCVhak&Q`k_w7tY(DMNeZ~{{D9d2W)EAuQHCaRU97zs7==VrPwltL
zHSx^U;DT6$WT+rQeB7a9cNes${5n{&g?hOAUy)YXcl2ipd2#ncNJZ>^$Lb)9^EvVo
zw5bu!LE?U15QhewtDRP)zoM<`nA)91^7deXzoY}3Mr6i#S`j?2D~x08q1^}Tx?Nc^
z=kO-(`vP>?$C|`4DHg%HL8kA%y!>@9wG$hR-j0qeSmfMK_oTpA_2dQ4A&_!5*pG^#
zquKfCR*Gldi<5#mo@M#l2M?<}Mx7y|%sd0#e`059=?Cl;(|4-+AF!43dON(Pof|*`
zw?#nHx3kQUu&#PqjDvyA(3SYJZX6v($GFpHu1)=vzO{23OT2j_lMQj*WftdHwncsL
zx`@CJWraTx+QGWFY+kfu#?S0qPe#t&6^{9LNI~igV~6~@lfRu=xN3V)IY{`PGcry?
z4HY-3_wr*x{+N(AW-SL%p(Ji7CRw|as6(3IxV(j;Lw_MSve}^(G!}gD=4?y-q*NM5
zEI95q|L<?y#)RUi+2FGl47IN#IEaODC?7}CW6>u9@9E4?(1M~7*HD==A(5Pc+>ju<
zE7Xw{A>486uiKO|=!QPtbY9;x3Pj*rqNA`RY@JN9&ngZ{-(*-M5av?qZQ41qN1vJ<
zA`caS1`s|V9Sr5u1eU<kBmP=D+79CgsOtn+-2rk7h5>Ex&%Z)HUFORqD03q=22eMM
zvwg&yf5Hn1b*wx34<BP*!l$)?S2CyqWlO+Cj;$20@6eT1=z#p(@94~iwNio9{aZ3T
z1=OS%-W+CmDyd0vzJbta1>5jS21xHHeyMA}<@QvYP2lMQ#Y!%}>DVt{jKk-Q%fFo;
zm4|D|%|fUqU%hpgD@iEA*sr>q4iPHv9}i}O-FiQ1)<8yrW$JQwtwoo6?LVZ9yx@6#
zTT@9ZH||XbP`4F0I1(Jqj_Nri4UWk}+q4D~!j8L~g0T(u63(s9YInpD?TvNeW{mNI
zSU%sDva>tx)A(TYZ@Sfb9+Z<F1QJ-I))Us?L<RH7i<|mf``HA1OY`}920oJdG=QC0
z3HX~_M+HD3W>1T8{Qg!^)Z77eCPCC(HdVa@tEZdv62kj^^I8%}i?hF31ne`%th-OS
zfsfn|>11&NlG$G<Tnd-i#v9yu>Q=WW?kE_}gmA~Z)ZxYFLYOUDbmS5pS__6ePUTu0
zPe>u{2C~lpvJpl5A?hGg(A}j;*3K`L!Qj@{1qUeJkBa0;_<{Bso)~>@OlR$+Yl@lN
zkw&y-1IgXctWB}1hdOmVV%nE(Y&FFTHS+#h4ufNnsDncS-}v4%^ri!{DrgpqoMdyC
zriz<NoJtHm1A$Y|qW_=l=PB6nx2LT?ojCR1FgMTnrG{S5lx5rU)1TeRs4WOMArnPk
zlRhaM+>-<5IEw{RNQKR}q60V2U#o{*sMktlPOw<st{O@8`lEGmuXf5?ZY}jUS!!aL
zj)n>nrFq#CL+Tr=++pfUS#hFF4#K0>1-&XM$=sX+<^|G&|Ew;$0$&Faaou7ThMe3}
zKD~W9;VpTx0*#^FqM9x0WNlqUd|Au#<-p_DkE=mv7xH%3t}u*OGrCZ|dKBHv>$cTS
zRtsrJ>UbJM*v6W9@gj|E@z5Pi`e%P8vv%|$C~!O((#CXV2iJ70t=2p$U8l&Q(oi6G
zo)wN7uR8UX)P!Q(9PkfM;(Yn<h2FN!umaop$&<+PAgQsOr;G~inpiJm@1^v!<9|*K
zRe@S86{3sl^j@_f7CJKq9;e8p5-B)Fy0M^QaDU7YzK!i2(COvc2Q55<HRx6tzO952
zotVGs*6DvBR8A47L3b>r^J!H(KTj(n*i>=(Na&oJV6_8&93j+m&Zghvdx&HRO^zWy
zuZ@)pycXAVEti#->NC4zqB~0s3q9T*lUFRIed4drz*>swF#9M^5?JDUv%Hv&MTbw)
z@B|KM-D?eL6LdcMU}_e{pAhMzF}{UvGrf<Y(S?VmvmB{%yjJGei`WU@ee}M;Qg8>D
zfuSaV&Y(TuJ!r?oo^iHBs2rOfNS1BoqHxyu`nX83tvrNp|8o@jOuvnSaW|cDmzZ%6
z<=ndyeUr<<Zn9>n^Uz9}{GfZF$VR3uTjxBK>sP4D{tgUAY!$QVTy(2{1cs>FyX+U2
z3Aw~ZTO<I#@HClwd{ZYn3wJOV1-s}0P-ba*H*oT=^4xc=52!V6HY!5^^tRLZeEZ-H
z%ZBrwrLOh5wZ>4RvT?v9N;ku29CqOGQ|y;zqvn)$rbtrSW)R|;Cv7vQ5hb#z2NrZ>
z@8;CkfoOo}<uL|_)pn?{MF^GN`zWvrq;s;9Mc$;5f979i1Y{OSyuJ7j0W?|JAD0oq
zXELX3A#LkNwHSU^nb_GuSdH4OuuESxpr?jq*3((0b;ee2w#o4#UToTfVov`=0Ia5I
zX4kRr2jIe8JFtYquL>tA`fz2YOhTGgSTGPYq-qk<yz)73EW~tTuQE4%zo}%^Mow_U
zUs-<p{VMBG6K9^^(q2L!B6&z=_g|CM<Z!caqzOq+CeZ}euSRw|1PYxC{a*LpZ&;Wc
z;iVw8%hZd;HOpkCT~0t&w*~bkt{Io6Fh38{bPvKmRs(seC1I*SlYu2lzY2-DE`&P+
zsMBOqeY+k(;gk&m(`ZDAwPm9a2X`@=sF1NX+q&SE($!gnRuk)SAiX}(c^9rVoW#LA
zd`w1@C=Fa8VfmfNtV^yifjH@nczPy!D||eib674~P`PeIbXS5nPKkeo?_aIPHzxj}
zGbM}BS@%S9g^!!_JhRckp9q!iE>5T@wkla?yf}ym+kaw4*zTsDi_X3mSKO_?fff8M
z%=&L(OMeTa{aYAhU#!Ex>;`1Nu>JcWW!41Ia*!<=1rFAdt$t)4db`lfIV1DxJJ%S{
z0$u&i6|I84cP9ZYu<u;%fxt?W-<?&Rz9A5U7oss^577nrJ=YvtZw@2JHT=cJ;u`FK
z;H+V)KX6aGDzz*4b@)~3`u+=;lU*2N^VZU!SU-(7?eB=w*uuL#=|rq%H@pKKhOwq^
z(W+qywG_X%@9;^th<>UY^_30t`!{`g^mn`dpfNd`;N;K)uSOfrcmObUX}-p)GPwu;
zkuJJD95sIu?|Pw;y^~jlW3O&>{h|4PlMP)_pQC!ZP8e*;vn!3S#u&a_4A66_x9t-%
zDx3D4Pur+5u3e4<NI2J9^i8%{A4;DZ!2o@h2K0TE`e|L2{Ao57{INC${Qr__p+PbG
z#L*?|Jus~ZQe659H2&X%q7j{oPPNX6?vLQQi+Z)#$i#ymf=RW{$b_O1!w-RSMEt?*
zHJAH82E|!g7p-dbAHqhlpU-$>!@7ydep#7Ut5fPY{KLATjZbN1gRX%Iw4^LRZ!+0?
zAmt+JHA6R=wxnT=gBxXg4S%B8)5#leo#H>jmDUY8eM*xXCR9wE3cgFTe(S<W$+<7u
zMQRYhS!46ACT=Q?W>(npTau~bYcfEo#O{A_2?hV#*8jV!DTV)4=j(1{=aJMMrcne3
z7&T(ukO=HgaotZBEM!A_+=(<79ptP|xWC=R`K~@#0D%7Tk-#=OENMH;TuK&iWVT-l
zJN8_QGR`G0@m%XG6ijb1KK>o}k1Id*y%LQ5Pars>VjN4&a-`B|NJ?>f*3pI(q&O|@
zX+s8l4SHDwBEII`RQ*$6CK(|sV%yEOgG>UX9n~qOvkfUv@o(YF9_a*C-sWjk{i9$5
zX(3f&+qE`+){MgMleqyz=Mz>7{&ov)a*=_Q1I-*}_ggOD=*F(tlGFRi;W_3?ach_N
zGf8j`r~4C;C6osL=R|(Jgy@0N3gZ17bFYo{R{WCi^F9>;&ic6D!6451&hsu6`u_;L
z+%AsL-<(`8A%4)kf|#0P&IVa|xjApe4>VW&7n1DF*N;YIlJlBQ4tu|Z>ql<`cy>c}
z6PX7t0cYU4&92PK*VvxcEGjUmEc~RjVEOW9#if|z+nAVFDjVj<KksPJXy0nXx;_MW
zH_2=ZsPB=E-ZEjksgcQ}!t_&6)(eNHOB}*7waPJ-sqZ2bKS?&qC?EqxWMnS^^-r!i
zK0XgJ11&Axn0tG+xFj8@ebU+MRBnLu+@-A3VwXPRo`Z&LEcw)#;759E7tqJs8ZG1p
zlM^|C)bd6flKxhJ`n|%G?ask&ByFKDS{!@`;WVr#qIOh}s;4?|YL)V*q;d+8J|dF+
z?En&Zoo#|g!!$~BT|x4Q9)+Lo^+P>56XZdLt;`f@AoTG*7$+WNl$&@<n7~pJ`0`ct
z(dYuE2QPma*2wlH%NTpq!8mBJL3y^_DX=+mz1_;_^4l@p0=GOG%p=@fK?+;%9S0lQ
z`@h_i(RzOUIbCn0?5sVYGF}B{Tsk9azsff;IS5l6xy3S}AS`|#r)eG~FLEy0J3DHX
z;t{_lYJx3sC{p&X&L@z+`gzVbH$U5s$VXndN`=&Uwad5{Ti-5G*V<T{Xlnc5(QNU1
z9c$}cu?ilmWQXJhQPuE!UIOJh5AFojrw|2mz2G*PbK4T98H&H1ySdqKTV?w$X23Y!
zZ}h3b_U9(w9%|{?0*Y`Ut|l5U`r+CxYK-;+vap{y{eS7PdL?ed#TbnhjaShJjLvH8
z#Bg<7?tQLXqYZjMsOUE>(2?(zNUW8~T~~V&a?KQgV!N)MM#l#l&{2SzKH|a8d+q2t
zEIf+Z^!!xJ*_LYIm$-)aTLxa%&|A<Qup-wH`A9u|NRr6FCJxhg>7bV_Jd-j|TLaxY
zU;UgjG9yGb;5%XJNgP*)@YQT{!fBVkS&F_ht#03A%9`!fpCw{wK;5UN`(z60?@M@>
z>i%c~1o{3*U~c-8q1pT8Ga-igMOlv07jS-N-_zzLnU}`%k#WP_w;{-A+@tQz(#$)-
zUD8%Hyci0MF8?4f>4s^1ePy~Ae<^vu%1#XKx>D5=`h}Q4-4=}>jVF7zF}Q{qE#8ro
zl<jn8aP>Hc-X_Fhoz**s9_y7NExKUAp5$B<W-_Z2=HY*>gXZ(I&ZJ2|77szdaWyX5
z&S|!YmAOi@G0GWLVVvu8M1S}ZKr*!UZe3JTVQp8hOoV);rsQd6zOnZkn<ms$O|AE%
z4X#O@+7$?2Wmot?TG(rpXKqzXGdgB6vRTloyCkR1am6(z?Tp#eSMI}Xz@Ai}%ivp8
zmZ@z!Z(L}jkTokGb#yjok3lRWu1@d1WkE&s%ra{p6D823a^@A@%2saaT%Kc=K940u
z_N86k$=PmDN${czvQSoSwQO4~aV&0CeH_IZ)rLz#IjUt$KcBZrOgp%bjn|dsqwqVZ
zwB(cs;_VxC^Y7cdWr*p~+3sV0ydUx|xF}r7xENxo$-q+a@%>v%x-4rO&(uKec^i?~
zX+!>D`q;pIOQH=wwO!AI7P-p=xrCEL@R-?kW3LCXmhZBb1A7k-rU&VvA}ca3R)S9M
ztb;L7zx~FR6^1oO`ki1w@xv)z=Z4n<rA3lMMUu}}GM0&o43h}ndfd_iFZ%G4g$Z{`
z=Fz^5tJGgm%krXHG#177OTCZczBwOVO>P)3;ACNqKt~im<1yf^R^l?%D10>+=@my7
zdQJKMl{6)hemW&9u220-^owc_IjcN66${Y!mSNU6MbVN2<Am}Fl~)NP-W(+M|11T`
z>I)eCw0s#`Sev90D?H~+_=zwZI84^`5?o{o{HQ>RtrlafJ{{h;I>m0@xXm7rKY8FV
z-jx;-IV6WK2ZxfpZit?YsJ#8;7l&TjhhgSDYoZn((<TBPg;K9O95(-UH+fZMyO#Gu
z9IZ3cs=-aBtYwMM9i~O#U?dWEy4ALq=VR)9S`HSJ4#UxKII#ld1ZAD{$I2LvbAD<Q
zxa)%QaJm+NNilW$wTCAR9Xxz{!yfs@CpX4hV{Rk#%0tz|XqH<YBx1=-?KVnp*?0wC
z#Kparte}A>%H1;I&HR|4Tr@~neyVJ!$rGBQ_0-J1F}Y_#{i!$us@$8DPSugc(`c&B
z&}&8~MJrL@hR`+3!byI^#)*jNWtHxag44ho@q`I`diOkt(t`}ORT2AzvDy*8Qh)Ze
zYSvI;N1$CH*$>I^mkvOW#eg#|Bg)Q{mo9Z<kxpjg#mxtZb@)fqLNTZ<MqM?&C18J(
zDf`+@)PoZ8XL6j~mn-y~N$q5CssB*MFZKByB3m@$T2DVDqeEl`)qYX?NvY(eq~bfi
z;*7;dKi|H4yD;^MuGF&5<0o9HI!|=O#>XFP0pR0L^yK!%h97gri;ZZD48Vt<zS6z-
z{FYN`Jgp0S%#@hbXVief#AnQ;m@VK@N?AJM_!BhmT$m!s;pJT-X3O6!Dk&s7R9?r4
zck83$B-$UOT&chrxx~l%0<B5`;?v773BWJoO$}`tVJ*|nMf+tyOzb?>5vnl{C$;%z
zBO${%ru0n%V3x9W&acPhpb0RyolMLo?U*%G<G)_P^9Hp2n$q^Y*qC$G(ln=YLLd#{
ztCcLf7C(A@>-?@X!$nAQnvrcps{G+nlU;(8>0vjYAjEx?$<D;*gTrKrSFa7j6%X)D
z@S(Y_u98xqsGtPxBg(Ul>Trm==yUyR149W*k(}+H?QfkbbMf#Lo)Sg2xmbMt1=%Z?
hNG@H$<1Z0h`t~mhm-^*i4SkI^f}k&Pusf&We*kf!+_L}x
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..979d0a5018299955cebb5378c941f72c907769cb
GIT binary patch
literal 26440
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..15605473f77ac5371ca3757e2a13a0e7c1427e5c
GIT binary patch
literal 26228
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b328c7b44144c68fa6d908cdb0b663b1b02d757e
GIT binary patch
literal 2446
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
M07*qoM6N<$f~=CL8~^|S
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..983d8a654a4d06dd66e8dee5888aa93f1235dd25
GIT binary patch
literal 5566
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
HxBU7)7Ok&t
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..dba656bd09b8129980ba9bf9f238f23c7e59c345
GIT binary patch
literal 10764
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
new file mode 100644
--- /dev/null
+++ b/browser/base/content/aboutaccounts/main.css
@@ -0,0 +1,388 @@
+/* Border box all the things by default */
+*, *:before, *:after {
+  -moz-box-sizing: border-box;
+  -webkit-box-sizing: border-box;
+  box-sizing: border-box;
+}
+
+html {
+  background-color: #f1f1f1;
+  background-image: -webkit-linear-gradient(-90deg, #fefefe 0%, #f1f1f1 100%);
+  background-image:    -moz-linear-gradient(-90deg, #fefefe 0%, #f1f1f1 100%);
+  background-image:     -ms-linear-gradient(-90deg, #fefefe 0%, #f1f1f1 100%);
+  background-image:      -o-linear-gradient(-90deg, #fefefe 0%, #f1f1f1 100%);
+  background-image:         linear-gradient(-180deg, #fefefe 0%, #f1f1f1 100%);
+  background-repeat: no-repeat;
+}
+
+body {
+  color: #424F59;
+  font-family: "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-size: 14px;
+}
+
+a {
+  color: #0096DD;
+  cursor: pointer; /* Use the correct cursor for anchors without an href */
+}
+
+noscript {
+  color: #f00;
+  display: block;
+  margin: 160px 0 0 0;
+}
+
+#stage {
+  background: #fff url(images/fox.png) no-repeat left 50% bottom 30px;
+  background-size: 30px 31px;
+  -webkit-border-radius: 10px;
+  border-radius: 10px;
+  -webkit-box-shadow: 0px 1px 3px 0px rgba(0,0,0,0.25);;
+  box-shadow: 0px 1px 3px 0px rgba(0,0,0,0.25);
+  margin: 60px auto 0;
+  min-height: 550px;
+  opacity: 0;
+  /* padding-bottom = 30px (below icon) + 31px (firefox icon) + 40px (above icon) */
+  padding: 50px 25px 101px 25px;
+  text-align: center;
+  width: 320px;
+}
+
+header h1, header h2 {
+  font-family: "Fira Sans", Helvetica, Arial, sans-serif;
+  line-height: 1em;
+  margin: 0;
+}
+
+header h1 {
+  font-size: 24px;
+  font-weight: normal;
+  margin-bottom: 21px;
+}
+
+header h2 {
+  font-size: 20px;
+  font-weight: 200;
+  margin-bottom: 40px;
+}
+
+section p {
+  font-size: 18px;
+}
+
+.error {
+  background: #D63920;
+  color: #fff;
+  margin-bottom: 5px;
+  padding: 5px;
+}
+
+.error:empty {
+  display: none;
+}
+
+.graphic {
+  background-repeat: none;
+  background-size: 150px 130px;
+  height: 130px;
+  margin: 0 auto;
+  overflow: hidden;
+  text-indent: 100%;
+  white-space: nowrap;
+  width: 150px;
+}
+
+.graphic-checkbox {
+  background-image: url(../images/graphic_checkbox.png);
+}
+
+.graphic-mail {
+  background-image: url(../images/graphic_mail.png);
+}
+
+.input-row {
+  margin-bottom: 15px;
+  width: 100%;
+}
+
+.input-row input, .input-row select {
+  border: 1px solid #C0C9D0;
+  -webkit-border-radius: 5px;
+  border-radius: 5px;
+  color: #454f59;
+  font-size: 18px;
+  height: 45px;
+  padding: 0 20px;
+  width: 100%;
+}
+
+.input-row input::-webkit-input-placeholder {
+   color: #C0C9D0;
+}
+
+.input-row input:-moz-placeholder { /* Firefox 18- */
+   color: #C0C9D0;
+}
+
+.input-row input::-moz-placeholder {  /* Firefox 19+ */
+   color: #C0C9D0;
+}
+
+.input-row input:-ms-input-placeholder {
+   color: #C0C9D0;
+}
+
+.input-row select {
+  -webkit-appearance: none;
+     -moz-appearance: none;
+    -moz-user-select: none;
+          appearance: none;
+  background: transparent url(../images/ddarrow_inactive.png) no-repeat right 14px top 50%;
+  background-size: 10px 17px;
+  color: #C0C9D0;
+  height: auto;
+  outline: none;
+  padding: 8px 20px;
+
+  /* Some hackery for Firefox since moz-appearance: none doesn't remove the arrow */
+  text-indent: 0.01px;
+  text-overflow: "";
+
+  width: 100%;
+}
+
+.input-row input:focus, .input-row select:focus {
+  border-color: #6f7a85;
+}
+
+.input-row select:focus {
+  background-image: url(../images/ddarrow_active.png);
+  color: #454f59;
+}
+
+.input-row:last-child {
+  margin-bottom: 0;
+}
+
+.input-row .input-help {
+  margin-top: 10px;
+  color: #C0C9D0;
+}
+
+.button-row {
+  margin-bottom: 20px;
+  margin-top: 45px;
+}
+
+.button-row button, .button-row a.button {
+  background: #E66000;
+  border: none;
+  -webkit-border-radius: 5px;
+  border-radius: 5px;
+  color: #fff;
+  font-size: 24px;
+  padding: 15px 0;
+  width: 100%;
+}
+
+.button-row a.button {
+  display: inline-block;
+  text-decoration: none;
+}
+
+.button-row button:active,
+.button-row button:hover,
+.button-row button:focus {
+  background: #FF9500;
+}
+
+.button-row button:disabled {
+  background: #C0C9D0;
+}
+
+/* Custom rows */
+.input-row-month-day select {
+  width: 45%;
+}
+
+.description {
+  font-size: 18px;
+}
+
+.links {
+  margin-bottom: 20px;
+  overflow: auto;
+}
+
+.links a {
+  text-decoration: none;
+}
+
+.links a:hover {
+  text-decoration: underline;
+}
+
+.links .left {
+  float: left;
+}
+
+.links .right {
+  float: right;
+}
+
+.privacy-links, .privacy-links a {
+  color: #C0C9D0;
+  margin-top: 35px;
+}
+
+.privacy-links + .button-row {
+  margin-top: 20px;
+}
+
+.verification-email-message {
+  word-wrap: break-word;
+}
+
+.password-row {
+  position: relative;
+}
+
+.password-row > .password {
+  padding-right: 75px;
+}
+
+.password-row > .password + label {
+  border-left: 1px solid #C0C9D0;
+  color: #C0C9D0;
+  cursor: pointer;
+  font-size: 16px;
+  height: 44px;
+  line-height: 44px;
+  position: absolute;
+  right: 0;
+  top: 0;
+  /* it is very easy to accidentally select the text when clicking */
+  -webkit-touch-callout: none;
+  -webkit-user-select: none;
+   -khtml-user-select: none;
+     -moz-user-select: none;
+      -ms-user-select: none;
+          user-select: none;
+  width: 55px;
+}
+
+.password-row > .password:focus + label {
+  border-color: #6F7A85;
+}
+
+.password-row > .password + label > input[type=checkbox] {
+  visibility: hidden;
+}
+
+.links + .privacy-links {
+  margin: 20px 0 15px 0;
+}
+
+.cannot-create-account-content {
+  margin-top: 105px;
+}
+
+.spinner {
+  -webkit-animation: 0.9s spin infinite linear;
+  -moz-animation: 0.9s spin infinite linear;
+  animation: 0.9s spin infinite linear;
+  background-image: url(../images/spinnerlight.png);
+  background-size: 30px 30px;
+  display: block;
+  margin: 130px auto 0;
+  width: 30px;
+  height: 30px;
+  overflow: hidden;
+  text-indent: 100%;
+  white-space: nowrap;
+}
+
+@-webkit-keyframes spin {
+  from {
+    -webkit-transform: rotate(0deg);
+  }
+  to {
+    -webkit-transform: rotate(365deg);
+  }
+}
+
+@-moz-keyframes spin {
+  from {
+    -moz-transform: rotate(0deg);
+  }
+  to {
+    -moz-transform: rotate(365deg);
+  }
+}
+
+@keyframes spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(365deg);
+  }
+}
+
+/* TODO: These need further consideration */
+
+.hidden {
+  display: none;
+}
+
+/* Responsiveness */
+
+/* TODO: Confirm breakpoint sizes */
+@media only screen and (max-width: 500px) {
+  html {
+    background: #fff;
+  }
+
+  #stage {
+    -webkit-box-shadow: none;
+    box-shadow: none;
+    margin: 0 auto;
+    min-height: none;
+    width: 100%;
+  }
+}
+
+/* Retina */
+
+@media
+only screen and (-webkit-min-device-pixel-ratio: 2),
+only screen and (   min--moz-device-pixel-ratio: 2),
+only screen and (   -moz-min-device-pixel-ratio: 2),
+only screen and (     -o-min-device-pixel-ratio: 2/1),
+only screen and (        min-device-pixel-ratio: 2),
+only screen and (                min-resolution: 192dpi),
+only screen and (                min-resolution: 2dppx) {
+  #stage {
+    background-image: url(../images/fox@2x.png);
+  }
+
+  .graphic-checkbox {
+    background-image: url(../images/graphic_checkbox@2x.png);
+  }
+
+  .graphic-mail {
+    background-image: url(../images/graphic_mail@2x.png);
+  }
+
+  .spinner {
+    background-image: url(../images/spinnerlight@2x.png);
+  }
+
+  .input-row select {
+    background-image: url(../images/ddarrow_inactive@2x.png)
+  }
+
+  .input-row select:focus {
+    background-image: url(../images/ddarrow_active@2x.png);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/aboutaccounts/normalize.css
@@ -0,0 +1,406 @@
+/*! normalize.css v2.1.3 | MIT License | git.io/normalize */
+
+/* ==========================================================================
+   HTML5 display definitions
+   ========================================================================== */
+
+/**
+ * Correct `block` display not defined in IE 8/9.
+ */
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+nav,
+section,
+summary {
+    display: block;
+}
+
+/**
+ * Correct `inline-block` display not defined in IE 8/9.
+ */
+
+audio,
+canvas,
+video {
+    display: inline-block;
+}
+
+/**
+ * Prevent modern browsers from displaying `audio` without controls.
+ * Remove excess height in iOS 5 devices.
+ */
+
+audio:not([controls]) {
+    display: none;
+    height: 0;
+}
+
+/**
+ * Address `[hidden]` styling not present in IE 8/9.
+ * Hide the `template` element in IE, Safari, and Firefox < 22.
+ */
+
+[hidden],
+template {
+    display: none;
+}
+
+/* ==========================================================================
+   Base
+   ========================================================================== */
+
+/**
+ * 1. Set default font family to sans-serif.
+ * 2. Prevent iOS text size adjust after orientation change, without disabling
+ *    user zoom.
+ */
+
+html {
+    font-family: sans-serif; /* 1 */
+    -ms-text-size-adjust: 100%; /* 2 */
+    -webkit-text-size-adjust: 100%; /* 2 */
+}
+
+/**
+ * Remove default margin.
+ */
+
+body {
+    margin: 0;
+}
+
+/* ==========================================================================
+   Links
+   ========================================================================== */
+
+/**
+ * Remove the gray background color from active links in IE 10.
+ */
+
+a {
+    background: transparent;
+}
+
+/**
+ * Address `outline` inconsistency between Chrome and other browsers.
+ */
+
+a:focus {
+    outline: thin dotted;
+}
+
+/**
+ * Improve readability when focused and also mouse hovered in all browsers.
+ */
+
+a:active,
+a:hover {
+    outline: 0;
+}
+
+/* ==========================================================================
+   Typography
+   ========================================================================== */
+
+/**
+ * Address variable `h1` font-size and margin within `section` and `article`
+ * contexts in Firefox 4+, Safari 5, and Chrome.
+ */
+
+h1 {
+    font-size: 2em;
+    margin: 0.67em 0;
+}
+
+/**
+ * Address styling not present in IE 8/9, Safari 5, and Chrome.
+ */
+
+abbr[title] {
+    border-bottom: 1px dotted;
+}
+
+/**
+ * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome.
+ */
+
+b,
+strong {
+    font-weight: bold;
+}
+
+/**
+ * Address styling not present in Safari 5 and Chrome.
+ */
+
+dfn {
+    font-style: italic;
+}
+
+/**
+ * Address differences between Firefox and other browsers.
+ */
+
+hr {
+    -moz-box-sizing: content-box;
+    box-sizing: content-box;
+    height: 0;
+}
+
+/**
+ * Address styling not present in IE 8/9.
+ */
+
+mark {
+    background: #ff0;
+    color: #000;
+}
+
+/**
+ * Correct font family set oddly in Safari 5 and Chrome.
+ */
+
+code,
+kbd,
+pre,
+samp {
+    font-family: monospace, serif;
+    font-size: 1em;
+}
+
+/**
+ * Improve readability of pre-formatted text in all browsers.
+ */
+
+pre {
+    white-space: pre-wrap;
+}
+
+/**
+ * Set consistent quote types.
+ */
+
+q {
+    quotes: "\201C" "\201D" "\2018" "\2019";
+}
+
+/**
+ * Address inconsistent and variable font size in all browsers.
+ */
+
+small {
+    font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` affecting `line-height` in all browsers.
+ */
+
+sub,
+sup {
+    font-size: 75%;
+    line-height: 0;
+    position: relative;
+    vertical-align: baseline;
+}
+
+sup {
+    top: -0.5em;
+}
+
+sub {
+    bottom: -0.25em;
+}
+
+/* ==========================================================================
+   Embedded content
+   ========================================================================== */
+
+/**
+ * Remove border when inside `a` element in IE 8/9.
+ */
+
+img {
+    border: 0;
+}
+
+/**
+ * Correct overflow displayed oddly in IE 9.
+ */
+
+svg:not(:root) {
+    overflow: hidden;
+}
+
+/* ==========================================================================
+   Figures
+   ========================================================================== */
+
+/**
+ * Address margin not present in IE 8/9 and Safari 5.
+ */
+
+figure {
+    margin: 0;
+}
+
+/* ==========================================================================
+   Forms
+   ========================================================================== */
+
+/**
+ * Define consistent border, margin, and padding.
+ */
+
+fieldset {
+    border: 1px solid #c0c0c0;
+    margin: 0 2px;
+    padding: 0.35em 0.625em 0.75em;
+}
+
+/**
+ * 1. Correct `color` not being inherited in IE 8/9.
+ * 2. Remove padding so people aren't caught out if they zero out fieldsets.
+ */
+
+legend {
+    border: 0; /* 1 */
+    padding: 0; /* 2 */
+}
+
+/**
+ * 1. Correct font family not being inherited in all browsers.
+ * 2. Correct font size not being inherited in all browsers.
+ * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome.
+ */
+
+button,
+input,
+select,
+textarea {
+    font-family: inherit; /* 1 */
+    font-size: 100%; /* 2 */
+    margin: 0; /* 3 */
+}
+
+/**
+ * Address Firefox 4+ setting `line-height` on `input` using `!important` in
+ * the UA stylesheet.
+ */
+
+button,
+input {
+    line-height: normal;
+}
+
+/**
+ * Address inconsistent `text-transform` inheritance for `button` and `select`.
+ * All other form control elements do not inherit `text-transform` values.
+ * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+.
+ * Correct `select` style inheritance in Firefox 4+ and Opera.
+ */
+
+button,
+select {
+    text-transform: none;
+}
+
+/**
+ * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
+ *    and `video` controls.
+ * 2. Correct inability to style clickable `input` types in iOS.
+ * 3. Improve usability and consistency of cursor style between image-type
+ *    `input` and others.
+ */
+
+button,
+html input[type="button"], /* 1 */
+input[type="reset"],
+input[type="submit"] {
+    -webkit-appearance: button; /* 2 */
+    cursor: pointer; /* 3 */
+}
+
+/**
+ * Re-set default cursor for disabled elements.
+ */
+
+button[disabled],
+html input[disabled] {
+    cursor: default;
+}
+
+/**
+ * 1. Address box sizing set to `content-box` in IE 8/9/10.
+ * 2. Remove excess padding in IE 8/9/10.
+ */
+
+input[type="checkbox"],
+input[type="radio"] {
+    box-sizing: border-box; /* 1 */
+    padding: 0; /* 2 */
+}
+
+/**
+ * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
+ * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
+ *    (include `-moz` to future-proof).
+ */
+
+input[type="search"] {
+    -webkit-appearance: textfield; /* 1 */
+    -moz-box-sizing: content-box;
+    -webkit-box-sizing: content-box; /* 2 */
+    box-sizing: content-box;
+}
+
+/**
+ * Remove inner padding and search cancel button in Safari 5 and Chrome
+ * on OS X.
+ */
+
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+    -webkit-appearance: none;
+}
+
+/**
+ * Remove inner padding and border in Firefox 4+.
+ */
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+    border: 0;
+    padding: 0;
+}
+
+/**
+ * 1. Remove default vertical scrollbar in IE 8/9.
+ * 2. Improve readability and alignment in all browsers.
+ */
+
+textarea {
+    overflow: auto; /* 1 */
+    vertical-align: top; /* 2 */
+}
+
+/* ==========================================================================
+   Tables
+   ========================================================================== */
+
+/**
+ * Remove most spacing between table cells.
+ */
+
+table {
+    border-collapse: collapse;
+    border-spacing: 0;
+}
--- a/browser/base/content/browser-feeds.js
+++ b/browser/base/content/browser-feeds.js
@@ -51,17 +51,16 @@ var FeedHandler = {
       var baseTitle = feedInfo.title || feedInfo.href;
       var labelStr = gNavigatorBundle.getFormattedString("feedShowFeedNew", [baseTitle]);
       item.setAttribute("label", labelStr);
       item.setAttribute("feed", feedInfo.href);
       item.setAttribute("tooltiptext", feedInfo.href);
       item.setAttribute("crop", "center");
       let className = "feed-" + itemNodeType;
       if (isSubview) {
-        item.setAttribute("tabindex", "0");
         className += " subviewbutton";
       }
       item.setAttribute("class", className);
       container.appendChild(item);
     }
     return true;
   },
 
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -109,17 +109,16 @@
     <command id="History:UndoCloseTab" oncommand="undoCloseTab();"/>
     <command id="History:UndoCloseWindow" oncommand="undoCloseWindow();"/>
     <command id="Social:SharePage" oncommand="SocialShare.sharePage();" disabled="true"/>
     <command id="Social:ToggleSidebar" oncommand="Social.toggleSidebar();" hidden="true"/>
     <command id="Social:ToggleNotifications" oncommand="Social.toggleNotifications();" hidden="true"/>
     <command id="Social:FocusChat" oncommand="SocialChatBar.focus();" hidden="true" disabled="true"/>
     <command id="Social:Toggle" oncommand="Social.toggle();" hidden="true"/>
     <command id="Social:Addons" oncommand="BrowserOpenAddonsMgr('addons://list/service');"/>
-    <command id="MenuPanel:Toggle" oncommand="PanelUI.toggle(event);"/>
   </commandset>
 
   <commandset id="placesCommands">
     <command id="Browser:ShowAllBookmarks"
              oncommand="PlacesCommandHook.showPlacesOrganizer('AllBookmarks');"/>
     <command id="Browser:ShowAllHistory"
              oncommand="PlacesCommandHook.showPlacesOrganizer('History');"/>
   </commandset>
@@ -401,23 +400,16 @@
     <key id="key_quitApplication" key="&quitApplicationCmdUnix.key;" command="cmd_quitApplication" modifiers="accel"/>
 #endif
 
 #ifdef FULL_BROWSER_WINDOW
     <key id="key_undoCloseTab" command="History:UndoCloseTab" key="&tabCmd.commandkey;" modifiers="accel,shift"/>
 #endif
     <key id="key_undoCloseWindow" command="History:UndoCloseWindow" key="&newNavigatorCmd.key;" modifiers="accel,shift"/>
 
-    <key id="key_menuButton" command="MenuPanel:Toggle"
-#ifdef XP_MACOSX
-         key="&toggleMenuPanelMac.key;" modifiers="accel,shift"/>
-#else
-         key="&toggleMenuPanel.key;" modifiers="accel"/>
-#endif
-
 #ifdef XP_GNOME
 #define NUM_SELECT_TAB_MODIFIER alt
 #else
 #define NUM_SELECT_TAB_MODIFIER accel
 #endif
 
 #expand    <key id="key_selectTab1" oncommand="gBrowser.selectTabAtIndex(0, event);" key="1" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
 #expand    <key id="key_selectTab2" oncommand="gBrowser.selectTabAtIndex(1, event);" key="2" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
--- a/browser/base/content/test/general/browser_bug817947.js
+++ b/browser/base/content/test/general/browser_bug817947.js
@@ -29,22 +29,22 @@ function test() {
     });
   });
 }
 
 function preparePendingTab(aCallback) {
   let tab = gBrowser.addTab(URL);
 
   whenLoaded(tab.linkedBrowser, function () {
-    let state = SessionStore.getTabState(tab);
     gBrowser.removeTab(tab);
+    let [{state}] = JSON.parse(SessionStore.getClosedTabData(window));
 
     tab = gBrowser.addTab("about:blank");
     whenLoaded(tab.linkedBrowser, function () {
-      SessionStore.setTabState(tab, state);
+      SessionStore.setTabState(tab, JSON.stringify(state));
       ok(tab.hasAttribute("pending"), "tab should be pending");
       aCallback(tab);
     });
   });
 }
 
 function whenLoaded(aElement, aCallback) {
   aElement.addEventListener("load", function onLoad() {
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -48,16 +48,23 @@ browser.jar:
 #ifdef MOZ_SERVICES_HEALTHREPORT
         content/browser/abouthealthreport/abouthealth.xhtml   (content/abouthealthreport/abouthealth.xhtml)
         content/browser/abouthealthreport/abouthealth.js      (content/abouthealthreport/abouthealth.js)
         content/browser/abouthealthreport/abouthealth.css     (content/abouthealthreport/abouthealth.css)
 #endif
         content/browser/aboutaccounts/aboutaccounts.xhtml   (content/aboutaccounts/aboutaccounts.xhtml)
         content/browser/aboutaccounts/aboutaccounts.js      (content/aboutaccounts/aboutaccounts.js)
         content/browser/aboutaccounts/aboutaccounts.css     (content/aboutaccounts/aboutaccounts.css)
+        content/browser/aboutaccounts/main.css              (content/aboutaccounts/main.css)
+        content/browser/aboutaccounts/normalize.css         (content/aboutaccounts/normalize.css)
+        content/browser/aboutaccounts/fonts.css             (content/aboutaccounts/fonts.css)
+        content/browser/aboutaccounts/images/fox.png             (content/aboutaccounts/images/fox.png)
+        content/browser/aboutaccounts/images/graphic_sync_intro.png             (content/aboutaccounts/images/graphic_sync_intro.png)
+        content/browser/aboutaccounts/images/graphic_sync_intro@2x.png             (content/aboutaccounts/images/graphic_sync_intro@2x.png)
+
         content/browser/aboutRobots-icon.png          (content/aboutRobots-icon.png)
         content/browser/aboutRobots-widget-left.png   (content/aboutRobots-widget-left.png)
         content/browser/aboutSocialError.xhtml        (content/aboutSocialError.xhtml)
         content/browser/aboutTabCrashed.js            (content/aboutTabCrashed.js)
         content/browser/aboutTabCrashed.xhtml         (content/aboutTabCrashed.xhtml)
 *       content/browser/browser.css                   (content/browser.css)
 *       content/browser/browser.js                    (content/browser.js)
 *       content/browser/browser.xul                   (content/browser.xul)
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -13,66 +13,66 @@
       <vbox id="PanelUI-contents-scroller">
         <vbox id="PanelUI-contents" class="panelUI-grid"/>
       </vbox>
 
       <footer id="PanelUI-footer">
         <!-- The parentNode is used so that the footer is presented as the anchor
              instead of just the button being the anchor. -->
         <toolbarbutton id="PanelUI-customize" label="&appMenuCustomize.label;"
-                       exitLabel="&appMenuCustomizeExit.label;" tabindex="0"
+                       exitLabel="&appMenuCustomizeExit.label;"
                        oncommand="gCustomizeMode.toggle();"/>
         <toolbarseparator/>
-        <toolbarbutton id="PanelUI-help" label="&helpMenu.label;" tabindex="0"
+        <toolbarbutton id="PanelUI-help" label="&helpMenu.label;"
                        tooltiptext="&helpMenu.label;"
                        oncommand="PanelUI.showHelpView(this.parentNode);"/>
         <toolbarseparator/>
-        <toolbarbutton id="PanelUI-quit" tabindex="0"
+        <toolbarbutton id="PanelUI-quit"
 #ifdef XP_WIN
                        label="&quitApplicationCmdWin.label;"
                        tooltiptext="&quitApplicationCmdWin.label;"
 #else
                        label="&quitApplicationCmd.label;"
                        tooltiptext="&quitApplicationCmd.label;"
 #endif
                        command="cmd_quitApplication"/>
       </footer>
     </panelview>
 
     <panelview id="PanelUI-history" flex="1">
       <label value="&appMenuHistory.label;" class="panel-subview-header"/>
-      <toolbarbutton id="appMenuViewHistorySidebar" tabindex="0"
+      <toolbarbutton id="appMenuViewHistorySidebar"
                      label="&appMenuHistory.viewSidebar.label;"
                      type="checkbox"
                      class="subviewbutton"
                      oncommand="toggleSidebar('viewHistorySidebar'); PanelUI.hide();">
         <observes element="viewHistorySidebar" attribute="checked"/>
       </toolbarbutton>
-      <toolbarbutton id="appMenuClearRecentHistory" tabindex="0"
+      <toolbarbutton id="appMenuClearRecentHistory"
                      label="&appMenuHistory.clearRecent.label;"
                      class="subviewbutton"
                      command="Tools:Sanitize"/>
 #ifdef MOZ_SERVICES_SYNC
       <toolbarbutton id="sync-tabs-menuitem2"
                      class="syncTabsMenuItem subviewbutton"
                      label="&syncTabsMenu2.label;"
                      oncommand="BrowserOpenSyncTabs();"
                      disabled="true"/>
 #endif
-      <toolbarbutton id="appMenuRestoreLastSession" tabindex="0"
+      <toolbarbutton id="appMenuRestoreLastSession"
                      label="&appMenuHistory.restoreSession.label;"
                      class="subviewbutton"
                      command="Browser:RestoreLastSession"/>
       <menuseparator id="PanelUI-recentlyClosedTabs-separator"/>
       <vbox id="PanelUI-recentlyClosedTabs" tooltip="bhTooltip"/>
       <menuseparator id="PanelUI-recentlyClosedWindows-separator"/>
       <vbox id="PanelUI-recentlyClosedWindows" tooltip="bhTooltip"/>
       <menuseparator id="PanelUI-historyItems-separator"/>
       <vbox id="PanelUI-historyItems" tooltip="bhTooltip"/>
-      <toolbarbutton id="PanelUI-historyMore" tabindex="0"
+      <toolbarbutton id="PanelUI-historyMore"
                      class="panel-subview-footer subviewbutton"
                      label="&appMenuHistory.showAll.label;"
                      oncommand="PlacesCommandHook.showPlacesOrganizer('History'); CustomizableUI.hidePanelForNode(this);"/>
     </panelview>
 
     <panelview id="PanelUI-bookmarks" flex="1" class="PanelUI-subView">
       <label value="&bookmarksMenu.label;" class="panel-subview-header"/>
       <toolbarbutton id="panelMenuBookmarkThisPage"
--- a/browser/components/customizableui/content/panelUI.js
+++ b/browser/components/customizableui/content/panelUI.js
@@ -136,22 +136,16 @@ const PanelUI = {
           aEvent.type == "command") {
         anchor = this.menuButton;
       } else {
         anchor = aEvent.target;
       }
       let iconAnchor =
         document.getAnonymousElementByAttribute(anchor, "class",
                                                 "toolbarbutton-icon");
-
-      // Only focus the panel if it's opened using the keyboard, so that
-      // cut/copy/paste buttons will work for mouse users.
-      let keyboardOpened = aEvent && aEvent.sourceEvent &&
-                           aEvent.sourceEvent.target.localName == "key";
-      this.panel.setAttribute("noautofocus", !keyboardOpened);
       this.panel.openPopup(iconAnchor || anchor, "bottomcenter topright");
 
       this.panel.addEventListener("popupshown", function onPopupShown() {
         this.removeEventListener("popupshown", onPopupShown);
         deferred.resolve();
       });
     });
 
--- a/browser/components/customizableui/content/panelUI.xml
+++ b/browser/components/customizableui/content/panelUI.xml
@@ -261,16 +261,20 @@
           }
           this.anchorElement = aAnchor;
         ]]></body>
       </method>
 
       <method name="handleEvent">
         <parameter name="aEvent"/>
         <body><![CDATA[
+          if (aEvent.type.startsWith("popup") && aEvent.target != this._panel) {
+            // Shouldn't act on e.g. context menus being shown from within the panel.
+            return;
+          }
           switch(aEvent.type) {
             case "click":
               if (aEvent.originalTarget == this._clickCapturer) {
                 this.showMainView();
               }
               break;
             case "overflow":
               // Resize the right view on the next tick.
--- a/browser/components/customizableui/src/CustomizableUI.jsm
+++ b/browser/components/customizableui/src/CustomizableUI.jsm
@@ -472,19 +472,22 @@ let CustomizableUIInternal = {
         if (inPrivateWindow && provider == CustomizableUI.PROVIDER_API) {
           let widget = gPalette.get(id);
           if (!widget.showInPrivateBrowsing && inPrivateWindow) {
             continue;
           }
         }
 
         this.ensureButtonContextMenu(node, aAreaNode);
-        if (node.localName == "toolbarbutton" && aArea == CustomizableUI.AREA_PANEL) {
-          node.setAttribute("tabindex", "0");
-          node.setAttribute("wrap", "true");
+        if (node.localName == "toolbarbutton") {
+          if (aArea == CustomizableUI.AREA_PANEL) {
+            node.setAttribute("wrap", "true");
+          } else {
+            node.removeAttribute("wrap");
+          }
         }
 
         this.insertWidgetBefore(node, currentNode, container, aArea);
         if (gResetting) {
           this.notifyListeners("onWidgetReset", node, container);
         }
       }
 
@@ -650,17 +653,16 @@ let CustomizableUIInternal = {
     for (let child of aPanel.children) {
       if (child.localName != "toolbarbutton") {
         if (child.localName == "toolbaritem") {
           this.ensureButtonContextMenu(child, aPanel);
         }
         continue;
       }
       this.ensureButtonContextMenu(child, aPanel);
-      child.setAttribute("tabindex", "0");
       child.setAttribute("wrap", "true");
     }
 
     this.registerBuildArea(CustomizableUI.AREA_PANEL, aPanel);
   },
 
   onWidgetAdded: function(aWidgetId, aArea, aPosition) {
     this.insertNode(aWidgetId, aArea, aPosition, true);
@@ -696,21 +698,20 @@ let CustomizableUIInternal = {
         container = areaNode.overflowable.getContainerFor(widgetNode);
       }
 
       this.notifyListeners("onWidgetBeforeDOMChange", widgetNode, null, container, true);
 
       // We remove location attributes here to make sure they're gone too when a
       // widget is removed from a toolbar to the palette. See bug 930950.
       this.removeLocationAttributes(widgetNode);
+      widgetNode.removeAttribute("wrap");
       if (gPalette.has(aWidgetId) || this.isSpecialWidget(aWidgetId)) {
         container.removeChild(widgetNode);
       } else {
-        widgetNode.removeAttribute("tabindex");
-        widgetNode.removeAttribute("wrap");
         areaNode.toolbox.palette.appendChild(widgetNode);
       }
       this.notifyListeners("onWidgetAfterDOMChange", widgetNode, null, container, true);
 
       if (isToolbar) {
         areaNode.setAttribute("currentset", gPlacements.get(aArea).join(','));
       }
 
@@ -849,17 +850,16 @@ let CustomizableUIInternal = {
       ERROR("Widget '" + aWidgetId + "' not found, unable to move");
       return;
     }
 
     let areaId = aAreaNode.id;
     if (isNew) {
       this.ensureButtonContextMenu(widgetNode, aAreaNode);
       if (widgetNode.localName == "toolbarbutton" && areaId == CustomizableUI.AREA_PANEL) {
-        widgetNode.setAttribute("tabindex", "0");
         widgetNode.setAttribute("wrap", "true");
       }
     }
 
     let container = aAreaNode.customizationTarget;
     let [insertionContainer, nextNode] = this.findInsertionPoints(widgetNode, aNextNodeId, aAreaNode);
     this.insertWidgetBefore(widgetNode, nextNode, insertionContainer, areaId);
 
--- a/browser/components/customizableui/src/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/src/CustomizableWidgets.jsm
@@ -95,17 +95,16 @@ const CustomizableWidgets = [{
           for (let row, i = 0; (row = aResultSet.getNextRow()); i++) {
             try {
               let uri = row.getResultByIndex(1);
               let title = row.getResultByIndex(2);
               let icon = row.getResultByIndex(6);
 
               let item = doc.createElementNS(kNSXUL, "toolbarbutton");
               item.setAttribute("label", title || uri);
-              item.setAttribute("tabindex", "0");
               item.setAttribute("targetURI", uri);
               item.setAttribute("class", "subviewbutton");
               item.addEventListener("command", function (aEvent) {
                 onHistoryVisit(uri, aEvent, item);
               });
               item.addEventListener("click", function (aEvent) {
                 onHistoryVisit(uri, aEvent, item);
               });
@@ -251,17 +250,16 @@ const CustomizableWidgets = [{
         if (node.hidden)
           continue;
 
         let item;
         if (node.localName == "menuseparator") {
           item = doc.createElementNS(kNSXUL, "menuseparator");
         } else if (node.localName == "menuitem") {
           item = doc.createElementNS(kNSXUL, "toolbarbutton");
-          item.setAttribute("tabindex", "0");
           item.setAttribute("class", "subviewbutton");
         } else {
           continue;
         }
         for (let attr of attrs) {
           let attrVal = node.getAttribute(attr);
           if (attrVal)
             item.setAttribute(attr, attrVal);
@@ -362,18 +360,16 @@ const CustomizableWidgets = [{
       node.classList.add("toolbaritem-combined-buttons");
       node.classList.add(kWidePanelItemClass);
 
       buttons.forEach(function(aButton, aIndex) {
         if (aIndex != 0)
           node.appendChild(aDocument.createElementNS(kNSXUL, "separator"));
         let btnNode = aDocument.createElementNS(kNSXUL, "toolbarbutton");
         setAttributes(btnNode, aButton);
-        if (inPanel)
-          btnNode.setAttribute("tabindex", "0");
         node.appendChild(btnNode);
       });
 
       // The middle node is the 'Reset Zoom' button.
       let zoomResetButton = node.childNodes[2];
       let window = aDocument.defaultView;
       function updateZoomResetButton() {
         //XXXgijs in some tests we get called very early, and there's no docShell on the
--- a/browser/components/sessionstore/content/content-sessionStore.js
+++ b/browser/components/sessionstore/content/content-sessionStore.js
@@ -71,74 +71,57 @@ function isSessionStorageEvent(event) {
 /**
  * Listens for and handles content events that we need for the
  * session store service to be notified of state changes in content.
  */
 let EventListener = {
 
   init: function () {
     addEventListener("load", this, true);
-    addEventListener("pageshow", this, true);
   },
 
   handleEvent: function (event) {
-    switch (event.type) {
-      case "load":
-        // Ignore load events from subframes.
-        if (event.target == content.document) {
-          // If we're in the process of restoring, this load may signal
-          // the end of the restoration.
-          let epoch = gContentRestore.getRestoreEpoch();
-          if (epoch) {
-            // Restore the form data and scroll position.
-            gContentRestore.restoreDocument();
+    // Ignore load events from subframes.
+    if (event.target != content.document) {
+      return;
+    }
 
-            // Ask SessionStore.jsm to trigger SSTabRestored.
-            sendAsyncMessage("SessionStore:restoreDocumentComplete", {epoch: epoch});
-          }
+    // If we're in the process of restoring, this load may signal
+    // the end of the restoration.
+    let epoch = gContentRestore.getRestoreEpoch();
+    if (epoch) {
+      // Restore the form data and scroll position.
+      gContentRestore.restoreDocument();
 
-          // Send a load message for all loads so we can invalidate the TabStateCache.
-          sendAsyncMessage("SessionStore:load");
-        }
-        break;
-      case "pageshow":
-        if (event.persisted && event.target == content.document)
-          sendAsyncMessage("SessionStore:pageshow");
-        break;
-      default:
-        debug("received unknown event '" + event.type + "'");
-        break;
+      // Ask SessionStore.jsm to trigger SSTabRestored.
+      sendAsyncMessage("SessionStore:restoreDocumentComplete", {epoch: epoch});
     }
+
+    // Send a load message for all loads.
+    sendAsyncMessage("SessionStore:load");
   }
 };
 
 /**
  * Listens for and handles messages sent by the session store service.
  */
 let MessageListener = {
 
   MESSAGES: [
-    "SessionStore:collectSessionHistory",
-
     "SessionStore:restoreHistory",
     "SessionStore:restoreTabContent",
     "SessionStore:resetRestore",
   ],
 
   init: function () {
     this.MESSAGES.forEach(m => addMessageListener(m, this));
   },
 
   receiveMessage: function ({name, data}) {
-    let id = data ? data.id : 0;
     switch (name) {
-      case "SessionStore:collectSessionHistory":
-        let history = SessionHistory.collect(docShell);
-        sendAsyncMessage(name, {id: id, data: history});
-        break;
       case "SessionStore:restoreHistory":
         let reloadCallback = () => {
           // Inform SessionStore.jsm about the reload. It will send
           // restoreTabContent in response.
           sendAsyncMessage("SessionStore:reloadPendingTab", {epoch: data.epoch});
         };
         gContentRestore.restoreHistory(data.epoch, data.tabData, reloadCallback);
 
@@ -175,36 +158,32 @@ let MessageListener = {
       default:
         debug("received unknown message '" + name + "'");
         break;
     }
   }
 };
 
 /**
- * If session data must be collected synchronously, we do it via
- * method calls to this object (rather than via messages to
- * MessageListener). When using multiple processes, these methods run
- * in the content process, but the parent synchronously waits on them
- * using cross-process object wrappers. Without multiple processes, we
- * still use this code for synchronous collection.
+ * On initialization, this handler gets sent to the parent process as a CPOW.
+ * The parent will use it only to flush pending data from the frame script
+ * when needed, i.e. when closing a tab, closing a window, shutting down, etc.
+ *
+ * This will hopefully not be needed in the future once we have async APIs for
+ * closing windows and tabs.
  */
 let SyncHandler = {
   init: function () {
     // Send this object as a CPOW to chrome. In single-process mode,
     // the synchronous send ensures that the handler object is
     // available in SessionStore.jsm immediately upon loading
     // content-sessionStore.js.
     sendSyncMessage("SessionStore:setupSyncHandler", {}, {handler: this});
   },
 
-  collectSessionHistory: function (includePrivateData) {
-    return SessionHistory.collect(docShell);
-  },
-
   /**
    * This function is used to make the tab process flush all data that
    * hasn't been sent to the parent process, yet.
    *
    * @param id (int)
    *        A unique id that represents the last message received by the chrome
    *        process before flushing. We will use this to determine data that
    *        would be lost when data has been sent asynchronously shortly
@@ -220,32 +199,67 @@ let SyncHandler = {
    * This function is used to simulate certain situations where race conditions
    * can occur by sending data shortly before flushing synchronously.
    */
   flushAsync: function () {
     MessageQueue.flushAsync();
   }
 };
 
-let ProgressListener = {
-  init: function() {
-    let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
-                              .getInterface(Ci.nsIWebProgress);
-    webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
+/**
+ * Listens for changes to the session history. Whenever the user navigates
+ * we will collect URLs and everything belonging to session history.
+ *
+ * Causes a SessionStore:update message to be sent that contains the current
+ * session history.
+ *
+ * Example:
+ *   {entries: [{url: "about:mozilla", ...}, ...], index: 1}
+ */
+let SessionHistoryListener = {
+  init: function () {
+    gFrameTree.addObserver(this);
+    addEventListener("load", this, true);
+    addEventListener("hashchange", this, true);
+    Services.obs.addObserver(this, "browser:purge-session-history", false);
+  },
+
+  uninit: function () {
+    Services.obs.removeObserver(this, "browser:purge-session-history");
   },
-  onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) {
-    // We are changing page, so time to invalidate the state of the tab
-    sendAsyncMessage("SessionStore:loadStart");
+
+  observe: function () {
+    // We need to use setTimeout() here because we listen for
+    // "browser:purge-session-history". When that is fired all observers are
+    // expected to purge their data. We can't expect to be called *after* the
+    // observer in browser.xml that clears session history so we need to wait
+    // a tick before actually collecting data.
+    setTimeout(() => this.collect(), 0);
   },
-  onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {},
-  onProgressChange: function() {},
-  onStatusChange: function() {},
-  onSecurityChange: function() {},
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
-                                         Ci.nsISupportsWeakReference])
+
+  handleEvent: function (event) {
+    // We are only interested in "load" events from subframes.
+    if (event.type == "hashchange" || event.target != content.document) {
+      this.collect();
+    }
+  },
+
+  collect: function () {
+    if (docShell) {
+      MessageQueue.push("history", () => SessionHistory.collect(docShell));
+    }
+  },
+
+  onFrameTreeCollected: function () {
+    this.collect();
+  },
+
+  onFrameTreeReset: function () {
+    this.collect();
+  }
 };
 
 /**
  * Listens for scroll position changes. Whenever the user scrolls the top-most
  * frame we update the scroll position and will restore it when requested.
  *
  * Causes a SessionStore:update message to be sent that contains the current
  * scroll positions as a tree of strings. If no frame of the whole frame tree
@@ -365,19 +379,17 @@ let PageStyleListener = {
   },
 
   onFrameTreeCollected: function () {
     MessageQueue.push("pageStyle", () => this.collect());
   },
 
   onFrameTreeReset: function () {
     MessageQueue.push("pageStyle", () => null);
-  },
-
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver])
+  }
 };
 
 /**
  * Listens for changes to docShell capabilities. Whenever a new load is started
  * we need to re-check the list of capabilities and send message when it has
  * changed.
  *
  * Causes a SessionStore:update message to be sent that contains the currently
@@ -451,19 +463,17 @@ let SessionStorageListener = {
   },
 
   onFrameTreeCollected: function () {
     this.collect();
   },
 
   onFrameTreeReset: function () {
     this.collect();
-  },
-
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver])
+  }
 };
 
 /**
  * Listen for changes to the privacy status of the tab.
  * By definition, tabs start in non-private mode.
  *
  * Causes a SessionStore:update message to be sent for
  * field "isPrivate". This message contains
@@ -652,24 +662,25 @@ let MessageQueue = {
     this.send();
   }
 };
 
 EventListener.init();
 MessageListener.init();
 FormDataListener.init();
 SyncHandler.init();
-ProgressListener.init();
 PageStyleListener.init();
+SessionHistoryListener.init();
 SessionStorageListener.init();
 ScrollPositionListener.init();
 DocShellCapabilitiesListener.init();
 PrivacyListener.init();
 
 addEventListener("unload", () => {
   // Remove all registered nsIObservers.
   PageStyleListener.uninit();
   SessionStorageListener.uninit();
+  SessionHistoryListener.uninit();
 
   // We don't need to take care of any gFrameTree observers as the gFrameTree
   // will die with the content script. The same goes for the privacy transition
   // observer that will die with the docShell when the tab is closed.
 });
deleted file mode 100644
--- a/browser/components/sessionstore/src/Messenger.jsm
+++ /dev/null
@@ -1,71 +0,0 @@
-/* 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/. */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["Messenger"];
-
-const Cu = Components.utils;
-
-Cu.import("resource://gre/modules/Promise.jsm", this);
-Cu.import("resource://gre/modules/Timer.jsm", this);
-
-/**
- * The external API exported by this module.
- */
-this.Messenger = Object.freeze({
-  send: function (tab, type, options = {}) {
-    return MessengerInternal.send(tab, type, options);
-  }
-});
-
-/**
- * A module that handles communication between the main and content processes.
- */
-let MessengerInternal = {
-  // The id of the last message we sent. This is used to assign a unique ID to
-  // every message we send to handle multiple responses from the same browser.
-  _latestMessageID: 0,
-
-  /**
-   * Sends a message to the given tab and waits for a response.
-   *
-   * @param tab
-   *        tabbrowser tab
-   * @param type
-   *        {string} the type of the message
-   * @param options (optional)
-   *        {timeout: int} to set the timeout in milliseconds
-   * @return {Promise} A promise that will resolve to the response message or
-   *                   be reject when timing out.
-   */
-  send: function (tab, type, options = {}) {
-    let browser = tab.linkedBrowser;
-    let mm = browser.messageManager;
-    let deferred = Promise.defer();
-    let id = ++this._latestMessageID;
-    let timeout;
-
-    function onMessage({data: {id: mid, data}}) {
-      if (mid == id) {
-        mm.removeMessageListener(type, onMessage);
-        clearTimeout(timeout);
-        deferred.resolve(data);
-      }
-    }
-
-    mm.addMessageListener(type, onMessage);
-    mm.sendAsyncMessage(type, {id: id});
-
-    function onTimeout() {
-      mm.removeMessageListener(type, onMessage);
-      deferred.reject(new Error("Timed out while waiting for a " + type + " " +
-                                "response message."));
-    }
-
-    let delay = (options && options.timeout) || 5000;
-    timeout = setTimeout(onTimeout, delay);
-    return deferred.promise;
-  }
-};
--- a/browser/components/sessionstore/src/SessionSaver.jsm
+++ b/browser/components/sessionstore/src/SessionSaver.jsm
@@ -229,32 +229,18 @@ let SessionSaverInternal = {
    * Saves the current session state. Collects data asynchronously and calls
    * _saveState() to collect data again (with a cache hit rate of hopefully
    * 100%) and write to disk afterwards.
    */
   _saveStateAsync: function () {
     // Allow scheduling delayed saves again.
     this._timeoutID = null;
 
-    // Check whether asynchronous data collection is disabled.
-    if (!Services.prefs.getBoolPref("browser.sessionstore.async")) {
-      this._saveState();
-      return;
-    }
-
-    // Update the last save time to make sure we wait at least another interval
-    // length until we call _saveStateAsync() again.
-    this.updateLastSaveTime();
-
-    // Save state synchronously after all tab caches have been filled. The data
-    // for the tab caches is collected asynchronously. We will reuse this
-    // cached data if the tab hasn't been invalidated in the meantime. In that
-    // case we will just fall back to synchronous data collection for single
-    // tabs.
-    SessionStore.fillTabCachesAsynchronously().then(() => this._saveState());
+    // Write to disk.
+    this._saveState();
   },
 
   /**
    * Write the given state object to disk.
    */
   _writeState: function (state) {
     stopWatchStart("SERIALIZE_DATA_MS", "SERIALIZE_DATA_LONGEST_OP_MS");
     let data = JSON.stringify(state);
--- a/browser/components/sessionstore/src/SessionStore.jsm
+++ b/browser/components/sessionstore/src/SessionStore.jsm
@@ -44,34 +44,25 @@ const WINDOW_ATTRIBUTES = ["width", "hei
 
 // Hideable window features to (re)store
 // Restored in restoreWindowFeatures()
 const WINDOW_HIDEABLE_FEATURES = [
   "menubar", "toolbar", "locationbar", "personalbar", "statusbar", "scrollbars"
 ];
 
 const MESSAGES = [
-  // The content script has received a pageshow event. This happens when a
-  // page is loaded from bfcache without any network activity, i.e. when
-  // clicking the back or forward button.
-  "SessionStore:pageshow",
-
-  // The content script tells us that a new page just started loading in a
-  // browser.
-  "SessionStore:loadStart",
-
   // The content script gives us a reference to an object that performs
   // synchronous collection of session data.
   "SessionStore:setupSyncHandler",
 
   // The content script sends us data that has been invalidated and needs to
   // be saved to disk.
   "SessionStore:update",
 
-  // A "load" event happened. Invalidate the TabStateCache.
+  // A "load" event happened.
   "SessionStore:load",
 
   // The restoreHistory code has run. This is a good time to run SSTabRestoring.
   "SessionStore:restoreHistoryComplete",
 
   // The load for the restoring tab has begun. We update the URL bar at this
   // time; if we did it before, the load would overwrite it.
   "SessionStore:restoreTabContentStarted",
@@ -90,21 +81,16 @@ const MESSAGES = [
 ];
 
 // These are tab events that we listen to.
 const TAB_EVENTS = [
   "TabOpen", "TabClose", "TabSelect", "TabShow", "TabHide", "TabPinned",
   "TabUnpinned"
 ];
 
-// Browser events observed.
-const BROWSER_EVENTS = [
-  "SwapDocShells", "UserTypedValueChanged"
-];
-
 // The number of milliseconds in a day
 const MS_PER_DAY = 1000.0 * 60.0 * 60.0 * 24.0;
 
 Cu.import("resource://gre/modules/Services.jsm", this);
 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", this);
 Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this);
 Cu.import("resource://gre/modules/osfile.jsm", this);
@@ -116,24 +102,23 @@ XPCOMUtils.defineLazyServiceGetter(this,
   "@mozilla.org/browser/sessionstartup;1", "nsISessionStartup");
 XPCOMUtils.defineLazyServiceGetter(this, "gScreenManager",
   "@mozilla.org/gfx/screenmanager;1", "nsIScreenManager");
 XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
   "@mozilla.org/base/telemetry;1", "nsITelemetry");
 
 XPCOMUtils.defineLazyModuleGetter(this, "console",
   "resource://gre/modules/devtools/Console.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
+  "resource:///modules/RecentWindow.jsm");
+
 XPCOMUtils.defineLazyModuleGetter(this, "GlobalState",
   "resource:///modules/sessionstore/GlobalState.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Messenger",
-  "resource:///modules/sessionstore/Messenger.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivacyFilter",
   "resource:///modules/sessionstore/PrivacyFilter.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
-  "resource:///modules/RecentWindow.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ScratchpadManager",
   "resource:///modules/devtools/scratchpad-manager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionSaver",
   "resource:///modules/sessionstore/SessionSaver.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionCookies",
   "resource:///modules/sessionstore/SessionCookies.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
   "resource:///modules/sessionstore/SessionFile.jsm");
@@ -283,20 +268,16 @@ this.SessionStore = {
   restoreLastSession: function ss_restoreLastSession() {
     SessionStoreInternal.restoreLastSession();
   },
 
   getCurrentState: function (aUpdateAll) {
     return SessionStoreInternal.getCurrentState(aUpdateAll);
   },
 
-  fillTabCachesAsynchronously: function () {
-    return SessionStoreInternal.fillTabCachesAsynchronously();
-  },
-
   /**
    * Backstage pass to implementation details, used for testing purpose.
    * Controlled by preference "browser.sessionstore.testmode".
    */
   get _internal() {
     if (Services.prefs.getBoolPref("browser.sessionstore.debug")) {
       return SessionStoreInternal;
     }
@@ -604,32 +585,25 @@ let SessionStoreInternal = {
    * This method handles incoming messages sent by the session store content
    * script and thus enables communication with OOP tabs.
    */
   receiveMessage: function ssi_receiveMessage(aMessage) {
     var browser = aMessage.target;
     var win = browser.ownerDocument.defaultView;
 
     switch (aMessage.name) {
-      case "SessionStore:pageshow":
-        this.onTabLoad(win, browser);
-        break;
-      case "SessionStore:loadStart":
-        TabStateCache.delete(browser);
-        break;
       case "SessionStore:setupSyncHandler":
         TabState.setSyncHandler(browser, aMessage.objects.handler);
         break;
       case "SessionStore:update":
         this.recordTelemetry(aMessage.data.telemetry);
         TabState.update(browser, aMessage.data);
         this.saveStateDelayed(win);
         break;
       case "SessionStore:load":
-        TabStateCache.delete(browser);
         this.onTabLoad(win, browser);
         break;
       case "SessionStore:restoreHistoryComplete":
         if (this.isCurrentEpoch(browser, aMessage.data.epoch)) {
           // Notify the tabbrowser that the tab chrome has been restored.
           let tab = this._getTabForBrowser(browser);
           let tabData = browser.__SS_data;
 
@@ -733,32 +707,16 @@ let SessionStoreInternal = {
 
   /**
    * Implement nsIDOMEventListener for handling various window and tab events
    */
   handleEvent: function ssi_handleEvent(aEvent) {
     var win = aEvent.currentTarget.ownerDocument.defaultView;
     let browser;
     switch (aEvent.type) {
-      case "SwapDocShells":
-        browser = aEvent.currentTarget;
-        let otherBrowser = aEvent.detail;
-        TabState.onBrowserContentsSwapped(browser, otherBrowser);
-        TabStateCache.onBrowserContentsSwapped(browser, otherBrowser);
-        break;
-      case "UserTypedValueChanged":
-        browser = aEvent.currentTarget;
-        if (browser.userTypedValue) {
-          TabStateCache.updateField(browser, "userTypedValue", browser.userTypedValue);
-          TabStateCache.updateField(browser, "userTypedClear", browser.userTypedClear);
-        } else {
-          TabStateCache.removeField(browser, "userTypedValue");
-          TabStateCache.removeField(browser, "userTypedClear");
-        }
-        break;
       case "TabOpen":
         this.onTabAdd(win, aEvent.originalTarget);
         break;
       case "TabClose":
         // aEvent.detail determines if the tab was closed by moving to a different window
         if (!aEvent.detail)
           this.onTabClose(win, aEvent.originalTarget);
         this.onTabRemove(win, aEvent.originalTarget);
@@ -768,23 +726,17 @@ let SessionStoreInternal = {
         break;
       case "TabShow":
         this.onTabShow(win, aEvent.originalTarget);
         break;
       case "TabHide":
         this.onTabHide(win, aEvent.originalTarget);
         break;
       case "TabPinned":
-        // If possible, update cached data without having to invalidate it
-        TabStateCache.updateField(aEvent.originalTarget, "pinned", true);
-        this.saveStateDelayed(win);
-        break;
       case "TabUnpinned":
-        // If possible, update cached data without having to invalidate it
-        TabStateCache.updateField(aEvent.originalTarget, "pinned", false);
         this.saveStateDelayed(win);
         break;
     }
     this._clearRestoringWindows();
   },
 
   /**
    * Generate a unique window identifier
@@ -1217,17 +1169,16 @@ let SessionStoreInternal = {
     // session data on disk as this notification fires after the
     // quit-application notification so the browser is about to exit.
     if (this._loadState == STATE_QUITTING)
       return;
     LastSession.clear();
     let openWindows = {};
     this._forEachBrowserWindow(function(aWindow) {
       Array.forEach(aWindow.gBrowser.tabs, function(aTab) {
-        TabStateCache.delete(aTab);
         delete aTab.linkedBrowser.__SS_data;
         if (aTab.linkedBrowser.__SS_restoreState)
           this._resetTabRestoringState(aTab);
       }, this);
       openWindows[aWindow.__SSi] = true;
     });
     // also clear all data about closed tabs and windows
     for (let ix in this._windows) {
@@ -1335,20 +1286,17 @@ let SessionStoreInternal = {
    * @param aWindow
    *        Window reference
    * @param aTab
    *        Tab reference
    * @param aNoNotification
    *        bool Do not save state if we're updating an existing tab
    */
   onTabAdd: function ssi_onTabAdd(aWindow, aTab, aNoNotification) {
-    let browser = aTab.linkedBrowser;
-    BROWSER_EVENTS.forEach(msg => browser.addEventListener(msg, this, true));
-
-    let mm = browser.messageManager;
+    let mm = aTab.linkedBrowser.messageManager;
     MESSAGES.forEach(msg => mm.addMessageListener(msg, this));
 
     // Load the frame script after registering listeners.
     mm.loadFrameScript("chrome://browser/content/content-sessionStore.js", false);
 
     if (!aNoNotification) {
       this.saveStateDelayed(aWindow);
     }
@@ -1362,18 +1310,16 @@ let SessionStoreInternal = {
    *        Window reference
    * @param aTab
    *        Tab reference
    * @param aNoNotification
    *        bool Do not save state if we're updating an existing tab
    */
   onTabRemove: function ssi_onTabRemove(aWindow, aTab, aNoNotification) {
     let browser = aTab.linkedBrowser;
-    BROWSER_EVENTS.forEach(msg => browser.removeEventListener(msg, this, true));
-
     let mm = browser.messageManager;
     MESSAGES.forEach(msg => mm.removeMessageListener(msg, this));
 
     delete browser.__SS_data;
 
     // If this tab was in the middle of restoring or still needs to be restored,
     // we need to reset that state. If the tab was restoring, we will attempt to
     // restore the next tab.
@@ -1407,17 +1353,17 @@ let SessionStoreInternal = {
     if (this._max_tabs_undo == 0) {
       return;
     }
 
     // Flush all data queued in the content script before the tab is gone.
     TabState.flush(aTab.linkedBrowser);
 
     // Get the latest data for this tab (generally, from the cache)
-    let tabState = TabState.collectSync(aTab);
+    let tabState = TabState.collect(aTab);
 
     // Don't save private tabs
     let isPrivateWindow = PrivateBrowsingUtils.isWindowPrivate(aWindow);
     if (!isPrivateWindow && tabState.isPrivate) {
       return;
     }
 
     // store closed-tab data for undo
@@ -1443,27 +1389,23 @@ let SessionStoreInternal = {
    * When a tab loads, invalidate its cached state, trigger async save.
    *
    * @param aWindow
    *        Window reference
    * @param aBrowser
    *        Browser reference
    */
   onTabLoad: function ssi_onTabLoad(aWindow, aBrowser) {
-    // react on "load" and solitary "pageshow" events (the first "pageshow"
-    // following "load" is too late for deleting the data caches)
     // It's possible to get a load event after calling stop on a browser (when
     // overwriting tabs). We want to return early if the tab hasn't been restored yet.
     if (aBrowser.__SS_restoreState &&
         aBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
       return;
     }
 
-    TabStateCache.delete(aBrowser);
-
     delete aBrowser.__SS_data;
     this.saveStateDelayed(aWindow);
 
     // attempt to update the current URL we send in a crash report
     this._updateCrashReportURL(aWindow);
   },
 
   /**
@@ -1494,47 +1436,39 @@ let SessionStoreInternal = {
         aTab.linkedBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
       TabRestoreQueue.hiddenToVisible(aTab);
 
       // let's kick off tab restoration again to ensure this tab gets restored
       // with "restore_hidden_tabs" == false (now that it has become visible)
       this.restoreNextTab();
     }
 
-    // If possible, update cached data without having to invalidate it
-    TabStateCache.updateField(aTab, "hidden", false);
-
     // Default delay of 2 seconds gives enough time to catch multiple TabShow
     // events due to changing groups in Panorama.
     this.saveStateDelayed(aWindow);
   },
 
   onTabHide: function ssi_onTabHide(aWindow, aTab) {
     // If the tab hasn't been restored yet, move it into the right bucket
     if (aTab.linkedBrowser.__SS_restoreState &&
         aTab.linkedBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
       TabRestoreQueue.visibleToHidden(aTab);
     }
 
-    // If possible, update cached data without having to invalidate it
-    TabStateCache.updateField(aTab, "hidden", true);
-
     // Default delay of 2 seconds gives enough time to catch multiple TabHide
     // events due to changing groups in Panorama.
     this.saveStateDelayed(aWindow);
   },
 
   onGatherTelemetry: function() {
     // On the first gather-telemetry notification of the session,
     // gather telemetry data.
     Services.obs.removeObserver(this, "gather-telemetry");
-    this.fillTabCachesAsynchronously().then(function() {
-      let stateString = SessionStore.getBrowserState();
-      return SessionFile.gatherTelemetry(stateString);
-    });
+    let stateString = SessionStore.getBrowserState();
+    return SessionFile.gatherTelemetry(stateString);
   },
 
   /* ........ nsISessionStore API .............. */
 
   getBrowserState: function ssi_getBrowserState() {
     let state = this.getCurrentState();
 
     // Don't include the last session state in getBrowserState().
@@ -1618,17 +1552,17 @@ let SessionStoreInternal = {
   getTabState: function ssi_getTabState(aTab) {
     if (!aTab.ownerDocument) {
       throw Components.Exception("Invalid tab object: no ownerDocument", Cr.NS_ERROR_INVALID_ARG);
     }
     if (!aTab.ownerDocument.defaultView.__SSi) {
       throw Components.Exception("Default view is not tracked", Cr.NS_ERROR_INVALID_ARG);
     }
 
-    let tabState = TabState.collectSync(aTab);
+    let tabState = TabState.collect(aTab);
 
     return this._toJSONString(tabState);
   },
 
   setTabState: function ssi_setTabState(aTab, aState) {
     // Remove the tab state from the cache.
     // Note that we cannot simply replace the contents of the cache
     // as |aState| can be an incomplete state that will be completed
@@ -1651,17 +1585,16 @@ let SessionStoreInternal = {
     if (!("__SSi" in window)) {
       throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
     }
 
     if (aTab.linkedBrowser.__SS_restoreState) {
       this._resetTabRestoringState(aTab);
     }
 
-    TabStateCache.delete(aTab);
     this._setWindowStateBusy(window);
     this.restoreTabs(window, [aTab], [tabState], 0);
   },
 
   duplicateTab: function ssi_duplicateTab(aWindow, aTab, aDelta = 0) {
     if (!aTab.ownerDocument) {
       throw Components.Exception("Invalid tab object: no ownerDocument", Cr.NS_ERROR_INVALID_ARG);
     }
@@ -1856,17 +1789,16 @@ let SessionStoreInternal = {
       saveTo = aTab.linkedBrowser.__SS_data.extData;
     }
     else {
       aTab.__SS_extdata = {};
       saveTo = aTab.__SS_extdata;
     }
 
     saveTo[aKey] = aStringValue;
-    TabStateCache.updateField(aTab, "extData", saveTo);
     this.saveStateDelayed(aTab.ownerDocument.defaultView);
   },
 
   deleteTabValue: function ssi_deleteTabValue(aTab, aKey) {
     // We want to make sure that if data is accessed early, we attempt to delete
     // that data from __SS_data as well. Otherwise we'll throw in cases where
     // data can be set or read.
     let deleteFrom;
@@ -1874,25 +1806,16 @@ let SessionStoreInternal = {
       deleteFrom = aTab.__SS_extdata;
     }
     else if (aTab.linkedBrowser.__SS_data && aTab.linkedBrowser.__SS_data.extData) {
       deleteFrom = aTab.linkedBrowser.__SS_data.extData;
     }
 
     if (deleteFrom && aKey in deleteFrom) {
       delete deleteFrom[aKey];
-
-      // Keep the extData object only if it is not empty, to save
-      // a little disk space when serializing the tab state later.
-      if (Object.keys(deleteFrom).length) {
-        TabStateCache.updateField(aTab, "extData", deleteFrom);
-      } else {
-        TabStateCache.removeField(aTab, "extData");
-      }
-
       this.saveStateDelayed(aTab.ownerDocument.defaultView);
     }
   },
 
   getGlobalValue: function ssi_getGlobalValue(aKey) {
     return this._globalState.get(aKey);
   },
 
@@ -1903,17 +1826,16 @@ let SessionStoreInternal = {
 
   deleteGlobalValue: function ssi_deleteGlobalValue(aKey) {
     this._globalState.delete(aKey);
     this.saveStateDelayed();
   },
 
   persistTabAttribute: function ssi_persistTabAttribute(aName) {
     if (TabAttributes.persist(aName)) {
-      TabStateCache.clear();
       this.saveStateDelayed();
     }
   },
 
   /**
    * Restores the session state stored in LastSession. This will attempt
    * to merge data into the current session. If a window was opened at startup
    * with pinned tab(s), then the remaining data from the previous session for
@@ -2081,78 +2003,16 @@ let SessionStoreInternal = {
       for (let i = removableTabs.length - 1; i >= 0; i--) {
         tabbrowser.removeTab(removableTabs.pop(), { animate: false });
       }
     }
 
     return [true, canOverwriteTabs];
   },
 
-  /* ........ Async Data Collection .............. */
-
-  /**
-   * Kicks off asynchronous data collection for all tabs that do not have any
-   * cached data. The returned promise will only notify that the tab collection
-   * has been finished without resolving to any data. The tab collection for a
-   * a few or all tabs might have failed or timed out. By calling
-   * fillTabCachesAsynchronously() and waiting for the promise to be resolved
-   * before calling getCurrentState(), callers ensure that most of the data
-   * should have been collected asynchronously, without blocking the main
-   * thread.
-   *
-   * @return {Promise} the promise that is fulfilled when the tab data is ready
-   */
-  fillTabCachesAsynchronously: function () {
-    let countdown = 0;
-    let deferred = Promise.defer();
-    let activeWindow = this._getMostRecentBrowserWindow();
-
-    // The callback that will be called when a promise has been resolved
-    // successfully, i.e. the tab data has been collected.
-    function done() {
-      if (--countdown === 0) {
-        deferred.resolve();
-      }
-    }
-
-    // The callback that will be called when a promise is rejected, i.e. we
-    // we couldn't collect the tab data because of a script error or a timeout.
-    function fail(reason) {
-      debug("Failed collecting tab data asynchronously: " + reason);
-      done();
-    }
-
-    this._forEachBrowserWindow(win => {
-      if (!this._isWindowLoaded(win)) {
-        // Bail out if the window hasn't even loaded, yet.
-        return;
-      }
-
-      if (!DirtyWindows.has(win) && win != activeWindow) {
-        // Bail out if the window is not dirty and inactive.
-        return;
-      }
-
-      for (let tab of win.gBrowser.tabs) {
-        if (!tab.closing && !TabStateCache.has(tab)) {
-          countdown++;
-          TabState.collect(tab).then(done, fail);
-        }
-      }
-    });
-
-    // If no dirty tabs were found, return a resolved
-    // promise because there is nothing to do here.
-    if (countdown == 0) {
-      return Promise.resolve();
-    }
-
-    return deferred.promise;
-  },
-
   /* ........ Saving Functionality .............. */
 
   /**
    * Store window dimensions, visibility, sidebar
    * @param aWindow
    *        Window reference
    */
   _updateWindowFeatures: function ssi_updateWindowFeatures(aWindow) {
@@ -2318,17 +2178,17 @@ let SessionStoreInternal = {
 
     let tabbrowser = aWindow.gBrowser;
     let tabs = tabbrowser.tabs;
     let winData = this._windows[aWindow.__SSi];
     let tabsData = winData.tabs = [];
 
     // update the internal state data for this window
     for (let tab of tabs) {
-      tabsData.push(TabState.collectSync(tab));
+      tabsData.push(TabState.collect(tab));
     }
     winData.selected = tabbrowser.mTabBox.selectedIndex + 1;
 
     this._updateWindowFeatures(aWindow);
 
     // Make sure we keep __SS_lastSessionWindowID around for cases like entering
     // or leaving PB mode.
     if (aWindow.__SS_lastSessionWindowID)
@@ -2698,20 +2558,16 @@ let SessionStoreInternal = {
       else
         tabbrowser.showTab(tab);
 
       if ("attributes" in tabData) {
         // Ensure that we persist tab attributes restored from previous sessions.
         Object.keys(tabData.attributes).forEach(a => TabAttributes.persist(a));
       }
 
-      // Any data that's in the process of being collected for this tab will be
-      // out of date now that we're restoring it.
-      TabState.dropPendingCollections(browser);
-
       if (!tabData.entries) {
         tabData.entries = [];
       }
       if (tabData.extData) {
         tab.__SS_extdata = {};
         for (let key in tabData.extData)
          tab.__SS_extdata[key] = tabData.extData[key];
       } else {
@@ -2730,27 +2586,28 @@ let SessionStoreInternal = {
 
       // Save the index in case we updated it above.
       tabData.index = activeIndex + 1;
 
       // Start a new epoch and include the epoch in the restoreHistory
       // message. If a message is received that relates to a previous epoch, we
       // discard it.
       let epoch = this._nextRestoreEpoch++;
-      this._browserEpochs.set(browser, epoch);
+      this._browserEpochs.set(browser.permanentKey, epoch);
 
       // keep the data around to prevent dataloss in case
       // a tab gets closed before it's been properly restored
       browser.__SS_data = tabData;
       browser.__SS_restoreState = TAB_STATE_NEEDS_RESTORE;
       browser.setAttribute("pending", "true");
       tab.setAttribute("pending", "true");
 
       // Update the persistent tab state cache with |tabData| information.
-      TabStateCache.updatePersistent(browser, {
+      TabStateCache.update(browser, {
+        history: {entries: tabData.entries, index: tabData.index},
         scroll: tabData.scroll || null,
         storage: tabData.storage || null,
         formdata: tabData.formdata || null,
         disallow: tabData.disallow || null,
         pageStyle: tabData.pageStyle || null
       });
 
       // In electrolysis, we may need to change the browser's remote
@@ -3595,17 +3452,17 @@ let SessionStoreInternal = {
     let window = aTab.ownerDocument.defaultView;
     let browser = aTab.linkedBrowser;
 
     // Keep the tab's previous state for later in this method
     let previousState = browser.__SS_restoreState;
 
     // The browser is no longer in any sort of restoring state.
     delete browser.__SS_restoreState;
-    this._browserEpochs.delete(browser);
+    this._browserEpochs.delete(browser.permanentKey);
 
     aTab.removeAttribute("pending");
     browser.removeAttribute("pending");
 
     if (previousState == TAB_STATE_RESTORING) {
       if (this._tabsRestoringCount)
         this._tabsRestoringCount--;
     } else if (previousState == TAB_STATE_NEEDS_RESTORE) {
@@ -3627,17 +3484,17 @@ let SessionStoreInternal = {
   /**
    * Each time a <browser> element is restored, we increment its "epoch". To
    * check if a message from content-sessionStore.js is out of date, we can
    * compare the epoch received with the message to the <browser> element's
    * epoch. This function does that, and returns true if |epoch| is up-to-date
    * with respect to |browser|.
    */
   isCurrentEpoch: function (browser, epoch) {
-    return this._browserEpochs.get(browser, 0) == epoch;
+    return this._browserEpochs.get(browser.permanentKey, 0) == epoch;
   },
 
 };
 
 /**
  * Priority queue that keeps track of a list of tabs to restore and returns
  * the tab we should restore next, based on priority rules. We decide between
  * pinned, visible and hidden tabs in that and FIFO order. Hidden tabs are only
--- a/browser/components/sessionstore/src/TabState.jsm
+++ b/browser/components/sessionstore/src/TabState.jsm
@@ -9,451 +9,142 @@ this.EXPORTED_SYMBOLS = ["TabState"];
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 Cu.import("resource://gre/modules/Promise.jsm", this);
 Cu.import("resource://gre/modules/Task.jsm", this);
 
 XPCOMUtils.defineLazyModuleGetter(this, "console",
   "resource://gre/modules/devtools/Console.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Messenger",
-  "resource:///modules/sessionstore/Messenger.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivacyFilter",
   "resource:///modules/sessionstore/PrivacyFilter.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TabStateCache",
   "resource:///modules/sessionstore/TabStateCache.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TabAttributes",
   "resource:///modules/sessionstore/TabAttributes.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Utils",
-  "resource:///modules/sessionstore/Utils.jsm");
 
 /**
  * Module that contains tab state collection methods.
  */
 this.TabState = Object.freeze({
   setSyncHandler: function (browser, handler) {
     TabStateInternal.setSyncHandler(browser, handler);
   },
 
-  onBrowserContentsSwapped: function (browser, otherBrowser) {
-    TabStateInternal.onBrowserContentsSwapped(browser, otherBrowser);
-  },
-
   update: function (browser, data) {
     TabStateInternal.update(browser, data);
   },
 
   flush: function (browser) {
     TabStateInternal.flush(browser);
   },
 
   flushWindow: function (window) {
     TabStateInternal.flushWindow(window);
   },
 
   collect: function (tab) {
     return TabStateInternal.collect(tab);
   },
 
-  collectSync: function (tab) {
-    return TabStateInternal.collectSync(tab);
-  },
-
   clone: function (tab) {
     return TabStateInternal.clone(tab);
-  },
-
-  dropPendingCollections: function (browser) {
-    TabStateInternal.dropPendingCollections(browser);
   }
 });
 
 let TabStateInternal = {
-  // A map (xul:browser -> promise) that keeps track of tabs and
-  // their promises when collecting tab data asynchronously.
-  _pendingCollections: new WeakMap(),
-
   // A map (xul:browser -> handler) that maps a tab to the
   // synchronous collection handler object for that tab.
   // See SyncHandler in content-sessionStore.js.
   _syncHandlers: new WeakMap(),
 
   // A map (xul:browser -> int) that maps a browser to the
   // last "SessionStore:update" message ID we received for it.
   _latestMessageID: new WeakMap(),
 
   /**
    * Install the sync handler object from a given tab.
    */
   setSyncHandler: function (browser, handler) {
-    this._syncHandlers.set(browser, handler);
-    this._latestMessageID.set(browser, 0);
+    this._syncHandlers.set(browser.permanentKey, handler);
+    this._latestMessageID.set(browser.permanentKey, 0);
   },
 
   /**
    * Processes a data update sent by the content script.
    */
   update: function (browser, {id, data}) {
     // Only ever process messages that have an ID higher than the last one we
     // saw. This ensures we don't use stale data that has already been received
     // synchronously.
-    if (id > this._latestMessageID.get(browser)) {
-      this._latestMessageID.set(browser, id);
-      TabStateCache.updatePersistent(browser, data);
+    if (id > this._latestMessageID.get(browser.permanentKey)) {
+      this._latestMessageID.set(browser.permanentKey, id);
+      TabStateCache.update(browser, data);
     }
   },
 
   /**
    * Flushes all data currently queued in the given browser's content script.
    */
   flush: function (browser) {
-    if (this._syncHandlers.has(browser)) {
-      let lastID = this._latestMessageID.get(browser);
-      this._syncHandlers.get(browser).flush(lastID);
+    if (this._syncHandlers.has(browser.permanentKey)) {
+      let lastID = this._latestMessageID.get(browser.permanentKey);
+      this._syncHandlers.get(browser.permanentKey).flush(lastID);
     }
   },
 
   /**
    * Flushes queued content script data for all browsers of a given window.
    */
   flushWindow: function (window) {
     for (let browser of window.gBrowser.browsers) {
       this.flush(browser);
     }
   },
 
   /**
-   * When a docshell swap happens, a xul:browser element will be
-   * associated with a different content-sessionStore.js script
-   * global. In this case, the sync handler for the element needs to
-   * be swapped just like the docshell.
-   */
-  onBrowserContentsSwapped: function (browser, otherBrowser) {
-    // Data collected while docShells have been swapped should not go into
-    // the TabStateCache. Collections will most probably time out but we want
-    // to make sure.
-    this.dropPendingCollections(browser);
-    this.dropPendingCollections(otherBrowser);
-
-    // Swap data stored per-browser.
-    [this._syncHandlers, this._latestMessageID]
-      .forEach(map => Utils.swapMapEntries(map, browser, otherBrowser));
-  },
-
-  /**
-   * Collect data related to a single tab, asynchronously.
-   *
-   * @param tab
-   *        tabbrowser tab
-   *
-   * @returns {Promise} A promise that will resolve to a TabData instance.
-   */
-  collect: function (tab) {
-    if (!tab) {
-      throw new TypeError("Expecting a tab");
-    }
-
-    // Don't collect if we don't need to.
-    if (TabStateCache.has(tab)) {
-      return Promise.resolve(TabStateCache.get(tab));
-    }
-
-    // If the tab was recently added, or if it's being restored, we
-    // just collect basic data about it and skip the cache.
-    if (!this._tabNeedsExtraCollection(tab)) {
-      let tabData = this._collectBaseTabData(tab);
-      return Promise.resolve(tabData);
-    }
-
-    let browser = tab.linkedBrowser;
-
-    let promise = Task.spawn(function task() {
-      // Collect session history data asynchronously.
-      let history = yield Messenger.send(tab, "SessionStore:collectSessionHistory");
-
-      // The tab could have been closed while waiting for a response.
-      if (!tab.linkedBrowser) {
-        return;
-      }
-
-      // Collect basic tab data, without session history and storage.
-      let tabData = this._collectBaseTabData(tab);
-
-      // Apply collected data.
-      tabData.entries = history.entries;
-      if ("index" in history) {
-        tabData.index = history.index;
-      }
-
-      // If we're still the latest async collection for the given tab and
-      // the cache hasn't been filled by collect() in the meantime, let's
-      // fill the cache with the data we received.
-      if (this._pendingCollections.get(browser) == promise) {
-        TabStateCache.set(tab, tabData);
-        this._pendingCollections.delete(browser);
-      }
-
-      // Copy data from the persistent cache. We need to create an explicit
-      // copy of the |tabData| object so that the properties injected by
-      // |_copyFromPersistentCache| don't end up in the non-persistent cache.
-      // The persistent cache does not store "null" values, so any values that
-      // have been cleared by the frame script would not be overriden by
-      // |_copyFromPersistentCache|. These two caches are only an interim
-      // solution and the non-persistent one will go away soon.
-      tabData = Utils.copy(tabData);
-      this._copyFromPersistentCache(tab, tabData);
-
-      throw new Task.Result(tabData);
-    }.bind(this));
-
-    // Save the current promise as the latest asynchronous collection that is
-    // running. This will be used to check whether the collected data is still
-    // valid and will be used to fill the tab state cache.
-    this._pendingCollections.set(browser, promise);
-
-    return promise;
-  },
-
-  /**
    * Collect data related to a single tab, synchronously.
    *
    * @param tab
    *        tabbrowser tab
    *
    * @returns {TabData} An object with the data for this tab.  If the
    * tab has not been invalidated since the last call to
-   * collectSync(aTab), the same object is returned.
+   * collect(aTab), the same object is returned.
    */
-  collectSync: function (tab) {
-    if (!tab) {
-      throw new TypeError("Expecting a tab");
-    }
-    if (TabStateCache.has(tab)) {
-      // Copy data from the persistent cache. We need to create an explicit
-      // copy of the |tabData| object so that the properties injected by
-      // |_copyFromPersistentCache| don't end up in the non-persistent cache.
-      // The persistent cache does not store "null" values, so any values that
-      // have been cleared by the frame script would not be overriden by
-      // |_copyFromPersistentCache|. These two caches are only an interim
-      // solution and the non-persistent one will go away soon.
-      let tabData = Utils.copy(TabStateCache.get(tab));
-      this._copyFromPersistentCache(tab, tabData);
-      return tabData;
-    }
-
-    let tabData = this._collectSyncUncached(tab);
-
-    if (this._tabCachingAllowed(tab)) {
-      TabStateCache.set(tab, tabData);
-    }
-
-    // Copy data from the persistent cache. We need to create an explicit
-    // copy of the |tabData| object so that the properties injected by
-    // |_copyFromPersistentCache| don't end up in the non-persistent cache.
-    // The persistent cache does not store "null" values, so any values that
-    // have been cleared by the frame script would not be overriden by
-    // |_copyFromPersistentCache|. These two caches are only an interim
-    // solution and the non-persistent one will go away soon.
-    tabData = Utils.copy(tabData);
-    this._copyFromPersistentCache(tab, tabData);
-
-    // Prevent all running asynchronous collections from filling the cache.
-    // Every asynchronous data collection started before a collectSync() call
-    // can't expect to retrieve different data than the sync call. That's why
-    // we just fill the cache with the data collected from the sync call and
-    // discard any data collected asynchronously.
-    this.dropPendingCollections(tab.linkedBrowser);
-
-    return tabData;
-  },
-
-  /**
-   * Drop any pending calls to TabState.collect. These calls will
-   * continue to run, but they won't store their results in the
-   * TabStateCache.
-   *
-   * @param browser
-   *        xul:browser
-   */
-  dropPendingCollections: function (browser) {
-    this._pendingCollections.delete(browser);
+  collect: function (tab) {
+    return this._collectBaseTabData(tab);
   },
 
   /**
    * Collect data related to a single tab, including private data.
    * Use with caution.
    *
    * @param tab
    *        tabbrowser tab
    *
    * @returns {object} An object with the data for this tab. This data is never
    *                   cached, it will always be read from the tab and thus be
    *                   up-to-date.
    */
   clone: function (tab) {
-    let options = {includePrivateData: true};
-    let tabData = this._collectSyncUncached(tab, options);
-
-    // Copy data from the persistent cache.
-    this._copyFromPersistentCache(tab, tabData, options);
-
-    return tabData;
-  },
-
-  /**
-   * Synchronously collect all session data for a tab. The
-   * TabStateCache is not consulted, and the resulting data is not put
-   * in the cache.
-   */
-  _collectSyncUncached: function (tab, options = {}) {
-    // Collect basic tab data, without session history and storage.
-    let tabData = this._collectBaseTabData(tab);
-
-    // If we don't need any other data, return what we have.
-    if (!this._tabNeedsExtraCollection(tab)) {
-      return tabData;
-    }
-
-    // In multiprocess Firefox, there is a small window of time after
-    // tab creation when we haven't received a sync handler object. In
-    // this case the tab shouldn't have any history or cookie data, so we
-    // just return the base data already collected.
-    if (!this._syncHandlers.has(tab.linkedBrowser)) {
-      return tabData;
-    }
-
-    let syncHandler = this._syncHandlers.get(tab.linkedBrowser);
-
-    let includePrivateData = options && options.includePrivateData;
-
-    let history;
-    try {
-      history = syncHandler.collectSessionHistory(includePrivateData);
-    } catch (e) {
-      // This may happen if the tab has crashed.
-      console.error(e);
-      return tabData;
-    }
-
-    tabData.entries = history.entries;
-    if ("index" in history) {
-      tabData.index = history.index;
-    }
-
-    return tabData;
-  },
-
-  /**
-   * Copy tab data for the given |tab| from the persistent cache to |tabData|.
-   *
-   * @param tab (xul:tab)
-   *        The tab belonging to the given |tabData| object.
-   * @param tabData (object)
-   *        The tab data belonging to the given |tab|.
-   * @param options (object)
-   *        {includePrivateData: true} to always include private data
-   */
-  _copyFromPersistentCache: function (tab, tabData, options = {}) {
-    let data = TabStateCache.getPersistent(tab.linkedBrowser);
-    if (!data) {
-      return;
-    }
-
-    // The caller may explicitly request to omit privacy checks.
-    let includePrivateData = options && options.includePrivateData;
-
-    for (let key of Object.keys(data)) {
-      let value = data[key];
-
-      // Filter sensitive data according to the current privacy level.
-      if (!includePrivateData) {
-        if (key === "storage") {
-          value = PrivacyFilter.filterSessionStorageData(value, tab.pinned);
-        } else if (key === "formdata") {
-          value = PrivacyFilter.filterFormData(value, tab.pinned);
-        }
-      }
-
-      if (value) {
-        tabData[key] = value;
-      }
-    }
-  },
-
-  /*
-   * Returns true if the xul:tab element is newly added (i.e., if it's
-   * showing about:blank with no history).
-   */
-  _tabIsNew: function (tab) {
-    let browser = tab.linkedBrowser;
-    return (!browser || !browser.currentURI);
-  },
-
-  /*
-   * Returns true if the xul:tab element is in the process of being
-   * restored.
-   */
-  _tabIsRestoring: function (tab) {
-    return !!tab.linkedBrowser.__SS_data;
-  },
-
-  /**
-   * This function returns true if we need to collect history, page
-   * style, and text and scroll data from the tab. Normally we do. The
-   * cases when we don't are:
-   * 1. the tab is about:blank with no history, or
-   * 2. the tab is waiting to be restored.
-   *
-   * @param tab   A xul:tab element.
-   * @returns     True if the tab is in the process of being restored.
-   */
-  _tabNeedsExtraCollection: function (tab) {
-    if (this._tabIsNew(tab)) {
-      // Tab is about:blank with no history.
-      return false;
-    }
-
-    if (this._tabIsRestoring(tab)) {
-      // Tab is waiting to be restored.
-      return false;
-    }
-
-    // Otherwise we need the extra data.
-    return true;
-  },
-
-  /*
-   * Returns true if we should cache the tabData for the given the
-   * xul:tab element.
-   */
-  _tabCachingAllowed: function (tab) {
-    if (this._tabIsNew(tab)) {
-      // No point in caching data for newly created tabs.
-      return false;
-    }
-
-    if (this._tabIsRestoring(tab)) {
-      // If the tab is being restored, we just return the data being
-      // restored. This data may be incomplete (if supplied by
-      // setBrowserState, for example), so we don't want to cache it.
-      return false;
-    }
-
-    return true;
+    return this._collectBaseTabData(tab, {includePrivateData: true});
   },
 
   /**
    * Collects basic tab data for a given tab.
    *
    * @param tab
    *        tabbrowser tab
+   * @param options (object)
+   *        {includePrivateData: true} to always include private data
    *
    * @returns {object} An object with the basic data for this tab.
    */
-  _collectBaseTabData: function (tab) {
+  _collectBaseTabData: function (tab, options) {
     let tabData = {entries: [], lastAccessed: tab.lastAccessed };
     let browser = tab.linkedBrowser;
 
     if (!browser || !browser.currentURI) {
       // can happen when calling this function right after .addTab()
       return tabData;
     }
     if (browser.__SS_data) {
@@ -502,11 +193,58 @@ let TabStateInternal = {
     let tabbrowser = tab.ownerDocument.defaultView.gBrowser;
     tabData.image = tabbrowser.getIcon(tab);
 
     if (tab.__SS_extdata)
       tabData.extData = tab.__SS_extdata;
     else if (tabData.extData)
       delete tabData.extData;
 
+    // Copy data from the tab state cache only if the tab has fully finished
+    // restoring. We don't want to overwrite data contained in __SS_data.
+    this._copyFromCache(tab, tabData, options);
+
     return tabData;
+  },
+
+  /**
+   * Copy tab data for the given |tab| from the cache to |tabData|.
+   *
+   * @param tab (xul:tab)
+   *        The tab belonging to the given |tabData| object.
+   * @param tabData (object)
+   *        The tab data belonging to the given |tab|.
+   * @param options (object)
+   *        {includePrivateData: true} to always include private data
+   */
+  _copyFromCache: function (tab, tabData, options = {}) {
+    let data = TabStateCache.get(tab.linkedBrowser);
+    if (!data) {
+      return;
+    }
+
+    // The caller may explicitly request to omit privacy checks.
+    let includePrivateData = options && options.includePrivateData;
+
+    for (let key of Object.keys(data)) {
+      let value = data[key];
+
+      // Filter sensitive data according to the current privacy level.
+      if (!includePrivateData) {
+        if (key === "storage") {
+          value = PrivacyFilter.filterSessionStorageData(value, tab.pinned);
+        } else if (key === "formdata") {
+          value = PrivacyFilter.filterFormData(value, tab.pinned);
+        }
+      }
+
+      if (key === "history") {
+        tabData.entries = value.entries;
+
+        if (value.hasOwnProperty("index")) {
+          tabData.index = value.index;
+        }
+      } else if (value) {
+        tabData[key] = value;
+      }
+    }
   }
 };
--- a/browser/components/sessionstore/src/TabStateCache.jsm
+++ b/browser/components/sessionstore/src/TabStateCache.jsm
@@ -1,383 +1,82 @@
 /* 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/. */
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["TabStateCache"];
 
-const Cu = Components.utils;
-Cu.import("resource://gre/modules/Services.jsm", this);
-Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
-
-XPCOMUtils.defineLazyModuleGetter(this, "Utils",
-  "resource:///modules/sessionstore/Utils.jsm");
-
 /**
  * A cache for tabs data.
  *
  * This cache implements a weak map from tabs (as XUL elements)
  * to tab data (as objects).
  *
  * Note that we should never cache private data, as:
  * - that data is used very seldom by SessionStore;
  * - caching private data in addition to public data is memory consuming.
  */
 this.TabStateCache = Object.freeze({
   /**
-   * Tells whether an entry is in the cache.
-   *
-   * @param {XULElement} aKey The tab or the associated browser.
-   * @return {bool} Whether there's a cached entry for the given tab.
-   */
-  has: function (aTab) {
-    return TabStateCacheInternal.has(aTab);
-  },
-
-  /**
-   * Add or replace an entry in the cache.
-   *
-   * @param {XULElement} aTab The key, which may be either a tab
-   * or the corresponding browser. The binding will disappear
-   * if the tab/browser is destroyed.
-   * @param {*} aValue The data associated to |aTab|.
-   */
-  set: function(aTab, aValue) {
-    return TabStateCacheInternal.set(aTab, aValue);
-  },
-
-  /**
-   * Return the tab data associated with a tab.
-   *
-   * @param {XULElement} aKey The tab or the associated browser.
-   *
-   * @return {*|undefined} The data if available, |undefined|
-   * otherwise.
-   */
-  get: function(aKey) {
-    return TabStateCacheInternal.get(aKey);
-  },
-
-  /**
-   * Delete the tab data associated with a tab.
-   *
-   * @param {XULElement} aKey The tab or the associated browser.
-   *
-   * Noop of there is no tab data associated with the tab.
-   */
-  delete: function(aKey) {
-    return TabStateCacheInternal.delete(aKey);
-  },
-
-  /**
-   * Delete all tab data.
-   */
-  clear: function() {
-    return TabStateCacheInternal.clear();
-  },
-
-  /**
-   * Update in place a piece of data.
-   *
-   * @param {XULElement} aKey The tab or the associated browser.
-   * If the tab/browser is not present, do nothing.
-   * @param {string} aField The field to update.
-   * @param {*} aValue The new value to place in the field.
-   */
-  updateField: function(aKey, aField, aValue) {
-    return TabStateCacheInternal.updateField(aKey, aField, aValue);
-  },
-
-  /**
-   * Remove a given field from a cached tab state.
-   *
-   * @param {XULElement} aKey The tab or the associated browser.
-   * If the tab/browser is not present, do nothing.
-   * @param {string} aField The field to remove.
-   */
-  removeField: function(aKey, aField) {
-    return TabStateCacheInternal.removeField(aKey, aField);
-  },
-
-  /**
-   * Swap cached data for two given browsers.
-   *
-   * @param {xul:browser} browser
-   *        The first of the two browsers that swapped docShells.
-   * @param {xul:browser} otherBrowser
-   *        The second of the two browsers that swapped docShells.
-   */
-  onBrowserContentsSwapped: function(browser, otherBrowser) {
-    TabStateCacheInternal.onBrowserContentsSwapped(browser, otherBrowser);
-  },
-
-  /**
-   * Retrieves persistently cached data for a given |browser|.
+   * Retrieves cached data for a given |browser|.
    *
    * @param browser (xul:browser)
    *        The browser to retrieve cached data for.
    * @return (object)
-   *         The persistently cached data stored for the given |browser|.
+   *         The cached data stored for the given |browser|.
    */
-  getPersistent: function (browser) {
-    return TabStateCacheInternal.getPersistent(browser);
+  get: function (browser) {
+    return TabStateCacheInternal.get(browser);
   },
 
   /**
-   * Updates persistently cached data for a given |browser|. This data is
-   * persistently in the sense that we never clear it, it will always be
-   * overwritten.
+   * Updates cached data for a given |browser|.
    *
    * @param browser (xul:browser)
    *        The browser belonging to the given tab data.
    * @param newData (object)
    *        The new data to be stored for the given |browser|.
    */
-  updatePersistent: function (browser, newData) {
-    TabStateCacheInternal.updatePersistent(browser, newData);
-  },
-
-  /**
-   * Total number of cache hits during the session.
-   */
-  get hits() {
-    return TabStateCacheTelemetry.hits;
-  },
-
-  /**
-   * Total number of cache misses during the session.
-   */
-  get misses() {
-    return TabStateCacheTelemetry.misses;
-  },
-
-  /**
-   * Total number of cache clears during the session.
-   */
-  get clears() {
-    return TabStateCacheTelemetry.clears;
-  },
+  update: function (browser, newData) {
+    TabStateCacheInternal.update(browser, newData);
+  }
 });
 
 let TabStateCacheInternal = {
   _data: new WeakMap(),
-  _persistentData: new WeakMap(),
-
-  /**
-   * Tells whether an entry is in the cache.
-   *
-   * @param {XULElement} aKey The tab or the associated browser.
-   * @return {bool} Whether there's a cached entry for the given tab.
-   */
-  has: function (aTab) {
-    let key = this._normalizeToBrowser(aTab);
-    return this._data.has(key);
-  },
-
-  /**
-   * Add or replace an entry in the cache.
-   *
-   * @param {XULElement} aTab The key, which may be either a tab
-   * or the corresponding browser. The binding will disappear
-   * if the tab/browser is destroyed.
-   * @param {*} aValue The data associated to |aTab|.
-   */
-  set: function(aTab, aValue) {
-    let key = this._normalizeToBrowser(aTab);
-    this._data.set(key, aValue);
-  },
-
-  /**
-   * Return the tab data associated with a tab.
-   *
-   * @param {XULElement} aKey The tab or the associated browser.
-   *
-   * @return {*|undefined} The data if available, |undefined|
-   * otherwise.
-   */
-  get: function(aKey) {
-    let key = this._normalizeToBrowser(aKey);
-    let result = this._data.get(key);
-    TabStateCacheTelemetry.recordAccess(!!result);
-    return result;
-  },
-
-  /**
-   * Delete the tab data associated with a tab.
-   *
-   * @param {XULElement} aKey The tab or the associated browser.
-   *
-   * Noop of there is no tab data associated with the tab.
-   */
-  delete: function(aKey) {
-    let key = this._normalizeToBrowser(aKey);
-    this._data.delete(key);
-  },
 
   /**
-   * Delete all tab data.
-   */
-  clear: function() {
-    TabStateCacheTelemetry.recordClear();
-    this._data.clear();
-  },
-
-  /**
-   * Update in place a piece of data.
-   *
-   * @param {XULElement} aKey The tab or the associated browser.
-   * If the tab/browser is not present, do nothing.
-   * @param {string} aField The field to update.
-   * @param {*} aValue The new value to place in the field.
-   */
-  updateField: function(aKey, aField, aValue) {
-    let key = this._normalizeToBrowser(aKey);
-    let data = this._data.get(key);
-    if (data) {
-      data[aField] = aValue;
-    }
-  },
-
-  /**
-   * Remove a given field from a cached tab state.
-   *
-   * @param {XULElement} aKey The tab or the associated browser.
-   * If the tab/browser is not present, do nothing.
-   * @param {string} aField The field to remove.
-   */
-  removeField: function(aKey, aField) {
-    let key = this._normalizeToBrowser(aKey);
-    let data = this._data.get(key);
-    if (data && aField in data) {
-      delete data[aField];
-    }
-  },
-
-  /**
-   * Swap cached data for two given browsers.
-   *
-   * @param {xul:browser} browser
-   *        The first of the two browsers that swapped docShells.
-   * @param {xul:browser} otherBrowser
-   *        The second of the two browsers that swapped docShells.
-   */
-  onBrowserContentsSwapped: function(browser, otherBrowser) {
-    // Swap data stored per-browser.
-    [this._data, this._persistentData]
-      .forEach(map => Utils.swapMapEntries(map, browser, otherBrowser));
-  },
-
-  /**
-   * Retrieves persistently cached data for a given |browser|.
+   * Retrieves cached data for a given |browser|.
    *
    * @param browser (xul:browser)
    *        The browser to retrieve cached data for.
    * @return (object)
-   *         The persistently cached data stored for the given |browser|.
+   *         The cached data stored for the given |browser|.
    */
-  getPersistent: function (browser) {
-    return this._persistentData.get(browser);
+  get: function (browser) {
+    return this._data.get(browser.permanentKey);
   },
 
   /**
-   * Updates persistently cached data for a given |browser|. This data is
-   * persistent in the sense that we never clear it, it will always be
-   * overwritten.
+   * Updates cached data for a given |browser|.
    *
    * @param browser (xul:browser)
    *        The browser belonging to the given tab data.
    * @param newData (object)
    *        The new data to be stored for the given |browser|.
    */
-  updatePersistent: function (browser, newData) {
-    let data = this._persistentData.get(browser) || {};
+  update: function (browser, newData) {
+    let data = this._data.get(browser.permanentKey) || {};
 
     for (let key of Object.keys(newData)) {
       let value = newData[key];
       if (value === null) {
         delete data[key];
       } else {
         data[key] = value;
       }
     }
 
-    this._persistentData.set(browser, data);
-  },
-
-  _normalizeToBrowser: function(aKey) {
-    let nodeName = aKey.localName;
-    if (nodeName == "tab") {
-      return aKey.linkedBrowser;
-    }
-    if (nodeName == "browser") {
-      return aKey;
-    }
-    throw new TypeError("Key is neither a tab nor a browser: " + nodeName);
+    this._data.set(browser.permanentKey, data);
   }
 };
-
-let TabStateCacheTelemetry = {
-  // Total number of hits during the session
-  hits: 0,
-  // Total number of misses during the session
-  misses: 0,
-  // Total number of clears during the session
-  clears: 0,
-  // |true| once we have been initialized
-  _initialized: false,
-
-  /**
-   * Record a cache access.
-   *
-   * @param {boolean} isHit If |true|, the access was a hit, otherwise
-   * a miss.
-   */
-  recordAccess: function(isHit) {
-    this._init();
-    if (isHit) {
-      ++this.hits;
-    } else {
-      ++this.misses;
-    }
-  },
-
-  /**
-   * Record a cache clear
-   */
-  recordClear: function() {
-    this._init();
-    ++this.clears;
-  },
-
-  /**
-   * Initialize the telemetry.
-   */
-  _init: function() {
-    if (this._initialized) {
-      // Avoid double initialization
-      return;
-    }
-    this._initialized = true;
-    Services.obs.addObserver(this, "profile-before-change", false);
-  },
-
-  observe: function() {
-    Services.obs.removeObserver(this, "profile-before-change");
-
-    // Record hit/miss rate
-    let accesses = this.hits + this.misses;
-    if (accesses == 0) {
-      return;
-    }
-
-    this._fillHistogram("HIT_RATE", this.hits, accesses);
-    this._fillHistogram("CLEAR_RATIO", this.clears, accesses);
-  },
-
-  _fillHistogram: function(suffix, positive, total) {
-    let PREFIX = "FX_SESSION_RESTORE_TABSTATECACHE_";
-    let histo = Services.telemetry.getHistogramById(PREFIX + suffix);
-    let rate = Math.floor( ( positive * 100 ) / total );
-    histo.add(rate);
-  }
-};
--- a/browser/components/sessionstore/src/Utils.jsm
+++ b/browser/components/sessionstore/src/Utils.jsm
@@ -36,44 +36,10 @@ this.Utils = Object.freeze({
       return false;
 
     if (host == domain)
       return true;
 
     let prevChar = host[index - 1];
     return (index == (host.length - domain.length)) &&
            (prevChar == "." || prevChar == "/");
-  },
-
-  swapMapEntries: function (map, key, otherKey) {
-    // Make sure that one or the other of these has an entry in the map,
-    // and let it be |key|.
-    if (!map.has(key)) {
-      [key, otherKey] = [otherKey, key];
-      if (!map.has(key)) {
-        return;
-      }
-    }
-
-    // At this point, |key| is guaranteed to have an entry,
-    // although |otherKey| may not. Perform the swap.
-    let value = map.get(key);
-    if (map.has(otherKey)) {
-      let otherValue = map.get(otherKey);
-      map.set(key, otherValue);
-      map.set(otherKey, value);
-    } else {
-      map.set(otherKey, value);
-      map.delete(key);
-    }
-  },
-
-  // Copies all properties of a given object to a new one and returns it.
-  copy: function (from) {
-    let to = {};
-
-    for (let key of Object.keys(from)) {
-      to[key] = from[key];
-    }
-
-    return to;
   }
 });
--- a/browser/components/sessionstore/src/moz.build
+++ b/browser/components/sessionstore/src/moz.build
@@ -13,17 +13,16 @@ EXTRA_COMPONENTS += [
 JS_MODULES_PATH = 'modules/sessionstore'
 
 EXTRA_JS_MODULES = [
     'ContentRestore.jsm',
     'DocShellCapabilities.jsm',
     'FormData.jsm',
     'FrameTree.jsm',
     'GlobalState.jsm',
-    'Messenger.jsm',
     'PageStyle.jsm',
     'PrivacyFilter.jsm',
     'PrivacyLevel.jsm',
     'RecentlyClosedTabsAndWindowsMenuUtils.jsm',
     'ScrollPosition.jsm',
     'SessionCookies.jsm',
     'SessionFile.jsm',
     'SessionHistory.jsm',
--- a/browser/components/sessionstore/test/browser.ini
+++ b/browser/components/sessionstore/test/browser.ini
@@ -59,23 +59,22 @@ support-files =
 [browser_form_restore_events.js]
 [browser_formdata.js]
 [browser_formdata_format.js]
 [browser_formdata_xpath.js]
 [browser_frametree.js]
 [browser_global_store.js]
 [browser_label_and_icon.js]
 [browser_merge_closed_tabs.js]
-[browser_pageshow.js]
 [browser_pageStyle.js]
 [browser_privatetabs.js]
 [browser_scrollPositions.js]
+[browser_sessionHistory.js]
 [browser_sessionStorage.js]
 [browser_swapDocShells.js]
-[browser_tabStateCache.js]
 [browser_telemetry.js]
 [browser_upgrade_backup.js]
 [browser_windowRestore_perwindowpb.js]
 [browser_248970_b_perwindowpb.js]
 # Disabled because of leaks.
 # Re-enabling and rewriting this test is tracked in bug 936919.
 skip-if = true
 [browser_339445.js]
@@ -144,17 +143,16 @@ skip-if = true
 [browser_599909.js]
 [browser_600545.js]
 [browser_601955.js]
 [browser_607016.js]
 [browser_615394-SSWindowState_events.js]
 [browser_618151.js]
 [browser_623779.js]
 [browser_624727.js]
-[browser_625257.js]
 [browser_628270.js]
 [browser_635418.js]
 [browser_636279.js]
 [browser_637020.js]
 [browser_637020_slow.sjs]
 [browser_644409-scratchpads.js]
 [browser_645428.js]
 [browser_659591.js]
--- a/browser/components/sessionstore/test/browser_393716.js
+++ b/browser/components/sessionstore/test/browser_393716.js
@@ -13,16 +13,17 @@ add_task(function test_set_tabstate() {
   let value = "Unique value: " + Math.random();
 
   // create a new tab
   let tab = gBrowser.addTab(URL);
   ss.setTabValue(tab, key, value);
   yield promiseBrowserLoaded(tab.linkedBrowser);
 
   // get the tab's state
+  SyncHandlers.get(tab.linkedBrowser).flush();
   let state = ss.getTabState(tab);
   ok(state, "get the tab's state");
 
   // verify the tab state's integrity
   state = JSON.parse(state);
   ok(state instanceof Object && state.entries instanceof Array && state.entries.length > 0,
      "state object seems valid");
   ok(state.entries.length == 1 && state.entries[0].url == URL,
--- a/browser/components/sessionstore/test/browser_423132.js
+++ b/browser/components/sessionstore/test/browser_423132.js
@@ -24,16 +24,17 @@ function test() {
   // make sure sessionstore saves the cookie data, then close the window
   newWin.addEventListener("load", function (aEvent) {
     newWin.removeEventListener("load", arguments.callee, false);
 
     newWin.gBrowser.loadURI(testURL, null, null);
 
     whenBrowserLoaded(newWin.gBrowser.selectedBrowser, function() {
       // get the sessionstore state for the window
+      SyncHandlers.get(newWin.gBrowser.selectedBrowser).flush();
       let state = ss.getWindowState(newWin);
 
       // verify our cookie got set during pageload
       let e = cs.enumerator;
       let cookie;
       let i = 0;
       while (e.hasMoreElements()) {
         cookie = e.getNext().QueryInterface(Ci.nsICookie);
--- a/browser/components/sessionstore/test/browser_447951.js
+++ b/browser/components/sessionstore/test/browser_447951.js
@@ -13,28 +13,26 @@ function test() {
   whenBrowserLoaded(tab.linkedBrowser, function() {
     let tabState = { entries: [] };
     let max_entries = gPrefService.getIntPref("browser.sessionhistory.max_entries");
     for (let i = 0; i < max_entries; i++)
       tabState.entries.push({ url: baseURL + i });
 
     ss.setTabState(tab, JSON.stringify(tabState));
     whenTabRestored(tab, function() {
+      SyncHandlers.get(tab.linkedBrowser).flush();
       tabState = JSON.parse(ss.getTabState(tab));
       is(tabState.entries.length, max_entries, "session history filled to the limit");
       is(tabState.entries[0].url, baseURL + 0, "... but not more");
 
       // visit yet another anchor (appending it to session history)
-      let doc = tab.linkedBrowser.contentDocument;
-      let event = doc.createEvent("MouseEvents");
-      event.initMouseEvent("click", true, true, doc.defaultView, 1,
-                           0, 0, 0, 0, false, false, false, false, 0, null);
-      doc.querySelector("a").dispatchEvent(event);
+      tab.linkedBrowser.contentDocument.querySelector("a").click();
 
       function check() {
+        SyncHandlers.get(tab.linkedBrowser).flush();
         tabState = JSON.parse(ss.getTabState(tab));
         if (tabState.entries[tabState.entries.length - 1].url != baseURL + "end") {
           // It may take a few passes through the event loop before we
           // get the right URL.
           executeSoon(check);
           return;
         }
 
--- a/browser/components/sessionstore/test/browser_500328.js
+++ b/browser/components/sessionstore/test/browser_500328.js
@@ -84,16 +84,17 @@ function test() {
       //   testURL        (state object: {obj1:1})
       //   testURL?page2  (state object: {obj3:/^a$/})  <-- newest
       let contentWindow = tab.linkedBrowser.contentWindow;
       let history = contentWindow.history;
       history.pushState({obj1:1}, "title-obj1");
       history.pushState({obj2:2}, "title-obj2", "?page2");
       history.replaceState({obj3:/^a$/}, "title-obj3");
 
+      SyncHandlers.get(tab.linkedBrowser).flush();
       let state = ss.getTabState(tab);
       gBrowser.removeTab(tab);
 
       // Restore the state into a new tab.  Things don't work well when we
       // restore into the old tab, but that's not a real use case anyway.
       let tab2 = gBrowser.addTab("about:blank");
       ss.setTabState(tab2, state, true);
 
deleted file mode 100644
--- a/browser/components/sessionstore/test/browser_625257.js
+++ /dev/null
@@ -1,79 +0,0 @@
-/* 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/. */
-
-let Scope = {};
-Cu.import("resource://gre/modules/Task.jsm", Scope);
-Cu.import("resource://gre/modules/Promise.jsm", Scope);
-let {Task, Promise} = Scope;
-
-
-// This tests that a tab which is closed while loading is not lost.
-// Specifically, that session store does not rely on an invalid cache when
-// constructing data for a tab which is loading.
-
-// This test steps through the following parts:
-//  1. Tab has been created is loading URI_TO_LOAD.
-//  2. Before URI_TO_LOAD finishes loading, browser.currentURI has changed and
-//     tab is scheduled to be removed.
-//  3. After the tab has been closed, undoCloseTab() has been called and the tab
-//     should fully load.
-const URI_TO_LOAD = "about:mozilla";
-
-function waitForLoadStarted(aTab) {
-  return promiseContentMessage(aTab.linkedBrowser, "SessionStore:loadStart");
-}
-
-function waitForTabLoaded(aTab) {
-  let deferred = Promise.defer();
-  whenBrowserLoaded(aTab.linkedBrowser, deferred.resolve);
-  return deferred.promise;
-}
-
-function waitForTabClosed() {
-  let deferred = Promise.defer();
-  let observer = function() {
-    gBrowser.tabContainer.removeEventListener("TabClose", observer, true);
-    deferred.resolve();
-  };
-  gBrowser.tabContainer.addEventListener("TabClose", observer, true);
-  return deferred.promise;
-}
-
-function test() {
-  waitForExplicitFinish();
-
-  Task.spawn(function() {
-    try {
-      // Open a new tab
-      let tab = gBrowser.addTab("about:blank");
-      yield waitForTabLoaded(tab);
-
-      // Trigger a save state, to initialize any caches
-      ss.getBrowserState();
-
-      is(gBrowser.tabs[1], tab, "newly created tab should exist by now");
-
-      // Start a load and interrupt it by closing the tab
-      tab.linkedBrowser.loadURI(URI_TO_LOAD);
-      yield waitForLoadStarted(tab);
-
-      let tabClosing = waitForTabClosed();
-      gBrowser.removeTab(tab);
-      info("Now waiting for TabClose to close");
-      yield tabClosing;
-
-      // Undo the tab, ensure that it proceeds with loading
-      tab = ss.undoCloseTab(window, 0);
-      yield waitForTabLoaded(tab);
-      is(tab.linkedBrowser.currentURI.spec, URI_TO_LOAD, "loading proceeded as expected");
-
-      gBrowser.removeTab(tab);
-
-      executeSoon(finish);
-    } catch (ex) {
-      ok(false, ex);
-      info(ex.stack);
-    }
-  });
-}
--- a/browser/components/sessionstore/test/browser_705597.js
+++ b/browser/components/sessionstore/test/browser_705597.js
@@ -22,16 +22,17 @@ function test() {
 
     let sessionHistory = browser.sessionHistory;
     let entry = sessionHistory.getEntryAtIndex(0, false);
     entry.QueryInterface(Ci.nsISHContainer);
 
     whenChildCount(entry, 1, function () {
       whenChildCount(entry, 2, function () {
         whenBrowserLoaded(browser, function () {
+          SyncHandlers.get(browser).flush();
           let {entries} = JSON.parse(ss.getTabState(tab));
           is(entries.length, 1, "tab has one history entry");
           ok(!entries[0].children, "history entry has no subframes");
 
           // Make sure that we reset the state.
           let blankState = { windows: [{ tabs: [{ entries: [{ url: "about:blank" }] }]}]};
           waitForBrowserState(blankState, finish);
         });
--- a/browser/components/sessionstore/test/browser_dying_cache.js
+++ b/browser/components/sessionstore/test/browser_dying_cache.js
@@ -19,16 +19,17 @@ function runTests() {
   // Load some URL in the current tab.
   let flags = Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY;
   win.gBrowser.selectedBrowser.loadURIWithFlags("about:robots", flags);
   yield whenBrowserLoaded(win.gBrowser.selectedBrowser);
 
   // Open a second tab and close the first one.
   let tab = win.gBrowser.addTab("about:mozilla");
   yield whenBrowserLoaded(tab.linkedBrowser);
+  SyncHandlers.get(tab.linkedBrowser).flush();
   win.gBrowser.removeTab(win.gBrowser.tabs[0]);
 
   // Make sure our window is still tracked by sessionstore
   // and the window state is as expected.
   ok("__SSi" in win, "window is being tracked by sessionstore");
   ss.setWindowValue(win, "foo", "bar");
   checkWindowState(win);
 
deleted file mode 100644
--- a/browser/components/sessionstore/test/browser_pageshow.js
+++ /dev/null
@@ -1,87 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-function test() {
-  TestRunner.run();
-}
-
-/**
- * This test ensures that loading a page from bfcache (by going back or forward
- * in history) marks the window as dirty and causes data about the tab that
- * changed to be re-collected.
- *
- * We will do this by creating a tab with two history entries and going back
- * to the first. When we now request the current browser state from the
- * session store service the first history entry must be selected.
- */
-
-const URL = "data:text/html,<h1>first</h1>";
-const URL2 = "data:text/html,<h1>second</h1>";
-
-function runTests() {
-  // Create a dummy window that is regarded as active. We need to do this
-  // because we always collect data for tabs of active windows no matter if
-  // the window is dirty or not.
-  let win = OpenBrowserWindow();
-  yield whenDelayedStartupFinished(win, next);
-
-  // Create a tab with two history entries.
-  let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
-  yield loadURI(URL);
-  yield loadURI(URL2);
-
-  // All windows currently marked as dirty will be written to disk
-  // and thus marked clean afterwards.
-  yield forceWriteState();
-
-  // Go back to 'about:robots' - which is loaded from the bfcache and thus
-  // will not fire a 'load' event but a 'pageshow' event with persisted=true.
-  waitForPageShow();
-  yield gBrowser.selectedBrowser.goBack();
-  is(tab.linkedBrowser.currentURI.spec, URL, "correct url after going back");
-
-  // If by receiving the 'pageshow' event the first window has correctly
-  // been marked as dirty, getBrowserState() should return the tab we created
-  // with the right history entry (about:robots) selected.
-  let state = JSON.parse(ss.getBrowserState());
-  is(state.windows[0].tabs[1].index, 1, "first history entry is selected");
-
-  // Clean up after ourselves.
-  gBrowser.removeTab(tab);
-  win.close();
-}
-
-function forceWriteState() {
-  const PREF = "browser.sessionstore.interval";
-  const TOPIC = "sessionstore-state-write";
-
-  Services.obs.addObserver(function observe() {
-    Services.obs.removeObserver(observe, TOPIC);
-    Services.prefs.clearUserPref(PREF);
-    executeSoon(next);
-  }, TOPIC, false);
-
-  Services.prefs.setIntPref(PREF, 0);
-}
-
-function loadURI(aURI) {
-  let browser = gBrowser.selectedBrowser;
-  waitForLoad(browser);
-  browser.loadURI(aURI);
-}
-
-function waitForLoad(aElement) {
-  aElement.addEventListener("load", function onLoad() {
-    aElement.removeEventListener("load", onLoad, true);
-    executeSoon(next);
-  }, true);
-}
-
-function waitForPageShow() {
-  let mm = gBrowser.selectedBrowser.messageManager;
-
-  mm.addMessageListener("SessionStore:pageshow", function onPageShow() {
-    mm.removeMessageListener("SessionStore:pageshow", onPageShow);
-    executeSoon(next);
-  });
-}
--- a/browser/components/sessionstore/test/browser_privatetabs.js
+++ b/browser/components/sessionstore/test/browser_privatetabs.js
@@ -25,16 +25,17 @@ add_task(function() {
     info("Setting up private tab");
     tab2 = gBrowser.addTab();
     yield promiseBrowserLoaded(tab2.linkedBrowser);
     yield setUsePrivateBrowsing(tab2.linkedBrowser, true);
     tab2.linkedBrowser.loadURI(URL_PRIVATE);
     yield promiseBrowserLoaded(tab2.linkedBrowser);
 
     info("Flush to make sure chrome received all data.");
+    SyncHandlers.get(tab1.linkedBrowser).flush();
     SyncHandlers.get(tab2.linkedBrowser).flush();
 
     info("Checking out state");
     yield SessionSaver.run();
     let path = OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js");
     let data = yield OS.File.read(path);
     let state = new TextDecoder().decode(data);
     info("State: " + state);
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_sessionHistory.js
@@ -0,0 +1,167 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Ensure that starting a load invalidates shistory.
+ */
+add_task(function test_load_start() {
+  // Create a new tab.
+  let tab = gBrowser.addTab("about:blank");
+  let browser = tab.linkedBrowser;
+  yield promiseBrowserLoaded(browser);
+
+  // Load a new URI but remove the tab before it has finished loading.
+  browser.loadURI("about:mozilla");
+  yield promiseContentMessage(browser, "ss-test:onFrameTreeReset");
+  gBrowser.removeTab(tab);
+
+  // Undo close the tab.
+  tab = ss.undoCloseTab(window, 0);
+  browser = tab.linkedBrowser;
+  yield promiseBrowserLoaded(browser);
+
+  // Check that the correct URL was restored.
+  is(browser.currentURI.spec, "about:mozilla", "url is correct");
+
+  // Cleanup.
+  gBrowser.removeTab(tab);
+});
+
+/**
+ * Ensure that purging shistory invalidates.
+ */
+add_task(function test_purge() {
+  // Create a new tab.
+  let tab = gBrowser.addTab("about:mozilla");
+  let browser = tab.linkedBrowser;
+  yield promiseBrowserLoaded(browser);
+
+  // Create a second shistory entry.
+  browser.loadURI("about:robots");
+  yield promiseBrowserLoaded(browser);
+
+  // Check that we now have two shistory entries.
+  SyncHandlers.get(browser).flush();
+  let {entries} = JSON.parse(ss.getTabState(tab));
+  is(entries.length, 2, "there are two shistory entries");
+
+  // Purge session history.
+  yield sendMessage(browser, "ss-test:purgeSessionHistory");
+
+  // Check that we are left with a single shistory entry.
+  SyncHandlers.get(browser).flush();
+  let {entries} = JSON.parse(ss.getTabState(tab));
+  is(entries.length, 1, "there is one shistory entry");
+
+  // Cleanup.
+  gBrowser.removeTab(tab);
+});
+
+/**
+ * Ensure that anchor navigation invalidates shistory.
+ */
+add_task(function test_hashchange() {
+  const URL = "data:text/html;charset=utf-8,<a id=a href=%23>clickme</a>";
+
+  // Create a new tab.
+  let tab = gBrowser.addTab(URL);
+  let browser = tab.linkedBrowser;
+  yield promiseBrowserLoaded(browser);
+
+  // Check that we start with a single shistory entry.
+  SyncHandlers.get(browser).flush();
+  let {entries} = JSON.parse(ss.getTabState(tab));
+  is(entries.length, 1, "there is one shistory entry");
+
+  // Click the link and wait for a hashchange event.
+  browser.messageManager.sendAsyncMessage("ss-test:click", {id: "a"});
+  yield promiseContentMessage(browser, "ss-test:hashchange");
+
+  // Check that we now have two shistory entries.
+  SyncHandlers.get(browser).flush();
+  let {entries} = JSON.parse(ss.getTabState(tab));
+  is(entries.length, 2, "there are two shistory entries");
+
+  // Cleanup.
+  gBrowser.removeTab(tab);
+});
+
+/**
+ * Ensure that loading pages from the bfcache invalidates shistory.
+ */
+add_task(function test_pageshow() {
+  const URL = "data:text/html;charset=utf-8,<h1>first</h1>";
+  const URL2 = "data:text/html;charset=utf-8,<h1>second</h1>";
+
+  // Create a new tab.
+  let tab = gBrowser.addTab(URL);
+  let browser = tab.linkedBrowser;
+  yield promiseBrowserLoaded(browser);
+
+  // Create a second shistory entry.
+  browser.loadURI(URL2);
+  yield promiseBrowserLoaded(browser);
+
+  // Go back to the previous url which is loaded from the bfcache.
+  browser.goBack();
+  yield promiseContentMessage(browser, "ss-test:onFrameTreeCollected");
+  is(browser.currentURI.spec, URL, "correct url after going back");
+
+  // Check that loading from bfcache did invalidate shistory.
+  SyncHandlers.get(browser).flush();
+  let {index} = JSON.parse(ss.getTabState(tab));
+  is(index, 1, "first history entry is selected");
+
+  // Cleanup.
+  gBrowser.removeTab(tab);
+});
+
+/**
+ * Ensure that subframe navigation invalidates shistory.
+ */
+add_task(function test_subframes() {
+  const URL = "data:text/html;charset=utf-8," +
+              "<iframe src=http%3A//example.com/ name=t></iframe>" +
+              "<a id=a1 href=http%3A//example.com/1 target=t>clickme</a>" +
+              "<a id=a2 href=http%3A//example.com/%23 target=t>clickme</a>";
+
+  // Create a new tab.
+  let tab = gBrowser.addTab(URL);
+  let browser = tab.linkedBrowser;
+  yield promiseBrowserLoaded(browser);
+
+  // Check that we have a single shistory entry.
+  SyncHandlers.get(browser).flush();
+  let {entries} = JSON.parse(ss.getTabState(tab));
+  is(entries.length, 1, "there is one shistory entry");
+  is(entries[0].children.length, 1, "the entry has one child");
+
+  // Navigate the subframe.
+  browser.messageManager.sendAsyncMessage("ss-test:click", {id: "a1"});
+  yield promiseBrowserLoaded(browser, false /* don't ignore subframes */);
+
+  // Check shistory.
+  SyncHandlers.get(browser).flush();
+  let {entries} = JSON.parse(ss.getTabState(tab));
+  is(entries.length, 2, "there now are two shistory entries");
+  is(entries[1].children.length, 1, "the second entry has one child");
+
+  // Go back in history.
+  browser.goBack();
+  yield promiseBrowserLoaded(browser, false /* don't ignore subframes */);
+
+  // Navigate the subframe again.
+  browser.messageManager.sendAsyncMessage("ss-test:click", {id: "a2"});
+  yield promiseContentMessage(browser, "ss-test:hashchange");
+
+  // Check shistory.
+  SyncHandlers.get(browser).flush();
+  let {entries} = JSON.parse(ss.getTabState(tab));
+  is(entries.length, 2, "there now are two shistory entries");
+  is(entries[1].children.length, 1, "the second entry has one child");
+
+  // Cleanup.
+  gBrowser.removeTab(tab);
+});
deleted file mode 100644
--- a/browser/components/sessionstore/test/browser_tabStateCache.js
+++ /dev/null
@@ -1,139 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-let wrapper = {};
-Cu.import("resource:///modules/sessionstore/TabStateCache.jsm", wrapper);
-let {TabStateCache} = wrapper;
-
-// The number of tabs that are present in the browser but that we're not dealing
-// with. This should be one (for an empty about:blank), but let's not make this
-// a magic number.
-let numberOfUntrackedTabs;
-
-// Arbitrary URL prefix, used to generate the URL of pages we visit
-const URL_PREFIX = "http://example.org:80/";
-
-/**
- * Check tab state cache telemetry statistics before and after an operation.
- *
- * @param {function} f The operation being measured. If it returns a promise,
- * we wait until the promise is resolved before proceeding.
- * @return {promise}
- */
-function getTelemetryDelta(f) {
-  return Task.spawn(function() {
-    let KEYS = ["hits", "misses", "clears"];
-    let old = {};
-    for (let key of KEYS) {
-      old[key] = TabStateCache[key];
-    }
-    yield f();
-    let result = {};
-    for (let key of KEYS) {
-      result[key] = TabStateCache[key] - old[key];
-    }
-    ok(result.hits >= 0, "Sanity check: hits have not decreased");
-    ok(result.misses >= 0, "Sanity check: misses have not decreased");
-    ok(result.clears >= 0, "Sanity check: clears have not decreased");
-    throw new Task.Result(result);
-  });
-}
-
-add_task(function init() {
-  // Start with an empty cache
-  closeAllButPrimaryWindow();
-  TabStateCache.clear();
-  numberOfUntrackedTabs = gBrowser.tabs.length;
-  info("Starting with " + numberOfUntrackedTabs + " tabs");
-});
-
-add_task(function add_remove() {
-  info("Adding the first tab");
-  // Initialize one tab, save to initialize cache
-  let tab1 = gBrowser.addTab(URL_PREFIX + "?tab1");
-  yield promiseBrowserLoaded(tab1.linkedBrowser);
-  yield getTelemetryDelta(forceSaveState);
-
-  // Save/collect again a few times, ensure that we always hit
-  info("Save/collect a few times with one tab");
-  for (let collector of [forceSaveState, ss.getBrowserState]) {
-    for (let i = 0; i < 5; ++i) {
-      let PREFIX = "Trivial test " + i + " using " + collector.name + ": ";
-      let delta = yield getTelemetryDelta(collector);
-      is(delta.hits, numberOfUntrackedTabs + 1, PREFIX + " has at least one hit " + delta.hits);
-      is(delta.misses, 0, PREFIX + " has no miss");
-      is(delta.clears, 0, PREFIX + " has no clear");
-    }
-  }
-
-  // Add a second tab, ensure that we have both hits and misses
-  info("Adding the second tab");
-  let tab2 = gBrowser.addTab(URL_PREFIX + "?tab2");
-  yield promiseBrowserLoaded(tab2.linkedBrowser);
-
-  let PREFIX = "Adding second tab: ";
-  ok(!TabStateCache.has(tab2), PREFIX + " starts out of the cache");
-  let delta = yield getTelemetryDelta(forceSaveState);
-  is(delta.hits, numberOfUntrackedTabs + 2, PREFIX + " we hit all tabs, thanks to prefetching");
-  is(delta.misses, 0, PREFIX + " we missed no tabs, thanks to prefetching");
-  is(delta.clears, 0, PREFIX + " has no clear");
-
-  // Save/collect again a few times, ensure that we always hit
-  info("Save/collect a few times with two tabs");
-  for (let collector of [forceSaveState, ss.getBrowserState]) {
-    for (let i = 0; i < 5; ++i) {
-      let PREFIX = "With two tabs " + i + " using " + collector.name + ": ";
-      let delta = yield getTelemetryDelta(collector);
-      is(delta.hits, numberOfUntrackedTabs + 2, PREFIX + " both tabs hit");
-      is(delta.misses, 0, PREFIX + " has no miss");
-      is(delta.clears, 0, PREFIX + " has no clear");
-    }
-  }
-
-  info("Removing second tab");
-  gBrowser.removeTab(tab2);
-  PREFIX = "Removing second tab: ";
-  delta = yield getTelemetryDelta(forceSaveState);
-  is(delta.hits, numberOfUntrackedTabs + 1, PREFIX + " we hit for one tab");
-  is(delta.misses, 0, PREFIX + " has no miss");
-  is(delta.clears, 0, PREFIX + " has no clear");
-
-  info("Removing first tab");
-  gBrowser.removeTab(tab1);
-});
-
-add_task(function browsing() {
-  info("Opening first browsing tab");
-  let tab1 = gBrowser.addTab(URL_PREFIX + "?do_not_move_from_here");
-  let browser1 = tab1.linkedBrowser;
-  yield promiseBrowserLoaded(browser1);
-  yield forceSaveState();
-
-  info("Opening second browsing tab");
-  let tab2 = gBrowser.addTab(URL_PREFIX + "?start_browsing_from_here");
-  let browser2 = tab2.linkedBrowser;
-  yield promiseBrowserLoaded(browser2);
-
-  for (let i = 0; i < 4; ++i) {
-    let url = URL_PREFIX + "?browsing" + i; // Arbitrary url, easy to recognize
-    let PREFIX = "Browsing to " + url;
-    info(PREFIX);
-    let delta = yield getTelemetryDelta(function() {
-      return Task.spawn(function() {
-        // Move to new URI then save session
-        let promise = promiseBrowserLoaded(browser2);
-        browser2.loadURI(url);
-        yield promise;
-        ok(!TabStateCache.has(browser2), PREFIX + " starts out of the cache");
-        yield forceSaveState();
-      });
-    });
-    is(delta.hits, numberOfUntrackedTabs + 2, PREFIX + " has all hits, thanks to prefetching");
-    is(delta.misses, 0, PREFIX + " has no miss, thanks to prefetching");
-    is(delta.clears, 0, PREFIX + " has no clear");
-  }
-  gBrowser.removeTab(tab2);
-  gBrowser.removeTab(tab1);
-});
--- a/browser/components/sessionstore/test/browser_telemetry.js
+++ b/browser/components/sessionstore/test/browser_telemetry.js
@@ -1,16 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 
 let tmp = {};
 Cu.import("resource:///modules/sessionstore/SessionFile.jsm", tmp);
-Cu.import("resource:///modules/sessionstore/TabStateCache.jsm", tmp);
-let {SessionFile, TabStateCache} = tmp;
+let {SessionFile} = tmp;
 
 // Shortcuts for histogram names
 let Keys = {};
 for (let k of ["HISTORY", "FORMDATA", "OPEN_WINDOWS", "CLOSED_WINDOWS", "CLOSED_TABS_IN_OPEN_WINDOWS", "DOM_STORAGE"]) {
   Keys[k] = "FX_SESSION_RESTORE_TOTAL_" + k + "_SIZE_BYTES";
 }
 
 function lt(a, b, message) {
@@ -58,17 +57,18 @@ add_task(function history() {
   let KEY = Keys.HISTORY;
   let tab = gBrowser.addTab("http://example.org:80/?");
   yield promiseBrowserLoaded(tab.linkedBrowser);
   try {
     SyncHandlers.get(tab.linkedBrowser).flush();
     let statistics = yield promiseStats();
 
     info("Now changing history");
-    tab.linkedBrowser.contentWindow.history.pushState({foo:1}, "ref");
+    tab.linkedBrowser.loadURI("http://example.org:80/1");
+    yield promiseBrowserLoaded(tab.linkedBrowser);
     SyncHandlers.get(tab.linkedBrowser).flush();
     let statistics2 = yield promiseStats();
 
     // We have changed history, so it must have increased
     isnot(statistics[KEY], undefined, "Key was defined");
     isnot(statistics2[KEY], undefined, "Key is still defined");
     gt(statistics2[KEY], statistics[KEY], "The total size of HISTORY has increased");
 
@@ -219,17 +219,16 @@ add_task(function formdata() {
   try {
     SyncHandlers.get(tab.linkedBrowser).flush();
     let statistics = yield promiseStats();
 
     info("Now changing form data");
 
     yield setInputValue(tab.linkedBrowser, {id: "input", value: "This is some form data"});
     SyncHandlers.get(tab.linkedBrowser).flush();
-    TabStateCache.delete(tab.linkedBrowser);
 
     let statistics2 = yield promiseStats();
 
     isnot(statistics[KEY], undefined, "Key was defined");
     isnot(statistics2[KEY], undefined, "Key is still defined");
     gt(statistics2[KEY], statistics[KEY], "The total size of FORMDATA has increased");
 
     // Almost nothing else should
--- a/browser/components/sessionstore/test/content.js
+++ b/browser/components/sessionstore/test/content.js
@@ -20,16 +20,20 @@ gFrameTree.addObserver({
   }
 });
 
 /**
  * This frame script is only loaded for sessionstore mochitests. It enables us
  * to modify and query docShell data when running with multiple processes.
  */
 
+addEventListener("hashchange", function () {
+  sendAsyncMessage("ss-test:hashchange");
+});
+
 addEventListener("MozStorageChanged", function () {
   sendSyncMessage("ss-test:MozStorageChanged");
 });
 
 addMessageListener("ss-test:modifySessionStorage", function (msg) {
   for (let key of Object.keys(msg.data)) {
     content.sessionStorage[key] = msg.data[key];
   }
@@ -41,16 +45,21 @@ addMessageListener("ss-test:modifySessio
   }
 });
 
 addMessageListener("ss-test:purgeDomainData", function ({data: domain}) {
   Services.obs.notifyObservers(null, "browser:purge-domain-data", domain);
   content.setTimeout(() => sendAsyncMessage("ss-test:purgeDomainData"));
 });
 
+addMessageListener("ss-test:purgeSessionHistory", function () {
+  Services.obs.notifyObservers(null, "browser:purge-session-history", "");
+  content.setTimeout(() => sendAsyncMessage("ss-test:purgeSessionHistory"));
+});
+
 addMessageListener("ss-test:getStyleSheets", function (msg) {
   let sheets = content.document.styleSheets;
   let titles = Array.map(sheets, ss => [ss.title, ss.disabled]);
   sendSyncMessage("ss-test:getStyleSheets", titles);
 });
 
 addMessageListener("ss-test:enableStyleSheetsForSet", function (msg) {
   content.document.enableStyleSheetsForSet(msg.data);
@@ -144,8 +153,13 @@ addMessageListener("ss-test:removeLastFr
   frames.lastElementChild.remove();
   sendAsyncMessage("ss-test:removeLastFrame");
 });
 
 addMessageListener("ss-test:mapFrameTree", function (msg) {
   let result = gFrameTree.map(frame => ({href: frame.location.href}));
   sendAsyncMessage("ss-test:mapFrameTree", result);
 });
+
+addMessageListener("ss-test:click", function ({data}) {
+  content.document.getElementById(data.id).click();
+  sendAsyncMessage("ss-test:click");
+});
--- a/browser/components/sessionstore/test/head.js
+++ b/browser/components/sessionstore/test/head.js
@@ -283,27 +283,27 @@ function forceSaveState() {
     },
     function onError(x) {
       Services.prefs.clearUserPref(PREF);
       throw x;
     }
   );
 }
 
-function whenBrowserLoaded(aBrowser, aCallback = next) {
+function whenBrowserLoaded(aBrowser, aCallback = next, ignoreSubFrames = true) {
   aBrowser.addEventListener("load", function onLoad(event) {
-    if (event.target == aBrowser.contentDocument) {
+    if (!ignoreSubFrames || event.target == aBrowser.contentDocument) {
       aBrowser.removeEventListener("load", onLoad, true);
       executeSoon(aCallback);
     }
   }, true);
 }
-function promiseBrowserLoaded(aBrowser) {
+function promiseBrowserLoaded(aBrowser, ignoreSubFrames = true) {
   let deferred = Promise.defer();
-  whenBrowserLoaded(aBrowser, deferred.resolve);
+  whenBrowserLoaded(aBrowser, deferred.resolve, ignoreSubFrames);
   return deferred.promise;
 }
 function whenBrowserUnloaded(aBrowser, aContainer, aCallback = next) {
   aBrowser.addEventListener("unload", function onUnload() {
     aBrowser.removeEventListener("unload", onUnload, true);
     executeSoon(aCallback);
   }, true);
 }
--- a/browser/devtools/fontinspector/font-inspector.css
+++ b/browser/devtools/fontinspector/font-inspector.css
@@ -1,16 +1,17 @@
 /* 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/. */
 
 .dim > #root,
 .font:not(.has-code) .font-css-code,
 .font-is-local,
 .font-is-remote,
-.font.is-local .font-format-url {
+.font.is-local .font-format-url,
+#template {
   display: none;
 }
 
 .font.is-remote .font-is-remote,
 .font.is-local .font-is-local {
   display: inline;
 }
--- a/browser/devtools/fontinspector/font-inspector.js
+++ b/browser/devtools/fontinspector/font-inspector.js
@@ -17,16 +17,19 @@ function FontInspector(inspector, window
 }
 
 FontInspector.prototype = {
   init: function FI_init() {
     this.update = this.update.bind(this);
     this.onNewNode = this.onNewNode.bind(this);
     this.inspector.selection.on("new-node", this.onNewNode);
     this.inspector.sidebar.on("fontinspector-selected", this.onNewNode);
+    this.showAll = this.showAll.bind(this);
+    this.showAllButton = this.chromeDoc.getElementById("showall");
+    this.showAllButton.addEventListener("click", this.showAll);
     this.update();
   },
 
   /**
    * Is the fontinspector visible in the sidebar?
    */
   isActive: function FI_isActive() {
     return this.inspector.sidebar &&
@@ -35,16 +38,17 @@ FontInspector.prototype = {
 
   /**
    * Remove listeners.
    */
   destroy: function FI_destroy() {
     this.chromeDoc = null;
     this.inspector.sidebar.off("layoutview-selected", this.onNewNode);
     this.inspector.selection.off("new-node", this.onNewNode);
+    this.showAllButton.removeEventListener("click", this.showAll);
   },
 
   /**
    * Selection 'new-node' event handler.
    */
   onNewNode: function FI_onNewNode() {
     if (this.isActive() &&
         this.inspector.selection.isLocal() &&
--- a/browser/devtools/fontinspector/font-inspector.xhtml
+++ b/browser/devtools/fontinspector/font-inspector.xhtml
@@ -15,19 +15,19 @@
     <link rel="stylesheet" href="chrome://browser/skin/devtools/common.css" type="text/css"/>
     <link rel="stylesheet" href="chrome://browser/skin/devtools/font-inspector.css" type="text/css"/>
     <script type="application/javascript;version=1.8" src="chrome://browser/content/devtools/theme-switching.js"/>
   </head>
   <body class="theme-body devtools-monospace" role="application">
     <script type="application/javascript;version=1.8" src="font-inspector.js"></script>
     <div id="root">
       <ul id="all-fonts"></ul>
-      <button id="showall" onclick="fontInspector.showAll()">&showAllFonts;</button>
+      <button id="showall">&showAllFonts;</button>
     </div>
-    <div id="template" style="display:none">
+    <div id="template">
       <section class="font">
         <iframe sandbox="" class="font-preview"></iframe>
         <div class="font-info">
           <h1 class="font-name"></h1>
           <span class="font-is-local">&system;</span>
           <span class="font-is-remote">&remote;</span>
           <p class="font-format-url">
             <input readonly="readonly" class="font-url"></input>
--- a/browser/devtools/framework/toolbox.js
+++ b/browser/devtools/framework/toolbox.js
@@ -68,16 +68,17 @@ function Toolbox(target, selectedTool, h
   this._toolPanels = new Map();
   this._telemetry = new Telemetry();
 
   this._toolRegistered = this._toolRegistered.bind(this);
   this._toolUnregistered = this._toolUnregistered.bind(this);
   this._refreshHostTitle = this._refreshHostTitle.bind(this);
   this._splitConsoleOnKeypress = this._splitConsoleOnKeypress.bind(this)
   this.destroy = this.destroy.bind(this);
+  this.stopPicker = this.stopPicker.bind(this);
 
   this._target.on("close", this.destroy);
 
   if (!hostType) {
     hostType = Services.prefs.getCharPref(this._prefs.LAST_HOST);
   }
   if (!selectedTool) {
     selectedTool = Services.prefs.getCharPref(this._prefs.LAST_TOOL);
@@ -1107,16 +1108,17 @@ Toolbox.prototype = {
    * are hovered.
    * @return A promise that resolves when the picker has started
    */
   startPicker: function() {
     let deferred = promise.defer();
 
     let done = () => {
       this.emit("picker-started");
+      this.on("select", this.stopPicker);
       deferred.resolve();
     };
 
     promise.all([
       this.initInspector(),
       this.selectTool("inspector")
     ]).then(() => {
       this._isPicking = true;
@@ -1151,16 +1153,17 @@ Toolbox.prototype = {
    * Stop the element picker
    * @return A promise that resolves when the picker has stopped
    */
   stopPicker: function() {
     let deferred = promise.defer();
 
     let done = () => {
       this.emit("picker-stopped");
+      this.off("select", this.stopPicker);
       deferred.resolve();
     };
 
     this.initInspector().then(() => {
       this._isPicking = false;
       this._pickerButton.removeAttribute("checked");
       if (this.isRemoteHighlightable) {
         this.highlighter.cancelPick().then(done);
--- a/browser/devtools/inspector/test/browser.ini
+++ b/browser/devtools/inspector/test/browser.ini
@@ -43,8 +43,9 @@ support-files =
 [browser_inspector_scrolling.js]
 [browser_inspector_select_last_selected.js]
 [browser_inspector_sidebarstate.js]
 [browser_inspector_bug_848731_reset_selection_on_delete.js]
 [browser_inspector_bug_922125_destroy_on_navigate.js]
 [browser_inspector_bug_952294_tooltips_dimensions.js]
 [browser_inspector_bug_958456_highlight_comments.js]
 [browser_inspector_bug_958169_switch_to_inspector_on_pick.js]
+[browser_inspector_bug_961771_picker_stops_on_tool_select.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/inspector/test/browser_inspector_bug_961771_picker_stops_on_tool_select.js
@@ -0,0 +1,48 @@
+/* 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/. */
+
+// Test that the highlighter's picker should be stopped when a different tool is selected
+
+function test() {
+  let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+  let {require} = devtools;
+  let promise = require("sdk/core/promise");
+  let {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
+
+  waitForExplicitFinish();
+
+  let inspector, doc, toolbox;
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function onload() {
+    gBrowser.selectedBrowser.removeEventListener("load", onload, true);
+    doc = content.document;
+    waitForFocus(setupTest, content);
+  }, true);
+  content.location = "data:text/html,testing the highlighter goes away on tool selection";
+
+  function setupTest() {
+    openInspector((aInspector, aToolbox) => {
+      toolbox = aToolbox;
+      inspector = aInspector;
+
+      toolbox.once("picker-stopped", () => {
+        ok(true, "picker-stopped event fired after switch tools, so picker is closed");
+        finishUp();
+      });
+
+      Task.spawn(function() {
+        yield toolbox.startPicker();
+        yield toolbox.selectNextTool();
+      }).then(null, Cu.reportError);
+    });
+  }
+
+  function finishUp() {
+    inspector = doc = toolbox = null;
+    gBrowser.removeCurrentTab();
+    finish();
+  }
+}
+
--- a/browser/devtools/scratchpad/test/browser_scratchpad_wrong_window_focus.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_wrong_window_focus.js
@@ -43,17 +43,17 @@ function test()
 }
 
 function testFocus(sw, hud) {
   let sp = sw.Scratchpad;
 
   function onMessage(event, messages) {
     let msg = [...messages][0];
 
-    var loc = msg.querySelector(".location");
+    var loc = msg.querySelector(".message-location");
     ok(loc, "location element exists");
     is(loc.textContent.trim(), sw.Scratchpad.uniqueName + ":1",
         "location value is correct");
 
     sw.addEventListener("focus", function onFocus() {
       sw.removeEventListener("focus", onFocus, true);
 
       let win = Services.wm.getMostRecentWindow("devtools:scratchpad");
--- a/browser/devtools/webconsole/console-output.js
+++ b/browser/devtools/webconsole/console-output.js
@@ -141,16 +141,28 @@ ConsoleOutput.prototype = {
    * Getter for the debugger WebConsoleClient.
    * @type object
    */
   get webConsoleClient() {
     return this.owner.webConsoleClient;
   },
 
   /**
+   * Release an actor.
+   *
+   * @private
+   * @param string actorId
+   *        The actor ID you want to release.
+   */
+  _releaseObject: function(actorId)
+  {
+    this.owner._releaseObject(actorId);
+  },
+
+  /**
    * Add a message to output.
    *
    * @param object ...args
    *        Any number of Message objects.
    * @return this
    */
   addMessage: function(...args)
   {
@@ -842,17 +854,17 @@ Messages.Simple.prototype = Heritage.ext
   _renderRepeatNode: function()
   {
     if (!this._filterDuplicates) {
       return null;
     }
 
     let repeatNode = this.document.createElementNS(XHTML_NS, "span");
     repeatNode.setAttribute("value", "1");
-    repeatNode.className = "repeats";
+    repeatNode.className = "message-repeats";
     repeatNode.textContent = 1;
     repeatNode._uid = this.getRepeatID();
     return repeatNode;
   },
 
   /**
    * Render the message source location DOM element.
    * @private
@@ -1072,16 +1084,144 @@ Messages.ConsoleGeneric = function(packe
 Messages.ConsoleGeneric.prototype = Heritage.extend(Messages.Extended.prototype,
 {
   _renderBodyPieceSeparator: function()
   {
     return this.document.createTextNode(" ");
   },
 }); // Messages.ConsoleGeneric.prototype
 
+/**
+ * The ConsoleTrace message is used for console.trace() calls.
+ *
+ * @constructor
+ * @extends Messages.Simple
+ * @param object packet
+ *        The Console API call packet received from the server.
+ */
+Messages.ConsoleTrace = function(packet)
+{
+  let options = {
+    className: "consoleTrace cm-s-mozilla",
+    timestamp: packet.timeStamp,
+    category: "webdev",
+    severity: CONSOLE_API_LEVELS_TO_SEVERITIES[packet.level],
+    private: packet.private,
+    filterDuplicates: true,
+    location: {
+      url: packet.filename,
+      line: packet.lineNumber,
+    },
+  };
+
+  this._renderStack = this._renderStack.bind(this);
+  Messages.Simple.call(this, this._renderStack, options);
+
+  this._repeatID.consoleApiLevel = packet.level;
+  this._stacktrace = this._repeatID.stacktrace = packet.stacktrace;
+  this._arguments = packet.arguments;
+};
+
+Messages.ConsoleTrace.prototype = Heritage.extend(Messages.Simple.prototype,
+{
+  /**
+   * Holds the stackframes received from the server.
+   *
+   * @private
+   * @type array
+   */
+  _stacktrace: null,
+
+  /**
+   * Holds the arguments the content script passed to the console.trace()
+   * method. This array is cleared when the message is initialized, and
+   * associated actors are released.
+   *
+   * @private
+   * @type array
+   */
+  _arguments: null,
+
+  init: function()
+  {
+    let result = Messages.Simple.prototype.init.apply(this, arguments);
+
+    // We ignore console.trace() arguments. Release object actors.
+    if (Array.isArray(this._arguments)) {
+      for (let arg of this._arguments) {
+        if (WebConsoleUtils.isActorGrip(arg)) {
+          this.output._releaseObject(arg.actor);
+        }
+      }
+    }
+    this._arguments = null;
+
+    return result;
+  },
+
+  /**
+   * Render the stack frames.
+   *
+   * @private
+   * @return DOMElement
+   */
+  _renderStack: function()
+  {
+    let cmvar = this.document.createElementNS(XHTML_NS, "span");
+    cmvar.className = "cm-variable";
+    cmvar.textContent = "console";
+
+    let cmprop = this.document.createElementNS(XHTML_NS, "span");
+    cmprop.className = "cm-property";
+    cmprop.textContent = "trace";
+
+    let title = this.document.createElementNS(XHTML_NS, "span");
+    title.className = "title devtools-monospace";
+    title.appendChild(cmvar);
+    title.appendChild(this.document.createTextNode("."));
+    title.appendChild(cmprop);
+    title.appendChild(this.document.createTextNode("():"));
+
+    let repeatNode = Messages.Simple.prototype._renderRepeatNode.call(this);
+    let location = Messages.Simple.prototype._renderLocation.call(this);
+    if (location) {
+      location.target = "jsdebugger";
+    }
+
+    let widget = new Widgets.Stacktrace(this, this._stacktrace).render();
+
+    let body = this.document.createElementNS(XHTML_NS, "div");
+    body.appendChild(title);
+    if (repeatNode) {
+      body.appendChild(repeatNode);
+    }
+    if (location) {
+      body.appendChild(location);
+    }
+    body.appendChild(this.document.createTextNode("\n"));
+
+    let frag = this.document.createDocumentFragment();
+    frag.appendChild(body);
+    frag.appendChild(widget.element);
+
+    return frag;
+  },
+
+  _renderBody: function()
+  {
+    let body = Messages.Simple.prototype._renderBody.apply(this, arguments);
+    body.classList.remove("devtools-monospace");
+    return body;
+  },
+
+  // no-op for the message location and .repeats elements.
+  // |this._renderStack| handles customized message output.
+  _renderLocation: function() { },
+  _renderRepeatNode: function() { },
+}); // Messages.ConsoleTrace.prototype
 
 let Widgets = {};
 
 /**
  * The base widget class.
  *
  * @constructor
  * @param object message
@@ -1349,16 +1489,101 @@ Widgets.LongString.prototype = Heritage.
       category: "output",
       severity: "warning",
     });
     this.output.addMessage(msg);
   },
 }); // Widgets.LongString.prototype
 
 
+/**
+ * The stacktrace widget.
+ *
+ * @constructor
+ * @extends Widgets.BaseWidget
+ * @param object message
+ *        The owning message.
+ * @param array stacktrace
+ *        The stacktrace to display, array of frames as supplied by the server,
+ *        over the remote protocol.
+ */
+Widgets.Stacktrace = function(message, stacktrace)
+{
+  Widgets.BaseWidget.call(this, message);
+  this.stacktrace = stacktrace;
+};
+
+Widgets.Stacktrace.prototype = Heritage.extend(Widgets.BaseWidget.prototype,
+{
+  /**
+   * The stackframes received from the server.
+   * @type array
+   */
+  stacktrace: null,
+
+  render: function()
+  {
+    if (this.element) {
+      return this;
+    }
+
+    let result = this.element = this.document.createElementNS(XHTML_NS, "ul");
+    result.className = "stacktrace devtools-monospace";
+
+    for (let frame of this.stacktrace) {
+      result.appendChild(this._renderFrame(frame));
+    }
+
+    return this;
+  },
+
+  /**
+   * Render a frame object received from the server.
+   *
+   * @param object frame
+   *        The stack frame to display. This object should have the following
+   *        properties: functionName, filename and lineNumber.
+   * @return DOMElement
+   *         The DOM element to display for the given frame.
+   */
+  _renderFrame: function(frame)
+  {
+    let fn = this.document.createElementNS(XHTML_NS, "span");
+    fn.className = "function";
+    if (frame.functionName) {
+      let span = this.document.createElementNS(XHTML_NS, "span");
+      span.className = "cm-variable";
+      span.textContent = frame.functionName;
+      fn.appendChild(span);
+      fn.appendChild(this.document.createTextNode("()"));
+    } else {
+      fn.classList.add("cm-comment");
+      fn.textContent = l10n.getStr("stacktrace.anonymousFunction");
+    }
+
+    let location = this.output.owner.createLocationNode(frame.filename,
+                                                        frame.lineNumber,
+                                                        "jsdebugger");
+
+    // .devtools-monospace sets font-size to 80%, however .body already has
+    // .devtools-monospace. If we keep it here, the location would be rendered
+    // smaller.
+    location.classList.remove("devtools-monospace");
+
+    let elem = this.document.createElementNS(XHTML_NS, "li");
+    elem.appendChild(fn);
+    elem.appendChild(location);
+    elem.appendChild(this.document.createTextNode("\n"));
+
+    return elem;
+  },
+
+}); // Widgets.Stacktrace.prototype
+
+
 function gSequenceId()
 {
   return gSequenceId.n++;
 }
 gSequenceId.n = 0;
 
 exports.ConsoleOutput = ConsoleOutput;
 exports.Messages = Messages;
--- a/browser/devtools/webconsole/test/browser.ini
+++ b/browser/devtools/webconsole/test/browser.ini
@@ -98,16 +98,17 @@ support-files =
   test_bug_770099_bad_policy_uri.html^headers^
   test_bug_770099_violation.html
   test_bug_770099_violation.html^headers^
   test-autocomplete-in-stackframe.html
   testscript.js
   test-bug_923281_console_log_filter.html
   test-bug_923281_test1.js
   test-bug_923281_test2.js
+  test-bug_939783_console_trace_duplicates.html
 
 [browser_bug664688_sandbox_update_after_navigation.js]
 [browser_bug_638949_copy_link_location.js]
 [browser_bug_862916_console_dir_and_filter_off.js]
 [browser_bug_865288_repeat_different_objects.js]
 [browser_bug_865871_variables_view_close_on_esc_key.js]
 [browser_bug_869003_inspect_cross_domain_object.js]
 [browser_bug_871156_ctrlw_close_tab.js]
@@ -252,8 +253,9 @@ run-if = os == "mac"
 [browser_webconsole_expandable_timestamps.js]
 [browser_webconsole_autocomplete_in_debugger_stackframe.js]
 [browser_webconsole_autocomplete_popup_close_on_tab_switch.js]
 [browser_webconsole_output_01.js]
 [browser_webconsole_output_02.js]
 [browser_webconsole_output_03.js]
 [browser_webconsole_output_04.js]
 [browser_webconsole_output_events.js]
+[browser_webconsole_console_trace_duplicates.js]
--- a/browser/devtools/webconsole/test/browser_console_addonsdk_loader_exception.js
+++ b/browser/devtools/webconsole/test/browser_console_addonsdk_loader_exception.js
@@ -65,17 +65,17 @@ function test()
       onMessageFound(results);
     });
   }
 
   function onMessageFound(results)
   {
     let msg = [...results[0].matched][0];
     ok(msg, "message element found");
-    let locationNode = msg.querySelector(".location");
+    let locationNode = msg.querySelector(".message-location");
     ok(locationNode, "message location element found");
 
     let title = locationNode.getAttribute("title");
     info("location node title: " + title);
     isnot(title.indexOf(" -> "), -1, "error comes from a subscript");
 
     let viewSource = browserconsole.viewSource;
     let URL = null;
--- a/browser/devtools/webconsole/test/browser_console_error_source_click.js
+++ b/browser/devtools/webconsole/test/browser_console_error_source_click.js
@@ -56,17 +56,17 @@ function test()
     let viewSourceCalled = false;
     hud.viewSource = () => viewSourceCalled = true;
 
     for (let result of results) {
       viewSourceCalled = false;
 
       let msg = [...results[0].matched][0];
       ok(msg, "message element found for: " + result.text);
-      let locationNode = msg.querySelector(".location");
+      let locationNode = msg.querySelector(".message-location");
       ok(locationNode, "message location element found");
 
       EventUtils.synthesizeMouse(locationNode, 2, 2, {}, hud.iframeWindow);
 
       ok(viewSourceCalled, "view source opened");
     }
 
     hud.viewSource = viewSource;
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_585956_console_trace.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_585956_console_trace.js
@@ -1,49 +1,46 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* 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/. */
 
 const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-585956-console-trace.html";
 
 function test() {
-  addTab("data:text/html;charset=utf8,<p>hello");
-  browser.addEventListener("load", tabLoaded, true);
+  Task.spawn(runner).then(finishTest);
 
-  function tabLoaded() {
-    browser.removeEventListener("load", tabLoaded, true);
+  function* runner() {
+    let {tab} = yield loadTab("data:text/html;charset=utf8,<p>hello");
+    let hud = yield openConsole(tab);
 
-    openConsole(null, function(hud) {
-      content.location = TEST_URI;
+    content.location = TEST_URI;
 
-      waitForMessages({
-        webconsole: hud,
-        messages: [{
-          name: "console.trace output",
-          consoleTrace: {
-            file: "test-bug-585956-console-trace.html",
-            fn: "window.foobar585956c",
-          },
-        }],
-      }).then(performChecks);
+    let [result] = yield waitForMessages({
+      webconsole: hud,
+      messages: [{
+        name: "console.trace output",
+        consoleTrace: {
+          file: "test-bug-585956-console-trace.html",
+          fn: "window.foobar585956c",
+        },
+      }],
     });
-  }
 
-  function performChecks(results) {
-    let node = [...results[0].matched][0];
+    let node = [...result.matched][0];
+    ok(node, "found trace log node");
+
+    let obj = node._messageObject;
+    ok(obj, "console.trace message object");
 
     // The expected stack trace object.
     let stacktrace = [
       { filename: TEST_URI, lineNumber: 9, functionName: "window.foobar585956c", language: 2 },
       { filename: TEST_URI, lineNumber: 14, functionName: "foobar585956b", language: 2 },
       { filename: TEST_URI, lineNumber: 18, functionName: "foobar585956a", language: 2 },
       { filename: TEST_URI, lineNumber: 21, functionName: null, language: 2 }
     ];
 
-    ok(node, "found trace log node");
-    ok(node._stacktrace, "found stacktrace object");
-    is(node._stacktrace.toSource(), stacktrace.toSource(), "stacktrace is correct");
+    ok(obj._stacktrace, "found stacktrace object");
+    is(obj._stacktrace.toSource(), stacktrace.toSource(), "stacktrace is correct");
     isnot(node.textContent.indexOf("bug-585956"), -1, "found file name");
-
-    finishTest();
   }
 }
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_642108_pruneTest.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_642108_pruneTest.js
@@ -69,17 +69,17 @@ function testCSSPruning(hudRef) {
       }],
     }).then(([result]) => {
       is(countMessageNodes(), LOG_LIMIT, "number of messages");
 
       is(Object.keys(hudRef.ui._repeatNodes).length, LOG_LIMIT,
          "repeated nodes pruned from repeatNodes");
 
       let msg = [...result.matched][0];
-      let repeats = msg.querySelector(".repeats");
+      let repeats = msg.querySelector(".message-repeats");
       is(repeats.getAttribute("value"), 1,
          "repeated nodes pruned from repeatNodes (confirmed)");
 
       finishTest();
     });
   });
 }
 
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_766001_JS_Console_in_Debugger.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_766001_JS_Console_in_Debugger.js
@@ -29,18 +29,18 @@ function test() {
         text: "Blah Blah",
         category: CATEGORY_WEBDEV,
         severity: SEVERITY_LOG,
       }],
     });
 
     let exceptionMsg = [...exceptionRule.matched][0];
     let consoleMsg = [...consoleRule.matched][0];
-    let nodes = [exceptionMsg.querySelector(".location"),
-                 consoleMsg.querySelector(".location")];
+    let nodes = [exceptionMsg.querySelector(".message-location"),
+                 consoleMsg.querySelector(".message-location")];
     ok(nodes[0], ".location node for the exception message");
     ok(nodes[1], ".location node for the console message");
 
     for (let i = 0; i < nodes.length; i++) {
       yield checkClickOnNode(i, nodes[i]);
       yield gDevTools.showToolbox(hud.target, "webconsole");
     }
 
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_782653_CSS_links_in_Style_Editor.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_782653_CSS_links_in_Style_Editor.js
@@ -36,20 +36,20 @@ function testViewSource(aHud)
     {
       text: "'color'",
       category: CATEGORY_CSS,
       severity: SEVERITY_WARNING,
     }],
   }).then(([error1Rule, error2Rule]) => {
     let error1Msg = [...error1Rule.matched][0];
     let error2Msg = [...error2Rule.matched][0];
-    nodes = [error1Msg.querySelector(".location"),
-             error2Msg.querySelector(".location")];
-    ok(nodes[0], ".location node for the first error");
-    ok(nodes[1], ".location node for the second error");
+    nodes = [error1Msg.querySelector(".message-location"),
+             error2Msg.querySelector(".message-location")];
+    ok(nodes[0], ".message-location node for the first error");
+    ok(nodes[1], ".message-location node for the second error");
 
     let target = TargetFactory.forTab(gBrowser.selectedTab);
     let toolbox = gDevTools.getToolbox(target);
     toolbox.once("styleeditor-selected", (event, panel) => {
       StyleEditorUI = panel.UI;
 
       let count = 0;
       StyleEditorUI.on("editor-added", function() {
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_console_trace_duplicates.js
@@ -0,0 +1,36 @@
+/* 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/. */
+
+function test() {
+  const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug_939783_console_trace_duplicates.html";
+
+  Task.spawn(runner).then(finishTest);
+
+  function* runner() {
+    const {tab} = yield loadTab("data:text/html;charset=utf8,<p>hello");
+    const hud = yield openConsole(tab);
+
+    content.location = TEST_URI;
+
+    yield waitForMessages({
+      webconsole: hud,
+      messages: [{
+        name: "console.trace output for foo1()",
+        text: "foo1()",
+        repeats: 2,
+        consoleTrace: {
+          file: "test-bug_939783_console_trace_duplicates.html",
+          fn: "foo3()",
+        },
+      }, {
+        name: "console.trace output for foo1b()",
+        text: "foo1b()",
+        consoleTrace: {
+          file: "test-bug_939783_console_trace_duplicates.html",
+          fn: "foo3()",
+        },
+      }],
+    });
+  }
+}
--- a/browser/devtools/webconsole/test/browser_webconsole_scratchpad_panel_link.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_scratchpad_panel_link.js
@@ -48,17 +48,17 @@ function runTests(aToolbox)
       webconsole: hud,
       messages: [{ text: "foobar-from-scratchpad" }]
     });
 
     info("Clicking link to switch to and focus Scratchpad");
 
     let [matched] = [...messages[0].matched];
     ok(matched, "Found logged message from Scratchpad");
-    let anchor = matched.querySelector("a.location");
+    let anchor = matched.querySelector("a.message-location");
 
     aToolbox.on("scratchpad-selected", function selected() {
       aToolbox.off("scratchpad-selected", selected);
 
       is(aToolbox.getCurrentPanel(), scratchpadPanel,
         "Clicking link switches to Scratchpad panel");
       
       is(Services.ww.activeWindow, aToolbox.frame.ownerGlobal,
--- a/browser/devtools/webconsole/test/browser_webconsole_view_source.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_view_source.js
@@ -42,17 +42,17 @@ function testViewSource(hud) {
         }],
       }).then(onMessage);
     });
   });
 
   function onMessage([result]) {
     let msg = [...result.matched][0];
     ok(msg, "error message");
-    let locationNode = msg.querySelector(".location");
+    let locationNode = msg.querySelector(".message-location");
     ok(locationNode, "location node");
 
     Services.ww.registerNotification(observer);
 
     containsValue = Sources.containsValue;
     Sources.containsValue = () => {
       containsValueInvoked = true;
       return false;
--- a/browser/devtools/webconsole/test/head.js
+++ b/browser/devtools/webconsole/test/head.js
@@ -1,26 +1,22 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* 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/. */
 
-let WebConsoleUtils, TargetFactory, require;
 let {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
 let {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
 let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
 let {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
+let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+let {require, TargetFactory} = devtools;
+let {Utils: WebConsoleUtils} = require("devtools/toolkit/webconsole/utils");
+let {Messages} = require("devtools/webconsole/console-output");
 
-(() => {
-  let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
-  let utils = devtools.require("devtools/toolkit/webconsole/utils");
-  TargetFactory = devtools.TargetFactory;
-  WebConsoleUtils = utils.Utils;
-  require = devtools.require;
-})();
 // promise._reportErrors = true; // please never leave me.
 
 let gPendingOutputTest = 0;
 
 // The various categories of messages.
 const CATEGORY_NETWORK = 0;
 const CATEGORY_CSS = 1;
 const CATEGORY_JS = 2;
@@ -278,17 +274,17 @@ function dumpConsoles()
  * Dump to output debug information for the given webconsole message.
  *
  * @param nsIDOMNode aMessage
  *        The message element you want to display.
  */
 function dumpMessageElement(aMessage)
 {
   let text = aMessage.textContent;
-  let repeats = aMessage.querySelector(".repeats");
+  let repeats = aMessage.querySelector(".message-repeats");
   if (repeats) {
     repeats = repeats.getAttribute("value");
   }
   console.debug("id", aMessage.getAttribute("id"),
                 "date", aMessage.timestamp,
                 "class", aMessage.className,
                 "category", aMessage.category,
                 "severity", aMessage.severity,
@@ -927,54 +923,54 @@ function waitForMessages(aOptions)
     return result;
   }
 
   function checkConsoleTrace(aRule, aElement)
   {
     let elemText = aElement.textContent;
     let trace = aRule.consoleTrace;
 
-    if (!checkText("Stack trace from ", elemText)) {
+    if (!checkText("console.trace():", elemText)) {
       return false;
     }
 
-    let clickable = aElement.querySelector(".body a");
-    if (!clickable) {
-      ok(false, "console.trace() message is missing .hud-clickable");
-      displayErrorContext(aRule, aElement);
-      return false;
-    }
-    aRule.clickableElements = [clickable];
-
-    if (trace.file &&
-        !checkText("from " + trace.file + ", ", elemText)) {
-      ok(false, "console.trace() message is missing the file name: " +
-                trace.file);
-      displayErrorContext(aRule, aElement);
-      return false;
+    let frame = aElement.querySelector(".stacktrace li:first-child");
+    if (trace.file) {
+      let file = frame.querySelector(".message-location").title;
+      if (!checkText(trace.file, file)) {
+        ok(false, "console.trace() message is missing the file name: " +
+                  trace.file);
+        displayErrorContext(aRule, aElement);
+        return false;
+      }
     }
 
-    if (trace.fn &&
-        !checkText(", function " + trace.fn + ", ", elemText)) {
-      ok(false, "console.trace() message is missing the function name: " +
-                trace.fn);
-      displayErrorContext(aRule, aElement);
-      return false;
+    if (trace.fn) {
+      let fn = frame.querySelector(".function").textContent;
+      if (!checkText(trace.fn, fn)) {
+        ok(false, "console.trace() message is missing the function name: " +
+                  trace.fn);
+        displayErrorContext(aRule, aElement);
+        return false;
+      }
     }
 
-    if (trace.line &&
-        !checkText(", line " + trace.line + ".", elemText)) {
-      ok(false, "console.trace() message is missing the line number: " +
-                trace.line);
-      displayErrorContext(aRule, aElement);
-      return false;
+    if (trace.line) {
+      let line = frame.querySelector(".message-location").sourceLine;
+      if (!checkText(trace.line, line)) {
+        ok(false, "console.trace() message is missing the line number: " +
+                  trace.line);
+        displayErrorContext(aRule, aElement);
+        return false;
+      }
     }
 
     aRule.category = CATEGORY_WEBDEV;
     aRule.severity = SEVERITY_LOG;
+    aRule.type = Messages.ConsoleTrace;
 
     return true;
   }
 
   function checkConsoleTime(aRule, aElement)
   {
     let elemText = aElement.textContent;
     let time = aRule.consoleTime;
@@ -1033,17 +1029,17 @@ function waitForMessages(aOptions)
     aRule.category = CATEGORY_WEBDEV;
     aRule.severity = SEVERITY_LOG;
 
     return true;
   }
 
   function checkSource(aRule, aElement)
   {
-    let location = aElement.querySelector(".location");
+    let location = aElement.querySelector(".message-location");
     if (!location) {
       return false;
     }
 
     if (!checkText(aRule.source.url, location.getAttribute("title"))) {
       return false;
     }
 
@@ -1085,26 +1081,33 @@ function waitForMessages(aOptions)
     if (aRule.consoleGroup && !checkConsoleGroup(aRule, aElement)) {
       return false;
     }
 
     if (aRule.source && !checkSource(aRule, aElement)) {
       return false;
     }
 
+    let partialMatch = !!(aRule.consoleTrace || aRule.consoleTime ||
+                          aRule.consoleTimeEnd);
+
     // The rule tries to match the newer types of messages, based on their
     // object constructor.
-    if (aRule.type && (!aElement._messageObject ||
-                       !(aElement._messageObject instanceof aRule.type))) {
-      return false;
+    if (aRule.type) {
+      if (!aElement._messageObject ||
+          !(aElement._messageObject instanceof aRule.type)) {
+        if (partialMatch) {
+          ok(false, "message type for rule: " + displayRule(aRule));
+          displayErrorContext(aRule, aElement);
+        }
+        return false;
+      }
+      partialMatch = true;
     }
 
-    let partialMatch = !!(aRule.consoleTrace || aRule.consoleTime ||
-                          aRule.consoleTimeEnd || aRule.type);
-
     if ("category" in aRule && aElement.category != aRule.category) {
       if (partialMatch) {
         is(aElement.category, aRule.category,
            "message category for rule: " + displayRule(aRule));
         displayErrorContext(aRule, aElement);
       }
       return false;
     }
@@ -1119,17 +1122,17 @@ function waitForMessages(aOptions)
     }
 
     if (aRule.category == CATEGORY_NETWORK && "url" in aRule &&
         !checkText(aRule.url, aElement.url)) {
       return false;
     }
 
     if ("repeats" in aRule) {
-      let repeats = aElement.querySelector(".repeats");
+      let repeats = aElement.querySelector(".message-repeats");
       if (!repeats || repeats.getAttribute("value") != aRule.repeats) {
         return false;
       }
     }
 
     if ("groupDepth" in aRule) {
       let timestamp = aElement.querySelector(".timestamp");
       let indent = (GROUP_INDENT * aRule.groupDepth) + "px";
@@ -1175,17 +1178,17 @@ function waitForMessages(aOptions)
     aRule.matched.add(aElement);
 
     return aRule.matched.size == count;
   }
 
   function onMessagesAdded(aEvent, aNewElements)
   {
     for (let elem of aNewElements) {
-      let location = elem.querySelector(".location");
+      let location = elem.querySelector(".message-location");
       if (location) {
         let url = location.title;
         // Prevent recursion with the browser console and any potential
         // messages coming from head.js.
         if (url.indexOf("browser/devtools/webconsole/test/head.js") != -1) {
           continue;
         }
       }
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug_939783_console_trace_duplicates.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <title>Web Console test for bug 939783 - different console.trace() calls
+      wrongly filtered as duplicates</title>
+    <!-- Any copyright is dedicated to the Public Domain.
+         http://creativecommons.org/publicdomain/zero/1.0/ -->
+<script type="application/javascript">
+function foo1() {
+  foo2();
+}
+
+function foo1b() {
+  foo2();
+}
+
+function foo2() {
+  foo3();
+}
+
+function foo3() {
+  console.trace();
+}
+
+foo1(); foo1();
+foo1b();
+
+</script>
+  </head>
+  <body>
+    <p>Web Console test for bug 939783 - different console.trace() calls
+      wrongly filtered as duplicates</p>
+  </body>
+</html>
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -1051,17 +1051,17 @@ WebConsoleFrame.prototype = {
    * @param nsIDOMNode aOriginal
    *        The Original Node. The one being merged into.
    * @param nsIDOMNode aFiltered
    *        The node being filtered out because it is repeated.
    */
   mergeFilteredMessageNode:
   function WCF_mergeFilteredMessageNode(aOriginal, aFiltered)
   {
-    let repeatNode = aOriginal.getElementsByClassName("repeats")[0];
+    let repeatNode = aOriginal.getElementsByClassName("message-repeats")[0];
     if (!repeatNode) {
       return; // no repeat node, return early.
     }
 
     let occurrences = parseInt(repeatNode.getAttribute("value")) + 1;
     repeatNode.setAttribute("value", occurrences);
     repeatNode.textContent = occurrences;
     let str = l10n.getStr("messageRepeats.tooltip2");
@@ -1076,17 +1076,17 @@ WebConsoleFrame.prototype = {
    * @param nsIDOMNode aNode
    *        The message node to be filtered or not.
    * @returns nsIDOMNode|null
    *          Returns the duplicate node if the message was filtered, null
    *          otherwise.
    */
   _filterRepeatedMessage: function WCF__filterRepeatedMessage(aNode)
   {
-    let repeatNode = aNode.getElementsByClassName("repeats")[0];
+    let repeatNode = aNode.getElementsByClassName("message-repeats")[0];
     if (!repeatNode) {
       return null;
     }
 
     let uid = repeatNode._uid;
     let dupeNode = null;
 
     if (aNode.category == CATEGORY_CSS ||
@@ -1100,17 +1100,17 @@ WebConsoleFrame.prototype = {
               aNode.category == CATEGORY_JS) &&
              aNode.category != CATEGORY_NETWORK &&
              !aNode.classList.contains("inlined-variables-view")) {
       let lastMessage = this.outputNode.lastChild;
       if (!lastMessage) {
         return null;
       }
 
-      let lastRepeatNode = lastMessage.getElementsByClassName("repeats")[0];
+      let lastRepeatNode = lastMessage.getElementsByClassName("message-repeats")[0];
       if (lastRepeatNode && lastRepeatNode._uid == uid) {
         dupeNode = lastMessage;
       }
     }
 
     if (dupeNode) {
       this.mergeFilteredMessageNode(dupeNode, aNode);
       return dupeNode;
@@ -1186,58 +1186,31 @@ WebConsoleFrame.prototype = {
       case "error":
       case "exception":
       case "assert":
       case "debug": {
         let msg = new Messages.ConsoleGeneric(aMessage);
         node = msg.init(this.output).render().element;
         break;
       }
+      case "trace": {
+        let msg = new Messages.ConsoleTrace(aMessage);
+        node = msg.init(this.output).render().element;
+        break;
+      }
       case "dir": {
         body = { arguments: args };
         let clipboardArray = [];
         args.forEach((aValue) => {
           clipboardArray.push(VariablesView.getString(aValue));
         });
         clipboardText = clipboardArray.join(" ");
         break;
       }
 
-      case "trace": {
-        let filename = WebConsoleUtils.abbreviateSourceURL(aMessage.filename);
-        let functionName = aMessage.functionName ||
-                           l10n.getStr("stacktrace.anonymousFunction");
-
-        body = this.document.createElementNS(XHTML_NS, "a");
-        body.setAttribute("aria-haspopup", true);
-        body.href = "#";
-        body.draggable = false;
-        body.textContent = l10n.getFormatStr("stacktrace.outputMessage",
-                                             [filename, functionName,
-                                              sourceLine]);
-
-        this._addMessageLinkCallback(body, () => {
-          this.jsterm.openVariablesView({
-            rawObject: aMessage.stacktrace,
-            autofocus: true,
-          });
-        });
-
-        clipboardText = body.textContent + "\n";
-
-        aMessage.stacktrace.forEach(function(aFrame) {
-          clipboardText += aFrame.filename + " :: " +
-                           aFrame.functionName + " :: " +
-                           aFrame.lineNumber + "\n";
-        });
-
-        clipboardText = clipboardText.trimRight();
-        break;
-      }
-
       case "group":
       case "groupCollapsed":
         clipboardText = body = aMessage.groupName;
         this.groupDepth++;
         break;
 
       case "groupEnd":
         if (this.groupDepth > 0) {
@@ -1276,17 +1249,16 @@ WebConsoleFrame.prototype = {
     }
 
     // Release object actors for arguments coming from console API methods that
     // we ignore their arguments.
     switch (level) {
       case "group":
       case "groupCollapsed":
       case "groupEnd":
-      case "trace":
       case "time":
       case "timeEnd":
         for (let actor of objectActors) {
           this._releaseObject(actor);
         }
         objectActors.clear();
     }
 
@@ -1302,25 +1274,21 @@ WebConsoleFrame.prototype = {
         node.setAttribute("private", true);
       }
     }
 
     if (objectActors.size > 0) {
       node._objectActors = objectActors;
 
       if (!node._messageObject) {
-        let repeatNode = node.getElementsByClassName("repeats")[0];
+        let repeatNode = node.getElementsByClassName("message-repeats")[0];
         repeatNode._uid += [...objectActors].join("-");
       }
     }
 
-    if (level == "trace") {
-      node._stacktrace = aMessage.stacktrace;
-    }
-
     return node;
   },
 
   /**
    * Handle ConsoleAPICall objects received from the server. This method outputs
    * the window.console API call.
    *
    * @param object aMessage
@@ -2383,17 +2351,17 @@ WebConsoleFrame.prototype = {
       for (let actor of aNode._objectActors) {
         this._releaseObject(actor);
       }
       aNode._objectActors.clear();
     }
 
     if (aNode.category == CATEGORY_CSS ||
         aNode.category == CATEGORY_SECURITY) {
-      let repeatNode = aNode.getElementsByClassName("repeats")[0];
+      let repeatNode = aNode.getElementsByClassName("message-repeats")[0];
       if (repeatNode && repeatNode._uid) {
         delete this._repeatNodes[repeatNode._uid];
       }
     }
     else if (aNode._connectionId &&
              aNode.category == CATEGORY_NETWORK) {
       delete this._networkRequests[aNode._connectionId];
       this._releaseObject(aNode._connectionId);
@@ -2496,17 +2464,17 @@ WebConsoleFrame.prototype = {
     // Add the message repeats node only when needed.
     let repeatNode = null;
     if (aCategory != CATEGORY_INPUT &&
         aCategory != CATEGORY_OUTPUT &&
         aCategory != CATEGORY_NETWORK &&
         !(aCategory == CATEGORY_CSS && aSeverity == SEVERITY_LOG)) {
       repeatNode = this.document.createElementNS(XHTML_NS, "span");
       repeatNode.setAttribute("value", "1");
-      repeatNode.className = "repeats";
+      repeatNode.className = "message-repeats";
       repeatNode.textContent = 1;
       repeatNode._uid = [bodyNode.textContent, aCategory, aSeverity, aLevel,
                          aSourceURL, aSourceLine].join(":");
     }
 
     // Create the timestamp.
     let timestampNode = this.document.createElementNS(XHTML_NS, "span");
     timestampNode.className = "timestamp devtools-monospace";
@@ -2562,21 +2530,28 @@ WebConsoleFrame.prototype = {
    * Creates the anchor that displays the textual location of an incoming
    * message.
    *
    * @param string aSourceURL
    *        The URL of the source file responsible for the error.
    * @param number aSourceLine [optional]
    *        The line number on which the error occurred. If zero or omitted,
    *        there is no line number associated with this message.
+   * @param string aTarget [optional]
+   *        Tells which tool to open the link with, on click. Supported tools:
+   *        jsdebugger, styleeditor, scratchpad.
    * @return nsIDOMNode
    *         The new anchor element, ready to be added to the message node.
    */
-  createLocationNode: function WCF_createLocationNode(aSourceURL, aSourceLine)
+  createLocationNode:
+  function WCF_createLocationNode(aSourceURL, aSourceLine, aTarget)
   {
+    if (!aSourceURL) {
+      aSourceURL = "";
+    }
     let locationNode = this.document.createElementNS(XHTML_NS, "a");
     let filenameNode = this.document.createElementNS(XHTML_NS, "span");
 
     // Create the text, which consists of an abbreviated version of the URL
     // Scratchpad URLs should not be abbreviated.
     let filename;
     let fullURL;
     let isScratchpad = false;
@@ -2587,40 +2562,49 @@ WebConsoleFrame.prototype = {
       isScratchpad = true;
     }
     else {
       fullURL = aSourceURL.split(" -> ").pop();
       filename = WebConsoleUtils.abbreviateSourceURL(fullURL);
     }
 
     filenameNode.className = "filename";
-    filenameNode.textContent = " " + filename;
+    filenameNode.textContent = " " + (filename || l10n.getStr("unknownLocation"));
     locationNode.appendChild(filenameNode);
 
-    locationNode.href = isScratchpad ? "#" : fullURL;
+    locationNode.href = isScratchpad || !fullURL ? "#" : fullURL;
     locationNode.draggable = false;
+    locationNode.target = aTarget;
     locationNode.setAttribute("title", aSourceURL);
-    locationNode.className = "location theme-link devtools-monospace";
+    locationNode.className = "message-location theme-link devtools-monospace";
 
     // Make the location clickable.
-    this._addMessageLinkCallback(locationNode, () => {
-      if (isScratchpad) {
+    let onClick = () => {
+      let target = locationNode.target;
+      if (target == "scratchpad" || isScratchpad) {
         this.owner.viewSourceInScratchpad(aSourceURL);
+        return;
       }
-      else if (locationNode.parentNode.category == CATEGORY_CSS) {
+
+      let category = locationNode.parentNode.category;
+      if (target == "styleeditor" || category == CATEGORY_CSS) {
         this.owner.viewSourceInStyleEditor(fullURL, aSourceLine);
       }
-      else if (locationNode.parentNode.category == CATEGORY_JS ||
-               locationNode.parentNode.category == CATEGORY_WEBDEV) {
+      else if (target == "jsdebugger" ||
+               category == CATEGORY_JS || category == CATEGORY_WEBDEV) {
         this.owner.viewSourceInDebugger(fullURL, aSourceLine);
       }
       else {
         this.owner.viewSource(fullURL, aSourceLine);
       }
-    });
+    };
+
+    if (fullURL) {
+      this._addMessageLinkCallback(locationNode, onClick);
+    }
 
     if (aSourceLine) {
       let lineNumberNode = this.document.createElementNS(XHTML_NS, "span");
       lineNumberNode.className = "line-number";
       lineNumberNode.textContent = ":" + aSourceLine;
       locationNode.appendChild(lineNumberNode);
       locationNode.sourceLine = aSourceLine;
     }
--- 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.8.759
+Current extension version is: 0.8.934
 
--- a/browser/extensions/pdfjs/components/PdfStreamConverter.js
+++ b/browser/extensions/pdfjs/components/PdfStreamConverter.js
@@ -372,21 +372,29 @@ ChromeActions.prototype = {
               !this.telemetryState.streamTypesUsed[streamTypeId]) {
             PdfJsTelemetry.onStreamType(streamTypeId);
             this.telemetryState.streamTypesUsed[streamTypeId] = true;
           }
         }
         break;
     }
   },
-  fallback: function(url, sendResponse) {
+  fallback: function(args, sendResponse) {
+    var featureId = args.featureId;
+    var url = args.url;
+
     var self = this;
     var domWindow = this.domWindow;
     var strings = getLocalizedStrings('chrome.properties');
-    var message = getLocalizedString(strings, 'unsupported_feature');
+    var message;
+    if (featureId === 'forms') {
+      message = getLocalizedString(strings, 'unsupported_feature_forms');
+    } else {
+      message = getLocalizedString(strings, 'unsupported_feature');
+    }
 
     PdfJsTelemetry.onFallback();
 
     var notificationBox = null;
     try {
       // Based on MDN's "Working with windows in chrome code"
       var mainWindow = domWindow
         .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -15,18 +15,18 @@
  * limitations under the License.
  */
 
 // Initializing PDFJS global object (if still undefined)
 if (typeof PDFJS === 'undefined') {
   (typeof window !== 'undefined' ? window : this).PDFJS = {};
 }
 
-PDFJS.version = '0.8.759';
-PDFJS.build = 'd3b5aa3';
+PDFJS.version = '0.8.934';
+PDFJS.build = 'c80df60';
 
 (function pdfjsWrapper() {
   // Use strict in our context only - users might not want it
   'use strict';
 
 /* -*- 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
@@ -38,27 +38,25 @@ PDFJS.build = 'd3b5aa3';
  *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-/* globals Cmd, ColorSpace, Dict, MozBlobBuilder, Name, PDFJS, Ref, URL */
+/* globals Cmd, ColorSpace, Dict, MozBlobBuilder, Name, PDFJS, Ref, URL,
+           Promise */
 
 'use strict';
 
 var globalScope = (typeof window === 'undefined') ? this : window;
 
 var isWorker = (typeof window == 'undefined');
 
-var ERRORS = 0, WARNINGS = 1, INFOS = 5;
-var verbosity = WARNINGS;
-
 var FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0];
 
 var TextRenderingMode = {
   FILL: 0,
   STROKE: 1,
   FILL_STROKE: 2,
   INVISIBLE: 3,
   FILL_ADD_TO_PATH: 4,
@@ -73,16 +71,22 @@ var TextRenderingMode = {
 // In production, it will be declared outside a global wrapper
 // In development, it will be declared here
 if (!globalScope.PDFJS) {
   globalScope.PDFJS = {};
 }
 
 globalScope.PDFJS.pdfBug = false;
 
+PDFJS.VERBOSITY_LEVELS = {
+  errors: 0,
+  warnings: 1,
+  infos: 5
+};
+
 // All the possible operations for an operator list.
 var OPS = PDFJS.OPS = {
   // Intentionally start from 1 so it is easy to spot bad operators that will be
   // 0's.
   dependency: 1,
   setLineWidth: 2,
   setLineCap: 3,
   setLineJoin: 4,
@@ -166,87 +170,94 @@ var OPS = PDFJS.OPS = {
   paintJpegXObject: 82,
   paintImageMaskXObject: 83,
   paintImageMaskXObjectGroup: 84,
   paintImageXObject: 85,
   paintInlineImageXObject: 86,
   paintInlineImageXObjectGroup: 87
 };
 
-// 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 {
-    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
-// disabled, which is important to devs but not end users.
+// A notice for devs. These are good for things that are helpful to devs, such
+// as warning that Workers were disabled, which is important to devs but not
+// end users.
 function info(msg) {
-  if (verbosity >= INFOS) {
-    log('Info: ' + msg);
-    PDFJS.LogManager.notify('info', msg);
+  if (PDFJS.verbosity >= PDFJS.VERBOSITY_LEVELS.infos) {
+    console.log('Info: ' + msg);
   }
 }
 
-// Non-fatal warnings that should trigger the fallback UI.
+// Non-fatal warnings.
 function warn(msg) {
-  if (verbosity >= WARNINGS) {
-    log('Warning: ' + msg);
-    PDFJS.LogManager.notify('warn', msg);
+  if (PDFJS.verbosity >= PDFJS.VERBOSITY_LEVELS.warnings) {
+    console.log('Warning: ' + msg);
   }
 }
 
 // Fatal errors that should trigger the fallback UI and halt execution by
 // throwing an exception.
 function error(msg) {
   // If multiple arguments were passed, pass them all to the log function.
   if (arguments.length > 1) {
     var logArguments = ['Error:'];
     logArguments.push.apply(logArguments, arguments);
-    log.apply(null, logArguments);
+    console.log.apply(console, logArguments);
     // Join the arguments into a single string for the lines below.
     msg = [].join.call(arguments, ' ');
   } else {
-    log('Error: ' + msg);
+    console.log('Error: ' + msg);
   }
-  log(backtrace());
-  PDFJS.LogManager.notify('error', msg);
+  console.log(backtrace());
+  UnsupportedManager.notify(UNSUPPORTED_FEATURES.unknown);
   throw new Error(msg);
 }
 
-// Missing features that should trigger the fallback UI.
-function TODO(what) {
-  warn('TODO: ' + what);
-}
-
 function backtrace() {
   try {
     throw new Error();
   } catch (e) {
     return e.stack ? e.stack.split('\n').slice(2).join('\n') : '';
   }
 }
 
 function assert(cond, msg) {
   if (!cond)
     error(msg);
 }
 
+var UNSUPPORTED_FEATURES = PDFJS.UNSUPPORTED_FEATURES = {
+  unknown: 'unknown',
+  forms: 'forms',
+  javaScript: 'javaScript',
+  smask: 'smask',
+  shadingPattern: 'shadingPattern',
+  font: 'font'
+};
+
+var UnsupportedManager = PDFJS.UnsupportedManager =
+  (function UnsupportedManagerClosure() {
+  var listeners = [];
+  return {
+    listen: function (cb) {
+      listeners.push(cb);
+    },
+    notify: function (featureId) {
+      warn('Unsupported feature "' + featureId + '"');
+      for (var i = 0, ii = listeners.length; i < ii; i++) {
+        listeners[i](featureId);
+      }
+    }
+  };
+})();
+
 // Combines two URLs. The baseUrl shall be absolute URL. If the url is an
 // absolute URL, it will be returned as is.
 function combineUrl(baseUrl, url) {
   if (!url)
     return baseUrl;
-  if (url.indexOf(':') >= 0)
+  if (/^[a-z][a-z0-9+\-.]*:/i.test(url))
     return url;
   if (url.charAt(0) == '/') {
     // absolute path
     var i = baseUrl.indexOf('://');
     i = baseUrl.indexOf('/', i + 3);
     return baseUrl.substring(0, i) + url;
   } else {
     // relative path
@@ -260,21 +271,23 @@ function combineUrl(baseUrl, url) {
   }
 }
 
 // Validates if URL is safe and allowed, e.g. to avoid XSS.
 function isValidUrl(url, allowRelative) {
   if (!url) {
     return false;
   }
-  var colon = url.indexOf(':');
-  if (colon < 0) {
+  // RFC 3986 (http://tools.ietf.org/html/rfc3986#section-3.1)
+  // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
+  var protocol = /^[a-z][a-z0-9+\-.]*(?=:)/i.exec(url);
+  if (!protocol) {
     return allowRelative;
   }
-  var protocol = url.substr(0, colon);
+  protocol = protocol[0].toLowerCase();
   switch (protocol) {
     case 'http':
     case 'https':
     case 'ftp':
     case 'mailto':
       return true;
     default:
       return false;
@@ -284,32 +297,16 @@ PDFJS.isValidUrl = isValidUrl;
 
 // In a well-formed PDF, |cond| holds.  If it doesn't, subsequent
 // behavior is undefined.
 function assertWellFormed(cond, msg) {
   if (!cond)
     error(msg);
 }
 
-var LogManager = PDFJS.LogManager = (function LogManagerClosure() {
-  var loggers = [];
-  return {
-    addLogger: function logManager_addLogger(logger) {
-      loggers.push(logger);
-    },
-    notify: function(type, message) {
-      for (var i = 0, ii = loggers.length; i < ii; i++) {
-        var logger = loggers[i];
-        if (logger[type])
-          logger[type](message);
-      }
-    }
-  };
-})();
-
 function shadow(obj, prop, value) {
   Object.defineProperty(obj, prop, { value: value,
                                      enumerable: true,
                                      configurable: true,
                                      writable: false });
   return value;
 }
 
@@ -829,247 +826,76 @@ function isPDFFunction(v) {
   else if (isStream(v))
     fnDict = v.dict;
   else
     return false;
   return fnDict.has('FunctionType');
 }
 
 /**
+ * Legacy support for PDFJS Promise implementation.
+ * TODO remove eventually
+ */
+var LegacyPromise = PDFJS.LegacyPromise = (function LegacyPromiseClosure() {
+  return function LegacyPromise() {
+    var resolve, reject;
+    var promise = new Promise(function (resolve_, reject_) {
+      resolve = resolve_;
+      reject = reject_;
+    });
+    promise.resolve = resolve;
+    promise.reject = reject;
+    return promise;
+  };
+})();
+
+/**
+ * Polyfill for Promises:
  * The following promise implementation tries to generally implment the
  * Promise/A+ spec. Some notable differences from other promise libaries are:
  * - There currently isn't a seperate deferred and promise object.
  * - Unhandled rejections eventually show an error if they aren't handled.
  *
  * Based off of the work in:
  * https://bugzilla.mozilla.org/show_bug.cgi?id=810490
  */
-var Promise = PDFJS.Promise = (function PromiseClosure() {
-  var STATUS_PENDING = 0;
-  var STATUS_RESOLVED = 1;
-  var STATUS_REJECTED = 2;
-
-  // In an attempt to avoid silent exceptions, unhandled rejections are
-  // tracked and if they aren't handled in a certain amount of time an
-  // error is logged.
-  var REJECTION_TIMEOUT = 500;
-
-  var HandlerManager = {
-    handlers: [],
-    running: false,
-    unhandledRejections: [],
-    pendingRejectionCheck: false,
-
-    scheduleHandlers: function scheduleHandlers(promise) {
-      if (promise._status == STATUS_PENDING) {
-        return;
-      }
-
-      this.handlers = this.handlers.concat(promise._handlers);
-      promise._handlers = [];
-
-      if (this.running) {
-        return;
-      }
-      this.running = true;
-
-      setTimeout(this.runHandlers.bind(this), 0);
-    },
-
-    runHandlers: function runHandlers() {
-      while (this.handlers.length > 0) {
-        var handler = this.handlers.shift();
-
-        var nextStatus = handler.thisPromise._status;
-        var nextValue = handler.thisPromise._value;
-
-        try {
-          if (nextStatus === STATUS_RESOLVED) {
-            if (typeof(handler.onResolve) == 'function') {
-              nextValue = handler.onResolve(nextValue);
+(function PromiseClosure() {
+  if (globalScope.Promise) {
+    // Promises existing in the DOM/Worker, checking presence of all/resolve
+    if (typeof globalScope.Promise.all !== 'function') {
+      globalScope.Promise.all = function (iterable) {
+        var count = 0, results = [], resolve, reject;
+        var promise = new globalScope.Promise(function (resolve_, reject_) {
+          resolve = resolve_;
+          reject = reject_;
+        });
+        iterable.forEach(function (p, i) {
+          count++;
+          p.then(function (result) {
+            results[i] = result;
+            count--;
+            if (count === 0) {
+              resolve(results);
             }
-          } else if (typeof(handler.onReject) === 'function') {
-              nextValue = handler.onReject(nextValue);
-              nextStatus = STATUS_RESOLVED;
-
-              if (handler.thisPromise._unhandledRejection) {
-                this.removeUnhandeledRejection(handler.thisPromise);
-              }
-          }
-        } catch (ex) {
-          nextStatus = STATUS_REJECTED;
-          nextValue = ex;
+          }, reject);
+        });
+        if (count === 0) {
+          resolve(results);
         }
-
-        handler.nextPromise._updateStatus(nextStatus, nextValue);
-      }
-
-      this.running = false;
-    },
-
-    addUnhandledRejection: function addUnhandledRejection(promise) {
-      this.unhandledRejections.push({
-        promise: promise,
-        time: Date.now()
-      });
-      this.scheduleRejectionCheck();
-    },
-
-    removeUnhandeledRejection: function removeUnhandeledRejection(promise) {
-      promise._unhandledRejection = false;
-      for (var i = 0; i < this.unhandledRejections.length; i++) {
-        if (this.unhandledRejections[i].promise === promise) {
-          this.unhandledRejections.splice(i);
-          i--;
-        }
-      }
-    },
-
-    scheduleRejectionCheck: function scheduleRejectionCheck() {
-      if (this.pendingRejectionCheck) {
-        return;
-      }
-      this.pendingRejectionCheck = true;
-      setTimeout(function rejectionCheck() {
-        this.pendingRejectionCheck = false;
-        var now = Date.now();
-        for (var i = 0; i < this.unhandledRejections.length; i++) {
-          if (now - this.unhandledRejections[i].time > REJECTION_TIMEOUT) {
-            var unhandled = this.unhandledRejections[i].promise._value;
-            var msg = 'Unhandled rejection: ' + unhandled;
-            if (unhandled.stack) {
-              msg += '\n' + unhandled.stack;
-            }
-            warn(msg);
-            this.unhandledRejections.splice(i);
-            i--;
-          }
-        }
-        if (this.unhandledRejections.length) {
-          this.scheduleRejectionCheck();
-        }
-      }.bind(this), REJECTION_TIMEOUT);
+        return promise;
+      };
     }
-  };
-
-  function Promise() {
-    this._status = STATUS_PENDING;
-    this._handlers = [];
+    if (typeof globalScope.Promise.resolve !== 'function') {
+      globalScope.Promise.resolve = function (x) {
+        return new globalScope.Promise(function (resolve) { resolve(x); });
+      };
+    }
+    return;
   }
-  /**
-   * Builds a promise that is resolved when all the passed in promises are
-   * resolved.
-   * @param {array} array of data and/or promises to wait for.
-   * @return {Promise} New dependant promise.
-   */
-  Promise.all = function Promise_all(promises) {
-    var deferred = new Promise();
-    var unresolved = promises.length;
-    var results = [];
-    if (unresolved === 0) {
-      deferred.resolve(results);
-      return deferred;
-    }
-    function reject(reason) {
-      if (deferred._status === STATUS_REJECTED) {
-        return;
-      }
-      results = [];
-      deferred.reject(reason);
-    }
-    for (var i = 0, ii = promises.length; i < ii; ++i) {
-      var promise = promises[i];
-      var resolve = (function(i) {
-        return function(value) {
-          if (deferred._status === STATUS_REJECTED) {
-            return;
-          }
-          results[i] = value;
-          unresolved--;
-          if (unresolved === 0)
-            deferred.resolve(results);
-        };
-      })(i);
-      if (Promise.isPromise(promise)) {
-        promise.then(resolve, reject);
-      } else {
-        resolve(promise);
-      }
-    }
-    return deferred;
-  };
-
-  /**
-   * Checks if the value is likely a promise (has a 'then' function).
-   * @return {boolean} true if x is thenable
-   */
-  Promise.isPromise = function Promise_isPromise(value) {
-    return value && typeof value.then === 'function';
-  };
-
-  Promise.prototype = {
-    _status: null,
-    _value: null,
-    _handlers: null,
-    _unhandledRejection: null,
-
-    _updateStatus: function Promise__updateStatus(status, value) {
-      if (this._status === STATUS_RESOLVED ||
-          this._status === STATUS_REJECTED) {
-        return;
-      }
-
-      if (status == STATUS_RESOLVED &&
-          Promise.isPromise(value)) {
-        value.then(this._updateStatus.bind(this, STATUS_RESOLVED),
-                   this._updateStatus.bind(this, STATUS_REJECTED));
-        return;
-      }
-
-      this._status = status;
-      this._value = value;
-
-      if (status === STATUS_REJECTED && this._handlers.length === 0) {
-        this._unhandledRejection = true;
-        HandlerManager.addUnhandledRejection(this);
-      }
-
-      HandlerManager.scheduleHandlers(this);
-    },
-
-    get isResolved() {
-      return this._status === STATUS_RESOLVED;
-    },
-
-    get isRejected() {
-      return this._status === STATUS_REJECTED;
-    },
-
-    resolve: function Promise_resolve(value) {
-      this._updateStatus(STATUS_RESOLVED, value);
-    },
-
-    reject: function Promise_reject(reason) {
-      this._updateStatus(STATUS_REJECTED, reason);
-    },
-
-    then: function Promise_then(onResolve, onReject) {
-      var nextPromise = new Promise();
-      this._handlers.push({
-        thisPromise: this,
-        onResolve: onResolve,
-        onReject: onReject,
-        nextPromise: nextPromise
-      });
-      HandlerManager.scheduleHandlers(this);
-      return nextPromise;
-    }
-  };
-
-  return Promise;
+  throw new Error('DOM Promise is not present');
 })();
 
 var StatTimer = (function StatTimerClosure() {
   function rpad(str, pad, length) {
     while (str.length < length)
       str += pad;
     return str;
   }
@@ -1125,28 +951,27 @@ PDFJS.createBlob = function createBlob(d
     return new Blob([data], { type: contentType });
   // Blob builder is deprecated in FF14 and removed in FF18.
   var bb = new MozBlobBuilder();
   bb.append(data);
   return bb.getBlob(contentType);
 };
 
 PDFJS.createObjectURL = (function createObjectURLClosure() {
-  if (typeof URL !== 'undefined' && URL.createObjectURL) {
-    return function createObjectURL(data, contentType) {
-      var blob = PDFJS.createBlob(data, contentType);
-      return URL.createObjectURL(blob);
-    };
-  }
-
   // Blob/createObjectURL is not available, falling back to data schema.
   var digits =
     'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
 
   return function createObjectURL(data, contentType) {
+    if (!PDFJS.disableCreateObjectURL &&
+        typeof URL !== 'undefined' && URL.createObjectURL) {
+      var blob = PDFJS.createBlob(data, contentType);
+      return URL.createObjectURL(blob);
+    }
+
     var buffer = 'data:' + contentType + ';base64,';
     for (var i = 0, ii = data.length; i < ii; i += 3) {
       var b1 = data[i] & 0xFF;
       var b2 = data[i + 1] & 0xFF;
       var b3 = data[i + 2] & 0xFF;
       var d1 = b1 >> 2, d2 = ((b1 & 3) << 4) | (b2 >> 4);
       var d3 = i + 1 < ii ? ((b2 & 0xF) << 2) | (b3 >> 6) : 64;
       var d4 = i + 2 < ii ? (b3 & 0x3F) : 64;
@@ -1160,56 +985,53 @@ function MessageHandler(name, comObj) {
   this.name = name;
   this.comObj = comObj;
   this.callbackIndex = 1;
   this.postMessageTransfers = true;
   var callbacks = this.callbacks = {};
   var ah = this.actionHandler = {};
 
   ah['console_log'] = [function ahConsoleLog(data) {
-    log.apply(null, data);
+    console.log.apply(console, data);
   }];
-  // 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);
+  ah['console_error'] = [function ahConsoleError(data) {
+    console.error.apply(console, data);
+  }];
+  ah['_unsupported_feature'] = [function ah_unsupportedFeature(data) {
+    UnsupportedManager.notify(data);
   }];
 
   comObj.onmessage = function messageHandlerComObjOnMessage(event) {
     var data = event.data;
     if (data.isReply) {
       var callbackId = data.callbackId;
       if (data.callbackId in callbacks) {
         var callback = callbacks[callbackId];
         delete callbacks[callbackId];
         callback(data.data);
       } else {
         error('Cannot resolve callback ' + callbackId);
       }
     } else if (data.action in ah) {
       var action = ah[data.action];
       if (data.callbackId) {
-        var promise = new Promise();
+        var deferred = {};
+        var promise = new Promise(function (resolve, reject) {
+          deferred.resolve = resolve;
+          deferred.reject = reject;
+        });
+        deferred.promise = promise;
         promise.then(function(resolvedData) {
           comObj.postMessage({
             isReply: true,
             callbackId: data.callbackId,
             data: resolvedData
           });
         });
-        action[0].call(action[1], data.data, promise);
+        action[0].call(action[1], data.data, deferred);
       } else {
         action[0].call(action[1], data.data);
       }
     } else {
       error('Unkown action from worker: ' + data.action);
     }
   };
 }
@@ -1278,75 +1100,119 @@ var ColorSpace = (function ColorSpaceClo
     getRgbItem: function ColorSpace_getRgb(src, srcOffset, dest, destOffset) {
       error('Should not call ColorSpace.getRgbItem');
     },
     /**
      * Converts the specified number of the color values to the RGB colors.
      * The colors are located in the src array starting from the srcOffset.
      * The result is placed into the dest array starting from the destOffset.
      * The src array items shall be in [0,2^bits) range, the dest array items
-     * will be in [0,255] range.
+     * will be in [0,255] range. alpha01 indicates how many alpha components
+     * there are in the dest array; it will be either 0 (RGB array) or 1 (RGBA
+     * array).
      */
     getRgbBuffer: function ColorSpace_getRgbBuffer(src, srcOffset, count,
-                                                   dest, destOffset, bits) {
+                                                   dest, destOffset, bits,
+                                                   alpha01) {
       error('Should not call ColorSpace.getRgbBuffer');
     },
     /**
-     * Determines amount of the bytes is required to store the reslut of the
-     * conversion that done by the getRgbBuffer method.
+     * Determines the number of bytes required to store the result of the
+     * conversion done by the getRgbBuffer method. As in getRgbBuffer,
+     * |alpha01| is either 0 (RGB output) or 1 (RGBA output).
      */
-    getOutputLength: function ColorSpace_getOutputLength(inputLength) {
+    getOutputLength: function ColorSpace_getOutputLength(inputLength,
+                                                         alpha01) {
       error('Should not call ColorSpace.getOutputLength');
     },
     /**
      * Returns true if source data will be equal the result/output data.
      */
     isPassthrough: function ColorSpace_isPassthrough(bits) {
       return false;
     },
     /**
-     * Creates the output buffer and converts the specified number of the color
-     * values to the RGB colors, similar to the getRgbBuffer.
+     * Fills in the RGB colors in an RGBA buffer.
      */
-    createRgbBuffer: function ColorSpace_createRgbBuffer(src, srcOffset,
-                                                         count, bits) {
-      if (this.isPassthrough(bits)) {
-        return src.subarray(srcOffset);
-      }
-      var dest = new Uint8Array(count * 3);
-      var numComponentColors = 1 << bits;
-      // Optimization: create a color map when there is just one component and
-      // we are converting more colors than the size of the color map. We
-      // don't build the map if the colorspace is gray or rgb since those
-      // methods are faster than building a map. This mainly offers big speed
-      // ups for indexed and alternate colorspaces.
-      if (this.numComps === 1 && count > numComponentColors &&
+    fillRgb: function ColorSpace_fillRgb(rgbaBuf, originalWidth,
+                                         originalHeight, width, height,
+                                         actualHeight, bpc, comps) {
+      var count = originalWidth * originalHeight;
+      var rgbBuf = null;
+      var numComponentColors = 1 << bpc;
+      var needsResizing = originalHeight != height || originalWidth != width;
+
+      if (this.isPassthrough(bpc)) {
+        rgbBuf = comps;
+
+      } else if (this.numComps === 1 && count > numComponentColors &&
           this.name !== 'DeviceGray' && this.name !== 'DeviceRGB') {
+        // Optimization: create a color map when there is just one component and
+        // we are converting more colors than the size of the color map. We
+        // don't build the map if the colorspace is gray or rgb since those
+        // methods are faster than building a map. This mainly offers big speed
+        // ups for indexed and alternate colorspaces.
+        //
         // TODO it may be worth while to cache the color map. While running
         // testing I never hit a cache so I will leave that out for now (perhaps
         // we are reparsing colorspaces too much?).
-        var allColors = bits <= 8 ? new Uint8Array(numComponentColors) :
-                                    new Uint16Array(numComponentColors);
+        var allColors = bpc <= 8 ? new Uint8Array(numComponentColors) :
+                                   new Uint16Array(numComponentColors);
         for (var i = 0; i < numComponentColors; i++) {
           allColors[i] = i;
         }
         var colorMap = new Uint8Array(numComponentColors * 3);
-        this.getRgbBuffer(allColors, 0, numComponentColors, colorMap, 0, bits);
-
-        var destOffset = 0;
-        for (var i = 0; i < count; ++i) {
-          var key = src[srcOffset++] * 3;
-          dest[destOffset++] = colorMap[key];
-          dest[destOffset++] = colorMap[key + 1];
-          dest[destOffset++] = colorMap[key + 2];
+        this.getRgbBuffer(allColors, 0, numComponentColors, colorMap, 0, bpc,
+                          /* alpha01 = */ 0);
+
+        if (!needsResizing) {
+          // Fill in the RGB values directly into |rgbaBuf|.
+          var rgbaPos = 0;
+          for (var i = 0; i < count; ++i) {
+            var key = comps[i] * 3;
+            rgbaBuf[rgbaPos++] = colorMap[key];
+            rgbaBuf[rgbaPos++] = colorMap[key + 1];
+            rgbaBuf[rgbaPos++] = colorMap[key + 2];
+            rgbaPos++;
+          }
+        } else {
+          rgbBuf = new Uint8Array(count * 3);
+          var rgbPos = 0;
+          for (var i = 0; i < count; ++i) {
+            var key = comps[i] * 3;
+            rgbBuf[rgbPos++] = colorMap[key];
+            rgbBuf[rgbPos++] = colorMap[key + 1];
+            rgbBuf[rgbPos++] = colorMap[key + 2];
+          }
         }
-        return dest;
-      }
-      this.getRgbBuffer(src, srcOffset, count, dest, 0, bits);
-      return dest;
+      } else {
+        if (!needsResizing) {
+          // Fill in the RGB values directly into |rgbaBuf|.
+          this.getRgbBuffer(comps, 0, width * actualHeight, rgbaBuf, 0, bpc,
+                            /* alpha01 = */ 1);
+        } else {
+          rgbBuf = new Uint8Array(count * 3);
+          this.getRgbBuffer(comps, 0, count, rgbBuf, 0, bpc,
+                            /* alpha01 = */ 0);
+        }
+      }
+
+      if (rgbBuf) {
+        if (needsResizing) {
+          rgbBuf = PDFImage.resize(rgbBuf, bpc, 3, originalWidth,
+                                   originalHeight, width, height);
+        }
+        var rgbPos = 0;
+        var actualLength = width * actualHeight * 4;
+        for (var i = 0; i < actualLength; i += 4) {
+          rgbaBuf[i] = rgbBuf[rgbPos++];
+          rgbaBuf[i + 1] = rgbBuf[rgbPos++];
+          rgbaBuf[i + 2] = rgbBuf[rgbPos++];
+        }
+      }
     },
     /**
      * True if the colorspace has components in the default range of [0, 1].
      * This should be true for all colorspaces except for lab color spaces
      * which are [0,100], [-128, 127], [-128, 127].
      */
     usesZeroToOneRange: true
   };
@@ -1568,23 +1434,25 @@ var AlternateCS = (function AlternateCSC
       var baseNumComps = this.base.numComps;
       var input = 'subarray' in src ?
         src.subarray(srcOffset, srcOffset + this.numComps) :
         Array.prototype.slice.call(src, srcOffset, srcOffset + this.numComps);
       var tinted = this.tintFn(input);
       this.base.getRgbItem(tinted, 0, dest, destOffset);
     },
     getRgbBuffer: function AlternateCS_getRgbBuffer(src, srcOffset, count,
-                                                    dest, destOffset, bits) {
+                                                    dest, destOffset, bits,
+                                                    alpha01) {
       var tintFn = this.tintFn;
       var base = this.base;
       var scale = 1 / ((1 << bits) - 1);
       var baseNumComps = base.numComps;
       var usesZeroToOneRange = base.usesZeroToOneRange;
-      var isPassthrough = base.isPassthrough(8) || !usesZeroToOneRange;
+      var isPassthrough = (base.isPassthrough(8) || !usesZeroToOneRange) &&
+                          alpha01 === 0;
       var pos = isPassthrough ? destOffset : 0;
       var baseBuf = isPassthrough ? dest : new Uint8Array(baseNumComps * count);
       var numComps = this.numComps;
 
       var scaled = new Float32Array(numComps);
       for (var i = 0; i < count; i++) {
         for (var j = 0; j < numComps; j++) {
           scaled[j] = src[srcOffset++] * scale;
@@ -1595,25 +1463,27 @@ var AlternateCS = (function AlternateCSC
             baseBuf[pos++] = tinted[j] * 255;
           }
         } else {
           base.getRgbItem(tinted, 0, baseBuf, pos);
           pos += baseNumComps;
         }
       }
       if (!isPassthrough) {
-        base.getRgbBuffer(baseBuf, 0, count, dest, destOffset, 8);
-      }
-    },
-    getOutputLength: function AlternateCS_getOutputLength(inputLength) {
+        base.getRgbBuffer(baseBuf, 0, count, dest, destOffset, 8, alpha01);
+      }
+    },
+    getOutputLength: function AlternateCS_getOutputLength(inputLength,
+                                                          alpha01) {
       return this.base.getOutputLength(inputLength *
-                                       this.base.numComps / this.numComps);
+                                       this.base.numComps / this.numComps,
+                                       alpha01);
     },
     isPassthrough: ColorSpace.prototype.isPassthrough,
-    createRgbBuffer: ColorSpace.prototype.createRgbBuffer,
+    fillRgb: ColorSpace.prototype.fillRgb,
     isDefaultDecode: function AlternateCS_isDefaultDecode(decodeMap) {
       return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
     },
     usesZeroToOneRange: true
   };
 
   return AlternateCS;
 })();
@@ -1664,33 +1534,35 @@ var IndexedCS = (function IndexedCSClosu
     },
     getRgbItem: function IndexedCS_getRgbItem(src, srcOffset,
                                               dest, destOffset) {
       var numComps = this.base.numComps;
       var start = src[srcOffset] * numComps;
       this.base.getRgbItem(this.lookup, start, dest, destOffset);
     },
     getRgbBuffer: function IndexedCS_getRgbBuffer(src, srcOffset, count,
-                                                  dest, destOffset) {
+                                                  dest, destOffset, bits,
+                                                  alpha01) {
       var base = this.base;
       var numComps = base.numComps;
-      var outputDelta = base.getOutputLength(numComps);
+      var outputDelta = base.getOutputLength(numComps, alpha01);
       var lookup = this.lookup;
 
       for (var i = 0; i < count; ++i) {
         var lookupPos = src[srcOffset++] * numComps;
-        base.getRgbBuffer(lookup, lookupPos, 1, dest, destOffset, 8);
+        base.getRgbBuffer(lookup, lookupPos, 1, dest, destOffset, 8, alpha01);
         destOffset += outputDelta;
       }
     },
-    getOutputLength: function IndexedCS_getOutputLength(inputLength) {
-      return this.base.getOutputLength(inputLength * this.base.numComps);
+    getOutputLength: function IndexedCS_getOutputLength(inputLength, alpha01) {
+      return this.base.getOutputLength(inputLength * this.base.numComps,
+                                       alpha01);
     },
     isPassthrough: ColorSpace.prototype.isPassthrough,
-    createRgbBuffer: ColorSpace.prototype.createRgbBuffer,
+    fillRgb: ColorSpace.prototype.fillRgb,
     isDefaultDecode: function IndexedCS_isDefaultDecode(decodeMap) {
       // indexed color maps shouldn't be changed
       return true;
     },
     usesZeroToOneRange: true
   };
   return IndexedCS;
 })();
@@ -1710,31 +1582,34 @@ var DeviceGrayCS = (function DeviceGrayC
     },
     getRgbItem: function DeviceGrayCS_getRgbItem(src, srcOffset,
                                                  dest, destOffset) {
       var c = (src[srcOffset] * 255) | 0;
       c = c < 0 ? 0 : c > 255 ? 255 : c;
       dest[destOffset] = dest[destOffset + 1] = dest[destOffset + 2] = c;
     },
     getRgbBuffer: function DeviceGrayCS_getRgbBuffer(src, srcOffset, count,
-                                                     dest, destOffset, bits) {
+                                                     dest, destOffset, bits,
+                                                     alpha01) {
       var scale = 255 / ((1 << bits) - 1);
       var j = srcOffset, q = destOffset;
       for (var i = 0; i < count; ++i) {
         var c = (scale * src[j++]) | 0;
         dest[q++] = c;
         dest[q++] = c;
         dest[q++] = c;
-      }
-    },
-    getOutputLength: function DeviceGrayCS_getOutputLength(inputLength) {
-      return inputLength * 3;
+        q += alpha01;
+      }
+    },
+    getOutputLength: function DeviceGrayCS_getOutputLength(inputLength,
+                                                           alpha01) {
+      return inputLength * (3 + alpha01);
     },
     isPassthrough: ColorSpace.prototype.isPassthrough,
-    createRgbBuffer: ColorSpace.prototype.createRgbBuffer,
+    fillRgb: ColorSpace.prototype.fillRgb,
     isDefaultDecode: function DeviceGrayCS_isDefaultDecode(decodeMap) {
       return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
     },
     usesZeroToOneRange: true
   };
   return DeviceGrayCS;
 })();
 
@@ -1755,35 +1630,39 @@ var DeviceRgbCS = (function DeviceRgbCSC
       var r = (src[srcOffset] * 255) | 0;
       var g = (src[srcOffset + 1] * 255) | 0;
       var b = (src[srcOffset + 2] * 255) | 0;
       dest[destOffset] = r < 0 ? 0 : r > 255 ? 255 : r;
       dest[destOffset + 1] = g < 0 ? 0 : g > 255 ? 255 : g;
       dest[destOffset + 2] = b < 0 ? 0 : b > 255 ? 255 : b;
     },
     getRgbBuffer: function DeviceRgbCS_getRgbBuffer(src, srcOffset, count,
-                                                    dest, destOffset, bits) {
-      var length = count * 3;
-      if (bits == 8) {
-        dest.set(src.subarray(srcOffset, srcOffset + length), destOffset);
+                                                    dest, destOffset, bits,
+                                                    alpha01) {
+      if (bits === 8 && alpha01 === 0) {
+        dest.set(src.subarray(srcOffset, srcOffset + count * 3), destOffset);
         return;
       }
       var scale = 255 / ((1 << bits) - 1);
       var j = srcOffset, q = destOffset;
-      for (var i = 0; i < length; ++i) {
+      for (var i = 0; i < count; ++i) {
+        dest[q++] = (scale * src[j++]) | 0;
+        dest[q++] = (scale * src[j++]) | 0;
         dest[q++] = (scale * src[j++]) | 0;
-      }
-    },
-    getOutputLength: function DeviceRgbCS_getOutputLength(inputLength) {
-      return inputLength;
+        q += alpha01;
+      }
+    },
+    getOutputLength: function DeviceRgbCS_getOutputLength(inputLength,
+                                                          alpha01) {
+      return (inputLength * (3 + alpha01) / 3) | 0;
     },
     isPassthrough: function DeviceRgbCS_isPassthrough(bits) {
       return bits == 8;
     },
-    createRgbBuffer: ColorSpace.prototype.createRgbBuffer,
+    fillRgb: ColorSpace.prototype.fillRgb,
     isDefaultDecode: function DeviceRgbCS_isDefaultDecode(decodeMap) {
       return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
     },
     usesZeroToOneRange: true
   };
   return DeviceRgbCS;
 })();
 
@@ -1843,46 +1722,48 @@ var DeviceCmykCS = (function DeviceCmykC
       convertToRgb(src, srcOffset, 1, rgb, 0);
       return rgb;
     },
     getRgbItem: function DeviceCmykCS_getRgbItem(src, srcOffset,
                                                  dest, destOffset) {
       convertToRgb(src, srcOffset, 1, dest, destOffset);
     },
     getRgbBuffer: function DeviceCmykCS_getRgbBuffer(src, srcOffset, count,
-                                                     dest, destOffset, bits) {
+                                                     dest, destOffset, bits,
+                                                     alpha01) {
       var scale = 1 / ((1 << bits) - 1);
       for (var i = 0; i < count; i++) {
         convertToRgb(src, srcOffset, scale, dest, destOffset);
         srcOffset += 4;
-        destOffset += 3;
-      }
-    },
-    getOutputLength: function DeviceCmykCS_getOutputLength(inputLength) {
-      return (inputLength >> 2) * 3;
+        destOffset += 3 + alpha01;
+      }
+    },
+    getOutputLength: function DeviceCmykCS_getOutputLength(inputLength,
+                                                           alpha01) {
+      return (inputLength / 4 * (3 + alpha01)) | 0;
     },
     isPassthrough: ColorSpace.prototype.isPassthrough,
-    createRgbBuffer: ColorSpace.prototype.createRgbBuffer,
+    fillRgb: ColorSpace.prototype.fillRgb,
     isDefaultDecode: function DeviceCmykCS_isDefaultDecode(decodeMap) {
       return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
     },
     usesZeroToOneRange: true
   };
 
   return DeviceCmykCS;
 })();
 
 //
 // CalGrayCS: Based on "PDF Reference, Sixth Ed", p.245
 //
 var CalGrayCS = (function CalGrayCSClosure() {
   function CalGrayCS(whitePoint, blackPoint, gamma) {
     this.name = 'CalGray';
-    this.numComps = 3;
-    this.defaultColor = new Float32Array([0, 0, 0]);
+    this.numComps = 1;
+    this.defaultColor = new Float32Array([0]);
 
     if (!whitePoint) {
       error('WhitePoint missing - required for color space CalGray');
     }
     blackPoint = blackPoint || [0, 0, 0];
     gamma = gamma || 1;
 
     // Translate arguments to spec variables.
@@ -1903,77 +1784,80 @@ var CalGrayCS = (function CalGrayCSClosu
     }
 
     if (this.XB < 0 || this.YB < 0 || this.ZB < 0) {
       info('Invalid BlackPoint for ' + this.name + ', falling back to default');
       this.XB = this.YB = this.ZB = 0;
     }
 
     if (this.XB !== 0 || this.YB !== 0 || this.ZB !== 0) {
-      TODO(this.name + ', BlackPoint: XB: ' + this.XB + ', YB: ' + this.YB +
+      warn(this.name + ', BlackPoint: XB: ' + this.XB + ', YB: ' + this.YB +
            ', ZB: ' + this.ZB + ', only default values are supported.');
     }
 
     if (this.G < 1) {
       info('Invalid Gamma: ' + this.G + ' for ' + this.name +
            ', falling back to default');
       this.G = 1;
     }
   }
 
+  function convertToRgb(cs, src, srcOffset, dest, destOffset, scale) {
+    // A represents a gray component of a calibrated gray space.
+    // A <---> AG in the spec
+    var A = src[srcOffset] * scale;
+    var AG = Math.pow(A, cs.G);
+
+    // Computes intermediate variables M, L, N as per spec.
+    // Except if other than default BlackPoint values are used.
+    var M = cs.XW * AG;
+    var L = cs.YW * AG;
+    var N = cs.ZW * AG;
+
+    // Decode XYZ, as per spec.
+    var X = M;
+    var Y = L;
+    var Z = N;
+
+    // http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html, Ch 4.
+    // This yields values in range [0, 100].
+    var Lstar = Math.max(116 * Math.pow(Y, 1 / 3) - 16, 0);
+
+    // Convert values to rgb range [0, 255].
+    dest[destOffset] = Lstar * 255 / 100;
+    dest[destOffset + 1] = Lstar * 255 / 100;
+    dest[destOffset + 2] = Lstar * 255 / 100;
+  }
+
   CalGrayCS.prototype = {
     getRgb: function CalGrayCS_getRgb(src, srcOffset) {
       var rgb = new Uint8Array(3);
       this.getRgbItem(src, srcOffset, rgb, 0);
       return rgb;
     },
     getRgbItem: function CalGrayCS_getRgbItem(src, srcOffset,
                                               dest, destOffset) {
-      // A represents a gray component of a calibrated gray space.
-      // A <---> AG in the spec
-      var A = src[srcOffset];
-      var AG = Math.pow(A, this.G);
-
-      // Computes intermediate variables M, L, N as per spec.
-      // Except if other than default BlackPoint values are used.
-      var M = this.XW * AG;
-      var L = this.YW * AG;
-      var N = this.ZW * AG;
-
-      // Decode XYZ, as per spec.
-      var X = M;
-      var Y = L;
-      var Z = N;
-
-      // http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html, Ch 4.
-      // This yields values in range [0, 100].
-      var Lstar = Math.max(116 * Math.pow(Y, 1 / 3) - 16, 0);
-
-      // Convert values to rgb range [0, 255].
-      dest[destOffset] = Lstar * 255 / 100;
-      dest[destOffset + 1] = Lstar * 255 / 100;
-      dest[destOffset + 2] = Lstar * 255 / 100;
+      convertToRgb(this, src, srcOffset, dest, destOffset, 1);
     },
     getRgbBuffer: function CalGrayCS_getRgbBuffer(src, srcOffset, count,
-                                                  dest, destOffset, bits) {
-      // TODO: This part is copied from DeviceGray. Make this utility function.
-      var scale = 255 / ((1 << bits) - 1);
-      var j = srcOffset, q = destOffset;
+                                                  dest, destOffset, bits,
+                                                  alpha01) {
+      var scale = 1 / ((1 << bits) - 1);
+
       for (var i = 0; i < count; ++i) {
-        var c = (scale * src[j++]) | 0;
-        dest[q++] = c;
-        dest[q++] = c;
-        dest[q++] = c;
-      }
-    },
-    getOutputLength: function CalGrayCS_getOutputLength(inputLength) {
-      return inputLength * 3;
+        convertToRgb(this, src, srcOffset, dest, destOffset, scale);
+        srcOffset += 1;
+        destOffset += 3 + alpha01;
+      }
+    },
+    getOutputLength: function CalGrayCS_getOutputLength(inputLength, alpha01) {
+      return inputLength * (3 + alpha01);
     },
     isPassthrough: ColorSpace.prototype.isPassthrough,
-    createRgbBuffer: ColorSpace.prototype.createRgbBuffer,
+    fillRgb: ColorSpace.prototype.fillRgb,
     isDefaultDecode: function CalGrayCS_isDefaultDecode(decodeMap) {
       return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
     },
     usesZeroToOneRange: true
   };
   return CalGrayCS;
 })();
 
@@ -2091,26 +1975,27 @@ var LabCS = (function LabCSClosure() {
       var rgb = new Uint8Array(3);
       convertToRgb(this, src, srcOffset, false, rgb, 0);
       return rgb;
     },
     getRgbItem: function LabCS_getRgbItem(src, srcOffset, dest, destOffset) {
       convertToRgb(this, src, srcOffset, false, dest, destOffset);
     },
     getRgbBuffer: function LabCS_getRgbBuffer(src, srcOffset, count,
-                                              dest, destOffset, bits) {
+                                              dest, destOffset, bits,
+                                              alpha01) {
       var maxVal = (1 << bits) - 1;
       for (var i = 0; i < count; i++) {
         convertToRgb(this, src, srcOffset, maxVal, dest, destOffset);
         srcOffset += 3;
-        destOffset += 3;
-      }
-    },
-    getOutputLength: function LabCS_getOutputLength(inputLength) {
-      return inputLength;
+        destOffset += 3 + alpha01;
+      }
+    },
+    getOutputLength: function LabCS_getOutputLength(inputLength, alpha01) {
+      return (inputLength * (3 + alpha01) / 3) | 0;
     },
     isPassthrough: ColorSpace.prototype.isPassthrough,
     isDefaultDecode: function LabCS_isDefaultDecode(decodeMap) {
       // XXX: Decoding is handled with the lab conversion because of the strange
       // ranges that are used.
       return true;
     },
     usesZeroToOneRange: false
@@ -2150,17 +2035,17 @@ var Pattern = (function PatternClosure()
     var type = dict.get('ShadingType');
 
     switch (type) {
       case PatternType.AXIAL:
       case PatternType.RADIAL:
         // Both radial and axial shadings are handled by RadialAxial shading.
         return new Shadings.RadialAxial(dict, matrix, xref, res);
       default:
-        TODO('Unsupported shading type: ' + type);
+        UnsupportedManager.notify(UNSUPPORTED_FEATURES.shadingPattern);
         return new Shadings.Dummy();
     }
   };
   return Pattern;
 })();
 
 var Shadings = {};
 
@@ -2410,17 +2295,17 @@ var TilingPattern = (function TilingPatt
       var ystep = this.ystep;
       var paintType = this.paintType;
       var tilingType = this.tilingType;
       var color = this.color;
       var objs = this.objs;
       var commonObjs = this.commonObjs;
       var ctx = this.ctx;
 
-      TODO('TilingType: ' + tilingType);
+      info('TilingType: ' + tilingType);
 
       var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3];
 
       var topLeft = [x0, y0];
       // we want the canvas to be as large as the step size
       var botRight = [x0 + xstep, y0 + ystep];
 
       var width = botRight[0] - topLeft[0];
@@ -2623,17 +2508,17 @@ var PDFFunction = (function PDFFunctionC
       range = toMultiArray(range);
 
       var size = dict.get('Size');
       var bps = dict.get('BitsPerSample');
       var order = dict.get('Order') || 1;
       if (order !== 1) {
         // No description how cubic spline interpolation works in PDF32000:2008
         // As in poppler, ignoring order, linear interpolation may work as good
-        TODO('No support for cubic spline interpolation: ' + order);
+        info('No support for cubic spline interpolation: ' + order);
       }
 
       var encode = dict.get('Encode');
       if (!encode) {
         encode = [];
         for (var i = 0; i < inputSize; ++i) {
           encode.push(0);
           encode.push(size[i] - 1);
@@ -3472,16 +3357,39 @@ var Annotation = (function AnnotationClo
     // Some types of annotations have border style dict which has more
     // info than the border array
     if (dict.has('BS')) {
       var borderStyle = dict.get('BS');
       data.borderWidth = borderStyle.has('W') ? borderStyle.get('W') : 1;
     } else {
       var borderArray = dict.get('Border') || [0, 0, 1];
       data.borderWidth = borderArray[2] || 0;
+
+      // TODO: implement proper support for annotations with line dash patterns.
+      var dashArray = borderArray[3];
+      if (data.borderWidth > 0 && dashArray && isArray(dashArray)) {
+        var dashArrayLength = dashArray.length;
+        if (dashArrayLength > 0) {
+          // According to the PDF specification: the elements in a dashArray
+          // shall be numbers that are nonnegative and not all equal to zero.
+          var isInvalid = false;
+          var numPositive = 0;
+          for (var i = 0; i < dashArrayLength; i++) {
+            if (!(+dashArray[i] >= 0)) {
+              isInvalid = true;
+              break;
+            } else if (dashArray[i] > 0) {
+              numPositive++;
+            }
+          }
+          if (isInvalid || numPositive === 0) {
+            data.borderWidth = 0;
+          }
+        }
+      }
     }
 
     this.appearance = getDefaultAppearance(dict);
     data.hasAppearance = !!this.appearance;
   }
 
   Annotation.prototype = {
 
@@ -3516,17 +3424,17 @@ var Annotation = (function AnnotationClo
         data &&
         (!data.annotationFlags ||
          !(data.annotationFlags & 0x22)) && // Hidden or NoView
         data.rect                            // rectangle is nessessary
       );
     },
 
     loadResources: function(keys) {
-      var promise = new Promise();
+      var promise = new LegacyPromise();
       this.appearance.dict.getAsync('Resources').then(function(resources) {
         if (!resources) {
           promise.resolve();
           return;
         }
         var objectLoader = new ObjectLoader(resources.map,
                                             keys,
                                             resources.xref);
@@ -3535,17 +3443,17 @@ var Annotation = (function AnnotationClo
         });
       }.bind(this));
 
       return promise;
     },
 
     getOperatorList: function Annotation_getToOperatorList(evaluator) {
 
-      var promise = new Promise();
+      var promise = new LegacyPromise();
 
       if (!this.appearance) {
         promise.resolve(new OperatorList());
         return promise;
       }
 
       var data = this.data;
 
@@ -3641,28 +3549,28 @@ var Annotation = (function AnnotationClo
       ref: ref,
     };
 
     var annotation = new Constructor(params);
 
     if (annotation.isViewable()) {
       return annotation;
     } else {
-      TODO('unimplemented annotation type: ' + subtype);
+      warn('unimplemented annotation type: ' + subtype);
     }
   };
 
   Annotation.appendToOperatorList = function Annotation_appendToOperatorList(
       annotations, opList, pdfManager, partialEvaluator) {
 
     function reject(e) {
       annotationsReadyPromise.reject(e);
     }
 
-    var annotationsReadyPromise = new Promise();
+    var annotationsReadyPromise = new LegacyPromise();
 
     var annotationPromises = [];
     for (var i = 0, n = annotations.length; i < n; ++i) {
       annotationPromises.push(annotations[i].getOperatorList(partialEvaluator));
     }
     Promise.all(annotationPromises).then(function(datas) {
       opList.addOp(OPS.beginAnnotations, []);
       for (var i = 0, n = datas.length; i < n; ++i) {
@@ -3733,17 +3641,17 @@ var WidgetAnnotation = (function WidgetA
     }
     data.fullName = fieldName.join('.');
   }
 
   var parent = Annotation.prototype;
   Util.inherit(WidgetAnnotation, Annotation, {
     isViewable: function WidgetAnnotation_isViewable() {
       if (this.data.fieldType === 'Sig') {
-        TODO('unimplemented annotation type: Widget signature');
+        warn('unimplemented annotation type: Widget signature');
         return false;
       }
 
       return parent.isViewable.call(this);
     }
   });
 
   return WidgetAnnotation;
@@ -3814,17 +3722,17 @@ var TextWidgetAnnotation = (function Tex
       return element;
     },
 
     getOperatorList: function TextWidgetAnnotation_getOperatorList(evaluator) {
       if (this.appearance) {
         return Annotation.prototype.getOperatorList.call(this, evaluator);
       }
 
-      var promise = new Promise();
+      var promise = new LegacyPromise();
       var opList = new OperatorList();
       var data = this.data;
 
       // Even if there is an appearance stream, ignore it. This is the
       // behaviour used by Adobe Reader.
 
       var defaultAppearance = data.defaultAppearance;
       if (!defaultAppearance) {
@@ -3891,17 +3799,17 @@ var TextAnnotation = (function TextAnnot
     data.name = !dict.has('Name') ? 'Note' : dict.get('Name').name;
   }
 
   var ANNOT_MIN_SIZE = 10;
 
   Util.inherit(TextAnnotation, Annotation, {
 
     getOperatorList: function TextAnnotation_getOperatorList(evaluator) {
-      var promise = new Promise();
+      var promise = new LegacyPromise();
       promise.resolve(new OperatorList());
       return promise;
     },
 
     hasHtml: function TextAnnotation_hasHtml() {
       return true;
     },
 
@@ -3991,17 +3899,23 @@ var LinkAnnotation = (function LinkAnnot
 
     var dict = params.dict;
     var data = this.data;
 
     var action = dict.get('A');
     if (action) {
       var linkType = action.get('S').name;
       if (linkType === 'URI') {
-        var url = addDefaultProtocolToUrl(action.get('URI'));
+        var url = action.get('URI');
+        if (isName(url)) {
+          // Some bad PDFs do not put parentheses around relative URLs.
+          url = '/' + url.name;
+        } else {
+          url = addDefaultProtocolToUrl(url);
+        }
         // TODO: pdf spec mentions urls can be relative to a Base
         // entry in the dictionary.
         if (!isValidUrl(url, false)) {
           url = '';
         }
         data.url = url;
       } else if (linkType === 'GoTo') {
         data.dest = action.get('D');
@@ -4018,17 +3932,17 @@ var LinkAnnotation = (function LinkAnnot
         if (!isValidUrl(url, false)) {
           url = '';
         }
         data.url = url;
         data.dest = action.get('D');
       } else if (linkType === 'Named') {
         data.action = action.get('N').name;
       } else {
-        TODO('unrecognized link type: ' + linkType);
+        warn('unrecognized link type: ' + linkType);
       }
     } else if (dict.has('Dest')) {
       // simple destination link
       var dest = dict.get('Dest');
       data.dest = isName(dest) ? dest.name : dest;
     }
   }
 
@@ -4075,111 +3989,140 @@ var LinkAnnotation = (function LinkAnnot
 
   return LinkAnnotation;
 })();
 
 
 /**
  * The maximum allowed image size in total pixels e.g. width * height. Images
  * above this value will not be drawn. Use -1 for no limit.
- * @var {Number}
+ * @var {number}
  */
 PDFJS.maxImageSize = PDFJS.maxImageSize === undefined ? -1 : PDFJS.maxImageSize;
 
 /**
  * By default fonts are converted to OpenType fonts and loaded via font face
  * rules. If disabled, the font will be rendered using a built in font renderer
  * that constructs the glyphs with primitive path commands.
- * @var {Boolean}
+ * @var {boolean}
  */
 PDFJS.disableFontFace = PDFJS.disableFontFace === undefined ?
                         false : PDFJS.disableFontFace;
 
 /**
  * Path for image resources, mainly for annotation icons. Include trailing
  * slash.
- * @var {String}
+ * @var {string}
  */
 PDFJS.imageResourcesPath = PDFJS.imageResourcesPath === undefined ?
                            '' : PDFJS.imageResourcesPath;
 
 /**
  * Disable the web worker and run all code on the main thread. This will happen
  * automatically if the browser doesn't support workers or sending typed arrays
  * to workers.
- * @var {Boolean}
+ * @var {boolean}
  */
 PDFJS.disableWorker = PDFJS.disableWorker === undefined ?
                       false : PDFJS.disableWorker;
 
 /**
  * Path and filename of the worker file. Required when the worker is enabled in
  * development mode. If unspecified in the production build, the worker will be
  * loaded based on the location of the pdf.js file.
- * @var {String}
+ * @var {string}
  */
 PDFJS.workerSrc = PDFJS.workerSrc === undefined ? null : PDFJS.workerSrc;
 
 /**
  * Disable range request loading of PDF files. When enabled and if the server
  * supports partial content requests then the PDF will be fetched in chunks.
  * Enabled (false) by default.
- * @var {Boolean}
+ * @var {boolean}
  */
 PDFJS.disableRange = PDFJS.disableRange === undefined ?
                      false : PDFJS.disableRange;
 
 /**
  * Disable pre-fetching of PDF file data. When range requests are enabled PDF.js
  * will automatically keep fetching more data even if it isn't needed to display
  * the current page. This default behavior can be disabled.
- * @var {Boolean}
+ * @var {boolean}
  */
 PDFJS.disableAutoFetch = PDFJS.disableAutoFetch === undefined ?
                          false : PDFJS.disableAutoFetch;
 
 /**
  * Enables special hooks for debugging PDF.js.
- * @var {Boolean}
+ * @var {boolean}
  */
 PDFJS.pdfBug = PDFJS.pdfBug === undefined ? false : PDFJS.pdfBug;
 
 /**
  * Enables transfer usage in postMessage for ArrayBuffers.
  * @var {boolean}
  */
 PDFJS.postMessageTransfers = PDFJS.postMessageTransfers === undefined ?
                              true : PDFJS.postMessageTransfers;
+
+/**
+ * Disables URL.createObjectURL usage.
+ * @var {boolean}
+ */
+PDFJS.disableCreateObjectURL = PDFJS.disableCreateObjectURL === undefined ?
+                               false : PDFJS.disableCreateObjectURL;
+
+/**
+ * Controls the logging level.
+ * The constants from PDFJS.VERBOSITY_LEVELS should be used:
+ * - errors
+ * - warnings [default]
+ * - infos
+ * @var {number}
+ */
+PDFJS.verbosity = PDFJS.verbosity === undefined ?
+                  PDFJS.VERBOSITY_LEVELS.warnings : PDFJS.verbosity;
+
+/**
+ * Document initialization / loading parameters object.
+ *
+ * @typedef {Object} DocumentInitParameters
+ * @property {string}     url   - The URL of the PDF.
+ * @property {TypedArray} data  - A typed array with PDF data.
+ * @property {Object}     httpHeaders - Basic authentication headers.
+ * @property {boolean}    withCredentials - Indicates whether or not cross-site
+ *   Access-Control requests should be made using credentials such as cookies
+ *   or authorization headers. The default is false.
+ * @property {string}     password - For decrypting password-protected PDFs.
+ * @property {TypedArray} initialData - A typed array with the first portion or
+ *   all of the pdf data. Used by the extension since some data is already
+ *   loaded before the switch to range requests.
+ */
+
 /**
  * This is the main entry point for loading a PDF and interacting with it.
  * NOTE: If a URL is used to fetch the PDF data a standard XMLHttpRequest(XHR)
  * is used, which means it must follow the same origin rules that any XHR does
  * e.g. No cross domain requests without CORS.
  *
- * @param {string|TypedAray|object} source Can be an url to where a PDF is
- * located, a typed array (Uint8Array) already populated with data or
- * and parameter object with the following possible fields:
- *  - url   - The URL of the PDF.
- *  - data  - A typed array with PDF data.
- *  - httpHeaders - Basic authentication headers.
- *  - password - For decrypting password-protected PDFs.
- *  - initialData - A typed array with the first portion or all of the pdf data.
- *                  Used by the extension since some data is already loaded
- *                  before the switch to range requests. 
+ * @param {string|TypedArray|DocumentInitParameters} source Can be a url to
+ * where a PDF is located, a typed array (Uint8Array) already populated with
+ * data or parameter object.
  *
- * @param {object} pdfDataRangeTransport is optional. It is used if you want
+ * @param {Object} pdfDataRangeTransport is optional. It is used if you want
  * to manually serve range requests for data in the PDF. See viewer.js for
  * an example of pdfDataRangeTransport's interface.
  *
  * @param {function} passwordCallback is optional. It is used to request a
  * password if wrong or no password was provided. The callback receives two
  * parameters: function that needs to be called with new password and reason
  * (see {PasswordResponses}).
  *
- * @return {Promise} A promise that is resolved with {PDFDocumentProxy} object.
+ * @return {Promise} A promise that is resolved with {@link PDFDocumentProxy}
+ *   object.
  */
 PDFJS.getDocument = function getDocument(source,
                                          pdfDataRangeTransport,
                                          passwordCallback,
                                          progressCallback) {
   var workerInitializedPromise, workerReadyPromise, transport;
 
   if (typeof source === 'string') {
@@ -4199,37 +4142,38 @@ PDFJS.getDocument = function getDocument
   for (var key in source) {
     if (key === 'url' && typeof window !== 'undefined') {
       params[key] = combineUrl(window.location.href, source[key]);
       continue;
     }
     params[key] = source[key];
   }
 
-  workerInitializedPromise = new PDFJS.Promise();
-  workerReadyPromise = new PDFJS.Promise();
+  workerInitializedPromise = new PDFJS.LegacyPromise();
+  workerReadyPromise = new PDFJS.LegacyPromise();
   transport = new WorkerTransport(workerInitializedPromise,
       workerReadyPromise, pdfDataRangeTransport, progressCallback);
   workerInitializedPromise.then(function transportInitialized() {
     transport.passwordCallback = passwordCallback;
     transport.fetchDocument(params);
   });
   return workerReadyPromise;
 };
 
 /**
  * Proxy to a PDFDocument in the worker thread. Also, contains commonly used
  * properties that can be read synchronously.
+ * @class
  */
 var PDFDocumentProxy = (function PDFDocumentProxyClosure() {
   function PDFDocumentProxy(pdfInfo, transport) {
     this.pdfInfo = pdfInfo;
     this.transport = transport;
   }
-  PDFDocumentProxy.prototype = {
+  PDFDocumentProxy.prototype = /** @lends PDFDocumentProxy.prototype */ {
     /**
      * @return {number} Total number of pages the PDF contains.
      */
     get numPages() {
       return this.pdfInfo.numPages;
     },
     /**
      * @return {string} A unique ID to identify a PDF. Not guaranteed to be
@@ -4241,25 +4185,26 @@ var PDFDocumentProxy = (function PDFDocu
     /**
      * @return {boolean} true if embedded document fonts are in use. Will be
      * set during rendering of the pages.
      */
     get embeddedFontsUsed() {
       return this.transport.embeddedFontsUsed;
     },
     /**
-     * @param {number} The page number to get. The first page is 1.
-     * @return {Promise} A promise that is resolved with a {PDFPageProxy}
+     * @param {number} pageNumber The page number to get. The first page is 1.
+     * @return {Promise} A promise that is resolved with a {@link PDFPageProxy}
      * object.
      */
-    getPage: function PDFDocumentProxy_getPage(number) {
-      return this.transport.getPage(number);
+    getPage: function PDFDocumentProxy_getPage(pageNumber) {
+      return this.transport.getPage(pageNumber);
     },
     /**
-     * @param {object} Must have 'num' and 'gen' properties.
+     * @param {{num: number, gen: number}} ref The page reference. Must have
+     *   the 'num' and 'gen' properties.
      * @return {Promise} A promise that is resolved with the page index that is
      * associated with the reference.
      */
     getPageIndex: function PDFDocumentProxy_getPageIndex(ref) {
       return this.transport.getPageIndex(ref);
     },
     /**
      * @return {Promise} A promise that is resolved with a lookup table for
@@ -4267,125 +4212,142 @@ var PDFDocumentProxy = (function PDFDocu
      */
     getDestinations: function PDFDocumentProxy_getDestinations() {
       return this.transport.getDestinations();
     },
     /**
      * @return {Promise} A promise that is resolved with an array of all the
      * JavaScript strings in the name tree.
      */
-    getJavaScript: function PDFDocumentProxy_getDestinations() {
-      var promise = new PDFJS.Promise();
+    getJavaScript: function PDFDocumentProxy_getJavaScript() {
+      var promise = new PDFJS.LegacyPromise();
       var js = this.pdfInfo.javaScript;
       promise.resolve(js);
       return promise;
     },
     /**
-     * @return {Promise} A promise that is resolved with an {array} that is a
+     * @return {Promise} A promise that is resolved with an {Array} that is a
      * tree outline (if it has one) of the PDF. The tree is in the format of:
      * [
      *  {
      *   title: string,
      *   bold: boolean,
      *   italic: boolean,
      *   color: rgb array,
      *   dest: dest obj,
      *   items: array of more items like this
      *  },
      *  ...
      * ].
      */
     getOutline: function PDFDocumentProxy_getOutline() {
-      var promise = new PDFJS.Promise();
+      var promise = new PDFJS.LegacyPromise();
       var outline = this.pdfInfo.outline;
       promise.resolve(outline);
       return promise;
     },
     /**
-     * @return {Promise} A promise that is resolved with an {object} that has
-     * info and metadata properties.  Info is an {object} filled with anything
+     * @return {Promise} A promise that is resolved with an {Object} that has
+     * info and metadata properties.  Info is an {Object} filled with anything
      * available in the information dictionary and similarly metadata is a
      * {Metadata} object with information from the metadata section of the PDF.
      */
     getMetadata: function PDFDocumentProxy_getMetadata() {
-      var promise = new PDFJS.Promise();
+      var promise = new PDFJS.LegacyPromise();
       var info = this.pdfInfo.info;
       var metadata = this.pdfInfo.metadata;
       promise.resolve({
         info: info,
         metadata: metadata ? new PDFJS.Metadata(metadata) : null
       });
       return promise;
     },
-    isEncrypted: function PDFDocumentProxy_isEncrypted() {
-      var promise = new PDFJS.Promise();
-      promise.resolve(this.pdfInfo.encrypted);
-      return promise;
-    },
     /**
      * @return {Promise} A promise that is resolved with a TypedArray that has
      * the raw data from the PDF.
      */
     getData: function PDFDocumentProxy_getData() {
-      var promise = new PDFJS.Promise();
+      var promise = new PDFJS.LegacyPromise();
       this.transport.getData(promise);
       return promise;
     },
     /**
      * @return {Promise} A promise that is resolved when the document's data
      * is loaded
      */
     dataLoaded: function PDFDocumentProxy_dataLoaded() {
       return this.transport.dataLoaded();
     },
+    /**
+     * Cleans up resources allocated by the document, e.g. created @font-face.
+     */
     cleanup: function PDFDocumentProxy_cleanup() {
       this.transport.startCleanup();
     },
+    /**
+     * Destroys current document instance and terminates worker.
+     */
     destroy: function PDFDocumentProxy_destroy() {
       this.transport.destroy();
     }
   };
   return PDFDocumentProxy;
 })();
 
+/**
+ * Page text content part.
+ *
+ * @typedef {Object} BidiText
+ * @property {string} str - text content.
+ * @property {string} dir - text direction: 'ttb', 'ltr' or 'rtl'.
+ * @property {number} x - x position of the text on the page.
+ * @property {number} y - y position of the text on the page.
+ * @property {number} angle - text rotation.
+ * @property {number} size - font size.
+ */
+
+/**
+ * Proxy to a PDFPage in the worker thread.
+ * @class
+ */
 var PDFPageProxy = (function PDFPageProxyClosure() {
   function PDFPageProxy(pageInfo, transport) {
     this.pageInfo = pageInfo;
     this.transport = transport;
     this.stats = new StatTimer();
     this.stats.enabled = !!globalScope.PDFJS.enableStats;
     this.commonObjs = transport.commonObjs;
     this.objs = new PDFObjects();
     this.receivingOperatorList  = false;
     this.cleanupAfterRender = false;
     this.pendingDestroy = false;
     this.renderTasks = [];
   }
-  PDFPageProxy.prototype = {
+  PDFPageProxy.prototype = /** @lends PDFPageProxy.prototype */ {
     /**
      * @return {number} Page number of the page. First page is 1.
      */
     get pageNumber() {
       return this.pageInfo.pageIndex + 1;
     },
     /**
      * @return {number} The number of degrees the page is rotated clockwise.
      */
     get rotate() {
       return this.pageInfo.rotate;
     },
     /**
-     * @return {object} The reference that points to this page. It has 'num' and
+     * @return {Object} The reference that points to this page. It has 'num' and
      * 'gen' properties.
      */
     get ref() {
       return this.pageInfo.ref;
     },
     /**
-     * @return {array} An array of the visible portion of the PDF page in the
+     * @return {Array} An array of the visible portion of the PDF page in the
      * user space units - [x1, y1, x2, y2].
      */
     get view() {
       return this.pageInfo.view;
     },
     /**
      * @param {number} scale The desired scale of the viewport.
      * @param {number} rotate Degrees to rotate the viewport. If omitted this
@@ -4394,31 +4356,31 @@ var PDFPageProxy = (function PDFPageProx
      * with transforms required for rendering.
      */
     getViewport: function PDFPageProxy_getViewport(scale, rotate) {
       if (arguments.length < 2)
         rotate = this.rotate;
       return new PDFJS.PageViewport(this.view, scale, rotate, 0, 0);
     },
     /**
-     * @return {Promise} A promise that is resolved with an {array} of the
+     * @return {Promise} A promise that is resolved with an {Array} of the
      * annotation objects.
      */
     getAnnotations: function PDFPageProxy_getAnnotations() {
       if (this.annotationsPromise)
         return this.annotationsPromise;
 
-      var promise = new PDFJS.Promise();
+      var promise = new PDFJS.LegacyPromise();
       this.annotationsPromise = promise;
       this.transport.getAnnotations(this.pageInfo.pageIndex);
       return promise;
     },
     /**
      * Begins the process of rendering a page to the desired context.
-     * @param {object} params A parameter object that supports:
+     * @param {Object} params A parameter object that supports:
      * {
      *   canvasContext(required): A 2D context of a DOM Canvas object.,
      *   textLayer(optional): An object that has beginLayout, endLayout, and
      *                        appendText functions.,
      *   imageLayer(optional): An object that has beginLayout, endLayout and
      *                         appendImage functions.,
      *   continueCallback(optional): A function that will be called each time
      *                               the rendering is paused.  To continue
@@ -4435,17 +4397,17 @@ var PDFPageProxy = (function PDFPageProx
       // If there was a pending destroy cancel it so no cleanup happens during
       // this call to render.
       this.pendingDestroy = false;
 
       // If there is no displayReadyPromise yet, then the operatorList was never
       // requested before. Make the request and create the promise.
       if (!this.displayReadyPromise) {
         this.receivingOperatorList = true;
-        this.displayReadyPromise = new Promise();
+        this.displayReadyPromise = new LegacyPromise();
         this.operatorList = {
           fnArray: [],
           argsArray: [],
           lastChunk: false
         };
 
         this.stats.time('Page Request');
         this.transport.messageHandler.send('RenderPageRequest', {
@@ -4482,84 +4444,75 @@ var PDFPageProxy = (function PDFPageProx
         }
 
         if (self.cleanupAfterRender) {
           self.pendingDestroy = true;
         }
         self._tryDestroy();
 
         if (error) {
-          renderTask.reject(error);
+          renderTask.promise.reject(error);
         } else {
-          renderTask.resolve();
+          renderTask.promise.resolve();
         }
         stats.timeEnd('Rendering');
         stats.timeEnd('Overall');
       }
 
       return renderTask;
     },
     /**
-     * @return {Promise} That is resolved with the a {string} that is the text
-     * content from the page.
+     * @return {Promise} That is resolved with the array of {@link BidiText}
+     * objects that represent the page text content.
      */
     getTextContent: function PDFPageProxy_getTextContent() {
-      var promise = new PDFJS.Promise();
+      var promise = new PDFJS.LegacyPromise();
       this.transport.messageHandler.send('GetTextContent', {
           pageIndex: this.pageNumber - 1
         },
         function textContentCallback(textContent) {
           promise.resolve(textContent);
         }
       );
       return promise;
     },
     /**
-     * Stub for future feature.
-     */
-    getOperationList: function PDFPageProxy_getOperationList() {
-      var promise = new PDFJS.Promise();
-      var operationList = { // not implemented
-        dependencyFontsID: null,
-        operatorList: null
-      };
-      promise.resolve(operationList);
-      return promise;
-    },
-    /**
      * Destroys resources allocated by the page.
      */
     destroy: function PDFPageProxy_destroy() {
       this.pendingDestroy = true;
       this._tryDestroy();
     },
     /**
      * For internal use only. Attempts to clean up if rendering is in a state
      * where that's possible.
+     * @ignore
      */
     _tryDestroy: function PDFPageProxy__destroy() {
       if (!this.pendingDestroy ||
           this.renderTasks.length !== 0 ||
           this.receivingOperatorList) {
         return;
       }
 
       delete this.operatorList;
       delete this.displayReadyPromise;
       this.objs.clear();
       this.pendingDestroy = false;
     },
     /**
      * For internal use only.
+     * @ignore
      */
     _startRenderPage: function PDFPageProxy_startRenderPage(transparency) {
       this.displayReadyPromise.resolve(transparency);
     },
     /**
      * For internal use only.
+     * @ignore
      */
     _renderPageChunk: function PDFPageProxy_renderPageChunk(operatorListChunk) {
       // Add the new chunk to the current operator list.
       for (var i = 0, ii = operatorListChunk.length; i < ii; i++) {
         this.operatorList.fnArray.push(operatorListChunk.fnArray[i]);
         this.operatorList.argsArray.push(operatorListChunk.argsArray[i]);
       }
       this.operatorList.lastChunk = operatorListChunk.lastChunk;
@@ -4572,18 +4525,20 @@ var PDFPageProxy = (function PDFPageProx
       if (operatorListChunk.lastChunk) {
         this.receivingOperatorList = false;
         this._tryDestroy();
       }
     }
   };
   return PDFPageProxy;
 })();
+
 /**
  * For internal use only.
+ * @ignore
  */
 var WorkerTransport = (function WorkerTransportClosure() {
   function WorkerTransport(workerInitializedPromise, workerReadyPromise,
       pdfDataRangeTransport, progressCallback) {
     this.pdfDataRangeTransport = pdfDataRangeTransport;
 
     this.workerReadyPromise = workerReadyPromise;
     this.progressCallback = progressCallback;
@@ -4655,25 +4610,26 @@ var WorkerTransport = (function WorkerTr
     }.bind(this));
   }
   WorkerTransport.prototype = {
     destroy: function WorkerTransport_destroy() {
       this.pageCache = [];
       this.pagePromises = [];
       var self = this;
       this.messageHandler.send('Terminate', null, function () {
+        FontLoader.clear();
         if (self.worker) {
           self.worker.terminate();
         }
       });
     },
 
     loadFakeWorkerFiles: function WorkerTransport_loadFakeWorkerFiles() {
       if (!PDFJS.fakeWorkerFilesLoadedPromise) {
-        PDFJS.fakeWorkerFilesLoadedPromise = new Promise();
+        PDFJS.fakeWorkerFilesLoadedPromise = new LegacyPromise();
         // In the developer build load worker_loader which in turn loads all the
         // other files and resolves the promise. In production only the
         // pdf.worker.js file is needed.
         Util.loadScript(PDFJS.workerSrc, function() {
           PDFJS.fakeWorkerFilesLoadedPromise.resolve();
         });
       }
       return PDFJS.fakeWorkerFilesLoadedPromise;
@@ -4868,17 +4824,17 @@ var WorkerTransport = (function WorkerTr
       messageHandler.on('PageError', function transportError(data) {
         var page = this.pageCache[data.pageNum - 1];
         if (page.displayReadyPromise)
           page.displayReadyPromise.reject(data.error);
         else
           error(data.error);
       }, this);
 
-      messageHandler.on('JpegDecode', function(data, promise) {
+      messageHandler.on('JpegDecode', function(data, deferred) {
         var imageUrl = data[0];
         var components = data[1];
         if (components != 3 && components != 1)
           error('Only 3 component or 1 component can be returned');
 
         var img = new Image();
         img.onload = (function messageHandler_onloadClosure() {
           var width = img.width;
@@ -4897,74 +4853,80 @@ var WorkerTransport = (function WorkerTr
               buf[j + 1] = data[i + 1];
               buf[j + 2] = data[i + 2];
             }
           } else if (components == 1) {
             for (var i = 0, j = 0; i < rgbaLength; i += 4, j++) {
               buf[j] = data[i];
             }
           }
-          promise.resolve({ data: buf, width: width, height: height});
+          deferred.resolve({ data: buf, width: width, height: height});
         }).bind(this);
         img.src = imageUrl;
       });
     },
 
     fetchDocument: function WorkerTransport_fetchDocument(source) {
       source.disableAutoFetch = PDFJS.disableAutoFetch;
       source.chunkedViewerLoading = !!this.pdfDataRangeTransport;
       this.messageHandler.send('GetDocRequest', {
         source: source,
         disableRange: PDFJS.disableRange,
         maxImageSize: PDFJS.maxImageSize,
-        disableFontFace: PDFJS.disableFontFace
+        disableFontFace: PDFJS.disableFontFace,
+        disableCreateObjectURL: PDFJS.disableCreateObjectURL,
+        verbosity: PDFJS.verbosity
       });
     },
 
     getData: function WorkerTransport_getData(promise) {
       this.messageHandler.send('GetData', null, function(data) {
         promise.resolve(data);
       });
     },
 
     dataLoaded: function WorkerTransport_dataLoaded() {
-      var promise = new PDFJS.Promise();
+      if (this.dataLoadedPromise) {
+        return this.dataLoadedPromise;
+      }
+      var promise = new PDFJS.LegacyPromise();
       this.messageHandler.send('DataLoaded', null, function(args) {
         promise.resolve(args);
       });
+      this.dataLoadedPromise = promise;
       return promise;
     },
 
     getPage: function WorkerTransport_getPage(pageNumber, promise) {
       var pageIndex = pageNumber - 1;
       if (pageIndex in this.pagePromises)
         return this.pagePromises[pageIndex];
-      var promise = new PDFJS.Promise('Page ' + pageNumber);
+      var promise = new PDFJS.LegacyPromise();
       this.pagePromises[pageIndex] = promise;
       this.messageHandler.send('GetPageRequest', { pageIndex: pageIndex });
       return promise;
     },
 
     getPageIndex: function WorkerTransport_getPageIndexByRef(ref) {
-      var promise = new PDFJS.Promise();
+      var promise = new PDFJS.LegacyPromise();
       this.messageHandler.send('GetPageIndex', { ref: ref },
         function (pageIndex) {
           promise.resolve(pageIndex);
         }
       );
       return promise;
     },
 
     getAnnotations: function WorkerTransport_getAnnotations(pageIndex) {
       this.messageHandler.send('GetAnnotationsRequest',
         { pageIndex: pageIndex });
     },
 
     getDestinations: function WorkerTransport_getDestinations() {
-      var promise = new PDFJS.Promise();
+      var promise = new PDFJS.LegacyPromise();
       this.messageHandler.send('GetDestinations', null,
         function transportDestinations(destinations) {
           promise.resolve(destinations);
         }
       );
       return promise;
     },
 
@@ -4987,33 +4949,34 @@ var WorkerTransport = (function WorkerTr
 
 })();
 
 /**
  * A PDF document and page is built of many objects. E.g. there are objects
  * for fonts, images, rendering code and such. These objects might get processed
  * inside of a worker. The `PDFObjects` implements some basic functions to
  * manage these objects.
+ * @ignore
  */
 var PDFObjects = (function PDFObjectsClosure() {
   function PDFObjects() {
     this.objs = {};
   }
 
   PDFObjects.prototype = {
     /**
      * Internal function.
      * Ensures there is an object defined for `objId`.
      */
     ensureObj: function PDFObjects_ensureObj(objId) {
       if (this.objs[objId])
         return this.objs[objId];
 
       var obj = {
-        promise: new Promise(objId),
+        promise: new LegacyPromise(),
         data: null,
         resolved: false
       };
       this.objs[objId] = obj;
 
       return obj;
     },
 
@@ -5084,39 +5047,50 @@ var PDFObjects = (function PDFObjectsClo
     },
 
     clear: function PDFObjects_clear() {
       this.objs = {};
     }
   };
   return PDFObjects;
 })();
-/*
- * RenderTask is basically a promise but adds a cancel function to terminate it.
+
+/**
+ * Allows controlling of the rendering tasks.
+ * @class
  */
 var RenderTask = (function RenderTaskClosure() {
   function RenderTask(internalRenderTask) {
     this.internalRenderTask = internalRenderTask;
-    Promise.call(this);
+    /**
+     * Promise for rendering task completion.
+     * @type {Promise}
+     */
+    this.promise = new PDFJS.LegacyPromise();
   }
 
-  RenderTask.prototype = Object.create(Promise.prototype);
-
-  /**
-   * Cancel the rendering task. If the task is curently rendering it will not be
-   * cancelled until graphics pauses with a timeout. The promise that this
-   * object extends will resolved when cancelled.
-   */
-  RenderTask.prototype.cancel = function RenderTask_cancel() {
-    this.internalRenderTask.cancel();
+  RenderTask.prototype = /** @lends RenderTask.prototype */ {
+    /**
+     * Cancels the rendering task. If the task is currently rendering it will
+     * not be cancelled until graphics pauses with a timeout. The promise that
+     * this object extends will resolved when cancelled.
+     */
+    cancel: function RenderTask_cancel() {
+      this.internalRenderTask.cancel();
+      this.promise.reject(new Error('Rendering is cancelled'));
+    }
   };
 
   return RenderTask;
 })();
 
+/**
+ * For internal use only.
+ * @ignore
+ */
 var InternalRenderTask = (function InternalRenderTaskClosure() {
 
   function InternalRenderTask(callback, params, objs, commonObjs, operatorList,
                               pageNumber) {
     this.callback = callback;
     this.params = params;
     this.objs = objs;
     this.commonObjs = commonObjs;
@@ -5460,84 +5434,96 @@ var CachedCanvases = (function CachedCan
 function compileType3Glyph(imgData) {
   var POINT_TO_PROCESS_LIMIT = 1000;
 
   var width = imgData.width, height = imgData.height;
   var i, j, j0, width1 = width + 1;
   var points = new Uint8Array(width1 * (height + 1));
   var POINT_TYPES =
       new Uint8Array([0, 2, 4, 0, 1, 0, 5, 4, 8, 10, 0, 8, 0, 2, 1, 0]);
+
+  // decodes bit-packed mask data
+  var lineSize = (width + 7) & ~7, data0 = imgData.data;
+  var data = new Uint8Array(lineSize * height), pos = 0, ii;
+  for (i = 0, ii = data0.length; i < ii; i++) {
+    var mask = 128, elem = data0[i];
+    while (mask > 0) {
+      data[pos++] = (elem & mask) ? 0 : 255;
+      mask >>= 1;
+    }
+  }
+
   // finding iteresting points: every point is located between mask pixels,
   // so there will be points of the (width + 1)x(height + 1) grid. Every point
   // will have flags assigned based on neighboring mask pixels:
   //   4 | 8
   //   --P--
   //   2 | 1
   // We are interested only in points with the flags:
   //   - outside corners: 1, 2, 4, 8;
   //   - inside corners: 7, 11, 13, 14;
   //   - and, intersections: 5, 10.
-  var pos = 3, data = imgData.data, lineSize = width * 4, count = 0;
-  if (data[3] !== 0) {
+  var count = 0;
+  pos = 0;
+  if (data[pos] !== 0) {
     points[0] = 1;
     ++count;
   }
   for (j = 1; j < width; j++) {
-    if (data[pos] !== data[pos + 4]) {
+    if (data[pos] !== data[pos + 1]) {
       points[j] = data[pos] ? 2 : 1;
       ++count;
     }
-    pos += 4;
+    pos++;
   }
   if (data[pos] !== 0) {
     points[j] = 2;
     ++count;
   }
-  pos += 4;
   for (i = 1; i < height; i++) {
+    pos = i * lineSize;
     j0 = i * width1;
     if (data[pos - lineSize] !== data[pos]) {
       points[j0] = data[pos] ? 1 : 8;
       ++count;
     }
     // 'sum' is the position of the current pixel configuration in the 'TYPES'
     // array (in order 8-1-2-4, so we can use '>>2' to shift the column).
     var sum = (data[pos] ? 4 : 0) + (data[pos - lineSize] ? 8 : 0);
     for (j = 1; j < width; j++) {
-      sum = (sum >> 2) + (data[pos + 4] ? 4 : 0) +
-            (data[pos - lineSize + 4] ? 8 : 0);
+      sum = (sum >> 2) + (data[pos + 1] ? 4 : 0) +
+            (data[pos - lineSize + 1] ? 8 : 0);
       if (POINT_TYPES[sum]) {
         points[j0 + j] = POINT_TYPES[sum];
         ++count;
       }
-      pos += 4;
+      pos++;
     }
     if (data[pos - lineSize] !== data[pos]) {
       points[j0 + j] = data[pos] ? 2 : 4;
       ++count;
     }
-    pos += 4;
 
     if (count > POINT_TO_PROCESS_LIMIT) {
       return null;
     }
   }
 
-  pos -= lineSize;
+  pos = lineSize * (height - 1);
   j0 = i * width1;
   if (data[pos] !== 0) {
     points[j0] = 8;
     ++count;
   }
   for (j = 1; j < width; j++) {
-    if (data[pos] !== data[pos + 4]) {
+    if (data[pos] !== data[pos + 1]) {
       points[j0 + j] = data[pos] ? 4 : 8;
       ++count;
     }
-    pos += 4;
+    pos++;
   }
   if (data[pos] !== 0) {
     points[j0 + j] = 4;
     ++count;
   }
   if (count > POINT_TO_PROCESS_LIMIT) {
     return null;
   }
@@ -5633,17 +5619,16 @@ var CanvasExtraState = (function CanvasE
     this.strokeColorObj = null;
     // Default fore and background colors
     this.fillColor = '#000000';
     this.strokeColor = '#000000';
     // Note: fill alpha applies to all non-stroking operations
     this.fillAlpha = 1;
     this.strokeAlpha = 1;
     this.lineWidth = 1;
-    this.paintFormXObjectDepth = 0;
 
     this.old = old;
   }
 
   CanvasExtraState.prototype = {
     clone: function CanvasExtraState_clone() {
       return Object.create(this);
     },
@@ -5685,26 +5670,87 @@ var CanvasGraphics = (function CanvasGra
   }
 
   function putBinaryImageData(ctx, imgData) {
     if (typeof ImageData !== 'undefined' && imgData instanceof ImageData) {
       ctx.putImageData(imgData, 0, 0);
       return;
     }
 
-    var tmpImgData = ctx.createImageData(imgData.width, imgData.height);
-
+    // Put the image data to the canvas in chunks, rather than putting the
+    // whole image at once.  This saves JS memory, because the ImageData object
+    // is smaller. It also possibly saves C++ memory within the implementation
+    // of putImageData(). (E.g. in Firefox we make two short-lived copies of
+    // the data passed to putImageData()). |n| shouldn't be too small, however,
+    // because too many putImageData() calls will slow things down.
+
+    var rowsInFullChunks = 16;
+    var fullChunks = (imgData.height / rowsInFullChunks) | 0;
+    var rowsInLastChunk = imgData.height - fullChunks * rowsInFullChunks;
+    var elemsInFullChunks = imgData.width * rowsInFullChunks * 4;
+    var elemsInLastChunk = imgData.width * rowsInLastChunk * 4;
+
+    var chunkImgData = ctx.createImageData(imgData.width, rowsInFullChunks);
+    var srcPos = 0;
+    var src = imgData.data;
+    var dst = chunkImgData.data;
+    var haveSetAndSubarray = 'set' in dst && 'subarray' in src;
+
+    // Do all the full-size chunks.
+    for (var i = 0; i < fullChunks; i++) {
+      if (haveSetAndSubarray) {
+        dst.set(src.subarray(srcPos, srcPos + elemsInFullChunks));
+        srcPos += elemsInFullChunks;
+      } else {
+        for (var j = 0; j < elemsInFullChunks; j++) {
+          chunkImgData.data[j] = imgData.data[srcPos++];
+        }
+      }
+      ctx.putImageData(chunkImgData, 0, i * rowsInFullChunks);
+    }
+
+    // Do the final, partial chunk, if required.
+    if (rowsInLastChunk !== 0) {
+      if (haveSetAndSubarray) {
+        dst.set(src.subarray(srcPos, srcPos + elemsInLastChunk));
+        srcPos += elemsInLastChunk;
+      } else {
+        for (var j = 0; j < elemsInLastChunk; j++) {
+          chunkImgData.data[j] = imgData.data[srcPos++];
+        }
+      }
+      // This (conceptually) puts pixels past the bounds of the canvas.  But
+      // that's ok; any such pixels are ignored.
+      ctx.putImageData(chunkImgData, 0, fullChunks * rowsInFullChunks);
+    }
+  }
+
+  function putBinaryImageMask(ctx, imgData) {
+    var width = imgData.width, height = imgData.height;
+    var tmpImgData = ctx.createImageData(width, height);
     var data = imgData.data;
     var tmpImgDataPixels = tmpImgData.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];
+    var dataPos = 0;
+
+    // Expand the mask so it can be used by the canvas.  Any required inversion
+    // has already been handled.
+    var tmpPos = 3; // alpha component offset
+    for (var i = 0; i < height; i++) {
+      var mask = 0;
+      for (var j = 0; j < width; j++) {
+        if (!mask) {
+          var elem = data[dataPos++];
+          mask = 128;
+        }
+        if (!(elem & mask)) {
+          tmpImgDataPixels[tmpPos] = 255;
+        }
+        tmpPos += 4;
+        mask >>= 1;
+      }
     }
 
     ctx.putImageData(tmpImgData, 0, 0);
   }
 
   function copyCtxState(sourceCtx, destCtx) {
     var properties = ['strokeStyle', 'fillStyle', 'fillRule', 'globalAlpha',
                       'lineWidth', 'lineCap', 'lineJoin', 'miterLimit',
@@ -5777,16 +5823,17 @@ var CanvasGraphics = (function CanvasGra
       }
 
       var executionEndIdx;
       var endTime = Date.now() + EXECUTION_TIME;
 
       var commonObjs = this.commonObjs;
       var objs = this.objs;
       var fnId;
+      var deferred = Promise.resolve();
 
       while (true) {
         if (stepper && i === stepper.nextBreakPoint) {
           stepper.breakIt(i, continueCallback);
           return i;
         }
 
         fnId = fnArray[i];
@@ -5818,17 +5865,17 @@ var CanvasGraphics = (function CanvasGra
         if (i == argsArrayLen) {
           return i;
         }
 
         // If the execution took longer then a certain amount of time, schedule
         // to continue exeution after a short delay.
         // However, this is only possible if a 'continueCallback' is passed in.
         if (continueCallback && Date.now() > endTime) {
-          setTimeout(continueCallback, 0);
+          deferred.then(continueCallback);
           return i;
         }
 
         // If the operatorList isn't executed completely yet OR the execution
         // time was short enough, do another execution round.
       }
     },
 
@@ -6222,16 +6269,18 @@ var CanvasGraphics = (function CanvasGra
       geometry.y = y;
       geometry.hScale = sx;
       geometry.vScale = sy;
       geometry.angle = angle;
       geometry.spaceWidth = font.spaceWidth;
       geometry.fontName = font.loadedName;
       geometry.fontFamily = font.fallbackName;
       geometry.fontSize = this.current.fontSize;
+      geometry.ascent = font.ascent;
+      geometry.descent = font.descent;
       return geometry;
     },
 
     paintChar: function (character, x, y) {
       var ctx = this.ctx;
       var current = this.current;
       var font = current.font;
       var fontSize = current.fontSize / current.fontSizeScale;
@@ -6278,16 +6327,33 @@ var CanvasGraphics = (function CanvasGra
           x: x,
           y: y,
           fontSize: fontSize,
           addToPath: addToPath
         });
       }
     },
 
+    get isFontSubpixelAAEnabled() {
+      // Checks if anti-aliasing is enabled when scaled text is painted.
+      // On Windows GDI scaled fonts looks bad.
+      var ctx = document.createElement('canvas').getContext('2d');
+      ctx.scale(1.5, 1);
+      ctx.fillText('I', 0, 10);
+      var data = ctx.getImageData(0, 0, 10, 10).data;
+      var enabled = false;
+      for (var i = 3; i < data.length; i += 4) {
+        if (data[i] > 0 && data[i] < 255) {
+          enabled = true;
+          break;
+        }
+      }
+      return shadow(this, 'isFontSubpixelAAEnabled', enabled);
+    },
+
     showText: function CanvasGraphics_showText(glyphs, skipTextSelection) {
       var ctx = this.ctx;
       var current = this.current;
       var font = current.font;
       var fontSize = current.fontSize;
       var fontSizeScale = current.fontSizeScale;
       var charSpacing = current.charSpacing;
       var wordSpacing = current.wordSpacing;
@@ -6392,17 +6458,17 @@ var CanvasGraphics = (function CanvasGra
             if (vertical) {
               scaledX = vx / fontSizeScale;
               scaledY = (x + vy) / fontSizeScale;
             } else {
               scaledX = x / fontSizeScale;
               scaledY = 0;
             }
 
-            if (font.remeasure && width > 0) {
+            if (font.remeasure && width > 0 && this.isFontSubpixelAAEnabled) {
               // some standard fonts may not have the exact width, trying to
               // rescale per character
               var measuredWidth = ctx.measureText(character).width * 1000 /
                 current.fontSize * current.fontSizeScale;
               var characterScaleX = width / measuredWidth;
               restoreNeeded = true;
               ctx.save();
               ctx.scale(characterScaleX, 1);
@@ -6679,17 +6745,16 @@ var CanvasGraphics = (function CanvasGra
     },
     beginImageData: function CanvasGraphics_beginImageData() {
       error('Should not call beginImageData');
     },
 
     paintFormXObjectBegin: function CanvasGraphics_paintFormXObjectBegin(matrix,
                                                                         bbox) {
       this.save();
-      this.current.paintFormXObjectDepth++;
       this.baseTransformStack.push(this.baseTransform);
 
       if (matrix && isArray(matrix) && 6 == matrix.length)
         this.transform.apply(this, matrix);
 
       this.baseTransform = this.ctx.mozCurrentTransform;
 
       if (bbox && isArray(bbox) && 4 == bbox.length) {
@@ -6697,22 +6762,17 @@ var CanvasGraphics = (function CanvasGra
         var height = bbox[3] - bbox[1];
         this.rectangle(bbox[0], bbox[1], width, height);
         this.clip();
         this.endPath();
       }
     },
 
     paintFormXObjectEnd: function CanvasGraphics_paintFormXObjectEnd() {
-      var depth = this.current.paintFormXObjectDepth;
-      do {
-        this.restore();
-        // some pdf don't close all restores inside object
-        // closing those for them
-      } while (this.current.paintFormXObjectDepth >= depth);
+      this.restore();
       this.baseTransform = this.baseTransformStack.pop();
     },
 
     beginGroup: function CanvasGraphics_beginGroup(group) {
       this.save();
       var currentCtx = this.ctx;
       // TODO non-isolated groups - according to Rik at adobe non-isolated
       // group results aren't usually that different and they even have tools
@@ -6729,17 +6789,17 @@ var CanvasGraphics = (function CanvasGra
       // colorNew = color - alphaNew *colorBackdrop /(1 - alphaNew)
       if (!group.isolated) {
         info('TODO: Support non-isolated groups.');
       }
 
       // TODO knockout - supposedly possible with the clever use of compositing
       // modes.
       if (group.knockout) {
-        TODO('Support knockout groups.');
+        warn('Knockout groups not supported.');
       }
 
       var currentTransform = currentCtx.mozCurrentTransform;
       if (group.matrix) {
         currentCtx.transform.apply(currentCtx, group.matrix);
       }
       assert(group.bbox, 'Bounding box is required.');
 
@@ -6879,17 +6939,17 @@ var CanvasGraphics = (function CanvasGra
         glyph.compiled(ctx);
         return;
       }
 
       var maskCanvas = CachedCanvases.getCanvas('maskCanvas', width, height);
       var maskCtx = maskCanvas.context;
       maskCtx.save();
 
-      putBinaryImageData(maskCtx, img);
+      putBinaryImageMask(maskCtx, img);
 
       maskCtx.globalCompositeOperation = 'source-in';
 
       var fillColor = this.current.fillColor;
       maskCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') &&
                           fillColor.type === 'Pattern') ?
                           fillColor.getPattern(maskCtx, this) : fillColor;
       maskCtx.fillRect(0, 0, width, height);
@@ -6906,17 +6966,17 @@ var CanvasGraphics = (function CanvasGra
       for (var i = 0, ii = images.length; i < ii; i++) {
         var image = images[i];
         var width = image.width, height = image.height;
 
         var maskCanvas = CachedCanvases.getCanvas('maskCanvas', width, height);
         var maskCtx = maskCanvas.context;
         maskCtx.save();
 
-        putBinaryImageData(maskCtx, image);
+        putBinaryImageMask(maskCtx, image);
 
         maskCtx.globalCompositeOperation = 'source-in';
 
         var fillColor = this.current.fillColor;
         maskCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') &&
                             fillColor.type === 'Pattern') ?
                             fillColor.getPattern(maskCtx, this) : fillColor;
         maskCtx.fillRect(0, 0, width, height);
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -15,18 +15,18 @@
  * limitations under the License.
  */
 
 // Initializing PDFJS global object (if still undefined)
 if (typeof PDFJS === 'undefined') {
   (typeof window !== 'undefined' ? window : this).PDFJS = {};
 }
 
-PDFJS.version = '0.8.759';
-PDFJS.build = 'd3b5aa3';
+PDFJS.version = '0.8.934';
+PDFJS.build = 'c80df60';
 
 (function pdfjsWrapper() {
   // Use strict in our context only - users might not want it
   'use strict';
 
 /* -*- 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
@@ -38,27 +38,25 @@ PDFJS.build = 'd3b5aa3';
  *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-/* globals Cmd, ColorSpace, Dict, MozBlobBuilder, Name, PDFJS, Ref, URL */
+/* globals Cmd, ColorSpace, Dict, MozBlobBuilder, Name, PDFJS, Ref, URL,
+           Promise */
 
 'use strict';
 
 var globalScope = (typeof window === 'undefined') ? this : window;
 
 var isWorker = (typeof window == 'undefined');
 
-var ERRORS = 0, WARNINGS = 1, INFOS = 5;
-var verbosity = WARNINGS;
-
 var FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0];
 
 var TextRenderingMode = {
   FILL: 0,
   STROKE: 1,
   FILL_STROKE: 2,
   INVISIBLE: 3,
   FILL_ADD_TO_PATH: 4,
@@ -73,16 +71,22 @@ var TextRenderingMode = {
 // In production, it will be declared outside a global wrapper
 // In development, it will be declared here
 if (!globalScope.PDFJS) {
   globalScope.PDFJS = {};
 }
 
 globalScope.PDFJS.pdfBug = false;
 
+PDFJS.VERBOSITY_LEVELS = {
+  errors: 0,
+  warnings: 1,
+  infos: 5
+};
+
 // All the possible operations for an operator list.
 var OPS = PDFJS.OPS = {
   // Intentionally start from 1 so it is easy to spot bad operators that will be
   // 0's.
   dependency: 1,
   setLineWidth: 2,
   setLineCap: 3,
   setLineJoin: 4,
@@ -166,87 +170,94 @@ var OPS = PDFJS.OPS = {
   paintJpegXObject: 82,
   paintImageMaskXObject: 83,
   paintImageMaskXObjectGroup: 84,
   paintImageXObject: 85,
   paintInlineImageXObject: 86,
   paintInlineImageXObjectGroup: 87
 };
 
-// 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 {
-    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
-// disabled, which is important to devs but not end users.
+// A notice for devs. These are good for things that are helpful to devs, such
+// as warning that Workers were disabled, which is important to devs but not
+// end users.
 function info(msg) {
-  if (verbosity >= INFOS) {
-    log('Info: ' + msg);
-    PDFJS.LogManager.notify('info', msg);
-  }
-}
-
-// Non-fatal warnings that should trigger the fallback UI.
+  if (PDFJS.verbosity >= PDFJS.VERBOSITY_LEVELS.infos) {
+    console.log('Info: ' + msg);
+  }
+}
+
+// Non-fatal warnings.
 function warn(msg) {
-  if (verbosity >= WARNINGS) {
-    log('Warning: ' + msg);
-    PDFJS.LogManager.notify('warn', msg);
+  if (PDFJS.verbosity >= PDFJS.VERBOSITY_LEVELS.warnings) {
+    console.log('Warning: ' + msg);
   }
 }
 
 // Fatal errors that should trigger the fallback UI and halt execution by
 // throwing an exception.
 function error(msg) {
   // If multiple arguments were passed, pass them all to the log function.
   if (arguments.length > 1) {
     var logArguments = ['Error:'];
     logArguments.push.apply(logArguments, arguments);
-    log.apply(null, logArguments);
+    console.log.apply(console, logArguments);
     // Join the arguments into a single string for the lines below.
     msg = [].join.call(arguments, ' ');
   } else {
-    log('Error: ' + msg);
-  }
-  log(backtrace());
-  PDFJS.LogManager.notify('error', msg);
+    console.log('Error: ' + msg);
+  }
+  console.log(backtrace());
+  UnsupportedManager.notify(UNSUPPORTED_FEATURES.unknown);
   throw new Error(msg);
 }
 
-// Missing features that should trigger the fallback UI.
-function TODO(what) {
-  warn('TODO: ' + what);
-}
-
 function backtrace() {
   try {
     throw new Error();
   } catch (e) {
     return e.stack ? e.stack.split('\n').slice(2).join('\n') : '';
   }
 }
 
 function assert(cond, msg) {
   if (!cond)
     error(msg);
 }
 
+var UNSUPPORTED_FEATURES = PDFJS.UNSUPPORTED_FEATURES = {
+  unknown: 'unknown',
+  forms: 'forms',
+  javaScript: 'javaScript',
+  smask: 'smask',
+  shadingPattern: 'shadingPattern',
+  font: 'font'
+};
+
+var UnsupportedManager = PDFJS.UnsupportedManager =
+  (function UnsupportedManagerClosure() {
+  var listeners = [];
+  return {
+    listen: function (cb) {
+      listeners.push(cb);
+    },
+    notify: function (featureId) {
+      warn('Unsupported feature "' + featureId + '"');
+      for (var i = 0, ii = listeners.length; i < ii; i++) {
+        listeners[i](featureId);
+      }
+    }
+  };
+})();
+
 // Combines two URLs. The baseUrl shall be absolute URL. If the url is an
 // absolute URL, it will be returned as is.
 function combineUrl(baseUrl, url) {
   if (!url)
     return baseUrl;
-  if (url.indexOf(':') >= 0)
+  if (/^[a-z][a-z0-9+\-.]*:/i.test(url))
     return url;
   if (url.charAt(0) == '/') {
     // absolute path
     var i = baseUrl.indexOf('://');
     i = baseUrl.indexOf('/', i + 3);
     return baseUrl.substring(0, i) + url;
   } else {
     // relative path
@@ -260,21 +271,23 @@ function combineUrl(baseUrl, url) {
   }
 }
 
 // Validates if URL is safe and allowed, e.g. to avoid XSS.
 function isValidUrl(url, allowRelative) {
   if (!url) {
     return false;
   }
-  var colon = url.indexOf(':');
-  if (colon < 0) {
+  // RFC 3986 (http://tools.ietf.org/html/rfc3986#section-3.1)
+  // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
+  var protocol = /^[a-z][a-z0-9+\-.]*(?=:)/i.exec(url);
+  if (!protocol) {
     return allowRelative;
   }
-  var protocol = url.substr(0, colon);
+  protocol = protocol[0].toLowerCase();
   switch (protocol) {
     case 'http':
     case 'https':
     case 'ftp':
     case 'mailto':
       return true;
     default:
       return false;
@@ -284,32 +297,16 @@ PDFJS.isValidUrl = isValidUrl;
 
 // In a well-formed PDF, |cond| holds.  If it doesn't, subsequent
 // behavior is undefined.
 function assertWellFormed(cond, msg) {
   if (!cond)
     error(msg);
 }
 
-var LogManager = PDFJS.LogManager = (function LogManagerClosure() {
-  var loggers = [];
-  return {
-    addLogger: function logManager_addLogger(logger) {
-      loggers.push(logger);
-    },
-    notify: function(type, message) {
-      for (var i = 0, ii = loggers.length; i < ii; i++) {
-        var logger = loggers[i];
-        if (logger[type])
-          logger[type](message);
-      }
-    }
-  };
-})();
-
 function shadow(obj, prop, value) {
   Object.defineProperty(obj, prop, { value: value,
                                      enumerable: true,
                                      configurable: true,
                                      writable: false });
   return value;
 }
 
@@ -829,247 +826,76 @@ function isPDFFunction(v) {
   else if (isStream(v))
     fnDict = v.dict;
   else
     return false;
   return fnDict.has('FunctionType');
 }
 
 /**
+ * Legacy support for PDFJS Promise implementation.
+ * TODO remove eventually
+ */
+var LegacyPromise = PDFJS.LegacyPromise = (function LegacyPromiseClosure() {
+  return function LegacyPromise() {
+    var resolve, reject;
+    var promise = new Promise(function (resolve_, reject_) {
+      resolve = resolve_;
+      reject = reject_;
+    });
+    promise.resolve = resolve;
+    promise.reject = reject;
+    return promise;
+  };
+})();
+
+/**
+ * Polyfill for Promises:
  * The following promise implementation tries to generally implment the
  * Promise/A+ spec. Some notable differences from other promise libaries are:
  * - There currently isn't a seperate deferred and promise object.
  * - Unhandled rejections eventually show an error if they aren't handled.
  *
  * Based off of the work in:
  * https://bugzilla.mozilla.org/show_bug.cgi?id=810490
  */
-var Promise = PDFJS.Promise = (function PromiseClosure() {
-  var STATUS_PENDING = 0;
-  var STATUS_RESOLVED = 1;
-  var STATUS_REJECTED = 2;
-
-  // In an attempt to avoid silent exceptions, unhandled rejections are
-  // tracked and if they aren't handled in a certain amount of time an
-  // error is logged.
-  var REJECTION_TIMEOUT = 500;
-
-  var HandlerManager = {
-    handlers: [],
-    running: false,
-    unhandledRejections: [],
-    pendingRejectionCheck: false,
-
-    scheduleHandlers: function scheduleHandlers(promise) {
-      if (promise._status == STATUS_PENDING) {
-        return;
-      }
-
-      this.handlers = this.handlers.concat(promise._handlers);
-      promise._handlers = [];
-
-      if (this.running) {
-        return;
-      }
-      this.running = true;
-
-      setTimeout(this.runHandlers.bind(this), 0);
-    },
-
-    runHandlers: function runHandlers() {
-      while (this.handlers.length > 0) {
-        var handler = this.handlers.shift();
-
-        var nextStatus = handler.thisPromise._status;
-        var nextValue = handler.thisPromise._value;
-
-        try {
-          if (nextStatus === STATUS_RESOLVED) {
-            if (typeof(handler.onResolve) == 'function') {
-              nextValue = handler.onResolve(nextValue);
-            }
-          } else if (typeof(handler.onReject) === 'function') {
-              nextValue = handler.onReject(nextValue);
-              nextStatus = STATUS_RESOLVED;
-
-              if (handler.thisPromise._unhandledRejection) {
-                this.removeUnhandeledRejection(handler.thisPromise);
-              }
-          }
-        } catch (ex) {
-          nextStatus = STATUS_REJECTED;
-          nextValue = ex;
-        }
-
-        handler.nextPromise._updateStatus(nextStatus, nextValue);
-      }
-
-      this.running = false;
-    },
-
-    addUnhandledRejection: function addUnhandledRejection(promise) {
-      this.unhandledRejections.push({
-        promise: promise,
-        time: Date.now()
-      });
-      this.scheduleRejectionCheck();
-    },
-
-    removeUnhandeledRejection: function removeUnhandeledRejection(promise) {
-      promise._unhandledRejection = false;
-      for (var i = 0; i < this.unhandledRejections.length; i++) {
-        if (this.unhandledRejections[i].promise === promise) {
-          this.unhandledRejections.splice(i);
-          i--;
-        }
-      }
-    },
-
-    scheduleRejectionCheck: function scheduleRejectionCheck() {
-      if (this.pendingRejectionCheck) {
-        return;
-      }
-      this.pendingRejectionCheck = true;
-      setTimeout(function rejectionCheck() {
-        this.pendingRejectionCheck = false;
-        var now = Date.now();
-        for (var i = 0; i < this.unhandledRejections.length; i++) {
-          if (now - this.unhandledRejections[i].time > REJECTION_TIMEOUT) {
-            var unhandled = this.unhandledRejections[i].promise._value;
-            var msg = 'Unhandled rejection: ' + unhandled;
-            if (unhandled.stack) {
-              msg += '\n' + unhandled.stack;
-            }
-            warn(msg);
-            this.unhandledRejections.splice(i);
-            i--;
-          }
-        }
-        if (this.unhandledRejections.length) {
-          this.scheduleRejectionCheck();
-        }
-      }.bind(this), REJECTION_TIMEOUT);
-    }
-  };
-
-  function Promise() {
-    this._status = STATUS_PENDING;
-    this._handlers = [];
-  }
-  /**
-   * Builds a promise that is resolved when all the passed in promises are
-   * resolved.
-   * @param {array} array of data and/or promises to wait for.
-   * @return {Promise} New dependant promise.
-   */
-  Promise.all = function Promise_all(promises) {
-    var deferred = new Promise();
-    var unresolved = promises.length;
-    var results = [];
-    if (unresolved === 0) {
-      deferred.resolve(results);
-      return deferred;
-    }
-    function reject(reason) {
-      if (deferred._status === STATUS_REJECTED) {
-        return;
-      }
-      results = [];
-      deferred.reject(reason);
-    }
-    for (var i = 0, ii = promises.length; i < ii; ++i) {
-      var promise = promises[i];
-      var resolve = (function(i) {
-        return function(value) {
-          if (deferred._status === STATUS_REJECTED) {
-            return;
-          }
-          results[i] = value;
-          unresolved--;
-          if (unresolved === 0)
-            deferred.resolve(results);
-        };
-      })(i);
-      if (Promise.isPromise(promise)) {
-        promise.then(resolve, reject);
-      } else {
-        resolve(promise);
-      }
-    }
-    return deferred;
-  };
-
-  /**
-   * Checks if the value is likely a promise (has a 'then' function).
-   * @return {boolean} true if x is thenable
-   */
-  Promise.isPromise = function Promise_isPromise(value) {
-    return value && typeof value.then === 'function';
-  };
-
-  Promise.prototype = {
-    _status: null,
-    _value: null,
-    _handlers: null,
-    _unhandledRejection: null,
-
-    _updateStatus: function Promise__updateStatus(status, value) {
-      if (this._status === STATUS_RESOLVED ||
-          this._status === STATUS_REJECTED) {
-        return;
-      }
-
-      if (status == STATUS_RESOLVED &&
-          Promise.isPromise(value)) {
-        value.then(this._updateStatus.bind(this, STATUS_RESOLVED),
-                   this._updateStatus.bind(this, STATUS_REJECTED));
-        return;
-      }
-
-      this._status = status;
-      this._value = value;
-
-      if (status === STATUS_REJECTED && this._handlers.length === 0) {
-        this._unhandledRejection = true;
-        HandlerManager.addUnhandledRejection(this);
-      }
-
-      HandlerManager.scheduleHandlers(this);
-    },
-
-    get isResolved() {
-      return this._status === STATUS_RESOLVED;
-    },
-
-    get isRejected() {
-      return this._status === STATUS_REJECTED;
-    },
-
-    resolve: function Promise_resolve(value) {
-      this._updateStatus(STATUS_RESOLVED, value);
-    },
-
-    reject: function Promise_reject(reason) {
-      this._updateStatus(STATUS_REJECTED, reason);
-    },
-
-    then: function Promise_then(onResolve, onReject) {
-      var nextPromise = new Promise();
-      this._handlers.push({
-        thisPromise: this,
-        onResolve: onResolve,
-        onReject: onReject,
-        nextPromise: nextPromise
-      });
-      HandlerManager.scheduleHandlers(this);
-      return nextPromise;
-    }
-  };
-
-  return Promise;
+(function PromiseClosure() {
+  if (globalScope.Promise) {
+    // Promises existing in the DOM/Worker, checking presence of all/resolve
+    if (typeof globalScope.Promise.all !== 'function') {
+      globalScope.Promise.all = function (iterable) {
+        var count = 0, results = [], resolve, reject;
+        var promise = new globalScope.Promise(function (resolve_, reject_) {
+          resolve = resolve_;
+          reject = reject_;
+        });
+        iterable.forEach(function (p, i) {
+          count++;
+          p.then(function (result) {
+            results[i] = result;
+            count--;
+            if (count === 0) {
+              resolve(results);
+            }
+          }, reject);
+        });
+        if (count === 0) {
+          resolve(results);
+        }
+        return promise;
+      };
+    }
+    if (typeof globalScope.Promise.resolve !== 'function') {
+      globalScope.Promise.resolve = function (x) {
+        return new globalScope.Promise(function (resolve) { resolve(x); });
+      };
+    }
+    return;
+  }
+  throw new Error('DOM Promise is not present');
 })();
 
 var StatTimer = (function StatTimerClosure() {
   function rpad(str, pad, length) {
     while (str.length < length)
       str += pad;
     return str;
   }
@@ -1125,28 +951,27 @@ PDFJS.createBlob = function createBlob(d
     return new Blob([data], { type: contentType });
   // Blob builder is deprecated in FF14 and removed in FF18.
   var bb = new MozBlobBuilder();
   bb.append(data);
   return bb.getBlob(contentType);
 };
 
 PDFJS.createObjectURL = (function createObjectURLClosure() {
-  if (typeof URL !== 'undefined' && URL.createObjectURL) {
-    return function createObjectURL(data, contentType) {
-      var blob = PDFJS.createBlob(data, contentType);
-      return URL.createObjectURL(blob);
-    };
-  }
-
   // Blob/createObjectURL is not available, falling back to data schema.
   var digits =
     'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
 
   return function createObjectURL(data, contentType) {
+    if (!PDFJS.disableCreateObjectURL &&
+        typeof URL !== 'undefined' && URL.createObjectURL) {
+      var blob = PDFJS.createBlob(data, contentType);
+      return URL.createObjectURL(blob);
+    }
+
     var buffer = 'data:' + contentType + ';base64,';
     for (var i = 0, ii = data.length; i < ii; i += 3) {
       var b1 = data[i] & 0xFF;
       var b2 = data[i + 1] & 0xFF;
       var b3 = data[i + 2] & 0xFF;
       var d1 = b1 >> 2, d2 = ((b1 & 3) << 4) | (b2 >> 4);
       var d3 = i + 1 < ii ? ((b2 & 0xF) << 2) | (b3 >> 6) : 64;
       var d4 = i + 2 < ii ? (b3 & 0x3F) : 64;
@@ -1160,56 +985,53 @@ function MessageHandler(name, comObj) {
   this.name = name;
   this.comObj = comObj;
   this.callbackIndex = 1;
   this.postMessageTransfers = true;
   var callbacks = this.callbacks = {};
   var ah = this.actionHandler = {};
 
   ah['console_log'] = [function ahConsoleLog(data) {
-    log.apply(null, data);
+    console.log.apply(console, data);
   }];
-  // 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);
+  ah['console_error'] = [function ahConsoleError(data) {
+    console.error.apply(console, data);
+  }];
+  ah['_unsupported_feature'] = [function ah_unsupportedFeature(data) {
+    UnsupportedManager.notify(data);
   }];
 
   comObj.onmessage = function messageHandlerComObjOnMessage(event) {
     var data = event.data;
     if (data.isReply) {
       var callbackId = data.callbackId;
       if (data.callbackId in callbacks) {
         var callback = callbacks[callbackId];
         delete callbacks[callbackId];
         callback(data.data);
       } else {
         error('Cannot resolve callback ' + callbackId);
       }
     } else if (data.action in ah) {
       var action = ah[data.action];
       if (data.callbackId) {
-        var promise = new Promise();
+        var deferred = {};
+        var promise = new Promise(function (resolve, reject) {
+          deferred.resolve = resolve;
+          deferred.reject = reject;
+        });
+        deferred.promise = promise;
         promise.then(function(resolvedData) {
           comObj.postMessage({
             isReply: true,
             callbackId: data.callbackId,
             data: resolvedData
           });
         });
-        action[0].call(action[1], data.data, promise);
+        action[0].call(action[1], data.data, deferred);
       } else {
         action[0].call(action[1], data.data);
       }
     } else {
       error('Unkown action from worker: ' + data.action);
     }
   };
 }
@@ -1251,16 +1073,943 @@ function loadJpegStream(id, imageUrl, ob
   var img = new Image();
   img.onload = (function loadJpegStream_onloadClosure() {
     objs.resolve(id, img);
   });
   img.src = imageUrl;
 }
 
 
+var ColorSpace = (function ColorSpaceClosure() {
+  // Constructor should define this.numComps, this.defaultColor, this.name
+  function ColorSpace() {
+    error('should not call ColorSpace constructor');
+  }
+
+  ColorSpace.prototype = {
+    /**
+     * Converts the color value to the RGB color. The color components are
+     * located in the src array starting from the srcOffset. Returns the array
+     * of the rgb components, each value ranging from [0,255].
+     */
+    getRgb: function ColorSpace_getRgb(src, srcOffset) {
+      error('Should not call ColorSpace.getRgb');
+    },
+    /**
+     * Converts the color value to the RGB color, similar to the getRgb method.
+     * The result placed into the dest array starting from the destOffset.
+     */
+    getRgbItem: function ColorSpace_getRgb(src, srcOffset, dest, destOffset) {
+      error('Should not call ColorSpace.getRgbItem');
+    },
+    /**
+     * Converts the specified number of the color values to the RGB colors.
+     * The colors are located in the src array starting from the srcOffset.
+     * The result is placed into the dest array starting from the destOffset.
+     * The src array items shall be in [0,2^bits) range, the dest array items
+     * will be in [0,255] range. alpha01 indicates how many alpha components
+     * there are in the dest array; it will be either 0 (RGB array) or 1 (RGBA
+     * array).
+     */
+    getRgbBuffer: function ColorSpace_getRgbBuffer(src, srcOffset, count,
+                                                   dest, destOffset, bits,
+                                                   alpha01) {
+      error('Should not call ColorSpace.getRgbBuffer');
+    },
+    /**
+     * Determines the number of bytes required to store the result of the
+     * conversion done by the getRgbBuffer method. As in getRgbBuffer,
+     * |alpha01| is either 0 (RGB output) or 1 (RGBA output).
+     */
+    getOutputLength: function ColorSpace_getOutputLength(inputLength,
+                                                         alpha01) {
+      error('Should not call ColorSpace.getOutputLength');
+    },
+    /**
+     * Returns true if source data will be equal the result/output data.
+     */
+    isPassthrough: function ColorSpace_isPassthrough(bits) {
+      return false;
+    },
+    /**
+     * Fills in the RGB colors in an RGBA buffer.
+     */
+    fillRgb: function ColorSpace_fillRgb(rgbaBuf, originalWidth,
+                                         originalHeight, width, height,
+                                         actualHeight, bpc, comps) {
+      var count = originalWidth * originalHeight;
+      var rgbBuf = null;
+      var numComponentColors = 1 << bpc;
+      var needsResizing = originalHeight != height || originalWidth != width;
+
+      if (this.isPassthrough(bpc)) {
+        rgbBuf = comps;
+
+      } else if (this.numComps === 1 && count > numComponentColors &&
+          this.name !== 'DeviceGray' && this.name !== 'DeviceRGB') {
+        // Optimization: create a color map when there is just one component and
+        // we are converting more colors than the size of the color map. We
+        // don't build the map if the colorspace is gray or rgb since those
+        // methods are faster than building a map. This mainly offers big speed
+        // ups for indexed and alternate colorspaces.
+        //
+        // TODO it may be worth while to cache the color map. While running
+        // testing I never hit a cache so I will leave that out for now (perhaps
+        // we are reparsing colorspaces too much?).
+        var allColors = bpc <= 8 ? new Uint8Array(numComponentColors) :
+                                   new Uint16Array(numComponentColors);
+        for (var i = 0; i < numComponentColors; i++) {
+          allColors[i] = i;
+        }
+        var colorMap = new Uint8Array(numComponentColors * 3);
+        this.getRgbBuffer(allColors, 0, numComponentColors, colorMap, 0, bpc,
+                          /* alpha01 = */ 0);
+
+        if (!needsResizing) {
+          // Fill in the RGB values directly into |rgbaBuf|.
+          var rgbaPos = 0;
+          for (var i = 0; i < count; ++i) {
+            var key = comps[i] * 3;
+            rgbaBuf[rgbaPos++] = colorMap[key];
+            rgbaBuf[rgbaPos++] = colorMap[key + 1];
+            rgbaBuf[rgbaPos++] = colorMap[key + 2];
+            rgbaPos++;
+          }
+        } else {
+          rgbBuf = new Uint8Array(count * 3);
+          var rgbPos = 0;
+          for (var i = 0; i < count; ++i) {
+            var key = comps[i] * 3;
+            rgbBuf[rgbPos++] = colorMap[key];
+            rgbBuf[rgbPos++] = colorMap[key + 1];
+            rgbBuf[rgbPos++] = colorMap[key + 2];
+          }
+        }
+      } else {
+        if (!needsResizing) {
+          // Fill in the RGB values directly into |rgbaBuf|.
+          this.getRgbBuffer(comps, 0, width * actualHeight, rgbaBuf, 0, bpc,
+                            /* alpha01 = */ 1);
+        } else {
+          rgbBuf = new Uint8Array(count * 3);
+          this.getRgbBuffer(comps, 0, count, rgbBuf, 0, bpc,
+                            /* alpha01 = */ 0);
+        }
+      }
+
+      if (rgbBuf) {
+        if (needsResizing) {
+          rgbBuf = PDFImage.resize(rgbBuf, bpc, 3, originalWidth,
+                                   originalHeight, width, height);
+        }
+        var rgbPos = 0;
+        var actualLength = width * actualHeight * 4;
+        for (var i = 0; i < actualLength; i += 4) {
+          rgbaBuf[i] = rgbBuf[rgbPos++];
+          rgbaBuf[i + 1] = rgbBuf[rgbPos++];
+          rgbaBuf[i + 2] = rgbBuf[rgbPos++];
+        }
+      }
+    },
+    /**
+     * True if the colorspace has components in the default range of [0, 1].
+     * This should be true for all colorspaces except for lab color spaces
+     * which are [0,100], [-128, 127], [-128, 127].
+     */
+    usesZeroToOneRange: true
+  };
+
+  ColorSpace.parse = function ColorSpace_parse(cs, xref, res) {
+    var IR = ColorSpace.parseToIR(cs, xref, res);
+    if (IR instanceof AlternateCS)
+      return IR;
+
+    return Colo