Merge mozilla-inbound to mozilla-central. a=merge
authorAndreea Pavel <apavel@mozilla.com>
Mon, 21 May 2018 12:47:08 +0300
changeset 419124 dc1868d255be744a7d2d462216be205086cc60af
parent 419070 8850728602d63cec6ffc39945ccaa82ae44cf1e0 (current diff)
parent 419123 daca961e0469a2786bdfabce6b03b4840f6b8ee9 (diff)
child 419125 77c06979d9e88979ec96263eccdbd750cb9221a4
child 419130 e0e34d725988edb47ebce3eeb95f663ed2b65a78
child 419156 d193d9e81b9a58f348ca63ceaec0df9c08c6f37d
push id34026
push userapavel@mozilla.com
push dateMon, 21 May 2018 09:47:33 +0000
treeherdermozilla-central@dc1868d255be [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone62.0a1
first release with
nightly linux32
dc1868d255be / 62.0a1 / 20180521100109 / files
nightly linux64
dc1868d255be / 62.0a1 / 20180521100109 / files
nightly mac
dc1868d255be / 62.0a1 / 20180521100109 / files
nightly win32
dc1868d255be / 62.0a1 / 20180521100109 / files
nightly win64
dc1868d255be / 62.0a1 / 20180521100109 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-inbound to mozilla-central. a=merge
testing/web-platform/meta/webdriver/tests/actions/key.py.ini
testing/web-platform/meta/webdriver/tests/close_window/close.py.ini
testing/web-platform/meta/webdriver/tests/delete_session/delete.py.ini
testing/web-platform/meta/webdriver/tests/get_element_attribute/get.py.ini
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,5 +1,5 @@
 This is the PDF.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 2.0.491
+Current extension version is: 2.0.506
 
-Taken from upstream commit: 2dc4af52
+Taken from upstream commit: b7a3a5e7
--- a/browser/extensions/pdfjs/content/PdfJsDefaultPreferences.jsm
+++ b/browser/extensions/pdfjs/content/PdfJsDefaultPreferences.jsm
@@ -32,10 +32,12 @@ var PdfJsDefaultPreferences = Object.fre
  "disableFontFace": false,
  "textLayerMode": 1,
  "useOnlyCssZoom": false,
  "externalLinkTarget": 0,
  "renderer": "canvas",
  "renderInteractiveForms": false,
  "enablePrintAutoRotate": false,
  "disablePageMode": false,
- "disablePageLabels": false
+ "disablePageLabels": false,
+ "scrollModeOnLoad": 0,
+ "spreadModeOnLoad": 0
 });
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -1645,18 +1645,18 @@ exports.GlobalWorkerOptions = GlobalWork
 
 /***/ }),
 /* 5 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 
-var pdfjsVersion = '2.0.491';
-var pdfjsBuild = '2dc4af52';
+var pdfjsVersion = '2.0.506';
+var pdfjsBuild = 'b7a3a5e7';
 var pdfjsSharedUtil = __w_pdfjs_require__(0);
 var pdfjsDisplayAPI = __w_pdfjs_require__(9);
 var pdfjsDisplayTextLayer = __w_pdfjs_require__(17);
 var pdfjsDisplayAnnotationLayer = __w_pdfjs_require__(18);
 var pdfjsDisplayDOMUtils = __w_pdfjs_require__(1);
 var pdfjsDisplaySVG = __w_pdfjs_require__(19);
 let pdfjsDisplayWorkerOptions = __w_pdfjs_require__(4);
 let pdfjsDisplayAPICompatibility = __w_pdfjs_require__(3);
@@ -4924,17 +4924,17 @@ function _fetchDocument(worker, source, 
     return Promise.reject(new Error('Worker was destroyed'));
   }
   if (pdfDataRangeTransport) {
     source.length = pdfDataRangeTransport.length;
     source.initialData = pdfDataRangeTransport.initialData;
   }
   return worker.messageHandler.sendWithPromise('GetDocRequest', {
     docId,
-    apiVersion: '2.0.491',
+    apiVersion: '2.0.506',
     source: {
       data: source.data,
       url: source.url,
       password: source.password,
       disableAutoFetch: source.disableAutoFetch,
       rangeChunkSize: source.rangeChunkSize,
       length: source.length
     },
@@ -6247,18 +6247,18 @@ var InternalRenderTask = function Intern
         }
       }
     }
   };
   return InternalRenderTask;
 }();
 var version, build;
 {
-  exports.version = version = '2.0.491';
-  exports.build = build = '2dc4af52';
+  exports.version = version = '2.0.506';
+  exports.build = build = 'b7a3a5e7';
 }
 exports.getDocument = getDocument;
 exports.LoopbackPort = LoopbackPort;
 exports.PDFDataRangeTransport = PDFDataRangeTransport;
 exports.PDFWorker = PDFWorker;
 exports.PDFDocumentProxy = PDFDocumentProxy;
 exports.PDFPageProxy = PDFPageProxy;
 exports.setPDFNetworkStreamFactory = setPDFNetworkStreamFactory;
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -21098,18 +21098,18 @@ exports.PostScriptCompiler = PostScriptC
 
 /***/ }),
 /* 19 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 
-var pdfjsVersion = '2.0.491';
-var pdfjsBuild = '2dc4af52';
+var pdfjsVersion = '2.0.506';
+var pdfjsBuild = 'b7a3a5e7';
 var pdfjsCoreWorker = __w_pdfjs_require__(20);
 exports.WorkerMessageHandler = pdfjsCoreWorker.WorkerMessageHandler;
 
 /***/ }),
 /* 20 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
@@ -21300,17 +21300,17 @@ var WorkerMessageHandler = {
     });
   },
   createDocumentHandler(docParams, port) {
     var pdfManager;
     var terminated = false;
     var cancelXHRs = null;
     var WorkerTasks = [];
     let apiVersion = docParams.apiVersion;
-    let workerVersion = '2.0.491';
+    let workerVersion = '2.0.506';
     if (apiVersion !== null && apiVersion !== workerVersion) {
       throw new Error(`The API version "${apiVersion}" does not match ` + `the Worker version "${workerVersion}".`);
     }
     var docId = docParams.docId;
     var docBaseUrl = docParams.docBaseUrl;
     var workerHandlerName = docParams.docId + '_worker';
     var handler = new _util.MessageHandler(workerHandlerName, docId, port);
     handler.postMessageTransfers = docParams.postMessageTransfers;
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..cb702fc4d11149a31f22495ad285b98cc3cf21c4
GIT binary patch
literal 218
zc$@*+044v4P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0001?Nkl<ZILqyj
zu?j*l3`I|D>*%Ckqm%#nzTkbB2o8#L5J}@;s<rwCXT4BDI5)YZ@Yh3TCT1qzaxt?V
zkVItZ#sknBi-@e8?}1Y1BX9&3jrjtcs=;f^ufVxB5m0|s>Iu@+PaMFaH&X@x>v-#*
zT%ew8%}^g;^@P+XsOmg~P#3(NpsFc^aQ}m07`CO9Heo%E<1FX=-1y2jJ^yvx04Rl6
U>n3b|U;qFB07*qoM6N<$f*JE%`Tzg`
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..7f05289bb157e3a8bb79578e556ff6c886c9d57b
GIT binary patch
literal 332
zc$@)D0ki&zP)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F80003KNkl<ZSi|j8
z!EFN}5S)$p`H?o9zxzltF2N0@gDW5vL^{PO;gH`<KAf~!?*dk&e8Nbl1F<tKGXP9X
zOicWD2+5B+J^*+Zkyl+n@(sXgP%q-Y=sf{&|ED?ufE$3LBYLcBJ+Q&2nitgsbOU==
zo-mAvJqd!EeM8Op(L_=O(h%_0XvPsgI)v*$MCO3~QB7$PlOoGN_pOM~Xdu*1#$t9h
zC?-q2E*%%diz*FR?A;MQ-~^4!^cv{pJ7P5k?f|1ZfX>P2JUa$YvE|?$&qg2(0Xi}8
zfoQyg@~LjHi+wr*R{+R4v(^@qYf;}<z-P|cK34)@T9)OkinAkd0r0%8E4FRx<^5a!
e#KgqJ-{Tv#QIqbb2T;lY0000<MNUMnLSTYQr;R%R
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0b8427a16c0f347dc36dc4b1619edb8e166d3b53
GIT binary patch
literal 228
zc$@*`02}{_P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00021Nkl<ZILpP7
zO$tIG6h_a@hz2=m3k}*sD}EDK)FOhEprETwd?0cIZIU43=g0B>3C1d=Jb-(>N<F=Z
z9D4880;sAw=k_gARkP(a0n4(SVvL(s^WK{PD}0%GlP|^_utrd-uZYNTC<WjJ#J-5b
zH2`w`dEEdy15n_-hF;G+3&@7X%pB}`Z5R+jxTKW+I|o<5^_hvtd9;X6Aa$Cb?SeLK
e{Amt`+V}zBl~6G#_K*4i0000<MNUMnLSTaGBws-Q
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..72ab55ebf270e855928094dd99efa70e54006903
GIT binary patch
literal 349
zc$@)U0iyniP)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F80003bNkl<ZSi|jC
z!EM7Z47>~acHj9o8x-B5D`bbPkWsoqw<x+nWB1u-ApI-m;}`}Z8KSKy2oeH@44EL$
zqK`ZinI<BCFc`^oj^C3Zrl@tumGDDp0HIGNkyR&VZqQY(qjT=igAlcz?}y8K|5N+~
zx~|(f=bm$Xo2K!2*sT~1ec%(o>jXW=vA#*Zmj-Z%P*2A>KL^Q|OZ0(MBMLPdkr?-{
z27ne&b6PP}y`|PHi1mC~N<%9EN$j)cHxNr0th8Q2!`SGRxh1(e4YT!t_x?9B|Fu34
z0HOCSdjf6S?jL&K?)AV2fVYV_V>Wz|+?J-{2EZ}1^KEL)g2guX$3!N|z)Jzp?YPJZ
vSpfVe-mcm-Oo<Y$j7@+_iBeJGG`kEZYdeB5BDu|}00000NkvXXu0mjfTn3rZ
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..165fc8bc01381b74b773f0775eabaf9f47b16ad9
GIT binary patch
literal 297
zc$@(#0oMMBP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0002+Nkl<ZILob)
zQAz|c42HkhqJnloZ=es}!4vmoPUImLc7_EPOyh&?kS*ICgw0EvereL?j{un25jgN?
zQr9FR&yLTW+fGEDB7mxD2;tE|Q`OQORV^We2Y*{t6(9oSoZshpzViVor8Pm$`EAI}
zOc<D%$rhKo*#oVD)jpVrybL&%zsBmET$9!&x?(3_@x_7N+5@1}Lvt0>T_~Nu^5~qO
z_Vl`zF1R?>lAa|fmnyjZ_yu(i?l0-Q#UsEpO`prM4Cmk~zttRk0ipk+h<t4E*2M2l
v=Y9@6Z%U<J1$FS63N1$eD`V+KT>*XpK1rZ4>56aa00000NkvXXu0mjftGk6u
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..4246141192214e00d2b789685bd28591e355a40d
GIT binary patch
literal 490
zc$@+10Tup<P)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F800058Nkl<ZSi|jC
zK~BR!4D_I!%b9!g0uaC83GjyU1^5YH=r4dDv{{ZEtL&XMm$Yn*?cJmxgj7pWl9lbu
zI=06P8ibkOX@UT_$lvGMH@OdSKj?~gHheP8kQ_i7KE}#jegF;tX(u-`T0h%A^nJe_
zK(M&)pAT1zaU(q|_Bk-l{l*x#-y#q~xbOS^ye4dm#UjeJ5W?yh`;+{cAgkjw5nVai
zooD_lfE&>V)<kqEGF>ZS<n}p$ty4k}4hX<f0N5D%Y6${pwLV}qqBOA)c{(E?%@20C
zl3voHI|qQ(<=F893xw8+snAlVM2btlr<tN$5#poBh@D+vMh<v(T#5jU-Jt-0mFj`V
zkc|;StA4ri7eKHetO);B8P+E5*E|qod{g*e&Ie3HY0w=>j4_BA!t|BfA0p6o-NP|_
z-gVvMX&(6Z^S~1kT|0VtCO-qX6Ljp^OL+lsE6cDYqFrU@YmK#CC4glu7fcbJ(6);Q
z6Yo$_>hG)^ZscgEazTmFN)bvG{NU2@hjZkb%Z9AYlNJDLDAB?PXXKK=Q!O2*!UzUO
gM}`~DfHTU0Py7T36}?uA&j0`b07*qoM6N<$f)Qxjl>h($
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..3fa07e703eaa56fa201db3e4ed6cde8bf849d608
GIT binary patch
literal 347
zc$@)S0i^zkP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0003ZNkl<ZILp<N
zJ4!@B5JgY<9%3-b82DL$U|?V-6DYU^KMN2eGxy*MjEqc;90gM&4Fp3!f%r#E1Q`$p
zL716i(oY&eClo8`x^<eWTXe(!4>f*c{dlCL1q@VmAOItx{sSlR;WCL2patwCy#$P9
z!#;o~U>_)eWneV7(o#p%hB~JXrl}QwPUoUJqYhQPa_|N$0IR@xL>#N#%t23mO7{06
z;#BSB4j$ACwVCvW`uL4ssS`<W<{b4--Aeg(YId9Pd-X=WR(DhFEMK7=5o_u!Z~`0w
z>)8q~5z$bWfY($TBzu~7bp@DAwE>`)HK?lxz&J3i9#$X#I-SmCQ52p0lXSb?vMkG~
te!K)|Hk<S9cDu+g)M~Xa2mScp#wROi)}|jGGD!dc002ovPDHLkV1giIi$VYZ
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..32e5033d7db1b74644f29815fd96b7153d422e1e
GIT binary patch
literal 694
zc$@*Y0!jUeP)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F80007gNkl<ZSi|j>
zJ!n)x6otRLCTJlEs31{61T7RHpkj!XC@2;diikhp52Uab1RFciK#J5BS_mo#f}oLf
zMnokcVh}W95>yBX62x7QBud_~$lYZ!dGj`4Vc}jFc4y|C;m(;eb00LK5smoY!>WHI
zJpy*+U=(A4VKY0t05s3v>9{;jbuA0|VFKd;K#4@F^O)J@D1osAJ{+s{&1?%mQvk?v
zNL6B%qex1rdYA=?G$lv_zztyB`x}81z)_%7CxL0;Ht-mj1e%?H5;&4`EHw!~c<V_S
zM$$RQj3)S_k~#|bT`|8D9YF?kJGfoaOG$&0IvsNlm`zA5;2kg~saeuxNpB@RleEn-
zcY(R0BbWqAl2!m`fmWZ}1H3Y`SxM6oFWkUZ;CVE@2G|R{2Y!13%Tfuf2cAY_zknyM
z$|hiCfdX-V&VAJ9WBXn8tjL54j4}gUGP6-h23)D*@Nn+FnO*h7S4%n%T!?N|t_S45
zUx71bHXx}9xaJQ{7B3R;l}PXh@CNu8V@f$YkONQo&f9=nz@Z3}QlPqlW0GD=>I3$f
z*@!DJRg^#md;+@6Y)aA&ckcFRaTO?#1<inUz-fQ|egY$@1Tr_=MKdc)+5q$b%l+!j
zc$-Tc0l-7x0MH5a`=j(7co1M(bNP~nC0(yqy(INY8cy*0C9N&swo4ic`SC57UP1*e
z^1v$KrldQ-3*ejI`8MDnaC}iw3bGr_se~34klMVNbws-xN)(;AJZqkAyYm!KoeHM{
zWwHdfuu9fUSgBOLL@rm3^99JU3nJjxZIw#p^CAySOiXl_%jMRkd`7jjv{d4;zvVZg
c5&tay0K-^<Vqu(^;{X5v07*qoM6N<$f&iK+82|tP
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..161147354c938270316935a777456fae2e31b169
GIT binary patch
literal 179
zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Rh}-6Ar`0aPB!FgP!M4GZj<b)
z^JgOevuv@pV@a0MTtyREl9HFmOiS!%c~F1)OiGHs)IsJ0A<7JDO0&<O>piW<usr#7
z@CH?dhBvc{Tqc}m)cpFe<u31<D)m0Dh^yPpl^>ktnD>>_BPdk-{4Q3*W423|m~tyV
d$^SQ3`dEaa-jzTV1)wV!JYD@<);T3K0RT5nKwSU;
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..8e51cf3b7d6ebed644db207d4e99391898ff3952
GIT binary patch
literal 261
zc$@(R0s8)lP)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F80002YNkl<ZSi|jB
z+YQ1n5Q`DtVF^a)8tlbBticH6gAtMeVtxvWP;<Jb=}CxK>PM>6+3`IT=LQA_{vFT;
zlDs+RCMl%Ic?WRmE#NVeKp_9*1#s5nJ+5#oSIC?~P!|Cd{-d=AS{%RvDO+ye>HrsR
z-|WKH60jo7L;!p2fwet=%G{Tw*wq0ldjpnZXubz1j3TVY@SE&?&K6Brt9iEQ0j?;|
z?hZ(6nN|*P>ry&s15--RLWKCuv3(r~AzWjOCrzyPK8?rbz|Y_X&c<GvKdEni00000
LNkvXXu0mjf(EDx_
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5126313a1dee1a2eb2ad0e46f52ad12915e86111
GIT binary patch
literal 344
zc$@)P0jK_nP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0003WNkl<ZILp=1
zy=qiZ6b0b#%v=Ov8U-Pc-pa;Cun_SXL`WABY$SODOG_&sAXUIZnYR!NYkLc|NyrZ}
zL?*d6E6thV5J;vJ7aY#d+H3E%_u>CXl5!ijiZAtWjM3s91u3Q6yMb5v`(3@v(c=@2
z@EUiUVsjkhV@>c1Yps>TB#)D<Cs|AKBgwCZKS}Z+$<rjyliaDUmbNbN7Q3}VaM?IG
z#Y=p^W+~;oI$kE2<6+%lsH?>eewI?Ulf1(jbk_un>O&pm0+;wwN_mrH4^OT;7~wO%
z;V<@a&?J~u@iTnId;Dn~qzcxtf!|F#aF%4Ni61RyRG{zs@8j{fZyO$lq3gQt(JH=!
q>2&&HHk*yxfXQUC{DoEgzr__9eVLMZ=Mj<s0000<MNUMnLSTXsot(q~
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5996b74db1e0c7c4ebf2b2f8a09fb4499d3119e6
GIT binary patch
literal 621
zc$@)k0+RiSP)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F80006sNkl<ZSi|j>
zyK59t6vlritY{&tt&m2HSSg}GQLwVM5G}OO%0?6mL5Zl9O+>`Mz*?*WwyA<d(ntzH
zT}^zTg$O&vhDDsuB4?I6H}}p&QdqbLE<1bAcOG+|17jM~nErRN>Os;RFrC0C#sW)b
zcBTeemp|&bluV_|Lix~v_W?i|M=L&NHWfMWE`ty6M*q!hKR_n{lnJ3quuMcG7pXQ(
z&5?<SS)Jd&Q{bR`dOhMRqr5uvzy(j~G;j`h?t05mzLW6!18~R8UPrt#70QJCoSEH<
zc+0@Dr+Fq9fy%Rc4!$I5U*ZkR1_w%D)@$Ohqy<Tfl1@1088B>1!2t)(0NXPmPB~Bl
zpMVKTJAli;ZeS;H(hF}0tTm-Tz}-wCsima_OnUx9eZPPg9+kcBP}x)i%JgrUxK9;f
z*gXT_wwb+=WWfEXqmpeHJCiezN<nD+J8;8|cYp^zsFZU+fAMy0bKos-)y#fK+5<cU
z4yAN-GDfVyfHM3lMJvD+GwVw_0zCF&EQ1!dwiGlKVN*1Qz)d$k2|V&nS_?4S!tU!N
z#^g2No8MKxFM+fEd>*)%iNL&>ee{<50-W9uM(GJ~swoAZ&FpJ!8JG=#Rafw{VGs>A
zv4Lw547cH8V$52!Kt_BsJ088+tIX1g*R$5e?O0P0vc)!RYULV@ilHcq)o9i$G5-a~
zu{8?hTTf9GD;sK{*Xzyp`~6*;`HbpzyG4ArRrxWE>7UUb)UPDB$k`IH00000NkvXX
Hu0mjf-&h!*
--- a/browser/extensions/pdfjs/content/web/viewer.css
+++ b/browser/extensions/pdfjs/content/web/viewer.css
@@ -285,16 +285,56 @@
   display: inline-block;
 }
 
 .pdfViewer.singlePageView .page {
   margin: 0;
   border: none;
 }
 
+.pdfViewer.scrollHorizontal, .pdfViewer.scrollWrapped, .spread {
+  margin-left: 3.5px;
+  margin-right: 3.5px;
+  text-align: center;
+}
+
+.pdfViewer.scrollHorizontal, .spread {
+  white-space: nowrap;
+}
+
+.pdfViewer.removePageBorders,
+.pdfViewer.scrollHorizontal .spread,
+.pdfViewer.scrollWrapped .spread {
+  margin-left: 0;
+  margin-right: 0;
+}
+
+.spread .page,
+.pdfViewer.scrollHorizontal .page,
+.pdfViewer.scrollWrapped .page,
+.pdfViewer.scrollHorizontal .spread,
+.pdfViewer.scrollWrapped .spread {
+  display: inline-block;
+  vertical-align: middle;
+}
+
+.spread .page,
+.pdfViewer.scrollHorizontal .page,
+.pdfViewer.scrollWrapped .page {
+  margin-left: -3.5px;
+  margin-right: -3.5px;
+}
+
+.pdfViewer.removePageBorders .spread .page,
+.pdfViewer.removePageBorders.scrollHorizontal .page,
+.pdfViewer.removePageBorders.scrollWrapped .page {
+  margin-left: 5px;
+  margin-right: 5px;
+}
+
 .pdfViewer .page canvas {
   margin: 0;
   display: block;
 }
 
 .pdfViewer .page canvas[hidden] {
   display: none;
 }
@@ -304,16 +344,32 @@
   display: block;
   left: 0;
   top: 0;
   right: 0;
   bottom: 0;
   background: url('images/loading-icon.gif') center no-repeat;
 }
 
+.pdfPresentationMode .pdfViewer {
+  margin-left: 0;
+  margin-right: 0;
+}
+
+.pdfPresentationMode .pdfViewer .page,
+.pdfPresentationMode .pdfViewer .spread {
+  display: block;
+}
+
+.pdfPresentationMode .pdfViewer .page,
+.pdfPresentationMode .pdfViewer.removePageBorders .page {
+  margin-left: auto;
+  margin-right: auto;
+}
+
 .pdfPresentationMode:-moz-full-screen .pdfViewer .page {
   margin-bottom: 100%;
   border: 0;
 }
 
 .pdfPresentationMode:fullscreen .pdfViewer .page {
   margin-bottom: 100%;
   border: 0;
@@ -1274,16 +1330,40 @@ html[dir="rtl"] .secondaryToolbarButton 
 .secondaryToolbarButton.selectTool::before {
   content: url(images/secondaryToolbarButton-selectTool.png);
 }
 
 .secondaryToolbarButton.handTool::before {
   content: url(images/secondaryToolbarButton-handTool.png);
 }
 
+.secondaryToolbarButton.scrollVertical::before {
+  content: url(images/secondaryToolbarButton-scrollVertical.png);
+}
+
+.secondaryToolbarButton.scrollHorizontal::before {
+  content: url(images/secondaryToolbarButton-scrollHorizontal.png);
+}
+
+.secondaryToolbarButton.scrollWrapped::before {
+  content: url(images/secondaryToolbarButton-scrollWrapped.png);
+}
+
+.secondaryToolbarButton.spreadNone::before {
+  content: url(images/secondaryToolbarButton-spreadNone.png);
+}
+
+.secondaryToolbarButton.spreadOdd::before {
+  content: url(images/secondaryToolbarButton-spreadOdd.png);
+}
+
+.secondaryToolbarButton.spreadEven::before {
+  content: url(images/secondaryToolbarButton-spreadEven.png);
+}
+
 .secondaryToolbarButton.documentProperties::before {
   content: url(images/secondaryToolbarButton-documentProperties.png);
 }
 
 .verticalToolbarSeparator {
   display: block;
   padding: 8px 0;
   margin: 8px 4px;
@@ -1991,16 +2071,40 @@ html[dir='rtl'] #documentPropertiesOverl
   .secondaryToolbarButton.selectTool::before {
     content: url(images/secondaryToolbarButton-selectTool@2x.png);
   }
 
   .secondaryToolbarButton.handTool::before {
     content: url(images/secondaryToolbarButton-handTool@2x.png);
   }
 
+  .secondaryToolbarButton.scrollVertical::before {
+    content: url(images/secondaryToolbarButton-scrollVertical@2x.png);
+  }
+
+  .secondaryToolbarButton.scrollHorizontal::before {
+    content: url(images/secondaryToolbarButton-scrollHorizontal@2x.png);
+  }
+
+  .secondaryToolbarButton.scrollWrapped::before {
+    content: url(images/secondaryToolbarButton-scrollWrapped@2x.png);
+  }
+
+  .secondaryToolbarButton.spreadNone::before {
+    content: url(images/secondaryToolbarButton-spreadNone@2x.png);
+  }
+
+  .secondaryToolbarButton.spreadOdd::before {
+    content: url(images/secondaryToolbarButton-spreadOdd@2x.png);
+  }
+
+  .secondaryToolbarButton.spreadEven::before {
+    content: url(images/secondaryToolbarButton-spreadEven@2x.png);
+  }
+
   .secondaryToolbarButton.documentProperties::before {
     content: url(images/secondaryToolbarButton-documentProperties@2x.png);
   }
 
   .outlineItemToggler::before {
     transform: scale(0.5);
     top: -1px;
     content: url(images/treeitem-expanded@2x.png);
--- a/browser/extensions/pdfjs/content/web/viewer.html
+++ b/browser/extensions/pdfjs/content/web/viewer.html
@@ -141,17 +141,41 @@ See https://github.com/adobe-type-tools/
               <span data-l10n-id="cursor_text_select_tool_label">Text Selection Tool</span>
             </button>
             <button id="cursorHandTool" class="secondaryToolbarButton handTool" title="Enable Hand Tool" tabindex="61" data-l10n-id="cursor_hand_tool">
               <span data-l10n-id="cursor_hand_tool_label">Hand Tool</span>
             </button>
 
             <div class="horizontalToolbarSeparator"></div>
 
-            <button id="documentProperties" class="secondaryToolbarButton documentProperties" title="Document Properties…" tabindex="62" data-l10n-id="document_properties">
+            <button id="scrollVertical" class="secondaryToolbarButton scrollVertical toggled" title="Use Vertical Scrolling" tabindex="62" data-l10n-id="scroll_vertical">
+              <span data-l10n-id="scroll_vertical_label">Vertical Scrolling</span>
+            </button>
+            <button id="scrollHorizontal" class="secondaryToolbarButton scrollHorizontal" title="Use Horizontal Scrolling" tabindex="63" data-l10n-id="scroll_horizontal">
+              <span data-l10n-id="scroll_horizontal_label">Horizontal Scrolling</span>
+            </button>
+            <button id="scrollWrapped" class="secondaryToolbarButton scrollWrapped" title="Use Wrapped Scrolling" tabindex="64" data-l10n-id="scroll_wrapped">
+              <span data-l10n-id="scroll_wrapped_label">Wrapped Scrolling</span>
+            </button>
+
+            <div class="horizontalToolbarSeparator"></div>
+
+            <button id="spreadNone" class="secondaryToolbarButton spreadNone toggled" title="Do not join page spreads" tabindex="65" data-l10n-id="spread_none">
+              <span data-l10n-id="spread_none_label">No Spreads</span>
+            </button>
+            <button id="spreadOdd" class="secondaryToolbarButton spreadOdd" title="Join page spreads starting with odd-numbered pages" tabindex="66" data-l10n-id="spread_odd">
+              <span data-l10n-id="spread_odd_label">Odd Spreads</span>
+            </button>
+            <button id="spreadEven" class="secondaryToolbarButton spreadEven" title="Join page spreads starting with even-numbered pages" tabindex="67" data-l10n-id="spread_even">
+              <span data-l10n-id="spread_even_label">Even Spreads</span>
+            </button>
+
+            <div class="horizontalToolbarSeparator"></div>
+
+            <button id="documentProperties" class="secondaryToolbarButton documentProperties" title="Document Properties…" tabindex="68" data-l10n-id="document_properties">
               <span data-l10n-id="document_properties_label">Document Properties…</span>
             </button>
           </div>
         </div>  <!-- secondaryToolbar -->
 
         <div class="toolbar">
           <div id="toolbarContainer">
             <div id="toolbarViewer">
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -77,30 +77,30 @@
 /******/
 /******/ 	// Object.prototype.hasOwnProperty.call
 /******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
 /******/
 /******/ 	// __webpack_public_path__
 /******/ 	__webpack_require__.p = "";
 /******/
 /******/ 	// Load entry module and return exports
