diff options
Diffstat (limited to 'remote')
140 files changed, 5029 insertions, 2900 deletions
diff --git a/remote/doc/marionette/SeleniumAtoms.md b/remote/doc/marionette/SeleniumAtoms.md index 9f25af46cc..be65cb315e 100644 --- a/remote/doc/marionette/SeleniumAtoms.md +++ b/remote/doc/marionette/SeleniumAtoms.md @@ -10,7 +10,6 @@ Currently the following atoms are in use: - `getElementText` - `isElementDisplayed` -- `isElementEnabled` To use one of those atoms Javascript modules will have to import [atom.sys.mjs]. @@ -56,7 +55,6 @@ commands. Make sure to [install bazelisk] first. ```bash bazel build //javascript/atoms/fragments:get-text bazel build //javascript/atoms/fragments:is-displayed -bazel build //javascript/atoms/fragments:is-enabled ``` For each of the exported atoms a file can now be found in the folder diff --git a/remote/jar.mn b/remote/jar.mn index 7d08451a6f..7431951644 100644 --- a/remote/jar.mn +++ b/remote/jar.mn @@ -23,7 +23,10 @@ remote.jar: content/shared/MobileTabBrowser.sys.mjs (shared/MobileTabBrowser.sys.mjs) content/shared/Navigate.sys.mjs (shared/Navigate.sys.mjs) content/shared/NavigationManager.sys.mjs (shared/NavigationManager.sys.mjs) + content/shared/NetworkRequest.sys.mjs (shared/NetworkRequest.sys.mjs) + content/shared/NetworkResponse.sys.mjs (shared/NetworkResponse.sys.mjs) content/shared/PDF.sys.mjs (shared/PDF.sys.mjs) + content/shared/Permissions.sys.mjs (shared/Permissions.sys.mjs) content/shared/Prompt.sys.mjs (shared/Prompt.sys.mjs) content/shared/Realm.sys.mjs (shared/Realm.sys.mjs) content/shared/RecommendedPreferences.sys.mjs (shared/RecommendedPreferences.sys.mjs) @@ -69,6 +72,7 @@ remote.jar: content/shared/messagehandler/transports/RootTransport.sys.mjs (shared/messagehandler/transports/RootTransport.sys.mjs) # shared modules (WebDriver HTTP / BiDi only) + content/shared/webdriver/Accessibility.sys.mjs (shared/webdriver/Accessibility.sys.mjs) content/shared/webdriver/Actions.sys.mjs (shared/webdriver/Actions.sys.mjs) content/shared/webdriver/Assert.sys.mjs (shared/webdriver/Assert.sys.mjs) content/shared/webdriver/Capabilities.sys.mjs (shared/webdriver/Capabilities.sys.mjs) diff --git a/remote/marionette/actors/MarionetteCommandsChild.sys.mjs b/remote/marionette/actors/MarionetteCommandsChild.sys.mjs index d454e03fb0..e4db24b1e6 100644 --- a/remote/marionette/actors/MarionetteCommandsChild.sys.mjs +++ b/remote/marionette/actors/MarionetteCommandsChild.sys.mjs @@ -7,7 +7,8 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { - accessibility: "chrome://remote/content/marionette/accessibility.sys.mjs", + accessibility: + "chrome://remote/content/shared/webdriver/Accessibility.sys.mjs", action: "chrome://remote/content/shared/webdriver/Actions.sys.mjs", atom: "chrome://remote/content/marionette/atom.sys.mjs", dom: "chrome://remote/content/shared/DOM.sys.mjs", @@ -282,17 +283,7 @@ export class MarionetteCommandsChild extends JSWindowActorChild { async getComputedLabel(options = {}) { const { elem } = options; - const accessible = await lazy.accessibility.getAccessible(elem); - if (!accessible) { - return ""; - } - - // If name is null (absent), expose the empty string. - if (accessible.name === null) { - return ""; - } - - return accessible.name; + return lazy.accessibility.getAccessibleName(elem); } /** @@ -301,13 +292,7 @@ export class MarionetteCommandsChild extends JSWindowActorChild { async getComputedRole(options = {}) { const { elem } = options; - const accessible = await lazy.accessibility.getAccessible(elem); - if (!accessible) { - // If it's not in the a11y tree, it's probably presentational. - return "none"; - } - - return accessible.computedARIARole; + return lazy.accessibility.getComputedRole(elem); } /** diff --git a/remote/marionette/atom.sys.mjs b/remote/marionette/atom.sys.mjs index d065849d43..35840cac24 100644 --- a/remote/marionette/atom.sys.mjs +++ b/remote/marionette/atom.sys.mjs @@ -25,11 +25,10 @@ export const atom = {}; // Follow the instructions to export all the atoms: // https://firefox-source-docs.mozilla.org/testing/marionette/SeleniumAtoms.html // -// Built from SHA1: bd5cbe5b3a3e60b5970d8168474dd69a996c392c +// Built from SHA1: 33c6b7841a59aaaad55744909c0600f066fd5593 const ATOMS = { - getVisibleText: "function(){return (function(){var k=this||self;function aa(a){return\"string\"==typeof a}function ba(a,b){a=a.split(\".\");var c=k;a[0]in c||\"undefined\"==typeof c.execScript||c.execScript(\"var \"+a[0]);for(var d;a.length&&(d=a.shift());)a.length||void 0===b?c[d]&&c[d]!==Object.prototype[d]?c=c[d]:c=c[d]={}:c[d]=b}\nfunction ca(a){var b=typeof a;if(\"object\"==b)if(a){if(a instanceof Array)return\"array\";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if(\"[object Window]\"==c)return\"object\";if(\"[object Array]\"==c||\"number\"==typeof a.length&&\"undefined\"!=typeof a.splice&&\"undefined\"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable(\"splice\"))return\"array\";if(\"[object Function]\"==c||\"undefined\"!=typeof a.call&&\"undefined\"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable(\"call\"))return\"function\"}else return\"null\";\nelse if(\"function\"==b&&\"undefined\"==typeof a.call)return\"object\";return b}function da(a,b,c){return a.call.apply(a.bind,arguments)}function ea(a,b,c){if(!a)throw Error();if(2<arguments.length){var d=Array.prototype.slice.call(arguments,2);return function(){var e=Array.prototype.slice.call(arguments);Array.prototype.unshift.apply(e,d);return a.apply(b,e)}}return function(){return a.apply(b,arguments)}}\nfunction fa(a,b,c){Function.prototype.bind&&-1!=Function.prototype.bind.toString().indexOf(\"native code\")?fa=da:fa=ea;return fa.apply(null,arguments)}function ha(a,b){var c=Array.prototype.slice.call(arguments,1);return function(){var d=c.slice();d.push.apply(d,arguments);return a.apply(this,d)}}function m(a,b){function c(){}c.prototype=b.prototype;a.prototype=new c;a.prototype.constructor=a};/*\n\n The MIT License\n\n Copyright (c) 2007 Cybozu Labs, Inc.\n Copyright (c) 2012 Google Inc.\n\n Permission is hereby granted, free of charge, to any person obtaining a copy\n of this software and associated documentation files (the \"Software\"), to\n deal in the Software without restriction, including without limitation the\n rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n sell copies of the Software, and to permit persons to whom the Software is\n furnished to do so, subject to the following conditions:\n\n The above copyright notice and this permission notice shall be included in\n all copies or substantial portions of the Software.\n\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n IN THE SOFTWARE.\n*/\nfunction ia(a,b,c){this.a=a;this.b=b||1;this.f=c||1};var ja=Array.prototype.indexOf?function(a,b){return Array.prototype.indexOf.call(a,b,void 0)}:function(a,b){if(\"string\"===typeof a)return\"string\"!==typeof b||1!=b.length?-1:a.indexOf(b,0);for(var c=0;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1},p=Array.prototype.forEach?function(a,b){Array.prototype.forEach.call(a,b,void 0)}:function(a,b){for(var c=a.length,d=\"string\"===typeof a?a.split(\"\"):a,e=0;e<c;e++)e in d&&b.call(void 0,d[e],e,a)},ka=Array.prototype.filter?function(a,b){return Array.prototype.filter.call(a,\nb,void 0)}:function(a,b){for(var c=a.length,d=[],e=0,f=\"string\"===typeof a?a.split(\"\"):a,g=0;g<c;g++)if(g in f){var h=f[g];b.call(void 0,h,g,a)&&(d[e++]=h)}return d},la=Array.prototype.map?function(a,b){return Array.prototype.map.call(a,b,void 0)}:function(a,b){for(var c=a.length,d=Array(c),e=\"string\"===typeof a?a.split(\"\"):a,f=0;f<c;f++)f in e&&(d[f]=b.call(void 0,e[f],f,a));return d},ma=Array.prototype.reduce?function(a,b,c){return Array.prototype.reduce.call(a,b,c)}:function(a,b,c){var d=c;p(a,\nfunction(e,f){d=b.call(void 0,d,e,f,a)});return d},na=Array.prototype.some?function(a,b){return Array.prototype.some.call(a,b,void 0)}:function(a,b){for(var c=a.length,d=\"string\"===typeof a?a.split(\"\"):a,e=0;e<c;e++)if(e in d&&b.call(void 0,d[e],e,a))return!0;return!1},oa=Array.prototype.every?function(a,b){return Array.prototype.every.call(a,b,void 0)}:function(a,b){for(var c=a.length,d=\"string\"===typeof a?a.split(\"\"):a,e=0;e<c;e++)if(e in d&&!b.call(void 0,d[e],e,a))return!1;return!0};\nfunction pa(a,b){a:{for(var c=a.length,d=\"string\"===typeof a?a.split(\"\"):a,e=0;e<c;e++)if(e in d&&b.call(void 0,d[e],e,a)){b=e;break a}b=-1}return 0>b?null:\"string\"===typeof a?a.charAt(b):a[b]}function qa(a){return Array.prototype.concat.apply([],arguments)}function ra(a,b,c){return 2>=arguments.length?Array.prototype.slice.call(a,b):Array.prototype.slice.call(a,b,c)};function sa(a){var b=a.length-1;return 0<=b&&a.indexOf(\" \",b)==b}var ta=String.prototype.trim?function(a){return a.trim()}:function(a){return/^[\\s\\xa0]*([\\s\\S]*?)[\\s\\xa0]*$/.exec(a)[1]};function ua(a,b){return a<b?-1:a>b?1:0};var r;a:{var va=k.navigator;if(va){var wa=va.userAgent;if(wa){r=wa;break a}}r=\"\"}function u(a){return-1!=r.indexOf(a)};function xa(){return u(\"Firefox\")||u(\"FxiOS\")}function ya(){return(u(\"Chrome\")||u(\"CriOS\"))&&!u(\"Edge\")};function za(a){return String(a).replace(/\\-([a-z])/g,function(b,c){return c.toUpperCase()})};function Aa(){return u(\"iPhone\")&&!u(\"iPod\")&&!u(\"iPad\")};function Ba(a,b){var c=Ca;return Object.prototype.hasOwnProperty.call(c,a)?c[a]:c[a]=b(a)};var Da=u(\"Opera\"),w=u(\"Trident\")||u(\"MSIE\"),Ea=u(\"Edge\"),Fa=u(\"Gecko\")&&!(-1!=r.toLowerCase().indexOf(\"webkit\")&&!u(\"Edge\"))&&!(u(\"Trident\")||u(\"MSIE\"))&&!u(\"Edge\"),Ga=-1!=r.toLowerCase().indexOf(\"webkit\")&&!u(\"Edge\");function Ha(){var a=k.document;return a?a.documentMode:void 0}var Ia;\na:{var Ja=\"\",Ka=function(){var a=r;if(Fa)return/rv:([^\\);]+)(\\)|;)/.exec(a);if(Ea)return/Edge\\/([\\d\\.]+)/.exec(a);if(w)return/\\b(?:MSIE|rv)[: ]([^\\);]+)(\\)|;)/.exec(a);if(Ga)return/WebKit\\/(\\S+)/.exec(a);if(Da)return/(?:Version)[ \\/]?(\\S+)/.exec(a)}();Ka&&(Ja=Ka?Ka[1]:\"\");if(w){var La=Ha();if(null!=La&&La>parseFloat(Ja)){Ia=String(La);break a}}Ia=Ja}var Ca={};\nfunction Ma(a){return Ba(a,function(){for(var b=0,c=ta(String(Ia)).split(\".\"),d=ta(String(a)).split(\".\"),e=Math.max(c.length,d.length),f=0;0==b&&f<e;f++){var g=c[f]||\"\",h=d[f]||\"\";do{g=/(\\d*)(\\D*)(.*)/.exec(g)||[\"\",\"\",\"\",\"\"];h=/(\\d*)(\\D*)(.*)/.exec(h)||[\"\",\"\",\"\",\"\"];if(0==g[0].length&&0==h[0].length)break;b=ua(0==g[1].length?0:parseInt(g[1],10),0==h[1].length?0:parseInt(h[1],10))||ua(0==g[2].length,0==h[2].length)||ua(g[2],h[2]);g=g[3];h=h[3]}while(0==b)}return 0<=b})}var Na;\nNa=k.document&&w?Ha():void 0;var x=w&&!(9<=Number(Na)),Oa=w&&!(8<=Number(Na));function Pa(a,b,c,d){this.a=a;this.nodeName=c;this.nodeValue=d;this.nodeType=2;this.parentNode=this.ownerElement=b}function Qa(a,b){var c=Oa&&\"href\"==b.nodeName?a.getAttribute(b.nodeName,2):b.nodeValue;return new Pa(b,a,b.nodeName,c)};function Ra(a){this.b=a;this.a=0}function Sa(a){a=a.match(Ta);for(var b=0;b<a.length;b++)Ua.test(a[b])&&a.splice(b,1);return new Ra(a)}var Ta=/\\$?(?:(?![0-9-\\.])(?:\\*|[\\w-\\.]+):)?(?![0-9-\\.])(?:\\*|[\\w-\\.]+)|\\/\\/|\\.\\.|::|\\d+(?:\\.\\d*)?|\\.\\d+|\"[^\"]*\"|'[^']*'|[!<>]=|\\s+|./g,Ua=/^\\s/;function y(a,b){return a.b[a.a+(b||0)]}function z(a){return a.b[a.a++]}function Va(a){return a.b.length<=a.a};function Wa(a,b){this.x=void 0!==a?a:0;this.y=void 0!==b?b:0}Wa.prototype.ceil=function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);return this};Wa.prototype.floor=function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);return this};Wa.prototype.round=function(){this.x=Math.round(this.x);this.y=Math.round(this.y);return this};function Xa(a,b){this.width=a;this.height=b}Xa.prototype.aspectRatio=function(){return this.width/this.height};Xa.prototype.ceil=function(){this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};Xa.prototype.floor=function(){this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};Xa.prototype.round=function(){this.width=Math.round(this.width);this.height=Math.round(this.height);return this};function Ya(a){for(;a&&1!=a.nodeType;)a=a.previousSibling;return a}function Za(a,b){if(!a||!b)return!1;if(a.contains&&1==b.nodeType)return a==b||a.contains(b);if(\"undefined\"!=typeof a.compareDocumentPosition)return a==b||!!(a.compareDocumentPosition(b)&16);for(;b&&a!=b;)b=b.parentNode;return b==a}\nfunction $a(a,b){if(a==b)return 0;if(a.compareDocumentPosition)return a.compareDocumentPosition(b)&2?1:-1;if(w&&!(9<=Number(Na))){if(9==a.nodeType)return-1;if(9==b.nodeType)return 1}if(\"sourceIndex\"in a||a.parentNode&&\"sourceIndex\"in a.parentNode){var c=1==a.nodeType,d=1==b.nodeType;if(c&&d)return a.sourceIndex-b.sourceIndex;var e=a.parentNode,f=b.parentNode;return e==f?ab(a,b):!c&&Za(e,b)?-1*bb(a,b):!d&&Za(f,a)?bb(b,a):(c?a.sourceIndex:e.sourceIndex)-(d?b.sourceIndex:f.sourceIndex)}d=A(a);c=d.createRange();\nc.selectNode(a);c.collapse(!0);a=d.createRange();a.selectNode(b);a.collapse(!0);return c.compareBoundaryPoints(k.Range.START_TO_END,a)}function bb(a,b){var c=a.parentNode;if(c==b)return-1;for(;b.parentNode!=c;)b=b.parentNode;return ab(b,a)}function ab(a,b){for(;b=b.previousSibling;)if(b==a)return-1;return 1}function A(a){return 9==a.nodeType?a:a.ownerDocument||a.document}function cb(a,b){a&&(a=a.parentNode);for(var c=0;a;){if(b(a))return a;a=a.parentNode;c++}return null}\nfunction db(a){this.a=a||k.document||document}db.prototype.getElementsByTagName=function(a,b){return(b||this.a).getElementsByTagName(String(a))};function B(a){var b=null,c=a.nodeType;1==c&&(b=a.textContent,b=void 0==b||null==b?a.innerText:b,b=void 0==b||null==b?\"\":b);if(\"string\"!=typeof b)if(x&&\"title\"==a.nodeName.toLowerCase()&&1==c)b=a.text;else if(9==c||1==c){a=9==c?a.documentElement:a.firstChild;c=0;var d=[];for(b=\"\";a;){do 1!=a.nodeType&&(b+=a.nodeValue),x&&\"title\"==a.nodeName.toLowerCase()&&(b+=a.text),d[c++]=a;while(a=a.firstChild);for(;c&&!(a=d[--c].nextSibling););}}else b=a.nodeValue;return b}\nfunction C(a,b,c){if(null===b)return!0;try{if(!a.getAttribute)return!1}catch(d){return!1}Oa&&\"class\"==b&&(b=\"className\");return null==c?!!a.getAttribute(b):a.getAttribute(b,2)==c}function eb(a,b,c,d,e){return(x?fb:gb).call(null,a,b,aa(c)?c:null,aa(d)?d:null,e||new E)}\nfunction fb(a,b,c,d,e){if(a instanceof F||8==a.b||c&&null===a.b){var f=b.all;if(!f)return e;a=ib(a);if(\"*\"!=a&&(f=b.getElementsByTagName(a),!f))return e;if(c){for(var g=[],h=0;b=f[h++];)C(b,c,d)&&g.push(b);f=g}for(h=0;b=f[h++];)\"*\"==a&&\"!\"==b.tagName||e.add(b);return e}jb(a,b,c,d,e);return e}\nfunction gb(a,b,c,d,e){b.getElementsByName&&d&&\"name\"==c&&!w?(b=b.getElementsByName(d),p(b,function(f){a.a(f)&&e.add(f)})):b.getElementsByClassName&&d&&\"class\"==c?(b=b.getElementsByClassName(d),p(b,function(f){f.className==d&&a.a(f)&&e.add(f)})):a instanceof G?jb(a,b,c,d,e):b.getElementsByTagName&&(b=b.getElementsByTagName(a.f()),p(b,function(f){C(f,c,d)&&e.add(f)}));return e}\nfunction kb(a,b,c,d,e){var f;if((a instanceof F||8==a.b||c&&null===a.b)&&(f=b.childNodes)){var g=ib(a);if(\"*\"!=g&&(f=ka(f,function(h){return h.tagName&&h.tagName.toLowerCase()==g}),!f))return e;c&&(f=ka(f,function(h){return C(h,c,d)}));p(f,function(h){\"*\"==g&&(\"!\"==h.tagName||\"*\"==g&&1!=h.nodeType)||e.add(h)});return e}return lb(a,b,c,d,e)}function lb(a,b,c,d,e){for(b=b.firstChild;b;b=b.nextSibling)C(b,c,d)&&a.a(b)&&e.add(b);return e}\nfunction jb(a,b,c,d,e){for(b=b.firstChild;b;b=b.nextSibling)C(b,c,d)&&a.a(b)&&e.add(b),jb(a,b,c,d,e)}function ib(a){if(a instanceof G){if(8==a.b)return\"!\";if(null===a.b)return\"*\"}return a.f()};function E(){this.b=this.a=null;this.l=0}function mb(a){this.f=a;this.a=this.b=null}function nb(a,b){if(!a.a)return b;if(!b.a)return a;var c=a.a;b=b.a;for(var d=null,e,f=0;c&&b;){e=c.f;var g=b.f;e==g||e instanceof Pa&&g instanceof Pa&&e.a==g.a?(e=c,c=c.a,b=b.a):0<$a(c.f,b.f)?(e=b,b=b.a):(e=c,c=c.a);(e.b=d)?d.a=e:a.a=e;d=e;f++}for(e=c||b;e;)e.b=d,d=d.a=e,f++,e=e.a;a.b=d;a.l=f;return a}function ob(a,b){b=new mb(b);b.a=a.a;a.b?a.a.b=b:a.a=a.b=b;a.a=b;a.l++}\nE.prototype.add=function(a){a=new mb(a);a.b=this.b;this.a?this.b.a=a:this.a=this.b=a;this.b=a;this.l++};function pb(a){return(a=a.a)?a.f:null}function qb(a){return(a=pb(a))?B(a):\"\"}function H(a,b){return new rb(a,!!b)}function rb(a,b){this.f=a;this.b=(this.s=b)?a.b:a.a;this.a=null}function I(a){var b=a.b;if(null==b)return null;var c=a.a=b;a.b=a.s?b.b:b.a;return c.f};function J(a){this.i=a;this.b=this.g=!1;this.f=null}function K(a){return\"\\n \"+a.toString().split(\"\\n\").join(\"\\n \")}function sb(a,b){a.g=b}function tb(a,b){a.b=b}function L(a,b){a=a.a(b);return a instanceof E?+qb(a):+a}function O(a,b){a=a.a(b);return a instanceof E?qb(a):\"\"+a}function ub(a,b){a=a.a(b);return a instanceof E?!!a.l:!!a};function vb(a,b,c){J.call(this,a.i);this.c=a;this.h=b;this.o=c;this.g=b.g||c.g;this.b=b.b||c.b;this.c==wb&&(c.b||c.g||4==c.i||0==c.i||!b.f?b.b||b.g||4==b.i||0==b.i||!c.f||(this.f={name:c.f.name,u:b}):this.f={name:b.f.name,u:c})}m(vb,J);\nfunction xb(a,b,c,d,e){b=b.a(d);c=c.a(d);var f;if(b instanceof E&&c instanceof E){b=H(b);for(d=I(b);d;d=I(b))for(e=H(c),f=I(e);f;f=I(e))if(a(B(d),B(f)))return!0;return!1}if(b instanceof E||c instanceof E){b instanceof E?(e=b,d=c):(e=c,d=b);f=H(e);for(var g=typeof d,h=I(f);h;h=I(f)){switch(g){case \"number\":h=+B(h);break;case \"boolean\":h=!!B(h);break;case \"string\":h=B(h);break;default:throw Error(\"Illegal primitive type for comparison.\");}if(e==b&&a(h,d)||e==c&&a(d,h))return!0}return!1}return e?\"boolean\"==\ntypeof b||\"boolean\"==typeof c?a(!!b,!!c):\"number\"==typeof b||\"number\"==typeof c?a(+b,+c):a(b,c):a(+b,+c)}vb.prototype.a=function(a){return this.c.m(this.h,this.o,a)};vb.prototype.toString=function(){var a=\"Binary Expression: \"+this.c;a+=K(this.h);return a+=K(this.o)};function yb(a,b,c,d){this.I=a;this.D=b;this.i=c;this.m=d}yb.prototype.toString=function(){return this.I};var zb={};\nfunction P(a,b,c,d){if(zb.hasOwnProperty(a))throw Error(\"Binary operator already created: \"+a);a=new yb(a,b,c,d);return zb[a.toString()]=a}P(\"div\",6,1,function(a,b,c){return L(a,c)/L(b,c)});P(\"mod\",6,1,function(a,b,c){return L(a,c)%L(b,c)});P(\"*\",6,1,function(a,b,c){return L(a,c)*L(b,c)});P(\"+\",5,1,function(a,b,c){return L(a,c)+L(b,c)});P(\"-\",5,1,function(a,b,c){return L(a,c)-L(b,c)});P(\"<\",4,2,function(a,b,c){return xb(function(d,e){return d<e},a,b,c)});\nP(\">\",4,2,function(a,b,c){return xb(function(d,e){return d>e},a,b,c)});P(\"<=\",4,2,function(a,b,c){return xb(function(d,e){return d<=e},a,b,c)});P(\">=\",4,2,function(a,b,c){return xb(function(d,e){return d>=e},a,b,c)});var wb=P(\"=\",3,2,function(a,b,c){return xb(function(d,e){return d==e},a,b,c,!0)});P(\"!=\",3,2,function(a,b,c){return xb(function(d,e){return d!=e},a,b,c,!0)});P(\"and\",2,2,function(a,b,c){return ub(a,c)&&ub(b,c)});P(\"or\",1,2,function(a,b,c){return ub(a,c)||ub(b,c)});function Ab(a,b){if(b.a.length&&4!=a.i)throw Error(\"Primary expression must evaluate to nodeset if filter has predicate(s).\");J.call(this,a.i);this.c=a;this.h=b;this.g=a.g;this.b=a.b}m(Ab,J);Ab.prototype.a=function(a){a=this.c.a(a);return Bb(this.h,a)};Ab.prototype.toString=function(){var a=\"Filter:\"+K(this.c);return a+=K(this.h)};function Cb(a,b){if(b.length<a.C)throw Error(\"Function \"+a.j+\" expects at least\"+a.C+\" arguments, \"+b.length+\" given\");if(null!==a.B&&b.length>a.B)throw Error(\"Function \"+a.j+\" expects at most \"+a.B+\" arguments, \"+b.length+\" given\");a.H&&p(b,function(c,d){if(4!=c.i)throw Error(\"Argument \"+d+\" to function \"+a.j+\" is not of type Nodeset: \"+c);});J.call(this,a.i);this.v=a;this.c=b;sb(this,a.g||na(b,function(c){return c.g}));tb(this,a.G&&!b.length||a.F&&!!b.length||na(b,function(c){return c.b}))}\nm(Cb,J);Cb.prototype.a=function(a){return this.v.m.apply(null,qa(a,this.c))};Cb.prototype.toString=function(){var a=\"Function: \"+this.v;if(this.c.length){var b=ma(this.c,function(c,d){return c+K(d)},\"Arguments:\");a+=K(b)}return a};function Db(a,b,c,d,e,f,g,h){this.j=a;this.i=b;this.g=c;this.G=d;this.F=!1;this.m=e;this.C=f;this.B=void 0!==g?g:f;this.H=!!h}Db.prototype.toString=function(){return this.j};var Eb={};\nfunction Q(a,b,c,d,e,f,g,h){if(Eb.hasOwnProperty(a))throw Error(\"Function already created: \"+a+\".\");Eb[a]=new Db(a,b,c,d,e,f,g,h)}Q(\"boolean\",2,!1,!1,function(a,b){return ub(b,a)},1);Q(\"ceiling\",1,!1,!1,function(a,b){return Math.ceil(L(b,a))},1);Q(\"concat\",3,!1,!1,function(a,b){return ma(ra(arguments,1),function(c,d){return c+O(d,a)},\"\")},2,null);Q(\"contains\",2,!1,!1,function(a,b,c){b=O(b,a);a=O(c,a);return-1!=b.indexOf(a)},2);Q(\"count\",1,!1,!1,function(a,b){return b.a(a).l},1,1,!0);\nQ(\"false\",2,!1,!1,function(){return!1},0);Q(\"floor\",1,!1,!1,function(a,b){return Math.floor(L(b,a))},1);Q(\"id\",4,!1,!1,function(a,b){function c(h){if(x){var l=e.all[h];if(l){if(l.nodeType&&h==l.id)return l;if(l.length)return pa(l,function(v){return h==v.id})}return null}return e.getElementById(h)}var d=a.a,e=9==d.nodeType?d:d.ownerDocument;a=O(b,a).split(/\\s+/);var f=[];p(a,function(h){h=c(h);!h||0<=ja(f,h)||f.push(h)});f.sort($a);var g=new E;p(f,function(h){g.add(h)});return g},1);\nQ(\"lang\",2,!1,!1,function(){return!1},1);Q(\"last\",1,!0,!1,function(a){if(1!=arguments.length)throw Error(\"Function last expects ()\");return a.f},0);Q(\"local-name\",3,!1,!0,function(a,b){return(a=b?pb(b.a(a)):a.a)?a.localName||a.nodeName.toLowerCase():\"\"},0,1,!0);Q(\"name\",3,!1,!0,function(a,b){return(a=b?pb(b.a(a)):a.a)?a.nodeName.toLowerCase():\"\"},0,1,!0);Q(\"namespace-uri\",3,!0,!1,function(){return\"\"},0,1,!0);\nQ(\"normalize-space\",3,!1,!0,function(a,b){return(b?O(b,a):B(a.a)).replace(/[\\s\\xa0]+/g,\" \").replace(/^\\s+|\\s+$/g,\"\")},0,1);Q(\"not\",2,!1,!1,function(a,b){return!ub(b,a)},1);Q(\"number\",1,!1,!0,function(a,b){return b?L(b,a):+B(a.a)},0,1);Q(\"position\",1,!0,!1,function(a){return a.b},0);Q(\"round\",1,!1,!1,function(a,b){return Math.round(L(b,a))},1);Q(\"starts-with\",2,!1,!1,function(a,b,c){b=O(b,a);a=O(c,a);return 0==b.lastIndexOf(a,0)},2);Q(\"string\",3,!1,!0,function(a,b){return b?O(b,a):B(a.a)},0,1);\nQ(\"string-length\",1,!1,!0,function(a,b){return(b?O(b,a):B(a.a)).length},0,1);Q(\"substring\",3,!1,!1,function(a,b,c,d){c=L(c,a);if(isNaN(c)||Infinity==c||-Infinity==c)return\"\";d=d?L(d,a):Infinity;if(isNaN(d)||-Infinity===d)return\"\";c=Math.round(c)-1;var e=Math.max(c,0);a=O(b,a);return Infinity==d?a.substring(e):a.substring(e,c+Math.round(d))},2,3);Q(\"substring-after\",3,!1,!1,function(a,b,c){b=O(b,a);a=O(c,a);c=b.indexOf(a);return-1==c?\"\":b.substring(c+a.length)},2);\nQ(\"substring-before\",3,!1,!1,function(a,b,c){b=O(b,a);a=O(c,a);a=b.indexOf(a);return-1==a?\"\":b.substring(0,a)},2);Q(\"sum\",1,!1,!1,function(a,b){a=H(b.a(a));b=0;for(var c=I(a);c;c=I(a))b+=+B(c);return b},1,1,!0);Q(\"translate\",3,!1,!1,function(a,b,c,d){b=O(b,a);c=O(c,a);var e=O(d,a);a={};for(d=0;d<c.length;d++){var f=c.charAt(d);f in a||(a[f]=e.charAt(d))}c=\"\";for(d=0;d<b.length;d++)f=b.charAt(d),c+=f in a?a[f]:f;return c},3);Q(\"true\",2,!1,!1,function(){return!0},0);function G(a,b){this.h=a;this.c=void 0!==b?b:null;this.b=null;switch(a){case \"comment\":this.b=8;break;case \"text\":this.b=3;break;case \"processing-instruction\":this.b=7;break;case \"node\":break;default:throw Error(\"Unexpected argument\");}}function Fb(a){return\"comment\"==a||\"text\"==a||\"processing-instruction\"==a||\"node\"==a}G.prototype.a=function(a){return null===this.b||this.b==a.nodeType};G.prototype.f=function(){return this.h};\nG.prototype.toString=function(){var a=\"Kind Test: \"+this.h;null===this.c||(a+=K(this.c));return a};function Gb(a){J.call(this,3);this.c=a.substring(1,a.length-1)}m(Gb,J);Gb.prototype.a=function(){return this.c};Gb.prototype.toString=function(){return\"Literal: \"+this.c};function F(a,b){this.j=a.toLowerCase();a=\"*\"==this.j?\"*\":\"http://www.w3.org/1999/xhtml\";this.c=b?b.toLowerCase():a}F.prototype.a=function(a){var b=a.nodeType;if(1!=b&&2!=b)return!1;b=void 0!==a.localName?a.localName:a.nodeName;return\"*\"!=this.j&&this.j!=b.toLowerCase()?!1:\"*\"==this.c?!0:this.c==(a.namespaceURI?a.namespaceURI.toLowerCase():\"http://www.w3.org/1999/xhtml\")};F.prototype.f=function(){return this.j};\nF.prototype.toString=function(){return\"Name Test: \"+(\"http://www.w3.org/1999/xhtml\"==this.c?\"\":this.c+\":\")+this.j};function Hb(a){J.call(this,1);this.c=a}m(Hb,J);Hb.prototype.a=function(){return this.c};Hb.prototype.toString=function(){return\"Number: \"+this.c};function Ib(a,b){J.call(this,a.i);this.h=a;this.c=b;this.g=a.g;this.b=a.b;1==this.c.length&&(a=this.c[0],a.A||a.c!=Jb||(a=a.o,\"*\"!=a.f()&&(this.f={name:a.f(),u:null})))}m(Ib,J);function Kb(){J.call(this,4)}m(Kb,J);Kb.prototype.a=function(a){var b=new E;a=a.a;9==a.nodeType?b.add(a):b.add(a.ownerDocument);return b};Kb.prototype.toString=function(){return\"Root Helper Expression\"};function Lb(){J.call(this,4)}m(Lb,J);Lb.prototype.a=function(a){var b=new E;b.add(a.a);return b};Lb.prototype.toString=function(){return\"Context Helper Expression\"};\nfunction Mb(a){return\"/\"==a||\"//\"==a}Ib.prototype.a=function(a){var b=this.h.a(a);if(!(b instanceof E))throw Error(\"Filter expression must evaluate to nodeset.\");a=this.c;for(var c=0,d=a.length;c<d&&b.l;c++){var e=a[c],f=H(b,e.c.s);if(e.g||e.c!=Nb)if(e.g||e.c!=Ob){var g=I(f);for(b=e.a(new ia(g));null!=(g=I(f));)g=e.a(new ia(g)),b=nb(b,g)}else g=I(f),b=e.a(new ia(g));else{for(g=I(f);(b=I(f))&&(!g.contains||g.contains(b))&&b.compareDocumentPosition(g)&8;g=b);b=e.a(new ia(g))}}return b};\nIb.prototype.toString=function(){var a=\"Path Expression:\"+K(this.h);if(this.c.length){var b=ma(this.c,function(c,d){return c+K(d)},\"Steps:\");a+=K(b)}return a};function Pb(a,b){this.a=a;this.s=!!b}\nfunction Bb(a,b,c){for(c=c||0;c<a.a.length;c++)for(var d=a.a[c],e=H(b),f=b.l,g,h=0;g=I(e);h++){var l=a.s?f-h:h+1;g=d.a(new ia(g,l,f));if(\"number\"==typeof g)l=l==g;else if(\"string\"==typeof g||\"boolean\"==typeof g)l=!!g;else if(g instanceof E)l=0<g.l;else throw Error(\"Predicate.evaluate returned an unexpected type.\");if(!l){l=e;g=l.f;var v=l.a;if(!v)throw Error(\"Next must be called at least once before remove.\");var n=v.b;v=v.a;n?n.a=v:g.a=v;v?v.b=n:g.b=n;g.l--;l.a=null}}return b}\nPb.prototype.toString=function(){return ma(this.a,function(a,b){return a+K(b)},\"Predicates:\")};function R(a,b,c,d){J.call(this,4);this.c=a;this.o=b;this.h=c||new Pb([]);this.A=!!d;b=this.h;b=0<b.a.length?b.a[0].f:null;a.J&&b&&(a=b.name,a=x?a.toLowerCase():a,this.f={name:a,u:b.u});a:{a=this.h;for(b=0;b<a.a.length;b++)if(c=a.a[b],c.g||1==c.i||0==c.i){a=!0;break a}a=!1}this.g=a}m(R,J);\nR.prototype.a=function(a){var b=a.a,c=this.f,d=null,e=null,f=0;c&&(d=c.name,e=c.u?O(c.u,a):null,f=1);if(this.A)if(this.g||this.c!=Qb)if(b=H((new R(Rb,new G(\"node\"))).a(a)),c=I(b))for(a=this.m(c,d,e,f);null!=(c=I(b));)a=nb(a,this.m(c,d,e,f));else a=new E;else a=eb(this.o,b,d,e),a=Bb(this.h,a,f);else a=this.m(a.a,d,e,f);return a};R.prototype.m=function(a,b,c,d){a=this.c.v(this.o,a,b,c);return a=Bb(this.h,a,d)};\nR.prototype.toString=function(){var a=\"Step:\"+K(\"Operator: \"+(this.A?\"//\":\"/\"));this.c.j&&(a+=K(\"Axis: \"+this.c));a+=K(this.o);if(this.h.a.length){var b=ma(this.h.a,function(c,d){return c+K(d)},\"Predicates:\");a+=K(b)}return a};function Sb(a,b,c,d){this.j=a;this.v=b;this.s=c;this.J=d}Sb.prototype.toString=function(){return this.j};var Tb={};function S(a,b,c,d){if(Tb.hasOwnProperty(a))throw Error(\"Axis already created: \"+a);b=new Sb(a,b,c,!!d);return Tb[a]=b}\nS(\"ancestor\",function(a,b){for(var c=new E;b=b.parentNode;)a.a(b)&&ob(c,b);return c},!0);S(\"ancestor-or-self\",function(a,b){var c=new E;do a.a(b)&&ob(c,b);while(b=b.parentNode);return c},!0);\nvar Jb=S(\"attribute\",function(a,b){var c=new E,d=a.f();if(\"style\"==d&&x&&b.style)return c.add(new Pa(b.style,b,\"style\",b.style.cssText)),c;var e=b.attributes;if(e)if(a instanceof G&&null===a.b||\"*\"==d)for(a=0;d=e[a];a++)x?d.nodeValue&&c.add(Qa(b,d)):c.add(d);else(d=e.getNamedItem(d))&&(x?d.nodeValue&&c.add(Qa(b,d)):c.add(d));return c},!1),Qb=S(\"child\",function(a,b,c,d,e){return(x?kb:lb).call(null,a,b,aa(c)?c:null,aa(d)?d:null,e||new E)},!1,!0);S(\"descendant\",eb,!1,!0);\nvar Rb=S(\"descendant-or-self\",function(a,b,c,d){var e=new E;C(b,c,d)&&a.a(b)&&e.add(b);return eb(a,b,c,d,e)},!1,!0),Nb=S(\"following\",function(a,b,c,d){var e=new E;do for(var f=b;f=f.nextSibling;)C(f,c,d)&&a.a(f)&&e.add(f),e=eb(a,f,c,d,e);while(b=b.parentNode);return e},!1,!0);S(\"following-sibling\",function(a,b){for(var c=new E;b=b.nextSibling;)a.a(b)&&c.add(b);return c},!1);S(\"namespace\",function(){return new E},!1);\nvar Ub=S(\"parent\",function(a,b){var c=new E;if(9==b.nodeType)return c;if(2==b.nodeType)return c.add(b.ownerElement),c;b=b.parentNode;a.a(b)&&c.add(b);return c},!1),Ob=S(\"preceding\",function(a,b,c,d){var e=new E,f=[];do f.unshift(b);while(b=b.parentNode);for(var g=1,h=f.length;g<h;g++){var l=[];for(b=f[g];b=b.previousSibling;)l.unshift(b);for(var v=0,n=l.length;v<n;v++)b=l[v],C(b,c,d)&&a.a(b)&&e.add(b),e=eb(a,b,c,d,e)}return e},!0,!0);\nS(\"preceding-sibling\",function(a,b){for(var c=new E;b=b.previousSibling;)a.a(b)&&ob(c,b);return c},!0);var Vb=S(\"self\",function(a,b){var c=new E;a.a(b)&&c.add(b);return c},!1);function Wb(a){J.call(this,1);this.c=a;this.g=a.g;this.b=a.b}m(Wb,J);Wb.prototype.a=function(a){return-L(this.c,a)};Wb.prototype.toString=function(){return\"Unary Expression: -\"+K(this.c)};function Xb(a){J.call(this,4);this.c=a;sb(this,na(this.c,function(b){return b.g}));tb(this,na(this.c,function(b){return b.b}))}m(Xb,J);Xb.prototype.a=function(a){var b=new E;p(this.c,function(c){c=c.a(a);if(!(c instanceof E))throw Error(\"Path expression must evaluate to NodeSet.\");b=nb(b,c)});return b};Xb.prototype.toString=function(){return ma(this.c,function(a,b){return a+K(b)},\"Union Expression:\")};function Yb(a,b){this.a=a;this.b=b}function Zb(a){for(var b,c=[];;){T(a,\"Missing right hand side of binary expression.\");b=bc(a);var d=z(a.a);if(!d)break;var e=(d=zb[d]||null)&&d.D;if(!e){a.a.a--;break}for(;c.length&&e<=c[c.length-1].D;)b=new vb(c.pop(),c.pop(),b);c.push(b,d)}for(;c.length;)b=new vb(c.pop(),c.pop(),b);return b}function T(a,b){if(Va(a.a))throw Error(b);}function cc(a,b){a=z(a.a);if(a!=b)throw Error(\"Bad token, expected: \"+b+\" got: \"+a);}\nfunction dc(a){a=z(a.a);if(\")\"!=a)throw Error(\"Bad token: \"+a);}function ec(a){a=z(a.a);if(2>a.length)throw Error(\"Unclosed literal string\");return new Gb(a)}\nfunction fc(a){var b=[];if(Mb(y(a.a))){var c=z(a.a);var d=y(a.a);if(\"/\"==c&&(Va(a.a)||\".\"!=d&&\"..\"!=d&&\"@\"!=d&&\"*\"!=d&&!/(?![0-9])[\\w]/.test(d)))return new Kb;d=new Kb;T(a,\"Missing next location step.\");c=gc(a,c);b.push(c)}else{a:{c=y(a.a);d=c.charAt(0);switch(d){case \"$\":throw Error(\"Variable reference not allowed in HTML XPath\");case \"(\":z(a.a);c=Zb(a);T(a,'unclosed \"(\"');cc(a,\")\");break;case '\"':case \"'\":c=ec(a);break;default:if(isNaN(+c))if(!Fb(c)&&/(?![0-9])[\\w]/.test(d)&&\"(\"==y(a.a,1)){c=z(a.a);\nc=Eb[c]||null;z(a.a);for(d=[];\")\"!=y(a.a);){T(a,\"Missing function argument list.\");d.push(Zb(a));if(\",\"!=y(a.a))break;z(a.a)}T(a,\"Unclosed function argument list.\");dc(a);c=new Cb(c,d)}else{c=null;break a}else c=new Hb(+z(a.a))}\"[\"==y(a.a)&&(d=new Pb(hc(a)),c=new Ab(c,d))}if(c)if(Mb(y(a.a)))d=c;else return c;else c=gc(a,\"/\"),d=new Lb,b.push(c)}for(;Mb(y(a.a));)c=z(a.a),T(a,\"Missing next location step.\"),c=gc(a,c),b.push(c);return new Ib(d,b)}\nfunction gc(a,b){if(\"/\"!=b&&\"//\"!=b)throw Error('Step op should be \"/\" or \"//\"');if(\".\"==y(a.a)){var c=new R(Vb,new G(\"node\"));z(a.a);return c}if(\"..\"==y(a.a))return c=new R(Ub,new G(\"node\")),z(a.a),c;if(\"@\"==y(a.a)){var d=Jb;z(a.a);T(a,\"Missing attribute name\")}else if(\"::\"==y(a.a,1)){if(!/(?![0-9])[\\w]/.test(y(a.a).charAt(0)))throw Error(\"Bad token: \"+z(a.a));var e=z(a.a);d=Tb[e]||null;if(!d)throw Error(\"No axis with name: \"+e);z(a.a);T(a,\"Missing node name\")}else d=Qb;e=y(a.a);if(/(?![0-9])[\\w\\*]/.test(e.charAt(0)))if(\"(\"==\ny(a.a,1)){if(!Fb(e))throw Error(\"Invalid node type: \"+e);e=z(a.a);if(!Fb(e))throw Error(\"Invalid type name: \"+e);cc(a,\"(\");T(a,\"Bad nodetype\");var f=y(a.a).charAt(0),g=null;if('\"'==f||\"'\"==f)g=ec(a);T(a,\"Bad nodetype\");dc(a);e=new G(e,g)}else if(e=z(a.a),f=e.indexOf(\":\"),-1==f)e=new F(e);else{g=e.substring(0,f);if(\"*\"==g)var h=\"*\";else if(h=a.b(g),!h)throw Error(\"Namespace prefix not declared: \"+g);e=e.substr(f+1);e=new F(e,h)}else throw Error(\"Bad token: \"+z(a.a));a=new Pb(hc(a),d.s);return c||new R(d,\ne,a,\"//\"==b)}function hc(a){for(var b=[];\"[\"==y(a.a);){z(a.a);T(a,\"Missing predicate expression.\");var c=Zb(a);b.push(c);T(a,\"Unclosed predicate expression.\");cc(a,\"]\")}return b}function bc(a){if(\"-\"==y(a.a))return z(a.a),new Wb(bc(a));var b=fc(a);if(\"|\"!=y(a.a))a=b;else{for(b=[b];\"|\"==z(a.a);)T(a,\"Missing next union location path.\"),b.push(fc(a));a.a.a--;a=new Xb(b)}return a};function ic(a){switch(a.nodeType){case 1:return ha(jc,a);case 9:return ic(a.documentElement);case 11:case 10:case 6:case 12:return kc;default:return a.parentNode?ic(a.parentNode):kc}}function kc(){return null}function jc(a,b){if(a.prefix==b)return a.namespaceURI||\"http://www.w3.org/1999/xhtml\";var c=a.getAttributeNode(\"xmlns:\"+b);return c&&c.specified?c.value||null:a.parentNode&&9!=a.parentNode.nodeType?jc(a.parentNode,b):null};function lc(a,b){if(!a.length)throw Error(\"Empty XPath expression.\");a=Sa(a);if(Va(a))throw Error(\"Invalid XPath expression.\");b?\"function\"==ca(b)||(b=fa(b.lookupNamespaceURI,b)):b=function(){return null};var c=Zb(new Yb(a,b));if(!Va(a))throw Error(\"Bad token: \"+z(a));this.evaluate=function(d,e){d=c.a(new ia(d));return new U(d,e)}}\nfunction U(a,b){if(0==b)if(a instanceof E)b=4;else if(\"string\"==typeof a)b=2;else if(\"number\"==typeof a)b=1;else if(\"boolean\"==typeof a)b=3;else throw Error(\"Unexpected evaluation result.\");if(2!=b&&1!=b&&3!=b&&!(a instanceof E))throw Error(\"value could not be converted to the specified type\");this.resultType=b;switch(b){case 2:this.stringValue=a instanceof E?qb(a):\"\"+a;break;case 1:this.numberValue=a instanceof E?+qb(a):+a;break;case 3:this.booleanValue=a instanceof E?0<a.l:!!a;break;case 4:case 5:case 6:case 7:var c=\nH(a);var d=[];for(var e=I(c);e;e=I(c))d.push(e instanceof Pa?e.a:e);this.snapshotLength=a.l;this.invalidIteratorState=!1;break;case 8:case 9:a=pb(a);this.singleNodeValue=a instanceof Pa?a.a:a;break;default:throw Error(\"Unknown XPathResult type.\");}var f=0;this.iterateNext=function(){if(4!=b&&5!=b)throw Error(\"iterateNext called with wrong result type\");return f>=d.length?null:d[f++]};this.snapshotItem=function(g){if(6!=b&&7!=b)throw Error(\"snapshotItem called with wrong result type\");return g>=d.length||\n0>g?null:d[g]}}U.ANY_TYPE=0;U.NUMBER_TYPE=1;U.STRING_TYPE=2;U.BOOLEAN_TYPE=3;U.UNORDERED_NODE_ITERATOR_TYPE=4;U.ORDERED_NODE_ITERATOR_TYPE=5;U.UNORDERED_NODE_SNAPSHOT_TYPE=6;U.ORDERED_NODE_SNAPSHOT_TYPE=7;U.ANY_UNORDERED_NODE_TYPE=8;U.FIRST_ORDERED_NODE_TYPE=9;function mc(a){this.lookupNamespaceURI=ic(a)}\nfunction nc(a,b){a=a||k;var c=a.Document&&a.Document.prototype||a.document;if(!c.evaluate||b)a.XPathResult=U,c.evaluate=function(d,e,f,g){return(new lc(d,f)).evaluate(e,g)},c.createExpression=function(d,e){return new lc(d,e)},c.createNSResolver=function(d){return new mc(d)}}ba(\"wgxpath.install\",nc);ba(\"wgxpath.install\",nc);var oc={aliceblue:\"#f0f8ff\",antiquewhite:\"#faebd7\",aqua:\"#00ffff\",aquamarine:\"#7fffd4\",azure:\"#f0ffff\",beige:\"#f5f5dc\",bisque:\"#ffe4c4\",black:\"#000000\",blanchedalmond:\"#ffebcd\",blue:\"#0000ff\",blueviolet:\"#8a2be2\",brown:\"#a52a2a\",burlywood:\"#deb887\",cadetblue:\"#5f9ea0\",chartreuse:\"#7fff00\",chocolate:\"#d2691e\",coral:\"#ff7f50\",cornflowerblue:\"#6495ed\",cornsilk:\"#fff8dc\",crimson:\"#dc143c\",cyan:\"#00ffff\",darkblue:\"#00008b\",darkcyan:\"#008b8b\",darkgoldenrod:\"#b8860b\",darkgray:\"#a9a9a9\",darkgreen:\"#006400\",\ndarkgrey:\"#a9a9a9\",darkkhaki:\"#bdb76b\",darkmagenta:\"#8b008b\",darkolivegreen:\"#556b2f\",darkorange:\"#ff8c00\",darkorchid:\"#9932cc\",darkred:\"#8b0000\",darksalmon:\"#e9967a\",darkseagreen:\"#8fbc8f\",darkslateblue:\"#483d8b\",darkslategray:\"#2f4f4f\",darkslategrey:\"#2f4f4f\",darkturquoise:\"#00ced1\",darkviolet:\"#9400d3\",deeppink:\"#ff1493\",deepskyblue:\"#00bfff\",dimgray:\"#696969\",dimgrey:\"#696969\",dodgerblue:\"#1e90ff\",firebrick:\"#b22222\",floralwhite:\"#fffaf0\",forestgreen:\"#228b22\",fuchsia:\"#ff00ff\",gainsboro:\"#dcdcdc\",\nghostwhite:\"#f8f8ff\",gold:\"#ffd700\",goldenrod:\"#daa520\",gray:\"#808080\",green:\"#008000\",greenyellow:\"#adff2f\",grey:\"#808080\",honeydew:\"#f0fff0\",hotpink:\"#ff69b4\",indianred:\"#cd5c5c\",indigo:\"#4b0082\",ivory:\"#fffff0\",khaki:\"#f0e68c\",lavender:\"#e6e6fa\",lavenderblush:\"#fff0f5\",lawngreen:\"#7cfc00\",lemonchiffon:\"#fffacd\",lightblue:\"#add8e6\",lightcoral:\"#f08080\",lightcyan:\"#e0ffff\",lightgoldenrodyellow:\"#fafad2\",lightgray:\"#d3d3d3\",lightgreen:\"#90ee90\",lightgrey:\"#d3d3d3\",lightpink:\"#ffb6c1\",lightsalmon:\"#ffa07a\",\nlightseagreen:\"#20b2aa\",lightskyblue:\"#87cefa\",lightslategray:\"#778899\",lightslategrey:\"#778899\",lightsteelblue:\"#b0c4de\",lightyellow:\"#ffffe0\",lime:\"#00ff00\",limegreen:\"#32cd32\",linen:\"#faf0e6\",magenta:\"#ff00ff\",maroon:\"#800000\",mediumaquamarine:\"#66cdaa\",mediumblue:\"#0000cd\",mediumorchid:\"#ba55d3\",mediumpurple:\"#9370db\",mediumseagreen:\"#3cb371\",mediumslateblue:\"#7b68ee\",mediumspringgreen:\"#00fa9a\",mediumturquoise:\"#48d1cc\",mediumvioletred:\"#c71585\",midnightblue:\"#191970\",mintcream:\"#f5fffa\",mistyrose:\"#ffe4e1\",\nmoccasin:\"#ffe4b5\",navajowhite:\"#ffdead\",navy:\"#000080\",oldlace:\"#fdf5e6\",olive:\"#808000\",olivedrab:\"#6b8e23\",orange:\"#ffa500\",orangered:\"#ff4500\",orchid:\"#da70d6\",palegoldenrod:\"#eee8aa\",palegreen:\"#98fb98\",paleturquoise:\"#afeeee\",palevioletred:\"#db7093\",papayawhip:\"#ffefd5\",peachpuff:\"#ffdab9\",peru:\"#cd853f\",pink:\"#ffc0cb\",plum:\"#dda0dd\",powderblue:\"#b0e0e6\",purple:\"#800080\",red:\"#ff0000\",rosybrown:\"#bc8f8f\",royalblue:\"#4169e1\",saddlebrown:\"#8b4513\",salmon:\"#fa8072\",sandybrown:\"#f4a460\",seagreen:\"#2e8b57\",\nseashell:\"#fff5ee\",sienna:\"#a0522d\",silver:\"#c0c0c0\",skyblue:\"#87ceeb\",slateblue:\"#6a5acd\",slategray:\"#708090\",slategrey:\"#708090\",snow:\"#fffafa\",springgreen:\"#00ff7f\",steelblue:\"#4682b4\",tan:\"#d2b48c\",teal:\"#008080\",thistle:\"#d8bfd8\",tomato:\"#ff6347\",turquoise:\"#40e0d0\",violet:\"#ee82ee\",wheat:\"#f5deb3\",white:\"#ffffff\",whitesmoke:\"#f5f5f5\",yellow:\"#ffff00\",yellowgreen:\"#9acd32\"};var pc=\"backgroundColor borderTopColor borderRightColor borderBottomColor borderLeftColor color outlineColor\".split(\" \"),qc=/#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])/,rc=/^#(?:[0-9a-f]{3}){1,2}$/i,sc=/^(?:rgba)?\\((\\d{1,3}),\\s?(\\d{1,3}),\\s?(\\d{1,3}),\\s?(0|1|0\\.\\d*)\\)$/i,tc=/^(?:rgb)?\\((0|[1-9]\\d{0,2}),\\s?(0|[1-9]\\d{0,2}),\\s?(0|[1-9]\\d{0,2})\\)$/i;function uc(a,b){this.code=a;this.a=V[a]||vc;this.message=b||\"\";a=this.a.replace(/((?:^|\\s+)[a-z])/g,function(c){return c.toUpperCase().replace(/^[\\s\\xa0]+/g,\"\")});b=a.length-5;if(0>b||a.indexOf(\"Error\",b)!=b)a+=\"Error\";this.name=a;a=Error(this.message);a.name=this.name;this.stack=a.stack||\"\"}m(uc,Error);var vc=\"unknown error\",V={15:\"element not selectable\",11:\"element not visible\"};V[31]=vc;V[30]=vc;V[24]=\"invalid cookie domain\";V[29]=\"invalid element coordinates\";V[12]=\"invalid element state\";\nV[32]=\"invalid selector\";V[51]=\"invalid selector\";V[52]=\"invalid selector\";V[17]=\"javascript error\";V[405]=\"unsupported operation\";V[34]=\"move target out of bounds\";V[27]=\"no such alert\";V[7]=\"no such element\";V[8]=\"no such frame\";V[23]=\"no such window\";V[28]=\"script timeout\";V[33]=\"session not created\";V[10]=\"stale element reference\";V[21]=\"timeout\";V[25]=\"unable to set cookie\";V[26]=\"unexpected alert open\";V[13]=vc;V[9]=\"unknown command\";var wc=xa(),xc=Aa()||u(\"iPod\"),yc=u(\"iPad\"),zc=u(\"Android\")&&!(ya()||xa()||u(\"Opera\")||u(\"Silk\")),Ac=ya(),Bc=u(\"Safari\")&&!(ya()||u(\"Coast\")||u(\"Opera\")||u(\"Edge\")||u(\"Edg/\")||u(\"OPR\")||xa()||u(\"Silk\")||u(\"Android\"))&&!(Aa()||u(\"iPad\")||u(\"iPod\"));function Cc(a){return(a=a.exec(r))?a[1]:\"\"}(function(){if(wc)return Cc(/Firefox\\/([0-9.]+)/);if(w||Ea||Da)return Ia;if(Ac)return Aa()||u(\"iPad\")||u(\"iPod\")?Cc(/CriOS\\/([0-9.]+)/):Cc(/Chrome\\/([0-9.]+)/);if(Bc&&!(Aa()||u(\"iPad\")||u(\"iPod\")))return Cc(/Version\\/([0-9.]+)/);if(xc||yc){var a=/Version\\/(\\S+).*Mobile\\/(\\S+)/.exec(r);if(a)return a[1]+\".\"+a[2]}else if(zc)return(a=Cc(/Android\\s+([0-9.]+)/))?a:Cc(/Version\\/([0-9.]+)/);return\"\"})();var Dc=w&&!(9<=Number(Na));function W(a,b){b&&\"string\"!==typeof b&&(b=b.toString());return!!a&&1==a.nodeType&&(!b||a.tagName.toUpperCase()==b)};var Ec=function(){var a={K:\"http://www.w3.org/2000/svg\"};return function(b){return a[b]||null}}();\nfunction Fc(a,b){var c=A(a);if(!c.documentElement)return null;(w||zc)&&nc(c?c.parentWindow||c.defaultView:window);try{var d=c.createNSResolver?c.createNSResolver(c.documentElement):Ec;if(w&&!Ma(7))return c.evaluate.call(c,b,a,d,9,null);if(!w||9<=Number(Na)){for(var e={},f=c.getElementsByTagName(\"*\"),g=0;g<f.length;++g){var h=f[g],l=h.namespaceURI;if(l&&!e[l]){var v=h.lookupPrefix(l);if(!v){var n=l.match(\".*/(\\\\w+)/?$\");v=n?n[1]:\"xhtml\"}e[l]=v}}var D={},M;for(M in e)D[e[M]]=M;d=function(N){return D[N]||\nnull}}try{return c.evaluate(b,a,d,9,null)}catch(N){if(\"TypeError\"===N.name)return d=c.createNSResolver?c.createNSResolver(c.documentElement):Ec,c.evaluate(b,a,d,9,null);throw N;}}catch(N){if(!Fa||\"NS_ERROR_ILLEGAL_VALUE\"!=N.name)throw new uc(32,\"Unable to locate an element with the xpath expression \"+b+\" because of the following error:\\n\"+N);}}\nfunction Gc(a,b){var c=function(){var d=Fc(b,a);return d?d.singleNodeValue||null:b.selectSingleNode?(d=A(b),d.setProperty&&d.setProperty(\"SelectionLanguage\",\"XPath\"),b.selectSingleNode(a)):null}();if(null!==c&&(!c||1!=c.nodeType))throw new uc(32,'The result of the xpath expression \"'+a+'\" is: '+c+\". It should be an element.\");return c};function Hc(a,b,c,d){this.c=a;this.a=b;this.b=c;this.f=d}Hc.prototype.ceil=function(){this.c=Math.ceil(this.c);this.a=Math.ceil(this.a);this.b=Math.ceil(this.b);this.f=Math.ceil(this.f);return this};Hc.prototype.floor=function(){this.c=Math.floor(this.c);this.a=Math.floor(this.a);this.b=Math.floor(this.b);this.f=Math.floor(this.f);return this};Hc.prototype.round=function(){this.c=Math.round(this.c);this.a=Math.round(this.a);this.b=Math.round(this.b);this.f=Math.round(this.f);return this};function X(a,b,c,d){this.a=a;this.b=b;this.width=c;this.height=d}X.prototype.ceil=function(){this.a=Math.ceil(this.a);this.b=Math.ceil(this.b);this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};X.prototype.floor=function(){this.a=Math.floor(this.a);this.b=Math.floor(this.b);this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};\nX.prototype.round=function(){this.a=Math.round(this.a);this.b=Math.round(this.b);this.width=Math.round(this.width);this.height=Math.round(this.height);return this};var Ic=\"function\"===typeof ShadowRoot;function Jc(a){for(a=a.parentNode;a&&1!=a.nodeType&&9!=a.nodeType&&11!=a.nodeType;)a=a.parentNode;return W(a)?a:null}\nfunction Y(a,b){b=za(b);if(\"float\"==b||\"cssFloat\"==b||\"styleFloat\"==b)b=Dc?\"styleFloat\":\"cssFloat\";a:{var c=b;var d=A(a);if(d.defaultView&&d.defaultView.getComputedStyle&&(d=d.defaultView.getComputedStyle(a,null))){c=d[c]||d.getPropertyValue(c)||\"\";break a}c=\"\"}a=c||Kc(a,b);if(null===a)a=null;else if(0<=ja(pc,b)){b:{var e=a.match(sc);if(e&&(b=Number(e[1]),c=Number(e[2]),d=Number(e[3]),e=Number(e[4]),0<=b&&255>=b&&0<=c&&255>=c&&0<=d&&255>=d&&0<=e&&1>=e)){b=[b,c,d,e];break b}b=null}if(!b)b:{if(d=a.match(tc))if(b=\nNumber(d[1]),c=Number(d[2]),d=Number(d[3]),0<=b&&255>=b&&0<=c&&255>=c&&0<=d&&255>=d){b=[b,c,d,1];break b}b=null}if(!b)b:{b=a.toLowerCase();c=oc[b.toLowerCase()];if(!c&&(c=\"#\"==b.charAt(0)?b:\"#\"+b,4==c.length&&(c=c.replace(qc,\"#$1$1$2$2$3$3\")),!rc.test(c))){b=null;break b}b=[parseInt(c.substr(1,2),16),parseInt(c.substr(3,2),16),parseInt(c.substr(5,2),16),1]}a=b?\"rgba(\"+b.join(\", \")+\")\":a}return a}\nfunction Kc(a,b){var c=a.currentStyle||a.style,d=c[b];void 0===d&&\"function\"==ca(c.getPropertyValue)&&(d=c.getPropertyValue(b));return\"inherit\"!=d?void 0!==d?d:null:(a=Jc(a))?Kc(a,b):null}\nfunction Lc(a,b,c){function d(g){var h=Mc(g);return 0<h.height&&0<h.width?!0:W(g,\"PATH\")&&(0<h.height||0<h.width)?(g=Y(g,\"stroke-width\"),!!g&&0<parseInt(g,10)):\"hidden\"!=Y(g,\"overflow\")&&na(g.childNodes,function(l){return 3==l.nodeType||W(l)&&d(l)})}function e(g){return Nc(g)==Z&&oa(g.childNodes,function(h){return!W(h)||e(h)||!d(h)})}if(!W(a))throw Error(\"Argument to isShown must be of type Element\");if(W(a,\"BODY\"))return!0;if(W(a,\"OPTION\")||W(a,\"OPTGROUP\"))return a=cb(a,function(g){return W(g,\"SELECT\")}),\n!!a&&Lc(a,!0,c);var f=Oc(a);if(f)return!!f.image&&0<f.rect.width&&0<f.rect.height&&Lc(f.image,b,c);if(W(a,\"INPUT\")&&\"hidden\"==a.type.toLowerCase()||W(a,\"NOSCRIPT\"))return!1;f=Y(a,\"visibility\");return\"collapse\"!=f&&\"hidden\"!=f&&c(a)&&(b||0!=Pc(a))&&d(a)?!e(a):!1}\nfunction Qc(a){function b(c){if(W(c)&&\"none\"==Y(c,\"display\"))return!1;var d;if((d=c.parentNode)&&d.shadowRoot&&void 0!==c.assignedSlot)d=c.assignedSlot?c.assignedSlot.parentNode:null;else if(c.getDestinationInsertionPoints){var e=c.getDestinationInsertionPoints();0<e.length&&(d=e[e.length-1])}if(Ic&&d instanceof ShadowRoot){if(d.host.shadowRoot&&d.host.shadowRoot!==d)return!1;d=d.host}return!d||9!=d.nodeType&&11!=d.nodeType?d&&W(d,\"DETAILS\")&&!d.open&&!W(c,\"SUMMARY\")?!1:!!d&&b(d):!0}return Lc(a,!1,\nb)}var Z=\"hidden\";\nfunction Nc(a){function b(q){function t(hb){if(hb==g)return!0;var $b=Y(hb,\"display\");return 0==$b.lastIndexOf(\"inline\",0)||\"contents\"==$b||\"absolute\"==ac&&\"static\"==Y(hb,\"position\")?!1:!0}var ac=Y(q,\"position\");if(\"fixed\"==ac)return v=!0,q==g?null:g;for(q=Jc(q);q&&!t(q);)q=Jc(q);return q}function c(q){var t=q;if(\"visible\"==l)if(q==g&&h)t=h;else if(q==h)return{x:\"visible\",y:\"visible\"};t={x:Y(t,\"overflow-x\"),y:Y(t,\"overflow-y\")};q==g&&(t.x=\"visible\"==t.x?\"auto\":t.x,t.y=\"visible\"==t.y?\"auto\":t.y);return t}\nfunction d(q){if(q==g){var t=(new db(f)).a;q=t.scrollingElement?t.scrollingElement:Ga||\"CSS1Compat\"!=t.compatMode?t.body||t.documentElement:t.documentElement;t=t.parentWindow||t.defaultView;q=w&&Ma(\"10\")&&t.pageYOffset!=q.scrollTop?new Wa(q.scrollLeft,q.scrollTop):new Wa(t.pageXOffset||q.scrollLeft,t.pageYOffset||q.scrollTop)}else q=new Wa(q.scrollLeft,q.scrollTop);return q}var e=Rc(a),f=A(a),g=f.documentElement,h=f.body,l=Y(g,\"overflow\"),v;for(a=b(a);a;a=b(a)){var n=c(a);if(\"visible\"!=n.x||\"visible\"!=\nn.y){var D=Mc(a);if(0==D.width||0==D.height)return Z;var M=e.a<D.a,N=e.b<D.b;if(M&&\"hidden\"==n.x||N&&\"hidden\"==n.y)return Z;if(M&&\"visible\"!=n.x||N&&\"visible\"!=n.y){M=d(a);N=e.b<D.b-M.y;if(e.a<D.a-M.x&&\"visible\"!=n.x||N&&\"visible\"!=n.x)return Z;e=Nc(a);return e==Z?Z:\"scroll\"}M=e.f>=D.a+D.width;D=e.c>=D.b+D.height;if(M&&\"hidden\"==n.x||D&&\"hidden\"==n.y)return Z;if(M&&\"visible\"!=n.x||D&&\"visible\"!=n.y){if(v&&(n=d(a),e.f>=g.scrollWidth-n.x||e.a>=g.scrollHeight-n.y))return Z;e=Nc(a);return e==Z?Z:\"scroll\"}}}return\"none\"}\nfunction Mc(a){var b=Oc(a);if(b)return b.rect;if(W(a,\"HTML\"))return a=A(a),a=((a?a.parentWindow||a.defaultView:window)||window).document,a=\"CSS1Compat\"==a.compatMode?a.documentElement:a.body,a=new Xa(a.clientWidth,a.clientHeight),new X(0,0,a.width,a.height);try{var c=a.getBoundingClientRect()}catch(d){return new X(0,0,0,0)}b=new X(c.left,c.top,c.right-c.left,c.bottom-c.top);w&&a.ownerDocument.body&&(a=A(a),b.a-=a.documentElement.clientLeft+a.body.clientLeft,b.b-=a.documentElement.clientTop+a.body.clientTop);\nreturn b}function Oc(a){var b=W(a,\"MAP\");if(!b&&!W(a,\"AREA\"))return null;var c=b?a:W(a.parentNode,\"MAP\")?a.parentNode:null,d=null,e=null;c&&c.name&&(d=Gc('/descendant::*[@usemap = \"#'+c.name+'\"]',A(c)))&&(e=Mc(d),b||\"default\"==a.shape.toLowerCase()||(a=Sc(a),b=Math.min(Math.max(a.a,0),e.width),c=Math.min(Math.max(a.b,0),e.height),e=new X(b+e.a,c+e.b,Math.min(a.width,e.width-b),Math.min(a.height,e.height-c))));return{image:d,rect:e||new X(0,0,0,0)}}\nfunction Sc(a){var b=a.shape.toLowerCase();a=a.coords.split(\",\");if(\"rect\"==b&&4==a.length){b=a[0];var c=a[1];return new X(b,c,a[2]-b,a[3]-c)}if(\"circle\"==b&&3==a.length)return b=a[2],new X(a[0]-b,a[1]-b,2*b,2*b);if(\"poly\"==b&&2<a.length){b=a[0];c=a[1];for(var d=b,e=c,f=2;f+1<a.length;f+=2)b=Math.min(b,a[f]),d=Math.max(d,a[f]),c=Math.min(c,a[f+1]),e=Math.max(e,a[f+1]);return new X(b,c,d-b,e-c)}return new X(0,0,0,0)}function Rc(a){a=Mc(a);return new Hc(a.b,a.a+a.width,a.b+a.height,a.a)}\nfunction Tc(a){return a.replace(/^[^\\S\\xa0]+|[^\\S\\xa0]+$/g,\"\")}\nfunction Uc(a,b,c){if(W(a,\"BR\"))b.push(\"\");else{var d=W(a,\"TD\"),e=Y(a,\"display\"),f=!d&&!(0<=ja(Vc,e)),g=void 0!==a.previousElementSibling?a.previousElementSibling:Ya(a.previousSibling);g=g?Y(g,\"display\"):\"\";var h=Y(a,\"float\")||Y(a,\"cssFloat\")||Y(a,\"styleFloat\");!f||\"run-in\"==g&&\"none\"==h||/^[\\s\\xa0]*$/.test(b[b.length-1]||\"\")||b.push(\"\");var l=Qc(a),v=null,n=null;l&&(v=Y(a,\"white-space\"),n=Y(a,\"text-transform\"));p(a.childNodes,function(D){c(D,b,l,v,n)});a=b[b.length-1]||\"\";!d&&\"table-cell\"!=e||!a||\nsa(a)||(b[b.length-1]+=\" \");f&&\"run-in\"!=e&&!/^[\\s\\xa0]*$/.test(a)&&b.push(\"\")}}function Wc(a,b){Uc(a,b,function(c,d,e,f,g){3==c.nodeType&&e?Xc(c,d,f,g):W(c)&&Wc(c,d)})}var Vc=\"inline inline-block inline-table none table-cell table-column table-column-group\".split(\" \");\nfunction Xc(a,b,c,d){a=a.nodeValue.replace(/[\\u200b\\u200e\\u200f]/g,\"\");a=a.replace(/(\\r\\n|\\r|\\n)/g,\"\\n\");if(\"normal\"==c||\"nowrap\"==c)a=a.replace(/\\n/g,\" \");a=\"pre\"==c||\"pre-wrap\"==c?a.replace(/[ \\f\\t\\v\\u2028\\u2029]/g,\"\\u00a0\"):a.replace(/[ \\f\\t\\v\\u2028\\u2029]+/g,\" \");\"capitalize\"==d?a=a.replace(w?/(^|\\s|\\b)(\\S)/g:/(^|[^\\d\\p{L}\\p{S}])([\\p{Ll}|\\p{S}])/gu,function(e,f,g){return f+g.toUpperCase()}):\"uppercase\"==d?a=a.toUpperCase():\"lowercase\"==d&&(a=a.toLowerCase());c=b.pop()||\"\";sa(c)&&0==a.lastIndexOf(\" \",\n0)&&(a=a.substr(1));b.push(c+a)}function Pc(a){if(Dc){if(\"relative\"==Y(a,\"position\"))return 1;a=Y(a,\"filter\");return(a=a.match(/^alpha\\(opacity=(\\d*)\\)/)||a.match(/^progid:DXImageTransform.Microsoft.Alpha\\(Opacity=(\\d*)\\)/))?Number(a[1])/100:1}return Yc(a)}function Yc(a){var b=1,c=Y(a,\"opacity\");c&&(b=Number(c));(a=Jc(a))&&(b*=Yc(a));return b}\nfunction Zc(a,b,c,d,e){if(3==a.nodeType&&c)Xc(a,b,d,e);else if(W(a))if(W(a,\"CONTENT\")||W(a,\"SLOT\")){for(var f=a;f.parentNode;)f=f.parentNode;f instanceof ShadowRoot?(f=W(a,\"CONTENT\")?a.getDistributedNodes():a.assignedNodes(),p(0<f.length?f:a.childNodes,function(g){Zc(g,b,c,d,e)})):$c(a,b)}else if(W(a,\"SHADOW\")){for(f=a;f.parentNode;)f=f.parentNode;if(f instanceof ShadowRoot&&(a=f))for(a=a.olderShadowRoot;a;)p(a.childNodes,function(g){Zc(g,b,c,d,e)}),a=a.olderShadowRoot}else $c(a,b)}\nfunction $c(a,b){a.shadowRoot&&p(a.shadowRoot.childNodes,function(c){Zc(c,b,!0,null,null)});Uc(a,b,function(c,d,e,f,g){var h=null;1==c.nodeType?h=c:3==c.nodeType&&(h=c);null!=h&&(null!=h.assignedSlot||h.getDestinationInsertionPoints&&0<h.getDestinationInsertionPoints().length)||Zc(c,d,e,f,g)})};ba(\"_\",function(a){var b=[];Ic?$c(a,b):Wc(a,b);a=la(b,Tc);return Tc(a.join(\"\\n\")).replace(/\\xa0/g,\" \")});; return this._.apply(null,arguments);}).apply({navigator:typeof window!='undefined'?window.navigator:null,document:typeof window!='undefined'?window.document:null}, arguments);}\n", + getVisibleText: "function(){return (function(){var h=this||self;\nfunction aa(a){var b=typeof a;if(\"object\"==b)if(a){if(a instanceof Array)return\"array\";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if(\"[object Window]\"==c)return\"object\";if(\"[object Array]\"==c||\"number\"==typeof a.length&&\"undefined\"!=typeof a.splice&&\"undefined\"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable(\"splice\"))return\"array\";if(\"[object Function]\"==c||\"undefined\"!=typeof a.call&&\"undefined\"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable(\"call\"))return\"function\"}else return\"null\";else if(\"function\"==\nb&&\"undefined\"==typeof a.call)return\"object\";return b}function ba(a,b){function c(){}c.prototype=b.prototype;a.prototype=new c;a.prototype.constructor=a};var ca=Array.prototype.indexOf?function(a,b){return Array.prototype.indexOf.call(a,b,void 0)}:function(a,b){if(\"string\"===typeof a)return\"string\"!==typeof b||1!=b.length?-1:a.indexOf(b,0);for(var c=0;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1},m=Array.prototype.forEach?function(a,b){Array.prototype.forEach.call(a,b,void 0)}:function(a,b){for(var c=a.length,d=\"string\"===typeof a?a.split(\"\"):a,e=0;e<c;e++)e in d&&b.call(void 0,d[e],e,a)},da=Array.prototype.map?function(a,b){return Array.prototype.map.call(a,\nb,void 0)}:function(a,b){for(var c=a.length,d=Array(c),e=\"string\"===typeof a?a.split(\"\"):a,f=0;f<c;f++)f in e&&(d[f]=b.call(void 0,e[f],f,a));return d},fa=Array.prototype.some?function(a,b){return Array.prototype.some.call(a,b,void 0)}:function(a,b){for(var c=a.length,d=\"string\"===typeof a?a.split(\"\"):a,e=0;e<c;e++)if(e in d&&b.call(void 0,d[e],e,a))return!0;return!1},ha=Array.prototype.every?function(a,b){return Array.prototype.every.call(a,b,void 0)}:function(a,b){for(var c=a.length,d=\"string\"===\ntypeof a?a.split(\"\"):a,e=0;e<c;e++)if(e in d&&!b.call(void 0,d[e],e,a))return!1;return!0};var ia={aliceblue:\"#f0f8ff\",antiquewhite:\"#faebd7\",aqua:\"#00ffff\",aquamarine:\"#7fffd4\",azure:\"#f0ffff\",beige:\"#f5f5dc\",bisque:\"#ffe4c4\",black:\"#000000\",blanchedalmond:\"#ffebcd\",blue:\"#0000ff\",blueviolet:\"#8a2be2\",brown:\"#a52a2a\",burlywood:\"#deb887\",cadetblue:\"#5f9ea0\",chartreuse:\"#7fff00\",chocolate:\"#d2691e\",coral:\"#ff7f50\",cornflowerblue:\"#6495ed\",cornsilk:\"#fff8dc\",crimson:\"#dc143c\",cyan:\"#00ffff\",darkblue:\"#00008b\",darkcyan:\"#008b8b\",darkgoldenrod:\"#b8860b\",darkgray:\"#a9a9a9\",darkgreen:\"#006400\",\ndarkgrey:\"#a9a9a9\",darkkhaki:\"#bdb76b\",darkmagenta:\"#8b008b\",darkolivegreen:\"#556b2f\",darkorange:\"#ff8c00\",darkorchid:\"#9932cc\",darkred:\"#8b0000\",darksalmon:\"#e9967a\",darkseagreen:\"#8fbc8f\",darkslateblue:\"#483d8b\",darkslategray:\"#2f4f4f\",darkslategrey:\"#2f4f4f\",darkturquoise:\"#00ced1\",darkviolet:\"#9400d3\",deeppink:\"#ff1493\",deepskyblue:\"#00bfff\",dimgray:\"#696969\",dimgrey:\"#696969\",dodgerblue:\"#1e90ff\",firebrick:\"#b22222\",floralwhite:\"#fffaf0\",forestgreen:\"#228b22\",fuchsia:\"#ff00ff\",gainsboro:\"#dcdcdc\",\nghostwhite:\"#f8f8ff\",gold:\"#ffd700\",goldenrod:\"#daa520\",gray:\"#808080\",green:\"#008000\",greenyellow:\"#adff2f\",grey:\"#808080\",honeydew:\"#f0fff0\",hotpink:\"#ff69b4\",indianred:\"#cd5c5c\",indigo:\"#4b0082\",ivory:\"#fffff0\",khaki:\"#f0e68c\",lavender:\"#e6e6fa\",lavenderblush:\"#fff0f5\",lawngreen:\"#7cfc00\",lemonchiffon:\"#fffacd\",lightblue:\"#add8e6\",lightcoral:\"#f08080\",lightcyan:\"#e0ffff\",lightgoldenrodyellow:\"#fafad2\",lightgray:\"#d3d3d3\",lightgreen:\"#90ee90\",lightgrey:\"#d3d3d3\",lightpink:\"#ffb6c1\",lightsalmon:\"#ffa07a\",\nlightseagreen:\"#20b2aa\",lightskyblue:\"#87cefa\",lightslategray:\"#778899\",lightslategrey:\"#778899\",lightsteelblue:\"#b0c4de\",lightyellow:\"#ffffe0\",lime:\"#00ff00\",limegreen:\"#32cd32\",linen:\"#faf0e6\",magenta:\"#ff00ff\",maroon:\"#800000\",mediumaquamarine:\"#66cdaa\",mediumblue:\"#0000cd\",mediumorchid:\"#ba55d3\",mediumpurple:\"#9370db\",mediumseagreen:\"#3cb371\",mediumslateblue:\"#7b68ee\",mediumspringgreen:\"#00fa9a\",mediumturquoise:\"#48d1cc\",mediumvioletred:\"#c71585\",midnightblue:\"#191970\",mintcream:\"#f5fffa\",mistyrose:\"#ffe4e1\",\nmoccasin:\"#ffe4b5\",navajowhite:\"#ffdead\",navy:\"#000080\",oldlace:\"#fdf5e6\",olive:\"#808000\",olivedrab:\"#6b8e23\",orange:\"#ffa500\",orangered:\"#ff4500\",orchid:\"#da70d6\",palegoldenrod:\"#eee8aa\",palegreen:\"#98fb98\",paleturquoise:\"#afeeee\",palevioletred:\"#db7093\",papayawhip:\"#ffefd5\",peachpuff:\"#ffdab9\",peru:\"#cd853f\",pink:\"#ffc0cb\",plum:\"#dda0dd\",powderblue:\"#b0e0e6\",purple:\"#800080\",red:\"#ff0000\",rosybrown:\"#bc8f8f\",royalblue:\"#4169e1\",saddlebrown:\"#8b4513\",salmon:\"#fa8072\",sandybrown:\"#f4a460\",seagreen:\"#2e8b57\",\nseashell:\"#fff5ee\",sienna:\"#a0522d\",silver:\"#c0c0c0\",skyblue:\"#87ceeb\",slateblue:\"#6a5acd\",slategray:\"#708090\",slategrey:\"#708090\",snow:\"#fffafa\",springgreen:\"#00ff7f\",steelblue:\"#4682b4\",tan:\"#d2b48c\",teal:\"#008080\",thistle:\"#d8bfd8\",tomato:\"#ff6347\",turquoise:\"#40e0d0\",violet:\"#ee82ee\",wheat:\"#f5deb3\",white:\"#ffffff\",whitesmoke:\"#f5f5f5\",yellow:\"#ffff00\",yellowgreen:\"#9acd32\"};var ja=\"backgroundColor borderTopColor borderRightColor borderBottomColor borderLeftColor color outlineColor\".split(\" \"),ka=/#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])/,la=/^#(?:[0-9a-f]{3}){1,2}$/i,ma=/^(?:rgba)?\\((\\d{1,3}),\\s?(\\d{1,3}),\\s?(\\d{1,3}),\\s?(0|1|0\\.\\d*)\\)$/i,na=/^(?:rgb)?\\((0|[1-9]\\d{0,2}),\\s?(0|[1-9]\\d{0,2}),\\s?(0|[1-9]\\d{0,2})\\)$/i;function p(a,b){this.code=a;this.a=q[a]||u;this.message=b||\"\";a=this.a.replace(/((?:^|\\s+)[a-z])/g,function(c){return c.toUpperCase().replace(/^[\\s\\xa0]+/g,\"\")});b=a.length-5;if(0>b||a.indexOf(\"Error\",b)!=b)a+=\"Error\";this.name=a;a=Error(this.message);a.name=this.name;this.stack=a.stack||\"\"}ba(p,Error);var u=\"unknown error\",q={15:\"element not selectable\",11:\"element not visible\"};q[31]=u;q[30]=u;q[24]=\"invalid cookie domain\";q[29]=\"invalid element coordinates\";q[12]=\"invalid element state\";\nq[32]=\"invalid selector\";q[51]=\"invalid selector\";q[52]=\"invalid selector\";q[17]=\"javascript error\";q[405]=\"unsupported operation\";q[34]=\"move target out of bounds\";q[27]=\"no such alert\";q[7]=\"no such element\";q[8]=\"no such frame\";q[23]=\"no such window\";q[28]=\"script timeout\";q[33]=\"session not created\";q[10]=\"stale element reference\";q[21]=\"timeout\";q[25]=\"unable to set cookie\";q[26]=\"unexpected alert open\";q[13]=u;q[9]=\"unknown command\";function oa(a){var b=a.length-1;return 0<=b&&a.indexOf(\" \",b)==b}var v=String.prototype.trim?function(a){return a.trim()}:function(a){return/^[\\s\\xa0]*([\\s\\S]*?)[\\s\\xa0]*$/.exec(a)[1]};\nfunction pa(a,b){var c=0;a=v(String(a)).split(\".\");b=v(String(b)).split(\".\");for(var d=Math.max(a.length,b.length),e=0;0==c&&e<d;e++){var f=a[e]||\"\",g=b[e]||\"\";do{f=/(\\d*)(\\D*)(.*)/.exec(f)||[\"\",\"\",\"\",\"\"];g=/(\\d*)(\\D*)(.*)/.exec(g)||[\"\",\"\",\"\",\"\"];if(0==f[0].length&&0==g[0].length)break;c=w(0==f[1].length?0:parseInt(f[1],10),0==g[1].length?0:parseInt(g[1],10))||w(0==f[2].length,0==g[2].length)||w(f[2],g[2]);f=f[3];g=g[3]}while(0==c)}return c}function w(a,b){return a<b?-1:a>b?1:0};var x;a:{var qa=h.navigator;if(qa){var ra=qa.userAgent;if(ra){x=ra;break a}}x=\"\"}function z(a){return-1!=x.indexOf(a)};function B(){return z(\"Firefox\")||z(\"FxiOS\")}function C(){return(z(\"Chrome\")||z(\"CriOS\"))&&!z(\"Edge\")};function sa(a){return String(a).replace(/\\-([a-z])/g,function(b,c){return c.toUpperCase()})};function D(){return z(\"iPhone\")&&!z(\"iPod\")&&!z(\"iPad\")};function ta(a,b){var c=ua;return Object.prototype.hasOwnProperty.call(c,a)?c[a]:c[a]=b(a)};var va=z(\"Opera\"),E=z(\"Trident\")||z(\"MSIE\"),ya=z(\"Edge\"),za=z(\"Gecko\")&&!(-1!=x.toLowerCase().indexOf(\"webkit\")&&!z(\"Edge\"))&&!(z(\"Trident\")||z(\"MSIE\"))&&!z(\"Edge\"),Aa=-1!=x.toLowerCase().indexOf(\"webkit\")&&!z(\"Edge\");function Ba(){var a=h.document;return a?a.documentMode:void 0}var F;\na:{var G=\"\",I=function(){var a=x;if(za)return/rv:([^\\);]+)(\\)|;)/.exec(a);if(ya)return/Edge\\/([\\d\\.]+)/.exec(a);if(E)return/\\b(?:MSIE|rv)[: ]([^\\);]+)(\\)|;)/.exec(a);if(Aa)return/WebKit\\/(\\S+)/.exec(a);if(va)return/(?:Version)[ \\/]?(\\S+)/.exec(a)}();I&&(G=I?I[1]:\"\");if(E){var J=Ba();if(null!=J&&J>parseFloat(G)){F=String(J);break a}}F=G}var ua={};function Ca(a){return ta(a,function(){return 0<=pa(F,a)})}var Da;Da=h.document&&E?Ba():void 0;var Ea=B(),Fa=D()||z(\"iPod\"),Ga=z(\"iPad\"),Ha=z(\"Android\")&&!(C()||B()||z(\"Opera\")||z(\"Silk\")),Ia=C(),Ja=z(\"Safari\")&&!(C()||z(\"Coast\")||z(\"Opera\")||z(\"Edge\")||z(\"Edg/\")||z(\"OPR\")||B()||z(\"Silk\")||z(\"Android\"))&&!(D()||z(\"iPad\")||z(\"iPod\"));function K(a){return(a=a.exec(x))?a[1]:\"\"}(function(){if(Ea)return K(/Firefox\\/([0-9.]+)/);if(E||ya||va)return F;if(Ia)return D()||z(\"iPad\")||z(\"iPod\")?K(/CriOS\\/([0-9.]+)/):K(/Chrome\\/([0-9.]+)/);if(Ja&&!(D()||z(\"iPad\")||z(\"iPod\")))return K(/Version\\/([0-9.]+)/);if(Fa||Ga){var a=/Version\\/(\\S+).*Mobile\\/(\\S+)/.exec(x);if(a)return a[1]+\".\"+a[2]}else if(Ha)return(a=K(/Android\\s+([0-9.]+)/))?a:K(/Version\\/([0-9.]+)/);return\"\"})();var Ka;if(Ka=E)Ka=!(9<=Number(Da));var La=Ka;function L(a,b){this.x=void 0!==a?a:0;this.y=void 0!==b?b:0}L.prototype.ceil=function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);return this};L.prototype.floor=function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);return this};L.prototype.round=function(){this.x=Math.round(this.x);this.y=Math.round(this.y);return this};function M(a,b){this.width=a;this.height=b}M.prototype.aspectRatio=function(){return this.width/this.height};M.prototype.ceil=function(){this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};M.prototype.floor=function(){this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};M.prototype.round=function(){this.width=Math.round(this.width);this.height=Math.round(this.height);return this};function Ma(a){for(;a&&1!=a.nodeType;)a=a.previousSibling;return a}function N(a){return 9==a.nodeType?a:a.ownerDocument||a.document}function Na(a,b){a&&(a=a.parentNode);for(var c=0;a;){if(b(a))return a;a=a.parentNode;c++}return null}function Oa(a){this.a=a||h.document||document};function O(a,b){b&&\"string\"!==typeof b&&(b=b.toString());return!!a&&1==a.nodeType&&(!b||a.tagName.toUpperCase()==b)};function P(a,b,c,d){this.f=a;this.a=b;this.b=c;this.c=d}P.prototype.ceil=function(){this.f=Math.ceil(this.f);this.a=Math.ceil(this.a);this.b=Math.ceil(this.b);this.c=Math.ceil(this.c);return this};P.prototype.floor=function(){this.f=Math.floor(this.f);this.a=Math.floor(this.a);this.b=Math.floor(this.b);this.c=Math.floor(this.c);return this};P.prototype.round=function(){this.f=Math.round(this.f);this.a=Math.round(this.a);this.b=Math.round(this.b);this.c=Math.round(this.c);return this};function R(a,b,c,d){this.a=a;this.b=b;this.width=c;this.height=d}R.prototype.ceil=function(){this.a=Math.ceil(this.a);this.b=Math.ceil(this.b);this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};R.prototype.floor=function(){this.a=Math.floor(this.a);this.b=Math.floor(this.b);this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};\nR.prototype.round=function(){this.a=Math.round(this.a);this.b=Math.round(this.b);this.width=Math.round(this.width);this.height=Math.round(this.height);return this};var Pa=\"function\"===typeof ShadowRoot;function S(a){for(a=a.parentNode;a&&1!=a.nodeType&&9!=a.nodeType&&11!=a.nodeType;)a=a.parentNode;return O(a)?a:null}\nfunction T(a,b){b=sa(b);if(\"float\"==b||\"cssFloat\"==b||\"styleFloat\"==b)b=La?\"styleFloat\":\"cssFloat\";a:{var c=b;var d=N(a);if(d.defaultView&&d.defaultView.getComputedStyle&&(d=d.defaultView.getComputedStyle(a,null))){c=d[c]||d.getPropertyValue(c)||\"\";break a}c=\"\"}a=c||Qa(a,b);if(null===a)a=null;else if(0<=ca(ja,b)){b:{var e=a.match(ma);if(e&&(b=Number(e[1]),c=Number(e[2]),d=Number(e[3]),e=Number(e[4]),0<=b&&255>=b&&0<=c&&255>=c&&0<=d&&255>=d&&0<=e&&1>=e)){b=[b,c,d,e];break b}b=null}if(!b)b:{if(d=a.match(na))if(b=\nNumber(d[1]),c=Number(d[2]),d=Number(d[3]),0<=b&&255>=b&&0<=c&&255>=c&&0<=d&&255>=d){b=[b,c,d,1];break b}b=null}if(!b)b:{b=a.toLowerCase();c=ia[b.toLowerCase()];if(!c&&(c=\"#\"==b.charAt(0)?b:\"#\"+b,4==c.length&&(c=c.replace(ka,\"#$1$1$2$2$3$3\")),!la.test(c))){b=null;break b}b=[parseInt(c.substr(1,2),16),parseInt(c.substr(3,2),16),parseInt(c.substr(5,2),16),1]}a=b?\"rgba(\"+b.join(\", \")+\")\":a}return a}\nfunction Qa(a,b){var c=a.currentStyle||a.style,d=c[b];void 0===d&&\"function\"==aa(c.getPropertyValue)&&(d=c.getPropertyValue(b));return\"inherit\"!=d?void 0!==d?d:null:(a=S(a))?Qa(a,b):null}\nfunction Ra(a,b,c){function d(g){var k=U(g);return 0<k.height&&0<k.width?!0:O(g,\"PATH\")&&(0<k.height||0<k.width)?(g=T(g,\"stroke-width\"),!!g&&0<parseInt(g,10)):\"hidden\"!=T(g,\"overflow\")&&fa(g.childNodes,function(y){return 3==y.nodeType||O(y)&&d(y)})}function e(g){return Sa(g)==V&&ha(g.childNodes,function(k){return!O(k)||e(k)||!d(k)})}if(!O(a))throw Error(\"Argument to isShown must be of type Element\");if(O(a,\"BODY\"))return!0;if(O(a,\"OPTION\")||O(a,\"OPTGROUP\"))return a=Na(a,function(g){return O(g,\"SELECT\")}),\n!!a&&Ra(a,!0,c);var f=Ta(a);if(f)return!!f.image&&0<f.rect.width&&0<f.rect.height&&Ra(f.image,b,c);if(O(a,\"INPUT\")&&\"hidden\"==a.type.toLowerCase()||O(a,\"NOSCRIPT\"))return!1;f=T(a,\"visibility\");return\"collapse\"!=f&&\"hidden\"!=f&&c(a)&&(b||0!=Ua(a))&&d(a)?!e(a):!1}\nfunction Va(a){function b(c){if(O(c)&&\"none\"==T(c,\"display\"))return!1;var d;if((d=c.parentNode)&&d.shadowRoot&&void 0!==c.assignedSlot)d=c.assignedSlot?c.assignedSlot.parentNode:null;else if(c.getDestinationInsertionPoints){var e=c.getDestinationInsertionPoints();0<e.length&&(d=e[e.length-1])}if(Pa&&d instanceof ShadowRoot){if(d.host.shadowRoot&&d.host.shadowRoot!==d)return!1;d=d.host}return!d||9!=d.nodeType&&11!=d.nodeType?d&&O(d,\"DETAILS\")&&!d.open&&!O(c,\"SUMMARY\")?!1:!!d&&b(d):!0}return Ra(a,!1,\nb)}var V=\"hidden\";\nfunction Sa(a){function b(l){function n(ea){if(ea==g)return!0;var wa=T(ea,\"display\");return 0==wa.lastIndexOf(\"inline\",0)||\"contents\"==wa||\"absolute\"==xa&&\"static\"==T(ea,\"position\")?!1:!0}var xa=T(l,\"position\");if(\"fixed\"==xa)return H=!0,l==g?null:g;for(l=S(l);l&&!n(l);)l=S(l);return l}function c(l){var n=l;if(\"visible\"==y)if(l==g&&k)n=k;else if(l==k)return{x:\"visible\",y:\"visible\"};n={x:T(n,\"overflow-x\"),y:T(n,\"overflow-y\")};l==g&&(n.x=\"visible\"==n.x?\"auto\":n.x,n.y=\"visible\"==n.y?\"auto\":n.y);return n}\nfunction d(l){if(l==g){var n=(new Oa(f)).a;l=n.scrollingElement?n.scrollingElement:Aa||\"CSS1Compat\"!=n.compatMode?n.body||n.documentElement:n.documentElement;n=n.parentWindow||n.defaultView;l=E&&Ca(\"10\")&&n.pageYOffset!=l.scrollTop?new L(l.scrollLeft,l.scrollTop):new L(n.pageXOffset||l.scrollLeft,n.pageYOffset||l.scrollTop)}else l=new L(l.scrollLeft,l.scrollTop);return l}var e=Wa(a),f=N(a),g=f.documentElement,k=f.body,y=T(g,\"overflow\"),H;for(a=b(a);a;a=b(a)){var r=c(a);if(\"visible\"!=r.x||\"visible\"!=\nr.y){var t=U(a);if(0==t.width||0==t.height)return V;var A=e.a<t.a,Q=e.b<t.b;if(A&&\"hidden\"==r.x||Q&&\"hidden\"==r.y)return V;if(A&&\"visible\"!=r.x||Q&&\"visible\"!=r.y){A=d(a);Q=e.b<t.b-A.y;if(e.a<t.a-A.x&&\"visible\"!=r.x||Q&&\"visible\"!=r.x)return V;e=Sa(a);return e==V?V:\"scroll\"}A=e.c>=t.a+t.width;t=e.f>=t.b+t.height;if(A&&\"hidden\"==r.x||t&&\"hidden\"==r.y)return V;if(A&&\"visible\"!=r.x||t&&\"visible\"!=r.y){if(H&&(r=d(a),e.c>=g.scrollWidth-r.x||e.a>=g.scrollHeight-r.y))return V;e=Sa(a);return e==V?V:\"scroll\"}}}return\"none\"}\nfunction U(a){var b=Ta(a);if(b)return b.rect;if(O(a,\"HTML\"))return a=N(a),a=((a?a.parentWindow||a.defaultView:window)||window).document,a=\"CSS1Compat\"==a.compatMode?a.documentElement:a.body,a=new M(a.clientWidth,a.clientHeight),new R(0,0,a.width,a.height);try{var c=a.getBoundingClientRect()}catch(d){return new R(0,0,0,0)}b=new R(c.left,c.top,c.right-c.left,c.bottom-c.top);E&&a.ownerDocument.body&&(a=N(a),b.a-=a.documentElement.clientLeft+a.body.clientLeft,b.b-=a.documentElement.clientTop+a.body.clientTop);\nreturn b}\nfunction Ta(a){var b=O(a,\"MAP\");if(!b&&!O(a,\"AREA\"))return null;var c=b?a:O(a.parentNode,\"MAP\")?a.parentNode:null,d=null,e=null;if(c&&c.name){d='*[usemap=\"#'+c.name+'\"]';c=N(c);var f;if(f=\"function\"!=aa(c.querySelector)&&E&&(E?0<=pa(Da,8):Ca(8))){f=c.querySelector;var g=typeof f;f=!(\"object\"==g&&null!=f||\"function\"==g)}if(f)throw Error(\"CSS selection is not supported\");if(!d)throw new p(32,\"No selector specified\");d=v(d);try{var k=c.querySelector(d)}catch(y){throw new p(32,\"An invalid or illegal selector was specified\");}if(d=\nk&&1==k.nodeType?k:null)e=U(d),b||\"default\"==a.shape.toLowerCase()||(a=Xa(a),b=Math.min(Math.max(a.a,0),e.width),k=Math.min(Math.max(a.b,0),e.height),e=new R(b+e.a,k+e.b,Math.min(a.width,e.width-b),Math.min(a.height,e.height-k)))}return{image:d,rect:e||new R(0,0,0,0)}}\nfunction Xa(a){var b=a.shape.toLowerCase();a=a.coords.split(\",\");if(\"rect\"==b&&4==a.length){b=a[0];var c=a[1];return new R(b,c,a[2]-b,a[3]-c)}if(\"circle\"==b&&3==a.length)return b=a[2],new R(a[0]-b,a[1]-b,2*b,2*b);if(\"poly\"==b&&2<a.length){b=a[0];c=a[1];for(var d=b,e=c,f=2;f+1<a.length;f+=2)b=Math.min(b,a[f]),d=Math.max(d,a[f]),c=Math.min(c,a[f+1]),e=Math.max(e,a[f+1]);return new R(b,c,d-b,e-c)}return new R(0,0,0,0)}function Wa(a){a=U(a);return new P(a.b,a.a+a.width,a.b+a.height,a.a)}\nfunction Ya(a){return a.replace(/^[^\\S\\xa0]+|[^\\S\\xa0]+$/g,\"\")}\nfunction Za(a,b,c){if(O(a,\"BR\"))b.push(\"\");else{var d=O(a,\"TD\"),e=T(a,\"display\"),f=!d&&!(0<=ca($a,e)),g=void 0!==a.previousElementSibling?a.previousElementSibling:Ma(a.previousSibling);g=g?T(g,\"display\"):\"\";var k=T(a,\"float\")||T(a,\"cssFloat\")||T(a,\"styleFloat\");!f||\"run-in\"==g&&\"none\"==k||/^[\\s\\xa0]*$/.test(b[b.length-1]||\"\")||b.push(\"\");var y=Va(a),H=null,r=null;y&&(H=T(a,\"white-space\"),r=T(a,\"text-transform\"));m(a.childNodes,function(t){c(t,b,y,H,r)});a=b[b.length-1]||\"\";!d&&\"table-cell\"!=e||!a||\noa(a)||(b[b.length-1]+=\" \");f&&\"run-in\"!=e&&!/^[\\s\\xa0]*$/.test(a)&&b.push(\"\")}}function ab(a,b){Za(a,b,function(c,d,e,f,g){3==c.nodeType&&e?bb(c,d,f,g):O(c)&&ab(c,d)})}var $a=\"inline inline-block inline-table none table-cell table-column table-column-group\".split(\" \");\nfunction bb(a,b,c,d){a=a.nodeValue.replace(/[\\u200b\\u200e\\u200f]/g,\"\");a=a.replace(/(\\r\\n|\\r|\\n)/g,\"\\n\");if(\"normal\"==c||\"nowrap\"==c)a=a.replace(/\\n/g,\" \");a=\"pre\"==c||\"pre-wrap\"==c?a.replace(/[ \\f\\t\\v\\u2028\\u2029]/g,\"\\u00a0\"):a.replace(/[ \\f\\t\\v\\u2028\\u2029]+/g,\" \");\"capitalize\"==d?a=a.replace(E?/(^|\\s|\\b)(\\S)/g:/(^|\\s|\\b)(\\S)/gu,function(e,f,g){return f+g.toUpperCase()}):\"uppercase\"==d?a=a.toUpperCase():\"lowercase\"==d&&(a=a.toLowerCase());c=b.pop()||\"\";oa(c)&&0==a.lastIndexOf(\" \",0)&&(a=a.substr(1));\nb.push(c+a)}function Ua(a){if(La){if(\"relative\"==T(a,\"position\"))return 1;a=T(a,\"filter\");return(a=a.match(/^alpha\\(opacity=(\\d*)\\)/)||a.match(/^progid:DXImageTransform.Microsoft.Alpha\\(Opacity=(\\d*)\\)/))?Number(a[1])/100:1}return cb(a)}function cb(a){var b=1,c=T(a,\"opacity\");c&&(b=Number(c));(a=S(a))&&(b*=cb(a));return b}\nfunction W(a,b,c,d,e){if(3==a.nodeType&&c)bb(a,b,d,e);else if(O(a))if(O(a,\"CONTENT\")||O(a,\"SLOT\")){for(var f=a;f.parentNode;)f=f.parentNode;f instanceof ShadowRoot?(f=O(a,\"CONTENT\")?a.getDistributedNodes():a.assignedNodes(),m(0<f.length?f:a.childNodes,function(g){W(g,b,c,d,e)})):db(a,b)}else if(O(a,\"SHADOW\")){for(f=a;f.parentNode;)f=f.parentNode;if(f instanceof ShadowRoot&&(a=f))for(a=a.olderShadowRoot;a;)m(a.childNodes,function(g){W(g,b,c,d,e)}),a=a.olderShadowRoot}else db(a,b)}\nfunction db(a,b){a.shadowRoot&&m(a.shadowRoot.childNodes,function(c){W(c,b,!0,null,null)});Za(a,b,function(c,d,e,f,g){var k=null;1==c.nodeType?k=c:3==c.nodeType&&(k=c);null!=k&&(null!=k.assignedSlot||k.getDestinationInsertionPoints&&0<k.getDestinationInsertionPoints().length)||W(c,d,e,f,g)})};function eb(a){var b=[];Pa?db(a,b):ab(a,b);a=da(b,Ya);return Ya(a.join(\"\\n\")).replace(/\\xa0/g,\" \")}var X=[\"_\"],Y=h;X[0]in Y||\"undefined\"==typeof Y.execScript||Y.execScript(\"var \"+X[0]);for(var Z;X.length&&(Z=X.shift());)X.length||void 0===eb?Y[Z]&&Y[Z]!==Object.prototype[Z]?Y=Y[Z]:Y=Y[Z]={}:Y[Z]=eb;; return this._.apply(null,arguments);}).apply({navigator:typeof window!='undefined'?window.navigator:null,document:typeof window!='undefined'?window.document:null}, arguments);}\n", isElementDisplayed: "function(){return (function(){var k=this||self;function aa(a){return\"string\"==typeof a}function ba(a,b){a=a.split(\".\");var c=k;a[0]in c||\"undefined\"==typeof c.execScript||c.execScript(\"var \"+a[0]);for(var d;a.length&&(d=a.shift());)a.length||void 0===b?c[d]&&c[d]!==Object.prototype[d]?c=c[d]:c=c[d]={}:c[d]=b}\nfunction ca(a){var b=typeof a;if(\"object\"==b)if(a){if(a instanceof Array)return\"array\";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if(\"[object Window]\"==c)return\"object\";if(\"[object Array]\"==c||\"number\"==typeof a.length&&\"undefined\"!=typeof a.splice&&\"undefined\"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable(\"splice\"))return\"array\";if(\"[object Function]\"==c||\"undefined\"!=typeof a.call&&\"undefined\"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable(\"call\"))return\"function\"}else return\"null\";\nelse if(\"function\"==b&&\"undefined\"==typeof a.call)return\"object\";return b}function da(a,b,c){return a.call.apply(a.bind,arguments)}function ea(a,b,c){if(!a)throw Error();if(2<arguments.length){var d=Array.prototype.slice.call(arguments,2);return function(){var e=Array.prototype.slice.call(arguments);Array.prototype.unshift.apply(e,d);return a.apply(b,e)}}return function(){return a.apply(b,arguments)}}\nfunction fa(a,b,c){Function.prototype.bind&&-1!=Function.prototype.bind.toString().indexOf(\"native code\")?fa=da:fa=ea;return fa.apply(null,arguments)}function ha(a,b){var c=Array.prototype.slice.call(arguments,1);return function(){var d=c.slice();d.push.apply(d,arguments);return a.apply(this,d)}}function l(a,b){function c(){}c.prototype=b.prototype;a.prototype=new c;a.prototype.constructor=a};/*\n\n The MIT License\n\n Copyright (c) 2007 Cybozu Labs, Inc.\n Copyright (c) 2012 Google Inc.\n\n Permission is hereby granted, free of charge, to any person obtaining a copy\n of this software and associated documentation files (the \"Software\"), to\n deal in the Software without restriction, including without limitation the\n rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n sell copies of the Software, and to permit persons to whom the Software is\n furnished to do so, subject to the following conditions:\n\n The above copyright notice and this permission notice shall be included in\n all copies or substantial portions of the Software.\n\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n IN THE SOFTWARE.\n*/\nfunction ia(a,b,c){this.a=a;this.b=b||1;this.f=c||1};var ja=Array.prototype.indexOf?function(a,b){return Array.prototype.indexOf.call(a,b,void 0)}:function(a,b){if(\"string\"===typeof a)return\"string\"!==typeof b||1!=b.length?-1:a.indexOf(b,0);for(var c=0;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1},n=Array.prototype.forEach?function(a,b){Array.prototype.forEach.call(a,b,void 0)}:function(a,b){for(var c=a.length,d=\"string\"===typeof a?a.split(\"\"):a,e=0;e<c;e++)e in d&&b.call(void 0,d[e],e,a)},ka=Array.prototype.filter?function(a,b){return Array.prototype.filter.call(a,\nb,void 0)}:function(a,b){for(var c=a.length,d=[],e=0,f=\"string\"===typeof a?a.split(\"\"):a,g=0;g<c;g++)if(g in f){var h=f[g];b.call(void 0,h,g,a)&&(d[e++]=h)}return d},la=Array.prototype.reduce?function(a,b,c){return Array.prototype.reduce.call(a,b,c)}:function(a,b,c){var d=c;n(a,function(e,f){d=b.call(void 0,d,e,f,a)});return d},ma=Array.prototype.some?function(a,b){return Array.prototype.some.call(a,b,void 0)}:function(a,b){for(var c=a.length,d=\"string\"===typeof a?a.split(\"\"):a,e=0;e<c;e++)if(e in\nd&&b.call(void 0,d[e],e,a))return!0;return!1},na=Array.prototype.every?function(a,b){return Array.prototype.every.call(a,b,void 0)}:function(a,b){for(var c=a.length,d=\"string\"===typeof a?a.split(\"\"):a,e=0;e<c;e++)if(e in d&&!b.call(void 0,d[e],e,a))return!1;return!0};function oa(a,b){a:{for(var c=a.length,d=\"string\"===typeof a?a.split(\"\"):a,e=0;e<c;e++)if(e in d&&b.call(void 0,d[e],e,a)){b=e;break a}b=-1}return 0>b?null:\"string\"===typeof a?a.charAt(b):a[b]}\nfunction pa(a){return Array.prototype.concat.apply([],arguments)}function qa(a,b,c){return 2>=arguments.length?Array.prototype.slice.call(a,b):Array.prototype.slice.call(a,b,c)};var ra=String.prototype.trim?function(a){return a.trim()}:function(a){return/^[\\s\\xa0]*([\\s\\S]*?)[\\s\\xa0]*$/.exec(a)[1]};function sa(a,b){return a<b?-1:a>b?1:0};var t;a:{var ta=k.navigator;if(ta){var ua=ta.userAgent;if(ua){t=ua;break a}}t=\"\"}function u(a){return-1!=t.indexOf(a)};function va(){return u(\"Firefox\")||u(\"FxiOS\")}function wa(){return(u(\"Chrome\")||u(\"CriOS\"))&&!u(\"Edge\")};function xa(a){return String(a).replace(/\\-([a-z])/g,function(b,c){return c.toUpperCase()})};function ya(){return u(\"iPhone\")&&!u(\"iPod\")&&!u(\"iPad\")};function za(a,b){var c=Aa;return Object.prototype.hasOwnProperty.call(c,a)?c[a]:c[a]=b(a)};var Ba=u(\"Opera\"),v=u(\"Trident\")||u(\"MSIE\"),Ca=u(\"Edge\"),Da=u(\"Gecko\")&&!(-1!=t.toLowerCase().indexOf(\"webkit\")&&!u(\"Edge\"))&&!(u(\"Trident\")||u(\"MSIE\"))&&!u(\"Edge\"),Ea=-1!=t.toLowerCase().indexOf(\"webkit\")&&!u(\"Edge\");function Fa(){var a=k.document;return a?a.documentMode:void 0}var Ga;\na:{var Ha=\"\",Ia=function(){var a=t;if(Da)return/rv:([^\\);]+)(\\)|;)/.exec(a);if(Ca)return/Edge\\/([\\d\\.]+)/.exec(a);if(v)return/\\b(?:MSIE|rv)[: ]([^\\);]+)(\\)|;)/.exec(a);if(Ea)return/WebKit\\/(\\S+)/.exec(a);if(Ba)return/(?:Version)[ \\/]?(\\S+)/.exec(a)}();Ia&&(Ha=Ia?Ia[1]:\"\");if(v){var Ja=Fa();if(null!=Ja&&Ja>parseFloat(Ha)){Ga=String(Ja);break a}}Ga=Ha}var Aa={};\nfunction Ka(a){return za(a,function(){for(var b=0,c=ra(String(Ga)).split(\".\"),d=ra(String(a)).split(\".\"),e=Math.max(c.length,d.length),f=0;0==b&&f<e;f++){var g=c[f]||\"\",h=d[f]||\"\";do{g=/(\\d*)(\\D*)(.*)/.exec(g)||[\"\",\"\",\"\",\"\"];h=/(\\d*)(\\D*)(.*)/.exec(h)||[\"\",\"\",\"\",\"\"];if(0==g[0].length&&0==h[0].length)break;b=sa(0==g[1].length?0:parseInt(g[1],10),0==h[1].length?0:parseInt(h[1],10))||sa(0==g[2].length,0==h[2].length)||sa(g[2],h[2]);g=g[3];h=h[3]}while(0==b)}return 0<=b})}var La;\nLa=k.document&&v?Fa():void 0;var x=v&&!(9<=Number(La)),Ma=v&&!(8<=Number(La));function Na(a,b,c,d){this.a=a;this.nodeName=c;this.nodeValue=d;this.nodeType=2;this.parentNode=this.ownerElement=b}function Oa(a,b){var c=Ma&&\"href\"==b.nodeName?a.getAttribute(b.nodeName,2):b.nodeValue;return new Na(b,a,b.nodeName,c)};function Pa(a){this.b=a;this.a=0}function Qa(a){a=a.match(Ra);for(var b=0;b<a.length;b++)Sa.test(a[b])&&a.splice(b,1);return new Pa(a)}var Ra=/\\$?(?:(?![0-9-\\.])(?:\\*|[\\w-\\.]+):)?(?![0-9-\\.])(?:\\*|[\\w-\\.]+)|\\/\\/|\\.\\.|::|\\d+(?:\\.\\d*)?|\\.\\d+|\"[^\"]*\"|'[^']*'|[!<>]=|\\s+|./g,Sa=/^\\s/;function y(a,b){return a.b[a.a+(b||0)]}function z(a){return a.b[a.a++]}function Ta(a){return a.b.length<=a.a};function Ua(a,b){this.x=void 0!==a?a:0;this.y=void 0!==b?b:0}Ua.prototype.ceil=function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);return this};Ua.prototype.floor=function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);return this};Ua.prototype.round=function(){this.x=Math.round(this.x);this.y=Math.round(this.y);return this};function Va(a,b){this.width=a;this.height=b}Va.prototype.aspectRatio=function(){return this.width/this.height};Va.prototype.ceil=function(){this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};Va.prototype.floor=function(){this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};Va.prototype.round=function(){this.width=Math.round(this.width);this.height=Math.round(this.height);return this};function Wa(a,b){if(!a||!b)return!1;if(a.contains&&1==b.nodeType)return a==b||a.contains(b);if(\"undefined\"!=typeof a.compareDocumentPosition)return a==b||!!(a.compareDocumentPosition(b)&16);for(;b&&a!=b;)b=b.parentNode;return b==a}\nfunction Xa(a,b){if(a==b)return 0;if(a.compareDocumentPosition)return a.compareDocumentPosition(b)&2?1:-1;if(v&&!(9<=Number(La))){if(9==a.nodeType)return-1;if(9==b.nodeType)return 1}if(\"sourceIndex\"in a||a.parentNode&&\"sourceIndex\"in a.parentNode){var c=1==a.nodeType,d=1==b.nodeType;if(c&&d)return a.sourceIndex-b.sourceIndex;var e=a.parentNode,f=b.parentNode;return e==f?Ya(a,b):!c&&Wa(e,b)?-1*Za(a,b):!d&&Wa(f,a)?Za(b,a):(c?a.sourceIndex:e.sourceIndex)-(d?b.sourceIndex:f.sourceIndex)}d=A(a);c=d.createRange();\nc.selectNode(a);c.collapse(!0);a=d.createRange();a.selectNode(b);a.collapse(!0);return c.compareBoundaryPoints(k.Range.START_TO_END,a)}function Za(a,b){var c=a.parentNode;if(c==b)return-1;for(;b.parentNode!=c;)b=b.parentNode;return Ya(b,a)}function Ya(a,b){for(;b=b.previousSibling;)if(b==a)return-1;return 1}function A(a){return 9==a.nodeType?a:a.ownerDocument||a.document}function $a(a,b){a&&(a=a.parentNode);for(var c=0;a;){if(b(a))return a;a=a.parentNode;c++}return null}\nfunction ab(a){this.a=a||k.document||document}ab.prototype.getElementsByTagName=function(a,b){return(b||this.a).getElementsByTagName(String(a))};function B(a){var b=null,c=a.nodeType;1==c&&(b=a.textContent,b=void 0==b||null==b?a.innerText:b,b=void 0==b||null==b?\"\":b);if(\"string\"!=typeof b)if(x&&\"title\"==a.nodeName.toLowerCase()&&1==c)b=a.text;else if(9==c||1==c){a=9==c?a.documentElement:a.firstChild;c=0;var d=[];for(b=\"\";a;){do 1!=a.nodeType&&(b+=a.nodeValue),x&&\"title\"==a.nodeName.toLowerCase()&&(b+=a.text),d[c++]=a;while(a=a.firstChild);for(;c&&!(a=d[--c].nextSibling););}}else b=a.nodeValue;return b}\nfunction C(a,b,c){if(null===b)return!0;try{if(!a.getAttribute)return!1}catch(d){return!1}Ma&&\"class\"==b&&(b=\"className\");return null==c?!!a.getAttribute(b):a.getAttribute(b,2)==c}function bb(a,b,c,d,e){return(x?cb:db).call(null,a,b,aa(c)?c:null,aa(d)?d:null,e||new E)}\nfunction cb(a,b,c,d,e){if(a instanceof F||8==a.b||c&&null===a.b){var f=b.all;if(!f)return e;a=eb(a);if(\"*\"!=a&&(f=b.getElementsByTagName(a),!f))return e;if(c){for(var g=[],h=0;b=f[h++];)C(b,c,d)&&g.push(b);f=g}for(h=0;b=f[h++];)\"*\"==a&&\"!\"==b.tagName||e.add(b);return e}gb(a,b,c,d,e);return e}\nfunction db(a,b,c,d,e){b.getElementsByName&&d&&\"name\"==c&&!v?(b=b.getElementsByName(d),n(b,function(f){a.a(f)&&e.add(f)})):b.getElementsByClassName&&d&&\"class\"==c?(b=b.getElementsByClassName(d),n(b,function(f){f.className==d&&a.a(f)&&e.add(f)})):a instanceof G?gb(a,b,c,d,e):b.getElementsByTagName&&(b=b.getElementsByTagName(a.f()),n(b,function(f){C(f,c,d)&&e.add(f)}));return e}\nfunction hb(a,b,c,d,e){var f;if((a instanceof F||8==a.b||c&&null===a.b)&&(f=b.childNodes)){var g=eb(a);if(\"*\"!=g&&(f=ka(f,function(h){return h.tagName&&h.tagName.toLowerCase()==g}),!f))return e;c&&(f=ka(f,function(h){return C(h,c,d)}));n(f,function(h){\"*\"==g&&(\"!\"==h.tagName||\"*\"==g&&1!=h.nodeType)||e.add(h)});return e}return ib(a,b,c,d,e)}function ib(a,b,c,d,e){for(b=b.firstChild;b;b=b.nextSibling)C(b,c,d)&&a.a(b)&&e.add(b);return e}\nfunction gb(a,b,c,d,e){for(b=b.firstChild;b;b=b.nextSibling)C(b,c,d)&&a.a(b)&&e.add(b),gb(a,b,c,d,e)}function eb(a){if(a instanceof G){if(8==a.b)return\"!\";if(null===a.b)return\"*\"}return a.f()};function E(){this.b=this.a=null;this.l=0}function jb(a){this.f=a;this.a=this.b=null}function kb(a,b){if(!a.a)return b;if(!b.a)return a;var c=a.a;b=b.a;for(var d=null,e,f=0;c&&b;){e=c.f;var g=b.f;e==g||e instanceof Na&&g instanceof Na&&e.a==g.a?(e=c,c=c.a,b=b.a):0<Xa(c.f,b.f)?(e=b,b=b.a):(e=c,c=c.a);(e.b=d)?d.a=e:a.a=e;d=e;f++}for(e=c||b;e;)e.b=d,d=d.a=e,f++,e=e.a;a.b=d;a.l=f;return a}function lb(a,b){b=new jb(b);b.a=a.a;a.b?a.a.b=b:a.a=a.b=b;a.a=b;a.l++}\nE.prototype.add=function(a){a=new jb(a);a.b=this.b;this.a?this.b.a=a:this.a=this.b=a;this.b=a;this.l++};function mb(a){return(a=a.a)?a.f:null}function nb(a){return(a=mb(a))?B(a):\"\"}function H(a,b){return new ob(a,!!b)}function ob(a,b){this.f=a;this.b=(this.s=b)?a.b:a.a;this.a=null}function I(a){var b=a.b;if(null==b)return null;var c=a.a=b;a.b=a.s?b.b:b.a;return c.f};function J(a){this.i=a;this.b=this.g=!1;this.f=null}function K(a){return\"\\n \"+a.toString().split(\"\\n\").join(\"\\n \")}function pb(a,b){a.g=b}function qb(a,b){a.b=b}function N(a,b){a=a.a(b);return a instanceof E?+nb(a):+a}function O(a,b){a=a.a(b);return a instanceof E?nb(a):\"\"+a}function rb(a,b){a=a.a(b);return a instanceof E?!!a.l:!!a};function sb(a,b,c){J.call(this,a.i);this.c=a;this.h=b;this.o=c;this.g=b.g||c.g;this.b=b.b||c.b;this.c==tb&&(c.b||c.g||4==c.i||0==c.i||!b.f?b.b||b.g||4==b.i||0==b.i||!c.f||(this.f={name:c.f.name,u:b}):this.f={name:b.f.name,u:c})}l(sb,J);\nfunction ub(a,b,c,d,e){b=b.a(d);c=c.a(d);var f;if(b instanceof E&&c instanceof E){b=H(b);for(d=I(b);d;d=I(b))for(e=H(c),f=I(e);f;f=I(e))if(a(B(d),B(f)))return!0;return!1}if(b instanceof E||c instanceof E){b instanceof E?(e=b,d=c):(e=c,d=b);f=H(e);for(var g=typeof d,h=I(f);h;h=I(f)){switch(g){case \"number\":h=+B(h);break;case \"boolean\":h=!!B(h);break;case \"string\":h=B(h);break;default:throw Error(\"Illegal primitive type for comparison.\");}if(e==b&&a(h,d)||e==c&&a(d,h))return!0}return!1}return e?\"boolean\"==\ntypeof b||\"boolean\"==typeof c?a(!!b,!!c):\"number\"==typeof b||\"number\"==typeof c?a(+b,+c):a(b,c):a(+b,+c)}sb.prototype.a=function(a){return this.c.m(this.h,this.o,a)};sb.prototype.toString=function(){var a=\"Binary Expression: \"+this.c;a+=K(this.h);return a+=K(this.o)};function vb(a,b,c,d){this.I=a;this.D=b;this.i=c;this.m=d}vb.prototype.toString=function(){return this.I};var wb={};\nfunction P(a,b,c,d){if(wb.hasOwnProperty(a))throw Error(\"Binary operator already created: \"+a);a=new vb(a,b,c,d);return wb[a.toString()]=a}P(\"div\",6,1,function(a,b,c){return N(a,c)/N(b,c)});P(\"mod\",6,1,function(a,b,c){return N(a,c)%N(b,c)});P(\"*\",6,1,function(a,b,c){return N(a,c)*N(b,c)});P(\"+\",5,1,function(a,b,c){return N(a,c)+N(b,c)});P(\"-\",5,1,function(a,b,c){return N(a,c)-N(b,c)});P(\"<\",4,2,function(a,b,c){return ub(function(d,e){return d<e},a,b,c)});\nP(\">\",4,2,function(a,b,c){return ub(function(d,e){return d>e},a,b,c)});P(\"<=\",4,2,function(a,b,c){return ub(function(d,e){return d<=e},a,b,c)});P(\">=\",4,2,function(a,b,c){return ub(function(d,e){return d>=e},a,b,c)});var tb=P(\"=\",3,2,function(a,b,c){return ub(function(d,e){return d==e},a,b,c,!0)});P(\"!=\",3,2,function(a,b,c){return ub(function(d,e){return d!=e},a,b,c,!0)});P(\"and\",2,2,function(a,b,c){return rb(a,c)&&rb(b,c)});P(\"or\",1,2,function(a,b,c){return rb(a,c)||rb(b,c)});function xb(a,b){if(b.a.length&&4!=a.i)throw Error(\"Primary expression must evaluate to nodeset if filter has predicate(s).\");J.call(this,a.i);this.c=a;this.h=b;this.g=a.g;this.b=a.b}l(xb,J);xb.prototype.a=function(a){a=this.c.a(a);return yb(this.h,a)};xb.prototype.toString=function(){var a=\"Filter:\"+K(this.c);return a+=K(this.h)};function zb(a,b){if(b.length<a.C)throw Error(\"Function \"+a.j+\" expects at least\"+a.C+\" arguments, \"+b.length+\" given\");if(null!==a.B&&b.length>a.B)throw Error(\"Function \"+a.j+\" expects at most \"+a.B+\" arguments, \"+b.length+\" given\");a.H&&n(b,function(c,d){if(4!=c.i)throw Error(\"Argument \"+d+\" to function \"+a.j+\" is not of type Nodeset: \"+c);});J.call(this,a.i);this.v=a;this.c=b;pb(this,a.g||ma(b,function(c){return c.g}));qb(this,a.G&&!b.length||a.F&&!!b.length||ma(b,function(c){return c.b}))}\nl(zb,J);zb.prototype.a=function(a){return this.v.m.apply(null,pa(a,this.c))};zb.prototype.toString=function(){var a=\"Function: \"+this.v;if(this.c.length){var b=la(this.c,function(c,d){return c+K(d)},\"Arguments:\");a+=K(b)}return a};function Ab(a,b,c,d,e,f,g,h){this.j=a;this.i=b;this.g=c;this.G=d;this.F=!1;this.m=e;this.C=f;this.B=void 0!==g?g:f;this.H=!!h}Ab.prototype.toString=function(){return this.j};var Bb={};\nfunction Q(a,b,c,d,e,f,g,h){if(Bb.hasOwnProperty(a))throw Error(\"Function already created: \"+a+\".\");Bb[a]=new Ab(a,b,c,d,e,f,g,h)}Q(\"boolean\",2,!1,!1,function(a,b){return rb(b,a)},1);Q(\"ceiling\",1,!1,!1,function(a,b){return Math.ceil(N(b,a))},1);Q(\"concat\",3,!1,!1,function(a,b){return la(qa(arguments,1),function(c,d){return c+O(d,a)},\"\")},2,null);Q(\"contains\",2,!1,!1,function(a,b,c){b=O(b,a);a=O(c,a);return-1!=b.indexOf(a)},2);Q(\"count\",1,!1,!1,function(a,b){return b.a(a).l},1,1,!0);\nQ(\"false\",2,!1,!1,function(){return!1},0);Q(\"floor\",1,!1,!1,function(a,b){return Math.floor(N(b,a))},1);Q(\"id\",4,!1,!1,function(a,b){function c(h){if(x){var m=e.all[h];if(m){if(m.nodeType&&h==m.id)return m;if(m.length)return oa(m,function(w){return h==w.id})}return null}return e.getElementById(h)}var d=a.a,e=9==d.nodeType?d:d.ownerDocument;a=O(b,a).split(/\\s+/);var f=[];n(a,function(h){h=c(h);!h||0<=ja(f,h)||f.push(h)});f.sort(Xa);var g=new E;n(f,function(h){g.add(h)});return g},1);\nQ(\"lang\",2,!1,!1,function(){return!1},1);Q(\"last\",1,!0,!1,function(a){if(1!=arguments.length)throw Error(\"Function last expects ()\");return a.f},0);Q(\"local-name\",3,!1,!0,function(a,b){return(a=b?mb(b.a(a)):a.a)?a.localName||a.nodeName.toLowerCase():\"\"},0,1,!0);Q(\"name\",3,!1,!0,function(a,b){return(a=b?mb(b.a(a)):a.a)?a.nodeName.toLowerCase():\"\"},0,1,!0);Q(\"namespace-uri\",3,!0,!1,function(){return\"\"},0,1,!0);\nQ(\"normalize-space\",3,!1,!0,function(a,b){return(b?O(b,a):B(a.a)).replace(/[\\s\\xa0]+/g,\" \").replace(/^\\s+|\\s+$/g,\"\")},0,1);Q(\"not\",2,!1,!1,function(a,b){return!rb(b,a)},1);Q(\"number\",1,!1,!0,function(a,b){return b?N(b,a):+B(a.a)},0,1);Q(\"position\",1,!0,!1,function(a){return a.b},0);Q(\"round\",1,!1,!1,function(a,b){return Math.round(N(b,a))},1);Q(\"starts-with\",2,!1,!1,function(a,b,c){b=O(b,a);a=O(c,a);return 0==b.lastIndexOf(a,0)},2);Q(\"string\",3,!1,!0,function(a,b){return b?O(b,a):B(a.a)},0,1);\nQ(\"string-length\",1,!1,!0,function(a,b){return(b?O(b,a):B(a.a)).length},0,1);Q(\"substring\",3,!1,!1,function(a,b,c,d){c=N(c,a);if(isNaN(c)||Infinity==c||-Infinity==c)return\"\";d=d?N(d,a):Infinity;if(isNaN(d)||-Infinity===d)return\"\";c=Math.round(c)-1;var e=Math.max(c,0);a=O(b,a);return Infinity==d?a.substring(e):a.substring(e,c+Math.round(d))},2,3);Q(\"substring-after\",3,!1,!1,function(a,b,c){b=O(b,a);a=O(c,a);c=b.indexOf(a);return-1==c?\"\":b.substring(c+a.length)},2);\nQ(\"substring-before\",3,!1,!1,function(a,b,c){b=O(b,a);a=O(c,a);a=b.indexOf(a);return-1==a?\"\":b.substring(0,a)},2);Q(\"sum\",1,!1,!1,function(a,b){a=H(b.a(a));b=0;for(var c=I(a);c;c=I(a))b+=+B(c);return b},1,1,!0);Q(\"translate\",3,!1,!1,function(a,b,c,d){b=O(b,a);c=O(c,a);var e=O(d,a);a={};for(d=0;d<c.length;d++){var f=c.charAt(d);f in a||(a[f]=e.charAt(d))}c=\"\";for(d=0;d<b.length;d++)f=b.charAt(d),c+=f in a?a[f]:f;return c},3);Q(\"true\",2,!1,!1,function(){return!0},0);function G(a,b){this.h=a;this.c=void 0!==b?b:null;this.b=null;switch(a){case \"comment\":this.b=8;break;case \"text\":this.b=3;break;case \"processing-instruction\":this.b=7;break;case \"node\":break;default:throw Error(\"Unexpected argument\");}}function Cb(a){return\"comment\"==a||\"text\"==a||\"processing-instruction\"==a||\"node\"==a}G.prototype.a=function(a){return null===this.b||this.b==a.nodeType};G.prototype.f=function(){return this.h};\nG.prototype.toString=function(){var a=\"Kind Test: \"+this.h;null===this.c||(a+=K(this.c));return a};function Db(a){J.call(this,3);this.c=a.substring(1,a.length-1)}l(Db,J);Db.prototype.a=function(){return this.c};Db.prototype.toString=function(){return\"Literal: \"+this.c};function F(a,b){this.j=a.toLowerCase();a=\"*\"==this.j?\"*\":\"http://www.w3.org/1999/xhtml\";this.c=b?b.toLowerCase():a}F.prototype.a=function(a){var b=a.nodeType;if(1!=b&&2!=b)return!1;b=void 0!==a.localName?a.localName:a.nodeName;return\"*\"!=this.j&&this.j!=b.toLowerCase()?!1:\"*\"==this.c?!0:this.c==(a.namespaceURI?a.namespaceURI.toLowerCase():\"http://www.w3.org/1999/xhtml\")};F.prototype.f=function(){return this.j};\nF.prototype.toString=function(){return\"Name Test: \"+(\"http://www.w3.org/1999/xhtml\"==this.c?\"\":this.c+\":\")+this.j};function Eb(a){J.call(this,1);this.c=a}l(Eb,J);Eb.prototype.a=function(){return this.c};Eb.prototype.toString=function(){return\"Number: \"+this.c};function Fb(a,b){J.call(this,a.i);this.h=a;this.c=b;this.g=a.g;this.b=a.b;1==this.c.length&&(a=this.c[0],a.A||a.c!=Gb||(a=a.o,\"*\"!=a.f()&&(this.f={name:a.f(),u:null})))}l(Fb,J);function Hb(){J.call(this,4)}l(Hb,J);Hb.prototype.a=function(a){var b=new E;a=a.a;9==a.nodeType?b.add(a):b.add(a.ownerDocument);return b};Hb.prototype.toString=function(){return\"Root Helper Expression\"};function Ib(){J.call(this,4)}l(Ib,J);Ib.prototype.a=function(a){var b=new E;b.add(a.a);return b};Ib.prototype.toString=function(){return\"Context Helper Expression\"};\nfunction Jb(a){return\"/\"==a||\"//\"==a}Fb.prototype.a=function(a){var b=this.h.a(a);if(!(b instanceof E))throw Error(\"Filter expression must evaluate to nodeset.\");a=this.c;for(var c=0,d=a.length;c<d&&b.l;c++){var e=a[c],f=H(b,e.c.s);if(e.g||e.c!=Kb)if(e.g||e.c!=Lb){var g=I(f);for(b=e.a(new ia(g));null!=(g=I(f));)g=e.a(new ia(g)),b=kb(b,g)}else g=I(f),b=e.a(new ia(g));else{for(g=I(f);(b=I(f))&&(!g.contains||g.contains(b))&&b.compareDocumentPosition(g)&8;g=b);b=e.a(new ia(g))}}return b};\nFb.prototype.toString=function(){var a=\"Path Expression:\"+K(this.h);if(this.c.length){var b=la(this.c,function(c,d){return c+K(d)},\"Steps:\");a+=K(b)}return a};function Mb(a,b){this.a=a;this.s=!!b}\nfunction yb(a,b,c){for(c=c||0;c<a.a.length;c++)for(var d=a.a[c],e=H(b),f=b.l,g,h=0;g=I(e);h++){var m=a.s?f-h:h+1;g=d.a(new ia(g,m,f));if(\"number\"==typeof g)m=m==g;else if(\"string\"==typeof g||\"boolean\"==typeof g)m=!!g;else if(g instanceof E)m=0<g.l;else throw Error(\"Predicate.evaluate returned an unexpected type.\");if(!m){m=e;g=m.f;var w=m.a;if(!w)throw Error(\"Next must be called at least once before remove.\");var r=w.b;w=w.a;r?r.a=w:g.a=w;w?w.b=r:g.b=r;g.l--;m.a=null}}return b}\nMb.prototype.toString=function(){return la(this.a,function(a,b){return a+K(b)},\"Predicates:\")};function R(a,b,c,d){J.call(this,4);this.c=a;this.o=b;this.h=c||new Mb([]);this.A=!!d;b=this.h;b=0<b.a.length?b.a[0].f:null;a.J&&b&&(a=b.name,a=x?a.toLowerCase():a,this.f={name:a,u:b.u});a:{a=this.h;for(b=0;b<a.a.length;b++)if(c=a.a[b],c.g||1==c.i||0==c.i){a=!0;break a}a=!1}this.g=a}l(R,J);\nR.prototype.a=function(a){var b=a.a,c=this.f,d=null,e=null,f=0;c&&(d=c.name,e=c.u?O(c.u,a):null,f=1);if(this.A)if(this.g||this.c!=Nb)if(b=H((new R(Ob,new G(\"node\"))).a(a)),c=I(b))for(a=this.m(c,d,e,f);null!=(c=I(b));)a=kb(a,this.m(c,d,e,f));else a=new E;else a=bb(this.o,b,d,e),a=yb(this.h,a,f);else a=this.m(a.a,d,e,f);return a};R.prototype.m=function(a,b,c,d){a=this.c.v(this.o,a,b,c);return a=yb(this.h,a,d)};\nR.prototype.toString=function(){var a=\"Step:\"+K(\"Operator: \"+(this.A?\"//\":\"/\"));this.c.j&&(a+=K(\"Axis: \"+this.c));a+=K(this.o);if(this.h.a.length){var b=la(this.h.a,function(c,d){return c+K(d)},\"Predicates:\");a+=K(b)}return a};function Pb(a,b,c,d){this.j=a;this.v=b;this.s=c;this.J=d}Pb.prototype.toString=function(){return this.j};var Qb={};function S(a,b,c,d){if(Qb.hasOwnProperty(a))throw Error(\"Axis already created: \"+a);b=new Pb(a,b,c,!!d);return Qb[a]=b}\nS(\"ancestor\",function(a,b){for(var c=new E;b=b.parentNode;)a.a(b)&&lb(c,b);return c},!0);S(\"ancestor-or-self\",function(a,b){var c=new E;do a.a(b)&&lb(c,b);while(b=b.parentNode);return c},!0);\nvar Gb=S(\"attribute\",function(a,b){var c=new E,d=a.f();if(\"style\"==d&&x&&b.style)return c.add(new Na(b.style,b,\"style\",b.style.cssText)),c;var e=b.attributes;if(e)if(a instanceof G&&null===a.b||\"*\"==d)for(a=0;d=e[a];a++)x?d.nodeValue&&c.add(Oa(b,d)):c.add(d);else(d=e.getNamedItem(d))&&(x?d.nodeValue&&c.add(Oa(b,d)):c.add(d));return c},!1),Nb=S(\"child\",function(a,b,c,d,e){return(x?hb:ib).call(null,a,b,aa(c)?c:null,aa(d)?d:null,e||new E)},!1,!0);S(\"descendant\",bb,!1,!0);\nvar Ob=S(\"descendant-or-self\",function(a,b,c,d){var e=new E;C(b,c,d)&&a.a(b)&&e.add(b);return bb(a,b,c,d,e)},!1,!0),Kb=S(\"following\",function(a,b,c,d){var e=new E;do for(var f=b;f=f.nextSibling;)C(f,c,d)&&a.a(f)&&e.add(f),e=bb(a,f,c,d,e);while(b=b.parentNode);return e},!1,!0);S(\"following-sibling\",function(a,b){for(var c=new E;b=b.nextSibling;)a.a(b)&&c.add(b);return c},!1);S(\"namespace\",function(){return new E},!1);\nvar Rb=S(\"parent\",function(a,b){var c=new E;if(9==b.nodeType)return c;if(2==b.nodeType)return c.add(b.ownerElement),c;b=b.parentNode;a.a(b)&&c.add(b);return c},!1),Lb=S(\"preceding\",function(a,b,c,d){var e=new E,f=[];do f.unshift(b);while(b=b.parentNode);for(var g=1,h=f.length;g<h;g++){var m=[];for(b=f[g];b=b.previousSibling;)m.unshift(b);for(var w=0,r=m.length;w<r;w++)b=m[w],C(b,c,d)&&a.a(b)&&e.add(b),e=bb(a,b,c,d,e)}return e},!0,!0);\nS(\"preceding-sibling\",function(a,b){for(var c=new E;b=b.previousSibling;)a.a(b)&&lb(c,b);return c},!0);var Sb=S(\"self\",function(a,b){var c=new E;a.a(b)&&c.add(b);return c},!1);function Tb(a){J.call(this,1);this.c=a;this.g=a.g;this.b=a.b}l(Tb,J);Tb.prototype.a=function(a){return-N(this.c,a)};Tb.prototype.toString=function(){return\"Unary Expression: -\"+K(this.c)};function Ub(a){J.call(this,4);this.c=a;pb(this,ma(this.c,function(b){return b.g}));qb(this,ma(this.c,function(b){return b.b}))}l(Ub,J);Ub.prototype.a=function(a){var b=new E;n(this.c,function(c){c=c.a(a);if(!(c instanceof E))throw Error(\"Path expression must evaluate to NodeSet.\");b=kb(b,c)});return b};Ub.prototype.toString=function(){return la(this.c,function(a,b){return a+K(b)},\"Union Expression:\")};function Vb(a,b){this.a=a;this.b=b}function Yb(a){for(var b,c=[];;){T(a,\"Missing right hand side of binary expression.\");b=Zb(a);var d=z(a.a);if(!d)break;var e=(d=wb[d]||null)&&d.D;if(!e){a.a.a--;break}for(;c.length&&e<=c[c.length-1].D;)b=new sb(c.pop(),c.pop(),b);c.push(b,d)}for(;c.length;)b=new sb(c.pop(),c.pop(),b);return b}function T(a,b){if(Ta(a.a))throw Error(b);}function $b(a,b){a=z(a.a);if(a!=b)throw Error(\"Bad token, expected: \"+b+\" got: \"+a);}\nfunction ac(a){a=z(a.a);if(\")\"!=a)throw Error(\"Bad token: \"+a);}function bc(a){a=z(a.a);if(2>a.length)throw Error(\"Unclosed literal string\");return new Db(a)}\nfunction cc(a){var b=[];if(Jb(y(a.a))){var c=z(a.a);var d=y(a.a);if(\"/\"==c&&(Ta(a.a)||\".\"!=d&&\"..\"!=d&&\"@\"!=d&&\"*\"!=d&&!/(?![0-9])[\\w]/.test(d)))return new Hb;d=new Hb;T(a,\"Missing next location step.\");c=dc(a,c);b.push(c)}else{a:{c=y(a.a);d=c.charAt(0);switch(d){case \"$\":throw Error(\"Variable reference not allowed in HTML XPath\");case \"(\":z(a.a);c=Yb(a);T(a,'unclosed \"(\"');$b(a,\")\");break;case '\"':case \"'\":c=bc(a);break;default:if(isNaN(+c))if(!Cb(c)&&/(?![0-9])[\\w]/.test(d)&&\"(\"==y(a.a,1)){c=z(a.a);\nc=Bb[c]||null;z(a.a);for(d=[];\")\"!=y(a.a);){T(a,\"Missing function argument list.\");d.push(Yb(a));if(\",\"!=y(a.a))break;z(a.a)}T(a,\"Unclosed function argument list.\");ac(a);c=new zb(c,d)}else{c=null;break a}else c=new Eb(+z(a.a))}\"[\"==y(a.a)&&(d=new Mb(ec(a)),c=new xb(c,d))}if(c)if(Jb(y(a.a)))d=c;else return c;else c=dc(a,\"/\"),d=new Ib,b.push(c)}for(;Jb(y(a.a));)c=z(a.a),T(a,\"Missing next location step.\"),c=dc(a,c),b.push(c);return new Fb(d,b)}\nfunction dc(a,b){if(\"/\"!=b&&\"//\"!=b)throw Error('Step op should be \"/\" or \"//\"');if(\".\"==y(a.a)){var c=new R(Sb,new G(\"node\"));z(a.a);return c}if(\"..\"==y(a.a))return c=new R(Rb,new G(\"node\")),z(a.a),c;if(\"@\"==y(a.a)){var d=Gb;z(a.a);T(a,\"Missing attribute name\")}else if(\"::\"==y(a.a,1)){if(!/(?![0-9])[\\w]/.test(y(a.a).charAt(0)))throw Error(\"Bad token: \"+z(a.a));var e=z(a.a);d=Qb[e]||null;if(!d)throw Error(\"No axis with name: \"+e);z(a.a);T(a,\"Missing node name\")}else d=Nb;e=y(a.a);if(/(?![0-9])[\\w\\*]/.test(e.charAt(0)))if(\"(\"==\ny(a.a,1)){if(!Cb(e))throw Error(\"Invalid node type: \"+e);e=z(a.a);if(!Cb(e))throw Error(\"Invalid type name: \"+e);$b(a,\"(\");T(a,\"Bad nodetype\");var f=y(a.a).charAt(0),g=null;if('\"'==f||\"'\"==f)g=bc(a);T(a,\"Bad nodetype\");ac(a);e=new G(e,g)}else if(e=z(a.a),f=e.indexOf(\":\"),-1==f)e=new F(e);else{g=e.substring(0,f);if(\"*\"==g)var h=\"*\";else if(h=a.b(g),!h)throw Error(\"Namespace prefix not declared: \"+g);e=e.substr(f+1);e=new F(e,h)}else throw Error(\"Bad token: \"+z(a.a));a=new Mb(ec(a),d.s);return c||new R(d,\ne,a,\"//\"==b)}function ec(a){for(var b=[];\"[\"==y(a.a);){z(a.a);T(a,\"Missing predicate expression.\");var c=Yb(a);b.push(c);T(a,\"Unclosed predicate expression.\");$b(a,\"]\")}return b}function Zb(a){if(\"-\"==y(a.a))return z(a.a),new Tb(Zb(a));var b=cc(a);if(\"|\"!=y(a.a))a=b;else{for(b=[b];\"|\"==z(a.a);)T(a,\"Missing next union location path.\"),b.push(cc(a));a.a.a--;a=new Ub(b)}return a};function fc(a){switch(a.nodeType){case 1:return ha(gc,a);case 9:return fc(a.documentElement);case 11:case 10:case 6:case 12:return hc;default:return a.parentNode?fc(a.parentNode):hc}}function hc(){return null}function gc(a,b){if(a.prefix==b)return a.namespaceURI||\"http://www.w3.org/1999/xhtml\";var c=a.getAttributeNode(\"xmlns:\"+b);return c&&c.specified?c.value||null:a.parentNode&&9!=a.parentNode.nodeType?gc(a.parentNode,b):null};function ic(a,b){if(!a.length)throw Error(\"Empty XPath expression.\");a=Qa(a);if(Ta(a))throw Error(\"Invalid XPath expression.\");b?\"function\"==ca(b)||(b=fa(b.lookupNamespaceURI,b)):b=function(){return null};var c=Yb(new Vb(a,b));if(!Ta(a))throw Error(\"Bad token: \"+z(a));this.evaluate=function(d,e){d=c.a(new ia(d));return new U(d,e)}}\nfunction U(a,b){if(0==b)if(a instanceof E)b=4;else if(\"string\"==typeof a)b=2;else if(\"number\"==typeof a)b=1;else if(\"boolean\"==typeof a)b=3;else throw Error(\"Unexpected evaluation result.\");if(2!=b&&1!=b&&3!=b&&!(a instanceof E))throw Error(\"value could not be converted to the specified type\");this.resultType=b;switch(b){case 2:this.stringValue=a instanceof E?nb(a):\"\"+a;break;case 1:this.numberValue=a instanceof E?+nb(a):+a;break;case 3:this.booleanValue=a instanceof E?0<a.l:!!a;break;case 4:case 5:case 6:case 7:var c=\nH(a);var d=[];for(var e=I(c);e;e=I(c))d.push(e instanceof Na?e.a:e);this.snapshotLength=a.l;this.invalidIteratorState=!1;break;case 8:case 9:a=mb(a);this.singleNodeValue=a instanceof Na?a.a:a;break;default:throw Error(\"Unknown XPathResult type.\");}var f=0;this.iterateNext=function(){if(4!=b&&5!=b)throw Error(\"iterateNext called with wrong result type\");return f>=d.length?null:d[f++]};this.snapshotItem=function(g){if(6!=b&&7!=b)throw Error(\"snapshotItem called with wrong result type\");return g>=d.length||\n0>g?null:d[g]}}U.ANY_TYPE=0;U.NUMBER_TYPE=1;U.STRING_TYPE=2;U.BOOLEAN_TYPE=3;U.UNORDERED_NODE_ITERATOR_TYPE=4;U.ORDERED_NODE_ITERATOR_TYPE=5;U.UNORDERED_NODE_SNAPSHOT_TYPE=6;U.ORDERED_NODE_SNAPSHOT_TYPE=7;U.ANY_UNORDERED_NODE_TYPE=8;U.FIRST_ORDERED_NODE_TYPE=9;function jc(a){this.lookupNamespaceURI=fc(a)}\nfunction kc(a,b){a=a||k;var c=a.Document&&a.Document.prototype||a.document;if(!c.evaluate||b)a.XPathResult=U,c.evaluate=function(d,e,f,g){return(new ic(d,f)).evaluate(e,g)},c.createExpression=function(d,e){return new ic(d,e)},c.createNSResolver=function(d){return new jc(d)}}ba(\"wgxpath.install\",kc);ba(\"wgxpath.install\",kc);var lc={aliceblue:\"#f0f8ff\",antiquewhite:\"#faebd7\",aqua:\"#00ffff\",aquamarine:\"#7fffd4\",azure:\"#f0ffff\",beige:\"#f5f5dc\",bisque:\"#ffe4c4\",black:\"#000000\",blanchedalmond:\"#ffebcd\",blue:\"#0000ff\",blueviolet:\"#8a2be2\",brown:\"#a52a2a\",burlywood:\"#deb887\",cadetblue:\"#5f9ea0\",chartreuse:\"#7fff00\",chocolate:\"#d2691e\",coral:\"#ff7f50\",cornflowerblue:\"#6495ed\",cornsilk:\"#fff8dc\",crimson:\"#dc143c\",cyan:\"#00ffff\",darkblue:\"#00008b\",darkcyan:\"#008b8b\",darkgoldenrod:\"#b8860b\",darkgray:\"#a9a9a9\",darkgreen:\"#006400\",\ndarkgrey:\"#a9a9a9\",darkkhaki:\"#bdb76b\",darkmagenta:\"#8b008b\",darkolivegreen:\"#556b2f\",darkorange:\"#ff8c00\",darkorchid:\"#9932cc\",darkred:\"#8b0000\",darksalmon:\"#e9967a\",darkseagreen:\"#8fbc8f\",darkslateblue:\"#483d8b\",darkslategray:\"#2f4f4f\",darkslategrey:\"#2f4f4f\",darkturquoise:\"#00ced1\",darkviolet:\"#9400d3\",deeppink:\"#ff1493\",deepskyblue:\"#00bfff\",dimgray:\"#696969\",dimgrey:\"#696969\",dodgerblue:\"#1e90ff\",firebrick:\"#b22222\",floralwhite:\"#fffaf0\",forestgreen:\"#228b22\",fuchsia:\"#ff00ff\",gainsboro:\"#dcdcdc\",\nghostwhite:\"#f8f8ff\",gold:\"#ffd700\",goldenrod:\"#daa520\",gray:\"#808080\",green:\"#008000\",greenyellow:\"#adff2f\",grey:\"#808080\",honeydew:\"#f0fff0\",hotpink:\"#ff69b4\",indianred:\"#cd5c5c\",indigo:\"#4b0082\",ivory:\"#fffff0\",khaki:\"#f0e68c\",lavender:\"#e6e6fa\",lavenderblush:\"#fff0f5\",lawngreen:\"#7cfc00\",lemonchiffon:\"#fffacd\",lightblue:\"#add8e6\",lightcoral:\"#f08080\",lightcyan:\"#e0ffff\",lightgoldenrodyellow:\"#fafad2\",lightgray:\"#d3d3d3\",lightgreen:\"#90ee90\",lightgrey:\"#d3d3d3\",lightpink:\"#ffb6c1\",lightsalmon:\"#ffa07a\",\nlightseagreen:\"#20b2aa\",lightskyblue:\"#87cefa\",lightslategray:\"#778899\",lightslategrey:\"#778899\",lightsteelblue:\"#b0c4de\",lightyellow:\"#ffffe0\",lime:\"#00ff00\",limegreen:\"#32cd32\",linen:\"#faf0e6\",magenta:\"#ff00ff\",maroon:\"#800000\",mediumaquamarine:\"#66cdaa\",mediumblue:\"#0000cd\",mediumorchid:\"#ba55d3\",mediumpurple:\"#9370db\",mediumseagreen:\"#3cb371\",mediumslateblue:\"#7b68ee\",mediumspringgreen:\"#00fa9a\",mediumturquoise:\"#48d1cc\",mediumvioletred:\"#c71585\",midnightblue:\"#191970\",mintcream:\"#f5fffa\",mistyrose:\"#ffe4e1\",\nmoccasin:\"#ffe4b5\",navajowhite:\"#ffdead\",navy:\"#000080\",oldlace:\"#fdf5e6\",olive:\"#808000\",olivedrab:\"#6b8e23\",orange:\"#ffa500\",orangered:\"#ff4500\",orchid:\"#da70d6\",palegoldenrod:\"#eee8aa\",palegreen:\"#98fb98\",paleturquoise:\"#afeeee\",palevioletred:\"#db7093\",papayawhip:\"#ffefd5\",peachpuff:\"#ffdab9\",peru:\"#cd853f\",pink:\"#ffc0cb\",plum:\"#dda0dd\",powderblue:\"#b0e0e6\",purple:\"#800080\",red:\"#ff0000\",rosybrown:\"#bc8f8f\",royalblue:\"#4169e1\",saddlebrown:\"#8b4513\",salmon:\"#fa8072\",sandybrown:\"#f4a460\",seagreen:\"#2e8b57\",\nseashell:\"#fff5ee\",sienna:\"#a0522d\",silver:\"#c0c0c0\",skyblue:\"#87ceeb\",slateblue:\"#6a5acd\",slategray:\"#708090\",slategrey:\"#708090\",snow:\"#fffafa\",springgreen:\"#00ff7f\",steelblue:\"#4682b4\",tan:\"#d2b48c\",teal:\"#008080\",thistle:\"#d8bfd8\",tomato:\"#ff6347\",turquoise:\"#40e0d0\",violet:\"#ee82ee\",wheat:\"#f5deb3\",white:\"#ffffff\",whitesmoke:\"#f5f5f5\",yellow:\"#ffff00\",yellowgreen:\"#9acd32\"};var mc=\"backgroundColor borderTopColor borderRightColor borderBottomColor borderLeftColor color outlineColor\".split(\" \"),nc=/#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])/,oc=/^#(?:[0-9a-f]{3}){1,2}$/i,pc=/^(?:rgba)?\\((\\d{1,3}),\\s?(\\d{1,3}),\\s?(\\d{1,3}),\\s?(0|1|0\\.\\d*)\\)$/i,qc=/^(?:rgb)?\\((0|[1-9]\\d{0,2}),\\s?(0|[1-9]\\d{0,2}),\\s?(0|[1-9]\\d{0,2})\\)$/i;function rc(a,b){this.code=a;this.a=V[a]||sc;this.message=b||\"\";a=this.a.replace(/((?:^|\\s+)[a-z])/g,function(c){return c.toUpperCase().replace(/^[\\s\\xa0]+/g,\"\")});b=a.length-5;if(0>b||a.indexOf(\"Error\",b)!=b)a+=\"Error\";this.name=a;a=Error(this.message);a.name=this.name;this.stack=a.stack||\"\"}l(rc,Error);var sc=\"unknown error\",V={15:\"element not selectable\",11:\"element not visible\"};V[31]=sc;V[30]=sc;V[24]=\"invalid cookie domain\";V[29]=\"invalid element coordinates\";V[12]=\"invalid element state\";\nV[32]=\"invalid selector\";V[51]=\"invalid selector\";V[52]=\"invalid selector\";V[17]=\"javascript error\";V[405]=\"unsupported operation\";V[34]=\"move target out of bounds\";V[27]=\"no such alert\";V[7]=\"no such element\";V[8]=\"no such frame\";V[23]=\"no such window\";V[28]=\"script timeout\";V[33]=\"session not created\";V[10]=\"stale element reference\";V[21]=\"timeout\";V[25]=\"unable to set cookie\";V[26]=\"unexpected alert open\";V[13]=sc;V[9]=\"unknown command\";var tc=va(),uc=ya()||u(\"iPod\"),vc=u(\"iPad\"),wc=u(\"Android\")&&!(wa()||va()||u(\"Opera\")||u(\"Silk\")),xc=wa(),yc=u(\"Safari\")&&!(wa()||u(\"Coast\")||u(\"Opera\")||u(\"Edge\")||u(\"Edg/\")||u(\"OPR\")||va()||u(\"Silk\")||u(\"Android\"))&&!(ya()||u(\"iPad\")||u(\"iPod\"));function zc(a){return(a=a.exec(t))?a[1]:\"\"}(function(){if(tc)return zc(/Firefox\\/([0-9.]+)/);if(v||Ca||Ba)return Ga;if(xc)return ya()||u(\"iPad\")||u(\"iPod\")?zc(/CriOS\\/([0-9.]+)/):zc(/Chrome\\/([0-9.]+)/);if(yc&&!(ya()||u(\"iPad\")||u(\"iPod\")))return zc(/Version\\/([0-9.]+)/);if(uc||vc){var a=/Version\\/(\\S+).*Mobile\\/(\\S+)/.exec(t);if(a)return a[1]+\".\"+a[2]}else if(wc)return(a=zc(/Android\\s+([0-9.]+)/))?a:zc(/Version\\/([0-9.]+)/);return\"\"})();var Ac=v&&!(9<=Number(La));function W(a,b){b&&\"string\"!==typeof b&&(b=b.toString());return!!a&&1==a.nodeType&&(!b||a.tagName.toUpperCase()==b)};var Bc=function(){var a={K:\"http://www.w3.org/2000/svg\"};return function(b){return a[b]||null}}();\nfunction Cc(a,b){var c=A(a);if(!c.documentElement)return null;(v||wc)&&kc(c?c.parentWindow||c.defaultView:window);try{var d=c.createNSResolver?c.createNSResolver(c.documentElement):Bc;if(v&&!Ka(7))return c.evaluate.call(c,b,a,d,9,null);if(!v||9<=Number(La)){for(var e={},f=c.getElementsByTagName(\"*\"),g=0;g<f.length;++g){var h=f[g],m=h.namespaceURI;if(m&&!e[m]){var w=h.lookupPrefix(m);if(!w){var r=m.match(\".*/(\\\\w+)/?$\");w=r?r[1]:\"xhtml\"}e[m]=w}}var D={},L;for(L in e)D[e[L]]=L;d=function(M){return D[M]||\nnull}}try{return c.evaluate(b,a,d,9,null)}catch(M){if(\"TypeError\"===M.name)return d=c.createNSResolver?c.createNSResolver(c.documentElement):Bc,c.evaluate(b,a,d,9,null);throw M;}}catch(M){if(!Da||\"NS_ERROR_ILLEGAL_VALUE\"!=M.name)throw new rc(32,\"Unable to locate an element with the xpath expression \"+b+\" because of the following error:\\n\"+M);}}\nfunction Dc(a,b){var c=function(){var d=Cc(b,a);return d?d.singleNodeValue||null:b.selectSingleNode?(d=A(b),d.setProperty&&d.setProperty(\"SelectionLanguage\",\"XPath\"),b.selectSingleNode(a)):null}();if(null!==c&&(!c||1!=c.nodeType))throw new rc(32,'The result of the xpath expression \"'+a+'\" is: '+c+\". It should be an element.\");return c};function Ec(a,b,c,d){this.c=a;this.a=b;this.b=c;this.f=d}Ec.prototype.ceil=function(){this.c=Math.ceil(this.c);this.a=Math.ceil(this.a);this.b=Math.ceil(this.b);this.f=Math.ceil(this.f);return this};Ec.prototype.floor=function(){this.c=Math.floor(this.c);this.a=Math.floor(this.a);this.b=Math.floor(this.b);this.f=Math.floor(this.f);return this};Ec.prototype.round=function(){this.c=Math.round(this.c);this.a=Math.round(this.a);this.b=Math.round(this.b);this.f=Math.round(this.f);return this};function X(a,b,c,d){this.a=a;this.b=b;this.width=c;this.height=d}X.prototype.ceil=function(){this.a=Math.ceil(this.a);this.b=Math.ceil(this.b);this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};X.prototype.floor=function(){this.a=Math.floor(this.a);this.b=Math.floor(this.b);this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};\nX.prototype.round=function(){this.a=Math.round(this.a);this.b=Math.round(this.b);this.width=Math.round(this.width);this.height=Math.round(this.height);return this};var Fc=\"function\"===typeof ShadowRoot;function Gc(a){for(a=a.parentNode;a&&1!=a.nodeType&&9!=a.nodeType&&11!=a.nodeType;)a=a.parentNode;return W(a)?a:null}\nfunction Y(a,b){b=xa(b);if(\"float\"==b||\"cssFloat\"==b||\"styleFloat\"==b)b=Ac?\"styleFloat\":\"cssFloat\";a:{var c=b;var d=A(a);if(d.defaultView&&d.defaultView.getComputedStyle&&(d=d.defaultView.getComputedStyle(a,null))){c=d[c]||d.getPropertyValue(c)||\"\";break a}c=\"\"}a=c||Hc(a,b);if(null===a)a=null;else if(0<=ja(mc,b)){b:{var e=a.match(pc);if(e&&(b=Number(e[1]),c=Number(e[2]),d=Number(e[3]),e=Number(e[4]),0<=b&&255>=b&&0<=c&&255>=c&&0<=d&&255>=d&&0<=e&&1>=e)){b=[b,c,d,e];break b}b=null}if(!b)b:{if(d=a.match(qc))if(b=\nNumber(d[1]),c=Number(d[2]),d=Number(d[3]),0<=b&&255>=b&&0<=c&&255>=c&&0<=d&&255>=d){b=[b,c,d,1];break b}b=null}if(!b)b:{b=a.toLowerCase();c=lc[b.toLowerCase()];if(!c&&(c=\"#\"==b.charAt(0)?b:\"#\"+b,4==c.length&&(c=c.replace(nc,\"#$1$1$2$2$3$3\")),!oc.test(c))){b=null;break b}b=[parseInt(c.substr(1,2),16),parseInt(c.substr(3,2),16),parseInt(c.substr(5,2),16),1]}a=b?\"rgba(\"+b.join(\", \")+\")\":a}return a}\nfunction Hc(a,b){var c=a.currentStyle||a.style,d=c[b];void 0===d&&\"function\"==ca(c.getPropertyValue)&&(d=c.getPropertyValue(b));return\"inherit\"!=d?void 0!==d?d:null:(a=Gc(a))?Hc(a,b):null}\nfunction Ic(a,b,c){function d(g){var h=Jc(g);return 0<h.height&&0<h.width?!0:W(g,\"PATH\")&&(0<h.height||0<h.width)?(g=Y(g,\"stroke-width\"),!!g&&0<parseInt(g,10)):\"hidden\"!=Y(g,\"overflow\")&&ma(g.childNodes,function(m){return 3==m.nodeType||W(m)&&d(m)})}function e(g){return Kc(g)==Z&&na(g.childNodes,function(h){return!W(h)||e(h)||!d(h)})}if(!W(a))throw Error(\"Argument to isShown must be of type Element\");if(W(a,\"BODY\"))return!0;if(W(a,\"OPTION\")||W(a,\"OPTGROUP\"))return a=$a(a,function(g){return W(g,\"SELECT\")}),\n!!a&&Ic(a,!0,c);var f=Lc(a);if(f)return!!f.image&&0<f.rect.width&&0<f.rect.height&&Ic(f.image,b,c);if(W(a,\"INPUT\")&&\"hidden\"==a.type.toLowerCase()||W(a,\"NOSCRIPT\"))return!1;f=Y(a,\"visibility\");return\"collapse\"!=f&&\"hidden\"!=f&&c(a)&&(b||0!=Mc(a))&&d(a)?!e(a):!1}var Z=\"hidden\";\nfunction Kc(a){function b(p){function q(fb){if(fb==g)return!0;var Wb=Y(fb,\"display\");return 0==Wb.lastIndexOf(\"inline\",0)||\"contents\"==Wb||\"absolute\"==Xb&&\"static\"==Y(fb,\"position\")?!1:!0}var Xb=Y(p,\"position\");if(\"fixed\"==Xb)return w=!0,p==g?null:g;for(p=Gc(p);p&&!q(p);)p=Gc(p);return p}function c(p){var q=p;if(\"visible\"==m)if(p==g&&h)q=h;else if(p==h)return{x:\"visible\",y:\"visible\"};q={x:Y(q,\"overflow-x\"),y:Y(q,\"overflow-y\")};p==g&&(q.x=\"visible\"==q.x?\"auto\":q.x,q.y=\"visible\"==q.y?\"auto\":q.y);return q}\nfunction d(p){if(p==g){var q=(new ab(f)).a;p=q.scrollingElement?q.scrollingElement:Ea||\"CSS1Compat\"!=q.compatMode?q.body||q.documentElement:q.documentElement;q=q.parentWindow||q.defaultView;p=v&&Ka(\"10\")&&q.pageYOffset!=p.scrollTop?new Ua(p.scrollLeft,p.scrollTop):new Ua(q.pageXOffset||p.scrollLeft,q.pageYOffset||p.scrollTop)}else p=new Ua(p.scrollLeft,p.scrollTop);return p}var e=Nc(a),f=A(a),g=f.documentElement,h=f.body,m=Y(g,\"overflow\"),w;for(a=b(a);a;a=b(a)){var r=c(a);if(\"visible\"!=r.x||\"visible\"!=\nr.y){var D=Jc(a);if(0==D.width||0==D.height)return Z;var L=e.a<D.a,M=e.b<D.b;if(L&&\"hidden\"==r.x||M&&\"hidden\"==r.y)return Z;if(L&&\"visible\"!=r.x||M&&\"visible\"!=r.y){L=d(a);M=e.b<D.b-L.y;if(e.a<D.a-L.x&&\"visible\"!=r.x||M&&\"visible\"!=r.x)return Z;e=Kc(a);return e==Z?Z:\"scroll\"}L=e.f>=D.a+D.width;D=e.c>=D.b+D.height;if(L&&\"hidden\"==r.x||D&&\"hidden\"==r.y)return Z;if(L&&\"visible\"!=r.x||D&&\"visible\"!=r.y){if(w&&(r=d(a),e.f>=g.scrollWidth-r.x||e.a>=g.scrollHeight-r.y))return Z;e=Kc(a);return e==Z?Z:\"scroll\"}}}return\"none\"}\nfunction Jc(a){var b=Lc(a);if(b)return b.rect;if(W(a,\"HTML\"))return a=A(a),a=((a?a.parentWindow||a.defaultView:window)||window).document,a=\"CSS1Compat\"==a.compatMode?a.documentElement:a.body,a=new Va(a.clientWidth,a.clientHeight),new X(0,0,a.width,a.height);try{var c=a.getBoundingClientRect()}catch(d){return new X(0,0,0,0)}b=new X(c.left,c.top,c.right-c.left,c.bottom-c.top);v&&a.ownerDocument.body&&(a=A(a),b.a-=a.documentElement.clientLeft+a.body.clientLeft,b.b-=a.documentElement.clientTop+a.body.clientTop);\nreturn b}function Lc(a){var b=W(a,\"MAP\");if(!b&&!W(a,\"AREA\"))return null;var c=b?a:W(a.parentNode,\"MAP\")?a.parentNode:null,d=null,e=null;c&&c.name&&(d=Dc('/descendant::*[@usemap = \"#'+c.name+'\"]',A(c)))&&(e=Jc(d),b||\"default\"==a.shape.toLowerCase()||(a=Oc(a),b=Math.min(Math.max(a.a,0),e.width),c=Math.min(Math.max(a.b,0),e.height),e=new X(b+e.a,c+e.b,Math.min(a.width,e.width-b),Math.min(a.height,e.height-c))));return{image:d,rect:e||new X(0,0,0,0)}}\nfunction Oc(a){var b=a.shape.toLowerCase();a=a.coords.split(\",\");if(\"rect\"==b&&4==a.length){b=a[0];var c=a[1];return new X(b,c,a[2]-b,a[3]-c)}if(\"circle\"==b&&3==a.length)return b=a[2],new X(a[0]-b,a[1]-b,2*b,2*b);if(\"poly\"==b&&2<a.length){b=a[0];c=a[1];for(var d=b,e=c,f=2;f+1<a.length;f+=2)b=Math.min(b,a[f]),d=Math.max(d,a[f]),c=Math.min(c,a[f+1]),e=Math.max(e,a[f+1]);return new X(b,c,d-b,e-c)}return new X(0,0,0,0)}function Nc(a){a=Jc(a);return new Ec(a.b,a.a+a.width,a.b+a.height,a.a)}\nfunction Mc(a){if(Ac){if(\"relative\"==Y(a,\"position\"))return 1;a=Y(a,\"filter\");return(a=a.match(/^alpha\\(opacity=(\\d*)\\)/)||a.match(/^progid:DXImageTransform.Microsoft.Alpha\\(Opacity=(\\d*)\\)/))?Number(a[1])/100:1}return Pc(a)}function Pc(a){var b=1,c=Y(a,\"opacity\");c&&(b=Number(c));(a=Gc(a))&&(b*=Pc(a));return b};ba(\"_\",function(a,b){function c(d){if(W(d)&&\"none\"==Y(d,\"display\"))return!1;var e;if((e=d.parentNode)&&e.shadowRoot&&void 0!==d.assignedSlot)e=d.assignedSlot?d.assignedSlot.parentNode:null;else if(d.getDestinationInsertionPoints){var f=d.getDestinationInsertionPoints();0<f.length&&(e=f[f.length-1])}if(Fc&&e instanceof ShadowRoot){if(e.host.shadowRoot&&e.host.shadowRoot!==e)return!1;e=e.host}return!e||9!=e.nodeType&&11!=e.nodeType?e&&W(e,\"DETAILS\")&&!e.open&&!W(d,\"SUMMARY\")?!1:!!e&&c(e):!0}return Ic(a,\n!!b,c)});; return this._.apply(null,arguments);}).apply({navigator:typeof window!='undefined'?window.navigator:null,document:typeof window!='undefined'?window.document:null}, arguments);}\n", - isElementEnabled: "function(){return (function(){var k=this||self;function aa(a){return\"string\"==typeof a}function ba(a,b){a=a.split(\".\");var c=k;a[0]in c||\"undefined\"==typeof c.execScript||c.execScript(\"var \"+a[0]);for(var d;a.length&&(d=a.shift());)a.length||void 0===b?c[d]&&c[d]!==Object.prototype[d]?c=c[d]:c=c[d]={}:c[d]=b}\nfunction ca(a){var b=typeof a;if(\"object\"==b)if(a){if(a instanceof Array)return\"array\";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if(\"[object Window]\"==c)return\"object\";if(\"[object Array]\"==c||\"number\"==typeof a.length&&\"undefined\"!=typeof a.splice&&\"undefined\"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable(\"splice\"))return\"array\";if(\"[object Function]\"==c||\"undefined\"!=typeof a.call&&\"undefined\"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable(\"call\"))return\"function\"}else return\"null\";\nelse if(\"function\"==b&&\"undefined\"==typeof a.call)return\"object\";return b}function da(a,b,c){return a.call.apply(a.bind,arguments)}function ea(a,b,c){if(!a)throw Error();if(2<arguments.length){var d=Array.prototype.slice.call(arguments,2);return function(){var e=Array.prototype.slice.call(arguments);Array.prototype.unshift.apply(e,d);return a.apply(b,e)}}return function(){return a.apply(b,arguments)}}\nfunction fa(a,b,c){Function.prototype.bind&&-1!=Function.prototype.bind.toString().indexOf(\"native code\")?fa=da:fa=ea;return fa.apply(null,arguments)}function ha(a,b){var c=Array.prototype.slice.call(arguments,1);return function(){var d=c.slice();d.push.apply(d,arguments);return a.apply(this,d)}}function l(a,b){function c(){}c.prototype=b.prototype;a.prototype=new c;a.prototype.constructor=a};/*\n\n The MIT License\n\n Copyright (c) 2007 Cybozu Labs, Inc.\n Copyright (c) 2012 Google Inc.\n\n Permission is hereby granted, free of charge, to any person obtaining a copy\n of this software and associated documentation files (the \"Software\"), to\n deal in the Software without restriction, including without limitation the\n rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n sell copies of the Software, and to permit persons to whom the Software is\n furnished to do so, subject to the following conditions:\n\n The above copyright notice and this permission notice shall be included in\n all copies or substantial portions of the Software.\n\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n IN THE SOFTWARE.\n*/\nfunction m(a,b,c){this.a=a;this.b=b||1;this.f=c||1};var ia=Array.prototype.indexOf?function(a,b){return Array.prototype.indexOf.call(a,b,void 0)}:function(a,b){if(\"string\"===typeof a)return\"string\"!==typeof b||1!=b.length?-1:a.indexOf(b,0);for(var c=0;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1},n=Array.prototype.forEach?function(a,b){Array.prototype.forEach.call(a,b,void 0)}:function(a,b){for(var c=a.length,d=\"string\"===typeof a?a.split(\"\"):a,e=0;e<c;e++)e in d&&b.call(void 0,d[e],e,a)},ja=Array.prototype.filter?function(a,b){return Array.prototype.filter.call(a,\nb,void 0)}:function(a,b){for(var c=a.length,d=[],e=0,f=\"string\"===typeof a?a.split(\"\"):a,g=0;g<c;g++)if(g in f){var h=f[g];b.call(void 0,h,g,a)&&(d[e++]=h)}return d},p=Array.prototype.reduce?function(a,b,c){return Array.prototype.reduce.call(a,b,c)}:function(a,b,c){var d=c;n(a,function(e,f){d=b.call(void 0,d,e,f,a)});return d},r=Array.prototype.some?function(a,b){return Array.prototype.some.call(a,b,void 0)}:function(a,b){for(var c=a.length,d=\"string\"===typeof a?a.split(\"\"):a,e=0;e<c;e++)if(e in d&&\nb.call(void 0,d[e],e,a))return!0;return!1};function ka(a,b){a:{for(var c=a.length,d=\"string\"===typeof a?a.split(\"\"):a,e=0;e<c;e++)if(e in d&&b.call(void 0,d[e],e,a)){b=e;break a}b=-1}return 0>b?null:\"string\"===typeof a?a.charAt(b):a[b]}function la(a){return Array.prototype.concat.apply([],arguments)}function ma(a,b,c){return 2>=arguments.length?Array.prototype.slice.call(a,b):Array.prototype.slice.call(a,b,c)};var t;a:{var na=k.navigator;if(na){var oa=na.userAgent;if(oa){t=oa;break a}}t=\"\"}function u(a){return-1!=t.indexOf(a)};function pa(){return u(\"Firefox\")||u(\"FxiOS\")}function qa(){return(u(\"Chrome\")||u(\"CriOS\"))&&!u(\"Edge\")};function ra(){return u(\"iPhone\")&&!u(\"iPod\")&&!u(\"iPad\")};var sa=u(\"Opera\"),v=u(\"Trident\")||u(\"MSIE\"),ta=u(\"Edge\"),ua=u(\"Gecko\")&&!(-1!=t.toLowerCase().indexOf(\"webkit\")&&!u(\"Edge\"))&&!(u(\"Trident\")||u(\"MSIE\"))&&!u(\"Edge\"),va=-1!=t.toLowerCase().indexOf(\"webkit\")&&!u(\"Edge\");function wa(){var a=k.document;return a?a.documentMode:void 0}var xa;\na:{var ya=\"\",za=function(){var a=t;if(ua)return/rv:([^\\);]+)(\\)|;)/.exec(a);if(ta)return/Edge\\/([\\d\\.]+)/.exec(a);if(v)return/\\b(?:MSIE|rv)[: ]([^\\);]+)(\\)|;)/.exec(a);if(va)return/WebKit\\/(\\S+)/.exec(a);if(sa)return/(?:Version)[ \\/]?(\\S+)/.exec(a)}();za&&(ya=za?za[1]:\"\");if(v){var Aa=wa();if(null!=Aa&&Aa>parseFloat(ya)){xa=String(Aa);break a}}xa=ya}var Ba;Ba=k.document&&v?wa():void 0;var w=v&&!(9<=Number(Ba)),Ca=v&&!(8<=Number(Ba));function y(a,b,c,d){this.a=a;this.nodeName=c;this.nodeValue=d;this.nodeType=2;this.parentNode=this.ownerElement=b}function Da(a,b){var c=Ca&&\"href\"==b.nodeName?a.getAttribute(b.nodeName,2):b.nodeValue;return new y(b,a,b.nodeName,c)};function Ea(a){this.b=a;this.a=0}function Fa(a){a=a.match(Ga);for(var b=0;b<a.length;b++)Ha.test(a[b])&&a.splice(b,1);return new Ea(a)}var Ga=/\\$?(?:(?![0-9-\\.])(?:\\*|[\\w-\\.]+):)?(?![0-9-\\.])(?:\\*|[\\w-\\.]+)|\\/\\/|\\.\\.|::|\\d+(?:\\.\\d*)?|\\.\\d+|\"[^\"]*\"|'[^']*'|[!<>]=|\\s+|./g,Ha=/^\\s/;function z(a,b){return a.b[a.a+(b||0)]}function A(a){return a.b[a.a++]}function Ia(a){return a.b.length<=a.a};function Ja(a){for(;a&&1!=a.nodeType;)a=a.previousSibling;return a}function Ka(a,b){if(!a||!b)return!1;if(a.contains&&1==b.nodeType)return a==b||a.contains(b);if(\"undefined\"!=typeof a.compareDocumentPosition)return a==b||!!(a.compareDocumentPosition(b)&16);for(;b&&a!=b;)b=b.parentNode;return b==a}\nfunction La(a,b){if(a==b)return 0;if(a.compareDocumentPosition)return a.compareDocumentPosition(b)&2?1:-1;if(v&&!(9<=Number(Ba))){if(9==a.nodeType)return-1;if(9==b.nodeType)return 1}if(\"sourceIndex\"in a||a.parentNode&&\"sourceIndex\"in a.parentNode){var c=1==a.nodeType,d=1==b.nodeType;if(c&&d)return a.sourceIndex-b.sourceIndex;var e=a.parentNode,f=b.parentNode;return e==f?Ma(a,b):!c&&Ka(e,b)?-1*Na(a,b):!d&&Ka(f,a)?Na(b,a):(c?a.sourceIndex:e.sourceIndex)-(d?b.sourceIndex:f.sourceIndex)}d=9==a.nodeType?\na:a.ownerDocument||a.document;c=d.createRange();c.selectNode(a);c.collapse(!0);a=d.createRange();a.selectNode(b);a.collapse(!0);return c.compareBoundaryPoints(k.Range.START_TO_END,a)}function Na(a,b){var c=a.parentNode;if(c==b)return-1;for(;b.parentNode!=c;)b=b.parentNode;return Ma(b,a)}function Ma(a,b){for(;b=b.previousSibling;)if(b==a)return-1;return 1}function Oa(a,b){for(var c=0;a;){if(b(a))return a;a=a.parentNode;c++}return null};function B(a){var b=null,c=a.nodeType;1==c&&(b=a.textContent,b=void 0==b||null==b?a.innerText:b,b=void 0==b||null==b?\"\":b);if(\"string\"!=typeof b)if(w&&\"title\"==a.nodeName.toLowerCase()&&1==c)b=a.text;else if(9==c||1==c){a=9==c?a.documentElement:a.firstChild;c=0;var d=[];for(b=\"\";a;){do 1!=a.nodeType&&(b+=a.nodeValue),w&&\"title\"==a.nodeName.toLowerCase()&&(b+=a.text),d[c++]=a;while(a=a.firstChild);for(;c&&!(a=d[--c].nextSibling););}}else b=a.nodeValue;return b}\nfunction C(a,b,c){if(null===b)return!0;try{if(!a.getAttribute)return!1}catch(d){return!1}Ca&&\"class\"==b&&(b=\"className\");return null==c?!!a.getAttribute(b):a.getAttribute(b,2)==c}function D(a,b,c,d,e){return(w?Pa:Qa).call(null,a,b,aa(c)?c:null,aa(d)?d:null,e||new E)}\nfunction Pa(a,b,c,d,e){if(a instanceof F||8==a.b||c&&null===a.b){var f=b.all;if(!f)return e;a=Ra(a);if(\"*\"!=a&&(f=b.getElementsByTagName(a),!f))return e;if(c){for(var g=[],h=0;b=f[h++];)C(b,c,d)&&g.push(b);f=g}for(h=0;b=f[h++];)\"*\"==a&&\"!\"==b.tagName||e.add(b);return e}Sa(a,b,c,d,e);return e}\nfunction Qa(a,b,c,d,e){b.getElementsByName&&d&&\"name\"==c&&!v?(b=b.getElementsByName(d),n(b,function(f){a.a(f)&&e.add(f)})):b.getElementsByClassName&&d&&\"class\"==c?(b=b.getElementsByClassName(d),n(b,function(f){f.className==d&&a.a(f)&&e.add(f)})):a instanceof G?Sa(a,b,c,d,e):b.getElementsByTagName&&(b=b.getElementsByTagName(a.f()),n(b,function(f){C(f,c,d)&&e.add(f)}));return e}\nfunction Ta(a,b,c,d,e){var f;if((a instanceof F||8==a.b||c&&null===a.b)&&(f=b.childNodes)){var g=Ra(a);if(\"*\"!=g&&(f=ja(f,function(h){return h.tagName&&h.tagName.toLowerCase()==g}),!f))return e;c&&(f=ja(f,function(h){return C(h,c,d)}));n(f,function(h){\"*\"==g&&(\"!\"==h.tagName||\"*\"==g&&1!=h.nodeType)||e.add(h)});return e}return Ua(a,b,c,d,e)}function Ua(a,b,c,d,e){for(b=b.firstChild;b;b=b.nextSibling)C(b,c,d)&&a.a(b)&&e.add(b);return e}\nfunction Sa(a,b,c,d,e){for(b=b.firstChild;b;b=b.nextSibling)C(b,c,d)&&a.a(b)&&e.add(b),Sa(a,b,c,d,e)}function Ra(a){if(a instanceof G){if(8==a.b)return\"!\";if(null===a.b)return\"*\"}return a.f()};function E(){this.b=this.a=null;this.l=0}function Va(a){this.f=a;this.a=this.b=null}function Wa(a,b){if(!a.a)return b;if(!b.a)return a;var c=a.a;b=b.a;for(var d=null,e,f=0;c&&b;){e=c.f;var g=b.f;e==g||e instanceof y&&g instanceof y&&e.a==g.a?(e=c,c=c.a,b=b.a):0<La(c.f,b.f)?(e=b,b=b.a):(e=c,c=c.a);(e.b=d)?d.a=e:a.a=e;d=e;f++}for(e=c||b;e;)e.b=d,d=d.a=e,f++,e=e.a;a.b=d;a.l=f;return a}function Xa(a,b){b=new Va(b);b.a=a.a;a.b?a.a.b=b:a.a=a.b=b;a.a=b;a.l++}\nE.prototype.add=function(a){a=new Va(a);a.b=this.b;this.a?this.b.a=a:this.a=this.b=a;this.b=a;this.l++};function Ya(a){return(a=a.a)?a.f:null}function Za(a){return(a=Ya(a))?B(a):\"\"}function H(a,b){return new $a(a,!!b)}function $a(a,b){this.f=a;this.b=(this.s=b)?a.b:a.a;this.a=null}function I(a){var b=a.b;if(null==b)return null;var c=a.a=b;a.b=a.s?b.b:b.a;return c.f};function J(a){this.i=a;this.b=this.g=!1;this.f=null}function K(a){return\"\\n \"+a.toString().split(\"\\n\").join(\"\\n \")}function ab(a,b){a.g=b}function bb(a,b){a.b=b}function L(a,b){a=a.a(b);return a instanceof E?+Za(a):+a}function M(a,b){a=a.a(b);return a instanceof E?Za(a):\"\"+a}function N(a,b){a=a.a(b);return a instanceof E?!!a.l:!!a};function O(a,b,c){J.call(this,a.i);this.c=a;this.h=b;this.o=c;this.g=b.g||c.g;this.b=b.b||c.b;this.c==cb&&(c.b||c.g||4==c.i||0==c.i||!b.f?b.b||b.g||4==b.i||0==b.i||!c.f||(this.f={name:c.f.name,u:b}):this.f={name:b.f.name,u:c})}l(O,J);\nfunction P(a,b,c,d,e){b=b.a(d);c=c.a(d);var f;if(b instanceof E&&c instanceof E){b=H(b);for(d=I(b);d;d=I(b))for(e=H(c),f=I(e);f;f=I(e))if(a(B(d),B(f)))return!0;return!1}if(b instanceof E||c instanceof E){b instanceof E?(e=b,d=c):(e=c,d=b);f=H(e);for(var g=typeof d,h=I(f);h;h=I(f)){switch(g){case \"number\":h=+B(h);break;case \"boolean\":h=!!B(h);break;case \"string\":h=B(h);break;default:throw Error(\"Illegal primitive type for comparison.\");}if(e==b&&a(h,d)||e==c&&a(d,h))return!0}return!1}return e?\"boolean\"==\ntypeof b||\"boolean\"==typeof c?a(!!b,!!c):\"number\"==typeof b||\"number\"==typeof c?a(+b,+c):a(b,c):a(+b,+c)}O.prototype.a=function(a){return this.c.m(this.h,this.o,a)};O.prototype.toString=function(){var a=\"Binary Expression: \"+this.c;a+=K(this.h);return a+=K(this.o)};function db(a,b,c,d){this.I=a;this.D=b;this.i=c;this.m=d}db.prototype.toString=function(){return this.I};var eb={};\nfunction Q(a,b,c,d){if(eb.hasOwnProperty(a))throw Error(\"Binary operator already created: \"+a);a=new db(a,b,c,d);return eb[a.toString()]=a}Q(\"div\",6,1,function(a,b,c){return L(a,c)/L(b,c)});Q(\"mod\",6,1,function(a,b,c){return L(a,c)%L(b,c)});Q(\"*\",6,1,function(a,b,c){return L(a,c)*L(b,c)});Q(\"+\",5,1,function(a,b,c){return L(a,c)+L(b,c)});Q(\"-\",5,1,function(a,b,c){return L(a,c)-L(b,c)});Q(\"<\",4,2,function(a,b,c){return P(function(d,e){return d<e},a,b,c)});\nQ(\">\",4,2,function(a,b,c){return P(function(d,e){return d>e},a,b,c)});Q(\"<=\",4,2,function(a,b,c){return P(function(d,e){return d<=e},a,b,c)});Q(\">=\",4,2,function(a,b,c){return P(function(d,e){return d>=e},a,b,c)});var cb=Q(\"=\",3,2,function(a,b,c){return P(function(d,e){return d==e},a,b,c,!0)});Q(\"!=\",3,2,function(a,b,c){return P(function(d,e){return d!=e},a,b,c,!0)});Q(\"and\",2,2,function(a,b,c){return N(a,c)&&N(b,c)});Q(\"or\",1,2,function(a,b,c){return N(a,c)||N(b,c)});function fb(a,b){if(b.a.length&&4!=a.i)throw Error(\"Primary expression must evaluate to nodeset if filter has predicate(s).\");J.call(this,a.i);this.c=a;this.h=b;this.g=a.g;this.b=a.b}l(fb,J);fb.prototype.a=function(a){a=this.c.a(a);return gb(this.h,a)};fb.prototype.toString=function(){var a=\"Filter:\"+K(this.c);return a+=K(this.h)};function hb(a,b){if(b.length<a.C)throw Error(\"Function \"+a.j+\" expects at least\"+a.C+\" arguments, \"+b.length+\" given\");if(null!==a.B&&b.length>a.B)throw Error(\"Function \"+a.j+\" expects at most \"+a.B+\" arguments, \"+b.length+\" given\");a.H&&n(b,function(c,d){if(4!=c.i)throw Error(\"Argument \"+d+\" to function \"+a.j+\" is not of type Nodeset: \"+c);});J.call(this,a.i);this.v=a;this.c=b;ab(this,a.g||r(b,function(c){return c.g}));bb(this,a.G&&!b.length||a.F&&!!b.length||r(b,function(c){return c.b}))}l(hb,J);\nhb.prototype.a=function(a){return this.v.m.apply(null,la(a,this.c))};hb.prototype.toString=function(){var a=\"Function: \"+this.v;if(this.c.length){var b=p(this.c,function(c,d){return c+K(d)},\"Arguments:\");a+=K(b)}return a};function ib(a,b,c,d,e,f,g,h){this.j=a;this.i=b;this.g=c;this.G=d;this.F=!1;this.m=e;this.C=f;this.B=void 0!==g?g:f;this.H=!!h}ib.prototype.toString=function(){return this.j};var jb={};\nfunction R(a,b,c,d,e,f,g,h){if(jb.hasOwnProperty(a))throw Error(\"Function already created: \"+a+\".\");jb[a]=new ib(a,b,c,d,e,f,g,h)}R(\"boolean\",2,!1,!1,function(a,b){return N(b,a)},1);R(\"ceiling\",1,!1,!1,function(a,b){return Math.ceil(L(b,a))},1);R(\"concat\",3,!1,!1,function(a,b){return p(ma(arguments,1),function(c,d){return c+M(d,a)},\"\")},2,null);R(\"contains\",2,!1,!1,function(a,b,c){b=M(b,a);a=M(c,a);return-1!=b.indexOf(a)},2);R(\"count\",1,!1,!1,function(a,b){return b.a(a).l},1,1,!0);\nR(\"false\",2,!1,!1,function(){return!1},0);R(\"floor\",1,!1,!1,function(a,b){return Math.floor(L(b,a))},1);R(\"id\",4,!1,!1,function(a,b){function c(h){if(w){var q=e.all[h];if(q){if(q.nodeType&&h==q.id)return q;if(q.length)return ka(q,function(x){return h==x.id})}return null}return e.getElementById(h)}var d=a.a,e=9==d.nodeType?d:d.ownerDocument;a=M(b,a).split(/\\s+/);var f=[];n(a,function(h){h=c(h);!h||0<=ia(f,h)||f.push(h)});f.sort(La);var g=new E;n(f,function(h){g.add(h)});return g},1);\nR(\"lang\",2,!1,!1,function(){return!1},1);R(\"last\",1,!0,!1,function(a){if(1!=arguments.length)throw Error(\"Function last expects ()\");return a.f},0);R(\"local-name\",3,!1,!0,function(a,b){return(a=b?Ya(b.a(a)):a.a)?a.localName||a.nodeName.toLowerCase():\"\"},0,1,!0);R(\"name\",3,!1,!0,function(a,b){return(a=b?Ya(b.a(a)):a.a)?a.nodeName.toLowerCase():\"\"},0,1,!0);R(\"namespace-uri\",3,!0,!1,function(){return\"\"},0,1,!0);\nR(\"normalize-space\",3,!1,!0,function(a,b){return(b?M(b,a):B(a.a)).replace(/[\\s\\xa0]+/g,\" \").replace(/^\\s+|\\s+$/g,\"\")},0,1);R(\"not\",2,!1,!1,function(a,b){return!N(b,a)},1);R(\"number\",1,!1,!0,function(a,b){return b?L(b,a):+B(a.a)},0,1);R(\"position\",1,!0,!1,function(a){return a.b},0);R(\"round\",1,!1,!1,function(a,b){return Math.round(L(b,a))},1);R(\"starts-with\",2,!1,!1,function(a,b,c){b=M(b,a);a=M(c,a);return 0==b.lastIndexOf(a,0)},2);R(\"string\",3,!1,!0,function(a,b){return b?M(b,a):B(a.a)},0,1);\nR(\"string-length\",1,!1,!0,function(a,b){return(b?M(b,a):B(a.a)).length},0,1);R(\"substring\",3,!1,!1,function(a,b,c,d){c=L(c,a);if(isNaN(c)||Infinity==c||-Infinity==c)return\"\";d=d?L(d,a):Infinity;if(isNaN(d)||-Infinity===d)return\"\";c=Math.round(c)-1;var e=Math.max(c,0);a=M(b,a);return Infinity==d?a.substring(e):a.substring(e,c+Math.round(d))},2,3);R(\"substring-after\",3,!1,!1,function(a,b,c){b=M(b,a);a=M(c,a);c=b.indexOf(a);return-1==c?\"\":b.substring(c+a.length)},2);\nR(\"substring-before\",3,!1,!1,function(a,b,c){b=M(b,a);a=M(c,a);a=b.indexOf(a);return-1==a?\"\":b.substring(0,a)},2);R(\"sum\",1,!1,!1,function(a,b){a=H(b.a(a));b=0;for(var c=I(a);c;c=I(a))b+=+B(c);return b},1,1,!0);R(\"translate\",3,!1,!1,function(a,b,c,d){b=M(b,a);c=M(c,a);var e=M(d,a);a={};for(d=0;d<c.length;d++){var f=c.charAt(d);f in a||(a[f]=e.charAt(d))}c=\"\";for(d=0;d<b.length;d++)f=b.charAt(d),c+=f in a?a[f]:f;return c},3);R(\"true\",2,!1,!1,function(){return!0},0);function G(a,b){this.h=a;this.c=void 0!==b?b:null;this.b=null;switch(a){case \"comment\":this.b=8;break;case \"text\":this.b=3;break;case \"processing-instruction\":this.b=7;break;case \"node\":break;default:throw Error(\"Unexpected argument\");}}function kb(a){return\"comment\"==a||\"text\"==a||\"processing-instruction\"==a||\"node\"==a}G.prototype.a=function(a){return null===this.b||this.b==a.nodeType};G.prototype.f=function(){return this.h};\nG.prototype.toString=function(){var a=\"Kind Test: \"+this.h;null===this.c||(a+=K(this.c));return a};function lb(a){J.call(this,3);this.c=a.substring(1,a.length-1)}l(lb,J);lb.prototype.a=function(){return this.c};lb.prototype.toString=function(){return\"Literal: \"+this.c};function F(a,b){this.j=a.toLowerCase();a=\"*\"==this.j?\"*\":\"http://www.w3.org/1999/xhtml\";this.c=b?b.toLowerCase():a}F.prototype.a=function(a){var b=a.nodeType;if(1!=b&&2!=b)return!1;b=void 0!==a.localName?a.localName:a.nodeName;return\"*\"!=this.j&&this.j!=b.toLowerCase()?!1:\"*\"==this.c?!0:this.c==(a.namespaceURI?a.namespaceURI.toLowerCase():\"http://www.w3.org/1999/xhtml\")};F.prototype.f=function(){return this.j};\nF.prototype.toString=function(){return\"Name Test: \"+(\"http://www.w3.org/1999/xhtml\"==this.c?\"\":this.c+\":\")+this.j};function mb(a){J.call(this,1);this.c=a}l(mb,J);mb.prototype.a=function(){return this.c};mb.prototype.toString=function(){return\"Number: \"+this.c};function nb(a,b){J.call(this,a.i);this.h=a;this.c=b;this.g=a.g;this.b=a.b;1==this.c.length&&(a=this.c[0],a.A||a.c!=ob||(a=a.o,\"*\"!=a.f()&&(this.f={name:a.f(),u:null})))}l(nb,J);function S(){J.call(this,4)}l(S,J);S.prototype.a=function(a){var b=new E;a=a.a;9==a.nodeType?b.add(a):b.add(a.ownerDocument);return b};S.prototype.toString=function(){return\"Root Helper Expression\"};function pb(){J.call(this,4)}l(pb,J);pb.prototype.a=function(a){var b=new E;b.add(a.a);return b};pb.prototype.toString=function(){return\"Context Helper Expression\"};\nfunction qb(a){return\"/\"==a||\"//\"==a}nb.prototype.a=function(a){var b=this.h.a(a);if(!(b instanceof E))throw Error(\"Filter expression must evaluate to nodeset.\");a=this.c;for(var c=0,d=a.length;c<d&&b.l;c++){var e=a[c],f=H(b,e.c.s);if(e.g||e.c!=rb)if(e.g||e.c!=sb){var g=I(f);for(b=e.a(new m(g));null!=(g=I(f));)g=e.a(new m(g)),b=Wa(b,g)}else g=I(f),b=e.a(new m(g));else{for(g=I(f);(b=I(f))&&(!g.contains||g.contains(b))&&b.compareDocumentPosition(g)&8;g=b);b=e.a(new m(g))}}return b};\nnb.prototype.toString=function(){var a=\"Path Expression:\"+K(this.h);if(this.c.length){var b=p(this.c,function(c,d){return c+K(d)},\"Steps:\");a+=K(b)}return a};function tb(a,b){this.a=a;this.s=!!b}\nfunction gb(a,b,c){for(c=c||0;c<a.a.length;c++)for(var d=a.a[c],e=H(b),f=b.l,g,h=0;g=I(e);h++){var q=a.s?f-h:h+1;g=d.a(new m(g,q,f));if(\"number\"==typeof g)q=q==g;else if(\"string\"==typeof g||\"boolean\"==typeof g)q=!!g;else if(g instanceof E)q=0<g.l;else throw Error(\"Predicate.evaluate returned an unexpected type.\");if(!q){q=e;g=q.f;var x=q.a;if(!x)throw Error(\"Next must be called at least once before remove.\");var T=x.b;x=x.a;T?T.a=x:g.a=x;x?x.b=T:g.b=T;g.l--;q.a=null}}return b}\ntb.prototype.toString=function(){return p(this.a,function(a,b){return a+K(b)},\"Predicates:\")};function U(a,b,c,d){J.call(this,4);this.c=a;this.o=b;this.h=c||new tb([]);this.A=!!d;b=this.h;b=0<b.a.length?b.a[0].f:null;a.J&&b&&(a=b.name,a=w?a.toLowerCase():a,this.f={name:a,u:b.u});a:{a=this.h;for(b=0;b<a.a.length;b++)if(c=a.a[b],c.g||1==c.i||0==c.i){a=!0;break a}a=!1}this.g=a}l(U,J);\nU.prototype.a=function(a){var b=a.a,c=this.f,d=null,e=null,f=0;c&&(d=c.name,e=c.u?M(c.u,a):null,f=1);if(this.A)if(this.g||this.c!=ub)if(b=H((new U(vb,new G(\"node\"))).a(a)),c=I(b))for(a=this.m(c,d,e,f);null!=(c=I(b));)a=Wa(a,this.m(c,d,e,f));else a=new E;else a=D(this.o,b,d,e),a=gb(this.h,a,f);else a=this.m(a.a,d,e,f);return a};U.prototype.m=function(a,b,c,d){a=this.c.v(this.o,a,b,c);return a=gb(this.h,a,d)};\nU.prototype.toString=function(){var a=\"Step:\"+K(\"Operator: \"+(this.A?\"//\":\"/\"));this.c.j&&(a+=K(\"Axis: \"+this.c));a+=K(this.o);if(this.h.a.length){var b=p(this.h.a,function(c,d){return c+K(d)},\"Predicates:\");a+=K(b)}return a};function wb(a,b,c,d){this.j=a;this.v=b;this.s=c;this.J=d}wb.prototype.toString=function(){return this.j};var xb={};function V(a,b,c,d){if(xb.hasOwnProperty(a))throw Error(\"Axis already created: \"+a);b=new wb(a,b,c,!!d);return xb[a]=b}\nV(\"ancestor\",function(a,b){for(var c=new E;b=b.parentNode;)a.a(b)&&Xa(c,b);return c},!0);V(\"ancestor-or-self\",function(a,b){var c=new E;do a.a(b)&&Xa(c,b);while(b=b.parentNode);return c},!0);\nvar ob=V(\"attribute\",function(a,b){var c=new E,d=a.f();if(\"style\"==d&&w&&b.style)return c.add(new y(b.style,b,\"style\",b.style.cssText)),c;var e=b.attributes;if(e)if(a instanceof G&&null===a.b||\"*\"==d)for(a=0;d=e[a];a++)w?d.nodeValue&&c.add(Da(b,d)):c.add(d);else(d=e.getNamedItem(d))&&(w?d.nodeValue&&c.add(Da(b,d)):c.add(d));return c},!1),ub=V(\"child\",function(a,b,c,d,e){return(w?Ta:Ua).call(null,a,b,aa(c)?c:null,aa(d)?d:null,e||new E)},!1,!0);V(\"descendant\",D,!1,!0);\nvar vb=V(\"descendant-or-self\",function(a,b,c,d){var e=new E;C(b,c,d)&&a.a(b)&&e.add(b);return D(a,b,c,d,e)},!1,!0),rb=V(\"following\",function(a,b,c,d){var e=new E;do for(var f=b;f=f.nextSibling;)C(f,c,d)&&a.a(f)&&e.add(f),e=D(a,f,c,d,e);while(b=b.parentNode);return e},!1,!0);V(\"following-sibling\",function(a,b){for(var c=new E;b=b.nextSibling;)a.a(b)&&c.add(b);return c},!1);V(\"namespace\",function(){return new E},!1);\nvar yb=V(\"parent\",function(a,b){var c=new E;if(9==b.nodeType)return c;if(2==b.nodeType)return c.add(b.ownerElement),c;b=b.parentNode;a.a(b)&&c.add(b);return c},!1),sb=V(\"preceding\",function(a,b,c,d){var e=new E,f=[];do f.unshift(b);while(b=b.parentNode);for(var g=1,h=f.length;g<h;g++){var q=[];for(b=f[g];b=b.previousSibling;)q.unshift(b);for(var x=0,T=q.length;x<T;x++)b=q[x],C(b,c,d)&&a.a(b)&&e.add(b),e=D(a,b,c,d,e)}return e},!0,!0);\nV(\"preceding-sibling\",function(a,b){for(var c=new E;b=b.previousSibling;)a.a(b)&&Xa(c,b);return c},!0);var zb=V(\"self\",function(a,b){var c=new E;a.a(b)&&c.add(b);return c},!1);function Ab(a){J.call(this,1);this.c=a;this.g=a.g;this.b=a.b}l(Ab,J);Ab.prototype.a=function(a){return-L(this.c,a)};Ab.prototype.toString=function(){return\"Unary Expression: -\"+K(this.c)};function Bb(a){J.call(this,4);this.c=a;ab(this,r(this.c,function(b){return b.g}));bb(this,r(this.c,function(b){return b.b}))}l(Bb,J);Bb.prototype.a=function(a){var b=new E;n(this.c,function(c){c=c.a(a);if(!(c instanceof E))throw Error(\"Path expression must evaluate to NodeSet.\");b=Wa(b,c)});return b};Bb.prototype.toString=function(){return p(this.c,function(a,b){return a+K(b)},\"Union Expression:\")};function Cb(a,b){this.a=a;this.b=b}function Db(a){for(var b,c=[];;){W(a,\"Missing right hand side of binary expression.\");b=Eb(a);var d=A(a.a);if(!d)break;var e=(d=eb[d]||null)&&d.D;if(!e){a.a.a--;break}for(;c.length&&e<=c[c.length-1].D;)b=new O(c.pop(),c.pop(),b);c.push(b,d)}for(;c.length;)b=new O(c.pop(),c.pop(),b);return b}function W(a,b){if(Ia(a.a))throw Error(b);}function Fb(a,b){a=A(a.a);if(a!=b)throw Error(\"Bad token, expected: \"+b+\" got: \"+a);}\nfunction Gb(a){a=A(a.a);if(\")\"!=a)throw Error(\"Bad token: \"+a);}function Hb(a){a=A(a.a);if(2>a.length)throw Error(\"Unclosed literal string\");return new lb(a)}\nfunction Ib(a){var b=[];if(qb(z(a.a))){var c=A(a.a);var d=z(a.a);if(\"/\"==c&&(Ia(a.a)||\".\"!=d&&\"..\"!=d&&\"@\"!=d&&\"*\"!=d&&!/(?![0-9])[\\w]/.test(d)))return new S;d=new S;W(a,\"Missing next location step.\");c=Jb(a,c);b.push(c)}else{a:{c=z(a.a);d=c.charAt(0);switch(d){case \"$\":throw Error(\"Variable reference not allowed in HTML XPath\");case \"(\":A(a.a);c=Db(a);W(a,'unclosed \"(\"');Fb(a,\")\");break;case '\"':case \"'\":c=Hb(a);break;default:if(isNaN(+c))if(!kb(c)&&/(?![0-9])[\\w]/.test(d)&&\"(\"==z(a.a,1)){c=A(a.a);\nc=jb[c]||null;A(a.a);for(d=[];\")\"!=z(a.a);){W(a,\"Missing function argument list.\");d.push(Db(a));if(\",\"!=z(a.a))break;A(a.a)}W(a,\"Unclosed function argument list.\");Gb(a);c=new hb(c,d)}else{c=null;break a}else c=new mb(+A(a.a))}\"[\"==z(a.a)&&(d=new tb(Kb(a)),c=new fb(c,d))}if(c)if(qb(z(a.a)))d=c;else return c;else c=Jb(a,\"/\"),d=new pb,b.push(c)}for(;qb(z(a.a));)c=A(a.a),W(a,\"Missing next location step.\"),c=Jb(a,c),b.push(c);return new nb(d,b)}\nfunction Jb(a,b){if(\"/\"!=b&&\"//\"!=b)throw Error('Step op should be \"/\" or \"//\"');if(\".\"==z(a.a)){var c=new U(zb,new G(\"node\"));A(a.a);return c}if(\"..\"==z(a.a))return c=new U(yb,new G(\"node\")),A(a.a),c;if(\"@\"==z(a.a)){var d=ob;A(a.a);W(a,\"Missing attribute name\")}else if(\"::\"==z(a.a,1)){if(!/(?![0-9])[\\w]/.test(z(a.a).charAt(0)))throw Error(\"Bad token: \"+A(a.a));var e=A(a.a);d=xb[e]||null;if(!d)throw Error(\"No axis with name: \"+e);A(a.a);W(a,\"Missing node name\")}else d=ub;e=z(a.a);if(/(?![0-9])[\\w\\*]/.test(e.charAt(0)))if(\"(\"==\nz(a.a,1)){if(!kb(e))throw Error(\"Invalid node type: \"+e);e=A(a.a);if(!kb(e))throw Error(\"Invalid type name: \"+e);Fb(a,\"(\");W(a,\"Bad nodetype\");var f=z(a.a).charAt(0),g=null;if('\"'==f||\"'\"==f)g=Hb(a);W(a,\"Bad nodetype\");Gb(a);e=new G(e,g)}else if(e=A(a.a),f=e.indexOf(\":\"),-1==f)e=new F(e);else{g=e.substring(0,f);if(\"*\"==g)var h=\"*\";else if(h=a.b(g),!h)throw Error(\"Namespace prefix not declared: \"+g);e=e.substr(f+1);e=new F(e,h)}else throw Error(\"Bad token: \"+A(a.a));a=new tb(Kb(a),d.s);return c||new U(d,\ne,a,\"//\"==b)}function Kb(a){for(var b=[];\"[\"==z(a.a);){A(a.a);W(a,\"Missing predicate expression.\");var c=Db(a);b.push(c);W(a,\"Unclosed predicate expression.\");Fb(a,\"]\")}return b}function Eb(a){if(\"-\"==z(a.a))return A(a.a),new Ab(Eb(a));var b=Ib(a);if(\"|\"!=z(a.a))a=b;else{for(b=[b];\"|\"==A(a.a);)W(a,\"Missing next union location path.\"),b.push(Ib(a));a.a.a--;a=new Bb(b)}return a};function Lb(a){switch(a.nodeType){case 1:return ha(Mb,a);case 9:return Lb(a.documentElement);case 11:case 10:case 6:case 12:return Nb;default:return a.parentNode?Lb(a.parentNode):Nb}}function Nb(){return null}function Mb(a,b){if(a.prefix==b)return a.namespaceURI||\"http://www.w3.org/1999/xhtml\";var c=a.getAttributeNode(\"xmlns:\"+b);return c&&c.specified?c.value||null:a.parentNode&&9!=a.parentNode.nodeType?Mb(a.parentNode,b):null};function Ob(a,b){if(!a.length)throw Error(\"Empty XPath expression.\");a=Fa(a);if(Ia(a))throw Error(\"Invalid XPath expression.\");b?\"function\"==ca(b)||(b=fa(b.lookupNamespaceURI,b)):b=function(){return null};var c=Db(new Cb(a,b));if(!Ia(a))throw Error(\"Bad token: \"+A(a));this.evaluate=function(d,e){d=c.a(new m(d));return new X(d,e)}}\nfunction X(a,b){if(0==b)if(a instanceof E)b=4;else if(\"string\"==typeof a)b=2;else if(\"number\"==typeof a)b=1;else if(\"boolean\"==typeof a)b=3;else throw Error(\"Unexpected evaluation result.\");if(2!=b&&1!=b&&3!=b&&!(a instanceof E))throw Error(\"value could not be converted to the specified type\");this.resultType=b;switch(b){case 2:this.stringValue=a instanceof E?Za(a):\"\"+a;break;case 1:this.numberValue=a instanceof E?+Za(a):+a;break;case 3:this.booleanValue=a instanceof E?0<a.l:!!a;break;case 4:case 5:case 6:case 7:var c=\nH(a);var d=[];for(var e=I(c);e;e=I(c))d.push(e instanceof y?e.a:e);this.snapshotLength=a.l;this.invalidIteratorState=!1;break;case 8:case 9:a=Ya(a);this.singleNodeValue=a instanceof y?a.a:a;break;default:throw Error(\"Unknown XPathResult type.\");}var f=0;this.iterateNext=function(){if(4!=b&&5!=b)throw Error(\"iterateNext called with wrong result type\");return f>=d.length?null:d[f++]};this.snapshotItem=function(g){if(6!=b&&7!=b)throw Error(\"snapshotItem called with wrong result type\");return g>=d.length||\n0>g?null:d[g]}}X.ANY_TYPE=0;X.NUMBER_TYPE=1;X.STRING_TYPE=2;X.BOOLEAN_TYPE=3;X.UNORDERED_NODE_ITERATOR_TYPE=4;X.ORDERED_NODE_ITERATOR_TYPE=5;X.UNORDERED_NODE_SNAPSHOT_TYPE=6;X.ORDERED_NODE_SNAPSHOT_TYPE=7;X.ANY_UNORDERED_NODE_TYPE=8;X.FIRST_ORDERED_NODE_TYPE=9;function Pb(a){this.lookupNamespaceURI=Lb(a)}\nfunction Qb(a,b){a=a||k;var c=a.Document&&a.Document.prototype||a.document;if(!c.evaluate||b)a.XPathResult=X,c.evaluate=function(d,e,f,g){return(new Ob(d,f)).evaluate(e,g)},c.createExpression=function(d,e){return new Ob(d,e)},c.createNSResolver=function(d){return new Pb(d)}}ba(\"wgxpath.install\",Qb);ba(\"wgxpath.install\",Qb);var Rb=pa(),Sb=ra()||u(\"iPod\"),Tb=u(\"iPad\"),Ub=u(\"Android\")&&!(qa()||pa()||u(\"Opera\")||u(\"Silk\")),Vb=qa(),Wb=u(\"Safari\")&&!(qa()||u(\"Coast\")||u(\"Opera\")||u(\"Edge\")||u(\"Edg/\")||u(\"OPR\")||pa()||u(\"Silk\")||u(\"Android\"))&&!(ra()||u(\"iPad\")||u(\"iPod\"));function Y(a){return(a=a.exec(t))?a[1]:\"\"}(function(){if(Rb)return Y(/Firefox\\/([0-9.]+)/);if(v||ta||sa)return xa;if(Vb)return ra()||u(\"iPad\")||u(\"iPod\")?Y(/CriOS\\/([0-9.]+)/):Y(/Chrome\\/([0-9.]+)/);if(Wb&&!(ra()||u(\"iPad\")||u(\"iPod\")))return Y(/Version\\/([0-9.]+)/);if(Sb||Tb){var a=/Version\\/(\\S+).*Mobile\\/(\\S+)/.exec(t);if(a)return a[1]+\".\"+a[2]}else if(Ub)return(a=Y(/Android\\s+([0-9.]+)/))?a:Y(/Version\\/([0-9.]+)/);return\"\"})();function Z(a,b){b&&\"string\"!==typeof b&&(b=b.toString());return!!a&&1==a.nodeType&&(!b||a.tagName.toUpperCase()==b)};var Xb=\"BUTTON INPUT OPTGROUP OPTION SELECT TEXTAREA\".split(\" \");function Yb(a){return r(Xb,function(b){return Z(a,b)})?a.disabled?!1:a.parentNode&&1==a.parentNode.nodeType&&Z(a,\"OPTGROUP\")||Z(a,\"OPTION\")?Yb(a.parentNode):!Oa(a,function(b){var c=b.parentNode;if(c&&Z(c,\"FIELDSET\")&&c.disabled){if(!Z(b,\"LEGEND\"))return!0;for(;b=void 0!==b.previousElementSibling?b.previousElementSibling:Ja(b.previousSibling);)if(Z(b,\"LEGEND\"))return!0}return!1}):!0};ba(\"_\",Yb);; return this._.apply(null,arguments);}).apply({navigator:typeof window!='undefined'?window.navigator:null,document:typeof window!='undefined'?window.document:null}, arguments);}\n", }; atom.getVisibleText = async function (element, window) { @@ -40,10 +39,6 @@ atom.isElementDisplayed = function (element, window) { return executeInContent("isElementDisplayed", element, window); } -atom.isElementEnabled = function (element, window) { - return executeInContent("isElementEnabled", element, window); -} - function executeInContent(name, element, window) { const sandbox = lazy.sandbox.createMutable(window); diff --git a/remote/marionette/driver.sys.mjs b/remote/marionette/driver.sys.mjs index 6b5b1cf082..95b1352ab3 100644 --- a/remote/marionette/driver.sys.mjs +++ b/remote/marionette/driver.sys.mjs @@ -29,7 +29,7 @@ ChromeUtils.defineESModuleGetters(lazy, { MarionettePrefs: "chrome://remote/content/marionette/prefs.sys.mjs", modal: "chrome://remote/content/shared/Prompt.sys.mjs", navigate: "chrome://remote/content/marionette/navigate.sys.mjs", - permissions: "chrome://remote/content/marionette/permissions.sys.mjs", + permissions: "chrome://remote/content/shared/Permissions.sys.mjs", pprint: "chrome://remote/content/shared/Format.sys.mjs", print: "chrome://remote/content/shared/PDF.sys.mjs", PromptListener: @@ -1373,9 +1373,20 @@ GeckoDriver.prototype.switchToFrame = async function (cmd) { byFrame = el; } - const { browsingContext } = await this.getActor({ top }).switchToFrame( - byFrame || id - ); + // If the current context changed during the switchToFrame call, attempt to + // call switchToFrame again until the browsing context remains stable. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=1786640#c11 + let browsingContext; + for (let i = 0; i < 5; i++) { + const currentBrowsingContext = this.currentSession.contentBrowsingContext; + ({ browsingContext } = await this.getActor({ top }).switchToFrame( + byFrame || id + )); + + if (currentBrowsingContext == this.currentSession.contentBrowsingContext) { + break; + } + } this.currentSession.contentBrowsingContext = browsingContext; }; @@ -3346,22 +3357,16 @@ GeckoDriver.prototype.setPermission = async function (cmd) { const { descriptor, state, oneRealm = false } = cmd.parameters; const browsingContext = lazy.assert.open(this.getBrowsingContext()); - // XXX: We currently depend on camera/microphone tests throwing UnsupportedOperationError, - // the fix is ongoing in bug 1609427. - if (["camera", "microphone"].includes(descriptor.name)) { - throw new lazy.error.UnsupportedOperationError( - "setPermission: camera and microphone permissions are currently unsupported" - ); - } + lazy.permissions.validatePermission(descriptor.name); - // XXX: Allowing this permission causes timing related Android crash, see also bug 1878741 + // Bug 1878741: Allowing this permission causes timing related Android crash. if (descriptor.name === "notifications") { if (Services.prefs.getBoolPref("notification.prompt.testing", false)) { // Okay, do nothing. The notifications module will work without permission. return; } throw new lazy.error.UnsupportedOperationError( - "setPermission: expected notification.prompt.testing to be set" + `Setting "descriptor.name" "notifications" expected "notification.prompt.testing" preference to be set` ); } @@ -3378,7 +3383,26 @@ GeckoDriver.prototype.setPermission = async function (cmd) { lazy.assert.boolean(oneRealm); - lazy.permissions.set(params.type, params.state, oneRealm, browsingContext); + if (!lazy.MarionettePrefs.setPermissionEnabled) { + throw new lazy.error.UnsupportedOperationError( + "'Set Permission' is not available" + ); + } + + let origin = browsingContext.currentURI.prePath; + + // storage-access is a special case. + if (descriptor.name === "storage-access") { + origin = browsingContext.top.currentURI.prePath; + + params = { + type: lazy.permissions.getStorageAccessPermissionsType( + browsingContext.currentWindowGlobal.documentURI + ), + }; + } + + lazy.permissions.set(params, state, origin); }; /** diff --git a/remote/marionette/interaction.sys.mjs b/remote/marionette/interaction.sys.mjs index d710f2eb46..7fa7df0abe 100644 --- a/remote/marionette/interaction.sys.mjs +++ b/remote/marionette/interaction.sys.mjs @@ -9,7 +9,8 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { setTimeout: "resource://gre/modules/Timer.sys.mjs", - accessibility: "chrome://remote/content/marionette/accessibility.sys.mjs", + accessibility: + "chrome://remote/content/shared/webdriver/Accessibility.sys.mjs", atom: "chrome://remote/content/marionette/atom.sys.mjs", dom: "chrome://remote/content/shared/DOM.sys.mjs", error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs", @@ -236,9 +237,7 @@ async function webdriverClickElement(el, a11y) { } async function chromeClick(el, a11y) { - const win = getWindow(el); - - if (!(await lazy.atom.isElementEnabled(el, win))) { + if (!(await lazy.dom.isEnabled(el))) { throw new lazy.error.InvalidElementStateError("Element is not enabled"); } @@ -266,7 +265,7 @@ async function seleniumClickElement(el, a11y) { throw new lazy.error.ElementNotInteractableError(); } - if (!(await lazy.atom.isElementEnabled(el, win))) { + if (!(await lazy.dom.isEnabled(el))) { throw new lazy.error.InvalidElementStateError("Element is not enabled"); } @@ -775,7 +774,7 @@ interaction.isElementEnabled = async function (el, strict = false) { ) { enabled = false; } else { - enabled = await lazy.atom.isElementEnabled(el, win); + enabled = await lazy.dom.isEnabled(el); } let a11y = lazy.accessibility.get(strict); diff --git a/remote/marionette/jar.mn b/remote/marionette/jar.mn index 21c37ee455..2c04e0866c 100644 --- a/remote/marionette/jar.mn +++ b/remote/marionette/jar.mn @@ -4,7 +4,6 @@ remote.jar: % content remote %content/ - content/marionette/accessibility.sys.mjs (accessibility.sys.mjs) content/marionette/actors/MarionetteCommandsChild.sys.mjs (actors/MarionetteCommandsChild.sys.mjs) content/marionette/actors/MarionetteCommandsParent.sys.mjs (actors/MarionetteCommandsParent.sys.mjs) content/marionette/actors/MarionetteEventsChild.sys.mjs (actors/MarionetteEventsChild.sys.mjs) @@ -24,7 +23,6 @@ remote.jar: content/marionette/message.sys.mjs (message.sys.mjs) content/marionette/navigate.sys.mjs (navigate.sys.mjs) content/marionette/packets.sys.mjs (packets.sys.mjs) - content/marionette/permissions.sys.mjs (permissions.sys.mjs) content/marionette/prefs.sys.mjs (prefs.sys.mjs) content/marionette/reftest.sys.mjs (reftest.sys.mjs) content/marionette/reftest.xhtml (chrome/reftest.xhtml) diff --git a/remote/marionette/permissions.sys.mjs b/remote/marionette/permissions.sys.mjs deleted file mode 100644 index 5238bf8347..0000000000 --- a/remote/marionette/permissions.sys.mjs +++ /dev/null @@ -1,95 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -const lazy = {}; - -ChromeUtils.defineESModuleGetters(lazy, { - error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs", - MarionettePrefs: "chrome://remote/content/marionette/prefs.sys.mjs", -}); - -/** @namespace */ -export const permissions = {}; - -function mapToInternalPermissionParameters(browsingContext, permissionType) { - const currentURI = browsingContext.currentWindowGlobal.documentURI; - - // storage-access is quite special... - if (permissionType === "storage-access") { - const thirdPartyPrincipalSite = Services.eTLD.getSite(currentURI); - - const topLevelURI = browsingContext.top.currentWindowGlobal.documentURI; - const topLevelPrincipal = - Services.scriptSecurityManager.createContentPrincipal(topLevelURI, {}); - - return { - name: "3rdPartyFrameStorage^" + thirdPartyPrincipalSite, - principal: topLevelPrincipal, - }; - } - - const currentPrincipal = - Services.scriptSecurityManager.createContentPrincipal(currentURI, {}); - - return { - name: permissionType, - principal: currentPrincipal, - }; -} - -/** - * Set a permission's state. - * Note: Currently just a shim to support testdriver's set_permission. - * - * @param {object} permissionType - * The Gecko internal permission type - * @param {string} state - * State of the permission. It can be `granted`, `denied` or `prompt`. - * @param {boolean} oneRealm - * Currently ignored - * @param {browsingContext=} browsingContext - * Current browsing context object - * @throws {UnsupportedOperationError} - * If `marionette.setpermission.enabled` is not set or - * an unsupported permission is used. - */ -permissions.set = function (permissionType, state, oneRealm, browsingContext) { - if (!lazy.MarionettePrefs.setPermissionEnabled) { - throw new lazy.error.UnsupportedOperationError( - "'Set Permission' is not available" - ); - } - - const { name, principal } = mapToInternalPermissionParameters( - browsingContext, - permissionType - ); - - switch (state) { - case "granted": { - Services.perms.addFromPrincipal( - principal, - name, - Services.perms.ALLOW_ACTION - ); - return; - } - case "denied": { - Services.perms.addFromPrincipal( - principal, - name, - Services.perms.DENY_ACTION - ); - return; - } - case "prompt": { - Services.perms.removeFromPrincipal(principal, name); - return; - } - default: - throw new lazy.error.UnsupportedOperationError( - "Unrecognized permission keyword for 'Set Permission' operation" - ); - } -}; diff --git a/remote/shared/Capture.sys.mjs b/remote/shared/Capture.sys.mjs index ec34d09aba..a9a20c0b81 100644 --- a/remote/shared/Capture.sys.mjs +++ b/remote/shared/Capture.sys.mjs @@ -74,7 +74,7 @@ capture.canvas = async function ( ) { // FIXME(bug 1761032): This looks a bit sketchy, overrideDPPX doesn't // influence rendering... - const scale = win.browsingContext.overrideDPPX || win.devicePixelRatio; + const scale = browsingContext.overrideDPPX || win.devicePixelRatio; let canvasHeight = height * scale; let canvasWidth = width * scale; diff --git a/remote/shared/DOM.sys.mjs b/remote/shared/DOM.sys.mjs index 664f02328c..51c9298183 100644 --- a/remote/shared/DOM.sys.mjs +++ b/remote/shared/DOM.sys.mjs @@ -622,24 +622,14 @@ dom.isDisabled = function (el) { return false; } - switch (el.localName) { - case "option": - case "optgroup": - if (el.disabled) { - return true; - } - let parent = dom.findClosest(el, "optgroup,select"); - return dom.isDisabled(parent); - - case "button": - case "input": - case "select": - case "textarea": - return el.disabled; - - default: - return false; + // Selenium expects that even an enabled "option" element that is a child + // of a disabled "optgroup" or "select" element to be disabled. + if (["optgroup", "option"].includes(el.localName) && !el.disabled) { + const parent = dom.findClosest(el, "optgroup,select"); + return dom.isDisabled(parent); } + + return el.matches(":disabled"); }; /** @@ -1064,6 +1054,16 @@ dom.isElement = function (obj) { return dom.isDOMElement(obj) || dom.isXULElement(obj); }; +dom.isEnabled = function (el) { + let enabled = false; + + if (el.ownerDocument.contentType !== "text/xml") { + enabled = !dom.isDisabled(el); + } + + return enabled; +}; + /** * Returns the shadow root of an element. * diff --git a/remote/shared/Navigate.sys.mjs b/remote/shared/Navigate.sys.mjs index 9b72c0dfbf..cdb23b54c7 100644 --- a/remote/shared/Navigate.sys.mjs +++ b/remote/shared/Navigate.sys.mjs @@ -91,9 +91,14 @@ export async function waitForInitialNavigationCompleted( isInitial = browsingContext.currentWindowGlobal.isInitialDocument; } + const isLoadingDocument = listener.isLoadingDocument; + lazy.logger.trace( + lazy.truncate`[${browsingContext.id}] Wait for initial navigation: isInitial=${isInitial}, isLoadingDocument=${isLoadingDocument}` + ); + // If the current document is not the initial "about:blank" and is also // no longer loading, assume the navigation is done and return. - if (!isInitial && !listener.isLoadingDocument) { + if (!isInitial && !isLoadingDocument) { lazy.logger.trace( lazy.truncate`[${browsingContext.id}] Document already finished loading: ${browsingContext.currentURI?.spec}` ); diff --git a/remote/shared/NetworkRequest.sys.mjs b/remote/shared/NetworkRequest.sys.mjs new file mode 100644 index 0000000000..6524132752 --- /dev/null +++ b/remote/shared/NetworkRequest.sys.mjs @@ -0,0 +1,254 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const lazy = {}; +ChromeUtils.defineESModuleGetters(lazy, { + NetworkUtils: + "resource://devtools/shared/network-observer/NetworkUtils.sys.mjs", + + notifyNavigationStarted: + "chrome://remote/content/shared/NavigationManager.sys.mjs", + TabManager: "chrome://remote/content/shared/TabManager.sys.mjs", +}); + +/** + * The NetworkRequest class is a wrapper around the internal channel which + * provides getters and methods closer to fetch's response concept + * (https://fetch.spec.whatwg.org/#concept-response). + */ +export class NetworkRequest { + #channel; + #contextId; + #navigationId; + #navigationManager; + #postData; + #rawHeaders; + #redirectCount; + #requestId; + #timedChannel; + #wrappedChannel; + + /** + * + * @param {nsIChannel} channel + * The channel for the request. + * @param {object} params + * @param {NavigationManager} params.navigationManager + * The NavigationManager where navigations for the current session are + * monitored. + * @param {string=} params.rawHeaders + * The request's raw (ie potentially compressed) headers + */ + constructor(channel, params) { + const { navigationManager, rawHeaders = "" } = params; + + this.#channel = channel; + this.#navigationManager = navigationManager; + this.#rawHeaders = rawHeaders; + + this.#timedChannel = this.#channel.QueryInterface(Ci.nsITimedChannel); + this.#wrappedChannel = ChannelWrapper.get(channel); + + this.#redirectCount = this.#timedChannel.redirectCount; + // The wrappedChannel id remains identical across redirects, whereas + // nsIChannel.channelId is different for each and every request. + this.#requestId = this.#wrappedChannel.id.toString(); + + this.#contextId = this.#getContextId(); + this.#navigationId = this.#getNavigationId(); + } + + get contextId() { + return this.#contextId; + } + + get errorText() { + // TODO: Update with a proper error text. Bug 1873037. + return ChromeUtils.getXPCOMErrorName(this.#channel.status); + } + + get headersSize() { + // TODO: rawHeaders will not be updated after modifying the headers via + // request interception. Need to find another way to retrieve the + // information dynamically. + return this.#rawHeaders.length; + } + + get method() { + return this.#channel.requestMethod; + } + + get navigationId() { + return this.#navigationId; + } + + get postDataSize() { + return this.#postData ? this.#postData.size : 0; + } + + get redirectCount() { + return this.#redirectCount; + } + + get requestId() { + return this.#requestId; + } + + get serializedURL() { + return this.#channel.URI.spec; + } + + get wrappedChannel() { + return this.#wrappedChannel; + } + + /** + * Retrieve the Fetch timings for the NetworkRequest. + * + * @returns {object} + * Object with keys corresponding to fetch timing names, and their + * corresponding values. + */ + getFetchTimings() { + const { + channelCreationTime, + redirectStartTime, + redirectEndTime, + dispatchFetchEventStartTime, + cacheReadStartTime, + domainLookupStartTime, + domainLookupEndTime, + connectStartTime, + connectEndTime, + secureConnectionStartTime, + requestStartTime, + responseStartTime, + responseEndTime, + } = this.#timedChannel; + + // fetchStart should be the post-redirect start time, which should be the + // first non-zero timing from: dispatchFetchEventStart, cacheReadStart and + // domainLookupStart. See https://www.w3.org/TR/navigation-timing-2/#processing-model + const fetchStartTime = + dispatchFetchEventStartTime || + cacheReadStartTime || + domainLookupStartTime; + + // Bug 1805478: Per spec, the origin time should match Performance API's + // timeOrigin for the global which initiated the request. This is not + // available in the parent process, so for now we will use 0. + const timeOrigin = 0; + + return { + timeOrigin, + requestTime: this.#convertTimestamp(channelCreationTime, timeOrigin), + redirectStart: this.#convertTimestamp(redirectStartTime, timeOrigin), + redirectEnd: this.#convertTimestamp(redirectEndTime, timeOrigin), + fetchStart: this.#convertTimestamp(fetchStartTime, timeOrigin), + dnsStart: this.#convertTimestamp(domainLookupStartTime, timeOrigin), + dnsEnd: this.#convertTimestamp(domainLookupEndTime, timeOrigin), + connectStart: this.#convertTimestamp(connectStartTime, timeOrigin), + connectEnd: this.#convertTimestamp(connectEndTime, timeOrigin), + tlsStart: this.#convertTimestamp(secureConnectionStartTime, timeOrigin), + tlsEnd: this.#convertTimestamp(connectEndTime, timeOrigin), + requestStart: this.#convertTimestamp(requestStartTime, timeOrigin), + responseStart: this.#convertTimestamp(responseStartTime, timeOrigin), + responseEnd: this.#convertTimestamp(responseEndTime, timeOrigin), + }; + } + + /** + * Retrieve the list of headers for the NetworkRequest. + * + * @returns {Array.Array} + * Array of (name, value) tuples. + */ + getHeadersList() { + const headers = []; + + this.#channel.visitRequestHeaders({ + visitHeader(name, value) { + // The `Proxy-Authorization` header even though it appears on the channel is not + // actually sent to the server for non CONNECT requests after the HTTP/HTTPS tunnel + // is setup by the proxy. + if (name == "Proxy-Authorization") { + return; + } + headers.push([name, value]); + }, + }); + + return headers; + } + + /** + * Update the postData for this NetworkRequest. This is currently forwarded + * by the DevTools' NetworkObserver. + * + * TODO: We should read this information dynamically from the channel so that + * we can get updated information in case it was modified via network + * interception. + * + * @param {object} postData + * The request POST data. + */ + setPostData(postData) { + this.#postData = postData; + } + + /** + * Convert the provided request timing to a timing relative to the beginning + * of the request. All timings are numbers representing high definition + * timestamps. + * + * @param {number} timing + * High definition timestamp for a request timing relative from the time + * origin. + * @param {number} requestTime + * High definition timestamp for the request start time relative from the + * time origin. + * + * @returns {number} + * High definition timestamp for the request timing relative to the start + * time of the request, or 0 if the provided timing was 0. + */ + #convertTimestamp(timing, requestTime) { + if (timing == 0) { + return 0; + } + + return timing - requestTime; + } + + #getContextId() { + const id = lazy.NetworkUtils.getChannelBrowsingContextID(this.#channel); + const browsingContext = BrowsingContext.get(id); + return lazy.TabManager.getIdForBrowsingContext(browsingContext); + } + + #getNavigationId() { + if (!this.#channel.isMainDocumentChannel) { + return null; + } + + const browsingContext = lazy.TabManager.getBrowsingContextById( + this.#contextId + ); + + let navigation = + this.#navigationManager.getNavigationForBrowsingContext(browsingContext); + + // `onBeforeRequestSent` might be too early for the NavigationManager. + // If there is no ongoing navigation, create one ourselves. + // TODO: Bug 1835704 to detect navigations earlier and avoid this. + if (!navigation || navigation.finished) { + navigation = lazy.notifyNavigationStarted({ + contextDetails: { context: browsingContext }, + url: this.serializedURL, + }); + } + + return navigation ? navigation.navigationId : null; + } +} diff --git a/remote/shared/NetworkResponse.sys.mjs b/remote/shared/NetworkResponse.sys.mjs new file mode 100644 index 0000000000..45a03fb445 --- /dev/null +++ b/remote/shared/NetworkResponse.sys.mjs @@ -0,0 +1,131 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const lazy = {}; +ChromeUtils.defineESModuleGetters(lazy, { + NetworkUtils: + "resource://devtools/shared/network-observer/NetworkUtils.sys.mjs", +}); + +/** + * The NetworkResponse class is a wrapper around the internal channel which + * provides getters and methods closer to fetch's response concept + * (https://fetch.spec.whatwg.org/#concept-response). + */ +export class NetworkResponse { + #channel; + #decodedBodySize; + #encodedBodySize; + #fromCache; + #headersTransmittedSize; + #status; + #statusMessage; + #totalTransmittedSize; + #wrappedChannel; + + /** + * + * @param {nsIChannel} channel + * The channel for the response. + * @param {object} params + * @param {boolean} params.fromCache + * Whether the response was read from the cache or not. + * @param {string=} params.rawHeaders + * The response's raw (ie potentially compressed) headers + */ + constructor(channel, params) { + this.#channel = channel; + const { fromCache, rawHeaders = "" } = params; + this.#fromCache = fromCache; + this.#wrappedChannel = ChannelWrapper.get(channel); + + this.#decodedBodySize = 0; + this.#encodedBodySize = 0; + this.#headersTransmittedSize = rawHeaders.length; + this.#totalTransmittedSize = rawHeaders.length; + + // TODO: responseStatus and responseStatusText are sometimes inconsistent. + // For instance, they might be (304, Not Modified) when retrieved during the + // responseStarted event, and then (200, OK) during the responseCompleted + // event. + // For now consider them as immutable and store them on startup. + this.#status = this.#channel.responseStatus; + this.#statusMessage = this.#channel.responseStatusText; + } + + get decodedBodySize() { + return this.#decodedBodySize; + } + + get encodedBodySize() { + return this.#encodedBodySize; + } + + get headersTransmittedSize() { + return this.#headersTransmittedSize; + } + + get fromCache() { + return this.#fromCache; + } + + get protocol() { + return lazy.NetworkUtils.getProtocol(this.#channel); + } + + get serializedURL() { + return this.#channel.URI.spec; + } + + get status() { + return this.#status; + } + + get statusMessage() { + return this.#statusMessage; + } + + get totalTransmittedSize() { + return this.#totalTransmittedSize; + } + + addResponseContent(responseContent) { + this.#decodedBodySize = responseContent.decodedBodySize; + this.#encodedBodySize = responseContent.bodySize; + this.#totalTransmittedSize = responseContent.transferredSize; + } + + getComputedMimeType() { + // TODO: DevTools NetworkObserver is computing a similar value in + // addResponseContent, but uses an inconsistent implementation in + // addResponseStart. This approach can only be used as early as in + // addResponseHeaders. We should move this logic to the NetworkObserver and + // expose mimeType in addResponseStart. Bug 1809670. + let mimeType = ""; + + try { + mimeType = this.#wrappedChannel.contentType; + const contentCharset = this.#channel.contentCharset; + if (contentCharset) { + mimeType += `;charset=${contentCharset}`; + } + } catch (e) { + // Ignore exceptions when reading contentType/contentCharset + } + + return mimeType; + } + + getHeadersList() { + const headers = []; + + this.#channel.visitOriginalResponseHeaders({ + visitHeader(name, value) { + headers.push([name, value]); + }, + }); + + return headers; + } +} diff --git a/remote/shared/Permissions.sys.mjs b/remote/shared/Permissions.sys.mjs new file mode 100644 index 0000000000..50996bf701 --- /dev/null +++ b/remote/shared/Permissions.sys.mjs @@ -0,0 +1,90 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs", +}); + +/** @namespace */ +export const permissions = {}; + +/** + * Get a permission type for the "storage-access" permission. + * + * @param {nsIURI} uri + * The URI to use for building the permission type. + * + * @returns {string} permissionType + * The permission type for the "storage-access" permission. + */ +permissions.getStorageAccessPermissionsType = function (uri) { + const thirdPartyPrincipalSite = Services.eTLD.getSite(uri); + return "3rdPartyFrameStorage^" + thirdPartyPrincipalSite; +}; + +/** + * Set a permission given a permission descriptor, a permission state, + * an origin. + * + * @param {PermissionDescriptor} descriptor + * The descriptor of the permission which will be updated. + * @param {string} state + * State of the permission. It can be `granted`, `denied` or `prompt`. + * @param {string} origin + * The origin which is used as a target for permission update. + * + * @throws {UnsupportedOperationError} + * If <var>state</var> has unsupported value. + */ +permissions.set = function (descriptor, state, origin) { + const principal = + Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin); + + switch (state) { + case "granted": { + Services.perms.addFromPrincipal( + principal, + descriptor.type, + Services.perms.ALLOW_ACTION + ); + return; + } + case "denied": { + Services.perms.addFromPrincipal( + principal, + descriptor.type, + Services.perms.DENY_ACTION + ); + return; + } + case "prompt": { + Services.perms.removeFromPrincipal(principal, descriptor.type); + return; + } + default: + throw new lazy.error.UnsupportedOperationError( + "Unrecognized permission keyword for 'Set Permission' operation" + ); + } +}; + +/** + * Validate the permission. + * + * @param {string} permissionName + * The name of the permission which will be validated. + * + * @throws {UnsupportedOperationError} + * If <var>permissionName</var> is not supported. + */ +permissions.validatePermission = function (permissionName) { + // Bug 1609427: PermissionDescriptor for "camera" and "microphone" are not yet implemented. + if (["camera", "microphone"].includes(permissionName)) { + throw new lazy.error.UnsupportedOperationError( + `"descriptor.name" "${permissionName}" is currently unsupported` + ); + } +}; diff --git a/remote/shared/listeners/NetworkEventRecord.sys.mjs b/remote/shared/listeners/NetworkEventRecord.sys.mjs index 72b43e3de1..0f592d62b0 100644 --- a/remote/shared/listeners/NetworkEventRecord.sys.mjs +++ b/remote/shared/listeners/NetworkEventRecord.sys.mjs @@ -4,10 +4,8 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { - NetworkUtils: - "resource://devtools/shared/network-observer/NetworkUtils.sys.mjs", - - TabManager: "chrome://remote/content/shared/TabManager.sys.mjs", + NetworkRequest: "chrome://remote/content/shared/NetworkRequest.sys.mjs", + NetworkResponse: "chrome://remote/content/shared/NetworkResponse.sys.mjs", }); /** @@ -18,17 +16,10 @@ ChromeUtils.defineESModuleGetters(lazy, { * NetworkListener instance which created it. */ export class NetworkEventRecord { - #contextId; #fromCache; - #isMainDocumentChannel; #networkListener; - #redirectCount; - #requestChannel; - #requestData; - #requestId; - #responseChannel; - #responseData; - #wrappedChannel; + #request; + #response; /** * @@ -39,56 +30,21 @@ export class NetworkEventRecord { * The nsIChannel behind this network event. * @param {NetworkListener} networkListener * The NetworkListener which created this NetworkEventRecord. + * @param {NavigationManager} navigationManager + * The NavigationManager which belongs to the same session as this + * NetworkEventRecord. */ - constructor(networkEvent, channel, networkListener) { - this.#requestChannel = channel; - this.#responseChannel = null; + constructor(networkEvent, channel, networkListener, navigationManager) { + this.#request = new lazy.NetworkRequest(channel, { + navigationManager, + rawHeaders: networkEvent.rawHeaders, + }); + this.#response = null; this.#fromCache = networkEvent.fromCache; - this.#isMainDocumentChannel = channel.isMainDocumentChannel; - - this.#wrappedChannel = ChannelWrapper.get(channel); this.#networkListener = networkListener; - // The context ids computed by TabManager have the lifecycle of a navigable - // and can be reused for all the events emitted from this record. - this.#contextId = this.#getContextId(); - - // The wrappedChannel id remains identical across redirects, whereas - // nsIChannel.channelId is different for each and every request. - this.#requestId = this.#wrappedChannel.id.toString(); - - const { cookies, headers } = - lazy.NetworkUtils.fetchRequestHeadersAndCookies(channel); - - // See the RequestData type definition for the full list of properties that - // should be set on this object. - this.#requestData = { - bodySize: null, - cookies, - headers, - headersSize: networkEvent.rawHeaders ? networkEvent.rawHeaders.length : 0, - method: channel.requestMethod, - request: this.#requestId, - timings: {}, - url: channel.URI.spec, - }; - - // See the ResponseData type definition for the full list of properties that - // should be set on this object. - this.#responseData = { - // encoded size (body) - bodySize: null, - content: { - // decoded size - size: null, - }, - // encoded size (headers) - headersSize: null, - url: channel.URI.spec, - }; - // NetworkObserver creates a network event when request headers have been // parsed. // According to the BiDi spec, we should emit beforeRequestSent when adding @@ -113,8 +69,7 @@ export class NetworkEventRecord { * The request POST data. */ addRequestPostData(postData) { - // Only the postData size is needed for RemoteAgent consumers. - this.#requestData.bodySize = postData.size; + this.#request.setPostData(postData); } /** @@ -130,25 +85,11 @@ export class NetworkEventRecord { * @param {string} options.rawHeaders */ addResponseStart(options) { - const { channel, fromCache, rawHeaders = "" } = options; - this.#responseChannel = channel; - - const { headers } = - lazy.NetworkUtils.fetchResponseHeadersAndCookies(channel); - - const headersSize = rawHeaders.length; - this.#responseData = { - ...this.#responseData, - bodySize: 0, - bytesReceived: headersSize, + const { channel, fromCache, rawHeaders } = options; + this.#response = new lazy.NetworkResponse(channel, { + rawHeaders, fromCache: this.#fromCache || !!fromCache, - headers, - headersSize, - mimeType: this.#getMimeType(), - protocol: lazy.NetworkUtils.getProtocol(channel), - status: channel.responseStatus, - statusText: channel.responseStatusText, - }; + }); // This should be triggered when all headers have been received, matching // the WebDriverBiDi response started trigger in `4.6. HTTP-network fetch` @@ -189,25 +130,16 @@ export class NetworkEventRecord { * * Required API for a NetworkObserver event owner. * - * @param {object} response + * @param {object} responseContent * An object which represents the response content. * @param {object} responseInfo * Additional meta data about the response. */ - addResponseContent(response, responseInfo) { - // Update content-related sizes with the latest data from addResponseContent. - this.#responseData = { - ...this.#responseData, - bodySize: response.bodySize, - bytesReceived: response.transferredSize, - content: { - size: response.decodedBodySize, - }, - }; - + addResponseContent(responseContent, responseInfo) { if (responseInfo.blockedReason) { this.#emitFetchError(); } else { + this.#response.addResponseContent(responseContent); this.#emitResponseCompleted(); } } @@ -234,201 +166,37 @@ export class NetworkEventRecord { this.#emitAuthRequired(authCallbacks); } - /** - * Convert the provided request timing to a timing relative to the beginning - * of the request. All timings are numbers representing high definition - * timestamps. - * - * @param {number} timing - * High definition timestamp for a request timing relative from the time - * origin. - * @param {number} requestTime - * High definition timestamp for the request start time relative from the - * time origin. - * @returns {number} - * High definition timestamp for the request timing relative to the start - * time of the request, or 0 if the provided timing was 0. - */ - #convertTimestamp(timing, requestTime) { - if (timing == 0) { - return 0; - } - - return timing - requestTime; - } - #emitAuthRequired(authCallbacks) { - this.#updateDataFromTimedChannel(); - this.#networkListener.emit("auth-required", { authCallbacks, - contextId: this.#contextId, - isNavigationRequest: this.#isMainDocumentChannel, - redirectCount: this.#redirectCount, - requestChannel: this.#requestChannel, - requestData: this.#requestData, - responseChannel: this.#responseChannel, - responseData: this.#responseData, - timestamp: Date.now(), + request: this.#request, + response: this.#response, }); } #emitBeforeRequestSent() { - this.#updateDataFromTimedChannel(); - this.#networkListener.emit("before-request-sent", { - contextId: this.#contextId, - isNavigationRequest: this.#isMainDocumentChannel, - redirectCount: this.#redirectCount, - requestChannel: this.#requestChannel, - requestData: this.#requestData, - timestamp: Date.now(), + request: this.#request, }); } #emitFetchError() { - this.#updateDataFromTimedChannel(); - this.#networkListener.emit("fetch-error", { - contextId: this.#contextId, - // TODO: Update with a proper error text. Bug 1873037. - errorText: ChromeUtils.getXPCOMErrorName(this.#requestChannel.status), - isNavigationRequest: this.#isMainDocumentChannel, - redirectCount: this.#redirectCount, - requestChannel: this.#requestChannel, - requestData: this.#requestData, - timestamp: Date.now(), + request: this.#request, }); } #emitResponseCompleted() { - this.#updateDataFromTimedChannel(); - this.#networkListener.emit("response-completed", { - contextId: this.#contextId, - isNavigationRequest: this.#isMainDocumentChannel, - redirectCount: this.#redirectCount, - requestChannel: this.#requestChannel, - requestData: this.#requestData, - responseChannel: this.#responseChannel, - responseData: this.#responseData, - timestamp: Date.now(), + request: this.#request, + response: this.#response, }); } #emitResponseStarted() { - this.#updateDataFromTimedChannel(); - this.#networkListener.emit("response-started", { - contextId: this.#contextId, - isNavigationRequest: this.#isMainDocumentChannel, - redirectCount: this.#redirectCount, - requestChannel: this.#requestChannel, - requestData: this.#requestData, - responseChannel: this.#responseChannel, - responseData: this.#responseData, - timestamp: Date.now(), + request: this.#request, + response: this.#response, }); } - - #getBrowsingContext() { - const id = lazy.NetworkUtils.getChannelBrowsingContextID( - this.#requestChannel - ); - return BrowsingContext.get(id); - } - - /** - * Retrieve the navigable id for the current browsing context associated to - * the requests' channel. Network events are recorded in the parent process - * so we always expect to be able to use TabManager.getIdForBrowsingContext. - * - * @returns {string} - * The navigable id corresponding to the given browsing context. - */ - #getContextId() { - return lazy.TabManager.getIdForBrowsingContext(this.#getBrowsingContext()); - } - - #getMimeType() { - // TODO: DevTools NetworkObserver is computing a similar value in - // addResponseContent, but uses an inconsistent implementation in - // addResponseStart. This approach can only be used as early as in - // addResponseHeaders. We should move this logic to the NetworkObserver and - // expose mimeType in addResponseStart. Bug 1809670. - let mimeType = ""; - - try { - mimeType = this.#wrappedChannel.contentType; - const contentCharset = this.#requestChannel.contentCharset; - if (contentCharset) { - mimeType += `;charset=${contentCharset}`; - } - } catch (e) { - // Ignore exceptions when reading contentType/contentCharset - } - - return mimeType; - } - - #getTimingsFromTimedChannel(timedChannel) { - const { - channelCreationTime, - redirectStartTime, - redirectEndTime, - dispatchFetchEventStartTime, - cacheReadStartTime, - domainLookupStartTime, - domainLookupEndTime, - connectStartTime, - connectEndTime, - secureConnectionStartTime, - requestStartTime, - responseStartTime, - responseEndTime, - } = timedChannel; - - // fetchStart should be the post-redirect start time, which should be the - // first non-zero timing from: dispatchFetchEventStart, cacheReadStart and - // domainLookupStart. See https://www.w3.org/TR/navigation-timing-2/#processing-model - const fetchStartTime = - dispatchFetchEventStartTime || - cacheReadStartTime || - domainLookupStartTime; - - // Bug 1805478: Per spec, the origin time should match Performance API's - // timeOrigin for the global which initiated the request. This is not - // available in the parent process, so for now we will use 0. - const timeOrigin = 0; - - return { - timeOrigin, - requestTime: this.#convertTimestamp(channelCreationTime, timeOrigin), - redirectStart: this.#convertTimestamp(redirectStartTime, timeOrigin), - redirectEnd: this.#convertTimestamp(redirectEndTime, timeOrigin), - fetchStart: this.#convertTimestamp(fetchStartTime, timeOrigin), - dnsStart: this.#convertTimestamp(domainLookupStartTime, timeOrigin), - dnsEnd: this.#convertTimestamp(domainLookupEndTime, timeOrigin), - connectStart: this.#convertTimestamp(connectStartTime, timeOrigin), - connectEnd: this.#convertTimestamp(connectEndTime, timeOrigin), - tlsStart: this.#convertTimestamp(secureConnectionStartTime, timeOrigin), - tlsEnd: this.#convertTimestamp(connectEndTime, timeOrigin), - requestStart: this.#convertTimestamp(requestStartTime, timeOrigin), - responseStart: this.#convertTimestamp(responseStartTime, timeOrigin), - responseEnd: this.#convertTimestamp(responseEndTime, timeOrigin), - }; - } - - /** - * Update the timings and the redirect count from the nsITimedChannel - * corresponding to the current channel. This should be called before emitting - * any event from this class. - */ - #updateDataFromTimedChannel() { - const timedChannel = this.#requestChannel.QueryInterface( - Ci.nsITimedChannel - ); - this.#redirectCount = timedChannel.redirectCount; - this.#requestData.timings = this.#getTimingsFromTimedChannel(timedChannel); - } } diff --git a/remote/shared/listeners/NetworkListener.sys.mjs b/remote/shared/listeners/NetworkListener.sys.mjs index 500d2005dc..d0d6d0e44f 100644 --- a/remote/shared/listeners/NetworkListener.sys.mjs +++ b/remote/shared/listeners/NetworkListener.sys.mjs @@ -44,11 +44,13 @@ ChromeUtils.defineESModuleGetters(lazy, { export class NetworkListener { #devtoolsNetworkObserver; #listening; + #navigationManager; - constructor() { + constructor(navigationManager) { lazy.EventEmitter.decorate(this); this.#listening = false; + this.#navigationManager = navigationManager; } destroy() { @@ -104,6 +106,11 @@ export class NetworkListener { }; #onNetworkEvent = (networkEvent, channel) => { - return new lazy.NetworkEventRecord(networkEvent, channel, this); + return new lazy.NetworkEventRecord( + networkEvent, + channel, + this, + this.#navigationManager + ); }; } diff --git a/remote/shared/listeners/test/browser/browser_NetworkListener.js b/remote/shared/listeners/test/browser/browser_NetworkListener.js index cc1b42f2fc..211ccef49c 100644 --- a/remote/shared/listeners/test/browser/browser_NetworkListener.js +++ b/remote/shared/listeners/test/browser/browser_NetworkListener.js @@ -2,6 +2,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ +const { NavigationManager } = ChromeUtils.importESModule( + "chrome://remote/content/shared/NavigationManager.sys.mjs" +); const { NetworkListener } = ChromeUtils.importESModule( "chrome://remote/content/shared/listeners/NetworkListener.sys.mjs" ); @@ -10,7 +13,10 @@ const { TabManager } = ChromeUtils.importESModule( ); add_task(async function test_beforeRequestSent() { - const listener = new NetworkListener(); + const navigationManager = new NavigationManager(); + navigationManager.startMonitoring(); + + const listener = new NetworkListener(navigationManager); const events = []; const onEvent = (name, data) => events.push(data); listener.on("before-request-sent", onEvent); @@ -54,10 +60,14 @@ add_task(async function test_beforeRequestSent() { gBrowser.removeTab(tab2); listener.off("before-request-sent", onEvent); listener.destroy(); + navigationManager.destroy(); }); add_task(async function test_beforeRequestSent_newTab() { - const listener = new NetworkListener(); + const navigationManager = new NavigationManager(); + navigationManager.startMonitoring(); + + const listener = new NetworkListener(navigationManager); const onBeforeRequestSent = listener.once("before-request-sent"); listener.startListening(); @@ -76,10 +86,14 @@ add_task(async function test_beforeRequestSent_newTab() { "https://example.com/document-builder.sjs?html=tab" ); gBrowser.removeTab(tab); + navigationManager.destroy(); }); add_task(async function test_fetchError() { - const listener = new NetworkListener(); + const navigationManager = new NavigationManager(); + navigationManager.startMonitoring(); + + const listener = new NetworkListener(navigationManager); const onFetchError = listener.once("fetch-error"); listener.startListening(); @@ -90,11 +104,16 @@ add_task(async function test_fetchError() { const event = await onFetchError; assertNetworkEvent(event, contextId, "https://not_a_valid_url/"); - is(event.errorText, "NS_ERROR_UNKNOWN_HOST"); + is(event.request.errorText, "NS_ERROR_UNKNOWN_HOST"); gBrowser.removeTab(tab); + navigationManager.destroy(); }); function assertNetworkEvent(event, expectedContextId, expectedUrl) { - is(event.contextId, expectedContextId, "Event has the expected context id"); - is(event.requestData.url, expectedUrl, "Event has the expected url"); + is( + event.request.contextId, + expectedContextId, + "Event has the expected context id" + ); + is(event.request.serializedURL, expectedUrl, "Event has the expected url"); } diff --git a/remote/shared/test/xpcshell/test_DOM.js b/remote/shared/test/xpcshell/test_DOM.js index 19844659b9..03ac27ed45 100644 --- a/remote/shared/test/xpcshell/test_DOM.js +++ b/remote/shared/test/xpcshell/test_DOM.js @@ -75,27 +75,55 @@ function setupTest() { <iframe></iframe> <video></video> <svg xmlns="http://www.w3.org/2000/svg"></svg> - <textarea></textarea> + <form> + <button/> + <input/> + <fieldset> + <legend><input id="first"/></legend> + <legend><input id="second"/></legend> + </fieldset> + <select> + <optgroup> + <option id="in-group">foo</options> + </optgroup> + <option id="no-group">bar</option> + </select> + <textarea></textarea> + </form> </div> `; const divEl = browser.document.querySelector("div"); const svgEl = browser.document.querySelector("svg"); - const textareaEl = browser.document.querySelector("textarea"); const videoEl = browser.document.querySelector("video"); + const shadowRoot = videoEl.openOrClosedShadowRoot; + + const buttonEl = browser.document.querySelector("button"); + const fieldsetEl = browser.document.querySelector("fieldset"); + const inputEl = browser.document.querySelector("input"); + const optgroupEl = browser.document.querySelector("optgroup"); + const optionInGroupEl = browser.document.querySelector("option#in-group"); + const optionNoGroupEl = browser.document.querySelector("option#no-group"); + const selectEl = browser.document.querySelector("select"); + const textareaEl = browser.document.querySelector("textarea"); const iframeEl = browser.document.querySelector("iframe"); const childEl = iframeEl.contentDocument.createElement("div"); iframeEl.contentDocument.body.appendChild(childEl); - const shadowRoot = videoEl.openOrClosedShadowRoot; - return { browser, - nodeCache: new NodeCache(), + buttonEl, childEl, divEl, + inputEl, + fieldsetEl, iframeEl, + nodeCache: new NodeCache(), + optgroupEl, + optionInGroupEl, + optionNoGroupEl, + selectEl, shadowRoot, svgEl, textareaEl, @@ -252,38 +280,70 @@ add_task(function test_isReadOnly() { ok(!dom.isReadOnly(null)); }); -add_task(function test_isDisabled() { - const { browser, divEl, svgEl } = setupTest(); +add_task(function test_isDisabledSelect() { + const { optgroupEl, optionInGroupEl, optionNoGroupEl, selectEl } = + setupTest(); + + optionNoGroupEl.disabled = true; + ok(dom.isDisabled(optionNoGroupEl)); + optionNoGroupEl.disabled = false; + ok(!dom.isDisabled(optionNoGroupEl)); + + optgroupEl.disabled = true; + ok(dom.isDisabled(optgroupEl)); + ok(dom.isDisabled(optionInGroupEl)); + optgroupEl.disabled = false; + ok(!dom.isDisabled(optgroupEl)); + ok(!dom.isDisabled(optionInGroupEl)); + + selectEl.disabled = true; + ok(dom.isDisabled(selectEl)); + ok(dom.isDisabled(optgroupEl)); + ok(dom.isDisabled(optionNoGroupEl)); + selectEl.disabled = false; + ok(!dom.isDisabled(selectEl)); + ok(!dom.isDisabled(optgroupEl)); + ok(!dom.isDisabled(optionNoGroupEl)); +}); - const select = browser.document.createElement("select"); - const option = browser.document.createElement("option"); - select.appendChild(option); - select.disabled = true; - ok(dom.isDisabled(option)); +add_task(function test_isDisabledFormControl() { + const { buttonEl, fieldsetEl, inputEl, selectEl, textareaEl } = setupTest(); - const optgroup = browser.document.createElement("optgroup"); - option.parentNode = optgroup; - ok(dom.isDisabled(option)); + for (const elem of [buttonEl, inputEl, selectEl, textareaEl]) { + elem.disabled = true; + ok(dom.isDisabled(elem)); + elem.disabled = false; + ok(!dom.isDisabled(elem)); + } - optgroup.parentNode = select; - ok(dom.isDisabled(option)); + const inputs = fieldsetEl.querySelectorAll("input"); + fieldsetEl.disabled = true; + ok(dom.isDisabled(fieldsetEl)); + ok(!dom.isDisabled(inputs[0])); + ok(dom.isDisabled(inputs[1])); + fieldsetEl.disabled = false; + ok(!dom.isDisabled(fieldsetEl)); + ok(!dom.isDisabled(inputs[0])); + ok(!dom.isDisabled(inputs[1])); +}); - select.disabled = false; - ok(!dom.isDisabled(option)); +add_task(function test_isDisabledElement() { + const { divEl, svgEl } = setupTest(); + const mockXulEl = new MockXULElement("browser", { disabled: true }); - for (const type of ["button", "input", "select", "textarea"]) { - const elem = browser.document.createElement(type); + for (const elem of [divEl, svgEl, mockXulEl]) { ok(!dom.isDisabled(elem)); elem.disabled = true; - ok(dom.isDisabled(elem)); + ok(!dom.isDisabled(elem)); } +}); - ok(!dom.isDisabled(divEl)); - - svgEl.disabled = true; - ok(!dom.isDisabled(svgEl)); +add_task(function test_isDisabledNoDOMElement() { + ok(!dom.isDisabled()); - ok(!dom.isDisabled(new MockXULElement("browser", { disabled: true }))); + for (const obj of [null, undefined, 42, "", {}, []]) { + ok(!dom.isDisabled(obj)); + } }); add_task(function test_isEditingHost() { diff --git a/remote/marionette/accessibility.sys.mjs b/remote/shared/webdriver/Accessibility.sys.mjs index c500f2121e..4c7b2a6c69 100644 --- a/remote/marionette/accessibility.sys.mjs +++ b/remote/shared/webdriver/Accessibility.sys.mjs @@ -10,9 +10,7 @@ ChromeUtils.defineESModuleGetters(lazy, { waitForObserverTopic: "chrome://remote/content/marionette/sync.sys.mjs", }); -ChromeUtils.defineLazyGetter(lazy, "logger", () => - lazy.Log.get(lazy.Log.TYPES.MARIONETTE) -); +ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get()); ChromeUtils.defineLazyGetter(lazy, "service", () => { try { @@ -159,6 +157,48 @@ accessibility.getAccessible = async function (element) { }; /** + * Retrieve the accessible name for the provided element. + * + * @param {Element} element + * The element for which we need to retrieve the accessible name. + * + * @returns {string} + * The accessible name. + */ +accessibility.getAccessibleName = async function (element) { + const accessible = await accessibility.getAccessible(element); + if (!accessible) { + return ""; + } + + // If name is null (absent), expose the empty string. + if (accessible.name === null) { + return ""; + } + + return accessible.name; +}; + +/** + * Compute the role for the provided element. + * + * @param {Element} element + * The element for which we need to compute the role. + * + * @returns {string} + * The computed role. + */ +accessibility.getComputedRole = async function (element) { + const accessible = await accessibility.getAccessible(element); + if (!accessible) { + // If it's not in the a11y tree, it's probably presentational. + return "none"; + } + + return accessible.computedARIARole; +}; + +/** * Component responsible for interacting with platform accessibility * API. * diff --git a/remote/shared/webdriver/Actions.sys.mjs b/remote/shared/webdriver/Actions.sys.mjs index 2639c4dc9f..2c21e989dd 100644 --- a/remote/shared/webdriver/Actions.sys.mjs +++ b/remote/shared/webdriver/Actions.sys.mjs @@ -1318,6 +1318,7 @@ class WheelScrollAction extends WheelAction { this.duration ?? tickDuration, deltaTarget => this.performOneWheelScroll( + state, scrollCoordinates, deltaPosition, deltaTarget, @@ -1329,12 +1330,19 @@ class WheelScrollAction extends WheelAction { /** * Perform one part of a wheel scroll corresponding to a specific emitted event. * + * @param {State} state - Actions state. * @param {Array<number>} scrollCoordinates - [x, y] viewport coordinates of the scroll. * @param {Array<number>} deltaPosition - [deltaX, deltaY] coordinates of the scroll before this event. * @param {Array<Array<number>>} deltaTargets - Array of [deltaX, deltaY] coordinates to scroll to. * @param {WindowProxy} win - Current window global. */ - performOneWheelScroll(scrollCoordinates, deltaPosition, deltaTargets, win) { + performOneWheelScroll( + state, + scrollCoordinates, + deltaPosition, + deltaTargets, + win + ) { if (deltaTargets.length !== 1) { throw new Error("Can only scroll one wheel at a time"); } @@ -1350,6 +1358,7 @@ class WheelScrollAction extends WheelAction { deltaY, deltaZ: 0, }); + eventData.update(state); lazy.event.synthesizeWheelAtPoint( scrollCoordinates[0], @@ -2237,6 +2246,22 @@ class WheelEventData extends InputEventData { this.deltaY = deltaY; this.deltaZ = deltaZ; this.deltaMode = deltaMode; + + this.altKey = false; + this.ctrlKey = false; + this.metaKey = false; + this.shiftKey = false; + } + + update(state) { + // set modifier properties based on whether any corresponding keys are + // pressed on any key input source + for (const [, otherInputSource] of state.inputSourcesByType("key")) { + this.altKey = otherInputSource.alt || this.altKey; + this.ctrlKey = otherInputSource.ctrl || this.ctrlKey; + this.metaKey = otherInputSource.meta || this.metaKey; + this.shiftKey = otherInputSource.shift || this.shiftKey; + } } } diff --git a/remote/shared/webdriver/Assert.sys.mjs b/remote/shared/webdriver/Assert.sys.mjs index 6c254173aa..fe83bc9181 100644 --- a/remote/shared/webdriver/Assert.sys.mjs +++ b/remote/shared/webdriver/Assert.sys.mjs @@ -410,6 +410,36 @@ assert.object = function (obj, msg = "") { }; /** + * Asserts that <var>obj</var> is an instance of a specified class. + * <var>constructor</var> should have a static isInstance method implemented. + * + * @param {?} obj + * Value to test. + * @param {?} constructor + * Class constructor. + * @param {string=} msg + * Custom error message. + * + * @returns {object} + * <var>obj</var> is returned unaltered. + * + * @throws {InvalidArgumentError} + * If <var>obj</var> is not an instance of a specified class. + */ +assert.isInstance = function (obj, constructor, msg = "") { + assert.object(obj, msg); + assert.object(constructor.prototype, msg); + + msg = + msg || + lazy.pprint`Expected ${obj} to be an instance of ${constructor.name}`; + return assert.that( + o => Object.hasOwn(constructor, "isInstance") && constructor.isInstance(o), + msg + )(obj); +}; + +/** * Asserts that <var>prop</var> is in <var>obj</var>. * * @param {?} prop diff --git a/remote/shared/webdriver/Session.sys.mjs b/remote/shared/webdriver/Session.sys.mjs index edffeea7b6..3d7b074ac9 100644 --- a/remote/shared/webdriver/Session.sys.mjs +++ b/remote/shared/webdriver/Session.sys.mjs @@ -5,7 +5,8 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { - accessibility: "chrome://remote/content/marionette/accessibility.sys.mjs", + accessibility: + "chrome://remote/content/shared/webdriver/Accessibility.sys.mjs", allowAllCerts: "chrome://remote/content/marionette/cert.sys.mjs", Capabilities: "chrome://remote/content/shared/webdriver/Capabilities.sys.mjs", error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs", diff --git a/remote/shared/webdriver/test/xpcshell/test_Assert.js b/remote/shared/webdriver/test/xpcshell/test_Assert.js index cf474868b6..aabd8656dd 100644 --- a/remote/shared/webdriver/test/xpcshell/test_Assert.js +++ b/remote/shared/webdriver/test/xpcshell/test_Assert.js @@ -150,6 +150,20 @@ add_task(function test_object() { Assert.throws(() => assert.object(null, "custom"), /custom/); }); +add_task(function test_isInstance() { + class Foo { + static isInstance(obj) { + return obj instanceof Foo; + } + } + assert.isInstance(new Foo(), Foo); + for (let typ of [{}, 42, "foo", true, null, undefined]) { + Assert.throws(() => assert.isInstance(typ, Foo), /InvalidArgumentError/); + } + + Assert.throws(() => assert.isInstance(null, null, "custom"), /custom/); +}); + add_task(function test_in() { assert.in("foo", { foo: 42 }); for (let typ of [{}, 42, true, null, undefined]) { diff --git a/remote/test/puppeteer/.release-please-manifest.json b/remote/test/puppeteer/.release-please-manifest.json index a3ccb1a4ae..85dc75087e 100644 --- a/remote/test/puppeteer/.release-please-manifest.json +++ b/remote/test/puppeteer/.release-please-manifest.json @@ -1,7 +1,7 @@ { - "packages/puppeteer": "22.4.0", - "packages/puppeteer-core": "22.4.0", + "packages/puppeteer": "22.6.5", + "packages/puppeteer-core": "22.6.5", "packages/testserver": "0.6.0", "packages/ng-schematics": "0.6.0", - "packages/browsers": "2.1.0" + "packages/browsers": "2.2.2" } diff --git a/remote/test/puppeteer/.vscode/launch.template.json b/remote/test/puppeteer/.vscode/launch.template.json new file mode 100644 index 0000000000..51870281bd --- /dev/null +++ b/remote/test/puppeteer/.vscode/launch.template.json @@ -0,0 +1,46 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "inputs": [ + { + "type": "pickString", + "id": "suit", + "description": "Which test suit to run?", + "options": [ + "chrome-headless", + "chrome-headful", + "chrome-headless-shell", + "firefox-headless", + "firefox-headful", + "firefox-bidi", + "chrome-bidi" + ], + "default": "chrome-headless" + } + ], + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch Tests", + "skipFiles": ["<node_internals>/**"], + "runtimeExecutable": "npm", + "cwd": "${workspaceFolder}", + "runtimeArgs": [ + "run-script", + "test", + "--", + "--test-suite", + "${input:suit}", + "--no-coverage", + "--no-suggestions" + ], + "outFiles": ["${workspaceFolder}/**/*.js"], + "env": { + "DEBUGGER_ATTACHED": true + } + } + ] +} diff --git a/remote/test/puppeteer/Herebyfile.mjs b/remote/test/puppeteer/Herebyfile.mjs index 30f9c75262..7b1e27958f 100644 --- a/remote/test/puppeteer/Herebyfile.mjs +++ b/remote/test/puppeteer/Herebyfile.mjs @@ -6,20 +6,20 @@ /* eslint-disable import/order */ -import {copyFile, readFile, writeFile} from 'fs/promises'; +import {readFile, writeFile} from 'fs/promises'; import {docgen, spliceIntoSection} from '@puppeteer/docgen'; import {execa} from 'execa'; import {task} from 'hereby'; import semver from 'semver'; -export const docsNgSchematicsTask = task({ - name: 'docs:ng-schematics', - run: async () => { - const readme = await readFile('packages/ng-schematics/README.md', 'utf-8'); - await writeFile('docs/integrations/ng-schematics.md', readme); - }, -}); +function addNoTocHeader(markdown) { + return `--- +hide_table_of_contents: true +--- + +${markdown}`; +} /** * This logic should match the one in `website/docusaurus.config.js`. @@ -34,10 +34,18 @@ function getApiUrl(version) { } } +export const docsNgSchematicsTask = task({ + name: 'docs:ng-schematics', + run: async () => { + const readme = await readFile('packages/ng-schematics/README.md', 'utf-8'); + await writeFile('docs/guides/ng-schematics.md', readme); + }, +}); + export const docsChromiumSupportTask = task({ - name: 'docs:chromium-support', + name: 'docs:supported-browsers', run: async () => { - const content = await readFile('docs/chromium-support.md', { + const content = await readFile('docs/supported-browsers.md', { encoding: 'utf8', }); const {versionsPerRelease} = await import('./versions.js'); @@ -61,7 +69,7 @@ export const docsChromiumSupportTask = task({ } } await writeFile( - 'docs/chromium-support.md', + 'docs/supported-browsers.md', spliceIntoSection('version', content, buffer.join('\n')) ); }, @@ -72,7 +80,8 @@ export const docsTask = task({ dependencies: [docsNgSchematicsTask, docsChromiumSupportTask], run: async () => { // Copy main page. - await copyFile('README.md', 'docs/index.md'); + const mainPage = await readFile('README.md', 'utf-8'); + await writeFile('docs/index.md', addNoTocHeader(mainPage)); // Generate documentation for (const [name, folder] of [ diff --git a/remote/test/puppeteer/README.md b/remote/test/puppeteer/README.md index 288a5b623c..6078b23c2c 100644 --- a/remote/test/puppeteer/README.md +++ b/remote/test/puppeteer/README.md @@ -1,145 +1,21 @@ # Puppeteer -[![Build status](https://github.com/puppeteer/puppeteer/workflows/CI/badge.svg)](https://github.com/puppeteer/puppeteer/actions?query=workflow%3ACI) +[![build](https://github.com/puppeteer/puppeteer/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/puppeteer/puppeteer/actions/workflows/ci.yml) [![npm puppeteer package](https://img.shields.io/npm/v/puppeteer.svg)](https://npmjs.org/package/puppeteer) <img src="https://user-images.githubusercontent.com/10379601/29446482-04f7036a-841f-11e7-9872-91d1fc2ea683.png" height="200" align="right"/> -#### [Guides](https://pptr.dev/category/guides) | [API](https://pptr.dev/api) | [FAQ](https://pptr.dev/faq) | [Contributing](https://pptr.dev/contributing) | [Troubleshooting](https://pptr.dev/troubleshooting) - > Puppeteer is a Node.js library which provides a high-level API to control > Chrome/Chromium over the > [DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/). > Puppeteer runs in -> [headless](https://developer.chrome.com/articles/new-headless/) +> [headless](https://developer.chrome.com/docs/chromium/new-headless/) > mode by default, but can be configured to run in full ("headful") > Chrome/Chromium. -#### What can I do? - -Most things that you can do manually in the browser can be done using Puppeteer! -Here are a few examples to get you started: - -- Generate screenshots and PDFs of pages. -- Crawl a SPA (Single-Page Application) and generate pre-rendered content (i.e. - "SSR" (Server-Side Rendering)). -- Automate form submission, UI testing, keyboard input, etc. -- Create an automated testing environment using the latest JavaScript and - browser features. -- Capture a - [timeline trace](https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/reference) - of your site to help diagnose performance issues. -- [Test Chrome Extensions](https://pptr.dev/guides/chrome-extensions). - -## Getting Started - -### Installation - -To use Puppeteer in your project, run: - -```bash -npm i puppeteer -# or using yarn -yarn add puppeteer -# or using pnpm -pnpm i puppeteer -``` - -When you install Puppeteer, it automatically downloads a recent version of -[Chrome for Testing](https://developer.chrome.com/blog/chrome-for-testing/) (~170MB macOS, ~282MB Linux, ~280MB Windows) and a `chrome-headless-shell` binary (starting with Puppeteer v21.6.0) that is [guaranteed to -work](https://pptr.dev/faq#q-why-doesnt-puppeteer-vxxx-work-with-chromium-vyyy) -with Puppeteer. The browser is downloaded to the `$HOME/.cache/puppeteer` folder -by default (starting with Puppeteer v19.0.0). See [configuration](https://pptr.dev/api/puppeteer.configuration) for configuration options and environmental variables to control the download behavor. - -If you deploy a project using Puppeteer to a hosting provider, such as Render or -Heroku, you might need to reconfigure the location of the cache to be within -your project folder (see an example below) because not all hosting providers -include `$HOME/.cache` into the project's deployment. - -For a version of Puppeteer without the browser installation, see -[`puppeteer-core`](#puppeteer-core). - -If used with TypeScript, the minimum supported TypeScript version is `4.7.4`. - -#### Configuration - -Puppeteer uses several defaults that can be customized through configuration -files. - -For example, to change the default cache directory Puppeteer uses to install -browsers, you can add a `.puppeteerrc.cjs` (or `puppeteer.config.cjs`) at the -root of your application with the contents - -```js -const {join} = require('path'); - -/** - * @type {import("puppeteer").Configuration} - */ -module.exports = { - // Changes the cache location for Puppeteer. - cacheDirectory: join(__dirname, '.cache', 'puppeteer'), -}; -``` - -After adding the configuration file, you will need to remove and reinstall -`puppeteer` for it to take effect. - -See the [configuration guide](https://pptr.dev/guides/configuration) for more -information. - -#### `puppeteer-core` - -For every release since v1.7.0 we publish two packages: - -- [`puppeteer`](https://www.npmjs.com/package/puppeteer) -- [`puppeteer-core`](https://www.npmjs.com/package/puppeteer-core) - -`puppeteer` is a _product_ for browser automation. When installed, it downloads -a version of Chrome, which it then drives using `puppeteer-core`. Being an -end-user product, `puppeteer` automates several workflows using reasonable -defaults [that can be customized](https://pptr.dev/guides/configuration). - -`puppeteer-core` is a _library_ to help drive anything that supports DevTools -protocol. Being a library, `puppeteer-core` is fully driven through its -programmatic interface implying no defaults are assumed and `puppeteer-core` -will not download Chrome when installed. - -You should use `puppeteer-core` if you are -[connecting to a remote browser](https://pptr.dev/api/puppeteer.puppeteer.connect) -or [managing browsers yourself](https://pptr.dev/browsers-api/). -If you are managing browsers yourself, you will need to call -[`puppeteer.launch`](https://pptr.dev/api/puppeteer.puppeteernode.launch) with -an explicit -[`executablePath`](https://pptr.dev/api/puppeteer.launchoptions) -(or [`channel`](https://pptr.dev/api/puppeteer.launchoptions) if it's -installed in a standard location). - -When using `puppeteer-core`, remember to change the import: - -```ts -import puppeteer from 'puppeteer-core'; -``` - -### Usage - -Puppeteer follows the latest -[maintenance LTS](https://github.com/nodejs/Release#release-schedule) version of -Node. - -Puppeteer will be familiar to people using other browser testing frameworks. You -[launch](https://pptr.dev/api/puppeteer.puppeteernode.launch)/[connect](https://pptr.dev/api/puppeteer.puppeteernode.connect) -a [browser](https://pptr.dev/api/puppeteer.browser), -[create](https://pptr.dev/api/puppeteer.browser.newpage) some -[pages](https://pptr.dev/api/puppeteer.page), and then manipulate them with -[Puppeteer's API](https://pptr.dev/api). - -For more in-depth usage, check our [guides](https://pptr.dev/category/guides) -and [examples](https://github.com/puppeteer/puppeteer/tree/main/examples). +## [Get started](https://pptr.dev/docs) | [API](https://pptr.dev/api) | [FAQ](https://pptr.dev/faq) | [Contributing](https://pptr.dev/contributing) | [Troubleshooting](https://pptr.dev/troubleshooting) -#### Example - -The following example searches [developer.chrome.com](https://developer.chrome.com/) for blog posts with text "automate beyond recorder", click on the first result and print the full title of the blog post. +## Example ```ts import puppeteer from 'puppeteer'; @@ -175,87 +51,3 @@ import puppeteer from 'puppeteer'; await browser.close(); })(); ``` - -### Default runtime settings - -**1. Uses Headless mode** - -By default Puppeteer launches Chrome in -[the Headless mode](https://developer.chrome.com/articles/new-headless/). - -```ts -const browser = await puppeteer.launch(); -// Equivalent to -const browser = await puppeteer.launch({headless: true}); -``` - -Before v22, Puppeteer launched the [old Headless mode](https://developer.chrome.com/articles/new-headless/) by default. -The old headless mode is now known as -[`chrome-headless-shell`](https://developer.chrome.com/blog/chrome-headless-shell) -and ships as a separate binary. `chrome-headless-shell` does not match the -behavior of the regular Chrome completely but it is currently more performant -for automation tasks where the complete Chrome feature set is not needed. If the performance -is more important for your use case, switch to `chrome-headless-shell` as following: - -```ts -const browser = await puppeteer.launch({headless: 'shell'}); -``` - -To launch a "headful" version of Chrome, set the -[`headless`](https://pptr.dev/api/puppeteer.browserlaunchargumentoptions) to `false` -option when launching a browser: - -```ts -const browser = await puppeteer.launch({headless: false}); -``` - -**2. Runs a bundled version of Chrome** - -By default, Puppeteer downloads and uses a specific version of Chrome so its -API is guaranteed to work out of the box. To use Puppeteer with a different -version of Chrome or Chromium, pass in the executable's path when creating a -`Browser` instance: - -```ts -const browser = await puppeteer.launch({executablePath: '/path/to/Chrome'}); -``` - -You can also use Puppeteer with Firefox. See -[status of cross-browser support](https://pptr.dev/faq/#q-what-is-the-status-of-cross-browser-support) for -more information. - -See -[`this article`](https://www.howtogeek.com/202825/what%E2%80%99s-the-difference-between-chromium-and-chrome/) -for a description of the differences between Chromium and Chrome. -[`This article`](https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/chromium_browser_vs_google_chrome.md) -describes some differences for Linux users. - -**3. Creates a fresh user profile** - -Puppeteer creates its own browser user profile which it **cleans up on every -run**. - -#### Using Docker - -See our [Docker guide](https://pptr.dev/guides/docker). - -#### Using Chrome Extensions - -See our [Chrome extensions guide](https://pptr.dev/guides/chrome-extensions). - -## Resources - -- [API Documentation](https://pptr.dev/api) -- [Guides](https://pptr.dev/category/guides) -- [Examples](https://github.com/puppeteer/puppeteer/tree/main/examples) -- [Community list of Puppeteer resources](https://github.com/transitive-bullshit/awesome-puppeteer) - -## Contributing - -Check out our [contributing guide](https://pptr.dev/contributing) to get an -overview of Puppeteer development. - -## FAQ - -Our [FAQ](https://pptr.dev/faq) has migrated to -[our site](https://pptr.dev/faq). diff --git a/remote/test/puppeteer/examples/README.md b/remote/test/puppeteer/examples/README.md index 92e83eb39c..b87324b7e0 100644 --- a/remote/test/puppeteer/examples/README.md +++ b/remote/test/puppeteer/examples/README.md @@ -1,6 +1,18 @@ # Running the examples -Assuming you have a checkout of the Puppeteer repo and have run `npm i` (or `yarn`) to install the dependencies, and `npm run build` (or `yarn run build`) to build the project, the examples can be run from the root folder like so: +Assuming you have a checkout of the Puppeteer repo and install the dependencies: + +```bash npm2yarn +npm install +``` + +Build the project: + +```bash npm2yarn +npm run build +``` + +The examples can be run from the root folder like so: ```bash NODE_PATH=../ node examples/search.js diff --git a/remote/test/puppeteer/moz.yaml b/remote/test/puppeteer/moz.yaml index 35363b67dc..9c6b0e80c3 100644 --- a/remote/test/puppeteer/moz.yaml +++ b/remote/test/puppeteer/moz.yaml @@ -5,6 +5,6 @@ origin: description: Headless Chrome Node API license: Apache-2.0 name: puppeteer - release: puppeteer-v22.4.0 - url: ../puppeteer + release: puppeteer-v22.6.5 + url: /Users/juliandescottes/Development/git/puppeteer schema: 1 diff --git a/remote/test/puppeteer/package-lock.json b/remote/test/puppeteer/package-lock.json index b1fb3b0189..d99df978d1 100644 --- a/remote/test/puppeteer/package-lock.json +++ b/remote/test/puppeteer/package-lock.json @@ -21,32 +21,32 @@ "@types/node": "20.8.4", "@types/semver": "7.5.8", "@types/sinon": "17.0.3", - "@typescript-eslint/eslint-plugin": "7.1.0", - "@typescript-eslint/parser": "7.1.0", - "esbuild": "0.20.1", + "@typescript-eslint/eslint-plugin": "7.6.0", + "@typescript-eslint/parser": "7.6.0", + "esbuild": "0.20.2", "eslint": "8.57.0", "eslint-config-prettier": "9.1.0", "eslint-import-resolver-typescript": "3.6.1", "eslint-plugin-import": "2.29.1", - "eslint-plugin-mocha": "10.3.0", + "eslint-plugin-mocha": "10.4.2", "eslint-plugin-prettier": "5.1.3", "eslint-plugin-rulesdir": "0.2.2", "eslint-plugin-tsdoc": "0.2.17", "eslint-plugin-unused-imports": "3.1.0", "execa": "8.0.1", "expect": "29.7.0", - "gts": "5.2.0", + "gts": "5.3.0", "hereby": "1.8.9", "license-checker": "25.0.1", - "mocha": "10.3.0", + "mocha": "10.4.0", "npm-run-all2": "6.1.2", "prettier": "3.2.5", "semver": "7.6.0", "sinon": "17.0.1", "source-map-support": "0.5.21", "spdx-satisfies": "5.0.1", - "tsd": "0.30.7", - "tsx": "4.7.1", + "tsd": "0.31.0", + "tsx": "4.7.2", "typescript": "5.3.3", "wireit": "0.14.4" } @@ -80,6 +80,18 @@ "undici": "^5.25.4" } }, + "node_modules/@actions/http-client/node_modules/undici": { + "version": "5.28.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", + "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", + "dev": true, + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", @@ -544,16 +556,16 @@ } }, "node_modules/@microsoft/api-documenter": { - "version": "7.23.35", - "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.23.35.tgz", - "integrity": "sha512-zSHTX0abumOsfA3GsWJADFtiqxgwcoSCGSO+84e4s/SWotAqlUwXMYVJk6/huJFKSL+LG46gvcn7r8bsOd3K2Q==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.24.2.tgz", + "integrity": "sha512-q03DXLBj7nzAzLyLRAVklBynqKgSFI/JBmrhF/mEEIpg8orNo4qKXWO1RSkD2IYrqvZV63b13mcUPYgcFdifQA==", "dev": true, "dependencies": { - "@microsoft/api-extractor-model": "7.28.13", + "@microsoft/api-extractor-model": "7.28.14", "@microsoft/tsdoc": "0.14.2", - "@rushstack/node-core-library": "4.0.2", - "@rushstack/terminal": "0.10.0", - "@rushstack/ts-command-line": "4.19.0", + "@rushstack/node-core-library": "4.1.0", + "@rushstack/terminal": "0.10.1", + "@rushstack/ts-command-line": "4.19.2", "js-yaml": "~3.13.1", "resolve": "~1.22.1" }, @@ -562,12 +574,12 @@ } }, "node_modules/@microsoft/api-documenter/node_modules/@rushstack/terminal": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.10.0.tgz", - "integrity": "sha512-UbELbXnUdc7EKwfH2sb8ChqNgapUOdqcCIdQP4NGxBpTZV2sQyeekuK3zmfQSa/MN+/7b4kBogl2wq0vpkpYGw==", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.10.1.tgz", + "integrity": "sha512-C6Vi/m/84IYJTkfzmXr1+W8Wi3MmBjVF/q3za91Gb3VYjKbpALHVxY6FgH625AnDe5Z0Kh4MHKWA3Z7bqgAezA==", "dev": true, "dependencies": { - "@rushstack/node-core-library": "4.0.2", + "@rushstack/node-core-library": "4.1.0", "supports-color": "~8.1.1" }, "peerDependencies": { @@ -595,12 +607,12 @@ } }, "node_modules/@microsoft/api-documenter/node_modules/@rushstack/ts-command-line": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.19.0.tgz", - "integrity": "sha512-0sIHWOFGLFb6tC1zk2R0aM79ic3CF0XGzVBvhf6ytMyjDwt03DVb1qe5/5NQ0FGcvB5YyQ2WVfGsnxG6SANvHA==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.19.2.tgz", + "integrity": "sha512-cqmXXmBEBlzo9WtyUrHtF9e6kl0LvBY7aTSVX4jfnBfXWZQWnPq9JTFPlQZ+L/ZwjZ4HrNwQsOVvhe9oOucZkw==", "dev": true, "dependencies": { - "@rushstack/terminal": "0.10.0", + "@rushstack/terminal": "0.10.1", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" @@ -635,47 +647,47 @@ "dev": true }, "node_modules/@microsoft/api-extractor": { - "version": "7.42.2", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.42.2.tgz", - "integrity": "sha512-HYiOQDO4WR+Pj4XQZZE5qK5R6e3MF6Ut5s+Hi2IkeI6MiCXkdmRugQH6ppc9YzTUiydRqZ+jshZD7UWNGSA8bg==", + "version": "7.43.1", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.43.1.tgz", + "integrity": "sha512-ohg40SsvFFgzHFAtYq5wKJc8ZDyY46bphjtnSvhSSlXpPTG7GHwyyXkn48UZiUCBwr2WC7TRC1Jfwz7nreuiyQ==", "dev": true, "dependencies": { - "@microsoft/api-extractor-model": "7.28.13", + "@microsoft/api-extractor-model": "7.28.14", "@microsoft/tsdoc": "0.14.2", "@microsoft/tsdoc-config": "~0.16.1", - "@rushstack/node-core-library": "4.0.2", + "@rushstack/node-core-library": "4.1.0", "@rushstack/rig-package": "0.5.2", - "@rushstack/terminal": "0.10.0", - "@rushstack/ts-command-line": "4.19.0", + "@rushstack/terminal": "0.10.1", + "@rushstack/ts-command-line": "4.19.2", "lodash": "~4.17.15", "minimatch": "~3.0.3", "resolve": "~1.22.1", "semver": "~7.5.4", "source-map": "~0.6.1", - "typescript": "5.3.3" + "typescript": "5.4.2" }, "bin": { "api-extractor": "bin/api-extractor" } }, "node_modules/@microsoft/api-extractor-model": { - "version": "7.28.13", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.28.13.tgz", - "integrity": "sha512-39v/JyldX4MS9uzHcdfmjjfS6cYGAoXV+io8B5a338pkHiSt+gy2eXQ0Q7cGFJ7quSa1VqqlMdlPrB6sLR/cAw==", + "version": "7.28.14", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.28.14.tgz", + "integrity": "sha512-Bery/c8A8SsKPSvA82cTTuy/+OcxZbLRmKhPkk91/AJOQzxZsShcrmHFAGeiEqSIrv1nPZ3tKq9kfMLdCHmsqg==", "dev": true, "dependencies": { "@microsoft/tsdoc": "0.14.2", "@microsoft/tsdoc-config": "~0.16.1", - "@rushstack/node-core-library": "4.0.2" + "@rushstack/node-core-library": "4.1.0" } }, "node_modules/@microsoft/api-extractor/node_modules/@rushstack/terminal": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.10.0.tgz", - "integrity": "sha512-UbELbXnUdc7EKwfH2sb8ChqNgapUOdqcCIdQP4NGxBpTZV2sQyeekuK3zmfQSa/MN+/7b4kBogl2wq0vpkpYGw==", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.10.1.tgz", + "integrity": "sha512-C6Vi/m/84IYJTkfzmXr1+W8Wi3MmBjVF/q3za91Gb3VYjKbpALHVxY6FgH625AnDe5Z0Kh4MHKWA3Z7bqgAezA==", "dev": true, "dependencies": { - "@rushstack/node-core-library": "4.0.2", + "@rushstack/node-core-library": "4.1.0", "supports-color": "~8.1.1" }, "peerDependencies": { @@ -703,12 +715,12 @@ } }, "node_modules/@microsoft/api-extractor/node_modules/@rushstack/ts-command-line": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.19.0.tgz", - "integrity": "sha512-0sIHWOFGLFb6tC1zk2R0aM79ic3CF0XGzVBvhf6ytMyjDwt03DVb1qe5/5NQ0FGcvB5YyQ2WVfGsnxG6SANvHA==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.19.2.tgz", + "integrity": "sha512-cqmXXmBEBlzo9WtyUrHtF9e6kl0LvBY7aTSVX4jfnBfXWZQWnPq9JTFPlQZ+L/ZwjZ4HrNwQsOVvhe9oOucZkw==", "dev": true, "dependencies": { - "@rushstack/terminal": "0.10.0", + "@rushstack/terminal": "0.10.1", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" @@ -905,9 +917,9 @@ "link": true }, "node_modules/@rushstack/node-core-library": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-4.0.2.tgz", - "integrity": "sha512-hyES82QVpkfQMeBMteQUnrhASL/KHPhd7iJ8euduwNJG4mu2GSOKybf0rOEjOm1Wz7CwJEUm9y0yD7jg2C1bfg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-4.1.0.tgz", + "integrity": "sha512-qz4JFBZJCf1YN5cAXa1dP6Mki/HrsQxc/oYGAGx29dF2cwF2YMxHoly0FBhMw3IEnxo5fMj0boVfoHVBkpkx/w==", "dev": true, "dependencies": { "fs-extra": "~7.0.1", @@ -1002,9 +1014,9 @@ "dev": true }, "node_modules/@swc/core": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.4.2.tgz", - "integrity": "sha512-vWgY07R/eqj1/a0vsRKLI9o9klGZfpLNOVEnrv4nrccxBgYPjcf22IWwAoaBJ+wpA7Q4fVjCUM8lP0m01dpxcg==", + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.4.13.tgz", + "integrity": "sha512-rOtusBE+2gaeRkAJn5E4zp5yzZekZOypzSOz5ZG6P1hFbd+Cc26fWEdK6sUSnrkkvTd0Oj33KXLB/4UkbK/UHA==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -1019,16 +1031,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.4.2", - "@swc/core-darwin-x64": "1.4.2", - "@swc/core-linux-arm-gnueabihf": "1.4.2", - "@swc/core-linux-arm64-gnu": "1.4.2", - "@swc/core-linux-arm64-musl": "1.4.2", - "@swc/core-linux-x64-gnu": "1.4.2", - "@swc/core-linux-x64-musl": "1.4.2", - "@swc/core-win32-arm64-msvc": "1.4.2", - "@swc/core-win32-ia32-msvc": "1.4.2", - "@swc/core-win32-x64-msvc": "1.4.2" + "@swc/core-darwin-arm64": "1.4.13", + "@swc/core-darwin-x64": "1.4.13", + "@swc/core-linux-arm-gnueabihf": "1.4.13", + "@swc/core-linux-arm64-gnu": "1.4.13", + "@swc/core-linux-arm64-musl": "1.4.13", + "@swc/core-linux-x64-gnu": "1.4.13", + "@swc/core-linux-x64-musl": "1.4.13", + "@swc/core-win32-arm64-msvc": "1.4.13", + "@swc/core-win32-ia32-msvc": "1.4.13", + "@swc/core-win32-x64-msvc": "1.4.13" }, "peerDependencies": { "@swc/helpers": "^0.5.0" @@ -1040,9 +1052,9 @@ } }, "node_modules/@swc/core/node_modules/@swc/core-darwin-arm64": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.4.2.tgz", - "integrity": "sha512-1uSdAn1MRK5C1m/TvLZ2RDvr0zLvochgrZ2xL+lRzugLlCTlSA+Q4TWtrZaOz+vnnFVliCpw7c7qu0JouhgQIw==", + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.4.13.tgz", + "integrity": "sha512-36P72FLpm5iq85IvoEjBvi22DiqkkEIanJ1M0E8bkxcFHUbjBrYfPY9T6cpPyK5oQqkaTBvNAc3j1BlVD6IH6w==", "cpu": [ "arm64" ], @@ -1056,9 +1068,9 @@ } }, "node_modules/@swc/core/node_modules/@swc/core-darwin-x64": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.4.2.tgz", - "integrity": "sha512-TYD28+dCQKeuxxcy7gLJUCFLqrwDZnHtC2z7cdeGfZpbI2mbfppfTf2wUPzqZk3gEC96zHd4Yr37V3Tvzar+lQ==", + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.4.13.tgz", + "integrity": "sha512-ye7OgKpDdyA8AMIVVdmD1ICDaFXgoEXORnVO8bBHyul0WN71yUBZMX+YxEx2lpWtiftA2vY/1MAuOR80vHkBCw==", "cpu": [ "x64" ], @@ -1072,9 +1084,9 @@ } }, "node_modules/@swc/core/node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.4.2.tgz", - "integrity": "sha512-Eyqipf7ZPGj0vplKHo8JUOoU1un2sg5PjJMpEesX0k+6HKE2T8pdyeyXODN0YTFqzndSa/J43EEPXm+rHAsLFQ==", + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.4.13.tgz", + "integrity": "sha512-+x593Jlmu4c3lJtZUKRejWpV2MAij1Js5nmQLLdjo6ChR2D4B2rzj3iMiKn5gITew7fraF9t3fvXALdWh7HmUg==", "cpu": [ "arm" ], @@ -1088,9 +1100,9 @@ } }, "node_modules/@swc/core/node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.4.2.tgz", - "integrity": "sha512-wZn02DH8VYPv3FC0ub4my52Rttsus/rFw+UUfzdb3tHMHXB66LqN+rR0ssIOZrH6K+VLN6qpTw9VizjyoH0BxA==", + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.4.13.tgz", + "integrity": "sha512-0x8OVw4dfyNerrs/9eZX9wNnmvwbwXSMCi+LbE6Xt1pXOIwvoLtFIXcV3NsrlkFboO3sr5UAQIwDxKqbIZA9pQ==", "cpu": [ "arm64" ], @@ -1104,9 +1116,9 @@ } }, "node_modules/@swc/core/node_modules/@swc/core-linux-arm64-musl": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.4.2.tgz", - "integrity": "sha512-3G0D5z9hUj9bXNcwmA1eGiFTwe5rWkuL3DsoviTj73TKLpk7u64ND0XjEfO0huVv4vVu9H1jodrKb7nvln/dlw==", + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.4.13.tgz", + "integrity": "sha512-Z9c4JiequtZvngPcxbCuAOkmWBxi2vInZbjjhD5I+Q9oiJdXUz1t2USGwsGPS41Xvk1BOA3ecK2Sn1ilY3titg==", "cpu": [ "arm64" ], @@ -1120,9 +1132,9 @@ } }, "node_modules/@swc/core/node_modules/@swc/core-linux-x64-gnu": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.4.2.tgz", - "integrity": "sha512-LFxn9U8cjmYHw3jrdPNqPAkBGglKE3tCZ8rA7hYyp0BFxuo7L2ZcEnPm4RFpmSCCsExFH+LEJWuMGgWERoktvg==", + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.4.13.tgz", + "integrity": "sha512-ChatHtk+vX0Ke5QG+jO+rIapw/KwZsi9MedCBHFXHH6iWF4z8d51cJeN68ykcn+vAXzjNeFNdlNy5Vbkd1zAqg==", "cpu": [ "x64" ], @@ -1136,9 +1148,9 @@ } }, "node_modules/@swc/core/node_modules/@swc/core-linux-x64-musl": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.4.2.tgz", - "integrity": "sha512-dp0fAmreeVVYTUcb4u9njTPrYzKnbIH0EhH2qvC9GOYNNREUu2GezSIDgonjOXkHiTCvopG4xU7y56XtXj4VrQ==", + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.4.13.tgz", + "integrity": "sha512-0Pz39YR530mXpsztwQkmEKdkkZy4fY4Smdh4pkm6Ly8Nndyo0te/l4bcAGqN24Jp7aVwF/QSy14SAtw4HRjU9g==", "cpu": [ "x64" ], @@ -1152,9 +1164,9 @@ } }, "node_modules/@swc/core/node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.4.2.tgz", - "integrity": "sha512-HlVIiLMQkzthAdqMslQhDkoXJ5+AOLUSTV6fm6shFKZKqc/9cJvr4S8UveNERL9zUficA36yM3bbfo36McwnvQ==", + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.4.13.tgz", + "integrity": "sha512-LVZfhlD+jHcAbz5NN+gAJ1BEasB0WpcvUzcsJt0nQSRsojgzPzFjJ+fzEBnvT7SMtqKkrnVJ0OmDYeh88bDRpw==", "cpu": [ "arm64" ], @@ -1168,9 +1180,9 @@ } }, "node_modules/@swc/core/node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.4.2.tgz", - "integrity": "sha512-WCF8faPGjCl4oIgugkp+kL9nl3nUATlzKXCEGFowMEmVVCFM0GsqlmGdPp1pjZoWc9tpYanoXQDnp5IvlDSLhA==", + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.4.13.tgz", + "integrity": "sha512-78hxHWUvUZtWsnhcf8DKwhBcNFJw+j4y4fN2B9ioXmBWX2tIyw+BqUHOrismOtjPihaZmwe/Ok2e4qmkawE2fw==", "cpu": [ "ia32" ], @@ -1184,9 +1196,9 @@ } }, "node_modules/@swc/core/node_modules/@swc/core-win32-x64-msvc": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.4.2.tgz", - "integrity": "sha512-oV71rwiSpA5xre2C5570BhCsg1HF97SNLsZ/12xv7zayGzqr3yvFALFJN8tHKpqUdCB4FGPjoP3JFdV3i+1wUw==", + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.4.13.tgz", + "integrity": "sha512-WSfy1u2Xde6jU7UpHIInCUMW98Zw9iZglddKUAvmr1obkZji5U6EX0Oca3asEJdZPFb+2lMLjt0Mh5a1YisROg==", "cpu": [ "x64" ], @@ -1216,15 +1228,6 @@ "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==" }, - "node_modules/@tsd/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/@tsd/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-CQlfzol0ldaU+ftWuG52vH29uRoKboLinLy84wS8TQOu+m+tWoaUfk4svL4ij2V8M5284KymJBlHUusKj6k34w==", - "dev": true, - "engines": { - "node": ">=14.17" - } - }, "node_modules/@types/argparse": { "version": "1.0.38", "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz", @@ -1479,25 +1482,25 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.1.0.tgz", - "integrity": "sha512-j6vT/kCulhG5wBmGtstKeiVr1rdXE4nk+DT1k6trYkwlrvW9eOF5ZbgKnd/YR6PcM4uTEXa0h6Fcvf6X7Dxl0w==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.6.0.tgz", + "integrity": "sha512-gKmTNwZnblUdnTIJu3e9kmeRRzV2j1a/LUO27KNNAnIC5zjy1aSvXSRp4rVNlmAoHlQ7HzX42NbKpcSr4jF80A==", "dev": true, "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "7.1.0", - "@typescript-eslint/type-utils": "7.1.0", - "@typescript-eslint/utils": "7.1.0", - "@typescript-eslint/visitor-keys": "7.1.0", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.6.0", + "@typescript-eslint/type-utils": "7.6.0", + "@typescript-eslint/utils": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0", "debug": "^4.3.4", "graphemer": "^1.4.0", - "ignore": "^5.2.4", + "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1514,16 +1517,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.1.0.tgz", - "integrity": "sha512-6TmN4OJiohHfoOdGZ3huuLhpiUgOGTpgXNUPJgeZOZR3DnIpdSgtt83RS35OYNNXxM4TScVlpVKC9jyQSETR1A==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.6.0.tgz", + "integrity": "sha512-ngttyfExA5PsHSx0rdFgnADMYQi+Zkeiv4/ZxGYUWd0nLs63Ha0ksmp8VMxAIC0wtCFxMos7Lt3PszJssG/E6w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.0", - "@typescript-eslint/visitor-keys": "7.1.0" + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1531,12 +1534,12 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager/node_modules/@typescript-eslint/types": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.1.0.tgz", - "integrity": "sha512-qTWjWieJ1tRJkxgZYXx6WUYtWlBc48YRxgY2JN1aGeVpkhmnopq+SUC8UEVGNXIvWH7XyuTjwALfG6bFEgCkQA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.6.0.tgz", + "integrity": "sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==", "dev": true, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1544,18 +1547,18 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.1.0.tgz", - "integrity": "sha512-UZIhv8G+5b5skkcuhgvxYWHjk7FW7/JP5lPASMEUoliAPwIH/rxoUSQPia2cuOj9AmDZmwUl1usKm85t5VUMew==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.6.0.tgz", + "integrity": "sha512-NxAfqAPNLG6LTmy7uZgpK8KcuiS2NZD/HlThPXQRGwz6u7MDBWRVliEEl1Gj6U7++kVJTpehkhZzCJLMK66Scw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.1.0", - "@typescript-eslint/utils": "7.1.0", + "@typescript-eslint/typescript-estree": "7.6.0", + "@typescript-eslint/utils": "7.6.0", "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1571,22 +1574,22 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.1.0.tgz", - "integrity": "sha512-k7MyrbD6E463CBbSpcOnwa8oXRdHzH1WiVzOipK3L5KSML92ZKgUBrTlehdi7PEIMT8k0bQixHUGXggPAlKnOQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.6.0.tgz", + "integrity": "sha512-+7Y/GP9VuYibecrCQWSKgl3GvUM5cILRttpWtnAu8GNL9j11e4tbuGZmZjJ8ejnKYyBRb2ddGQ3rEFCq3QjMJw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.0", - "@typescript-eslint/visitor-keys": "7.1.0", + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1599,29 +1602,44 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/types": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.1.0.tgz", - "integrity": "sha512-qTWjWieJ1tRJkxgZYXx6WUYtWlBc48YRxgY2JN1aGeVpkhmnopq+SUC8UEVGNXIvWH7XyuTjwALfG6bFEgCkQA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.6.0.tgz", + "integrity": "sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==", "dev": true, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.1.0.tgz", - "integrity": "sha512-FhUqNWluiGNzlvnDZiXad4mZRhtghdoKW6e98GoEOYSu5cND+E39rG5KwJMUzeENwm1ztYBRqof8wMLP+wNPIA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.6.0.tgz", + "integrity": "sha512-4eLB7t+LlNUmXzfOu1VAIAdkjbu5xNSerURS9X/S5TUKWFRpXRQZbmtPqgKmYx8bj3J0irtQXSiWAOY82v+cgw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "7.6.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1629,32 +1647,44 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys/node_modules/@typescript-eslint/types": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.1.0.tgz", - "integrity": "sha512-qTWjWieJ1tRJkxgZYXx6WUYtWlBc48YRxgY2JN1aGeVpkhmnopq+SUC8UEVGNXIvWH7XyuTjwALfG6bFEgCkQA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.6.0.tgz", + "integrity": "sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==", "dev": true, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/@typescript-eslint/parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.1.0.tgz", - "integrity": "sha512-V1EknKUubZ1gWFjiOZhDSNToOjs63/9O0puCgGS8aDOgpZY326fzFu15QAUjwaXzRZjf/qdsdBrckYdv9YxB8w==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.6.0.tgz", + "integrity": "sha512-usPMPHcwX3ZoPWnBnhhorc14NJw9J4HpSXQX4urF2TPKG0au0XhJoZyX62fmvdHONUkmyUe74Hzm1//XA+BoYg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.1.0", - "@typescript-eslint/types": "7.1.0", - "@typescript-eslint/typescript-estree": "7.1.0", - "@typescript-eslint/visitor-keys": "7.1.0", + "@typescript-eslint/scope-manager": "7.6.0", + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/typescript-estree": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0", "debug": "^4.3.4" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1670,16 +1700,16 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.1.0.tgz", - "integrity": "sha512-6TmN4OJiohHfoOdGZ3huuLhpiUgOGTpgXNUPJgeZOZR3DnIpdSgtt83RS35OYNNXxM4TScVlpVKC9jyQSETR1A==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.6.0.tgz", + "integrity": "sha512-ngttyfExA5PsHSx0rdFgnADMYQi+Zkeiv4/ZxGYUWd0nLs63Ha0ksmp8VMxAIC0wtCFxMos7Lt3PszJssG/E6w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.0", - "@typescript-eslint/visitor-keys": "7.1.0" + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1687,12 +1717,12 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.1.0.tgz", - "integrity": "sha512-qTWjWieJ1tRJkxgZYXx6WUYtWlBc48YRxgY2JN1aGeVpkhmnopq+SUC8UEVGNXIvWH7XyuTjwALfG6bFEgCkQA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.6.0.tgz", + "integrity": "sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==", "dev": true, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1700,22 +1730,22 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.1.0.tgz", - "integrity": "sha512-k7MyrbD6E463CBbSpcOnwa8oXRdHzH1WiVzOipK3L5KSML92ZKgUBrTlehdi7PEIMT8k0bQixHUGXggPAlKnOQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.6.0.tgz", + "integrity": "sha512-+7Y/GP9VuYibecrCQWSKgl3GvUM5cILRttpWtnAu8GNL9j11e4tbuGZmZjJ8ejnKYyBRb2ddGQ3rEFCq3QjMJw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.0", - "@typescript-eslint/visitor-keys": "7.1.0", + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1727,17 +1757,44 @@ } } }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree/node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.1.0.tgz", - "integrity": "sha512-FhUqNWluiGNzlvnDZiXad4mZRhtghdoKW6e98GoEOYSu5cND+E39rG5KwJMUzeENwm1ztYBRqof8wMLP+wNPIA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.6.0.tgz", + "integrity": "sha512-4eLB7t+LlNUmXzfOu1VAIAdkjbu5xNSerURS9X/S5TUKWFRpXRQZbmtPqgKmYx8bj3J0irtQXSiWAOY82v+cgw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "7.6.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1745,21 +1802,21 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.1.0.tgz", - "integrity": "sha512-WUFba6PZC5OCGEmbweGpnNJytJiLG7ZvDBJJoUcX4qZYf1mGZ97mO2Mps6O2efxJcJdRNpqweCistDbZMwIVHw==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.6.0.tgz", + "integrity": "sha512-x54gaSsRRI+Nwz59TXpCsr6harB98qjXYzsRxGqvA5Ue3kQH+FxS7FYU81g/omn22ML2pZJkisy6Q+ElK8pBCA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "7.1.0", - "@typescript-eslint/types": "7.1.0", - "@typescript-eslint/typescript-estree": "7.1.0", - "semver": "^7.5.4" + "@types/json-schema": "^7.0.15", + "@types/semver": "^7.5.8", + "@typescript-eslint/scope-manager": "7.6.0", + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/typescript-estree": "7.6.0", + "semver": "^7.6.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1770,16 +1827,16 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.1.0.tgz", - "integrity": "sha512-6TmN4OJiohHfoOdGZ3huuLhpiUgOGTpgXNUPJgeZOZR3DnIpdSgtt83RS35OYNNXxM4TScVlpVKC9jyQSETR1A==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.6.0.tgz", + "integrity": "sha512-ngttyfExA5PsHSx0rdFgnADMYQi+Zkeiv4/ZxGYUWd0nLs63Ha0ksmp8VMxAIC0wtCFxMos7Lt3PszJssG/E6w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.0", - "@typescript-eslint/visitor-keys": "7.1.0" + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1787,16 +1844,16 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager/node_modules/@typescript-eslint/visitor-keys": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.1.0.tgz", - "integrity": "sha512-FhUqNWluiGNzlvnDZiXad4mZRhtghdoKW6e98GoEOYSu5cND+E39rG5KwJMUzeENwm1ztYBRqof8wMLP+wNPIA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.6.0.tgz", + "integrity": "sha512-4eLB7t+LlNUmXzfOu1VAIAdkjbu5xNSerURS9X/S5TUKWFRpXRQZbmtPqgKmYx8bj3J0irtQXSiWAOY82v+cgw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "7.6.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1804,12 +1861,12 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.1.0.tgz", - "integrity": "sha512-qTWjWieJ1tRJkxgZYXx6WUYtWlBc48YRxgY2JN1aGeVpkhmnopq+SUC8UEVGNXIvWH7XyuTjwALfG6bFEgCkQA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.6.0.tgz", + "integrity": "sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==", "dev": true, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1817,22 +1874,22 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.1.0.tgz", - "integrity": "sha512-k7MyrbD6E463CBbSpcOnwa8oXRdHzH1WiVzOipK3L5KSML92ZKgUBrTlehdi7PEIMT8k0bQixHUGXggPAlKnOQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.6.0.tgz", + "integrity": "sha512-+7Y/GP9VuYibecrCQWSKgl3GvUM5cILRttpWtnAu8GNL9j11e4tbuGZmZjJ8ejnKYyBRb2ddGQ3rEFCq3QjMJw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.0", - "@typescript-eslint/visitor-keys": "7.1.0", + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1845,22 +1902,49 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/visitor-keys": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.1.0.tgz", - "integrity": "sha512-FhUqNWluiGNzlvnDZiXad4mZRhtghdoKW6e98GoEOYSu5cND+E39rG5KwJMUzeENwm1ztYBRqof8wMLP+wNPIA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.6.0.tgz", + "integrity": "sha512-4eLB7t+LlNUmXzfOu1VAIAdkjbu5xNSerURS9X/S5TUKWFRpXRQZbmtPqgKmYx8bj3J0irtQXSiWAOY82v+cgw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "7.6.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree/node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", @@ -2755,14 +2839,6 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, - "node_modules/cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dependencies": { - "node-fetch": "^2.6.12" - } - }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2926,11 +3002,6 @@ "node": ">= 14" } }, - "node_modules/devtools-protocol": { - "version": "0.0.1249869", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1249869.tgz", - "integrity": "sha512-Ctp4hInA0BEavlUoRy9mhGq0i+JSo/AwVyX2EFgZmV1kYB+Zq+EMBAn52QWu6FbRr10hRb6pBl420upbp4++vg==" - }, "node_modules/dezalgo": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", @@ -2996,6 +3067,7 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, "optional": true, "dependencies": { "iconv-lite": "^0.6.2" @@ -3005,6 +3077,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, "optional": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -3173,9 +3246,9 @@ } }, "node_modules/esbuild": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.1.tgz", - "integrity": "sha512-OJwEgrpWm/PCMsLVWXKqvcjme3bHNpOgN7Tb6cQnR5n0TPbQx1/Xrn7rqM+wn17bYeT6MGB5sn1Bh5YiGi70nA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", "dev": true, "hasInstallScript": true, "bin": { @@ -3185,35 +3258,35 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.1", - "@esbuild/android-arm": "0.20.1", - "@esbuild/android-arm64": "0.20.1", - "@esbuild/android-x64": "0.20.1", - "@esbuild/darwin-arm64": "0.20.1", - "@esbuild/darwin-x64": "0.20.1", - "@esbuild/freebsd-arm64": "0.20.1", - "@esbuild/freebsd-x64": "0.20.1", - "@esbuild/linux-arm": "0.20.1", - "@esbuild/linux-arm64": "0.20.1", - "@esbuild/linux-ia32": "0.20.1", - "@esbuild/linux-loong64": "0.20.1", - "@esbuild/linux-mips64el": "0.20.1", - "@esbuild/linux-ppc64": "0.20.1", - "@esbuild/linux-riscv64": "0.20.1", - "@esbuild/linux-s390x": "0.20.1", - "@esbuild/linux-x64": "0.20.1", - "@esbuild/netbsd-x64": "0.20.1", - "@esbuild/openbsd-x64": "0.20.1", - "@esbuild/sunos-x64": "0.20.1", - "@esbuild/win32-arm64": "0.20.1", - "@esbuild/win32-ia32": "0.20.1", - "@esbuild/win32-x64": "0.20.1" + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" } }, "node_modules/esbuild/node_modules/@esbuild/aix-ppc64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.1.tgz", - "integrity": "sha512-m55cpeupQ2DbuRGQMMZDzbv9J9PgVelPjlcmM5kxHnrBdBx6REaEd7LamYV7Dm8N7rCyR/XwU6rVP8ploKtIkA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", "cpu": [ "ppc64" ], @@ -3227,9 +3300,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/android-arm": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.1.tgz", - "integrity": "sha512-4j0+G27/2ZXGWR5okcJi7pQYhmkVgb4D7UKwxcqrjhvp5TKWx3cUjgB1CGj1mfdmJBQ9VnUGgUhign+FPF2Zgw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", "cpu": [ "arm" ], @@ -3243,9 +3316,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/android-arm64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.1.tgz", - "integrity": "sha512-hCnXNF0HM6AjowP+Zou0ZJMWWa1VkD77BXe959zERgGJBBxB+sV+J9f/rcjeg2c5bsukD/n17RKWXGFCO5dD5A==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", "cpu": [ "arm64" ], @@ -3259,9 +3332,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/android-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.1.tgz", - "integrity": "sha512-MSfZMBoAsnhpS+2yMFYIQUPs8Z19ajwfuaSZx+tSl09xrHZCjbeXXMsUF/0oq7ojxYEpsSo4c0SfjxOYXRbpaA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", "cpu": [ "x64" ], @@ -3275,9 +3348,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/darwin-arm64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.1.tgz", - "integrity": "sha512-Ylk6rzgMD8klUklGPzS414UQLa5NPXZD5tf8JmQU8GQrj6BrFA/Ic9tb2zRe1kOZyCbGl+e8VMbDRazCEBqPvA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", "cpu": [ "arm64" ], @@ -3291,9 +3364,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/darwin-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.1.tgz", - "integrity": "sha512-pFIfj7U2w5sMp52wTY1XVOdoxw+GDwy9FsK3OFz4BpMAjvZVs0dT1VXs8aQm22nhwoIWUmIRaE+4xow8xfIDZA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", "cpu": [ "x64" ], @@ -3307,9 +3380,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.1.tgz", - "integrity": "sha512-UyW1WZvHDuM4xDz0jWun4qtQFauNdXjXOtIy7SYdf7pbxSWWVlqhnR/T2TpX6LX5NI62spt0a3ldIIEkPM6RHw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", "cpu": [ "arm64" ], @@ -3323,9 +3396,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/freebsd-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.1.tgz", - "integrity": "sha512-itPwCw5C+Jh/c624vcDd9kRCCZVpzpQn8dtwoYIt2TJF3S9xJLiRohnnNrKwREvcZYx0n8sCSbvGH349XkcQeg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", "cpu": [ "x64" ], @@ -3339,9 +3412,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/linux-arm": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.1.tgz", - "integrity": "sha512-LojC28v3+IhIbfQ+Vu4Ut5n3wKcgTu6POKIHN9Wpt0HnfgUGlBuyDDQR4jWZUZFyYLiz4RBBBmfU6sNfn6RhLw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", "cpu": [ "arm" ], @@ -3355,9 +3428,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/linux-arm64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.1.tgz", - "integrity": "sha512-cX8WdlF6Cnvw/DO9/X7XLH2J6CkBnz7Twjpk56cshk9sjYVcuh4sXQBy5bmTwzBjNVZze2yaV1vtcJS04LbN8w==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", "cpu": [ "arm64" ], @@ -3371,9 +3444,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/linux-ia32": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.1.tgz", - "integrity": "sha512-4H/sQCy1mnnGkUt/xszaLlYJVTz3W9ep52xEefGtd6yXDQbz/5fZE5dFLUgsPdbUOQANcVUa5iO6g3nyy5BJiw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", "cpu": [ "ia32" ], @@ -3387,9 +3460,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/linux-loong64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.1.tgz", - "integrity": "sha512-c0jgtB+sRHCciVXlyjDcWb2FUuzlGVRwGXgI+3WqKOIuoo8AmZAddzeOHeYLtD+dmtHw3B4Xo9wAUdjlfW5yYA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", "cpu": [ "loong64" ], @@ -3403,9 +3476,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/linux-mips64el": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.1.tgz", - "integrity": "sha512-TgFyCfIxSujyuqdZKDZ3yTwWiGv+KnlOeXXitCQ+trDODJ+ZtGOzLkSWngynP0HZnTsDyBbPy7GWVXWaEl6lhA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", "cpu": [ "mips64el" ], @@ -3419,9 +3492,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/linux-ppc64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.1.tgz", - "integrity": "sha512-b+yuD1IUeL+Y93PmFZDZFIElwbmFfIKLKlYI8M6tRyzE6u7oEP7onGk0vZRh8wfVGC2dZoy0EqX1V8qok4qHaw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", "cpu": [ "ppc64" ], @@ -3435,9 +3508,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/linux-riscv64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.1.tgz", - "integrity": "sha512-wpDlpE0oRKZwX+GfomcALcouqjjV8MIX8DyTrxfyCfXxoKQSDm45CZr9fanJ4F6ckD4yDEPT98SrjvLwIqUCgg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", "cpu": [ "riscv64" ], @@ -3451,9 +3524,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/linux-s390x": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.1.tgz", - "integrity": "sha512-5BepC2Au80EohQ2dBpyTquqGCES7++p7G+7lXe1bAIvMdXm4YYcEfZtQrP4gaoZ96Wv1Ute61CEHFU7h4FMueQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", "cpu": [ "s390x" ], @@ -3467,9 +3540,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/linux-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.1.tgz", - "integrity": "sha512-5gRPk7pKuaIB+tmH+yKd2aQTRpqlf1E4f/mC+tawIm/CGJemZcHZpp2ic8oD83nKgUPMEd0fNanrnFljiruuyA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", "cpu": [ "x64" ], @@ -3483,9 +3556,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/netbsd-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.1.tgz", - "integrity": "sha512-4fL68JdrLV2nVW2AaWZBv3XEm3Ae3NZn/7qy2KGAt3dexAgSVT+Hc97JKSZnqezgMlv9x6KV0ZkZY7UO5cNLCg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", "cpu": [ "x64" ], @@ -3499,9 +3572,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/openbsd-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.1.tgz", - "integrity": "sha512-GhRuXlvRE+twf2ES+8REbeCb/zeikNqwD3+6S5y5/x+DYbAQUNl0HNBs4RQJqrechS4v4MruEr8ZtAin/hK5iw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", "cpu": [ "x64" ], @@ -3515,9 +3588,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/sunos-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.1.tgz", - "integrity": "sha512-ZnWEyCM0G1Ex6JtsygvC3KUUrlDXqOihw8RicRuQAzw+c4f1D66YlPNNV3rkjVW90zXVsHwZYWbJh3v+oQFM9Q==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", "cpu": [ "x64" ], @@ -3531,9 +3604,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/win32-arm64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.1.tgz", - "integrity": "sha512-QZ6gXue0vVQY2Oon9WyLFCdSuYbXSoxaZrPuJ4c20j6ICedfsDilNPYfHLlMH7vGfU5DQR0czHLmJvH4Nzis/A==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", "cpu": [ "arm64" ], @@ -3547,9 +3620,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/win32-ia32": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.1.tgz", - "integrity": "sha512-HzcJa1NcSWTAU0MJIxOho8JftNp9YALui3o+Ny7hCh0v5f90nprly1U3Sj1Ldj/CvKKdvvFsCRvDkpsEMp4DNw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", "cpu": [ "ia32" ], @@ -3563,9 +3636,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/win32-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.1.tgz", - "integrity": "sha512-0MBh53o6XtI6ctDnRMeQ+xoCN8kD2qI1rY1KgF/xdWQwoFeKou7puvDfV8/Wv4Ctx2rRpET/gGdz3YlNtNACSA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", "cpu": [ "x64" ], @@ -3914,12 +3987,13 @@ } }, "node_modules/eslint-plugin-mocha": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.3.0.tgz", - "integrity": "sha512-IWzbg2K6B1Q7h37Ih4zMyW+nhmw1JvUlHlbCUUUu6PfOOAUGCB0gxmvv7/U+TQQ6e8yHUv+q7KMdIIum4bx+PA==", + "version": "10.4.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.4.2.tgz", + "integrity": "sha512-cur4dVYnSEWTBwdqIBQFxa/9siAhesu0TX+lbJ4ClE9j0eNMNe6BSx3vkFFNz6tGoveyMyELFXa30f3fvuAVDg==", "dev": true, "dependencies": { "eslint-utils": "^3.0.0", + "globals": "^13.24.0", "rambda": "^7.4.0" }, "engines": { @@ -4720,6 +4794,28 @@ "node": ">= 10.0.0" } }, + "node_modules/glob": { + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.6", + "minimatch": "^9.0.1", + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -4732,6 +4828,31 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/path-scurry": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", + "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, "node_modules/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -4806,24 +4927,24 @@ "dev": true }, "node_modules/gts": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/gts/-/gts-5.2.0.tgz", - "integrity": "sha512-25qOnePUUX7upFc4ycqWersDBq+o1X6hXUTW56JOWCxPYKJXQ1RWzqT9q+2SU3LfPKJf+4sz4Dw3VT0p96Kv6g==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/gts/-/gts-5.3.0.tgz", + "integrity": "sha512-/V0nbaWLBv8g0v2kol5M7Vf+kHXk19Ew5sa3wQJXeUaccesXg7AFo7eEInoIpR+aIJ3QDjoYt4zHYeFr8w8rng==", "dev": true, "dependencies": { "@typescript-eslint/eslint-plugin": "5.62.0", "@typescript-eslint/parser": "5.62.0", "chalk": "^4.1.2", - "eslint": "8.50.0", - "eslint-config-prettier": "9.0.0", + "eslint": "8.53.0", + "eslint-config-prettier": "9.1.0", "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "5.0.0", + "eslint-plugin-prettier": "5.1.3", "execa": "^5.0.0", "inquirer": "^7.3.3", "json5": "^2.1.3", "meow": "^9.0.0", "ncp": "^2.0.0", - "prettier": "3.0.3", + "prettier": "3.1.1", "rimraf": "3.0.2", "write-file-atomic": "^4.0.0" }, @@ -4837,15 +4958,6 @@ "typescript": ">=3" } }, - "node_modules/gts/node_modules/@eslint/js": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz", - "integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, "node_modules/gts/node_modules/@typescript-eslint/eslint-plugin": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", @@ -5058,18 +5170,19 @@ } }, "node_modules/gts/node_modules/eslint": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", - "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz", + "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.50.0", - "@humanwhocodes/config-array": "^0.11.11", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.53.0", + "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -5111,45 +5224,13 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/gts/node_modules/eslint-config-prettier": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz", - "integrity": "sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==", + "node_modules/gts/node_modules/eslint/node_modules/@eslint/js": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz", + "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==", "dev": true, - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/gts/node_modules/eslint-plugin-prettier": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.0.tgz", - "integrity": "sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w==", - "dev": true, - "dependencies": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.8.5" - }, "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/prettier" - }, - "peerDependencies": { - "@types/eslint": ">=8.0.0", - "eslint": ">=8.0.0", - "prettier": ">=3.0.0" - }, - "peerDependenciesMeta": { - "@types/eslint": { - "optional": true - }, - "eslint-config-prettier": { - "optional": true - } + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/gts/node_modules/estraverse": { @@ -5266,9 +5347,9 @@ } }, "node_modules/gts/node_modules/prettier": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", - "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz", + "integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -6616,9 +6697,9 @@ } }, "node_modules/mocha": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.3.0.tgz", - "integrity": "sha512-uF2XJs+7xSLsrmIvn37i/wnc91nw7XjOQB8ccyx5aEgdnohr7n+rEiZP23WkCYHjilR6+EboEnbq/ZQDz4LSbg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.4.0.tgz", + "integrity": "sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==", "dependencies": { "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", @@ -6750,25 +6831,6 @@ "path-to-regexp": "^6.2.1" } }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/nopt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", @@ -7239,6 +7301,7 @@ "version": "1.10.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dev": true, "dependencies": { "lru-cache": "^9.1.1 || ^10.0.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -7254,6 +7317,7 @@ "version": "10.2.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "dev": true, "engines": { "node": "14 || >=16.14" } @@ -8183,7 +8247,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "devOptional": true + "dev": true }, "node_modules/semver": { "version": "7.6.0", @@ -8853,11 +8917,6 @@ "node": ">=8.0" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, "node_modules/treeify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz", @@ -8876,18 +8935,6 @@ "node": ">=8" } }, - "node_modules/ts-api-utils": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz", - "integrity": "sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==", - "dev": true, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "typescript": ">=4.2.0" - } - }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -8913,12 +8960,12 @@ } }, "node_modules/tsd": { - "version": "0.30.7", - "resolved": "https://registry.npmjs.org/tsd/-/tsd-0.30.7.tgz", - "integrity": "sha512-oTiJ28D6B/KXoU3ww/Eji+xqHJojiuPVMwA12g4KYX1O72N93Nb6P3P3h2OAhhf92Xl8NIhb/xFmBZd5zw/xUw==", + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/tsd/-/tsd-0.31.0.tgz", + "integrity": "sha512-yjBiQ5n8OMv/IZOuhDjBy0ZLCoJ7rky/RxRh5W4sJ0oNNCU/kf6s3puPAkGNi59PptDdkcpUm+RsKSdjR2YbNg==", "dev": true, "dependencies": { - "@tsd/typescript": "~5.3.3", + "@tsd/typescript": "~5.4.3", "eslint-formatter-pretty": "^4.1.0", "globby": "^11.0.1", "jest-diff": "^29.0.3", @@ -8933,6 +8980,15 @@ "node": ">=14.16" } }, + "node_modules/tsd/node_modules/@tsd/typescript": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/@tsd/typescript/-/typescript-5.4.4.tgz", + "integrity": "sha512-sqE6Rz9UNHBuCtuREo/PwsuUaegP1KDSfxMd+9K1qPpt7XO8BmkIImUp7zAqyvwXWUHs+sj6osEkkpyE0tFgfA==", + "dev": true, + "engines": { + "node": ">=14.17" + } + }, "node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -8955,9 +9011,9 @@ } }, "node_modules/tsx": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.7.1.tgz", - "integrity": "sha512-8d6VuibXHtlN5E3zFkgY8u4DX7Y3Z27zvvPKVmLon/D4AjuKzarkUBTLDBgj9iTQ0hg5xM7c/mYiRVM+HETf0g==", + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.7.2.tgz", + "integrity": "sha512-BCNd4kz6fz12fyrgCTEdZHGJ9fWTGeUzXmQysh0RVocDY3h4frk05ZNCXSy4kIenF7y/QnrdiVpTsyNRn6vlAw==", "dev": true, "dependencies": { "esbuild": "~0.19.10", @@ -9540,18 +9596,6 @@ "through": "^2.3.8" } }, - "node_modules/undici": { - "version": "5.28.3", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.3.tgz", - "integrity": "sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA==", - "dev": true, - "dependencies": { - "@fastify/busboy": "^2.0.0" - }, - "engines": { - "node": ">=14.0" - } - }, "node_modules/undici-types": { "version": "5.25.3", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz", @@ -9641,20 +9685,6 @@ "defaults": "^1.0.3" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -9944,14 +9974,13 @@ "version": "3.22.4", "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", - "dev": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } }, "packages/browsers": { "name": "@puppeteer/browsers", - "version": "2.1.0", + "version": "2.2.2", "license": "Apache-2.0", "dependencies": { "debug": "4.3.4", @@ -11996,9 +12025,9 @@ } }, "packages/ng-schematics/node_modules/@angular/cli/node_modules/pacote/node_modules/tar": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", - "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "dev": true, "dependencies": { "chownr": "^2.0.0", @@ -12206,13 +12235,14 @@ } }, "packages/puppeteer": { - "version": "22.4.0", + "version": "22.6.5", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "2.1.0", + "@puppeteer/browsers": "2.2.2", "cosmiconfig": "9.0.0", - "puppeteer-core": "22.4.0" + "devtools-protocol": "0.0.1262051", + "puppeteer-core": "22.6.5" }, "bin": { "puppeteer": "lib/esm/puppeteer/node/cli.js" @@ -12225,14 +12255,13 @@ } }, "packages/puppeteer-core": { - "version": "22.4.0", + "version": "22.6.5", "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "2.1.0", - "chromium-bidi": "0.5.12", - "cross-fetch": "4.0.0", + "@puppeteer/browsers": "2.2.2", + "chromium-bidi": "0.5.17", "debug": "4.3.4", - "devtools-protocol": "0.0.1249869", + "devtools-protocol": "0.0.1262051", "ws": "8.16.0" }, "devDependencies": { @@ -12253,17 +12282,23 @@ "license": "MIT" }, "packages/puppeteer-core/node_modules/chromium-bidi": { - "version": "0.5.12", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.5.12.tgz", - "integrity": "sha512-sZMgEBWKbupD0Q7lyFu8AWkrE+rs5ycE12jFkGwIgD/VS8lDPtelPlXM7LYaq4zrkZ/O2L3f4afHUHL0ICdKog==", + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.5.17.tgz", + "integrity": "sha512-BqOuIWUgTPj8ayuBFJUYCCuwIcwjBsb3/614P7tt1bEPJ4i1M0kCdIl0Wi9xhtswBXnfO2bTpTMkHD71H8rJMg==", "dependencies": { "mitt": "3.0.1", - "urlpattern-polyfill": "10.0.0" + "urlpattern-polyfill": "10.0.0", + "zod": "3.22.4" }, "peerDependencies": { "devtools-protocol": "*" } }, + "packages/puppeteer-core/node_modules/devtools-protocol": { + "version": "0.0.1262051", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1262051.tgz", + "integrity": "sha512-YJe4CT5SA8on3Spa+UDtNhEqtuV6Epwz3OZ4HQVLhlRccpZ9/PAYk0/cy/oKxFKRrZPBUPyxympQci4yWNWZ9g==" + }, "packages/puppeteer-core/node_modules/rxjs": { "version": "7.8.1", "dev": true, @@ -12306,6 +12341,11 @@ } } }, + "packages/puppeteer/node_modules/devtools-protocol": { + "version": "0.0.1262051", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1262051.tgz", + "integrity": "sha512-YJe4CT5SA8on3Spa+UDtNhEqtuV6Epwz3OZ4HQVLhlRccpZ9/PAYk0/cy/oKxFKRrZPBUPyxympQci4yWNWZ9g==" + }, "packages/puppeteer/node_modules/parse-json": { "version": "5.2.0", "license": "MIT", @@ -12353,26 +12393,20 @@ "name": "@puppeteer-test/installation", "version": "latest", "dependencies": { - "glob": "10.3.10", - "mocha": "10.3.0" + "glob": "10.3.12", + "mocha": "10.4.0" } }, - "test/node_modules/diff": { - "version": "5.2.0", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "test/node_modules/glob": { - "version": "10.3.10", - "license": "ISC", + "test/installation/node_modules/glob": { + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", + "jackspeak": "^2.3.6", "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" }, "bin": { "glob": "dist/esm/bin.mjs" @@ -12384,16 +12418,46 @@ "url": "https://github.com/sponsors/isaacs" } }, + "test/installation/node_modules/glob/node_modules/path-scurry": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", + "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "test/installation/node_modules/glob/node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "engines": { + "node": "14 || >=16.14" + } + }, + "test/node_modules/diff": { + "version": "5.2.0", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "tools/docgen": { "name": "@puppeteer/docgen", "version": "0.1.0", "license": "Apache-2.0", "devDependencies": { - "@microsoft/api-documenter": "7.23.35", - "@microsoft/api-extractor": "7.42.2", - "@microsoft/api-extractor-model": "7.28.13", + "@microsoft/api-documenter": "7.24.2", + "@microsoft/api-extractor": "7.43.1", + "@microsoft/api-extractor-model": "7.28.14", "@microsoft/tsdoc": "0.14.2", - "@rushstack/node-core-library": "4.0.2" + "@rushstack/node-core-library": "4.1.0" } }, "tools/doctest": { @@ -12404,13 +12468,13 @@ "doctest": "bin/doctest.js" }, "devDependencies": { - "@swc/core": "1.4.2", + "@swc/core": "1.4.13", "@types/doctrine": "0.0.9", "@types/source-map-support": "0.5.10", "@types/yargs": "17.0.32", "acorn": "8.11.3", "doctrine": "3.0.0", - "glob": "10.3.10", + "glob": "10.3.12", "pkg-dir": "8.0.0", "source-map": "0.7.4", "source-map-support": "0.5.21", @@ -12430,27 +12494,6 @@ "node": ">=12" } }, - "tools/doctest/node_modules/glob": { - "version": "10.3.10", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "tools/doctest/node_modules/source-map": { "version": "0.7.4", "dev": true, @@ -12490,7 +12533,7 @@ "license": "Apache-2.0", "devDependencies": { "@prettier/sync": "0.5.1", - "@typescript-eslint/utils": "7.1.0" + "@typescript-eslint/utils": "7.6.0" } }, "tools/mocha-runner": { @@ -12503,7 +12546,7 @@ "devDependencies": { "@types/yargs": "17.0.32", "c8": "9.1.0", - "glob": "10.3.10", + "glob": "10.3.12", "yargs": "17.7.2", "zod": "3.22.4" } @@ -12521,27 +12564,6 @@ "node": ">=12" } }, - "tools/mocha-runner/node_modules/glob": { - "version": "10.3.10", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "tools/mocha-runner/node_modules/yargs": { "version": "17.7.2", "dev": true, diff --git a/remote/test/puppeteer/package.json b/remote/test/puppeteer/package.json index 0ffdceedf7..a871a1d459 100644 --- a/remote/test/puppeteer/package.json +++ b/remote/test/puppeteer/package.json @@ -142,13 +142,13 @@ "@types/node": "20.8.4", "@types/semver": "7.5.8", "@types/sinon": "17.0.3", - "@typescript-eslint/eslint-plugin": "7.1.0", - "@typescript-eslint/parser": "7.1.0", - "esbuild": "0.20.1", + "@typescript-eslint/eslint-plugin": "7.6.0", + "@typescript-eslint/parser": "7.6.0", + "esbuild": "0.20.2", "eslint-config-prettier": "9.1.0", "eslint-import-resolver-typescript": "3.6.1", "eslint-plugin-import": "2.29.1", - "eslint-plugin-mocha": "10.3.0", + "eslint-plugin-mocha": "10.4.2", "eslint-plugin-prettier": "5.1.3", "eslint-plugin-rulesdir": "0.2.2", "eslint-plugin-tsdoc": "0.2.17", @@ -156,18 +156,18 @@ "eslint": "8.57.0", "execa": "8.0.1", "expect": "29.7.0", - "gts": "5.2.0", + "gts": "5.3.0", "hereby": "1.8.9", "license-checker": "25.0.1", - "mocha": "10.3.0", + "mocha": "10.4.0", "npm-run-all2": "6.1.2", "prettier": "3.2.5", "semver": "7.6.0", "sinon": "17.0.1", "source-map-support": "0.5.21", "spdx-satisfies": "5.0.1", - "tsd": "0.30.7", - "tsx": "4.7.1", + "tsd": "0.31.0", + "tsx": "4.7.2", "typescript": "5.3.3", "wireit": "0.14.4" }, diff --git a/remote/test/puppeteer/packages/browsers/CHANGELOG.md b/remote/test/puppeteer/packages/browsers/CHANGELOG.md index a2798068e9..75fa1c370b 100644 --- a/remote/test/puppeteer/packages/browsers/CHANGELOG.md +++ b/remote/test/puppeteer/packages/browsers/CHANGELOG.md @@ -1,5 +1,32 @@ # Changelog +## [2.2.2](https://github.com/puppeteer/puppeteer/compare/browsers-v2.2.1...browsers-v2.2.2) (2024-04-15) + + +### Bug Fixes + +* remove NetworkServiceInProcess2 set by default ([#12261](https://github.com/puppeteer/puppeteer/issues/12261)) ([ff4f70f](https://github.com/puppeteer/puppeteer/commit/ff4f70f4ae7ca8deb0becbec2e49b35322dba336)), closes [#12257](https://github.com/puppeteer/puppeteer/issues/12257) + +## [2.2.1](https://github.com/puppeteer/puppeteer/compare/browsers-v2.2.0...browsers-v2.2.1) (2024-04-05) + + +### Bug Fixes + +* do not use fallback download URLs if custom baseUrl is provided ([#12206](https://github.com/puppeteer/puppeteer/issues/12206)) ([ab560bc](https://github.com/puppeteer/puppeteer/commit/ab560bcf6fee57cabde94d9d261d28ffc2112948)) +* only set up a single process event listener in launch ([#12200](https://github.com/puppeteer/puppeteer/issues/12200)) ([7bc5e0f](https://github.com/puppeteer/puppeteer/commit/7bc5e0fb2dc443765e2512e4dc15fb2bcc1cb4be)) + +## [2.2.0](https://github.com/puppeteer/puppeteer/compare/browsers-v2.1.0...browsers-v2.2.0) (2024-03-15) + + +### Features + +* allow downloading Firefox channels other than nightly ([#12051](https://github.com/puppeteer/puppeteer/issues/12051)) ([e4cc2f9](https://github.com/puppeteer/puppeteer/commit/e4cc2f9ee944f2507a03cf8f5af99759c97ee2ec)) + + +### Bug Fixes + +* don't keep connection alive ([#12096](https://github.com/puppeteer/puppeteer/issues/12096)) ([0a142bf](https://github.com/puppeteer/puppeteer/commit/0a142bf0aa8a6666d1ca230d05a1ece0e03ad7d4)) + ## [2.1.0](https://github.com/puppeteer/puppeteer/compare/browsers-v2.0.1...browsers-v2.1.0) (2024-02-21) diff --git a/remote/test/puppeteer/packages/browsers/README.md b/remote/test/puppeteer/packages/browsers/README.md index f5342126c6..a05a4d38b8 100644 --- a/remote/test/puppeteer/packages/browsers/README.md +++ b/remote/test/puppeteer/packages/browsers/README.md @@ -10,7 +10,7 @@ Use `npx` to run the CLI: npx @puppeteer/browsers --help ``` -CLI help will provide all documentation you need to use the CLI. +Built-in per-command `help` will provide all documentation you need to use the CLI. ```bash npx @puppeteer/browsers --help # help for all commands @@ -18,10 +18,28 @@ npx @puppeteer/browsers install --help # help for the install command npx @puppeteer/browsers launch --help # help for the launch command ``` +Some example to give an idea of what the CLI looks like (use the `--help` command for more examples): + +```sh +# Download the latest available Chrome for Testing binary corresponding to the Stable channel. +npx @puppeteer/browsers install chrome@stable + +# Download a specific Chrome for Testing version. +npx @puppeteer/browsers install chrome@116.0.5793.0 + +# Download the latest Chrome for Testing version for the given milestone. +npx @puppeteer/browsers install chrome@117 + +# Download the latest available ChromeDriver version corresponding to the Canary channel. +npx @puppeteer/browsers install chromedriver@canary + +# Download a specific ChromeDriver version. +npx @puppeteer/browsers install chromedriver@116.0.5793.0 +``` + ## Known limitations -1. We support installing and running Firefox, Chrome and Chromium. The `latest`, `beta`, `dev`, `canary`, `stable` keywords are only supported for the install command. For the `launch` command you need to specify an exact build ID. The build ID is provided by the `install` command (see `npx @puppeteer/browsers install --help` for the format). -2. Launching the system browsers is only possible for Chrome/Chromium. +1. Launching the system browsers is only possible for Chrome/Chromium. ## API diff --git a/remote/test/puppeteer/packages/browsers/package.json b/remote/test/puppeteer/packages/browsers/package.json index 0f2afa74de..f9f2b1c5ea 100644 --- a/remote/test/puppeteer/packages/browsers/package.json +++ b/remote/test/puppeteer/packages/browsers/package.json @@ -1,6 +1,6 @@ { "name": "@puppeteer/browsers", - "version": "2.1.0", + "version": "2.2.2", "description": "Download and launch browsers", "scripts": { "build:docs": "wireit", diff --git a/remote/test/puppeteer/packages/browsers/src/CLI.ts b/remote/test/puppeteer/packages/browsers/src/CLI.ts index 281f22c9f5..3bae584e62 100644 --- a/remote/test/puppeteer/packages/browsers/src/CLI.ts +++ b/remote/test/puppeteer/packages/browsers/src/CLI.ts @@ -222,7 +222,31 @@ export class CLI { ); yargs.example( '$0 install firefox', - 'Install the latest available build of the Firefox browser.' + 'Install the latest nightly available build of the Firefox browser.' + ); + yargs.example( + '$0 install firefox@stable', + 'Install the latest stable build of the Firefox browser.' + ); + yargs.example( + '$0 install firefox@beta', + 'Install the latest beta build of the Firefox browser.' + ); + yargs.example( + '$0 install firefox@devedition', + 'Install the latest devedition build of the Firefox browser.' + ); + yargs.example( + '$0 install firefox@esr', + 'Install the latest ESR build of the Firefox browser.' + ); + yargs.example( + '$0 install firefox@nightly', + 'Install the latest nightly build of the Firefox browser.' + ); + yargs.example( + '$0 install firefox@stable_111.0.1', + 'Install a specific version of the Firefox browser.' ); yargs.example( '$0 install firefox --platform mac', @@ -395,7 +419,7 @@ export function makeProgressCallback( return (downloadedBytes: number, totalBytes: number) => { if (!progressBar) { progressBar = new ProgressBar( - `Downloading ${browser} r${buildId} - ${toMegabytes( + `Downloading ${browser} ${buildId} - ${toMegabytes( totalBytes )} [:bar] :percent :etas `, { diff --git a/remote/test/puppeteer/packages/browsers/src/browser-data/browser-data.ts b/remote/test/puppeteer/packages/browsers/src/browser-data/browser-data.ts index 3e78030aa7..fa7ec9be14 100644 --- a/remote/test/puppeteer/packages/browsers/src/browser-data/browser-data.ts +++ b/remote/test/puppeteer/packages/browsers/src/browser-data/browser-data.ts @@ -54,28 +54,36 @@ export const versionComparators = { export {Browser, BrowserPlatform, ChromeReleaseChannel}; /** - * @public + * @internal */ -export async function resolveBuildId( +async function resolveBuildIdForBrowserTag( browser: Browser, platform: BrowserPlatform, - tag: string + tag: BrowserTag ): Promise<string> { switch (browser) { case Browser.FIREFOX: - switch (tag as BrowserTag) { + switch (tag) { case BrowserTag.LATEST: - return await firefox.resolveBuildId('FIREFOX_NIGHTLY'); + return await firefox.resolveBuildId(firefox.FirefoxChannel.NIGHTLY); case BrowserTag.BETA: + return await firefox.resolveBuildId(firefox.FirefoxChannel.BETA); + case BrowserTag.NIGHTLY: + return await firefox.resolveBuildId(firefox.FirefoxChannel.NIGHTLY); + case BrowserTag.DEVEDITION: + return await firefox.resolveBuildId( + firefox.FirefoxChannel.DEVEDITION + ); + case BrowserTag.STABLE: + return await firefox.resolveBuildId(firefox.FirefoxChannel.STABLE); + case BrowserTag.ESR: + return await firefox.resolveBuildId(firefox.FirefoxChannel.ESR); case BrowserTag.CANARY: case BrowserTag.DEV: - case BrowserTag.STABLE: - throw new Error( - `${tag} is not supported for ${browser}. Use 'latest' instead.` - ); + throw new Error(`${tag.toUpperCase()} is not available for Firefox`); } case Browser.CHROME: { - switch (tag as BrowserTag) { + switch (tag) { case BrowserTag.LATEST: return await chrome.resolveBuildId(ChromeReleaseChannel.CANARY); case BrowserTag.BETA: @@ -86,13 +94,11 @@ export async function resolveBuildId( return await chrome.resolveBuildId(ChromeReleaseChannel.DEV); case BrowserTag.STABLE: return await chrome.resolveBuildId(ChromeReleaseChannel.STABLE); - default: - const result = await chrome.resolveBuildId(tag); - if (result) { - return result; - } + case BrowserTag.NIGHTLY: + case BrowserTag.DEVEDITION: + case BrowserTag.ESR: + throw new Error(`${tag.toUpperCase()} is not available for Chrome`); } - return tag; } case Browser.CHROMEDRIVER: { switch (tag) { @@ -105,13 +111,13 @@ export async function resolveBuildId( return await chromedriver.resolveBuildId(ChromeReleaseChannel.DEV); case BrowserTag.STABLE: return await chromedriver.resolveBuildId(ChromeReleaseChannel.STABLE); - default: - const result = await chromedriver.resolveBuildId(tag); - if (result) { - return result; - } + case BrowserTag.NIGHTLY: + case BrowserTag.DEVEDITION: + case BrowserTag.ESR: + throw new Error( + `${tag.toUpperCase()} is not available for ChromeDriver` + ); } - return tag; } case Browser.CHROMEHEADLESSSHELL: { switch (tag) { @@ -132,29 +138,68 @@ export async function resolveBuildId( return await chromeHeadlessShell.resolveBuildId( ChromeReleaseChannel.STABLE ); - default: - const result = await chromeHeadlessShell.resolveBuildId(tag); - if (result) { - return result; - } + case BrowserTag.NIGHTLY: + case BrowserTag.DEVEDITION: + case BrowserTag.ESR: + throw new Error(`${tag} is not available for chrome-headless-shell`); } - return tag; } case Browser.CHROMIUM: - switch (tag as BrowserTag) { + switch (tag) { case BrowserTag.LATEST: return await chromium.resolveBuildId(platform); - case BrowserTag.BETA: + case BrowserTag.NIGHTLY: case BrowserTag.CANARY: case BrowserTag.DEV: + case BrowserTag.DEVEDITION: + case BrowserTag.BETA: case BrowserTag.STABLE: + case BrowserTag.ESR: throw new Error( - `${tag} is not supported for ${browser}. Use 'latest' instead.` + `${tag} is not supported for Chromium. Use 'latest' instead.` ); } } - // We assume the tag is the buildId if it didn't match any keywords. - return tag; +} + +/** + * @public + */ +export async function resolveBuildId( + browser: Browser, + platform: BrowserPlatform, + tag: string +): Promise<string> { + const browserTag = tag as BrowserTag; + if (Object.values(BrowserTag).includes(browserTag)) { + return await resolveBuildIdForBrowserTag(browser, platform, browserTag); + } + + switch (browser) { + case Browser.FIREFOX: + return tag; + case Browser.CHROME: + const chromeResult = await chrome.resolveBuildId(tag); + if (chromeResult) { + return chromeResult; + } + return tag; + case Browser.CHROMEDRIVER: + const chromeDriverResult = await chromedriver.resolveBuildId(tag); + if (chromeDriverResult) { + return chromeDriverResult; + } + return tag; + case Browser.CHROMEHEADLESSSHELL: + const chromeHeadlessShellResult = + await chromeHeadlessShell.resolveBuildId(tag); + if (chromeHeadlessShellResult) { + return chromeHeadlessShellResult; + } + return tag; + case Browser.CHROMIUM: + return tag; + } } /** diff --git a/remote/test/puppeteer/packages/browsers/src/browser-data/firefox.ts b/remote/test/puppeteer/packages/browsers/src/browser-data/firefox.ts index 87e3804c8f..95e03c54c0 100644 --- a/remote/test/puppeteer/packages/browsers/src/browser-data/firefox.ts +++ b/remote/test/puppeteer/packages/browsers/src/browser-data/firefox.ts @@ -11,7 +11,7 @@ import {getJSON} from '../httpUtil.js'; import {BrowserPlatform, type ProfileOptions} from './types.js'; -function archive(platform: BrowserPlatform, buildId: string): string { +function archiveNightly(platform: BrowserPlatform, buildId: string): string { switch (platform) { case BrowserPlatform.LINUX: return `firefox-${buildId}.en-US.${platform}-x86_64.tar.bz2`; @@ -24,48 +24,146 @@ function archive(platform: BrowserPlatform, buildId: string): string { } } +function archive(platform: BrowserPlatform, buildId: string): string { + switch (platform) { + case BrowserPlatform.LINUX: + return `firefox-${buildId}.tar.bz2`; + case BrowserPlatform.MAC_ARM: + case BrowserPlatform.MAC: + return `Firefox ${buildId}.dmg`; + case BrowserPlatform.WIN32: + case BrowserPlatform.WIN64: + return `Firefox Setup ${buildId}.exe`; + } +} + +function platformName(platform: BrowserPlatform): string { + switch (platform) { + case BrowserPlatform.LINUX: + return `linux-x86_64`; + case BrowserPlatform.MAC_ARM: + case BrowserPlatform.MAC: + return `mac`; + case BrowserPlatform.WIN32: + case BrowserPlatform.WIN64: + return platform; + } +} + +function parseBuildId(buildId: string): [FirefoxChannel, string] { + for (const value of Object.values(FirefoxChannel)) { + if (buildId.startsWith(value + '_')) { + buildId = buildId.substring(value.length + 1); + return [value, buildId]; + } + } + // Older versions do not have channel as the prefix.« + return [FirefoxChannel.NIGHTLY, buildId]; +} + export function resolveDownloadUrl( platform: BrowserPlatform, buildId: string, - baseUrl = 'https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central' + baseUrl?: string ): string { - return `${baseUrl}/${resolveDownloadPath(platform, buildId).join('/')}`; + const [channel, resolvedBuildId] = parseBuildId(buildId); + switch (channel) { + case FirefoxChannel.NIGHTLY: + baseUrl ??= + 'https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central'; + break; + case FirefoxChannel.DEVEDITION: + baseUrl ??= 'https://archive.mozilla.org/pub/devedition/releases'; + break; + case FirefoxChannel.BETA: + case FirefoxChannel.STABLE: + case FirefoxChannel.ESR: + baseUrl ??= 'https://archive.mozilla.org/pub/firefox/releases'; + break; + } + switch (channel) { + case FirefoxChannel.NIGHTLY: + return `${baseUrl}/${resolveDownloadPath(platform, resolvedBuildId).join('/')}`; + case FirefoxChannel.DEVEDITION: + case FirefoxChannel.BETA: + case FirefoxChannel.STABLE: + case FirefoxChannel.ESR: + return `${baseUrl}/${resolvedBuildId}/${platformName(platform)}/en-US/${archive(platform, resolvedBuildId)}`; + } } export function resolveDownloadPath( platform: BrowserPlatform, buildId: string ): string[] { - return [archive(platform, buildId)]; + return [archiveNightly(platform, buildId)]; } export function relativeExecutablePath( platform: BrowserPlatform, - _buildId: string + buildId: string ): string { - switch (platform) { - case BrowserPlatform.MAC_ARM: - case BrowserPlatform.MAC: - return path.join('Firefox Nightly.app', 'Contents', 'MacOS', 'firefox'); - case BrowserPlatform.LINUX: - return path.join('firefox', 'firefox'); - case BrowserPlatform.WIN32: - case BrowserPlatform.WIN64: - return path.join('firefox', 'firefox.exe'); + const [channel] = parseBuildId(buildId); + switch (channel) { + case FirefoxChannel.NIGHTLY: + switch (platform) { + case BrowserPlatform.MAC_ARM: + case BrowserPlatform.MAC: + return path.join( + 'Firefox Nightly.app', + 'Contents', + 'MacOS', + 'firefox' + ); + case BrowserPlatform.LINUX: + return path.join('firefox', 'firefox'); + case BrowserPlatform.WIN32: + case BrowserPlatform.WIN64: + return path.join('firefox', 'firefox.exe'); + } + case FirefoxChannel.BETA: + case FirefoxChannel.DEVEDITION: + case FirefoxChannel.ESR: + case FirefoxChannel.STABLE: + switch (platform) { + case BrowserPlatform.MAC_ARM: + case BrowserPlatform.MAC: + return path.join('Firefox.app', 'Contents', 'MacOS', 'firefox'); + case BrowserPlatform.LINUX: + return path.join('firefox', 'firefox'); + case BrowserPlatform.WIN32: + case BrowserPlatform.WIN64: + return path.join('core', 'firefox.exe'); + } } } +export enum FirefoxChannel { + STABLE = 'stable', + ESR = 'esr', + DEVEDITION = 'devedition', + BETA = 'beta', + NIGHTLY = 'nightly', +} + export async function resolveBuildId( - channel: 'FIREFOX_NIGHTLY' = 'FIREFOX_NIGHTLY' + channel: FirefoxChannel = FirefoxChannel.NIGHTLY ): Promise<string> { + const channelToVersionKey = { + [FirefoxChannel.ESR]: 'FIREFOX_ESR', + [FirefoxChannel.STABLE]: 'LATEST_FIREFOX_VERSION', + [FirefoxChannel.DEVEDITION]: 'FIREFOX_DEVEDITION', + [FirefoxChannel.BETA]: 'FIREFOX_DEVEDITION', + [FirefoxChannel.NIGHTLY]: 'FIREFOX_NIGHTLY', + }; const versions = (await getJSON( new URL('https://product-details.mozilla.org/1.0/firefox_versions.json') )) as Record<string, string>; - const version = versions[channel]; + const version = versions[channelToVersionKey[channel]]; if (!version) { throw new Error(`Channel ${channel} is not found.`); } - return version; + return channel + '_' + version; } export async function createProfile(options: ProfileOptions): Promise<void> { diff --git a/remote/test/puppeteer/packages/browsers/src/browser-data/types.ts b/remote/test/puppeteer/packages/browsers/src/browser-data/types.ts index ac72661a2d..4990350659 100644 --- a/remote/test/puppeteer/packages/browsers/src/browser-data/types.ts +++ b/remote/test/puppeteer/packages/browsers/src/browser-data/types.ts @@ -36,9 +36,12 @@ export enum BrowserPlatform { */ export enum BrowserTag { CANARY = 'canary', + NIGHTLY = 'nightly', BETA = 'beta', DEV = 'dev', + DEVEDITION = 'devedition', STABLE = 'stable', + ESR = 'esr', LATEST = 'latest', } diff --git a/remote/test/puppeteer/packages/browsers/src/fileUtil.ts b/remote/test/puppeteer/packages/browsers/src/fileUtil.ts index 50a6897853..28168afad1 100644 --- a/remote/test/puppeteer/packages/browsers/src/fileUtil.ts +++ b/remote/test/puppeteer/packages/browsers/src/fileUtil.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {exec as execChildProcess} from 'child_process'; +import {exec as execChildProcess, spawnSync} from 'child_process'; import {createReadStream} from 'fs'; import {mkdir, readdir} from 'fs/promises'; import * as path from 'path'; @@ -30,6 +30,18 @@ export async function unpackArchive( } else if (archivePath.endsWith('.dmg')) { await mkdir(folderPath); await installDMG(archivePath, folderPath); + } else if (archivePath.endsWith('.exe')) { + // Firefox on Windows. + const result = spawnSync(archivePath, [`/ExtractDir=${folderPath}`], { + env: { + __compat_layer: 'RunAsInvoker', + }, + }); + if (result.status !== 0) { + throw new Error( + `Failed to extract ${archivePath} to ${folderPath}: ${result.output}` + ); + } } else { throw new Error(`Unsupported archive format: ${archivePath}`); } diff --git a/remote/test/puppeteer/packages/browsers/src/httpUtil.ts b/remote/test/puppeteer/packages/browsers/src/httpUtil.ts index 96f7fc9f36..084bae599c 100644 --- a/remote/test/puppeteer/packages/browsers/src/httpUtil.ts +++ b/remote/test/puppeteer/packages/browsers/src/httpUtil.ts @@ -54,6 +54,9 @@ export function httpRequest( res.headers.location ) { httpRequest(new URL(res.headers.location), method, response); + // consume response data to free up memory + // And prevents the connection from being kept alive + res.resume(); } else { response(res); } diff --git a/remote/test/puppeteer/packages/browsers/src/install.ts b/remote/test/puppeteer/packages/browsers/src/install.ts index e78b2c3461..f585021df2 100644 --- a/remote/test/puppeteer/packages/browsers/src/install.ts +++ b/remote/test/puppeteer/packages/browsers/src/install.ts @@ -92,6 +92,11 @@ export interface InstallOptions { * @defaultValue `true` */ unpack?: boolean; + /** + * @internal + * @defaultValue `false` + */ + forceFallbackForTesting?: boolean; } /** @@ -125,6 +130,10 @@ export async function install( try { return await installUrl(url, options); } catch (err) { + // If custom baseUrl is provided, do not fall back to CfT dashboard. + if (options.baseUrl && !options.forceFallbackForTesting) { + throw err; + } debugInstall(`Error downloading from ${url}.`); switch (options.browser) { case Browser.CHROME: diff --git a/remote/test/puppeteer/packages/browsers/src/launch.ts b/remote/test/puppeteer/packages/browsers/src/launch.ts index dfb0fbf633..434f34c0f6 100644 --- a/remote/test/puppeteer/packages/browsers/src/launch.ts +++ b/remote/test/puppeteer/packages/browsers/src/launch.ts @@ -135,6 +135,59 @@ export const CDP_WEBSOCKET_ENDPOINT_REGEX = export const WEBDRIVER_BIDI_WEBSOCKET_ENDPOINT_REGEX = /^WebDriver BiDi listening on (ws:\/\/.*)$/; +type EventHandler = (...args: any[]) => void; +const processListeners = new Map<string, EventHandler[]>(); +const dispatchers = { + exit: (...args: any[]) => { + processListeners.get('exit')?.forEach(handler => { + return handler(...args); + }); + }, + SIGINT: (...args: any[]) => { + processListeners.get('SIGINT')?.forEach(handler => { + return handler(...args); + }); + }, + SIGHUP: (...args: any[]) => { + processListeners.get('SIGHUP')?.forEach(handler => { + return handler(...args); + }); + }, + SIGTERM: (...args: any[]) => { + processListeners.get('SIGTERM')?.forEach(handler => { + return handler(...args); + }); + }, +}; + +function subscribeToProcessEvent( + event: 'exit' | 'SIGINT' | 'SIGHUP' | 'SIGTERM', + handler: EventHandler +): void { + const listeners = processListeners.get(event) || []; + if (listeners.length === 0) { + process.on(event, dispatchers[event]); + } + listeners.push(handler); + processListeners.set(event, listeners); +} + +function unsubscribeFromProcessEvent( + event: 'exit' | 'SIGINT' | 'SIGHUP' | 'SIGTERM', + handler: EventHandler +): void { + const listeners = processListeners.get(event) || []; + const existingListenerIdx = listeners.indexOf(handler); + if (existingListenerIdx === -1) { + return; + } + listeners.splice(existingListenerIdx, 1); + processListeners.set(event, listeners); + if (listeners.length === 0) { + process.off(event, dispatchers[event]); + } +} + /** * @public */ @@ -201,15 +254,15 @@ export class Process { this.#browserProcess.stderr?.pipe(process.stderr); this.#browserProcess.stdout?.pipe(process.stdout); } - process.on('exit', this.#onDriverProcessExit); + subscribeToProcessEvent('exit', this.#onDriverProcessExit); if (opts.handleSIGINT) { - process.on('SIGINT', this.#onDriverProcessSignal); + subscribeToProcessEvent('SIGINT', this.#onDriverProcessSignal); } if (opts.handleSIGTERM) { - process.on('SIGTERM', this.#onDriverProcessSignal); + subscribeToProcessEvent('SIGTERM', this.#onDriverProcessSignal); } if (opts.handleSIGHUP) { - process.on('SIGHUP', this.#onDriverProcessSignal); + subscribeToProcessEvent('SIGHUP', this.#onDriverProcessSignal); } if (opts.onExit) { this.#onExitHook = opts.onExit; @@ -262,10 +315,10 @@ export class Process { } #clearListeners(): void { - process.off('exit', this.#onDriverProcessExit); - process.off('SIGINT', this.#onDriverProcessSignal); - process.off('SIGTERM', this.#onDriverProcessSignal); - process.off('SIGHUP', this.#onDriverProcessSignal); + unsubscribeFromProcessEvent('exit', this.#onDriverProcessExit); + unsubscribeFromProcessEvent('SIGINT', this.#onDriverProcessSignal); + unsubscribeFromProcessEvent('SIGTERM', this.#onDriverProcessSignal); + unsubscribeFromProcessEvent('SIGHUP', this.#onDriverProcessSignal); } #onDriverProcessExit = (_code: number) => { diff --git a/remote/test/puppeteer/packages/browsers/test/src/chrome/install.spec.ts b/remote/test/puppeteer/packages/browsers/test/src/chrome/install.spec.ts index f669d1c57c..d7c9dfbf4a 100644 --- a/remote/test/puppeteer/packages/browsers/test/src/chrome/install.spec.ts +++ b/remote/test/puppeteer/packages/browsers/test/src/chrome/install.spec.ts @@ -138,6 +138,7 @@ describe('Chrome install', () => { }); it('falls back to the chrome-for-testing dashboard URLs if URL is not available', async function () { + this.timeout(60000); const expectedOutputPath = path.join( tmpDir, 'chrome', @@ -150,6 +151,7 @@ describe('Chrome install', () => { platform: BrowserPlatform.LINUX, buildId: testChromeBuildId, baseUrl: 'https://127.0.0.1', + forceFallbackForTesting: true, }); assert.strictEqual(fs.existsSync(expectedOutputPath), true); }); diff --git a/remote/test/puppeteer/packages/browsers/test/src/chrome/launch.spec.ts b/remote/test/puppeteer/packages/browsers/test/src/chrome/launch.spec.ts index c420d9e0b6..dc3d118d67 100644 --- a/remote/test/puppeteer/packages/browsers/test/src/chrome/launch.spec.ts +++ b/remote/test/puppeteer/packages/browsers/test/src/chrome/launch.spec.ts @@ -77,7 +77,6 @@ describe('Chrome', () => { '--disable-renderer-backgrounding', '--disable-sync', '--enable-automation', - '--enable-features=NetworkServiceInProcess2', '--export-tagged-pdf', '--force-color-profile=srgb', '--headless=new', diff --git a/remote/test/puppeteer/packages/browsers/test/src/chromium/launch.spec.ts b/remote/test/puppeteer/packages/browsers/test/src/chromium/launch.spec.ts index 8cf7c8255b..e382761981 100644 --- a/remote/test/puppeteer/packages/browsers/test/src/chromium/launch.spec.ts +++ b/remote/test/puppeteer/packages/browsers/test/src/chromium/launch.spec.ts @@ -77,7 +77,6 @@ describe('Chromium', () => { '--disable-renderer-backgrounding', '--disable-sync', '--enable-automation', - '--enable-features=NetworkServiceInProcess2', '--export-tagged-pdf', '--force-color-profile=srgb', '--headless=new', diff --git a/remote/test/puppeteer/packages/browsers/test/src/firefox/firefox-data.spec.ts b/remote/test/puppeteer/packages/browsers/test/src/firefox/firefox-data.spec.ts index b5dd2db0b3..22962b8a68 100644 --- a/remote/test/puppeteer/packages/browsers/test/src/firefox/firefox-data.spec.ts +++ b/remote/test/puppeteer/packages/browsers/test/src/firefox/firefox-data.spec.ts @@ -18,7 +18,7 @@ import { } from '../../../lib/cjs/browser-data/firefox.js'; describe('Firefox', () => { - it('should resolve download URLs', () => { + it('should resolve download URLs for Nightly', () => { assert.strictEqual( resolveDownloadUrl(BrowserPlatform.LINUX, '111.0a1'), 'https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/firefox-111.0a1.en-US.linux-x86_64.tar.bz2' @@ -41,6 +41,75 @@ describe('Firefox', () => { ); }); + it('should resolve download URLs for beta', () => { + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.LINUX, 'beta_115.0b8'), + 'https://archive.mozilla.org/pub/firefox/releases/115.0b8/linux-x86_64/en-US/firefox-115.0b8.tar.bz2' + ); + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.MAC, 'beta_115.0b8'), + 'https://archive.mozilla.org/pub/firefox/releases/115.0b8/mac/en-US/Firefox 115.0b8.dmg' + ); + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.MAC_ARM, 'beta_115.0b8'), + 'https://archive.mozilla.org/pub/firefox/releases/115.0b8/mac/en-US/Firefox 115.0b8.dmg' + ); + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.WIN32, 'beta_115.0b8'), + 'https://archive.mozilla.org/pub/firefox/releases/115.0b8/win32/en-US/Firefox Setup 115.0b8.exe' + ); + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.WIN64, 'beta_115.0b8'), + 'https://archive.mozilla.org/pub/firefox/releases/115.0b8/win64/en-US/Firefox Setup 115.0b8.exe' + ); + }); + + it('should resolve download URLs for stable', () => { + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.LINUX, 'stable_111.0.1'), + 'https://archive.mozilla.org/pub/firefox/releases/111.0.1/linux-x86_64/en-US/firefox-111.0.1.tar.bz2' + ); + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.MAC, 'stable_111.0.1'), + 'https://archive.mozilla.org/pub/firefox/releases/111.0.1/mac/en-US/Firefox 111.0.1.dmg' + ); + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.MAC_ARM, 'stable_111.0.1'), + 'https://archive.mozilla.org/pub/firefox/releases/111.0.1/mac/en-US/Firefox 111.0.1.dmg' + ); + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.WIN32, 'stable_111.0.1'), + 'https://archive.mozilla.org/pub/firefox/releases/111.0.1/win32/en-US/Firefox Setup 111.0.1.exe' + ); + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.WIN64, 'stable_111.0.1'), + 'https://archive.mozilla.org/pub/firefox/releases/111.0.1/win64/en-US/Firefox Setup 111.0.1.exe' + ); + }); + + it('should resolve download URLs for devedition', () => { + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.LINUX, 'devedition_115.0b8'), + 'https://archive.mozilla.org/pub/devedition/releases/115.0b8/linux-x86_64/en-US/firefox-115.0b8.tar.bz2' + ); + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.MAC, 'devedition_115.0b8'), + 'https://archive.mozilla.org/pub/devedition/releases/115.0b8/mac/en-US/Firefox 115.0b8.dmg' + ); + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.MAC_ARM, 'devedition_115.0b8'), + 'https://archive.mozilla.org/pub/devedition/releases/115.0b8/mac/en-US/Firefox 115.0b8.dmg' + ); + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.WIN32, 'devedition_115.0b8'), + 'https://archive.mozilla.org/pub/devedition/releases/115.0b8/win32/en-US/Firefox Setup 115.0b8.exe' + ); + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.WIN64, 'devedition_115.0b8'), + 'https://archive.mozilla.org/pub/devedition/releases/115.0b8/win64/en-US/Firefox Setup 115.0b8.exe' + ); + }); + it('should resolve executable paths', () => { assert.strictEqual( relativeExecutablePath(BrowserPlatform.LINUX, '111.0a1'), diff --git a/remote/test/puppeteer/packages/browsers/test/src/versions.ts b/remote/test/puppeteer/packages/browsers/test/src/versions.ts index 83f26ca41a..097973b82d 100644 --- a/remote/test/puppeteer/packages/browsers/test/src/versions.ts +++ b/remote/test/puppeteer/packages/browsers/test/src/versions.ts @@ -6,6 +6,6 @@ export const testChromeBuildId = '121.0.6167.85'; export const testChromiumBuildId = '1083080'; -export const testFirefoxBuildId = '125.0a1'; +export const testFirefoxBuildId = '126.0a1'; export const testChromeDriverBuildId = '121.0.6167.85'; export const testChromeHeadlessShellBuildId = '121.0.6167.85'; diff --git a/remote/test/puppeteer/packages/ng-schematics/.eslintignore b/remote/test/puppeteer/packages/ng-schematics/.eslintignore index 8424d7004d..c4c05af793 100644 --- a/remote/test/puppeteer/packages/ng-schematics/.eslintignore +++ b/remote/test/puppeteer/packages/ng-schematics/.eslintignore @@ -1,5 +1,5 @@ # Ignore File that will be copied to Angular /files/ -# Ignore sandbox enviroment +# Ignore sandbox environment ./sandbox/ diff --git a/remote/test/puppeteer/packages/ng-schematics/README.md b/remote/test/puppeteer/packages/ng-schematics/README.md index 975f74a704..2c84b7bc9e 100644 --- a/remote/test/puppeteer/packages/ng-schematics/README.md +++ b/remote/test/puppeteer/packages/ng-schematics/README.md @@ -85,7 +85,7 @@ node tools/smoke.mjs The schematics utilize `@angular-devkit/schematics/testing` for verifying correct file creation and `package.json` updates. To execute the test suit: -```bash +```bash npm2yarn npm run test ``` diff --git a/remote/test/puppeteer/packages/puppeteer-core/CHANGELOG.md b/remote/test/puppeteer/packages/puppeteer-core/CHANGELOG.md index 5076077c9f..eeaecef87d 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/CHANGELOG.md +++ b/remote/test/puppeteer/packages/puppeteer-core/CHANGELOG.md @@ -20,6 +20,104 @@ All notable changes to this project will be documented in this file. See [standa * dependencies * @puppeteer/browsers bumped from 1.5.1 to 1.6.0 +## [22.6.5](https://github.com/puppeteer/puppeteer/compare/puppeteer-core-v22.6.4...puppeteer-core-v22.6.5) (2024-04-15) + + +### Bug Fixes + +* remove NetworkServiceInProcess2 set by default ([#12261](https://github.com/puppeteer/puppeteer/issues/12261)) ([ff4f70f](https://github.com/puppeteer/puppeteer/commit/ff4f70f4ae7ca8deb0becbec2e49b35322dba336)), closes [#12257](https://github.com/puppeteer/puppeteer/issues/12257) +* use setImmediate to reduce flakiness when processing events ([#12264](https://github.com/puppeteer/puppeteer/issues/12264)) ([73403b3](https://github.com/puppeteer/puppeteer/commit/73403b323ec0dd8a08c164cb2c07751451215788)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @puppeteer/browsers bumped from 2.2.1 to 2.2.2 + +## [22.6.4](https://github.com/puppeteer/puppeteer/compare/puppeteer-core-v22.6.3...puppeteer-core-v22.6.4) (2024-04-11) + + +### Bug Fixes + +* **a11y:** query only unignored nodes ([#12224](https://github.com/puppeteer/puppeteer/issues/12224)) ([e20cd64](https://github.com/puppeteer/puppeteer/commit/e20cd64fff374c4113777912c193f4a5d7d04297)) +* retain stale main frame for longer ([#12225](https://github.com/puppeteer/puppeteer/issues/12225)) ([aa5b182](https://github.com/puppeteer/puppeteer/commit/aa5b1824a5c82005fcfc05b002facfbbb9810f8f)) +* roll to Chrome 123.0.6312.122 (r1262506) ([#12248](https://github.com/puppeteer/puppeteer/issues/12248)) ([50b6659](https://github.com/puppeteer/puppeteer/commit/50b66591e70a7b6907d86594d7dacee6e76afc2d)) +* **webdriver:** suppress error for error code errors ([5f7254c](https://github.com/puppeteer/puppeteer/commit/5f7254c41c7c1bda82477488f10254d204373d54)) + +## [22.6.3](https://github.com/puppeteer/puppeteer/compare/puppeteer-core-v22.6.2...puppeteer-core-v22.6.3) (2024-04-05) + + +### Bug Fixes + +* check if executablePath exists ([#12201](https://github.com/puppeteer/puppeteer/issues/12201)) ([4ec0280](https://github.com/puppeteer/puppeteer/commit/4ec02800801d441238d6160a933f88f98c5f7165)) +* roll to Chrome 123.0.6312.105 (r1262506) ([#12209](https://github.com/puppeteer/puppeteer/issues/12209)) ([ee31272](https://github.com/puppeteer/puppeteer/commit/ee312721152cce61a9e9cb2b78b71b40c4fa9e64)) +* wait for fonts before pdf printing ([#12175](https://github.com/puppeteer/puppeteer/issues/12175)) ([59bffce](https://github.com/puppeteer/puppeteer/commit/59bffce9720b4d5e5204b26b335735e0a5ca9cc1)) +* **webdriver:** request redirect chain ([#12168](https://github.com/puppeteer/puppeteer/issues/12168)) ([d345055](https://github.com/puppeteer/puppeteer/commit/d345055af3c63effbdfb2751274b9d7137b8a308)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @puppeteer/browsers bumped from 2.2.0 to 2.2.1 + +## [22.6.2](https://github.com/puppeteer/puppeteer/compare/puppeteer-core-v22.6.1...puppeteer-core-v22.6.2) (2024-03-28) + + +### Bug Fixes + +* roll to Chrome 123.0.6312.86 (r1262506) ([#12156](https://github.com/puppeteer/puppeteer/issues/12156)) ([29637f2](https://github.com/puppeteer/puppeteer/commit/29637f2b8f2dc1d684dbbb62d1a75857e016be33)) + +## [22.6.1](https://github.com/puppeteer/puppeteer/compare/puppeteer-core-v22.6.0...puppeteer-core-v22.6.1) (2024-03-25) + + +### Bug Fixes + +* apply timeout to waiting for a response ([#12142](https://github.com/puppeteer/puppeteer/issues/12142)) ([ac1767d](https://github.com/puppeteer/puppeteer/commit/ac1767da0b4214ced548a62dd737e2863f92c715)) +* reload should not resolve early on fragment navigations ([#12119](https://github.com/puppeteer/puppeteer/issues/12119)) ([d476031](https://github.com/puppeteer/puppeteer/commit/d4760317c9bd359c9ecdb5f36231449dae16a8d2)) +* support clip in ElementHandle.screenshot ([#12115](https://github.com/puppeteer/puppeteer/issues/12115)) ([b096ffa](https://github.com/puppeteer/puppeteer/commit/b096ffaa0359078bd5748b53b67e87c9453c7196)) + +## [22.6.0](https://github.com/puppeteer/puppeteer/compare/puppeteer-core-v22.5.0...puppeteer-core-v22.6.0) (2024-03-20) + + +### Features + +* roll to Chrome 123.0.6312.58 (r1262506) ([#12110](https://github.com/puppeteer/puppeteer/issues/12110)) ([6f5b3bc](https://github.com/puppeteer/puppeteer/commit/6f5b3bc9b88c6d3204dda396f8963591ea6eb883)) + + +### Bug Fixes + +* **webdriver:** emit RequestServedFromCache for requests ([#12104](https://github.com/puppeteer/puppeteer/issues/12104)) ([6ba6bef](https://github.com/puppeteer/puppeteer/commit/6ba6bef1b99742543942cef2f6c840bd543f5dee)) + +## [22.5.0](https://github.com/puppeteer/puppeteer/compare/puppeteer-core-v22.4.1...puppeteer-core-v22.5.0) (2024-03-15) + + +### Features + +* deprecate `Frame.prototype.name` ([#12084](https://github.com/puppeteer/puppeteer/issues/12084)) ([0203b45](https://github.com/puppeteer/puppeteer/commit/0203b4533dfec503f9ce7fcd07c3493021a9cecb)) + + +### Bug Fixes + +* fix keyboard.sendCharacter ([#12088](https://github.com/puppeteer/puppeteer/issues/12088)) ([2637622](https://github.com/puppeteer/puppeteer/commit/26376224d557ce30c911f670c5e7625dd1a1df72)) +* roll to Chrome 122.0.6261.128 (r1250580) ([#12078](https://github.com/puppeteer/puppeteer/issues/12078)) ([ef7a9ea](https://github.com/puppeteer/puppeteer/commit/ef7a9eac16dcb466b220bcb0bc06a1eac3492354)) +* **webdriver:** emit CDP events ([#12058](https://github.com/puppeteer/puppeteer/issues/12058)) ([9afe424](https://github.com/puppeteer/puppeteer/commit/9afe4246bb58c30a13215a254f9326935b24ece3)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @puppeteer/browsers bumped from 2.1.0 to 2.2.0 + +## [22.4.1](https://github.com/puppeteer/puppeteer/compare/puppeteer-core-v22.4.0...puppeteer-core-v22.4.1) (2024-03-08) + + +### Bug Fixes + +* roll to Chrome 122.0.6261.111 (r1250580) ([#12055](https://github.com/puppeteer/puppeteer/issues/12055)) ([9b31bca](https://github.com/puppeteer/puppeteer/commit/9b31bca01adeb2ce04c97d9fcb3c6b6461469f07)) + ## [22.4.0](https://github.com/puppeteer/puppeteer/compare/puppeteer-core-v22.3.0...puppeteer-core-v22.4.0) (2024-03-05) diff --git a/remote/test/puppeteer/packages/puppeteer-core/Herebyfile.mjs b/remote/test/puppeteer/packages/puppeteer-core/Herebyfile.mjs index 972a080ba0..426d908c0c 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/Herebyfile.mjs +++ b/remote/test/puppeteer/packages/puppeteer-core/Herebyfile.mjs @@ -110,8 +110,10 @@ export const buildTask = task({ bundle: true, allowOverwrite: true, format, - target: 'node16', - minify: true, + target: 'node18', + // Do not minify for readability and leave minification to + // consumers. + minify: false, legalComments: 'inline', }) ); diff --git a/remote/test/puppeteer/packages/puppeteer-core/package.json b/remote/test/puppeteer/packages/puppeteer-core/package.json index 1d4d564c4f..fe0eedbe24 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/package.json +++ b/remote/test/puppeteer/packages/puppeteer-core/package.json @@ -1,6 +1,6 @@ { "name": "puppeteer-core", - "version": "22.4.0", + "version": "22.6.5", "description": "A high-level API to control headless Chrome over the DevTools Protocol", "keywords": [ "puppeteer", @@ -119,11 +119,10 @@ "author": "The Chromium Authors", "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "2.1.0", - "chromium-bidi": "0.5.12", - "cross-fetch": "4.0.0", + "@puppeteer/browsers": "2.2.2", + "chromium-bidi": "0.5.17", "debug": "4.3.4", - "devtools-protocol": "0.0.1249869", + "devtools-protocol": "0.0.1262051", "ws": "8.16.0" }, "devDependencies": { diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/api/Browser.ts b/remote/test/puppeteer/packages/puppeteer-core/src/api/Browser.ts index 6d7ea19d49..67835e4aa2 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/api/Browser.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/api/Browser.ts @@ -197,7 +197,7 @@ export interface DebugInfo { * - connected to via {@link Puppeteer.connect} or * - launched by {@link PuppeteerNode.launch}. * - * {@link Browser} {@link EventEmitter | emits} various events which are + * {@link Browser} {@link EventEmitter.emit | emits} various events which are * documented in the {@link BrowserEvent} enum. * * @example Using a {@link Browser} to create a {@link Page}: diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/api/BrowserContext.ts b/remote/test/puppeteer/packages/puppeteer-core/src/api/BrowserContext.ts index 5e6a5d5d5c..7cedd5f3a4 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/api/BrowserContext.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/api/BrowserContext.ts @@ -151,7 +151,7 @@ export abstract class BrowserContext extends EventEmitter<BrowserContextEvents> * * @deprecated In Chrome, the * {@link Browser.defaultBrowserContext | default browser context} can also be - * "icognito" if configured via the arguments and in such cases this getter + * "incognito" if configured via the arguments and in such cases this getter * returns wrong results (see * https://github.com/puppeteer/puppeteer/issues/8836). Also, the term * "incognito" is not applicable to other browsers. To migrate, check the diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/api/CDPSession.ts b/remote/test/puppeteer/packages/puppeteer-core/src/api/CDPSession.ts index 3a1fdf1e24..d80b9d9013 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/api/CDPSession.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/api/CDPSession.ts @@ -74,7 +74,7 @@ export interface CommandOptions { * @example * * ```ts - * const client = await page.target().createCDPSession(); + * const client = await page.createCDPSession(); * await client.send('Animation.enable'); * client.on('Animation.animationCreated', () => * console.log('Animation created!') diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/api/ElementHandle.ts b/remote/test/puppeteer/packages/puppeteer-core/src/api/ElementHandle.ts index 9b1326f998..84e61f33c1 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/api/ElementHandle.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/api/ElementHandle.ts @@ -623,7 +623,7 @@ export abstract class ElementHandle< /** * This method scrolls element into view if needed, and then - * uses {@link Page} to hover over the center of the element. + * uses {@link Page.mouse} to hover over the center of the element. * If the element is detached from DOM, the method throws an error. */ @throwIfDisposed() @@ -636,7 +636,7 @@ export abstract class ElementHandle< /** * This method scrolls element into view if needed, and then - * uses {@link Page | Page.mouse} to click in the center of the element. + * uses {@link Page.mouse} to click in the center of the element. * If the element is detached from DOM, the method throws an error. */ @throwIfDisposed() @@ -1236,9 +1236,9 @@ export abstract class ElementHandle< this: ElementHandle<Element>, options: Readonly<ElementScreenshotOptions> = {} ): Promise<string | Buffer> { - const {scrollIntoView = true} = options; + const {scrollIntoView = true, clip} = options; - let clip = await this.#nonEmptyVisibleBoundingBox(); + let elementClip = await this.#nonEmptyVisibleBoundingBox(); const page = this.frame.page(); @@ -1247,7 +1247,7 @@ export abstract class ElementHandle< await this.scrollIntoViewIfNeeded(); // We measure again just in case. - clip = await this.#nonEmptyVisibleBoundingBox(); + elementClip = await this.#nonEmptyVisibleBoundingBox(); } const [pageLeft, pageTop] = await this.evaluate(() => { @@ -1259,10 +1259,16 @@ export abstract class ElementHandle< window.visualViewport.pageTop, ] as const; }); - clip.x += pageLeft; - clip.y += pageTop; + elementClip.x += pageLeft; + elementClip.y += pageTop; + if (clip) { + elementClip.x += clip.x; + elementClip.y += clip.y; + elementClip.height = clip.height; + elementClip.width = clip.width; + } - return await page.screenshot({...options, clip}); + return await page.screenshot({...options, clip: elementClip}); } async #nonEmptyVisibleBoundingBox() { diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/api/Frame.ts b/remote/test/puppeteer/packages/puppeteer-core/src/api/Frame.ts index 19b5eb7fa0..ff476cd054 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/api/Frame.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/api/Frame.ts @@ -18,7 +18,6 @@ import type {PuppeteerLifeCycleEvent} from '../cdp/LifecycleWatcher.js'; import {EventEmitter, type EventType} from '../common/EventEmitter.js'; import {getQueryHandlerAndSelector} from '../common/GetQueryHandler.js'; import {transposeIterableHandle} from '../common/HandleIterator.js'; -import {LazyArg} from '../common/LazyArg.js'; import type { Awaitable, EvaluateFunc, @@ -63,6 +62,10 @@ export interface WaitForOptions { * @defaultValue `'load'` */ waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[]; + /** + * @internal + */ + ignoreSameDocumentNavigation?: boolean; } /** @@ -405,7 +408,7 @@ export abstract class Frame extends EventEmitter<FrameEvents> { } /** - * @internal + * @returns The frame element associated with this frame (if any). */ @throwIfDetached async frameElement(): Promise<HandleFor<HTMLIFrameElement> | null> { @@ -447,7 +450,7 @@ export abstract class Frame extends EventEmitter<FrameEvents> { } /** - * Behaves identically to {@link Page.evaluate} except it's run within the + * Behaves identically to {@link Page.evaluate} except it's run within * the context of this frame. * * @see {@link Page.evaluate} for details. @@ -760,6 +763,13 @@ export abstract class Frame extends EventEmitter<FrameEvents> { * @remarks * This value is calculated once when the frame is created, and will not * update if the attribute is changed later. + * + * @deprecated Use + * + * ```ts + * const element = await frame.frameElement(); + * const nameOrId = await element.evaluate(frame => frame.name ?? frame.id); + * ``` */ name(): string { return this._name || ''; @@ -830,42 +840,37 @@ export abstract class Frame extends EventEmitter<FrameEvents> { return await this.mainRealm().transferHandle( await this.isolatedRealm().evaluateHandle( - async ({Deferred}, {url, id, type, content}) => { - const deferred = Deferred.create<void>(); - const script = document.createElement('script'); - script.type = type; - script.text = content; - if (url) { - script.src = url; - script.addEventListener( - 'load', - () => { - return deferred.resolve(); - }, - {once: true} - ); + async ({url, id, type, content}) => { + return await new Promise<HTMLScriptElement>((resolve, reject) => { + const script = document.createElement('script'); + script.type = type; + script.text = content; script.addEventListener( 'error', event => { - deferred.reject( - new Error(event.message ?? 'Could not load script') - ); + reject(new Error(event.message ?? 'Could not load script')); }, {once: true} ); - } else { - deferred.resolve(); - } - if (id) { - script.id = id; - } - document.head.appendChild(script); - await deferred.valueOrThrow(); - return script; + if (id) { + script.id = id; + } + if (url) { + script.src = url; + script.addEventListener( + 'load', + () => { + resolve(script); + }, + {once: true} + ); + document.head.appendChild(script); + } else { + document.head.appendChild(script); + resolve(script); + } + }); }, - LazyArg.create(context => { - return context.puppeteerUtil; - }), {...options, type, content} ) ); @@ -915,46 +920,42 @@ export abstract class Frame extends EventEmitter<FrameEvents> { } return await this.mainRealm().transferHandle( - await this.isolatedRealm().evaluateHandle( - async ({Deferred}, {url, content}) => { - const deferred = Deferred.create<void>(); - let element: HTMLStyleElement | HTMLLinkElement; - if (!url) { - element = document.createElement('style'); - element.appendChild(document.createTextNode(content!)); - } else { - const link = document.createElement('link'); - link.rel = 'stylesheet'; - link.href = url; - element = link; + await this.isolatedRealm().evaluateHandle(async ({url, content}) => { + return await new Promise<HTMLStyleElement | HTMLLinkElement>( + (resolve, reject) => { + let element: HTMLStyleElement | HTMLLinkElement; + if (!url) { + element = document.createElement('style'); + element.appendChild(document.createTextNode(content!)); + } else { + const link = document.createElement('link'); + link.rel = 'stylesheet'; + link.href = url; + element = link; + } + element.addEventListener( + 'load', + () => { + resolve(element); + }, + {once: true} + ); + element.addEventListener( + 'error', + event => { + reject( + new Error( + (event as ErrorEvent).message ?? 'Could not load style' + ) + ); + }, + {once: true} + ); + document.head.appendChild(element); + return element; } - element.addEventListener( - 'load', - () => { - deferred.resolve(); - }, - {once: true} - ); - element.addEventListener( - 'error', - event => { - deferred.reject( - new Error( - (event as ErrorEvent).message ?? 'Could not load style' - ) - ); - }, - {once: true} - ); - document.head.appendChild(element); - await deferred.valueOrThrow(); - return element; - }, - LazyArg.create(context => { - return context.puppeteerUtil; - }), - options - ) + ); + }, options) ); } diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/api/HTTPRequest.ts b/remote/test/puppeteer/packages/puppeteer-core/src/api/HTTPRequest.ts index d72f088686..674abc61f2 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/api/HTTPRequest.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/api/HTTPRequest.ts @@ -5,6 +5,10 @@ */ import type {Protocol} from 'devtools-protocol'; +import type {ProtocolError} from '../common/Errors.js'; +import {debugError} from '../common/util.js'; +import {assert} from '../util/assert.js'; + import type {CDPSession} from './CDPSession.js'; import type {Frame} from './Frame.js'; import type {HTTPResponse} from './HTTPResponse.js'; @@ -118,6 +122,29 @@ export abstract class HTTPRequest { _redirectChain: HTTPRequest[] = []; /** + * @internal + */ + protected interception: { + enabled: boolean; + handled: boolean; + handlers: Array<() => void | PromiseLike<any>>; + resolutionState: InterceptResolutionState; + requestOverrides: ContinueRequestOverrides; + response: Partial<ResponseForRequest> | null; + abortReason: Protocol.Network.ErrorReason | null; + } = { + enabled: false, + handled: false, + handlers: [], + resolutionState: { + action: InterceptResolutionAction.None, + }, + requestOverrides: {}, + response: null, + abortReason: null, + }; + + /** * Warning! Using this client can break Puppeteer. Use with caution. * * @experimental @@ -139,18 +166,27 @@ export abstract class HTTPRequest { * if the interception is allowed to continue (ie, `abort()` and * `respond()` aren't called). */ - abstract continueRequestOverrides(): ContinueRequestOverrides; + continueRequestOverrides(): ContinueRequestOverrides { + assert(this.interception.enabled, 'Request Interception is not enabled!'); + return this.interception.requestOverrides; + } /** * The `ResponseForRequest` that gets used if the * interception is allowed to respond (ie, `abort()` is not called). */ - abstract responseForRequest(): Partial<ResponseForRequest> | null; + responseForRequest(): Partial<ResponseForRequest> | null { + assert(this.interception.enabled, 'Request Interception is not enabled!'); + return this.interception.response; + } /** * The most recent reason for aborting the request */ - abstract abortErrorReason(): Protocol.Network.ErrorReason | null; + abortErrorReason(): Protocol.Network.ErrorReason | null { + assert(this.interception.enabled, 'Request Interception is not enabled!'); + return this.interception.abortReason; + } /** * An InterceptResolutionState object describing the current resolution @@ -163,13 +199,23 @@ export abstract class HTTPRequest { * InterceptResolutionAction is one of: `abort`, `respond`, `continue`, * `disabled`, `none`, or `already-handled`. */ - abstract interceptResolutionState(): InterceptResolutionState; + interceptResolutionState(): InterceptResolutionState { + if (!this.interception.enabled) { + return {action: InterceptResolutionAction.Disabled}; + } + if (this.interception.handled) { + return {action: InterceptResolutionAction.AlreadyHandled}; + } + return {...this.interception.resolutionState}; + } /** * Is `true` if the intercept resolution has already been handled, * `false` otherwise. */ - abstract isInterceptResolutionHandled(): boolean; + isInterceptResolutionHandled(): boolean { + return this.interception.handled; + } /** * Adds an async request handler to the processing queue. @@ -177,15 +223,51 @@ export abstract class HTTPRequest { * but they are guaranteed to resolve before the request interception * is finalized. */ - abstract enqueueInterceptAction( + enqueueInterceptAction( pendingHandler: () => void | PromiseLike<unknown> - ): void; + ): void { + this.interception.handlers.push(pendingHandler); + } + + /** + * @internal + */ + abstract _abort( + errorReason: Protocol.Network.ErrorReason | null + ): Promise<void>; + + /** + * @internal + */ + abstract _respond(response: Partial<ResponseForRequest>): Promise<void>; + + /** + * @internal + */ + abstract _continue(overrides: ContinueRequestOverrides): Promise<void>; /** * Awaits pending interception handlers and then decides how to fulfill * the request interception. */ - abstract finalizeInterceptions(): Promise<void>; + async finalizeInterceptions(): Promise<void> { + await this.interception.handlers.reduce((promiseChain, interceptAction) => { + return promiseChain.then(interceptAction); + }, Promise.resolve()); + this.interception.handlers = []; // TODO: verify this is correct top let gc run + const {action} = this.interceptResolutionState(); + switch (action) { + case 'abort': + return await this._abort(this.interception.abortReason); + case 'respond': + if (this.interception.response === null) { + throw new Error('Response is missing for the interception'); + } + return await this._respond(this.interception.response); + case 'continue': + return await this._continue(this.interception.requestOverrides); + } + } /** * Contains the request's resource type as it was perceived by the rendering @@ -323,10 +405,42 @@ export abstract class HTTPRequest { * * Exception is immediately thrown if the request interception is not enabled. */ - abstract continue( - overrides?: ContinueRequestOverrides, + async continue( + overrides: ContinueRequestOverrides = {}, priority?: number - ): Promise<void>; + ): Promise<void> { + // Request interception is not supported for data: urls. + if (this.url().startsWith('data:')) { + return; + } + assert(this.interception.enabled, 'Request Interception is not enabled!'); + assert(!this.interception.handled, 'Request is already handled!'); + if (priority === undefined) { + return await this._continue(overrides); + } + this.interception.requestOverrides = overrides; + if ( + this.interception.resolutionState.priority === undefined || + priority > this.interception.resolutionState.priority + ) { + this.interception.resolutionState = { + action: InterceptResolutionAction.Continue, + priority, + }; + return; + } + if (priority === this.interception.resolutionState.priority) { + if ( + this.interception.resolutionState.action === 'abort' || + this.interception.resolutionState.action === 'respond' + ) { + return; + } + this.interception.resolutionState.action = + InterceptResolutionAction.Continue; + } + return; + } /** * Fulfills a request with the given response. @@ -360,10 +474,38 @@ export abstract class HTTPRequest { * * Exception is immediately thrown if the request interception is not enabled. */ - abstract respond( + async respond( response: Partial<ResponseForRequest>, priority?: number - ): Promise<void>; + ): Promise<void> { + // Mocking responses for dataURL requests is not currently supported. + if (this.url().startsWith('data:')) { + return; + } + assert(this.interception.enabled, 'Request Interception is not enabled!'); + assert(!this.interception.handled, 'Request is already handled!'); + if (priority === undefined) { + return await this._respond(response); + } + this.interception.response = response; + if ( + this.interception.resolutionState.priority === undefined || + priority > this.interception.resolutionState.priority + ) { + this.interception.resolutionState = { + action: InterceptResolutionAction.Respond, + priority, + }; + return; + } + if (priority === this.interception.resolutionState.priority) { + if (this.interception.resolutionState.action === 'abort') { + return; + } + this.interception.resolutionState.action = + InterceptResolutionAction.Respond; + } + } /** * Aborts a request. @@ -379,7 +521,33 @@ export abstract class HTTPRequest { * {@link Page.setRequestInterception}. If it is not enabled, this method will * throw an exception immediately. */ - abstract abort(errorCode?: ErrorCode, priority?: number): Promise<void>; + async abort( + errorCode: ErrorCode = 'failed', + priority?: number + ): Promise<void> { + // Request interception is not supported for data: urls. + if (this.url().startsWith('data:')) { + return; + } + const errorReason = errorReasons[errorCode]; + assert(errorReason, 'Unknown error code: ' + errorCode); + assert(this.interception.enabled, 'Request Interception is not enabled!'); + assert(!this.interception.handled, 'Request is already handled!'); + if (priority === undefined) { + return await this._abort(errorReason); + } + this.interception.abortReason = errorReason; + if ( + this.interception.resolutionState.priority === undefined || + priority >= this.interception.resolutionState.priority + ) { + this.interception.resolutionState = { + action: InterceptResolutionAction.Abort, + priority, + }; + return; + } + } } /** @@ -513,3 +681,33 @@ export const STATUS_TEXTS: Record<string, string> = { '510': 'Not Extended', '511': 'Network Authentication Required', } as const; + +const errorReasons: Record<ErrorCode, Protocol.Network.ErrorReason> = { + aborted: 'Aborted', + accessdenied: 'AccessDenied', + addressunreachable: 'AddressUnreachable', + blockedbyclient: 'BlockedByClient', + blockedbyresponse: 'BlockedByResponse', + connectionaborted: 'ConnectionAborted', + connectionclosed: 'ConnectionClosed', + connectionfailed: 'ConnectionFailed', + connectionrefused: 'ConnectionRefused', + connectionreset: 'ConnectionReset', + internetdisconnected: 'InternetDisconnected', + namenotresolved: 'NameNotResolved', + timedout: 'TimedOut', + failed: 'Failed', +} as const; + +/** + * @internal + */ +export function handleError(error: ProtocolError): void { + if (error.originalMessage.includes('Invalid header')) { + throw error; + } + // In certain cases, protocol will return error if the request was + // already canceled or the page was closed. We should tolerate these + // errors. + debugError(error); +} diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/api/HTTPResponse.ts b/remote/test/puppeteer/packages/puppeteer-core/src/api/HTTPResponse.ts index 906479eb43..f5ea35722e 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/api/HTTPResponse.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/api/HTTPResponse.ts @@ -81,11 +81,18 @@ export abstract class HTTPResponse { /** * Promise which resolves to a buffer with response body. + * + * @remarks + * + * The buffer might be re-encoded by the browser + * based on HTTP-headers or other heuristics. If the browser + * failed to detect the correct encoding, the buffer might + * be encoded incorrectly. See https://github.com/puppeteer/puppeteer/issues/6478. */ abstract buffer(): Promise<Buffer>; /** - * Promise which resolves to a text representation of response body. + * Promise which resolves to a text (utf8) representation of response body. */ async text(): Promise<string> { const content = await this.buffer(); diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/api/Page.ts b/remote/test/puppeteer/packages/puppeteer-core/src/api/Page.ts index b094d14b2f..1c1874a856 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/api/Page.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/api/Page.ts @@ -32,7 +32,7 @@ import type {HTTPResponse} from '../api/HTTPResponse.js'; import type {Accessibility} from '../cdp/Accessibility.js'; import type {Coverage} from '../cdp/Coverage.js'; import type {DeviceRequestPrompt} from '../cdp/DeviceRequestPrompt.js'; -import type {Credentials, NetworkConditions} from '../cdp/NetworkManager.js'; +import type {NetworkConditions} from '../cdp/NetworkManager.js'; import type {Tracing} from '../cdp/Tracing.js'; import type {ConsoleMessage} from '../common/ConsoleMessage.js'; import type { @@ -134,6 +134,14 @@ export interface Metrics { /** * @public */ +export interface Credentials { + username: string; + password: string; +} + +/** + * @public + */ export interface WaitForNetworkIdleOptions extends WaitTimeoutOptions { /** * Time (in milliseconds) the network should be idle. @@ -274,7 +282,7 @@ export interface ScreenshotOptions { */ path?: string; /** - * Specifies the region of the page to clip. + * Specifies the region of the page/element to clip. */ clip?: ScreenshotClip; /** @@ -644,7 +652,7 @@ export abstract class Page extends EventEmitter<PageEvents> { * * @deprecated We no longer support intercepting drag payloads. Use the new * drag APIs found on {@link ElementHandle} to drag (or just use the - * {@link Page | Page.mouse}). + * {@link Page.mouse}). */ abstract isDragInterceptionEnabled(): boolean; @@ -875,7 +883,7 @@ export abstract class Page extends EventEmitter<PageEvents> { * * @deprecated We no longer support intercepting drag payloads. Use the new * drag APIs found on {@link ElementHandle} to drag (or just use the - * {@link Page | Page.mouse}). + * {@link Page.mouse}). */ abstract setDragInterception(enabled: boolean): Promise<void>; @@ -1342,7 +1350,7 @@ export abstract class Page extends EventEmitter<PageEvents> { * * Functions installed via `page.exposeFunction` survive navigations. * - * :::note + * ::: * * @example * An example of adding an `md5` function into the page: @@ -2116,7 +2124,8 @@ export abstract class Page extends EventEmitter<PageEvents> { * * This is either the viewport set with the previous {@link Page.setViewport} * call or the default viewport set via - * {@link BrowserConnectOptions | BrowserConnectOptions.defaultViewport}. + * {@link BrowserConnectOptions.defaultViewport | + * BrowserConnectOptions.defaultViewport}. */ abstract viewport(): Viewport | null; @@ -2458,7 +2467,7 @@ export abstract class Page extends EventEmitter<PageEvents> { }; if (options.type === undefined && options.path !== undefined) { const filePath = options.path; - // Note we cannot use Node.js here due to browser compatability. + // Note we cannot use Node.js here due to browser compatibility. const extension = filePath .slice(filePath.lastIndexOf('.') + 1) .toLowerCase(); @@ -2609,7 +2618,7 @@ export abstract class Page extends EventEmitter<PageEvents> { /** * This method fetches an element with `selector`, scrolls it into view if - * needed, and then uses {@link Page | Page.mouse} to click in the center of the + * needed, and then uses {@link Page.mouse} to click in the center of the * element. If there's no element matching `selector`, the method throws an * error. * @@ -2660,7 +2669,7 @@ export abstract class Page extends EventEmitter<PageEvents> { /** * This method fetches an element with `selector`, scrolls it into view if - * needed, and then uses {@link Page | Page.mouse} + * needed, and then uses {@link Page.mouse} * to hover over the center of the element. * If there's no element matching `selector`, the method throws an error. * @param selector - A @@ -2709,7 +2718,7 @@ export abstract class Page extends EventEmitter<PageEvents> { /** * This method fetches an element with `selector`, scrolls it into view if - * needed, and then uses {@link Page | Page.touchscreen} + * needed, and then uses {@link Page.touchscreen} * to tap in the center of the element. * If there's no element matching `selector`, the method throws an error. * @param selector - A diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/api/locators/locators.ts b/remote/test/puppeteer/packages/puppeteer-core/src/api/locators/locators.ts index d88cc0a17d..ea77dc94a1 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/api/locators/locators.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/api/locators/locators.ts @@ -172,19 +172,23 @@ export abstract class Locator<T> extends EventEmitter<LocatorEvents> { }); }, retryAndRaceWithSignalAndTimer: <T>( - signal?: AbortSignal + signal?: AbortSignal, + cause?: Error ): OperatorFunction<T, T> => { const candidates = []; if (signal) { candidates.push( fromEvent(signal, 'abort').pipe( map(() => { + if (signal.reason instanceof Error) { + signal.reason.cause = cause; + } throw signal.reason; }) ) ); } - candidates.push(timeout(this._timeout)); + candidates.push(timeout(this._timeout, cause)); return pipe( retry({delay: RETRY_DELAY}), raceWith<T, never[]>(...candidates) @@ -368,6 +372,7 @@ export abstract class Locator<T> extends EventEmitter<LocatorEvents> { options?: Readonly<LocatorClickOptions> ): Observable<void> { const signal = options?.signal; + const cause = new Error('Locator.click'); return this._wait(options).pipe( this.operators.conditions( [ @@ -388,7 +393,7 @@ export abstract class Locator<T> extends EventEmitter<LocatorEvents> { }) ); }), - this.operators.retryAndRaceWithSignalAndTimer(signal) + this.operators.retryAndRaceWithSignalAndTimer(signal, cause) ); } @@ -398,6 +403,7 @@ export abstract class Locator<T> extends EventEmitter<LocatorEvents> { options?: Readonly<ActionOptions> ): Observable<void> { const signal = options?.signal; + const cause = new Error('Locator.fill'); return this._wait(options).pipe( this.operators.conditions( [ @@ -521,7 +527,7 @@ export abstract class Locator<T> extends EventEmitter<LocatorEvents> { }) ); }), - this.operators.retryAndRaceWithSignalAndTimer(signal) + this.operators.retryAndRaceWithSignalAndTimer(signal, cause) ); } @@ -530,6 +536,7 @@ export abstract class Locator<T> extends EventEmitter<LocatorEvents> { options?: Readonly<ActionOptions> ): Observable<void> { const signal = options?.signal; + const cause = new Error('Locator.hover'); return this._wait(options).pipe( this.operators.conditions( [ @@ -549,7 +556,7 @@ export abstract class Locator<T> extends EventEmitter<LocatorEvents> { }) ); }), - this.operators.retryAndRaceWithSignalAndTimer(signal) + this.operators.retryAndRaceWithSignalAndTimer(signal, cause) ); } @@ -558,6 +565,7 @@ export abstract class Locator<T> extends EventEmitter<LocatorEvents> { options?: Readonly<LocatorScrollOptions> ): Observable<void> { const signal = options?.signal; + const cause = new Error('Locator.scroll'); return this._wait(options).pipe( this.operators.conditions( [ @@ -590,7 +598,7 @@ export abstract class Locator<T> extends EventEmitter<LocatorEvents> { }) ); }), - this.operators.retryAndRaceWithSignalAndTimer(signal) + this.operators.retryAndRaceWithSignalAndTimer(signal, cause) ); } @@ -617,9 +625,10 @@ export abstract class Locator<T> extends EventEmitter<LocatorEvents> { * @public */ async waitHandle(options?: Readonly<ActionOptions>): Promise<HandleFor<T>> { + const cause = new Error('Locator.waitHandle'); return await firstValueFrom( this._wait(options).pipe( - this.operators.retryAndRaceWithSignalAndTimer(options?.signal) + this.operators.retryAndRaceWithSignalAndTimer(options?.signal, cause) ) ); } diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/BidiOverCdp.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/BidiOverCdp.ts index ace35a52b0..6f58d3e6a9 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/BidiOverCdp.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/BidiOverCdp.ts @@ -25,9 +25,7 @@ const bidiServerLogger = (prefix: string, ...args: unknown[]): void => { */ export async function connectBidiOverCdp( cdp: CdpConnection, - // TODO: replace with `BidiMapper.MapperOptions`, once it's exported in - // https://github.com/puppeteer/puppeteer/pull/11415. - options: {acceptInsecureCerts: boolean} + options: BidiMapper.MapperOptions ): Promise<BidiConnection> { const transportBiDi = new NoOpTransport(); const cdpConnectionAdapter = new CdpConnectionAdapter(cdp); diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Browser.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Browser.ts index 8798d8325d..2e6b80459b 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Browser.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Browser.ts @@ -19,7 +19,6 @@ import { import {BrowserContextEvent} from '../api/BrowserContext.js'; import type {Page} from '../api/Page.js'; import type {Target} from '../api/Target.js'; -import {UnsupportedOperation} from '../common/Errors.js'; import {EventEmitter} from '../common/EventEmitter.js'; import {debugError} from '../common/util.js'; import type {Viewport} from '../common/Viewport.js'; @@ -50,7 +49,7 @@ export class BidiBrowser extends Browser { readonly protocol = 'webDriverBiDi'; // TODO: Update generator to include fully module - static readonly subscribeModules: string[] = [ + static readonly subscribeModules: [string, ...string[]] = [ 'browsingContext', 'network', 'log', @@ -133,8 +132,8 @@ export class BidiBrowser extends Browser { return !this.#browserName.toLocaleLowerCase().includes('firefox'); } - override userAgent(): never { - throw new UnsupportedOperation(); + override async userAgent(): Promise<string> { + return this.#browserCore.session.capabilities.userAgent; } #createBrowserContext(userContext: UserContext) { diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/CDPSession.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/CDPSession.ts index 1e0c503498..5782056259 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/CDPSession.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/CDPSession.ts @@ -5,6 +5,7 @@ */ import type ProtocolMapping from 'devtools-protocol/types/protocol-mapping.js'; +import type {CommandOptions} from '../api/CDPSession.js'; import {CDPSession} from '../api/CDPSession.js'; import type {Connection as CdpConnection} from '../cdp/Connection.js'; import {TargetCloseError, UnsupportedOperation} from '../common/Errors.js'; @@ -61,7 +62,8 @@ export class BidiCdpSession extends CDPSession { override async send<T extends keyof ProtocolMapping.Commands>( method: T, - params?: ProtocolMapping.Commands[T]['paramsType'][0] + params?: ProtocolMapping.Commands[T]['paramsType'][0], + options?: CommandOptions ): Promise<ProtocolMapping.Commands[T]['returnType']> { if (this.#connection === undefined) { throw new UnsupportedOperation( @@ -74,11 +76,15 @@ export class BidiCdpSession extends CDPSession { ); } const session = await this.#sessionId.valueOrThrow(); - const {result} = await this.#connection.send('cdp.sendCommand', { - method: method, - params: params, - session, - }); + const {result} = await this.#connection.send( + 'cdp.sendCommand', + { + method: method, + params: params, + session, + }, + options?.timeout + ); return result.result; } diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Connection.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Connection.ts index dd688c309a..5e19390371 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Connection.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Connection.ts @@ -97,11 +97,12 @@ export class BidiConnection send<T extends keyof Commands>( method: T, - params: Commands[T]['params'] + params: Commands[T]['params'], + timeout?: number ): Promise<{result: Commands[T]['returnType']}> { assert(!this.#closed, 'Protocol error: Connection closed.'); - return this.#callbacks.create(method, this.#timeout, id => { + return this.#callbacks.create(method, timeout ?? this.#timeout, id => { const stringifiedMessage = JSON.stringify({ id, method, diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Frame.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Frame.ts index f2bfd5f64e..ce44c0637d 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Frame.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Frame.ts @@ -37,6 +37,7 @@ import {TargetCloseError, UnsupportedOperation} from '../common/Errors.js'; import type {TimeoutSettings} from '../common/TimeoutSettings.js'; import type {Awaitable, NodeFor} from '../common/types.js'; import {debugError, fromEmitterEvent, timeout} from '../common/util.js'; +import {isErrorLike} from '../util/ErrorLike.js'; import {BidiCdpSession} from './CDPSession.js'; import type {BrowsingContext} from './core/BrowsingContext.js'; @@ -114,13 +115,13 @@ export class BidiFrame extends Frame { this.browsingContext.on('request', ({request}) => { const httpRequest = BidiHTTPRequest.from(request, this); request.once('success', () => { - // SAFETY: BidiHTTPRequest will create this before here. this.page().trustedEmitter.emit(PageEvent.RequestFinished, httpRequest); }); request.once('error', () => { this.page().trustedEmitter.emit(PageEvent.RequestFailed, httpRequest); }); + void httpRequest.finalizeInterceptions(); }); this.browsingContext.on('navigation', ({navigation}) => { @@ -300,10 +301,18 @@ export class BidiFrame extends Frame { // readiness=interactive. // // Related: https://bugzilla.mozilla.org/show_bug.cgi?id=1846601 - this.browsingContext.navigate( - url, - Bidi.BrowsingContext.ReadinessState.Interactive - ), + this.browsingContext + .navigate(url, Bidi.BrowsingContext.ReadinessState.Interactive) + .catch(error => { + if ( + isErrorLike(error) && + error.message.includes('net::ERR_HTTP_RESPONSE_CODE_FAILURE') + ) { + return; + } + + throw error; + }), ]).catch( rewriteNavigationError( url, @@ -351,11 +360,7 @@ export class BidiFrame extends Frame { }), raceWith( fromEmitterEvent(navigation, 'fragment'), - fromEmitterEvent(navigation, 'failed').pipe( - map(({url}) => { - throw new Error(`Navigation failed: ${url}`); - }) - ), + fromEmitterEvent(navigation, 'failed'), fromEmitterEvent(navigation, 'aborted').pipe( map(({url}) => { throw new Error(`Navigation aborted: ${url}`); @@ -401,11 +406,9 @@ export class BidiFrame extends Frame { if (!request) { return null; } - const httpRequest = requests.get(request)!; - const lastRedirect = httpRequest.redirectChain().at(-1); - return ( - lastRedirect !== undefined ? lastRedirect : httpRequest - ).response(); + const lastRequest = request.lastRedirect ?? request; + const httpRequest = requests.get(lastRequest)!; + return httpRequest.response(); }), raceWith( timeout(ms), @@ -471,6 +474,7 @@ export class BidiFrame extends Frame { targetId: this._id, flatten: true, }); + await this.browsingContext.subscribe([Bidi.ChromiumBidi.BiDiModule.Cdp]); return new BidiCdpSession(this, sessionId); } diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/HTTPRequest.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/HTTPRequest.ts index e75bb0cf3c..39ce4fec4b 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/HTTPRequest.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/HTTPRequest.ts @@ -10,7 +10,12 @@ import type { ContinueRequestOverrides, ResponseForRequest, } from '../api/HTTPRequest.js'; -import {HTTPRequest, type ResourceType} from '../api/HTTPRequest.js'; +import { + HTTPRequest, + STATUS_TEXTS, + type ResourceType, + handleError, +} from '../api/HTTPRequest.js'; import {PageEvent} from '../api/Page.js'; import {UnsupportedOperation} from '../common/Errors.js'; @@ -26,41 +31,61 @@ export const requests = new WeakMap<Request, BidiHTTPRequest>(); export class BidiHTTPRequest extends HTTPRequest { static from( bidiRequest: Request, - frame: BidiFrame | undefined + frame: BidiFrame, + redirect?: BidiHTTPRequest ): BidiHTTPRequest { - const request = new BidiHTTPRequest(bidiRequest, frame); + const request = new BidiHTTPRequest(bidiRequest, frame, redirect); request.#initialize(); return request; } - - #redirect: BidiHTTPRequest | undefined; + #redirectBy: BidiHTTPRequest | undefined; #response: BidiHTTPResponse | null = null; override readonly id: string; - readonly #frame: BidiFrame | undefined; + readonly #frame: BidiFrame; readonly #request: Request; - private constructor(request: Request, frame: BidiFrame | undefined) { + private constructor( + request: Request, + frame: BidiFrame, + redirectBy?: BidiHTTPRequest + ) { super(); requests.set(request, this); + this.interception.enabled = request.isBlocked; + this.#request = request; this.#frame = frame; + this.#redirectBy = redirectBy; this.id = request.id; } override get client(): CDPSession { - throw new UnsupportedOperation(); + return this.#frame.client; } #initialize() { this.#request.on('redirect', request => { - this.#redirect = BidiHTTPRequest.from(request, this.#frame); + const httpRequest = BidiHTTPRequest.from(request, this.#frame, this); + void httpRequest.finalizeInterceptions(); }); this.#request.once('success', data => { this.#response = BidiHTTPResponse.from(data, this); }); - - this.#frame?.page().trustedEmitter.emit(PageEvent.Request, this); + this.#request.on('authenticate', this.#handleAuthentication); + + this.#frame.page().trustedEmitter.emit(PageEvent.Request, this); + + if (Object.keys(this.#extraHTTPHeaders).length) { + this.interception.handlers.push(async () => { + await this.continue( + { + headers: this.headers(), + }, + 0 + ); + }); + } } override url(): string { @@ -68,7 +93,7 @@ export class BidiHTTPRequest extends HTTPRequest { } override resourceType(): ResourceType { - return this.initiator().type.toLowerCase() as ResourceType; + throw new UnsupportedOperation(); } override method(): string { @@ -87,12 +112,19 @@ export class BidiHTTPRequest extends HTTPRequest { throw new UnsupportedOperation(); } + get #extraHTTPHeaders(): Record<string, string> { + return this.#frame?.page()._extraHTTPHeaders ?? {}; + } + override headers(): Record<string, string> { const headers: Record<string, string> = {}; for (const header of this.#request.headers) { headers[header.name.toLowerCase()] = header.value.value; } - return headers; + return { + ...headers, + ...this.#extraHTTPHeaders, + }; } override response(): BidiHTTPResponse | null { @@ -115,65 +147,167 @@ export class BidiHTTPRequest extends HTTPRequest { } override redirectChain(): BidiHTTPRequest[] { - if (this.#redirect === undefined) { + if (this.#redirectBy === undefined) { return []; } - const redirects = [this.#redirect]; + const redirects = [this.#redirectBy]; for (const redirect of redirects) { - if (redirect.#redirect !== undefined) { - redirects.push(redirect.#redirect); + if (redirect.#redirectBy !== undefined) { + redirects.push(redirect.#redirectBy); } } return redirects; } - override enqueueInterceptAction( - pendingHandler: () => void | PromiseLike<unknown> - ): void { - // Execute the handler when interception is not supported - void pendingHandler(); + override frame(): BidiFrame { + return this.#frame; } - override frame(): BidiFrame | null { - return this.#frame ?? null; + override async continue( + overrides?: ContinueRequestOverrides, + priority?: number | undefined + ): Promise<void> { + return await super.continue( + { + headers: Object.keys(this.#extraHTTPHeaders).length + ? this.headers() + : undefined, + ...overrides, + }, + priority + ); } - override continueRequestOverrides(): never { - throw new UnsupportedOperation(); + override async _continue( + overrides: ContinueRequestOverrides = {} + ): Promise<void> { + const headers: Bidi.Network.Header[] = getBidiHeaders(overrides.headers); + this.interception.handled = true; + + return await this.#request + .continueRequest({ + url: overrides.url, + method: overrides.method, + body: overrides.postData + ? { + type: 'base64', + value: btoa(overrides.postData), + } + : undefined, + headers: headers.length > 0 ? headers : undefined, + }) + .catch(error => { + this.interception.handled = false; + return handleError(error); + }); } - override continue(_overrides: ContinueRequestOverrides = {}): never { - throw new UnsupportedOperation(); - } - - override responseForRequest(): never { - throw new UnsupportedOperation(); + override async _abort(): Promise<void> { + this.interception.handled = true; + return await this.#request.failRequest().catch(error => { + this.interception.handled = false; + throw error; + }); } - override abortErrorReason(): never { - throw new UnsupportedOperation(); - } + override async _respond( + response: Partial<ResponseForRequest>, + _priority?: number + ): Promise<void> { + this.interception.handled = true; + const responseBody: string | undefined = + response.body && response.body instanceof Uint8Array + ? response.body.toString('base64') + : response.body + ? btoa(response.body) + : undefined; + + const headers: Bidi.Network.Header[] = getBidiHeaders(response.headers); + const hasContentLength = headers.some(header => { + return header.name === 'content-length'; + }); - override interceptResolutionState(): never { - throw new UnsupportedOperation(); - } + if (response.contentType) { + headers.push({ + name: 'content-type', + value: { + type: 'string', + value: response.contentType, + }, + }); + } - override isInterceptResolutionHandled(): never { - throw new UnsupportedOperation(); + if (responseBody && !hasContentLength) { + const encoder = new TextEncoder(); + headers.push({ + name: 'content-length', + value: { + type: 'string', + value: String(encoder.encode(responseBody).byteLength), + }, + }); + } + const status = response.status || 200; + + return await this.#request + .provideResponse({ + statusCode: status, + headers: headers.length > 0 ? headers : undefined, + reasonPhrase: STATUS_TEXTS[status], + body: responseBody + ? { + type: 'base64', + value: responseBody, + } + : undefined, + }) + .catch(error => { + this.interception.handled = false; + throw error; + }); } - override finalizeInterceptions(): never { - throw new UnsupportedOperation(); - } + #authenticationHandled = false; + #handleAuthentication = async () => { + if (!this.#frame) { + return; + } + const credentials = this.#frame.page()._credentials; + if (credentials && !this.#authenticationHandled) { + this.#authenticationHandled = true; + void this.#request.continueWithAuth({ + action: 'provideCredentials', + credentials: { + type: 'password', + username: credentials.username, + password: credentials.password, + }, + }); + } else { + void this.#request.continueWithAuth({ + action: 'cancel', + }); + } + }; +} - override abort(): never { - throw new UnsupportedOperation(); +function getBidiHeaders(rawHeaders?: Record<string, unknown>) { + const headers: Bidi.Network.Header[] = []; + for (const [name, value] of Object.entries(rawHeaders ?? [])) { + if (!Object.is(value, undefined)) { + const values = Array.isArray(value) ? value : [value]; + + for (const value of values) { + headers.push({ + name: name.toLowerCase(), + value: { + type: 'string', + value: String(value), + }, + }); + } + } } - override respond( - _response: Partial<ResponseForRequest>, - _priority?: number - ): never { - throw new UnsupportedOperation(); - } + return headers; } diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/HTTPResponse.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/HTTPResponse.ts index bad44ff089..ffb2ac298a 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/HTTPResponse.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/HTTPResponse.ts @@ -40,6 +40,12 @@ export class BidiHTTPResponse extends HTTPResponse { } #initialize() { + if (this.#data.fromCache) { + this.#request + .frame() + ?.page() + .trustedEmitter.emit(PageEvent.RequestServedFromCache, this.#request); + } this.#request.frame()?.page().trustedEmitter.emit(PageEvent.Response, this); } diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Page.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Page.ts index c662496a18..55a2e79310 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Page.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Page.ts @@ -13,8 +13,9 @@ import type {BoundingBox} from '../api/ElementHandle.js'; import type {WaitForOptions} from '../api/Frame.js'; import type {HTTPResponse} from '../api/HTTPResponse.js'; import type { - MediaFeature, + Credentials, GeolocationOptions, + MediaFeature, PageEvents, } from '../api/Page.js'; import { @@ -27,13 +28,22 @@ import {Accessibility} from '../cdp/Accessibility.js'; import {Coverage} from '../cdp/Coverage.js'; import {EmulationManager} from '../cdp/EmulationManager.js'; import {Tracing} from '../cdp/Tracing.js'; -import type {Cookie, CookieParam, CookieSameSite} from '../common/Cookie.js'; -import type {DeleteCookiesRequest} from '../common/Cookie.js'; +import type { + Cookie, + CookieParam, + CookieSameSite, + DeleteCookiesRequest, +} from '../common/Cookie.js'; import {UnsupportedOperation} from '../common/Errors.js'; import {EventEmitter} from '../common/EventEmitter.js'; import type {PDFOptions} from '../common/PDFOptions.js'; import type {Awaitable} from '../common/types.js'; -import {evaluationString, parsePDFOptions, timeout} from '../common/util.js'; +import { + evaluationString, + isString, + parsePDFOptions, + timeout, +} from '../common/util.js'; import type {Viewport} from '../common/Viewport.js'; import {assert} from '../util/assert.js'; import {bubble} from '../util/decorators.js'; @@ -43,7 +53,6 @@ import type {BidiBrowser} from './Browser.js'; import type {BidiBrowserContext} from './BrowserContext.js'; import type {BidiCdpSession} from './CDPSession.js'; import type {BrowsingContext} from './core/BrowsingContext.js'; -import {BidiElementHandle} from './ElementHandle.js'; import {BidiFrame} from './Frame.js'; import type {BidiHTTPResponse} from './HTTPResponse.js'; import {BidiKeyboard, BidiMouse, BidiTouchscreen} from './Input.js'; @@ -161,21 +170,28 @@ export class BidiPage extends Page { } async focusedFrame(): Promise<BidiFrame> { - using frame = await this.mainFrame() + using handle = (await this.mainFrame() .isolatedRealm() .evaluateHandle(() => { - let frame: HTMLIFrameElement | undefined; - let win: Window | null = window; - while (win?.document.activeElement instanceof HTMLIFrameElement) { - frame = win.document.activeElement; - win = frame.contentWindow; + let win = window; + while ( + win.document.activeElement instanceof win.HTMLIFrameElement || + win.document.activeElement instanceof win.HTMLFrameElement + ) { + if (win.document.activeElement.contentWindow === null) { + break; + } + win = win.document.activeElement.contentWindow as typeof win; } - return frame; - }); - if (!(frame instanceof BidiElementHandle)) { - return this.mainFrame(); - } - return await frame.contentFrame(); + return win; + })) as BidiJSHandle<Window & typeof globalThis>; + const value = handle.remoteValue(); + assert(value.type === 'window'); + const frame = this.frames().find(frame => { + return frame._id === value.value.context; + }); + assert(frame); + return frame; } override frames(): BidiFrame[] { @@ -311,6 +327,17 @@ export class BidiPage extends Page { preferCSSPageSize, } = parsePDFOptions(options, 'cm'); const pageRanges = ranges ? ranges.split(', ') : []; + + await firstValueFrom( + from( + this.mainFrame() + .isolatedRealm() + .evaluate(() => { + return document.fonts.ready; + }) + ).pipe(raceWith(timeout(ms))) + ); + const data = await firstValueFrom( from( this.#frame.browsingContext.print({ @@ -489,8 +516,71 @@ export class BidiPage extends Page { return [...this.#workers]; } - override setRequestInterception(): never { - throw new UnsupportedOperation(); + #userInterception?: string; + override async setRequestInterception(enable: boolean): Promise<void> { + this.#userInterception = await this.#toggleInterception( + [Bidi.Network.InterceptPhase.BeforeRequestSent], + this.#userInterception, + enable + ); + } + + /** + * @internal + */ + _extraHTTPHeaders: Record<string, string> = {}; + #extraHeadersInterception?: string; + override async setExtraHTTPHeaders( + headers: Record<string, string> + ): Promise<void> { + const extraHTTPHeaders: Record<string, string> = {}; + for (const [key, value] of Object.entries(headers)) { + assert( + isString(value), + `Expected value of header "${key}" to be String, but "${typeof value}" is found.` + ); + extraHTTPHeaders[key.toLowerCase()] = value; + } + this._extraHTTPHeaders = extraHTTPHeaders; + + this.#extraHeadersInterception = await this.#toggleInterception( + [Bidi.Network.InterceptPhase.BeforeRequestSent], + this.#extraHeadersInterception, + Boolean(Object.keys(this._extraHTTPHeaders).length) + ); + } + + /** + * @internal + */ + _credentials: Credentials | null = null; + #authInterception?: string; + override async authenticate(credentials: Credentials | null): Promise<void> { + this.#authInterception = await this.#toggleInterception( + [Bidi.Network.InterceptPhase.AuthRequired], + this.#authInterception, + Boolean(credentials) + ); + + this._credentials = credentials; + } + + async #toggleInterception( + phases: [Bidi.Network.InterceptPhase, ...Bidi.Network.InterceptPhase[]], + interception: string | undefined, + expected: boolean + ): Promise<string | undefined> { + if (expected && !interception) { + return await this.#frame.browsingContext.addIntercept({ + phases, + }); + } else if (!expected && interception) { + await this.#frame.browsingContext.userContext.browser.removeIntercept( + interception + ); + return; + } + return interception; } override setDragInterception(): never { @@ -603,14 +693,6 @@ export class BidiPage extends Page { await this.#frame.removeExposedFunction(name); } - override authenticate(): never { - throw new UnsupportedOperation(); - } - - override setExtraHTTPHeaders(): never { - throw new UnsupportedOperation(); - } - override metrics(): never { throw new UnsupportedOperation(); } diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Browser.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Browser.ts index efeabc3a59..5f51895585 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Browser.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Browser.ts @@ -51,20 +51,17 @@ export class Browser extends EventEmitter<{ return browser; } - // keep-sorted start #closed = false; #reason: string | undefined; readonly #disposables = new DisposableStack(); readonly #userContexts = new Map<string, UserContext>(); readonly session: Session; readonly #sharedWorkers = new Map<string, SharedWorkerRealm>(); - // keep-sorted end private constructor(session: Session) { super(); - // keep-sorted start + this.session = session; - // keep-sorted end } async #initialize() { @@ -141,7 +138,6 @@ export class Browser extends EventEmitter<{ return userContext; } - // keep-sorted start block=yes get closed(): boolean { return this.#closed; } @@ -158,7 +154,6 @@ export class Browser extends EventEmitter<{ get userContexts(): Iterable<UserContext> { return this.#userContexts.values(); } - // keep-sorted end @inertIfDisposed dispose(reason?: string, closed = false): void { @@ -203,6 +198,16 @@ export class Browser extends EventEmitter<{ // SAFETY: By definition of `disposed`, `#reason` is defined. return browser.#reason!; }) + async removeIntercept(intercept: Bidi.Network.Intercept): Promise<void> { + await this.session.send('network.removeIntercept', { + intercept, + }); + } + + @throwIfDisposed<Browser>(browser => { + // SAFETY: By definition of `disposed`, `#reason` is defined. + return browser.#reason!; + }) async removePreloadScript(script: string): Promise<void> { await this.session.send('script.removePreloadScript', { script, diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/BrowsingContext.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/BrowsingContext.ts index 07309576a3..ed3a235f5d 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/BrowsingContext.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/BrowsingContext.ts @@ -21,6 +21,14 @@ import {UserPrompt} from './UserPrompt.js'; /** * @internal */ +export type AddInterceptOptions = Omit< + Bidi.Network.AddInterceptParameters, + 'contexts' +>; + +/** + * @internal + */ export type CaptureScreenshotOptions = Omit< Bidi.BrowsingContext.CaptureScreenshotParameters, 'context' @@ -121,7 +129,6 @@ export class BrowsingContext extends EventEmitter<{ return browsingContext; } - // keep-sorted start #navigation: Navigation | undefined; #reason?: string; #url: string; @@ -133,7 +140,6 @@ export class BrowsingContext extends EventEmitter<{ readonly id: string; readonly parent: BrowsingContext | undefined; readonly userContext: UserContext; - // keep-sorted end private constructor( context: UserContext, @@ -142,12 +148,11 @@ export class BrowsingContext extends EventEmitter<{ url: string ) { super(); - // keep-sorted start + this.#url = url; this.id = id; this.parent = parent; this.userContext = context; - // keep-sorted end this.defaultRealm = this.#createWindowRealm(); } @@ -275,7 +280,6 @@ export class BrowsingContext extends EventEmitter<{ }); } - // keep-sorted start block=yes get #session() { return this.userContext.browser.session; } @@ -306,7 +310,6 @@ export class BrowsingContext extends EventEmitter<{ get url(): string { return this.#url; } - // keep-sorted end #createWindowRealm(sandbox?: string) { const realm = WindowRealm.from(this, sandbox); @@ -478,7 +481,7 @@ export class BrowsingContext extends EventEmitter<{ functionDeclaration, { ...options, - contexts: [this, ...(options.contexts ?? [])], + contexts: [this], } ); } @@ -487,6 +490,21 @@ export class BrowsingContext extends EventEmitter<{ // SAFETY: Disposal implies this exists. return context.#reason!; }) + async addIntercept(options: AddInterceptOptions): Promise<string> { + const { + result: {intercept}, + } = await this.userContext.browser.session.send('network.addIntercept', { + ...options, + contexts: [this.id], + }); + + return intercept; + } + + @throwIfDisposed<BrowsingContext>(context => { + // SAFETY: Disposal implies this exists. + return context.#reason!; + }) async removePreloadScript(script: string): Promise<void> { await this.userContext.browser.removePreloadScript(script); } @@ -539,6 +557,22 @@ export class BrowsingContext extends EventEmitter<{ }); } + @throwIfDisposed<BrowsingContext>(context => { + // SAFETY: Disposal implies this exists. + return context.#reason!; + }) + async subscribe(events: [string, ...string[]]): Promise<void> { + await this.#session.subscribe(events, [this.id]); + } + + @throwIfDisposed<BrowsingContext>(context => { + // SAFETY: Disposal implies this exists. + return context.#reason!; + }) + async addInterception(events: [string, ...string[]]): Promise<void> { + await this.#session.subscribe(events, [this.id]); + } + [disposeSymbol](): void { this.#reason ??= 'Browsing context already closed, probably because the user context closed.'; diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Connection.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Connection.ts index 9c26a03503..7e5da052bb 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Connection.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Connection.ts @@ -149,6 +149,31 @@ export interface Commands { params: Bidi.Storage.SetCookieParameters; returnType: Bidi.Storage.SetCookieParameters; }; + + 'network.addIntercept': { + params: Bidi.Network.AddInterceptParameters; + returnType: Bidi.Network.AddInterceptResult; + }; + 'network.removeIntercept': { + params: Bidi.Network.RemoveInterceptParameters; + returnType: Bidi.EmptyResult; + }; + 'network.continueRequest': { + params: Bidi.Network.ContinueRequestParameters; + returnType: Bidi.EmptyResult; + }; + 'network.continueWithAuth': { + params: Bidi.Network.ContinueWithAuthParameters; + returnType: Bidi.EmptyResult; + }; + 'network.failRequest': { + params: Bidi.Network.FailRequestParameters; + returnType: Bidi.EmptyResult; + }; + 'network.provideResponse': { + params: Bidi.Network.ProvideResponseParameters; + returnType: Bidi.EmptyResult; + }; } /** diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Navigation.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Navigation.ts index 50040164a5..d50cf091f9 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Navigation.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Navigation.ts @@ -6,7 +6,6 @@ import {EventEmitter} from '../../common/EventEmitter.js'; import {inertIfDisposed} from '../../util/decorators.js'; -import {Deferred} from '../../util/Deferred.js'; import {DisposableStack, disposeSymbol} from '../../util/disposable.js'; import type {BrowsingContext} from './BrowsingContext.js'; @@ -39,19 +38,16 @@ export class Navigation extends EventEmitter<{ return navigation; } - // keep-sorted start #request: Request | undefined; #navigation: Navigation | undefined; readonly #browsingContext: BrowsingContext; readonly #disposables = new DisposableStack(); - readonly #id = new Deferred<string | null>(); - // keep-sorted end + #id?: string | null; private constructor(context: BrowsingContext) { super(); - // keep-sorted start + this.#browsingContext = context; - // keep-sorted end } #initialize() { @@ -69,7 +65,6 @@ export class Navigation extends EventEmitter<{ browsingContextEmitter.on('request', ({request}) => { if ( request.navigation === undefined || - this.#request !== undefined || // If a request with a navigation ID comes in, then the navigation ID is // for this navigation. !this.#matches(request.navigation) @@ -79,6 +74,13 @@ export class Navigation extends EventEmitter<{ this.#request = request; this.emit('request', request); + const requestEmitter = this.#disposables.use( + new EventEmitter(this.#request) + ); + + requestEmitter.on('redirect', request => { + this.#request = request; + }); }); const sessionEmitter = this.#disposables.use( @@ -139,14 +141,13 @@ export class Navigation extends EventEmitter<{ if (this.#navigation !== undefined && !this.#navigation.disposed) { return false; } - if (!this.#id.resolved()) { - this.#id.resolve(navigation); + if (this.#id === undefined) { + this.#id = navigation; return true; } - return this.#id.value() === navigation; + return this.#id === navigation; } - // keep-sorted start block=yes get #session() { return this.#browsingContext.userContext.browser.session; } @@ -159,7 +160,6 @@ export class Navigation extends EventEmitter<{ get navigation(): Navigation | undefined { return this.#navigation; } - // keep-sorted end @inertIfDisposed private dispose(): void { diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Realm.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Realm.ts index 392194cec8..87298f8730 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Realm.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Realm.ts @@ -44,22 +44,19 @@ export abstract class Realm extends EventEmitter<{ /** Emitted when a shared worker is created in the realm. */ sharedworker: SharedWorkerRealm; }> { - // keep-sorted start #reason?: string; protected readonly disposables = new DisposableStack(); readonly id: string; readonly origin: string; - // keep-sorted end + protected executionContextId?: number; protected constructor(id: string, origin: string) { super(); - // keep-sorted start + this.id = id; this.origin = origin; - // keep-sorted end } - // keep-sorted start block=yes get disposed(): boolean { return this.#reason !== undefined; } @@ -67,7 +64,6 @@ export abstract class Realm extends EventEmitter<{ get target(): Bidi.Script.Target { return {realm: this.id}; } - // keep-sorted end @inertIfDisposed protected dispose(reason?: string): void { @@ -127,11 +123,15 @@ export abstract class Realm extends EventEmitter<{ return realm.#reason!; }) async resolveExecutionContextId(): Promise<number> { - const {result} = await (this.session.connection as BidiConnection).send( - 'cdp.resolveRealm', - {realm: this.id} - ); - return result.executionContextId; + if (!this.executionContextId) { + const {result} = await (this.session.connection as BidiConnection).send( + 'cdp.resolveRealm', + {realm: this.id} + ); + this.executionContextId = result.executionContextId; + } + + return this.executionContextId; } [disposeSymbol](): void { @@ -154,19 +154,16 @@ export class WindowRealm extends Realm { return realm; } - // keep-sorted start readonly browsingContext: BrowsingContext; readonly sandbox?: string; - // keep-sorted end readonly #workers = new Map<string, DedicatedWorkerRealm>(); private constructor(context: BrowsingContext, sandbox?: string) { super('', ''); - // keep-sorted start + this.browsingContext = context; this.sandbox = sandbox; - // keep-sorted end } #initialize(): void { @@ -188,6 +185,7 @@ export class WindowRealm extends Realm { } (this as any).id = info.realm; (this as any).origin = info.origin; + this.executionContextId = undefined; this.emit('updated', this); }); sessionEmitter.on('script.realmCreated', info => { @@ -242,10 +240,8 @@ export class DedicatedWorkerRealm extends Realm { return realm; } - // keep-sorted start readonly #workers = new Map<string, DedicatedWorkerRealm>(); readonly owners: Set<DedicatedWorkerOwnerRealm>; - // keep-sorted end private constructor( owner: DedicatedWorkerOwnerRealm, @@ -300,10 +296,8 @@ export class SharedWorkerRealm extends Realm { return realm; } - // keep-sorted start readonly #workers = new Map<string, DedicatedWorkerRealm>(); readonly browser: Browser; - // keep-sorted end private constructor(browser: Browser, id: string, origin: string) { super(id, origin); diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Request.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Request.ts index fd616b668d..241bebf3f9 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Request.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Request.ts @@ -19,6 +19,8 @@ export class Request extends EventEmitter<{ /** Emitted when the request is redirected. */ redirect: Request; /** Emitted when the request succeeds. */ + authenticate: void; + /** Emitted when the request succeeds. */ success: Bidi.Network.ResponseData; /** Emitted when the request fails. */ error: string; @@ -32,24 +34,21 @@ export class Request extends EventEmitter<{ return request; } - // keep-sorted start #error?: string; #redirect?: Request; #response?: Bidi.Network.ResponseData; readonly #browsingContext: BrowsingContext; readonly #disposables = new DisposableStack(); readonly #event: Bidi.Network.BeforeRequestSentParameters; - // keep-sorted end private constructor( browsingContext: BrowsingContext, event: Bidi.Network.BeforeRequestSentParameters ) { super(); - // keep-sorted start + this.#browsingContext = browsingContext; this.#event = event; - // keep-sorted end } #initialize() { @@ -77,6 +76,17 @@ export class Request extends EventEmitter<{ this.emit('redirect', this.#redirect); this.dispose(); }); + sessionEmitter.on('network.authRequired', event => { + if ( + event.context !== this.#browsingContext.id || + event.request.request !== this.id || + // Don't try to authenticate for events that are not blocked + !event.isBlocked + ) { + return; + } + this.emit('authenticate', undefined); + }); sessionEmitter.on('network.fetchError', event => { if ( event.context !== this.#browsingContext.id || @@ -107,7 +117,6 @@ export class Request extends EventEmitter<{ }); } - // keep-sorted start block=yes get #session() { return this.#browsingContext.userContext.browser.session; } @@ -135,13 +144,82 @@ export class Request extends EventEmitter<{ get redirect(): Request | undefined { return this.#redirect; } + get lastRedirect(): Request | undefined { + let redirect = this.#redirect; + while (redirect) { + if (redirect && !redirect.#redirect) { + return redirect; + } + redirect = redirect.#redirect; + } + return redirect; + } get response(): Bidi.Network.ResponseData | undefined { return this.#response; } get url(): string { return this.#event.request.url; } - // keep-sorted end + get isBlocked(): boolean { + return this.#event.isBlocked; + } + + async continueRequest({ + url, + method, + headers, + cookies, + body, + }: Omit<Bidi.Network.ContinueRequestParameters, 'request'>): Promise<void> { + await this.#session.send('network.continueRequest', { + request: this.id, + url, + method, + headers, + body, + cookies, + }); + } + + async failRequest(): Promise<void> { + await this.#session.send('network.failRequest', { + request: this.id, + }); + } + + async provideResponse({ + statusCode, + reasonPhrase, + headers, + body, + }: Omit<Bidi.Network.ProvideResponseParameters, 'request'>): Promise<void> { + await this.#session.send('network.provideResponse', { + request: this.id, + statusCode, + reasonPhrase, + headers, + body, + }); + } + + async continueWithAuth( + parameters: + | Bidi.Network.ContinueWithAuthCredentials + | Bidi.Network.ContinueWithAuthNoCredentials + ): Promise<void> { + if (parameters.action === 'provideCredentials') { + await this.#session.send('network.continueWithAuth', { + request: this.id, + action: parameters.action, + credentials: parameters.credentials, + }); + } else { + await this.#session.send('network.continueWithAuth', { + request: this.id, + action: parameters.action, + }); + } + } @inertIfDisposed private dispose(): void { diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Session.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Session.ts index ffd39769e7..3957556c69 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Session.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Session.ts @@ -71,8 +71,9 @@ export class Session platformName: '', setWindowRect: false, webSocketUrl: '', + userAgent: '', }, - }; + } satisfies Bidi.Session.NewResult; } const session = new Session(connection, result); @@ -80,21 +81,18 @@ export class Session return session; } - // keep-sorted start #reason: string | undefined; readonly #disposables = new DisposableStack(); readonly #info: Bidi.Session.NewResult; readonly browser!: Browser; @bubble() accessor connection: Connection; - // keep-sorted end private constructor(connection: Connection, info: Bidi.Session.NewResult) { super(); - // keep-sorted start + this.#info = info; this.connection = connection; - // keep-sorted end } async #initialize(): Promise<void> { @@ -120,7 +118,6 @@ export class Session }); } - // keep-sorted start block=yes get capabilities(): Bidi.Session.NewResult['capabilities'] { return this.#info.capabilities; } @@ -133,7 +130,6 @@ export class Session get id(): string { return this.#info.sessionId; } - // keep-sorted end @inertIfDisposed private dispose(reason?: string): void { @@ -163,9 +159,27 @@ export class Session // SAFETY: By definition of `disposed`, `#reason` is defined. return session.#reason!; }) - async subscribe(events: string[]): Promise<void> { + async subscribe( + events: [string, ...string[]], + contexts?: [string, ...string[]] + ): Promise<void> { + await this.send('session.subscribe', { + events, + contexts, + }); + } + + @throwIfDisposed<Session>(session => { + // SAFETY: By definition of `disposed`, `#reason` is defined. + return session.#reason!; + }) + async addIntercepts( + events: [string, ...string[]], + contexts?: [string, ...string[]] + ): Promise<void> { await this.send('session.subscribe', { events, + contexts, }); } diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/UserContext.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/UserContext.ts index 72859c6a53..a6af520522 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/UserContext.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/UserContext.ts @@ -52,21 +52,18 @@ export class UserContext extends EventEmitter<{ return context; } - // keep-sorted start #reason?: string; // Note these are only top-level contexts. readonly #browsingContexts = new Map<string, BrowsingContext>(); readonly #disposables = new DisposableStack(); readonly #id: string; readonly browser: Browser; - // keep-sorted end private constructor(browser: Browser, id: string) { super(); - // keep-sorted start + this.#id = id; this.browser = browser; - // keep-sorted end } #initialize() { @@ -110,7 +107,6 @@ export class UserContext extends EventEmitter<{ }); } - // keep-sorted start block=yes get #session() { return this.browser.session; } @@ -126,7 +122,6 @@ export class UserContext extends EventEmitter<{ get id(): string { return this.#id; } - // keep-sorted end @inertIfDisposed private dispose(reason?: string): void { @@ -227,8 +222,7 @@ export class UserContext extends EventEmitter<{ origin, descriptor, state, - // @ts-expect-error not standard implementation. - 'goog:userContext': this.#id, + userContext: this.#id, }); } diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/UserPrompt.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/UserPrompt.ts index 073233bed0..13a455e4ac 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/UserPrompt.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/UserPrompt.ts @@ -49,23 +49,20 @@ export class UserPrompt extends EventEmitter<{ return userPrompt; } - // keep-sorted start #reason?: string; #result?: UserPromptResult; readonly #disposables = new DisposableStack(); readonly browsingContext: BrowsingContext; readonly info: Bidi.BrowsingContext.UserPromptOpenedParameters; - // keep-sorted end private constructor( context: BrowsingContext, info: Bidi.BrowsingContext.UserPromptOpenedParameters ) { super(); - // keep-sorted start + this.browsingContext = context; this.info = info; - // keep-sorted end } #initialize() { @@ -89,7 +86,6 @@ export class UserPrompt extends EventEmitter<{ }); } - // keep-sorted start block=yes get #session() { return this.browsingContext.userContext.browser.session; } @@ -105,7 +101,6 @@ export class UserPrompt extends EventEmitter<{ get result(): UserPromptResult | undefined { return this.#result; } - // keep-sorted end @inertIfDisposed private dispose(reason?: string): void { diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/AriaQueryHandler.ts b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/AriaQueryHandler.ts index 2286723758..2b9b14fdc7 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/AriaQueryHandler.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/AriaQueryHandler.ts @@ -27,7 +27,16 @@ const queryAXTree = async ( role, }); return nodes.filter((node: Protocol.Accessibility.AXNode) => { - return !node.role || !NON_ELEMENT_NODE_ROLES.has(node.role.value); + if (node.ignored) { + return false; + } + if (!node.role) { + return false; + } + if (NON_ELEMENT_NODE_ROLES.has(node.role.value)) { + return false; + } + return true; }); }; diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/Browser.ts b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/Browser.ts index 5c8a4c24da..9ae2cbfff1 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/Browser.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/Browser.ts @@ -12,20 +12,18 @@ import type {DebugInfo} from '../api/Browser.js'; import { Browser as BrowserBase, BrowserEvent, - WEB_PERMISSION_TO_PROTOCOL_PERMISSION, type BrowserCloseCallback, type BrowserContextOptions, type IsPageTargetCallback, - type Permission, type TargetFilterCallback, } from '../api/Browser.js'; -import {BrowserContext, BrowserContextEvent} from '../api/BrowserContext.js'; +import {BrowserContextEvent} from '../api/BrowserContext.js'; import {CDPSessionEvent, type CDPSession} from '../api/CDPSession.js'; import type {Page} from '../api/Page.js'; import type {Target} from '../api/Target.js'; import type {Viewport} from '../common/Viewport.js'; -import {assert} from '../util/assert.js'; +import {CdpBrowserContext} from './BrowserContext.js'; import {ChromeTargetManager} from './ChromeTargetManager.js'; import type {Connection} from './Connection.js'; import {FirefoxTargetManager} from './FirefoxTargetManager.js'; @@ -424,90 +422,3 @@ export class CdpBrowser extends BrowserBase { }; } } - -/** - * @internal - */ -export class CdpBrowserContext extends BrowserContext { - #connection: Connection; - #browser: CdpBrowser; - #id?: string; - - constructor(connection: Connection, browser: CdpBrowser, contextId?: string) { - super(); - this.#connection = connection; - this.#browser = browser; - this.#id = contextId; - } - - override get id(): string | undefined { - return this.#id; - } - - override targets(): CdpTarget[] { - return this.#browser.targets().filter(target => { - return target.browserContext() === this; - }); - } - - override async pages(): Promise<Page[]> { - const pages = await Promise.all( - this.targets() - .filter(target => { - return ( - target.type() === 'page' || - (target.type() === 'other' && - this.#browser._getIsPageTargetCallback()?.(target)) - ); - }) - .map(target => { - return target.page(); - }) - ); - return pages.filter((page): page is Page => { - return !!page; - }); - } - - override isIncognito(): boolean { - return !!this.#id; - } - - override async overridePermissions( - origin: string, - permissions: Permission[] - ): Promise<void> { - const protocolPermissions = permissions.map(permission => { - const protocolPermission = - WEB_PERMISSION_TO_PROTOCOL_PERMISSION.get(permission); - if (!protocolPermission) { - throw new Error('Unknown permission: ' + permission); - } - return protocolPermission; - }); - await this.#connection.send('Browser.grantPermissions', { - origin, - browserContextId: this.#id || undefined, - permissions: protocolPermissions, - }); - } - - override async clearPermissionOverrides(): Promise<void> { - await this.#connection.send('Browser.resetPermissions', { - browserContextId: this.#id || undefined, - }); - } - - override newPage(): Promise<Page> { - return this.#browser._createPageInContext(this.#id); - } - - override browser(): CdpBrowser { - return this.#browser; - } - - override async close(): Promise<void> { - assert(this.#id, 'Non-incognito profiles cannot be closed!'); - await this.#browser._disposeContext(this.#id); - } -} diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/BrowserContext.ts b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/BrowserContext.ts new file mode 100644 index 0000000000..f2279617f1 --- /dev/null +++ b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/BrowserContext.ts @@ -0,0 +1,104 @@ +/** + * @license + * Copyright 2024 Google Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + WEB_PERMISSION_TO_PROTOCOL_PERMISSION, + type Permission, +} from '../api/Browser.js'; +import {BrowserContext} from '../api/BrowserContext.js'; +import type {Page} from '../api/Page.js'; +import {assert} from '../util/assert.js'; + +import type {CdpBrowser} from './Browser.js'; +import type {Connection} from './Connection.js'; +import type {CdpTarget} from './Target.js'; + +/** + * @internal + */ +export class CdpBrowserContext extends BrowserContext { + #connection: Connection; + #browser: CdpBrowser; + #id?: string; + + constructor(connection: Connection, browser: CdpBrowser, contextId?: string) { + super(); + this.#connection = connection; + this.#browser = browser; + this.#id = contextId; + } + + override get id(): string | undefined { + return this.#id; + } + + override targets(): CdpTarget[] { + return this.#browser.targets().filter(target => { + return target.browserContext() === this; + }); + } + + override async pages(): Promise<Page[]> { + const pages = await Promise.all( + this.targets() + .filter(target => { + return ( + target.type() === 'page' || + (target.type() === 'other' && + this.#browser._getIsPageTargetCallback()?.(target)) + ); + }) + .map(target => { + return target.page(); + }) + ); + return pages.filter((page): page is Page => { + return !!page; + }); + } + + override isIncognito(): boolean { + return !!this.#id; + } + + override async overridePermissions( + origin: string, + permissions: Permission[] + ): Promise<void> { + const protocolPermissions = permissions.map(permission => { + const protocolPermission = + WEB_PERMISSION_TO_PROTOCOL_PERMISSION.get(permission); + if (!protocolPermission) { + throw new Error('Unknown permission: ' + permission); + } + return protocolPermission; + }); + await this.#connection.send('Browser.grantPermissions', { + origin, + browserContextId: this.#id || undefined, + permissions: protocolPermissions, + }); + } + + override async clearPermissionOverrides(): Promise<void> { + await this.#connection.send('Browser.resetPermissions', { + browserContextId: this.#id || undefined, + }); + } + + override newPage(): Promise<Page> { + return this.#browser._createPageInContext(this.#id); + } + + override browser(): CdpBrowser { + return this.#browser; + } + + override async close(): Promise<void> { + assert(this.#id, 'Non-incognito profiles cannot be closed!'); + await this.#browser._disposeContext(this.#id); + } +} diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/Frame.ts b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/Frame.ts index edc7009b11..c7c2885a65 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/Frame.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/Frame.ts @@ -206,6 +206,7 @@ export class CdpFrame extends Frame { options: { timeout?: number; waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[]; + ignoreSameDocumentNavigation?: boolean; } = {} ): Promise<HTTPResponse | null> { const { @@ -220,14 +221,22 @@ export class CdpFrame extends Frame { ); const error = await Deferred.race([ watcher.terminationPromise(), - watcher.sameDocumentNavigationPromise(), + ...(options.ignoreSameDocumentNavigation + ? [] + : [watcher.sameDocumentNavigationPromise()]), watcher.newDocumentNavigationPromise(), ]); try { if (error) { throw error; } - return await watcher.navigationResponse(); + const result = await Deferred.race< + Error | HTTPResponse | null | undefined + >([watcher.terminationPromise(), watcher.navigationResponse()]); + if (result instanceof Error) { + throw error; + } + return result || null; } finally { watcher.dispose(); } diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/FrameTree.ts b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/FrameTree.ts index 7ee1b86b5f..dcd341c9e3 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/FrameTree.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/FrameTree.ts @@ -21,6 +21,7 @@ export class FrameTree<FrameType extends Frame> { // frameID -> childFrameIDs #childIds = new Map<string, Set<string>>(); #mainFrame?: FrameType; + #isMainFrameStale = false; #waitRequests = new Map<string, Set<Deferred<FrameType>>>(); getMainFrame(): FrameType | undefined { @@ -59,8 +60,9 @@ export class FrameTree<FrameType extends Frame> { this.#childIds.set(frame._parentId, new Set()); } this.#childIds.get(frame._parentId)!.add(frame._id); - } else if (!this.#mainFrame) { + } else if (!this.#mainFrame || this.#isMainFrameStale) { this.#mainFrame = frame; + this.#isMainFrameStale = false; } this.#waitRequests.get(frame._id)?.forEach(request => { return request.resolve(frame); @@ -73,7 +75,7 @@ export class FrameTree<FrameType extends Frame> { if (frame._parentId) { this.#childIds.get(frame._parentId)?.delete(frame._id); } else { - this.#mainFrame = undefined; + this.#isMainFrameStale = true; } } diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/HTTPRequest.ts b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/HTTPRequest.ts index 1331513e19..59fc0c940a 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/HTTPRequest.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/HTTPRequest.ts @@ -9,18 +9,14 @@ import type {CDPSession} from '../api/CDPSession.js'; import type {Frame} from '../api/Frame.js'; import { type ContinueRequestOverrides, - type ErrorCode, headersArray, HTTPRequest, - InterceptResolutionAction, - type InterceptResolutionState, type ResourceType, type ResponseForRequest, STATUS_TEXTS, + handleError, } from '../api/HTTPRequest.js'; -import type {ProtocolError} from '../common/Errors.js'; import {debugError, isString} from '../common/util.js'; -import {assert} from '../util/assert.js'; import type {CdpHTTPResponse} from './HTTPResponse.js'; @@ -34,8 +30,7 @@ export class CdpHTTPRequest extends HTTPRequest { #client: CDPSession; #isNavigationRequest: boolean; - #allowInterception: boolean; - #interceptionHandled = false; + #url: string; #resourceType: ResourceType; @@ -44,13 +39,6 @@ export class CdpHTTPRequest extends HTTPRequest { #postData?: string; #headers: Record<string, string> = {}; #frame: Frame | null; - #continueRequestOverrides: ContinueRequestOverrides; - #responseForRequest: Partial<ResponseForRequest> | null = null; - #abortErrorReason: Protocol.Network.ErrorReason | null = null; - #interceptResolutionState: InterceptResolutionState = { - action: InterceptResolutionAction.None, - }; - #interceptHandlers: Array<() => void | PromiseLike<any>>; #initiator?: Protocol.Network.Initiator; override get client(): CDPSession { @@ -96,7 +84,6 @@ export class CdpHTTPRequest extends HTTPRequest { this.#isNavigationRequest = data.requestId === data.loaderId && data.type === 'Document'; this._interceptionId = interceptionId; - this.#allowInterception = allowInterception; this.#url = data.request.url; this.#resourceType = (data.type || 'other').toLowerCase() as ResourceType; this.#method = data.request.method; @@ -104,10 +91,10 @@ export class CdpHTTPRequest extends HTTPRequest { this.#hasPostData = data.request.hasPostData ?? false; this.#frame = frame; this._redirectChain = redirectChain; - this.#continueRequestOverrides = {}; - this.#interceptHandlers = []; this.#initiator = data.initiator; + this.interception.enabled = allowInterception; + for (const [key, value] of Object.entries(data.request.headers)) { this.#headers[key.toLowerCase()] = value; } @@ -117,59 +104,6 @@ export class CdpHTTPRequest extends HTTPRequest { return this.#url; } - override continueRequestOverrides(): ContinueRequestOverrides { - assert(this.#allowInterception, 'Request Interception is not enabled!'); - return this.#continueRequestOverrides; - } - - override responseForRequest(): Partial<ResponseForRequest> | null { - assert(this.#allowInterception, 'Request Interception is not enabled!'); - return this.#responseForRequest; - } - - override abortErrorReason(): Protocol.Network.ErrorReason | null { - assert(this.#allowInterception, 'Request Interception is not enabled!'); - return this.#abortErrorReason; - } - - override interceptResolutionState(): InterceptResolutionState { - if (!this.#allowInterception) { - return {action: InterceptResolutionAction.Disabled}; - } - if (this.#interceptionHandled) { - return {action: InterceptResolutionAction.AlreadyHandled}; - } - return {...this.#interceptResolutionState}; - } - - override isInterceptResolutionHandled(): boolean { - return this.#interceptionHandled; - } - - enqueueInterceptAction( - pendingHandler: () => void | PromiseLike<unknown> - ): void { - this.#interceptHandlers.push(pendingHandler); - } - - override async finalizeInterceptions(): Promise<void> { - await this.#interceptHandlers.reduce((promiseChain, interceptAction) => { - return promiseChain.then(interceptAction); - }, Promise.resolve()); - const {action} = this.interceptResolutionState(); - switch (action) { - case 'abort': - return await this.#abort(this.#abortErrorReason); - case 'respond': - if (this.#responseForRequest === null) { - throw new Error('Response is missing for the interception'); - } - return await this.#respond(this.#responseForRequest); - case 'continue': - return await this.#continue(this.#continueRequestOverrides); - } - } - override resourceType(): ResourceType { return this.#resourceType; } @@ -231,46 +165,12 @@ export class CdpHTTPRequest extends HTTPRequest { }; } - override async continue( - overrides: ContinueRequestOverrides = {}, - priority?: number - ): Promise<void> { - // Request interception is not supported for data: urls. - if (this.#url.startsWith('data:')) { - return; - } - assert(this.#allowInterception, 'Request Interception is not enabled!'); - assert(!this.#interceptionHandled, 'Request is already handled!'); - if (priority === undefined) { - return await this.#continue(overrides); - } - this.#continueRequestOverrides = overrides; - if ( - this.#interceptResolutionState.priority === undefined || - priority > this.#interceptResolutionState.priority - ) { - this.#interceptResolutionState = { - action: InterceptResolutionAction.Continue, - priority, - }; - return; - } - if (priority === this.#interceptResolutionState.priority) { - if ( - this.#interceptResolutionState.action === 'abort' || - this.#interceptResolutionState.action === 'respond' - ) { - return; - } - this.#interceptResolutionState.action = - InterceptResolutionAction.Continue; - } - return; - } - - async #continue(overrides: ContinueRequestOverrides = {}): Promise<void> { + /** + * @internal + */ + async _continue(overrides: ContinueRequestOverrides = {}): Promise<void> { const {url, method, postData, headers} = overrides; - this.#interceptionHandled = true; + this.interception.handled = true; const postDataBinaryBase64 = postData ? Buffer.from(postData).toString('base64') @@ -290,45 +190,13 @@ export class CdpHTTPRequest extends HTTPRequest { headers: headers ? headersArray(headers) : undefined, }) .catch(error => { - this.#interceptionHandled = false; + this.interception.handled = false; return handleError(error); }); } - override async respond( - response: Partial<ResponseForRequest>, - priority?: number - ): Promise<void> { - // Mocking responses for dataURL requests is not currently supported. - if (this.#url.startsWith('data:')) { - return; - } - assert(this.#allowInterception, 'Request Interception is not enabled!'); - assert(!this.#interceptionHandled, 'Request is already handled!'); - if (priority === undefined) { - return await this.#respond(response); - } - this.#responseForRequest = response; - if ( - this.#interceptResolutionState.priority === undefined || - priority > this.#interceptResolutionState.priority - ) { - this.#interceptResolutionState = { - action: InterceptResolutionAction.Respond, - priority, - }; - return; - } - if (priority === this.#interceptResolutionState.priority) { - if (this.#interceptResolutionState.action === 'abort') { - return; - } - this.#interceptResolutionState.action = InterceptResolutionAction.Respond; - } - } - - async #respond(response: Partial<ResponseForRequest>): Promise<void> { - this.#interceptionHandled = true; + async _respond(response: Partial<ResponseForRequest>): Promise<void> { + this.interception.handled = true; const responseBody: Buffer | null = response.body && isString(response.body) @@ -371,43 +239,15 @@ export class CdpHTTPRequest extends HTTPRequest { body: responseBody ? responseBody.toString('base64') : undefined, }) .catch(error => { - this.#interceptionHandled = false; + this.interception.handled = false; return handleError(error); }); } - override async abort( - errorCode: ErrorCode = 'failed', - priority?: number - ): Promise<void> { - // Request interception is not supported for data: urls. - if (this.#url.startsWith('data:')) { - return; - } - const errorReason = errorReasons[errorCode]; - assert(errorReason, 'Unknown error code: ' + errorCode); - assert(this.#allowInterception, 'Request Interception is not enabled!'); - assert(!this.#interceptionHandled, 'Request is already handled!'); - if (priority === undefined) { - return await this.#abort(errorReason); - } - this.#abortErrorReason = errorReason; - if ( - this.#interceptResolutionState.priority === undefined || - priority >= this.#interceptResolutionState.priority - ) { - this.#interceptResolutionState = { - action: InterceptResolutionAction.Abort, - priority, - }; - return; - } - } - - async #abort( + async _abort( errorReason: Protocol.Network.ErrorReason | null ): Promise<void> { - this.#interceptionHandled = true; + this.interception.handled = true; if (this._interceptionId === undefined) { throw new Error( 'HTTPRequest is missing _interceptionId needed for Fetch.failRequest' @@ -421,30 +261,3 @@ export class CdpHTTPRequest extends HTTPRequest { .catch(handleError); } } - -const errorReasons: Record<ErrorCode, Protocol.Network.ErrorReason> = { - aborted: 'Aborted', - accessdenied: 'AccessDenied', - addressunreachable: 'AddressUnreachable', - blockedbyclient: 'BlockedByClient', - blockedbyresponse: 'BlockedByResponse', - connectionaborted: 'ConnectionAborted', - connectionclosed: 'ConnectionClosed', - connectionfailed: 'ConnectionFailed', - connectionrefused: 'ConnectionRefused', - connectionreset: 'ConnectionReset', - internetdisconnected: 'InternetDisconnected', - namenotresolved: 'NameNotResolved', - timedout: 'TimedOut', - failed: 'Failed', -} as const; - -async function handleError(error: ProtocolError) { - if (['Invalid header'].includes(error.originalMessage)) { - throw error; - } - // In certain cases, protocol will return error if the request was - // already canceled or the page was closed. We should tolerate these - // errors. - debugError(error); -} diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/NetworkManager.ts b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/NetworkManager.ts index 4fd61116d2..70f7370f2c 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/NetworkManager.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/NetworkManager.ts @@ -8,6 +8,7 @@ import type {Protocol} from 'devtools-protocol'; import {CDPSessionEvent, type CDPSession} from '../api/CDPSession.js'; import type {Frame} from '../api/Frame.js'; +import type {Credentials} from '../api/Page.js'; import {EventEmitter, EventSubscription} from '../common/EventEmitter.js'; import { NetworkManagerEvent, @@ -27,14 +28,6 @@ import { /** * @public */ -export interface Credentials { - username: string; - password: string; -} - -/** - * @public - */ export interface NetworkConditions { /** * Download speed (bytes/s) @@ -147,18 +140,16 @@ export class NetworkManager extends EventEmitter<NetworkManagerEvents> { ); } - async setExtraHTTPHeaders( - extraHTTPHeaders: Record<string, string> - ): Promise<void> { - this.#extraHTTPHeaders = {}; - for (const key of Object.keys(extraHTTPHeaders)) { - const value = extraHTTPHeaders[key]; + async setExtraHTTPHeaders(headers: Record<string, string>): Promise<void> { + const extraHTTPHeaders: Record<string, string> = {}; + for (const [key, value] of Object.entries(headers)) { assert( isString(value), `Expected value of header "${key}" to be String, but "${typeof value}" is found.` ); - this.#extraHTTPHeaders[key.toLowerCase()] = value; + extraHTTPHeaders[key.toLowerCase()] = value; } + this.#extraHTTPHeaders = extraHTTPHeaders; await this.#applyToAllClients(this.#applyExtraHTTPHeaders.bind(this)); } diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/Page.ts b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/Page.ts index d5341cf3bb..32ded73b42 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/Page.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/Page.ts @@ -15,6 +15,7 @@ import type {Frame, WaitForOptions} from '../api/Frame.js'; import type {HTTPRequest} from '../api/HTTPRequest.js'; import type {HTTPResponse} from '../api/HTTPResponse.js'; import type {JSHandle} from '../api/JSHandle.js'; +import type {Credentials} from '../api/Page.js'; import { Page, PageEvent, @@ -71,7 +72,7 @@ import {FrameManagerEvent} from './FrameManagerEvents.js'; import {CdpKeyboard, CdpMouse, CdpTouchscreen} from './Input.js'; import {MAIN_WORLD} from './IsolatedWorlds.js'; import {releaseObject} from './JSHandle.js'; -import type {Credentials, NetworkConditions} from './NetworkManager.js'; +import type {NetworkConditions} from './NetworkManager.js'; import type {CdpTarget} from './Target.js'; import type {TargetManager} from './TargetManager.js'; import {TargetManagerEvent} from './TargetManager.js'; @@ -916,7 +917,10 @@ export class CdpPage extends Page { options?: WaitForOptions ): Promise<HTTPResponse | null> { const [result] = await Promise.all([ - this.waitForNavigation(options), + this.waitForNavigation({ + ...options, + ignoreSameDocumentNavigation: true, + }), this.#primaryTargetClient.send('Page.reload'), ]); @@ -1130,6 +1134,16 @@ export class CdpPage extends Page { await this.#emulationManager.setTransparentBackgroundColor(); } + await firstValueFrom( + from( + this.mainFrame() + .isolatedRealm() + .evaluate(() => { + return document.fonts.ready; + }) + ).pipe(raceWith(timeout(ms))) + ); + const printCommandPromise = this.#primaryTargetClient.send( 'Page.printToPDF', { diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/common/CallbackRegistry.ts b/remote/test/puppeteer/packages/puppeteer-core/src/common/CallbackRegistry.ts index ea9f3d5abb..6b8379177b 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/common/CallbackRegistry.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/common/CallbackRegistry.ts @@ -31,17 +31,14 @@ export class CallbackRegistry { } catch (error) { // We still throw sync errors synchronously and clean up the scheduled // callback. - callback.promise - .valueOrThrow() - .catch(debugError) - .finally(() => { - this.#callbacks.delete(callback.id); - }); + callback.promise.catch(debugError).finally(() => { + this.#callbacks.delete(callback.id); + }); callback.reject(error as Error); throw error; } // Must only have sync code up until here. - return callback.promise.valueOrThrow().finally(() => { + return callback.promise.finally(() => { this.#callbacks.delete(callback.id); }); } @@ -148,8 +145,8 @@ export class Callback { return this.#id; } - get promise(): Deferred<unknown> { - return this.#deferred; + get promise(): Promise<unknown> { + return this.#deferred.valueOrThrow(); } get error(): ProtocolError { diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/common/Configuration.ts b/remote/test/puppeteer/packages/puppeteer-core/src/common/Configuration.ts index fe71e57587..9d26cecfbb 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/common/Configuration.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/common/Configuration.ts @@ -102,7 +102,7 @@ export interface Configuration { /** * Tells Puppeteer to not chrome-headless-shell download during installation. * - * Can be overridden by `PUPPETEER_SKIP_CHROME_HEADLESSS_HELL_DOWNLOAD`. + * Can be overridden by `PUPPETEER_SKIP_CHROME_HEADLESS_SHELL_DOWNLOAD`. */ skipChromeHeadlessShellDownload?: boolean; /** diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/common/Errors.ts b/remote/test/puppeteer/packages/puppeteer-core/src/common/Errors.ts index 4d0a43ea33..46a3548cf3 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/common/Errors.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/common/Errors.ts @@ -13,8 +13,8 @@ export class PuppeteerError extends Error { /** * @internal */ - constructor(message?: string) { - super(message); + constructor(message?: string, options?: ErrorOptions) { + super(message, options); this.name = this.constructor.name; } diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/common/PDFOptions.ts b/remote/test/puppeteer/packages/puppeteer-core/src/common/PDFOptions.ts index f87ec6817b..e88a94380d 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/common/PDFOptions.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/common/PDFOptions.ts @@ -98,7 +98,7 @@ export interface PDFOptions { headerTemplate?: string; /** * HTML template for the print footer. Has the same constraints and support - * for special classes as {@link PDFOptions | PDFOptions.headerTemplate}. + * for special classes as {@link PDFOptions.headerTemplate}. */ footerTemplate?: string; /** diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/common/util.ts b/remote/test/puppeteer/packages/puppeteer-core/src/common/util.ts index f84453c612..44d22ae01d 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/common/util.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/common/util.ts @@ -312,12 +312,12 @@ export function validateDialogType( /** * @internal */ -export function timeout(ms: number): Observable<never> { +export function timeout(ms: number, cause?: Error): Observable<never> { return ms === 0 ? NEVER : timer(ms).pipe( map(() => { - throw new TimeoutError(`Timed out after waiting ${ms}ms`); + throw new TimeoutError(`Timed out after waiting ${ms}ms`, {cause}); }) ); } diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/node/ChromeLauncher.ts b/remote/test/puppeteer/packages/puppeteer-core/src/node/ChromeLauncher.ts index 0cec3de9ae..9b71952a2e 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/node/ChromeLauncher.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/node/ChromeLauncher.ts @@ -166,6 +166,9 @@ export class ChromeLauncher extends ProductLauncher { removeMatchingFlags(options.args, '--disable-features'); } + const turnOnExperimentalFeaturesForTesting = + process.env['PUPPETEER_TEST_EXPERIMENTAL_CHROME_FEATURES'] === 'true'; + // Merge default disabled features with user-provided ones, if any. const disabledFeatures = [ 'Translate', @@ -174,9 +177,13 @@ export class ChromeLauncher extends ProductLauncher { 'MediaRouter', 'OptimizationHints', // https://crbug.com/1492053 - 'ProcessPerSiteUpToMainFrameThreshold', + turnOnExperimentalFeaturesForTesting + ? '' + : 'ProcessPerSiteUpToMainFrameThreshold', ...userDisabledFeatures, - ]; + ].filter(feature => { + return feature !== ''; + }); const userEnabledFeatures = getFeatures('--enable-features', options.args); if (options.args && userEnabledFeatures.length > 0) { @@ -185,9 +192,11 @@ export class ChromeLauncher extends ProductLauncher { // Merge default enabled features with user-provided ones, if any. const enabledFeatures = [ - 'NetworkServiceInProcess2', + // Add features to enable by default here. ...userEnabledFeatures, - ]; + ].filter(feature => { + return feature !== ''; + }); const chromeArguments = [ '--allow-pre-commit-input', @@ -201,7 +210,9 @@ export class ChromeLauncher extends ProductLauncher { '--disable-default-apps', '--disable-dev-shm-usage', '--disable-extensions', - '--disable-field-trial-config', // https://source.chromium.org/chromium/chromium/src/+/main:testing/variations/README.md + turnOnExperimentalFeaturesForTesting + ? '' + : '--disable-field-trial-config', // https://source.chromium.org/chromium/chromium/src/+/main:testing/variations/README.md '--disable-hang-monitor', '--disable-infobars', '--disable-ipc-flooding-protection', @@ -220,7 +231,9 @@ export class ChromeLauncher extends ProductLauncher { '--use-mock-keychain', `--disable-features=${disabledFeatures.join(',')}`, `--enable-features=${enabledFeatures.join(',')}`, - ]; + ].filter(arg => { + return arg !== ''; + }); const { devtools = false, headless = !devtools, diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/node/NodeWebSocketTransport.ts b/remote/test/puppeteer/packages/puppeteer-core/src/node/NodeWebSocketTransport.ts index f4ac592e4f..13f1e8349c 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/node/NodeWebSocketTransport.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/node/NodeWebSocketTransport.ts @@ -41,14 +41,18 @@ export class NodeWebSocketTransport implements ConnectionTransport { constructor(ws: NodeWebSocket) { this.#ws = ws; this.#ws.addEventListener('message', event => { - if (this.onmessage) { - this.onmessage.call(null, event.data); - } + setImmediate(() => { + if (this.onmessage) { + this.onmessage.call(null, event.data); + } + }); }); this.#ws.addEventListener('close', () => { - if (this.onclose) { - this.onclose.call(null); - } + setImmediate(() => { + if (this.onclose) { + this.onclose.call(null); + } + }); }); // Silently ignore all errors - we don't know what to do with them. this.#ws.addEventListener('error', () => {}); diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/node/ProductLauncher.ts b/remote/test/puppeteer/packages/puppeteer-core/src/node/ProductLauncher.ts index 2da07e8f7c..6f4008f23d 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/node/ProductLauncher.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/node/ProductLauncher.ts @@ -98,6 +98,12 @@ export abstract class ProductLauncher { const launchArgs = await this.computeLaunchArguments(options); + if (!existsSync(launchArgs.executablePath)) { + throw new Error( + `Browser was not found at the configured executablePath (${launchArgs.executablePath})` + ); + } + const usePipe = launchArgs.args.includes('--remote-debugging-pipe'); const onProcessExit = async () => { diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/node/PuppeteerNode.ts b/remote/test/puppeteer/packages/puppeteer-core/src/node/PuppeteerNode.ts index 726ee24cbb..5a5d43bf3c 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/node/PuppeteerNode.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/node/PuppeteerNode.ts @@ -136,11 +136,11 @@ export class PuppeteerNode extends Puppeteer { * specified. * * When using with `puppeteer-core`, - * {@link LaunchOptions | options.executablePath} or - * {@link LaunchOptions | options.channel} must be provided. + * {@link LaunchOptions.executablePath | options.executablePath} or + * {@link LaunchOptions.channel | options.channel} must be provided. * * @example - * You can use {@link LaunchOptions | options.ignoreDefaultArgs} + * You can use {@link LaunchOptions.ignoreDefaultArgs | options.ignoreDefaultArgs} * to filter out `--mute-audio` from default arguments: * * ```ts diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/revisions.ts b/remote/test/puppeteer/packages/puppeteer-core/src/revisions.ts index c543cd9517..b75257ab50 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/revisions.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/revisions.ts @@ -8,7 +8,7 @@ * @internal */ export const PUPPETEER_REVISIONS = Object.freeze({ - chrome: '122.0.6261.94', - 'chrome-headless-shell': '122.0.6261.94', + chrome: '123.0.6312.122', + 'chrome-headless-shell': '123.0.6312.122', firefox: 'latest', }); diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/util/Function.ts b/remote/test/puppeteer/packages/puppeteer-core/src/util/Function.ts index 41db98830b..497a31501d 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/util/Function.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/util/Function.ts @@ -72,7 +72,7 @@ export const interpolateFunction = <T extends (...args: never[]) => unknown>( for (const [name, jsValue] of Object.entries(replacements)) { value = value.replace( new RegExp(`PLACEHOLDER\\(\\s*(?:'${name}'|"${name}")\\s*\\)`, 'g'), - // Wrapping this ensures tersers that accidently inline PLACEHOLDER calls + // Wrapping this ensures tersers that accidentally inline PLACEHOLDER calls // are still valid. Without, we may get calls like ()=>{...}() which is // not valid. `(${jsValue})` diff --git a/remote/test/puppeteer/packages/puppeteer/CHANGELOG.md b/remote/test/puppeteer/packages/puppeteer/CHANGELOG.md index 8f98608527..e8fa0d38b8 100644 --- a/remote/test/puppeteer/packages/puppeteer/CHANGELOG.md +++ b/remote/test/puppeteer/packages/puppeteer/CHANGELOG.md @@ -29,6 +29,121 @@ All notable changes to this project will be documented in this file. See [standa * puppeteer-core bumped from 21.0.2 to 21.0.3 * @puppeteer/browsers bumped from 1.5.1 to 1.6.0 +## [22.6.5](https://github.com/puppeteer/puppeteer/compare/puppeteer-v22.6.4...puppeteer-v22.6.5) (2024-04-15) + + +### Miscellaneous Chores + +* **puppeteer:** Synchronize puppeteer versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * puppeteer-core bumped from 22.6.4 to 22.6.5 + * @puppeteer/browsers bumped from 2.2.1 to 2.2.2 + +## [22.6.4](https://github.com/puppeteer/puppeteer/compare/puppeteer-v22.6.3...puppeteer-v22.6.4) (2024-04-11) + + +### Miscellaneous Chores + +* **puppeteer:** Synchronize puppeteer versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * puppeteer-core bumped from 22.6.3 to 22.6.4 + +## [22.6.3](https://github.com/puppeteer/puppeteer/compare/puppeteer-v22.6.2...puppeteer-v22.6.3) (2024-04-05) + + +### Bug Fixes + +* deprecate configuration via package.json ([#12176](https://github.com/puppeteer/puppeteer/issues/12176)) ([c96c762](https://github.com/puppeteer/puppeteer/commit/c96c7623bc2258ba7419812333ec42cdf83bf432)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * puppeteer-core bumped from 22.6.2 to 22.6.3 + * @puppeteer/browsers bumped from 2.2.0 to 2.2.1 + +## [22.6.2](https://github.com/puppeteer/puppeteer/compare/puppeteer-v22.6.1...puppeteer-v22.6.2) (2024-03-28) + + +### Miscellaneous Chores + +* **puppeteer:** Synchronize puppeteer versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * puppeteer-core bumped from 22.6.1 to 22.6.2 + +## [22.6.1](https://github.com/puppeteer/puppeteer/compare/puppeteer-v22.6.0...puppeteer-v22.6.1) (2024-03-25) + + +### Miscellaneous Chores + +* **puppeteer:** Synchronize puppeteer versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * puppeteer-core bumped from 22.6.0 to 22.6.1 + +## [22.6.0](https://github.com/puppeteer/puppeteer/compare/puppeteer-v22.5.0...puppeteer-v22.6.0) (2024-03-20) + + +### Features + +* roll to Chrome 123.0.6312.58 (r1262506) ([#12110](https://github.com/puppeteer/puppeteer/issues/12110)) ([6f5b3bc](https://github.com/puppeteer/puppeteer/commit/6f5b3bc9b88c6d3204dda396f8963591ea6eb883)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * puppeteer-core bumped from 22.5.0 to 22.6.0 + +## [22.5.0](https://github.com/puppeteer/puppeteer/compare/puppeteer-v22.4.1...puppeteer-v22.5.0) (2024-03-15) + + +### Miscellaneous Chores + +* **puppeteer:** Synchronize puppeteer versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * puppeteer-core bumped from 22.4.1 to 22.5.0 + * @puppeteer/browsers bumped from 2.1.0 to 2.2.0 + +## [22.4.1](https://github.com/puppeteer/puppeteer/compare/puppeteer-v22.4.0...puppeteer-v22.4.1) (2024-03-08) + + +### Miscellaneous Chores + +* **puppeteer:** Synchronize puppeteer versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * puppeteer-core bumped from 22.4.0 to 22.4.1 + ## [22.4.0](https://github.com/puppeteer/puppeteer/compare/puppeteer-v22.3.0...puppeteer-v22.4.0) (2024-03-05) diff --git a/remote/test/puppeteer/packages/puppeteer/package.json b/remote/test/puppeteer/packages/puppeteer/package.json index 88637f3add..6d3b385640 100644 --- a/remote/test/puppeteer/packages/puppeteer/package.json +++ b/remote/test/puppeteer/packages/puppeteer/package.json @@ -1,6 +1,6 @@ { "name": "puppeteer", - "version": "22.4.0", + "version": "22.6.5", "description": "A high-level API to control headless Chrome over the DevTools Protocol", "keywords": [ "puppeteer", @@ -124,8 +124,9 @@ "license": "Apache-2.0", "dependencies": { "cosmiconfig": "9.0.0", - "puppeteer-core": "22.4.0", - "@puppeteer/browsers": "2.1.0" + "puppeteer-core": "22.6.5", + "@puppeteer/browsers": "2.2.2", + "devtools-protocol": "0.0.1262051" }, "devDependencies": { "@types/node": "18.17.15" diff --git a/remote/test/puppeteer/packages/puppeteer/src/getConfiguration.ts b/remote/test/puppeteer/packages/puppeteer/src/getConfiguration.ts index 6fd88678a4..ddd3386a70 100644 --- a/remote/test/puppeteer/packages/puppeteer/src/getConfiguration.ts +++ b/remote/test/puppeteer/packages/puppeteer/src/getConfiguration.ts @@ -127,6 +127,17 @@ export const getConfiguration = (): Configuration => { downloadHost; } + if ( + Object.keys(process.env).some(key => { + return key.startsWith('npm_package_config_puppeteer_'); + }) && + configuration.logLevel === 'warn' + ) { + console.warn( + `Configuring Puppeteer via npm/package.json is deprecated. Use https://pptr.dev/guides/configuration instead.` + ); + } + configuration.cacheDirectory = process.env['PUPPETEER_CACHE_DIR'] ?? process.env['npm_config_puppeteer_cache_dir'] ?? diff --git a/remote/test/puppeteer/test/.eslintrc.js b/remote/test/puppeteer/test/.eslintrc.js index 5f7746a76b..ea697b0a47 100644 --- a/remote/test/puppeteer/test/.eslintrc.js +++ b/remote/test/puppeteer/test/.eslintrc.js @@ -36,6 +36,12 @@ module.exports = { selector: 'CallExpression[callee.object.name="it"] > MemberExpression > Identifier[name="deflake"], CallExpression[callee.object.name="it"] > MemberExpression > Identifier[name="deflakeOnly"]', }, + { + message: + 'No `expect` in EventHandler. They will never throw errors', + selector: + 'CallExpression[callee.property.name="on"] BlockStatement > :not(TryStatement) > ExpressionStatement > CallExpression[callee.object.callee.name="expect"]', + }, ], }, }, diff --git a/remote/test/puppeteer/test/TestExpectations.json b/remote/test/puppeteer/test/TestExpectations.json index e55f223441..9f021882f5 100644 --- a/remote/test/puppeteer/test/TestExpectations.json +++ b/remote/test/puppeteer/test/TestExpectations.json @@ -18,7 +18,7 @@ "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "comment": "Chrome-only feature blocked on https://github.com/GoogleChromeLabs/chromium-bidi/issues/2082" }, { "testIdPattern": "[device-request-prompt.spec] *", @@ -58,9 +58,9 @@ { "testIdPattern": "[headful.spec] *", "platforms": ["darwin", "linux", "win32"], - "parameters": ["headless", "firefox"], + "parameters": ["headless"], "expectations": ["SKIP"], - "comment": "Cannot be run in headless mode" + "comment": "Spawns headful browser, needs display or `xvfb` like which is not required for other headless tests" }, { "testIdPattern": "[idle_override.spec] *", @@ -84,20 +84,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[network.spec] network Page.authenticate *", - "platforms": ["darwin", "linux"], - "parameters": ["webDriverBiDi"], - "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { - "testIdPattern": "[network.spec] network Page.authenticate *", - "platforms": ["win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[network.spec] network Page.setBypassServiceWorker *", "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox"], @@ -112,13 +98,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[network.spec] network Page.setExtraHTTPHeaders *", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[network.spec] network Request.isNavigationRequest *", "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], @@ -175,25 +154,18 @@ "comment": "Chrome-specific test" }, { - "testIdPattern": "[requestinterception-experimental.spec] *", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { - "testIdPattern": "[requestinterception.spec] *", + "testIdPattern": "[screencast.spec] *", "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], + "parameters": ["chrome"], "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "comment": "Currently no reliable ffmpeg downloads for testing https://github.com/puppeteer/puppeteer/issues/12121" }, { "testIdPattern": "[screencast.spec] *", "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox"], "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "comment": "CDP-specific feature" }, { "testIdPattern": "[screenshot.spec] Screenshots Cdp *", @@ -224,20 +196,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[ariaqueryhandler.spec] AriaQueryHandler queryOne (Chromium web test) should find by role \"button\"", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { - "testIdPattern": "[ariaqueryhandler.spec] AriaQueryHandler queryOne (Chromium web test) should find by role \"heading\"", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[autofill.spec] *", "platforms": ["darwin", "linux", "win32"], "parameters": ["chrome", "chrome-headless-shell"], @@ -363,14 +321,14 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[devtools.spec] DevTools target.page() should return a DevTools page if asPage is used", + "testIdPattern": "[devtools.spec] DevTools target.page() should return a DevTools page if custom isPageTarget is provided", "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox"], "expectations": ["SKIP"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[devtools.spec] DevTools target.page() should return a DevTools page if custom isPageTarget is provided", + "testIdPattern": "[devtools.spec] DevTools target.page() should return Page when calling asPage on DevTools target", "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox"], "expectations": ["SKIP"], @@ -391,13 +349,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.clickablePoint should not work if the click box is not visible due to the iframe", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should replace symbols with undefined", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp"], @@ -440,13 +391,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[frame.spec] Frame specs Frame Management should handle nested frames", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[frame.spec] Frame specs Frame Management should report frame.name()", "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], @@ -482,17 +426,17 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[jshandle.spec] JSHandle Page.evaluateHandle should return the RemoteObject", + "testIdPattern": "[jshandle.spec] JSHandle JSHandle.toString should work with window subtypes", "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], + "parameters": ["cdp"], "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "comment": "CDP does not have special type for window" }, { - "testIdPattern": "[keyboard.spec] Keyboard should send a character with sendCharacter in iframe", + "testIdPattern": "[jshandle.spec] JSHandle Page.evaluateHandle should return the RemoteObject", "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], - "expectations": ["SKIP"], + "expectations": ["FAIL"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { @@ -580,6 +524,13 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { + "testIdPattern": "[navigation.spec] navigation Page.goto should navigate to URL with hash and fire requests without hash", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "BiDi spec expect the request to not trim the hash" + }, + { "testIdPattern": "[navigation.spec] navigation Page.goto should send referer", "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], @@ -599,11 +550,11 @@ "expectations": ["PASS"] }, { - "testIdPattern": "[network.spec] network Page.setBypassServiceWorker *", - "platforms": ["win32"], + "testIdPattern": "[network.spec] network Page.setExtraHTTPHeaders *", + "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "comment": "Firefox does not support headers override" }, { "testIdPattern": "[network.spec] network Request.initiator should return the initiator", @@ -625,6 +576,12 @@ "expectations": ["PASS"] }, { + "testIdPattern": "[network.spec] network Request.isNavigationRequest should work with request interception", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { "testIdPattern": "[network.spec] network Request.postData should be |undefined| when there is no post data", "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], @@ -671,7 +628,7 @@ "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "comment": "See https://github.com/puppeteer/puppeteer/issues/4840" }, { "testIdPattern": "[page.spec] Page Page.addStyleTag should throw when added with content to the CSP page", @@ -681,13 +638,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[page.spec] Page Page.bringToFront should work", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[page.spec] Page Page.close should *not* run beforeunload by default", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -713,7 +663,7 @@ "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "comment": "BiDi does not support getting a Handle for log args" }, { "testIdPattern": "[page.spec] Page Page.Events.Console should return remote objects", @@ -744,18 +694,11 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[page.spec] Page Page.exposeFunction should work with loading frames", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["SKIP"], - "comment": "Missing request interception" - }, - { "testIdPattern": "[page.spec] Page Page.pdf should respect timeout", "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox"], "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "comment": "https://github.com/puppeteer/puppeteer/issues/12152" }, { "testIdPattern": "[page.spec] Page Page.setBypassCSP *", @@ -765,13 +708,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[page.spec] Page Page.setCacheEnabled should stay disabled when toggling request interception on/off", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[page.spec] Page Page.setOfflineMode should emulate navigator.onLine", "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], @@ -802,6 +738,13 @@ { "testIdPattern": "[prerender.spec] Prerender can screencast", "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome"], + "expectations": ["SKIP"], + "comment": "Currently no reliable ffmpeg downloads for testing https://github.com/puppeteer/puppeteer/issues/12121" + }, + { + "testIdPattern": "[prerender.spec] Prerender can screencast", + "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox"], "expectations": ["SKIP"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" @@ -849,6 +792,27 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should navigate to URL with hash and fire requests without hash", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "BiDi spec expect the request to not trim the hash" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Request.continue *", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs full support for continueRequest in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1850680" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Request.respond *", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs full support for continueResponse in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1853887" + }, + { "testIdPattern": "[requestinterception.spec] *", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -856,6 +820,55 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should be abortable with custom error codes", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "`HTTPRequest.resourceType()` has no eqivalent in BiDi spec" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should intercept", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "`request.postData()` has no eqivalent in BiDi spec" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should navigate to URL with hash and fire requests without hash", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "BiDi spec and WPT require expect the Hash" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work with redirects", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "`HTTPRequest.resourceType()` has no eqivalent in BiDi spec" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work with redirects for subresources", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "`HTTPRequest.resourceType()` has no eqivalent in BiDi spec" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Request.continue *", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs full support for continueRequest in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1850680" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Request.respond *", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs full support for continueResponse in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1853887" + }, + { "testIdPattern": "[screencast.spec] Screencasts Page.screencast should validate options", "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox"], @@ -953,53 +966,74 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[bfcache.spec] BFCache can navigate to a BFCached page containing an OOPIF and a worker", + "testIdPattern": "[accessibility.spec] Accessibility should work", "platforms": ["darwin", "linux", "win32"], - "parameters": ["cdp", "firefox"], - "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "parameters": ["chrome", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "Change in A11Y tree on Canary" }, { - "testIdPattern": "[browser.spec] Browser specs Browser.isConnected should set the browser connected state", + "testIdPattern": "[ariaqueryhandler.spec] AriaQueryHandler queryAllArray $$eval should handle many elements", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "webDriverBiDi"], + "expectations": ["PASS", "TIMEOUT"], + "comment": "times out flakily" + }, + { + "testIdPattern": "[ariaqueryhandler.spec] AriaQueryHandler queryOne (Chromium web test) should find by role \"button\"", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "Querying by a11y attributes is not standard behavior" + }, + { + "testIdPattern": "[ariaqueryhandler.spec] AriaQueryHandler queryOne (Chromium web test) should find by role \"heading\"", "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "Querying by a11y attributes is not standard behavior" + }, + { + "testIdPattern": "[bfcache.spec] BFCache can navigate to a BFCached page containing an OOPIF and a worker", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["cdp", "firefox"], "expectations": ["SKIP"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[browser.spec] Browser specs Browser.process should not return child_process for remote browser", + "testIdPattern": "[browser.spec] Browser specs Browser.isConnected should set the browser connected state", "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], "expectations": ["SKIP"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[browser.spec] Browser specs Browser.userAgent should include Browser engine", + "testIdPattern": "[browser.spec] Browser specs Browser.process should keep connected after the last page is closed", "platforms": ["darwin", "linux", "win32"], "parameters": ["chrome", "webDriverBiDi"], "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "comment": "The new headless does not allow opening a tab after the browser was closed" }, { - "testIdPattern": "[browser.spec] Browser specs Browser.userAgent should include Browser engine", + "testIdPattern": "[browser.spec] Browser specs Browser.process should not return child_process for remote browser", "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["FAIL"], + "expectations": ["SKIP"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[browser.spec] Browser specs Browser.version should return version", + "testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should deny permission when not listed", "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], + "parameters": ["cdp", "firefox"], "expectations": ["FAIL"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { "testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should deny permission when not listed", "platforms": ["darwin", "linux", "win32"], - "parameters": ["cdp", "firefox"], + "parameters": ["firefox", "webDriverBiDi"], "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1894217" }, { "testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should fail when bad permission is given", @@ -1015,6 +1049,13 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { + "testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should grant permission when listed", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1894217" + }, + { "testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should grant persistent-storage", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -1022,6 +1063,13 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { + "testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should grant persistent-storage", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1894217" + }, + { "testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should isolate permissions between browser contexts", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -1029,6 +1077,13 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { + "testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should isolate permissions between browser contexts", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1894217" + }, + { "testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should reset permissions", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -1036,6 +1091,13 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { + "testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should reset permissions", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1894217" + }, + { "testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should trigger permission onchange", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -1043,10 +1105,11 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[browsercontext.spec] BrowserContext should create new incognito context", + "testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should trigger permission onchange", "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["PASS"] + "expectations": ["FAIL"], + "comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1894217" }, { "testIdPattern": "[browsercontext.spec] BrowserContext should fire target events", @@ -1063,12 +1126,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[browsercontext.spec] BrowserContext should have default context", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["PASS"] - }, - { "testIdPattern": "[browsercontext.spec] BrowserContext should wait for a target", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -1106,13 +1163,6 @@ { "testIdPattern": "[CDPSession.spec] Target.createCDPSession should respect custom timeout", "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { - "testIdPattern": "[CDPSession.spec] Target.createCDPSession should respect custom timeout", - "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], "expectations": ["SKIP"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" @@ -1139,13 +1189,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[chromiumonly.spec] Chromium-Specific Page Tests Page.setRequestInterception should work with intervention headers", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[click.spec] Page.click should click on checkbox label and toggle", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -1160,20 +1203,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[click.spec] Page.click should click the button with deviceScaleFactor set", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { - "testIdPattern": "[click.spec] Page.click should click the button with fixed position inside an iframe", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[click.spec] Page.click should click the button with fixed position inside an iframe", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -1248,7 +1277,7 @@ "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], "expectations": ["FAIL"], - "comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1884648" + "comment": "Firefox default partition key is inconsistent: #12004" }, { "testIdPattern": "[cookies.spec] Cookie specs Page.deleteCookie should delete cookie", @@ -1269,7 +1298,7 @@ "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], "expectations": ["FAIL"], - "comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1884648" + "comment": "Firefox default partition key is inconsistent: #12004" }, { "testIdPattern": "[cookies.spec] Cookie specs Page.deleteCookie should not delete cookie for different domain", @@ -1311,7 +1340,7 @@ "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], "expectations": ["FAIL"], - "comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1884648" + "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { "testIdPattern": "[cookies.spec] Cookie specs Page.setCookie should set cookie with reasonable defaults", @@ -1332,7 +1361,7 @@ "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], "expectations": ["FAIL"], - "comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1884648" + "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { "testIdPattern": "[cookies.spec] Cookie specs Page.setCookie should set multiple cookies", @@ -1346,7 +1375,7 @@ "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], "expectations": ["FAIL"], - "comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1884648" + "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { "testIdPattern": "[cookies.spec] Cookie specs Page.setCookie should set secure same-site cookies from a frame", @@ -1367,7 +1396,7 @@ "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], "expectations": ["FAIL"], - "comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1884648" + "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { "testIdPattern": "[debugInfo.spec] DebugInfo Browser.debugInfo should work", @@ -1381,7 +1410,7 @@ "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], "expectations": ["FAIL", "PASS"], - "comment": "https://github.com/puppeteer/puppeteer/issues/12010" + "comment": "Firefox CDP does not support isolation so this test might fail if other tests set cookies" }, { "testIdPattern": "[defaultbrowsercontext.spec] DefaultBrowserContext page.deleteCookie() should work", @@ -1395,7 +1424,7 @@ "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], "expectations": ["FAIL"], - "comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1884648" + "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { "testIdPattern": "[defaultbrowsercontext.spec] DefaultBrowserContext page.setCookie() should work", @@ -1409,7 +1438,7 @@ "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], "expectations": ["FAIL"], - "comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1884648" + "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { "testIdPattern": "[device-request-prompt.spec] device request prompt does not crash", @@ -1594,13 +1623,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[emulation.spec] Emulation Page.viewport should load correct pictures when emulation dpr", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[emulation.spec] Emulation Page.viewport should support landscape emulation", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -1622,13 +1644,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[emulation.spec] Emulation Page.viewport should update media queries when resoltion changes", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should simulate a user gesture", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -1755,6 +1770,12 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { + "testIdPattern": "[idle_override.spec] Emulate idle state changing idle state emulation causes change of the IdleDetector state", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "webDriverBiDi"], + "expectations": ["PASS"] + }, + { "testIdPattern": "[ignorehttpserrors.spec] ignoreHTTPSErrors Response.securityDetails Network redirects should report SecurityDetails", "platforms": ["darwin", "linux", "win32"], "parameters": ["chrome", "webDriverBiDi"], @@ -1813,25 +1834,11 @@ { "testIdPattern": "[ignorehttpserrors.spec] ignoreHTTPSErrors should work with request interception", "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { - "testIdPattern": "[ignorehttpserrors.spec] ignoreHTTPSErrors should work with request interception", - "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], "expectations": ["FAIL"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[ignorehttpserrors.spec] ignoreHTTPSErrors should work with request interception", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[input.spec] input tests FileChooser.accept should accept single file", "platforms": ["darwin", "linux", "win32"], "parameters": ["chrome", "webDriverBiDi"], @@ -2212,13 +2219,6 @@ { "testIdPattern": "[launcher.spec] Launcher specs Puppeteer Browser.disconnect should reject navigation when browser closes", "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["SKIP"], - "comment": "https://github.com/puppeteer/puppeteer/issues/11849" - }, - { - "testIdPattern": "[launcher.spec] Launcher specs Puppeteer Browser.disconnect should reject navigation when browser closes", - "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], "expectations": ["FAIL", "PASS"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" @@ -2228,7 +2228,7 @@ "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "comment": "https://github.com/puppeteer/puppeteer/issues/11849" }, { "testIdPattern": "[launcher.spec] Launcher specs Puppeteer Puppeteer.connect should be able to close remote browser", @@ -2350,20 +2350,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[launcher.spec] Launcher specs Puppeteer Puppeteer.launch can launch and close the browser", - "platforms": ["win32"], - "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { - "testIdPattern": "[launcher.spec] Launcher specs Puppeteer Puppeteer.launch should be able to launch Chrome", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[launcher.spec] Launcher specs Puppeteer Puppeteer.launch should be able to launch Firefox", "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], @@ -2546,20 +2532,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[navigation.spec] navigation Page.goto should navigate to URL with hash and fire requests without hash", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { - "testIdPattern": "[navigation.spec] navigation Page.goto should not leak listeners during navigation", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[navigation.spec] navigation Page.goto should send referer", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -2696,8 +2668,7 @@ "testIdPattern": "[network.spec] network Network Events Page.Events.RequestServedFromCache", "platforms": ["darwin", "linux", "win32"], "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "expectations": ["PASS"] }, { "testIdPattern": "[network.spec] network Network Events Page.Events.Response", @@ -2742,6 +2713,13 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { + "testIdPattern": "[network.spec] network Page.authenticate should allow disable authentication", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL", "PASS"], + "comment": "FAIL: The Puppeteer implementation does not expect 2 responseCompleted events (that AuthRequired triggered). PASS: Only one event on late beta (Bug 1893664)." + }, + { "testIdPattern": "[network.spec] network Page.authenticate should fail if wrong credentials", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -2756,6 +2734,13 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { + "testIdPattern": "[network.spec] network Page.authenticate should not disable caching", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "Firefox returns `fromCache: false`" + }, + { "testIdPattern": "[network.spec] network Page.authenticate should work", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -2763,6 +2748,20 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { + "testIdPattern": "[network.spec] network Page.authenticate should work", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "webDriverBiDi"], + "expectations": ["FAIL", "PASS"], + "comment": "Flaky see https://github.com/puppeteer/puppeteer/issues/12253" + }, + { + "testIdPattern": "[network.spec] network Page.authenticate should work", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["TIMEOUT"], + "comment": "When navigating to page with authentication the command response (error) never comes without interception" + }, + { "testIdPattern": "[network.spec] network Page.setExtraHTTPHeaders should work", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -2847,13 +2846,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[network.spec] network Response.buffer should throw if the response does not have a body", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[network.spec] network Response.buffer should work", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -2890,13 +2882,6 @@ }, { "testIdPattern": "[network.spec] network Response.fromServiceWorker Response.fromServiceWorker", - "platforms": ["win32"], - "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { - "testIdPattern": "[network.spec] network Response.fromServiceWorker Response.fromServiceWorker", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], "expectations": ["SKIP"], @@ -2931,13 +2916,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[network.spec] network Response.text should wait until response completes", - "platforms": ["win32"], - "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[network.spec] network Response.text should work", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -2980,38 +2958,17 @@ "comment": "Failed previously and currently times out" }, { - "testIdPattern": "[oopif.spec] OOPIF should load oopif iframes with subresources and request interception", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { - "testIdPattern": "[oopif.spec] OOPIF should load oopif iframes with subresources and request interception", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { - "testIdPattern": "[oopif.spec] OOPIF should report google.com frame", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[oopif.spec] OOPIF should report google.com frame", "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["FAIL"], + "expectations": ["FAIL", "TIMEOUT"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { "testIdPattern": "[oopif.spec] OOPIF should support lazy OOP frames", "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["FAIL"], + "expectations": ["FAIL", "TIMEOUT"], "comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=187816" }, { @@ -3082,7 +3039,7 @@ "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "chrome"], "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "comment": "See https://github.com/puppeteer/puppeteer/issues/4840" }, { "testIdPattern": "[page.spec] Page Page.addScriptTag should throw when added with content to the CSP page", @@ -3311,6 +3268,13 @@ { "testIdPattern": "[page.spec] Page Page.setCacheEnabled should stay disabled when toggling request interception on/off", "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + }, + { + "testIdPattern": "[page.spec] Page Page.setCacheEnabled should stay disabled when toggling request interception on/off", + "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], "expectations": ["FAIL"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" @@ -3369,14 +3333,14 @@ "platforms": ["darwin", "linux", "win32"], "parameters": ["chrome", "headful"], "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "comment": "only works in the old headless code" }, { "testIdPattern": "[pdf.spec] Page.pdf can print to PDF with outline", "platforms": ["darwin", "linux", "win32"], "parameters": ["chrome", "headless"], "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "comment": "only works in the old headless code" }, { "testIdPattern": "[proxy.spec] request proxy in incognito browser context should proxy requests when configured at context level", @@ -3426,6 +3390,158 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should be able to fetch dataURL and fire dataURL requests", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for data URIs in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1805176" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should be able to remove headers", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs investigation, on Firefox the test is passing even if the origin header is not removed" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should be abortable with custom error codes", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Test relies on chrome-only error code (Firefox currently outputs NS_ERROR_ABORT)" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should cache if cache enabled", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for enabling cache in BiDi without CDP https://github.com/w3c/webdriver-bidi/issues/582" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should cooperatively continue by priority", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs full support for continueRequest in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1850680" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should cooperatively respond by priority", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs full support for continueRequest in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1850680" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should intercept", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs Puppeteer support for BidiHTTPRequest.postData" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should load fonts if cache enabled", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for enabling cache in BiDi without CDP https://github.com/w3c/webdriver-bidi/issues/582" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should navigate to dataURL and fire dataURL requests", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for data URIs in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1805176" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should not cache if cache disabled", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for enabling cache in BiDi without CDP https://github.com/w3c/webdriver-bidi/issues/582" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should send referer", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "Firefox does not support headers override" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should show custom HTTP headers", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "Firefox does not support headers override" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should work when header manipulation headers with redirect", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs investigation, on Firefox the test is passing even if headers are not actually modified" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should work with custom referer headers", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "Firefox does not support headers override" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should work with encoded server - 2", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for data URIs in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1805176" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should work with equal requests", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "TODO: Needs investigation, it looks like Firefox lets the request go also to the server" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should work with file URLs", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for file URIs in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1826210" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should work with redirects", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for BidiHTTPRequest.resourceType" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should work with redirects for subresources", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for BidiHTTPRequest.resourceType" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should work with requests without networkId", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "Test requires CDP" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Request.continue should work", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Request.respond should indicate already-handled if an intercept has been handled", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["PASS"] + }, + { "testIdPattern": "[requestinterception-experimental.spec] request interception \"after each\" hook in \"request interception\"", "platforms": ["win32"], "parameters": ["cdp", "chrome"], @@ -3454,6 +3570,55 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should be able to fetch dataURL and fire dataURL requests", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for data URIs in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1805176" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should be able to remove headers", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs investigation, on Firefox the test is passing even if the origin header is not removed" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should be abortable with custom error codes", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Test relies on chrome-only error code (Firefox currently outputs NS_ERROR_ABORT)" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should cache if cache enabled", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for enabling cache in BiDi without CDP https://github.com/w3c/webdriver-bidi/issues/582" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should intercept", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs Puppeteer support for BidiHTTPRequest.postData" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should load fonts if cache enabled", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for enabling cache in BiDi without CDP https://github.com/w3c/webdriver-bidi/issues/582" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should navigate to dataURL and fire dataURL requests", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for data URIs in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1805176" + }, + { "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should navigate to URL with hash and fire requests without hash", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "chrome"], @@ -3461,6 +3626,117 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should not cache if cache disabled", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for enabling cache in BiDi without CDP https://github.com/w3c/webdriver-bidi/issues/582" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should send referer", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "Firefox does not support headers override" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should send referer", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "Firefox does not support headers override" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should show custom HTTP headers", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "Firefox does not support headers override" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should show custom HTTP headers", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "Firefox does not support headers override" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work when header manipulation headers with redirect", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs investigation, on Firefox the test is passing even if headers are not actually modified" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work with custom referer headers", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "Firefox does not support headers override" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work with custom referer headers", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "Firefox does not support headers override" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work with encoded server - 2", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for data URIs in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1805176" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work with equal requests", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "TODO: Needs investigation" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work with file URLs", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for file URIs in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1826210" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work with redirects", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for BidiHTTPRequest.resourceType" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work with redirects for subresources", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for BidiHTTPRequest.resourceType" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work with requests without networkId", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "Test requires CDP" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work with requests without networkId", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "CDP specific issue, maybe we can support it from BiDi+" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Request.continue should work", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["PASS"] + }, + { "testIdPattern": "[screenshot.spec] Screenshots Cdp should use scale for clip", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -3789,6 +4065,12 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { + "testIdPattern": "[worker.spec] Workers should report errors", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["PASS"] + }, + { "testIdPattern": "[CDPSession.spec] Target.createCDPSession should send events", "platforms": ["win32"], "parameters": ["cdp", "chrome", "headless"], @@ -3810,14 +4092,14 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[devtools.spec] DevTools target.page() should return a DevTools page if asPage is used", + "testIdPattern": "[devtools.spec] DevTools target.page() should return a DevTools page if custom isPageTarget is provided", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "chrome", "chrome-headless-shell"], "expectations": ["SKIP"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[devtools.spec] DevTools target.page() should return a DevTools page if custom isPageTarget is provided", + "testIdPattern": "[devtools.spec] DevTools target.page() should return Page when calling asPage on DevTools target", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "chrome", "chrome-headless-shell"], "expectations": ["SKIP"], @@ -3845,13 +4127,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[launcher.spec] Launcher specs Puppeteer Puppeteer.launch userDataDir option restores preferences", - "platforms": ["win32"], - "parameters": ["firefox", "headless", "webDriverBiDi"], - "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[network.spec] network Network Events Page.Events.Request", "platforms": ["linux"], "parameters": ["cdp", "chrome", "headless"], @@ -3873,52 +4148,52 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should be abortable", + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should be abortable with custom error codes", "platforms": ["darwin", "linux", "win32"], - "parameters": ["cdp", "chrome", "headful"], - "expectations": ["FAIL", "PASS"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "parameters": ["chrome", "headless", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "`HTTPRequest.resourceType()` has no eqivalent in BiDi spec" }, { - "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work with redirects", - "platforms": ["win32"], - "parameters": ["cdp", "chrome", "headless"], - "expectations": ["FAIL", "PASS"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should intercept", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "headless", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "`request.postData()` has no eqivalent in BiDi spec" }, { - "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work with redirects", - "platforms": ["win32"], - "parameters": ["cdp", "chrome", "headful"], - "expectations": ["FAIL", "PASS"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should work with redirects", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "headless", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "`HTTPRequest.resourceType()` has no eqivalent in BiDi spec" }, { - "testIdPattern": "[screenshot.spec] Screenshots ElementHandle.screenshot should work for an element with an offset", + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should work with redirects for subresources", "platforms": ["darwin", "linux", "win32"], - "parameters": ["cdp", "firefox", "headful"], + "parameters": ["chrome", "headless", "webDriverBiDi"], "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "comment": "`HTTPRequest.resourceType()` has no eqivalent in BiDi spec" }, { - "testIdPattern": "[screenshot.spec] Screenshots ElementHandle.screenshot should work for an element with an offset", + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should be abortable", "platforms": ["darwin", "linux", "win32"], - "parameters": ["cdp", "firefox", "headless"], - "expectations": ["FAIL"], + "parameters": ["cdp", "chrome", "headful"], + "expectations": ["FAIL", "PASS"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[screenshot.spec] Screenshots ElementHandle.screenshot should work with a rotated element", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["cdp", "firefox", "headful"], - "expectations": ["FAIL"], + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work with redirects", + "platforms": ["win32"], + "parameters": ["cdp", "chrome", "headless"], + "expectations": ["FAIL", "PASS"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[screenshot.spec] Screenshots ElementHandle.screenshot should work with a rotated element", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["cdp", "firefox", "headless"], - "expectations": ["FAIL"], + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work with redirects", + "platforms": ["win32"], + "parameters": ["cdp", "chrome", "headful"], + "expectations": ["FAIL", "PASS"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { diff --git a/remote/test/puppeteer/test/TestSuites.json b/remote/test/puppeteer/test/TestSuites.json index 3c36f8d7a4..1b36a3ef4c 100644 --- a/remote/test/puppeteer/test/TestSuites.json +++ b/remote/test/puppeteer/test/TestSuites.json @@ -45,7 +45,7 @@ { "id": "chrome-bidi", "platforms": ["linux"], - "parameters": ["chrome", "chrome-headless-shell", "webDriverBiDi"], + "parameters": ["chrome", "headless", "webDriverBiDi"], "expectedLineCoverage": 56 } ], diff --git a/remote/test/puppeteer/test/golden-chrome/screenshot-element-clip.png b/remote/test/puppeteer/test/golden-chrome/screenshot-element-clip.png Binary files differnew file mode 100644 index 0000000000..609952cd3d --- /dev/null +++ b/remote/test/puppeteer/test/golden-chrome/screenshot-element-clip.png diff --git a/remote/test/puppeteer/test/golden-firefox/screenshot-element-clip.png b/remote/test/puppeteer/test/golden-firefox/screenshot-element-clip.png Binary files differnew file mode 100644 index 0000000000..609952cd3d --- /dev/null +++ b/remote/test/puppeteer/test/golden-firefox/screenshot-element-clip.png diff --git a/remote/test/puppeteer/test/installation/assets/puppeteer-core/launch.js b/remote/test/puppeteer/test/installation/assets/puppeteer-core/launch.js index 4776d7e261..b0982cdd90 100644 --- a/remote/test/puppeteer/test/installation/assets/puppeteer-core/launch.js +++ b/remote/test/puppeteer/test/installation/assets/puppeteer-core/launch.js @@ -13,7 +13,11 @@ import puppeteer from 'puppeteer-core'; executablePath: 'node', }); } catch (error) { - if (error.message.includes('Failed to launch the browser process')) { + if ( + error.message.includes( + 'Browser was not found at the configured executablePath (node)' + ) + ) { process.exit(0); } console.error(error); diff --git a/remote/test/puppeteer/test/installation/package.json b/remote/test/puppeteer/test/installation/package.json index bd21ac4b0a..17ee450391 100644 --- a/remote/test/puppeteer/test/installation/package.json +++ b/remote/test/puppeteer/test/installation/package.json @@ -44,7 +44,7 @@ "assets" ], "dependencies": { - "glob": "10.3.10", - "mocha": "10.3.0" + "glob": "10.3.12", + "mocha": "10.4.0" } } diff --git a/remote/test/puppeteer/test/installation/src/puppeteer-firefox.spec.ts b/remote/test/puppeteer/test/installation/src/puppeteer-firefox.spec.ts index b599af01dc..addaebbbd0 100644 --- a/remote/test/puppeteer/test/installation/src/puppeteer-firefox.spec.ts +++ b/remote/test/puppeteer/test/installation/src/puppeteer-firefox.spec.ts @@ -5,6 +5,8 @@ */ import assert from 'assert'; +import {spawnSync} from 'child_process'; +import {existsSync} from 'fs'; import {readdir} from 'fs/promises'; import {platform} from 'os'; import {join} from 'path'; @@ -49,3 +51,36 @@ import {readAsset} from './util.js'; }); } ); + +describe('Firefox download', () => { + configureSandbox({ + dependencies: ['@puppeteer/browsers', 'puppeteer-core', 'puppeteer'], + env: cwd => { + return { + PUPPETEER_CACHE_DIR: join(cwd, '.cache', 'puppeteer'), + PUPPETEER_SKIP_DOWNLOAD: 'true', + }; + }, + }); + + it('can download Firefox stable', async function () { + assert.ok(!existsSync(join(this.sandbox, '.cache', 'puppeteer'))); + const result = spawnSync( + 'npx', + ['puppeteer', 'browsers', 'install', 'firefox@stable'], + { + // npx is not found without the shell flag on Windows. + shell: process.platform === 'win32', + cwd: this.sandbox, + env: { + ...process.env, + PUPPETEER_CACHE_DIR: join(this.sandbox, '.cache', 'puppeteer'), + }, + } + ); + assert.strictEqual(result.status, 0); + const files = await readdir(join(this.sandbox, '.cache', 'puppeteer')); + assert.equal(files.length, 1); + assert.equal(files[0], 'firefox'); + }); +}); diff --git a/remote/test/puppeteer/test/src/ariaqueryhandler.spec.ts b/remote/test/puppeteer/test/src/ariaqueryhandler.spec.ts index 0ffb8ae6a5..4ab1df3a0a 100644 --- a/remote/test/puppeteer/test/src/ariaqueryhandler.spec.ts +++ b/remote/test/puppeteer/test/src/ariaqueryhandler.spec.ts @@ -697,20 +697,13 @@ describe('AriaQueryHandler', () => { ElementHandle<HTMLButtonElement> >; const ids = await getIds(found); - expect(ids).toEqual([ - 'node5', - 'node6', - 'node7', - 'node8', - 'node10', - 'node21', - ]); + expect(ids).toEqual(['node5', 'node6', 'node8', 'node10', 'node21']); }); it('should find by role "heading"', async () => { const {page} = await setupPage(); const found = await page.$$('aria/[role="heading"]'); const ids = await getIds(found); - expect(ids).toEqual(['shown', 'hidden', 'node11', 'node13']); + expect(ids).toEqual(['shown', 'node11', 'node13']); }); it('should find both ignored and unignored', async () => { const {page} = await setupPage(); diff --git a/remote/test/puppeteer/test/src/browser.spec.ts b/remote/test/puppeteer/test/src/browser.spec.ts index b8e0c8bb07..edfa075c4d 100644 --- a/remote/test/puppeteer/test/src/browser.spec.ts +++ b/remote/test/puppeteer/test/src/browser.spec.ts @@ -6,7 +6,7 @@ import expect from 'expect'; -import {getTestState, setupTestBrowserHooks} from './mocha-utils.js'; +import {getTestState, launch, setupTestBrowserHooks} from './mocha-utils.js'; describe('Browser specs', function () { setupTestBrowserHooks(); @@ -64,6 +64,23 @@ describe('Browser specs', function () { expect(remoteBrowser.process()).toBe(null); await remoteBrowser.disconnect(); }); + it('should keep connected after the last page is closed', async () => { + const {browser, close} = await launch({}, {createContext: false}); + try { + const pages = await browser.pages(); + await Promise.all( + pages.map(page => { + return page.close(); + }) + ); + // Verify the browser is still connected. + expect(browser.connected).toBe(true); + // Verify the browser can open a new page. + await browser.newPage(); + } finally { + await close(); + } + }); }); describe('Browser.isConnected', () => { diff --git a/remote/test/puppeteer/test/src/cdp/CDPSession.spec.ts b/remote/test/puppeteer/test/src/cdp/CDPSession.spec.ts index 887152f097..01b0009433 100644 --- a/remote/test/puppeteer/test/src/cdp/CDPSession.spec.ts +++ b/remote/test/puppeteer/test/src/cdp/CDPSession.spec.ts @@ -134,7 +134,7 @@ describe('Target.createCDPSession', function () { } ) ).rejects.toThrowError( - `Runtime.evaluate timed out. Increase the 'protocolTimeout' setting in launch/connect calls for a higher timeout if needed.` + /Increase the 'protocolTimeout' setting in launch\/connect calls for a higher timeout if needed./gi ); }); diff --git a/remote/test/puppeteer/test/src/cdp/devtools.spec.ts b/remote/test/puppeteer/test/src/cdp/devtools.spec.ts index c48b4c353b..0f9330d15f 100644 --- a/remote/test/puppeteer/test/src/cdp/devtools.spec.ts +++ b/remote/test/puppeteer/test/src/cdp/devtools.spec.ts @@ -67,9 +67,9 @@ describe('DevTools', function () { return 2 * 3; }) ).toBe(6); - expect(await browser.pages()).toContainEqual(page); + expect(await browser.pages()).toContain(page); }); - it('target.page() should return a DevTools page if asPage is used', async function () { + it('target.page() should return Page when calling asPage on DevTools target', async function () { const {puppeteer} = await getTestState({skipLaunch: true}); const originalBrowser = await launchBrowser(launchOptions); @@ -87,7 +87,8 @@ describe('DevTools', function () { return 2 * 3; }) ).toBe(6); - expect(await browser.pages()).toContainEqual(page); + // The page won't be part of browser.pages() if a custom isPageTarget is not provided + expect(await browser.pages()).not.toContain(page); }); it('should open devtools when "devtools: true" option is given', async () => { const browser = await launchBrowser( diff --git a/remote/test/puppeteer/test/src/elementhandle.spec.ts b/remote/test/puppeteer/test/src/elementhandle.spec.ts index e0f1e41878..434ac9ca40 100644 --- a/remote/test/puppeteer/test/src/elementhandle.spec.ts +++ b/remote/test/puppeteer/test/src/elementhandle.spec.ts @@ -385,8 +385,15 @@ describe('ElementHandle specs', function () { await page.setContent( `<iframe name='frame' style='position: absolute; left: -100px' srcdoc="<button style='width: 10px; height: 10px;'></button>"></iframe>` ); - const frame = await page.waitForFrame(frame => { - return frame.name() === 'frame'; + const frame = await page.waitForFrame(async frame => { + using element = await frame.frameElement(); + if (!element) { + return false; + } + const name = await element.evaluate(frame => { + return frame.name; + }); + return name === 'frame'; }); using handle = await frame.locator('button').waitHandle(); @@ -395,8 +402,15 @@ describe('ElementHandle specs', function () { await page.setContent( `<iframe name='frame2' style='position: absolute; top: -100px' srcdoc="<button style='width: 10px; height: 10px;'></button>"></iframe>` ); - const frame2 = await page.waitForFrame(frame => { - return frame.name() === 'frame2'; + const frame2 = await page.waitForFrame(async frame => { + using element = await frame.frameElement(); + if (!element) { + return false; + } + const name = await element.evaluate(frame => { + return frame.name; + }); + return name === 'frame2'; }); using handle2 = await frame2.locator('button').waitHandle(); diff --git a/remote/test/puppeteer/test/src/frame.spec.ts b/remote/test/puppeteer/test/src/frame.spec.ts index a49fb19482..758725f932 100644 --- a/remote/test/puppeteer/test/src/frame.spec.ts +++ b/remote/test/puppeteer/test/src/frame.spec.ts @@ -7,6 +7,7 @@ import expect from 'expect'; import {CDPSession} from 'puppeteer-core/internal/api/CDPSession.js'; import type {Frame} from 'puppeteer-core/internal/api/Frame.js'; +import {assert} from 'puppeteer-core/internal/util/assert.js'; import {getTestState, setupTestBrowserHooks} from './mocha-utils.js'; import { @@ -78,7 +79,7 @@ describe('Frame specs', function () { const {page, server} = await getTestState(); await page.goto(server.PREFIX + '/frames/nested-frames.html'); - expect(dumpFrames(page.mainFrame())).toEqual([ + expect(await dumpFrames(page.mainFrame())).toEqual([ 'http://localhost:<PORT>/frames/nested-frames.html', ' http://localhost:<PORT>/frames/two-frames.html (2frames)', ' http://localhost:<PORT>/frames/frame.html (uno)', @@ -232,23 +233,6 @@ describe('Frame specs', function () { expect(page.frames()).toHaveLength(2); expect(page.frames()[1]!.url()).toBe(server.EMPTY_PAGE); }); - it('should report frame.name()', async () => { - const {page, server} = await getTestState(); - - await attachFrame(page, 'theFrameId', server.EMPTY_PAGE); - await page.evaluate((url: string) => { - const frame = document.createElement('iframe'); - frame.name = 'theFrameName'; - frame.src = url; - document.body.appendChild(frame); - return new Promise(x => { - return (frame.onload = x); - }); - }, server.EMPTY_PAGE); - expect(page.frames()[0]!.name()).toBe(''); - expect(page.frames()[1]!.name()).toBe('theFrameId'); - expect(page.frames()[2]!.name()).toBe('theFrameName'); - }); it('should report frame.parent()', async () => { const {page, server} = await getTestState(); @@ -306,4 +290,35 @@ describe('Frame specs', function () { expect(page.mainFrame().client).toBeInstanceOf(CDPSession); }); }); + + describe('Frame.prototype.frameElement', function () { + it('should work', async () => { + const {page, server} = await getTestState(); + + await attachFrame(page, 'theFrameId', server.EMPTY_PAGE); + await page.evaluate((url: string) => { + const frame = document.createElement('iframe'); + frame.name = 'theFrameName'; + frame.src = url; + document.body.appendChild(frame); + return new Promise(x => { + return (frame.onload = x); + }); + }, server.EMPTY_PAGE); + using frame0 = await page.frames()[0]?.frameElement(); + assert(!frame0); + using frame1 = await page.frames()[1]?.frameElement(); + assert(frame1); + using frame2 = await page.frames()[2]?.frameElement(); + assert(frame2); + const name1 = await frame1.evaluate(frame => { + return frame.id; + }); + expect(name1).toBe('theFrameId'); + const name2 = await frame2.evaluate(frame => { + return frame.name; + }); + expect(name2).toBe('theFrameName'); + }); + }); }); diff --git a/remote/test/puppeteer/test/src/jshandle.spec.ts b/remote/test/puppeteer/test/src/jshandle.spec.ts index 28097811e4..0c5de6cde0 100644 --- a/remote/test/puppeteer/test/src/jshandle.spec.ts +++ b/remote/test/puppeteer/test/src/jshandle.spec.ts @@ -326,6 +326,16 @@ describe('JSHandle', function () { 'JSHandle@proxy' ); }); + it('should work with window subtypes', async () => { + const {page} = await getTestState(); + + expect((await page.evaluateHandle('window')).toString()).toBe( + 'JSHandle@window' + ); + expect((await page.evaluateHandle('globalThis')).toString()).toBe( + 'JSHandle@window' + ); + }); }); describe('JSHandle[Symbol.dispose]', () => { diff --git a/remote/test/puppeteer/test/src/keyboard.spec.ts b/remote/test/puppeteer/test/src/keyboard.spec.ts index 9157465242..c6cc78c68b 100644 --- a/remote/test/puppeteer/test/src/keyboard.spec.ts +++ b/remote/test/puppeteer/test/src/keyboard.spec.ts @@ -186,8 +186,15 @@ describe('Keyboard', function () { await page.setContent(` <iframe srcdoc="<iframe name='test' srcdoc='<textarea></textarea>'></iframe>"</iframe> `); - const frame = await page.waitForFrame(frame => { - return frame.name() === 'test'; + const frame = await page.waitForFrame(async frame => { + using element = await frame.frameElement(); + if (!element) { + return false; + } + const name = await element.evaluate(frame => { + return frame.name; + }); + return name === 'test'; }); await frame.focus('textarea'); diff --git a/remote/test/puppeteer/test/src/launcher.spec.ts b/remote/test/puppeteer/test/src/launcher.spec.ts index 876f8d1624..e1eefaceb1 100644 --- a/remote/test/puppeteer/test/src/launcher.spec.ts +++ b/remote/test/puppeteer/test/src/launcher.spec.ts @@ -116,6 +116,30 @@ describe('Launcher specs', function () { const {close} = await launch({}); await close(); }); + + it('can launch multiple instances without node warnings', async () => { + const instances = []; + let warning = null; + const warningHandler: NodeJS.WarningListener = w => { + return (warning = w); + }; + process.on('warning', warningHandler); + process.setMaxListeners(1); + try { + for (let i = 0; i < 2; i++) { + instances.push(launch({})); + } + await Promise.all( + (await Promise.all(instances)).map(instance => { + return instance.close(); + }) + ); + } finally { + process.setMaxListeners(10); + } + process.off('warning', warningHandler); + expect(warning).toBe(null); + }); it('should have default url when launching browser', async function () { const {browser, close} = await launch({}, {createContext: false}); try { @@ -166,7 +190,9 @@ describe('Launcher specs', function () { }).catch(error => { return (waitError = error); }); - expect(waitError.message).toContain('Failed to launch'); + expect(waitError.message).toBe( + 'Browser was not found at the configured executablePath (random-invalid-path)' + ); }); it('userDataDir option', async () => { const userDataDir = await mkdtemp(TMP_FOLDER); @@ -591,6 +617,20 @@ describe('Launcher specs', function () { }); expect(error.message).toContain('either pipe or debugging port'); }); + + it('throws an error if executable path is not valid with pipe=true', async () => { + const options = { + executablePath: '/tmp/does-not-exist', + pipe: true, + }; + let error!: Error; + await launch(options).catch(error_ => { + return (error = error_); + }); + expect(error.message).toContain( + 'Browser was not found at the configured executablePath (/tmp/does-not-exist)' + ); + }); }); describe('Puppeteer.launch', function () { @@ -793,7 +833,7 @@ describe('Launcher specs', function () { const restoredPage = pages.find(page => { return page.url() === server.PREFIX + '/frames/nested-frames.html'; })!; - expect(dumpFrames(restoredPage.mainFrame())).toEqual([ + expect(await dumpFrames(restoredPage.mainFrame())).toEqual([ 'http://localhost:<PORT>/frames/nested-frames.html', ' http://localhost:<PORT>/frames/two-frames.html (2frames)', ' http://localhost:<PORT>/frames/frame.html (uno)', diff --git a/remote/test/puppeteer/test/src/navigation.spec.ts b/remote/test/puppeteer/test/src/navigation.spec.ts index dd59c98349..927dd02cd5 100644 --- a/remote/test/puppeteer/test/src/navigation.spec.ts +++ b/remote/test/puppeteer/test/src/navigation.spec.ts @@ -112,6 +112,27 @@ describe('navigation', function () { const response = await page.goto(server.PREFIX + '/grid.html'); expect(response!.status()).toBe(200); }); + it('should work when reload causes history API in beforeunload', async () => { + const {page, server} = await getTestState(); + + await page.goto(server.EMPTY_PAGE); + await page.evaluate(() => { + window.addEventListener( + 'beforeunload', + () => { + return history.replaceState(null, 'initial', window.location.href); + }, + false + ); + }); + await page.reload(); + // Evaluate still works. + expect( + await page.evaluate(() => { + return 1; + }) + ).toBe(1); + }); it('should navigate to empty page with networkidle0', async () => { const {page, server} = await getTestState(); diff --git a/remote/test/puppeteer/test/src/network.spec.ts b/remote/test/puppeteer/test/src/network.spec.ts index c6f51a3412..9d5b28d0c3 100644 --- a/remote/test/puppeteer/test/src/network.spec.ts +++ b/remote/test/puppeteer/test/src/network.spec.ts @@ -722,7 +722,7 @@ describe('network', function () { } catch (error) { // In headful, an error is thrown instead of 401. if ( - !(error as Error).message.startsWith( + !(error as Error).message?.includes( 'net::ERR_INVALID_AUTH_CREDENTIALS' ) ) { @@ -772,7 +772,7 @@ describe('network', function () { } catch (error) { // In headful, an error is thrown instead of 401. if ( - !(error as Error).message.startsWith( + !(error as Error).message?.includes( 'net::ERR_INVALID_AUTH_CREDENTIALS' ) ) { diff --git a/remote/test/puppeteer/test/src/page.spec.ts b/remote/test/puppeteer/test/src/page.spec.ts index d83920d3ff..bc07b1d259 100644 --- a/remote/test/puppeteer/test/src/page.spec.ts +++ b/remote/test/puppeteer/test/src/page.spec.ts @@ -506,11 +506,14 @@ describe('Page', function () { console.log(1, 2, 3, globalThis); }); const log = await logPromise; - expect(log.text()).toBe('1 2 3 JSHandle@object'); + + expect(log.text()).atLeastOneToContain([ + '1 2 3 JSHandle@object', + '1 2 3 JSHandle@window', + ]); expect(log.args()).toHaveLength(4); - expect(await (await log.args()[3]!.getProperty('test')).jsonValue()).toBe( - 1 - ); + using property = await log.args()[3]!.getProperty('test'); + expect(await property.jsonValue()).toBe(1); }); it('should trigger correct Log', async () => { const {page, server, isChrome} = await getTestState(); @@ -1210,13 +1213,15 @@ describe('Page', function () { expect(result).toBe(36); await page.removeExposedFunction('compute'); - let error: Error | null = null; - await page + const error = await page .evaluate(async function () { return (globalThis as any).compute(9, 4); }) - .catch(_error => { - return (error = _error); + .then(() => { + return null; + }) + .catch(error => { + return error; }); expect(error).toBeTruthy(); }); diff --git a/remote/test/puppeteer/test/src/requestinterception-experimental.spec.ts b/remote/test/puppeteer/test/src/requestinterception-experimental.spec.ts index 966554fd5d..ce3429f0b7 100644 --- a/remote/test/puppeteer/test/src/requestinterception-experimental.spec.ts +++ b/remote/test/puppeteer/test/src/requestinterception-experimental.spec.ts @@ -23,8 +23,7 @@ describe('cooperative request interception', function () { describe('Page.setRequestInterception', function () { const expectedActions: ActionResult[] = ['abort', 'continue', 'respond']; - while (expectedActions.length > 0) { - const expectedAction = expectedActions.pop(); + for (const expectedAction of expectedActions) { it(`should cooperatively ${expectedAction} by priority`, async () => { const {page, server} = await getTestState(); @@ -94,24 +93,36 @@ describe('cooperative request interception', function () { const {page, server} = await getTestState(); await page.setRequestInterception(true); + let requestError; page.on('request', request => { if (isFavicon(request)) { void request.continue({}, 0); return; } - expect(request.url()).toContain('empty.html'); - expect(request.headers()['user-agent']).toBeTruthy(); - expect(request.method()).toBe('GET'); - expect(request.postData()).toBe(undefined); - expect(request.isNavigationRequest()).toBe(true); - expect(request.resourceType()).toBe('document'); - expect(request.frame() === page.mainFrame()).toBe(true); - expect(request.frame()!.url()).toBe('about:blank'); - void request.continue({}, 0); + try { + expect(request).toBeTruthy(); + expect(request.url()).toContain('empty.html'); + expect(request.headers()['user-agent']).toBeTruthy(); + expect(request.method()).toBe('GET'); + expect(request.postData()).toBe(undefined); + expect(request.isNavigationRequest()).toBe(true); + expect(request.resourceType()).toBe('document'); + expect(request.frame()!.url()).toBe('about:blank'); + expect(request.frame() === page.mainFrame()).toBe(true); + } catch (error) { + requestError = error; + } finally { + void request.continue({}, 0); + } }); + const response = (await page.goto(server.EMPTY_PAGE))!; - expect(response!.ok()).toBe(true); - expect(response!.remoteAddress().port).toBe(server.PORT); + if (requestError) { + throw requestError; + } + + expect(response.ok()).toBe(true); + expect(response.remoteAddress().port).toBe(server.PORT); }); // @see https://github.com/puppeteer/puppeteer/pull/3105 it('should work when POST is redirected with 302', async () => { @@ -141,16 +152,24 @@ describe('cooperative request interception', function () { server.setRedirect('/rrredirect', '/empty.html'); await page.setRequestInterception(true); + let requestError; page.on('request', request => { const headers = Object.assign({}, request.headers(), { foo: 'bar', }); void request.continue({headers}, 0); - - expect(request.continueRequestOverrides()).toEqual({headers}); + try { + expect(request.continueRequestOverrides()).toEqual({headers}); + } catch (error) { + requestError = error; + } }); // Make sure that the goto does not time out. await page.goto(server.PREFIX + '/rrredirect'); + + if (requestError) { + throw requestError; + } }); // @see https://github.com/puppeteer/puppeteer/issues/4743 it('should be able to remove headers', async () => { @@ -220,11 +239,20 @@ describe('cooperative request interception', function () { foo: 'bar', }); await page.setRequestInterception(true); + let requestError; page.on('request', request => { - expect(request.headers()['foo']).toBe('bar'); - void request.continue({}, 0); + try { + expect(request.headers()['foo']).toBe('bar'); + } catch (error) { + requestError = error; + } finally { + void request.continue({}, 0); + } }); const response = await page.goto(server.EMPTY_PAGE); + if (requestError) { + throw requestError; + } expect(response!.ok()).toBe(true); }); // @see https://github.com/puppeteer/puppeteer/issues/4337 @@ -250,11 +278,20 @@ describe('cooperative request interception', function () { await page.setExtraHTTPHeaders({referer: server.EMPTY_PAGE}); await page.setRequestInterception(true); + let requestError; page.on('request', request => { - expect(request.headers()['referer']).toBe(server.EMPTY_PAGE); - void request.continue({}, 0); + try { + expect(request.headers()['referer']).toBe(server.EMPTY_PAGE); + } catch (error) { + requestError = error; + } finally { + void request.continue({}, 0); + } }); const response = await page.goto(server.EMPTY_PAGE); + if (requestError) { + throw requestError; + } expect(response!.ok()).toBe(true); }); it('should be abortable', async () => { @@ -340,7 +377,7 @@ describe('cooperative request interception', function () { if (isChrome) { expect(error.message).toContain('net::ERR_FAILED'); } else { - expect(error.message).toContain('NS_ERROR_FAILURE'); + expect(error.message).toContain('NS_ERROR_ABORT'); } }); it('should work with redirects', async () => { @@ -947,14 +984,26 @@ describe('cooperative request interception', function () { page.on('request', request => { void request.continue(); }); + let requestError; page.on('request', request => { - expect(request.isInterceptResolutionHandled()).toBeTruthy(); + try { + expect(request.isInterceptResolutionHandled()).toBeTruthy(); + } catch (error) { + requestError = error; + } }); page.on('request', request => { const {action} = request.interceptResolutionState(); - expect(action).toBe(InterceptResolutionAction.AlreadyHandled); + try { + expect(action).toBe(InterceptResolutionAction.AlreadyHandled); + } catch (error) { + requestError = error; + } }); await page.goto(server.EMPTY_PAGE); + if (requestError) { + throw requestError; + } }); }); }); diff --git a/remote/test/puppeteer/test/src/requestinterception.spec.ts b/remote/test/puppeteer/test/src/requestinterception.spec.ts index 4b88d30a3b..2f73ae6974 100644 --- a/remote/test/puppeteer/test/src/requestinterception.spec.ts +++ b/remote/test/puppeteer/test/src/requestinterception.spec.ts @@ -22,23 +22,34 @@ describe('request interception', function () { const {page, server} = await getTestState(); await page.setRequestInterception(true); + let requestError; page.on('request', request => { if (isFavicon(request)) { void request.continue(); return; } - expect(request.url()).toContain('empty.html'); - expect(request.headers()['user-agent']).toBeTruthy(); - expect(request.headers()['accept']).toBeTruthy(); - expect(request.method()).toBe('GET'); - expect(request.postData()).toBe(undefined); - expect(request.isNavigationRequest()).toBe(true); - expect(request.resourceType()).toBe('document'); - expect(request.frame() === page.mainFrame()).toBe(true); - expect(request.frame()!.url()).toBe('about:blank'); - void request.continue(); + try { + expect(request).toBeTruthy(); + expect(request.url()).toContain('empty.html'); + expect(request.headers()['user-agent']).toBeTruthy(); + expect(request.method()).toBe('GET'); + expect(request.postData()).toBe(undefined); + expect(request.isNavigationRequest()).toBe(true); + expect(request.resourceType()).toBe('document'); + expect(request.frame()!.url()).toBe('about:blank'); + expect(request.frame() === page.mainFrame()).toBe(true); + } catch (error) { + requestError = error; + } finally { + void request.continue(); + } }); + const response = (await page.goto(server.EMPTY_PAGE))!; + if (requestError) { + throw requestError; + } + expect(response.ok()).toBe(true); expect(response.remoteAddress().port).toBe(server.PORT); }); @@ -76,7 +87,11 @@ describe('request interception', function () { }); void request.continue({headers}); }); - await page.goto(server.PREFIX + '/rrredirect'); + const [request] = await Promise.all([ + server.waitForRequest('/empty.html'), + page.goto(server.PREFIX + '/rrredirect'), + ]); + expect(request.headers['foo']).toBe('bar'); }); // @see https://github.com/puppeteer/puppeteer/issues/4743 it('should be able to remove headers', async () => { @@ -162,11 +177,21 @@ describe('request interception', function () { foo: 'bar', }); await page.setRequestInterception(true); + let requestError; page.on('request', request => { - expect(request.headers()['foo']).toBe('bar'); - void request.continue(); + try { + expect(request.headers()['foo']).toBe('bar'); + } catch (error) { + requestError = error; + } finally { + void request.continue(); + } }); + const response = (await page.goto(server.EMPTY_PAGE))!; + if (requestError) { + throw requestError; + } expect(response.ok()).toBe(true); }); // @see https://github.com/puppeteer/puppeteer/issues/4337 @@ -192,11 +217,13 @@ describe('request interception', function () { await page.setExtraHTTPHeaders({referer: server.EMPTY_PAGE}); await page.setRequestInterception(true); - page.on('request', request => { - expect(request.headers()['referer']).toBe(server.EMPTY_PAGE); + let request!: HTTPRequest; + page.on('request', req => { + request = req; void request.continue(); }); const response = (await page.goto(server.EMPTY_PAGE))!; + expect(request.headers()['referer']).toBe(server.EMPTY_PAGE); expect(response.ok()).toBe(true); }); it('should be abortable', async () => { @@ -267,7 +294,7 @@ describe('request interception', function () { if (isChrome) { expect(error.message).toContain('net::ERR_FAILED'); } else { - expect(error.message).toContain('NS_ERROR_FAILURE'); + expect(error.message).toContain('NS_ERROR_ABORT'); } }); it('should work with redirects', async () => { @@ -493,7 +520,7 @@ describe('request interception', function () { ))!; expect(response.status()).toBe(200); }); - it('should work wit h encoded server - 2', async () => { + it('should work with encoded server - 2', async () => { const {page, server} = await getTestState(); // The requestWillBeSent will report URL as-is, whereas interception will diff --git a/remote/test/puppeteer/test/src/screenshot.spec.ts b/remote/test/puppeteer/test/src/screenshot.spec.ts index 9176d0c920..9880581217 100644 --- a/remote/test/puppeteer/test/src/screenshot.spec.ts +++ b/remote/test/puppeteer/test/src/screenshot.spec.ts @@ -393,6 +393,33 @@ describe('Screenshots', function () { await context.close(); }); + + it('should use element clip', async () => { + const {page} = await getTestState(); + + await page.setViewport({width: 500, height: 500}); + await page.setContent(` + something above + <style>div { + border: 2px solid blue; + background: green; + width: 50px; + height: 50px; + } + </style> + <div></div> + `); + using elementHandle = (await page.$('div'))!; + const screenshot = await elementHandle.screenshot({ + clip: { + x: 10, + y: 10, + width: 20, + height: 20, + }, + }); + expect(screenshot).toBeGolden('screenshot-element-clip.png'); + }); }); describe('Cdp', () => { diff --git a/remote/test/puppeteer/test/src/utils.ts b/remote/test/puppeteer/test/src/utils.ts index d1bad65a16..d0dc08c33b 100644 --- a/remote/test/puppeteer/test/src/utils.ts +++ b/remote/test/puppeteer/test/src/utils.ts @@ -112,15 +112,24 @@ export async function navigateFrame( } } -export const dumpFrames = (frame: Frame, indentation?: string): string[] => { +export const dumpFrames = async ( + frame: Frame, + indentation?: string +): Promise<string[]> => { indentation = indentation || ''; let description = frame.url().replace(/:\d{4,5}\//, ':<PORT>/'); - if (frame.name()) { - description += ' (' + frame.name() + ')'; + using element = await frame.frameElement(); + if (element) { + const nameOrId = await element.evaluate(frame => { + return frame.name || frame.id; + }); + if (nameOrId) { + description += ' (' + nameOrId + ')'; + } } const result = [indentation + description]; for (const child of frame.childFrames()) { - result.push(...dumpFrames(child, ' ' + indentation)); + result.push(...(await dumpFrames(child, ' ' + indentation))); } return result; }; diff --git a/remote/test/puppeteer/tools/analyze_issue.mjs b/remote/test/puppeteer/tools/analyze_issue.mjs index eff6a4122e..359ce93b87 100755 --- a/remote/test/puppeteer/tools/analyze_issue.mjs +++ b/remote/test/puppeteer/tools/analyze_issue.mjs @@ -36,9 +36,7 @@ const LAST_PUPPETEER_VERSION = packageJson.version; if (!LAST_PUPPETEER_VERSION) { core.setFailed('No maintained version found.'); } -const LAST_SUPPORTED_NODE_VERSION = removeVersionPrefix( - packageJson.engines.node.slice(2).trim() -); +const LAST_SUPPORTED_NODE_VERSION = packageJson.engines.node; const SUPPORTED_OSES = ['windows', 'macos', 'linux']; const SUPPORTED_PACKAGE_MANAGERS = ['yarn', 'npm', 'pnpm']; @@ -65,7 +63,7 @@ This issue has an invalid package manager version: \`${value}\`. Versions must f }, unsupportedNodeVersion(value) { return formatMessage(` -This issue has an unsupported Node.js version: \`${value}\`. Only versions above \`v${LAST_SUPPORTED_NODE_VERSION}\` are supported. Please verify the issue on a supported version of Node.js and update the form. +This issue has an unsupported Node.js version: \`${value}\`. Only versions satisfying \`${LAST_SUPPORTED_NODE_VERSION}\` are supported. Please verify the issue on a supported version of Node.js and update the form. `); }, invalidNodeVersion(value) { @@ -109,8 +107,8 @@ This issue has an invalid Puppeteer version: \`${value}\`. Versions must follow let set = () => { return void 0; }; - let j = 1; - let i = 1; + let j = 0; + let i = 0; for (; i < lines.length; ++i) { if (lines[i].startsWith('### Bug behavior')) { set(lines.slice(j, i).join('\n').trim()); @@ -211,7 +209,7 @@ This issue has an invalid Puppeteer version: \`${value}\`. Versions must follow ); core.setFailed('Invalid Node version'); } - if (semver.lt(nodeVersion, LAST_SUPPORTED_NODE_VERSION)) { + if (!semver.satisfies(nodeVersion, LAST_SUPPORTED_NODE_VERSION)) { core.setOutput( 'errorMessage', ERROR_MESSAGES.unsupportedNodeVersion(nodeVersion) diff --git a/remote/test/puppeteer/tools/docgen/package.json b/remote/test/puppeteer/tools/docgen/package.json index 82f6d4d6c4..a89323e31b 100644 --- a/remote/test/puppeteer/tools/docgen/package.json +++ b/remote/test/puppeteer/tools/docgen/package.json @@ -24,10 +24,10 @@ } }, "devDependencies": { - "@microsoft/api-extractor": "7.42.2", - "@microsoft/api-documenter": "7.23.35", - "@microsoft/api-extractor-model": "7.28.13", + "@microsoft/api-extractor": "7.43.1", + "@microsoft/api-documenter": "7.24.2", + "@microsoft/api-extractor-model": "7.28.14", "@microsoft/tsdoc": "0.14.2", - "@rushstack/node-core-library": "4.0.2" + "@rushstack/node-core-library": "4.1.0" } } diff --git a/remote/test/puppeteer/tools/docgen/src/custom_markdown_documenter.ts b/remote/test/puppeteer/tools/docgen/src/custom_markdown_documenter.ts index d63a8b96ef..abf48e200c 100644 --- a/remote/test/puppeteer/tools/docgen/src/custom_markdown_documenter.ts +++ b/remote/test/puppeteer/tools/docgen/src/custom_markdown_documenter.ts @@ -69,6 +69,9 @@ import { DocSection, StandardTags, StringBuilder, + DocHtmlStartTag, + DocHtmlEndTag, + DocHtmlAttribute, type TSDocConfiguration, } from '@microsoft/tsdoc'; import { @@ -85,7 +88,7 @@ export interface IMarkdownDocumenterOptions { export class CustomMarkdownEmitter extends ApiFormatterMarkdownEmitter { protected override getEscapedText(text: string): string { - const textWithBackslashes: string = text + const textWithBackslashes = text .replace(/\\/g, '\\\\') // first replace the escape character .replace(/[*#[\]_|`~]/g, x => { return '\\' + x; @@ -98,15 +101,6 @@ export class CustomMarkdownEmitter extends ApiFormatterMarkdownEmitter { .replace(/\}/g, '}'); return textWithBackslashes; } - - protected override getTableEscapedText(text: string): string { - return text - .replace(/&/g, '&') - .replace(/"/g, '"') - .replace(/</g, '<') - .replace(/>/g, '>') - .replace(/\|/g, '|'); - } } /** @@ -156,12 +150,12 @@ export class MarkdownDocumenter { } private _writeApiItemPage(apiItem: ApiItem): void { - const configuration: TSDocConfiguration = this._tsdocConfiguration; - const output: DocSection = new DocSection({ - configuration: this._tsdocConfiguration, + const configuration = this._tsdocConfiguration; + const output = new DocSection({ + configuration, }); - const scopedName: string = apiItem.getScopedNameWithinPackage(); + const scopedName = apiItem.getScopedNameWithinPackage(); switch (apiItem.kind) { case ApiItemKind.Class: @@ -259,7 +253,7 @@ export class MarkdownDocumenter { new DocNoteBox({configuration: this._tsdocConfiguration}, [ new DocParagraph({configuration: this._tsdocConfiguration}, [ new DocPlainText({ - configuration: this._tsdocConfiguration, + configuration, text: 'Warning: This API is now obsolete. ', }), ]), @@ -369,11 +363,11 @@ export class MarkdownDocumenter { this._writeRemarksSection(output, apiItem); } - const filename: string = path.join( + const filename = path.join( this._outputFolder, this._getFilenameForApiItem(apiItem) ); - const stringBuilder: StringBuilder = new StringBuilder(); + const stringBuilder = new StringBuilder(); this._markdownEmitter.emit(stringBuilder, output, { contextApiItem: apiItem, @@ -382,7 +376,7 @@ export class MarkdownDocumenter { }, }); - let pageContent: string = stringBuilder.toString(); + let pageContent = stringBuilder.toString(); if (this._pluginLoader.markdownDocumenterFeature) { // Allow the plugin to customize the pageContent @@ -413,18 +407,15 @@ export class MarkdownDocumenter { output: DocSection, apiItem: ApiDeclaredItem ): void { - const configuration: TSDocConfiguration = this._tsdocConfiguration; + const configuration = this._tsdocConfiguration; if (apiItem instanceof ApiClass) { if (apiItem.extendsType) { - const extendsParagraph: DocParagraph = new DocParagraph( - {configuration}, - [ - new DocEmphasisSpan({configuration, bold: true}, [ - new DocPlainText({configuration, text: 'Extends: '}), - ]), - ] - ); + const extendsParagraph = new DocParagraph({configuration}, [ + new DocEmphasisSpan({configuration, bold: true}, [ + new DocPlainText({configuration, text: 'Extends: '}), + ]), + ]); this._appendExcerptWithHyperlinks( extendsParagraph, apiItem.extendsType.excerpt @@ -432,14 +423,11 @@ export class MarkdownDocumenter { output.appendNode(extendsParagraph); } if (apiItem.implementsTypes.length > 0) { - const extendsParagraph: DocParagraph = new DocParagraph( - {configuration}, - [ - new DocEmphasisSpan({configuration, bold: true}, [ - new DocPlainText({configuration, text: 'Implements: '}), - ]), - ] - ); + const extendsParagraph = new DocParagraph({configuration}, [ + new DocEmphasisSpan({configuration, bold: true}, [ + new DocPlainText({configuration, text: 'Implements: '}), + ]), + ]); let needsComma = false; for (const implementsType of apiItem.implementsTypes) { if (needsComma) { @@ -459,14 +447,11 @@ export class MarkdownDocumenter { if (apiItem instanceof ApiInterface) { if (apiItem.extendsTypes.length > 0) { - const extendsParagraph: DocParagraph = new DocParagraph( - {configuration}, - [ - new DocEmphasisSpan({configuration, bold: true}, [ - new DocPlainText({configuration, text: 'Extends: '}), - ]), - ] - ); + const extendsParagraph = new DocParagraph({configuration}, [ + new DocEmphasisSpan({configuration, bold: true}, [ + new DocPlainText({configuration, text: 'Extends: '}), + ]), + ]); let needsComma = false; for (const extendsType of apiItem.extendsTypes) { if (needsComma) { @@ -496,14 +481,11 @@ export class MarkdownDocumenter { ); }); if (refs.length > 0) { - const referencesParagraph: DocParagraph = new DocParagraph( - {configuration}, - [ - new DocEmphasisSpan({configuration, bold: true}, [ - new DocPlainText({configuration, text: 'References: '}), - ]), - ] - ); + const referencesParagraph = new DocParagraph({configuration}, [ + new DocEmphasisSpan({configuration, bold: true}, [ + new DocPlainText({configuration, text: 'References: '}), + ]), + ]); let needsComma = false; const visited = new Set<string>(); for (const ref of refs) { @@ -548,6 +530,8 @@ export class MarkdownDocumenter { } private _writeRemarksSection(output: DocSection, apiItem: ApiItem): void { + const configuration = this._tsdocConfiguration; + if (apiItem instanceof ApiDocumentedItem) { const tsdocComment: DocComment | undefined = apiItem.tsdocComment; @@ -556,7 +540,7 @@ export class MarkdownDocumenter { if (tsdocComment.remarksBlock) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Remarks', }) ); @@ -580,7 +564,7 @@ export class MarkdownDocumenter { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: heading, }) ); @@ -627,15 +611,15 @@ export class MarkdownDocumenter { * GENERATE PAGE: MODEL */ private _writeModelTable(output: DocSection, apiModel: ApiModel): void { - const configuration: TSDocConfiguration = this._tsdocConfiguration; + const configuration = this._tsdocConfiguration; - const packagesTable: DocTable = new DocTable({ + const packagesTable = new DocTable({ configuration, headerTitles: ['Package', 'Description'], }); for (const apiMember of apiModel.members) { - const row: DocTableRow = new DocTableRow({configuration}, [ + const row = new DocTableRow({configuration}, [ this._createTitleCell(apiMember), this._createDescriptionCell(apiMember), ]); @@ -651,7 +635,7 @@ export class MarkdownDocumenter { if (packagesTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Packages', }) ); @@ -666,39 +650,39 @@ export class MarkdownDocumenter { output: DocSection, apiContainer: ApiPackage | ApiNamespace ): void { - const configuration: TSDocConfiguration = this._tsdocConfiguration; + const configuration = this._tsdocConfiguration; - const classesTable: DocTable = new DocTable({ + const classesTable = new DocTable({ configuration, headerTitles: ['Class', 'Description'], }); - const enumerationsTable: DocTable = new DocTable({ + const enumerationsTable = new DocTable({ configuration, headerTitles: ['Enumeration', 'Description'], }); - const functionsTable: DocTable = new DocTable({ + const functionsTable = new DocTable({ configuration, headerTitles: ['Function', 'Description'], }); - const interfacesTable: DocTable = new DocTable({ + const interfacesTable = new DocTable({ configuration, headerTitles: ['Interface', 'Description'], }); - const namespacesTable: DocTable = new DocTable({ + const namespacesTable = new DocTable({ configuration, headerTitles: ['Namespace', 'Description'], }); - const variablesTable: DocTable = new DocTable({ + const variablesTable = new DocTable({ configuration, headerTitles: ['Variable', 'Description'], }); - const typeAliasesTable: DocTable = new DocTable({ + const typeAliasesTable = new DocTable({ configuration, headerTitles: ['Type Alias', 'Description'], }); @@ -709,7 +693,7 @@ export class MarkdownDocumenter { : (apiContainer as ApiNamespace).members; for (const apiMember of apiMembers) { - const row: DocTableRow = new DocTableRow({configuration}, [ + const row = new DocTableRow({configuration}, [ this._createTitleCell(apiMember), this._createDescriptionCell(apiMember), ]); @@ -755,7 +739,7 @@ export class MarkdownDocumenter { if (classesTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Classes', }) ); @@ -765,7 +749,7 @@ export class MarkdownDocumenter { if (enumerationsTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Enumerations', }) ); @@ -774,7 +758,7 @@ export class MarkdownDocumenter { if (functionsTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Functions', }) ); @@ -784,7 +768,7 @@ export class MarkdownDocumenter { if (interfacesTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Interfaces', }) ); @@ -794,7 +778,7 @@ export class MarkdownDocumenter { if (namespacesTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Namespaces', }) ); @@ -804,7 +788,7 @@ export class MarkdownDocumenter { if (variablesTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Variables', }) ); @@ -814,7 +798,7 @@ export class MarkdownDocumenter { if (typeAliasesTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Type Aliases', }) ); @@ -826,24 +810,24 @@ export class MarkdownDocumenter { * GENERATE PAGE: CLASS */ private _writeClassTables(output: DocSection, apiClass: ApiClass): void { - const configuration: TSDocConfiguration = this._tsdocConfiguration; + const configuration = this._tsdocConfiguration; - const eventsTable: DocTable = new DocTable({ + const eventsTable = new DocTable({ configuration, headerTitles: ['Property', 'Modifiers', 'Type', 'Description'], }); - const constructorsTable: DocTable = new DocTable({ + const constructorsTable = new DocTable({ configuration, headerTitles: ['Constructor', 'Modifiers', 'Description'], }); - const propertiesTable: DocTable = new DocTable({ + const propertiesTable = new DocTable({ configuration, headerTitles: ['Property', 'Modifiers', 'Type', 'Description'], }); - const methodsTable: DocTable = new DocTable({ + const methodsTable = new DocTable({ configuration, headerTitles: ['Method', 'Modifiers', 'Description'], }); @@ -902,7 +886,7 @@ export class MarkdownDocumenter { if (eventsTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Events', }) ); @@ -912,7 +896,7 @@ export class MarkdownDocumenter { if (constructorsTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Constructors', }) ); @@ -922,7 +906,7 @@ export class MarkdownDocumenter { if (propertiesTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Properties', }) ); @@ -932,7 +916,7 @@ export class MarkdownDocumenter { if (methodsTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Methods', }) ); @@ -944,9 +928,9 @@ export class MarkdownDocumenter { * GENERATE PAGE: ENUM */ private _writeEnumTables(output: DocSection, apiEnum: ApiEnum): void { - const configuration: TSDocConfiguration = this._tsdocConfiguration; + const configuration = this._tsdocConfiguration; - const enumMembersTable: DocTable = new DocTable({ + const enumMembersTable = new DocTable({ configuration, headerTitles: ['Member', 'Value', 'Description'], }); @@ -971,7 +955,7 @@ export class MarkdownDocumenter { if (enumMembersTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Enumeration Members', }) ); @@ -986,19 +970,19 @@ export class MarkdownDocumenter { output: DocSection, apiClass: ApiInterface ): void { - const configuration: TSDocConfiguration = this._tsdocConfiguration; + const configuration = this._tsdocConfiguration; - const eventsTable: DocTable = new DocTable({ + const eventsTable = new DocTable({ configuration, headerTitles: ['Property', 'Modifiers', 'Type', 'Description'], }); - const propertiesTable: DocTable = new DocTable({ + const propertiesTable = new DocTable({ configuration, headerTitles: ['Property', 'Modifiers', 'Type', 'Description', 'Default'], }); - const methodsTable: DocTable = new DocTable({ + const methodsTable = new DocTable({ configuration, headerTitles: ['Method', 'Description'], }); @@ -1046,7 +1030,7 @@ export class MarkdownDocumenter { if (eventsTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Events', }) ); @@ -1056,7 +1040,7 @@ export class MarkdownDocumenter { if (propertiesTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Properties', }) ); @@ -1066,7 +1050,7 @@ export class MarkdownDocumenter { if (methodsTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Methods', }) ); @@ -1081,14 +1065,14 @@ export class MarkdownDocumenter { output: DocSection, apiParameterListMixin: ApiParameterListMixin ): void { - const configuration: TSDocConfiguration = this._tsdocConfiguration; + const configuration = this._tsdocConfiguration; - const parametersTable: DocTable = new DocTable({ + const parametersTable = new DocTable({ configuration, headerTitles: ['Parameter', 'Type', 'Description'], }); for (const apiParameter of apiParameterListMixin.parameters) { - const parameterDescription: DocSection = new DocSection({configuration}); + const parameterDescription = new DocSection({configuration}); if (apiParameter.isOptional) { parameterDescription.appendNodesInParagraph([ @@ -1126,7 +1110,7 @@ export class MarkdownDocumenter { if (parametersTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Parameters', }) ); @@ -1161,9 +1145,9 @@ export class MarkdownDocumenter { } private _createParagraphForTypeExcerpt(excerpt: Excerpt): DocParagraph { - const configuration: TSDocConfiguration = this._tsdocConfiguration; + const configuration = this._tsdocConfiguration; - const paragraph: DocParagraph = new DocParagraph({configuration}); + const paragraph = new DocParagraph({configuration}); if (!excerpt.text.trim()) { paragraph.appendNode( new DocPlainText({configuration, text: '(not declared)'}) @@ -1188,13 +1172,13 @@ export class MarkdownDocumenter { docNodeContainer: DocNodeContainer, token: ExcerptToken ): void { - const configuration: TSDocConfiguration = this._tsdocConfiguration; + const configuration = this._tsdocConfiguration; // Markdown doesn't provide a standardized syntax for hyperlinks inside code // spans, so we will render the type expression as DocPlainText. Instead of // creating multiple DocParagraphs, we can simply discard any newlines and // let the renderer do normal word-wrapping. - const unwrappedTokenText: string = token.text.replace(/[\r\n]+/g, ' '); + const unwrappedTokenText = token.text.replace(/[\r\n]+/g, ' '); // If it's hyperlinkable, then append a DocLinkTag if (token.kind === ExcerptTokenKind.Reference && token.canonicalReference) { @@ -1226,12 +1210,23 @@ export class MarkdownDocumenter { } private _createTitleCell(apiItem: ApiItem, plain = false): DocTableCell { - const configuration: TSDocConfiguration = this._tsdocConfiguration; + const configuration = this._tsdocConfiguration; - const text: string = Utilities.getConciseSignature(apiItem); + const text = Utilities.getConciseSignature(apiItem); return new DocTableCell({configuration}, [ new DocParagraph({configuration}, [ + new DocHtmlStartTag({ + configuration, + name: 'span', + htmlAttributes: [ + new DocHtmlAttribute({ + configuration, + name: 'id', + value: `"${Utilities.getSafeFilenameForName(apiItem.displayName)}"`, + }), + ], + }), plain ? new DocPlainText({configuration, text}) : new DocLinkTag({ @@ -1240,6 +1235,10 @@ export class MarkdownDocumenter { linkText: text, urlDestination: this._getLinkFilenameForApiItem(apiItem), }), + new DocHtmlEndTag({ + configuration, + name: 'span', + }), ]), ]); } @@ -1254,9 +1253,9 @@ export class MarkdownDocumenter { * cast. */ private _createDescriptionCell(apiItem: ApiItem): DocTableCell { - const configuration: TSDocConfiguration = this._tsdocConfiguration; + const configuration = this._tsdocConfiguration; - const section: DocSection = new DocSection({configuration}); + const section = new DocSection({configuration}); if (ApiReleaseTagMixin.isBaseClassOf(apiItem)) { if (apiItem.releaseTag === ReleaseTag.Beta) { @@ -1275,6 +1274,20 @@ export class MarkdownDocumenter { section, apiItem.tsdocComment.summarySection ); + + if (apiItem.tsdocComment.deprecatedBlock) { + section.appendNode( + new DocParagraph({configuration}, [ + new DocEmphasisSpan({configuration, bold: true}, [ + new DocPlainText({configuration, text: 'Deprecated: '}), + ]), + ]) + ); + + section.appendNodes( + apiItem.tsdocComment.deprecatedBlock.content.getChildNodes() + ); + } } } @@ -1282,7 +1295,7 @@ export class MarkdownDocumenter { } private _createDefaultCell(apiItem: ApiItem): DocTableCell { - const configuration: TSDocConfiguration = this._tsdocConfiguration; + const configuration = this._tsdocConfiguration; if (apiItem instanceof ApiDocumentedItem) { const block = apiItem.tsdocComment?.customBlocks.find(block => { @@ -1300,57 +1313,56 @@ export class MarkdownDocumenter { } private _createModifiersCell(apiItem: ApiItem): DocTableCell { - const configuration: TSDocConfiguration = this._tsdocConfiguration; + const configuration = this._tsdocConfiguration; + + const section = new DocSection({configuration}); - const section: DocSection = new DocSection({configuration}); + const codes = []; if (ApiProtectedMixin.isBaseClassOf(apiItem)) { if (apiItem.isProtected) { - section.appendNode( - new DocParagraph({configuration}, [ - new DocCodeSpan({configuration, code: 'protected'}), - ]) - ); + codes.push('protected'); } } if (ApiReadonlyMixin.isBaseClassOf(apiItem)) { if (apiItem.isReadonly) { - section.appendNode( - new DocParagraph({configuration}, [ - new DocCodeSpan({configuration, code: 'readonly'}), - ]) - ); + codes.push('readonly'); } } if (ApiStaticMixin.isBaseClassOf(apiItem)) { if (apiItem.isStatic) { - section.appendNode( - new DocParagraph({configuration}, [ - new DocCodeSpan({configuration, code: 'static'}), - ]) - ); + codes.push('static'); } } if (ApiOptionalMixin.isBaseClassOf(apiItem)) { if (apiItem.isOptional) { - section.appendNode( - new DocParagraph({configuration}, [ - new DocCodeSpan({configuration, code: 'optional'}), - ]) - ); + codes.push('optional'); } } + if (apiItem instanceof ApiDocumentedItem) { + if (apiItem.tsdocComment?.deprecatedBlock) { + codes.push('deprecated'); + } + } + if (codes.length) { + section.appendNode( + new DocParagraph({configuration}, [ + new DocCodeSpan({configuration, code: codes.join(', ')}), + ]) + ); + } + return new DocTableCell({configuration}, section.nodes); } private _createPropertyTypeCell(apiItem: ApiItem): DocTableCell { - const configuration: TSDocConfiguration = this._tsdocConfiguration; + const configuration = this._tsdocConfiguration; - const section: DocSection = new DocSection({configuration}); + const section = new DocSection({configuration}); if (apiItem instanceof ApiPropertyItem) { section.appendNode( @@ -1362,9 +1374,9 @@ export class MarkdownDocumenter { } private _createInitializerCell(apiItem: ApiItem): DocTableCell { - const configuration: TSDocConfiguration = this._tsdocConfiguration; + const configuration = this._tsdocConfiguration; - const section: DocSection = new DocSection({configuration}); + const section = new DocSection({configuration}); if (ApiInitializerMixin.isBaseClassOf(apiItem)) { if (apiItem.initializerExcerpt) { @@ -1381,8 +1393,8 @@ export class MarkdownDocumenter { } private _writeBetaWarning(output: DocSection): void { - const configuration: TSDocConfiguration = this._tsdocConfiguration; - const betaWarning: string = + const configuration = this._tsdocConfiguration; + const betaWarning = 'This API is provided as a preview for developers and may change' + ' based on feedback that we receive. Do not use this API in a production environment.'; output.appendNode( @@ -1427,7 +1439,7 @@ export class MarkdownDocumenter { let baseName = ''; for (const hierarchyItem of apiItem.getHierarchy()) { // For overloaded methods, add a suffix such as "MyClass.myMethod_2". - let qualifiedName: string = hierarchyItem.displayName; + let qualifiedName = hierarchyItem.displayName; if (ApiParameterListMixin.isBaseClassOf(hierarchyItem)) { if (hierarchyItem.overloadIndex > 1) { // Subtract one for compatibility with earlier releases of API Documenter. @@ -1448,15 +1460,16 @@ export class MarkdownDocumenter { return baseName.slice(0, baseName.length - 1); } - private _getFilenameForApiItem(apiItem: ApiItem): string { + private _getFilenameForApiItem(apiItem: ApiItem, link = false): string { if (apiItem.kind === ApiItemKind.Package) { return 'index.md'; } let baseName = ''; + let suffix = ''; for (const hierarchyItem of apiItem.getHierarchy()) { // For overloaded methods, add a suffix such as "MyClass.myMethod_2". - let qualifiedName: string = Utilities.getSafeFilenameForName( + let qualifiedName = Utilities.getSafeFilenameForName( hierarchyItem.displayName ); if (ApiParameterListMixin.isBaseClassOf(hierarchyItem)) { @@ -1471,6 +1484,9 @@ export class MarkdownDocumenter { case ApiItemKind.Model: case ApiItemKind.EntryPoint: case ApiItemKind.EnumMember: + // Properties don't have separate pages + case ApiItemKind.Property: + case ApiItemKind.PropertySignature: break; case ApiItemKind.Package: baseName = Utilities.getSafeFilenameForName( @@ -1480,12 +1496,26 @@ export class MarkdownDocumenter { default: baseName += '.' + qualifiedName; } + + if (link) { + switch (hierarchyItem.kind) { + case ApiItemKind.Property: + case ApiItemKind.PropertySignature: + suffix = + '#' + + Utilities.getSafeFilenameForName( + PackageName.getUnscopedName(hierarchyItem.displayName) + ); + break; + } + } } - return baseName + '.md'; + + return `${baseName}.md${suffix}`; } private _getLinkFilenameForApiItem(apiItem: ApiItem): string { - return './' + this._getFilenameForApiItem(apiItem); + return './' + this._getFilenameForApiItem(apiItem, true); } private _deleteOldOutputFiles(): void { diff --git a/remote/test/puppeteer/tools/doctest/package.json b/remote/test/puppeteer/tools/doctest/package.json index 1d0adb633b..ee3be2a6ea 100644 --- a/remote/test/puppeteer/tools/doctest/package.json +++ b/remote/test/puppeteer/tools/doctest/package.json @@ -24,13 +24,13 @@ } }, "devDependencies": { - "@swc/core": "1.4.2", + "@swc/core": "1.4.13", "@types/doctrine": "0.0.9", "@types/source-map-support": "0.5.10", "@types/yargs": "17.0.32", "acorn": "8.11.3", "doctrine": "3.0.0", - "glob": "10.3.10", + "glob": "10.3.12", "pkg-dir": "8.0.0", "source-map-support": "0.5.21", "source-map": "0.7.4", diff --git a/remote/test/puppeteer/tools/eslint/package.json b/remote/test/puppeteer/tools/eslint/package.json index c7f7f4f38d..97976a37f2 100644 --- a/remote/test/puppeteer/tools/eslint/package.json +++ b/remote/test/puppeteer/tools/eslint/package.json @@ -33,6 +33,6 @@ "license": "Apache-2.0", "devDependencies": { "@prettier/sync": "0.5.1", - "@typescript-eslint/utils": "7.1.0" + "@typescript-eslint/utils": "7.6.0" } } diff --git a/remote/test/puppeteer/tools/mocha-runner/package.json b/remote/test/puppeteer/tools/mocha-runner/package.json index a817020d5e..94a31ae2e1 100644 --- a/remote/test/puppeteer/tools/mocha-runner/package.json +++ b/remote/test/puppeteer/tools/mocha-runner/package.json @@ -36,7 +36,7 @@ "devDependencies": { "@types/yargs": "17.0.32", "c8": "9.1.0", - "glob": "10.3.10", + "glob": "10.3.12", "yargs": "17.7.2", "zod": "3.22.4" } diff --git a/remote/test/puppeteer/tools/sort-test-expectations.mjs b/remote/test/puppeteer/tools/sort-test-expectations.mjs index 972d244874..a80f0cfbd2 100644 --- a/remote/test/puppeteer/tools/sort-test-expectations.mjs +++ b/remote/test/puppeteer/tools/sort-test-expectations.mjs @@ -78,14 +78,14 @@ const toBeRemoved = new Set(); for (let i = testExpectations.length - 1; i >= 0; i--) { const expectation = testExpectations[i]; const params = new Set(expectation.parameters); - const labels = new Set(expectation.expectations); + const expectations = new Set(expectation.expectations); const platforms = new Set(expectation.platforms); let foundMatch = false; for (let j = i - 1; j >= 0; j--) { const candidate = testExpectations[j]; const candidateParams = new Set(candidate.parameters); - const candidateLabels = new Set(candidate.expectations); + const candidateExpectations = new Set(candidate.expectations); const candidatePlatforms = new Set(candidate.platforms); if ( @@ -93,11 +93,11 @@ for (let i = testExpectations.length - 1; i >= 0; i--) { expectation.testIdPattern, candidate.testIdPattern ) && - isSubset(candidateParams, params) && - isSubset(candidatePlatforms, platforms) + isSubset(candidatePlatforms, platforms) && + (isSubset(params, candidateParams) || isSubset(candidateParams, params)) ) { foundMatch = true; - if (isSubset(candidateLabels, labels)) { + if (isSubset(candidateExpectations, expectations)) { console.log('removing', expectation, 'already covered by', candidate); toBeRemoved.add(expectation); } @@ -105,7 +105,7 @@ for (let i = testExpectations.length - 1; i >= 0; i--) { } } - if (!foundMatch && isSubset(new Set(['PASS']), labels)) { + if (!foundMatch && isSubset(new Set(['PASS']), expectations)) { console.log( 'removing', expectation, diff --git a/remote/test/puppeteer/tools/update_chrome_revision.mjs b/remote/test/puppeteer/tools/update_chrome_revision.mjs index 64eeef74d5..0083bc0bbb 100644 --- a/remote/test/puppeteer/tools/update_chrome_revision.mjs +++ b/remote/test/puppeteer/tools/update_chrome_revision.mjs @@ -99,6 +99,12 @@ async function updateDevToolsProtocolVersion(revision) { `"devtools-protocol": "${currentProtocol}"`, `"devtools-protocol": "${bestNewProtocol}"` ); + + await replaceInFile( + './packages/puppeteer/package.json', + `"devtools-protocol": "${currentProtocol}"`, + `"devtools-protocol": "${bestNewProtocol}"` + ); } async function updateVersionFileLastMaintained(oldVersion, newVersion) { diff --git a/remote/test/puppeteer/versions.js b/remote/test/puppeteer/versions.js index 05d8429789..ca01e45511 100644 --- a/remote/test/puppeteer/versions.js +++ b/remote/test/puppeteer/versions.js @@ -7,6 +7,12 @@ const versionsPerRelease = new Map([ // This is a mapping from Chrome version => Puppeteer version. // In Chrome roll patches, use `NEXT` for the Puppeteer version. + ['123.0.6312.122', 'v22.6.4'], + ['123.0.6312.105', 'v22.6.3'], + ['123.0.6312.86', 'v22.6.2'], + ['123.0.6312.58', 'v22.6.0'], + ['122.0.6261.128', 'v22.5.0'], + ['122.0.6261.111', 'v22.4.1'], ['122.0.6261.94', 'v22.4.0'], ['122.0.6261.69', 'v22.3.0'], ['122.0.6261.57', 'v22.2.0'], @@ -65,7 +71,7 @@ const versionsPerRelease = new Map([ ]); // Should not be more than 2 major versions behind Chrome Stable (https://chromestatus.com/roadmap). -const lastMaintainedChromeVersion = '119.0.6045.105'; +const lastMaintainedChromeVersion = '120.0.6099.109'; if (!versionsPerRelease.has(lastMaintainedChromeVersion)) { throw new Error( diff --git a/remote/webdriver-bidi/jar.mn b/remote/webdriver-bidi/jar.mn index 6f0b2493d8..ed28fda6ba 100644 --- a/remote/webdriver-bidi/jar.mn +++ b/remote/webdriver-bidi/jar.mn @@ -21,6 +21,7 @@ remote.jar: content/webdriver-bidi/modules/root/input.sys.mjs (modules/root/input.sys.mjs) content/webdriver-bidi/modules/root/log.sys.mjs (modules/root/log.sys.mjs) content/webdriver-bidi/modules/root/network.sys.mjs (modules/root/network.sys.mjs) + content/webdriver-bidi/modules/root/permissions.sys.mjs (modules/root/permissions.sys.mjs) content/webdriver-bidi/modules/root/script.sys.mjs (modules/root/script.sys.mjs) content/webdriver-bidi/modules/root/session.sys.mjs (modules/root/session.sys.mjs) content/webdriver-bidi/modules/root/storage.sys.mjs (modules/root/storage.sys.mjs) diff --git a/remote/webdriver-bidi/modules/ModuleRegistry.sys.mjs b/remote/webdriver-bidi/modules/ModuleRegistry.sys.mjs index 63713f1f02..145e227fc7 100644 --- a/remote/webdriver-bidi/modules/ModuleRegistry.sys.mjs +++ b/remote/webdriver-bidi/modules/ModuleRegistry.sys.mjs @@ -18,6 +18,8 @@ ChromeUtils.defineESModuleGetters(modules.root, { log: "chrome://remote/content/webdriver-bidi/modules/root/log.sys.mjs", network: "chrome://remote/content/webdriver-bidi/modules/root/network.sys.mjs", + permissions: + "chrome://remote/content/webdriver-bidi/modules/root/permissions.sys.mjs", script: "chrome://remote/content/webdriver-bidi/modules/root/script.sys.mjs", session: "chrome://remote/content/webdriver-bidi/modules/root/session.sys.mjs", diff --git a/remote/webdriver-bidi/modules/root/browsingContext.sys.mjs b/remote/webdriver-bidi/modules/root/browsingContext.sys.mjs index 8424bebf4a..649e801175 100644 --- a/remote/webdriver-bidi/modules/root/browsingContext.sys.mjs +++ b/remote/webdriver-bidi/modules/root/browsingContext.sys.mjs @@ -85,6 +85,7 @@ const CreateType = { * @enum {LocatorType} */ export const LocatorType = { + accessibility: "accessibility", css: "css", innerText: "innerText", xpath: "xpath", @@ -545,7 +546,7 @@ class BrowsingContextModule extends Module { // On Android there is only a single window allowed. As such fallback to // open a new tab instead. const type = lazy.AppInfo.isAndroid ? "tab" : typeHint; - + let waitForVisibilityChangePromise; switch (type) { case "window": { const newWindow = await lazy.windowManager.openBrowserWindow({ @@ -572,8 +573,6 @@ class BrowsingContextModule extends Module { window = lazy.TabManager.getWindowForTab(referenceTab); } - const promises = []; - if (!background && !lazy.AppInfo.isAndroid) { // When opening a new foreground tab we need to wait until the // "document.visibilityState" of the currently selected tab in this @@ -581,32 +580,32 @@ class BrowsingContextModule extends Module { // // Bug 1884142: It's not supported on Android for the TestRunner package. const selectedTab = lazy.TabManager.getTabBrowser(window).selectedTab; - promises.push( - this.#waitForVisibilityChange( - lazy.TabManager.getBrowserForTab(selectedTab).browsingContext - ) + + // Create the promise immediately, but await it later in parallel with + // waitForInitialNavigationCompleted. + waitForVisibilityChangePromise = this.#waitForVisibilityChange( + lazy.TabManager.getBrowserForTab(selectedTab).browsingContext ); } - promises.unshift( - lazy.TabManager.addTab({ - focus: !background, - referenceTab, - userContextId: userContext, - }) - ); - - const [tab] = await Promise.all(promises); + const tab = await lazy.TabManager.addTab({ + focus: !background, + referenceTab, + userContextId: userContext, + }); browser = lazy.TabManager.getBrowserForTab(tab); } } - await lazy.waitForInitialNavigationCompleted( - browser.browsingContext.webProgress, - { - unloadTimeout: 5000, - } - ); + await Promise.all([ + lazy.waitForInitialNavigationCompleted( + browser.browsingContext.webProgress, + { + unloadTimeout: 5000, + } + ), + waitForVisibilityChangePromise, + ]); // The tab on Android is always opened in the foreground, // so we need to select the previous tab, @@ -805,13 +804,33 @@ class BrowsingContextModule extends Module { /** * Used as an argument for browsingContext.locateNodes command, as one of the available variants - * {CssLocator}, {InnerTextLocator} or {XPathLocator}, to represent a way of how lookup of nodes + * {AccessibilityLocator}, {CssLocator}, {InnerTextLocator} or {XPathLocator}, to represent a way of how lookup of nodes * is going to be performed. * * @typedef Locator */ /** + * Used as a value argument for browsingContext.locateNodes command + * in case of a lookup by accessibility attributes. + * + * @typedef AccessibilityLocatorValue + * + * @property {string=} name + * @property {string=} role + */ + + /** + * Used as an argument for browsingContext.locateNodes command + * to represent a lookup by accessibility attributes. + * + * @typedef AccessibilityLocator + * + * @property {LocatorType} [type=LocatorType.accessibility] + * @property {AccessibilityLocatorValue} value + */ + + /** * Used as an argument for browsingContext.locateNodes command * to represent a lookup by css selector. * @@ -900,7 +919,42 @@ class BrowsingContextModule extends Module { `Expected "locator.type" to be one of ${locatorTypes}, got ${locator.type}` )(locator.type); - if (![LocatorType.css, LocatorType.xpath].includes(locator.type)) { + if ( + [LocatorType.css, LocatorType.innerText, LocatorType.xpath].includes( + locator.type + ) + ) { + lazy.assert.string( + locator.value, + `Expected "locator.value" of "locator.type" "${locator.type}" to be a string, got ${locator.value}` + ); + } + if (locator.type == LocatorType.accessibility) { + lazy.assert.object( + locator.value, + `Expected "locator.value" of "locator.type" "${locator.type}" to be an object, got ${locator.value}` + ); + + const { name = null, role = null } = locator.value; + if (name !== null) { + lazy.assert.string( + locator.value.name, + `Expected "locator.value.name" of "locator.type" "${locator.type}" to be a string, got ${name}` + ); + } + if (role !== null) { + lazy.assert.string( + locator.value.role, + `Expected "locator.value.role" of "locator.type" "${locator.type}" to be a string, got ${role}` + ); + } + } + + if ( + ![LocatorType.accessibility, LocatorType.css, LocatorType.xpath].includes( + locator.type + ) + ) { throw new lazy.error.UnsupportedOperationError( `"locator.type" argument with value: ${locator.type} is not supported yet.` ); @@ -1249,7 +1303,11 @@ class BrowsingContextModule extends Module { * @param {object=} options * @param {string} options.context * Id of the browsing context. - * @param {Viewport|null} options.viewport + * @param {(number|null)=} options.devicePixelRatio + * A value to override device pixel ratio, or `null` to reset it to + * the original value. Different values will not cause the rendering to change, + * only image srcsets and media queries will be applied as if DPR is redefined. + * @param {(Viewport|null)=} options.viewport * Dimensions to set the viewport to, or `null` to reset it * to the original dimensions. * @@ -1259,7 +1317,7 @@ class BrowsingContextModule extends Module { * Raised when the command is called on Android. */ async setViewport(options = {}) { - const { context: contextId, viewport } = options; + const { context: contextId, devicePixelRatio, viewport } = options; if (lazy.AppInfo.isAndroid) { // Bug 1840084: Add Android support for modifying the viewport. @@ -1322,6 +1380,24 @@ class BrowsingContextModule extends Module { browser.style.setProperty("width", targetWidth + "px"); } + if (devicePixelRatio !== undefined) { + if (devicePixelRatio !== null) { + lazy.assert.number( + devicePixelRatio, + `Expected "devicePixelRatio" to be a number or null, got ${devicePixelRatio}` + ); + lazy.assert.that( + devicePixelRatio => devicePixelRatio > 0, + `Expected "devicePixelRatio" to be greater than 0, got ${devicePixelRatio}` + )(devicePixelRatio); + + context.overrideDPPX = devicePixelRatio; + } else { + // Will reset to use the global default scaling factor. + context.overrideDPPX = 0; + } + } + if (targetHeight !== currentHeight || targetWidth !== currentWidth) { // Wait until the viewport has been resized await this.messageHandler.forwardCommand({ diff --git a/remote/webdriver-bidi/modules/root/network.sys.mjs b/remote/webdriver-bidi/modules/root/network.sys.mjs index 6850e3f372..326fa87a02 100644 --- a/remote/webdriver-bidi/modules/root/network.sys.mjs +++ b/remote/webdriver-bidi/modules/root/network.sys.mjs @@ -12,8 +12,6 @@ ChromeUtils.defineESModuleGetters(lazy, { generateUUID: "chrome://remote/content/shared/UUID.sys.mjs", matchURLPattern: "chrome://remote/content/shared/webdriver/URLPattern.sys.mjs", - notifyNavigationStarted: - "chrome://remote/content/shared/NavigationManager.sys.mjs", NetworkListener: "chrome://remote/content/shared/listeners/NetworkListener.sys.mjs", parseChallengeHeader: @@ -309,7 +307,9 @@ class NetworkModule extends Module { // Set of event names which have active subscriptions this.#subscribedEvents = new Set(); - this.#networkListener = new lazy.NetworkListener(); + this.#networkListener = new lazy.NetworkListener( + this.messageHandler.navigationManager + ); this.#networkListener.on("auth-required", this.#onAuthRequired); this.#networkListener.on("before-request-sent", this.#onBeforeRequestSent); this.#networkListener.on("fetch-error", this.#onFetchError); @@ -549,8 +549,7 @@ class NetworkModule extends Module { ); } - const wrapper = ChannelWrapper.get(request); - wrapper.resume(); + request.wrappedChannel.resume(); resolveBlockedEvent(); } @@ -684,8 +683,7 @@ class NetworkModule extends Module { await authCallbacks.provideAuthCredentials(); } } else { - const wrapper = ChannelWrapper.get(request); - wrapper.resume(); + request.wrappedChannel.resume(); } resolveBlockedEvent(); @@ -803,9 +801,8 @@ class NetworkModule extends Module { ); } - const wrapper = ChannelWrapper.get(request); - wrapper.resume(); - wrapper.cancel( + request.wrappedChannel.resume(); + request.wrappedChannel.cancel( Cr.NS_ERROR_ABORT, Ci.nsILoadInfo.BLOCKING_REASON_WEBDRIVER_BIDI ); @@ -933,8 +930,7 @@ class NetworkModule extends Module { if (phase === InterceptPhase.AuthRequired) { await authCallbacks.provideAuthCredentials(); } else { - const wrapper = ChannelWrapper.get(request); - wrapper.resume(); + request.wrappedChannel.resume(); } resolveBlockedEvent(); @@ -987,11 +983,7 @@ class NetworkModule extends Module { * The response channel. */ #addBlockedRequest(requestId, phase, options = {}) { - const { - authCallbacks, - requestChannel: request, - responseChannel: response, - } = options; + const { authCallbacks, request, response } = options; const { promise: blockedEventPromise, resolve: resolveBlockedEvent } = Promise.withResolvers(); @@ -1117,14 +1109,14 @@ class NetworkModule extends Module { } } - #extractChallenges(responseData) { + #extractChallenges(response) { let headerName; // Using case-insensitive match for header names, so we use the lowercase // version of the "WWW-Authenticate" / "Proxy-Authenticate" strings. - if (responseData.status === 401) { + if (response.status === 401) { headerName = "www-authenticate"; - } else if (responseData.status === 407) { + } else if (response.status === 407) { headerName = "proxy-authenticate"; } else { return null; @@ -1132,10 +1124,10 @@ class NetworkModule extends Module { const challenges = []; - for (const header of responseData.headers) { - if (header.name.toLowerCase() === headerName) { + for (const [name, value] of response.getHeadersList()) { + if (name.toLowerCase() === headerName) { // A single header can contain several challenges. - const headerChallenges = lazy.parseChallengeHeader(header.value); + const headerChallenges = lazy.parseChallengeHeader(value); for (const headerChallenge of headerChallenges) { const realmParam = headerChallenge.params.find( param => param.name == "realm" @@ -1177,7 +1169,7 @@ class NetworkModule extends Module { }; } - #getNetworkIntercepts(event, requestData, contextId) { + #getNetworkIntercepts(event, request, topContextId) { const intercepts = []; let phase; @@ -1197,17 +1189,11 @@ class NetworkModule extends Module { return intercepts; } - // Retrieve the top browsing context id for this network event. - const browsingContext = lazy.TabManager.getBrowsingContextById(contextId); - const topLevelContextId = lazy.TabManager.getIdForBrowsingContext( - browsingContext.top - ); - - const url = requestData.url; + const url = request.serializedURL; for (const [interceptId, intercept] of this.#interceptMap) { if ( intercept.contexts !== null && - !intercept.contexts.includes(topLevelContextId) + !intercept.contexts.includes(topContextId) ) { // Skip this intercept if the event's context does not match the list // of contexts for this intercept. @@ -1228,31 +1214,96 @@ class NetworkModule extends Module { return intercepts; } - #getNavigationId(eventName, isNavigationRequest, browsingContext, url) { - if (!isNavigationRequest) { - // Not a navigation request return null. - return null; + #getRequestData(request) { + const requestId = request.requestId; + + // "Let url be the result of running the URL serializer with request’s URL" + // request.serializedURL is already serialized. + const url = request.serializedURL; + const method = request.method; + + const bodySize = request.postDataSize; + const headersSize = request.headersSize; + const headers = []; + const cookies = []; + + for (const [name, value] of request.getHeadersList()) { + headers.push(this.#serializeHeader(name, value)); + if (name.toLowerCase() == "cookie") { + // TODO: Retrieve the actual cookies from the cookie store. + const headerCookies = value.split(";"); + for (const cookie of headerCookies) { + const equal = cookie.indexOf("="); + const cookieName = cookie.substr(0, equal); + const cookieValue = cookie.substr(equal + 1); + const serializedCookie = this.#serializeHeader( + unescape(cookieName.trim()), + unescape(cookieValue.trim()) + ); + cookies.push(serializedCookie); + } + } } - let navigation = - this.messageHandler.navigationManager.getNavigationForBrowsingContext( - browsingContext - ); + const timings = request.getFetchTimings(); - // `onBeforeRequestSent` might be too early for the NavigationManager. - // If there is no ongoing navigation, create one ourselves. - // TODO: Bug 1835704 to detect navigations earlier and avoid this. - if ( - eventName === "network.beforeRequestSent" && - (!navigation || navigation.finished) - ) { - navigation = lazy.notifyNavigationStarted({ - contextDetails: { context: browsingContext }, - url, - }); + return { + request: requestId, + url, + method, + bodySize, + headersSize, + headers, + cookies, + timings, + }; + } + + #getResponseContentInfo(response) { + return { + size: response.decodedBodySize, + }; + } + + #getResponseData(response) { + const url = response.serializedURL; + const protocol = response.protocol; + const status = response.status; + const statusText = response.statusMessage; + // TODO: Ideally we should have a `isCacheStateLocal` getter + // const fromCache = response.isCacheStateLocal(); + const fromCache = response.fromCache; + const mimeType = response.getComputedMimeType(); + const headers = []; + for (const [name, value] of response.getHeadersList()) { + headers.push(this.#serializeHeader(name, value)); + } + + const bytesReceived = response.totalTransmittedSize; + const headersSize = response.headersTransmittedSize; + const bodySize = response.encodedBodySize; + const content = this.#getResponseContentInfo(response); + const authChallenges = this.#extractChallenges(response); + + const params = { + url, + protocol, + status, + statusText, + fromCache, + headers, + mimeType, + bytesReceived, + headersSize, + bodySize, + content, + }; + + if (authChallenges !== null) { + params.authChallenges = authChallenges; } - return navigation ? navigation.navigationId : null; + return params; } #getSuspendMarkerText(requestData, phase) { @@ -1260,21 +1311,13 @@ class NetworkModule extends Module { } #onAuthRequired = (name, data) => { - const { - authCallbacks, - contextId, - isNavigationRequest, - redirectCount, - requestChannel, - requestData, - responseChannel, - responseData, - timestamp, - } = data; + const { authCallbacks, request, response } = data; let isBlocked = false; try { - const browsingContext = lazy.TabManager.getBrowsingContextById(contextId); + const browsingContext = lazy.TabManager.getBrowsingContextById( + request.contextId + ); if (!browsingContext) { // Do not emit events if the context id does not match any existing // browsing context. @@ -1283,18 +1326,9 @@ class NetworkModule extends Module { const protocolEventName = "network.authRequired"; - // Process the navigation to create potentially missing navigation ids - // before the early return below. - const navigation = this.#getNavigationId( - protocolEventName, - isNavigationRequest, - browsingContext, - requestData.url - ); - const isListening = this.messageHandler.eventsDispatcher.hasListener( protocolEventName, - { contextId } + { contextId: request.contextId } ); if (!isListening) { // If there are no listeners subscribed to this event and this context, @@ -1302,23 +1336,16 @@ class NetworkModule extends Module { return; } - const baseParameters = this.#processNetworkEvent(protocolEventName, { - contextId, - navigation, - redirectCount, - requestData, - timestamp, - }); + const baseParameters = this.#processNetworkEvent( + protocolEventName, + request + ); - const authRequiredEvent = this.#serializeNetworkEvent({ + const responseData = this.#getResponseData(response); + const authRequiredEvent = { ...baseParameters, response: responseData, - }); - - const authChallenges = this.#extractChallenges(responseData); - // authChallenges should never be null for a request which triggered an - // authRequired event. - authRequiredEvent.response.authChallenges = authChallenges; + }; this.emitEvent( protocolEventName, @@ -1337,8 +1364,8 @@ class NetworkModule extends Module { InterceptPhase.AuthRequired, { authCallbacks, - requestChannel, - responseChannel, + request, + response, } ); } @@ -1352,16 +1379,11 @@ class NetworkModule extends Module { }; #onBeforeRequestSent = (name, data) => { - const { - contextId, - isNavigationRequest, - redirectCount, - requestChannel, - requestData, - timestamp, - } = data; + const { request } = data; - const browsingContext = lazy.TabManager.getBrowsingContextById(contextId); + const browsingContext = lazy.TabManager.getBrowsingContextById( + request.contextId + ); if (!browsingContext) { // Do not emit events if the context id does not match any existing // browsing context. @@ -1371,15 +1393,6 @@ class NetworkModule extends Module { const internalEventName = "network._beforeRequestSent"; const protocolEventName = "network.beforeRequestSent"; - // Process the navigation to create potentially missing navigation ids - // before the early return below. - const navigation = this.#getNavigationId( - protocolEventName, - isNavigationRequest, - browsingContext, - requestData.url - ); - // Always emit internal events, they are used to support the browsingContext // navigate command. // Bug 1861922: Replace internal events with a Network listener helper @@ -1387,15 +1400,15 @@ class NetworkModule extends Module { this.emitEvent( internalEventName, { - navigation, - url: requestData.url, + navigation: request.navigationId, + url: request.serializedURL, }, this.#getContextInfo(browsingContext) ); const isListening = this.messageHandler.eventsDispatcher.hasListener( protocolEventName, - { contextId } + { contextId: request.contextId } ); if (!isListening) { // If there are no listeners subscribed to this event and this context, @@ -1403,23 +1416,20 @@ class NetworkModule extends Module { return; } - const baseParameters = this.#processNetworkEvent(protocolEventName, { - contextId, - navigation, - redirectCount, - requestData, - timestamp, - }); + const baseParameters = this.#processNetworkEvent( + protocolEventName, + request + ); // Bug 1805479: Handle the initiator, including stacktrace details. const initiator = { type: InitiatorType.Other, }; - const beforeRequestSentEvent = this.#serializeNetworkEvent({ + const beforeRequestSentEvent = { ...baseParameters, initiator, - }); + }; this.emitEvent( protocolEventName, @@ -1430,32 +1440,26 @@ class NetworkModule extends Module { if (beforeRequestSentEvent.isBlocked) { // TODO: Requests suspended in beforeRequestSent still reach the server at // the moment. https://bugzilla.mozilla.org/show_bug.cgi?id=1849686 - const wrapper = ChannelWrapper.get(requestChannel); - wrapper.suspend( - this.#getSuspendMarkerText(requestData, "beforeRequestSent") + request.wrappedChannel.suspend( + this.#getSuspendMarkerText(request, "beforeRequestSent") ); this.#addBlockedRequest( beforeRequestSentEvent.request.request, InterceptPhase.BeforeRequestSent, { - requestChannel, + request, } ); } }; #onFetchError = (name, data) => { - const { - contextId, - errorText, - isNavigationRequest, - redirectCount, - requestData, - timestamp, - } = data; + const { request } = data; - const browsingContext = lazy.TabManager.getBrowsingContextById(contextId); + const browsingContext = lazy.TabManager.getBrowsingContextById( + request.contextId + ); if (!browsingContext) { // Do not emit events if the context id does not match any existing // browsing context. @@ -1465,15 +1469,6 @@ class NetworkModule extends Module { const internalEventName = "network._fetchError"; const protocolEventName = "network.fetchError"; - // Process the navigation to create potentially missing navigation ids - // before the early return below. - const navigation = this.#getNavigationId( - protocolEventName, - isNavigationRequest, - browsingContext, - requestData.url - ); - // Always emit internal events, they are used to support the browsingContext // navigate command. // Bug 1861922: Replace internal events with a Network listener helper @@ -1481,15 +1476,15 @@ class NetworkModule extends Module { this.emitEvent( internalEventName, { - navigation, - url: requestData.url, + navigation: request.navigationId, + url: request.serializedURL, }, this.#getContextInfo(browsingContext) ); const isListening = this.messageHandler.eventsDispatcher.hasListener( protocolEventName, - { contextId } + { contextId: request.contextId } ); if (!isListening) { // If there are no listeners subscribed to this event and this context, @@ -1497,18 +1492,15 @@ class NetworkModule extends Module { return; } - const baseParameters = this.#processNetworkEvent(protocolEventName, { - contextId, - navigation, - redirectCount, - requestData, - timestamp, - }); + const baseParameters = this.#processNetworkEvent( + protocolEventName, + request + ); - const fetchErrorEvent = this.#serializeNetworkEvent({ + const fetchErrorEvent = { ...baseParameters, - errorText, - }); + errorText: request.errorText, + }; this.emitEvent( protocolEventName, @@ -1518,18 +1510,11 @@ class NetworkModule extends Module { }; #onResponseEvent = (name, data) => { - const { - contextId, - isNavigationRequest, - redirectCount, - requestChannel, - requestData, - responseChannel, - responseData, - timestamp, - } = data; + const { request, response } = data; - const browsingContext = lazy.TabManager.getBrowsingContextById(contextId); + const browsingContext = lazy.TabManager.getBrowsingContextById( + request.contextId + ); if (!browsingContext) { // Do not emit events if the context id does not match any existing // browsing context. @@ -1546,15 +1531,6 @@ class NetworkModule extends Module { ? "network._responseStarted" : "network._responseCompleted"; - // Process the navigation to create potentially missing navigation ids - // before the early return below. - const navigation = this.#getNavigationId( - protocolEventName, - isNavigationRequest, - browsingContext, - requestData.url - ); - // Always emit internal events, they are used to support the browsingContext // navigate command. // Bug 1861922: Replace internal events with a Network listener helper @@ -1562,15 +1538,15 @@ class NetworkModule extends Module { this.emitEvent( internalEventName, { - navigation, - url: requestData.url, + navigation: request.navigationId, + url: request.serializedURL, }, this.#getContextInfo(browsingContext) ); const isListening = this.messageHandler.eventsDispatcher.hasListener( protocolEventName, - { contextId } + { contextId: request.contextId } ); if (!isListening) { // If there are no listeners subscribed to this event and this context, @@ -1578,23 +1554,17 @@ class NetworkModule extends Module { return; } - const baseParameters = this.#processNetworkEvent(protocolEventName, { - contextId, - navigation, - redirectCount, - requestData, - timestamp, - }); + const baseParameters = this.#processNetworkEvent( + protocolEventName, + request + ); - const responseEvent = this.#serializeNetworkEvent({ + const responseData = this.#getResponseData(response); + + const responseEvent = { ...baseParameters, response: responseData, - }); - - const authChallenges = this.#extractChallenges(responseData); - if (authChallenges !== null) { - responseEvent.response.authChallenges = authChallenges; - } + }; this.emitEvent( protocolEventName, @@ -1606,51 +1576,40 @@ class NetworkModule extends Module { protocolEventName === "network.responseStarted" && responseEvent.isBlocked ) { - const wrapper = ChannelWrapper.get(requestChannel); - wrapper.suspend( - this.#getSuspendMarkerText(requestData, "responseStarted") + request.wrappedChannel.suspend( + this.#getSuspendMarkerText(request, "responseStarted") ); this.#addBlockedRequest( responseEvent.request.request, InterceptPhase.ResponseStarted, { - requestChannel, - responseChannel, + request, + response, } ); } }; - /** - * Process the network event data for a given network event name and create - * the corresponding base parameters. - * - * @param {string} eventName - * One of the supported network event names. - * @param {object} data - * @param {string} data.contextId - * The browsing context id for the network event. - * @param {string|null} data.navigation - * The navigation id if this is a network event for a navigation request. - * @param {number} data.redirectCount - * The redirect count for the network event. - * @param {RequestData} data.requestData - * The network.RequestData information for the network event. - * @param {number} data.timestamp - * The timestamp when the network event was created. - */ - #processNetworkEvent(eventName, data) { - const { contextId, navigation, redirectCount, requestData, timestamp } = - data; - const intercepts = this.#getNetworkIntercepts( - eventName, - requestData, - contextId - ); - const isBlocked = !!intercepts.length; + #processNetworkEvent(event, request) { + const requestData = this.#getRequestData(request); + const navigation = request.navigationId; + let contextId = null; + let topContextId = null; + if (request.contextId) { + // Retrieve the top browsing context id for this network event. + contextId = request.contextId; + const browsingContext = lazy.TabManager.getBrowsingContextById(contextId); + topContextId = lazy.TabManager.getIdForBrowsingContext( + browsingContext.top + ); + } - const baseParameters = { + const intercepts = this.#getNetworkIntercepts(event, request, topContextId); + const redirectCount = request.redirectCount; + const timestamp = Date.now(); + const isBlocked = !!intercepts.length; + const params = { context: contextId, isBlocked, navigation, @@ -1660,51 +1619,17 @@ class NetworkModule extends Module { }; if (isBlocked) { - baseParameters.intercepts = intercepts; + params.intercepts = intercepts; } - return baseParameters; - } - - #serializeHeadersOrCookies(headersOrCookies) { - return headersOrCookies.map(item => ({ - name: item.name, - value: this.#serializeStringAsBytesValue(item.value), - })); + return params; } - /** - * Serialize in-place all cookies and headers arrays found in a given network - * event payload. - * - * @param {object} networkEvent - * The network event parameters object to serialize. - * @returns {object} - * The serialized network event parameters. - */ - #serializeNetworkEvent(networkEvent) { - // Make a shallow copy of networkEvent before serializing the headers and - // cookies arrays in request/response. - const serialized = { ...networkEvent }; - - // Make a shallow copy of the request data. - serialized.request = { ...networkEvent.request }; - serialized.request.cookies = this.#serializeHeadersOrCookies( - networkEvent.request.cookies - ); - serialized.request.headers = this.#serializeHeadersOrCookies( - networkEvent.request.headers - ); - - if (networkEvent.response?.headers) { - // Make a shallow copy of the response data. - serialized.response = { ...networkEvent.response }; - serialized.response.headers = this.#serializeHeadersOrCookies( - networkEvent.response.headers - ); - } - - return serialized; + #serializeHeader(name, value) { + return { + name, + value: this.#serializeStringAsBytesValue(value), + }; } /** diff --git a/remote/webdriver-bidi/modules/root/permissions.sys.mjs b/remote/webdriver-bidi/modules/root/permissions.sys.mjs new file mode 100644 index 0000000000..7c66b113b0 --- /dev/null +++ b/remote/webdriver-bidi/modules/root/permissions.sys.mjs @@ -0,0 +1,140 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { Module } from "chrome://remote/content/shared/messagehandler/Module.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs", + error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs", + permissions: "chrome://remote/content/shared/Permissions.sys.mjs", +}); + +export const PermissionState = { + denied: "denied", + granted: "granted", + prompt: "prompt", +}; + +class PermissionsModule extends Module { + constructor(messageHandler) { + super(messageHandler); + } + + destroy() {} + + /** + * An object that holds the information about permission descriptor + * for Webdriver BiDi permissions.setPermission command. + * + * @typedef PermissionDescriptor + * + * @property {string} name + * The name of the permission. + */ + + /** + * Set to a given permission descriptor a given state on a provided origin. + * + * @param {object=} options + * @param {PermissionDescriptor} options.descriptor + * The descriptor of the permission which will be updated. + * @param {PermissionState} options.state + * The state which will be set to the permission. + * @param {string} options.origin + * The origin which is used as a target for permission update. + * @param {string=} options.userContext [unsupported] + * The id of the user context which should be used as a target + * for permission update. + * + * @throws {InvalidArgumentError} + * Raised if an argument is of an invalid type or value. + * @throws {UnsupportedOperationError} + * Raised when unsupported permissions are set or <var>userContext</var> + * argument is used. + */ + async setPermission(options = {}) { + const { + descriptor, + state, + origin, + userContext: userContextId = null, + } = options; + + lazy.assert.object( + descriptor, + `Expected "descriptor" to be an object, got ${descriptor}` + ); + const permissionName = descriptor.name; + lazy.assert.string( + permissionName, + `Expected "descriptor.name" to be a string, got ${permissionName}` + ); + + lazy.permissions.validatePermission(permissionName); + + // Bug 1878741: Allowing this permission causes timing related Android crash. + if (descriptor.name === "notifications") { + if (Services.prefs.getBoolPref("notification.prompt.testing", false)) { + // Okay, do nothing. The notifications module will work without permission. + return; + } + throw new lazy.error.UnsupportedOperationError( + `Setting "descriptor.name" "notifications" expected "notification.prompt.testing" preference to be set` + ); + } + + if (permissionName === "storage-access") { + // TODO: Bug 1895457. Add support for "storage-access" permission. + throw new lazy.error.UnsupportedOperationError( + `"descriptor.name" "${permissionName}" is currently unsupported` + ); + } + + const permissionStateTypes = Object.keys(PermissionState); + lazy.assert.that( + state => permissionStateTypes.includes(state), + `Expected "state" to be one of ${permissionStateTypes}, got ${state}` + )(state); + + lazy.assert.string( + origin, + `Expected "origin" to be a string, got ${origin}` + ); + lazy.assert.that( + origin => URL.canParse(origin), + `Expected "origin" to be a valid URL, got ${origin}` + )(origin); + + if (userContextId !== null) { + lazy.assert.string( + userContextId, + `Expected "userContext" to be a string, got ${userContextId}` + ); + + // TODO: Bug 1894217. Add support for "userContext" argument. + throw new lazy.error.UnsupportedOperationError( + `"userContext" is not supported yet` + ); + } + + const activeWindow = Services.wm.getMostRecentBrowserWindow(); + let typedDescriptor; + try { + typedDescriptor = activeWindow.navigator.permissions.parseSetParameters({ + descriptor, + state, + }); + } catch (err) { + throw new lazy.error.InvalidArgumentError( + `The conversion of "descriptor" was not successful: ${err.message}` + ); + } + + lazy.permissions.set(typedDescriptor, state, origin); + } +} + +export const permissions = PermissionsModule; diff --git a/remote/webdriver-bidi/modules/windowglobal/browsingContext.sys.mjs b/remote/webdriver-bidi/modules/windowglobal/browsingContext.sys.mjs index ef61954284..6dbd5440e0 100644 --- a/remote/webdriver-bidi/modules/windowglobal/browsingContext.sys.mjs +++ b/remote/webdriver-bidi/modules/windowglobal/browsingContext.sys.mjs @@ -7,6 +7,8 @@ import { WindowGlobalBiDiModule } from "chrome://remote/content/webdriver-bidi/m const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { + accessibility: + "chrome://remote/content/shared/webdriver/Accessibility.sys.mjs", AnimationFramePromise: "chrome://remote/content/shared/Sync.sys.mjs", assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs", ClipRectangleType: @@ -48,6 +50,66 @@ class BrowsingContextModule extends WindowGlobalBiDiModule { this.#subscribedEvents = null; } + /** + * Collect nodes using accessibility attributes. + * + * @see https://w3c.github.io/webdriver-bidi/#collect-nodes-using-accessibility-attributes + */ + async #collectNodesUsingAccessibilityAttributes( + contextNodes, + selector, + maxReturnedNodeCount, + returnedNodes + ) { + if (returnedNodes === null) { + returnedNodes = []; + } + + for (const contextNode of contextNodes) { + let match = true; + + if (contextNode.nodeType === ELEMENT_NODE) { + if ("role" in selector) { + const role = await lazy.accessibility.getComputedRole(contextNode); + + if (selector.role !== role) { + match = false; + } + } + + if ("name" in selector) { + const name = await lazy.accessibility.getAccessibleName(contextNode); + if (selector.name !== name) { + match = false; + } + } + } else { + match = false; + } + + if (match) { + if ( + maxReturnedNodeCount !== null && + returnedNodes.length === maxReturnedNodeCount + ) { + break; + } + returnedNodes.push(contextNode); + } + + const childNodes = [...contextNode.children]; + + await this.#collectNodesUsingAccessibilityAttributes( + childNodes, + selector, + maxReturnedNodeCount, + returnedNodes + ); + } + + return returnedNodes; + } + #getNavigationInfo(data) { // Note: the navigation id is collected in the parent-process and will be // added via event interception by the windowglobal-in-root module. @@ -85,75 +147,29 @@ class BrowsingContextModule extends WindowGlobalBiDiModule { ); } - #startListening() { - if (this.#subscribedEvents.size == 0) { - this.#loadListener.startListening(); - } - } - - #stopListening() { - if (this.#subscribedEvents.size == 0) { - this.#loadListener.stopListening(); - } - } - - #subscribeEvent(event) { - switch (event) { - case "browsingContext._documentInteractive": - this.#startListening(); - this.#subscribedEvents.add("browsingContext._documentInteractive"); - break; - case "browsingContext.domContentLoaded": - this.#startListening(); - this.#subscribedEvents.add("browsingContext.domContentLoaded"); - break; - case "browsingContext.load": - this.#startListening(); - this.#subscribedEvents.add("browsingContext.load"); - break; - } - } - - #unsubscribeEvent(event) { - switch (event) { - case "browsingContext._documentInteractive": - this.#subscribedEvents.delete("browsingContext._documentInteractive"); - break; - case "browsingContext.domContentLoaded": - this.#subscribedEvents.delete("browsingContext.domContentLoaded"); - break; - case "browsingContext.load": - this.#subscribedEvents.delete("browsingContext.load"); - break; - } - - this.#stopListening(); - } - - #onDOMContentLoaded = (eventName, data) => { - if (this.#subscribedEvents.has("browsingContext._documentInteractive")) { - this.messageHandler.emitEvent("browsingContext._documentInteractive", { - baseURL: data.target.baseURI, - contextId: this.messageHandler.contextId, - documentURL: data.target.URL, - innerWindowId: this.messageHandler.innerWindowId, - readyState: data.target.readyState, - }); - } - - if (this.#subscribedEvents.has("browsingContext.domContentLoaded")) { - this.emitEvent( - "browsingContext.domContentLoaded", - this.#getNavigationInfo(data) + /** + * Locate nodes using accessibility attributes. + * + * @see https://w3c.github.io/webdriver-bidi/#locate-nodes-using-accessibility-attributes + */ + async #locateNodesUsingAccessibilityAttributes( + contextNodes, + selector, + maxReturnedNodeCount + ) { + if (!("role" in selector) && !("name" in selector)) { + throw new lazy.error.InvalidSelectorError( + "Locating nodes by accessibility attributes requires `role` or `name` arguments" ); } - }; - #onLoad = (eventName, data) => { - if (this.#subscribedEvents.has("browsingContext.load")) { - this.emitEvent("browsingContext.load", this.#getNavigationInfo(data)); - } - }; + return this.#collectNodesUsingAccessibilityAttributes( + contextNodes, + selector, + maxReturnedNodeCount, + null + ); + } /** * Locate nodes using css selector. @@ -259,6 +275,31 @@ class BrowsingContextModule extends WindowGlobalBiDiModule { return new DOMRect(x, y, width, height); } + #onDOMContentLoaded = (eventName, data) => { + if (this.#subscribedEvents.has("browsingContext._documentInteractive")) { + this.messageHandler.emitEvent("browsingContext._documentInteractive", { + baseURL: data.target.baseURI, + contextId: this.messageHandler.contextId, + documentURL: data.target.URL, + innerWindowId: this.messageHandler.innerWindowId, + readyState: data.target.readyState, + }); + } + + if (this.#subscribedEvents.has("browsingContext.domContentLoaded")) { + this.emitEvent( + "browsingContext.domContentLoaded", + this.#getNavigationInfo(data) + ); + } + }; + + #onLoad = (eventName, data) => { + if (this.#subscribedEvents.has("browsingContext.load")) { + this.emitEvent("browsingContext.load", this.#getNavigationInfo(data)); + } + }; + /** * Create a new rectangle which will be an intersection of * rectangles specified as arguments. @@ -288,6 +329,51 @@ class BrowsingContextModule extends WindowGlobalBiDiModule { return new DOMRect(x_min, y_min, width, height); } + #startListening() { + if (this.#subscribedEvents.size == 0) { + this.#loadListener.startListening(); + } + } + + #stopListening() { + if (this.#subscribedEvents.size == 0) { + this.#loadListener.stopListening(); + } + } + + #subscribeEvent(event) { + switch (event) { + case "browsingContext._documentInteractive": + this.#startListening(); + this.#subscribedEvents.add("browsingContext._documentInteractive"); + break; + case "browsingContext.domContentLoaded": + this.#startListening(); + this.#subscribedEvents.add("browsingContext.domContentLoaded"); + break; + case "browsingContext.load": + this.#startListening(); + this.#subscribedEvents.add("browsingContext.load"); + break; + } + } + + #unsubscribeEvent(event) { + switch (event) { + case "browsingContext._documentInteractive": + this.#subscribedEvents.delete("browsingContext._documentInteractive"); + break; + case "browsingContext.domContentLoaded": + this.#subscribedEvents.delete("browsingContext.domContentLoaded"); + break; + case "browsingContext.load": + this.#subscribedEvents.delete("browsingContext.load"); + break; + } + + this.#stopListening(); + } + /** * Internal commands */ @@ -425,7 +511,7 @@ class BrowsingContextModule extends WindowGlobalBiDiModule { return this.#rectangleIntersection(originRect, clipRect); } - _locateNodes(params = {}) { + async _locateNodes(params = {}) { const { locator, maxNodeCount, serializationOptions, startNodes } = params; const realm = this.messageHandler.getRealm(); @@ -451,6 +537,14 @@ class BrowsingContextModule extends WindowGlobalBiDiModule { let returnedNodes; switch (locator.type) { + case lazy.LocatorType.accessibility: { + returnedNodes = await this.#locateNodesUsingAccessibilityAttributes( + contextNodes, + locator.value, + maxNodeCount + ); + break; + } case lazy.LocatorType.css: { returnedNodes = this.#locateNodesUsingCss( contextNodes, diff --git a/remote/webdriver-bidi/modules/windowglobal/input.sys.mjs b/remote/webdriver-bidi/modules/windowglobal/input.sys.mjs index b91cce2310..8db2cbb30b 100644 --- a/remote/webdriver-bidi/modules/windowglobal/input.sys.mjs +++ b/remote/webdriver-bidi/modules/windowglobal/input.sys.mjs @@ -34,6 +34,10 @@ class InputModule extends WindowGlobalBiDiModule { const actionChain = lazy.action.Chain.fromJSON(this.#actionState, actions); await actionChain.dispatch(this.#actionState, this.messageHandler.window); + + // Terminate the current wheel transaction if there is one. Wheel + // transactions should not live longer than a single action chain. + ChromeUtils.endWheelTransaction(); } async releaseActions() { |