-/******/ 	return __webpack_require__(__webpack_require__.s = 10);
+/******/ 	return __webpack_require__(__webpack_require__.s = 11);
 /******/ })
 /************************************************************************/
 /******/ ([
 /* 0 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
-exports.waitOnEventOrTimeout = exports.WaitOnType = exports.animationStarted = exports.normalizeWheelEventDelta = exports.binarySearchFirstItem = exports.watchScroll = exports.scrollIntoView = exports.getOutputScale = exports.approximateFraction = exports.getPageSizeInches = exports.roundToDivide = exports.getVisibleElements = exports.parseQueryString = exports.noContextMenuHandler = exports.getPDFFileNameFromURL = exports.ProgressBar = exports.EventBus = exports.NullL10n = exports.TextLayerMode = exports.RendererType = exports.PresentationModeState = exports.cloneObj = exports.isFileSchema = exports.isPortraitOrientation = exports.isValidRotation = exports.VERTICAL_PADDING = exports.SCROLLBAR_PADDING = exports.MAX_AUTO_SCALE = exports.UNKNOWN_SCALE = exports.MAX_SCALE = exports.MIN_SCALE = exports.DEFAULT_SCALE = exports.DEFAULT_SCALE_VALUE = exports.CSS_UNITS = undefined;
+exports.moveToEndOfArray = exports.waitOnEventOrTimeout = exports.WaitOnType = exports.animationStarted = exports.normalizeWheelEventDelta = exports.binarySearchFirstItem = exports.watchScroll = exports.scrollIntoView = exports.getOutputScale = exports.approximateFraction = exports.getPageSizeInches = exports.roundToDivide = exports.getVisibleElements = exports.backtrackBeforeAllVisibleElements = exports.parseQueryString = exports.noContextMenuHandler = exports.getPDFFileNameFromURL = exports.ProgressBar = exports.EventBus = exports.NullL10n = exports.TextLayerMode = exports.RendererType = exports.PresentationModeState = exports.cloneObj = exports.isFileSchema = exports.isPortraitOrientation = exports.isValidRotation = exports.VERTICAL_PADDING = exports.SCROLLBAR_PADDING = exports.MAX_AUTO_SCALE = exports.UNKNOWN_SCALE = exports.MAX_SCALE = exports.MIN_SCALE = exports.DEFAULT_SCALE = exports.DEFAULT_SCALE_VALUE = exports.CSS_UNITS = undefined;
 
 var _pdfjsLib = __webpack_require__(1);
 
 const CSS_UNITS = 96.0 / 72.0;
 const DEFAULT_SCALE_VALUE = 'auto';
 const DEFAULT_SCALE = 1.0;
 const MIN_SCALE = 0.10;
 const MAX_SCALE = 10.0;
@@ -158,17 +158,17 @@ function getOutputScale(ctx) {
 function scrollIntoView(element, spot, skipOverflowHiddenElements = false) {
   let parent = element.offsetParent;
   if (!parent) {
     console.error('offsetParent is not set -- cannot scroll');
     return;
   }
   let offsetY = element.offsetTop + element.clientTop;
   let offsetX = element.offsetLeft + element.clientLeft;
-  while (parent.clientHeight === parent.scrollHeight || skipOverflowHiddenElements && getComputedStyle(parent).overflow === 'hidden') {
+  while (parent.clientHeight === parent.scrollHeight && parent.clientWidth === parent.scrollWidth || skipOverflowHiddenElements && getComputedStyle(parent).overflow === 'hidden') {
     if (parent.dataset._scaleY) {
       offsetY /= parent.dataset._scaleY;
       offsetX /= parent.dataset._scaleX;
     }
     offsetY += parent.offsetTop;
     offsetX += parent.offsetLeft;
     parent = parent.offsetParent;
     if (!parent) {
@@ -188,27 +188,35 @@ function scrollIntoView(element, spot, s
 }
 function watchScroll(viewAreaElement, callback) {
   let debounceScroll = function (evt) {
     if (rAF) {
       return;
     }
     rAF = window.requestAnimationFrame(function viewAreaElementScrolled() {
       rAF = null;
+      let currentX = viewAreaElement.scrollLeft;
+      let lastX = state.lastX;
+      if (currentX !== lastX) {
+        state.right = currentX > lastX;
+      }
+      state.lastX = currentX;
       let currentY = viewAreaElement.scrollTop;
       let lastY = state.lastY;
       if (currentY !== lastY) {
         state.down = currentY > lastY;
       }
       state.lastY = currentY;
       callback(state);
     });
   };
   let state = {
+    right: true,
     down: true,
+    lastX: viewAreaElement.scrollLeft,
     lastY: viewAreaElement.scrollTop,
     _eventHandler: debounceScroll
   };
   let rAF = null;
   viewAreaElement.addEventListener('scroll', debounceScroll, true);
   return state;
 }
 function parseQueryString(query) {
@@ -289,53 +297,89 @@ function getPageSizeInches({ view, userU
   const changeOrientation = rotate % 180 !== 0;
   const width = (x2 - x1) / 72 * userUnit;
   const height = (y2 - y1) / 72 * userUnit;
   return {
     width: changeOrientation ? height : width,
     height: changeOrientation ? width : height
   };
 }
-function getVisibleElements(scrollEl, views, sortByVisibility = false) {
+function backtrackBeforeAllVisibleElements(index, views, top) {
+  if (index < 2) {
+    return index;
+  }
+  let elt = views[index].div;
+  let pageTop = elt.offsetTop + elt.clientTop;
+  if (pageTop >= top) {
+    elt = views[index - 1].div;
+    pageTop = elt.offsetTop + elt.clientTop;
+  }
+  for (let i = index - 2; i >= 0; --i) {
+    elt = views[i].div;
+    if (elt.offsetTop + elt.clientTop + elt.clientHeight <= pageTop) {
+      break;
+    }
+    index = i;
+  }
+  return index;
+}
+function getVisibleElements(scrollEl, views, sortByVisibility = false, horizontal = false) {
   let top = scrollEl.scrollTop,
       bottom = top + scrollEl.clientHeight;
   let left = scrollEl.scrollLeft,
       right = left + scrollEl.clientWidth;
-  function isElementBottomBelowViewTop(view) {
+  function isElementBottomAfterViewTop(view) {
     let element = view.div;
     let elementBottom = element.offsetTop + element.clientTop + element.clientHeight;
     return elementBottom > top;
   }
+  function isElementRightAfterViewLeft(view) {
+    let element = view.div;
+    let elementRight = element.offsetLeft + element.clientLeft + element.clientWidth;
+    return elementRight > left;
+  }
   let visible = [],
       view,
       element;
-  let currentHeight, viewHeight, hiddenHeight, percentHeight;
-  let currentWidth, viewWidth;
-  let firstVisibleElementInd = views.length === 0 ? 0 : binarySearchFirstItem(views, isElementBottomBelowViewTop);
+  let currentHeight, viewHeight, viewBottom, hiddenHeight;
+  let currentWidth, viewWidth, viewRight, hiddenWidth;
+  let percentVisible;
+  let firstVisibleElementInd = views.length === 0 ? 0 : binarySearchFirstItem(views, horizontal ? isElementRightAfterViewLeft : isElementBottomAfterViewTop);
+  if (views.length > 0 && !horizontal) {
+    firstVisibleElementInd = backtrackBeforeAllVisibleElements(firstVisibleElementInd, views, top);
+  }
+  let lastEdge = horizontal ? right : -1;
   for (let i = firstVisibleElementInd, ii = views.length; i < ii; i++) {
     view = views[i];
     element = view.div;
+    currentWidth = element.offsetLeft + element.clientLeft;
     currentHeight = element.offsetTop + element.clientTop;
+    viewWidth = element.clientWidth;
     viewHeight = element.clientHeight;
-    if (currentHeight > bottom) {
+    viewRight = currentWidth + viewWidth;
+    viewBottom = currentHeight + viewHeight;
+    if (lastEdge === -1) {
+      if (viewBottom >= bottom) {
+        lastEdge = viewBottom;
+      }
+    } else if ((horizontal ? currentWidth : currentHeight) > lastEdge) {
       break;
     }
-    currentWidth = element.offsetLeft + element.clientLeft;
-    viewWidth = element.clientWidth;
-    if (currentWidth + viewWidth < left || currentWidth > right) {
+    if (viewBottom <= top || currentHeight >= bottom || viewRight <= left || currentWidth >= right) {
       continue;
     }
-    hiddenHeight = Math.max(0, top - currentHeight) + Math.max(0, currentHeight + viewHeight - bottom);
-    percentHeight = (viewHeight - hiddenHeight) * 100 / viewHeight | 0;
+    hiddenHeight = Math.max(0, top - currentHeight) + Math.max(0, viewBottom - bottom);
+    hiddenWidth = Math.max(0, left - currentWidth) + Math.max(0, viewRight - right);
+    percentVisible = (viewHeight - hiddenHeight) * (viewWidth - hiddenWidth) * 100 / viewHeight / viewWidth | 0;
     visible.push({
       id: view.id,
       x: currentWidth,
       y: currentHeight,
       view,
-      percent: percentHeight
+      percent: percentVisible
     });
   }
   let first = visible[0];
   let last = visible[visible.length - 1];
   if (sortByVisibility) {
     visible.sort(function (a, b) {
       let pc = a.percent - b.percent;
       if (Math.abs(pc) > 0.001) {
@@ -538,16 +582,32 @@ class ProgressBar {
     if (this.visible) {
       return;
     }
     this.visible = true;
     document.body.classList.add('loadingInProgress');
     this.bar.classList.remove('hidden');
   }
 }
+function moveToEndOfArray(arr, condition) {
+  const moved = [],
+        len = arr.length;
+  let write = 0;
+  for (let read = 0; read < len; ++read) {
+    if (condition(arr[read])) {
+      moved.push(arr[read]);
+    } else {
+      arr[write] = arr[read];
+      ++write;
+    }
+  }
+  for (let read = 0; write < len; ++read, ++write) {
+    arr[write] = moved[read];
+  }
+}
 exports.CSS_UNITS = CSS_UNITS;
 exports.DEFAULT_SCALE_VALUE = DEFAULT_SCALE_VALUE;
 exports.DEFAULT_SCALE = DEFAULT_SCALE;
 exports.MIN_SCALE = MIN_SCALE;
 exports.MAX_SCALE = MAX_SCALE;
 exports.UNKNOWN_SCALE = UNKNOWN_SCALE;
 exports.MAX_AUTO_SCALE = MAX_AUTO_SCALE;
 exports.SCROLLBAR_PADDING = SCROLLBAR_PADDING;
@@ -560,28 +620,30 @@ exports.PresentationModeState = Presenta
 exports.RendererType = RendererType;
 exports.TextLayerMode = TextLayerMode;
 exports.NullL10n = NullL10n;
 exports.EventBus = EventBus;
 exports.ProgressBar = ProgressBar;
 exports.getPDFFileNameFromURL = getPDFFileNameFromURL;
 exports.noContextMenuHandler = noContextMenuHandler;
 exports.parseQueryString = parseQueryString;
+exports.backtrackBeforeAllVisibleElements = backtrackBeforeAllVisibleElements;
 exports.getVisibleElements = getVisibleElements;
 exports.roundToDivide = roundToDivide;
 exports.getPageSizeInches = getPageSizeInches;
 exports.approximateFraction = approximateFraction;
 exports.getOutputScale = getOutputScale;
 exports.scrollIntoView = scrollIntoView;
 exports.watchScroll = watchScroll;
 exports.binarySearchFirstItem = binarySearchFirstItem;
 exports.normalizeWheelEventDelta = normalizeWheelEventDelta;
 exports.animationStarted = animationStarted;
 exports.WaitOnType = WaitOnType;
 exports.waitOnEventOrTimeout = waitOnEventOrTimeout;
+exports.moveToEndOfArray = moveToEndOfArray;
 
 /***/ }),
 /* 1 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
@@ -839,47 +901,47 @@ exports.PDFPrintServiceFactory = exports
 var _ui_utils = __webpack_require__(0);
 
 var _pdfjsLib = __webpack_require__(1);
 
 var _pdf_cursor_tools = __webpack_require__(6);
 
 var _pdf_rendering_queue = __webpack_require__(3);
 
-var _pdf_sidebar = __webpack_require__(12);
+var _pdf_sidebar = __webpack_require__(13);
 
 var _app_options = __webpack_require__(7);
 
 var _dom_events = __webpack_require__(2);
 
-var _overlay_manager = __webpack_require__(13);
-
-var _password_prompt = __webpack_require__(14);
-
-var _pdf_attachment_viewer = __webpack_require__(15);
-
-var _pdf_document_properties = __webpack_require__(16);
-
-var _pdf_find_bar = __webpack_require__(17);
+var _overlay_manager = __webpack_require__(14);
+
+var _password_prompt = __webpack_require__(15);
+
+var _pdf_attachment_viewer = __webpack_require__(16);
+
+var _pdf_document_properties = __webpack_require__(17);
+
+var _pdf_find_bar = __webpack_require__(18);
 
 var _pdf_find_controller = __webpack_require__(9);
 
-var _pdf_history = __webpack_require__(18);
+var _pdf_history = __webpack_require__(19);
 
 var _pdf_link_service = __webpack_require__(5);
 
-var _pdf_outline_viewer = __webpack_require__(19);
-
-var _pdf_presentation_mode = __webpack_require__(20);
-
-var _pdf_sidebar_resizer = __webpack_require__(21);
-
-var _pdf_thumbnail_viewer = __webpack_require__(22);
-
-var _pdf_viewer = __webpack_require__(24);
+var _pdf_outline_viewer = __webpack_require__(20);
+
+var _pdf_presentation_mode = __webpack_require__(21);
+
+var _pdf_sidebar_resizer = __webpack_require__(22);
+
+var _pdf_thumbnail_viewer = __webpack_require__(23);
+
+var _pdf_viewer = __webpack_require__(25);
 
 var _secondary_toolbar = __webpack_require__(29);
 
 var _toolbar = __webpack_require__(30);
 
 var _view_history = __webpack_require__(31);
 
 const DEFAULT_SCALE_DELTA = 1.1;
@@ -1012,16 +1074,20 @@ let PDFViewerApplication = {
     }), preferences.get('renderInteractiveForms').then(function resolved(value) {
       _app_options.AppOptions.set('renderInteractiveForms', value);
     }), preferences.get('disablePageMode').then(function resolved(value) {
       _app_options.AppOptions.set('disablePageMode', value);
     }), preferences.get('disablePageLabels').then(function resolved(value) {
       _app_options.AppOptions.set('disablePageLabels', value);
     }), preferences.get('enablePrintAutoRotate').then(function resolved(value) {
       _app_options.AppOptions.set('enablePrintAutoRotate', value);
+    }), preferences.get('scrollModeOnLoad').then(function resolved(value) {
+      _app_options.AppOptions.set('scrollModeOnLoad', value);
+    }), preferences.get('spreadModeOnLoad').then(function resolved(value) {
+      _app_options.AppOptions.set('spreadModeOnLoad', value);
     })]).catch(function (reason) {});
   },
   _parseHashParameters() {
     let { appConfig } = this;
     let waitOn = [];
     if (_app_options.AppOptions.get('pdfBugEnabled')) {
       let hash = document.location.hash.substring(1);
       let hashParams = (0, _ui_utils.parseQueryString)(hash);
@@ -1529,41 +1595,55 @@ let PDFViewerApplication = {
       };
       let storePromise = store.getMultiple({
         exists: false,
         page: '1',
         zoom: _ui_utils.DEFAULT_SCALE_VALUE,
         scrollLeft: '0',
         scrollTop: '0',
         rotation: null,
-        sidebarView: _pdf_sidebar.SidebarView.NONE
+        sidebarView: _pdf_sidebar.SidebarView.NONE,
+        scrollMode: null,
+        spreadMode: null
       }).catch(() => {});
       Promise.all([storePromise, pageModePromise]).then(([values = {}, pageMode]) => {
         let hash = _app_options.AppOptions.get('defaultZoomValue') ? 'zoom=' + _app_options.AppOptions.get('defaultZoomValue') : null;
         let rotation = null;
         let sidebarView = _app_options.AppOptions.get('sidebarViewOnLoad');
+        let scrollMode = _app_options.AppOptions.get('scrollModeOnLoad');
+        let spreadMode = _app_options.AppOptions.get('spreadModeOnLoad');
         if (values.exists && _app_options.AppOptions.get('showPreviousViewOnLoad')) {
           hash = 'page=' + values.page + '&zoom=' + (_app_options.AppOptions.get('defaultZoomValue') || values.zoom) + ',' + values.scrollLeft + ',' + values.scrollTop;
           rotation = parseInt(values.rotation, 10);
           sidebarView = sidebarView || values.sidebarView | 0;
+          if (values.scrollMode !== null) {
+            scrollMode = values.scrollMode;
+          }
+          if (values.spreadMode !== null) {
+            spreadMode = values.spreadMode;
+          }
         }
         if (pageMode && !_app_options.AppOptions.get('disablePageMode')) {
           sidebarView = sidebarView || apiPageModeToSidebarView(pageMode);
         }
         return {
           hash,
           rotation,
-          sidebarView
+          sidebarView,
+          scrollMode,
+          spreadMode
         };
-      }).then(({ hash, rotation, sidebarView }) => {
+      }).then(({ hash, rotation, sidebarView, scrollMode, spreadMode }) => {
         initialParams.bookmark = this.initialBookmark;
         initialParams.hash = hash;
         this.setInitialView(hash, {
           rotation,
-          sidebarView
+          sidebarView,
+          scrollMode,
+          spreadMode
         });
         if (!this.isViewerEmbedded) {
           pdfViewer.focus();
         }
         return pagesPromise;
       }).then(() => {
         if (!initialParams.bookmark && !initialParams.hash) {
           return;
@@ -1675,22 +1755,29 @@ let PDFViewerApplication = {
       this.externalServices.reportTelemetry({
         type: 'documentInfo',
         version: versionId,
         generator: generatorId,
         formType
       });
     });
   },
-  setInitialView(storedHash, { rotation, sidebarView } = {}) {
+  setInitialView(storedHash, values = {}) {
+    let { rotation, sidebarView, scrollMode, spreadMode } = values;
     let setRotation = angle => {
       if ((0, _ui_utils.isValidRotation)(angle)) {
         this.pdfViewer.pagesRotation = angle;
       }
     };
+    if (Number.isInteger(scrollMode)) {
+      this.pdfViewer.setScrollMode(scrollMode);
+    }
+    if (Number.isInteger(spreadMode)) {
+      this.pdfViewer.setSpreadMode(spreadMode);
+    }
     this.isInitialViewSet = true;
     this.pdfSidebar.setInitialView(sidebarView);
     if (this.initialBookmark) {
       setRotation(this.initialRotation);
       delete this.initialRotation;
       this.pdfLinkService.setHash(this.initialBookmark);
       this.initialBookmark = null;
     } else if (storedHash) {
@@ -1789,16 +1876,20 @@ let PDFViewerApplication = {
     eventBus.on('nextpage', webViewerNextPage);
     eventBus.on('previouspage', webViewerPreviousPage);
     eventBus.on('zoomin', webViewerZoomIn);
     eventBus.on('zoomout', webViewerZoomOut);
     eventBus.on('pagenumberchanged', webViewerPageNumberChanged);
     eventBus.on('scalechanged', webViewerScaleChanged);
     eventBus.on('rotatecw', webViewerRotateCw);
     eventBus.on('rotateccw', webViewerRotateCcw);
+    eventBus.on('switchscrollmode', webViewerSwitchScrollMode);
+    eventBus.on('scrollmodechanged', webViewerScrollModeChanged);
+    eventBus.on('switchspreadmode', webViewerSwitchSpreadMode);
+    eventBus.on('spreadmodechanged', webViewerSpreadModeChanged);
     eventBus.on('documentproperties', webViewerDocumentProperties);
     eventBus.on('find', webViewerFind);
     eventBus.on('findfromurlhash', webViewerFindFromUrlHash);
   },
   bindWindowEvents() {
     let { eventBus, _boundEvents } = this;
     _boundEvents.windowResize = () => {
       eventBus.dispatch('resize', { source: window });
@@ -1845,16 +1936,20 @@ let PDFViewerApplication = {
     eventBus.off('nextpage', webViewerNextPage);
     eventBus.off('previouspage', webViewerPreviousPage);
     eventBus.off('zoomin', webViewerZoomIn);
     eventBus.off('zoomout', webViewerZoomOut);
     eventBus.off('pagenumberchanged', webViewerPageNumberChanged);
     eventBus.off('scalechanged', webViewerScaleChanged);
     eventBus.off('rotatecw', webViewerRotateCw);
     eventBus.off('rotateccw', webViewerRotateCcw);
+    eventBus.off('switchscrollmode', webViewerSwitchScrollMode);
+    eventBus.off('scrollmodechanged', webViewerScrollModeChanged);
+    eventBus.off('switchspreadmode', webViewerSwitchSpreadMode);
+    eventBus.off('spreadmodechanged', webViewerSpreadModeChanged);
     eventBus.off('documentproperties', webViewerDocumentProperties);
     eventBus.off('find', webViewerFind);
     eventBus.off('findfromurlhash', webViewerFindFromUrlHash);
     _boundEvents.beforePrint = null;
     _boundEvents.afterPrint = null;
   },
   unbindWindowEvents() {
     let { _boundEvents } = this;
@@ -2051,16 +2146,28 @@ function webViewerUpdateViewarea(evt) {
   }
   let href = PDFViewerApplication.pdfLinkService.getAnchorUrl(location.pdfOpenParams);
   PDFViewerApplication.appConfig.toolbar.viewBookmark.href = href;
   PDFViewerApplication.appConfig.secondaryToolbar.viewBookmarkButton.href = href;
   let currentPage = PDFViewerApplication.pdfViewer.getPageView(PDFViewerApplication.page - 1);
   let loading = currentPage.renderingState !== _pdf_rendering_queue.RenderingStates.FINISHED;
   PDFViewerApplication.toolbar.updateLoadingIndicatorState(loading);
 }
+function webViewerScrollModeChanged(evt) {
+  let store = PDFViewerApplication.store;
+  if (store && PDFViewerApplication.isInitialViewSet) {
+    store.set('scrollMode', evt.mode).catch(function () {});
+  }
+}
+function webViewerSpreadModeChanged(evt) {
+  let store = PDFViewerApplication.store;
+  if (store && PDFViewerApplication.isInitialViewSet) {
+    store.set('spreadMode', evt.mode).catch(function () {});
+  }
+}
 function webViewerResize() {
   let { pdfDocument, pdfViewer } = PDFViewerApplication;
   if (!pdfDocument) {
     return;
   }
   let currentScaleValue = pdfViewer.currentScaleValue;
   if (currentScaleValue === 'auto' || currentScaleValue === 'page-fit' || currentScaleValue === 'page-width') {
     pdfViewer.currentScaleValue = currentScaleValue;
@@ -2123,16 +2230,22 @@ function webViewerScaleChanged(evt) {
   PDFViewerApplication.pdfViewer.currentScaleValue = evt.value;
 }
 function webViewerRotateCw() {
   PDFViewerApplication.rotatePages(90);
 }
 function webViewerRotateCcw() {
   PDFViewerApplication.rotatePages(-90);
 }
+function webViewerSwitchScrollMode(evt) {
+  PDFViewerApplication.pdfViewer.setScrollMode(evt.mode);
+}
+function webViewerSwitchSpreadMode(evt) {
+  PDFViewerApplication.pdfViewer.setSpreadMode(evt.mode);
+}
 function webViewerDocumentProperties() {
   PDFViewerApplication.pdfDocumentProperties.open();
 }
 function webViewerFind(evt) {
   PDFViewerApplication.findController.executeCommand('find' + evt.type, {
     query: evt.query,
     phraseSearch: evt.phraseSearch,
     caseSensitive: evt.caseSensitive,
@@ -2319,61 +2432,71 @@ function webViewerKeyDown(evt) {
   let curElement = document.activeElement || document.querySelector(':focus');
   let curElementTagName = curElement && curElement.tagName.toUpperCase();
   if (curElementTagName === 'INPUT' || curElementTagName === 'TEXTAREA' || curElementTagName === 'SELECT') {
     if (evt.keyCode !== 27) {
       return;
     }
   }
   if (cmd === 0) {
+    let turnPage = 0,
+        turnOnlyIfPageFit = false;
     switch (evt.keyCode) {
       case 38:
       case 33:
+        if (pdfViewer.isVerticalScrollbarEnabled) {
+          turnOnlyIfPageFit = true;
+        }
+        turnPage = -1;
+        break;
       case 8:
-        if (!isViewerInPresentationMode && pdfViewer.currentScaleValue !== 'page-fit') {
-          break;
+        if (!isViewerInPresentationMode) {
+          turnOnlyIfPageFit = true;
         }
+        turnPage = -1;
+        break;
       case 37:
         if (pdfViewer.isHorizontalScrollbarEnabled) {
-          break;
+          turnOnlyIfPageFit = true;
         }
       case 75:
       case 80:
-        if (PDFViewerApplication.page > 1) {
-          PDFViewerApplication.page--;
-        }
-        handled = true;
+        turnPage = -1;
         break;
       case 27:
         if (PDFViewerApplication.secondaryToolbar.isOpen) {
           PDFViewerApplication.secondaryToolbar.close();
           handled = true;
         }
         if (!PDFViewerApplication.supportsIntegratedFind && PDFViewerApplication.findBar.opened) {
           PDFViewerApplication.findBar.close();
           handled = true;
         }
         break;
-      case 13:
       case 40:
       case 34:
+        if (pdfViewer.isVerticalScrollbarEnabled) {
+          turnOnlyIfPageFit = true;
+        }
+        turnPage = 1;
+        break;
+      case 13:
       case 32:
-        if (!isViewerInPresentationMode && pdfViewer.currentScaleValue !== 'page-fit') {
-          break;
+        if (!isViewerInPresentationMode) {
+          turnOnlyIfPageFit = true;
         }
+        turnPage = 1;
+        break;
       case 39:
         if (pdfViewer.isHorizontalScrollbarEnabled) {
-          break;
+          turnOnlyIfPageFit = true;
         }
       case 74:
       case 78:
-        if (PDFViewerApplication.page < PDFViewerApplication.pagesCount) {
-          PDFViewerApplication.page++;
-        }
-        handled = true;
+        turnPage = 1;
         break;
       case 36:
         if (isViewerInPresentationMode || PDFViewerApplication.page > 1) {
           PDFViewerApplication.page = 1;
           handled = true;
           ensureViewerFocused = true;
         }
         break;
@@ -2389,16 +2512,28 @@ function webViewerKeyDown(evt) {
         break;
       case 72:
         PDFViewerApplication.pdfCursorTools.switchTool(_pdf_cursor_tools.CursorTool.HAND);
         break;
       case 82:
         PDFViewerApplication.rotatePages(90);
         break;
     }
+    if (turnPage !== 0 && (!turnOnlyIfPageFit || pdfViewer.currentScaleValue === 'page-fit')) {
+      if (turnPage > 0) {
+        if (PDFViewerApplication.page < PDFViewerApplication.pagesCount) {
+          PDFViewerApplication.page++;
+        }
+      } else {
+        if (PDFViewerApplication.page > 1) {
+          PDFViewerApplication.page--;
+        }
+      }
+      handled = true;
+    }
   }
   if (cmd === 4) {
     switch (evt.keyCode) {
       case 13:
       case 32:
         if (!isViewerInPresentationMode && pdfViewer.currentScaleValue !== 'page-fit') {
           break;
         }
@@ -2791,17 +2926,17 @@ exports.SimpleLinkService = SimpleLinkSe
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
 exports.PDFCursorTools = exports.CursorTool = undefined;
 
-var _grab_to_pan = __webpack_require__(11);
+var _grab_to_pan = __webpack_require__(12);
 
 const CursorTool = {
   SELECT: 0,
   HAND: 1,
   ZOOM: 2
 };
 class PDFCursorTools {
   constructor({ container, eventBus, cursorToolOnLoad = CursorTool.SELECT }) {
@@ -3460,16 +3595,762 @@ exports.PDFFindController = PDFFindContr
 
 /***/ }),
 /* 10 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.SpreadMode = exports.ScrollMode = exports.BaseViewer = undefined;
+
+var _ui_utils = __webpack_require__(0);
+
+var _pdf_rendering_queue = __webpack_require__(3);
+
+var _annotation_layer_builder = __webpack_require__(26);
+
+var _pdfjsLib = __webpack_require__(1);
+
+var _dom_events = __webpack_require__(2);
+
+var _pdf_page_view = __webpack_require__(27);
+
+var _pdf_link_service = __webpack_require__(5);
+
+var _text_layer_builder = __webpack_require__(28);
+
+const DEFAULT_CACHE_SIZE = 10;
+const ScrollMode = {
+  VERTICAL: 0,
+  HORIZONTAL: 1,
+  WRAPPED: 2
+};
+const SpreadMode = {
+  NONE: 0,
+  ODD: 1,
+  EVEN: 2
+};
+function PDFPageViewBuffer(size) {
+  let data = [];
+  this.push = function (view) {
+    let i = data.indexOf(view);
+    if (i >= 0) {
+      data.splice(i, 1);
+    }
+    data.push(view);
+    if (data.length > size) {
+      data.shift().destroy();
+    }
+  };
+  this.resize = function (newSize, pagesToKeep) {
+    size = newSize;
+    if (pagesToKeep) {
+      const pageIdsToKeep = new Set();
+      for (let i = 0, iMax = pagesToKeep.length; i < iMax; ++i) {
+        pageIdsToKeep.add(pagesToKeep[i].id);
+      }
+      (0, _ui_utils.moveToEndOfArray)(data, function (page) {
+        return pageIdsToKeep.has(page.id);
+      });
+    }
+    while (data.length > size) {
+      data.shift().destroy();
+    }
+  };
+}
+function isSameScale(oldScale, newScale) {
+  if (newScale === oldScale) {
+    return true;
+  }
+  if (Math.abs(newScale - oldScale) < 1e-15) {
+    return true;
+  }
+  return false;
+}
+class BaseViewer {
+  constructor(options) {
+    if (this.constructor === BaseViewer) {
+      throw new Error('Cannot initialize BaseViewer.');
+    }
+    this._name = this.constructor.name;
+    this.container = options.container;
+    this.viewer = options.viewer || options.container.firstElementChild;
+    this.eventBus = options.eventBus || (0, _dom_events.getGlobalEventBus)();
+    this.linkService = options.linkService || new _pdf_link_service.SimpleLinkService();
+    this.downloadManager = options.downloadManager || null;
+    this.removePageBorders = options.removePageBorders || false;
+    this.textLayerMode = Number.isInteger(options.textLayerMode) ? options.textLayerMode : _ui_utils.TextLayerMode.ENABLE;
+    this.enhanceTextSelection = options.enhanceTextSelection || false;
+    this.imageResourcesPath = options.imageResourcesPath || '';
+    this.renderInteractiveForms = options.renderInteractiveForms || false;
+    this.enablePrintAutoRotate = options.enablePrintAutoRotate || false;
+    this.renderer = options.renderer || _ui_utils.RendererType.CANVAS;
+    this.enableWebGL = options.enableWebGL || false;
+    this.useOnlyCssZoom = options.useOnlyCssZoom || false;
+    this.maxCanvasPixels = options.maxCanvasPixels;
+    this.l10n = options.l10n || _ui_utils.NullL10n;
+    this.scrollMode = options.scrollMode || ScrollMode.VERTICAL;
+    this.spreadMode = options.spreadMode || SpreadMode.NONE;
+    this.defaultRenderingQueue = !options.renderingQueue;
+    if (this.defaultRenderingQueue) {
+      this.renderingQueue = new _pdf_rendering_queue.PDFRenderingQueue();
+      this.renderingQueue.setViewer(this);
+    } else {
+      this.renderingQueue = options.renderingQueue;
+    }
+    this.scroll = (0, _ui_utils.watchScroll)(this.container, this._scrollUpdate.bind(this));
+    this.presentationModeState = _ui_utils.PresentationModeState.UNKNOWN;
+    this._resetView();
+    if (this.removePageBorders) {
+      this.viewer.classList.add('removePageBorders');
+    }
+    this._updateScrollModeClasses();
+  }
+  get pagesCount() {
+    return this._pages.length;
+  }
+  getPageView(index) {
+    return this._pages[index];
+  }
+  get pageViewsReady() {
+    return this._pageViewsReady;
+  }
+  get currentPageNumber() {
+    return this._currentPageNumber;
+  }
+  set currentPageNumber(val) {
+    if (!Number.isInteger(val)) {
+      throw new Error('Invalid page number.');
+    }
+    if (!this.pdfDocument) {
+      return;
+    }
+    this._setCurrentPageNumber(val, true);
+  }
+  _setCurrentPageNumber(val, resetCurrentPageView = false) {
+    if (this._currentPageNumber === val) {
+      if (resetCurrentPageView) {
+        this._resetCurrentPageView();
+      }
+      return;
+    }
+    if (!(0 < val && val <= this.pagesCount)) {
+      console.error(`${this._name}._setCurrentPageNumber: "${val}" is out of bounds.`);
+      return;
+    }
+    let arg = {
+      source: this,
+      pageNumber: val,
+      pageLabel: this._pageLabels && this._pageLabels[val - 1]
+    };
+    this._currentPageNumber = val;
+    this.eventBus.dispatch('pagechanging', arg);
+    this.eventBus.dispatch('pagechange', arg);
+    if (resetCurrentPageView) {
+      this._resetCurrentPageView();
+    }
+  }
+  get currentPageLabel() {
+    return this._pageLabels && this._pageLabels[this._currentPageNumber - 1];
+  }
+  set currentPageLabel(val) {
+    let pageNumber = val | 0;
+    if (this._pageLabels) {
+      let i = this._pageLabels.indexOf(val);
+      if (i >= 0) {
+        pageNumber = i + 1;
+      }
+    }
+    this.currentPageNumber = pageNumber;
+  }
+  get currentScale() {
+    return this._currentScale !== _ui_utils.UNKNOWN_SCALE ? this._currentScale : _ui_utils.DEFAULT_SCALE;
+  }
+  set currentScale(val) {
+    if (isNaN(val)) {
+      throw new Error('Invalid numeric scale');
+    }
+    if (!this.pdfDocument) {
+      return;
+    }
+    this._setScale(val, false);
+  }
+  get currentScaleValue() {
+    return this._currentScaleValue;
+  }
+  set currentScaleValue(val) {
+    if (!this.pdfDocument) {
+      return;
+    }
+    this._setScale(val, false);
+  }
+  get pagesRotation() {
+    return this._pagesRotation;
+  }
+  set pagesRotation(rotation) {
+    if (!(0, _ui_utils.isValidRotation)(rotation)) {
+      throw new Error('Invalid pages rotation angle.');
+    }
+    if (!this.pdfDocument) {
+      return;
+    }
+    if (this._pagesRotation === rotation) {
+      return;
+    }
+    this._pagesRotation = rotation;
+    let pageNumber = this._currentPageNumber;
+    for (let i = 0, ii = this._pages.length; i < ii; i++) {
+      let pageView = this._pages[i];
+      pageView.update(pageView.scale, rotation);
+    }
+    if (this._currentScaleValue) {
+      this._setScale(this._currentScaleValue, true);
+    }
+    this.eventBus.dispatch('rotationchanging', {
+      source: this,
+      pagesRotation: rotation,
+      pageNumber
+    });
+    if (this.defaultRenderingQueue) {
+      this.update();
+    }
+  }
+  get _setDocumentViewerElement() {
+    throw new Error('Not implemented: _setDocumentViewerElement');
+  }
+  setDocument(pdfDocument) {
+    if (this.pdfDocument) {
+      this._cancelRendering();
+      this._resetView();
+    }
+    this.pdfDocument = pdfDocument;
+    if (!pdfDocument) {
+      return;
+    }
+    let pagesCount = pdfDocument.numPages;
+    let pagesCapability = (0, _pdfjsLib.createPromiseCapability)();
+    this.pagesPromise = pagesCapability.promise;
+    pagesCapability.promise.then(() => {
+      this._pageViewsReady = true;
+      this.eventBus.dispatch('pagesloaded', {
+        source: this,
+        pagesCount
+      });
+    });
+    let isOnePageRenderedResolved = false;
+    let onePageRenderedCapability = (0, _pdfjsLib.createPromiseCapability)();
+    this.onePageRendered = onePageRenderedCapability.promise;
+    let bindOnAfterAndBeforeDraw = pageView => {
+      pageView.onBeforeDraw = () => {
+        this._buffer.push(pageView);
+      };
+      pageView.onAfterDraw = () => {
+        if (!isOnePageRenderedResolved) {
+          isOnePageRenderedResolved = true;
+          onePageRenderedCapability.resolve();
+        }
+      };
+    };
+    let firstPagePromise = pdfDocument.getPage(1);
+    this.firstPagePromise = firstPagePromise;
+    firstPagePromise.then(pdfPage => {
+      let scale = this.currentScale;
+      let viewport = pdfPage.getViewport(scale * _ui_utils.CSS_UNITS);
+      for (let pageNum = 1; pageNum <= pagesCount; ++pageNum) {
+        let textLayerFactory = null;
+        if (this.textLayerMode !== _ui_utils.TextLayerMode.DISABLE) {
+          textLayerFactory = this;
+        }
+        let pageView = new _pdf_page_view.PDFPageView({
+          container: this._setDocumentViewerElement,
+          eventBus: this.eventBus,
+          id: pageNum,
+          scale,
+          defaultViewport: viewport.clone(),
+          renderingQueue: this.renderingQueue,
+          textLayerFactory,
+          textLayerMode: this.textLayerMode,
+          annotationLayerFactory: this,
+          imageResourcesPath: this.imageResourcesPath,
+          renderInteractiveForms: this.renderInteractiveForms,
+          renderer: this.renderer,
+          enableWebGL: this.enableWebGL,
+          useOnlyCssZoom: this.useOnlyCssZoom,
+          maxCanvasPixels: this.maxCanvasPixels,
+          l10n: this.l10n
+        });
+        bindOnAfterAndBeforeDraw(pageView);
+        this._pages.push(pageView);
+      }
+      if (this.spreadMode !== SpreadMode.NONE) {
+        this._regroupSpreads();
+      }
+      onePageRenderedCapability.promise.then(() => {
+        if (pdfDocument.loadingParams['disableAutoFetch']) {
+          pagesCapability.resolve();
+          return;
+        }
+        let getPagesLeft = pagesCount;
+        for (let pageNum = 1; pageNum <= pagesCount; ++pageNum) {
+          pdfDocument.getPage(pageNum).then(pdfPage => {
+            let pageView = this._pages[pageNum - 1];
+            if (!pageView.pdfPage) {
+              pageView.setPdfPage(pdfPage);
+            }
+            this.linkService.cachePageRef(pageNum, pdfPage.ref);
+            if (--getPagesLeft === 0) {
+              pagesCapability.resolve();
+            }
+          }, reason => {
+            console.error(`Unable to get page ${pageNum} to initialize viewer`, reason);
+            if (--getPagesLeft === 0) {
+              pagesCapability.resolve();
+            }
+          });
+        }
+      });
+      this.eventBus.dispatch('pagesinit', { source: this });
+      if (this.defaultRenderingQueue) {
+        this.update();
+      }
+      if (this.findController) {
+        this.findController.resolveFirstPage();
+      }
+    }).catch(reason => {
+      console.error('Unable to initialize viewer', reason);
+    });
+  }
+  setPageLabels(labels) {
+    if (!this.pdfDocument) {
+      return;
+    }
+    if (!labels) {
+      this._pageLabels = null;
+    } else if (!(labels instanceof Array && this.pdfDocument.numPages === labels.length)) {
+      this._pageLabels = null;
+      console.error(`${this._name}.setPageLabels: Invalid page labels.`);
+    } else {
+      this._pageLabels = labels;
+    }
+    for (let i = 0, ii = this._pages.length; i < ii; i++) {
+      let pageView = this._pages[i];
+      let label = this._pageLabels && this._pageLabels[i];
+      pageView.setPageLabel(label);
+    }
+  }
+  _resetView() {
+    this._pages = [];
+    this._currentPageNumber = 1;
+    this._currentScale = _ui_utils.UNKNOWN_SCALE;
+    this._currentScaleValue = null;
+    this._pageLabels = null;
+    this._buffer = new PDFPageViewBuffer(DEFAULT_CACHE_SIZE);
+    this._location = null;
+    this._pagesRotation = 0;
+    this._pagesRequests = [];
+    this._pageViewsReady = false;
+    this.viewer.textContent = '';
+  }
+  _scrollUpdate() {
+    if (this.pagesCount === 0) {
+      return;
+    }
+    this.update();
+  }
+  _scrollIntoView({ pageDiv, pageSpot = null, pageNumber = null }) {
+    throw new Error('Not implemented: _scrollIntoView');
+  }
+  _setScaleDispatchEvent(newScale, newValue, preset = false) {
+    let arg = {
+      source: this,
+      scale: newScale,
+      presetValue: preset ? newValue : undefined
+    };
+    this.eventBus.dispatch('scalechanging', arg);
+    this.eventBus.dispatch('scalechange', arg);
+  }
+  _setScaleUpdatePages(newScale, newValue, noScroll = false, preset = false) {
+    this._currentScaleValue = newValue.toString();
+    if (isSameScale(this._currentScale, newScale)) {
+      if (preset) {
+        this._setScaleDispatchEvent(newScale, newValue, true);
+      }
+      return;
+    }
+    for (let i = 0, ii = this._pages.length; i < ii; i++) {
+      this._pages[i].update(newScale);
+    }
+    this._currentScale = newScale;
+    if (!noScroll) {
+      let page = this._currentPageNumber,
+          dest;
+      if (this._location && !(this.isInPresentationMode || this.isChangingPresentationMode)) {
+        page = this._location.pageNumber;
+        dest = [null, { name: 'XYZ' }, this._location.left, this._location.top, null];
+      }
+      this.scrollPageIntoView({
+        pageNumber: page,
+        destArray: dest,
+        allowNegativeOffset: true
+      });
+    }
+    this._setScaleDispatchEvent(newScale, newValue, preset);
+    if (this.defaultRenderingQueue) {
+      this.update();
+    }
+  }
+  _setScale(value, noScroll = false) {
+    let scale = parseFloat(value);
+    if (scale > 0) {
+      this._setScaleUpdatePages(scale, value, noScroll, false);
+    } else {
+      let currentPage = this._pages[this._currentPageNumber - 1];
+      if (!currentPage) {
+        return;
+      }
+      let hPadding = this.isInPresentationMode || this.removePageBorders ? 0 : _ui_utils.SCROLLBAR_PADDING;
+      let vPadding = this.isInPresentationMode || this.removePageBorders ? 0 : _ui_utils.VERTICAL_PADDING;
+      if (this.scrollMode === ScrollMode.HORIZONTAL) {
+        const temp = hPadding;
+        hPadding = vPadding;
+        vPadding = temp;
+      }
+      let pageWidthScale = (this.container.clientWidth - hPadding) / currentPage.width * currentPage.scale;
+      let pageHeightScale = (this.container.clientHeight - vPadding) / currentPage.height * currentPage.scale;
+      switch (value) {
+        case 'page-actual':
+          scale = 1;
+          break;
+        case 'page-width':
+          scale = pageWidthScale;
+          break;
+        case 'page-height':
+          scale = pageHeightScale;
+          break;
+        case 'page-fit':
+          scale = Math.min(pageWidthScale, pageHeightScale);
+          break;
+        case 'auto':
+          let horizontalScale = (0, _ui_utils.isPortraitOrientation)(currentPage) ? pageWidthScale : Math.min(pageHeightScale, pageWidthScale);
+          scale = Math.min(_ui_utils.MAX_AUTO_SCALE, horizontalScale);
+          break;
+        default:
+          console.error(`${this._name}._setScale: "${value}" is an unknown zoom value.`);
+          return;
+      }
+      this._setScaleUpdatePages(scale, value, noScroll, true);
+    }
+  }
+  _resetCurrentPageView() {
+    if (this.isInPresentationMode) {
+      this._setScale(this._currentScaleValue, true);
+    }
+    let pageView = this._pages[this._currentPageNumber - 1];
+    this._scrollIntoView({ pageDiv: pageView.div });
+  }
+  scrollPageIntoView(params) {
+    if (!this.pdfDocument) {
+      return;
+    }
+    let pageNumber = params.pageNumber || 0;
+    let dest = params.destArray || null;
+    let allowNegativeOffset = params.allowNegativeOffset || false;
+    if (this.isInPresentationMode || !dest) {
+      this._setCurrentPageNumber(pageNumber, true);
+      return;
+    }
+    let pageView = this._pages[pageNumber - 1];
+    if (!pageView) {
+      console.error(`${this._name}.scrollPageIntoView: Invalid "pageNumber" parameter.`);
+      return;
+    }
+    let x = 0,
+        y = 0;
+    let width = 0,
+        height = 0,
+        widthScale,
+        heightScale;
+    let changeOrientation = pageView.rotation % 180 === 0 ? false : true;
+    let pageWidth = (changeOrientation ? pageView.height : pageView.width) / pageView.scale / _ui_utils.CSS_UNITS;
+    let pageHeight = (changeOrientation ? pageView.width : pageView.height) / pageView.scale / _ui_utils.CSS_UNITS;
+    let scale = 0;
+    switch (dest[1].name) {
+      case 'XYZ':
+        x = dest[2];
+        y = dest[3];
+        scale = dest[4];
+        x = x !== null ? x : 0;
+        y = y !== null ? y : pageHeight;
+        break;
+      case 'Fit':
+      case 'FitB':
+        scale = 'page-fit';
+        break;
+      case 'FitH':
+      case 'FitBH':
+        y = dest[2];
+        scale = 'page-width';
+        if (y === null && this._location) {
+          x = this._location.left;
+          y = this._location.top;
+        }
+        break;
+      case 'FitV':
+      case 'FitBV':
+        x = dest[2];
+        width = pageWidth;
+        height = pageHeight;
+        scale = 'page-height';
+        break;
+      case 'FitR':
+        x = dest[2];
+        y = dest[3];
+        width = dest[4] - x;
+        height = dest[5] - y;
+        let hPadding = this.removePageBorders ? 0 : _ui_utils.SCROLLBAR_PADDING;
+        let vPadding = this.removePageBorders ? 0 : _ui_utils.VERTICAL_PADDING;
+        widthScale = (this.container.clientWidth - hPadding) / width / _ui_utils.CSS_UNITS;
+        heightScale = (this.container.clientHeight - vPadding) / height / _ui_utils.CSS_UNITS;
+        scale = Math.min(Math.abs(widthScale), Math.abs(heightScale));
+        break;
+      default:
+        console.error(`${this._name}.scrollPageIntoView: "${dest[1].name}" ` + 'is not a valid destination type.');
+        return;
+    }
+    if (scale && scale !== this._currentScale) {
+      this.currentScaleValue = scale;
+    } else if (this._currentScale === _ui_utils.UNKNOWN_SCALE) {
+      this.currentScaleValue = _ui_utils.DEFAULT_SCALE_VALUE;
+    }
+    if (scale === 'page-fit' && !dest[4]) {
+      this._scrollIntoView({
+        pageDiv: pageView.div,
+        pageNumber
+      });
+      return;
+    }
+    let boundingRect = [pageView.viewport.convertToViewportPoint(x, y), pageView.viewport.convertToViewportPoint(x + width, y + height)];
+    let left = Math.min(boundingRect[0][0], boundingRect[1][0]);
+    let top = Math.min(boundingRect[0][1], boundingRect[1][1]);
+    if (!allowNegativeOffset) {
+      left = Math.max(left, 0);
+      top = Math.max(top, 0);
+    }
+    this._scrollIntoView({
+      pageDiv: pageView.div,
+      pageSpot: {
+        left,
+        top
+      },
+      pageNumber
+    });
+  }
+  _resizeBuffer(numVisiblePages, visiblePages) {
+    let suggestedCacheSize = Math.max(DEFAULT_CACHE_SIZE, 2 * numVisiblePages + 1);
+    this._buffer.resize(suggestedCacheSize, visiblePages);
+  }
+  _updateLocation(firstPage) {
+    let currentScale = this._currentScale;
+    let currentScaleValue = this._currentScaleValue;
+    let normalizedScaleValue = parseFloat(currentScaleValue) === currentScale ? Math.round(currentScale * 10000) / 100 : currentScaleValue;
+    let pageNumber = firstPage.id;
+    let pdfOpenParams = '#page=' + pageNumber;
+    pdfOpenParams += '&zoom=' + normalizedScaleValue;
+    let currentPageView = this._pages[pageNumber - 1];
+    let container = this.container;
+    let topLeft = currentPageView.getPagePoint(container.scrollLeft - firstPage.x, container.scrollTop - firstPage.y);
+    let intLeft = Math.round(topLeft[0]);
+    let intTop = Math.round(topLeft[1]);
+    pdfOpenParams += ',' + intLeft + ',' + intTop;
+    this._location = {
+      pageNumber,
+      scale: normalizedScaleValue,
+      top: intTop,
+      left: intLeft,
+      rotation: this._pagesRotation,
+      pdfOpenParams
+    };
+  }
+  update() {
+    throw new Error('Not implemented: update');
+  }
+  containsElement(element) {
+    return this.container.contains(element);
+  }
+  focus() {
+    this.container.focus();
+  }
+  get isInPresentationMode() {
+    return this.presentationModeState === _ui_utils.PresentationModeState.FULLSCREEN;
+  }
+  get isChangingPresentationMode() {
+    return this.presentationModeState === _ui_utils.PresentationModeState.CHANGING;
+  }
+  get isHorizontalScrollbarEnabled() {
+    return this.isInPresentationMode ? false : this.container.scrollWidth > this.container.clientWidth;
+  }
+  get isVerticalScrollbarEnabled() {
+    return this.isInPresentationMode ? false : this.container.scrollHeight > this.container.clientHeight;
+  }
+  _getVisiblePages() {
+    throw new Error('Not implemented: _getVisiblePages');
+  }
+  cleanup() {
+    for (let i = 0, ii = this._pages.length; i < ii; i++) {
+      if (this._pages[i] && this._pages[i].renderingState !== _pdf_rendering_queue.RenderingStates.FINISHED) {
+        this._pages[i].reset();
+      }
+    }
+  }
+  _cancelRendering() {
+    for (let i = 0, ii = this._pages.length; i < ii; i++) {
+      if (this._pages[i]) {
+        this._pages[i].cancelRendering();
+      }
+    }
+  }
+  _ensurePdfPageLoaded(pageView) {
+    if (pageView.pdfPage) {
+      return Promise.resolve(pageView.pdfPage);
+    }
+    let pageNumber = pageView.id;
+    if (this._pagesRequests[pageNumber]) {
+      return this._pagesRequests[pageNumber];
+    }
+    let promise = this.pdfDocument.getPage(pageNumber).then(pdfPage => {
+      if (!pageView.pdfPage) {
+        pageView.setPdfPage(pdfPage);
+      }
+      this._pagesRequests[pageNumber] = null;
+      return pdfPage;
+    }).catch(reason => {
+      console.error('Unable to get page for page view', reason);
+      this._pagesRequests[pageNumber] = null;
+    });
+    this._pagesRequests[pageNumber] = promise;
+    return promise;
+  }
+  forceRendering(currentlyVisiblePages) {
+    let visiblePages = currentlyVisiblePages || this._getVisiblePages();
+    let scrollAhead = this.scrollMode === ScrollMode.HORIZONTAL ? this.scroll.right : this.scroll.down;
+    let pageView = this.renderingQueue.getHighestPriority(visiblePages, this._pages, scrollAhead);
+    if (pageView) {
+      this._ensurePdfPageLoaded(pageView).then(() => {
+        this.renderingQueue.renderView(pageView);
+      });
+      return true;
+    }
+    return false;
+  }
+  getPageTextContent(pageIndex) {
+    return this.pdfDocument.getPage(pageIndex + 1).then(function (page) {
+      return page.getTextContent({ normalizeWhitespace: true });
+    });
+  }
+  createTextLayerBuilder(textLayerDiv, pageIndex, viewport, enhanceTextSelection = false) {
+    return new _text_layer_builder.TextLayerBuilder({
+      textLayerDiv,
+      eventBus: this.eventBus,
+      pageIndex,
+      viewport,
+      findController: this.isInPresentationMode ? null : this.findController,
+      enhanceTextSelection: this.isInPresentationMode ? false : enhanceTextSelection
+    });
+  }
+  createAnnotationLayerBuilder(pageDiv, pdfPage, imageResourcesPath = '', renderInteractiveForms = false, l10n = _ui_utils.NullL10n) {
+    return new _annotation_layer_builder.AnnotationLayerBuilder({
+      pageDiv,
+      pdfPage,
+      imageResourcesPath,
+      renderInteractiveForms,
+      linkService: this.linkService,
+      downloadManager: this.downloadManager,
+      l10n
+    });
+  }
+  setFindController(findController) {
+    this.findController = findController;
+  }
+  get hasEqualPageSizes() {
+    let firstPageView = this._pages[0];
+    for (let i = 1, ii = this._pages.length; i < ii; ++i) {
+      let pageView = this._pages[i];
+      if (pageView.width !== firstPageView.width || pageView.height !== firstPageView.height) {
+        return false;
+      }
+    }
+    return true;
+  }
+  getPagesOverview() {
+    let pagesOverview = this._pages.map(function (pageView) {
+      let viewport = pageView.pdfPage.getViewport(1);
+      return {
+        width: viewport.width,
+        height: viewport.height,
+        rotation: viewport.rotation
+      };
+    });
+    if (!this.enablePrintAutoRotate) {
+      return pagesOverview;
+    }
+    let isFirstPagePortrait = (0, _ui_utils.isPortraitOrientation)(pagesOverview[0]);
+    return pagesOverview.map(function (size) {
+      if (isFirstPagePortrait === (0, _ui_utils.isPortraitOrientation)(size)) {
+        return size;
+      }
+      return {
+        width: size.height,
+        height: size.width,
+        rotation: (size.rotation + 90) % 360
+      };
+    });
+  }
+  setScrollMode(mode) {
+    if (mode !== this.scrollMode) {
+      this.scrollMode = mode;
+      this._updateScrollModeClasses();
+      this.eventBus.dispatch('scrollmodechanged', { mode });
+      const pageNumber = this._currentPageNumber;
+      if (isNaN(this._currentScaleValue)) {
+        this._setScale(this._currentScaleValue, this.isInPresentationMode);
+      }
+      this.scrollPageIntoView({ pageNumber });
+      this.update();
+    }
+  }
+  _updateScrollModeClasses() {
+    const mode = this.scrollMode,
+          { classList } = this.viewer;
+    classList.toggle('scrollHorizontal', mode === ScrollMode.HORIZONTAL);
+    classList.toggle('scrollWrapped', mode === ScrollMode.WRAPPED);
+  }
+  setSpreadMode(mode) {
+    if (mode !== this.spreadMode) {
+      this.spreadMode = mode;
+      this.eventBus.dispatch('spreadmodechanged', { mode });
+      this._regroupSpreads();
+    }
+  }
+  _regroupSpreads() {}
+}
+exports.BaseViewer = BaseViewer;
+exports.ScrollMode = ScrollMode;
+exports.SpreadMode = SpreadMode;
+
+/***/ }),
+/* 11 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
 ;
 let pdfjsWebApp, pdfjsWebAppOptions;
 {
   pdfjsWebApp = __webpack_require__(4);
   pdfjsWebAppOptions = __webpack_require__(7);
 }
 {
   __webpack_require__(32);
@@ -3512,16 +4393,22 @@ function getViewerConfiguration() {
       downloadButton: document.getElementById('secondaryDownload'),
       viewBookmarkButton: document.getElementById('secondaryViewBookmark'),
       firstPageButton: document.getElementById('firstPage'),
       lastPageButton: document.getElementById('lastPage'),
       pageRotateCwButton: document.getElementById('pageRotateCw'),
       pageRotateCcwButton: document.getElementById('pageRotateCcw'),
       cursorSelectToolButton: document.getElementById('cursorSelectTool'),
       cursorHandToolButton: document.getElementById('cursorHandTool'),
+      scrollVerticalButton: document.getElementById('scrollVertical'),
+      scrollHorizontalButton: document.getElementById('scrollHorizontal'),
+      scrollWrappedButton: document.getElementById('scrollWrapped'),
+      spreadNoneButton: document.getElementById('spreadNone'),
+      spreadOddButton: document.getElementById('spreadOdd'),
+      spreadEvenButton: document.getElementById('spreadEven'),
       documentPropertiesButton: document.getElementById('documentProperties')
     },
     fullscreen: {
       contextFirstPage: document.getElementById('contextFirstPage'),
       contextLastPage: document.getElementById('contextLastPage'),
       contextPageRotateCw: document.getElementById('contextPageRotateCw'),
       contextPageRotateCcw: document.getElementById('contextPageRotateCcw')
     },
@@ -3601,17 +4488,17 @@ function webViewerLoad() {
 }
 if (document.readyState === 'interactive' || document.readyState === 'complete') {
   webViewerLoad();
 } else {
   document.addEventListener('DOMContentLoaded', webViewerLoad, true);
 }
 
 /***/ }),
-/* 11 */
+/* 12 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
@@ -3742,17 +4629,17 @@ function isLeftMouseReleased(event) {
   }
   if (isChrome15OrOpera15plus || isSafari6plus) {
     return event.which === 0;
   }
 }
 exports.GrabToPan = GrabToPan;
 
 /***/ }),
-/* 12 */
+/* 13 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
@@ -4043,17 +4930,17 @@ class PDFSidebar {
       }
     });
   }
 }
 exports.SidebarView = SidebarView;
 exports.PDFSidebar = PDFSidebar;
 
 /***/ }),
-/* 13 */
+/* 14 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
@@ -4143,17 +5030,17 @@ class OverlayManager {
     if (this._active) {
       this.close(this._active);
     }
   }
 }
 exports.OverlayManager = OverlayManager;
 
 /***/ }),
-/* 14 */
+/* 15 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
@@ -4213,17 +5100,17 @@ class PasswordPrompt {
   setUpdateCallback(updateCallback, reason) {
     this.updateCallback = updateCallback;
     this.reason = reason;
   }
 }
 exports.PasswordPrompt = PasswordPrompt;
 
 /***/ }),
-/* 15 */
+/* 16 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
@@ -4326,17 +5213,17 @@ class PDFAttachmentViewer {
         keepRenderedCapability: true
       });
     });
   }
 }
 exports.PDFAttachmentViewer = PDFAttachmentViewer;
 
 /***/ }),
-/* 16 */
+/* 17 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
@@ -4584,17 +5471,17 @@ class PDFDocumentProperties {
       date: dateString,
       time: timeString
     }, '{{date}}, {{time}}');
   }
 }
 exports.PDFDocumentProperties = PDFDocumentProperties;
 
 /***/ }),
-/* 17 */
+/* 18 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
@@ -4752,17 +5639,17 @@ class PDFFindBar {
     if (findbarHeight > inputContainerHeight) {
       this.bar.classList.add('wrapContainers');
     }
   }
 }
 exports.PDFFindBar = PDFFindBar;
 
 /***/ }),
-/* 18 */
+/* 19 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
@@ -5129,17 +6016,17 @@ function isDestArraysEqual(firstDest, se
   }
   return true;
 }
 exports.PDFHistory = PDFHistory;
 exports.isDestHashesEqual = isDestHashesEqual;
 exports.isDestArraysEqual = isDestArraysEqual;
 
 /***/ }),
-/* 19 */
+/* 20 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
@@ -5270,17 +6157,17 @@ class PDFOutlineViewer {
     }
     this.container.appendChild(fragment);
     this._dispatchEvent(outlineCount);
   }
 }
 exports.PDFOutlineViewer = PDFOutlineViewer;
 
 /***/ }),
-/* 20 */
+/* 21 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
@@ -5587,17 +6474,17 @@ class PDFPresentationMode {
     window.removeEventListener('fullscreenchange', this.fullscreenChangeBind);
     window.removeEventListener('mozfullscreenchange', this.fullscreenChangeBind);
     delete this.fullscreenChangeBind;
   }
 }
 exports.PDFPresentationMode = PDFPresentationMode;
 
 /***/ }),
-/* 21 */
+/* 22 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
@@ -5707,30 +6594,30 @@ class PDFSidebarResizer {
         }
       }
     });
   }
 }
 exports.PDFSidebarResizer = PDFSidebarResizer;
 
 /***/ }),
-/* 22 */
+/* 23 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
 exports.PDFThumbnailViewer = undefined;
 
 var _ui_utils = __webpack_require__(0);
 
-var _pdf_thumbnail_view = __webpack_require__(23);
+var _pdf_thumbnail_view = __webpack_require__(24);
 
 const THUMBNAIL_SCROLL_MARGIN = -19;
 const THUMBNAIL_SELECTED_CLASS = 'selected';
 class PDFThumbnailViewer {
   constructor({ container, linkService, renderingQueue, l10n = _ui_utils.NullL10n }) {
     this.container = container;
     this.linkService = linkService;
     this.renderingQueue = renderingQueue;
@@ -5896,17 +6783,17 @@ class PDFThumbnailViewer {
       return true;
     }
     return false;
   }
 }
 exports.PDFThumbnailViewer = PDFThumbnailViewer;
 
 /***/ }),
-/* 23 */
+/* 24 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
@@ -6204,43 +7091,54 @@ class PDFThumbnailView {
   }
   static cleanup() {
     TempImageFactory.destroyCanvas();
   }
 }
 exports.PDFThumbnailView = PDFThumbnailView;
 
 /***/ }),
-/* 24 */
+/* 25 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
 exports.PDFViewer = undefined;
 
+var _base_viewer = __webpack_require__(10);
+
 var _ui_utils = __webpack_require__(0);
 
-var _base_viewer = __webpack_require__(25);
-
 var _pdfjsLib = __webpack_require__(1);
 
 class PDFViewer extends _base_viewer.BaseViewer {
   get _setDocumentViewerElement() {
     return (0, _pdfjsLib.shadow)(this, '_setDocumentViewerElement', this.viewer);
   }
   _scrollIntoView({ pageDiv, pageSpot = null }) {
+    if (!pageSpot) {
+      const left = pageDiv.offsetLeft + pageDiv.clientLeft;
+      const right = left + pageDiv.clientWidth;
+      const { scrollLeft, clientWidth } = this.container;
+      if (this.scrollMode === _base_viewer.ScrollMode.HORIZONTAL || left < scrollLeft || right > scrollLeft + clientWidth) {
+        pageSpot = {
+          left: 0,
+          top: 0
+        };
+      }
+    }
     (0, _ui_utils.scrollIntoView)(pageDiv, pageSpot);
   }
   _getVisiblePages() {
     if (!this.isInPresentationMode) {
-      return (0, _ui_utils.getVisibleElements)(this.container, this._pages, true);
+      return (0, _ui_utils.getVisibleElements)(this.container, this._pages, true, this.scrollMode === _base_viewer.ScrollMode.HORIZONTAL);
     }
     let currentPage = this._pages[this._currentPageNumber - 1];
     let visible = [{
       id: currentPage.id,
       view: currentPage
     }];
     return {
       first: currentPage,
@@ -6250,17 +7148,17 @@ class PDFViewer extends _base_viewer.Bas
   }
   update() {
     let visible = this._getVisiblePages();
     let visiblePages = visible.views,
         numVisiblePages = visiblePages.length;
     if (numVisiblePages === 0) {
       return;
     }
-    this._resizeBuffer(numVisiblePages);
+    this._resizeBuffer(numVisiblePages, visiblePages);
     this.renderingQueue.renderHighestPriority(visible);
     let currentId = this._currentPageNumber;
     let stillFullyVisible = false;
     for (let i = 0; i < numVisiblePages; ++i) {
       let page = visiblePages[i];
       if (page.percent < 100) {
         break;
       }
@@ -6276,703 +7174,48 @@ class PDFViewer extends _base_viewer.Bas
       this._setCurrentPageNumber(currentId);
     }
     this._updateLocation(visible.first);
     this.eventBus.dispatch('updateviewarea', {
       source: this,
       location: this._location
     });
   }
+  _regroupSpreads() {
+    const container = this._setDocumentViewerElement,
+          pages = this._pages;
+    while (container.firstChild) {
+      container.firstChild.remove();
+    }
+    if (this.spreadMode === _base_viewer.SpreadMode.NONE) {
+      for (let i = 0, iMax = pages.length; i < iMax; ++i) {
+        container.appendChild(pages[i].div);
+      }
+    } else {
+      const parity = this.spreadMode - 1;
+      let spread = null;
+      for (let i = 0, iMax = pages.length; i < iMax; ++i) {
+        if (spread === null) {
+          spread = document.createElement('div');
+          spread.className = 'spread';
+          container.appendChild(spread);
+        } else if (i % 2 === parity) {
+          spread = spread.cloneNode(false);
+          container.appendChild(spread);
+        }
+        spread.appendChild(pages[i].div);
+      }
+    }
+    this.scrollPageIntoView({ pageNumber: this._currentPageNumber });
+    this.update();
+  }
 }
 exports.PDFViewer = PDFViewer;
 
 /***/ }),
-/* 25 */
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-Object.defineProperty(exports, "__esModule", {
-  value: true
-});
-exports.BaseViewer = undefined;
-
-var _ui_utils = __webpack_require__(0);
-
-var _pdf_rendering_queue = __webpack_require__(3);
-
-var _annotation_layer_builder = __webpack_require__(26);
-
-var _pdfjsLib = __webpack_require__(1);
-
-var _dom_events = __webpack_require__(2);
-
-var _pdf_page_view = __webpack_require__(27);
-
-var _pdf_link_service = __webpack_require__(5);
-
-var _text_layer_builder = __webpack_require__(28);
-
-const DEFAULT_CACHE_SIZE = 10;
-function PDFPageViewBuffer(size) {
-  let data = [];
-  this.push = function (view) {
-    let i = data.indexOf(view);
-    if (i >= 0) {
-      data.splice(i, 1);
-    }
-    data.push(view);
-    if (data.length > size) {
-      data.shift().destroy();
-    }
-  };
-  this.resize = function (newSize) {
-    size = newSize;
-    while (data.length > size) {
-      data.shift().destroy();
-    }
-  };
-}
-function isSameScale(oldScale, newScale) {
-  if (newScale === oldScale) {
-    return true;
-  }
-  if (Math.abs(newScale - oldScale) < 1e-15) {
-    return true;
-  }
-  return false;
-}
-class BaseViewer {
-  constructor(options) {
-    if (this.constructor === BaseViewer) {
-      throw new Error('Cannot initialize BaseViewer.');
-    }
-    this._name = this.constructor.name;
-    this.container = options.container;
-    this.viewer = options.viewer || options.container.firstElementChild;
-    this.eventBus = options.eventBus || (0, _dom_events.getGlobalEventBus)();
-    this.linkService = options.linkService || new _pdf_link_service.SimpleLinkService();
-    this.downloadManager = options.downloadManager || null;
-    this.removePageBorders = options.removePageBorders || false;
-    this.textLayerMode = Number.isInteger(options.textLayerMode) ? options.textLayerMode : _ui_utils.TextLayerMode.ENABLE;
-    this.enhanceTextSelection = options.enhanceTextSelection || false;
-    this.imageResourcesPath = options.imageResourcesPath || '';
-    this.renderInteractiveForms = options.renderInteractiveForms || false;
-    this.enablePrintAutoRotate = options.enablePrintAutoRotate || false;
-    this.renderer = options.renderer || _ui_utils.RendererType.CANVAS;
-    this.enableWebGL = options.enableWebGL || false;
-    this.useOnlyCssZoom = options.useOnlyCssZoom || false;
-    this.maxCanvasPixels = options.maxCanvasPixels;
-    this.l10n = options.l10n || _ui_utils.NullL10n;
-    this.defaultRenderingQueue = !options.renderingQueue;
-    if (this.defaultRenderingQueue) {
-      this.renderingQueue = new _pdf_rendering_queue.PDFRenderingQueue();
-      this.renderingQueue.setViewer(this);
-    } else {
-      this.renderingQueue = options.renderingQueue;
-    }
-    this.scroll = (0, _ui_utils.watchScroll)(this.container, this._scrollUpdate.bind(this));
-    this.presentationModeState = _ui_utils.PresentationModeState.UNKNOWN;
-    this._resetView();
-    if (this.removePageBorders) {
-      this.viewer.classList.add('removePageBorders');
-    }
-  }
-  get pagesCount() {
-    return this._pages.length;
-  }
-  getPageView(index) {
-    return this._pages[index];
-  }
-  get pageViewsReady() {
-    return this._pageViewsReady;
-  }
-  get currentPageNumber() {
-    return this._currentPageNumber;
-  }
-  set currentPageNumber(val) {
-    if (!Number.isInteger(val)) {
-      throw new Error('Invalid page number.');
-    }
-    if (!this.pdfDocument) {
-      return;
-    }
-    this._setCurrentPageNumber(val, true);
-  }
-  _setCurrentPageNumber(val, resetCurrentPageView = false) {
-    if (this._currentPageNumber === val) {
-      if (resetCurrentPageView) {
-        this._resetCurrentPageView();
-      }
-      return;
-    }
-    if (!(0 < val && val <= this.pagesCount)) {
-      console.error(`${this._name}._setCurrentPageNumber: "${val}" is out of bounds.`);
-      return;
-    }
-    let arg = {
-      source: this,
-      pageNumber: val,
-      pageLabel: this._pageLabels && this._pageLabels[val - 1]
-    };
-    this._currentPageNumber = val;
-    this.eventBus.dispatch('pagechanging', arg);
-    this.eventBus.dispatch('pagechange', arg);
-    if (resetCurrentPageView) {
-      this._resetCurrentPageView();
-    }
-  }
-  get currentPageLabel() {
-    return this._pageLabels && this._pageLabels[this._currentPageNumber - 1];
-  }
-  set currentPageLabel(val) {
-    let pageNumber = val | 0;
-    if (this._pageLabels) {
-      let i = this._pageLabels.indexOf(val);
-      if (i >= 0) {
-        pageNumber = i + 1;
-      }
-    }
-    this.currentPageNumber = pageNumber;
-  }
-  get currentScale() {
-    return this._currentScale !== _ui_utils.UNKNOWN_SCALE ? this._currentScale : _ui_utils.DEFAULT_SCALE;
-  }
-  set currentScale(val) {
-    if (isNaN(val)) {
-      throw new Error('Invalid numeric scale');
-    }
-    if (!this.pdfDocument) {
-      return;
-    }
-    this._setScale(val, false);
-  }
-  get currentScaleValue() {
-    return this._currentScaleValue;
-  }
-  set currentScaleValue(val) {
-    if (!this.pdfDocument) {
-      return;
-    }
-    this._setScale(val, false);
-  }
-  get pagesRotation() {
-    return this._pagesRotation;
-  }
-  set pagesRotation(rotation) {
-    if (!(0, _ui_utils.isValidRotation)(rotation)) {
-      throw new Error('Invalid pages rotation angle.');
-    }
-    if (!this.pdfDocument) {
-      return;
-    }
-    if (this._pagesRotation === rotation) {
-      return;
-    }
-    this._pagesRotation = rotation;
-    let pageNumber = this._currentPageNumber;
-    for (let i = 0, ii = this._pages.length; i < ii; i++) {
-      let pageView = this._pages[i];
-      pageView.update(pageView.scale, rotation);
-    }
-    if (this._currentScaleValue) {
-      this._setScale(this._currentScaleValue, true);
-    }
-    this.eventBus.dispatch('rotationchanging', {
-      source: this,
-      pagesRotation: rotation,
-      pageNumber
-    });
-    if (this.defaultRenderingQueue) {
-      this.update();
-    }
-  }
-  get _setDocumentViewerElement() {
-    throw new Error('Not implemented: _setDocumentViewerElement');
-  }
-  setDocument(pdfDocument) {
-    if (this.pdfDocument) {
-      this._cancelRendering();
-      this._resetView();
-    }
-    this.pdfDocument = pdfDocument;
-    if (!pdfDocument) {
-      return;
-    }
-    let pagesCount = pdfDocument.numPages;
-    let pagesCapability = (0, _pdfjsLib.createPromiseCapability)();
-    this.pagesPromise = pagesCapability.promise;
-    pagesCapability.promise.then(() => {
-      this._pageViewsReady = true;
-      this.eventBus.dispatch('pagesloaded', {
-        source: this,
-        pagesCount
-      });
-    });
-    let isOnePageRenderedResolved = false;
-    let onePageRenderedCapability = (0, _pdfjsLib.createPromiseCapability)();
-    this.onePageRendered = onePageRenderedCapability.promise;
-    let bindOnAfterAndBeforeDraw = pageView => {
-      pageView.onBeforeDraw = () => {
-        this._buffer.push(pageView);
-      };
-      pageView.onAfterDraw = () => {
-        if (!isOnePageRenderedResolved) {
-          isOnePageRenderedResolved = true;
-          onePageRenderedCapability.resolve();
-        }
-      };
-    };
-    let firstPagePromise = pdfDocument.getPage(1);
-    this.firstPagePromise = firstPagePromise;
-    firstPagePromise.then(pdfPage => {
-      let scale = this.currentScale;
-      let viewport = pdfPage.getViewport(scale * _ui_utils.CSS_UNITS);
-      for (let pageNum = 1; pageNum <= pagesCount; ++pageNum) {
-        let textLayerFactory = null;
-        if (this.textLayerMode !== _ui_utils.TextLayerMode.DISABLE) {
-          textLayerFactory = this;
-        }
-        let pageView = new _pdf_page_view.PDFPageView({
-          container: this._setDocumentViewerElement,
-          eventBus: this.eventBus,
-          id: pageNum,
-          scale,
-          defaultViewport: viewport.clone(),
-          renderingQueue: this.renderingQueue,
-          textLayerFactory,
-          textLayerMode: this.textLayerMode,
-          annotationLayerFactory: this,
-          imageResourcesPath: this.imageResourcesPath,
-          renderInteractiveForms: this.renderInteractiveForms,
-          renderer: this.renderer,
-          enableWebGL: this.enableWebGL,
-          useOnlyCssZoom: this.useOnlyCssZoom,
-          maxCanvasPixels: this.maxCanvasPixels,
-          l10n: this.l10n
-        });
-        bindOnAfterAndBeforeDraw(pageView);
-        this._pages.push(pageView);
-      }
-      onePageRenderedCapability.promise.then(() => {
-        if (pdfDocument.loadingParams['disableAutoFetch']) {
-          pagesCapability.resolve();
-          return;
-        }
-        let getPagesLeft = pagesCount;
-        for (let pageNum = 1; pageNum <= pagesCount; ++pageNum) {
-          pdfDocument.getPage(pageNum).then(pdfPage => {
-            let pageView = this._pages[pageNum - 1];
-            if (!pageView.pdfPage) {
-              pageView.setPdfPage(pdfPage);
-            }
-            this.linkService.cachePageRef(pageNum, pdfPage.ref);
-            if (--getPagesLeft === 0) {
-              pagesCapability.resolve();
-            }
-          }, reason => {
-            console.error(`Unable to get page ${pageNum} to initialize viewer`, reason);
-            if (--getPagesLeft === 0) {
-              pagesCapability.resolve();
-            }
-          });
-        }
-      });
-      this.eventBus.dispatch('pagesinit', { source: this });
-      if (this.defaultRenderingQueue) {
-        this.update();
-      }
-      if (this.findController) {
-        this.findController.resolveFirstPage();
-      }
-    }).catch(reason => {
-      console.error('Unable to initialize viewer', reason);
-    });
-  }
-  setPageLabels(labels) {
-    if (!this.pdfDocument) {
-      return;
-    }
-    if (!labels) {
-      this._pageLabels = null;
-    } else if (!(labels instanceof Array && this.pdfDocument.numPages === labels.length)) {
-      this._pageLabels = null;
-      console.error(`${this._name}.setPageLabels: Invalid page labels.`);
-    } else {
-      this._pageLabels = labels;
-    }
-    for (let i = 0, ii = this._pages.length; i < ii; i++) {
-      let pageView = this._pages[i];
-      let label = this._pageLabels && this._pageLabels[i];
-      pageView.setPageLabel(label);
-    }
-  }
-  _resetView() {
-    this._pages = [];
-    this._currentPageNumber = 1;
-    this._currentScale = _ui_utils.UNKNOWN_SCALE;
-    this._currentScaleValue = null;
-    this._pageLabels = null;
-    this._buffer = new PDFPageViewBuffer(DEFAULT_CACHE_SIZE);
-    this._location = null;
-    this._pagesRotation = 0;
-    this._pagesRequests = [];
-    this._pageViewsReady = false;
-    this.viewer.textContent = '';
-  }
-  _scrollUpdate() {
-    if (this.pagesCount === 0) {
-      return;
-    }
-    this.update();
-  }
-  _scrollIntoView({ pageDiv, pageSpot = null, pageNumber = null }) {
-    throw new Error('Not implemented: _scrollIntoView');
-  }
-  _setScaleDispatchEvent(newScale, newValue, preset = false) {
-    let arg = {
-      source: this,
-      scale: newScale,
-      presetValue: preset ? newValue : undefined
-    };
-    this.eventBus.dispatch('scalechanging', arg);
-    this.eventBus.dispatch('scalechange', arg);
-  }
-  _setScaleUpdatePages(newScale, newValue, noScroll = false, preset = false) {
-    this._currentScaleValue = newValue.toString();
-    if (isSameScale(this._currentScale, newScale)) {
-      if (preset) {
-        this._setScaleDispatchEvent(newScale, newValue, true);
-      }
-      return;
-    }
-    for (let i = 0, ii = this._pages.length; i < ii; i++) {
-      this._pages[i].update(newScale);
-    }
-    this._currentScale = newScale;
-    if (!noScroll) {
-      let page = this._currentPageNumber,
-          dest;
-      if (this._location && !(this.isInPresentationMode || this.isChangingPresentationMode)) {
-        page = this._location.pageNumber;
-        dest = [null, { name: 'XYZ' }, this._location.left, this._location.top, null];
-      }
-      this.scrollPageIntoView({
-        pageNumber: page,
-        destArray: dest,
-        allowNegativeOffset: true
-      });
-    }
-    this._setScaleDispatchEvent(newScale, newValue, preset);
-    if (this.defaultRenderingQueue) {
-      this.update();
-    }
-  }
-  _setScale(value, noScroll = false) {
-    let scale = parseFloat(value);
-    if (scale > 0) {
-      this._setScaleUpdatePages(scale, value, noScroll, false);
-    } else {
-      let currentPage = this._pages[this._currentPageNumber - 1];
-      if (!currentPage) {
-        return;
-      }
-      let hPadding = this.isInPresentationMode || this.removePageBorders ? 0 : _ui_utils.SCROLLBAR_PADDING;
-      let vPadding = this.isInPresentationMode || this.removePageBorders ? 0 : _ui_utils.VERTICAL_PADDING;
-      let pageWidthScale = (this.container.clientWidth - hPadding) / currentPage.width * currentPage.scale;
-      let pageHeightScale = (this.container.clientHeight - vPadding) / currentPage.height * currentPage.scale;
-      switch (value) {
-        case 'page-actual':
-          scale = 1;
-          break;
-        case 'page-width':
-          scale = pageWidthScale;
-          break;
-        case 'page-height':
-          scale = pageHeightScale;
-          break;
-        case 'page-fit':
-          scale = Math.min(pageWidthScale, pageHeightScale);
-          break;
-        case 'auto':
-          let horizontalScale = (0, _ui_utils.isPortraitOrientation)(currentPage) ? pageWidthScale : Math.min(pageHeightScale, pageWidthScale);
-          scale = Math.min(_ui_utils.MAX_AUTO_SCALE, horizontalScale);
-          break;
-        default:
-          console.error(`${this._name}._setScale: "${value}" is an unknown zoom value.`);
-          return;
-      }
-      this._setScaleUpdatePages(scale, value, noScroll, true);
-    }
-  }
-  _resetCurrentPageView() {
-    if (this.isInPresentationMode) {
-      this._setScale(this._currentScaleValue, true);
-    }
-    let pageView = this._pages[this._currentPageNumber - 1];
-    this._scrollIntoView({ pageDiv: pageView.div });
-  }
-  scrollPageIntoView(params) {
-    if (!this.pdfDocument) {
-      return;
-    }
-    let pageNumber = params.pageNumber || 0;
-    let dest = params.destArray || null;
-    let allowNegativeOffset = params.allowNegativeOffset || false;
-    if (this.isInPresentationMode || !dest) {
-      this._setCurrentPageNumber(pageNumber, true);
-      return;
-    }
-    let pageView = this._pages[pageNumber - 1];
-    if (!pageView) {
-      console.error(`${this._name}.scrollPageIntoView: Invalid "pageNumber" parameter.`);
-      return;
-    }
-    let x = 0,
-        y = 0;
-    let width = 0,
-        height = 0,
-        widthScale,
-        heightScale;
-    let changeOrientation = pageView.rotation % 180 === 0 ? false : true;
-    let pageWidth = (changeOrientation ? pageView.height : pageView.width) / pageView.scale / _ui_utils.CSS_UNITS;
-    let pageHeight = (changeOrientation ? pageView.width : pageView.height) / pageView.scale / _ui_utils.CSS_UNITS;
-    let scale = 0;
-    switch (dest[1].name) {
-      case 'XYZ':
-        x = dest[2];
-        y = dest[3];
-        scale = dest[4];
-        x = x !== null ? x : 0;
-        y = y !== null ? y : pageHeight;
-        break;
-      case 'Fit':
-      case 'FitB':
-        scale = 'page-fit';
-        break;
-      case 'FitH':
-      case 'FitBH':
-        y = dest[2];
-        scale = 'page-width';
-        if (y === null && this._location) {
-          x = this._location.left;
-          y = this._location.top;
-        }
-        break;
-      case 'FitV':
-      case 'FitBV':
-        x = dest[2];
-        width = pageWidth;
-        height = pageHeight;
-        scale = 'page-height';
-        break;
-      case 'FitR':
-        x = dest[2];
-        y = dest[3];
-        width = dest[4] - x;
-        height = dest[5] - y;
-        let hPadding = this.removePageBorders ? 0 : _ui_utils.SCROLLBAR_PADDING;
-        let vPadding = this.removePageBorders ? 0 : _ui_utils.VERTICAL_PADDING;
-        widthScale = (this.container.clientWidth - hPadding) / width / _ui_utils.CSS_UNITS;
-        heightScale = (this.container.clientHeight - vPadding) / height / _ui_utils.CSS_UNITS;
-        scale = Math.min(Math.abs(widthScale), Math.abs(heightScale));
-        break;
-      default:
-        console.error(`${this._name}.scrollPageIntoView: "${dest[1].name}" ` + 'is not a valid destination type.');
-        return;
-    }
-    if (scale && scale !== this._currentScale) {
-      this.currentScaleValue = scale;
-    } else if (this._currentScale === _ui_utils.UNKNOWN_SCALE) {
-      this.currentScaleValue = _ui_utils.DEFAULT_SCALE_VALUE;
-    }
-    if (scale === 'page-fit' && !dest[4]) {
-      this._scrollIntoView({
-        pageDiv: pageView.div,
-        pageNumber
-      });
-      return;
-    }
-    let boundingRect = [pageView.viewport.convertToViewportPoint(x, y), pageView.viewport.convertToViewportPoint(x + width, y + height)];
-    let left = Math.min(boundingRect[0][0], boundingRect[1][0]);
-    let top = Math.min(boundingRect[0][1], boundingRect[1][1]);
-    if (!allowNegativeOffset) {
-      left = Math.max(left, 0);
-      top = Math.max(top, 0);
-    }
-    this._scrollIntoView({
-      pageDiv: pageView.div,
-      pageSpot: {
-        left,
-        top
-      },
-      pageNumber
-    });
-  }
-  _resizeBuffer(numVisiblePages) {
-    let suggestedCacheSize = Math.max(DEFAULT_CACHE_SIZE, 2 * numVisiblePages + 1);
-    this._buffer.resize(suggestedCacheSize);
-  }
-  _updateLocation(firstPage) {
-    let currentScale = this._currentScale;
-    let currentScaleValue = this._currentScaleValue;
-    let normalizedScaleValue = parseFloat(currentScaleValue) === currentScale ? Math.round(currentScale * 10000) / 100 : currentScaleValue;
-    let pageNumber = firstPage.id;
-    let pdfOpenParams = '#page=' + pageNumber;
-    pdfOpenParams += '&zoom=' + normalizedScaleValue;
-    let currentPageView = this._pages[pageNumber - 1];
-    let container = this.container;
-    let topLeft = currentPageView.getPagePoint(container.scrollLeft - firstPage.x, container.scrollTop - firstPage.y);
-    let intLeft = Math.round(topLeft[0]);
-    let intTop = Math.round(topLeft[1]);
-    pdfOpenParams += ',' + intLeft + ',' + intTop;
-    this._location = {
-      pageNumber,
-      scale: normalizedScaleValue,
-      top: intTop,
-      left: intLeft,
-      rotation: this._pagesRotation,
-      pdfOpenParams
-    };
-  }
-  update() {
-    throw new Error('Not implemented: update');
-  }
-  containsElement(element) {
-    return this.container.contains(element);
-  }
-  focus() {
-    this.container.focus();
-  }
-  get isInPresentationMode() {
-    return this.presentationModeState === _ui_utils.PresentationModeState.FULLSCREEN;
-  }
-  get isChangingPresentationMode() {
-    return this.presentationModeState === _ui_utils.PresentationModeState.CHANGING;
-  }
-  get isHorizontalScrollbarEnabled() {
-    return this.isInPresentationMode ? false : this.container.scrollWidth > this.container.clientWidth;
-  }
-  _getVisiblePages() {
-    throw new Error('Not implemented: _getVisiblePages');
-  }
-  cleanup() {
-    for (let i = 0, ii = this._pages.length; i < ii; i++) {
-      if (this._pages[i] && this._pages[i].renderingState !== _pdf_rendering_queue.RenderingStates.FINISHED) {
-        this._pages[i].reset();
-      }
-    }
-  }
-  _cancelRendering() {
-    for (let i = 0, ii = this._pages.length; i < ii; i++) {
-      if (this._pages[i]) {
-        this._pages[i].cancelRendering();
-      }
-    }
-  }
-  _ensurePdfPageLoaded(pageView) {
-    if (pageView.pdfPage) {
-      return Promise.resolve(pageView.pdfPage);
-    }
-    let pageNumber = pageView.id;
-    if (this._pagesRequests[pageNumber]) {
-      return this._pagesRequests[pageNumber];
-    }
-    let promise = this.pdfDocument.getPage(pageNumber).then(pdfPage => {
-      if (!pageView.pdfPage) {
-        pageView.setPdfPage(pdfPage);
-      }
-      this._pagesRequests[pageNumber] = null;
-      return pdfPage;
-    }).catch(reason => {
-      console.error('Unable to get page for page view', reason);
-      this._pagesRequests[pageNumber] = null;
-    });
-    this._pagesRequests[pageNumber] = promise;
-    return promise;
-  }
-  forceRendering(currentlyVisiblePages) {
-    let visiblePages = currentlyVisiblePages || this._getVisiblePages();
-    let pageView = this.renderingQueue.getHighestPriority(visiblePages, this._pages, this.scroll.down);
-    if (pageView) {
-      this._ensurePdfPageLoaded(pageView).then(() => {
-        this.renderingQueue.renderView(pageView);
-      });
-      return true;
-    }
-    return false;
-  }
-  getPageTextContent(pageIndex) {
-    return this.pdfDocument.getPage(pageIndex + 1).then(function (page) {
-      return page.getTextContent({ normalizeWhitespace: true });
-    });
-  }
-  createTextLayerBuilder(textLayerDiv, pageIndex, viewport, enhanceTextSelection = false) {
-    return new _text_layer_builder.TextLayerBuilder({
-      textLayerDiv,
-      eventBus: this.eventBus,
-      pageIndex,
-      viewport,
-      findController: this.isInPresentationMode ? null : this.findController,
-      enhanceTextSelection: this.isInPresentationMode ? false : enhanceTextSelection
-    });
-  }
-  createAnnotationLayerBuilder(pageDiv, pdfPage, imageResourcesPath = '', renderInteractiveForms = false, l10n = _ui_utils.NullL10n) {
-    return new _annotation_layer_builder.AnnotationLayerBuilder({
-      pageDiv,
-      pdfPage,
-      imageResourcesPath,
-      renderInteractiveForms,
-      linkService: this.linkService,
-      downloadManager: this.downloadManager,
-      l10n
-    });
-  }
-  setFindController(findController) {
-    this.findController = findController;
-  }
-  get hasEqualPageSizes() {
-    let firstPageView = this._pages[0];
-    for (let i = 1, ii = this._pages.length; i < ii; ++i) {
-      let pageView = this._pages[i];
-      if (pageView.width !== firstPageView.width || pageView.height !== firstPageView.height) {
-        return false;
-      }
-    }
-    return true;
-  }
-  getPagesOverview() {
-    let pagesOverview = this._pages.map(function (pageView) {
-      let viewport = pageView.pdfPage.getViewport(1);
-      return {
-        width: viewport.width,
-        height: viewport.height,
-        rotation: viewport.rotation
-      };
-    });
-    if (!this.enablePrintAutoRotate) {
-      return pagesOverview;
-    }
-    let isFirstPagePortrait = (0, _ui_utils.isPortraitOrientation)(pagesOverview[0]);
-    return pagesOverview.map(function (size) {
-      if (isFirstPagePortrait === (0, _ui_utils.isPortraitOrientation)(size)) {
-        return size;
-      }
-      return {
-        width: size.height,
-        height: size.width,
-        rotation: (size.rotation + 90) % 360
-      };
-    });
-  }
-}
-exports.BaseViewer = BaseViewer;
-
-/***/ }),
 /* 26 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
@@ -7785,16 +8028,18 @@ exports.DefaultTextLayerFactory = Defaul
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
 exports.SecondaryToolbar = undefined;
 
+var _base_viewer = __webpack_require__(10);
+
 var _pdf_cursor_tools = __webpack_require__(6);
 
 var _ui_utils = __webpack_require__(0);
 
 class SecondaryToolbar {
   constructor(options, mainContainer, eventBus) {
     this.toolbar = options.toolbar;
     this.toggleButton = options.toggleButton;
@@ -7841,16 +8086,46 @@ class SecondaryToolbar {
       eventDetails: { tool: _pdf_cursor_tools.CursorTool.SELECT },
       close: true
     }, {
       element: options.cursorHandToolButton,
       eventName: 'switchcursortool',
       eventDetails: { tool: _pdf_cursor_tools.CursorTool.HAND },
       close: true
     }, {
+      element: options.scrollVerticalButton,
+      eventName: 'switchscrollmode',
+      eventDetails: { mode: _base_viewer.ScrollMode.VERTICAL },
+      close: true
+    }, {
+      element: options.scrollHorizontalButton,
+      eventName: 'switchscrollmode',
+      eventDetails: { mode: _base_viewer.ScrollMode.HORIZONTAL },
+      close: true
+    }, {
+      element: options.scrollWrappedButton,
+      eventName: 'switchscrollmode',
+      eventDetails: { mode: _base_viewer.ScrollMode.WRAPPED },
+      close: true
+    }, {
+      element: options.spreadNoneButton,
+      eventName: 'switchspreadmode',
+      eventDetails: { mode: _base_viewer.SpreadMode.NONE },
+      close: true
+    }, {
+      element: options.spreadOddButton,
+      eventName: 'switchspreadmode',
+      eventDetails: { mode: _base_viewer.SpreadMode.ODD },
+      close: true
+    }, {
+      element: options.spreadEvenButton,
+      eventName: 'switchspreadmode',
+      eventDetails: { mode: _base_viewer.SpreadMode.EVEN },
+      close: true
+    }, {
       element: options.documentPropertiesButton,
       eventName: 'documentproperties',
       close: true
     }];
     this.items = {
       firstPage: options.firstPageButton,
       lastPage: options.lastPageButton,
       pageRotateCw: options.pageRotateCwButton,
@@ -7859,16 +8134,18 @@ class SecondaryToolbar {
     this.mainContainer = mainContainer;
     this.eventBus = eventBus;
     this.opened = false;
     this.containerHeight = null;
     this.previousContainerHeight = null;
     this.reset();
     this._bindClickListeners();
     this._bindCursorToolsListener(options);
+    this._bindScrollModeListener(options);
+    this._bindSpreadModeListener(options);
     this.eventBus.on('resize', this._setMaxHeight.bind(this));
   }
   get isOpen() {
     return this.opened;
   }
   setPageNumber(pageNumber) {
     this.pageNumber = pageNumber;
     this._updateUIState();
@@ -7915,16 +8192,30 @@ class SecondaryToolbar {
           buttons.cursorSelectToolButton.classList.add('toggled');
           break;
         case _pdf_cursor_tools.CursorTool.HAND:
           buttons.cursorHandToolButton.classList.add('toggled');
           break;
       }
     });
   }
+  _bindScrollModeListener(buttons) {
+    this.eventBus.on('scrollmodechanged', function (evt) {
+      buttons.scrollVerticalButton.classList.toggle('toggled', evt.mode === _base_viewer.ScrollMode.VERTICAL);
+      buttons.scrollHorizontalButton.classList.toggle('toggled', evt.mode === _base_viewer.ScrollMode.HORIZONTAL);
+      buttons.scrollWrappedButton.classList.toggle('toggled', evt.mode === _base_viewer.ScrollMode.WRAPPED);
+    });
+  }
+  _bindSpreadModeListener(buttons) {
+    this.eventBus.on('spreadmodechanged', function (evt) {
+      buttons.spreadNoneButton.classList.toggle('toggled', evt.mode === _base_viewer.SpreadMode.NONE);
+      buttons.spreadOddButton.classList.toggle('toggled', evt.mode === _base_viewer.SpreadMode.ODD);
+      buttons.spreadEvenButton.classList.toggle('toggled', evt.mode === _base_viewer.SpreadMode.EVEN);
+    });
+  }
   open() {
     if (this.opened) {
       return;
     }
     this.opened = true;
     this._setMaxHeight();
     this.toggleButton.classList.add('toggled');
     this.toolbar.classList.remove('hidden');
@@ -8601,17 +8892,19 @@ function getDefaultPreferences() {
       "disableFontFace": false,
       "textLayerMode": 1,
       "useOnlyCssZoom": false,
       "externalLinkTarget": 0,
       "renderer": "canvas",
       "renderInteractiveForms": false,
       "enablePrintAutoRotate": false,
       "disablePageMode": false,
-      "disablePageLabels": false
+      "disablePageLabels": false,
+      "scrollModeOnLoad": 0,
+      "spreadModeOnLoad": 0
     });
   }
   return defaultPreferences;
 }
 class BasePreferences {
   constructor() {
     if (this.constructor === BasePreferences) {
       throw new Error('Cannot initialize BasePreferences.');
--- a/browser/locales/en-US/pdfviewer/viewer.properties
+++ b/browser/locales/en-US/pdfviewer/viewer.properties
@@ -60,16 +60,30 @@ page_rotate_ccw.title=Rotate Countercloc
 page_rotate_ccw.label=Rotate Counterclockwise
 page_rotate_ccw_label=Rotate Counterclockwise
 
 cursor_text_select_tool.title=Enable Text Selection Tool
 cursor_text_select_tool_label=Text Selection Tool
 cursor_hand_tool.title=Enable Hand Tool
 cursor_hand_tool_label=Hand Tool
 
+scroll_vertical.title=Use Vertical Scrolling
+scroll_vertical_label=Vertical Scrolling
+scroll_horizontal.title=Use Horizontal Scrolling
+scroll_horizontal_label=Horizontal Scrolling
+scroll_wrapped.title=Use Wrapped Scrolling
+scroll_wrapped_label=Wrapped Scrolling
+
+spread_none.title=Do not join page spreads
+spread_none_label=No Spreads
+spread_odd.title=Join page spreads starting with odd-numbered pages
+spread_odd_label=Odd Spreads
+spread_even.title=Join page spreads starting with even-numbered pages
+spread_even_label=Even Spreads
+
 # Document properties dialog box
 document_properties.title=Document Properties…
 document_properties_label=Document Properties…
 document_properties_file_name=File name:
 document_properties_file_size=File size:
 # LOCALIZATION NOTE (document_properties_kb): "{{size_kb}}" and "{{size_b}}"
 # will be replaced by the PDF file size in kilobytes, respectively in bytes.
 document_properties_kb={{size_kb}} KB ({{size_b}} bytes)
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -340,25 +340,16 @@ UpdateANGLEConfig()
   }
 }
 
 void
 gfxWindowsPlatform::InitAcceleration()
 {
   gfxPlatform::InitAcceleration();
 
-  // Set up the D3D11 feature levels we can ask for.
-  if (IsWin8OrLater()) {
-    mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_11_1);
-  }
-  mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_11_0);
-  mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_10_1);
-  mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_10_0);
-  mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_9_3);
-
   DeviceManagerDx::Init();
 
   InitializeConfig();
   InitializeDevices();
   UpdateANGLEConfig();
   UpdateRenderMode();
 
   // If we have Skia and we didn't init dwrite already, do it now.
--- a/gfx/thebes/gfxWindowsPlatform.h
+++ b/gfx/thebes/gfxWindowsPlatform.h
@@ -260,13 +260,11 @@ private:
     void InitializeD2DConfig();
     void InitializeDirectDrawConfig();
     void InitializeAdvancedLayersConfig();
 
     RefPtr<IDWriteRenderingParams> mRenderingParams[TEXT_RENDERING_COUNT];
     DWRITE_MEASURING_MODE mMeasuringMode;
 
     RefPtr<mozilla::layers::ReadbackManagerD3D11> mD3D11ReadbackManager;
-
-    nsTArray<D3D_FEATURE_LEVEL> mFeatureLevels;
 };
 
 #endif /* GFX_WINDOWS_PLATFORM_H */
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -1957,19 +1957,16 @@ class MOZ_STACK_CLASS TryEmitter
 //
 class MOZ_STACK_CLASS IfThenElseEmitter
 {
     BytecodeEmitter* bce_;
 
     JumpList jumpAroundThen_;
     JumpList jumpsAroundElse_;
 
-    // The source note index for SRC_IF, SRC_IF_ELSE, or SRC_COND.
-    unsigned noteIndex_;
-
     // The stack depth before emitting the then block.
     // Used for restoring stack depth before emitting the else block.
     // Also used for assertion to make sure then and else blocks pushed the
     // same number of values.
     int32_t thenDepth_;
 
 #ifdef DEBUG
     // The number of values pushed in the then and else blocks.
@@ -2009,17 +2006,16 @@ class MOZ_STACK_CLASS IfThenElseEmitter
         // After calling emitEnd.
         End
     };
     State state_;
 
   public:
     explicit IfThenElseEmitter(BytecodeEmitter* bce)
       : bce_(bce),
-        noteIndex_(-1),
         thenDepth_(0),
 #ifdef DEBUG
         pushed_(0),
         calculatedPushed_(false),
 #endif
         state_(Start)
     {}
 
@@ -2032,17 +2028,17 @@ class MOZ_STACK_CLASS IfThenElseEmitter
         MOZ_ASSERT(nextState == If || nextState == IfElse || nextState == Cond);
 
         // Clear jumpAroundThen_ offset that points previous JSOP_IFEQ.
         if (state_ == Else)
             jumpAroundThen_ = JumpList();
 
         // Emit an annotated branch-if-false around the then part.
         SrcNoteType type = nextState == If ? SRC_IF : nextState == IfElse ? SRC_IF_ELSE : SRC_COND;
-        if (!bce_->newSrcNote(type, &noteIndex_))
+        if (!bce_->newSrcNote(type))
             return false;
         if (!bce_->emitJump(JSOP_IFEQ, &jumpAroundThen_))
             return false;
 
         // To restore stack depth in else part, save depth of the then part.
 #ifdef DEBUG
         // If DEBUG, this is also necessary to calculate |pushed_|.
         thenDepth_ = bce_->stackDepth;
@@ -2088,27 +2084,16 @@ class MOZ_STACK_CLASS IfThenElseEmitter
         // the offset with jumpsAroundElse value.
         if (!bce_->emitJump(JSOP_GOTO, &jumpsAroundElse_))
             return false;
 
         // Ensure the branch-if-false comes here, then emit the else.
         if (!bce_->emitJumpTargetAndPatch(jumpAroundThen_))
             return false;
 
-        // Annotate SRC_IF_ELSE or SRC_COND with the offset from branch to
-        // jump, for IonMonkey's benefit.  We can't just "back up" from the pc
-        // of the else clause, because we don't know whether an extended
-        // jump was required to leap from the end of the then clause over
-        // the else clause.
-        if (!bce_->setSrcNoteOffset(noteIndex_, 0,
-                                    jumpsAroundElse_.offset - jumpAroundThen_.offset))
-        {
-            return false;
-        }
-
         // Restore stack depth of the then part.
         bce_->stackDepth = thenDepth_;
         state_ = Else;
         return true;
     }
 
     bool emitEnd() {
         MOZ_ASSERT(state_ == If || state_ == Else);
--- a/js/src/frontend/SourceNotes.h
+++ b/js/src/frontend/SourceNotes.h
@@ -32,18 +32,18 @@ namespace js {
  * SRC_COLSPAN, SRC_SETLINE, and SRC_XDELTA) applies to a given bytecode.
  *
  * NB: the js_SrcNoteSpec array in BytecodeEmitter.cpp is indexed by this
  * enum, so its initializers need to match the order here.
  */
 #define FOR_EACH_SRC_NOTE_TYPE(M)                                                                  \
     M(SRC_NULL,         "null",        0)  /* Terminates a note vector. */                         \
     M(SRC_IF,           "if",          0)  /* JSOP_IFEQ bytecode is from an if-then. */            \
-    M(SRC_IF_ELSE,      "if-else",     1)  /* JSOP_IFEQ bytecode is from an if-then-else. */       \
-    M(SRC_COND,         "cond",        1)  /* JSOP_IFEQ is from conditional ?: operator. */        \
+    M(SRC_IF_ELSE,      "if-else",     0)  /* JSOP_IFEQ bytecode is from an if-then-else. */       \
+    M(SRC_COND,         "cond",        0)  /* JSOP_IFEQ is from conditional ?: operator. */        \
     M(SRC_FOR,          "for",         3)  /* JSOP_NOP or JSOP_POP in for(;;) loop head. */        \
     M(SRC_WHILE,        "while",       1)  /* JSOP_GOTO to for or while loop condition from before \
                                               loop, else JSOP_NOP at top of do-while loop. */      \
     M(SRC_FOR_IN,       "for-in",      1)  /* JSOP_GOTO to for-in loop condition from before       \
                                               loop. */                                             \
     M(SRC_FOR_OF,       "for-of",      1)  /* JSOP_GOTO to for-of loop condition from before       \
                                               loop. */                                             \
     M(SRC_CONTINUE,     "continue",    0)  /* JSOP_GOTO is a continue. */                          \
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/cacheir/bug1462280.js
@@ -0,0 +1,3 @@
+for (var i = 0; i < 2; i++) {
+    evaluate("var obj = {[Symbol.iterator]: Symbol.iterator};");
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/generators/bug1462353.js
@@ -0,0 +1,11 @@
+class Base {}
+class Derived extends Base {
+    constructor() {
+        var fun = async() => {
+            for (var i = 0; i < 20; i++) {} // Trigger OSR.
+            super();
+        };
+        fun();
+    }
+}
+d = new Derived();
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -4693,29 +4693,16 @@ BaselineCompiler::emit_JSOP_RESUME()
     masm.loadPtr(Address(scratch1, JSScript::offsetOfBaselineScript()), scratch1);
     masm.branchPtr(Assembler::BelowOrEqual, scratch1, ImmPtr(BASELINE_DISABLED_SCRIPT), &interpret);
 
 #ifdef JS_TRACE_LOGGING
     if (!emitTraceLoggerResume(scratch1, regs))
         return false;
 #endif
 
-    Register constructing = regs.takeAny();
-    ValueOperand newTarget = regs.takeAnyValue();
-    masm.loadValue(Address(genObj, GeneratorObject::offsetOfNewTargetSlot()), newTarget);
-    masm.move32(Imm32(0), constructing);
-    {
-        Label notConstructing;
-        masm.branchTestObject(Assembler::NotEqual, newTarget, &notConstructing);
-        masm.pushValue(newTarget);
-        masm.move32(Imm32(CalleeToken_FunctionConstructing), constructing);
-        masm.bind(&notConstructing);
-    }
-    regs.add(newTarget);
-
     // Push |undefined| for all formals.
     Register scratch2 = regs.takeAny();
     Label loop, loopDone;
     masm.load16ZeroExtend(Address(callee, JSFunction::offsetOfNargs()), scratch2);
     masm.bind(&loop);
     masm.branchTest32(Assembler::Zero, scratch2, scratch2, &loopDone);
     {
         masm.pushValue(UndefinedValue());
@@ -4730,24 +4717,20 @@ BaselineCompiler::emit_JSOP_RESUME()
     // Update BaselineFrame frameSize field and create the frame descriptor.
     masm.computeEffectiveAddress(Address(BaselineFrameReg, BaselineFrame::FramePointerOffset),
                                  scratch2);
     masm.subStackPtrFrom(scratch2);
     masm.store32(scratch2, Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfFrameSize()));
     masm.makeFrameDescriptor(scratch2, JitFrame_BaselineJS, JitFrameLayout::Size());
 
     masm.Push(Imm32(0)); // actual argc
-
-    // Duplicate PushCalleeToken with a variable instead.
-    masm.orPtr(constructing, callee);
-    masm.Push(callee);
+    masm.PushCalleeToken(callee, /* constructing = */ false);
     masm.Push(scratch2); // frame descriptor
 
     regs.add(callee);
-    regs.add(constructing);
 
     // Load the return value.
     ValueOperand retVal = regs.takeAnyValue();
     masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), retVal);
 
     // Push a fake return address on the stack. We will resume here when the
     // generator returns.
     Label genStart, returnTarget;
--- a/js/src/jit/CacheIRCompiler.cpp
+++ b/js/src/jit/CacheIRCompiler.cpp
@@ -698,17 +698,16 @@ CacheRegisterAllocator::assertValidState
         const auto& loc1 = operandLocations_[i];
         if (loc1.isUninitialized())
             continue;
 
         for (size_t j = 0; j < i; j++) {
             const auto& loc2 = operandLocations_[j];
             if (loc2.isUninitialized())
                 continue;
-            MOZ_ASSERT(loc1 != loc2);
             MOZ_ASSERT(!loc1.aliasesReg(loc2));
         }
     }
 }
 #endif
 
 bool
 OperandLocation::aliasesReg(const OperandLocation& other) const
--- a/js/src/jit/IonControlFlow.cpp
+++ b/js/src/jit/IonControlFlow.cpp
@@ -1757,46 +1757,46 @@ ControlFlowGenerator::processIfStart(JSO
     CFGBlock* ifFalse = CFGBlock::New(alloc(), falseStart);
 
     CFGTest* test = CFGTest::New(alloc(), ifTrue, ifFalse);
     current->setStopIns(test);
     current->setStopPc(pc);
 
     // The bytecode for if/ternary gets emitted either like this:
     //
-    //    IFEQ X  ; src note (IF_ELSE, COND) points to the GOTO
+    //    IFEQ X     ; src note (IF_ELSE, COND)
     //    ...
     //    GOTO Z
-    // X: ...     ; else/else if
+    // X: JUMPTARGET ; else/else if
     //    ...
-    // Z:         ; join
+    // Z: JUMPTARGET ; join
     //
     // Or like this:
     //
-    //    IFEQ X  ; src note (IF) has no offset
+    //    IFEQ X     ; src note (IF)
     //    ...
-    // Z: ...     ; join
+    // X: JUMPTARGET ; join
     //
     // We want to parse the bytecode as if we were parsing the AST, so for the
-    // IF_ELSE/COND cases, we use the source note and follow the GOTO. For the
-    // IF case, the IFEQ offset is the join point.
+    // IF_ELSE/COND cases, we use the IFEQ/GOTO bytecode offsets to follow the
+    // branch. For the IF case, the IFEQ offset is the join point.
     switch (SN_TYPE(sn)) {
       case SRC_IF:
         if (!cfgStack_.append(CFGState::If(falseStart, test)))
             return ControlStatus::Error;
         break;
 
       case SRC_IF_ELSE:
       case SRC_COND:
       {
         // Infer the join point from the JSOP_GOTO[X] sitting here, then
         // assert as we much we can that this is the right GOTO.
-        jsbytecode* trueEnd = pc + GetSrcNoteOffset(sn, 0);
+        MOZ_ASSERT(JSOp(*falseStart) == JSOP_JUMPTARGET);
+        jsbytecode* trueEnd = falseStart - JSOP_GOTO_LENGTH;
         MOZ_ASSERT(trueEnd > pc);
-        MOZ_ASSERT(trueEnd < falseStart);
         MOZ_ASSERT(JSOp(*trueEnd) == JSOP_GOTO);
         MOZ_ASSERT(!GetSrcNote(gsn, script, trueEnd));
 
         jsbytecode* falseEnd = trueEnd + GetJumpOffset(trueEnd);
         MOZ_ASSERT(falseEnd > trueEnd);
         MOZ_ASSERT(falseEnd >= falseStart);
 
         if (!cfgStack_.append(CFGState::IfElse(trueEnd, falseEnd, test)))
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -2690,16 +2690,18 @@ SrcNotes(JSContext* cx, HandleScript scr
                           unsigned(sn - notes), lineno, offset, delta, name))
         {
             return false;
         }
 
         switch (type) {
           case SRC_NULL:
           case SRC_IF:
+          case SRC_IF_ELSE:
+          case SRC_COND:
           case SRC_CONTINUE:
           case SRC_BREAK:
           case SRC_BREAK2LABEL:
           case SRC_SWITCHBREAK:
           case SRC_ASSIGNOP:
           case SRC_XDELTA:
             break;
 
@@ -2724,28 +2726,22 @@ SrcNotes(JSContext* cx, HandleScript scr
                               unsigned(GetSrcNoteOffset(sn, 0)),
                               unsigned(GetSrcNoteOffset(sn, 1)),
                               unsigned(GetSrcNoteOffset(sn, 2))))
             {
                 return false;
             }
             break;
 
-          case SRC_IF_ELSE:
-            if (!sp->jsprintf(" else %u", unsigned(GetSrcNoteOffset(sn, 0))))
-                return false;
-            break;
-
           case SRC_FOR_IN:
           case SRC_FOR_OF:
             if (!sp->jsprintf(" closingjump %u", unsigned(GetSrcNoteOffset(sn, 0))))
                 return false;
             break;
 
-          case SRC_COND:
           case SRC_WHILE:
           case SRC_NEXTCASE:
             if (!sp->jsprintf(" offset %u", unsigned(GetSrcNoteOffset(sn, 0))))
                 return false;
             break;
 
           case SRC_TABLESWITCH: {
             JSOp op = JSOp(script->code()[offset]);
--- a/js/src/vm/GeneratorObject.cpp
+++ b/js/src/vm/GeneratorObject.cpp
@@ -16,16 +16,17 @@
 
 using namespace js;
 
 JSObject*
 GeneratorObject::create(JSContext* cx, AbstractFramePtr frame)
 {
     MOZ_ASSERT(frame.script()->isGenerator() || frame.script()->isAsync());
     MOZ_ASSERT(frame.script()->nfixed() == 0);
+    MOZ_ASSERT(!frame.isConstructing());
 
     Rooted<GlobalObject*> global(cx, cx->global());
 
     RootedValue pval(cx);
     RootedObject fun(cx, frame.callee());
     // FIXME: This would be faster if we could avoid doing a lookup to get
     // the prototype for the instance.  Bug 906600.
     if (!GetProperty(cx, fun, fun, cx->names().prototype, &pval))
@@ -36,17 +37,16 @@ GeneratorObject::create(JSContext* cx, A
         if (!proto)
             return nullptr;
     }
     Rooted<GeneratorObject*> genObj(cx, NewObjectWithGivenProto<GeneratorObject>(cx, proto));
     if (!genObj)
         return nullptr;
 
     genObj->setCallee(*frame.callee());
-    genObj->setNewTarget(frame.newTarget());
     genObj->setEnvironmentChain(*frame.environmentChain());
     if (frame.script()->needsArgsObj())
         genObj->setArgsObj(frame.argsObj());
     genObj->clearExpressionStack();
 
     return genObj;
 }
 
@@ -132,19 +132,18 @@ js::GeneratorThrowOrReturn(JSContext* cx
 bool
 GeneratorObject::resume(JSContext* cx, InterpreterActivation& activation,
                         HandleObject obj, HandleValue arg, GeneratorObject::ResumeKind resumeKind)
 {
     Rooted<GeneratorObject*> genObj(cx, &obj->as<GeneratorObject>());
     MOZ_ASSERT(genObj->isSuspended());
 
     RootedFunction callee(cx, &genObj->callee());
-    RootedValue newTarget(cx, genObj->newTarget());
     RootedObject envChain(cx, &genObj->environmentChain());
-    if (!activation.resumeGeneratorFrame(callee, newTarget, envChain))
+    if (!activation.resumeGeneratorFrame(callee, envChain))
         return false;
     activation.regs().fp()->setResumedGenerator();
 
     if (genObj->hasArgsObj())
         activation.regs().fp()->initArgsObj(genObj->argsObj());
 
     if (genObj->hasExpressionStack() && !genObj->isExpressionStackEmpty()) {
         uint32_t len = genObj->expressionStack().getDenseInitializedLength();
--- a/js/src/vm/GeneratorObject.h
+++ b/js/src/vm/GeneratorObject.h
@@ -24,17 +24,16 @@ class GeneratorObject : public NativeObj
     static const int32_t YIELD_AND_AWAIT_INDEX_CLOSING = INT32_MAX - 1;
 
     enum {
         CALLEE_SLOT = 0,
         ENV_CHAIN_SLOT,
         ARGS_OBJ_SLOT,
         EXPRESSION_STACK_SLOT,
         YIELD_AND_AWAIT_INDEX_SLOT,
-        NEWTARGET_SLOT,
         RESERVED_SLOTS
     };
 
     enum ResumeKind { NEXT, THROW, RETURN };
 
     static const Class class_;
 
   private:
@@ -109,27 +108,16 @@ class GeneratorObject : public NativeObj
     }
     void setExpressionStack(ArrayObject& expressionStack) {
         setFixedSlot(EXPRESSION_STACK_SLOT, ObjectValue(expressionStack));
     }
     void clearExpressionStack() {
         setFixedSlot(EXPRESSION_STACK_SLOT, NullValue());
     }
 
-    bool isConstructing() const {
-        return getFixedSlot(NEWTARGET_SLOT).isObject();
-    }
-    const Value& newTarget() const {
-        return getFixedSlot(NEWTARGET_SLOT);
-    }
-    void setNewTarget(const Value& newTarget) {
-        setFixedSlot(NEWTARGET_SLOT, newTarget);
-    }
-
-
     // The yield index slot is abused for a few purposes.  It's undefined if
     // it hasn't been set yet (before the initial yield), and null if the
     // generator is closed. If the generator is running, the yield index is
     // YIELD_AND_AWAIT_INDEX_RUNNING. If the generator is in that bizarre
     // "closing" state, the yield index is YIELD_AND_AWAIT_INDEX_CLOSING.
     //
     // If the generator is suspended, it's the yield index (stored as
     // JSOP_INITIALYIELD/JSOP_YIELD/JSOP_AWAIT operand) of the yield
@@ -175,17 +163,16 @@ class GeneratorObject : public NativeObj
         return getFixedSlot(CALLEE_SLOT).isNull();
     }
     void setClosed() {
         setFixedSlot(CALLEE_SLOT, NullValue());
         setFixedSlot(ENV_CHAIN_SLOT, NullValue());
         setFixedSlot(ARGS_OBJ_SLOT, NullValue());
         setFixedSlot(EXPRESSION_STACK_SLOT, NullValue());
         setFixedSlot(YIELD_AND_AWAIT_INDEX_SLOT, NullValue());
-        setFixedSlot(NEWTARGET_SLOT, NullValue());
     }
 
     bool isAfterYield();
     bool isAfterAwait();
 
   private:
     bool isAfterYieldOrAwait(JSOp op);
 
@@ -200,19 +187,16 @@ class GeneratorObject : public NativeObj
         return getFixedSlotOffset(ARGS_OBJ_SLOT);
     }
     static size_t offsetOfYieldAndAwaitIndexSlot() {
         return getFixedSlotOffset(YIELD_AND_AWAIT_INDEX_SLOT);
     }
     static size_t offsetOfExpressionStackSlot() {
         return getFixedSlotOffset(EXPRESSION_STACK_SLOT);
     }
-    static size_t offsetOfNewTargetSlot() {
-        return getFixedSlotOffset(NEWTARGET_SLOT);
-    }
 };
 
 bool GeneratorThrowOrReturn(JSContext* cx, AbstractFramePtr frame, Handle<GeneratorObject*> obj,
                             HandleValue val, uint32_t resumeKind);
 void SetGeneratorClosed(JSContext* cx, AbstractFramePtr frame);
 
 MOZ_MUST_USE bool
 CheckGeneratorResumptionValue(JSContext* cx, HandleValue v);
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -313,50 +313,48 @@ InterpreterStack::pushInlineFrame(JSCont
                       constructing);
 
     regs.prepareToRun(*fp, script);
     return true;
 }
 
 MOZ_ALWAYS_INLINE bool
 InterpreterStack::resumeGeneratorCallFrame(JSContext* cx, InterpreterRegs& regs,
-                                           HandleFunction callee, HandleValue newTarget,
-                                           HandleObject envChain)
+                                           HandleFunction callee, HandleObject envChain)
 {
     MOZ_ASSERT(callee->isGenerator() || callee->isAsync());
     RootedScript script(cx, JSFunction::getOrCreateScript(cx, callee));
     InterpreterFrame* prev = regs.fp();
     jsbytecode* prevpc = regs.pc;
     Value* prevsp = regs.sp;
     MOZ_ASSERT(prev);
 
     script->ensureNonLazyCanonicalFunction();
 
     LifoAlloc::Mark mark = allocator_.mark();
 
-    MaybeConstruct constructing = MaybeConstruct(newTarget.isObject());
+    // (Async) generators and async functions are not constructors.
+    MOZ_ASSERT(!callee->isConstructor());
 
     // Include callee, |this|, and maybe |new.target|
     unsigned nformal = callee->nargs();
-    unsigned nvals = 2 + constructing + nformal + script->nslots();
+    unsigned nvals = 2 + nformal + script->nslots();
 
     uint8_t* buffer = allocateFrame(cx, sizeof(InterpreterFrame) + nvals * sizeof(Value));
     if (!buffer)
         return false;
 
     Value* argv = reinterpret_cast<Value*>(buffer) + 2;
     argv[-2] = ObjectValue(*callee);
     argv[-1] = UndefinedValue();
     SetValueRangeToUndefined(argv, nformal);
-    if (constructing)
-        argv[nformal] = newTarget;
 
-    InterpreterFrame* fp = reinterpret_cast<InterpreterFrame*>(argv + nformal + constructing);
+    InterpreterFrame* fp = reinterpret_cast<InterpreterFrame*>(argv + nformal);
     fp->mark_ = mark;
-    fp->initCallFrame(prev, prevpc, prevsp, *callee, script, argv, 0, constructing);
+    fp->initCallFrame(prev, prevpc, prevsp, *callee, script, argv, 0, NO_CONSTRUCT);
     fp->resumeGeneratorFrame(envChain);
 
     regs.prepareToRun(*fp, script);
     return true;
 }
 
 MOZ_ALWAYS_INLINE void
 InterpreterStack::popInlineFrame(InterpreterRegs& regs)
@@ -643,16 +641,28 @@ AbstractFramePtr::unsetIsDebuggee()
         asBaselineFrame()->unsetIsDebuggee();
     else if (isWasmDebugFrame())
         asWasmDebugFrame()->unsetIsDebuggee();
     else
         asRematerializedFrame()->unsetIsDebuggee();
 }
 
 inline bool
+AbstractFramePtr::isConstructing() const
+{
+    if (isInterpreterFrame())
+        return asInterpreterFrame()->isConstructing();
+    if (isBaselineFrame())
+        return asBaselineFrame()->isConstructing();
+    if (isRematerializedFrame())
+        return asRematerializedFrame()->isConstructing();
+    MOZ_CRASH("Unexpected frame");
+}
+
+inline bool
 AbstractFramePtr::hasArgs() const
 {
     return isFunctionFrame();
 }
 
 inline bool
 AbstractFramePtr::hasScript() const
 {
@@ -958,21 +968,20 @@ InterpreterActivation::popInlineFrame(In
     (void)frame; // Quell compiler warning.
     MOZ_ASSERT(regs_.fp() == frame);
     MOZ_ASSERT(regs_.fp() != entryFrame_);
 
     cx_->interpreterStack().popInlineFrame(regs_);
 }
 
 inline bool
-InterpreterActivation::resumeGeneratorFrame(HandleFunction callee, HandleValue newTarget,
-                                            HandleObject envChain)
+InterpreterActivation::resumeGeneratorFrame(HandleFunction callee, HandleObject envChain)
 {
     InterpreterStack& stack = cx_->interpreterStack();
-    if (!stack.resumeGeneratorCallFrame(cx_, regs_, callee, newTarget, envChain))
+    if (!stack.resumeGeneratorCallFrame(cx_, regs_, callee, envChain))
         return false;
 
     MOZ_ASSERT(regs_.fp()->script()->compartment() == compartment_);
     return true;
 }
 
 /* static */ inline mozilla::Maybe<LiveSavedFrameCache::FramePtr>
 LiveSavedFrameCache::FramePtr::create(const FrameIter& iter)
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -226,16 +226,17 @@ class AbstractFramePtr
     inline bool hasScript() const;
     inline JSScript* script() const;
     inline wasm::Instance* wasmInstance() const;
     inline GlobalObject* global() const;
     inline JSFunction* callee() const;
     inline Value calleev() const;
     inline Value& thisArgument() const;
 
+    inline bool isConstructing() const;
     inline Value newTarget() const;
 
     inline bool debuggerNeedsCheckPrimitiveReturn() const;
 
     inline bool isFunctionFrame() const;
     inline bool isNonStrictDirectEvalFrame() const;
     inline bool isStrictEvalFrame() const;
 
@@ -901,18 +902,17 @@ class InterpreterStack
     // The interpreter can push light-weight, "inline" frames without entering a
     // new InterpreterActivation or recursively calling Interpret.
     bool pushInlineFrame(JSContext* cx, InterpreterRegs& regs, const CallArgs& args,
                          HandleScript script, MaybeConstruct constructing);
 
     void popInlineFrame(InterpreterRegs& regs);
 
     bool resumeGeneratorCallFrame(JSContext* cx, InterpreterRegs& regs,
-                                  HandleFunction callee, HandleValue newTarget,
-                                  HandleObject envChain);
+                                  HandleFunction callee, HandleObject envChain);
 
     inline void purge(JSRuntime* rt);
 
     size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
         return allocator_.sizeOfExcludingThis(mallocSizeOf);
     }
 };
 
@@ -1547,18 +1547,17 @@ class InterpreterActivation : public Act
   public:
     inline InterpreterActivation(RunState& state, JSContext* cx, InterpreterFrame* entryFrame);
     inline ~InterpreterActivation();
 
     inline bool pushInlineFrame(const CallArgs& args, HandleScript script,
                                 MaybeConstruct constructing);
     inline void popInlineFrame(InterpreterFrame* frame);
 
-    inline bool resumeGeneratorFrame(HandleFunction callee, HandleValue newTarget,
-                                     HandleObject envChain);
+    inline bool resumeGeneratorFrame(HandleFunction callee, HandleObject envChain);
 
     InterpreterFrame* current() const {
         return regs_.fp();
     }
     InterpreterRegs& regs() {
         return regs_;
     }
     InterpreterFrame* entryFrame() const {
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -106802,16 +106802,40 @@
       [
        "/css/css-contain/reference/contain-layout-005-ref.html",
        "=="
       ]
      ],
      {}
     ]
    ],
+   "css/css-contain/contain-layout-006.html": [
+    [
+     "/css/css-contain/contain-layout-006.html",
+     [
+      [
+       "/css/reference/ref-filled-green-100px-square.xht",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-contain/contain-layout-007.html": [
+    [
+     "/css/css-contain/contain-layout-007.html",
+     [
+      [
+       "/css/reference/ref-filled-green-100px-square.xht",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/css-contain/contain-layout-breaks-001.html": [
     [
      "/css/css-contain/contain-layout-breaks-001.html",
      [
       [
        "/css/css-contain/reference/contain-style-breaks-004-ref.html",
        "=="
       ]
@@ -106922,16 +106946,40 @@
       [
        "/css/css-contain/reference/contain-paint-008-ref.html",
        "=="
       ]
      ],
      {}
     ]
    ],
+   "css/css-contain/contain-paint-009.html": [
+    [
+     "/css/css-contain/contain-paint-009.html",
+     [
+      [
+       "/css/reference/ref-filled-green-100px-square.xht",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-contain/contain-paint-010.html": [
+    [
+     "/css/css-contain/contain-paint-010.html",
+     [
+      [
+       "/css/reference/ref-filled-green-100px-square.xht",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/css-contain/contain-size-001.html": [
     [
      "/css/css-contain/contain-size-001.html",
      [
       [
        "/css/css-contain/reference/contain-size-001-ref.html",
        "=="
       ]
@@ -128474,16 +128522,40 @@
       [
        "/css/css-sizing/intrinsic-percent-non-replaced-002-ref.html",
        "=="
       ]
      ],
      {}
     ]
    ],
+   "css/css-sizing/intrinsic-percent-non-replaced-004.html": [
+    [
+     "/css/css-sizing/intrinsic-percent-non-replaced-004.html",
+     [
+      [
+       "/css/css-sizing/intrinsic-percent-non-replaced-004-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-sizing/intrinsic-percent-non-replaced-005.html": [
+    [
+     "/css/css-sizing/intrinsic-percent-non-replaced-005.html",
+     [
+      [
+       "/css/css-sizing/intrinsic-percent-non-replaced-005-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/css-style-attr/style-attr-braces-001.xht": [
     [
      "/css/css-style-attr/style-attr-braces-001.xht",
      [
       [
        "/css/css-style-attr/reference/ref-green-on-green.xht",
        "=="
       ]
@@ -188179,16 +188251,21 @@
      {}
     ]
    ],
    "2dcontext/4x2.png": [
     [
      {}
     ]
    ],
+   "2dcontext/OWNERS": [
+    [
+     {}
+    ]
+   ],
    "2dcontext/best-practices/.gitkeep": [
     [
      {}
     ]
    ],
    "2dcontext/building-paths/.gitkeep": [
     [
      {}
@@ -189249,16 +189326,21 @@
      {}
     ]
    ],
    "2dcontext/transformations/transform_ref.html": [
     [
      {}
     ]
    ],
+   "BackgroundSync/OWNERS": [
+    [
+     {}
+    ]
+   ],
    "FileAPI/BlobURL/support/file_test2.txt": [
     [
      {}
     ]
    ],
    "FileAPI/FileReader/support/file_test1.txt": [
     [
      {}
@@ -192704,21 +192786,31 @@
      {}
     ]
    ],
    "annotation-vocab/tools/vocab_tester.py": [
     [
      {}
     ]
    ],
+   "apng/OWNERS": [
+    [
+     {}
+    ]
+   ],
    "apng/animated-png-timeout-ref.html": [
     [
      {}
     ]
    ],
+   "audio-output/OWNERS": [
+    [
+     {}
+    ]
+   ],
    "background-fetch/OWNERS": [
     [
      {}
     ]
    ],
    "background-fetch/resources/sw.js": [
     [
      {}
@@ -193209,16 +193301,21 @@
      {}
     ]
    ],
    "common/stringifiers.js.headers": [
     [
      {}
     ]
    ],
+   "common/subset-tests.js": [
+    [
+     {}
+    ]
+   ],
    "common/test-setting-immutable-prototype.js": [
     [
      {}
     ]
    ],
    "common/test-setting-immutable-prototype.js.headers": [
     [
      {}
@@ -217049,16 +217146,21 @@
      {}
     ]
    ],
    "cors/support.js": [
     [
      {}
     ]
    ],
+   "credential-management/OWNERS": [
+    [
+     {}
+    ]
+   ],
    "credential-management/support/echoing-nester.html": [
     [
      {}
     ]
    ],
    "credential-management/support/federatedcredential-get.html": [
     [
      {}
@@ -240714,16 +240816,21 @@
      {}
     ]
    ],
    "css/css-contain/reference/quote-scoping-003-ref.html": [
     [
      {}
     ]
    ],
+   "css/css-content/OWNERS": [
+    [
+     {}
+    ]
+   ],
    "css/css-content/attr-case-insensitive-ref.html": [
     [
      {}
     ]
    ],
    "css/css-content/element-replacement-ref.html": [
     [
      {}
@@ -240994,16 +241101,21 @@
      {}
     ]
    ],
    "css/css-exclusions/resources/helper.js": [
     [
      {}
     ]
    ],
+   "css/css-fill-stroke/OWNERS": [
+    [
+     {}
+    ]
+   ],
    "css/css-fill-stroke/reference/paint-order-001-ref.tentative.html": [
     [
      {}
     ]
    ],
    "css/css-filter/filtered-block-is-container-ref.html": [
     [
      {}
@@ -242349,16 +242461,21 @@
      {}
     ]
    ],
    "css/css-flexbox/whitespace-in-flexitem-001-ref.html": [
     [
      {}
     ]
    ],
+   "css/css-font-loading/OWNERS": [
+    [
+     {}
+    ]
+   ],
    "css/css-fonts/OWNERS": [
     [
      {}
     ]
    ],
    "css/css-fonts/README": [
     [
      {}
@@ -253054,16 +253171,21 @@
      {}
     ]
    ],
    "css/css-position/resources/sticky-util.js": [
     [
      {}
     ]
    ],
+   "css/css-properties-values-api/OWNERS": [
+    [
+     {}
+    ]
+   ],
    "css/css-pseudo/OWNERS": [
     [
      {}
     ]
    ],
    "css/css-pseudo/first-letter-001-ref.html": [
     [
      {}
@@ -254239,21 +254361,36 @@
      {}
     ]
    ],
    "css/css-scoping/slotted-with-pseudo-element-ref.html": [
     [
      {}
     ]
    ],
+   "css/css-scroll-anchoring/OWNERS": [
+    [
+     {}
+    ]
+   ],
    "css/css-scroll-anchoring/README.md": [
     [
      {}
     ]
    ],
+   "css/css-scroll-snap/OWNERS": [
+    [
+     {}
+    ]
+   ],
+   "css/css-shadow-parts/OWNERS": [
+    [
+     {}
+    ]
+   ],
    "css/css-shadow-parts/support/shadow-helper.js": [
     [
      {}
     ]
    ],
    "css/css-shapes/OWNERS": [
     [
      {}
@@ -254759,16 +254896,26 @@
      {}
     ]
    ],
    "css/css-sizing/intrinsic-percent-non-replaced-002-ref.html": [
     [
      {}
     ]
    ],
+   "css/css-sizing/intrinsic-percent-non-replaced-004-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-sizing/intrinsic-percent-non-replaced-005-ref.html": [
+    [
+     {}
+    ]
+   ],
    "css/css-speech/OWNERS": [
     [
      {}
     ]
    ],
    "css/css-style-attr/OWNERS": [
     [
      {}
@@ -272309,16 +272456,21 @@
      {}
     ]
    ],
    "custom-elements/resources/my-custom-element-html-document.html": [
     [
      {}
     ]
    ],
+   "device-memory/OWNERS": [
+    [
+     {}
+    ]
+   ],
    "docs/.gitignore": [
     [
      {}
     ]
    ],
    "docs/.ruby-version": [
     [
      {}
@@ -288459,16 +288611,21 @@
      {}
     ]
    ],
    "html/webappapis/user-prompts/simple-dialogs/.gitkeep": [
     [
      {}
     ]
    ],
+   "imagebitmap-renderingcontext/OWNERS": [
+    [
+     {}
+    ]
+   ],
    "images/OWNERS": [
     [
      {}
     ]
    ],
    "images/anim-gr.gif": [
     [
      {}
@@ -289924,16 +290081,21 @@
      {}
     ]
    ],
    "mediacapture-fromelement/OWNERS": [
     [
      {}
     ]
    ],
+   "mediacapture-image/OWNERS": [
+    [
+     {}
+    ]
+   ],
    "mediacapture-record/OWNERS": [
     [
      {}
     ]
    ],
    "mediacapture-streams/OWNERS": [
     [
      {}
@@ -290674,16 +290836,21 @@
      {}
     ]
    ],
    "notifications/resources/shownotification-sw.js": [
     [
      {}
     ]
    ],
+   "offscreen-canvas/OWNERS": [
+    [
+     {}
+    ]
+   ],
    "offscreen-canvas/filter/offscreencanvas.filter.js": [
     [
      {}
     ]
    ],
    "offscreen-canvas/tools/OWNERS": [
     [
      {}
@@ -294284,16 +294451,21 @@
      {}
     ]
    ],
    "selection/test-iframe.html": [
     [
      {}
     ]
    ],
+   "server-timing/OWNERS": [
+    [
+     {}
+    ]
+   ],
    "server-timing/resources/blue.png": [
     [
      {}
     ]
    ],
    "server-timing/resources/blue.png.sub.headers": [
     [
      {}
@@ -298399,16 +298571,21 @@
      {}
     ]
    ],
    "wake-lock/wakelock-enabled-on-self-origin-by-feature-policy.https.sub.html.headers": [
     [
      {}
     ]
    ],
+   "wasm/OWNERS": [
+    [
+     {}
+    ]
+   ],
    "wasm/compile_worker.js": [
     [
      {}
     ]
    ],
    "wasm/incrementer.wasm": [
     [
      {}
@@ -299129,16 +299306,21 @@
      {}
     ]
    ],
    "webdriver/tests/switch_to_parent_frame/__init__.py": [
     [
      {}
     ]
    ],
+   "webgl/OWNERS": [
+    [
+     {}
+    ]
+   ],
    "webgl/common.js": [
     [
      {}
     ]
    ],
    "webmessaging/MessageEvent-trusted-worker.js": [
     [
      {}
@@ -301939,16 +302121,21 @@
      {}
     ]
    ],
    "workers/modules/resources/static-import-and-then-dynamic-import-worker.js": [
     [
      {}
     ]
    ],
+   "workers/modules/resources/static-import-non-existent-script-worker.js": [
+    [
+     {}
+    ]
+   ],
    "workers/modules/resources/static-import-worker.js": [
     [
      {}
     ]
    ],
    "workers/non-automated/application-cache-dedicated.html": [
     [
      {}
@@ -302194,16 +302381,21 @@
      {}
     ]
    ],
    "workers/support/shared-name.js": [
     [
      {}
     ]
    ],
+   "worklets/OWNERS": [
+    [
+     {}
+    ]
+   ],
    "worklets/README.md": [
     [
      {}
     ]
    ],
    "worklets/resources/addmodule-window.html": [
     [
      {}
@@ -373822,16 +374014,22 @@
     ]
    ],
    "webrtc/RTCIceTransport.html": [
     [
      "/webrtc/RTCIceTransport.html",
      {}
     ]
    ],
+   "webrtc/RTCPeerConnection-add-track-no-deadlock.https.html": [
+    [
+     "/webrtc/RTCPeerConnection-add-track-no-deadlock.https.html",
+     {}
+    ]
+   ],
    "webrtc/RTCPeerConnection-addIceCandidate.html": [
     [
      "/webrtc/RTCPeerConnection-addIceCandidate.html",
      {}
     ]
    ],
    "webrtc/RTCPeerConnection-addTrack.https.html": [
     [
@@ -399671,16 +399869,20 @@
   "2dcontext/2x4.png": [
    "690bac789fecf2530b36dd889c68db3bd93ed9fd",
    "support"
   ],
   "2dcontext/4x2.png": [
    "16f72935aaf97175593bcf27794506f0884f091b",
    "support"
   ],
+  "2dcontext/OWNERS": [
+   "07a7da62ef2c26f061cfc076d268f06a0cdcf72b",
+   "support"
+  ],
   "2dcontext/best-practices/.gitkeep": [
    "da39a3ee5e6b4b0d3255bfef95601890afd80709",
    "support"
   ],
   "2dcontext/building-paths/.gitkeep": [
    "da39a3ee5e6b4b0d3255bfef95601890afd80709",
    "support"
   ],
@@ -403747,16 +403949,20 @@
   "2dcontext/transformations/transform_a.html": [
    "f82fd46f1f39726dea744e11b5dd9fb7a085606c",
    "reftest"
   ],
   "2dcontext/transformations/transform_ref.html": [
    "73f0071c06f429c3d47eac8d0863e11f1d6e8c25",
    "support"
   ],
+  "BackgroundSync/OWNERS": [
+   "ed48014d817ef034062db60403704ed2c0cf9aeb",
+   "support"
+  ],
   "BackgroundSync/interfaces.any.js": [
    "2a158ca33b93b78a581341d885150d252a0e4555",
    "testharness"
   ],
   "FileAPI/BlobURL/support/file_test2.txt": [
    "da39a3ee5e6b4b0d3255bfef95601890afd80709",
    "support"
   ],
@@ -405684,57 +405890,57 @@
    "dc18838f5c4f84436c892051a728de7ad83fe854",
    "testharness"
   ],
   "WebCryptoAPI/generateKey/failures_RSASSA-PKCS1-v1_5.https.any.js": [
    "170964b911cd3d1aacaa261de01b013ee3fd745a",
    "testharness"
   ],
   "WebCryptoAPI/generateKey/successes.js": [
-   "db8091eaf59ea342ae7c8f64e6548c976b5f0b36",
+   "f0f112ddcf43a46cfccc0e7cf29b46208f87bb5f",
    "support"
   ],
   "WebCryptoAPI/generateKey/successes_AES-CBC.https.any.js": [
-   "71f7d2beac74da25795366722aea79fb88c197ab",
+   "49ce28516303c7525f74d29fbbe36b578e9e0d08",
    "testharness"
   ],
   "WebCryptoAPI/generateKey/successes_AES-CTR.https.any.js": [
-   "57f1bff81966f8fc20b7870ed1a5ecf939f1c4d1",
+   "377e8f0ec969f36949c9dc08ae935063d25c08d8",
    "testharness"
   ],
   "WebCryptoAPI/generateKey/successes_AES-GCM.https.any.js": [
-   "faa70a7dda91b0602987ec8e7ef0787c4ed163e3",
+   "a65a71f8f83b017fe9eb8fccbd8da3489ed1f9f0",
    "testharness"
   ],
   "WebCryptoAPI/generateKey/successes_AES-KW.https.any.js": [
-   "23b4567c82e12847f41fb6aa8a6a2cb6bd219a94",
+   "165159903403f67cc27d29f78d46369eb598811c",
    "testharness"
   ],
   "WebCryptoAPI/generateKey/successes_ECDH.https.any.js": [
-   "51f5990d71b6d0008163c5550dfe3ca88fe56356",
+   "525b874eff610c6db8cca7f50ba0c074b8975001",
    "testharness"
   ],
   "WebCryptoAPI/generateKey/successes_ECDSA.https.any.js": [
-   "bfce352ef583a31aaae4a5a303a8b98737cfc22f",
+   "2a63f8d0d12987ca2e4c41e5eb28a53741a091f5",
    "testharness"
   ],
   "WebCryptoAPI/generateKey/successes_HMAC.https.any.js": [
-   "64b1846740147585fc4d497ba8d20a4b5bdef315",
+   "af064fd9cc422c35718d1dcf2f6c861aab7385cc",
    "testharness"
   ],
   "WebCryptoAPI/generateKey/successes_RSA-OAEP.https.any.js": [
-   "ccb63d91473cb7ee6554ad80f0ee2be004e75c91",
+   "f82ce4307fea94a097ccdf470b766f110d876b87",
    "testharness"
   ],
   "WebCryptoAPI/generateKey/successes_RSA-PSS.https.any.js": [
-   "9549e735a6d5d2a570f2b6ec4b09c49fd6a88f51",
+   "8ff33e42d855b7482455ebb3caab5ac311e7cbf5",
    "testharness"
   ],
   "WebCryptoAPI/generateKey/successes_RSASSA-PKCS1-v1_5.https.any.js": [
-   "11aff41d669c5a44c5929ac8754d680b675f3c8c",
+   "fbbbef333461a7845ea1b3cba2169435c7f82761",
    "testharness"
   ],
   "WebCryptoAPI/getRandomValues.any.js": [
    "44475110f616c78bc04b93246f015e7a83e78fa7",
    "testharness"
   ],
   "WebCryptoAPI/idlharness.https.html": [
    "a0ac6f063c49d9c14b614dee18b681968eee05ad",
@@ -409251,16 +409457,20 @@
   "annotation-vocab/tools/samples/incorrect/anno9.json": [
    "2441675561674811069ff556a9252bd514075131",
    "support"
   ],
   "annotation-vocab/tools/vocab_tester.py": [
    "789767132e47fb6cad1ea60584a04b4f2e2c9551",
    "support"
   ],
+  "apng/OWNERS": [
+   "5fa81c84bf276ac842e54c02c4889aac62806f86",
+   "support"
+  ],
   "apng/animated-png-timeout-ref.html": [
    "dcd3c58b9200109868f2b98bda346bf26f823e07",
    "support"
   ],
   "apng/animated-png-timeout.html": [
    "b9ba0287c92e5dbda1dc207ab45e9c90e8618878",
    "reftest"
   ],
@@ -409271,16 +409481,20 @@
   "async-local-storage/storage-smoke-test.https.tentative.html": [
    "1e00cf5aff9d85ca66d8b831ee4c2c3cb8259071",
    "testharness"
   ],
   "audio-output/HTMLMediaElement-sinkId-idl.html": [
    "8f37d8d2fc1cb9b5ad0d85234f733f534f4f0db8",
    "testharness"
   ],
+  "audio-output/OWNERS": [
+   "8d5030068c8ea67ad77f7dabb274470685b734a6",
+   "support"
+  ],
   "audio-output/setSinkId-manual.https.html": [
    "6743b9f6843a1c6134ac9cc627375b0368986e55",
    "manual"
   ],
   "audio-output/setSinkId.https.html": [
    "556ab735b0461aab4dbbb02d277da6ea07106469",
    "testharness"
   ],
@@ -410531,16 +410745,20 @@
   "common/stringifiers.js": [
    "4c6d28a4b6d982edeb5232fa624936254f2df5f0",
    "support"
   ],
   "common/stringifiers.js.headers": [
    "e3593850f8098d3f3ff82c042deab15f51c66a52",
    "support"
   ],
+  "common/subset-tests.js": [
+   "ee72de5b5f2302d111a0f131eddf5081ad02b789",
+   "support"
+  ],
   "common/test-setting-immutable-prototype.js": [
    "2cafc5c2b867e0fd6f738b1fbeaa503761c400b7",
    "support"
   ],
   "common/test-setting-immutable-prototype.js.headers": [
    "e3593850f8098d3f3ff82c042deab15f51c66a52",
    "support"
   ],
@@ -432487,16 +432705,20 @@
   "cors/status.htm": [
    "1dd55535f1759d4ec23b9bb3652e7b56f0f82234",
    "testharness"
   ],
   "cors/support.js": [
    "8307ed240a531033c96da89197dcfb5ea25cde87",
    "support"
   ],
+  "credential-management/OWNERS": [
+   "c7e6702e4472c55bb6e9c543bed62de2d2a7331d",
+   "support"
+  ],
   "credential-management/credentialscontainer-create-basics.https.html": [
    "4889217f5e821965907d4d60a9ffdd19d4bc79af",
    "testharness"
   ],
   "credential-management/federatedcredential-framed-get.sub.https.html": [
    "561636e62d50da2d14e50516c62cbaea1c5bb924",
    "testharness"
   ],
@@ -496195,16 +496417,24 @@
   "css/css-contain/contain-layout-004.html": [
    "dea2a6b1dada9e57f36d2245ac67c05fd9c4ad0b",
    "reftest"
   ],
   "css/css-contain/contain-layout-005.html": [
    "60b834ef7b5becf76f5cacd55b2b9c4774562637",
    "reftest"
   ],
+  "css/css-contain/contain-layout-006.html": [
+   "89c68e7ce325295d6a36da91b6d4dd39580f7165",
+   "reftest"
+  ],
+  "css/css-contain/contain-layout-007.html": [
+   "799965dfb9989fc3a8226e80fa3c67cafb24374b",
+   "reftest"
+  ],
   "css/css-contain/contain-layout-breaks-001.html": [
    "09fcfd311126800c7ce63f27c3154ef31fc647bd",
    "reftest"
   ],
   "css/css-contain/contain-layout-breaks-002.html": [
    "985252e8226e0cba567b49f44665804e3a12bc40",
    "reftest"
   ],
@@ -496235,16 +496465,24 @@
   "css/css-contain/contain-paint-007.html": [
    "3e7d3cd3ccb55b7b8574be24fb2f180a6ce0af36",
    "reftest"
   ],
   "css/css-contain/contain-paint-008.html": [
    "5540aaf33b5b841d95f84ead3459f634cf444299",
    "reftest"
   ],
+  "css/css-contain/contain-paint-009.html": [
+   "b3a8ee7512f1f893b630453a8443edaed983f4c3",
+   "reftest"
+  ],
+  "css/css-contain/contain-paint-010.html": [
+   "e47f99a8fedb60f34ec7877686f478cbc002c6b4",
+   "reftest"
+  ],
   "css/css-contain/contain-size-001.html": [
    "089c4fd7352f91a85d5d8f6bf8c5f261c868cd37",
    "reftest"
   ],
   "css/css-contain/contain-size-002.html": [
    "ea16d90e56729c1678b327412cd945a0dea6b762",
    "reftest"
   ],
@@ -496391,16 +496629,20 @@
   "css/css-contain/reference/quote-scoping-002-ref.html": [
    "e0b0fd4dae5325229725bae754998960aa0f6eb4",
    "support"
   ],
   "css/css-contain/reference/quote-scoping-003-ref.html": [
    "6b72ea53450c5a08ef3ddd897608aa9cf7e68e00",
    "support"
   ],
+  "css/css-content/OWNERS": [
+   "d3f0adf12724f2b230ffa3b2845e273ba4200076",
+   "support"
+  ],
   "css/css-content/attr-case-insensitive-ref.html": [
    "30577fc39afb6ac028e25be11f363e060c0850b2",
    "support"
   ],
   "css/css-content/attr-case-insensitive.html": [
    "6b6cf2c15295940fb8831d17209635dc4e31cd78",
    "reftest"
   ],
@@ -498459,16 +498701,20 @@
   "css/css-exclusions/wrap-flow-006.html": [
    "2c92a167aa6dc0b1735589105bdd73549327f8e6",
    "testharness"
   ],
   "css/css-exclusions/wrap-through-001.html": [
    "cfaff82945bd52baafb8c4c16eb38c9dbee14da6",
    "testharness"
   ],
+  "css/css-fill-stroke/OWNERS": [
+   "d9c8054b356c9273a054a83abeb9be0626c23921",
+   "support"
+  ],
   "css/css-fill-stroke/paint-order-001.tentative.html": [
    "46e784929d8a661eb432fa04cc79e0612bd5d194",
    "reftest"
   ],
   "css/css-fill-stroke/reference/paint-order-001-ref.tentative.html": [
    "35eb3b4cfe4505a5c9761dcecc047a8cd09f8fb9",
    "support"
   ],
@@ -501607,16 +501853,20 @@
   "css/css-flexbox/whitespace-in-flexitem-001-ref.html": [
    "09714cc32e9189de91ac4644e2651d90b080a20e",
    "support"
   ],
   "css/css-flexbox/whitespace-in-flexitem-001.html": [
    "62ff3e2eac64bb2057391e4dcc4664a4839bbbe8",
    "reftest"
   ],
+  "css/css-font-loading/OWNERS": [
+   "19b55d317925d28a18230592db5d05426f16537c",
+   "support"
+  ],
   "css/css-font-loading/fontfacesetloadevent-constructor.html": [
    "ad355c3d5220c1b938182241a8e8abe030ace699",
    "testharness"
   ],
   "css/css-font-loading/idlharness.https.html": [
    "00399ffcb0ff8000e79ab4aeefdb90cabdb0fd4d",
    "testharness"
   ],
@@ -515107,16 +515357,20 @@
   "css/css-position/position-sticky-writing-modes.html": [
    "b6d16a38b73d4c107e587194818a542fee9d0716",
    "reftest"
   ],
   "css/css-position/resources/sticky-util.js": [
    "7f8a5b4d9047c07378473362acb3d001e70d5663",
    "support"
   ],
+  "css/css-properties-values-api/OWNERS": [
+   "b098aad4eacf3991e0930a0953067c1b1135c519",
+   "support"
+  ],
   "css/css-properties-values-api/register-property-syntax-parsing.html": [
    "b065f4840b3c1deb4a2f8a59428e102f2ae11686",
    "testharness"
   ],
   "css/css-properties-values-api/register-property.html": [
    "df61ce5dd13847deaa9b7165dd1277c1ddefb646",
    "testharness"
   ],
@@ -517567,16 +517821,20 @@
   "css/css-scoping/stylesheet-title-001.html": [
    "2e49b6d74d2021144444ca77e62acbc6aeffac2a",
    "reftest"
   ],
   "css/css-scoping/stylesheet-title-002.html": [
    "5816d3d7af3c4bef07f4a299ab65c74b7b8d80f9",
    "testharness"
   ],
+  "css/css-scroll-anchoring/OWNERS": [
+   "d9c8054b356c9273a054a83abeb9be0626c23921",
+   "support"
+  ],
   "css/css-scroll-anchoring/README.md": [
    "31205944cbcf321f7aa77e3bef0f8835cc7b6d13",
    "support"
   ],
   "css/css-scroll-anchoring/abspos-containing-block-outside-scroller.html": [
    "d7a8e9904637c833d897b2e9c0da0a1628455670",
    "testharness"
   ],
@@ -517647,16 +517905,20 @@
   "css/css-scroll-anchoring/subtree-exclusion.html": [
    "dbfd02f30f8dc2750d697756e3c5f95bc1937c8a",
    "testharness"
   ],
   "css/css-scroll-anchoring/wrapped-text.html": [
    "de66dba5bce15b7403e9e582d982d4e3e4aed552",
    "testharness"
   ],
+  "css/css-scroll-snap/OWNERS": [
+   "d9c8054b356c9273a054a83abeb9be0626c23921",
+   "support"
+  ],
   "css/css-scroll-snap/scroll-snap-type-proximity.html": [
    "75bfc0b6c7686afbbf431e653ab674496ad3fe46",
    "testharness"
   ],
   "css/css-scroll-snap/scrollTo-scrollBy-snaps.html": [
    "6e921f3b1c54df09d594d5e450674618eee42143",
    "testharness"
   ],
@@ -517667,16 +517929,20 @@
   "css/css-scroll-snap/snap-inline-block.html": [
    "4fc646db848f597af6f4562e7b60815e9ca4ef2b",
    "testharness"
   ],
   "css/css-scroll-snap/snap-to-visible-areas.html": [
    "dfaf8675bec557c9f2178ad48b29c803f94056b5",
    "testharness"
   ],
+  "css/css-shadow-parts/OWNERS": [
+   "d9c8054b356c9273a054a83abeb9be0626c23921",
+   "support"
+  ],
   "css/css-shadow-parts/all-hosts.html": [
    "a92019bba916ed3242dcdf66184d73b915f2689d",
    "testharness"
   ],
   "css/css-shadow-parts/chaining-invalid-selector.html": [
    "f578d6766e7e20c31c677b6dfe1aea201f24ef65",
    "testharness"
   ],
@@ -518752,69 +519018,69 @@
    "cb4bf3851c4064cb650366da300998faf07e37fc",
    "reftest"
   ],
   "css/css-shapes/spec-examples/shape-outside-008.html": [
    "5a9f340648d85f2fdd3cd3fe74b2e145fbc1a54a",
    "reftest"
   ],
   "css/css-shapes/spec-examples/shape-outside-010.html": [
-   "7baf8e86ee451f08ab18e03000d64a529a2824d0",
+   "929078a33a23f1d10ce9d0f89016725f233e133e",
    "testharness"
   ],
   "css/css-shapes/spec-examples/shape-outside-011.html": [
-   "3620cce0bf178d1bc67faa372406c50b3b07c086",
+   "af178c8f965ce5f48673a4b67aa15f4974a09a2a",
    "testharness"
   ],
   "css/css-shapes/spec-examples/shape-outside-012.html": [
-   "1db1130bb5f2f5a8a12bab7ec5a9fa0c40165be4",
+   "67e23bdb9873267a12790cf4cd60b704f423f8a5",
    "testharness"
   ],
   "css/css-shapes/spec-examples/shape-outside-013.html": [
-   "155d33ea047169acdc6b29b4716fa7237b133d51",
+   "7bd3ac39a58324354fa2c36fa98b151ce1292c85",
    "testharness"
   ],
   "css/css-shapes/spec-examples/shape-outside-014.html": [
-   "ab449eda8aa379e0149c759e9dcd9a866c1ed3db",
+   "e87ca4ce9f46103091139fda326452e4c66b1980",
    "testharness"
   ],
   "css/css-shapes/spec-examples/shape-outside-015.html": [
-   "f0ce9eb24d3b6bf8b8aa03f5cd02f82acfcdf8cd",
+   "d30174b5995f0831f2535a7a817f6ad5af5a6ba8",
    "testharness"
   ],
   "css/css-shapes/spec-examples/shape-outside-016.html": [
-   "2ea1538bff7b1825cdd63ea6510435c7aa52aebc",
+   "585b41b91bd9d452b616f87e81c8a47576299719",
    "testharness"
   ],
   "css/css-shapes/spec-examples/shape-outside-017.html": [
-   "366e0ad5c7ae7f2663f9c6bc6c0eba51e299f120",
+   "9593d57329acf1998d4b5ada5a230f3fed0d3c32",
    "testharness"
   ],
   "css/css-shapes/spec-examples/shape-outside-018.html": [
-   "08c9fd21d74ae52b98581c7d013275c30d49e18c",
+   "c81ef4303d00a4f490f8cada68d8a6449daa56c3",
    "testharness"
   ],
   "css/css-shapes/spec-examples/shape-outside-019.html": [
-   "46de7762b9a9c29a0aa798775cea5dfb17bbf5a0",
+   "3eaf5c74cfbbb676b3f2ccae55a96d3999ac890b",
    "testharness"
   ],
   "css/css-shapes/spec-examples/support/circle-no-shadow.png": [
    "456a5b15b1866f5a169531639b0cd71c06b31b25",
    "support"
   ],
   "css/css-shapes/spec-examples/support/circle-shadow.png": [
    "6a149114684e9f3b40fb8c2c9452e7862b573c7e",
    "support"
   ],
   "css/css-shapes/spec-examples/support/rounded-triangle.svg": [
    "ef31c9d31d9aa7d27b94cacb7c3b5522c357ea8c",
    "support"
   ],
   "css/css-shapes/spec-examples/support/spec-example-utils.js": [
-   "137b347da1085eb0042f1d2fc017694ff528180b",
+   "de748e548169b360cf0d31a5499d766dd8bc2a7d",
    "support"
   ],
   "css/css-shapes/support/1x1-green.png": [
    "51e7b6974a09eda6cb31337717c5eaeb9c44b443",
    "support"
   ],
   "css/css-shapes/support/1x1-lime.png": [
    "b040eb633a35c0648ad72a2902361faf25bc419d",
@@ -518967,16 +519233,32 @@
   "css/css-sizing/intrinsic-percent-non-replaced-002.html": [
    "9d9c514c876bb9aa2c577dbd15c14358e7b0d0c4",
    "visual"
   ],
   "css/css-sizing/intrinsic-percent-non-replaced-003.html": [
    "43d44ad3a75e6abb9d66d06c3cdc71dbcb366651",
    "reftest"
   ],
+  "css/css-sizing/intrinsic-percent-non-replaced-004-ref.html": [
+   "4b93517397b4332b3062804e2d6047afdd169fae",
+   "support"
+  ],
+  "css/css-sizing/intrinsic-percent-non-replaced-004.html": [
+   "42bc0191ff3ba22c5ceb377f1bc68131d652509e",
+   "reftest"
+  ],
+  "css/css-sizing/intrinsic-percent-non-replaced-005-ref.html": [
+   "a2cd75ab26376e74e26414013fc34b8e7fdb1a68",
+   "support"
+  ],
+  "css/css-sizing/intrinsic-percent-non-replaced-005.html": [
+   "33aa880de7eb7c4128022871dfd5e2895be30dcc",
+   "reftest"
+  ],
   "css/css-speech/Integer.html": [
    "8db91e711b22ead393b1d9a18c224a7f838b85b0",
    "manual"
   ],
   "css/css-speech/OWNERS": [
    "3275f13bf63e920a63a84777b91a1e5d35019c0f",
    "support"
   ],
@@ -532024,17 +532306,17 @@
    "4db1d84665ccf75d993d877eb08574b1fa7d0203",
    "testharness"
   ],
   "css/css-transitions/transitions-animatable-properties-01.html": [
    "538b95863c061da60e95c1a61ef9dc93da007aa4",
    "testharness"
   ],
   "css/css-transitions/zero-duration-multiple-transition.html": [
-   "7db6bc8641a6a2eb5f1ee1fdbc9b6215a0462bbc",
+   "bcbcd704e617cf1bb057a67d9ff5635ef6ebda7e",
    "testharness"
   ],
   "css/css-typed-om/CSSMatrixComponent-DOMMatrix-mutable.html": [
    "f6056e2480829c7aa9885673d332496faf7777b5",
    "testharness"
   ],
   "css/css-typed-om/OWNERS": [
    "f5f0861ac3382b3b12008133c1334f812a5a2caa",
@@ -556143,16 +556425,20 @@
   "custom-elements/upgrading/upgrading-enqueue-reactions.html": [
    "322d77285d9cb280298c28c16e4d4310993e5b08",
    "testharness"
   ],
   "custom-elements/upgrading/upgrading-parser-created-element.html": [
    "2ec52a6baa7490478ec61e72bf89b2ec09d453b5",
    "testharness"
   ],
+  "device-memory/OWNERS": [
+   "c328f48fe12bce670646911964930d221c768f1d",
+   "support"
+  ],
   "device-memory/device-memory.https.any.js": [
    "4e746987c6580c6ff388f1512a02cc6fe33393ea",
    "testharness"
   ],
   "docs/.gitignore": [
    "e74984773578df84decce0bfd0db831220a2b008",
    "support"
   ],
@@ -556308,17 +556594,17 @@
    "2ffb3871fb13e6acd3171205bf80517dca06bcfe",
    "support"
   ],
   "docs/_writing-tests/testharness-api.md": [
    "6e49764657b4aa160f3db1a85f92910bfccae1e2",
    "support"
   ],
   "docs/_writing-tests/testharness.md": [
-   "6d581e1021a2401b34c0c024990b5e85dbef1fa5",
+   "0a0d88c5ddde91514a51d607405c2b7dedee4776",
    "support"
   ],
   "docs/_writing-tests/visual.md": [
    "43ab66e46d0a59851c3ad9d11cc0e2d5dd3ca8c5",
    "support"
   ],
   "docs/assets/_reftest_graph_example.dot": [
    "bc8ebc09e30ff18994b32aa9e5ea43334c276ade",
@@ -562328,17 +562614,17 @@
    "32dd5cdbc638ac7c141036633e136137854c3bb4",
    "testharness"
   ],
   "fetch/nosniff/worker.html": [
    "a9e0f5b9b70917aabbff3ad5dd03a5d5dccfa9f0",
    "testharness"
   ],
   "fetch/security/dangling-markup-mitigation-data-url.tentative.sub.html": [
-   "92c4633c3d684de4b1d959dbff4f2bab1eaf15a0",
+   "1059b1c0907444bd416889f99d766a566ba9bde3",
    "testharness"
   ],
   "fetch/security/dangling-markup-mitigation.tentative.html": [
    "f7eb7151eb44f6b879beb325e923507f9430a3a5",
    "testharness"
   ],
   "fetch/security/embedded-credentials.tentative.sub.html": [
    "678e1f80a090021a240933034d1a9206cddde8a4",
@@ -579260,17 +579546,17 @@
    "01089de4c049a4b35e675f8ac6c5c82f0770201e",
    "testharness"
   ],
   "html/semantics/scripting-1/the-script-element/execution-timing/076.html": [
    "c410028036e51ae5529acce711ea3dc1cbc83728",
    "testharness"
   ],
   "html/semantics/scripting-1/the-script-element/execution-timing/077.html": [
-   "2bfe280f0ae7b745f2217055e66579ab6fb06769",
+   "077bf7437a9ef4f6d51d0fa26b45ae7c765feb9f",
    "testharness"
   ],
   "html/semantics/scripting-1/the-script-element/execution-timing/078.html": [
    "71244598c4d1c581fb0b8c9a9eb1b19f642428f7",
    "testharness"
   ],
   "html/semantics/scripting-1/the-script-element/execution-timing/079.html": [
    "c8ad7d7bd5cdfbd52d4250b43df2abd4f06320e5",
@@ -582807,16 +583093,20 @@
   "html/webappapis/user-prompts/printing/.gitkeep": [
    "da39a3ee5e6b4b0d3255bfef95601890afd80709",
    "support"
   ],
   "html/webappapis/user-prompts/simple-dialogs/.gitkeep": [
    "da39a3ee5e6b4b0d3255bfef95601890afd80709",
    "support"
   ],
+  "imagebitmap-renderingcontext/OWNERS": [
+   "07a7da62ef2c26f061cfc076d268f06a0cdcf72b",
+   "support"
+  ],
   "imagebitmap-renderingcontext/bitmaprenderer-as-imagesource.html": [
    "f80d1496329c64643d2b40e478779929de20c499",
    "testharness"
   ],
   "imagebitmap-renderingcontext/context-creation-with-alpha.html": [
    "a7fc0b2af3b6d824ea86ebe96de07c947938e4c0",
    "testharness"
   ],
@@ -584927,16 +585217,20 @@
   "mediacapture-fromelement/ended.html": [
    "c816abe4302804f8e3ba65bad23443812111acb1",
    "testharness"
   ],
   "mediacapture-fromelement/idlharness.html": [
    "ceeb48e7982eb88561f4c1630cb0fcf15d9cf73c",
    "testharness"
   ],
+  "mediacapture-image/OWNERS": [
+   "d53c2a28adfbc343850c3362a180bba24ae63609",
+   "support"
+  ],
   "mediacapture-image/idlharness.html": [
    "7ccf7fcab0344a2e1893e89d7689e2312287b64d",
    "testharness"
   ],
   "mediacapture-record/BlobEvent-constructor.html": [
    "29d5649ff97ca0631f8c841425a88248525f9774",
    "testharness"
   ],
@@ -586943,16 +587237,20 @@
   "notifications/tag-different-manual.html": [
    "17b9a1a612b9f479786ce55119e5cc4c0ce5c7a8",
    "manual"
   ],
   "notifications/tag-same-manual.html": [
    "f31d50ead567908aab1d1dad577eb06c912c97f3",
    "manual"
   ],
+  "offscreen-canvas/OWNERS": [
+   "07a7da62ef2c26f061cfc076d268f06a0cdcf72b",
+   "support"
+  ],
   "offscreen-canvas/compositing/2d.composite.canvas.copy.html": [
    "983ae27346b47cd73000d9e3cd2264e3233705d1",
    "testharness"
   ],
   "offscreen-canvas/compositing/2d.composite.canvas.copy.worker.js": [
    "24a3f240a69897adda2afbec4267f8b0c1a6dbe0",
    "testharness"
   ],
@@ -593304,17 +593602,17 @@
    "20d0e6d34a096a9e8c59a8e6ae7d0bfeaa0e6344",
    "support"
   ],
   "payment-request/OWNERS": [
    "60cf95eb7b2e583315fe8539d2caf38667412d19",
    "support"
   ],
   "payment-request/PaymentAddress/attributes-and-toJSON-method-manual.https.html": [
-   "1a2bf6b62a5021eec649c0919a35574779e30d09",
+   "46cac5ba402841a8bc311b1a11de4a82589ee567",
    "manual"
   ],
   "payment-request/PaymentItem/type_member.https.html": [
    "5568f96eac7b0ebf1d91d468bb30b9eaa8b1a9f0",
    "testharness"
   ],
   "payment-request/PaymentRequestUpdateEvent/constructor.http.html": [
    "017f1f1aca43171083833ddb27ff66e39902e85d",
@@ -603175,16 +603473,20 @@
   "selection/test-iframe.html": [
    "3803c785b4a2fe2bbf9ecb895e6d3e1ae9e40164",
    "support"
   ],
   "selection/type.html": [
    "01ae6e757d428800555012783e290ebba575bcab",
    "testharness"
   ],
+  "server-timing/OWNERS": [
+   "99acae581c71af3f6306bff62a1172955e83f756",
+   "support"
+  ],
   "server-timing/cross_origin.html": [
    "e20e60bef34167b4608a837d0ddb311effa20773",
    "testharness"
   ],
   "server-timing/navigation_timing_idl.html": [
    "191f42a92f0ac135de816275920e54fa50065b15",
    "testharness"
   ],
@@ -603904,17 +604206,17 @@
    "10f756bbf749b7ad8f7c6eb4efe752ee79c44b4a",
    "testharness"
   ],
   "server-timing/service_worker_idl.html": [
    "cb5ea3136399f88fb6c4e8071ad8e3b7ccebb242",
    "testharness"
   ],
   "server-timing/sw.js": [
-   "0c12328f152814e2f0bde7fe026cf12c8ea77ff0",
+   "47e2601a903e34c2c8afc90e544026e8c6d11d05",
    "support"
   ],
   "server-timing/test_server_timing.html": [
    "7c778ca856e5cff0bbc785f59c9ccf1ec86456fb",
    "testharness"
   ],
   "server-timing/test_server_timing.html.sub.headers": [
    "77000d65537ef522a3471002118a120d2faf296a",
@@ -606404,17 +606706,17 @@
    "f728caa68d0112033fb599880404811b17596396",
    "testharness"
   ],
   "shadow-dom/event-with-related-target.html": [
    "572ddb9624ba8871d93cb13fad830f1acc8d4cac",
    "testharness"
   ],
   "shadow-dom/form-control-form-attribute.html": [
-   "79b4a278f0e35646cfdffeebf8f0523e2772bc9b",
+   "7726f8fe9056d3d5c9fb7b963c4bc6e777a8256a",
    "testharness"
   ],
   "shadow-dom/historical.html": [
    "1469992db34a25397dc3d5a5e1eb600e8afcf71b",
    "testharness"
   ],
   "shadow-dom/layout-slot-no-longer-assigned.html": [
    "224a688177941774e0bd3be74cb4aef20160d903",
@@ -612963,16 +613265,20 @@
   "wake-lock/wakelock-type.https.html": [
    "583647213b49b7bc67cad08192db3e6abcd1de9f",
    "testharness"
   ],
   "wake-lock/wakelockrequest-is-independent.https.html": [
    "caaf2634451eb9228c0b20f0ac817d7a3d3fa685",
    "testharness"
   ],
+  "wasm/OWNERS": [
+   "e4284c7c52365b575b20abcf696a5634ef86b769",
+   "support"
+  ],
   "wasm/compile_worker.js": [
    "652193b876206d7a0f361f145469a604d03e3784",
    "support"
   ],
   "wasm/create_multiple_memory.worker.js": [
    "893d408fc56d030416a3c89ae3680dc028ecf1d7",
    "testharness"
   ],
@@ -614927,16 +615233,20 @@
   "webdriver/tests/switch_to_parent_frame/__init__.py": [
    "da39a3ee5e6b4b0d3255bfef95601890afd80709",
    "support"
   ],
   "webdriver/tests/switch_to_parent_frame/switch.py": [
    "c184952fc8a78f5edc7d573a9f8440b5eec334cc",
    "wdspec"
   ],
+  "webgl/OWNERS": [
+   "0df8e72f3ab70719aac73d3b06f043fac629939b",
+   "support"
+  ],
   "webgl/bufferSubData.html": [
    "526612470551a0eb157b310c587d50080087808d",
    "testharness"
   ],
   "webgl/common.js": [
    "a671260c24103c4b3d41d57251e691caa286ace4",
    "support"
   ],
@@ -615479,16 +615789,20 @@
   "webrtc/RTCIceCandidate-constructor.html": [
    "6938c88a0167e418aa9e93416865c857cc3489c5",
    "testharness"
   ],
   "webrtc/RTCIceTransport.html": [
    "db758cc2a744c049c291575e408dbb5f280cdf19",
    "testharness"
   ],
+  "webrtc/RTCPeerConnection-add-track-no-deadlock.https.html": [
+   "a16eaf6805528c2b73650694f26568f41be5d5ff",
+   "testharness"
+  ],
   "webrtc/RTCPeerConnection-addIceCandidate.html": [
    "dd19f1d7a8d12ee85101e53bb30c553e94d67b6a",
    "testharness"
   ],
   "webrtc/RTCPeerConnection-addTrack.https.html": [
    "c434d2cdcb134f28b203df07cecca04e11195700",
    "testharness"
   ],
@@ -615620,17 +615934,17 @@
    "6614b5b0febd718a94bbec110568b9aaf80dc9eb",
    "testharness"
   ],
   "webrtc/RTCPeerConnection-setRemoteDescription-tracks.https.html": [
    "16fe3b155e55d1b66181788c93e570b36e5cc67d",
    "testharness"
   ],
   "webrtc/RTCPeerConnection-setRemoteDescription.html": [
-   "59f826d4d23cd52f15f3940cc3abe66933e91037",
+   "8a3e2f1e157e1ceed18ac66e57040a941b658f24",
    "testharness"
   ],
   "webrtc/RTCPeerConnection-track-stats.https.html": [
    "c31d22b6d9322d6d9b12414682d04137ad8cfb5d",
    "testharness"
   ],
   "webrtc/RTCPeerConnectionIceEvent-constructor.html": [
    "f273bd7fdfc883a15e8fb16fef5309061254c6cc",
@@ -619988,17 +620302,17 @@
    "339f3f5f0e968076031f4a7c4361f9dba14d485d",
    "support"
   ],
   "webxr/OWNERS": [
    "13f9441d368248225df5adc3396a05061ba2924d",
    "support"
   ],
   "webxr/interfaces.https.html": [
-   "22e2278ab484ced51fd0a3880d2bfbf610650371",
+   "b723ad7105c7e5c12ba5fe75ceac3de3d990dfe2",
    "testharness"
   ],
   "webxr/resources/webxr_check.html": [
    "3fa96e39a073bda2e813f0b2c2411c26b73d374e",
    "support"
   ],
   "webxr/resources/webxr_util.js": [
    "df6490ff3370ea580015f6b02ef5ec7ef2bb1e2b",
@@ -620708,29 +621022,29 @@
    "6bffa3be83d81e2faa93119e710e4fee93fb855e",
    "testharness"
   ],
   "workers/modules/dedicated-worker-import-meta.html": [
    "32cd3419ff904a2440d9a6eaa7cb28f78d4a7e32",
    "testharness"
   ],
   "workers/modules/dedicated-worker-import.html": [
-   "95973c682b79618cc894417a4f89a758e7e8c7b1",
+   "b9f5a7a0384ac3f34c48f32a378b758880f59b06",
    "testharness"
   ],
   "workers/modules/dedicated-worker-options-type.html": [
    "9f6f1be759beb885e2baa746e36ace83685f649b",
    "testharness"
   ],
   "workers/modules/resources/dynamic-import-and-then-static-import-worker.js": [
    "f4df69196f64cd81e92705186325004ac94db659",
    "support"
   ],
   "workers/modules/resources/dynamic-import-given-url-worker.js": [
-   "372686abd1b2d2d09228a44f6420c646319e0bd1",
+   "0d2cfe16d71bb17577c6cf6cde5973c18c2c70fe",
    "support"
   ],
   "workers/modules/resources/dynamic-import-worker.js": [
    "444e313fe51923097e3672d88d0afd30aac5ecab",
    "support"
   ],
   "workers/modules/resources/empty-worker.js": [
    "84b3339c3419e318803e51f46d7252d9e8ac183b",
@@ -620755,16 +621069,20 @@
   "workers/modules/resources/post-message-on-load-worker.js": [
    "c67a79ade775435a67e5999d17e7cdda450c8e50",
    "support"
   ],
   "workers/modules/resources/static-import-and-then-dynamic-import-worker.js": [
    "f69987442b6a223a868e6c1a7ca6d9cee2976068",
    "support"
   ],
+  "workers/modules/resources/static-import-non-existent-script-worker.js": [
+   "e8e1f0aedcc780aac742af01387dd151b10104bc",
+   "support"
+  ],
   "workers/modules/resources/static-import-worker.js": [
    "6d5fb2c553d2f32cdd16722a85bd65e0a172768c",
    "support"
   ],
   "workers/name-property.html": [
    "1c53fc1fdc2d6c8ed5592d832a18bdbd3bca541b",
    "testharness"
   ],
@@ -621139,16 +621457,20 @@
   "workers/support/shared-name.js": [
    "671ecb263ceb15ca28ef9cfc2ee6efa6e08a70fb",
    "support"
   ],
   "workers/worker-performance.worker.js": [
    "c6a02eed61fe26e59252314cbbe090eeed9de9ca",
    "testharness"
   ],
+  "worklets/OWNERS": [
+   "e2dd08513daf7b513cf2db811841d50153999954",
+   "support"
+  ],
   "worklets/README.md": [
    "33a3530260a18e74dd96470d9890bbdb4ecdb08d",
    "support"
   ],
   "worklets/animation-worklet-credentials.https.html": [
    "4519b0a79879ead03e03fbd280699120ed17f06f",
    "testharness"
   ],
--- a/testing/web-platform/meta/cookies/secure/cookie-forcing.html.ini
+++ b/testing/web-platform/meta/cookies/secure/cookie-forcing.html.ini
@@ -1,8 +1,6 @@
 [cookie-forcing.html]
-  expected:
-    if debug and not webrender and not e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): TIMEOUT
   [non-secure origins should be able to force out insecure cookies.]
     expected:
       if debug and not webrender and not e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): TIMEOUT
       FAIL
 
--- a/testing/web-platform/meta/cookies/secure/create-cookie-http.html.ini
+++ b/testing/web-platform/meta/cookies/secure/create-cookie-http.html.ini
@@ -1,9 +1,7 @@
 [create-cookie-http.html]
-  expected:
-    if debug and not webrender and not e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): ERROR
   [Untitled]
     expected: FAIL
 
   [Secure cookies cannot be set by insecure origins]
     expected: FAIL
 
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-contain/contain-layout-006.html.ini
@@ -0,0 +1,2 @@
+[contain-layout-006.html]
+  expected: FAIL
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-contain/contain-layout-007.html.ini
@@ -0,0 +1,2 @@
+[contain-layout-007.html]
+  expected: FAIL
--- a/testing/web-platform/meta/custom-elements/CustomElementRegistry.html.ini
+++ b/testing/web-platform/meta/custom-elements/CustomElementRegistry.html.ini
@@ -6,8 +6,9 @@
   [customElements.define must rethrow an exception thrown while getting callbacks on the constructor prototype]
     expected: FAIL
 
   [customElements.define must rethrow an exception thrown while converting a callback value to Function callback type]
     expected: FAIL
 
   [customElements.define must get "observedAttributes" property on the constructor prototype when "attributeChangedCallback" is present]
     expected: FAIL
+
--- a/testing/web-platform/meta/fetch/security/dangling-markup-mitigation-data-url.tentative.sub.html.ini
+++ b/testing/web-platform/meta/fetch/security/dangling-markup-mitigation-data-url.tentative.sub.html.ini
@@ -1,10 +1,9 @@
 [dangling-markup-mitigation-data-url.tentative.sub.html]
-  expected: TIMEOUT
   [<iframe id="dangling"\\n        src="data:text/html,\\n            <img\\n              onload='window.parent.postMessage(&quot;loaded&quot;, &quot;*&quot;);'\\n              onerror='window.parent.postMessage(&quot;error&quot;, &quot;*&quot;);'\\n              src='http://web-platform.test:8000/images/green-256x256.png?&amp;amp;#10;&lt;'>\\n        ">\\n     </iframe>]
     expected: TIMEOUT
 
   [<iframe id="dangling"\\n        src="data:text/html,\\n            <img\\n              onload='window.parent.postMessage(&quot;loaded&quot;, &quot;*&quot;);'\\n              onerror='window.parent.postMessage(&quot;error&quot;, &quot;*&quot;);'\\n              src='http://web-platform.test:8000/images/gr&#10;een-256x256.png?&lt;'>\\n        ">\\n     </iframe>]
     expected: FAIL
 
   [<iframe id="dangling"\\n        src="     data:text/html,\\n            <img\\n              onload='window.parent.postMessage(&quot;loaded&quot;, &quot;*&quot;);'\\n              onerror='window.parent.postMessage(&quot;error&quot;, &quot;*&quot;);'\\n              src='http://web-platform.test:8000/images/gr&#10;een-256x256.png?&lt;'>\\n        ">\\n     </iframe>]
     expected: FAIL
--- a/testing/web-platform/meta/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/resume-timer-on-history-back.html.ini
+++ b/testing/web-platform/meta/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/resume-timer-on-history-back.html.ini
@@ -1,4 +1,3 @@
 [resume-timer-on-history-back.html]
   disabled:
     if os == "win": https://bugzilla.mozilla.org/show_bug.cgi?id=1321179
-
--- a/testing/web-platform/meta/html/dom/dynamic-markup-insertion/document-write/contentType.window.js.ini
+++ b/testing/web-platform/meta/html/dom/dynamic-markup-insertion/document-write/contentType.window.js.ini
@@ -1,9 +1,9 @@
 [contentType.window.html]
   expected:
     if debug and not webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): CRASH
     if debug and not webrender and not e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): CRASH
     if debug and not webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): CRASH
-    if debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): CRASH
+    if debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): TIMEOUT
     if debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): CRASH
     if debug and not webrender and e10s and (os == "win") and (version == "10.0.15063") and (processor == "x86_64") and (bits == 64): CRASH
     if debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): CRASH
--- a/testing/web-platform/meta/mozilla-sync
+++ b/testing/web-platform/meta/mozilla-sync
@@ -1,2 +1,2 @@
-local: 90d9d20d4e4d7a79509c9a13a07e63a463f89527
-upstream: b395fb03bf62cbda20a383842b8da1701080ec43
+local: f456831609d31619a99840f8079c894ff589dc06
+upstream: 43d7898e180de2585d39ab12e3fda81633725e91
--- a/testing/web-platform/meta/navigation-timing/nav2_test_redirect_server.html.ini
+++ b/testing/web-platform/meta/navigation-timing/nav2_test_redirect_server.html.ini
@@ -1,1 +1,2 @@
 prefs: [privacy.reduceTimerPrecision:false]
+[nav2_test_redirect_server.html]
deleted file mode 100644
--- a/testing/web-platform/meta/webdriver/tests/actions/key.py.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[key.py]
-  expected:
-    if debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): TIMEOUT
deleted file mode 100644
--- a/testing/web-platform/meta/webdriver/tests/close_window/close.py.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[close.py]
-  expected:
-    if debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): TIMEOUT
deleted file mode 100644
--- a/testing/web-platform/meta/webdriver/tests/delete_session/delete.py.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[delete.py]
-  expected:
-    if debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): TIMEOUT
--- a/testing/web-platform/meta/webdriver/tests/execute_async_script/collections.py.ini
+++ b/testing/web-platform/meta/webdriver/tests/execute_async_script/collections.py.ini
@@ -1,4 +1,5 @@
 [collections.py]
   [test_arguments]
     bug: 1453057
     expected: FAIL
+
deleted file mode 100644
--- a/testing/web-platform/meta/webdriver/tests/get_element_attribute/get.py.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[get.py]
-  expected:
-    if debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): TIMEOUT
--- a/testing/web-platform/meta/webxr/interfaces.https.html.ini
+++ b/testing/web-platform/meta/webxr/interfaces.https.html.ini
@@ -441,11 +441,8 @@
     expected: FAIL
 
   [XRCoordinateSystemEvent interface: attribute coordinateSystem]
     expected: FAIL
 
   [XRWebGLLayer interface: operation getViewport(XRView)]
     expected: FAIL
 
-  [Test IDL implementation of WebXR API]
-    expected: FAIL
-
--- a/testing/web-platform/meta/workers/modules/dedicated-worker-import.html.ini
+++ b/testing/web-platform/meta/workers/modules/dedicated-worker-import.html.ini
@@ -16,8 +16,17 @@
     expected: NOTRUN
 
   [Test dynamic import and then static import on DedicatedWorkerGlobalScope.]
     expected: NOTRUN
 
   [importScripts() on module worker should throw an exception.]
     expected: NOTRUN
 
+  [Worker construction for non-existent script should throw an exception.]
+    expected: NOTRUN
+
+  [Static import for non-existent script should throw an exception.]
+    expected: NOTRUN
+
+  [Dynamic import for non-existent script should throw an exception.]
+    expected: NOTRUN
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/2dcontext/OWNERS
@@ -0,0 +1,5 @@
+@AmeliaBR
+@annevk
+@kenrussell
+@jdashg
+@fserb
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/BackgroundSync/OWNERS
@@ -0,0 +1,1 @@
+@beverloo
--- a/testing/web-platform/tests/WebCryptoAPI/generateKey/successes.js
+++ b/testing/web-platform/tests/WebCryptoAPI/generateKey/successes.js
@@ -65,41 +65,23 @@ function run_test(algorithmNames, slowTe
                     assert_goodCryptoKey(result, algorithm, extractable, usages, "secret");
                 }
             }, function(err) {
                 assert_unreached("Threw an unexpected error: " + err.toString());
             });
         }, testTag + ": generateKey" + parameterString(algorithm, extractable, usages));
     }
 
-    // Only test a subset of tests with, e.g., ?1-10 in the URL
-    var subTestStart = 0;
-    var subTestEnd = Infinity;
-    var match;
-    if (location.search) {
-        match = /^\?(\d+)-(\d+|last)$/.exec(location.search);
-        if (match) {
-          subTestStart = match[1];
-          if (match[2] !== "last") {
-              subTestEnd = match[2];
-          }
-        }
-    }
-    var currentSubTest = 0;
-
     // Test all valid sets of parameters for successful
     // key generation.
     testVectors.forEach(function(vector) {
         allNameVariants(vector.name, slowTest).forEach(function(name) {
             allAlgorithmSpecifiersFor(name).forEach(function(algorithm) {
                 allValidUsages(vector.usages, false, vector.mandatoryUsages).forEach(function(usages) {
                     [false, true].forEach(function(extractable) {
-                        currentSubTest++;
-                        if (currentSubTest >= subTestStart && currentSubTest <= subTestEnd) {
-                            testSuccess(algorithm, extractable, usages, vector.resultType, "Success");
-                        }
+                        subsetTest(testSuccess, algorithm, extractable, usages, vector.resultType, "Success");
                     });
                 });
             });
         });
     });
 
 }
--- a/testing/web-platform/tests/WebCryptoAPI/generateKey/successes_AES-CBC.https.any.js
+++ b/testing/web-platform/tests/WebCryptoAPI/generateKey/successes_AES-CBC.https.any.js
@@ -1,4 +1,5 @@
 // META: timeout=long
 // META: script=../util/helpers.js
+// META: script=/common/subset-tests.js
 // META: script=successes.js
 run_test(["AES-CBC"]);
--- a/testing/web-platform/tests/WebCryptoAPI/generateKey/successes_AES-CTR.https.any.js
+++ b/testing/web-platform/tests/WebCryptoAPI/generateKey/successes_AES-CTR.https.any.js
@@ -1,4 +1,5 @@
 // META: timeout=long
 // META: script=../util/helpers.js
+// META: script=/common/subset-tests.js
 // META: script=successes.js
 run_test(["AES-CTR"]);
--- a/testing/web-platform/tests/WebCryptoAPI/generateKey/successes_AES-GCM.https.any.js
+++ b/testing/web-platform/tests/WebCryptoAPI/generateKey/successes_AES-GCM.https.any.js
@@ -1,4 +1,5 @@
 // META: timeout=long
 // META: script=../util/helpers.js
+// META: script=/common/subset-tests.js
 // META: script=successes.js
 run_test(["AES-GCM"]);
--- a/testing/web-platform/tests/WebCryptoAPI/generateKey/successes_AES-KW.https.any.js
+++ b/testing/web-platform/tests/WebCryptoAPI/generateKey/successes_AES-KW.https.any.js
@@ -1,4 +1,5 @@
 // META: timeout=long
 // META: script=../util/helpers.js
+// META: script=/common/subset-tests.js
 // META: script=successes.js
 run_test(["AES-KW"]);
--- a/testing/web-platform/tests/WebCryptoAPI/generateKey/successes_ECDH.https.any.js
+++ b/testing/web-platform/tests/WebCryptoAPI/generateKey/successes_ECDH.https.any.js
@@ -1,4 +1,5 @@
 // META: timeout=long
 // META: script=../util/helpers.js
+// META: script=/common/subset-tests.js
 // META: script=successes.js
 run_test(["ECDH"]);
--- a/testing/web-platform/tests/WebCryptoAPI/generateKey/successes_ECDSA.https.any.js
+++ b/testing/web-platform/tests/WebCryptoAPI/generateKey/successes_ECDSA.https.any.js
@@ -1,4 +1,5 @@
 // META: timeout=long
 // META: script=../util/helpers.js
+// META: script=/common/subset-tests.js
 // META: script=successes.js
 run_test(["ECDSA"]);
--- a/testing/web-platform/tests/WebCryptoAPI/generateKey/successes_HMAC.https.any.js
+++ b/testing/web-platform/tests/WebCryptoAPI/generateKey/successes_HMAC.https.any.js
@@ -1,4 +1,5 @@
 // META: timeout=long
 // META: script=../util/helpers.js
+// META: script=/common/subset-tests.js
 // META: script=successes.js
 run_test(["HMAC"]);
--- a/testing/web-platform/tests/WebCryptoAPI/generateKey/successes_RSA-OAEP.https.any.js
+++ b/testing/web-platform/tests/WebCryptoAPI/generateKey/successes_RSA-OAEP.https.any.js
@@ -11,10 +11,11 @@
 // META: variant=?91-100
 // META: variant=?101-110
 // META: variant=?111-120
 // META: variant=?121-130
 // META: variant=?131-140
 // META: variant=?141-150
 // META: variant=?151-last
 // META: script=../util/helpers.js
+// META: script=/common/subset-tests.js
 // META: script=successes.js
 run_test(["RSA-OAEP"]);
--- a/testing/web-platform/tests/WebCryptoAPI/generateKey/successes_RSA-PSS.https.any.js
+++ b/testing/web-platform/tests/WebCryptoAPI/generateKey/successes_RSA-PSS.https.any.js
@@ -1,8 +1,9 @@
 // META: timeout=long
 // META: variant=?1-10
 // META: variant=?11-20
 // META: variant=?21-30
 // META: variant=?31-last
 // META: script=../util/helpers.js
+// META: script=/common/subset-tests.js
 // META: script=successes.js
 run_test(["RSA-PSS"]);
--- a/testing/web-platform/tests/WebCryptoAPI/generateKey/successes_RSASSA-PKCS1-v1_5.https.any.js
+++ b/testing/web-platform/tests/WebCryptoAPI/generateKey/successes_RSASSA-PKCS1-v1_5.https.any.js
@@ -1,8 +1,9 @@
 // META: timeout=long
 // META: variant=?1-10
 // META: variant=?11-20
 // META: variant=?21-30
 // META: variant=?31-last
 // META: script=../util/helpers.js
+// META: script=/common/subset-tests.js
 // META: script=successes.js
 run_test(["RSASSA-PKCS1-v1_5"]);
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/apng/OWNERS
@@ -0,0 +1,3 @@
+@stuartparmenter
+@svgeesus
+@leonscroggins
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/audio-output/OWNERS
@@ -0,0 +1,1 @@
+@guidou
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/common/subset-tests.js
@@ -0,0 +1,33 @@
+// Only test a subset of tests with, e.g., ?1-10 in the URL.
+// Can be used together with <meta name="variant" content="...">
+// Sample usage:
+// for (const test of tests) {
+//   subsetTest(async_test, test.fn, test.name);
+// }
+(function() {
+  var subTestStart = 0;
+  var subTestEnd = Infinity;
+  var match;
+  if (location.search) {
+      match = /(?:^\?|&)(\d+)-(\d+|last)(?:&|$)/.exec(location.search);
+      if (match) {
+        subTestStart = parseInt(match[1], 10);
+        if (match[2] !== "last") {
+            subTestEnd = parseInt(match[2], 10);
+        }
+      }
+  }
+  function shouldRunSubTest(currentSubTest) {
+    return currentSubTest >= subTestStart && currentSubTest <= subTestEnd;
+  }
+  var currentSubTest = 0;
+  function subsetTest(testFunc, ...args) {
+    currentSubTest++;
+    if (shouldRunSubTest(currentSubTest)) {
+      return testFunc(...args);
+    }
+    return null;
+  }
+  self.shouldRunSubTest = shouldRunSubTest;
+  self.subsetTest = subsetTest;
+})();
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/OWNERS
@@ -0,0 +1,1 @@
+@mikewest
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-contain/contain-layout-006.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Containment Test: Layout containment absolutely positioned descendants</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-contain-1/#containment-layout">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<meta name=assert content="Layout containment makes an element to act as containing block for absolutely positioned descendants.">
+<style>
+#contain-layout {
+  contain: layout;
+  width: 100px;
+  height: 100px;
+  background: red;
+}
+
+#abspos {
+  position: absolute;
+  bottom: 0;
+  right: 0;
+  background: green;
+  width: 100px;
+  height: 100px;
+}
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div id="contain-layout">
+  <div id="abspos"></div>
+</div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-contain/contain-layout-007.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Containment Test: Layout containment fixed positioned descendants</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-contain-1/#containment-layout">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<meta name=assert content="Layout containment makes an element to act as containing block for fixed positioned descendants.">
+<style>
+#contain-layout {
+  contain: layout;
+  width: 100px;
+  height: 100px;
+  background: red;
+}
+
+#fixed {
+  position: fixed;
+  bottom: 0;
+  right: 0;
+  background: green;
+  width: 100px;
+  height: 100px;
+}
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div id="contain-layout">
+  <div id="fixed"></div>
+</div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-contain/contain-paint-009.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Containment Test: Paint containment absolutely positioned descendants</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-contain-1/#containment-paint">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<meta name=assert content="Paint containment makes an element to act as containing block for absolutely positioned descendants.">
+<style>
+#contain-paint {
+  contain: paint;
+  width: 100px;
+  height: 100px;
+  background: red;
+}
+
+#abspos {
+  position: absolute;
+  bottom: 0;
+  right: 0;
+  background: green;
+  width: 100px;
+  height: 100px;
+}
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div id="contain-paint">
+  <div id="abspos"></div>
+</div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-contain/contain-paint-010.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Containment Test: Paint containment fixed positioned descendants</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-contain-1/#containment-paint">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<meta name=assert content="Paint containment makes an element to act as containing block for fixed positioned descendants.">
+<style>
+#contain-paint {
+  contain: paint;
+  width: 100px;
+  height: 100px;
+  background: red;
+}
+
+#fixed {
+  position: fixed;
+  bottom: 0;
+  right: 0;
+  background: green;
+  width: 100px;
+  height: 100px;
+}
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div id="contain-paint">
+  <div id="fixed"></div>
+</div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-content/OWNERS
@@ -0,0 +1,1 @@
+@dauwhe
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-fill-stroke/OWNERS
@@ -0,0 +1,1 @@
+@tabatkins
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-font-loading/OWNERS
@@ -0,0 +1,2 @@
+@tabatkins
+@svgeesus
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/OWNERS
@@ -0,0 +1,2 @@
+@tabatkins
+@astearns
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-anchoring/OWNERS
@@ -0,0 +1,1 @@
+@tabatkins
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scroll-snap/OWNERS
@@ -0,0 +1,1 @@
+@tabatkins
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-shadow-parts/OWNERS
@@ -0,0 +1,1 @@
+@tabatkins
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-sizing/intrinsic-percent-non-replaced-004-ref.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<title>Reference</title>
+
+<style>
+  /* establish context */
+  .container {
+    clear: both;
+    padding: 10px;
+    width: 0;
+  }
+
+  span {
+    display: inline-block;
+    width: 20px;
+    height: 20px;
+    background-color: blue;
+  }
+
+  /* visualize size contribution */
+  .container > div {
+    float: left;
+    border: solid orange 20px;
+    border-style: none solid;
+  }
+  .container > div > div {
+    border-right: solid 20px aqua;
+    writing-mode: vertical-rl;
+    width: 40px;
+    height: 20px;
+  }
+
+  /* controls for min-width */
+  /* content = 100% = 80px = 4ch + border */
+  /* choose sizes that are larger than content to see if how they take effect */
+  .container > div > .raw-percent {
+    width: 120px;
+  }
+  .container > div > .calc-percent,
+  .container > div > .no-percent {
+    width: 160px;
+  }
+</style>
+
+<!-- calculating min-content widths -->
+<div class='container'>
+  <div><div class="control"><span></span></div></div>
+</div>
+<div class='container'>
+  <div style='width: 60px;'><div class="raw-percent"><span></span></div></div>
+</div>
+<div class='container'>
+  <div style='width: 60px;'><div class="calc-percent"><span></span></div></div>
+</div>
+<div class='container'>
+  <div><div class="no-percent"><span></span></div></div>
+</div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-sizing/intrinsic-percent-non-replaced-004.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<title>Percentages of width on non-replaced blocks are ignored for intrinsic sizing and resolved afterwards</title>
+<link rel="help" href="https://www.w3.org/TR/css-sizing-3/#intrinsic-contribution">
+<link rel="match" href="intrinsic-percent-non-replaced-004-ref.html">
+
+<style>
+  /* establish context */
+  .container {
+    clear: both;
+    padding: 10px;
+    width: 0;
+  }
+
+  span {
+    display: inline-block;
+    width: 20px;
+    height: 20px;
+    background-color: blue;
+  }
+
+  /* visualize size contribution */
+  .container > div {
+    float: left;
+    border: solid orange 20px;
+    border-style: none solid;
+  }
+  .container > div > div {
+    border-right: solid 20px aqua;
+    writing-mode: vertical-rl;
+    width: 40px;
+    height: 20px;
+  }
+
+  /* test width */
+  /* content = 100% = 60px = 40px + border */
+  /* choose sizes that are different than content to see if how they take effect */
+  .raw-percent {
+    min-width: 200%;
+  }
+  .calc-percent {
+    min-width: calc(160px + 0%);
+  }
+  .no-percent {
+    min-width: 160px;
+  }
+</style>
+
+<!-- calculating min-content widths -->
+<div class='container'>
+  <div><div class="control"><span></span></div></div>
+</div>
+<div class='container'>
+  <div><div class="raw-percent"><span></span></div></div>
+</div>
+<div class='container'>
+  <div><div class="calc-percent"><span></span></div></div>
+</div>
+<div class='container'>
+  <div><div class="no-percent"><span></span></div></div>
+</div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-sizing/intrinsic-percent-non-replaced-005-ref.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<title>Reference</title>
+
+<style>
+  /* establish context */
+  .container {
+    clear: both;
+    padding: 10px;
+    width: 0;
+  }
+
+  span {
+    display: inline-block;
+    width: 20px;
+    height: 20px;
+    background-color: blue;
+  }
+
+  /* visualize size contribution */
+  .container > div {
+    float: left;
+    border: solid orange 20px;
+    border-style: none solid;
+  }
+  .container > div > div {
+    border-right: solid 20px aqua;
+    writing-mode: vertical-rl;
+    width: 80px;
+    height: 20px;
+  }
+
+  /* test width */
+  /* content = 100% = 100px = 80px + border */
+  /* choose sizes that are different than content to see if how they take effect */
+  .container > div > .raw-percent {
+    width: 50px;
+  }
+  .container > div > .calc-percent,
+  .container > div > .no-percent {
+    width: 40px;
+  }
+</style>
+
+<!-- calculating min-content widths -->
+<div class='container'>
+  <div><div class="control"><span></span></div></div>
+</div>
+<div class='container'>
+  <div style='width: 100px;'><div class="raw-percent"><span></span></div></div>
+</div>
+<div class='container'>
+  <div style='width: 100px;'><div class="calc-percent"><span></span></div></div>
+</div>
+<div class='container'>
+  <div><div class="no-percent"><span></span></div></div>
+</div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-sizing/intrinsic-percent-non-replaced-005.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<title>Percentages of width on non-replaced blocks are ignored for intrinsic sizing and resolved afterwards</title>
+<link rel="help" href="https://www.w3.org/TR/css-sizing-3/#intrinsic-contribution">
+<link rel="match" href="intrinsic-percent-non-replaced-005-ref.html">
+
+<style>
+  /* establish context */
+  .container {
+    clear: both;
+    padding: 10px;
+    width: 0;
+  }
+
+  span {
+    display: inline-block;
+    width: 20px;
+    height: 20px;
+    background-color: blue;
+  }
+
+  /* visualize size contribution */
+  .container > div {
+    float: left;
+    border: solid orange 20px;
+    border-style: none solid;
+  }
+  .container > div > div {
+    border-right: solid 20px aqua;
+    writing-mode: vertical-rl;
+    width: 80px;
+    height: 20px;
+  }
+
+  /* test width */
+  /* content = 100% = 100px = 80px + border */
+  /* choose sizes that are different than content to see if how they take effect */
+  .raw-percent {
+    max-width: 50%;
+  }
+  .calc-percent {
+    max-width: calc(40px + 0%);
+  }
+  .no-percent {
+    max-width: 40px;
+  }
+</style>
+
+<!-- calculating min-content widths -->
+<div class='container'>
+  <div><div class="control"><span></span></div></div>
+</div>
+<div class='container'>
+  <div><div class="raw-percent"><span></span></div></div>
+</div>
+<div class='container'>
+  <div><div class="calc-percent"><span></span></div></div>
+</div>
+<div class='container'>
+  <div><div class="no-percent"><span></span></div></div>
+</div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/device-memory/OWNERS
@@ -0,0 +1,2 @@
+@tdresser
+@npm1
--- a/testing/web-platform/tests/docs/_writing-tests/testharness.md
+++ b/testing/web-platform/tests/docs/_writing-tests/testharness.md
@@ -14,16 +14,54 @@ documented in two sections:
     to the library and a detailed API reference.
 
   * [idlharness.js Documentation][idlharness] — A library for testing
      IDL interfaces using `testharness.js`.
 
 See [server features][] for advanced testing features that are commonly used
 with testharness.js. See also the [general guidelines][] for all test types.
 
+## Variants
+
+A test file can have multiple variants by including `meta` elements,
+for example:
+
+```
+<meta name="variant" content="">
+<meta name="variant" content="?wss">
+```
+
+The test can then do different things based on the URL.
+
+There is a utility script in `/common/subset-tests.js` that works
+well together with variants, where a test that would otherwise have
+too many tests to be useful can be split up in ranges of subtests.
+For example:
+
+```
+<!doctype html>
+<title>Testing variants</title>
+<meta name="variant" content="?1-1000">
+<meta name="variant" content="?1001-2000">
+<meta name="variant" content="?2001-last">
+<script src="/resources/testharness.js">
+<script src="/resources/testharnessreport.js">
+<script src="/common/subset-tests.js">
+<script>
+ const tests = [
+                 { fn: t => { ... }, name: "..." },
+                 ... lots of tests ...
+               ];
+ for (const test of tests) {
+   subsetTest(async_test, test.fn, test.name);
+ }
+</script>
+```
+
+
 ## Auto-generated test boilerplate
 
 While most JavaScript tests require a certain amount of HTML
 boilerplate to include the test library, etc., tests which are
 expressible purely in script (e.g. tests for workers) can have all the
 needed HTML and script boilerplate auto-generated.
 
 ### Standalone window tests
@@ -116,17 +154,17 @@ Use `// META: script=link/to/resource.js
     // META: script=resources/utils.js
 
 can be used to include both the global and a local `utils.js` in a test.
 
 ### Specifying a timeout of long in auto-generated boilerplate tests
 
 Use `// META: timeout=long` at the beginning of the resource.
 
-### Specifying test variants in auto-generated boilerplate tests
+### Specifying test [variants](#variants) in auto-generated boilerplate tests
 
 Use `// META: variant=url-suffix` at the beginning of the resource. For example,
 
     // META: variant=
     // META: variant=?wss
 
 
 [general guidelines]: {{ site.baseurl }}{% link _writing-tests/general-guidelines.md %}
--- a/testing/web-platform/tests/fetch/security/dangling-markup-mitigation-data-url.tentative.sub.html
+++ b/testing/web-platform/tests/fetch/security/dangling-markup-mitigation-data-url.tentative.sub.html
@@ -11,17 +11,18 @@
   // as a `srcdoc` attribute. Because we're injecting markup via `srcdoc`, we need to entity-escape
   // the content we'd like to treat as "raw" (e.g. `\n` => `&#10;`, `<` => `&lt;`), and
   // double-escape the "escaped" content.
   var rawBrace = "&lt;";
   var escapedBrace = "&amp;lt;";
   var doubleEscapedBrace = "&amp;amp;lt;";
   var rawNewline = "&#10;";
   var escapedNewline = "&amp;#10;";
-  var doubleEscapedNewline = "&amp;amp;#10;";
+  // doubleEscapedNewline is used inside a data URI, and so must have its '#' escaped.
+  var doubleEscapedNewline = "&amp;amp;%2310;";
 
   function appendFrameAndGetElement(test, frame) {
     return new Promise((resolve, reject) => {
       frame.onload = test.step_func(_ => {
         frame.onload = null;
         resolve(frame.contentDocument.querySelector('#dangling'));
       });
       document.body.appendChild(frame);
--- a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/077.html
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/execution-timing/077.html
@@ -12,22 +12,22 @@
                                 if (url) {
                                     props.src = url;
                                 }
                                 return testlib.addScript(contents, props, head, false);
                         }
                         var t = async_test()
 
                         function test() {
-                                var script = createScript('data:text\/javascript,log("Script #1 ran")');
+                                var script = createScript('data:text\/javascript,log("Script %231 ran")');
                                 var script2 = createScript('','log("Script #2 ran")');
                                 if(script2) {
                                     head.removeChild(script2);
                                 }
-                                var script3 = createScript('data:text\/javascript, log("Script #3 ran"); createScript(\'\', \'log("Script #4 ran")\')');
+                                var script3 = createScript('data:text\/javascript, log("Script %233 ran"); createScript(\'\', \'log("Script #4 ran")\')');
                                 if(script3) {
                                     head.removeChild(script3);
                                 }
                                 setTimeout(t.step_func(function(){
                                                assert_array_equals(eventOrder, ['Script #2 ran', 'Script #1 ran', 'Script #3 ran','Script #4 ran']);
                                                t.done();
                                            }), 400);
 
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/imagebitmap-renderingcontext/OWNERS
@@ -0,0 +1,5 @@
+@AmeliaBR
+@annevk
+@kenrussell
+@jdashg
+@fserb
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/mediacapture-image/OWNERS
@@ -0,0 +1,2 @@
+@yellowdoge
+@reillyeon
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/offscreen-canvas/OWNERS
@@ -0,0 +1,5 @@
+@AmeliaBR
+@annevk
+@kenrussell
+@jdashg
+@fserb
--- a/testing/web-platform/tests/payment-request/PaymentAddress/attributes-and-toJSON-method-manual.https.html
+++ b/testing/web-platform/tests/payment-request/PaymentAddress/attributes-and-toJSON-method-manual.https.html
@@ -63,17 +63,17 @@ function runManualTest(button, expected 
 <p>
   When prompted, please enter addresses as follows...
 </p>
 <ol>
   <li>
     <button onclick="
     const expectedAddress = {
       country: 'AU',
-      regionCode: 'AU-QLD',
+      regionCode: 'QLD',
       addressLine: '55 test st',
       city: 'Chapel Hill',
       dependentLocality: '',
       postalCode: '6095',
       sortingCode: '',
       languageCode: 'en',
       organization: 'w3c',
       recipient: 'web platform test',
@@ -85,18 +85,20 @@ function runManualTest(button, expected 
     Please use:
     <dl>
       <dt>Recipient:</dt>
       <dd>web platform test</dd>
       <dt>Address line:</dt>
       <dd>55 test st</dd>
       <dt>Country</dt>
       <dd>Australia</dd>
-      <dt>Suburb</dt>
+      <dt>City</dt>
       <dd>Chapel Hill</dd>
+      <dd>State/Region</dd>
+      <dd>Queensland</dd>
       <dt>postal code </dt>
       <dd>6095</dd>
       <dt>organization</dt>
       <dd>w3c</dd>
       <dt>Phone number</dt>
       <dd>+61 7 3378 0000</dd>
     </dl>
   </li>
--- a/testing/web-platform/tests/resources/chromium/chooser_service.mojom.js
+++ b/testing/web-platform/tests/resources/chromium/chooser_service.mojom.js
@@ -252,22 +252,22 @@
         break;
     }
     if (paramsClass === null)
       return validator.validationError.NONE;
     return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
   }
 
   var UsbChooserService = {
-    name: 'device::mojom::UsbChooserService',
+    name: 'device.mojom.UsbChooserService',
     kVersion: 0,
     ptrClass: UsbChooserServicePtr,
     proxyClass: UsbChooserServiceProxy,
     stubClass: UsbChooserServiceStub,
     validateRequest: validateUsbChooserServiceRequest,
     validateResponse: validateUsbChooserServiceResponse,
   };
   UsbChooserServiceStub.prototype.validator = validateUsbChooserServiceRequest;
   UsbChooserServiceProxy.prototype.validator = validateUsbChooserServiceResponse;
   exports.UsbChooserService = UsbChooserService;
   exports.UsbChooserServicePtr = UsbChooserServicePtr;
   exports.UsbChooserServiceAssociatedPtr = UsbChooserServiceAssociatedPtr;
-})();
\ No newline at end of file
+})();
--- a/testing/web-platform/tests/resources/chromium/device.mojom.js
+++ b/testing/web-platform/tests/resources/chromium/device.mojom.js
@@ -3390,17 +3390,17 @@
         break;
     }
     if (paramsClass === null)
       return validator.validationError.NONE;
     return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
   }
 
   var UsbDevice = {
-    name: 'device::mojom::UsbDevice',
+    name: 'device.mojom.UsbDevice',
     kVersion: 0,
     ptrClass: UsbDevicePtr,
     proxyClass: UsbDeviceProxy,
     stubClass: UsbDeviceStub,
     validateRequest: validateUsbDeviceRequest,
     validateResponse: validateUsbDeviceResponse,
   };
   UsbDeviceStub.prototype.validator = validateUsbDeviceRequest;
@@ -3416,9 +3416,9 @@
   exports.UsbInterfaceInfo = UsbInterfaceInfo;
   exports.UsbConfigurationInfo = UsbConfigurationInfo;
   exports.UsbDeviceInfo = UsbDeviceInfo;
   exports.UsbControlTransferParams = UsbControlTransferParams;
   exports.UsbIsochronousPacket = UsbIsochronousPacket;
   exports.UsbDevice = UsbDevice;
   exports.UsbDevicePtr = UsbDevicePtr;
   exports.UsbDeviceAssociatedPtr = UsbDeviceAssociatedPtr;
-})();
\ No newline at end of file
+})();
--- a/testing/web-platform/tests/resources/chromium/device_manager.mojom.js
+++ b/testing/web-platform/tests/resources/chromium/device_manager.mojom.js
@@ -697,17 +697,17 @@
         break;
     }
     if (paramsClass === null)
       return validator.validationError.NONE;
     return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
   }
 
   var UsbDeviceManager = {
-    name: 'device::mojom::UsbDeviceManager',
+    name: 'device.mojom.UsbDeviceManager',
     kVersion: 0,
     ptrClass: UsbDeviceManagerPtr,
     proxyClass: UsbDeviceManagerProxy,
     stubClass: UsbDeviceManagerStub,
     validateRequest: validateUsbDeviceManagerRequest,
     validateResponse: validateUsbDeviceManagerResponse,
   };
   UsbDeviceManagerStub.prototype.validator = validateUsbDeviceManagerRequest;
@@ -817,17 +817,17 @@
     return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
   }
 
   function validateUsbDeviceManagerClientResponse(messageValidator) {
     return validator.validationError.NONE;
   }
 
   var UsbDeviceManagerClient = {
-    name: 'device::mojom::UsbDeviceManagerClient',
+    name: 'device.mojom.UsbDeviceManagerClient',
     kVersion: 0,
     ptrClass: UsbDeviceManagerClientPtr,
     proxyClass: UsbDeviceManagerClientProxy,
     stubClass: UsbDeviceManagerClientStub,
     validateRequest: validateUsbDeviceManagerClientRequest,
     validateResponse: null,
   };
   UsbDeviceManagerClientStub.prototype.validator = validateUsbDeviceManagerClientRequest;
@@ -835,9 +835,9 @@
   exports.UsbDeviceFilter = UsbDeviceFilter;
   exports.UsbEnumerationOptions = UsbEnumerationOptions;
   exports.UsbDeviceManager = UsbDeviceManager;
   exports.UsbDeviceManagerPtr = UsbDeviceManagerPtr;
   exports.UsbDeviceManagerAssociatedPtr = UsbDeviceManagerAssociatedPtr;
   exports.UsbDeviceManagerClient = UsbDeviceManagerClient;
   exports.UsbDeviceManagerClientPtr = UsbDeviceManagerClientPtr;
   exports.UsbDeviceManagerClientAssociatedPtr = UsbDeviceManagerClientAssociatedPtr;
-})();
\ No newline at end of file
+})();
--- a/testing/web-platform/tests/resources/chromium/fake_bluetooth.mojom.js
+++ b/testing/web-platform/tests/resources/chromium/fake_bluetooth.mojom.js
@@ -4056,17 +4056,17 @@
         break;
     }
     if (paramsClass === null)
       return validator.validationError.NONE;
     return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
   }
 
   var FakeBluetooth = {
-    name: 'bluetooth::mojom::FakeBluetooth',
+    name: 'bluetooth.mojom.FakeBluetooth',
     kVersion: 0,
     ptrClass: FakeBluetoothPtr,
     proxyClass: FakeBluetoothProxy,
     stubClass: FakeBluetoothStub,
     validateRequest: validateFakeBluetoothRequest,
     validateResponse: validateFakeBluetoothResponse,
   };
   FakeBluetoothStub.prototype.validator = validateFakeBluetoothRequest;
@@ -5288,17 +5288,17 @@
         break;
     }
     if (paramsClass === null)
       return validator.validationError.NONE;
     return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
   }
 
   var FakeCentral = {
-    name: 'bluetooth::mojom::FakeCentral',
+    name: 'bluetooth.mojom.FakeCentral',
     kVersion: 0,
     ptrClass: FakeCentralPtr,
     proxyClass: FakeCentralProxy,
     stubClass: FakeCentralStub,
     validateRequest: validateFakeCentralRequest,
     validateResponse: validateFakeCentralResponse,
   };
   FakeCentralStub.prototype.validator = validateFakeCentralRequest;
@@ -5315,9 +5315,9 @@
   exports.ScanResult = ScanResult;
   exports.CharacteristicProperties = CharacteristicProperties;
   exports.FakeBluetooth = FakeBluetooth;
   exports.FakeBluetoothPtr = FakeBluetoothPtr;
   exports.FakeBluetoothAssociatedPtr = FakeBluetoothAssociatedPtr;
   exports.FakeCentral = FakeCentral;
   exports.FakeCentralPtr = FakeCentralPtr;
   exports.FakeCentralAssociatedPtr = FakeCentralAssociatedPtr;
-})();
\ No newline at end of file
+})();
--- a/testing/web-platform/tests/resources/chromium/fake_bluetooth_chooser.mojom.js
+++ b/testing/web-platform/tests/resources/chromium/fake_bluetooth_chooser.mojom.js
@@ -799,24 +799,24 @@
         break;
     }
     if (paramsClass === null)
       return validator.validationError.NONE;
     return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
   }
 
   var FakeBluetoothChooser = {
-    name: 'content::mojom::FakeBluetoothChooser',
+    name: 'content.mojom.FakeBluetoothChooser',
     kVersion: 0,
     ptrClass: FakeBluetoothChooserPtr,
     proxyClass: FakeBluetoothChooserProxy,
     stubClass: FakeBluetoothChooserStub,
     validateRequest: validateFakeBluetoothChooserRequest,
     validateResponse: validateFakeBluetoothChooserResponse,
   };
   FakeBluetoothChooserStub.prototype.validator = validateFakeBluetoothChooserRequest;
   FakeBluetoothChooserProxy.prototype.validator = validateFakeBluetoothChooserResponse;
   exports.ChooserEventType = ChooserEventType;
   exports.FakeBluetoothChooserEvent = FakeBluetoothChooserEvent;
   exports.FakeBluetoothChooser = FakeBluetoothChooser;
   exports.FakeBluetoothChooserPtr = FakeBluetoothChooserPtr;
   exports.FakeBluetoothChooserAssociatedPtr = FakeBluetoothChooserAssociatedPtr;
-})();
\ No newline at end of file
+})();
--- a/testing/web-platform/tests/resources/chromium/mojo_layouttest_test.mojom.js
+++ b/testing/web-platform/tests/resources/chromium/mojo_layouttest_test.mojom.js
@@ -243,22 +243,22 @@
         break;
     }
     if (paramsClass === null)
       return validator.validationError.NONE;
     return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
   }
 
   var MojoLayoutTestHelper = {
-    name: 'content::mojom::MojoLayoutTestHelper',
+    name: 'content.mojom.MojoLayoutTestHelper',
     kVersion: 0,
     ptrClass: MojoLayoutTestHelperPtr,
     proxyClass: MojoLayoutTestHelperProxy,
     stubClass: MojoLayoutTestHelperStub,
     validateRequest: validateMojoLayoutTestHelperRequest,
     validateResponse: validateMojoLayoutTestHelperResponse,
   };
   MojoLayoutTestHelperStub.prototype.validator = validateMojoLayoutTestHelperRequest;
   MojoLayoutTestHelperProxy.prototype.validator = validateMojoLayoutTestHelperResponse;
   exports.MojoLayoutTestHelper = MojoLayoutTestHelper;
   exports.MojoLayoutTestHelperPtr = MojoLayoutTestHelperPtr;
   exports.MojoLayoutTestHelperAssociatedPtr = MojoLayoutTestHelperAssociatedPtr;
-})();
\ No newline at end of file
+})();
--- a/testing/web-platform/tests/resources/chromium/sensor.mojom.js
+++ b/testing/web-platform/tests/resources/chromium/sensor.mojom.js
@@ -927,17 +927,17 @@
         break;
     }
     if (paramsClass === null)
       return validator.validationError.NONE;
     return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
   }
 
   var Sensor = {
-    name: 'device::mojom::Sensor',
+    name: 'device.mojom.Sensor',
     kVersion: 0,
     ptrClass: SensorPtr,
     proxyClass: SensorProxy,
     stubClass: SensorStub,
     validateRequest: validateSensorRequest,
     validateResponse: validateSensorResponse,
   };
   SensorStub.prototype.validator = validateSensorRequest;
@@ -1045,17 +1045,17 @@
     return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
   }
 
   function validateSensorClientResponse(messageValidator) {
     return validator.validationError.NONE;
   }
 
   var SensorClient = {
-    name: 'device::mojom::SensorClient',
+    name: 'device.mojom.SensorClient',
     kVersion: 0,
     ptrClass: SensorClientPtr,
     proxyClass: SensorClientProxy,
     stubClass: SensorClientStub,
     validateRequest: validateSensorClientRequest,
     validateResponse: null,
   };
   SensorClientStub.prototype.validator = validateSensorClientRequest;
@@ -1064,9 +1064,9 @@
   exports.ReportingMode = ReportingMode;
   exports.SensorConfiguration = SensorConfiguration;
   exports.Sensor = Sensor;
   exports.SensorPtr = SensorPtr;
   exports.SensorAssociatedPtr = SensorAssociatedPtr;
   exports.SensorClient = SensorClient;
   exports.SensorClientPtr = SensorClientPtr;
   exports.SensorClientAssociatedPtr = SensorClientAssociatedPtr;
-})();
\ No newline at end of file
+})();
--- a/testing/web-platform/tests/resources/chromium/sensor_provider.mojom.js
+++ b/testing/web-platform/tests/resources/chromium/sensor_provider.mojom.js
@@ -406,24 +406,24 @@
         break;
     }
     if (paramsClass === null)
       return validator.validationError.NONE;
     return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
   }
 
   var SensorProvider = {
-    name: 'device::mojom::SensorProvider',
+    name: 'device.mojom.SensorProvider',
     kVersion: 0,
     ptrClass: SensorProviderPtr,
     proxyClass: SensorProviderProxy,
     stubClass: SensorProviderStub,
     validateRequest: validateSensorProviderRequest,
     validateResponse: validateSensorProviderResponse,
   };
   SensorProviderStub.prototype.validator = validateSensorProviderRequest;
   SensorProviderProxy.prototype.validator = validateSensorProviderResponse;
   exports.SensorCreationResult = SensorCreationResult;
   exports.SensorInitParams = SensorInitParams;
   exports.SensorProvider = SensorProvider;
   exports.SensorProviderPtr = SensorProviderPtr;
   exports.SensorProviderAssociatedPtr = SensorProviderAssociatedPtr;
-})();
\ No newline at end of file
+})();
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/server-timing/OWNERS
@@ -0,0 +1,1 @@
+@igrigorik
--- a/testing/web-platform/tests/server-timing/sw.js
+++ b/testing/web-platform/tests/server-timing/sw.js
@@ -1,10 +1,19 @@
 importScripts('/resources/testharness.js')
 
-promise_test(async (test) => {
-  return fetch('./sw.js').then(function(response) {
-    assert_not_equals(typeof performance.getEntriesByName(response.url)[0].serverTiming,
-      'undefined',
-      'An instance of `PerformanceResourceTiming` should have a `serverTiming` attribute in the Service Worker context.')
-    done()
+promise_test((test) => {
+  return fetch('./sw.js').then((response) => {
+    return new Promise((resolve, reject) => {
+      step_timeout(() => {
+        const entry = performance.getEntriesByName(response.url)[0]
+        if (!entry) {
+          reject('no entry: ' + response.url)
+        }
+
+        assert_not_equals(typeof entry.serverTiming,
+          'undefined',
+          'An instance of `PerformanceResourceTiming` should have a `serverTiming` attribute in the Service Worker context.')
+        resolve()
+      }, 250)
+    })
   })
 })
--- a/testing/web-platform/tests/tools/wpt/browser.py
+++ b/testing/web-platform/tests/tools/wpt/browser.py
@@ -1,14 +1,15 @@
 import logging
 import os
 import platform
 import re
 import shutil
 import stat
+import subprocess
 import sys
 import tempfile
 from abc import ABCMeta, abstractmethod
 from ConfigParser import RawConfigParser
 from datetime import datetime, timedelta
 from distutils.spawn import find_executable
 from io import BytesIO
 
@@ -201,17 +202,16 @@ class Firefox(Browser):
                 # but to do better we need the actual build revision, which we
                 # can get if we have an application.ini file
                 tag = "tip"
 
         return "%s/archive/%s.zip/testing/profiles/" % (repo, tag)
 
     def install_prefs(self, binary, dest=None):
         version, channel = self.get_version_and_channel(binary)
-
         if dest is None:
             dest = os.pwd
 
         dest = os.path.join(dest, "profiles", channel, version)
         have_cache = False
         if os.path.exists(dest):
             if channel != "nightly":
                 have_cache = True
@@ -288,19 +288,28 @@ class Firefox(Browser):
 
 class Chrome(Browser):
     """Chrome-specific interface.
 
     Includes webdriver installation, and wptrunner setup methods.
     """
 
     product = "chrome"
-    binary = "/usr/bin/google-chrome"
     requirements = "requirements_chrome.txt"
 
+    @property
+    def binary(self):
+        if uname[0] == "Linux":
+            return "/usr/bin/google-chrome"
+        if uname[0] == "Darwin":
+            return "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
+        # TODO Windows?
+        logger.warn("Unable to find the browser binary.")
+        return None
+
     def install(self, dest=None):
         raise NotImplementedError
 
     def platform_string(self):
         platform = {
             "Linux": "linux",
             "Windows": "win",
             "Darwin": "mac"
@@ -334,19 +343,24 @@ class Chrome(Browser):
 
         path = find_executable("chromedriver", dest)
         st = os.stat(path)
         os.chmod(path, st.st_mode | stat.S_IEXEC)
         return path
 
     def version(self, binary=None):
         binary = binary or self.binary
-        version_string = call(binary, "--version").strip()
+        try:
+            version_string = call(binary, "--version").strip()
+        except subprocess.CalledProcessError:
+            logger.warn("Failed to call %s", binary)
+            return None
         m = re.match(r"Google Chrome (.*)", version_string)
         if not m:
+            logger.warn("Failed to extract version from: s%", version_string)
             return None
         return m.group(1)
 
 
 class ChromeAndroid(Browser):
     """Chrome-specific interface for Android.
 
     Includes webdriver installation.
@@ -374,19 +388,26 @@ class ChromeAndroid(Browser):
 
 class Opera(Browser):
     """Opera-specific interface.
 
     Includes webdriver installation, and wptrunner setup methods.
     """
 
     product = "opera"
-    binary = "/usr/bin/opera"
     requirements = "requirements_opera.txt"
 
+    @property
+    def binary(self):
+        if uname[0] == "Linux":
+            return "/usr/bin/opera"
+        # TODO Windows, Mac?
+        logger.warn("Unable to find the browser binary.")
+        return None
+
     def install(self, dest=None):
         raise NotImplementedError
 
     def platform_string(self):
         platform = {
             "Linux": "linux",
             "Windows": "win",
             "Darwin": "mac"
@@ -425,17 +446,21 @@ class Opera(Browser):
         path = find_executable("operadriver")
         st = os.stat(path)
         os.chmod(path, st.st_mode | stat.S_IEXEC)
         return path
 
     def version(self, binary):
         """Retrieve the release version of the installed browser."""
         binary = binary or self.binary
-        output = call(binary, "--version")
+        try:
+            output = call(binary, "--version")
+        except subprocess.CalledProcessError:
+            logger.warn("Failed to call %s", binary)
+            return None
         return re.search(r"[0-9\.]+( [a-z]+)?$", output.strip()).group(0)
 
 
 class Edge(Browser):
     """Edge-specific interface."""
 
     product = "edge"
     requirements = "requirements_edge.txt"
--- a/testing/web-platform/tests/tools/wpt/tests/test_wpt.py
+++ b/testing/web-platform/tests/tools/wpt/tests/test_wpt.py
@@ -8,16 +8,19 @@ import tempfile
 import time
 import urllib2
 
 import pytest
 
 from tools.wpt import wpt
 
 
+here = os.path.abspath(os.path.dirname(__file__))
+
+
 def is_port_8000_in_use():
     s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     try:
         s.bind(("127.0.0.1", 8000))
     except socket.error as e:
         if e.errno == errno.EADDRINUSE:
             return True
         else:
@@ -59,16 +62,78 @@ def test_help():
     # TODO: It seems like there's a bug in argparse that makes this argument order required
     # should try to work around that
     with pytest.raises(SystemExit) as excinfo:
         wpt.main(argv=["--help"])
     assert excinfo.value.code == 0
 
 
 @pytest.mark.slow
+def test_list_tests(manifest_dir):
+    """The `--list-tests` option should not produce an error under normal
+    conditions."""
+
+    with pytest.raises(SystemExit) as excinfo:
+        wpt.main(argv=["run", "--metadata", manifest_dir, "--list-tests",
+                       "--yes", "chrome", "/dom/nodes/Element-tagName.html"])
+    assert excinfo.value.code == 0
+
+
+@pytest.mark.slow
+def test_list_tests_missing_manifest(manifest_dir):
+    """The `--list-tests` option should not produce an error in the absence of
+    a test manifest file."""
+
+    os.remove(os.path.join(manifest_dir, "MANIFEST.json"))
+
+    with pytest.raises(SystemExit) as excinfo:
+        wpt.main(argv=["run",
+                       # This test triggers the creation of a new manifest
+                       # file which is not necessary to ensure successful
+                       # process completion. Specifying the current directory
+                       # as the tests source via the --tests` option
+                       # drastically reduces the time to execute the test.
+                       "--tests", here,
+                       "--metadata", manifest_dir,
+                       "--list-tests",
+                       "--yes",
+                       "firefox", "/dom/nodes/Element-tagName.html"])
+
+    assert excinfo.value.code == 0
+
+
+@pytest.mark.slow
+def test_list_tests_invalid_manifest(manifest_dir):
+    """The `--list-tests` option should not produce an error in the presence of
+    a malformed test manifest file."""
+
+    manifest_filename = os.path.join(manifest_dir, "MANIFEST.json")
+
+    assert os.path.isfile(manifest_filename)
+
+    with open(manifest_filename, "a+") as handle:
+        handle.write("extra text which invalidates the file")
+
+    with pytest.raises(SystemExit) as excinfo:
+        wpt.main(argv=["run",
+                       # This test triggers the creation of a new manifest
+                       # file which is not necessary to ensure successful
+                       # process completion. Specifying the current directory
+                       # as the tests source via the --tests` option
+                       # drastically reduces the time to execute the test.
+                       "--tests", here,
+                       "--metadata", manifest_dir,
+                       "--list-tests",
+                       "--yes",
+                       "firefox", "/dom/nodes/Element-tagName.html"])
+
+    assert excinfo.value.code == 0
+
+
+@pytest.mark.slow
 @pytest.mark.remote_network
 @pytest.mark.xfail(sys.platform == "win32",
                    reason="Tests currently don't work on Windows for path reasons")
 def test_run_firefox(manifest_dir):
     # TODO: It seems like there's a bug in argparse that makes this argument order required
     # should try to work around that
     if is_port_8000_in_use():
         pytest.skip("port 8000 already in use")
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/edge.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/edge.py
@@ -11,50 +11,66 @@ from ..executors.executoredge import Edg
                  "executor": {"testharness": "SeleniumTestharnessExecutor",
                               "reftest": "SeleniumRefTestExecutor",
                               "wdspec": "EdgeDriverWdspecExecutor"},
                  "browser_kwargs": "browser_kwargs",
                  "executor_kwargs": "executor_kwargs",
                  "env_extras": "env_extras",
                  "env_options": "env_options"}
 
+def get_timeout_multiplier(test_type, run_info_data, **kwargs):
+    if kwargs["timeout_multiplier"] is not None:
+        return kwargs["timeout_multiplier"]
+    if test_type == "wdspec":
+        return 10
+    return 1
 
 def check_args(**kwargs):
     require_arg(kwargs, "webdriver_binary")
 
 def browser_kwargs(test_type, run_info_data, **kwargs):
     return {"webdriver_binary": kwargs["webdriver_binary"],
-            "webdriver_args": kwargs.get("webdriver_args")}
+            "webdriver_args": kwargs.get("webdriver_args"),
+            "timeout_multiplier": get_timeout_multiplier(test_type,
+                                                         run_info_data,
+                                                         **kwargs)}
 
 def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
                     **kwargs):
     from selenium.webdriver import DesiredCapabilities
 
     executor_kwargs = base_executor_kwargs(test_type, server_config,
                                            cache_manager, **kwargs)
     executor_kwargs["close_after_done"] = True
+    executor_kwargs["timeout_multiplier"] = get_timeout_multiplier(test_type,
+                                                                   run_info_data,
+                                                                   **kwargs)
     executor_kwargs["capabilities"] = dict(DesiredCapabilities.EDGE.items())
     return executor_kwargs
 
 def env_extras(**kwargs):
     return []
 
 def env_options():
     return {"supports_debugger": False}
 
 class EdgeBrowser(Browser):
     used_ports = set()
+    init_timeout = 60
 
-    def __init__(self, logger, webdriver_binary, webdriver_args=None):
+    def __init__(self, logger, webdriver_binary, timeout_multiplier=None, webdriver_args=None):
         Browser.__init__(self, logger)
         self.server = EdgeDriverServer(self.logger,
                                        binary=webdriver_binary,
                                        args=webdriver_args)
         self.webdriver_host = "localhost"
         self.webdriver_port = self.server.port
+        if timeout_multiplier:
+            self.init_timeout = self.init_timeout * timeout_multiplier
+
 
     def start(self, **kwargs):
         print self.server.url
         self.server.start()
 
     def stop(self, force=False):
         self.server.stop(force=force)
 
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/testloader.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/testloader.py
@@ -411,20 +411,22 @@ class ManifestLoader(object):
             # TODO: make this not github-specific
             download_from_github(manifest_path, tests_path)
 
         if not recreate:
             try:
                 with open(manifest_path) as f:
                     json_data = json.load(f)
             except IOError:
-                #If the existing file doesn't exist just create one from scratch
-                pass
+                self.logger.info("Unable to find test manifest")
+            except ValueError:
+                self.logger.info("Unable to parse test manifest")
 
         if not json_data:
+            self.logger.info("Creating test manifest")
             manifest_file = manifest.Manifest(url_base)
         else:
             try:
                 manifest_file = manifest.Manifest.from_json(tests_path, json_data)
             except manifest.ManifestVersionMismatch:
                 manifest_file = manifest.Manifest(url_base)
 
         manifest_update.update(tests_path, manifest_file, True)
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/update/tree.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/update/tree.py
@@ -126,21 +126,21 @@ class HgTree(object):
             return True
         except subprocess.CalledProcessError:
             return False
 
 
 class GitTree(object):
     name = "git"
 
-    def __init__(self, root=None):
+    def __init__(self, root=None, log_error=True):
         if root is None:
-            root = git("rev-parse", "--show-toplevel").strip()
+            root = git("rev-parse", "--show-toplevel", log_error=log_error).strip()
         self.root = root
-        self.git = vcs.bind_to_repo(git, self.root)
+        self.git = vcs.bind_to_repo(git, self.root, log_error=log_error)
         self.message = None
         self.commit_cls = Commit
 
     def __getstate__(self):
         rv = self.__dict__.copy()
         del rv['git']
         return rv
 
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/vcs.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/vcs.py
@@ -22,28 +22,32 @@ def vcs(bin_name):
         proc_kwargs = {}
         if repo is not None:
             proc_kwargs["cwd"] = repo
 
         command_line = [bin_name, command] + args
         logger.debug(" ".join(command_line))
         try:
             return subprocess.check_output(command_line, stderr=subprocess.STDOUT, **proc_kwargs)
+        except OSError as e:
+            if log_error:
+                logger.error(e)
+            raise
         except subprocess.CalledProcessError as e:
             if log_error:
                 logger.error(e.output)
             raise
     return inner
 
 git = vcs("git")
 hg = vcs("hg")
 
 
-def bind_to_repo(vcs_func, repo):
-    return partial(vcs_func, repo=repo)
+def bind_to_repo(vcs_func, repo, log_error=True):
+    return partial(vcs_func, repo=repo, log_error=log_error)
 
 
-def is_git_root(path):
+def is_git_root(path, log_error=True):
     try:
-        rv = git("rev-parse", "--show-cdup", repo=path)
+        rv = git("rev-parse", "--show-cdup", repo=path, log_error=log_error)
     except subprocess.CalledProcessError:
         return False
     return rv == "\n"
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/wpttest.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/wpttest.py
@@ -1,9 +1,10 @@
 import os
+import subprocess
 from collections import defaultdict
 
 from wptmanifest.parser import atoms
 
 atom_reset = atoms["Reset"]
 enabled_tests = set(["testharness", "reftest", "wdspec"])
 
 
@@ -62,19 +63,28 @@ class WdspecSubtestResult(SubtestResult)
 
 def get_run_info(metadata_root, product, **kwargs):
     return RunInfo(metadata_root, product, **kwargs)
 
 
 class RunInfo(dict):
     def __init__(self, metadata_root, product, debug, browser_version=None, extras=None):
         import mozinfo
-
         self._update_mozinfo(metadata_root)
         self.update(mozinfo.info)
+
+        from update.tree import GitTree
+        try:
+            # GitTree.__init__ throws if we are not in a git tree.
+            rev = GitTree(log_error=False).rev
+        except (OSError, subprocess.CalledProcessError):
+            rev = None
+        if rev:
+            self["revision"] = rev
+
         self["product"] = product
         if debug is not None:
             self["debug"] = debug
         elif "debug" not in self:
             # Default to release
             self["debug"] = False
         if product == "firefox" and "stylo" not in self:
             self["stylo"] = False
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/wasm/OWNERS
@@ -0,0 +1,2 @@
+@titzer
+@Ms2ger
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/webgl/OWNERS
@@ -0,0 +1,1 @@
+@kenrussell
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/webrtc/RTCPeerConnection-add-track-no-deadlock.https.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCPeerConnection addTrack does not deadlock</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+  'use strict';
+
+  // This test sets up two peer connections using a sequence of operations
+  // that triggered a deadlock in Chrome. See https://crbug.com/736725.
+  // If a deadlock is introduced again, this test times out.
+  promise_test(async t => {
+    const pc1 = new RTCPeerConnection();
+    t.add_cleanup(() => pc1.close());
+    const stream = await navigator.mediaDevices.getUserMedia(
+      {audio: false, video: true});
+    const videoTrack = stream.getVideoTracks()[0];
+    t.add_cleanup(() => videoTrack.stop());
+    pc1.addTrack(videoTrack, stream);
+    const offer = await pc1.createOffer();
+    await pc1.setLocalDescription(offer);
+    const pc2 = new RTCPeerConnection();
+    t.add_cleanup(() => pc2.close());
+    const srdPromise = pc2.setRemoteDescription(offer);
+    pc2.addTrack(videoTrack, stream);
+    // The deadlock encountered in https://crbug.com/736725 occured here.
+    await srdPromise;
+    await pc2.createAnswer();
+  }, 'RTCPeerConnection addTrack does not deadlock.');
+</script>
--- a/testing/web-platform/tests/webxr/interfaces.https.html
+++ b/testing/web-platform/tests/webxr/interfaces.https.html
@@ -12,14 +12,15 @@
 promise_test(async () => {
   const idl_array = new IdlArray();
   const dom_idl = await fetch("/interfaces/dom.idl").then(r => r.text());
   const webxr_idl = await fetch("/interfaces/webxr.idl").then(r => r.text());
 
   idl_array.add_untested_idls(dom_idl);
   idl_array.add_untested_idls("interface Navigator {};");
   idl_array.add_idls(webxr_idl);
+  idl_array.add_idls("dictionary WebGLContextAttributes {};");
   idl_array.add_objects({
     Navigator:['navigator'],
   });
   idl_array.test();
 }, "Test IDL implementation of WebXR API");
 </script>
--- a/testing/web-platform/tests/workers/modules/dedicated-worker-import.html
+++ b/testing/web-platform/tests/workers/modules/dedicated-worker-import.html
@@ -47,9 +47,30 @@ promise_test(() => {
 
 promise_test(() => {
   const scriptURL = 'resources/import-scripts-worker.js';
   const worker = new Worker(scriptURL, { type: 'module' });
   return (new Promise(resolve => worker.onmessage = resolve))
       .then(e => assert_equals(e.data, 'TypeError'));
 }, 'importScripts() on module worker should throw an exception.');
 
+promise_test(() => {
+  const scriptURL = 'resources/non-existent-worker.js';
+  const worker = new Worker(scriptURL, { type: 'module' });
+  return new Promise(resolve => worker.onerror = resolve);
+}, 'Worker construction for non-existent script should throw an exception.');
+
+promise_test(() => {
+  const scriptURL = 'resources/static-import-non-existent-script-worker.js';
+  const worker = new Worker(scriptURL, { type: 'module' });
+  return new Promise(resolve => worker.onerror = resolve);
+}, 'Static import for non-existent script should throw an exception.');
+
+promise_test(() => {
+  const script_url = './non-existent-worker.js';
+  const worker = new Worker('resources/dynamic-import-given-url-worker.js',
+                            { type: 'module' });
+  worker.postMessage(script_url);
+  return new Promise(resolve => worker.onmessage = resolve)
+      .then(msg_event => assert_equals(msg_event.data, 'ERROR'));
+}, 'Dynamic import for non-existent script should throw an exception.');
+
 </script>
--- a/testing/web-platform/tests/workers/modules/resources/dynamic-import-given-url-worker.js
+++ b/testing/web-platform/tests/workers/modules/resources/dynamic-import-given-url-worker.js
@@ -1,4 +1,4 @@
 // Dynamically import the script URL sent by postMessage().
 self.addEventListener('message', e => {
-  import(e.data);
+  import(e.data).catch(error_event => postMessage('ERROR'));
 });
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/static-import-non-existent-script-worker.js
@@ -0,0 +1,1 @@
+import './non-existent-script.js';
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/worklets/OWNERS
@@ -0,0 +1,1 @@
+@bfgeek