From 1ef04497044071b59b03d477f4e1e8488ee1d6bd Mon Sep 17 00:00:00 2001 From: Slava Leleka Date: Tue, 1 Nov 2022 14:12:10 +0300 Subject: [PATCH] ditching sizzle. #110 AG-3532 Squashed commit of the following: commit f863d09f08c27645a9b07f217fce961afd477e48 Author: Slava Leleka Date: Tue Nov 1 12:33:28 2022 +0200 fix comment to trigger bamboo build as latest commit e356c15e71457d77946752e12b5499244b3635bf Author: Slava Leleka Date: Tue Nov 1 13:18:13 2022 +0300 AG-16039 update safari compatibility Squashed commit of the following: commit f2684b4d6e72b23f3711687d200d01f23fe6e788 Author: Slava Leleka Date: Mon Oct 31 20:12:10 2022 +0200 fix parseUserAgent commit ab4ca227d6885b22da2e1c286efb21890650029e Author: Slava Leleka Date: Mon Oct 31 20:01:19 2022 +0200 fix parseUserAgent() for safari 11.1 commit 8232829c43497929ff17c67ef29c04fa994d3a02 Author: Slava Leleka Date: Mon Oct 31 19:31:23 2022 +0200 fix safari version regexp commit 6df75c0b0d3ddc978b4dd85dc44ec362c8060b28 Author: Slava Leleka Date: Mon Oct 31 19:13:36 2022 +0200 update regexp mask and use minor version for safari in SUPPORTED_BROWSERS_DATA commit 0e6fb20c7d5b40406f7da04d22c5743cb671bd26 Author: Slava Leleka Date: Mon Oct 31 19:07:19 2022 +0200 update readme for safari supported version -- 11.1 commit f02892e31f0ffaccbab44cce252cbf23d4a0a2db Author: Slava Leleka Date: Mon Oct 31 19:06:53 2022 +0200 update SUPPORTED_BROWSERS_DATA safari major version commit 93ce2e7a595c8657d225cf3ddc4d6afce0e83dbb Author: Slava Leleka Date: Mon Oct 31 19:01:37 2022 +0200 revert other browsers commit 1ed5a3c22705183e5355cd98a900e555a7fbfb91 Author: Slava Leleka Date: Mon Oct 31 18:58:48 2022 +0200 try safari 11.1 commit f026309449a41595f10d13dda4e2d730aa45174f Author: Slava Leleka Date: Mon Oct 31 18:53:38 2022 +0200 add selenium_version for sarafi commit 84f30d5e1e104b04ee6361ccc7671d0f61b642fb Author: Slava Leleka Date: Mon Oct 31 18:50:28 2022 +0200 no devide for safari commit 06c92806065bcad3c4fdee00dff43235dfb26b80 Author: Slava Leleka Date: Mon Oct 31 18:40:03 2022 +0200 limit browserstack tests only to safari, check '11.0' version commit d3fe928cd616aed1cca403ddeb143eb28444a14a Author: Slava Leleka Date: Mon Oct 31 18:40:09 2022 +0300 AG-14460 handle style declarations with only debug property Squashed commit of the following: commit 0506fbd62f683d30eae75057423296a530a52330 Merge: fa3bd3d deb8dfc Author: Slava Leleka Date: Mon Oct 31 17:27:59 2022 +0200 Merge branch 'epic/AG-3532' into fix/AG-14460_02 commit fa3bd3d9773f80fd7f4b7a252cb2387445376f01 Author: Slava Leleka Date: Mon Oct 31 17:16:26 2022 +0200 add test for only debug property in stylesheet commit 2acd1cc18423671590109f772f7ce24ec5f71ac5 Merge: 9e0cf7f fe3bad2 Author: Slava Leleka Date: Mon Oct 31 17:12:16 2022 +0200 Merge branch 'epic/AG-3532' into fix/AG-14460_02 commit 9e0cf7f18d4a22d834289786ef38181dd3207b22 Author: Slava Leleka Date: Thu Oct 27 22:40:13 2022 +0300 handle style declarations with only debug property commit deb8dfc2406c1a2770439ee84fc5fbddead9d7b0 Author: Slava Leleka Date: Mon Oct 31 18:27:36 2022 +0300 AG-14291 performance tests on playwright Squashed commit of the following: commit 93b177f6bbc2ac93c60e50845ab2dfc3cfcde9d2 Author: Slava Leleka Date: Mon Oct 31 17:22:11 2022 +0200 update readme commit 773d29043691e086fe874edded16d1450ecfda95 Author: Slava Leleka Date: Mon Oct 31 17:22:01 2022 +0200 no script for test performance commit b57efc9013ce76db5d72c5bda6adb6fae65278d1 Merge: d5dcba3 fe3bad2 Author: Slava Leleka Date: Mon Oct 31 17:17:09 2022 +0200 Merge branch 'epic/AG-3532' into feature/AG-14291 commit d5dcba3d64cf585bb6b8bb080d232f24f4f13732 Author: Slava Leleka Date: Wed Oct 26 18:15:48 2022 +0300 save performance results commit bc868b91e180229982c2a10fc9a3ea76bf1aa637 Author: Slava Leleka Date: Wed Oct 26 18:07:26 2022 +0300 run performance tests separately commit 27dfec32fba633ba214a9275a6be978570ca6038 Author: Slava Leleka Date: Tue Oct 25 18:16:37 2022 +0300 remove unnecessary comments commit b476683ee7006ecbc9a3dcc552b4e8e05ffb632f Author: Slava Leleka Date: Mon Oct 24 19:29:05 2022 +0300 save performance selector results to comments commit f30330c7607fb64bb617ac941d33e7cb6201203e Author: Slava Leleka Date: Tue Oct 18 16:19:51 2022 +0300 compare results for v1 and v2 commit d0ef72d11afa95cc10871c5fbce7b63843c4fab8 Author: Vladimir Zhelvis Date: Mon Oct 24 18:16:14 2022 +0300 move extCssDocument instance from 'src/extended-css' to 'selector/query' commit d9b5f7e2bc7e3901dff419f042b76a79e44d2089 Author: Slava Leleka Date: Mon Oct 24 17:55:12 2022 +0300 fix extCssDoc cache commit f19dc30f83e50ca96d73267abdfbaadd50ef05e1 Author: Slava Leleka Date: Thu Oct 20 22:11:01 2022 +0300 use lib bundles for performance selector tests commit de1246797ec22bed13ab418d9b0b86bf079e497e Merge: b33b5ad 7dd35ed Author: Slava Leleka Date: Wed Oct 19 14:38:59 2022 +0300 Merge branch 'epic/AG-3532' into feature/AG-14291 commit b33b5ada7f63276b18702fdac971dd7a3d8a3a02 Merge: ece54a2 b8b7e8a Author: Slava Leleka Date: Fri Oct 14 17:57:30 2022 +0300 Merge branch 'epic/AG-3532' into feature/AG-14291 commit ece54a2e47079bab2e0e1907183a58d32c7af9b6 Merge: 762c0b6 a243fea Author: Slava Leleka Date: Mon Sep 12 14:32:29 2022 +0300 merge epic into feature/AG-14291, resolve conflicts commit 762c0b6eb1feccace41f7edf999f4782cc0a2b15 Author: Slava Leleka Date: Fri Aug 5 16:54:00 2022 +0300 renaming for checkPerformanceV2 commit 21a6996388746851769501f24d1f05b37fb75d21 Author: Slava Leleka Date: Fri Aug 5 16:52:48 2022 +0300 use ExtendedCss for checkPerformanceV2 commit 2fe722e8494360b0f3526c4ccf96b91e3371e929 Author: Slava Leleka Date: Fri Aug 5 16:37:57 2022 +0300 add jest.setTimeout for performance-selector tests commit ac4be92dc8dc4513b07180242581d0b0a3f704e5 Author: Slava Leleka Date: Fri Aug 5 16:19:37 2022 +0300 revert old lib for performance tests commit 2a75f149209df75253bdf552742ba50a537f73b7 Merge: dfc6e63 cc94234 Author: Slava Leleka Date: Fri Aug 5 16:14:41 2022 +0300 merge epic into feature/AG-14291 commit dfc6e63a9df184aff3d2f886dc55d04e625d089d Merge: 43b6360 c59d012 Author: Slava Leleka Date: Thu Jun 23 15:42:06 2022 +0300 Merge branch 'epic/AG-3532' into feature/AG-14291 ... and 11 more commits commit fe3bad271477892b7a424950908bd333ca81bbed Author: Slava Leleka Date: Mon Oct 31 18:10:29 2022 +0300 AG-16039 update browsers compatibility Squashed commit of the following: commit 8b21f67982571b3855de83527456c6ea13100281 Author: Slava Leleka Date: Thu Oct 27 22:45:11 2022 +0300 update browserstack config for safari commit 4a8131c171494d1883dda2137d636fcba7aae61a Author: Slava Leleka Date: Thu Oct 27 20:14:39 2022 +0300 fix ie comment commit 3b4cd0fcc00d05c53d1f938e3a4e975ad01246d6 Author: Slava Leleka Date: Thu Oct 27 20:13:00 2022 +0300 specify edge, firefox, and opera in babel config commit 0969f599371cacda693ac355e4cd1ee4c5079e6c Author: Slava Leleka Date: Thu Oct 27 20:12:20 2022 +0300 fix ie comment commit 8e7f66a958ae59525edd5c91047f4a0b0178c6d1 Author: Slava Leleka Date: Thu Oct 27 20:12:01 2022 +0300 support safari 11 or newer commit 7a315fb5abcec14a6b97b2137d71799058212c25 Author: Slava Leleka Date: Thu Oct 27 20:11:28 2022 +0300 do not support ie commit 7dd35ed566f2f7255cbce806d50d9cdae27862d6 Author: Slava Leleka Date: Wed Oct 19 14:38:32 2022 +0300 AG-16950 add jsdoc linter Squashed commit of the following: commit a30da46224572bfee218b3b22ca54c7254cee456 Author: Slava Leleka Date: Wed Oct 19 14:21:03 2022 +0300 update ExtMutationObserver.disconnect() comment commit de339a10c4e9be802d23f9eb275929b761556682 Author: Slava Leleka Date: Wed Oct 19 13:54:15 2022 +0300 fix style of jsdoc comment links commit 8d9df416e96f4f83323c5cd21802a0c0ebd833fb Author: Slava Leleka Date: Tue Oct 18 00:50:10 2022 +0300 fix error text for has limitation commit 83146e04f094c2dc5a43d0da1504e9611f849360 Author: Slava Leleka Date: Mon Oct 17 19:42:14 2022 +0300 fix multiple comments commit 3f3cef25f99bf19aaae8386a36e408ee29691307 Author: Slava Leleka Date: Mon Oct 17 19:42:01 2022 +0300 update eslintrc commit 6e6acdb6b8fec20a65891fc57e9c542824bd0f23 Author: Slava Leleka Date: Mon Oct 17 14:16:59 2022 +0300 fix jsdoc/check-tag-names commit 31d5391b34a0342fac65d2ed98b13eec52acd3a6 Author: Slava Leleka Date: Fri Oct 14 16:19:32 2022 +0300 fix comments due to jsdoc-linter warnings commit 4e30af31210f426b54cebf1d18d873dce6533ac2 Author: Slava Leleka Date: Fri Oct 14 16:19:02 2022 +0300 update eslintrc commit 78ebc7f3b01913c439010119ae831b4178c89da0 Author: Slava Leleka Date: Fri Oct 14 16:17:39 2022 +0300 install eslint-plugin-jsdoc, update eslint commit b8b7e8a8678e9b0cd7a02e8cdea3908c18f87c75 Author: Slava Leleka Date: Fri Oct 14 12:13:08 2022 +0300 AG-16951 allow has/is/where inside has Squashed commit of the following: commit 9752adcfcc7f47ed8c4648ebcbaf35d2bcd587e1 Author: Slava Leleka Date: Thu Oct 13 17:36:28 2022 +0300 add has(has) tests commit 96d0cfdd9f6ba202617475432b35a65f295606c1 Author: Slava Leleka Date: Thu Oct 13 16:30:37 2022 +0300 improve readme about has pseudo-class commit 53e16449dfdfafe9550e9c1016f4e47f5a360e26 Author: Slava Leleka Date: Thu Oct 13 16:29:43 2022 +0300 allow has/is/where inside has commit 1f14f0c826f1275642d8a3c3c057c307d5d9c107 Merge: 4d9e119 84db5bf Author: Slava Leleka Date: Thu Oct 13 11:46:01 2022 +0300 Merge branch 'master' into epic/AG-3532 commit 4d9e119c7fe9a90047de889ec2982e596c1a83df Author: Slava Leleka Date: Wed Oct 12 17:28:32 2022 +0300 fix ThrottleWrapper (former AsyncWrapper) Squashed commit of the following: commit 05270fb58665ae9c7fbd03b9effd227f4c566d92 Author: Slava Leleka Date: Tue Oct 11 16:08:40 2022 +0300 fix comment for throttleDelayMs commit 8fb1a295313083aa5ffc0d740bf723aa3f77996e Merge: 5bb1b4d b2359c3 Author: Slava Leleka Date: Tue Oct 11 16:03:18 2022 +0300 Merge branch 'epic/AG-3532' into fix/AG-16841 commit 5bb1b4da9ad17fd7761fc6bd373ba83847ddae99 Merge: 2cd4f82 4bd79b2 Author: Slava Leleka Date: Tue Oct 11 15:33:13 2022 +0300 Merge branch 'epic/AG-3532' into fix/AG-16841 commit 2cd4f82e59a0989a140ceb775be9ed16ae11e4fa Merge: c084e6e e8286b1 Author: Slava Leleka Date: Tue Oct 11 12:14:00 2022 +0300 Merge branch 'epic/AG-3532' into fix/AG-16841 commit c084e6eeb33a78fad3a087b1f2c09f9292ffb495 Author: Slava Leleka Date: Mon Oct 10 19:07:53 2022 +0300 rename file commit 7ecfb7657d4848b77f3e3438f31ea04d64facb2a Author: Slava Leleka Date: Fri Oct 7 13:09:53 2022 +0300 add comment for ThrottleWrapper's private wrappedCallback commit abd4874a42376e51e9e9a30b26c34e4071fb43b8 Author: Slava Leleka Date: Thu Oct 6 19:31:30 2022 +0300 fix WrappedCallback types commit fc79a7e7947a9bec9757ee006dfaddaa8b1d5dff Author: Slava Leleka Date: Thu Oct 6 19:10:33 2022 +0300 clear ThrottleWrapper timeouts better commit 5f531e48dd0c47138265c612fe15a70601fe8b87 Author: Slava Leleka Date: Thu Oct 6 16:02:58 2022 +0300 rename AsyncWrapper -> ThrottleWrapper commit d31ed8f6de95a723e97d2a4ff0375fedfa03002d Author: Slava Leleka Date: Thu Oct 6 15:47:22 2022 +0300 fix comment for throttleDelayMs commit e70dca281dd00596cfe47469997856cacd93a87d Author: Slava Leleka Date: Thu Oct 6 15:43:46 2022 +0300 fix comment for throttleDelayMs commit 4998b8ce15acd95cb50a76aa553e2145ec0f6939 Author: Slava Leleka Date: Thu Oct 6 15:34:45 2022 +0300 rename timeout and timeoutId commit b2359c345f8c2444fe93497488cc4e8445203a77 Author: Slava Leleka Date: Tue Oct 11 16:02:50 2022 +0300 AG-16903 add more :has() tests Squashed commit of the following: commit 5e20807369957bf2c785d63f75348f85cb9ad755 Merge: 95ff39c 4bd79b2 Author: Slava Leleka Date: Tue Oct 11 15:32:59 2022 +0300 Merge branch 'epic/AG-3532' into fix/AG-16903 commit 95ff39cf483ec4b38d56c28fe847e1948ff1db4b Author: Slava Leleka Date: Tue Oct 11 14:46:48 2022 +0300 add few more query-jsdom tests for has pseudo-class commit 56d3eeb11533cff40aa4700c8757a82947d3f9e6 Merge: dc9c22a e8286b1 Author: Slava Leleka Date: Tue Oct 11 12:14:17 2022 +0300 Merge branch 'epic/AG-3532' into fix/AG-16903 commit dc9c22afecc80f88db2562246599124a4a1c6fae Author: Slava Leleka Date: Mon Oct 10 18:58:54 2022 +0300 add few more query-jsdom tests for has pseudo-class commit 4bd79b2ad2f4c4afdfff4dcb31c4254e9c093c90 Author: Slava Leleka Date: Tue Oct 11 15:30:19 2022 +0300 AG-16874 fix EventTracker Squashed commit of the following: commit 3ec9b65dd598735bf695db05ecedb0f82ea68b94 Author: Slava Leleka Date: Tue Oct 11 15:17:07 2022 +0300 no extra type definition for applyRulesCallbackListener commit 1ea678ea52366f63ee0de086de8d40dc58dc3cc4 Merge: 94dd26b e8286b1 Author: Slava Leleka Date: Tue Oct 11 12:06:58 2022 +0300 Merge branch 'epic/AG-3532' into fix/AG-16874 commit 94dd26b7f3be35088e4de6bbb76c17676c9d6112 Author: Slava Leleka Date: Mon Oct 10 15:46:14 2022 +0300 remove event listener while ExtendedCss.dispose() commit fa99b5c7d90c20b1606128adc712c966b35295fb Author: Slava Leleka Date: Mon Oct 10 15:38:02 2022 +0300 remove eventTracker listeners on main document observer disconnect commit 1ca59ee9795a4a2bea6a6aa9d6278eded5c31545 Author: Slava Leleka Date: Fri Oct 7 15:33:01 2022 +0300 remove unused private lastEvent commit e8286b10dfd9139fb1f8d6e44ee200114eb4f20e Author: Slava Leleka Date: Tue Oct 11 12:05:33 2022 +0300 AG-16871 improve readme Squashed commit of the following: commit 6162f41b80c1370ad7bb68f321eec429f62176c8 Author: Slava Leleka Date: Fri Oct 7 15:28:04 2022 +0300 fix description commit 453949cfe02f42d57cfc8fb74e72689d31bd870f Author: Slava Leleka Date: Fri Oct 7 14:34:21 2022 +0300 add few badges to readme commit bb8a9168e47e006afec74803f126c32bd575c3c4 Author: Slava Leleka Date: Fri Oct 7 14:29:30 2022 +0300 improve lib description commit 214f4b80b612406d9d1f197c59948a6b353cb92d Author: Slava Leleka Date: Thu Oct 6 13:44:08 2022 +0300 check isNaN in isNumber commit 47c5f51d8279a08decb6049d06505cbde2995275 Author: Slava Leleka Date: Thu Oct 6 13:40:03 2022 +0300 fix tsconfig.json tab commit 0b96076b16b19f549e0ee120ad71a174c1a8e431 Author: Slava Leleka Date: Thu Oct 6 13:39:00 2022 +0300 AG-16833 remove private mainCallback Squashed commit of the following: commit 24f00fabaf37a9a6cf29da32a1d33df199571892 Author: Slava Leleka Date: Thu Oct 6 13:29:13 2022 +0300 remove private mainCallback commit 36433d9560a8a998a37381325088b1759b865844 Author: Slava Leleka Date: Thu Oct 6 13:10:21 2022 +0300 AG-16475 parse complex selector with extended pseudo-class inside Squashed commit of the following: commit f6d2aecf144c3daf50688f7408fccf4243d13c24 Author: Slava Leleka Date: Wed Oct 5 21:39:02 2022 +0300 fix typo: inside of it -> inside it commit a9a211f32fa23275becf4ac4ea34566b491099b7 Author: Slava Leleka Date: Wed Oct 5 21:37:02 2022 +0300 fix comment for square bracket left commit b97f90e77138763b067bbe2f1472db3daad2fb15 Author: Slava Leleka Date: Wed Oct 5 21:34:14 2022 +0300 parse standard pseudo-class with brackets after extended one in single complex selector commit 807f1c4998f6e09086a760d084890868989ec649 Merge: 0e8d5c3 92d6b8b Author: Slava Leleka Date: Wed Oct 5 14:08:47 2022 +0300 Merge branch 'epic/AG-3532' into fix/AG-16475 commit 0e8d5c377e40f7cbd7c62e6cab8160087493016d Author: Slava Leleka Date: Tue Oct 4 15:29:20 2022 +0300 add few more query-jsdom tests for complex selectors commit a2872da1456580dfc506ecdd360184ab70ed6462 Author: Slava Leleka Date: Tue Oct 4 15:26:17 2022 +0300 add query-jsdom tests for complex selectors commit 189a08c2d324fe8d09f7eef91fcb945f281f2577 Author: Slava Leleka Date: Tue Oct 4 15:14:25 2022 +0300 parse complext selector with standard pseudo after extended one commit 02607e20ef29e6c2c670b962fda7b2b7eda02f0f Author: Slava Leleka Date: Mon Oct 3 20:07:58 2022 +0300 add one more xpath limitation to readme commit 8e6a50770a918a0aaf67863f20ace32266f1552c Merge: e990fc8 1e77dac Author: Slava Leleka Date: Mon Oct 3 20:04:34 2022 +0300 Merge branch 'epic/AG-3532' into fix/AG-16475 commit e990fc83fde9bc5a022ff9a6fcd002087bd6f4c0 Author: Slava Leleka Date: Mon Oct 3 20:04:17 2022 +0300 fix xpath parsing commit e9af913aa4d07b3e21bb9d2c7ff9a262e959bc7b Author: Slava Leleka Date: Mon Oct 3 19:59:28 2022 +0300 add more complex selector tests commit 6a31811f72e5fd79c8cb24361c363061a6c6e88c Author: Slava Leleka Date: Mon Oct 3 19:57:31 2022 +0300 parse complex selector with extended pseudo-class inside of it commit 92d6b8b92c8ae5907b5070bcfcc12b9e7c67bdac Author: Slava Leleka Date: Wed Oct 5 14:07:58 2022 +0300 AG-16783 add few not(has(not)) tests Squashed commit of the following: commit c38f3a00df786c19e03f30ebf734539bea60349f Author: Slava Leleka Date: Tue Oct 4 17:05:54 2022 +0300 add few not(has(not)) tests commit 1e77dac2a66bb09ba9b001e9712f3c332099f2a3 Author: Slava Leleka Date: Mon Oct 3 19:40:36 2022 +0300 AG-16255 fix comments, add limitations to readme Squashed commit of the following: commit 6606a52c0c0be6f7a6b18f7dd896431c7b7de1ca Author: Slava Leleka Date: Thu Sep 29 14:12:49 2022 +0300 add remove limitations to readme commit 4374b850102e85d2ab6479d0437a3f88461d7af1 Author: Slava Leleka Date: Thu Sep 29 13:19:47 2022 +0300 fix grammar commit b50e2d55b5993a4db9d0cd0d67630a336d2027a1 Author: Slava Leleka Date: Thu Sep 29 13:18:07 2022 +0300 remove todo comment about limitations commit 9cb1e6b0f38194f8a9ef800621187ac5ee8638cb Author: Slava Leleka Date: Wed Sep 28 20:08:31 2022 +0300 fix few comments commit 1990c66480444a99fa6eb15ddc9faca141c07a97 Author: Slava Leleka Date: Wed Sep 28 20:07:55 2022 +0300 add Limitations to readme commit 487b933a2e5330642e0b7f7d41f7f7a1c4428b46 Author: Slava Leleka Date: Wed Sep 28 19:08:19 2022 +0300 fix comments for test/helpers/selector-parser.ts commit 3d5d47f8896019fea64c8e0b1f7264da49d74105 Author: Slava Leleka Date: Wed Sep 28 18:53:08 2022 +0300 style multiple comments better commit 6676638adc8176ad5e712bcf31b44f2bbf218a5a Author: Slava Leleka Date: Wed Sep 28 18:49:55 2022 +0300 add better comments for stylesheet parser commit 1c3c81ba6c4eeb7f3c998f15809e64690c3b5bf9 Author: Slava Leleka Date: Wed Sep 28 18:18:08 2022 +0300 fix comments for class ExtendedCss commit 90c6e9610e2ccdbc5256730bf347337a112bbd09 Author: Slava Leleka Date: Wed Sep 28 16:52:44 2022 +0300 fix comment for ExtCssConfiguration.debug commit cd06038eb17e9d9e5e6c3fe2e8810615d00eda8f Author: Slava Leleka Date: Wed Sep 28 16:48:24 2022 +0300 fix comment for ExtCssConfiguration.styleSheet commit 57011dedead0925a37448075a894a4a67e3e3489 Author: Slava Leleka Date: Wed Sep 28 16:40:05 2022 +0300 add comment for ExtendedCss Context commit 883b06048a9ce77292286ff7e1c64326771fc851 Author: Slava Leleka Date: Wed Sep 28 16:37:55 2022 +0300 fix comments for ExtCssConfiguration commit 2aced3365887c8708e121c79305619c30a461517 Author: Slava Leleka Date: Wed Sep 28 16:18:29 2022 +0300 fix comments for RelativePredicateArgsInterface commit 2a3420b16a0e273cefbd36edd89315ba893a6b5b Author: Slava Leleka Date: Thu Sep 15 19:15:35 2022 +0300 revert test specs commit 261f5a2326b3d760ff0cd159ae6d5469a194c331 Merge: 8234be6 ad9022e Author: Slava Leleka Date: Thu Sep 15 19:15:15 2022 +0300 merge master into epic, resolve conflicts ... and 78 more commits --- .eslintignore | 11 +- .eslintrc | 91 - .eslintrc.js | 83 + .github/workflows/workflow.yaml | 4 +- .gitignore | 3 +- .husky/pre-commit | 4 + README.md | 811 +- babel.config.js | 20 +- dist/extended-css.cjs.js | 5292 ------------- dist/extended-css.d.ts | 39 - dist/extended-css.esm.js | 5290 ------------- dist/extended-css.min.js | 5 - index.js | 3 - jest.config.ts | 14 + lib/attributes-matcher.js | 105 - lib/css-utils.js | 97 - lib/element-property-matcher.js | 231 - lib/extended-css-parser.js | 213 - lib/extended-css-selector.js | 741 -- lib/extended-css.js | 485 -- lib/is-any-matcher.js | 83 - lib/matcher-utils.js | 158 - lib/sizzle.patched.js | 2507 ------- lib/style-property-matcher.js | 170 - lib/utils.js | 509 -- package.json | 102 +- src/common/constants.ts | 220 + src/common/utils/arrays.ts | 40 + src/common/utils/logger.ts | 21 + src/common/utils/natives.ts | 20 + src/common/utils/nodes.ts | 77 + src/common/utils/numbers.ts | 9 + src/common/utils/objects.ts | 19 + src/common/utils/strings.ts | 98 + src/common/utils/user-agents.ts | 149 + src/extended-css/extended-css.ts | 219 + src/extended-css/helpers/document-observer.ts | 70 + src/extended-css/helpers/event-tracker.ts | 84 + src/extended-css/helpers/mutation-observer.ts | 64 + src/extended-css/helpers/rules-applier.ts | 120 + src/extended-css/helpers/style-protector.ts | 59 + src/extended-css/helpers/style-setter.ts | 113 + src/extended-css/helpers/throttle-wrapper.ts | 121 + src/extended-css/helpers/timing-stats.ts | 140 + src/extended-css/helpers/types.ts | 117 + src/extended-css/index.ts | 4 + src/index.ts | 2 + src/selector/converter.ts | 75 + src/selector/index.ts | 11 + src/selector/nodes.ts | 182 + src/selector/parser.ts | 959 +++ src/selector/query.ts | 101 + src/selector/tokenizer.ts | 47 + src/selector/utils/absolute-finder.ts | 54 + src/selector/utils/absolute-matcher.ts | 657 ++ src/selector/utils/absolute-processor.ts | 185 + src/selector/utils/query-helpers.ts | 417 ++ src/stylesheet/index.ts | 2 + src/stylesheet/parser.ts | 580 ++ tasks/browserstack.js | 19 - tasks/browserstack.json | 34 - tasks/compile.js | 93 - tasks/config.js | 7 - tasks/server.js | 43 - tasks/tests.js | 138 - .../browserstack.html} | 31 +- test/browserstack/browserstack.test.ts | 160 + test/browserstack/config.ts | 45 + test/browserstack/index.ts | 33 + test/css-parser/css-parser.html | 18 - test/css-parser/css-parser.test.js | 118 - test/dist/dist.test.js | 104 - test/extended-css.test.ts | 709 ++ test/extended-css/extended-css.html | 153 - test/extended-css/extended-css.test.js | 441 -- test/global-scope.test.ts | 48 + test/helpers/performance-checker.ts | 86 + test/helpers/selector-parser.ts | 241 + test/helpers/selector-query-jsdom.ts | 79 + test/helpers/server.ts | 86 + test/helpers/xpath-evaluate-counter.ts | 19 + test/index.html | 22 - test/index.js | 10 - test/performance-selector/jest.config.ts | 13 + .../performance-selector.test.ts | 220 + test/performance-xpath-evaluate.test.ts | 57 + test/performance/performance.html | 110 - test/performance/performance.test.js | 131 - test/qunit/qunit-2.10.0.css | 447 -- test/qunit/qunit-2.10.0.js | 6643 ----------------- test/selector/converter.test.ts | 187 + test/selector/parser.test.ts | 2578 +++++++ test/selector/query-jsdom.test.ts | 1796 +++++ test/selector/query-playwright.test.ts | 221 + test/selector/selector.html | 267 - test/selector/selector.test.js | 832 --- test/selector/tokenizer.test.ts | 102 + test/selector/utils/absolute-matcher.test.ts | 107 + test/stylesheet/parser.test.ts | 764 ++ test/test-files/empty.html | 10 + .../test-files/extCssV1.js | 41 +- .../performance-selector-results.txt | 98 + test/test-files/performance.html | 92 + test/utils/utils.html | 19 - test/utils/utils.test.js | 119 - tools/build.ts | 81 + tools/constants.ts | 23 + tools/rollup-commons.ts | 21 + tools/rollup-runner.ts | 46 + tools/test.ts | 289 + tools/utils.ts | 14 + tsconfig.eslint.json | 8 + tsconfig.json | 32 + types/extended-css.d.ts | 39 - yarn.lock | 6034 ++++++++++----- 115 files changed, 18262 insertions(+), 28023 deletions(-) delete mode 100644 .eslintrc create mode 100644 .eslintrc.js create mode 100755 .husky/pre-commit delete mode 100644 dist/extended-css.cjs.js delete mode 100644 dist/extended-css.d.ts delete mode 100644 dist/extended-css.esm.js delete mode 100644 dist/extended-css.min.js delete mode 100644 index.js create mode 100644 jest.config.ts delete mode 100644 lib/attributes-matcher.js delete mode 100644 lib/css-utils.js delete mode 100644 lib/element-property-matcher.js delete mode 100644 lib/extended-css-parser.js delete mode 100644 lib/extended-css-selector.js delete mode 100644 lib/extended-css.js delete mode 100644 lib/is-any-matcher.js delete mode 100644 lib/matcher-utils.js delete mode 100644 lib/sizzle.patched.js delete mode 100644 lib/style-property-matcher.js delete mode 100644 lib/utils.js create mode 100644 src/common/constants.ts create mode 100644 src/common/utils/arrays.ts create mode 100644 src/common/utils/logger.ts create mode 100644 src/common/utils/natives.ts create mode 100644 src/common/utils/nodes.ts create mode 100644 src/common/utils/numbers.ts create mode 100644 src/common/utils/objects.ts create mode 100644 src/common/utils/strings.ts create mode 100644 src/common/utils/user-agents.ts create mode 100644 src/extended-css/extended-css.ts create mode 100644 src/extended-css/helpers/document-observer.ts create mode 100644 src/extended-css/helpers/event-tracker.ts create mode 100644 src/extended-css/helpers/mutation-observer.ts create mode 100644 src/extended-css/helpers/rules-applier.ts create mode 100644 src/extended-css/helpers/style-protector.ts create mode 100644 src/extended-css/helpers/style-setter.ts create mode 100644 src/extended-css/helpers/throttle-wrapper.ts create mode 100644 src/extended-css/helpers/timing-stats.ts create mode 100644 src/extended-css/helpers/types.ts create mode 100644 src/extended-css/index.ts create mode 100644 src/index.ts create mode 100644 src/selector/converter.ts create mode 100644 src/selector/index.ts create mode 100644 src/selector/nodes.ts create mode 100644 src/selector/parser.ts create mode 100644 src/selector/query.ts create mode 100644 src/selector/tokenizer.ts create mode 100644 src/selector/utils/absolute-finder.ts create mode 100644 src/selector/utils/absolute-matcher.ts create mode 100644 src/selector/utils/absolute-processor.ts create mode 100644 src/selector/utils/query-helpers.ts create mode 100644 src/stylesheet/index.ts create mode 100644 src/stylesheet/parser.ts delete mode 100644 tasks/browserstack.js delete mode 100644 tasks/browserstack.json delete mode 100644 tasks/compile.js delete mode 100644 tasks/config.js delete mode 100644 tasks/server.js delete mode 100644 tasks/tests.js rename test/{dist/dist.html => browserstack/browserstack.html} (65%) create mode 100644 test/browserstack/browserstack.test.ts create mode 100644 test/browserstack/config.ts create mode 100644 test/browserstack/index.ts delete mode 100644 test/css-parser/css-parser.html delete mode 100644 test/css-parser/css-parser.test.js delete mode 100644 test/dist/dist.test.js create mode 100644 test/extended-css.test.ts delete mode 100644 test/extended-css/extended-css.html delete mode 100644 test/extended-css/extended-css.test.js create mode 100644 test/global-scope.test.ts create mode 100644 test/helpers/performance-checker.ts create mode 100644 test/helpers/selector-parser.ts create mode 100644 test/helpers/selector-query-jsdom.ts create mode 100644 test/helpers/server.ts create mode 100644 test/helpers/xpath-evaluate-counter.ts delete mode 100644 test/index.html delete mode 100644 test/index.js create mode 100644 test/performance-selector/jest.config.ts create mode 100644 test/performance-selector/performance-selector.test.ts create mode 100644 test/performance-xpath-evaluate.test.ts delete mode 100644 test/performance/performance.html delete mode 100644 test/performance/performance.test.js delete mode 100644 test/qunit/qunit-2.10.0.css delete mode 100644 test/qunit/qunit-2.10.0.js create mode 100644 test/selector/converter.test.ts create mode 100644 test/selector/parser.test.ts create mode 100644 test/selector/query-jsdom.test.ts create mode 100644 test/selector/query-playwright.test.ts delete mode 100644 test/selector/selector.html delete mode 100644 test/selector/selector.test.js create mode 100644 test/selector/tokenizer.test.ts create mode 100644 test/selector/utils/absolute-matcher.test.ts create mode 100644 test/stylesheet/parser.test.ts create mode 100644 test/test-files/empty.html rename dist/extended-css.js => test/test-files/extCssV1.js (99%) create mode 100644 test/test-files/performance-selector-results.txt create mode 100644 test/test-files/performance.html delete mode 100644 test/utils/utils.html delete mode 100644 test/utils/utils.test.js create mode 100644 tools/build.ts create mode 100644 tools/constants.ts create mode 100644 tools/rollup-commons.ts create mode 100644 tools/rollup-runner.ts create mode 100644 tools/test.ts create mode 100644 tools/utils.ts create mode 100644 tsconfig.eslint.json create mode 100644 tsconfig.json delete mode 100644 types/extended-css.d.ts diff --git a/.eslintignore b/.eslintignore index 4e4973a5..fa76e979 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,6 +1,5 @@ -node_modules/* -build/* -dist/* -lib/sizzle.patched.js -test/qunit/* -test/build/* +node_modules +dist +.eslintrc.js +babel.config.js +test/test-files diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 3dbc9204..00000000 --- a/.eslintrc +++ /dev/null @@ -1,91 +0,0 @@ -{ - "env": { - "browser": true, - "node": true - }, - "extends": "airbnb-base", - "globals": { - "QUnit": true - }, - "rules": { - "import/no-extraneous-dependencies": 0, - "indent": [ - "error", - 4, - { - "SwitchCase": 1, - "VariableDeclarator": 1, - "outerIIFEBody": 1, - "FunctionDeclaration": { - "parameters": 1, - "body": 1 - }, - "FunctionExpression": { - "parameters": 1, - "body": 1 - }, - "CallExpression": { - "arguments": 1 - }, - "ArrayExpression": 1, - "ObjectExpression": 1, - "ImportDeclaration": 1, - "flatTernaryExpressions": false, - "ignoredNodes": [ - "JSXElement", - "JSXElement > *", - "JSXAttribute", - "JSXIdentifier", - "JSXNamespacedName", - "JSXMemberExpression", - "JSXSpreadAttribute", - "JSXExpressionContainer", - "JSXOpeningElement", - "JSXClosingElement", - "JSXText", - "JSXEmptyExpression", - "JSXSpreadChild" - ], - "ignoreComments": false - } - ], - "comma-dangle": [ - "error", - { - "arrays": "always-multiline", - "objects": "always-multiline", - "imports": "always-multiline", - "exports": "always", - "functions": "never" - } - ], - "no-useless-escape": "off", - "no-param-reassign": "off", - "wrap-iife": "off", - "func-names": "off", - "no-shadow": "off", - "no-multi-spaces": [ - "error", - { - "ignoreEOLComments": true - } - ], - // Prefer destructuring from arrays and objects - // https://eslint.org/docs/rules/prefer-destructuring - "prefer-destructuring": ["warn", {"object": true, "array": false}], - "consistent-return": "off", - "no-prototype-builtins": "off", - "dot-notation": "off", - "quote-props": "off", - "no-continue": "off", - "strict": "off", - "no-bitwise": "off", - "no-underscore-dangle": "off", - "no-plusplus": "off", - "no-restricted-syntax": "warn", - "no-use-before-define": "off", - "max-len": ["error", { - "code": 130 - }] - } -} diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..a3abb2db --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,83 @@ +const path = require('path'); + +module.exports = { + root: true, + parser: '@typescript-eslint/parser', + parserOptions: { + tsconfigRootDir: path.join(__dirname), + project: 'tsconfig.eslint.json', + }, + plugins: [ + 'import', + '@typescript-eslint', + ], + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/eslint-recommended', + 'plugin:@typescript-eslint/recommended', + 'airbnb-typescript/base', + 'plugin:jsdoc/recommended', + ], + rules: { + 'indent': ['error', 4, { + 'SwitchCase': 1, + }], + '@typescript-eslint/indent': ['error', 4], + 'no-bitwise': 'off', + 'no-new': 'off', + 'max-len': ['error', { + 'code': 120, + 'comments': 120, + 'tabWidth': 4, + 'ignoreUrls': false, + 'ignoreTrailingComments': false, + 'ignoreComments': false + }], + 'import/prefer-default-export': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + 'no-continue': 'off', + 'import/no-extraneous-dependencies': ['error', { 'devDependencies': true }], + 'no-restricted-syntax': ['error', 'LabeledStatement', 'WithStatement'], + 'no-constant-condition': ['error', { 'checkLoops': false }], + '@typescript-eslint/interface-name-prefix': 'off', + 'arrow-body-style': 'off', + 'jsdoc/require-jsdoc': [ + 'error', + { + contexts: [ + 'ClassDeclaration', + 'ClassProperty', + 'FunctionDeclaration', + 'MethodDefinition', + ], + }, + ], + 'jsdoc/require-description': [ + 'error', + { + contexts: [ + 'ClassDeclaration', + 'ClassProperty', + 'FunctionDeclaration', + 'MethodDefinition', + ], + }, + ], + 'jsdoc/require-description-complete-sentence': [ + 'error', + { + abbreviations: ['e.g.', 'i.e.'], + }, + ], + 'jsdoc/require-throws': 'error', + 'jsdoc/tag-lines': 'off', + // disabled as types are described in typescript + 'jsdoc/require-param-type': 'off', + 'jsdoc/require-returns': 'off', + 'jsdoc/require-returns-type': 'off', + // for jest tests + 'jsdoc/check-tag-names': ['error', { + 'definedTags': ['jest-environment'], + }], + }, +}; diff --git a/.github/workflows/workflow.yaml b/.github/workflows/workflow.yaml index 5552cfd5..bb061b17 100644 --- a/.github/workflows/workflow.yaml +++ b/.github/workflows/workflow.yaml @@ -29,8 +29,8 @@ jobs: - name: yarn build run: yarn build - - name: yarn test - run: yarn test + - name: yarn test local + run: yarn test local - name: deploy uses: peaceiris/actions-gh-pages@v3 diff --git a/.gitignore b/.gitignore index b4d9cef1..9eb0f823 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,6 @@ .vscode .env node_modules -build +yarn-error.log +dist *.tgz diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 00000000..07f28d8c --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname "$0")/_/husky.sh" + +yarn lint-staged diff --git a/README.md b/README.md index 114de8da..12fc7cf2 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,11 @@ -# Extended Css engine +# ExtendedCss ![npm](https://img.shields.io/npm/v/@adguard/extended-css) [![install size](https://packagephobia.com/badge?p=@adguard/extended-css)](https://packagephobia.com/result?p=@adguard/extended-css) ![GitHub](https://img.shields.io/github/license/AdGuardTeam/ExtendedCss) -Module for applying CSS styles with extended selection properties. +AdGuard's TypeScript library for non-standard element selecting and applying CSS styles with extended properties. + +The idea of extended capabilities is an opportunity to match DOM elements with selectors based on their own representation (style, text content, etc.) or relations with other elements. There is also an opportunity to apply styles with non-standard CSS properties. * [Extended capabilities](#extended-capabilities) + * [Limitations](#extended-css-limitations) * [Pseudo-class :has()](#extended-css-has) * [Pseudo-class :if-not()](#extended-css-if-not) * [Pseudo-class :contains()](#extended-css-contains) @@ -14,253 +17,282 @@ Module for applying CSS styles with extended selection properties. * [Pseudo-class :upward()](#extended-css-upward) * [Pseudo-class :remove() and pseudo-property `remove`](#remove-pseudos) * [Pseudo-class :is()](#extended-css-is) + * [Pseudo-class :not()](#extended-css-not) * [Selectors debug mode](#selectors-debug-mode) + * [Backward compatible syntax](#extended-css-old-syntax) +* [How to build](#how-to-build) +* [How to test](#how-to-test) * [Usage](#usage) -* [Debugging extended selectors](#debugging-extended-selectors) -* [Projects using Extended Css](#projects-using-extended-css) -* [Test page](#test-page) + * [API description](#extended-css-api) + * [Constructor](#extended-css-constructor) + * [apply() and dispose()](#extended-css-apply-dispose) + * [query()](#extended-css-query) + * [validate()](#extended-css-validate) + * [Debugging extended selectors](#debugging-extended-selectors) +* [Projects using ExtendedCss](#projects-using-extended-css) +* [Browser compatibility](#browser-compatibility) + ## Extended capabilities - -### Pseudo-class `:has()` +> Some pseudo-classes does not require selector before it. Still adding a [universal selector](https://www.w3.org/TR/selectors-4/#the-universal-selector) `*` makes an extended selector easier to read, even though it has no effect on the matching behavior. So selector `#block :has(> .inner)` works exactly like `#block *:has(> .inner)` but second one is more obvious. + +> Pseudo-class names are case-insensitive, e.g. `:HAS()` will work as `:has()`. + +### Limitations + +1. CSS [comments](https://developer.mozilla.org/en-US/docs/Web/CSS/Comments) and [at-rules](https://developer.mozilla.org/en-US/docs/Web/CSS/At-rule) are not supported. + +2. Specific pseudo-class might have its own limitations: +[`:has()`](#extended-css-has-limitations), [`:xpath()`](#extended-css-xpath-limitations), [`:is()`](#extended-css-is-limitations), [`:not()`](#extended-css-not-limitations), and [`:remove()`](#extended-css-remove-limitations). + + +### Pseudo-class `:has()` -Draft CSS 4.0 specification describes [pseudo-class `:has`](https://drafts.csswg.org/selectors/#relational). Unfortunately, it is not yet supported by browsers. +Draft CSS 4.0 specification describes [pseudo-class `:has`](https://www.w3.org/TR/selectors-4/#relational). Unfortunately, it is not yet [supported by all popular browsers](https://caniuse.com/css-has). + +> Rules with `:has()` pseudo-class should use [native implementation of `:has()`]() if rules use `##` marker and it is possible, i.e. with no other extended pseudo-classes inside. To force ExtendedCss applying of rules with `:has()`, use `#?#`/`#$?#` marker obviously. + +> Synonyms `:-abp-has` and `:if` are supported by ExtendedCss for better compatibility. **Syntax** -``` -:has(selector) -``` -Backward compatible syntax: ``` -[-ext-has="selector"] +[target]:has(selector) ``` +- `target` — optional, standard or extended css selector, can be missed for checking *any* element +- `selector` — required, standard or extended css selector -Supported synonyms for better compatibility: `:-abp-has`, `:if`. +Pseudo-class `:has()` selects the `target` elements that includes the elements that fit to the `selector`. Also `selector` can start with a combinator. Selector list can be set in `selector` as well. -Pseudo-class `:has()` selects the elements that includes the elements that fit to `selector`. + **Limitations and notes** -**Examples** +> Usage of `:has()` pseudo-class is [restricted for some cases (2, 3)](https://bugs.chromium.org/p/chromium/issues/detail?id=669058#c54): +> - disallow `:has()` inside the pseudos accepting only compound selectors; +> - disallow `:has()` after regular pseudo-elements. + +> Native `:has()` pseudo-class does not allow `:has()`, `:is()`, `:where()` inside `:has()` argument to avoid increasing the `:has()` invalidation complexity ([case 1](https://bugs.chromium.org/p/chromium/issues/detail?id=669058#c54)). But ExtendedCss did not have such limitation earlier and filter lists already contain such rules, so we will not add this limitation in ExtendedCss and allow to use `:has()` inside `:has()` as it was possible before. To use it, just force ExtendedCss usage by setting `#?#`/`#$?#` rule marker. -Selecting all `div` elements, which contain an element with the `banner` class: +> Native implementation does not allow any usage of `:scope` inside `:has()` argument ([[1]](https://github.com/w3c/csswg-drafts/issues/7211), [[2]](https://github.com/w3c/csswg-drafts/issues/6399)). Still there some such rules in filter lists: `div:has(:scope > a)` which we will continue to support simply converting them to `div:has(> a)` as it was earlier. + +**Examples** +`div:has(.banner)` will select all `div` elements, which **includes** an element with the `banner` class: ```html -
Do not select this div
-
Select this div
+
Not selected
+
Selected + +
``` -Selector: +`div:has(> .banner)` will select all `div` elements, which **includes** an `banner` class element as a *direct child* of `div`: +```html + +
Not selected
+
Selected + +
``` -div:has(.banner) + +`div:has(+ .banner)` will select all `div` elements **preceding** `banner` class element which *immediately follows* the `div` and both are children of the same parent: +```html + +
Not selected
+
Selected
+ +Not selected ``` -Backward compatible syntax: +`div:has(~ .banner)` will select all `div` elements **preceding** `banner` class element which *follows* the `div` but *not necessarily immediately* and both are children of the same parent: +```html + +
Not selected
+
Selected
+Not selected + ``` -div[-ext-has=".banner"] + +`div:has(span, .banner)` will select all `div` elements, which **includes both** `span` element and `banner` class element: +```html + +
Not selected
+
Selected + child span + +
``` - -### Pseudo-class `:if-not()` +> [Backward compatible syntax for `:has()`](#old-syntax-has) is supported but not recommended. -This pseudo-class is basically a shortcut for `:not(:has())`. It is supported by ExtendedCss for better compatibility with some filters subscriptions, but it is not recommended to use it in AdGuard filters. The rationale is that one day browsers will add `:has` native support, but it will never happen to this pseudo-class. - -### Pseudo-class `:contains()` +### Pseudo-class `:if-not()` -This pseudo-class principle is very simple: it allows to select the elements that contain specified text or which content matches a specified regular expression. Regex flags are supported. Please note, that this pseudo-class uses `textContent` element property for matching (and not the `innerHTML`). +Pseudo-class `:if-not()` is basically a shortcut for `:not(:has())`. It is supported by ExtendedCss for better compatibility with some other filter lists. -**Syntax** -``` -// matching by plain text -:contains(text) +> `:if-not()` is not recommended to use in AdGuard filters. The reason is that one day browsers will add `:has` native support, but it will never happen to this pseudo-class. -// matching by a regular expression -:contains(/regex/i) -``` +### Pseudo-class `:contains()` -Backward compatible syntax: -``` -// matching by plain text -[-ext-contains="text"] +This pseudo-class principle is very simple: it allows to select the elements that contain specified text or which content matches a specified regular expression. Regexp flags are supported. -// matching by a regular expression -[-ext-contains="/regex/"] +> Pseudo-class `:contains()` uses the `textContent` element property for matching, not the `innerHTML`. + +> Synonyms `:-abp-contains` and `:has-text` are supported for better compatibility. + +**Syntax** + +``` +[target]:contains(match) ``` +- `target` — optional, standard or extended css selector, can be missed for checking *any* element +- `match` — required, string or regular expression for matching element textContent -> Supported synonyms for better compatibility: `:-abp-contains`, `:has-text`. +> Regexp flags are supported for `match`. **Examples** -Selecting all `div` elements, which contain text `banner`: +For such DOM: ```html -
Do not select this div
-
Select this div (banner)
-
Do not select this div
+
Not selected
+
Selected as IT contains "banner"
+
Not selected
``` -Selector: +`div#match` can be selected by any on these extended selectors: ``` -// matching by plain text +! plain text div:contains(banner) -// matching by a regular expression -div:contains(/this .* banner/) +! regular expression +div:contains(/as .* banner/) -// also with regex flags -div:contains(/this .* banner/gi) +! regular expression with flags +div:contains(/it .* banner/gi) ``` -Backward compatible syntax: -``` -// matching by plain text -div[-ext-contains="banner"] +> Only a `div` with `id=match` will be selected because the next element does not contain any text, and `banner` is a part of code, not a text. -// matching by a regular expression -div[-ext-contains="/this .* banner/"] -``` +> [Backward compatible syntax for `:contains()`](#old-syntax-contains) is supported but not recommended. -> Please note that in this example only a `div` with `id=selected` will be selected, because the next element does not contain any text; `banner` is a part of code, not a text. - -### Pseudo-class `:matches-css()` +### Pseudo-class `:matches-css()` -These pseudo-classes allow to select an element by its current style property. The work of this pseudo-class is based on using the [`window.getComputedStyle`](https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle) function. +Pseudo-class `:matches-css()` allows to match the element by its current style properties. The work of the pseudo-class is based on using the [`Window.getComputedStyle()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle) method. **Syntax** -``` -/* element style matching */ -selector:matches-css(property-name ":" pattern) - -/* ::before pseudo-element style matching */ -selector:matches-css-before(property-name ":" pattern) - -/* ::after pseudo-element style matching */ -selector:matches-css-after(property-name ":" pattern) -``` -Backward compatible syntax: ``` -selector[-ext-matches-css="property-name ":" pattern"] -selector[-ext-matches-css-after="property-name ":" pattern"] -selector[-ext-matches-css-before="property-name ":" pattern"] +[target]:matches-css([pseudo-element, ] property: pattern) ``` +- `target` — optional, standard or extended css selector, can be missed for checking *any* element +- `pseudo-element` — optional, valid standard pseudo-element, e.g. `before`, `after`, `first-line`, etc. +- `property` — required, a name of CSS property to check the element for +- `pattern` — required, a value pattern that is using the same simple wildcard matching as in the basic url filtering rules OR a regular expression. For this type of matching, AdGuard always does matching in a case insensitive manner. In the case of a regular expression, the pattern looks like `/regexp/`. -- `property-name` — a name of CSS property to check the element for -- `pattern` — a value pattern that is using the same simple wildcard matching as in the basic url filtering rules OR a regular expression. For this type of matching, AdGuard always does matching in a case insensitive manner. In the case of a regular expression, the pattern looks like `/regex/`. +> For **non-regexp** patterns `(`,`)`,`[`,`]` must be **unescaped**, e.g. `:matches-css(background-image:url(data:*))`. -> For non-regex patterns, `(`,`)`,`[`,`]` must be unescaped, because we require escaping them in the filtering rules. For example, `:matches-css(background-image:url(data:*))`. +> For **regexp** patterns `\` should be **escaped**, e.g. `:matches-css(background-image: /^url\\("data:image\\/gif;base64.+/)`. -> For regex patterns, `"` and `\` should be escaped, because we manually escape those in extended-css-selector.js. For example: `:matches-css(background-image: /^url\\(\\"data\\:\\image.+/)`. + +> Regexp patterns does not support flags. **Examples** -Selecting all `div` elements which contain pseudo-class `::before` with specified content: +For such DOM: ```html - - +
+
``` -Selector: +`div` elements with pseudo-element `::before` with specified `content` property can be selected by any of these extended selectors: ``` -// Simple matching -div.banner:matches-css-before(content: block me) +! string pattern +div:matches-css(before, content: block me) -// Regular expressions -div.banner:matches-css-before(content: /block me/) -``` +! string pattern with wildcard +div:matches-css(before, content: block*) -Backward compatible syntax: +! regular expression pattern +div:matches-css(before, content: /block me/) ``` -// Simple matching -div.banner[-ext-matches-css-before="content: block me"] -// Regular expressions -div.banner[-ext-matches-css-before="content: /block me/"] -``` +> Obsolete pseudo-classes `:matches-css-before()` and `:matches-css-after()` are supported for better compatibility. + +> [Backward compatible syntax for `:matches-css()`](#old-syntax-matches-css) is supported but not recommended. + - -### Pseudo-class `:matches-attr()` +### Pseudo-class `:matches-attr()` -This pseudo-class allows to select an element by its attributes, especially if they are randomized. +Pseudo-class `:matches-attr()` allows to select an element by its attributes, especially if they are randomized. **Syntax** + ``` -selector:matches-attr("name"[="value"]) +[target]:matches-attr("name"[="value"]) ``` +- `target` — optional, standard or extended css selector, can be missed for checking *any* element +- `name` — required, simple string *or* string with wildcard *or* regular expression for attribute name matching +- `value` — optional, simple string *or* string with wildcard *or* regular expression for attribute value matching -- `name` — attribute name OR regular expression for attribute name -- `value` — optional, attribute value OR regular expression for attribute value - -> For regex patterns, `"` and `\` should be escaped. +> For **regexp** patterns `"` and `\` should be **escaped**, e.g. `div:matches-attr(class=/[\\w]{5}/)`. **Examples** +`div:matches-attr("ad-link")` will select `div#target1`: ```html -
- -
-
-
- -
-
- socials -
-
- ads -
-
- -
-
- -
-
+
``` +`div:matches-attr("data-*"="adBanner")` will select `div#target2`: +```html + +
``` -// for div#targer1 -div:matches-attr("ad-link") -// for div#targer2 -div:has(> div:matches-attr("/data-/"="adbanner")) - -// for div#targer3 -div:matches-attr("/-unit/"="/click/"):has(> span:contains(ads)) +`div:matches-attr(*unit*=/^click$/)` will select `div#target3`: +```html + +
+``` -// for div#targer4 -*[class]:matches-attr("/.{5,}delay$/"="/^[0-9]*$/"):upward(2) +`*:matches-attr("/.{5,}delay$/"="/^[0-9]*$/")` will select `#target4`: +```html + +
+ +
``` - -### Pseudo-class `:matches-property()` -This pseudo-class allows to select an element by its properties. +### Pseudo-class `:matches-property()` + +Pseudo-class `:matches-property()` allows to select an element by matching its properties. **Syntax** + ``` -selector:matches-property("name"[="value"]) +[target]:matches-property("name"[="value"]) ``` +- `target` — optional, standard or extended css selector, can be missed for checking *any* element +- `name` — required, simple string *or* string with wildcard *or* regular expression for element property name matching +- `value` — optional, simple string *or* string with wildcard *or* regular expression for element property value matching -- `name` — property name OR regular expression for property name -- `value` — optional, property value OR regular expression for property value - -> For regex patterns, `"` and `\` should be escaped. +> For **regexp** patterns `"` and `\` should be escaped, e.g. `div:matches-property(prop=/[\\w]{4}/)`. -> `name` supports regexp for property in chain, e.g. `prop./^unit[\\d]{4}$/.type` +> `name` supports regexp for property in chain, e.g. `prop./^unit[\\d]{4}$/.type`. **Examples** +Element with such properties: ```javascript divProperties = { id: 1, @@ -279,95 +311,111 @@ divProperties = { }; ``` +can be selected by any of these extended selectors: ``` -// element with such properties can be matched by any of such rules: - -div:matches-property("check.track") +div:matches-property(check.track) div:matches-property("check./^unit_.{4,6}$/") -div:matches-property("memoizedProps.key"="null") - -div:matches-property("memoizedProps._owner.src"="/ad/") -``` +div:matches-property("check.unit_*"=true) -
- For filters maintainers +div:matches-property(memoizedProps.key="null") - To check properties of specific element, do: - 1. Select the element on the page. - 2. Go to Console tab and run `console.dir($0)`. -
+div:matches-property(memoizedProps._owner.src=/ad/) +``` - -### Pseudo-class `:xpath()` +> **For filters maintainers:** To check properties of specific element, you should do: +> 1. Inspect the needed page element or select it in `Elements` tab of browser DevTools. +> 2. Run `console.dir($0)` in `Console` tab. -This pseudo-class allows to select an element by evaluating a XPath expression. -> **Limited to work properly only at the end of selector, except of [pseudo-class :remove()](#remove-pseudos).** -The :xpath(...) pseudo is different than other pseudo-classes. Whereas all other operators are used to filter down a resultset of elements, the :xpath(...) operator can be used both to create a new resultset or filter down an existing one. For this reason, subject selector is optional. For example, an :xpath(...) operator could be used to create a new resultset consisting of all ancestors elements of a subject element, something not otherwise possible with either plain CSS selectors or other procedural operators. +### Pseudo-class `:xpath()` -Normally, a pseudo-class is applied to nodes selected by a `selector`. However, :xpath is special as the selector can be ommited. For any other pseudo-class that would mean "apply to ALL DOM nodes", but in case of :xpath it just means "apply me to the document", and that significantly slows elements selecting. That's why we convert `#?#:xpath(...)` rules for looking inside the body tag. Rules like `#?#*:xpath(...)` can still be used but we highly recommend you avoid it and specify the `selector`. +Pseudo-class `:xpath()` allows to select an element by evaluating a XPath expression. **Syntax** + ``` -[selector]:xpath(expression) +[target]:xpath(expression) ``` +- `target`- optional, standard or extended css selector +- `expression` — required, valid XPath expression + + **Limitations** + +> `target` can be omitted so it is optional. For any other pseudo-class that would mean "apply to *all* DOM nodes", but in case of `:xpath()` it just means "apply to the *whole* document", and such applying slows elements selecting significantly. That's why rules like `#?#:xpath(expression)` are limited for looking inside the `body` tag. For example, rule `#?#:xpath(//div[@data-st-area=\'Advert\'])` is parsed as `#?#body:xpath(//div[@data-st-area=\'Advert\'])`. + +> Extended selectors with defined `target` as *any* selector — `*:xpath(expression)` — can still be used but it is not recommended, so `target` should be specified instead. -- `selector`- optional, a plain CSS selector, or a Sizzle compatible selector -- `expression` — a valid XPath expression +> Works properly only at the end of selector, except of [pseudo-class :remove()](#remove-pseudos). **Examples** + +`:xpath(//*[@class="banner"])` will select `div#target1`: +```html + + ``` -// Filtering results from selector -div:xpath(//*[@class="test-xpath-class"]) -div:has-text(/test-xpath-content/):xpath(../../..) -// Use xpath only to select elements -facebook.com##:xpath(//div[@id="stream_pagelet"]//div[starts-with(@id,"hyperfeed_story_id_")][.//h6//span/text()="People You May Know"]) +`:xpath(//*[@class="inner"]/..)` will select `div#target2`: +```html + +
+
+
``` - -### Pseudo-class `:nth-ancestor()` -This pseudo-class allows to lookup the nth ancestor relative to the currently selected node. -> **Limited to work properly only at the end of selector, except of [pseudo-class :remove()](#remove-pseudos).** +### Pseudo-class `:nth-ancestor()` -It is a low-overhead equivalent to `:xpath(..[/..]*)`. +Pseudo-class `:nth-ancestor()` allows to lookup the *nth* ancestor relative to the previously selected element. **Syntax** + ``` -selector:nth-ancestor(n) +subject:nth-ancestor(n) ``` -- `selector` — a plain CSS selector, or a Sizzle compatible selector. -- `n` — positive number >= 1 and < 256, distance from the currently selected node. +- `subject` — required, standard or extended css selector +- `n` — required, number >= 1 and < 256, distance to the needed ancestor from the element selected by `subject` **Examples** -``` -div.test:nth-ancestor(4) -div:has-text(/test/):nth-ancestor(2) +For such DOM: +```html + +
+
+ +
+
+
+
+
+
+
+
``` - -### Pseudo-class `:upward()` +`.child:nth-ancestor(1)` will select `div#target1` +`div[class="inner"]:nth-ancestor(3)` will select `div#target2` + -This pseudo-class allows to lookup the ancestor relative to the currently selected node. -> **Limited to work properly only at the end of selector, except of [pseudo-class :remove()](#remove-pseudos).** +### Pseudo-class `:upward()` + +Pseudo-class `:upward()` allows to lookup the ancestor relative to the previously selected element. **Syntax** -``` -/* selector parameter */ -subjectSelector:upward(targetSelector) -/* number parameter */ -subjectSelector:upward(n) ``` -- `subjectSelector` — a plain CSS selector, or a Sizzle compatible selector -- `targetSelector` — a valid plain CSS selector -- `n` — positive number >= 1 and < 256, distance from the currently selected node +subject:upward(ancestor) +``` +- `subject` — required, standard or extended css selector +- `ancestor` — required, specification for the ancestor of the element selected by `subject`, can be set as: + - *number* >= 1 and < 256 for distance to the needed ancestor, same as [`:nth-ancestor()`](#extended-css-nth-ancestor) + - *standard css selector* for matching closest ancestor **Examples** + ``` div.child:upward(div[id]) div:contains(test):upward(div[class^="parent-wrapper-") @@ -376,14 +424,35 @@ div.test:upward(4) div:has-text(/test/):upward(2) ``` - -### Pseudo-class `:remove()` and pseudo-property `remove` +For such DOM: +```html + +
+
+ +
+
+
+
+
+
+
+
+``` -Sometimes, it is necessary to remove a matching element instead of hiding it or applying custom styles. In order to do it, you can use pseudo-class `:remove()` as well as pseudo-property `remove`. +`.inner:upward(div[data])` will select `div#target1` +`.inner:upward(div[id])` will select `div#target2` -> **Pseudo-class `:remove()` is limited to work properly only at the end of selector.** +`.child:upward(1)` will select `div#target1` +`.inner:upward(3)` will select `div#target2` + + +### Pseudo-class `:remove()` and pseudo-property `remove` + +Sometimes, it is necessary to remove a matching element instead of hiding it or applying custom styles. In order to do it, you can use pseudo-class `:remove()` as well as pseudo-property `remove`. **Syntax** + ``` ! pseudo-class selector:remove() @@ -391,45 +460,94 @@ selector:remove() ! pseudo-property selector { remove: true; } ``` -- `selector` — a plain CSS selector, or a Sizzle compatible selector +- `selector` — required, standard or extended css selector + + **Limitations** + +> Pseudo-class `:remove()` is limited to work properly only at the end of selector. + +> For applying `:remove()` pseudo-class to any element [universal selector](https://www.w3.org/TR/selectors-4/#the-universal-selector) `*` should be used. Otherwise extended selector may be considered as invalid, e.g. `.banner > :remove()` is not valid for removing any child element of `banner` class element, so it should look like `.banner > *:remove()`. + +> If `:remove()` pseudo-class or `remove` pseudo-property is used, all style properties will be ignored except of [`debug` pseudo-property](#selectors-debug-mode). **Examples** + ``` -div.inner:remove() +div.banner:remove() div:has(> div[ad-attr]):remove() -div:xpath(../..):remove() -div:contains(target text) { remove: true; } -div[class]:has(> a:not([id])) { remove: true; } +div:contains(advertisement) { remove: true; } +div[class]:has(> a > img) { remove: true; } ``` -> Please note that all style properties will be ignored if `:remove()` pseudo-class or `remove` pseudo-property is used. +> Rules with `remove` pseudo-property should use `#$?#` marker: `$` for CSS style rules syntax, `?` for ExtendedCss syntax. -> `remove` pseudo-property should be used with `#$?#` marker as it is a part of ExtendedCss library and at the same time it looks like CSS style rules. - -### Pseudo-class `:is()` +### Pseudo-class `:is()` -This pseudo-class allown to match any element that can be selected by one of the selectors passed to :is(). -If there is invalid selector passed, it will be passed and pseudo-class will deal with valid ones. -Our implementation of matches-any pseudo-class https://developer.mozilla.org/en-US/docs/Web/CSS/:is +Pseudo-class `:is()` allows to match any element that can be selected by any of selectors passed to it. Invalid selectors passed as arg will be skipped and pseudo-class will deal with valid ones with no error. Our implementation of [`:is() (:matches(), :any())` pseudo-class](https://developer.mozilla.org/en-US/docs/Web/CSS/:is). **Syntax** -``` -:is(selectors) ``` -- `selectors` — list of plain CSS selector +[target]:is(selectors) +``` +- `target` — optional, standard or extended css selector, can be missed for checking *any* element +- `selectors` — [*forgiving selector list*](https://drafts.csswg.org/selectors-4/#typedef-forgiving-selector-list) of standard or extended selectors + + **Limitations** + +> If `target` is not defined or defined as [universal selector](https://www.w3.org/TR/selectors-4/#the-universal-selector) `*`, pseudo-class `:is()` applying will be limited to `html` children, e.g. rules `#?#:is(...)` and `#?#*:is(...)` are parsed as `#?#html *:is(...)`. **Examples** + +`#container *:is(.inner, .footer)` will select only `div#target1` +```html + +
+
+
+
+
+
+
``` -#main :is(.header, .body, .footer) .banner-inner -:is(.div-inner, .div-inner2):contains(textmarker) + + +### Pseudo-class `:not()` + +Pseudo-class `:not()` allows to select elements which are *not matched* by selectors passed as arg. Invalid selectors in arg are not allowed and error will be thrown. Our implementation of [`:not()` pseudo-class](https://developer.mozilla.org/en-US/docs/Web/CSS/:not). + +**Syntax** + ``` +[target]:not(selectors) +``` +- `target` — optional, standard or extended css selector, can be missed for checking *any* element +- `selectors` — selector list of standard or extended selectors + + **Limitations** + +> If `target` is not defined or defined as [universal selector](https://www.w3.org/TR/selectors-4/#the-universal-selector) `*`, pseudo-class `:not()` applying will be limited to `html` children, e.g. rules `#?#:not(...)` and `#?#*:not(...)` are parsed as `#?#html *:not(...)`. + +> Inside [`:upward()` pseudo-class](#extended-css-upward) argument `:not()` is considered as a standard CSS pseudo-class because `:upward()` supports only standard selectors. + +**Examples** + +`#container > *:not(h2, .text)` will select only `div#target1` +```html + +
+

Header

+
+ text +
+``` + ### Selectors debug mode -Sometimes, you might need to check the performance of a given selector or a stylesheet. In order to do it without interacting with javascript directly, you can use a special `debug` style property. When `ExtendedCss` meets this property, it enables the "debug"-mode either for a single selector or for all selectors depending on the `debug` value. +Sometimes, you might need to check the performance of a given selector or a stylesheet. In order to do it without interacting with javascript directly, you can use a special `debug` style property. When `ExtendedCss` meets this property, it enables debug mode either for a single selector or for all selectors depending on the `debug` value. **Debugging a single selector** ``` @@ -441,11 +559,126 @@ Sometimes, you might need to check the performance of a given selector or a styl .banner { display: none; debug: global; } ``` -### Usage +> Global debugging mode also can be enabled by positive `debug` property in [`ExtCssConfiguration`](#ext-css-configuration-interface): +```js +const extendedCss = new ExtendedCss({ + styleSheet, // required, rules as string + debug, // optional, boolean +}); +``` + + +### Backward compatible syntax + +**Backward compatible syntax is supported but not recommended.** + +### Old syntax for pseudo-class `:has()` + +**Syntax** +``` +target[-ext-has="selector"] +``` + +**Examples** +``` +div[-ext-has=".banner"] +``` +```html + +
Not selected
+
Selected
+``` + + +### Old syntax for pseudo-class `:contains()` + +**Syntax** +``` +// matching by plain text +target[-ext-contains="text"] + +// matching by a regular expression +target[-ext-contains="/regex/"] +``` + +**Examples** +``` +// matching by plain text +div[-ext-contains="banner"] + +// matching by a regular expression +div[-ext-contains="/this .* banner/"] +``` + +```html + +
Not selected
+
Selected as it contains "banner"
+``` + + +### Old syntax for pseudo-class `:matches-css()` + +**Syntax** +``` +target[-ext-matches-css="property: pattern"] +target[-ext-matches-css-after="property: pattern"] +target[-ext-matches-css-before="property: pattern"] +``` + +**Examples** +```html + + +
+
+``` + +``` +! string pattern +div[-ext-matches-css-before="content: block me"] + +! regular expression pattern +div[-ext-matches-css-before="content: /block me/"] +``` + + +## How to build + +Install dependencies +``` +yarn install +``` + +And just run +``` +yarn build +``` + +## How to test -You can import, require or copy IIFE module with ExtendedCss into your code. +Install dependencies +``` +yarn install +``` + +Run local node testing +``` +yarn test local +``` + +Run performance tests which are not included in `test local` run and should be executed manually: +``` +yarn test performance +``` -e.g. +## Usage + +You can import, require or copy IIFE module with ExtendedCss into your code, e.g. ``` import ExtendedCss from 'extended-css'; ``` @@ -455,45 +688,141 @@ const ExtendedCss = require('extended-css'); ``` IIFE module can be found by the following path `./dist/extended-css.js` -After that you can use ExtendedCss as you wish: +After that you can use ExtendedCss as you wish. + +### API description + +#### Constructor + +``` +/** + * Creates an instance of ExtendedCss + * + * @param configuration — required + */ +constructor(configuration: ExtCssConfiguration) +``` + + +where +```ts +interface ExtCssConfiguration { + // css stylesheet + styleSheet: string; + + // the callback that handles affected elements + beforeStyleApplied?: BeforeStyleAppliedCallback; + // flag for applied selectors logging; equals to `debug: global` in `styleSheet` + debug?: boolean; +} ``` + +```ts +/** + * Needed for getting affected node elements and handle style properties before they are applied to them if it is necessary. + * + * Used by AdGuard Browser extension to display rules in Filtering log and `collect-hits-count` (via tsurlfilter's CssHitsCounter) + */ +type BeforeStyleAppliedCallback = (x:IAffectedElement) => IAffectedElement; + +/** + * Simplified just for representation + * there is a required property 'content' is an applied rule text + */ +interface IAffectedElement { + rules: { style: { content: string }}[] + node: HTMLElement; +} +``` + + + +After instance of ExtendedCss is created, it can be applied on page by `apply()` method. Its applying also can be stopped and styles will be restored by `dispose()` method. + +```js (function() { - var cssText = 'div.wrapper>div[-ext-has=".banner"] { display:none!important; }\n'; - cssText += 'div.wrapper>div[-ext-contains="some word"] { background:none!important; }'; - var extendedCss = new ExtendedCss({ cssText: cssText }); + let styleSheet = 'div.wrapper > div:has(.banner) { display:none!important; }\n'; + styleSheet += 'div.wrapper > div:contains(ads) { background:none!important; }'; + const extendedCss = new ExtendedCss({ styleSheet }); + + // apply styleSheet extendedCss.apply(); - // Just an example of how to stop applying this extended CSS + // stop applying of this styleSheet setTimeout(function() { extendedCss.dispose(); }, 10 * 1000); })(); ``` -### Debugging extended selectors - -To load ExtendedCss to a current page, copy and execute the following code in a browser console: -``` -!function(E,x,t,C,s,s_){C=E.createElement(x),s=E.getElementsByTagName(x)[0],C.src=t, -C.onload=function(){alert('ExtCss loaded successfully')},s.parentNode.insertBefore(C,s)} -(document,'script','https://AdguardTeam.github.io/ExtendedCss/extended-css.min.js') +#### Public method `query()` +```ts +/** + * Returns a list of the document's elements that match the specified selector + * + * @param {string} selector — selector text + * @param {boolean} [noTiming=true] — optional, if true -- do not print the timing to the console + * + * @returns a list of elements found + * @throws an error if the argument is not a valid selector + */ +public static query(selector: string, noTiming = true): HTMLElement[] +``` + +#### Public method `validate()` + +```ts +/** + * Validates selector + * @param selector — selector text + */ +public static validate(selector: string): ValidationResult +``` + +where +```ts +type ValidationResult = { + // true for valid selector, false for invalid one + ok: boolean, + // specified for invalid selector + error: string | null, +}; ``` -Alternative, install an "ExtendedCssDebugger" userscript: https://github.com/AdguardTeam/Userscripts/blob/master/extendedCssDebugger/extended-css.debugger.user.js +### Debugging extended selectors -You can now use the `ExtendedCss` constructor in the global scope, and its method `ExtendedCss.query` as `document.querySelectorAll`. +ExtendedCss can be executed on any page without using any AdGuard product. In order to do that you should copy and execute the following code in a browser console: +```js +!function(e,t,d){C=e.createElement(t),C.src=d,C.onload=function(){alert("ExtendedCss loaded successfully")},s=e.getElementsByTagName(t)[0],s?s.parentNode.insertBefore(C,s):(h=e.getElementsByTagName("head")[0],h.appendChild(C))}(document,"script","https://AdguardTeam.github.io/ExtendedCss/extended-css.min.js"); ``` -var selectorText = "div.block[-ext-has='.header:matches-css-after(content: Anzeige)']"; -ExtendedCss.query(selectorText) // returns an array of Elements matching selectorText +Now you can now use the `ExtendedCss` from global scope, and run its method [`query()`](#extended-css-query) as `Document.querySelectorAll()`. +```js +const selector = 'div.block:has=(.header:matches-css-after(content: Ads))'; + +// array of HTMLElement matched the `selector` will be returned +ExtendedCss.query(selector); ``` -### Projects using Extended Css -* [CoreLibs](https://github.com/AdguardTeam/CoreLibs) (Content Script should be updated) -* [TSUrlFilter](https://github.com/AdguardTeam/tsurlfilter) -* [AdguardForSafari](https://github.com/AdguardTeam/AdGuardForSafari) -### Test page +## Projects using ExtendedCss -[Link](https://AdguardTeam.github.io/ExtendedCss) +* [CoreLibs](https://github.com/AdguardTeam/CoreLibs) — `Content Script` dist should be updated +* [TSUrlFilter](https://github.com/AdguardTeam/tsurlfilter) +* [FiltersCompiler](https://github.com/AdguardTeam/FiltersCompiler) +* [AdguardBrowserExtension](https://github.com/AdguardTeam/AdguardBrowserExtension) — `TSUrlFilter` should be updated +* [AdguardForSafari](https://github.com/AdguardTeam/AdGuardForSafari) — `adguard-resources` should be updated +* [AdguardForiOS](https://github.com/AdguardTeam/AdguardForiOS) — both `ExtendedCss` and `TSUrlFilter` should be updated in `advanced-adblocker-web-extension` + + +### Browser compatibility + +| Browser | Version | +|-----------------------|:----------| +| Chrome | ✅ 55 | +| Firefox | ✅ 52 | +| Edge | ✅ 80 | +| Opera | ✅ 80 | +| Safari | ✅ 11.1 | +| Internet Explorer | ❌ | diff --git a/babel.config.js b/babel.config.js index 89d7558a..68f1e9c2 100644 --- a/babel.config.js +++ b/babel.config.js @@ -5,9 +5,23 @@ module.exports = (api) => { [ '@babel/env', { - // we do not support ie 11, - // because it requires polyfills, which are modifying global scope - targets: '>= 0.5%', + targets: [ + 'last 1 version', + '> 1%', + // ie 11 is dead and no longer supported + 'not dead', + 'chrome >= 55', + 'firefox >= 52', + 'opera >= 80', + 'edge >= 80', + 'safari >= 11', + ], + }, + ], + [ + '@babel/preset-typescript', + { + allowNamespaces: true, }, ], ], diff --git a/dist/extended-css.cjs.js b/dist/extended-css.cjs.js deleted file mode 100644 index c66c5994..00000000 --- a/dist/extended-css.cjs.js +++ /dev/null @@ -1,5292 +0,0 @@ -/*! extended-css - v1.3.16 - Thu Sep 15 2022 -* https://github.com/AdguardTeam/ExtendedCss -* Copyright (c) 2022 AdGuard. Licensed GPL-3.0 -*/ -'use strict'; - -function _typeof(obj) { - "@babel/helpers - typeof"; - - if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { - _typeof = function (obj) { - return typeof obj; - }; - } else { - _typeof = function (obj) { - return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; - }; - } - - return _typeof(obj); -} - -function _slicedToArray(arr, i) { - return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); -} - -function _toConsumableArray(arr) { - return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); -} - -function _arrayWithoutHoles(arr) { - if (Array.isArray(arr)) return _arrayLikeToArray(arr); -} - -function _arrayWithHoles(arr) { - if (Array.isArray(arr)) return arr; -} - -function _iterableToArray(iter) { - if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter); -} - -function _iterableToArrayLimit(arr, i) { - if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return; - var _arr = []; - var _n = true; - var _d = false; - var _e = undefined; - - try { - for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { - _arr.push(_s.value); - - if (i && _arr.length === i) break; - } - } catch (err) { - _d = true; - _e = err; - } finally { - try { - if (!_n && _i["return"] != null) _i["return"](); - } finally { - if (_d) throw _e; - } - } - - return _arr; -} - -function _unsupportedIterableToArray(o, minLen) { - if (!o) return; - if (typeof o === "string") return _arrayLikeToArray(o, minLen); - var n = Object.prototype.toString.call(o).slice(8, -1); - if (n === "Object" && o.constructor) n = o.constructor.name; - if (n === "Map" || n === "Set") return Array.from(o); - if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); -} - -function _arrayLikeToArray(arr, len) { - if (len == null || len > arr.length) len = arr.length; - - for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; - - return arr2; -} - -function _nonIterableSpread() { - throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); -} - -function _nonIterableRest() { - throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); -} - -/** - * Copyright 2016 Adguard Software Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* eslint-disable no-console */ -var utils = {}; -utils.MutationObserver = window.MutationObserver || window.WebKitMutationObserver; -/** - * Stores native Node textContent getter to be used for contains pseudo-class - * because elements' 'textContent' and 'innerText' properties might be mocked - * https://github.com/AdguardTeam/ExtendedCss/issues/127 - */ - -utils.nodeTextContentGetter = function () { - var nativeNode = window.Node || Node; - return Object.getOwnPropertyDescriptor(nativeNode.prototype, 'textContent').get; -}(); - -utils.isSafariBrowser = function () { - return navigator.vendor === 'Apple Computer, Inc.'; -}(); -/** - * Converts regular expressions passed as pseudo class arguments into RegExp instances. - * Have to unescape doublequote " as well, because we escape them while enclosing such - * arguments with doublequotes, and sizzle does not automatically unescapes them. - */ - - -utils.pseudoArgToRegex = function (regexSrc, flag) { - flag = flag || 'i'; - regexSrc = regexSrc.trim().replace(/\\(["\\])/g, '$1'); - return new RegExp(regexSrc, flag); -}; -/** - * Converts string to the regexp - * @param {string} str - * @returns {RegExp} - */ - - -utils.toRegExp = function (str) { - if (str[0] === '/' && str[str.length - 1] === '/') { - return new RegExp(str.slice(1, -1)); - } - - var escaped = str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - return new RegExp(escaped); -}; - -utils.startsWith = function (str, prefix) { - // if str === '', (str && false) will return '' - // that's why it has to be !!str - return !!str && str.indexOf(prefix) === 0; -}; - -utils.endsWith = function (str, postfix) { - if (!str || !postfix) { - return false; - } - - if (str.endsWith) { - return str.endsWith(postfix); - } - - var t = String(postfix); - var index = str.lastIndexOf(t); - return index >= 0 && index === str.length - t.length; -}; -/** - * Helper function for creating regular expression from a url filter rule syntax. - */ - - -utils.createURLRegex = function () { - // Constants - var regexConfiguration = { - maskStartUrl: '||', - maskPipe: '|', - maskSeparator: '^', - maskAnySymbol: '*', - regexAnySymbol: '.*', - regexSeparator: '([^ a-zA-Z0-9.%_-]|$)', - regexStartUrl: '^(http|https|ws|wss)://([a-z0-9-_.]+\\.)?', - regexStartString: '^', - regexEndString: '$' - }; // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/regexp - // should be escaped . * + ? ^ $ { } ( ) | [ ] / \ - // except of * | ^ - - var specials = ['.', '+', '?', '$', '{', '}', '(', ')', '[', ']', '\\', '/']; - var specialsRegex = new RegExp("[".concat(specials.join('\\'), "]"), 'g'); - /** - * Escapes regular expression string - */ - - var escapeRegExp = function escapeRegExp(str) { - return str.replace(specialsRegex, '\\$&'); - }; - - var replaceAll = function replaceAll(str, find, replace) { - if (!str) { - return str; - } - - return str.split(find).join(replace); - }; - /** - * Main function that converts a url filter rule string to a regex. - * @param {string} str - * @return {RegExp} - */ - - - var createRegexText = function createRegexText(str) { - var regex = escapeRegExp(str); - - if (utils.startsWith(regex, regexConfiguration.maskStartUrl)) { - regex = regex.substring(0, regexConfiguration.maskStartUrl.length) + replaceAll(regex.substring(regexConfiguration.maskStartUrl.length, regex.length - 1), '\|', '\\|') + regex.substring(regex.length - 1); - } else if (utils.startsWith(regex, regexConfiguration.maskPipe)) { - regex = regex.substring(0, regexConfiguration.maskPipe.length) + replaceAll(regex.substring(regexConfiguration.maskPipe.length, regex.length - 1), '\|', '\\|') + regex.substring(regex.length - 1); - } else { - regex = replaceAll(regex.substring(0, regex.length - 1), '\|', '\\|') + regex.substring(regex.length - 1); - } // Replacing special url masks - - - regex = replaceAll(regex, regexConfiguration.maskAnySymbol, regexConfiguration.regexAnySymbol); - regex = replaceAll(regex, regexConfiguration.maskSeparator, regexConfiguration.regexSeparator); - - if (utils.startsWith(regex, regexConfiguration.maskStartUrl)) { - regex = regexConfiguration.regexStartUrl + regex.substring(regexConfiguration.maskStartUrl.length); - } else if (utils.startsWith(regex, regexConfiguration.maskPipe)) { - regex = regexConfiguration.regexStartString + regex.substring(regexConfiguration.maskPipe.length); - } - - if (utils.endsWith(regex, regexConfiguration.maskPipe)) { - regex = regex.substring(0, regex.length - 1) + regexConfiguration.regexEndString; - } - - return new RegExp(regex, 'i'); - }; - - return createRegexText; -}(); -/** - * Creates an object implementing Location interface from a url. - * An alternative to URL. - * https://github.com/AdguardTeam/FingerprintingBlocker/blob/master/src/shared/url.ts#L64 - */ - - -utils.createLocation = function (href) { - var anchor = document.createElement('a'); - anchor.href = href; - - if (anchor.host === '') { - anchor.href = anchor.href; // eslint-disable-line no-self-assign - } - - return anchor; -}; -/** - * Checks whether A has the same origin as B. - * @param {string} urlA location.href of A. - * @param {Location} locationB location of B. - * @param {string} domainB document.domain of B. - * @return {boolean} - */ - - -utils.isSameOrigin = function (urlA, locationB, domainB) { - var locationA = utils.createLocation(urlA); // eslint-disable-next-line no-script-url - - if (locationA.protocol === 'javascript:' || locationA.href === 'about:blank') { - return true; - } - - if (locationA.protocol === 'data:' || locationA.protocol === 'file:') { - return false; - } - - return locationA.hostname === domainB && locationA.port === locationB.port && locationA.protocol === locationB.protocol; -}; -/** - * A helper class to throttle function calls with setTimeout and requestAnimationFrame. - */ - - -utils.AsyncWrapper = function () { - var supported = typeof window.requestAnimationFrame !== 'undefined'; - var rAF = supported ? requestAnimationFrame : setTimeout; - var cAF = supported ? cancelAnimationFrame : clearTimeout; - var perf = supported ? performance : Date; - /** - * @param {Function} callback - * @param {number} throttle number, the provided callback should be executed twice - * in this time frame. - * @constructor - */ - - function AsyncWrapper(callback, throttle) { - this.callback = callback; - this.throttle = throttle; - this.wrappedCallback = this.wrappedCallback.bind(this); - - if (this.wrappedAsapCallback) { - this.wrappedAsapCallback = this.wrappedAsapCallback.bind(this); - } - } - /** @private */ - - - AsyncWrapper.prototype.wrappedCallback = function (ts) { - this.lastRun = isNumber(ts) ? ts : perf.now(); - delete this.rAFid; - delete this.timerId; - delete this.asapScheduled; - this.callback(); - }; - /** @private Indicates whether there is a scheduled callback. */ - - - AsyncWrapper.prototype.hasPendingCallback = function () { - return isNumber(this.rAFid) || isNumber(this.timerId); - }; - /** - * Schedules a function call before the next animation frame. - */ - - - AsyncWrapper.prototype.run = function () { - if (this.hasPendingCallback()) { - // There is a pending execution scheduled. - return; - } - - if (typeof this.lastRun !== 'undefined') { - var elapsed = perf.now() - this.lastRun; - - if (elapsed < this.throttle) { - this.timerId = setTimeout(this.wrappedCallback, this.throttle - elapsed); - return; - } - } - - this.rAFid = rAF(this.wrappedCallback); - }; - /** - * Schedules a function call in the most immenent microtask. - * This cannot be canceled. - */ - - - AsyncWrapper.prototype.runAsap = function () { - if (this.asapScheduled) { - return; - } - - this.asapScheduled = true; - cAF(this.rAFid); - clearTimeout(this.timerId); - - if (utils.MutationObserver) { - /** - * Using MutationObservers to access microtask queue is a standard technique, - * used in ASAP library - * {@link https://github.com/kriskowal/asap/blob/master/browser-raw.js#L140} - */ - if (!this.mo) { - this.mo = new utils.MutationObserver(this.wrappedCallback); - this.node = document.createTextNode(1); - this.mo.observe(this.node, { - characterData: true - }); - } - - this.node.nodeValue = -this.node.nodeValue; - } else { - setTimeout(this.wrappedCallback); - } - }; - /** - * Runs scheduled execution immediately, if there were any. - */ - - - AsyncWrapper.prototype.runImmediately = function () { - if (this.hasPendingCallback()) { - cAF(this.rAFid); - clearTimeout(this.timerId); - delete this.rAFid; - delete this.timerId; - this.wrappedCallback(); - } - }; - - AsyncWrapper.now = function () { - return perf.now(); - }; - - return AsyncWrapper; -}(); -/** - * Stores native OdP to be used in WeakMap and Set polyfills. - */ - - -utils.defineProperty = Object.defineProperty; -utils.WeakMap = typeof WeakMap !== 'undefined' ? WeakMap : function () { - /** Originally based on {@link https://github.com/Polymer/WeakMap} */ - var counter = Date.now() % 1e9; - - var WeakMap = function WeakMap() { - this.name = "__st".concat(Math.random() * 1e9 >>> 0).concat(counter++, "__"); - }; - - WeakMap.prototype = { - set: function set(key, value) { - var entry = key[this.name]; - - if (entry && entry[0] === key) { - entry[1] = value; - } else { - utils.defineProperty(key, this.name, { - value: [key, value], - writable: true - }); - } - - return this; - }, - get: function get(key) { - var entry = key[this.name]; - return entry && entry[0] === key ? entry[1] : undefined; - }, - delete: function _delete(key) { - var entry = key[this.name]; - - if (!entry) { - return false; - } - - var hasValue = entry[0] === key; - delete entry[0]; - delete entry[1]; - return hasValue; - }, - has: function has(key) { - var entry = key[this.name]; - - if (!entry) { - return false; - } - - return entry[0] === key; - } - }; - return WeakMap; -}(); -utils.Set = typeof Set !== 'undefined' ? Set : function () { - var counter = Date.now() % 1e9; - /** - * A polyfill which covers only the basic usage. - * Only supports methods that are supported in IE11. - * {@link https://docs.microsoft.com/en-us/scripting/javascript/reference/set-object-javascript} - * Assumes that 'key's are all objects, not primitives such as a number. - * - * @param {Array} items Initial items in this set - */ - - var Set = function Set(items) { - this.name = "__st".concat(Math.random() * 1e9 >>> 0).concat(counter++, "__"); - this.keys = []; - - if (items && items.length) { - var iItems = items.length; - - while (iItems--) { - this.add(items[iItems]); - } - } - }; - - Set.prototype = { - add: function add(key) { - if (!isNumber(key[this.name])) { - var index = this.keys.push(key) - 1; - utils.defineProperty(key, this.name, { - value: index, - writable: true - }); - } - }, - delete: function _delete(key) { - if (isNumber(key[this.name])) { - var index = key[this.name]; - delete this.keys[index]; - key[this.name] = undefined; - } - }, - has: function has(key) { - return isNumber(key[this.name]); - }, - clear: function clear() { - this.keys.forEach(function (key) { - key[this.name] = undefined; - }); - this.keys.length = 0; - }, - forEach: function forEach(cb) { - var that = this; - this.keys.forEach(function (value) { - cb(value, value, that); - }); - } - }; - utils.defineProperty(Set.prototype, 'size', { - get: function get() { - // Skips holes. - return this.keys.reduce(function (acc) { - return acc + 1; - }, 0); - } - }); - return Set; -}(); -/** - * Vendor-specific Element.prototype.matches - */ - -utils.matchesPropertyName = function () { - var props = ['matches', 'matchesSelector', 'mozMatchesSelector', 'msMatchesSelector', 'oMatchesSelector', 'webkitMatchesSelector']; - - for (var i = 0; i < 6; i++) { - if (Element.prototype.hasOwnProperty(props[i])) { - return props[i]; - } - } -}(); -/** - * Provides stats information - */ - - -utils.Stats = function () { - /** @member {Array} */ - this.array = []; - /** @member {number} */ - - this.length = 0; - var zeroDescriptor = { - value: 0, - writable: true - }; - /** @member {number} @private */ - - Object.defineProperty(this, 'sum', zeroDescriptor); - /** @member {number} @private */ - - Object.defineProperty(this, 'squaredSum', zeroDescriptor); -}; -/** - * @param {number} dataPoint data point - */ - - -utils.Stats.prototype.push = function (dataPoint) { - this.array.push(dataPoint); - this.length++; - this.sum += dataPoint; - this.squaredSum += dataPoint * dataPoint; - /** @member {number} */ - - this.mean = this.sum / this.length; - /** @member {number} */ - // eslint-disable-next-line no-restricted-properties - - this.stddev = Math.sqrt(this.squaredSum / this.length - Math.pow(this.mean, 2)); -}; -/** Safe console.error version */ - - -utils.logError = typeof console !== 'undefined' && console.error && Function.prototype.bind && console.error.bind ? console.error.bind(window.console) : console.error; -/** Safe console.info version */ - -utils.logInfo = typeof console !== 'undefined' && console.info && Function.prototype.bind && console.info.bind ? console.info.bind(window.console) : console.info; - -function isNumber(obj) { - return typeof obj === 'number'; -} -/** - * Returns path to element we will use as element identifier - * @param {Element} inputEl - * @returns {string} - path to the element - */ - - -utils.getNodeSelector = function (inputEl) { - if (!(inputEl instanceof Element)) { - throw new Error('Function received argument with wrong type'); - } - - var el = inputEl; - var path = []; // we need to check '!!el' first because it is possible - // that some ancestor of the inputEl was removed before it - - while (!!el && el.nodeType === Node.ELEMENT_NODE) { - var selector = el.nodeName.toLowerCase(); - - if (el.id && typeof el.id === 'string') { - selector += "#".concat(el.id); - path.unshift(selector); - break; - } else { - var sibling = el; - var nth = 1; - - while (sibling.previousSibling) { - sibling = sibling.previousSibling; - - if (sibling.nodeType === Node.ELEMENT_NODE && sibling.nodeName.toLowerCase() === selector) { - nth++; - } - } - - if (nth !== 1) { - selector += ":nth-of-type(".concat(nth, ")"); - } - } - - path.unshift(selector); - el = el.parentNode; - } - - return path.join(' > '); -}; - -/** - * Copyright 2016 Adguard Software Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Helper class css utils - * - * @type {{normalize}} - */ -var cssUtils = function () { - /** - * Regex that matches AdGuard's backward compatible syntaxes. - */ - var reAttrFallback = /\[-(?:ext|abp)-([a-z-_]+)=(["'])((?:(?=(\\?))\4.)*?)\2\]/g; - /** - * Complex replacement function. - * Unescapes quote characters inside of an extended selector. - * - * @param match Whole matched string - * @param name Group 1 - * @param quoteChar Group 2 - * @param value Group 3 - */ - - var evaluateMatch = function evaluateMatch(match, name, quoteChar, value) { - // Unescape quotes - var re = new RegExp("([^\\\\]|^)\\\\".concat(quoteChar), 'g'); - value = value.replace(re, "$1".concat(quoteChar)); - return ":".concat(name, "(").concat(value, ")"); - }; // Sizzle's parsing of pseudo class arguments is buggy on certain circumstances - // We support following form of arguments: - // 1. for :matches-css, those of a form {propertyName}: /.*/ - // 2. for :contains, those of a form /.*/ - // We transform such cases in a way that Sizzle has no ambiguity in parsing arguments. - - - var reMatchesCss = /\:(matches-css(?:-after|-before)?)\(([a-z-\s]*\:\s*\/(?:\\.|[^\/])*?\/\s*)\)/g; - var reContains = /:(?:-abp-)?(contains|has-text)\((\s*\/(?:\\.|[^\/])*?\/\s*)\)/g; - var reScope = /\(\:scope >/g; // Note that we require `/` character in regular expressions to be escaped. - - /** - * Used for pre-processing pseudo-classes values with above two regexes. - */ - - var addQuotes = function addQuotes(_, c1, c2) { - return ":".concat(c1, "(\"").concat(c2.replace(/["\\]/g, '\\$&'), "\")"); - }; - - var SCOPE_REPLACER = '(>'; - /** - * Normalizes specified css text in a form that can be parsed by the - * Sizzle engine. - * Normalization means - * 1. transforming [-ext-*=""] attributes to pseudo classes - * 2. enclosing possibly ambiguous arguments of `:contains`, - * `:matches-css` pseudo classes with quotes. - * @param {string} cssText - * @return {string} - */ - - var normalize = function normalize(cssText) { - var normalizedCssText = cssText.replace(reAttrFallback, evaluateMatch).replace(reMatchesCss, addQuotes).replace(reContains, addQuotes).replace(reScope, SCOPE_REPLACER); - return normalizedCssText; - }; - - var isSimpleSelectorValid = function isSimpleSelectorValid(selector) { - try { - document.querySelectorAll(selector); - } catch (e) { - return false; - } - - return true; - }; - - return { - normalize: normalize, - isSimpleSelectorValid: isSimpleSelectorValid - }; -}(); - -/*! - * Sizzle CSS Selector Engine v2.3.4-pre-adguard - * https://sizzlejs.com/ - * - * Copyright JS Foundation and other contributors - * Released under the MIT license - * https://js.foundation/ - * - * Date: 2020-08-04 - */ - -/** - * Version of Sizzle patched by AdGuard in order to be used in the ExtendedCss module. - * https://github.com/AdguardTeam/sizzle-extcss - * - * Look for [AdGuard Patch] and ADGUARD_EXTCSS markers to find out what exactly was changed by us. - * - * Global changes: - * 1. Added additional parameters to the "Sizzle.tokenize" method so that it can be used for stylesheets parsing and validation. - * 2. Added tokens re-sorting mechanism forcing slow pseudos to be matched last (see sortTokenGroups). - * 3. Fix the nonnativeSelectorCache caching -- there was no value corresponding to a key. - * 4. Added Sizzle.compile call to the `:has` pseudo definition. - * - * Changes that are applied to the ADGUARD_EXTCSS build only: - * 1. Do not expose Sizzle to the global scope. Initialize it lazily via initializeSizzle(). - * 2. Removed :contains pseudo declaration -- its syntax is changed and declared outside of Sizzle. - * 3. Removed declarations for the following non-standard pseudo classes: - * :parent, :header, :input, :button, :text, :first, :last, :eq, - * :even, :odd, :lt, :gt, :nth, :radio, :checkbox, :file, - * :password, :image, :submit, :reset - * 4. Added es6 module export - */ -var Sizzle; -/** - * Initializes Sizzle object. - * In the case of AdGuard ExtendedCss we want to avoid initializing Sizzle right away - * and exposing it to the global scope. - */ - -var initializeSizzle = function initializeSizzle() { - // jshint ignore:line - if (!Sizzle) { - //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - Sizzle = function (window) { - var support, - Expr, - getText, - isXML, - tokenize, - compile, - select, - outermostContext, - sortInput, - hasDuplicate, - // Local document vars - setDocument, - document, - docElem, - documentIsHTML, - rbuggyQSA, - rbuggyMatches, - matches, - contains, - // Instance-specific data - expando = "sizzle" + 1 * new Date(), - preferredDoc = window.document, - dirruns = 0, - done = 0, - classCache = createCache(), - tokenCache = createCache(), - compilerCache = createCache(), - nonnativeSelectorCache = createCache(), - sortOrder = function sortOrder(a, b) { - if (a === b) { - hasDuplicate = true; - } - - return 0; - }, - // Instance methods - hasOwn = {}.hasOwnProperty, - arr = [], - pop = arr.pop, - push_native = arr.push, - push = arr.push, - slice = arr.slice, - // Use a stripped-down indexOf as it's faster than native - // https://jsperf.com/thor-indexof-vs-for/5 - indexOf = function indexOf(list, elem) { - var i = 0, - len = list.length; - - for (; i < len; i++) { - if (list[i] === elem) { - return i; - } - } - - return -1; - }, - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", - // Regular expressions - // http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier - identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", - // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors - attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + // Operator (capture 2) - "*([*^$|!~]?=)" + whitespace + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" - "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + "*\\]", - pseudos = ":(" + identifier + ")(?:\\((" + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: - // 1. quoted (capture 3; capture 4 or capture 5) - "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + // 2. simple (capture 6) - "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + // 3. anything else (capture 2) - ".*" + ")\\)|)", - // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter - rwhitespace = new RegExp(whitespace + "+", "g"), - rtrim = new RegExp("^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g"), - rcomma = new RegExp("^" + whitespace + "*," + whitespace + "*"), - rcombinators = new RegExp("^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*"), - rpseudo = new RegExp(pseudos), - ridentifier = new RegExp("^" + identifier + "$"), - matchExpr = { - "ID": new RegExp("^#(" + identifier + ")"), - "CLASS": new RegExp("^\\.(" + identifier + ")"), - "TAG": new RegExp("^(" + identifier + "|[*])"), - "ATTR": new RegExp("^" + attributes), - "PSEUDO": new RegExp("^" + pseudos), - "CHILD": new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i"), - "bool": new RegExp("^(?:" + booleans + ")$", "i"), - // For use in libraries implementing .is() - // We use this for POS matching in `select` - "needsContext": new RegExp("^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i") - }, - rnative = /^[^{]+\{\s*\[native \w/, - // Easily-parseable/retrievable ID or TAG or CLASS selectors - rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, - rsibling = /[+~]/, - // CSS escapes - // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp("\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig"), - funescape = function funescape(_, escaped, escapedWhitespace) { - var high = "0x" + escaped - 0x10000; // NaN means non-codepoint - // Support: Firefox<24 - // Workaround erroneous numeric interpretation of +"0x" - - return high !== high || escapedWhitespace ? escaped : high < 0 ? // BMP codepoint - String.fromCharCode(high + 0x10000) : // Supplemental Plane codepoint (surrogate pair) - String.fromCharCode(high >> 10 | 0xD800, high & 0x3FF | 0xDC00); - }, - // CSS string/identifier serialization - // https://drafts.csswg.org/cssom/#common-serializing-idioms - rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, - fcssescape = function fcssescape(ch, asCodePoint) { - if (asCodePoint) { - // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER - if (ch === "\0") { - return "\uFFFD"; - } // Control characters and (dependent upon position) numbers get escaped as code points - - - return ch.slice(0, -1) + "\\" + ch.charCodeAt(ch.length - 1).toString(16) + " "; - } // Other potentially-special ASCII characters get backslash-escaped - - - return "\\" + ch; - }, - // Used for iframes - // See setDocument() - // Removing the function wrapper causes a "Permission Denied" - // error in IE - unloadHandler = function unloadHandler() { - setDocument(); - }, - inDisabledFieldset = addCombinator(function (elem) { - return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; - }, { - dir: "parentNode", - next: "legend" - }); // Optimize for push.apply( _, NodeList ) - - - try { - push.apply(arr = slice.call(preferredDoc.childNodes), preferredDoc.childNodes); // Support: Android<4.0 - // Detect silently failing push.apply - - arr[preferredDoc.childNodes.length].nodeType; - } catch (e) { - push = { - apply: arr.length ? // Leverage slice if possible - function (target, els) { - push_native.apply(target, slice.call(els)); - } : // Support: IE<9 - // Otherwise append directly - function (target, els) { - var j = target.length, - i = 0; // Can't trust NodeList.length - - while (target[j++] = els[i++]) {} - - target.length = j - 1; - } - }; - } - - function Sizzle(selector, context, results, seed) { - var m, - i, - elem, - nid, - match, - groups, - newSelector, - newContext = context && context.ownerDocument, - // nodeType defaults to 9, since context defaults to document - nodeType = context ? context.nodeType : 9; - results = results || []; // Return early from calls with invalid selector or context - - if (typeof selector !== "string" || !selector || nodeType !== 1 && nodeType !== 9 && nodeType !== 11) { - return results; - } // Try to shortcut find operations (as opposed to filters) in HTML documents - - - if (!seed) { - if ((context ? context.ownerDocument || context : preferredDoc) !== document) { - setDocument(context); - } - - context = context || document; - - if (documentIsHTML) { - // If the selector is sufficiently simple, try using a "get*By*" DOM method - // (excepting DocumentFragment context, where the methods don't exist) - if (nodeType !== 11 && (match = rquickExpr.exec(selector))) { - // ID selector - if (m = match[1]) { - // Document context - if (nodeType === 9) { - if (elem = context.getElementById(m)) { - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if (elem.id === m) { - results.push(elem); - return results; - } - } else { - return results; - } // Element context - - } else { - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if (newContext && (elem = newContext.getElementById(m)) && contains(context, elem) && elem.id === m) { - results.push(elem); - return results; - } - } // Type selector - - } else if (match[2]) { - push.apply(results, context.getElementsByTagName(selector)); - return results; // Class selector - } else if ((m = match[3]) && support.getElementsByClassName && context.getElementsByClassName) { - push.apply(results, context.getElementsByClassName(m)); - return results; - } - } // Take advantage of querySelectorAll - - - if (support.qsa && !nonnativeSelectorCache[selector + " "] && (!rbuggyQSA || !rbuggyQSA.test(selector))) { - if (nodeType !== 1) { - newContext = context; - newSelector = selector; // qSA looks outside Element context, which is not what we want - // Thanks to Andrew Dupont for this workaround technique - // Support: IE <=8 - // Exclude object elements - } else if (context.nodeName.toLowerCase() !== "object") { - // Capture the context ID, setting it first if necessary - if (nid = context.getAttribute("id")) { - nid = nid.replace(rcssescape, fcssescape); - } else { - context.setAttribute("id", nid = expando); - } // Prefix every selector in the list - - - groups = tokenize(selector); - i = groups.length; - - while (i--) { - groups[i] = "#" + nid + " " + toSelector(groups[i]); - } - - newSelector = groups.join(","); // Expand context for sibling selectors - - newContext = rsibling.test(selector) && testContext(context.parentNode) || context; - } - - if (newSelector) { - try { - if (newSelector.indexOf(':has(') > -1) { - // https://github.com/AdguardTeam/ExtendedCss/issues/149 - throw new Error('Do not handle :has() pseudo-class by the native method'); - } - - push.apply(results, newContext.querySelectorAll(newSelector)); - return results; - } catch (qsaError) { - // [AdGuard Path]: Fix the cache value - nonnativeSelectorCache(selector, true); - } finally { - if (nid === expando) { - context.removeAttribute("id"); - } - } - } - } - } - } // All others - - - return select(selector.replace(rtrim, "$1"), context, results, seed); - } - /** - * Create key-value caches of limited size - * @returns {function(string, object)} Returns the Object data after storing it on itself with - * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) - * deleting the oldest entry - */ - - - function createCache() { - var keys = []; - - function cache(key, value) { - // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) - if (keys.push(key + " ") > Expr.cacheLength) { - // Only keep the most recent entries - delete cache[keys.shift()]; - } - - return cache[key + " "] = value; - } - - return cache; - } - /** - * Mark a function for special use by Sizzle - * @param {Function} fn The function to mark - */ - - - function markFunction(fn) { - fn[expando] = true; - return fn; - } - /** - * Support testing using an element - * @param {Function} fn Passed the created element and returns a boolean result - */ - - - function assert(fn) { - var el = document.createElement("fieldset"); - - try { - return !!fn(el); - } catch (e) { - return false; - } finally { - // Remove from its parent by default - if (el.parentNode) { - el.parentNode.removeChild(el); - } // release memory in IE - - - el = null; - } - } - /** - * Adds the same handler for all of the specified attrs - * @param {String} attrs Pipe-separated list of attributes - * @param {Function} handler The method that will be applied - */ - - - function addHandle(attrs, handler) { - var arr = attrs.split("|"), - i = arr.length; - - while (i--) { - Expr.attrHandle[arr[i]] = handler; - } - } - /** - * Checks document order of two siblings - * @param {Element} a - * @param {Element} b - * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b - */ - - - function siblingCheck(a, b) { - var cur = b && a, - diff = cur && a.nodeType === 1 && b.nodeType === 1 && a.sourceIndex - b.sourceIndex; // Use IE sourceIndex if available on both nodes - - if (diff) { - return diff; - } // Check if b follows a - - - if (cur) { - while (cur = cur.nextSibling) { - if (cur === b) { - return -1; - } - } - } - - return a ? 1 : -1; - } - /** - * Returns a function to use in pseudos for :enabled/:disabled - * @param {Boolean} disabled true for :disabled; false for :enabled - */ - - - function createDisabledPseudo(disabled) { - // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable - return function (elem) { - // Only certain elements can match :enabled or :disabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled - if ("form" in elem) { - // Check for inherited disabledness on relevant non-disabled elements: - // * listed form-associated elements in a disabled fieldset - // https://html.spec.whatwg.org/multipage/forms.html#category-listed - // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled - // * option elements in a disabled optgroup - // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled - // All such elements have a "form" property. - if (elem.parentNode && elem.disabled === false) { - // Option elements defer to a parent optgroup if present - if ("label" in elem) { - if ("label" in elem.parentNode) { - return elem.parentNode.disabled === disabled; - } else { - return elem.disabled === disabled; - } - } // Support: IE 6 - 11 - // Use the isDisabled shortcut property to check for disabled fieldset ancestors - - - return elem.isDisabled === disabled || // Where there is no isDisabled, check manually - - /* jshint -W018 */ - elem.isDisabled !== !disabled && inDisabledFieldset(elem) === disabled; - } - - return elem.disabled === disabled; // Try to winnow out elements that can't be disabled before trusting the disabled property. - // Some victims get caught in our net (label, legend, menu, track), but it shouldn't - // even exist on them, let alone have a boolean value. - } else if ("label" in elem) { - return elem.disabled === disabled; - } // Remaining elements are neither :enabled nor :disabled - - - return false; - }; - } - /** - * Checks a node for validity as a Sizzle context - * @param {Element|Object=} context - * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value - */ - - - function testContext(context) { - return context && typeof context.getElementsByTagName !== "undefined" && context; - } // Expose support vars for convenience - - - support = Sizzle.support = {}; - /** - * Detects XML nodes - * @param {Element|Object} elem An element or a document - * @returns {Boolean} True iff elem is a non-HTML XML node - */ - - isXML = Sizzle.isXML = function (elem) { - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = elem && (elem.ownerDocument || elem).documentElement; - return documentElement ? documentElement.nodeName !== "HTML" : false; - }; - /** - * Sets document-related variables once based on the current document - * @param {Element|Object} [doc] An element or document object to use to set the document - * @returns {Object} Returns the current document - */ - - - setDocument = Sizzle.setDocument = function (node) { - var hasCompare, - subWindow, - doc = node ? node.ownerDocument || node : preferredDoc; // Return early if doc is invalid or already selected - - if (doc === document || doc.nodeType !== 9 || !doc.documentElement) { - return document; - } // Update global variables - - - document = doc; - docElem = document.documentElement; - documentIsHTML = !isXML(document); // Support: IE 9-11, Edge - // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) - - if (preferredDoc !== document && (subWindow = document.defaultView) && subWindow.top !== subWindow) { - // Support: IE 11, Edge - if (subWindow.addEventListener) { - subWindow.addEventListener("unload", unloadHandler, false); // Support: IE 9 - 10 only - } else if (subWindow.attachEvent) { - subWindow.attachEvent("onunload", unloadHandler); - } - } - /* Attributes - ---------------------------------------------------------------------- */ - // Support: IE<8 - // Verify that getAttribute really returns attributes and not properties - // (excepting IE8 booleans) - - - support.attributes = assert(function (el) { - el.className = "i"; - return !el.getAttribute("className"); - }); - /* getElement(s)By* - ---------------------------------------------------------------------- */ - // Check if getElementsByTagName("*") returns only elements - - support.getElementsByTagName = assert(function (el) { - el.appendChild(document.createComment("")); - return !el.getElementsByTagName("*").length; - }); // Support: IE<9 - - support.getElementsByClassName = rnative.test(document.getElementsByClassName); // Support: IE<10 - // Check if getElementById returns elements by name - // The broken getElementById methods don't pick up programmatically-set names, - // so use a roundabout getElementsByName test - - support.getById = assert(function (el) { - docElem.appendChild(el).id = expando; - return !document.getElementsByName || !document.getElementsByName(expando).length; - }); // ID filter and find - - if (support.getById) { - Expr.filter["ID"] = function (id) { - var attrId = id.replace(runescape, funescape); - return function (elem) { - return elem.getAttribute("id") === attrId; - }; - }; - - Expr.find["ID"] = function (id, context) { - if (typeof context.getElementById !== "undefined" && documentIsHTML) { - var elem = context.getElementById(id); - return elem ? [elem] : []; - } - }; - } else { - Expr.filter["ID"] = function (id) { - var attrId = id.replace(runescape, funescape); - return function (elem) { - var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); - return node && node.value === attrId; - }; - }; // Support: IE 6 - 7 only - // getElementById is not reliable as a find shortcut - - - Expr.find["ID"] = function (id, context) { - if (typeof context.getElementById !== "undefined" && documentIsHTML) { - var node, - i, - elems, - elem = context.getElementById(id); - - if (elem) { - // Verify the id attribute - node = elem.getAttributeNode("id"); - - if (node && node.value === id) { - return [elem]; - } // Fall back on getElementsByName - - - elems = context.getElementsByName(id); - i = 0; - - while (elem = elems[i++]) { - node = elem.getAttributeNode("id"); - - if (node && node.value === id) { - return [elem]; - } - } - } - - return []; - } - }; - } // Tag - - - Expr.find["TAG"] = support.getElementsByTagName ? function (tag, context) { - if (typeof context.getElementsByTagName !== "undefined") { - return context.getElementsByTagName(tag); // DocumentFragment nodes don't have gEBTN - } else if (support.qsa) { - return context.querySelectorAll(tag); - } - } : function (tag, context) { - var elem, - tmp = [], - i = 0, - // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too - results = context.getElementsByTagName(tag); // Filter out possible comments - - if (tag === "*") { - while (elem = results[i++]) { - if (elem.nodeType === 1) { - tmp.push(elem); - } - } - - return tmp; - } - - return results; - }; // Class - - Expr.find["CLASS"] = support.getElementsByClassName && function (className, context) { - if (typeof context.getElementsByClassName !== "undefined" && documentIsHTML) { - return context.getElementsByClassName(className); - } - }; - /* QSA/matchesSelector - ---------------------------------------------------------------------- */ - // QSA and matchesSelector support - // matchesSelector(:active) reports false when true (IE9/Opera 11.5) - - - rbuggyMatches = []; // qSa(:focus) reports false when true (Chrome 21) - // We allow this because of a bug in IE8/9 that throws an error - // whenever `document.activeElement` is accessed on an iframe - // So, we allow :focus to pass through QSA all the time to avoid the IE error - // See https://bugs.jquery.com/ticket/13378 - - rbuggyQSA = []; - - if (support.qsa = rnative.test(document.querySelectorAll)) { - // Build QSA regex - // Regex strategy adopted from Diego Perini - assert(function (el) { - // Select is set to empty string on purpose - // This is to test IE's treatment of not explicitly - // setting a boolean content attribute, - // since its presence should be enough - // https://bugs.jquery.com/ticket/12359 - docElem.appendChild(el).innerHTML = AGPolicy.createHTML("" + ""); // Support: IE8, Opera 11-12.16 - // Nothing should be selected when empty strings follow ^= or $= or *= - // The test attribute must be unknown in Opera but "safe" for WinRT - // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section - - if (el.querySelectorAll("[msallowcapture^='']").length) { - rbuggyQSA.push("[*^$]=" + whitespace + "*(?:''|\"\")"); - } // Support: IE8 - // Boolean attributes and "value" are not treated correctly - - - if (!el.querySelectorAll("[selected]").length) { - rbuggyQSA.push("\\[" + whitespace + "*(?:value|" + booleans + ")"); - } // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ - - - if (!el.querySelectorAll("[id~=" + expando + "-]").length) { - rbuggyQSA.push("~="); - } // Webkit/Opera - :checked should return selected option elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - // IE8 throws error here and will not see later tests - - - if (!el.querySelectorAll(":checked").length) { - rbuggyQSA.push(":checked"); - } // Support: Safari 8+, iOS 8+ - // https://bugs.webkit.org/show_bug.cgi?id=136851 - // In-page `selector#id sibling-combinator selector` fails - - - if (!el.querySelectorAll("a#" + expando + "+*").length) { - rbuggyQSA.push(".#.+[+~]"); - } - }); - assert(function (el) { - el.innerHTML = AGPolicy.createHTML("" + ""); // Support: Windows 8 Native Apps - // The type and name attributes are restricted during .innerHTML assignment - - var input = document.createElement("input"); - input.setAttribute("type", "hidden"); - el.appendChild(input).setAttribute("name", "D"); // Support: IE8 - // Enforce case-sensitivity of name attribute - - if (el.querySelectorAll("[name=d]").length) { - rbuggyQSA.push("name" + whitespace + "*[*^$|!~]?="); - } // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) - // IE8 throws error here and will not see later tests - - - if (el.querySelectorAll(":enabled").length !== 2) { - rbuggyQSA.push(":enabled", ":disabled"); - } // Support: IE9-11+ - // IE's :disabled selector does not pick up the children of disabled fieldsets - - - docElem.appendChild(el).disabled = true; - - if (el.querySelectorAll(":disabled").length !== 2) { - rbuggyQSA.push(":enabled", ":disabled"); - } // Opera 10-11 does not throw on post-comma invalid pseudos - - - el.querySelectorAll("*,:x"); - rbuggyQSA.push(",.*:"); - }); - } - - if (support.matchesSelector = rnative.test(matches = docElem.matches || docElem.webkitMatchesSelector || docElem.mozMatchesSelector || docElem.oMatchesSelector || docElem.msMatchesSelector)) { - assert(function (el) { - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9) - support.disconnectedMatch = matches.call(el, "*"); // This should fail with an exception - // Gecko does not error, returns false instead - - matches.call(el, "[s!='']:x"); - rbuggyMatches.push("!=", pseudos); - }); - } - - rbuggyQSA = rbuggyQSA.length && new RegExp(rbuggyQSA.join("|")); - rbuggyMatches = rbuggyMatches.length && new RegExp(rbuggyMatches.join("|")); - /* Contains - ---------------------------------------------------------------------- */ - - hasCompare = rnative.test(docElem.compareDocumentPosition); // Element contains another - // Purposefully self-exclusive - // As in, an element does not contain itself - - contains = hasCompare || rnative.test(docElem.contains) ? function (a, b) { - var adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!(bup && bup.nodeType === 1 && (adown.contains ? adown.contains(bup) : a.compareDocumentPosition && a.compareDocumentPosition(bup) & 16)); - } : function (a, b) { - if (b) { - while (b = b.parentNode) { - if (b === a) { - return true; - } - } - } - - return false; - }; - /* Sorting - ---------------------------------------------------------------------- */ - // Document order sorting - - sortOrder = hasCompare ? function (a, b) { - // Flag for duplicate removal - if (a === b) { - hasDuplicate = true; - return 0; - } // Sort on method existence if only one input has compareDocumentPosition - - - var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; - - if (compare) { - return compare; - } // Calculate position if both inputs belong to the same document - - - compare = (a.ownerDocument || a) === (b.ownerDocument || b) ? a.compareDocumentPosition(b) : // Otherwise we know they are disconnected - 1; // Disconnected nodes - - if (compare & 1 || !support.sortDetached && b.compareDocumentPosition(a) === compare) { - // Choose the first element that is related to our preferred document - if (a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a)) { - return -1; - } - - if (b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b)) { - return 1; - } // Maintain original order - - - return sortInput ? indexOf(sortInput, a) - indexOf(sortInput, b) : 0; - } - - return compare & 4 ? -1 : 1; - } : function (a, b) { - // Exit early if the nodes are identical - if (a === b) { - hasDuplicate = true; - return 0; - } - - var cur, - i = 0, - aup = a.parentNode, - bup = b.parentNode, - ap = [a], - bp = [b]; // Parentless nodes are either documents or disconnected - - if (!aup || !bup) { - return a === document ? -1 : b === document ? 1 : aup ? -1 : bup ? 1 : sortInput ? indexOf(sortInput, a) - indexOf(sortInput, b) : 0; // If the nodes are siblings, we can do a quick check - } else if (aup === bup) { - return siblingCheck(a, b); - } // Otherwise we need full lists of their ancestors for comparison - - - cur = a; - - while (cur = cur.parentNode) { - ap.unshift(cur); - } - - cur = b; - - while (cur = cur.parentNode) { - bp.unshift(cur); - } // Walk down the tree looking for a discrepancy - - - while (ap[i] === bp[i]) { - i++; - } - - return i ? // Do a sibling check if the nodes have a common ancestor - siblingCheck(ap[i], bp[i]) : // Otherwise nodes in our document sort first - ap[i] === preferredDoc ? -1 : bp[i] === preferredDoc ? 1 : 0; - }; - return document; - }; - - Sizzle.matches = function (expr, elements) { - return Sizzle(expr, null, null, elements); - }; - - Sizzle.matchesSelector = function (elem, expr) { - // Set document vars if needed - if ((elem.ownerDocument || elem) !== document) { - setDocument(elem); - } - - if (support.matchesSelector && documentIsHTML && !nonnativeSelectorCache[expr + " "] && (!rbuggyMatches || !rbuggyMatches.test(expr)) && (!rbuggyQSA || !rbuggyQSA.test(expr))) { - try { - var ret = matches.call(elem, expr); // IE 9's matchesSelector returns false on disconnected nodes - - if (ret || support.disconnectedMatch || // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11) { - return ret; - } - } catch (e) { - // [AdGuard Path]: Fix the cache value - nonnativeSelectorCache(expr, true); - } - } - - return Sizzle(expr, document, null, [elem]).length > 0; - }; - - Sizzle.contains = function (context, elem) { - // Set document vars if needed - if ((context.ownerDocument || context) !== document) { - setDocument(context); - } - - return contains(context, elem); - }; - - Sizzle.attr = function (elem, name) { - // Set document vars if needed - if ((elem.ownerDocument || elem) !== document) { - setDocument(elem); - } - - var fn = Expr.attrHandle[name.toLowerCase()], - // Don't get fooled by Object.prototype properties (jQuery #13807) - val = fn && hasOwn.call(Expr.attrHandle, name.toLowerCase()) ? fn(elem, name, !documentIsHTML) : undefined; - return val !== undefined ? val : support.attributes || !documentIsHTML ? elem.getAttribute(name) : (val = elem.getAttributeNode(name)) && val.specified ? val.value : null; - }; - - Sizzle.escape = function (sel) { - return (sel + "").replace(rcssescape, fcssescape); - }; - - Sizzle.error = function (msg) { - throw new Error("Syntax error, unrecognized expression: " + msg); - }; - /** - * Document sorting and removing duplicates - * @param {ArrayLike} results - */ - - - Sizzle.uniqueSort = function (results) { - var elem, - duplicates = [], - j = 0, - i = 0; // Unless we *know* we can detect duplicates, assume their presence - - hasDuplicate = !support.detectDuplicates; - sortInput = !support.sortStable && results.slice(0); - results.sort(sortOrder); - - if (hasDuplicate) { - while (elem = results[i++]) { - if (elem === results[i]) { - j = duplicates.push(i); - } - } - - while (j--) { - results.splice(duplicates[j], 1); - } - } // Clear input after sorting to release objects - // See https://github.com/jquery/sizzle/pull/225 - - - sortInput = null; - return results; - }; - /** - * Utility function for retrieving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ - - - getText = Sizzle.getText = function (elem) { - var node, - ret = "", - i = 0, - nodeType = elem.nodeType; - - if (!nodeType) { - // If no nodeType, this is expected to be an array - while (node = elem[i++]) { - // Do not traverse comment nodes - ret += getText(node); - } - } else if (nodeType === 1 || nodeType === 9 || nodeType === 11) { - // Use textContent for elements - // innerText usage removed for consistency of new lines (jQuery #11153) - if (typeof elem.textContent === "string") { - return elem.textContent; - } else { - // Traverse its children - for (elem = elem.firstChild; elem; elem = elem.nextSibling) { - ret += getText(elem); - } - } - } else if (nodeType === 3 || nodeType === 4) { - return elem.nodeValue; - } // Do not include comment or processing instruction nodes - - - return ret; - }; - - Expr = Sizzle.selectors = { - // Can be adjusted by the user - cacheLength: 50, - createPseudo: markFunction, - match: matchExpr, - attrHandle: {}, - find: {}, - relative: { - ">": { - dir: "parentNode", - first: true - }, - " ": { - dir: "parentNode" - }, - "+": { - dir: "previousSibling", - first: true - }, - "~": { - dir: "previousSibling" - } - }, - preFilter: { - "ATTR": function ATTR(match) { - match[1] = match[1].replace(runescape, funescape); // Move the given value to match[3] whether quoted or unquoted - - match[3] = (match[3] || match[4] || match[5] || "").replace(runescape, funescape); - - if (match[2] === "~=") { - match[3] = " " + match[3] + " "; - } - - return match.slice(0, 4); - }, - "CHILD": function CHILD(match) { - /* matches from matchExpr["CHILD"] - 1 type (only|nth|...) - 2 what (child|of-type) - 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) - 4 xn-component of xn+y argument ([+-]?\d*n|) - 5 sign of xn-component - 6 x of xn-component - 7 sign of y-component - 8 y of y-component - */ - match[1] = match[1].toLowerCase(); - - if (match[1].slice(0, 3) === "nth") { - // nth-* requires argument - if (!match[3]) { - Sizzle.error(match[0]); - } // numeric x and y parameters for Expr.filter.CHILD - // remember that false/true cast respectively to 0/1 - - - match[4] = +(match[4] ? match[5] + (match[6] || 1) : 2 * (match[3] === "even" || match[3] === "odd")); - match[5] = +(match[7] + match[8] || match[3] === "odd"); // other types prohibit arguments - } else if (match[3]) { - Sizzle.error(match[0]); - } - - return match; - }, - "PSEUDO": function PSEUDO(match) { - var excess, - unquoted = !match[6] && match[2]; - - if (matchExpr["CHILD"].test(match[0])) { - return null; - } // Accept quoted arguments as-is - - - if (match[3]) { - match[2] = match[4] || match[5] || ""; // Strip excess characters from unquoted arguments - } else if (unquoted && rpseudo.test(unquoted) && ( // Get excess from tokenize (recursively) - excess = tokenize(unquoted, true)) && ( // advance to the next closing parenthesis - excess = unquoted.indexOf(")", unquoted.length - excess) - unquoted.length)) { - // excess is a negative index - match[0] = match[0].slice(0, excess); - match[2] = unquoted.slice(0, excess); - } // Return only captures needed by the pseudo filter method (type and argument) - - - return match.slice(0, 3); - } - }, - filter: { - "TAG": function TAG(nodeNameSelector) { - var nodeName = nodeNameSelector.replace(runescape, funescape).toLowerCase(); - return nodeNameSelector === "*" ? function () { - return true; - } : function (elem) { - return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; - }; - }, - "CLASS": function CLASS(className) { - var pattern = classCache[className + " "]; - return pattern || (pattern = new RegExp("(^|" + whitespace + ")" + className + "(" + whitespace + "|$)")) && classCache(className, function (elem) { - return pattern.test(typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || ""); - }); - }, - "ATTR": function ATTR(name, operator, check) { - return function (elem) { - var result = Sizzle.attr(elem, name); - - if (result == null) { - return operator === "!="; - } - - if (!operator) { - return true; - } - - result += ""; - return operator === "=" ? result === check : operator === "!=" ? result !== check : operator === "^=" ? check && result.indexOf(check) === 0 : operator === "*=" ? check && result.indexOf(check) > -1 : operator === "$=" ? check && result.slice(-check.length) === check : operator === "~=" ? (" " + result.replace(rwhitespace, " ") + " ").indexOf(check) > -1 : operator === "|=" ? result === check || result.slice(0, check.length + 1) === check + "-" : false; - }; - }, - "CHILD": function CHILD(type, what, argument, first, last) { - var simple = type.slice(0, 3) !== "nth", - forward = type.slice(-4) !== "last", - ofType = what === "of-type"; - return first === 1 && last === 0 ? // Shortcut for :nth-*(n) - function (elem) { - return !!elem.parentNode; - } : function (elem, context, xml) { - var cache, - uniqueCache, - outerCache, - node, - nodeIndex, - start, - dir = simple !== forward ? "nextSibling" : "previousSibling", - parent = elem.parentNode, - name = ofType && elem.nodeName.toLowerCase(), - useCache = !xml && !ofType, - diff = false; - - if (parent) { - // :(first|last|only)-(child|of-type) - if (simple) { - while (dir) { - node = elem; - - while (node = node[dir]) { - if (ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1) { - return false; - } - } // Reverse direction for :only-* (if we haven't yet done so) - - - start = dir = type === "only" && !start && "nextSibling"; - } - - return true; - } - - start = [forward ? parent.firstChild : parent.lastChild]; // non-xml :nth-child(...) stores cache data on `parent` - - if (forward && useCache) { - // Seek `elem` from a previously-cached index - // ...in a gzip-friendly way - node = parent; - outerCache = node[expando] || (node[expando] = {}); // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - - uniqueCache = outerCache[node.uniqueID] || (outerCache[node.uniqueID] = {}); - cache = uniqueCache[type] || []; - nodeIndex = cache[0] === dirruns && cache[1]; - diff = nodeIndex && cache[2]; - node = nodeIndex && parent.childNodes[nodeIndex]; - - while (node = ++nodeIndex && node && node[dir] || ( // Fallback to seeking `elem` from the start - diff = nodeIndex = 0) || start.pop()) { - // When found, cache indexes on `parent` and break - if (node.nodeType === 1 && ++diff && node === elem) { - uniqueCache[type] = [dirruns, nodeIndex, diff]; - break; - } - } - } else { - // Use previously-cached element index if available - if (useCache) { - // ...in a gzip-friendly way - node = elem; - outerCache = node[expando] || (node[expando] = {}); // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - - uniqueCache = outerCache[node.uniqueID] || (outerCache[node.uniqueID] = {}); - cache = uniqueCache[type] || []; - nodeIndex = cache[0] === dirruns && cache[1]; - diff = nodeIndex; - } // xml :nth-child(...) - // or :nth-last-child(...) or :nth(-last)?-of-type(...) - - - if (diff === false) { - // Use the same loop as above to seek `elem` from the start - while (node = ++nodeIndex && node && node[dir] || (diff = nodeIndex = 0) || start.pop()) { - if ((ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1) && ++diff) { - // Cache the index of each encountered element - if (useCache) { - outerCache = node[expando] || (node[expando] = {}); // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - - uniqueCache = outerCache[node.uniqueID] || (outerCache[node.uniqueID] = {}); - uniqueCache[type] = [dirruns, diff]; - } - - if (node === elem) { - break; - } - } - } - } - } // Incorporate the offset, then check against cycle size - - - diff -= last; - return diff === first || diff % first === 0 && diff / first >= 0; - } - }; - }, - "PSEUDO": function PSEUDO(pseudo, argument) { - // pseudo-class names are case-insensitive - // http://www.w3.org/TR/selectors/#pseudo-classes - // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters - // Remember that setFilters inherits from pseudos - var args, - fn = Expr.pseudos[pseudo] || Expr.setFilters[pseudo.toLowerCase()] || Sizzle.error("unsupported pseudo: " + pseudo); // The user may use createPseudo to indicate that - // arguments are needed to create the filter function - // just as Sizzle does - - if (fn[expando]) { - return fn(argument); - } // But maintain support for old signatures - - - if (fn.length > 1) { - args = [pseudo, pseudo, "", argument]; - return Expr.setFilters.hasOwnProperty(pseudo.toLowerCase()) ? markFunction(function (seed, matches) { - var idx, - matched = fn(seed, argument), - i = matched.length; - - while (i--) { - idx = indexOf(seed, matched[i]); - seed[idx] = !(matches[idx] = matched[i]); - } - }) : function (elem) { - return fn(elem, 0, args); - }; - } - - return fn; - } - }, - pseudos: { - // Potentially complex pseudos - "not": markFunction(function (selector) { - // Trim the selector passed to compile - // to avoid treating leading and trailing - // spaces as combinators - var input = [], - results = [], - matcher = compile(selector.replace(rtrim, "$1")); - return matcher[expando] ? markFunction(function (seed, matches, context, xml) { - var elem, - unmatched = matcher(seed, null, xml, []), - i = seed.length; // Match elements unmatched by `matcher` - - while (i--) { - if (elem = unmatched[i]) { - seed[i] = !(matches[i] = elem); - } - } - }) : function (elem, context, xml) { - input[0] = elem; - matcher(input, null, xml, results); // Don't keep the element (issue #299) - - input[0] = null; - return !results.pop(); - }; - }), - "has": markFunction(function (selector) { - if (typeof selector === "string") { - Sizzle.compile(selector); - } - - return function (elem) { - return Sizzle(selector, elem).length > 0; - }; - }), - // Removed :contains pseudo-class declaration - // "Whether an element is represented by a :lang() selector - // is based solely on the element's language value - // being equal to the identifier C, - // or beginning with the identifier C immediately followed by "-". - // The matching of C against the element's language value is performed case-insensitively. - // The identifier C does not have to be a valid language name." - // http://www.w3.org/TR/selectors/#lang-pseudo - "lang": markFunction(function (lang) { - // lang value must be a valid identifier - if (!ridentifier.test(lang || "")) { - Sizzle.error("unsupported lang: " + lang); - } - - lang = lang.replace(runescape, funescape).toLowerCase(); - return function (elem) { - var elemLang; - - do { - if (elemLang = documentIsHTML ? elem.lang : elem.getAttribute("xml:lang") || elem.getAttribute("lang")) { - elemLang = elemLang.toLowerCase(); - return elemLang === lang || elemLang.indexOf(lang + "-") === 0; - } - } while ((elem = elem.parentNode) && elem.nodeType === 1); - - return false; - }; - }), - // Miscellaneous - "target": function target(elem) { - var hash = window.location && window.location.hash; - return hash && hash.slice(1) === elem.id; - }, - "root": function root(elem) { - return elem === docElem; - }, - "focus": function focus(elem) { - return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); - }, - // Boolean properties - "enabled": createDisabledPseudo(false), - "disabled": createDisabledPseudo(true), - "checked": function checked(elem) { - // In CSS3, :checked should return both checked and selected elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - var nodeName = elem.nodeName.toLowerCase(); - return nodeName === "input" && !!elem.checked || nodeName === "option" && !!elem.selected; - }, - "selected": function selected(elem) { - // Accessing this property makes selected-by-default - // options in Safari work properly - if (elem.parentNode) { - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - // Contents - "empty": function empty(elem) { - // http://www.w3.org/TR/selectors/#empty-pseudo - // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), - // but not by others (comment: 8; processing instruction: 7; etc.) - // nodeType < 6 works because attributes (2) do not appear as children - for (elem = elem.firstChild; elem; elem = elem.nextSibling) { - if (elem.nodeType < 6) { - return false; - } - } - - return true; - } // Removed custom pseudo-classes - - } - }; // Removed custom pseudo-classes - // Easy API for creating new setFilters - - function setFilters() {} - - setFilters.prototype = Expr.filters = Expr.pseudos; - Expr.setFilters = new setFilters(); - /** - * [AdGuard Patch]: - * Sorts the tokens in order to mitigate the performance issues caused by matching slow pseudos first: - * https://github.com/AdguardTeam/ExtendedCss/issues/55#issuecomment-364058745 - */ - - var sortTokenGroups = function () { - /** - * Splits compound selector into a list of simple selectors - * - * @param {*} tokens Tokens to split into groups - * @returns an array consisting of token groups (arrays) and relation tokens. - */ - var splitCompoundSelector = function splitCompoundSelector(tokens) { - var groups = []; - var currentTokensGroup = []; - var maxIdx = tokens.length - 1; - - for (var i = 0; i <= maxIdx; i++) { - var token = tokens[i]; - var relative = Sizzle.selectors.relative[token.type]; - - if (relative) { - groups.push(currentTokensGroup); - groups.push(token); - currentTokensGroup = []; - } else { - currentTokensGroup.push(token); - } - - if (i === maxIdx) { - groups.push(currentTokensGroup); - } - } - - return groups; - }; - - var TOKEN_TYPES_VALUES = { - // nth-child, etc, always go last - "CHILD": 100, - "ID": 90, - "CLASS": 80, - "TAG": 70, - "ATTR": 70, - "PSEUDO": 60 - }; - var POSITIONAL_PSEUDOS = ["nth", "first", "last", "eq", "even", "odd", "lt", "gt", "not"]; - /** - * A function that defines the sort order. - * Returns a value lesser than 0 if "left" is less than "right". - */ - - var compareFunction = function compareFunction(left, right) { - var leftValue = TOKEN_TYPES_VALUES[left.type]; - var rightValue = TOKEN_TYPES_VALUES[right.type]; - return leftValue - rightValue; - }; - /** - * Checks if the specified tokens group is sortable. - * We do not re-sort tokens in case of any positional or child pseudos in the group - */ - - - var isSortable = function isSortable(tokens) { - var iTokens = tokens.length; - - while (iTokens--) { - var token = tokens[iTokens]; - - if (token.type === "PSEUDO" && POSITIONAL_PSEUDOS.indexOf(token.matches[0]) !== -1) { - return false; - } - - if (token.type === "CHILD") { - return false; - } - } - - return true; - }; - /** - * Sorts the tokens in order to mitigate the issues caused by the left-to-right matching. - * The idea is change the tokens order so that Sizzle was matching fast selectors first (id, class), - * and slow selectors after that (and here I mean our slow custom pseudo classes). - * - * @param {Array} tokens An array of tokens to sort - * @returns {Array} A new re-sorted array - */ - - - var sortTokens = function sortTokens(tokens) { - if (!tokens || tokens.length === 1) { - return tokens; - } - - var sortedTokens = []; - var groups = splitCompoundSelector(tokens); - - for (var i = 0; i < groups.length; i++) { - var group = groups[i]; - - if (group instanceof Array) { - if (isSortable(group)) { - group.sort(compareFunction); - } - - sortedTokens = sortedTokens.concat(group); - } else { - sortedTokens.push(group); - } - } - - return sortedTokens; - }; - /** - * Sorts every tokens array inside of the specified "groups" array. - * See "sortTokens" methods for more information on how tokens are sorted. - * - * @param {Array} groups An array of tokens arrays. - * @returns {Array} A new array that consists of the same tokens arrays after sorting - */ - - - var sortTokenGroups = function sortTokenGroups(groups) { - var sortedGroups = []; - var len = groups.length; - var i = 0; - - for (; i < len; i++) { - sortedGroups.push(sortTokens(groups[i])); - } - - return sortedGroups; - }; // Expose - - - return sortTokenGroups; - }(); - /** - * Creates custom policy to use TrustedTypes CSP policy - * https://w3c.github.io/webappsec-trusted-types/dist/spec/ - */ - - - var AGPolicy = function createPolicy() { - var defaultPolicy = { - createHTML: function createHTML(input) { - return input; - }, - createScript: function createScript(input) { - return input; - }, - createScriptURL: function createScriptURL(input) { - return input; - } - }; - - if (window.trustedTypes && window.trustedTypes.createPolicy) { - return window.trustedTypes.createPolicy("AGPolicy", defaultPolicy); - } - - return defaultPolicy; - }(); - /** - * [AdGuard Patch]: - * Removes trailing spaces from the tokens list - * - * @param {*} tokens An array of Sizzle tokens to post-process - */ - - - function removeTrailingSpaces(tokens) { - var iTokens = tokens.length; - - while (iTokens--) { - var token = tokens[iTokens]; - - if (token.type === " ") { - tokens.length = iTokens; - } else { - break; - } - } - } - /** - * [AdGuard Patch]: - * An object with the information about selectors and their token representation - * @typedef {{selectorText: string, groups: Array}} SelectorData - * @property {string} selectorText A CSS selector text - * @property {Array} groups An array of token groups corresponding to that selector - */ - - /** - * [AdGuard Patch]: - * This method processes parsed token groups, divides them into a number of selectors - * and makes sure that each selector's tokens are cached properly in Sizzle. - * - * @param {*} groups Token groups (see {@link Sizzle.tokenize}) - * @returns {Array.} An array of selectors data we got from the groups - */ - - - function tokenGroupsToSelectors(groups) { - // Remove trailing spaces which we can encounter in tolerant mode - // We're doing it in tolerant mode only as this is the only case when - // encountering trailing spaces is expected - removeTrailingSpaces(groups[groups.length - 1]); // We need sorted tokens to make cache work properly - - var sortedGroups = sortTokenGroups(groups); - var selectors = []; - - for (var i = 0; i < groups.length; i++) { - var tokenGroups = groups[i]; - var selectorText = toSelector(tokenGroups); - selectors.push({ - // Sizzle expects an array of token groups when compiling a selector - groups: [tokenGroups], - selectorText: selectorText - }); // Now make sure that selector tokens are cached - - var tokensCacheItem = { - groups: tokenGroups, - sortedGroups: [sortedGroups[i]] - }; - tokenCache(selectorText, tokensCacheItem); - } - - return selectors; - } - /** - * [AdGuard Patch]: - * Add an additional argument for Sizzle.tokenize which indicates that it - * should not throw on invalid tokens, and instead should return tokens - * that it has produced so far. - * - * One more additional argument that allow to choose if you want to receive sorted or unsorted tokens - * The problem is that the re-sorted selectors are valid for Sizzle, but not for the browser. - * options.returnUnsorted -- return unsorted tokens if true. - * options.cacheOnly -- return cached result only. Required for unit-tests. - * - * @param {*} options Optional configuration object with two additional flags - * (options.tolerant, options.returnUnsorted, options.cacheOnly) -- see patches #5 and #6 notes - */ - - - tokenize = Sizzle.tokenize = function (selector, parseOnly, options) { - var matched, - match, - tokens, - type, - soFar, - groups, - preFilters, - cached = tokenCache[selector + " "]; - var tolerant = options && options.tolerant; - var returnUnsorted = options && options.returnUnsorted; - var cacheOnly = options && options.cacheOnly; - - if (cached) { - if (parseOnly) { - return 0; - } else { - return (returnUnsorted ? cached.groups : cached.sortedGroups).slice(0); - } - } - - if (cacheOnly) { - return null; - } - - soFar = selector; - groups = []; - preFilters = Expr.preFilter; - - while (soFar) { - // Comma and first run - if (!matched || (match = rcomma.exec(soFar))) { - if (match) { - // Don't consume trailing commas as valid - soFar = soFar.slice(match[0].length) || soFar; - } - - groups.push(tokens = []); - } - - matched = false; // Combinators - - if (match = rcombinators.exec(soFar)) { - matched = match.shift(); - tokens.push({ - value: matched, - // Cast descendant combinators to space - type: match[0].replace(rtrim, " ") - }); - soFar = soFar.slice(matched.length); - } // Filters - - - for (type in Expr.filter) { - if ((match = matchExpr[type].exec(soFar)) && (!preFilters[type] || (match = preFilters[type](match)))) { - matched = match.shift(); - tokens.push({ - value: matched, - type: type, - matches: match - }); - soFar = soFar.slice(matched.length); - } - } - - if (!matched) { - break; - } - } // Return the length of the invalid excess - // if we're just parsing - // Otherwise, throw an error or return tokens - - - var invalidLen = soFar.length; - - if (parseOnly) { - return invalidLen; - } - - if (invalidLen !== 0 && !tolerant) { - Sizzle.error(selector); // Throws an error. - } - - if (tolerant) { - /** - * [AdGuard Patch]: - * In tolerant mode we return a special object that constists of - * an array of parsed selectors (and their tokens) and a "nextIndex" field - * that points to an index after which we're not able to parse selectors farther. - */ - var nextIndex = selector.length - invalidLen; - var selectors = tokenGroupsToSelectors(groups); - return { - selectors: selectors, - nextIndex: nextIndex - }; - } - /** [AdGuard Patch]: Sorting tokens */ - - - var sortedGroups = sortTokenGroups(groups); - /** [AdGuard Patch]: Change the way tokens are cached */ - - var tokensCacheItem = { - groups: groups, - sortedGroups: sortedGroups - }; - tokensCacheItem = tokenCache(selector, tokensCacheItem); - return (returnUnsorted ? tokensCacheItem.groups : tokensCacheItem.sortedGroups).slice(0); - }; - - function toSelector(tokens) { - var i = 0, - len = tokens.length, - selector = ""; - - for (; i < len; i++) { - selector += tokens[i].value; - } - - return selector; - } - - function addCombinator(matcher, combinator, base) { - var dir = combinator.dir, - skip = combinator.next, - key = skip || dir, - checkNonElements = base && key === "parentNode", - doneName = done++; - return combinator.first ? // Check against closest ancestor/preceding element - function (elem, context, xml) { - while (elem = elem[dir]) { - if (elem.nodeType === 1 || checkNonElements) { - return matcher(elem, context, xml); - } - } - - return false; - } : // Check against all ancestor/preceding elements - function (elem, context, xml) { - var oldCache, - uniqueCache, - outerCache, - newCache = [dirruns, doneName]; // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching - - if (xml) { - while (elem = elem[dir]) { - if (elem.nodeType === 1 || checkNonElements) { - if (matcher(elem, context, xml)) { - return true; - } - } - } - } else { - while (elem = elem[dir]) { - if (elem.nodeType === 1 || checkNonElements) { - outerCache = elem[expando] || (elem[expando] = {}); // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - - uniqueCache = outerCache[elem.uniqueID] || (outerCache[elem.uniqueID] = {}); - - if (skip && skip === elem.nodeName.toLowerCase()) { - elem = elem[dir] || elem; - } else if ((oldCache = uniqueCache[key]) && oldCache[0] === dirruns && oldCache[1] === doneName) { - // Assign to newCache so results back-propagate to previous elements - return newCache[2] = oldCache[2]; - } else { - // Reuse newcache so results back-propagate to previous elements - uniqueCache[key] = newCache; // A match means we're done; a fail means we have to keep checking - - if (newCache[2] = matcher(elem, context, xml)) { - return true; - } - } - } - } - } - - return false; - }; - } - - function elementMatcher(matchers) { - return matchers.length > 1 ? function (elem, context, xml) { - var i = matchers.length; - - while (i--) { - if (!matchers[i](elem, context, xml)) { - return false; - } - } - - return true; - } : matchers[0]; - } - - function multipleContexts(selector, contexts, results) { - var i = 0, - len = contexts.length; - - for (; i < len; i++) { - Sizzle(selector, contexts[i], results); - } - - return results; - } - - function condense(unmatched, map, filter, context, xml) { - var elem, - newUnmatched = [], - i = 0, - len = unmatched.length, - mapped = map != null; - - for (; i < len; i++) { - if (elem = unmatched[i]) { - if (!filter || filter(elem, context, xml)) { - newUnmatched.push(elem); - - if (mapped) { - map.push(i); - } - } - } - } - - return newUnmatched; - } - - function setMatcher(preFilter, selector, matcher, postFilter, postFinder, postSelector) { - if (postFilter && !postFilter[expando]) { - postFilter = setMatcher(postFilter); - } - - if (postFinder && !postFinder[expando]) { - postFinder = setMatcher(postFinder, postSelector); - } - - return markFunction(function (seed, results, context, xml) { - var temp, - i, - elem, - preMap = [], - postMap = [], - preexisting = results.length, - // Get initial elements from seed or context - elems = seed || multipleContexts(selector || "*", context.nodeType ? [context] : context, []), - // Prefilter to get matcher input, preserving a map for seed-results synchronization - matcherIn = preFilter && (seed || !selector) ? condense(elems, preMap, preFilter, context, xml) : elems, - matcherOut = matcher ? // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, - postFinder || (seed ? preFilter : preexisting || postFilter) ? // ...intermediate processing is necessary - [] : // ...otherwise use results directly - results : matcherIn; // Find primary matches - - if (matcher) { - matcher(matcherIn, matcherOut, context, xml); - } // Apply postFilter - - - if (postFilter) { - temp = condense(matcherOut, postMap); - postFilter(temp, [], context, xml); // Un-match failing elements by moving them back to matcherIn - - i = temp.length; - - while (i--) { - if (elem = temp[i]) { - matcherOut[postMap[i]] = !(matcherIn[postMap[i]] = elem); - } - } - } - - if (seed) { - if (postFinder || preFilter) { - if (postFinder) { - // Get the final matcherOut by condensing this intermediate into postFinder contexts - temp = []; - i = matcherOut.length; - - while (i--) { - if (elem = matcherOut[i]) { - // Restore matcherIn since elem is not yet a final match - temp.push(matcherIn[i] = elem); - } - } - - postFinder(null, matcherOut = [], temp, xml); - } // Move matched elements from seed to results to keep them synchronized - - - i = matcherOut.length; - - while (i--) { - if ((elem = matcherOut[i]) && (temp = postFinder ? indexOf(seed, elem) : preMap[i]) > -1) { - seed[temp] = !(results[temp] = elem); - } - } - } // Add elements to results, through postFinder if defined - - } else { - matcherOut = condense(matcherOut === results ? matcherOut.splice(preexisting, matcherOut.length) : matcherOut); - - if (postFinder) { - postFinder(null, results, matcherOut, xml); - } else { - push.apply(results, matcherOut); - } - } - }); - } - - function matcherFromTokens(tokens) { - var checkContext, - matcher, - j, - len = tokens.length, - leadingRelative = Expr.relative[tokens[0].type], - implicitRelative = leadingRelative || Expr.relative[" "], - i = leadingRelative ? 1 : 0, - // The foundational matcher ensures that elements are reachable from top-level context(s) - matchContext = addCombinator(function (elem) { - return elem === checkContext; - }, implicitRelative, true), - matchAnyContext = addCombinator(function (elem) { - return indexOf(checkContext, elem) > -1; - }, implicitRelative, true), - matchers = [function (elem, context, xml) { - var ret = !leadingRelative && (xml || context !== outermostContext) || ((checkContext = context).nodeType ? matchContext(elem, context, xml) : matchAnyContext(elem, context, xml)); // Avoid hanging onto element (issue #299) - - checkContext = null; - return ret; - }]; - - for (; i < len; i++) { - if (matcher = Expr.relative[tokens[i].type]) { - matchers = [addCombinator(elementMatcher(matchers), matcher)]; - } else { - matcher = Expr.filter[tokens[i].type].apply(null, tokens[i].matches); // Return special upon seeing a positional matcher - - if (matcher[expando]) { - // Find the next relative operator (if any) for proper handling - j = ++i; - - for (; j < len; j++) { - if (Expr.relative[tokens[j].type]) { - break; - } - } - - return setMatcher(i > 1 && elementMatcher(matchers), i > 1 && toSelector( // If the preceding token was a descendant combinator, insert an implicit any-element `*` - tokens.slice(0, i - 1).concat({ - value: tokens[i - 2].type === " " ? "*" : "" - })).replace(rtrim, "$1"), matcher, i < j && matcherFromTokens(tokens.slice(i, j)), j < len && matcherFromTokens(tokens = tokens.slice(j)), j < len && toSelector(tokens)); - } - - matchers.push(matcher); - } - } - - return elementMatcher(matchers); - } - - function matcherFromGroupMatchers(elementMatchers, setMatchers) { - var bySet = setMatchers.length > 0, - byElement = elementMatchers.length > 0, - superMatcher = function superMatcher(seed, context, xml, results, outermost) { - var elem, - j, - matcher, - matchedCount = 0, - i = "0", - unmatched = seed && [], - setMatched = [], - contextBackup = outermostContext, - // We must always have either seed elements or outermost context - elems = seed || byElement && Expr.find["TAG"]("*", outermost), - // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = dirruns += contextBackup == null ? 1 : Math.random() || 0.1, - len = elems.length; - - if (outermost) { - outermostContext = context === document || context || outermost; - } // Add elements passing elementMatchers directly to results - // Support: IE<9, Safari - // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id - - - for (; i !== len && (elem = elems[i]) != null; i++) { - if (byElement && elem) { - j = 0; - - if (!context && elem.ownerDocument !== document) { - setDocument(elem); - xml = !documentIsHTML; - } - - while (matcher = elementMatchers[j++]) { - if (matcher(elem, context || document, xml)) { - results.push(elem); - break; - } - } - - if (outermost) { - dirruns = dirrunsUnique; - } - } // Track unmatched elements for set filters - - - if (bySet) { - // They will have gone through all possible matchers - if (elem = !matcher && elem) { - matchedCount--; - } // Lengthen the array for every element, matched or not - - - if (seed) { - unmatched.push(elem); - } - } - } // `i` is now the count of elements visited above, and adding it to `matchedCount` - // makes the latter nonnegative. - - - matchedCount += i; // Apply set filters to unmatched elements - // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` - // equals `i`), unless we didn't visit _any_ elements in the above loop because we have - // no element matchers and no seed. - // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that - // case, which will result in a "00" `matchedCount` that differs from `i` but is also - // numerically zero. - - if (bySet && i !== matchedCount) { - j = 0; - - while (matcher = setMatchers[j++]) { - matcher(unmatched, setMatched, context, xml); - } - - if (seed) { - // Reintegrate element matches to eliminate the need for sorting - if (matchedCount > 0) { - while (i--) { - if (!(unmatched[i] || setMatched[i])) { - setMatched[i] = pop.call(results); - } - } - } // Discard index placeholder values to get only actual matches - - - setMatched = condense(setMatched); - } // Add matches to results - - - push.apply(results, setMatched); // Seedless set matches succeeding multiple successful matchers stipulate sorting - - if (outermost && !seed && setMatched.length > 0 && matchedCount + setMatchers.length > 1) { - Sizzle.uniqueSort(results); - } - } // Override manipulation of globals by nested matchers - - - if (outermost) { - dirruns = dirrunsUnique; - outermostContext = contextBackup; - } - - return unmatched; - }; - - return bySet ? markFunction(superMatcher) : superMatcher; - } - - compile = Sizzle.compile = function (selector, match - /* Internal Use Only */ - ) { - var i, - setMatchers = [], - elementMatchers = [], - cached = compilerCache[selector + " "]; - - if (!cached) { - // Generate a function of recursive functions that can be used to check each element - if (!match) { - match = tokenize(selector); - } - - i = match.length; - - while (i--) { - cached = matcherFromTokens(match[i]); - - if (cached[expando]) { - setMatchers.push(cached); - } else { - elementMatchers.push(cached); - } - } // Cache the compiled function - - - cached = compilerCache(selector, matcherFromGroupMatchers(elementMatchers, setMatchers)); // Save selector and tokenization - - cached.selector = selector; - } - - return cached; - }; - /** - * A low-level selection function that works with Sizzle's compiled - * selector functions - * @param {String|Function} selector A selector or a pre-compiled - * selector function built with Sizzle.compile - * @param {Element} context - * @param {Array} [results] - * @param {Array} [seed] A set of elements to match against - */ - - - select = Sizzle.select = function (selector, context, results, seed) { - var i, - tokens, - token, - type, - find, - compiled = typeof selector === "function" && selector, - match = !seed && tokenize(selector = compiled.selector || selector); - results = results || []; // Try to minimize operations if there is only one selector in the list and no seed - // (the latter of which guarantees us context) - - if (match.length === 1) { - // Reduce context if the leading compound selector is an ID - tokens = match[0] = match[0].slice(0); - - if (tokens.length > 2 && (token = tokens[0]).type === "ID" && context.nodeType === 9 && documentIsHTML && Expr.relative[tokens[1].type]) { - context = (Expr.find["ID"](token.matches[0].replace(runescape, funescape), context) || [])[0]; - - if (!context) { - return results; // Precompiled matchers will still verify ancestry, so step up a level - } else if (compiled) { - context = context.parentNode; - } - - selector = selector.slice(tokens.shift().value.length); - } // Fetch a seed set for right-to-left matching - - - i = matchExpr["needsContext"].test(selector) ? 0 : tokens.length; - - while (i--) { - token = tokens[i]; // Abort if we hit a combinator - - if (Expr.relative[type = token.type]) { - break; - } - - if (find = Expr.find[type]) { - // Search, expanding context for leading sibling combinators - if (seed = find(token.matches[0].replace(runescape, funescape), rsibling.test(tokens[0].type) && testContext(context.parentNode) || context)) { - // If seed is empty or no tokens remain, we can return early - tokens.splice(i, 1); - selector = seed.length && toSelector(tokens); - - if (!selector) { - push.apply(results, seed); - return results; - } - - break; - } - } - } - } // Compile and execute a filtering function if one is not provided - // Provide `match` to avoid retokenization if we modified the selector above - - - (compiled || compile(selector, match))(seed, context, !documentIsHTML, results, !context || rsibling.test(selector) && testContext(context.parentNode) || context); - return results; - }; // One-time assignments - // Sort stability - - - support.sortStable = expando.split("").sort(sortOrder).join("") === expando; // Support: Chrome 14-35+ - // Always assume duplicates if they aren't passed to the comparison function - - support.detectDuplicates = !!hasDuplicate; // Initialize against the default document - - setDocument(); // Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) - // Detached nodes confoundingly follow *each other* - - support.sortDetached = assert(function (el) { - // Should return 1, but returns 4 (following) - return el.compareDocumentPosition(document.createElement("fieldset")) & 1; - }); // Support: IE<8 - // Prevent attribute/property "interpolation" - // https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx - - if (!assert(function (el) { - el.innerHTML = AGPolicy.createHTML(""); - return el.firstChild.getAttribute("href") === "#"; - })) { - addHandle("type|href|height|width", function (elem, name, isXML) { - if (!isXML) { - return elem.getAttribute(name, name.toLowerCase() === "type" ? 1 : 2); - } - }); - } // Support: IE<9 - // Use defaultValue in place of getAttribute("value") - - - if (!support.attributes || !assert(function (el) { - el.innerHTML = AGPolicy.createHTML(""); - el.firstChild.setAttribute("value", ""); - return el.firstChild.getAttribute("value") === ""; - })) { - addHandle("value", function (elem, name, isXML) { - if (!isXML && elem.nodeName.toLowerCase() === "input") { - return elem.defaultValue; - } - }); - } // Support: IE<9 - // Use getAttributeNode to fetch booleans when getAttribute lies - - - if (!assert(function (el) { - return el.getAttribute("disabled") == null; - })) { - addHandle(booleans, function (elem, name, isXML) { - var val; - - if (!isXML) { - return elem[name] === true ? name.toLowerCase() : (val = elem.getAttributeNode(name)) && val.specified ? val.value : null; - } - }); - } // EXPOSE - // Do not expose Sizzle to the global scope in the case of AdGuard ExtendedCss build - - - return Sizzle; // EXPOSE - }(window); //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - } - - return Sizzle; -}; - -/* jshint ignore:end */ - -/** - * Copyright 2016 Adguard Software Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - * Class that extends Sizzle and adds support for "matches-css" pseudo element. - */ - -var StylePropertyMatcher = function (window) { - var isPhantom = !!window._phantom; - var useFallback = isPhantom && !!window.getMatchedCSSRules; - /** - * Unquotes specified value - * Webkit-based browsers singlequotes content property values - * Other browsers doublequotes content property values. - */ - - var removeContentQuotes = function removeContentQuotes(value) { - if (typeof value === 'string') { - return value.replace(/^(["'])([\s\S]*)\1$/, '$2'); - } - - return value; - }; - - var getComputedStyle = window.getComputedStyle.bind(window); - var getMatchedCSSRules = useFallback ? window.getMatchedCSSRules.bind(window) : null; - /** - * There is an issue in browsers based on old webkit: - * getComputedStyle(el, ":before") is empty if element is not visible. - * - * To circumvent this issue we use getMatchedCSSRules instead. - * - * It appears that getMatchedCSSRules sorts the CSS rules - * in increasing order of specifities of corresponding selectors. - * We pick the css rule that is being applied to an element based on this assumption. - * - * @param element DOM node - * @param pseudoElement Optional pseudoElement name - * @param propertyName CSS property name - */ - - var getComputedStylePropertyValue = function getComputedStylePropertyValue(element, pseudoElement, propertyName) { - var value = ''; - - if (useFallback && pseudoElement) { - var cssRules = getMatchedCSSRules(element, pseudoElement) || []; - var i = cssRules.length; - - while (i-- > 0 && !value) { - value = cssRules[i].style.getPropertyValue(propertyName); - } - } else { - var style = getComputedStyle(element, pseudoElement); - - if (style) { - value = style.getPropertyValue(propertyName); // https://bugs.webkit.org/show_bug.cgi?id=93445 - - if (propertyName === 'opacity' && utils.isSafariBrowser) { - value = (Math.round(parseFloat(value) * 100) / 100).toString(); - } - } - } - - if (propertyName === 'content') { - value = removeContentQuotes(value); - } - - return value; - }; - /** - * Adds url parameter quotes for non-regex pattern - * @param {string} pattern - */ - - - var addUrlQuotes = function addUrlQuotes(pattern) { - // for regex patterns - if (pattern[0] === '/' && pattern[pattern.length - 1] === '/' && pattern.indexOf('\\"') < 10) { - // e.g. /^url\\([a-z]{4}:[a-z]{5}/ - // or /^url\\(data\\:\\image\\/gif;base64.+/ - var re = /(\^)?url(\\)?\\\((\w|\[\w)/g; - return pattern.replace(re, '$1url$2\\\(\\"?$3'); - } // for non-regex patterns - - - if (pattern.indexOf('url("') === -1) { - var _re = /url\((.*?)\)/g; - return pattern.replace(_re, 'url("$1")'); - } - - return pattern; - }; - /** - * Class that matches element style against the specified expression - * @member {string} propertyName - * @member {string} pseudoElement - * @member {RegExp} regex - */ - - - var Matcher = function Matcher(propertyFilter, pseudoElement) { - this.pseudoElement = pseudoElement; - - try { - var index = propertyFilter.indexOf(':'); - this.propertyName = propertyFilter.substring(0, index).trim(); - var pattern = propertyFilter.substring(index + 1).trim(); - pattern = addUrlQuotes(pattern); // Unescaping pattern - // For non-regex patterns, (,),[,] should be unescaped, because we require escaping them in filter rules. - // For regex patterns, ",\ should be escaped, because we manually escape those in extended-css-selector.js. - - if (/^\/.*\/$/.test(pattern)) { - pattern = pattern.slice(1, -1); - this.regex = utils.pseudoArgToRegex(pattern); - } else { - pattern = pattern.replace(/\\([\\()[\]"])/g, '$1'); - this.regex = utils.createURLRegex(pattern); - } - } catch (ex) { - utils.logError("StylePropertyMatcher: invalid match string ".concat(propertyFilter)); - } - }; - /** - * Function to check if element CSS property matches filter pattern - * @param {Element} element to check - */ - - - Matcher.prototype.matches = function (element) { - if (!this.regex || !this.propertyName) { - return false; - } - - var value = getComputedStylePropertyValue(element, this.pseudoElement, this.propertyName); - return value && this.regex.test(value); - }; - /** - * Creates a new pseudo-class and registers it in Sizzle - */ - - - var extendSizzle = function extendSizzle(sizzle) { - // First of all we should prepare Sizzle engine - sizzle.selectors.pseudos['matches-css'] = sizzle.selectors.createPseudo(function (propertyFilter) { - var matcher = new Matcher(propertyFilter); - return function (element) { - return matcher.matches(element); - }; - }); - sizzle.selectors.pseudos['matches-css-before'] = sizzle.selectors.createPseudo(function (propertyFilter) { - var matcher = new Matcher(propertyFilter, ':before'); - return function (element) { - return matcher.matches(element); - }; - }); - sizzle.selectors.pseudos['matches-css-after'] = sizzle.selectors.createPseudo(function (propertyFilter) { - var matcher = new Matcher(propertyFilter, ':after'); - return function (element) { - return matcher.matches(element); - }; - }); - }; // EXPOSE - - - return { - extendSizzle: extendSizzle - }; -}(window); - -/** - * Copyright 2016 Adguard Software Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -var matcherUtils = {}; -matcherUtils.MutationObserver = window.MutationObserver || window.WebKitMutationObserver; -/** - * Parses argument of matcher pseudo (for matches-attr and matches-property) - * @param {string} matcherFilter argument of pseudo class - * @returns {Array} - */ - -matcherUtils.parseMatcherFilter = function (matcherFilter) { - var FULL_MATCH_MARKER = '"="'; - var rawArgs = []; - - if (matcherFilter.indexOf(FULL_MATCH_MARKER) === -1) { - // if there is only one pseudo arg - // e.g. :matches-attr("data-name") or :matches-property("inner.prop") - // Sizzle will parse it and get rid of quotes - // so it might be valid arg already without them - rawArgs.push(matcherFilter); - } else { - matcherFilter.split('=').forEach(function (arg) { - if (arg[0] === '"' && arg[arg.length - 1] === '"') { - rawArgs.push(arg.slice(1, -1)); - } - }); - } - - return rawArgs; -}; -/** - * @typedef {Object} ArgData - * @property {string} arg - * @property {boolean} isRegexp - */ - -/** - * Parses raw matcher arg - * @param {string} rawArg - * @returns {ArgData} - */ - - -matcherUtils.parseRawMatcherArg = function (rawArg) { - var arg = rawArg; - var isRegexp = !!rawArg && rawArg[0] === '/' && rawArg[rawArg.length - 1] === '/'; - - if (isRegexp) { - // to avoid at least such case — :matches-property("//") - if (rawArg.length > 2) { - arg = utils.toRegExp(rawArg); - } else { - throw new Error("Invalid regexp: ".concat(rawArg)); - } - } - - return { - arg: arg, - isRegexp: isRegexp - }; -}; -/** - * @typedef Chain - * @property {Object} base - * @property {string} prop - * @property {string} value - */ - -/** - * Checks if the property exists in the base object (recursively). - * @param {Object} base - * @param {ArgData[]} chain array of objects - parsed string property chain - * @param {Array} [output=[]] result acc - * @returns {Chain[]} array of objects - */ - - -matcherUtils.filterRootsByRegexpChain = function (base, chain) { - var output = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : []; - var tempProp = chain[0]; - - if (chain.length === 1) { - // eslint-disable-next-line no-restricted-syntax - for (var key in base) { - if (tempProp.isRegexp) { - if (tempProp.arg.test(key)) { - output.push({ - base: base, - prop: key, - value: base[key] - }); - } - } else if (tempProp.arg === key) { - output.push({ - base: base, - prop: tempProp.arg, - value: base[key] - }); - } - } - - return output; - } // if there is a regexp prop in input chain - // e.g. 'unit./^ad.+/.src' for 'unit.ad-1gf2.src unit.ad-fgd34.src'), - // every base keys should be tested by regexp and it can be more that one results - - - if (tempProp.isRegexp) { - var nextProp = chain.slice(1); - var baseKeys = []; // eslint-disable-next-line no-restricted-syntax - - for (var _key in base) { - if (tempProp.arg.test(_key)) { - baseKeys.push(_key); - } - } - - baseKeys.forEach(function (key) { - var item = base[key]; - matcherUtils.filterRootsByRegexpChain(item, nextProp, output); - }); - } // avoid TypeError while accessing to null-prop's child - - - if (base === null) { - return; - } - - var nextBase = base[tempProp.arg]; - chain = chain.slice(1); - - if (nextBase !== undefined) { - matcherUtils.filterRootsByRegexpChain(nextBase, chain, output); - } - - return output; -}; -/** - * Validates parsed args of matches-property pseudo - * @param {...ArgData} args - */ - - -matcherUtils.validatePropMatcherArgs = function () { - for (var _len = arguments.length, args = new Array(_len), _key2 = 0; _key2 < _len; _key2++) { - args[_key2] = arguments[_key2]; - } - - for (var i = 0; i < args.length; i += 1) { - if (args[i].isRegexp) { - if (!utils.startsWith(args[i].arg.toString(), '/') || !utils.endsWith(args[i].arg.toString(), '/')) { - return false; - } // simple arg check if it is not a regexp - - } else if (!/^[\w-]+$/.test(args[i].arg)) { - return false; - } - } - - return true; -}; - -/** - * Class that extends Sizzle and adds support for "matches-attr" pseudo element. - */ - -var AttributesMatcher = function () { - /** - * Class that matches element attributes against the specified expressions - * @param {ArgData} nameArg - parsed name argument - * @param {ArgData} valueArg - parsed value argument - * @param {string} pseudoElement - * @constructor - * - * @member {string|RegExp} attrName - * @member {boolean} isRegexpName - * @member {string|RegExp} attrValue - * @member {boolean} isRegexpValue - */ - var AttrMatcher = function AttrMatcher(nameArg, valueArg, pseudoElement) { - this.pseudoElement = pseudoElement; - this.attrName = nameArg.arg; - this.isRegexpName = nameArg.isRegexp; - this.attrValue = valueArg.arg; - this.isRegexpValue = valueArg.isRegexp; - }; - /** - * Function to check if element attributes matches filter pattern - * @param {Element} element to check - */ - - - AttrMatcher.prototype.matches = function (element) { - var elAttrs = element.attributes; - - if (elAttrs.length === 0 || !this.attrName) { - return false; - } - - var i = 0; - - while (i < elAttrs.length) { - var attr = elAttrs[i]; - var matched = false; - var attrNameMatched = this.isRegexpName ? this.attrName.test(attr.name) : this.attrName === attr.name; - - if (!this.attrValue) { - // for :matches-attr("/regex/") or :matches-attr("attr-name") - matched = attrNameMatched; - } else { - var attrValueMatched = this.isRegexpValue ? this.attrValue.test(attr.value) : this.attrValue === attr.value; - matched = attrNameMatched && attrValueMatched; - } - - if (matched) { - return true; - } - - i += 1; - } - }; - /** - * Creates a new pseudo-class and registers it in Sizzle - */ - - - var extendSizzle = function extendSizzle(sizzle) { - // First of all we should prepare Sizzle engine - sizzle.selectors.pseudos['matches-attr'] = sizzle.selectors.createPseudo(function (attrFilter) { - var _matcherUtils$parseMa = matcherUtils.parseMatcherFilter(attrFilter), - _matcherUtils$parseMa2 = _slicedToArray(_matcherUtils$parseMa, 2), - rawName = _matcherUtils$parseMa2[0], - rawValue = _matcherUtils$parseMa2[1]; - - var nameArg = matcherUtils.parseRawMatcherArg(rawName); - var valueArg = matcherUtils.parseRawMatcherArg(rawValue); - - if (!attrFilter || !matcherUtils.validatePropMatcherArgs(nameArg, valueArg)) { - throw new Error("Invalid argument of :matches-attr pseudo class: ".concat(attrFilter)); - } - - var matcher = new AttrMatcher(nameArg, valueArg); - return function (element) { - return matcher.matches(element); - }; - }); - }; // EXPOSE - - - return { - extendSizzle: extendSizzle - }; -}(); - -/** - * Parses raw property arg - * @param {string} input - * @returns {ArgData[]} array of objects - */ - -var parseRawPropChain = function parseRawPropChain(input) { - var PROPS_DIVIDER = '.'; - var REGEXP_MARKER = '/'; - var propsArr = []; - var str = input; - - while (str.length > 0) { - if (utils.startsWith(str, PROPS_DIVIDER)) { - // for cases like '.prop.id' and 'nested..test' - throw new Error("Invalid chain property: ".concat(input)); - } - - if (!utils.startsWith(str, REGEXP_MARKER)) { - var isRegexp = false; - var dividerIndex = str.indexOf(PROPS_DIVIDER); - - if (str.indexOf(PROPS_DIVIDER) === -1) { - // if there is no '.' left in str - // take the rest of str as prop - propsArr.push({ - arg: str, - isRegexp: isRegexp - }); - return propsArr; - } // else take prop from str - - - var prop = str.slice(0, dividerIndex); // for cases like 'asadf.?+/.test' - - if (prop.indexOf(REGEXP_MARKER) > -1) { - // prop is '?+/' - throw new Error("Invalid chain property: ".concat(prop)); - } - - propsArr.push({ - arg: prop, - isRegexp: isRegexp - }); // delete prop from str - - str = str.slice(dividerIndex); - } else { - // deal with regexp - var propChunks = []; - propChunks.push(str.slice(0, 1)); // if str starts with '/', delete it from str and find closing regexp slash. - // note that chained property name can not include '/' or '.' - // so there is no checking for escaped characters - - str = str.slice(1); - var regexEndIndex = str.indexOf(REGEXP_MARKER); - - if (regexEndIndex < 1) { - // regexp should be at least === '/./' - // so we should avoid args like '/id' and 'test.//.id' - throw new Error("Invalid regexp: ".concat(REGEXP_MARKER).concat(str)); - } - - var _isRegexp = true; // take the rest regexp part - - propChunks.push(str.slice(0, regexEndIndex + 1)); - - var _prop = utils.toRegExp(propChunks.join('')); - - propsArr.push({ - arg: _prop, - isRegexp: _isRegexp - }); // delete prop from str - - str = str.slice(regexEndIndex + 1); - } - - if (!str) { - return propsArr; - } // str should be like '.nextProp' now - // so 'zx.prop' or '.' is invalid - - - if (!utils.startsWith(str, PROPS_DIVIDER) || utils.startsWith(str, PROPS_DIVIDER) && str.length === 1) { - throw new Error("Invalid chain property: ".concat(input)); - } - - str = str.slice(1); - } -}; - -var convertTypeFromStr = function convertTypeFromStr(value) { - var numValue = Number(value); - var output; - - if (!Number.isNaN(numValue)) { - output = numValue; - } else { - switch (value) { - case 'undefined': - output = undefined; - break; - - case 'null': - output = null; - break; - - case 'true': - output = true; - break; - - case 'false': - output = false; - break; - - default: - output = value; - } - } - - return output; -}; - -var convertTypeIntoStr = function convertTypeIntoStr(value) { - var output; - - switch (value) { - case undefined: - output = 'undefined'; - break; - - case null: - output = 'null'; - break; - - default: - output = value.toString(); - } - - return output; -}; -/** - * Class that extends Sizzle and adds support for "matches-property" pseudo element. - */ - - -var ElementPropertyMatcher = function () { - /** - * Class that matches element properties against the specified expressions - * @param {ArgData[]} propsChainArg - array of parsed props chain objects - * @param {ArgData} valueArg - parsed value argument - * @param {string} pseudoElement - * @constructor - * - * @member {Array} chainedProps - * @member {boolean} isRegexpName - * @member {string|RegExp} propValue - * @member {boolean} isRegexpValue - */ - var PropMatcher = function PropMatcher(propsChainArg, valueArg, pseudoElement) { - this.pseudoElement = pseudoElement; - this.chainedProps = propsChainArg; - this.propValue = valueArg.arg; - this.isRegexpValue = valueArg.isRegexp; - }; - /** - * Function to check if element properties matches filter pattern - * @param {Element} element to check - */ - - - PropMatcher.prototype.matches = function (element) { - var ownerObjArr = matcherUtils.filterRootsByRegexpChain(element, this.chainedProps); - - if (ownerObjArr.length === 0) { - return false; - } - - var matched = true; - - if (this.propValue) { - for (var i = 0; i < ownerObjArr.length; i += 1) { - var realValue = ownerObjArr[i].value; - - if (this.isRegexpValue) { - matched = this.propValue.test(convertTypeIntoStr(realValue)); - } else { - // handle 'null' and 'undefined' property values set as string - if (realValue === 'null' || realValue === 'undefined') { - matched = this.propValue === realValue; - break; - } - - matched = convertTypeFromStr(this.propValue) === realValue; - } - - if (matched) { - break; - } - } - } - - return matched; - }; - /** - * Creates a new pseudo-class and registers it in Sizzle - */ - - - var extendSizzle = function extendSizzle(sizzle) { - // First of all we should prepare Sizzle engine - sizzle.selectors.pseudos['matches-property'] = sizzle.selectors.createPseudo(function (propertyFilter) { - if (!propertyFilter) { - throw new Error('No argument is given for :matches-property pseudo class'); - } - - var _matcherUtils$parseMa = matcherUtils.parseMatcherFilter(propertyFilter), - _matcherUtils$parseMa2 = _slicedToArray(_matcherUtils$parseMa, 2), - rawProp = _matcherUtils$parseMa2[0], - rawValue = _matcherUtils$parseMa2[1]; // chained property name can not include '/' or '.' - // so regex prop names with such escaped characters are invalid - - - if (rawProp.indexOf('\\/') > -1 || rawProp.indexOf('\\.') > -1) { - throw new Error("Invalid property name: ".concat(rawProp)); - } - - var propsChainArg = parseRawPropChain(rawProp); - var valueArg = matcherUtils.parseRawMatcherArg(rawValue); - var propsToValidate = [].concat(_toConsumableArray(propsChainArg), [valueArg]); - - if (!matcherUtils.validatePropMatcherArgs(propsToValidate)) { - throw new Error("Invalid argument of :matches-property pseudo class: ".concat(propertyFilter)); - } - - var matcher = new PropMatcher(propsChainArg, valueArg); - return function (element) { - return matcher.matches(element); - }; - }); - }; // EXPOSE - - - return { - extendSizzle: extendSizzle - }; -}(); - -/** - * Copyright 2020 Adguard Software Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - * Class that extends Sizzle and adds support for :is() pseudo element. - */ - -var IsAnyMatcher = function () { - /** - * Class that matches element by one of the selectors - * https://developer.mozilla.org/en-US/docs/Web/CSS/:is - * @param {Array} selectors - * @param {string} pseudoElement - * @constructor - */ - var IsMatcher = function IsMatcher(selectors, pseudoElement) { - this.selectors = selectors; - this.pseudoElement = pseudoElement; - }; - /** - * Function to check if element can be matched by any passed selector - * @param {Element} element to check - */ - - - IsMatcher.prototype.matches = function (element) { - var isMatched = !!this.selectors.find(function (selector) { - var nodes = document.querySelectorAll(selector); - return Array.from(nodes).find(function (node) { - return node === element; - }); - }); - return isMatched; - }; - /** - * Creates a new pseudo-class and registers it in Sizzle - */ - - - var extendSizzle = function extendSizzle(sizzle) { - // First of all we should prepare Sizzle engine - sizzle.selectors.pseudos['is'] = sizzle.selectors.createPseudo(function (input) { - if (input === '') { - throw new Error("Invalid argument of :is pseudo-class: ".concat(input)); - } - - var selectors = input.split(',').map(function (s) { - return s.trim(); - }); // collect valid selectors and log about invalid ones - - var validSelectors = selectors.reduce(function (acc, selector) { - if (cssUtils.isSimpleSelectorValid(selector)) { - acc.push(selector); - } else { - utils.logInfo("Invalid selector passed to :is() pseudo-class: '".concat(selector, "'")); - } - - return acc; - }, []); - var matcher = new IsMatcher(validSelectors); - return function (element) { - return matcher.matches(element); - }; - }); - }; - - return { - extendSizzle: extendSizzle - }; -}(); - -/** - * Copyright 2021 Adguard Software Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - * Extended selector factory module, for creating extended selector classes. - * - * Extended selection capabilities description: - * https://github.com/AdguardTeam/ExtendedCss/blob/master/README.md - */ - -var ExtendedSelectorFactory = function () { - // while adding new markers, constants in other AdGuard repos should be corrected - // AdGuard browser extension : CssFilterRule.SUPPORTED_PSEUDO_CLASSES and CssFilterRule.EXTENDED_CSS_MARKERS - // tsurlfilter, SafariConverterLib : EXT_CSS_PSEUDO_INDICATORS - var PSEUDO_EXTENSIONS_MARKERS = [':has', ':contains', ':has-text', ':matches-css', ':-abp-has', ':-abp-has-text', ':if', ':if-not', ':xpath', ':nth-ancestor', ':upward', ':remove', ':matches-attr', ':matches-property', ':-abp-contains', ':is']; - var initialized = false; - var Sizzle; - /** - * Lazy initialization of the ExtendedSelectorFactory and objects that might be necessary for creating and applying styles. - * This method extends Sizzle engine that we use under the hood with our custom pseudo-classes. - */ - - function initialize() { - if (initialized) { - return; - } - - initialized = true; // Our version of Sizzle is initialized lazily as well - - Sizzle = initializeSizzle(); // Add :matches-css-*() support - - StylePropertyMatcher.extendSizzle(Sizzle); // Add :matches-attr() support - - AttributesMatcher.extendSizzle(Sizzle); // Add :matches-property() support - - ElementPropertyMatcher.extendSizzle(Sizzle); // Add :is() support - - IsAnyMatcher.extendSizzle(Sizzle); // Add :contains, :has-text, :-abp-contains support - - var containsPseudo = Sizzle.selectors.createPseudo(function (text) { - if (/^\s*\/.*\/[gmisuy]*\s*$/.test(text)) { - text = text.trim(); - var flagsIndex = text.lastIndexOf('/'); - var flags = text.substring(flagsIndex + 1); - text = text.substr(0, flagsIndex + 1).slice(1, -1).replace(/\\([\\"])/g, '$1'); - var regex; - - try { - regex = new RegExp(text, flags); - } catch (e) { - throw new Error("Invalid argument of :contains pseudo class: ".concat(text)); - } - - return function (elem) { - var elemTextContent = utils.nodeTextContentGetter.apply(elem); - return regex.test(elemTextContent); - }; - } - - text = text.replace(/\\([\\()[\]"])/g, '$1'); - return function (elem) { - var elemTextContent = utils.nodeTextContentGetter.apply(elem); - return elemTextContent.indexOf(text) > -1; - }; - }); - Sizzle.selectors.pseudos['contains'] = containsPseudo; - Sizzle.selectors.pseudos['has-text'] = containsPseudo; - Sizzle.selectors.pseudos['-abp-contains'] = containsPseudo; // Add :if, :-abp-has support - - Sizzle.selectors.pseudos['if'] = Sizzle.selectors.pseudos['has']; - Sizzle.selectors.pseudos['-abp-has'] = Sizzle.selectors.pseudos['has']; // Add :if-not support - - Sizzle.selectors.pseudos['if-not'] = Sizzle.selectors.createPseudo(function (selector) { - if (typeof selector === 'string') { - Sizzle.compile(selector); - } - - return function (elem) { - return Sizzle(selector, elem).length === 0; - }; - }); - registerParserOnlyTokens(); - } - /** - * Registrate custom tokens for parser. - * Needed for proper work of pseudos: - * for checking if the token is last and pseudo-class arguments validation - */ - - - function registerParserOnlyTokens() { - Sizzle.selectors.pseudos['xpath'] = Sizzle.selectors.createPseudo(function (selector) { - try { - document.createExpression(selector, null); - } catch (e) { - throw new Error("Invalid argument of :xpath pseudo class: ".concat(selector)); - } - - return function () { - return true; - }; - }); - Sizzle.selectors.pseudos['nth-ancestor'] = Sizzle.selectors.createPseudo(function (selector) { - var deep = Number(selector); - - if (Number.isNaN(deep) || deep < 1 || deep >= 256) { - throw new Error("Invalid argument of :nth-ancestor pseudo class: ".concat(selector)); - } - - return function () { - return true; - }; - }); - Sizzle.selectors.pseudos['upward'] = Sizzle.selectors.createPseudo(function (input) { - if (input === '') { - throw new Error("Invalid argument of :upward pseudo class: ".concat(input)); - } else if (Number.isInteger(+input) && (+input < 1 || +input >= 256)) { - throw new Error("Invalid argument of :upward pseudo class: ".concat(input)); - } - - return function () { - return true; - }; - }); - Sizzle.selectors.pseudos['remove'] = Sizzle.selectors.createPseudo(function (input) { - if (input !== '') { - throw new Error("Invalid argument of :remove pseudo class: ".concat(input)); - } - - return function () { - return true; - }; - }); - } - /** - * Checks if specified token can be used by document.querySelectorAll. - */ - - - function isSimpleToken(token) { - var type = token.type; - - if (type === 'ID' || type === 'CLASS' || type === 'ATTR' || type === 'TAG' || type === 'CHILD') { - // known simple tokens - return true; - } - - if (type === 'PSEUDO') { - // check if value contains any of extended pseudo classes - var i = PSEUDO_EXTENSIONS_MARKERS.length; - - while (i--) { - if (token.value.indexOf(PSEUDO_EXTENSIONS_MARKERS[i]) >= 0) { - return false; - } - } - - return true; - } // all others aren't simple - - - return false; - } - /** - * Checks if specified token is a combinator - */ - - - function isRelationToken(token) { - var type = token.type; - return type === ' ' || type === '>' || type === '+' || type === '~'; - } - /** - * ExtendedSelectorParser is a helper class for creating various selector instances which - * all shares a method `querySelectorAll()` and `matches()` implementing different search strategies - * depending on a type of selector. - * - * Currently, there are 3 types: - * A trait-less extended selector - * - we directly feed selector strings to Sizzle. - * A splitted extended selector - * - such as #container #feedItem:has(.ads), where it is splitted to `#container` and `#feedItem:has(.ads)`. - */ - - - function ExtendedSelectorParser(selectorText, tokens, debug) { - initialize(); - - if (typeof tokens === 'undefined') { - this.selectorText = cssUtils.normalize(selectorText); // Passing `returnUnsorted` in order to receive tokens in the order that's valid for the browser - // In Sizzle internally, the tokens are re-sorted: https://github.com/AdguardTeam/ExtendedCss/issues/55 - - this.tokens = Sizzle.tokenize(this.selectorText, false, { - returnUnsorted: true - }); - } else { - this.selectorText = selectorText; - this.tokens = tokens; - } - - if (debug === true) { - this.debug = true; - } - } - - ExtendedSelectorParser.prototype = { - /** - * The main method, creates a selector instance depending on the type of a selector. - * @public - */ - createSelector: function createSelector() { - var debug = this.debug; - var tokens = this.tokens; - var selectorText = this.selectorText; - - if (tokens.length !== 1) { - // Comma-separate selector - can't optimize further - return new TraitLessSelector(selectorText, debug); - } - - var xpathPart = this.getXpathPart(); - - if (typeof xpathPart !== 'undefined') { - return new XpathSelector(selectorText, xpathPart, debug); - } - - var upwardPart = this.getUpwardPart(); - - if (typeof upwardPart !== 'undefined') { - var output; - var upwardDeep = parseInt(upwardPart, 10); // if upward parameter is not a number, we consider it as a selector - - if (Number.isNaN(upwardDeep)) { - output = new UpwardSelector(selectorText, upwardPart, debug); - } else { - // upward works like nth-ancestor - var xpath = this.convertNthAncestorToken(upwardDeep); - output = new XpathSelector(selectorText, xpath, debug); - } - - return output; - } // argument of pseudo-class remove; - // it's defined only if remove is parsed as last token - // and it's valid only if remove arg is empty string - - - var removePart = this.getRemovePart(); - - if (typeof removePart !== 'undefined') { - var hasValidRemovePart = removePart === ''; - return new RemoveSelector(selectorText, hasValidRemovePart, debug); - } - - tokens = tokens[0]; - var l = tokens.length; - var lastRelTokenInd = this.getSplitPoint(); - - if (typeof lastRelTokenInd === 'undefined') { - try { - document.querySelector(selectorText); - } catch (e) { - return new TraitLessSelector(selectorText, debug); - } - - return new NotAnExtendedSelector(selectorText, debug); - } - - var simple = ''; - var relation = null; - var complex = ''; - var i = 0; - - for (; i < lastRelTokenInd; i++) { - // build simple part - simple += tokens[i].value; - } - - if (i > 0) { - // build relation part - relation = tokens[i++].type; - } // i is pointing to the start of a complex part. - - - for (; i < l; i++) { - complex += tokens[i].value; - } - - return lastRelTokenInd === -1 ? new TraitLessSelector(selectorText, debug) : new SplittedSelector(selectorText, simple, relation, complex, debug); - }, - - /** - * @private - * @return {number|undefined} An index of a token that is split point. - * returns undefined if the selector does not contain any complex tokens - * or it is not eligible for splitting. - * Otherwise returns an integer indicating the index of the last relation token. - */ - getSplitPoint: function getSplitPoint() { - var tokens = this.tokens[0]; // We split selector only when the last compound selector - // is the only extended selector. - - var latestRelationTokenIndex = -1; - var haveMetComplexToken = false; - - for (var i = 0, l = tokens.length; i < l; i++) { - var token = tokens[i]; - - if (isRelationToken(token)) { - if (haveMetComplexToken) { - return; - } - - latestRelationTokenIndex = i; - } else if (!isSimpleToken(token)) { - haveMetComplexToken = true; - } - } - - if (!haveMetComplexToken) { - return; - } - - return latestRelationTokenIndex; - }, - - /** - * @private - * @return {string|undefined} xpath selector part if exists - * returns undefined if the selector does not contain xpath tokens - */ - getXpathPart: function getXpathPart() { - var tokens = this.tokens[0]; - - for (var i = 0, tokensLength = tokens.length; i < tokensLength; i++) { - var token = tokens[i]; - - if (token.type === 'PSEUDO') { - var matches = token.matches; - - if (matches && matches.length > 1) { - if (matches[0] === 'xpath') { - if (this.isLastToken(tokens, i)) { - throw new Error('Invalid pseudo: \':xpath\' should be at the end of the selector'); - } - - return matches[1]; - } - - if (matches[0] === 'nth-ancestor') { - if (this.isLastToken(tokens, i)) { - throw new Error('Invalid pseudo: \':nth-ancestor\' should be at the end of the selector'); - } - - var deep = matches[1]; - - if (deep > 0 && deep < 256) { - return this.convertNthAncestorToken(deep); - } - } - } - } - } - }, - - /** - * converts nth-ancestor/upward deep value to xpath equivalent - * @param {number} deep - * @return {string} - */ - convertNthAncestorToken: function convertNthAncestorToken(deep) { - var result = '..'; - - while (deep > 1) { - result += '/..'; - deep--; - } - - return result; - }, - - /** - * Checks if the token is last, - * except of remove pseudo-class - * @param {Array} tokens - * @param {number} i index of token - * @returns {boolean} - */ - isLastToken: function isLastToken(tokens, i) { - // check id the next parsed token is remove pseudo - var isNextRemoveToken = tokens[i + 1] && tokens[i + 1].type === 'PSEUDO' && tokens[i + 1].matches && tokens[i + 1].matches[0] === 'remove'; // check if the token is last - // and if it is not check if it is remove one - // which should be skipped - - return i + 1 !== tokens.length && !isNextRemoveToken; - }, - - /** - * @private - * @return {string|undefined} upward parameter - * or undefined if the input does not contain upward tokens - */ - getUpwardPart: function getUpwardPart() { - var tokens = this.tokens[0]; - - for (var i = 0, tokensLength = tokens.length; i < tokensLength; i++) { - var token = tokens[i]; - - if (token.type === 'PSEUDO') { - var matches = token.matches; - - if (matches && matches.length > 1) { - if (matches[0] === 'upward') { - if (this.isLastToken(tokens, i)) { - throw new Error('Invalid pseudo: \':upward\' should be at the end of the selector'); - } - - return matches[1]; - } - } - } - } - }, - - /** - * @private - * @return {string|undefined} remove parameter - * or undefined if the input does not contain remove tokens - */ - getRemovePart: function getRemovePart() { - var tokens = this.tokens[0]; - - for (var i = 0, tokensLength = tokens.length; i < tokensLength; i++) { - var token = tokens[i]; - - if (token.type === 'PSEUDO') { - var matches = token.matches; - - if (matches && matches.length > 1) { - if (matches[0] === 'remove') { - if (i + 1 !== tokensLength) { - throw new Error('Invalid pseudo: \':remove\' should be at the end of the selector'); - } - - return matches[1]; - } - } - } - } - } - }; - var globalDebuggingFlag = false; - - function isDebugging() { - return globalDebuggingFlag || this.debug; - } - /** - * This class represents a selector which is not an extended selector. - * @param {string} selectorText - * @param {boolean=} debug - * @final - */ - - - function NotAnExtendedSelector(selectorText, debug) { - this.selectorText = selectorText; - this.debug = debug; - } - - NotAnExtendedSelector.prototype = { - querySelectorAll: function querySelectorAll() { - return document.querySelectorAll(this.selectorText); - }, - matches: function matches(element) { - return element[utils.matchesPropertyName](this.selectorText); - }, - isDebugging: isDebugging - }; - /** - * A trait-less extended selector class. - * @param {string} selectorText - * @param {boolean=} debug - * @constructor - */ - - function TraitLessSelector(selectorText, debug) { - this.selectorText = selectorText; - this.debug = debug; - Sizzle.compile(selectorText); - } - - TraitLessSelector.prototype = { - querySelectorAll: function querySelectorAll() { - return Sizzle(this.selectorText); - }, - - /** @final */ - matches: function matches(element) { - return Sizzle.matchesSelector(element, this.selectorText); - }, - - /** @final */ - isDebugging: isDebugging - }; - /** - * Parental class for such pseudo-classes as xpath, upward, remove - * which are limited to be the last one token in selector - * - * @param {string} selectorText - * @param {string} pseudoClassArg pseudo-class arg - * @param {boolean=} debug - * @constructor - */ - - function BaseLastArgumentSelector(selectorText, pseudoClassArg, debug) { - this.selectorText = selectorText; - this.pseudoClassArg = pseudoClassArg; - this.debug = debug; - Sizzle.compile(this.selectorText); - } - - BaseLastArgumentSelector.prototype = { - querySelectorAll: function querySelectorAll() { - var _this = this; - - var resultNodes = []; - var simpleNodes; - - if (this.selectorText) { - simpleNodes = Sizzle(this.selectorText); - - if (!simpleNodes || !simpleNodes.length) { - return resultNodes; - } - } else { - simpleNodes = [document]; - } - - simpleNodes.forEach(function (node) { - _this.searchResultNodes(node, _this.pseudoClassArg, resultNodes); - }); - return Sizzle.uniqueSort(resultNodes); - }, - - /** @final */ - matches: function matches(element) { - var results = this.querySelectorAll(); - return results.indexOf(element) > -1; - }, - - /** @final */ - isDebugging: isDebugging, - - /** - * Primitive method that returns all nodes if pseudo-class arg is defined. - * That logic works for remove pseudo-class, - * but for others it should be overridden. - * @param {Object} node context element - * @param {string} pseudoClassArg pseudo-class argument - * @param {Array} result - */ - searchResultNodes: function searchResultNodes(node, pseudoClassArg, result) { - if (pseudoClassArg) { - result.push(node); - } - } - }; - /** - * Xpath selector class - * Limited to support 'xpath' to be only the last one token in selector - * @param {string} selectorText - * @param {string} xpath value - * @param {boolean=} debug - * @constructor - * @augments BaseLastArgumentSelector - */ - - function XpathSelector(selectorText, xpath, debug) { - var NO_SELECTOR_MARKER = ':xpath(//'; - var BODY_SELECTOR_REPLACER = 'body:xpath(//'; - var modifiedSelectorText = selectorText; // Normally, a pseudo-class is applied to nodes selected by a selector -- selector:xpath(...). - // However, :xpath is special as the selector can be ommited. - // For any other pseudo-class that would mean "apply to ALL DOM nodes", - // but in case of :xpath it just means "apply me to the document". - - if (utils.startsWith(selectorText, NO_SELECTOR_MARKER)) { - modifiedSelectorText = selectorText.replace(NO_SELECTOR_MARKER, BODY_SELECTOR_REPLACER); - } - - BaseLastArgumentSelector.call(this, modifiedSelectorText, xpath, debug); - } - - XpathSelector.prototype = Object.create(BaseLastArgumentSelector.prototype); - XpathSelector.prototype.constructor = XpathSelector; - /** - * Applies xpath pseudo-class to provided context node - * @param {Object} node context element - * @param {string} pseudoClassArg xpath - * @param {Array} result - * @override - */ - - XpathSelector.prototype.searchResultNodes = function (node, pseudoClassArg, result) { - var xpathResult = document.evaluate(pseudoClassArg, node, null, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null); - var iNode; // eslint-disable-next-line no-cond-assign - - while (iNode = xpathResult.iterateNext()) { - result.push(iNode); - } - }; - /** - * Upward selector class - * Limited to support 'upward' to be only the last one token in selector - * @param {string} selectorText - * @param {string} upwardSelector value - * @param {boolean=} debug - * @constructor - * @augments BaseLastArgumentSelector - */ - - - function UpwardSelector(selectorText, upwardSelector, debug) { - BaseLastArgumentSelector.call(this, selectorText, upwardSelector, debug); - } - - UpwardSelector.prototype = Object.create(BaseLastArgumentSelector.prototype); - UpwardSelector.prototype.constructor = UpwardSelector; - /** - * Applies upward pseudo-class to provided context node - * @param {Object} node context element - * @param {string} upwardSelector upward selector - * @param {Array} result - * @override - */ - - UpwardSelector.prototype.searchResultNodes = function (node, upwardSelector, result) { - if (upwardSelector !== '') { - var parent = node.parentElement; - - if (parent === null) { - return; - } - - node = parent.closest(upwardSelector); - - if (node === null) { - return; - } - } - - result.push(node); - }; - /** - * Remove selector class - * Limited to support 'remove' to be only the last one token in selector - * @param {string} selectorText - * @param {boolean} hasValidRemovePart - * @param {boolean=} debug - * @constructor - * @augments BaseLastArgumentSelector - */ - - - function RemoveSelector(selectorText, hasValidRemovePart, debug) { - var REMOVE_PSEUDO_MARKER = ':remove()'; - var removeMarkerIndex = selectorText.indexOf(REMOVE_PSEUDO_MARKER); // deleting remove part of rule instead of which - // pseudo-property property 'remove' will be added by ExtendedCssParser - - var modifiedSelectorText = selectorText.slice(0, removeMarkerIndex); - BaseLastArgumentSelector.call(this, modifiedSelectorText, hasValidRemovePart, debug); // mark extendedSelector as Remove one for ExtendedCssParser - - this.isRemoveSelector = true; - } - - RemoveSelector.prototype = Object.create(BaseLastArgumentSelector.prototype); - RemoveSelector.prototype.constructor = RemoveSelector; - /** - * A splitted extended selector class. - * - * #container #feedItem:has(.ads) - * +--------+ simple - * + relation - * +-----------------+ complex - * We split selector only when the last selector is complex - * @param {string} selectorText - * @param {string} simple - * @param {string} relation - * @param {string} complex - * @param {boolean=} debug - * @constructor - * @extends TraitLessSelector - */ - - function SplittedSelector(selectorText, simple, relation, complex, debug) { - TraitLessSelector.call(this, selectorText, debug); - this.simple = simple; - this.relation = relation; - this.complex = complex; - Sizzle.compile(complex); - } - - SplittedSelector.prototype = Object.create(TraitLessSelector.prototype); - SplittedSelector.prototype.constructor = SplittedSelector; - /** @override */ - - SplittedSelector.prototype.querySelectorAll = function () { - var _this2 = this; - - var resultNodes = []; - var simpleNodes; - var simple = this.simple; - var relation; - - if (simple) { - // First we use simple selector to narrow our search - simpleNodes = document.querySelectorAll(simple); - - if (!simpleNodes || !simpleNodes.length) { - return resultNodes; - } - - relation = this.relation; - } else { - simpleNodes = [document]; - relation = ' '; - } - - switch (relation) { - case ' ': - simpleNodes.forEach(function (node) { - _this2.relativeSearch(node, resultNodes); - }); - break; - - case '>': - { - simpleNodes.forEach(function (node) { - Object.values(node.children).forEach(function (childNode) { - if (_this2.matches(childNode)) { - resultNodes.push(childNode); - } - }); - }); - break; - } - - case '+': - { - simpleNodes.forEach(function (node) { - var parentNode = node.parentNode; - Object.values(parentNode.children).forEach(function (childNode) { - if (_this2.matches(childNode) && childNode.previousElementSibling === node) { - resultNodes.push(childNode); - } - }); - }); - break; - } - - case '~': - { - simpleNodes.forEach(function (node) { - var parentNode = node.parentNode; - Object.values(parentNode.children).forEach(function (childNode) { - if (_this2.matches(childNode) && node.compareDocumentPosition(childNode) === 4) { - resultNodes.push(childNode); - } - }); - }); - break; - } - } - - return Sizzle.uniqueSort(resultNodes); - }; - /** - * Performs a search of "complex" part relative to results for the "simple" part. - * @param {Node} node a node matching the "simple" part. - * @param {Node[]} result an array to append search result. - */ - - - SplittedSelector.prototype.relativeSearch = function (node, results) { - Sizzle(this.complex, node, results); - }; - - return { - /** - * Wraps the inner class so that the instance is not exposed. - */ - createSelector: function createSelector(selector, tokens, debug) { - return new ExtendedSelectorParser(selector, tokens, debug).createSelector(); - }, - - /** - * Mark every selector as a selector being debugged, so that timing information - * for the selector is printed to the console. - */ - enableGlobalDebugging: function enableGlobalDebugging() { - globalDebuggingFlag = true; - } - }; -}(); - -/** - * Copyright 2016 Adguard Software Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - * A helper class that parses stylesheets containing extended selectors - * into ExtendedSelector instances and key-value maps of style declarations. - * Please note, that it does not support any complex things like media queries and such. - */ - -var ExtendedCssParser = function () { - var reDeclEnd = /[;}]/g; - var reDeclDivider = /[;:}]/g; - var reNonWhitespace = /\S/g; - var Sizzle; - /** - * @param {string} cssText - * @constructor - */ - - function Parser(cssText) { - this.cssText = cssText; - } - - Parser.prototype = { - error: function error(position) { - throw new Error("CssParser: parse error at position ".concat(this.posOffset + position)); - }, - - /** - * Validates that the tokens correspond to a valid selector. - * Sizzle is different from browsers and some selectors that it tolerates aren't actually valid. - * For instance, "div >" won't work in a browser, but it will in Sizzle (it'd be the same as "div > *"). - * - * @param {*} selectors An array of SelectorData (selector, groups) - * @returns {boolean} false if any of the groups are invalid - */ - validateSelectors: function validateSelectors(selectors) { - var iSelectors = selectors.length; - - while (iSelectors--) { - var groups = selectors[iSelectors].groups; - var iGroups = groups.length; - - while (iGroups--) { - var tokens = groups[iGroups]; - var lastToken = tokens[tokens.length - 1]; - - if (Sizzle.selectors.relative[lastToken.type]) { - return false; - } - } - } - - return true; - }, - - /** - * Parses a stylesheet and returns a list of pairs of an ExtendedSelector and a styles map. - * This method will throw an error in case of an obviously invalid input. - * If any of the selectors used in the stylesheet cannot be compiled into an ExtendedSelector, - * it will be ignored. - * - * @typedef {Object} ExtendedStyle - * @property {Object} selector An instance of the {@link ExtendedSelector} class - * @property {Object} styleMap A map of styles parsed - * - * @returns {Array.} An array of the styles parsed - */ - parseCss: function parseCss() { - this.posOffset = 0; - - if (!this.cssText) { - this.error(0); - } - - var results = []; - - while (this.cssText) { - // Apply tolerant tokenization. - var parseResult = Sizzle.tokenize(this.cssText, false, { - tolerant: true, - returnUnsorted: true - }); - var selectorData = parseResult.selectors; - this.nextIndex = parseResult.nextIndex; - - if (this.cssText.charCodeAt(this.nextIndex) !== 123 || - /* charCode of '{' */ - !this.validateSelectors(selectorData)) { - this.error(this.nextIndex); - } - - this.nextIndex++; // Move the pointer to the start of style declaration. - - var styleMap = this.parseNextStyle(); - var debug = false; // If there is a style property 'debug', mark the selector - // as a debuggable selector, and delete the style declaration. - - var debugPropertyValue = styleMap['debug']; - - if (typeof debugPropertyValue !== 'undefined') { - if (debugPropertyValue === 'global') { - ExtendedSelectorFactory.enableGlobalDebugging(); - } - - debug = true; - delete styleMap['debug']; - } // Creating an ExtendedSelector instance for every selector we got from Sizzle.tokenize. - // This is quite important as Sizzle does a poor job at executing selectors like "selector1, selector2". - - - for (var i = 0, l = selectorData.length; i < l; i++) { - var data = selectorData[i]; - - try { - var extendedSelector = ExtendedSelectorFactory.createSelector(data.selectorText, data.groups, debug); - - if (extendedSelector.pseudoClassArg && extendedSelector.isRemoveSelector) { - // if there is remove pseudo-class in rule, - // the element will be removed and no other styles will be applied - styleMap['remove'] = 'true'; - } - - results.push({ - selector: extendedSelector, - style: styleMap - }); - } catch (ex) { - utils.logError("ExtendedCssParser: ignoring invalid selector ".concat(data.selectorText)); - } - } - } - - return results; - }, - parseNextStyle: function parseNextStyle() { - var styleMap = Object.create(null); - var bracketPos = this.parseUntilClosingBracket(styleMap); // Cut out matched portion from cssText. - - reNonWhitespace.lastIndex = bracketPos + 1; - var match = reNonWhitespace.exec(this.cssText); - - if (match === null) { - this.cssText = ''; - return styleMap; - } - - var matchPos = match.index; - this.cssText = this.cssText.slice(matchPos); - this.posOffset += matchPos; - return styleMap; - }, - - /** - * @return {number} an index of the next '}' in `this.cssText`. - */ - parseUntilClosingBracket: function parseUntilClosingBracket(styleMap) { - // Expects ":", ";", and "}". - reDeclDivider.lastIndex = this.nextIndex; - var match = reDeclDivider.exec(this.cssText); - - if (match === null) { - this.error(this.nextIndex); - } - - var matchPos = match.index; - var matched = match[0]; - - if (matched === '}') { - return matchPos; - } - - if (matched === ':') { - var colonIndex = matchPos; // Expects ";" and "}". - - reDeclEnd.lastIndex = colonIndex; - match = reDeclEnd.exec(this.cssText); - - if (match === null) { - this.error(colonIndex); - } - - matchPos = match.index; - matched = match[0]; // Populates the `styleMap` key-value map. - - var property = this.cssText.slice(this.nextIndex, colonIndex).trim(); - var value = this.cssText.slice(colonIndex + 1, matchPos).trim(); - styleMap[property] = value; // If found "}", re-run the outer loop. - - if (matched === '}') { - return matchPos; - } - } // matchPos is the position of the next ';'. - // Increase 'nextIndex' and re-run the loop. - - - this.nextIndex = matchPos + 1; - return this.parseUntilClosingBracket(styleMap); // Should be a subject of tail-call optimization. - } - }; - return { - parseCss: function parseCss(cssText) { - Sizzle = initializeSizzle(); - return new Parser(cssUtils.normalize(cssText)).parseCss(); - } - }; -}(); - -/** - * This callback is used to get affected node elements and handle style properties - * before they are applied to them if it is necessary - * @callback beforeStyleApplied - * @param {object} affectedElement - Object containing DOM node and rule to be applied - * @return {object} affectedElement - Same or modified object containing DOM node and rule to be applied - */ - -/** - * Extended css class - * - * @param {Object} configuration - * @param {string} configuration.styleSheet - the CSS stylesheet text - * @param {beforeStyleApplied} [configuration.beforeStyleApplied] - the callback that handles affected elements - * @constructor - */ - -function ExtendedCss(configuration) { - if (!configuration) { - throw new Error('Configuration is not provided.'); - } - - var styleSheet = configuration.styleSheet; - var beforeStyleApplied = configuration.beforeStyleApplied; - - if (beforeStyleApplied && typeof beforeStyleApplied !== 'function') { - // eslint-disable-next-line max-len - throw new Error("Wrong configuration. Type of 'beforeStyleApplied' field should be a function, received: ".concat(_typeof(beforeStyleApplied))); - } // We use EventTracker to track the event that is likely to cause the mutation. - // The problem is that we cannot use `window.event` directly from the mutation observer call - // as we're not in the event handler context anymore. - - - var EventTracker = function () { - var ignoredEventTypes = ['mouseover', 'mouseleave', 'mouseenter', 'mouseout']; - var LAST_EVENT_TIMEOUT_MS = 10; - var EVENTS = [// keyboard events - 'keydown', 'keypress', 'keyup', // mouse events - 'auxclick', 'click', 'contextmenu', 'dblclick', 'mousedown', 'mouseenter', 'mouseleave', 'mousemove', 'mouseover', 'mouseout', 'mouseup', 'pointerlockchange', 'pointerlockerror', 'select', 'wheel']; // 'wheel' event makes scrolling in Safari twitchy - // https://github.com/AdguardTeam/ExtendedCss/issues/120 - - var safariProblematicEvents = ['wheel']; - var trackedEvents = utils.isSafariBrowser ? EVENTS.filter(function (el) { - return !(safariProblematicEvents.indexOf(el) > -1); - }) : EVENTS; - var lastEventType; - var lastEventTime; - - var trackEvent = function trackEvent(e) { - lastEventType = e.type; - lastEventTime = Date.now(); - }; - - trackedEvents.forEach(function (evName) { - document.documentElement.addEventListener(evName, trackEvent, true); - }); - - var getLastEventType = function getLastEventType() { - return lastEventType; - }; - - var getTimeSinceLastEvent = function getTimeSinceLastEvent() { - return Date.now() - lastEventTime; - }; - - return { - isIgnoredEventType: function isIgnoredEventType() { - return ignoredEventTypes.indexOf(getLastEventType()) > -1 && getTimeSinceLastEvent() < LAST_EVENT_TIMEOUT_MS; - } - }; - }(); - - var rules = []; - var affectedElements = []; - var removalsStatistic = {}; - var domObserved; - var eventListenerSupported = window.addEventListener; - var domMutationObserver; - - function observeDocument(callback) { - // We are trying to limit the number of callback calls by not calling it on all kind of "hover" events. - // The rationale behind this is that "hover" events often cause attributes modification, - // but re-applying extCSS rules will be useless as these attribute changes are usually transient. - var isIgnoredMutation = function isIgnoredMutation(mutations) { - for (var i = 0; i < mutations.length; i += 1) { - if (mutations.type !== 'attributes') { - return false; - } - } - - return true; - }; - - if (utils.MutationObserver) { - domMutationObserver = new utils.MutationObserver(function (mutations) { - if (!mutations || mutations.length === 0) { - return; - } - - if (EventTracker.isIgnoredEventType() && isIgnoredMutation(mutations)) { - return; - } - - callback(); - }); - domMutationObserver.observe(document, { - childList: true, - subtree: true, - attributes: true, - attributeFilter: ['id', 'class'] - }); - } else if (eventListenerSupported) { - document.addEventListener('DOMNodeInserted', callback, false); - document.addEventListener('DOMNodeRemoved', callback, false); - document.addEventListener('DOMAttrModified', callback, false); - } - } - - function disconnectDocument(callback) { - if (domMutationObserver) { - domMutationObserver.disconnect(); - } else if (eventListenerSupported) { - document.removeEventListener('DOMNodeInserted', callback, false); - document.removeEventListener('DOMNodeRemoved', callback, false); - document.removeEventListener('DOMAttrModified', callback, false); - } - } - - var MAX_STYLE_PROTECTION_COUNT = 50; - var protectionObserverOption = { - attributes: true, - attributeOldValue: true, - attributeFilter: ['style'] - }; - /** - * Creates MutationObserver protection function - * - * @param styles - * @return {protectionFunction} - */ - - function createProtectionFunction(styles) { - function protectionFunction(mutations, observer) { - if (!mutations.length) { - return; - } - - var mutation = mutations[0]; - var target = mutation.target; - observer.disconnect(); - styles.forEach(function (style) { - setStyleToElement(target, style); - }); - - if (++observer.styleProtectionCount < MAX_STYLE_PROTECTION_COUNT) { - observer.observe(target, protectionObserverOption); - } else { - utils.logError('ExtendedCss: infinite loop protection for style'); - } - } - - return protectionFunction; - } - /** - * Sets up a MutationObserver which protects style attributes from changes - * @param node DOM node - * @param rules rules - * @returns Mutation observer used to protect attribute or null if there's nothing to protect - */ - - - function protectStyleAttribute(node, rules) { - if (!utils.MutationObserver) { - return null; - } - - var styles = rules.map(function (r) { - return r.style; - }); - var protectionObserver = new utils.MutationObserver(createProtectionFunction(styles)); - protectionObserver.observe(node, protectionObserverOption); // Adds an expando to the observer to keep 'style fix counts'. - - protectionObserver.styleProtectionCount = 0; - return protectionObserver; - } - - function removeSuffix(str, suffix) { - var index = str.indexOf(suffix, str.length - suffix.length); - - if (index >= 0) { - return str.substring(0, index); - } - - return str; - } - /** - * Finds affectedElement object for the specified DOM node - * @param node DOM node - * @returns affectedElement found or null - */ - - - function findAffectedElement(node) { - for (var i = 0; i < affectedElements.length; i += 1) { - if (affectedElements[i].node === node) { - return affectedElements[i]; - } - } - - return null; - } - - function removeElement(affectedElement) { - var node = affectedElement.node; - affectedElement.removed = true; - var elementSelector = utils.getNodeSelector(node); // check if the element has been already removed earlier - - var elementRemovalsCounter = removalsStatistic[elementSelector] || 0; // if removals attempts happened more than specified we do not try to remove node again - - if (elementRemovalsCounter > MAX_STYLE_PROTECTION_COUNT) { - utils.logError('ExtendedCss: infinite loop protection for SELECTOR', elementSelector); - return; - } - - if (node.parentNode) { - node.parentNode.removeChild(node); - removalsStatistic[elementSelector] = elementRemovalsCounter + 1; - } - } - /** - * Applies style to the specified DOM node - * @param affectedElement Object containing DOM node and rule to be applied - */ - - - function applyStyle(affectedElement) { - if (affectedElement.protectionObserver) { - // Style is already applied and protected by the observer - return; - } - - if (beforeStyleApplied) { - affectedElement = beforeStyleApplied(affectedElement); - - if (!affectedElement) { - return; - } - } - - var _affectedElement = affectedElement, - node = _affectedElement.node; - - for (var i = 0; i < affectedElement.rules.length; i++) { - var style = affectedElement.rules[i].style; - - if (style['remove'] === 'true') { - removeElement(affectedElement); - return; - } - - setStyleToElement(node, style); - } - } - /** - * Sets style to the specified DOM node - * @param node element - * @param style style - */ - - - function setStyleToElement(node, style) { - Object.keys(style).forEach(function (prop) { - // Apply this style only to existing properties - // We can't use hasOwnProperty here (does not work in FF) - if (typeof node.style.getPropertyValue(prop) !== 'undefined') { - var value = style[prop]; // First we should remove !important attribute (or it won't be applied') - - value = removeSuffix(value.trim(), '!important').trim(); - node.style.setProperty(prop, value, 'important'); - } - }); - } - /** - * Reverts style for the affected object - */ - - - function revertStyle(affectedElement) { - if (affectedElement.protectionObserver) { - affectedElement.protectionObserver.disconnect(); - } - - affectedElement.node.style.cssText = affectedElement.originalStyle; - } - /** - * Applies specified rule and returns list of elements affected - * @param rule Rule to apply - * @returns List of elements affected by this rule - */ - - - function applyRule(rule) { - var debug = rule.selector.isDebugging(); - var start; - - if (debug) { - start = utils.AsyncWrapper.now(); - } - - var selector = rule.selector; - var nodes = selector.querySelectorAll(); - nodes.forEach(function (node) { - var affectedElement = findAffectedElement(node); - - if (affectedElement) { - affectedElement.rules.push(rule); - applyStyle(affectedElement); - } else { - // Applying style first time - var originalStyle = node.style.cssText; - affectedElement = { - node: node, - // affected DOM node - rules: [rule], - // rules to be applied - originalStyle: originalStyle, - // original node style - protectionObserver: null // style attribute observer - - }; - applyStyle(affectedElement); - affectedElements.push(affectedElement); - } - }); - - if (debug) { - var elapsed = utils.AsyncWrapper.now() - start; - - if (!('timingStats' in rule)) { - rule.timingStats = new utils.Stats(); - } - - rule.timingStats.push(elapsed); - } - - return nodes; - } - /** - * Applies filtering rules - */ - - - function applyRules() { - var elementsIndex = []; // some rules could make call - selector.querySelectorAll() temporarily to change node id attribute - // this caused MutationObserver to call recursively - // https://github.com/AdguardTeam/ExtendedCss/issues/81 - - stopObserve(); - rules.forEach(function (rule) { - var nodes = applyRule(rule); - Array.prototype.push.apply(elementsIndex, nodes); - }); // Now revert styles for elements which are no more affected - - var l = affectedElements.length; // do nothing if there is no elements to process - - if (elementsIndex.length > 0) { - while (l--) { - var obj = affectedElements[l]; - - if (elementsIndex.indexOf(obj.node) === -1) { - // Time to revert style - revertStyle(obj); - affectedElements.splice(l, 1); - } else if (!obj.removed) { - // Add style protection observer - // Protect "style" attribute from changes - if (!obj.protectionObserver) { - obj.protectionObserver = protectStyleAttribute(obj.node, obj.rules); - } - } - } - } // After styles are applied we can start observe again - - - observe(); - printTimingInfo(); - } - - var APPLY_RULES_DELAY = 150; - var applyRulesScheduler = new utils.AsyncWrapper(applyRules, APPLY_RULES_DELAY); - var mainCallback = applyRulesScheduler.run.bind(applyRulesScheduler); - - function observe() { - if (domObserved) { - return; - } // Handle dynamically added elements - - - domObserved = true; - observeDocument(mainCallback); - } - - function stopObserve() { - if (!domObserved) { - return; - } - - domObserved = false; - disconnectDocument(mainCallback); - } - - function apply() { - applyRules(); - - if (document.readyState !== 'complete') { - document.addEventListener('DOMContentLoaded', applyRules); - } - } - /** - * Disposes ExtendedCss and removes our styles from matched elements - */ - - - function dispose() { - stopObserve(); - affectedElements.forEach(function (obj) { - revertStyle(obj); - }); - } - - var timingsPrinted = false; - /** - * Prints timing information for all selectors marked as "debug" - */ - - function printTimingInfo() { - if (timingsPrinted) { - return; - } - - timingsPrinted = true; - var timings = rules.filter(function (rule) { - return rule.selector.isDebugging(); - }).map(function (rule) { - return { - selectorText: rule.selector.selectorText, - timingStats: rule.timingStats - }; - }); - - if (timings.length === 0) { - return; - } // Add location.href to the message to distinguish frames - - - utils.logInfo('[ExtendedCss] Timings for %o:\n%o (in milliseconds)', window.location.href, timings); - } // First of all parse the stylesheet - - - rules = ExtendedCssParser.parseCss(styleSheet); // EXPOSE - - this.dispose = dispose; - this.apply = apply; - /** Exposed for testing purposes only */ - - this._getAffectedElements = function () { - return affectedElements; - }; -} -/** - * Expose querySelectorAll for debugging and validating selectors - * - * @param {string} selectorText selector text - * @param {boolean} noTiming if true -- do not print the timing to the console - * @returns {Array|NodeList} a list of elements found - * @throws Will throw an error if the argument is not a valid selector - */ - - -ExtendedCss.query = function (selectorText, noTiming) { - if (typeof selectorText !== 'string') { - throw new Error('Selector text is empty'); - } - - var now = utils.AsyncWrapper.now; - var start = now(); - - try { - return ExtendedSelectorFactory.createSelector(selectorText).querySelectorAll(); - } finally { - var end = now(); - - if (!noTiming) { - utils.logInfo("[ExtendedCss] Elapsed: ".concat(Math.round((end - start) * 1000), " \u03BCs.")); - } - } -}; - -module.exports = ExtendedCss; diff --git a/dist/extended-css.d.ts b/dist/extended-css.d.ts deleted file mode 100644 index 271203bf..00000000 --- a/dist/extended-css.d.ts +++ /dev/null @@ -1,39 +0,0 @@ -declare module 'extended-css' { - export interface IAffectedElement { - rules: { style: { content: string }}[] - node: HTMLElement; - } - - export interface IConfiguration { - styleSheet: string; - beforeStyleApplied?(x:IAffectedElement): IAffectedElement; - } - - export default class ExtendedCss { - constructor(configuration: IConfiguration); - - /** - * Applies filtering rules - */ - apply(): void; - - /** - * Disposes ExtendedCss and removes our styles from matched elements - */ - dispose(): void; - - /** - * Exposes querySelectorAll for debugging and validating selectors - * @param selectorText - * @param noTiming - */ - static query(selectorText: string, noTiming: boolean): void; - - /** - * Used for testing purposes only - */ - _getAffectedElements(): IAffectedElement[]; - } -} - - diff --git a/dist/extended-css.esm.js b/dist/extended-css.esm.js deleted file mode 100644 index ee39fbbd..00000000 --- a/dist/extended-css.esm.js +++ /dev/null @@ -1,5290 +0,0 @@ -/*! extended-css - v1.3.16 - Thu Sep 15 2022 -* https://github.com/AdguardTeam/ExtendedCss -* Copyright (c) 2022 AdGuard. Licensed GPL-3.0 -*/ -function _typeof(obj) { - "@babel/helpers - typeof"; - - if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { - _typeof = function (obj) { - return typeof obj; - }; - } else { - _typeof = function (obj) { - return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; - }; - } - - return _typeof(obj); -} - -function _slicedToArray(arr, i) { - return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); -} - -function _toConsumableArray(arr) { - return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); -} - -function _arrayWithoutHoles(arr) { - if (Array.isArray(arr)) return _arrayLikeToArray(arr); -} - -function _arrayWithHoles(arr) { - if (Array.isArray(arr)) return arr; -} - -function _iterableToArray(iter) { - if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter); -} - -function _iterableToArrayLimit(arr, i) { - if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return; - var _arr = []; - var _n = true; - var _d = false; - var _e = undefined; - - try { - for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { - _arr.push(_s.value); - - if (i && _arr.length === i) break; - } - } catch (err) { - _d = true; - _e = err; - } finally { - try { - if (!_n && _i["return"] != null) _i["return"](); - } finally { - if (_d) throw _e; - } - } - - return _arr; -} - -function _unsupportedIterableToArray(o, minLen) { - if (!o) return; - if (typeof o === "string") return _arrayLikeToArray(o, minLen); - var n = Object.prototype.toString.call(o).slice(8, -1); - if (n === "Object" && o.constructor) n = o.constructor.name; - if (n === "Map" || n === "Set") return Array.from(o); - if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); -} - -function _arrayLikeToArray(arr, len) { - if (len == null || len > arr.length) len = arr.length; - - for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; - - return arr2; -} - -function _nonIterableSpread() { - throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); -} - -function _nonIterableRest() { - throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); -} - -/** - * Copyright 2016 Adguard Software Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* eslint-disable no-console */ -var utils = {}; -utils.MutationObserver = window.MutationObserver || window.WebKitMutationObserver; -/** - * Stores native Node textContent getter to be used for contains pseudo-class - * because elements' 'textContent' and 'innerText' properties might be mocked - * https://github.com/AdguardTeam/ExtendedCss/issues/127 - */ - -utils.nodeTextContentGetter = function () { - var nativeNode = window.Node || Node; - return Object.getOwnPropertyDescriptor(nativeNode.prototype, 'textContent').get; -}(); - -utils.isSafariBrowser = function () { - return navigator.vendor === 'Apple Computer, Inc.'; -}(); -/** - * Converts regular expressions passed as pseudo class arguments into RegExp instances. - * Have to unescape doublequote " as well, because we escape them while enclosing such - * arguments with doublequotes, and sizzle does not automatically unescapes them. - */ - - -utils.pseudoArgToRegex = function (regexSrc, flag) { - flag = flag || 'i'; - regexSrc = regexSrc.trim().replace(/\\(["\\])/g, '$1'); - return new RegExp(regexSrc, flag); -}; -/** - * Converts string to the regexp - * @param {string} str - * @returns {RegExp} - */ - - -utils.toRegExp = function (str) { - if (str[0] === '/' && str[str.length - 1] === '/') { - return new RegExp(str.slice(1, -1)); - } - - var escaped = str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - return new RegExp(escaped); -}; - -utils.startsWith = function (str, prefix) { - // if str === '', (str && false) will return '' - // that's why it has to be !!str - return !!str && str.indexOf(prefix) === 0; -}; - -utils.endsWith = function (str, postfix) { - if (!str || !postfix) { - return false; - } - - if (str.endsWith) { - return str.endsWith(postfix); - } - - var t = String(postfix); - var index = str.lastIndexOf(t); - return index >= 0 && index === str.length - t.length; -}; -/** - * Helper function for creating regular expression from a url filter rule syntax. - */ - - -utils.createURLRegex = function () { - // Constants - var regexConfiguration = { - maskStartUrl: '||', - maskPipe: '|', - maskSeparator: '^', - maskAnySymbol: '*', - regexAnySymbol: '.*', - regexSeparator: '([^ a-zA-Z0-9.%_-]|$)', - regexStartUrl: '^(http|https|ws|wss)://([a-z0-9-_.]+\\.)?', - regexStartString: '^', - regexEndString: '$' - }; // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/regexp - // should be escaped . * + ? ^ $ { } ( ) | [ ] / \ - // except of * | ^ - - var specials = ['.', '+', '?', '$', '{', '}', '(', ')', '[', ']', '\\', '/']; - var specialsRegex = new RegExp("[".concat(specials.join('\\'), "]"), 'g'); - /** - * Escapes regular expression string - */ - - var escapeRegExp = function escapeRegExp(str) { - return str.replace(specialsRegex, '\\$&'); - }; - - var replaceAll = function replaceAll(str, find, replace) { - if (!str) { - return str; - } - - return str.split(find).join(replace); - }; - /** - * Main function that converts a url filter rule string to a regex. - * @param {string} str - * @return {RegExp} - */ - - - var createRegexText = function createRegexText(str) { - var regex = escapeRegExp(str); - - if (utils.startsWith(regex, regexConfiguration.maskStartUrl)) { - regex = regex.substring(0, regexConfiguration.maskStartUrl.length) + replaceAll(regex.substring(regexConfiguration.maskStartUrl.length, regex.length - 1), '\|', '\\|') + regex.substring(regex.length - 1); - } else if (utils.startsWith(regex, regexConfiguration.maskPipe)) { - regex = regex.substring(0, regexConfiguration.maskPipe.length) + replaceAll(regex.substring(regexConfiguration.maskPipe.length, regex.length - 1), '\|', '\\|') + regex.substring(regex.length - 1); - } else { - regex = replaceAll(regex.substring(0, regex.length - 1), '\|', '\\|') + regex.substring(regex.length - 1); - } // Replacing special url masks - - - regex = replaceAll(regex, regexConfiguration.maskAnySymbol, regexConfiguration.regexAnySymbol); - regex = replaceAll(regex, regexConfiguration.maskSeparator, regexConfiguration.regexSeparator); - - if (utils.startsWith(regex, regexConfiguration.maskStartUrl)) { - regex = regexConfiguration.regexStartUrl + regex.substring(regexConfiguration.maskStartUrl.length); - } else if (utils.startsWith(regex, regexConfiguration.maskPipe)) { - regex = regexConfiguration.regexStartString + regex.substring(regexConfiguration.maskPipe.length); - } - - if (utils.endsWith(regex, regexConfiguration.maskPipe)) { - regex = regex.substring(0, regex.length - 1) + regexConfiguration.regexEndString; - } - - return new RegExp(regex, 'i'); - }; - - return createRegexText; -}(); -/** - * Creates an object implementing Location interface from a url. - * An alternative to URL. - * https://github.com/AdguardTeam/FingerprintingBlocker/blob/master/src/shared/url.ts#L64 - */ - - -utils.createLocation = function (href) { - var anchor = document.createElement('a'); - anchor.href = href; - - if (anchor.host === '') { - anchor.href = anchor.href; // eslint-disable-line no-self-assign - } - - return anchor; -}; -/** - * Checks whether A has the same origin as B. - * @param {string} urlA location.href of A. - * @param {Location} locationB location of B. - * @param {string} domainB document.domain of B. - * @return {boolean} - */ - - -utils.isSameOrigin = function (urlA, locationB, domainB) { - var locationA = utils.createLocation(urlA); // eslint-disable-next-line no-script-url - - if (locationA.protocol === 'javascript:' || locationA.href === 'about:blank') { - return true; - } - - if (locationA.protocol === 'data:' || locationA.protocol === 'file:') { - return false; - } - - return locationA.hostname === domainB && locationA.port === locationB.port && locationA.protocol === locationB.protocol; -}; -/** - * A helper class to throttle function calls with setTimeout and requestAnimationFrame. - */ - - -utils.AsyncWrapper = function () { - var supported = typeof window.requestAnimationFrame !== 'undefined'; - var rAF = supported ? requestAnimationFrame : setTimeout; - var cAF = supported ? cancelAnimationFrame : clearTimeout; - var perf = supported ? performance : Date; - /** - * @param {Function} callback - * @param {number} throttle number, the provided callback should be executed twice - * in this time frame. - * @constructor - */ - - function AsyncWrapper(callback, throttle) { - this.callback = callback; - this.throttle = throttle; - this.wrappedCallback = this.wrappedCallback.bind(this); - - if (this.wrappedAsapCallback) { - this.wrappedAsapCallback = this.wrappedAsapCallback.bind(this); - } - } - /** @private */ - - - AsyncWrapper.prototype.wrappedCallback = function (ts) { - this.lastRun = isNumber(ts) ? ts : perf.now(); - delete this.rAFid; - delete this.timerId; - delete this.asapScheduled; - this.callback(); - }; - /** @private Indicates whether there is a scheduled callback. */ - - - AsyncWrapper.prototype.hasPendingCallback = function () { - return isNumber(this.rAFid) || isNumber(this.timerId); - }; - /** - * Schedules a function call before the next animation frame. - */ - - - AsyncWrapper.prototype.run = function () { - if (this.hasPendingCallback()) { - // There is a pending execution scheduled. - return; - } - - if (typeof this.lastRun !== 'undefined') { - var elapsed = perf.now() - this.lastRun; - - if (elapsed < this.throttle) { - this.timerId = setTimeout(this.wrappedCallback, this.throttle - elapsed); - return; - } - } - - this.rAFid = rAF(this.wrappedCallback); - }; - /** - * Schedules a function call in the most immenent microtask. - * This cannot be canceled. - */ - - - AsyncWrapper.prototype.runAsap = function () { - if (this.asapScheduled) { - return; - } - - this.asapScheduled = true; - cAF(this.rAFid); - clearTimeout(this.timerId); - - if (utils.MutationObserver) { - /** - * Using MutationObservers to access microtask queue is a standard technique, - * used in ASAP library - * {@link https://github.com/kriskowal/asap/blob/master/browser-raw.js#L140} - */ - if (!this.mo) { - this.mo = new utils.MutationObserver(this.wrappedCallback); - this.node = document.createTextNode(1); - this.mo.observe(this.node, { - characterData: true - }); - } - - this.node.nodeValue = -this.node.nodeValue; - } else { - setTimeout(this.wrappedCallback); - } - }; - /** - * Runs scheduled execution immediately, if there were any. - */ - - - AsyncWrapper.prototype.runImmediately = function () { - if (this.hasPendingCallback()) { - cAF(this.rAFid); - clearTimeout(this.timerId); - delete this.rAFid; - delete this.timerId; - this.wrappedCallback(); - } - }; - - AsyncWrapper.now = function () { - return perf.now(); - }; - - return AsyncWrapper; -}(); -/** - * Stores native OdP to be used in WeakMap and Set polyfills. - */ - - -utils.defineProperty = Object.defineProperty; -utils.WeakMap = typeof WeakMap !== 'undefined' ? WeakMap : function () { - /** Originally based on {@link https://github.com/Polymer/WeakMap} */ - var counter = Date.now() % 1e9; - - var WeakMap = function WeakMap() { - this.name = "__st".concat(Math.random() * 1e9 >>> 0).concat(counter++, "__"); - }; - - WeakMap.prototype = { - set: function set(key, value) { - var entry = key[this.name]; - - if (entry && entry[0] === key) { - entry[1] = value; - } else { - utils.defineProperty(key, this.name, { - value: [key, value], - writable: true - }); - } - - return this; - }, - get: function get(key) { - var entry = key[this.name]; - return entry && entry[0] === key ? entry[1] : undefined; - }, - delete: function _delete(key) { - var entry = key[this.name]; - - if (!entry) { - return false; - } - - var hasValue = entry[0] === key; - delete entry[0]; - delete entry[1]; - return hasValue; - }, - has: function has(key) { - var entry = key[this.name]; - - if (!entry) { - return false; - } - - return entry[0] === key; - } - }; - return WeakMap; -}(); -utils.Set = typeof Set !== 'undefined' ? Set : function () { - var counter = Date.now() % 1e9; - /** - * A polyfill which covers only the basic usage. - * Only supports methods that are supported in IE11. - * {@link https://docs.microsoft.com/en-us/scripting/javascript/reference/set-object-javascript} - * Assumes that 'key's are all objects, not primitives such as a number. - * - * @param {Array} items Initial items in this set - */ - - var Set = function Set(items) { - this.name = "__st".concat(Math.random() * 1e9 >>> 0).concat(counter++, "__"); - this.keys = []; - - if (items && items.length) { - var iItems = items.length; - - while (iItems--) { - this.add(items[iItems]); - } - } - }; - - Set.prototype = { - add: function add(key) { - if (!isNumber(key[this.name])) { - var index = this.keys.push(key) - 1; - utils.defineProperty(key, this.name, { - value: index, - writable: true - }); - } - }, - delete: function _delete(key) { - if (isNumber(key[this.name])) { - var index = key[this.name]; - delete this.keys[index]; - key[this.name] = undefined; - } - }, - has: function has(key) { - return isNumber(key[this.name]); - }, - clear: function clear() { - this.keys.forEach(function (key) { - key[this.name] = undefined; - }); - this.keys.length = 0; - }, - forEach: function forEach(cb) { - var that = this; - this.keys.forEach(function (value) { - cb(value, value, that); - }); - } - }; - utils.defineProperty(Set.prototype, 'size', { - get: function get() { - // Skips holes. - return this.keys.reduce(function (acc) { - return acc + 1; - }, 0); - } - }); - return Set; -}(); -/** - * Vendor-specific Element.prototype.matches - */ - -utils.matchesPropertyName = function () { - var props = ['matches', 'matchesSelector', 'mozMatchesSelector', 'msMatchesSelector', 'oMatchesSelector', 'webkitMatchesSelector']; - - for (var i = 0; i < 6; i++) { - if (Element.prototype.hasOwnProperty(props[i])) { - return props[i]; - } - } -}(); -/** - * Provides stats information - */ - - -utils.Stats = function () { - /** @member {Array} */ - this.array = []; - /** @member {number} */ - - this.length = 0; - var zeroDescriptor = { - value: 0, - writable: true - }; - /** @member {number} @private */ - - Object.defineProperty(this, 'sum', zeroDescriptor); - /** @member {number} @private */ - - Object.defineProperty(this, 'squaredSum', zeroDescriptor); -}; -/** - * @param {number} dataPoint data point - */ - - -utils.Stats.prototype.push = function (dataPoint) { - this.array.push(dataPoint); - this.length++; - this.sum += dataPoint; - this.squaredSum += dataPoint * dataPoint; - /** @member {number} */ - - this.mean = this.sum / this.length; - /** @member {number} */ - // eslint-disable-next-line no-restricted-properties - - this.stddev = Math.sqrt(this.squaredSum / this.length - Math.pow(this.mean, 2)); -}; -/** Safe console.error version */ - - -utils.logError = typeof console !== 'undefined' && console.error && Function.prototype.bind && console.error.bind ? console.error.bind(window.console) : console.error; -/** Safe console.info version */ - -utils.logInfo = typeof console !== 'undefined' && console.info && Function.prototype.bind && console.info.bind ? console.info.bind(window.console) : console.info; - -function isNumber(obj) { - return typeof obj === 'number'; -} -/** - * Returns path to element we will use as element identifier - * @param {Element} inputEl - * @returns {string} - path to the element - */ - - -utils.getNodeSelector = function (inputEl) { - if (!(inputEl instanceof Element)) { - throw new Error('Function received argument with wrong type'); - } - - var el = inputEl; - var path = []; // we need to check '!!el' first because it is possible - // that some ancestor of the inputEl was removed before it - - while (!!el && el.nodeType === Node.ELEMENT_NODE) { - var selector = el.nodeName.toLowerCase(); - - if (el.id && typeof el.id === 'string') { - selector += "#".concat(el.id); - path.unshift(selector); - break; - } else { - var sibling = el; - var nth = 1; - - while (sibling.previousSibling) { - sibling = sibling.previousSibling; - - if (sibling.nodeType === Node.ELEMENT_NODE && sibling.nodeName.toLowerCase() === selector) { - nth++; - } - } - - if (nth !== 1) { - selector += ":nth-of-type(".concat(nth, ")"); - } - } - - path.unshift(selector); - el = el.parentNode; - } - - return path.join(' > '); -}; - -/** - * Copyright 2016 Adguard Software Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Helper class css utils - * - * @type {{normalize}} - */ -var cssUtils = function () { - /** - * Regex that matches AdGuard's backward compatible syntaxes. - */ - var reAttrFallback = /\[-(?:ext|abp)-([a-z-_]+)=(["'])((?:(?=(\\?))\4.)*?)\2\]/g; - /** - * Complex replacement function. - * Unescapes quote characters inside of an extended selector. - * - * @param match Whole matched string - * @param name Group 1 - * @param quoteChar Group 2 - * @param value Group 3 - */ - - var evaluateMatch = function evaluateMatch(match, name, quoteChar, value) { - // Unescape quotes - var re = new RegExp("([^\\\\]|^)\\\\".concat(quoteChar), 'g'); - value = value.replace(re, "$1".concat(quoteChar)); - return ":".concat(name, "(").concat(value, ")"); - }; // Sizzle's parsing of pseudo class arguments is buggy on certain circumstances - // We support following form of arguments: - // 1. for :matches-css, those of a form {propertyName}: /.*/ - // 2. for :contains, those of a form /.*/ - // We transform such cases in a way that Sizzle has no ambiguity in parsing arguments. - - - var reMatchesCss = /\:(matches-css(?:-after|-before)?)\(([a-z-\s]*\:\s*\/(?:\\.|[^\/])*?\/\s*)\)/g; - var reContains = /:(?:-abp-)?(contains|has-text)\((\s*\/(?:\\.|[^\/])*?\/\s*)\)/g; - var reScope = /\(\:scope >/g; // Note that we require `/` character in regular expressions to be escaped. - - /** - * Used for pre-processing pseudo-classes values with above two regexes. - */ - - var addQuotes = function addQuotes(_, c1, c2) { - return ":".concat(c1, "(\"").concat(c2.replace(/["\\]/g, '\\$&'), "\")"); - }; - - var SCOPE_REPLACER = '(>'; - /** - * Normalizes specified css text in a form that can be parsed by the - * Sizzle engine. - * Normalization means - * 1. transforming [-ext-*=""] attributes to pseudo classes - * 2. enclosing possibly ambiguous arguments of `:contains`, - * `:matches-css` pseudo classes with quotes. - * @param {string} cssText - * @return {string} - */ - - var normalize = function normalize(cssText) { - var normalizedCssText = cssText.replace(reAttrFallback, evaluateMatch).replace(reMatchesCss, addQuotes).replace(reContains, addQuotes).replace(reScope, SCOPE_REPLACER); - return normalizedCssText; - }; - - var isSimpleSelectorValid = function isSimpleSelectorValid(selector) { - try { - document.querySelectorAll(selector); - } catch (e) { - return false; - } - - return true; - }; - - return { - normalize: normalize, - isSimpleSelectorValid: isSimpleSelectorValid - }; -}(); - -/*! - * Sizzle CSS Selector Engine v2.3.4-pre-adguard - * https://sizzlejs.com/ - * - * Copyright JS Foundation and other contributors - * Released under the MIT license - * https://js.foundation/ - * - * Date: 2020-08-04 - */ - -/** - * Version of Sizzle patched by AdGuard in order to be used in the ExtendedCss module. - * https://github.com/AdguardTeam/sizzle-extcss - * - * Look for [AdGuard Patch] and ADGUARD_EXTCSS markers to find out what exactly was changed by us. - * - * Global changes: - * 1. Added additional parameters to the "Sizzle.tokenize" method so that it can be used for stylesheets parsing and validation. - * 2. Added tokens re-sorting mechanism forcing slow pseudos to be matched last (see sortTokenGroups). - * 3. Fix the nonnativeSelectorCache caching -- there was no value corresponding to a key. - * 4. Added Sizzle.compile call to the `:has` pseudo definition. - * - * Changes that are applied to the ADGUARD_EXTCSS build only: - * 1. Do not expose Sizzle to the global scope. Initialize it lazily via initializeSizzle(). - * 2. Removed :contains pseudo declaration -- its syntax is changed and declared outside of Sizzle. - * 3. Removed declarations for the following non-standard pseudo classes: - * :parent, :header, :input, :button, :text, :first, :last, :eq, - * :even, :odd, :lt, :gt, :nth, :radio, :checkbox, :file, - * :password, :image, :submit, :reset - * 4. Added es6 module export - */ -var Sizzle; -/** - * Initializes Sizzle object. - * In the case of AdGuard ExtendedCss we want to avoid initializing Sizzle right away - * and exposing it to the global scope. - */ - -var initializeSizzle = function initializeSizzle() { - // jshint ignore:line - if (!Sizzle) { - //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - Sizzle = function (window) { - var support, - Expr, - getText, - isXML, - tokenize, - compile, - select, - outermostContext, - sortInput, - hasDuplicate, - // Local document vars - setDocument, - document, - docElem, - documentIsHTML, - rbuggyQSA, - rbuggyMatches, - matches, - contains, - // Instance-specific data - expando = "sizzle" + 1 * new Date(), - preferredDoc = window.document, - dirruns = 0, - done = 0, - classCache = createCache(), - tokenCache = createCache(), - compilerCache = createCache(), - nonnativeSelectorCache = createCache(), - sortOrder = function sortOrder(a, b) { - if (a === b) { - hasDuplicate = true; - } - - return 0; - }, - // Instance methods - hasOwn = {}.hasOwnProperty, - arr = [], - pop = arr.pop, - push_native = arr.push, - push = arr.push, - slice = arr.slice, - // Use a stripped-down indexOf as it's faster than native - // https://jsperf.com/thor-indexof-vs-for/5 - indexOf = function indexOf(list, elem) { - var i = 0, - len = list.length; - - for (; i < len; i++) { - if (list[i] === elem) { - return i; - } - } - - return -1; - }, - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", - // Regular expressions - // http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier - identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", - // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors - attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + // Operator (capture 2) - "*([*^$|!~]?=)" + whitespace + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" - "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + "*\\]", - pseudos = ":(" + identifier + ")(?:\\((" + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: - // 1. quoted (capture 3; capture 4 or capture 5) - "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + // 2. simple (capture 6) - "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + // 3. anything else (capture 2) - ".*" + ")\\)|)", - // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter - rwhitespace = new RegExp(whitespace + "+", "g"), - rtrim = new RegExp("^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g"), - rcomma = new RegExp("^" + whitespace + "*," + whitespace + "*"), - rcombinators = new RegExp("^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*"), - rpseudo = new RegExp(pseudos), - ridentifier = new RegExp("^" + identifier + "$"), - matchExpr = { - "ID": new RegExp("^#(" + identifier + ")"), - "CLASS": new RegExp("^\\.(" + identifier + ")"), - "TAG": new RegExp("^(" + identifier + "|[*])"), - "ATTR": new RegExp("^" + attributes), - "PSEUDO": new RegExp("^" + pseudos), - "CHILD": new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i"), - "bool": new RegExp("^(?:" + booleans + ")$", "i"), - // For use in libraries implementing .is() - // We use this for POS matching in `select` - "needsContext": new RegExp("^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i") - }, - rnative = /^[^{]+\{\s*\[native \w/, - // Easily-parseable/retrievable ID or TAG or CLASS selectors - rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, - rsibling = /[+~]/, - // CSS escapes - // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp("\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig"), - funescape = function funescape(_, escaped, escapedWhitespace) { - var high = "0x" + escaped - 0x10000; // NaN means non-codepoint - // Support: Firefox<24 - // Workaround erroneous numeric interpretation of +"0x" - - return high !== high || escapedWhitespace ? escaped : high < 0 ? // BMP codepoint - String.fromCharCode(high + 0x10000) : // Supplemental Plane codepoint (surrogate pair) - String.fromCharCode(high >> 10 | 0xD800, high & 0x3FF | 0xDC00); - }, - // CSS string/identifier serialization - // https://drafts.csswg.org/cssom/#common-serializing-idioms - rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, - fcssescape = function fcssescape(ch, asCodePoint) { - if (asCodePoint) { - // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER - if (ch === "\0") { - return "\uFFFD"; - } // Control characters and (dependent upon position) numbers get escaped as code points - - - return ch.slice(0, -1) + "\\" + ch.charCodeAt(ch.length - 1).toString(16) + " "; - } // Other potentially-special ASCII characters get backslash-escaped - - - return "\\" + ch; - }, - // Used for iframes - // See setDocument() - // Removing the function wrapper causes a "Permission Denied" - // error in IE - unloadHandler = function unloadHandler() { - setDocument(); - }, - inDisabledFieldset = addCombinator(function (elem) { - return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; - }, { - dir: "parentNode", - next: "legend" - }); // Optimize for push.apply( _, NodeList ) - - - try { - push.apply(arr = slice.call(preferredDoc.childNodes), preferredDoc.childNodes); // Support: Android<4.0 - // Detect silently failing push.apply - - arr[preferredDoc.childNodes.length].nodeType; - } catch (e) { - push = { - apply: arr.length ? // Leverage slice if possible - function (target, els) { - push_native.apply(target, slice.call(els)); - } : // Support: IE<9 - // Otherwise append directly - function (target, els) { - var j = target.length, - i = 0; // Can't trust NodeList.length - - while (target[j++] = els[i++]) {} - - target.length = j - 1; - } - }; - } - - function Sizzle(selector, context, results, seed) { - var m, - i, - elem, - nid, - match, - groups, - newSelector, - newContext = context && context.ownerDocument, - // nodeType defaults to 9, since context defaults to document - nodeType = context ? context.nodeType : 9; - results = results || []; // Return early from calls with invalid selector or context - - if (typeof selector !== "string" || !selector || nodeType !== 1 && nodeType !== 9 && nodeType !== 11) { - return results; - } // Try to shortcut find operations (as opposed to filters) in HTML documents - - - if (!seed) { - if ((context ? context.ownerDocument || context : preferredDoc) !== document) { - setDocument(context); - } - - context = context || document; - - if (documentIsHTML) { - // If the selector is sufficiently simple, try using a "get*By*" DOM method - // (excepting DocumentFragment context, where the methods don't exist) - if (nodeType !== 11 && (match = rquickExpr.exec(selector))) { - // ID selector - if (m = match[1]) { - // Document context - if (nodeType === 9) { - if (elem = context.getElementById(m)) { - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if (elem.id === m) { - results.push(elem); - return results; - } - } else { - return results; - } // Element context - - } else { - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if (newContext && (elem = newContext.getElementById(m)) && contains(context, elem) && elem.id === m) { - results.push(elem); - return results; - } - } // Type selector - - } else if (match[2]) { - push.apply(results, context.getElementsByTagName(selector)); - return results; // Class selector - } else if ((m = match[3]) && support.getElementsByClassName && context.getElementsByClassName) { - push.apply(results, context.getElementsByClassName(m)); - return results; - } - } // Take advantage of querySelectorAll - - - if (support.qsa && !nonnativeSelectorCache[selector + " "] && (!rbuggyQSA || !rbuggyQSA.test(selector))) { - if (nodeType !== 1) { - newContext = context; - newSelector = selector; // qSA looks outside Element context, which is not what we want - // Thanks to Andrew Dupont for this workaround technique - // Support: IE <=8 - // Exclude object elements - } else if (context.nodeName.toLowerCase() !== "object") { - // Capture the context ID, setting it first if necessary - if (nid = context.getAttribute("id")) { - nid = nid.replace(rcssescape, fcssescape); - } else { - context.setAttribute("id", nid = expando); - } // Prefix every selector in the list - - - groups = tokenize(selector); - i = groups.length; - - while (i--) { - groups[i] = "#" + nid + " " + toSelector(groups[i]); - } - - newSelector = groups.join(","); // Expand context for sibling selectors - - newContext = rsibling.test(selector) && testContext(context.parentNode) || context; - } - - if (newSelector) { - try { - if (newSelector.indexOf(':has(') > -1) { - // https://github.com/AdguardTeam/ExtendedCss/issues/149 - throw new Error('Do not handle :has() pseudo-class by the native method'); - } - - push.apply(results, newContext.querySelectorAll(newSelector)); - return results; - } catch (qsaError) { - // [AdGuard Path]: Fix the cache value - nonnativeSelectorCache(selector, true); - } finally { - if (nid === expando) { - context.removeAttribute("id"); - } - } - } - } - } - } // All others - - - return select(selector.replace(rtrim, "$1"), context, results, seed); - } - /** - * Create key-value caches of limited size - * @returns {function(string, object)} Returns the Object data after storing it on itself with - * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) - * deleting the oldest entry - */ - - - function createCache() { - var keys = []; - - function cache(key, value) { - // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) - if (keys.push(key + " ") > Expr.cacheLength) { - // Only keep the most recent entries - delete cache[keys.shift()]; - } - - return cache[key + " "] = value; - } - - return cache; - } - /** - * Mark a function for special use by Sizzle - * @param {Function} fn The function to mark - */ - - - function markFunction(fn) { - fn[expando] = true; - return fn; - } - /** - * Support testing using an element - * @param {Function} fn Passed the created element and returns a boolean result - */ - - - function assert(fn) { - var el = document.createElement("fieldset"); - - try { - return !!fn(el); - } catch (e) { - return false; - } finally { - // Remove from its parent by default - if (el.parentNode) { - el.parentNode.removeChild(el); - } // release memory in IE - - - el = null; - } - } - /** - * Adds the same handler for all of the specified attrs - * @param {String} attrs Pipe-separated list of attributes - * @param {Function} handler The method that will be applied - */ - - - function addHandle(attrs, handler) { - var arr = attrs.split("|"), - i = arr.length; - - while (i--) { - Expr.attrHandle[arr[i]] = handler; - } - } - /** - * Checks document order of two siblings - * @param {Element} a - * @param {Element} b - * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b - */ - - - function siblingCheck(a, b) { - var cur = b && a, - diff = cur && a.nodeType === 1 && b.nodeType === 1 && a.sourceIndex - b.sourceIndex; // Use IE sourceIndex if available on both nodes - - if (diff) { - return diff; - } // Check if b follows a - - - if (cur) { - while (cur = cur.nextSibling) { - if (cur === b) { - return -1; - } - } - } - - return a ? 1 : -1; - } - /** - * Returns a function to use in pseudos for :enabled/:disabled - * @param {Boolean} disabled true for :disabled; false for :enabled - */ - - - function createDisabledPseudo(disabled) { - // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable - return function (elem) { - // Only certain elements can match :enabled or :disabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled - if ("form" in elem) { - // Check for inherited disabledness on relevant non-disabled elements: - // * listed form-associated elements in a disabled fieldset - // https://html.spec.whatwg.org/multipage/forms.html#category-listed - // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled - // * option elements in a disabled optgroup - // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled - // All such elements have a "form" property. - if (elem.parentNode && elem.disabled === false) { - // Option elements defer to a parent optgroup if present - if ("label" in elem) { - if ("label" in elem.parentNode) { - return elem.parentNode.disabled === disabled; - } else { - return elem.disabled === disabled; - } - } // Support: IE 6 - 11 - // Use the isDisabled shortcut property to check for disabled fieldset ancestors - - - return elem.isDisabled === disabled || // Where there is no isDisabled, check manually - - /* jshint -W018 */ - elem.isDisabled !== !disabled && inDisabledFieldset(elem) === disabled; - } - - return elem.disabled === disabled; // Try to winnow out elements that can't be disabled before trusting the disabled property. - // Some victims get caught in our net (label, legend, menu, track), but it shouldn't - // even exist on them, let alone have a boolean value. - } else if ("label" in elem) { - return elem.disabled === disabled; - } // Remaining elements are neither :enabled nor :disabled - - - return false; - }; - } - /** - * Checks a node for validity as a Sizzle context - * @param {Element|Object=} context - * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value - */ - - - function testContext(context) { - return context && typeof context.getElementsByTagName !== "undefined" && context; - } // Expose support vars for convenience - - - support = Sizzle.support = {}; - /** - * Detects XML nodes - * @param {Element|Object} elem An element or a document - * @returns {Boolean} True iff elem is a non-HTML XML node - */ - - isXML = Sizzle.isXML = function (elem) { - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = elem && (elem.ownerDocument || elem).documentElement; - return documentElement ? documentElement.nodeName !== "HTML" : false; - }; - /** - * Sets document-related variables once based on the current document - * @param {Element|Object} [doc] An element or document object to use to set the document - * @returns {Object} Returns the current document - */ - - - setDocument = Sizzle.setDocument = function (node) { - var hasCompare, - subWindow, - doc = node ? node.ownerDocument || node : preferredDoc; // Return early if doc is invalid or already selected - - if (doc === document || doc.nodeType !== 9 || !doc.documentElement) { - return document; - } // Update global variables - - - document = doc; - docElem = document.documentElement; - documentIsHTML = !isXML(document); // Support: IE 9-11, Edge - // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) - - if (preferredDoc !== document && (subWindow = document.defaultView) && subWindow.top !== subWindow) { - // Support: IE 11, Edge - if (subWindow.addEventListener) { - subWindow.addEventListener("unload", unloadHandler, false); // Support: IE 9 - 10 only - } else if (subWindow.attachEvent) { - subWindow.attachEvent("onunload", unloadHandler); - } - } - /* Attributes - ---------------------------------------------------------------------- */ - // Support: IE<8 - // Verify that getAttribute really returns attributes and not properties - // (excepting IE8 booleans) - - - support.attributes = assert(function (el) { - el.className = "i"; - return !el.getAttribute("className"); - }); - /* getElement(s)By* - ---------------------------------------------------------------------- */ - // Check if getElementsByTagName("*") returns only elements - - support.getElementsByTagName = assert(function (el) { - el.appendChild(document.createComment("")); - return !el.getElementsByTagName("*").length; - }); // Support: IE<9 - - support.getElementsByClassName = rnative.test(document.getElementsByClassName); // Support: IE<10 - // Check if getElementById returns elements by name - // The broken getElementById methods don't pick up programmatically-set names, - // so use a roundabout getElementsByName test - - support.getById = assert(function (el) { - docElem.appendChild(el).id = expando; - return !document.getElementsByName || !document.getElementsByName(expando).length; - }); // ID filter and find - - if (support.getById) { - Expr.filter["ID"] = function (id) { - var attrId = id.replace(runescape, funescape); - return function (elem) { - return elem.getAttribute("id") === attrId; - }; - }; - - Expr.find["ID"] = function (id, context) { - if (typeof context.getElementById !== "undefined" && documentIsHTML) { - var elem = context.getElementById(id); - return elem ? [elem] : []; - } - }; - } else { - Expr.filter["ID"] = function (id) { - var attrId = id.replace(runescape, funescape); - return function (elem) { - var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); - return node && node.value === attrId; - }; - }; // Support: IE 6 - 7 only - // getElementById is not reliable as a find shortcut - - - Expr.find["ID"] = function (id, context) { - if (typeof context.getElementById !== "undefined" && documentIsHTML) { - var node, - i, - elems, - elem = context.getElementById(id); - - if (elem) { - // Verify the id attribute - node = elem.getAttributeNode("id"); - - if (node && node.value === id) { - return [elem]; - } // Fall back on getElementsByName - - - elems = context.getElementsByName(id); - i = 0; - - while (elem = elems[i++]) { - node = elem.getAttributeNode("id"); - - if (node && node.value === id) { - return [elem]; - } - } - } - - return []; - } - }; - } // Tag - - - Expr.find["TAG"] = support.getElementsByTagName ? function (tag, context) { - if (typeof context.getElementsByTagName !== "undefined") { - return context.getElementsByTagName(tag); // DocumentFragment nodes don't have gEBTN - } else if (support.qsa) { - return context.querySelectorAll(tag); - } - } : function (tag, context) { - var elem, - tmp = [], - i = 0, - // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too - results = context.getElementsByTagName(tag); // Filter out possible comments - - if (tag === "*") { - while (elem = results[i++]) { - if (elem.nodeType === 1) { - tmp.push(elem); - } - } - - return tmp; - } - - return results; - }; // Class - - Expr.find["CLASS"] = support.getElementsByClassName && function (className, context) { - if (typeof context.getElementsByClassName !== "undefined" && documentIsHTML) { - return context.getElementsByClassName(className); - } - }; - /* QSA/matchesSelector - ---------------------------------------------------------------------- */ - // QSA and matchesSelector support - // matchesSelector(:active) reports false when true (IE9/Opera 11.5) - - - rbuggyMatches = []; // qSa(:focus) reports false when true (Chrome 21) - // We allow this because of a bug in IE8/9 that throws an error - // whenever `document.activeElement` is accessed on an iframe - // So, we allow :focus to pass through QSA all the time to avoid the IE error - // See https://bugs.jquery.com/ticket/13378 - - rbuggyQSA = []; - - if (support.qsa = rnative.test(document.querySelectorAll)) { - // Build QSA regex - // Regex strategy adopted from Diego Perini - assert(function (el) { - // Select is set to empty string on purpose - // This is to test IE's treatment of not explicitly - // setting a boolean content attribute, - // since its presence should be enough - // https://bugs.jquery.com/ticket/12359 - docElem.appendChild(el).innerHTML = AGPolicy.createHTML("" + ""); // Support: IE8, Opera 11-12.16 - // Nothing should be selected when empty strings follow ^= or $= or *= - // The test attribute must be unknown in Opera but "safe" for WinRT - // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section - - if (el.querySelectorAll("[msallowcapture^='']").length) { - rbuggyQSA.push("[*^$]=" + whitespace + "*(?:''|\"\")"); - } // Support: IE8 - // Boolean attributes and "value" are not treated correctly - - - if (!el.querySelectorAll("[selected]").length) { - rbuggyQSA.push("\\[" + whitespace + "*(?:value|" + booleans + ")"); - } // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ - - - if (!el.querySelectorAll("[id~=" + expando + "-]").length) { - rbuggyQSA.push("~="); - } // Webkit/Opera - :checked should return selected option elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - // IE8 throws error here and will not see later tests - - - if (!el.querySelectorAll(":checked").length) { - rbuggyQSA.push(":checked"); - } // Support: Safari 8+, iOS 8+ - // https://bugs.webkit.org/show_bug.cgi?id=136851 - // In-page `selector#id sibling-combinator selector` fails - - - if (!el.querySelectorAll("a#" + expando + "+*").length) { - rbuggyQSA.push(".#.+[+~]"); - } - }); - assert(function (el) { - el.innerHTML = AGPolicy.createHTML("" + ""); // Support: Windows 8 Native Apps - // The type and name attributes are restricted during .innerHTML assignment - - var input = document.createElement("input"); - input.setAttribute("type", "hidden"); - el.appendChild(input).setAttribute("name", "D"); // Support: IE8 - // Enforce case-sensitivity of name attribute - - if (el.querySelectorAll("[name=d]").length) { - rbuggyQSA.push("name" + whitespace + "*[*^$|!~]?="); - } // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) - // IE8 throws error here and will not see later tests - - - if (el.querySelectorAll(":enabled").length !== 2) { - rbuggyQSA.push(":enabled", ":disabled"); - } // Support: IE9-11+ - // IE's :disabled selector does not pick up the children of disabled fieldsets - - - docElem.appendChild(el).disabled = true; - - if (el.querySelectorAll(":disabled").length !== 2) { - rbuggyQSA.push(":enabled", ":disabled"); - } // Opera 10-11 does not throw on post-comma invalid pseudos - - - el.querySelectorAll("*,:x"); - rbuggyQSA.push(",.*:"); - }); - } - - if (support.matchesSelector = rnative.test(matches = docElem.matches || docElem.webkitMatchesSelector || docElem.mozMatchesSelector || docElem.oMatchesSelector || docElem.msMatchesSelector)) { - assert(function (el) { - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9) - support.disconnectedMatch = matches.call(el, "*"); // This should fail with an exception - // Gecko does not error, returns false instead - - matches.call(el, "[s!='']:x"); - rbuggyMatches.push("!=", pseudos); - }); - } - - rbuggyQSA = rbuggyQSA.length && new RegExp(rbuggyQSA.join("|")); - rbuggyMatches = rbuggyMatches.length && new RegExp(rbuggyMatches.join("|")); - /* Contains - ---------------------------------------------------------------------- */ - - hasCompare = rnative.test(docElem.compareDocumentPosition); // Element contains another - // Purposefully self-exclusive - // As in, an element does not contain itself - - contains = hasCompare || rnative.test(docElem.contains) ? function (a, b) { - var adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!(bup && bup.nodeType === 1 && (adown.contains ? adown.contains(bup) : a.compareDocumentPosition && a.compareDocumentPosition(bup) & 16)); - } : function (a, b) { - if (b) { - while (b = b.parentNode) { - if (b === a) { - return true; - } - } - } - - return false; - }; - /* Sorting - ---------------------------------------------------------------------- */ - // Document order sorting - - sortOrder = hasCompare ? function (a, b) { - // Flag for duplicate removal - if (a === b) { - hasDuplicate = true; - return 0; - } // Sort on method existence if only one input has compareDocumentPosition - - - var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; - - if (compare) { - return compare; - } // Calculate position if both inputs belong to the same document - - - compare = (a.ownerDocument || a) === (b.ownerDocument || b) ? a.compareDocumentPosition(b) : // Otherwise we know they are disconnected - 1; // Disconnected nodes - - if (compare & 1 || !support.sortDetached && b.compareDocumentPosition(a) === compare) { - // Choose the first element that is related to our preferred document - if (a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a)) { - return -1; - } - - if (b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b)) { - return 1; - } // Maintain original order - - - return sortInput ? indexOf(sortInput, a) - indexOf(sortInput, b) : 0; - } - - return compare & 4 ? -1 : 1; - } : function (a, b) { - // Exit early if the nodes are identical - if (a === b) { - hasDuplicate = true; - return 0; - } - - var cur, - i = 0, - aup = a.parentNode, - bup = b.parentNode, - ap = [a], - bp = [b]; // Parentless nodes are either documents or disconnected - - if (!aup || !bup) { - return a === document ? -1 : b === document ? 1 : aup ? -1 : bup ? 1 : sortInput ? indexOf(sortInput, a) - indexOf(sortInput, b) : 0; // If the nodes are siblings, we can do a quick check - } else if (aup === bup) { - return siblingCheck(a, b); - } // Otherwise we need full lists of their ancestors for comparison - - - cur = a; - - while (cur = cur.parentNode) { - ap.unshift(cur); - } - - cur = b; - - while (cur = cur.parentNode) { - bp.unshift(cur); - } // Walk down the tree looking for a discrepancy - - - while (ap[i] === bp[i]) { - i++; - } - - return i ? // Do a sibling check if the nodes have a common ancestor - siblingCheck(ap[i], bp[i]) : // Otherwise nodes in our document sort first - ap[i] === preferredDoc ? -1 : bp[i] === preferredDoc ? 1 : 0; - }; - return document; - }; - - Sizzle.matches = function (expr, elements) { - return Sizzle(expr, null, null, elements); - }; - - Sizzle.matchesSelector = function (elem, expr) { - // Set document vars if needed - if ((elem.ownerDocument || elem) !== document) { - setDocument(elem); - } - - if (support.matchesSelector && documentIsHTML && !nonnativeSelectorCache[expr + " "] && (!rbuggyMatches || !rbuggyMatches.test(expr)) && (!rbuggyQSA || !rbuggyQSA.test(expr))) { - try { - var ret = matches.call(elem, expr); // IE 9's matchesSelector returns false on disconnected nodes - - if (ret || support.disconnectedMatch || // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11) { - return ret; - } - } catch (e) { - // [AdGuard Path]: Fix the cache value - nonnativeSelectorCache(expr, true); - } - } - - return Sizzle(expr, document, null, [elem]).length > 0; - }; - - Sizzle.contains = function (context, elem) { - // Set document vars if needed - if ((context.ownerDocument || context) !== document) { - setDocument(context); - } - - return contains(context, elem); - }; - - Sizzle.attr = function (elem, name) { - // Set document vars if needed - if ((elem.ownerDocument || elem) !== document) { - setDocument(elem); - } - - var fn = Expr.attrHandle[name.toLowerCase()], - // Don't get fooled by Object.prototype properties (jQuery #13807) - val = fn && hasOwn.call(Expr.attrHandle, name.toLowerCase()) ? fn(elem, name, !documentIsHTML) : undefined; - return val !== undefined ? val : support.attributes || !documentIsHTML ? elem.getAttribute(name) : (val = elem.getAttributeNode(name)) && val.specified ? val.value : null; - }; - - Sizzle.escape = function (sel) { - return (sel + "").replace(rcssescape, fcssescape); - }; - - Sizzle.error = function (msg) { - throw new Error("Syntax error, unrecognized expression: " + msg); - }; - /** - * Document sorting and removing duplicates - * @param {ArrayLike} results - */ - - - Sizzle.uniqueSort = function (results) { - var elem, - duplicates = [], - j = 0, - i = 0; // Unless we *know* we can detect duplicates, assume their presence - - hasDuplicate = !support.detectDuplicates; - sortInput = !support.sortStable && results.slice(0); - results.sort(sortOrder); - - if (hasDuplicate) { - while (elem = results[i++]) { - if (elem === results[i]) { - j = duplicates.push(i); - } - } - - while (j--) { - results.splice(duplicates[j], 1); - } - } // Clear input after sorting to release objects - // See https://github.com/jquery/sizzle/pull/225 - - - sortInput = null; - return results; - }; - /** - * Utility function for retrieving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ - - - getText = Sizzle.getText = function (elem) { - var node, - ret = "", - i = 0, - nodeType = elem.nodeType; - - if (!nodeType) { - // If no nodeType, this is expected to be an array - while (node = elem[i++]) { - // Do not traverse comment nodes - ret += getText(node); - } - } else if (nodeType === 1 || nodeType === 9 || nodeType === 11) { - // Use textContent for elements - // innerText usage removed for consistency of new lines (jQuery #11153) - if (typeof elem.textContent === "string") { - return elem.textContent; - } else { - // Traverse its children - for (elem = elem.firstChild; elem; elem = elem.nextSibling) { - ret += getText(elem); - } - } - } else if (nodeType === 3 || nodeType === 4) { - return elem.nodeValue; - } // Do not include comment or processing instruction nodes - - - return ret; - }; - - Expr = Sizzle.selectors = { - // Can be adjusted by the user - cacheLength: 50, - createPseudo: markFunction, - match: matchExpr, - attrHandle: {}, - find: {}, - relative: { - ">": { - dir: "parentNode", - first: true - }, - " ": { - dir: "parentNode" - }, - "+": { - dir: "previousSibling", - first: true - }, - "~": { - dir: "previousSibling" - } - }, - preFilter: { - "ATTR": function ATTR(match) { - match[1] = match[1].replace(runescape, funescape); // Move the given value to match[3] whether quoted or unquoted - - match[3] = (match[3] || match[4] || match[5] || "").replace(runescape, funescape); - - if (match[2] === "~=") { - match[3] = " " + match[3] + " "; - } - - return match.slice(0, 4); - }, - "CHILD": function CHILD(match) { - /* matches from matchExpr["CHILD"] - 1 type (only|nth|...) - 2 what (child|of-type) - 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) - 4 xn-component of xn+y argument ([+-]?\d*n|) - 5 sign of xn-component - 6 x of xn-component - 7 sign of y-component - 8 y of y-component - */ - match[1] = match[1].toLowerCase(); - - if (match[1].slice(0, 3) === "nth") { - // nth-* requires argument - if (!match[3]) { - Sizzle.error(match[0]); - } // numeric x and y parameters for Expr.filter.CHILD - // remember that false/true cast respectively to 0/1 - - - match[4] = +(match[4] ? match[5] + (match[6] || 1) : 2 * (match[3] === "even" || match[3] === "odd")); - match[5] = +(match[7] + match[8] || match[3] === "odd"); // other types prohibit arguments - } else if (match[3]) { - Sizzle.error(match[0]); - } - - return match; - }, - "PSEUDO": function PSEUDO(match) { - var excess, - unquoted = !match[6] && match[2]; - - if (matchExpr["CHILD"].test(match[0])) { - return null; - } // Accept quoted arguments as-is - - - if (match[3]) { - match[2] = match[4] || match[5] || ""; // Strip excess characters from unquoted arguments - } else if (unquoted && rpseudo.test(unquoted) && ( // Get excess from tokenize (recursively) - excess = tokenize(unquoted, true)) && ( // advance to the next closing parenthesis - excess = unquoted.indexOf(")", unquoted.length - excess) - unquoted.length)) { - // excess is a negative index - match[0] = match[0].slice(0, excess); - match[2] = unquoted.slice(0, excess); - } // Return only captures needed by the pseudo filter method (type and argument) - - - return match.slice(0, 3); - } - }, - filter: { - "TAG": function TAG(nodeNameSelector) { - var nodeName = nodeNameSelector.replace(runescape, funescape).toLowerCase(); - return nodeNameSelector === "*" ? function () { - return true; - } : function (elem) { - return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; - }; - }, - "CLASS": function CLASS(className) { - var pattern = classCache[className + " "]; - return pattern || (pattern = new RegExp("(^|" + whitespace + ")" + className + "(" + whitespace + "|$)")) && classCache(className, function (elem) { - return pattern.test(typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || ""); - }); - }, - "ATTR": function ATTR(name, operator, check) { - return function (elem) { - var result = Sizzle.attr(elem, name); - - if (result == null) { - return operator === "!="; - } - - if (!operator) { - return true; - } - - result += ""; - return operator === "=" ? result === check : operator === "!=" ? result !== check : operator === "^=" ? check && result.indexOf(check) === 0 : operator === "*=" ? check && result.indexOf(check) > -1 : operator === "$=" ? check && result.slice(-check.length) === check : operator === "~=" ? (" " + result.replace(rwhitespace, " ") + " ").indexOf(check) > -1 : operator === "|=" ? result === check || result.slice(0, check.length + 1) === check + "-" : false; - }; - }, - "CHILD": function CHILD(type, what, argument, first, last) { - var simple = type.slice(0, 3) !== "nth", - forward = type.slice(-4) !== "last", - ofType = what === "of-type"; - return first === 1 && last === 0 ? // Shortcut for :nth-*(n) - function (elem) { - return !!elem.parentNode; - } : function (elem, context, xml) { - var cache, - uniqueCache, - outerCache, - node, - nodeIndex, - start, - dir = simple !== forward ? "nextSibling" : "previousSibling", - parent = elem.parentNode, - name = ofType && elem.nodeName.toLowerCase(), - useCache = !xml && !ofType, - diff = false; - - if (parent) { - // :(first|last|only)-(child|of-type) - if (simple) { - while (dir) { - node = elem; - - while (node = node[dir]) { - if (ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1) { - return false; - } - } // Reverse direction for :only-* (if we haven't yet done so) - - - start = dir = type === "only" && !start && "nextSibling"; - } - - return true; - } - - start = [forward ? parent.firstChild : parent.lastChild]; // non-xml :nth-child(...) stores cache data on `parent` - - if (forward && useCache) { - // Seek `elem` from a previously-cached index - // ...in a gzip-friendly way - node = parent; - outerCache = node[expando] || (node[expando] = {}); // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - - uniqueCache = outerCache[node.uniqueID] || (outerCache[node.uniqueID] = {}); - cache = uniqueCache[type] || []; - nodeIndex = cache[0] === dirruns && cache[1]; - diff = nodeIndex && cache[2]; - node = nodeIndex && parent.childNodes[nodeIndex]; - - while (node = ++nodeIndex && node && node[dir] || ( // Fallback to seeking `elem` from the start - diff = nodeIndex = 0) || start.pop()) { - // When found, cache indexes on `parent` and break - if (node.nodeType === 1 && ++diff && node === elem) { - uniqueCache[type] = [dirruns, nodeIndex, diff]; - break; - } - } - } else { - // Use previously-cached element index if available - if (useCache) { - // ...in a gzip-friendly way - node = elem; - outerCache = node[expando] || (node[expando] = {}); // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - - uniqueCache = outerCache[node.uniqueID] || (outerCache[node.uniqueID] = {}); - cache = uniqueCache[type] || []; - nodeIndex = cache[0] === dirruns && cache[1]; - diff = nodeIndex; - } // xml :nth-child(...) - // or :nth-last-child(...) or :nth(-last)?-of-type(...) - - - if (diff === false) { - // Use the same loop as above to seek `elem` from the start - while (node = ++nodeIndex && node && node[dir] || (diff = nodeIndex = 0) || start.pop()) { - if ((ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1) && ++diff) { - // Cache the index of each encountered element - if (useCache) { - outerCache = node[expando] || (node[expando] = {}); // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - - uniqueCache = outerCache[node.uniqueID] || (outerCache[node.uniqueID] = {}); - uniqueCache[type] = [dirruns, diff]; - } - - if (node === elem) { - break; - } - } - } - } - } // Incorporate the offset, then check against cycle size - - - diff -= last; - return diff === first || diff % first === 0 && diff / first >= 0; - } - }; - }, - "PSEUDO": function PSEUDO(pseudo, argument) { - // pseudo-class names are case-insensitive - // http://www.w3.org/TR/selectors/#pseudo-classes - // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters - // Remember that setFilters inherits from pseudos - var args, - fn = Expr.pseudos[pseudo] || Expr.setFilters[pseudo.toLowerCase()] || Sizzle.error("unsupported pseudo: " + pseudo); // The user may use createPseudo to indicate that - // arguments are needed to create the filter function - // just as Sizzle does - - if (fn[expando]) { - return fn(argument); - } // But maintain support for old signatures - - - if (fn.length > 1) { - args = [pseudo, pseudo, "", argument]; - return Expr.setFilters.hasOwnProperty(pseudo.toLowerCase()) ? markFunction(function (seed, matches) { - var idx, - matched = fn(seed, argument), - i = matched.length; - - while (i--) { - idx = indexOf(seed, matched[i]); - seed[idx] = !(matches[idx] = matched[i]); - } - }) : function (elem) { - return fn(elem, 0, args); - }; - } - - return fn; - } - }, - pseudos: { - // Potentially complex pseudos - "not": markFunction(function (selector) { - // Trim the selector passed to compile - // to avoid treating leading and trailing - // spaces as combinators - var input = [], - results = [], - matcher = compile(selector.replace(rtrim, "$1")); - return matcher[expando] ? markFunction(function (seed, matches, context, xml) { - var elem, - unmatched = matcher(seed, null, xml, []), - i = seed.length; // Match elements unmatched by `matcher` - - while (i--) { - if (elem = unmatched[i]) { - seed[i] = !(matches[i] = elem); - } - } - }) : function (elem, context, xml) { - input[0] = elem; - matcher(input, null, xml, results); // Don't keep the element (issue #299) - - input[0] = null; - return !results.pop(); - }; - }), - "has": markFunction(function (selector) { - if (typeof selector === "string") { - Sizzle.compile(selector); - } - - return function (elem) { - return Sizzle(selector, elem).length > 0; - }; - }), - // Removed :contains pseudo-class declaration - // "Whether an element is represented by a :lang() selector - // is based solely on the element's language value - // being equal to the identifier C, - // or beginning with the identifier C immediately followed by "-". - // The matching of C against the element's language value is performed case-insensitively. - // The identifier C does not have to be a valid language name." - // http://www.w3.org/TR/selectors/#lang-pseudo - "lang": markFunction(function (lang) { - // lang value must be a valid identifier - if (!ridentifier.test(lang || "")) { - Sizzle.error("unsupported lang: " + lang); - } - - lang = lang.replace(runescape, funescape).toLowerCase(); - return function (elem) { - var elemLang; - - do { - if (elemLang = documentIsHTML ? elem.lang : elem.getAttribute("xml:lang") || elem.getAttribute("lang")) { - elemLang = elemLang.toLowerCase(); - return elemLang === lang || elemLang.indexOf(lang + "-") === 0; - } - } while ((elem = elem.parentNode) && elem.nodeType === 1); - - return false; - }; - }), - // Miscellaneous - "target": function target(elem) { - var hash = window.location && window.location.hash; - return hash && hash.slice(1) === elem.id; - }, - "root": function root(elem) { - return elem === docElem; - }, - "focus": function focus(elem) { - return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); - }, - // Boolean properties - "enabled": createDisabledPseudo(false), - "disabled": createDisabledPseudo(true), - "checked": function checked(elem) { - // In CSS3, :checked should return both checked and selected elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - var nodeName = elem.nodeName.toLowerCase(); - return nodeName === "input" && !!elem.checked || nodeName === "option" && !!elem.selected; - }, - "selected": function selected(elem) { - // Accessing this property makes selected-by-default - // options in Safari work properly - if (elem.parentNode) { - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - // Contents - "empty": function empty(elem) { - // http://www.w3.org/TR/selectors/#empty-pseudo - // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), - // but not by others (comment: 8; processing instruction: 7; etc.) - // nodeType < 6 works because attributes (2) do not appear as children - for (elem = elem.firstChild; elem; elem = elem.nextSibling) { - if (elem.nodeType < 6) { - return false; - } - } - - return true; - } // Removed custom pseudo-classes - - } - }; // Removed custom pseudo-classes - // Easy API for creating new setFilters - - function setFilters() {} - - setFilters.prototype = Expr.filters = Expr.pseudos; - Expr.setFilters = new setFilters(); - /** - * [AdGuard Patch]: - * Sorts the tokens in order to mitigate the performance issues caused by matching slow pseudos first: - * https://github.com/AdguardTeam/ExtendedCss/issues/55#issuecomment-364058745 - */ - - var sortTokenGroups = function () { - /** - * Splits compound selector into a list of simple selectors - * - * @param {*} tokens Tokens to split into groups - * @returns an array consisting of token groups (arrays) and relation tokens. - */ - var splitCompoundSelector = function splitCompoundSelector(tokens) { - var groups = []; - var currentTokensGroup = []; - var maxIdx = tokens.length - 1; - - for (var i = 0; i <= maxIdx; i++) { - var token = tokens[i]; - var relative = Sizzle.selectors.relative[token.type]; - - if (relative) { - groups.push(currentTokensGroup); - groups.push(token); - currentTokensGroup = []; - } else { - currentTokensGroup.push(token); - } - - if (i === maxIdx) { - groups.push(currentTokensGroup); - } - } - - return groups; - }; - - var TOKEN_TYPES_VALUES = { - // nth-child, etc, always go last - "CHILD": 100, - "ID": 90, - "CLASS": 80, - "TAG": 70, - "ATTR": 70, - "PSEUDO": 60 - }; - var POSITIONAL_PSEUDOS = ["nth", "first", "last", "eq", "even", "odd", "lt", "gt", "not"]; - /** - * A function that defines the sort order. - * Returns a value lesser than 0 if "left" is less than "right". - */ - - var compareFunction = function compareFunction(left, right) { - var leftValue = TOKEN_TYPES_VALUES[left.type]; - var rightValue = TOKEN_TYPES_VALUES[right.type]; - return leftValue - rightValue; - }; - /** - * Checks if the specified tokens group is sortable. - * We do not re-sort tokens in case of any positional or child pseudos in the group - */ - - - var isSortable = function isSortable(tokens) { - var iTokens = tokens.length; - - while (iTokens--) { - var token = tokens[iTokens]; - - if (token.type === "PSEUDO" && POSITIONAL_PSEUDOS.indexOf(token.matches[0]) !== -1) { - return false; - } - - if (token.type === "CHILD") { - return false; - } - } - - return true; - }; - /** - * Sorts the tokens in order to mitigate the issues caused by the left-to-right matching. - * The idea is change the tokens order so that Sizzle was matching fast selectors first (id, class), - * and slow selectors after that (and here I mean our slow custom pseudo classes). - * - * @param {Array} tokens An array of tokens to sort - * @returns {Array} A new re-sorted array - */ - - - var sortTokens = function sortTokens(tokens) { - if (!tokens || tokens.length === 1) { - return tokens; - } - - var sortedTokens = []; - var groups = splitCompoundSelector(tokens); - - for (var i = 0; i < groups.length; i++) { - var group = groups[i]; - - if (group instanceof Array) { - if (isSortable(group)) { - group.sort(compareFunction); - } - - sortedTokens = sortedTokens.concat(group); - } else { - sortedTokens.push(group); - } - } - - return sortedTokens; - }; - /** - * Sorts every tokens array inside of the specified "groups" array. - * See "sortTokens" methods for more information on how tokens are sorted. - * - * @param {Array} groups An array of tokens arrays. - * @returns {Array} A new array that consists of the same tokens arrays after sorting - */ - - - var sortTokenGroups = function sortTokenGroups(groups) { - var sortedGroups = []; - var len = groups.length; - var i = 0; - - for (; i < len; i++) { - sortedGroups.push(sortTokens(groups[i])); - } - - return sortedGroups; - }; // Expose - - - return sortTokenGroups; - }(); - /** - * Creates custom policy to use TrustedTypes CSP policy - * https://w3c.github.io/webappsec-trusted-types/dist/spec/ - */ - - - var AGPolicy = function createPolicy() { - var defaultPolicy = { - createHTML: function createHTML(input) { - return input; - }, - createScript: function createScript(input) { - return input; - }, - createScriptURL: function createScriptURL(input) { - return input; - } - }; - - if (window.trustedTypes && window.trustedTypes.createPolicy) { - return window.trustedTypes.createPolicy("AGPolicy", defaultPolicy); - } - - return defaultPolicy; - }(); - /** - * [AdGuard Patch]: - * Removes trailing spaces from the tokens list - * - * @param {*} tokens An array of Sizzle tokens to post-process - */ - - - function removeTrailingSpaces(tokens) { - var iTokens = tokens.length; - - while (iTokens--) { - var token = tokens[iTokens]; - - if (token.type === " ") { - tokens.length = iTokens; - } else { - break; - } - } - } - /** - * [AdGuard Patch]: - * An object with the information about selectors and their token representation - * @typedef {{selectorText: string, groups: Array}} SelectorData - * @property {string} selectorText A CSS selector text - * @property {Array} groups An array of token groups corresponding to that selector - */ - - /** - * [AdGuard Patch]: - * This method processes parsed token groups, divides them into a number of selectors - * and makes sure that each selector's tokens are cached properly in Sizzle. - * - * @param {*} groups Token groups (see {@link Sizzle.tokenize}) - * @returns {Array.} An array of selectors data we got from the groups - */ - - - function tokenGroupsToSelectors(groups) { - // Remove trailing spaces which we can encounter in tolerant mode - // We're doing it in tolerant mode only as this is the only case when - // encountering trailing spaces is expected - removeTrailingSpaces(groups[groups.length - 1]); // We need sorted tokens to make cache work properly - - var sortedGroups = sortTokenGroups(groups); - var selectors = []; - - for (var i = 0; i < groups.length; i++) { - var tokenGroups = groups[i]; - var selectorText = toSelector(tokenGroups); - selectors.push({ - // Sizzle expects an array of token groups when compiling a selector - groups: [tokenGroups], - selectorText: selectorText - }); // Now make sure that selector tokens are cached - - var tokensCacheItem = { - groups: tokenGroups, - sortedGroups: [sortedGroups[i]] - }; - tokenCache(selectorText, tokensCacheItem); - } - - return selectors; - } - /** - * [AdGuard Patch]: - * Add an additional argument for Sizzle.tokenize which indicates that it - * should not throw on invalid tokens, and instead should return tokens - * that it has produced so far. - * - * One more additional argument that allow to choose if you want to receive sorted or unsorted tokens - * The problem is that the re-sorted selectors are valid for Sizzle, but not for the browser. - * options.returnUnsorted -- return unsorted tokens if true. - * options.cacheOnly -- return cached result only. Required for unit-tests. - * - * @param {*} options Optional configuration object with two additional flags - * (options.tolerant, options.returnUnsorted, options.cacheOnly) -- see patches #5 and #6 notes - */ - - - tokenize = Sizzle.tokenize = function (selector, parseOnly, options) { - var matched, - match, - tokens, - type, - soFar, - groups, - preFilters, - cached = tokenCache[selector + " "]; - var tolerant = options && options.tolerant; - var returnUnsorted = options && options.returnUnsorted; - var cacheOnly = options && options.cacheOnly; - - if (cached) { - if (parseOnly) { - return 0; - } else { - return (returnUnsorted ? cached.groups : cached.sortedGroups).slice(0); - } - } - - if (cacheOnly) { - return null; - } - - soFar = selector; - groups = []; - preFilters = Expr.preFilter; - - while (soFar) { - // Comma and first run - if (!matched || (match = rcomma.exec(soFar))) { - if (match) { - // Don't consume trailing commas as valid - soFar = soFar.slice(match[0].length) || soFar; - } - - groups.push(tokens = []); - } - - matched = false; // Combinators - - if (match = rcombinators.exec(soFar)) { - matched = match.shift(); - tokens.push({ - value: matched, - // Cast descendant combinators to space - type: match[0].replace(rtrim, " ") - }); - soFar = soFar.slice(matched.length); - } // Filters - - - for (type in Expr.filter) { - if ((match = matchExpr[type].exec(soFar)) && (!preFilters[type] || (match = preFilters[type](match)))) { - matched = match.shift(); - tokens.push({ - value: matched, - type: type, - matches: match - }); - soFar = soFar.slice(matched.length); - } - } - - if (!matched) { - break; - } - } // Return the length of the invalid excess - // if we're just parsing - // Otherwise, throw an error or return tokens - - - var invalidLen = soFar.length; - - if (parseOnly) { - return invalidLen; - } - - if (invalidLen !== 0 && !tolerant) { - Sizzle.error(selector); // Throws an error. - } - - if (tolerant) { - /** - * [AdGuard Patch]: - * In tolerant mode we return a special object that constists of - * an array of parsed selectors (and their tokens) and a "nextIndex" field - * that points to an index after which we're not able to parse selectors farther. - */ - var nextIndex = selector.length - invalidLen; - var selectors = tokenGroupsToSelectors(groups); - return { - selectors: selectors, - nextIndex: nextIndex - }; - } - /** [AdGuard Patch]: Sorting tokens */ - - - var sortedGroups = sortTokenGroups(groups); - /** [AdGuard Patch]: Change the way tokens are cached */ - - var tokensCacheItem = { - groups: groups, - sortedGroups: sortedGroups - }; - tokensCacheItem = tokenCache(selector, tokensCacheItem); - return (returnUnsorted ? tokensCacheItem.groups : tokensCacheItem.sortedGroups).slice(0); - }; - - function toSelector(tokens) { - var i = 0, - len = tokens.length, - selector = ""; - - for (; i < len; i++) { - selector += tokens[i].value; - } - - return selector; - } - - function addCombinator(matcher, combinator, base) { - var dir = combinator.dir, - skip = combinator.next, - key = skip || dir, - checkNonElements = base && key === "parentNode", - doneName = done++; - return combinator.first ? // Check against closest ancestor/preceding element - function (elem, context, xml) { - while (elem = elem[dir]) { - if (elem.nodeType === 1 || checkNonElements) { - return matcher(elem, context, xml); - } - } - - return false; - } : // Check against all ancestor/preceding elements - function (elem, context, xml) { - var oldCache, - uniqueCache, - outerCache, - newCache = [dirruns, doneName]; // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching - - if (xml) { - while (elem = elem[dir]) { - if (elem.nodeType === 1 || checkNonElements) { - if (matcher(elem, context, xml)) { - return true; - } - } - } - } else { - while (elem = elem[dir]) { - if (elem.nodeType === 1 || checkNonElements) { - outerCache = elem[expando] || (elem[expando] = {}); // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - - uniqueCache = outerCache[elem.uniqueID] || (outerCache[elem.uniqueID] = {}); - - if (skip && skip === elem.nodeName.toLowerCase()) { - elem = elem[dir] || elem; - } else if ((oldCache = uniqueCache[key]) && oldCache[0] === dirruns && oldCache[1] === doneName) { - // Assign to newCache so results back-propagate to previous elements - return newCache[2] = oldCache[2]; - } else { - // Reuse newcache so results back-propagate to previous elements - uniqueCache[key] = newCache; // A match means we're done; a fail means we have to keep checking - - if (newCache[2] = matcher(elem, context, xml)) { - return true; - } - } - } - } - } - - return false; - }; - } - - function elementMatcher(matchers) { - return matchers.length > 1 ? function (elem, context, xml) { - var i = matchers.length; - - while (i--) { - if (!matchers[i](elem, context, xml)) { - return false; - } - } - - return true; - } : matchers[0]; - } - - function multipleContexts(selector, contexts, results) { - var i = 0, - len = contexts.length; - - for (; i < len; i++) { - Sizzle(selector, contexts[i], results); - } - - return results; - } - - function condense(unmatched, map, filter, context, xml) { - var elem, - newUnmatched = [], - i = 0, - len = unmatched.length, - mapped = map != null; - - for (; i < len; i++) { - if (elem = unmatched[i]) { - if (!filter || filter(elem, context, xml)) { - newUnmatched.push(elem); - - if (mapped) { - map.push(i); - } - } - } - } - - return newUnmatched; - } - - function setMatcher(preFilter, selector, matcher, postFilter, postFinder, postSelector) { - if (postFilter && !postFilter[expando]) { - postFilter = setMatcher(postFilter); - } - - if (postFinder && !postFinder[expando]) { - postFinder = setMatcher(postFinder, postSelector); - } - - return markFunction(function (seed, results, context, xml) { - var temp, - i, - elem, - preMap = [], - postMap = [], - preexisting = results.length, - // Get initial elements from seed or context - elems = seed || multipleContexts(selector || "*", context.nodeType ? [context] : context, []), - // Prefilter to get matcher input, preserving a map for seed-results synchronization - matcherIn = preFilter && (seed || !selector) ? condense(elems, preMap, preFilter, context, xml) : elems, - matcherOut = matcher ? // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, - postFinder || (seed ? preFilter : preexisting || postFilter) ? // ...intermediate processing is necessary - [] : // ...otherwise use results directly - results : matcherIn; // Find primary matches - - if (matcher) { - matcher(matcherIn, matcherOut, context, xml); - } // Apply postFilter - - - if (postFilter) { - temp = condense(matcherOut, postMap); - postFilter(temp, [], context, xml); // Un-match failing elements by moving them back to matcherIn - - i = temp.length; - - while (i--) { - if (elem = temp[i]) { - matcherOut[postMap[i]] = !(matcherIn[postMap[i]] = elem); - } - } - } - - if (seed) { - if (postFinder || preFilter) { - if (postFinder) { - // Get the final matcherOut by condensing this intermediate into postFinder contexts - temp = []; - i = matcherOut.length; - - while (i--) { - if (elem = matcherOut[i]) { - // Restore matcherIn since elem is not yet a final match - temp.push(matcherIn[i] = elem); - } - } - - postFinder(null, matcherOut = [], temp, xml); - } // Move matched elements from seed to results to keep them synchronized - - - i = matcherOut.length; - - while (i--) { - if ((elem = matcherOut[i]) && (temp = postFinder ? indexOf(seed, elem) : preMap[i]) > -1) { - seed[temp] = !(results[temp] = elem); - } - } - } // Add elements to results, through postFinder if defined - - } else { - matcherOut = condense(matcherOut === results ? matcherOut.splice(preexisting, matcherOut.length) : matcherOut); - - if (postFinder) { - postFinder(null, results, matcherOut, xml); - } else { - push.apply(results, matcherOut); - } - } - }); - } - - function matcherFromTokens(tokens) { - var checkContext, - matcher, - j, - len = tokens.length, - leadingRelative = Expr.relative[tokens[0].type], - implicitRelative = leadingRelative || Expr.relative[" "], - i = leadingRelative ? 1 : 0, - // The foundational matcher ensures that elements are reachable from top-level context(s) - matchContext = addCombinator(function (elem) { - return elem === checkContext; - }, implicitRelative, true), - matchAnyContext = addCombinator(function (elem) { - return indexOf(checkContext, elem) > -1; - }, implicitRelative, true), - matchers = [function (elem, context, xml) { - var ret = !leadingRelative && (xml || context !== outermostContext) || ((checkContext = context).nodeType ? matchContext(elem, context, xml) : matchAnyContext(elem, context, xml)); // Avoid hanging onto element (issue #299) - - checkContext = null; - return ret; - }]; - - for (; i < len; i++) { - if (matcher = Expr.relative[tokens[i].type]) { - matchers = [addCombinator(elementMatcher(matchers), matcher)]; - } else { - matcher = Expr.filter[tokens[i].type].apply(null, tokens[i].matches); // Return special upon seeing a positional matcher - - if (matcher[expando]) { - // Find the next relative operator (if any) for proper handling - j = ++i; - - for (; j < len; j++) { - if (Expr.relative[tokens[j].type]) { - break; - } - } - - return setMatcher(i > 1 && elementMatcher(matchers), i > 1 && toSelector( // If the preceding token was a descendant combinator, insert an implicit any-element `*` - tokens.slice(0, i - 1).concat({ - value: tokens[i - 2].type === " " ? "*" : "" - })).replace(rtrim, "$1"), matcher, i < j && matcherFromTokens(tokens.slice(i, j)), j < len && matcherFromTokens(tokens = tokens.slice(j)), j < len && toSelector(tokens)); - } - - matchers.push(matcher); - } - } - - return elementMatcher(matchers); - } - - function matcherFromGroupMatchers(elementMatchers, setMatchers) { - var bySet = setMatchers.length > 0, - byElement = elementMatchers.length > 0, - superMatcher = function superMatcher(seed, context, xml, results, outermost) { - var elem, - j, - matcher, - matchedCount = 0, - i = "0", - unmatched = seed && [], - setMatched = [], - contextBackup = outermostContext, - // We must always have either seed elements or outermost context - elems = seed || byElement && Expr.find["TAG"]("*", outermost), - // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = dirruns += contextBackup == null ? 1 : Math.random() || 0.1, - len = elems.length; - - if (outermost) { - outermostContext = context === document || context || outermost; - } // Add elements passing elementMatchers directly to results - // Support: IE<9, Safari - // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id - - - for (; i !== len && (elem = elems[i]) != null; i++) { - if (byElement && elem) { - j = 0; - - if (!context && elem.ownerDocument !== document) { - setDocument(elem); - xml = !documentIsHTML; - } - - while (matcher = elementMatchers[j++]) { - if (matcher(elem, context || document, xml)) { - results.push(elem); - break; - } - } - - if (outermost) { - dirruns = dirrunsUnique; - } - } // Track unmatched elements for set filters - - - if (bySet) { - // They will have gone through all possible matchers - if (elem = !matcher && elem) { - matchedCount--; - } // Lengthen the array for every element, matched or not - - - if (seed) { - unmatched.push(elem); - } - } - } // `i` is now the count of elements visited above, and adding it to `matchedCount` - // makes the latter nonnegative. - - - matchedCount += i; // Apply set filters to unmatched elements - // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` - // equals `i`), unless we didn't visit _any_ elements in the above loop because we have - // no element matchers and no seed. - // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that - // case, which will result in a "00" `matchedCount` that differs from `i` but is also - // numerically zero. - - if (bySet && i !== matchedCount) { - j = 0; - - while (matcher = setMatchers[j++]) { - matcher(unmatched, setMatched, context, xml); - } - - if (seed) { - // Reintegrate element matches to eliminate the need for sorting - if (matchedCount > 0) { - while (i--) { - if (!(unmatched[i] || setMatched[i])) { - setMatched[i] = pop.call(results); - } - } - } // Discard index placeholder values to get only actual matches - - - setMatched = condense(setMatched); - } // Add matches to results - - - push.apply(results, setMatched); // Seedless set matches succeeding multiple successful matchers stipulate sorting - - if (outermost && !seed && setMatched.length > 0 && matchedCount + setMatchers.length > 1) { - Sizzle.uniqueSort(results); - } - } // Override manipulation of globals by nested matchers - - - if (outermost) { - dirruns = dirrunsUnique; - outermostContext = contextBackup; - } - - return unmatched; - }; - - return bySet ? markFunction(superMatcher) : superMatcher; - } - - compile = Sizzle.compile = function (selector, match - /* Internal Use Only */ - ) { - var i, - setMatchers = [], - elementMatchers = [], - cached = compilerCache[selector + " "]; - - if (!cached) { - // Generate a function of recursive functions that can be used to check each element - if (!match) { - match = tokenize(selector); - } - - i = match.length; - - while (i--) { - cached = matcherFromTokens(match[i]); - - if (cached[expando]) { - setMatchers.push(cached); - } else { - elementMatchers.push(cached); - } - } // Cache the compiled function - - - cached = compilerCache(selector, matcherFromGroupMatchers(elementMatchers, setMatchers)); // Save selector and tokenization - - cached.selector = selector; - } - - return cached; - }; - /** - * A low-level selection function that works with Sizzle's compiled - * selector functions - * @param {String|Function} selector A selector or a pre-compiled - * selector function built with Sizzle.compile - * @param {Element} context - * @param {Array} [results] - * @param {Array} [seed] A set of elements to match against - */ - - - select = Sizzle.select = function (selector, context, results, seed) { - var i, - tokens, - token, - type, - find, - compiled = typeof selector === "function" && selector, - match = !seed && tokenize(selector = compiled.selector || selector); - results = results || []; // Try to minimize operations if there is only one selector in the list and no seed - // (the latter of which guarantees us context) - - if (match.length === 1) { - // Reduce context if the leading compound selector is an ID - tokens = match[0] = match[0].slice(0); - - if (tokens.length > 2 && (token = tokens[0]).type === "ID" && context.nodeType === 9 && documentIsHTML && Expr.relative[tokens[1].type]) { - context = (Expr.find["ID"](token.matches[0].replace(runescape, funescape), context) || [])[0]; - - if (!context) { - return results; // Precompiled matchers will still verify ancestry, so step up a level - } else if (compiled) { - context = context.parentNode; - } - - selector = selector.slice(tokens.shift().value.length); - } // Fetch a seed set for right-to-left matching - - - i = matchExpr["needsContext"].test(selector) ? 0 : tokens.length; - - while (i--) { - token = tokens[i]; // Abort if we hit a combinator - - if (Expr.relative[type = token.type]) { - break; - } - - if (find = Expr.find[type]) { - // Search, expanding context for leading sibling combinators - if (seed = find(token.matches[0].replace(runescape, funescape), rsibling.test(tokens[0].type) && testContext(context.parentNode) || context)) { - // If seed is empty or no tokens remain, we can return early - tokens.splice(i, 1); - selector = seed.length && toSelector(tokens); - - if (!selector) { - push.apply(results, seed); - return results; - } - - break; - } - } - } - } // Compile and execute a filtering function if one is not provided - // Provide `match` to avoid retokenization if we modified the selector above - - - (compiled || compile(selector, match))(seed, context, !documentIsHTML, results, !context || rsibling.test(selector) && testContext(context.parentNode) || context); - return results; - }; // One-time assignments - // Sort stability - - - support.sortStable = expando.split("").sort(sortOrder).join("") === expando; // Support: Chrome 14-35+ - // Always assume duplicates if they aren't passed to the comparison function - - support.detectDuplicates = !!hasDuplicate; // Initialize against the default document - - setDocument(); // Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) - // Detached nodes confoundingly follow *each other* - - support.sortDetached = assert(function (el) { - // Should return 1, but returns 4 (following) - return el.compareDocumentPosition(document.createElement("fieldset")) & 1; - }); // Support: IE<8 - // Prevent attribute/property "interpolation" - // https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx - - if (!assert(function (el) { - el.innerHTML = AGPolicy.createHTML(""); - return el.firstChild.getAttribute("href") === "#"; - })) { - addHandle("type|href|height|width", function (elem, name, isXML) { - if (!isXML) { - return elem.getAttribute(name, name.toLowerCase() === "type" ? 1 : 2); - } - }); - } // Support: IE<9 - // Use defaultValue in place of getAttribute("value") - - - if (!support.attributes || !assert(function (el) { - el.innerHTML = AGPolicy.createHTML(""); - el.firstChild.setAttribute("value", ""); - return el.firstChild.getAttribute("value") === ""; - })) { - addHandle("value", function (elem, name, isXML) { - if (!isXML && elem.nodeName.toLowerCase() === "input") { - return elem.defaultValue; - } - }); - } // Support: IE<9 - // Use getAttributeNode to fetch booleans when getAttribute lies - - - if (!assert(function (el) { - return el.getAttribute("disabled") == null; - })) { - addHandle(booleans, function (elem, name, isXML) { - var val; - - if (!isXML) { - return elem[name] === true ? name.toLowerCase() : (val = elem.getAttributeNode(name)) && val.specified ? val.value : null; - } - }); - } // EXPOSE - // Do not expose Sizzle to the global scope in the case of AdGuard ExtendedCss build - - - return Sizzle; // EXPOSE - }(window); //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - } - - return Sizzle; -}; - -/* jshint ignore:end */ - -/** - * Copyright 2016 Adguard Software Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - * Class that extends Sizzle and adds support for "matches-css" pseudo element. - */ - -var StylePropertyMatcher = function (window) { - var isPhantom = !!window._phantom; - var useFallback = isPhantom && !!window.getMatchedCSSRules; - /** - * Unquotes specified value - * Webkit-based browsers singlequotes content property values - * Other browsers doublequotes content property values. - */ - - var removeContentQuotes = function removeContentQuotes(value) { - if (typeof value === 'string') { - return value.replace(/^(["'])([\s\S]*)\1$/, '$2'); - } - - return value; - }; - - var getComputedStyle = window.getComputedStyle.bind(window); - var getMatchedCSSRules = useFallback ? window.getMatchedCSSRules.bind(window) : null; - /** - * There is an issue in browsers based on old webkit: - * getComputedStyle(el, ":before") is empty if element is not visible. - * - * To circumvent this issue we use getMatchedCSSRules instead. - * - * It appears that getMatchedCSSRules sorts the CSS rules - * in increasing order of specifities of corresponding selectors. - * We pick the css rule that is being applied to an element based on this assumption. - * - * @param element DOM node - * @param pseudoElement Optional pseudoElement name - * @param propertyName CSS property name - */ - - var getComputedStylePropertyValue = function getComputedStylePropertyValue(element, pseudoElement, propertyName) { - var value = ''; - - if (useFallback && pseudoElement) { - var cssRules = getMatchedCSSRules(element, pseudoElement) || []; - var i = cssRules.length; - - while (i-- > 0 && !value) { - value = cssRules[i].style.getPropertyValue(propertyName); - } - } else { - var style = getComputedStyle(element, pseudoElement); - - if (style) { - value = style.getPropertyValue(propertyName); // https://bugs.webkit.org/show_bug.cgi?id=93445 - - if (propertyName === 'opacity' && utils.isSafariBrowser) { - value = (Math.round(parseFloat(value) * 100) / 100).toString(); - } - } - } - - if (propertyName === 'content') { - value = removeContentQuotes(value); - } - - return value; - }; - /** - * Adds url parameter quotes for non-regex pattern - * @param {string} pattern - */ - - - var addUrlQuotes = function addUrlQuotes(pattern) { - // for regex patterns - if (pattern[0] === '/' && pattern[pattern.length - 1] === '/' && pattern.indexOf('\\"') < 10) { - // e.g. /^url\\([a-z]{4}:[a-z]{5}/ - // or /^url\\(data\\:\\image\\/gif;base64.+/ - var re = /(\^)?url(\\)?\\\((\w|\[\w)/g; - return pattern.replace(re, '$1url$2\\\(\\"?$3'); - } // for non-regex patterns - - - if (pattern.indexOf('url("') === -1) { - var _re = /url\((.*?)\)/g; - return pattern.replace(_re, 'url("$1")'); - } - - return pattern; - }; - /** - * Class that matches element style against the specified expression - * @member {string} propertyName - * @member {string} pseudoElement - * @member {RegExp} regex - */ - - - var Matcher = function Matcher(propertyFilter, pseudoElement) { - this.pseudoElement = pseudoElement; - - try { - var index = propertyFilter.indexOf(':'); - this.propertyName = propertyFilter.substring(0, index).trim(); - var pattern = propertyFilter.substring(index + 1).trim(); - pattern = addUrlQuotes(pattern); // Unescaping pattern - // For non-regex patterns, (,),[,] should be unescaped, because we require escaping them in filter rules. - // For regex patterns, ",\ should be escaped, because we manually escape those in extended-css-selector.js. - - if (/^\/.*\/$/.test(pattern)) { - pattern = pattern.slice(1, -1); - this.regex = utils.pseudoArgToRegex(pattern); - } else { - pattern = pattern.replace(/\\([\\()[\]"])/g, '$1'); - this.regex = utils.createURLRegex(pattern); - } - } catch (ex) { - utils.logError("StylePropertyMatcher: invalid match string ".concat(propertyFilter)); - } - }; - /** - * Function to check if element CSS property matches filter pattern - * @param {Element} element to check - */ - - - Matcher.prototype.matches = function (element) { - if (!this.regex || !this.propertyName) { - return false; - } - - var value = getComputedStylePropertyValue(element, this.pseudoElement, this.propertyName); - return value && this.regex.test(value); - }; - /** - * Creates a new pseudo-class and registers it in Sizzle - */ - - - var extendSizzle = function extendSizzle(sizzle) { - // First of all we should prepare Sizzle engine - sizzle.selectors.pseudos['matches-css'] = sizzle.selectors.createPseudo(function (propertyFilter) { - var matcher = new Matcher(propertyFilter); - return function (element) { - return matcher.matches(element); - }; - }); - sizzle.selectors.pseudos['matches-css-before'] = sizzle.selectors.createPseudo(function (propertyFilter) { - var matcher = new Matcher(propertyFilter, ':before'); - return function (element) { - return matcher.matches(element); - }; - }); - sizzle.selectors.pseudos['matches-css-after'] = sizzle.selectors.createPseudo(function (propertyFilter) { - var matcher = new Matcher(propertyFilter, ':after'); - return function (element) { - return matcher.matches(element); - }; - }); - }; // EXPOSE - - - return { - extendSizzle: extendSizzle - }; -}(window); - -/** - * Copyright 2016 Adguard Software Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -var matcherUtils = {}; -matcherUtils.MutationObserver = window.MutationObserver || window.WebKitMutationObserver; -/** - * Parses argument of matcher pseudo (for matches-attr and matches-property) - * @param {string} matcherFilter argument of pseudo class - * @returns {Array} - */ - -matcherUtils.parseMatcherFilter = function (matcherFilter) { - var FULL_MATCH_MARKER = '"="'; - var rawArgs = []; - - if (matcherFilter.indexOf(FULL_MATCH_MARKER) === -1) { - // if there is only one pseudo arg - // e.g. :matches-attr("data-name") or :matches-property("inner.prop") - // Sizzle will parse it and get rid of quotes - // so it might be valid arg already without them - rawArgs.push(matcherFilter); - } else { - matcherFilter.split('=').forEach(function (arg) { - if (arg[0] === '"' && arg[arg.length - 1] === '"') { - rawArgs.push(arg.slice(1, -1)); - } - }); - } - - return rawArgs; -}; -/** - * @typedef {Object} ArgData - * @property {string} arg - * @property {boolean} isRegexp - */ - -/** - * Parses raw matcher arg - * @param {string} rawArg - * @returns {ArgData} - */ - - -matcherUtils.parseRawMatcherArg = function (rawArg) { - var arg = rawArg; - var isRegexp = !!rawArg && rawArg[0] === '/' && rawArg[rawArg.length - 1] === '/'; - - if (isRegexp) { - // to avoid at least such case — :matches-property("//") - if (rawArg.length > 2) { - arg = utils.toRegExp(rawArg); - } else { - throw new Error("Invalid regexp: ".concat(rawArg)); - } - } - - return { - arg: arg, - isRegexp: isRegexp - }; -}; -/** - * @typedef Chain - * @property {Object} base - * @property {string} prop - * @property {string} value - */ - -/** - * Checks if the property exists in the base object (recursively). - * @param {Object} base - * @param {ArgData[]} chain array of objects - parsed string property chain - * @param {Array} [output=[]] result acc - * @returns {Chain[]} array of objects - */ - - -matcherUtils.filterRootsByRegexpChain = function (base, chain) { - var output = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : []; - var tempProp = chain[0]; - - if (chain.length === 1) { - // eslint-disable-next-line no-restricted-syntax - for (var key in base) { - if (tempProp.isRegexp) { - if (tempProp.arg.test(key)) { - output.push({ - base: base, - prop: key, - value: base[key] - }); - } - } else if (tempProp.arg === key) { - output.push({ - base: base, - prop: tempProp.arg, - value: base[key] - }); - } - } - - return output; - } // if there is a regexp prop in input chain - // e.g. 'unit./^ad.+/.src' for 'unit.ad-1gf2.src unit.ad-fgd34.src'), - // every base keys should be tested by regexp and it can be more that one results - - - if (tempProp.isRegexp) { - var nextProp = chain.slice(1); - var baseKeys = []; // eslint-disable-next-line no-restricted-syntax - - for (var _key in base) { - if (tempProp.arg.test(_key)) { - baseKeys.push(_key); - } - } - - baseKeys.forEach(function (key) { - var item = base[key]; - matcherUtils.filterRootsByRegexpChain(item, nextProp, output); - }); - } // avoid TypeError while accessing to null-prop's child - - - if (base === null) { - return; - } - - var nextBase = base[tempProp.arg]; - chain = chain.slice(1); - - if (nextBase !== undefined) { - matcherUtils.filterRootsByRegexpChain(nextBase, chain, output); - } - - return output; -}; -/** - * Validates parsed args of matches-property pseudo - * @param {...ArgData} args - */ - - -matcherUtils.validatePropMatcherArgs = function () { - for (var _len = arguments.length, args = new Array(_len), _key2 = 0; _key2 < _len; _key2++) { - args[_key2] = arguments[_key2]; - } - - for (var i = 0; i < args.length; i += 1) { - if (args[i].isRegexp) { - if (!utils.startsWith(args[i].arg.toString(), '/') || !utils.endsWith(args[i].arg.toString(), '/')) { - return false; - } // simple arg check if it is not a regexp - - } else if (!/^[\w-]+$/.test(args[i].arg)) { - return false; - } - } - - return true; -}; - -/** - * Class that extends Sizzle and adds support for "matches-attr" pseudo element. - */ - -var AttributesMatcher = function () { - /** - * Class that matches element attributes against the specified expressions - * @param {ArgData} nameArg - parsed name argument - * @param {ArgData} valueArg - parsed value argument - * @param {string} pseudoElement - * @constructor - * - * @member {string|RegExp} attrName - * @member {boolean} isRegexpName - * @member {string|RegExp} attrValue - * @member {boolean} isRegexpValue - */ - var AttrMatcher = function AttrMatcher(nameArg, valueArg, pseudoElement) { - this.pseudoElement = pseudoElement; - this.attrName = nameArg.arg; - this.isRegexpName = nameArg.isRegexp; - this.attrValue = valueArg.arg; - this.isRegexpValue = valueArg.isRegexp; - }; - /** - * Function to check if element attributes matches filter pattern - * @param {Element} element to check - */ - - - AttrMatcher.prototype.matches = function (element) { - var elAttrs = element.attributes; - - if (elAttrs.length === 0 || !this.attrName) { - return false; - } - - var i = 0; - - while (i < elAttrs.length) { - var attr = elAttrs[i]; - var matched = false; - var attrNameMatched = this.isRegexpName ? this.attrName.test(attr.name) : this.attrName === attr.name; - - if (!this.attrValue) { - // for :matches-attr("/regex/") or :matches-attr("attr-name") - matched = attrNameMatched; - } else { - var attrValueMatched = this.isRegexpValue ? this.attrValue.test(attr.value) : this.attrValue === attr.value; - matched = attrNameMatched && attrValueMatched; - } - - if (matched) { - return true; - } - - i += 1; - } - }; - /** - * Creates a new pseudo-class and registers it in Sizzle - */ - - - var extendSizzle = function extendSizzle(sizzle) { - // First of all we should prepare Sizzle engine - sizzle.selectors.pseudos['matches-attr'] = sizzle.selectors.createPseudo(function (attrFilter) { - var _matcherUtils$parseMa = matcherUtils.parseMatcherFilter(attrFilter), - _matcherUtils$parseMa2 = _slicedToArray(_matcherUtils$parseMa, 2), - rawName = _matcherUtils$parseMa2[0], - rawValue = _matcherUtils$parseMa2[1]; - - var nameArg = matcherUtils.parseRawMatcherArg(rawName); - var valueArg = matcherUtils.parseRawMatcherArg(rawValue); - - if (!attrFilter || !matcherUtils.validatePropMatcherArgs(nameArg, valueArg)) { - throw new Error("Invalid argument of :matches-attr pseudo class: ".concat(attrFilter)); - } - - var matcher = new AttrMatcher(nameArg, valueArg); - return function (element) { - return matcher.matches(element); - }; - }); - }; // EXPOSE - - - return { - extendSizzle: extendSizzle - }; -}(); - -/** - * Parses raw property arg - * @param {string} input - * @returns {ArgData[]} array of objects - */ - -var parseRawPropChain = function parseRawPropChain(input) { - var PROPS_DIVIDER = '.'; - var REGEXP_MARKER = '/'; - var propsArr = []; - var str = input; - - while (str.length > 0) { - if (utils.startsWith(str, PROPS_DIVIDER)) { - // for cases like '.prop.id' and 'nested..test' - throw new Error("Invalid chain property: ".concat(input)); - } - - if (!utils.startsWith(str, REGEXP_MARKER)) { - var isRegexp = false; - var dividerIndex = str.indexOf(PROPS_DIVIDER); - - if (str.indexOf(PROPS_DIVIDER) === -1) { - // if there is no '.' left in str - // take the rest of str as prop - propsArr.push({ - arg: str, - isRegexp: isRegexp - }); - return propsArr; - } // else take prop from str - - - var prop = str.slice(0, dividerIndex); // for cases like 'asadf.?+/.test' - - if (prop.indexOf(REGEXP_MARKER) > -1) { - // prop is '?+/' - throw new Error("Invalid chain property: ".concat(prop)); - } - - propsArr.push({ - arg: prop, - isRegexp: isRegexp - }); // delete prop from str - - str = str.slice(dividerIndex); - } else { - // deal with regexp - var propChunks = []; - propChunks.push(str.slice(0, 1)); // if str starts with '/', delete it from str and find closing regexp slash. - // note that chained property name can not include '/' or '.' - // so there is no checking for escaped characters - - str = str.slice(1); - var regexEndIndex = str.indexOf(REGEXP_MARKER); - - if (regexEndIndex < 1) { - // regexp should be at least === '/./' - // so we should avoid args like '/id' and 'test.//.id' - throw new Error("Invalid regexp: ".concat(REGEXP_MARKER).concat(str)); - } - - var _isRegexp = true; // take the rest regexp part - - propChunks.push(str.slice(0, regexEndIndex + 1)); - - var _prop = utils.toRegExp(propChunks.join('')); - - propsArr.push({ - arg: _prop, - isRegexp: _isRegexp - }); // delete prop from str - - str = str.slice(regexEndIndex + 1); - } - - if (!str) { - return propsArr; - } // str should be like '.nextProp' now - // so 'zx.prop' or '.' is invalid - - - if (!utils.startsWith(str, PROPS_DIVIDER) || utils.startsWith(str, PROPS_DIVIDER) && str.length === 1) { - throw new Error("Invalid chain property: ".concat(input)); - } - - str = str.slice(1); - } -}; - -var convertTypeFromStr = function convertTypeFromStr(value) { - var numValue = Number(value); - var output; - - if (!Number.isNaN(numValue)) { - output = numValue; - } else { - switch (value) { - case 'undefined': - output = undefined; - break; - - case 'null': - output = null; - break; - - case 'true': - output = true; - break; - - case 'false': - output = false; - break; - - default: - output = value; - } - } - - return output; -}; - -var convertTypeIntoStr = function convertTypeIntoStr(value) { - var output; - - switch (value) { - case undefined: - output = 'undefined'; - break; - - case null: - output = 'null'; - break; - - default: - output = value.toString(); - } - - return output; -}; -/** - * Class that extends Sizzle and adds support for "matches-property" pseudo element. - */ - - -var ElementPropertyMatcher = function () { - /** - * Class that matches element properties against the specified expressions - * @param {ArgData[]} propsChainArg - array of parsed props chain objects - * @param {ArgData} valueArg - parsed value argument - * @param {string} pseudoElement - * @constructor - * - * @member {Array} chainedProps - * @member {boolean} isRegexpName - * @member {string|RegExp} propValue - * @member {boolean} isRegexpValue - */ - var PropMatcher = function PropMatcher(propsChainArg, valueArg, pseudoElement) { - this.pseudoElement = pseudoElement; - this.chainedProps = propsChainArg; - this.propValue = valueArg.arg; - this.isRegexpValue = valueArg.isRegexp; - }; - /** - * Function to check if element properties matches filter pattern - * @param {Element} element to check - */ - - - PropMatcher.prototype.matches = function (element) { - var ownerObjArr = matcherUtils.filterRootsByRegexpChain(element, this.chainedProps); - - if (ownerObjArr.length === 0) { - return false; - } - - var matched = true; - - if (this.propValue) { - for (var i = 0; i < ownerObjArr.length; i += 1) { - var realValue = ownerObjArr[i].value; - - if (this.isRegexpValue) { - matched = this.propValue.test(convertTypeIntoStr(realValue)); - } else { - // handle 'null' and 'undefined' property values set as string - if (realValue === 'null' || realValue === 'undefined') { - matched = this.propValue === realValue; - break; - } - - matched = convertTypeFromStr(this.propValue) === realValue; - } - - if (matched) { - break; - } - } - } - - return matched; - }; - /** - * Creates a new pseudo-class and registers it in Sizzle - */ - - - var extendSizzle = function extendSizzle(sizzle) { - // First of all we should prepare Sizzle engine - sizzle.selectors.pseudos['matches-property'] = sizzle.selectors.createPseudo(function (propertyFilter) { - if (!propertyFilter) { - throw new Error('No argument is given for :matches-property pseudo class'); - } - - var _matcherUtils$parseMa = matcherUtils.parseMatcherFilter(propertyFilter), - _matcherUtils$parseMa2 = _slicedToArray(_matcherUtils$parseMa, 2), - rawProp = _matcherUtils$parseMa2[0], - rawValue = _matcherUtils$parseMa2[1]; // chained property name can not include '/' or '.' - // so regex prop names with such escaped characters are invalid - - - if (rawProp.indexOf('\\/') > -1 || rawProp.indexOf('\\.') > -1) { - throw new Error("Invalid property name: ".concat(rawProp)); - } - - var propsChainArg = parseRawPropChain(rawProp); - var valueArg = matcherUtils.parseRawMatcherArg(rawValue); - var propsToValidate = [].concat(_toConsumableArray(propsChainArg), [valueArg]); - - if (!matcherUtils.validatePropMatcherArgs(propsToValidate)) { - throw new Error("Invalid argument of :matches-property pseudo class: ".concat(propertyFilter)); - } - - var matcher = new PropMatcher(propsChainArg, valueArg); - return function (element) { - return matcher.matches(element); - }; - }); - }; // EXPOSE - - - return { - extendSizzle: extendSizzle - }; -}(); - -/** - * Copyright 2020 Adguard Software Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - * Class that extends Sizzle and adds support for :is() pseudo element. - */ - -var IsAnyMatcher = function () { - /** - * Class that matches element by one of the selectors - * https://developer.mozilla.org/en-US/docs/Web/CSS/:is - * @param {Array} selectors - * @param {string} pseudoElement - * @constructor - */ - var IsMatcher = function IsMatcher(selectors, pseudoElement) { - this.selectors = selectors; - this.pseudoElement = pseudoElement; - }; - /** - * Function to check if element can be matched by any passed selector - * @param {Element} element to check - */ - - - IsMatcher.prototype.matches = function (element) { - var isMatched = !!this.selectors.find(function (selector) { - var nodes = document.querySelectorAll(selector); - return Array.from(nodes).find(function (node) { - return node === element; - }); - }); - return isMatched; - }; - /** - * Creates a new pseudo-class and registers it in Sizzle - */ - - - var extendSizzle = function extendSizzle(sizzle) { - // First of all we should prepare Sizzle engine - sizzle.selectors.pseudos['is'] = sizzle.selectors.createPseudo(function (input) { - if (input === '') { - throw new Error("Invalid argument of :is pseudo-class: ".concat(input)); - } - - var selectors = input.split(',').map(function (s) { - return s.trim(); - }); // collect valid selectors and log about invalid ones - - var validSelectors = selectors.reduce(function (acc, selector) { - if (cssUtils.isSimpleSelectorValid(selector)) { - acc.push(selector); - } else { - utils.logInfo("Invalid selector passed to :is() pseudo-class: '".concat(selector, "'")); - } - - return acc; - }, []); - var matcher = new IsMatcher(validSelectors); - return function (element) { - return matcher.matches(element); - }; - }); - }; - - return { - extendSizzle: extendSizzle - }; -}(); - -/** - * Copyright 2021 Adguard Software Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - * Extended selector factory module, for creating extended selector classes. - * - * Extended selection capabilities description: - * https://github.com/AdguardTeam/ExtendedCss/blob/master/README.md - */ - -var ExtendedSelectorFactory = function () { - // while adding new markers, constants in other AdGuard repos should be corrected - // AdGuard browser extension : CssFilterRule.SUPPORTED_PSEUDO_CLASSES and CssFilterRule.EXTENDED_CSS_MARKERS - // tsurlfilter, SafariConverterLib : EXT_CSS_PSEUDO_INDICATORS - var PSEUDO_EXTENSIONS_MARKERS = [':has', ':contains', ':has-text', ':matches-css', ':-abp-has', ':-abp-has-text', ':if', ':if-not', ':xpath', ':nth-ancestor', ':upward', ':remove', ':matches-attr', ':matches-property', ':-abp-contains', ':is']; - var initialized = false; - var Sizzle; - /** - * Lazy initialization of the ExtendedSelectorFactory and objects that might be necessary for creating and applying styles. - * This method extends Sizzle engine that we use under the hood with our custom pseudo-classes. - */ - - function initialize() { - if (initialized) { - return; - } - - initialized = true; // Our version of Sizzle is initialized lazily as well - - Sizzle = initializeSizzle(); // Add :matches-css-*() support - - StylePropertyMatcher.extendSizzle(Sizzle); // Add :matches-attr() support - - AttributesMatcher.extendSizzle(Sizzle); // Add :matches-property() support - - ElementPropertyMatcher.extendSizzle(Sizzle); // Add :is() support - - IsAnyMatcher.extendSizzle(Sizzle); // Add :contains, :has-text, :-abp-contains support - - var containsPseudo = Sizzle.selectors.createPseudo(function (text) { - if (/^\s*\/.*\/[gmisuy]*\s*$/.test(text)) { - text = text.trim(); - var flagsIndex = text.lastIndexOf('/'); - var flags = text.substring(flagsIndex + 1); - text = text.substr(0, flagsIndex + 1).slice(1, -1).replace(/\\([\\"])/g, '$1'); - var regex; - - try { - regex = new RegExp(text, flags); - } catch (e) { - throw new Error("Invalid argument of :contains pseudo class: ".concat(text)); - } - - return function (elem) { - var elemTextContent = utils.nodeTextContentGetter.apply(elem); - return regex.test(elemTextContent); - }; - } - - text = text.replace(/\\([\\()[\]"])/g, '$1'); - return function (elem) { - var elemTextContent = utils.nodeTextContentGetter.apply(elem); - return elemTextContent.indexOf(text) > -1; - }; - }); - Sizzle.selectors.pseudos['contains'] = containsPseudo; - Sizzle.selectors.pseudos['has-text'] = containsPseudo; - Sizzle.selectors.pseudos['-abp-contains'] = containsPseudo; // Add :if, :-abp-has support - - Sizzle.selectors.pseudos['if'] = Sizzle.selectors.pseudos['has']; - Sizzle.selectors.pseudos['-abp-has'] = Sizzle.selectors.pseudos['has']; // Add :if-not support - - Sizzle.selectors.pseudos['if-not'] = Sizzle.selectors.createPseudo(function (selector) { - if (typeof selector === 'string') { - Sizzle.compile(selector); - } - - return function (elem) { - return Sizzle(selector, elem).length === 0; - }; - }); - registerParserOnlyTokens(); - } - /** - * Registrate custom tokens for parser. - * Needed for proper work of pseudos: - * for checking if the token is last and pseudo-class arguments validation - */ - - - function registerParserOnlyTokens() { - Sizzle.selectors.pseudos['xpath'] = Sizzle.selectors.createPseudo(function (selector) { - try { - document.createExpression(selector, null); - } catch (e) { - throw new Error("Invalid argument of :xpath pseudo class: ".concat(selector)); - } - - return function () { - return true; - }; - }); - Sizzle.selectors.pseudos['nth-ancestor'] = Sizzle.selectors.createPseudo(function (selector) { - var deep = Number(selector); - - if (Number.isNaN(deep) || deep < 1 || deep >= 256) { - throw new Error("Invalid argument of :nth-ancestor pseudo class: ".concat(selector)); - } - - return function () { - return true; - }; - }); - Sizzle.selectors.pseudos['upward'] = Sizzle.selectors.createPseudo(function (input) { - if (input === '') { - throw new Error("Invalid argument of :upward pseudo class: ".concat(input)); - } else if (Number.isInteger(+input) && (+input < 1 || +input >= 256)) { - throw new Error("Invalid argument of :upward pseudo class: ".concat(input)); - } - - return function () { - return true; - }; - }); - Sizzle.selectors.pseudos['remove'] = Sizzle.selectors.createPseudo(function (input) { - if (input !== '') { - throw new Error("Invalid argument of :remove pseudo class: ".concat(input)); - } - - return function () { - return true; - }; - }); - } - /** - * Checks if specified token can be used by document.querySelectorAll. - */ - - - function isSimpleToken(token) { - var type = token.type; - - if (type === 'ID' || type === 'CLASS' || type === 'ATTR' || type === 'TAG' || type === 'CHILD') { - // known simple tokens - return true; - } - - if (type === 'PSEUDO') { - // check if value contains any of extended pseudo classes - var i = PSEUDO_EXTENSIONS_MARKERS.length; - - while (i--) { - if (token.value.indexOf(PSEUDO_EXTENSIONS_MARKERS[i]) >= 0) { - return false; - } - } - - return true; - } // all others aren't simple - - - return false; - } - /** - * Checks if specified token is a combinator - */ - - - function isRelationToken(token) { - var type = token.type; - return type === ' ' || type === '>' || type === '+' || type === '~'; - } - /** - * ExtendedSelectorParser is a helper class for creating various selector instances which - * all shares a method `querySelectorAll()` and `matches()` implementing different search strategies - * depending on a type of selector. - * - * Currently, there are 3 types: - * A trait-less extended selector - * - we directly feed selector strings to Sizzle. - * A splitted extended selector - * - such as #container #feedItem:has(.ads), where it is splitted to `#container` and `#feedItem:has(.ads)`. - */ - - - function ExtendedSelectorParser(selectorText, tokens, debug) { - initialize(); - - if (typeof tokens === 'undefined') { - this.selectorText = cssUtils.normalize(selectorText); // Passing `returnUnsorted` in order to receive tokens in the order that's valid for the browser - // In Sizzle internally, the tokens are re-sorted: https://github.com/AdguardTeam/ExtendedCss/issues/55 - - this.tokens = Sizzle.tokenize(this.selectorText, false, { - returnUnsorted: true - }); - } else { - this.selectorText = selectorText; - this.tokens = tokens; - } - - if (debug === true) { - this.debug = true; - } - } - - ExtendedSelectorParser.prototype = { - /** - * The main method, creates a selector instance depending on the type of a selector. - * @public - */ - createSelector: function createSelector() { - var debug = this.debug; - var tokens = this.tokens; - var selectorText = this.selectorText; - - if (tokens.length !== 1) { - // Comma-separate selector - can't optimize further - return new TraitLessSelector(selectorText, debug); - } - - var xpathPart = this.getXpathPart(); - - if (typeof xpathPart !== 'undefined') { - return new XpathSelector(selectorText, xpathPart, debug); - } - - var upwardPart = this.getUpwardPart(); - - if (typeof upwardPart !== 'undefined') { - var output; - var upwardDeep = parseInt(upwardPart, 10); // if upward parameter is not a number, we consider it as a selector - - if (Number.isNaN(upwardDeep)) { - output = new UpwardSelector(selectorText, upwardPart, debug); - } else { - // upward works like nth-ancestor - var xpath = this.convertNthAncestorToken(upwardDeep); - output = new XpathSelector(selectorText, xpath, debug); - } - - return output; - } // argument of pseudo-class remove; - // it's defined only if remove is parsed as last token - // and it's valid only if remove arg is empty string - - - var removePart = this.getRemovePart(); - - if (typeof removePart !== 'undefined') { - var hasValidRemovePart = removePart === ''; - return new RemoveSelector(selectorText, hasValidRemovePart, debug); - } - - tokens = tokens[0]; - var l = tokens.length; - var lastRelTokenInd = this.getSplitPoint(); - - if (typeof lastRelTokenInd === 'undefined') { - try { - document.querySelector(selectorText); - } catch (e) { - return new TraitLessSelector(selectorText, debug); - } - - return new NotAnExtendedSelector(selectorText, debug); - } - - var simple = ''; - var relation = null; - var complex = ''; - var i = 0; - - for (; i < lastRelTokenInd; i++) { - // build simple part - simple += tokens[i].value; - } - - if (i > 0) { - // build relation part - relation = tokens[i++].type; - } // i is pointing to the start of a complex part. - - - for (; i < l; i++) { - complex += tokens[i].value; - } - - return lastRelTokenInd === -1 ? new TraitLessSelector(selectorText, debug) : new SplittedSelector(selectorText, simple, relation, complex, debug); - }, - - /** - * @private - * @return {number|undefined} An index of a token that is split point. - * returns undefined if the selector does not contain any complex tokens - * or it is not eligible for splitting. - * Otherwise returns an integer indicating the index of the last relation token. - */ - getSplitPoint: function getSplitPoint() { - var tokens = this.tokens[0]; // We split selector only when the last compound selector - // is the only extended selector. - - var latestRelationTokenIndex = -1; - var haveMetComplexToken = false; - - for (var i = 0, l = tokens.length; i < l; i++) { - var token = tokens[i]; - - if (isRelationToken(token)) { - if (haveMetComplexToken) { - return; - } - - latestRelationTokenIndex = i; - } else if (!isSimpleToken(token)) { - haveMetComplexToken = true; - } - } - - if (!haveMetComplexToken) { - return; - } - - return latestRelationTokenIndex; - }, - - /** - * @private - * @return {string|undefined} xpath selector part if exists - * returns undefined if the selector does not contain xpath tokens - */ - getXpathPart: function getXpathPart() { - var tokens = this.tokens[0]; - - for (var i = 0, tokensLength = tokens.length; i < tokensLength; i++) { - var token = tokens[i]; - - if (token.type === 'PSEUDO') { - var matches = token.matches; - - if (matches && matches.length > 1) { - if (matches[0] === 'xpath') { - if (this.isLastToken(tokens, i)) { - throw new Error('Invalid pseudo: \':xpath\' should be at the end of the selector'); - } - - return matches[1]; - } - - if (matches[0] === 'nth-ancestor') { - if (this.isLastToken(tokens, i)) { - throw new Error('Invalid pseudo: \':nth-ancestor\' should be at the end of the selector'); - } - - var deep = matches[1]; - - if (deep > 0 && deep < 256) { - return this.convertNthAncestorToken(deep); - } - } - } - } - } - }, - - /** - * converts nth-ancestor/upward deep value to xpath equivalent - * @param {number} deep - * @return {string} - */ - convertNthAncestorToken: function convertNthAncestorToken(deep) { - var result = '..'; - - while (deep > 1) { - result += '/..'; - deep--; - } - - return result; - }, - - /** - * Checks if the token is last, - * except of remove pseudo-class - * @param {Array} tokens - * @param {number} i index of token - * @returns {boolean} - */ - isLastToken: function isLastToken(tokens, i) { - // check id the next parsed token is remove pseudo - var isNextRemoveToken = tokens[i + 1] && tokens[i + 1].type === 'PSEUDO' && tokens[i + 1].matches && tokens[i + 1].matches[0] === 'remove'; // check if the token is last - // and if it is not check if it is remove one - // which should be skipped - - return i + 1 !== tokens.length && !isNextRemoveToken; - }, - - /** - * @private - * @return {string|undefined} upward parameter - * or undefined if the input does not contain upward tokens - */ - getUpwardPart: function getUpwardPart() { - var tokens = this.tokens[0]; - - for (var i = 0, tokensLength = tokens.length; i < tokensLength; i++) { - var token = tokens[i]; - - if (token.type === 'PSEUDO') { - var matches = token.matches; - - if (matches && matches.length > 1) { - if (matches[0] === 'upward') { - if (this.isLastToken(tokens, i)) { - throw new Error('Invalid pseudo: \':upward\' should be at the end of the selector'); - } - - return matches[1]; - } - } - } - } - }, - - /** - * @private - * @return {string|undefined} remove parameter - * or undefined if the input does not contain remove tokens - */ - getRemovePart: function getRemovePart() { - var tokens = this.tokens[0]; - - for (var i = 0, tokensLength = tokens.length; i < tokensLength; i++) { - var token = tokens[i]; - - if (token.type === 'PSEUDO') { - var matches = token.matches; - - if (matches && matches.length > 1) { - if (matches[0] === 'remove') { - if (i + 1 !== tokensLength) { - throw new Error('Invalid pseudo: \':remove\' should be at the end of the selector'); - } - - return matches[1]; - } - } - } - } - } - }; - var globalDebuggingFlag = false; - - function isDebugging() { - return globalDebuggingFlag || this.debug; - } - /** - * This class represents a selector which is not an extended selector. - * @param {string} selectorText - * @param {boolean=} debug - * @final - */ - - - function NotAnExtendedSelector(selectorText, debug) { - this.selectorText = selectorText; - this.debug = debug; - } - - NotAnExtendedSelector.prototype = { - querySelectorAll: function querySelectorAll() { - return document.querySelectorAll(this.selectorText); - }, - matches: function matches(element) { - return element[utils.matchesPropertyName](this.selectorText); - }, - isDebugging: isDebugging - }; - /** - * A trait-less extended selector class. - * @param {string} selectorText - * @param {boolean=} debug - * @constructor - */ - - function TraitLessSelector(selectorText, debug) { - this.selectorText = selectorText; - this.debug = debug; - Sizzle.compile(selectorText); - } - - TraitLessSelector.prototype = { - querySelectorAll: function querySelectorAll() { - return Sizzle(this.selectorText); - }, - - /** @final */ - matches: function matches(element) { - return Sizzle.matchesSelector(element, this.selectorText); - }, - - /** @final */ - isDebugging: isDebugging - }; - /** - * Parental class for such pseudo-classes as xpath, upward, remove - * which are limited to be the last one token in selector - * - * @param {string} selectorText - * @param {string} pseudoClassArg pseudo-class arg - * @param {boolean=} debug - * @constructor - */ - - function BaseLastArgumentSelector(selectorText, pseudoClassArg, debug) { - this.selectorText = selectorText; - this.pseudoClassArg = pseudoClassArg; - this.debug = debug; - Sizzle.compile(this.selectorText); - } - - BaseLastArgumentSelector.prototype = { - querySelectorAll: function querySelectorAll() { - var _this = this; - - var resultNodes = []; - var simpleNodes; - - if (this.selectorText) { - simpleNodes = Sizzle(this.selectorText); - - if (!simpleNodes || !simpleNodes.length) { - return resultNodes; - } - } else { - simpleNodes = [document]; - } - - simpleNodes.forEach(function (node) { - _this.searchResultNodes(node, _this.pseudoClassArg, resultNodes); - }); - return Sizzle.uniqueSort(resultNodes); - }, - - /** @final */ - matches: function matches(element) { - var results = this.querySelectorAll(); - return results.indexOf(element) > -1; - }, - - /** @final */ - isDebugging: isDebugging, - - /** - * Primitive method that returns all nodes if pseudo-class arg is defined. - * That logic works for remove pseudo-class, - * but for others it should be overridden. - * @param {Object} node context element - * @param {string} pseudoClassArg pseudo-class argument - * @param {Array} result - */ - searchResultNodes: function searchResultNodes(node, pseudoClassArg, result) { - if (pseudoClassArg) { - result.push(node); - } - } - }; - /** - * Xpath selector class - * Limited to support 'xpath' to be only the last one token in selector - * @param {string} selectorText - * @param {string} xpath value - * @param {boolean=} debug - * @constructor - * @augments BaseLastArgumentSelector - */ - - function XpathSelector(selectorText, xpath, debug) { - var NO_SELECTOR_MARKER = ':xpath(//'; - var BODY_SELECTOR_REPLACER = 'body:xpath(//'; - var modifiedSelectorText = selectorText; // Normally, a pseudo-class is applied to nodes selected by a selector -- selector:xpath(...). - // However, :xpath is special as the selector can be ommited. - // For any other pseudo-class that would mean "apply to ALL DOM nodes", - // but in case of :xpath it just means "apply me to the document". - - if (utils.startsWith(selectorText, NO_SELECTOR_MARKER)) { - modifiedSelectorText = selectorText.replace(NO_SELECTOR_MARKER, BODY_SELECTOR_REPLACER); - } - - BaseLastArgumentSelector.call(this, modifiedSelectorText, xpath, debug); - } - - XpathSelector.prototype = Object.create(BaseLastArgumentSelector.prototype); - XpathSelector.prototype.constructor = XpathSelector; - /** - * Applies xpath pseudo-class to provided context node - * @param {Object} node context element - * @param {string} pseudoClassArg xpath - * @param {Array} result - * @override - */ - - XpathSelector.prototype.searchResultNodes = function (node, pseudoClassArg, result) { - var xpathResult = document.evaluate(pseudoClassArg, node, null, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null); - var iNode; // eslint-disable-next-line no-cond-assign - - while (iNode = xpathResult.iterateNext()) { - result.push(iNode); - } - }; - /** - * Upward selector class - * Limited to support 'upward' to be only the last one token in selector - * @param {string} selectorText - * @param {string} upwardSelector value - * @param {boolean=} debug - * @constructor - * @augments BaseLastArgumentSelector - */ - - - function UpwardSelector(selectorText, upwardSelector, debug) { - BaseLastArgumentSelector.call(this, selectorText, upwardSelector, debug); - } - - UpwardSelector.prototype = Object.create(BaseLastArgumentSelector.prototype); - UpwardSelector.prototype.constructor = UpwardSelector; - /** - * Applies upward pseudo-class to provided context node - * @param {Object} node context element - * @param {string} upwardSelector upward selector - * @param {Array} result - * @override - */ - - UpwardSelector.prototype.searchResultNodes = function (node, upwardSelector, result) { - if (upwardSelector !== '') { - var parent = node.parentElement; - - if (parent === null) { - return; - } - - node = parent.closest(upwardSelector); - - if (node === null) { - return; - } - } - - result.push(node); - }; - /** - * Remove selector class - * Limited to support 'remove' to be only the last one token in selector - * @param {string} selectorText - * @param {boolean} hasValidRemovePart - * @param {boolean=} debug - * @constructor - * @augments BaseLastArgumentSelector - */ - - - function RemoveSelector(selectorText, hasValidRemovePart, debug) { - var REMOVE_PSEUDO_MARKER = ':remove()'; - var removeMarkerIndex = selectorText.indexOf(REMOVE_PSEUDO_MARKER); // deleting remove part of rule instead of which - // pseudo-property property 'remove' will be added by ExtendedCssParser - - var modifiedSelectorText = selectorText.slice(0, removeMarkerIndex); - BaseLastArgumentSelector.call(this, modifiedSelectorText, hasValidRemovePart, debug); // mark extendedSelector as Remove one for ExtendedCssParser - - this.isRemoveSelector = true; - } - - RemoveSelector.prototype = Object.create(BaseLastArgumentSelector.prototype); - RemoveSelector.prototype.constructor = RemoveSelector; - /** - * A splitted extended selector class. - * - * #container #feedItem:has(.ads) - * +--------+ simple - * + relation - * +-----------------+ complex - * We split selector only when the last selector is complex - * @param {string} selectorText - * @param {string} simple - * @param {string} relation - * @param {string} complex - * @param {boolean=} debug - * @constructor - * @extends TraitLessSelector - */ - - function SplittedSelector(selectorText, simple, relation, complex, debug) { - TraitLessSelector.call(this, selectorText, debug); - this.simple = simple; - this.relation = relation; - this.complex = complex; - Sizzle.compile(complex); - } - - SplittedSelector.prototype = Object.create(TraitLessSelector.prototype); - SplittedSelector.prototype.constructor = SplittedSelector; - /** @override */ - - SplittedSelector.prototype.querySelectorAll = function () { - var _this2 = this; - - var resultNodes = []; - var simpleNodes; - var simple = this.simple; - var relation; - - if (simple) { - // First we use simple selector to narrow our search - simpleNodes = document.querySelectorAll(simple); - - if (!simpleNodes || !simpleNodes.length) { - return resultNodes; - } - - relation = this.relation; - } else { - simpleNodes = [document]; - relation = ' '; - } - - switch (relation) { - case ' ': - simpleNodes.forEach(function (node) { - _this2.relativeSearch(node, resultNodes); - }); - break; - - case '>': - { - simpleNodes.forEach(function (node) { - Object.values(node.children).forEach(function (childNode) { - if (_this2.matches(childNode)) { - resultNodes.push(childNode); - } - }); - }); - break; - } - - case '+': - { - simpleNodes.forEach(function (node) { - var parentNode = node.parentNode; - Object.values(parentNode.children).forEach(function (childNode) { - if (_this2.matches(childNode) && childNode.previousElementSibling === node) { - resultNodes.push(childNode); - } - }); - }); - break; - } - - case '~': - { - simpleNodes.forEach(function (node) { - var parentNode = node.parentNode; - Object.values(parentNode.children).forEach(function (childNode) { - if (_this2.matches(childNode) && node.compareDocumentPosition(childNode) === 4) { - resultNodes.push(childNode); - } - }); - }); - break; - } - } - - return Sizzle.uniqueSort(resultNodes); - }; - /** - * Performs a search of "complex" part relative to results for the "simple" part. - * @param {Node} node a node matching the "simple" part. - * @param {Node[]} result an array to append search result. - */ - - - SplittedSelector.prototype.relativeSearch = function (node, results) { - Sizzle(this.complex, node, results); - }; - - return { - /** - * Wraps the inner class so that the instance is not exposed. - */ - createSelector: function createSelector(selector, tokens, debug) { - return new ExtendedSelectorParser(selector, tokens, debug).createSelector(); - }, - - /** - * Mark every selector as a selector being debugged, so that timing information - * for the selector is printed to the console. - */ - enableGlobalDebugging: function enableGlobalDebugging() { - globalDebuggingFlag = true; - } - }; -}(); - -/** - * Copyright 2016 Adguard Software Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - * A helper class that parses stylesheets containing extended selectors - * into ExtendedSelector instances and key-value maps of style declarations. - * Please note, that it does not support any complex things like media queries and such. - */ - -var ExtendedCssParser = function () { - var reDeclEnd = /[;}]/g; - var reDeclDivider = /[;:}]/g; - var reNonWhitespace = /\S/g; - var Sizzle; - /** - * @param {string} cssText - * @constructor - */ - - function Parser(cssText) { - this.cssText = cssText; - } - - Parser.prototype = { - error: function error(position) { - throw new Error("CssParser: parse error at position ".concat(this.posOffset + position)); - }, - - /** - * Validates that the tokens correspond to a valid selector. - * Sizzle is different from browsers and some selectors that it tolerates aren't actually valid. - * For instance, "div >" won't work in a browser, but it will in Sizzle (it'd be the same as "div > *"). - * - * @param {*} selectors An array of SelectorData (selector, groups) - * @returns {boolean} false if any of the groups are invalid - */ - validateSelectors: function validateSelectors(selectors) { - var iSelectors = selectors.length; - - while (iSelectors--) { - var groups = selectors[iSelectors].groups; - var iGroups = groups.length; - - while (iGroups--) { - var tokens = groups[iGroups]; - var lastToken = tokens[tokens.length - 1]; - - if (Sizzle.selectors.relative[lastToken.type]) { - return false; - } - } - } - - return true; - }, - - /** - * Parses a stylesheet and returns a list of pairs of an ExtendedSelector and a styles map. - * This method will throw an error in case of an obviously invalid input. - * If any of the selectors used in the stylesheet cannot be compiled into an ExtendedSelector, - * it will be ignored. - * - * @typedef {Object} ExtendedStyle - * @property {Object} selector An instance of the {@link ExtendedSelector} class - * @property {Object} styleMap A map of styles parsed - * - * @returns {Array.} An array of the styles parsed - */ - parseCss: function parseCss() { - this.posOffset = 0; - - if (!this.cssText) { - this.error(0); - } - - var results = []; - - while (this.cssText) { - // Apply tolerant tokenization. - var parseResult = Sizzle.tokenize(this.cssText, false, { - tolerant: true, - returnUnsorted: true - }); - var selectorData = parseResult.selectors; - this.nextIndex = parseResult.nextIndex; - - if (this.cssText.charCodeAt(this.nextIndex) !== 123 || - /* charCode of '{' */ - !this.validateSelectors(selectorData)) { - this.error(this.nextIndex); - } - - this.nextIndex++; // Move the pointer to the start of style declaration. - - var styleMap = this.parseNextStyle(); - var debug = false; // If there is a style property 'debug', mark the selector - // as a debuggable selector, and delete the style declaration. - - var debugPropertyValue = styleMap['debug']; - - if (typeof debugPropertyValue !== 'undefined') { - if (debugPropertyValue === 'global') { - ExtendedSelectorFactory.enableGlobalDebugging(); - } - - debug = true; - delete styleMap['debug']; - } // Creating an ExtendedSelector instance for every selector we got from Sizzle.tokenize. - // This is quite important as Sizzle does a poor job at executing selectors like "selector1, selector2". - - - for (var i = 0, l = selectorData.length; i < l; i++) { - var data = selectorData[i]; - - try { - var extendedSelector = ExtendedSelectorFactory.createSelector(data.selectorText, data.groups, debug); - - if (extendedSelector.pseudoClassArg && extendedSelector.isRemoveSelector) { - // if there is remove pseudo-class in rule, - // the element will be removed and no other styles will be applied - styleMap['remove'] = 'true'; - } - - results.push({ - selector: extendedSelector, - style: styleMap - }); - } catch (ex) { - utils.logError("ExtendedCssParser: ignoring invalid selector ".concat(data.selectorText)); - } - } - } - - return results; - }, - parseNextStyle: function parseNextStyle() { - var styleMap = Object.create(null); - var bracketPos = this.parseUntilClosingBracket(styleMap); // Cut out matched portion from cssText. - - reNonWhitespace.lastIndex = bracketPos + 1; - var match = reNonWhitespace.exec(this.cssText); - - if (match === null) { - this.cssText = ''; - return styleMap; - } - - var matchPos = match.index; - this.cssText = this.cssText.slice(matchPos); - this.posOffset += matchPos; - return styleMap; - }, - - /** - * @return {number} an index of the next '}' in `this.cssText`. - */ - parseUntilClosingBracket: function parseUntilClosingBracket(styleMap) { - // Expects ":", ";", and "}". - reDeclDivider.lastIndex = this.nextIndex; - var match = reDeclDivider.exec(this.cssText); - - if (match === null) { - this.error(this.nextIndex); - } - - var matchPos = match.index; - var matched = match[0]; - - if (matched === '}') { - return matchPos; - } - - if (matched === ':') { - var colonIndex = matchPos; // Expects ";" and "}". - - reDeclEnd.lastIndex = colonIndex; - match = reDeclEnd.exec(this.cssText); - - if (match === null) { - this.error(colonIndex); - } - - matchPos = match.index; - matched = match[0]; // Populates the `styleMap` key-value map. - - var property = this.cssText.slice(this.nextIndex, colonIndex).trim(); - var value = this.cssText.slice(colonIndex + 1, matchPos).trim(); - styleMap[property] = value; // If found "}", re-run the outer loop. - - if (matched === '}') { - return matchPos; - } - } // matchPos is the position of the next ';'. - // Increase 'nextIndex' and re-run the loop. - - - this.nextIndex = matchPos + 1; - return this.parseUntilClosingBracket(styleMap); // Should be a subject of tail-call optimization. - } - }; - return { - parseCss: function parseCss(cssText) { - Sizzle = initializeSizzle(); - return new Parser(cssUtils.normalize(cssText)).parseCss(); - } - }; -}(); - -/** - * This callback is used to get affected node elements and handle style properties - * before they are applied to them if it is necessary - * @callback beforeStyleApplied - * @param {object} affectedElement - Object containing DOM node and rule to be applied - * @return {object} affectedElement - Same or modified object containing DOM node and rule to be applied - */ - -/** - * Extended css class - * - * @param {Object} configuration - * @param {string} configuration.styleSheet - the CSS stylesheet text - * @param {beforeStyleApplied} [configuration.beforeStyleApplied] - the callback that handles affected elements - * @constructor - */ - -function ExtendedCss(configuration) { - if (!configuration) { - throw new Error('Configuration is not provided.'); - } - - var styleSheet = configuration.styleSheet; - var beforeStyleApplied = configuration.beforeStyleApplied; - - if (beforeStyleApplied && typeof beforeStyleApplied !== 'function') { - // eslint-disable-next-line max-len - throw new Error("Wrong configuration. Type of 'beforeStyleApplied' field should be a function, received: ".concat(_typeof(beforeStyleApplied))); - } // We use EventTracker to track the event that is likely to cause the mutation. - // The problem is that we cannot use `window.event` directly from the mutation observer call - // as we're not in the event handler context anymore. - - - var EventTracker = function () { - var ignoredEventTypes = ['mouseover', 'mouseleave', 'mouseenter', 'mouseout']; - var LAST_EVENT_TIMEOUT_MS = 10; - var EVENTS = [// keyboard events - 'keydown', 'keypress', 'keyup', // mouse events - 'auxclick', 'click', 'contextmenu', 'dblclick', 'mousedown', 'mouseenter', 'mouseleave', 'mousemove', 'mouseover', 'mouseout', 'mouseup', 'pointerlockchange', 'pointerlockerror', 'select', 'wheel']; // 'wheel' event makes scrolling in Safari twitchy - // https://github.com/AdguardTeam/ExtendedCss/issues/120 - - var safariProblematicEvents = ['wheel']; - var trackedEvents = utils.isSafariBrowser ? EVENTS.filter(function (el) { - return !(safariProblematicEvents.indexOf(el) > -1); - }) : EVENTS; - var lastEventType; - var lastEventTime; - - var trackEvent = function trackEvent(e) { - lastEventType = e.type; - lastEventTime = Date.now(); - }; - - trackedEvents.forEach(function (evName) { - document.documentElement.addEventListener(evName, trackEvent, true); - }); - - var getLastEventType = function getLastEventType() { - return lastEventType; - }; - - var getTimeSinceLastEvent = function getTimeSinceLastEvent() { - return Date.now() - lastEventTime; - }; - - return { - isIgnoredEventType: function isIgnoredEventType() { - return ignoredEventTypes.indexOf(getLastEventType()) > -1 && getTimeSinceLastEvent() < LAST_EVENT_TIMEOUT_MS; - } - }; - }(); - - var rules = []; - var affectedElements = []; - var removalsStatistic = {}; - var domObserved; - var eventListenerSupported = window.addEventListener; - var domMutationObserver; - - function observeDocument(callback) { - // We are trying to limit the number of callback calls by not calling it on all kind of "hover" events. - // The rationale behind this is that "hover" events often cause attributes modification, - // but re-applying extCSS rules will be useless as these attribute changes are usually transient. - var isIgnoredMutation = function isIgnoredMutation(mutations) { - for (var i = 0; i < mutations.length; i += 1) { - if (mutations.type !== 'attributes') { - return false; - } - } - - return true; - }; - - if (utils.MutationObserver) { - domMutationObserver = new utils.MutationObserver(function (mutations) { - if (!mutations || mutations.length === 0) { - return; - } - - if (EventTracker.isIgnoredEventType() && isIgnoredMutation(mutations)) { - return; - } - - callback(); - }); - domMutationObserver.observe(document, { - childList: true, - subtree: true, - attributes: true, - attributeFilter: ['id', 'class'] - }); - } else if (eventListenerSupported) { - document.addEventListener('DOMNodeInserted', callback, false); - document.addEventListener('DOMNodeRemoved', callback, false); - document.addEventListener('DOMAttrModified', callback, false); - } - } - - function disconnectDocument(callback) { - if (domMutationObserver) { - domMutationObserver.disconnect(); - } else if (eventListenerSupported) { - document.removeEventListener('DOMNodeInserted', callback, false); - document.removeEventListener('DOMNodeRemoved', callback, false); - document.removeEventListener('DOMAttrModified', callback, false); - } - } - - var MAX_STYLE_PROTECTION_COUNT = 50; - var protectionObserverOption = { - attributes: true, - attributeOldValue: true, - attributeFilter: ['style'] - }; - /** - * Creates MutationObserver protection function - * - * @param styles - * @return {protectionFunction} - */ - - function createProtectionFunction(styles) { - function protectionFunction(mutations, observer) { - if (!mutations.length) { - return; - } - - var mutation = mutations[0]; - var target = mutation.target; - observer.disconnect(); - styles.forEach(function (style) { - setStyleToElement(target, style); - }); - - if (++observer.styleProtectionCount < MAX_STYLE_PROTECTION_COUNT) { - observer.observe(target, protectionObserverOption); - } else { - utils.logError('ExtendedCss: infinite loop protection for style'); - } - } - - return protectionFunction; - } - /** - * Sets up a MutationObserver which protects style attributes from changes - * @param node DOM node - * @param rules rules - * @returns Mutation observer used to protect attribute or null if there's nothing to protect - */ - - - function protectStyleAttribute(node, rules) { - if (!utils.MutationObserver) { - return null; - } - - var styles = rules.map(function (r) { - return r.style; - }); - var protectionObserver = new utils.MutationObserver(createProtectionFunction(styles)); - protectionObserver.observe(node, protectionObserverOption); // Adds an expando to the observer to keep 'style fix counts'. - - protectionObserver.styleProtectionCount = 0; - return protectionObserver; - } - - function removeSuffix(str, suffix) { - var index = str.indexOf(suffix, str.length - suffix.length); - - if (index >= 0) { - return str.substring(0, index); - } - - return str; - } - /** - * Finds affectedElement object for the specified DOM node - * @param node DOM node - * @returns affectedElement found or null - */ - - - function findAffectedElement(node) { - for (var i = 0; i < affectedElements.length; i += 1) { - if (affectedElements[i].node === node) { - return affectedElements[i]; - } - } - - return null; - } - - function removeElement(affectedElement) { - var node = affectedElement.node; - affectedElement.removed = true; - var elementSelector = utils.getNodeSelector(node); // check if the element has been already removed earlier - - var elementRemovalsCounter = removalsStatistic[elementSelector] || 0; // if removals attempts happened more than specified we do not try to remove node again - - if (elementRemovalsCounter > MAX_STYLE_PROTECTION_COUNT) { - utils.logError('ExtendedCss: infinite loop protection for SELECTOR', elementSelector); - return; - } - - if (node.parentNode) { - node.parentNode.removeChild(node); - removalsStatistic[elementSelector] = elementRemovalsCounter + 1; - } - } - /** - * Applies style to the specified DOM node - * @param affectedElement Object containing DOM node and rule to be applied - */ - - - function applyStyle(affectedElement) { - if (affectedElement.protectionObserver) { - // Style is already applied and protected by the observer - return; - } - - if (beforeStyleApplied) { - affectedElement = beforeStyleApplied(affectedElement); - - if (!affectedElement) { - return; - } - } - - var _affectedElement = affectedElement, - node = _affectedElement.node; - - for (var i = 0; i < affectedElement.rules.length; i++) { - var style = affectedElement.rules[i].style; - - if (style['remove'] === 'true') { - removeElement(affectedElement); - return; - } - - setStyleToElement(node, style); - } - } - /** - * Sets style to the specified DOM node - * @param node element - * @param style style - */ - - - function setStyleToElement(node, style) { - Object.keys(style).forEach(function (prop) { - // Apply this style only to existing properties - // We can't use hasOwnProperty here (does not work in FF) - if (typeof node.style.getPropertyValue(prop) !== 'undefined') { - var value = style[prop]; // First we should remove !important attribute (or it won't be applied') - - value = removeSuffix(value.trim(), '!important').trim(); - node.style.setProperty(prop, value, 'important'); - } - }); - } - /** - * Reverts style for the affected object - */ - - - function revertStyle(affectedElement) { - if (affectedElement.protectionObserver) { - affectedElement.protectionObserver.disconnect(); - } - - affectedElement.node.style.cssText = affectedElement.originalStyle; - } - /** - * Applies specified rule and returns list of elements affected - * @param rule Rule to apply - * @returns List of elements affected by this rule - */ - - - function applyRule(rule) { - var debug = rule.selector.isDebugging(); - var start; - - if (debug) { - start = utils.AsyncWrapper.now(); - } - - var selector = rule.selector; - var nodes = selector.querySelectorAll(); - nodes.forEach(function (node) { - var affectedElement = findAffectedElement(node); - - if (affectedElement) { - affectedElement.rules.push(rule); - applyStyle(affectedElement); - } else { - // Applying style first time - var originalStyle = node.style.cssText; - affectedElement = { - node: node, - // affected DOM node - rules: [rule], - // rules to be applied - originalStyle: originalStyle, - // original node style - protectionObserver: null // style attribute observer - - }; - applyStyle(affectedElement); - affectedElements.push(affectedElement); - } - }); - - if (debug) { - var elapsed = utils.AsyncWrapper.now() - start; - - if (!('timingStats' in rule)) { - rule.timingStats = new utils.Stats(); - } - - rule.timingStats.push(elapsed); - } - - return nodes; - } - /** - * Applies filtering rules - */ - - - function applyRules() { - var elementsIndex = []; // some rules could make call - selector.querySelectorAll() temporarily to change node id attribute - // this caused MutationObserver to call recursively - // https://github.com/AdguardTeam/ExtendedCss/issues/81 - - stopObserve(); - rules.forEach(function (rule) { - var nodes = applyRule(rule); - Array.prototype.push.apply(elementsIndex, nodes); - }); // Now revert styles for elements which are no more affected - - var l = affectedElements.length; // do nothing if there is no elements to process - - if (elementsIndex.length > 0) { - while (l--) { - var obj = affectedElements[l]; - - if (elementsIndex.indexOf(obj.node) === -1) { - // Time to revert style - revertStyle(obj); - affectedElements.splice(l, 1); - } else if (!obj.removed) { - // Add style protection observer - // Protect "style" attribute from changes - if (!obj.protectionObserver) { - obj.protectionObserver = protectStyleAttribute(obj.node, obj.rules); - } - } - } - } // After styles are applied we can start observe again - - - observe(); - printTimingInfo(); - } - - var APPLY_RULES_DELAY = 150; - var applyRulesScheduler = new utils.AsyncWrapper(applyRules, APPLY_RULES_DELAY); - var mainCallback = applyRulesScheduler.run.bind(applyRulesScheduler); - - function observe() { - if (domObserved) { - return; - } // Handle dynamically added elements - - - domObserved = true; - observeDocument(mainCallback); - } - - function stopObserve() { - if (!domObserved) { - return; - } - - domObserved = false; - disconnectDocument(mainCallback); - } - - function apply() { - applyRules(); - - if (document.readyState !== 'complete') { - document.addEventListener('DOMContentLoaded', applyRules); - } - } - /** - * Disposes ExtendedCss and removes our styles from matched elements - */ - - - function dispose() { - stopObserve(); - affectedElements.forEach(function (obj) { - revertStyle(obj); - }); - } - - var timingsPrinted = false; - /** - * Prints timing information for all selectors marked as "debug" - */ - - function printTimingInfo() { - if (timingsPrinted) { - return; - } - - timingsPrinted = true; - var timings = rules.filter(function (rule) { - return rule.selector.isDebugging(); - }).map(function (rule) { - return { - selectorText: rule.selector.selectorText, - timingStats: rule.timingStats - }; - }); - - if (timings.length === 0) { - return; - } // Add location.href to the message to distinguish frames - - - utils.logInfo('[ExtendedCss] Timings for %o:\n%o (in milliseconds)', window.location.href, timings); - } // First of all parse the stylesheet - - - rules = ExtendedCssParser.parseCss(styleSheet); // EXPOSE - - this.dispose = dispose; - this.apply = apply; - /** Exposed for testing purposes only */ - - this._getAffectedElements = function () { - return affectedElements; - }; -} -/** - * Expose querySelectorAll for debugging and validating selectors - * - * @param {string} selectorText selector text - * @param {boolean} noTiming if true -- do not print the timing to the console - * @returns {Array|NodeList} a list of elements found - * @throws Will throw an error if the argument is not a valid selector - */ - - -ExtendedCss.query = function (selectorText, noTiming) { - if (typeof selectorText !== 'string') { - throw new Error('Selector text is empty'); - } - - var now = utils.AsyncWrapper.now; - var start = now(); - - try { - return ExtendedSelectorFactory.createSelector(selectorText).querySelectorAll(); - } finally { - var end = now(); - - if (!noTiming) { - utils.logInfo("[ExtendedCss] Elapsed: ".concat(Math.round((end - start) * 1000), " \u03BCs.")); - } - } -}; - -export default ExtendedCss; diff --git a/dist/extended-css.min.js b/dist/extended-css.min.js deleted file mode 100644 index 932bcbec..00000000 --- a/dist/extended-css.min.js +++ /dev/null @@ -1,5 +0,0 @@ -/*! extended-css - v1.3.16 - Thu Sep 15 2022 -* https://github.com/AdguardTeam/ExtendedCss -* Copyright (c) 2022 AdGuard. Licensed GPL-3.0 -*/ -var ExtendedCss=function(){"use strict";function e(t){return(e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(t)}function t(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){if("undefined"==typeof Symbol||!(Symbol.iterator in Object(e)))return;var r=[],n=!0,o=!1,i=void 0;try{for(var s,a=e[Symbol.iterator]();!(n=(s=a.next()).done)&&(r.push(s.value),!t||r.length!==t);n=!0);}catch(e){o=!0,i=e}finally{try{n||null==a.return||a.return()}finally{if(o)throw i}}return r}(e,t)||n(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function r(e){return function(e){if(Array.isArray(e))return o(e)}(e)||function(e){if("undefined"!=typeof Symbol&&Symbol.iterator in Object(e))return Array.from(e)}(e)||n(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function n(e,t){if(e){if("string"==typeof e)return o(e,t);var r=Object.prototype.toString.call(e).slice(8,-1);return"Object"===r&&e.constructor&&(r=e.constructor.name),"Map"===r||"Set"===r?Array.from(e):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?o(e,t):void 0}}function o(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=new Array(t);r=0&&n===e.length-r.length},m.createURLRegex=(s="||",a="|",u="^",c="*",l=".*",f="([^ a-zA-Z0-9.%_-]|$)",p="^(http|https|ws|wss)://([a-z0-9-_.]+\\.)?",d="^",h="$",g=new RegExp("[".concat([".","+","?","$","{","}","(",")","[","]","\\","/"].join("\\"),"]"),"g"),v=function(e,t,r){return e?e.split(t).join(r):e},function(e){var t=function(e){return e.replace(g,"\\$&")}(e);return t=m.startsWith(t,s)?t.substring(0,s.length)+v(t.substring(s.length,t.length-1),"|","\\|")+t.substring(t.length-1):m.startsWith(t,a)?t.substring(0,a.length)+v(t.substring(a.length,t.length-1),"|","\\|")+t.substring(t.length-1):v(t.substring(0,t.length-1),"|","\\|")+t.substring(t.length-1),t=v(t,c,l),t=v(t,u,f),m.startsWith(t,s)?t=p+t.substring(s.length):m.startsWith(t,a)&&(t=d+t.substring(a.length)),m.endsWith(t,a)&&(t=t.substring(0,t.length-1)+h),new RegExp(t,"i")}),m.createLocation=function(e){var t=document.createElement("a");return t.href=e,""===t.host&&(t.href=t.href),t},m.isSameOrigin=function(e,t,r){var n=m.createLocation(e);return"javascript:"===n.protocol||"about:blank"===n.href||"data:"!==n.protocol&&"file:"!==n.protocol&&(n.hostname===r&&n.port===t.port&&n.protocol===t.protocol)},m.AsyncWrapper=function(){var e=void 0!==window.requestAnimationFrame,t=e?requestAnimationFrame:setTimeout,r=e?cancelAnimationFrame:clearTimeout,n=e?performance:Date;function o(e,t){this.callback=e,this.throttle=t,this.wrappedCallback=this.wrappedCallback.bind(this),this.wrappedAsapCallback&&(this.wrappedAsapCallback=this.wrappedAsapCallback.bind(this))}return o.prototype.wrappedCallback=function(e){this.lastRun=y(e)?e:n.now(),delete this.rAFid,delete this.timerId,delete this.asapScheduled,this.callback()},o.prototype.hasPendingCallback=function(){return y(this.rAFid)||y(this.timerId)},o.prototype.run=function(){if(!this.hasPendingCallback()){if(void 0!==this.lastRun){var e=n.now()-this.lastRun;if(e>>0).concat(e++,"__")};return t.prototype={set:function(e,t){var r=e[this.name];return r&&r[0]===e?r[1]=t:m.defineProperty(e,this.name,{value:[e,t],writable:!0}),this},get:function(e){var t=e[this.name];return t&&t[0]===e?t[1]:void 0},delete:function(e){var t=e[this.name];if(!t)return!1;var r=t[0]===e;return delete t[0],delete t[1],r},has:function(e){var t=e[this.name];return!!t&&t[0]===e}},t}(),m.Set="undefined"!=typeof Set?Set:function(){var e=Date.now()%1e9,t=function(t){if(this.name="__st".concat(1e9*Math.random()>>>0).concat(e++,"__"),this.keys=[],t&&t.length)for(var r=t.length;r--;)this.add(t[r])};return t.prototype={add:function(e){if(!y(e[this.name])){var t=this.keys.push(e)-1;m.defineProperty(e,this.name,{value:t,writable:!0})}},delete:function(e){if(y(e[this.name])){var t=e[this.name];delete this.keys[t],e[this.name]=void 0}},has:function(e){return y(e[this.name])},clear:function(){this.keys.forEach((function(e){e[this.name]=void 0})),this.keys.length=0},forEach:function(e){var t=this;this.keys.forEach((function(r){e(r,r,t)}))}},m.defineProperty(t.prototype,"size",{get:function(){return this.keys.reduce((function(e){return e+1}),0)}}),t}(),m.matchesPropertyName=function(){for(var e=["matches","matchesSelector","mozMatchesSelector","msMatchesSelector","oMatchesSelector","webkitMatchesSelector"],t=0;t<6;t++)if(Element.prototype.hasOwnProperty(e[t]))return e[t]}(),m.Stats=function(){this.array=[],this.length=0;var e={value:0,writable:!0};Object.defineProperty(this,"sum",e),Object.defineProperty(this,"squaredSum",e)},m.Stats.prototype.push=function(e){this.array.push(e),this.length++,this.sum+=e,this.squaredSum+=e*e,this.mean=this.sum/this.length,this.stddev=Math.sqrt(this.squaredSum/this.length-Math.pow(this.mean,2))},m.logError="undefined"!=typeof console&&console.error&&Function.prototype.bind&&console.error.bind?console.error.bind(window.console):console.error,m.logInfo="undefined"!=typeof console&&console.info&&Function.prototype.bind&&console.info.bind?console.info.bind(window.console):console.info,m.getNodeSelector=function(e){if(!(e instanceof Element))throw new Error("Function received argument with wrong type");for(var t=e,r=[];t&&t.nodeType===Node.ELEMENT_NODE;){var n=t.nodeName.toLowerCase();if(t.id&&"string"==typeof t.id){n+="#".concat(t.id),r.unshift(n);break}for(var o=t,i=1;o.previousSibling;)(o=o.previousSibling).nodeType===Node.ELEMENT_NODE&&o.nodeName.toLowerCase()===n&&i++;1!==i&&(n+=":nth-of-type(".concat(i,")")),r.unshift(n),t=t.parentNode}return r.join(" > ")};var b,x,w,E,S,N,T,A=(b=/\[-(?:ext|abp)-([a-z-_]+)=(["'])((?:(?=(\\?))\4.)*?)\2\]/g,x=function(e,t,r,n){var o=new RegExp("([^\\\\]|^)\\\\".concat(r),"g");return n=n.replace(o,"$1".concat(r)),":".concat(t,"(").concat(n,")")},w=/\:(matches-css(?:-after|-before)?)\(([a-z-\s]*\:\s*\/(?:\\.|[^\/])*?\/\s*)\)/g,E=/:(?:-abp-)?(contains|has-text)\((\s*\/(?:\\.|[^\/])*?\/\s*)\)/g,S=/\(\:scope >/g,N=function(e,t,r){return":".concat(t,'("').concat(r.replace(/["\\]/g,"\\$&"),'")')},{normalize:function(e){return e.replace(b,x).replace(w,N).replace(E,N).replace(S,"(>")},isSimpleSelectorValid:function(e){try{document.querySelectorAll(e)}catch(e){return!1}return!0}}),C=function(){return T||(T=function(e){var t,r,n,o,i,s,a,u,c,l,f,p,d,h,g,v,m,y,b="sizzle"+1*new Date,x=e.document,w=0,E=0,S=ne(),N=ne(),T=ne(),A=ne(),C=function(e,t){return e===t&&(l=!0),0},O={}.hasOwnProperty,I=[],D=I.pop,R=I.push,k=I.push,P=I.slice,L=function(e,t){for(var r=0,n=e.length;r+~]|[\\x20\\t\\r\\n\\f])[\\x20\\t\\r\\n\\f]*"),U=new RegExp(z),H=new RegExp("^"+B+"$"),_={ID:new RegExp("^#("+B+")"),CLASS:new RegExp("^\\.("+B+")"),TAG:new RegExp("^("+B+"|[*])"),ATTR:new RegExp("^"+$),PSEUDO:new RegExp("^"+z),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\([\\x20\\t\\r\\n\\f]*(even|odd|(([+-]|)(\\d*)n|)[\\x20\\t\\r\\n\\f]*(?:([+-]|)[\\x20\\t\\r\\n\\f]*(\\d+)|))[\\x20\\t\\r\\n\\f]*\\)|)","i"),bool:new RegExp("^(?:"+M+")$","i"),needsContext:new RegExp("^[\\x20\\t\\r\\n\\f]*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\([\\x20\\t\\r\\n\\f]*((?:-\\d)?\\d*)[\\x20\\t\\r\\n\\f]*\\)|)(?=[^-]|$)","i")},G=/^[^{]+\{\s*\[native \w/,X=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,K=/[+~]/,Y=new RegExp("\\\\([\\da-f]{1,6}[\\x20\\t\\r\\n\\f]?|([\\x20\\t\\r\\n\\f])|.)","ig"),Z=function(e,t,r){var n="0x"+t-65536;return n!=n||r?t:n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320)},J=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,Q=function(e,t){return t?"\0"===e?"�":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},ee=function(){f()},te=xe((function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()}),{dir:"parentNode",next:"legend"});try{k.apply(I=P.call(x.childNodes),x.childNodes),I[x.childNodes.length].nodeType}catch(e){k={apply:I.length?function(e,t){R.apply(e,P.call(t))}:function(e,t){for(var r=e.length,n=0;e[r++]=t[n++];);e.length=r-1}}}function re(e,r,n,o){var s,u,c,l,d,v,m,w=r&&r.ownerDocument,E=r?r.nodeType:9;if(n=n||[],"string"!=typeof e||!e||1!==E&&9!==E&&11!==E)return n;if(!o&&((r?r.ownerDocument||r:x)!==p&&f(r),r=r||p,h)){if(11!==E&&(d=X.exec(e)))if(s=d[1]){if(9===E){if(!(c=r.getElementById(s)))return n;if(c.id===s)return n.push(c),n}else if(w&&(c=w.getElementById(s))&&y(r,c)&&c.id===s)return n.push(c),n}else{if(d[2])return k.apply(n,r.getElementsByTagName(e)),n;if((s=d[3])&&t.getElementsByClassName&&r.getElementsByClassName)return k.apply(n,r.getElementsByClassName(s)),n}if(t.qsa&&!A[e+" "]&&(!g||!g.test(e))){if(1!==E)w=r,m=e;else if("object"!==r.nodeName.toLowerCase()){for((l=r.getAttribute("id"))?l=l.replace(J,Q):r.setAttribute("id",l=b),u=(v=i(e)).length;u--;)v[u]="#"+l+" "+be(v[u]);m=v.join(","),w=K.test(e)&&ce(r.parentNode)||r}if(m)try{if(m.indexOf(":has(")>-1)throw new Error("Do not handle :has() pseudo-class by the native method");return k.apply(n,w.querySelectorAll(m)),n}catch(t){A(e,!0)}finally{l===b&&r.removeAttribute("id")}}}return a(e.replace(F,"$1"),r,n,o)}function ne(){var e=[];return function t(n,o){return e.push(n+" ")>r.cacheLength&&delete t[e.shift()],t[n+" "]=o}}function oe(e){return e[b]=!0,e}function ie(e){var t=p.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function se(e,t){for(var n=e.split("|"),o=n.length;o--;)r.attrHandle[n[o]]=t}function ae(e,t){var r=t&&e,n=r&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(n)return n;if(r)for(;r=r.nextSibling;)if(r===t)return-1;return e?1:-1}function ue(e){return function(t){return"form"in t?t.parentNode&&!1===t.disabled?"label"in t?"label"in t.parentNode?t.parentNode.disabled===e:t.disabled===e:t.isDisabled===e||t.isDisabled!==!e&&te(t)===e:t.disabled===e:"label"in t&&t.disabled===e}}function ce(e){return e&&void 0!==e.getElementsByTagName&&e}function le(){}t=re.support={},o=re.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return!!t&&"HTML"!==t.nodeName},f=re.setDocument=function(e){var n,i,s=e?e.ownerDocument||e:x;return s!==p&&9===s.nodeType&&s.documentElement?(d=(p=s).documentElement,h=!o(p),x!==p&&(i=p.defaultView)&&i.top!==i&&(i.addEventListener?i.addEventListener("unload",ee,!1):i.attachEvent&&i.attachEvent("onunload",ee)),t.attributes=ie((function(e){return e.className="i",!e.getAttribute("className")})),t.getElementsByTagName=ie((function(e){return e.appendChild(p.createComment("")),!e.getElementsByTagName("*").length})),t.getElementsByClassName=G.test(p.getElementsByClassName),t.getById=ie((function(e){return d.appendChild(e).id=b,!p.getElementsByName||!p.getElementsByName(b).length})),t.getById?(r.filter.ID=function(e){var t=e.replace(Y,Z);return function(e){return e.getAttribute("id")===t}},r.find.ID=function(e,t){if(void 0!==t.getElementById&&h){var r=t.getElementById(e);return r?[r]:[]}}):(r.filter.ID=function(e){var t=e.replace(Y,Z);return function(e){var r=void 0!==e.getAttributeNode&&e.getAttributeNode("id");return r&&r.value===t}},r.find.ID=function(e,t){if(void 0!==t.getElementById&&h){var r,n,o,i=t.getElementById(e);if(i){if((r=i.getAttributeNode("id"))&&r.value===e)return[i];for(o=t.getElementsByName(e),n=0;i=o[n++];)if((r=i.getAttributeNode("id"))&&r.value===e)return[i]}return[]}}),r.find.TAG=t.getElementsByTagName?function(e,r){return void 0!==r.getElementsByTagName?r.getElementsByTagName(e):t.qsa?r.querySelectorAll(e):void 0}:function(e,t){var r,n=[],o=0,i=t.getElementsByTagName(e);if("*"===e){for(;r=i[o++];)1===r.nodeType&&n.push(r);return n}return i},r.find.CLASS=t.getElementsByClassName&&function(e,t){if(void 0!==t.getElementsByClassName&&h)return t.getElementsByClassName(e)},v=[],g=[],(t.qsa=G.test(p.querySelectorAll))&&(ie((function(e){d.appendChild(e).innerHTML=ye.createHTML(""),e.querySelectorAll("[msallowcapture^='']").length&&g.push("[*^$]=[\\x20\\t\\r\\n\\f]*(?:''|\"\")"),e.querySelectorAll("[selected]").length||g.push("\\[[\\x20\\t\\r\\n\\f]*(?:value|"+M+")"),e.querySelectorAll("[id~="+b+"-]").length||g.push("~="),e.querySelectorAll(":checked").length||g.push(":checked"),e.querySelectorAll("a#"+b+"+*").length||g.push(".#.+[+~]")})),ie((function(e){e.innerHTML=ye.createHTML("");var t=p.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&g.push("name[\\x20\\t\\r\\n\\f]*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&g.push(":enabled",":disabled"),d.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&g.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),g.push(",.*:")}))),(t.matchesSelector=G.test(m=d.matches||d.webkitMatchesSelector||d.mozMatchesSelector||d.oMatchesSelector||d.msMatchesSelector))&&ie((function(e){t.disconnectedMatch=m.call(e,"*"),m.call(e,"[s!='']:x"),v.push("!=",z)})),g=g.length&&new RegExp(g.join("|")),v=v.length&&new RegExp(v.join("|")),n=G.test(d.compareDocumentPosition),y=n||G.test(d.contains)?function(e,t){var r=9===e.nodeType?e.documentElement:e,n=t&&t.parentNode;return e===n||!(!n||1!==n.nodeType||!(r.contains?r.contains(n):e.compareDocumentPosition&&16&e.compareDocumentPosition(n)))}:function(e,t){if(t)for(;t=t.parentNode;)if(t===e)return!0;return!1},C=n?function(e,r){if(e===r)return l=!0,0;var n=!e.compareDocumentPosition-!r.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)===(r.ownerDocument||r)?e.compareDocumentPosition(r):1)||!t.sortDetached&&r.compareDocumentPosition(e)===n?e===p||e.ownerDocument===x&&y(x,e)?-1:r===p||r.ownerDocument===x&&y(x,r)?1:c?L(c,e)-L(c,r):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var r,n=0,o=e.parentNode,i=t.parentNode,s=[e],a=[t];if(!o||!i)return e===p?-1:t===p?1:o?-1:i?1:c?L(c,e)-L(c,t):0;if(o===i)return ae(e,t);for(r=e;r=r.parentNode;)s.unshift(r);for(r=t;r=r.parentNode;)a.unshift(r);for(;s[n]===a[n];)n++;return n?ae(s[n],a[n]):s[n]===x?-1:a[n]===x?1:0},p):p},re.matches=function(e,t){return re(e,null,null,t)},re.matchesSelector=function(e,r){if((e.ownerDocument||e)!==p&&f(e),t.matchesSelector&&h&&!A[r+" "]&&(!v||!v.test(r))&&(!g||!g.test(r)))try{var n=m.call(e,r);if(n||t.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){A(r,!0)}return re(r,p,null,[e]).length>0},re.contains=function(e,t){return(e.ownerDocument||e)!==p&&f(e),y(e,t)},re.attr=function(e,n){(e.ownerDocument||e)!==p&&f(e);var o=r.attrHandle[n.toLowerCase()],i=o&&O.call(r.attrHandle,n.toLowerCase())?o(e,n,!h):void 0;return void 0!==i?i:t.attributes||!h?e.getAttribute(n):(i=e.getAttributeNode(n))&&i.specified?i.value:null},re.escape=function(e){return(e+"").replace(J,Q)},re.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},re.uniqueSort=function(e){var r,n=[],o=0,i=0;if(l=!t.detectDuplicates,c=!t.sortStable&&e.slice(0),e.sort(C),l){for(;r=e[i++];)r===e[i]&&(o=n.push(i));for(;o--;)e.splice(n[o],1)}return c=null,e},n=re.getText=function(e){var t,r="",o=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)r+=n(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[o++];)r+=n(t);return r},r=re.selectors={cacheLength:50,createPseudo:oe,match:_,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(Y,Z),e[3]=(e[3]||e[4]||e[5]||"").replace(Y,Z),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||re.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&re.error(e[0]),e},PSEUDO:function(e){var t,r=!e[6]&&e[2];return _.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":r&&U.test(r)&&(t=i(r,!0))&&(t=r.indexOf(")",r.length-t)-r.length)&&(e[0]=e[0].slice(0,t),e[2]=r.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(Y,Z).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=S[e+" "];return t||(t=new RegExp("(^|[\\x20\\t\\r\\n\\f])"+e+"("+q+"|$)"))&&S(e,(function(e){return t.test("string"==typeof e.className&&e.className||void 0!==e.getAttribute&&e.getAttribute("class")||"")}))},ATTR:function(e,t,r){return function(n){var o=re.attr(n,e);return null==o?"!="===t:!t||(o+="","="===t?o===r:"!="===t?o!==r:"^="===t?r&&0===o.indexOf(r):"*="===t?r&&o.indexOf(r)>-1:"$="===t?r&&o.slice(-r.length)===r:"~="===t?(" "+o.replace(j," ")+" ").indexOf(r)>-1:"|="===t&&(o===r||o.slice(0,r.length+1)===r+"-"))}},CHILD:function(e,t,r,n,o){var i="nth"!==e.slice(0,3),s="last"!==e.slice(-4),a="of-type"===t;return 1===n&&0===o?function(e){return!!e.parentNode}:function(t,r,u){var c,l,f,p,d,h,g=i!==s?"nextSibling":"previousSibling",v=t.parentNode,m=a&&t.nodeName.toLowerCase(),y=!u&&!a,x=!1;if(v){if(i){for(;g;){for(p=t;p=p[g];)if(a?p.nodeName.toLowerCase()===m:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[s?v.firstChild:v.lastChild],s&&y){for(x=(d=(c=(l=(f=(p=v)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===w&&c[1])&&c[2],p=d&&v.childNodes[d];p=++d&&p&&p[g]||(x=d=0)||h.pop();)if(1===p.nodeType&&++x&&p===t){l[e]=[w,d,x];break}}else if(y&&(x=d=(c=(l=(f=(p=t)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===w&&c[1]),!1===x)for(;(p=++d&&p&&p[g]||(x=d=0)||h.pop())&&((a?p.nodeName.toLowerCase()!==m:1!==p.nodeType)||!++x||(y&&((l=(f=p[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]=[w,x]),p!==t)););return(x-=o)===n||x%n==0&&x/n>=0}}},PSEUDO:function(e,t){var n,o=r.pseudos[e]||r.setFilters[e.toLowerCase()]||re.error("unsupported pseudo: "+e);return o[b]?o(t):o.length>1?(n=[e,e,"",t],r.setFilters.hasOwnProperty(e.toLowerCase())?oe((function(e,r){for(var n,i=o(e,t),s=i.length;s--;)e[n=L(e,i[s])]=!(r[n]=i[s])})):function(e){return o(e,0,n)}):o}},pseudos:{not:oe((function(e){var t=[],r=[],n=s(e.replace(F,"$1"));return n[b]?oe((function(e,t,r,o){for(var i,s=n(e,null,o,[]),a=e.length;a--;)(i=s[a])&&(e[a]=!(t[a]=i))})):function(e,o,i){return t[0]=e,n(t,null,i,r),t[0]=null,!r.pop()}})),has:oe((function(e){return"string"==typeof e&&re.compile(e),function(t){return re(e,t).length>0}})),lang:oe((function(e){return H.test(e||"")||re.error("unsupported lang: "+e),e=e.replace(Y,Z).toLowerCase(),function(t){var r;do{if(r=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(r=r.toLowerCase())===e||0===r.indexOf(e+"-")}while((t=t.parentNode)&&1===t.nodeType);return!1}})),target:function(t){var r=e.location&&e.location.hash;return r&&r.slice(1)===t.id},root:function(e){return e===d},focus:function(e){return e===p.activeElement&&(!p.hasFocus||p.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:ue(!1),disabled:ue(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0}}},le.prototype=r.filters=r.pseudos,r.setFilters=new le;var fe,pe,de,he,ge,ve,me=(fe={CHILD:100,ID:90,CLASS:80,TAG:70,ATTR:70,PSEUDO:60},pe=["nth","first","last","eq","even","odd","lt","gt","not"],de=function(e,t){return fe[e.type]-fe[t.type]},he=function(e){for(var t=e.length;t--;){var r=e[t];if("PSEUDO"===r.type&&-1!==pe.indexOf(r.matches[0]))return!1;if("CHILD"===r.type)return!1}return!0},ge=function(e){if(!e||1===e.length)return e;for(var t=[],r=function(e){for(var t=[],r=[],n=e.length-1,o=0;o<=n;o++){var i=e[o];re.selectors.relative[i.type]?(t.push(r),t.push(i),r=[]):r.push(i),o===n&&t.push(r)}return t}(e),n=0;n1?function(t,r,n){for(var o=e.length;o--;)if(!e[o](t,r,n))return!1;return!0}:e[0]}function Ee(e,t,r,n,o){for(var i,s=[],a=0,u=e.length,c=null!=t;a-1&&(i[c]=!(s[c]=f))}}else m=Ee(m===s?m.splice(h,m.length):m),o?o(null,s,m,u):k.apply(s,m)}))}function Ne(e){for(var t,n,o,i=e.length,s=r.relative[e[0].type],a=s||r.relative[" "],c=s?1:0,l=xe((function(e){return e===t}),a,!0),f=xe((function(e){return L(t,e)>-1}),a,!0),p=[function(e,r,n){var o=!s&&(n||r!==u)||((t=r).nodeType?l(e,r,n):f(e,r,n));return t=null,o}];c1&&we(p),c>1&&be(e.slice(0,c-1).concat({value:" "===e[c-2].type?"*":""})).replace(F,"$1"),n,c0,o=e.length>0,i=function(i,s,a,c,l){var d,g,v,m=0,y="0",b=i&&[],x=[],E=u,S=i||o&&r.find.TAG("*",l),N=w+=null==E?1:Math.random()||.1,T=S.length;for(l&&(u=s===p||s||l);y!==T&&null!=(d=S[y]);y++){if(o&&d){for(g=0,s||d.ownerDocument===p||(f(d),a=!h);v=e[g++];)if(v(d,s||p,a)){c.push(d);break}l&&(w=N)}n&&((d=!v&&d)&&m--,i&&b.push(d))}if(m+=y,n&&y!==m){for(g=0;v=t[g++];)v(b,x,s,a);if(i){if(m>0)for(;y--;)b[y]||x[y]||(x[y]=D.call(c));x=Ee(x)}k.apply(c,x),l&&!i&&x.length>0&&m+t.length>1&&re.uniqueSort(c)}return l&&(w=N,u=E),b};return n?oe(i):i}(s,o))).selector=e}return a},a=re.select=function(e,t,n,o){var a,u,c,l,f,p="function"==typeof e&&e,d=!o&&i(e=p.selector||e);if(n=n||[],1===d.length){if((u=d[0]=d[0].slice(0)).length>2&&"ID"===(c=u[0]).type&&9===t.nodeType&&h&&r.relative[u[1].type]){if(!(t=(r.find.ID(c.matches[0].replace(Y,Z),t)||[])[0]))return n;p&&(t=t.parentNode),e=e.slice(u.shift().value.length)}for(a=_.needsContext.test(e)?0:u.length;a--&&(c=u[a],!r.relative[l=c.type]);)if((f=r.find[l])&&(o=f(c.matches[0].replace(Y,Z),K.test(u[0].type)&&ce(t.parentNode)||t))){if(u.splice(a,1),!(e=o.length&&be(u)))return k.apply(n,o),n;break}}return(p||s(e,d))(o,t,!h,n,!t||K.test(e)&&ce(t.parentNode)||t),n},t.sortStable=b.split("").sort(C).join("")===b,t.detectDuplicates=!!l,f(),t.sortDetached=ie((function(e){return 1&e.compareDocumentPosition(p.createElement("fieldset"))})),ie((function(e){return e.innerHTML=ye.createHTML(""),"#"===e.firstChild.getAttribute("href")}))||se("type|href|height|width",(function(e,t,r){if(!r)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)})),t.attributes&&ie((function(e){return e.innerHTML=ye.createHTML(""),e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")}))||se("value",(function(e,t,r){if(!r&&"input"===e.nodeName.toLowerCase())return e.defaultValue})),ie((function(e){return null==e.getAttribute("disabled")}))||se(M,(function(e,t,r){var n;if(!r)return!0===e[t]?t.toLowerCase():(n=e.getAttributeNode(t))&&n.specified?n.value:null})),re}(window)),T},O=function(e){var t=!!e._phantom&&!!e.getMatchedCSSRules,r=e.getComputedStyle.bind(e),n=t?e.getMatchedCSSRules.bind(e):null,o=function(e,t){this.pseudoElement=t;try{var r=e.indexOf(":");this.propertyName=e.substring(0,r).trim();var n=e.substring(r+1).trim();n=function(e){if("/"===e[0]&&"/"===e[e.length-1]&&e.indexOf('\\"')<10)return e.replace(/(\^)?url(\\)?\\\((\w|\[\w)/g,'$1url$2\\(\\"?$3');if(-1===e.indexOf('url("'))return e.replace(/url\((.*?)\)/g,'url("$1")');return e}(n),/^\/.*\/$/.test(n)?(n=n.slice(1,-1),this.regex=m.pseudoArgToRegex(n)):(n=n.replace(/\\([\\()[\]"])/g,"$1"),this.regex=m.createURLRegex(n))}catch(t){m.logError("StylePropertyMatcher: invalid match string ".concat(e))}};o.prototype.matches=function(e){if(!this.regex||!this.propertyName)return!1;var o=function(e,o,i){var s="";if(t&&o)for(var a=n(e,o)||[],u=a.length;u-- >0&&!s;)s=a[u].style.getPropertyValue(i);else{var c=r(e,o);c&&(s=c.getPropertyValue(i),"opacity"===i&&m.isSafariBrowser&&(s=(Math.round(100*parseFloat(s))/100).toString()))}return"content"===i&&(s=function(e){return"string"==typeof e?e.replace(/^(["'])([\s\S]*)\1$/,"$2"):e}(s)),s}(e,this.pseudoElement,this.propertyName);return o&&this.regex.test(o)};return{extendSizzle:function(e){e.selectors.pseudos["matches-css"]=e.selectors.createPseudo((function(e){var t=new o(e);return function(e){return t.matches(e)}})),e.selectors.pseudos["matches-css-before"]=e.selectors.createPseudo((function(e){var t=new o(e,":before");return function(e){return t.matches(e)}})),e.selectors.pseudos["matches-css-after"]=e.selectors.createPseudo((function(e){var t=new o(e,":after");return function(e){return t.matches(e)}}))}}}(window),I={};I.MutationObserver=window.MutationObserver||window.WebKitMutationObserver,I.parseMatcherFilter=function(e){var t=[];return-1===e.indexOf('"="')?t.push(e):e.split("=").forEach((function(e){'"'===e[0]&&'"'===e[e.length-1]&&t.push(e.slice(1,-1))})),t},I.parseRawMatcherArg=function(e){var t=e,r=!!e&&"/"===e[0]&&"/"===e[e.length-1];if(r){if(!(e.length>2))throw new Error("Invalid regexp: ".concat(e));t=m.toRegExp(e)}return{arg:t,isRegexp:r}},I.filterRootsByRegexpChain=function(e,t){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],n=t[0];if(1===t.length){for(var o in e)n.isRegexp?n.arg.test(o)&&r.push({base:e,prop:o,value:e[o]}):n.arg===o&&r.push({base:e,prop:n.arg,value:e[o]});return r}if(n.isRegexp){var i=t.slice(1),s=[];for(var a in e)n.arg.test(a)&&s.push(a);s.forEach((function(t){var n=e[t];I.filterRootsByRegexpChain(n,i,r)}))}if(null!==e){var u=e[n.arg];return t=t.slice(1),void 0!==u&&I.filterRootsByRegexpChain(u,t,r),r}},I.validatePropMatcherArgs=function(){for(var e=arguments.length,t=new Array(e),r=0;r-1||i.indexOf("\\.")>-1)throw new Error("Invalid property name: ".concat(i));var a=function(e){for(var t=".",r="/",n=[],o=e;o.length>0;){if(m.startsWith(o,t))throw new Error("Invalid chain property: ".concat(e));if(m.startsWith(o,r)){var i=[];i.push(o.slice(0,1));var s=(o=o.slice(1)).indexOf(r);if(s<1)throw new Error("Invalid regexp: ".concat(r).concat(o));i.push(o.slice(0,s+1));var a=m.toRegExp(i.join(""));n.push({arg:a,isRegexp:!0}),o=o.slice(s+1)}else{var u=o.indexOf(t);if(-1===o.indexOf(t))return n.push({arg:o,isRegexp:!1}),n;var c=o.slice(0,u);if(c.indexOf(r)>-1)throw new Error("Invalid chain property: ".concat(c));n.push({arg:c,isRegexp:!1}),o=o.slice(u)}if(!o)return n;if(!m.startsWith(o,t)||m.startsWith(o,t)&&1===o.length)throw new Error("Invalid chain property: ".concat(e));o=o.slice(1)}}(i),u=I.parseRawMatcherArg(s),c=[].concat(r(a),[u]);if(!I.validatePropMatcherArgs(c))throw new Error("Invalid argument of :matches-property pseudo class: ".concat(n));var l=new e(a,u);return function(e){return l.matches(e)}}))}}}(),L=function(){var e=function(e,t){this.selectors=e,this.pseudoElement=t};e.prototype.matches=function(e){return!!this.selectors.find((function(t){var r=document.querySelectorAll(t);return Array.from(r).find((function(t){return t===e}))}))};return{extendSizzle:function(t){t.selectors.pseudos.is=t.selectors.createPseudo((function(t){if(""===t)throw new Error("Invalid argument of :is pseudo-class: ".concat(t));var r=t.split(",").map((function(e){return e.trim()})).reduce((function(e,t){return A.isSimpleSelectorValid(t)?e.push(t):m.logInfo("Invalid selector passed to :is() pseudo-class: '".concat(t,"'")),e}),[]),n=new e(r);return function(e){return n.matches(e)}}))}}}(),M=function(){var e,t=[":has",":contains",":has-text",":matches-css",":-abp-has",":-abp-has-text",":if",":if-not",":xpath",":nth-ancestor",":upward",":remove",":matches-attr",":matches-property",":-abp-contains",":is"],r=!1;function n(){if(!r){r=!0,e=C(),O.extendSizzle(e),D.extendSizzle(e),P.extendSizzle(e),L.extendSizzle(e);var t=e.selectors.createPseudo((function(e){if(/^\s*\/.*\/[gmisuy]*\s*$/.test(e)){var t,r=(e=e.trim()).lastIndexOf("/"),n=e.substring(r+1);e=e.substr(0,r+1).slice(1,-1).replace(/\\([\\"])/g,"$1");try{t=new RegExp(e,n)}catch(t){throw new Error("Invalid argument of :contains pseudo class: ".concat(e))}return function(e){var r=m.nodeTextContentGetter.apply(e);return t.test(r)}}return e=e.replace(/\\([\\()[\]"])/g,"$1"),function(t){return m.nodeTextContentGetter.apply(t).indexOf(e)>-1}}));e.selectors.pseudos.contains=t,e.selectors.pseudos["has-text"]=t,e.selectors.pseudos["-abp-contains"]=t,e.selectors.pseudos.if=e.selectors.pseudos.has,e.selectors.pseudos["-abp-has"]=e.selectors.pseudos.has,e.selectors.pseudos["if-not"]=e.selectors.createPseudo((function(t){return"string"==typeof t&&e.compile(t),function(r){return 0===e(t,r).length}})),e.selectors.pseudos.xpath=e.selectors.createPseudo((function(e){try{document.createExpression(e,null)}catch(t){throw new Error("Invalid argument of :xpath pseudo class: ".concat(e))}return function(){return!0}})),e.selectors.pseudos["nth-ancestor"]=e.selectors.createPseudo((function(e){var t=Number(e);if(Number.isNaN(t)||t<1||t>=256)throw new Error("Invalid argument of :nth-ancestor pseudo class: ".concat(e));return function(){return!0}})),e.selectors.pseudos.upward=e.selectors.createPseudo((function(e){if(""===e)throw new Error("Invalid argument of :upward pseudo class: ".concat(e));if(Number.isInteger(+e)&&(+e<1||+e>=256))throw new Error("Invalid argument of :upward pseudo class: ".concat(e));return function(){return!0}})),e.selectors.pseudos.remove=e.selectors.createPseudo((function(e){if(""!==e)throw new Error("Invalid argument of :remove pseudo class: ".concat(e));return function(){return!0}}))}}function o(e){var r=e.type;if("ID"===r||"CLASS"===r||"ATTR"===r||"TAG"===r||"CHILD"===r)return!0;if("PSEUDO"===r){for(var n=t.length;n--;)if(e.value.indexOf(t[n])>=0)return!1;return!0}return!1}function i(e){var t=e.type;return" "===t||">"===t||"+"===t||"~"===t}function s(t,r,o){n(),void 0===r?(this.selectorText=A.normalize(t),this.tokens=e.tokenize(this.selectorText,!1,{returnUnsorted:!0})):(this.selectorText=t,this.tokens=r),!0===o&&(this.debug=!0)}s.prototype={createSelector:function(){var e=this.debug,t=this.tokens,r=this.selectorText;if(1!==t.length)return new l(r,e);var n=this.getXpathPart();if(void 0!==n)return new p(r,n,e);var o=this.getUpwardPart();if(void 0!==o){var i,s=parseInt(o,10);if(Number.isNaN(s))i=new d(r,o,e);else i=new p(r,this.convertNthAncestorToken(s),e);return i}var a=this.getRemovePart();if(void 0!==a)return new h(r,""===a,e);var u=(t=t[0]).length,f=this.getSplitPoint();if(void 0===f){try{document.querySelector(r)}catch(t){return new l(r,e)}return new c(r,e)}for(var v="",m=null,y="",b=0;b0&&(m=t[b++].type);b1){if("xpath"===o[0]){if(this.isLastToken(e,t))throw new Error("Invalid pseudo: ':xpath' should be at the end of the selector");return o[1]}if("nth-ancestor"===o[0]){if(this.isLastToken(e,t))throw new Error("Invalid pseudo: ':nth-ancestor' should be at the end of the selector");var i=o[1];if(i>0&&i<256)return this.convertNthAncestorToken(i)}}}}},convertNthAncestorToken:function(e){for(var t="..";e>1;)t+="/..",e--;return t},isLastToken:function(e,t){var r=e[t+1]&&"PSEUDO"===e[t+1].type&&e[t+1].matches&&"remove"===e[t+1].matches[0];return t+1!==e.length&&!r},getUpwardPart:function(){for(var e=this.tokens[0],t=0,r=e.length;t1&&"upward"===o[0]){if(this.isLastToken(e,t))throw new Error("Invalid pseudo: ':upward' should be at the end of the selector");return o[1]}}}},getRemovePart:function(){for(var e=this.tokens[0],t=0,r=e.length;t1&&"remove"===o[0]){if(t+1!==r)throw new Error("Invalid pseudo: ':remove' should be at the end of the selector");return o[1]}}}}};var a=!1;function u(){return a||this.debug}function c(e,t){this.selectorText=e,this.debug=t}function l(t,r){this.selectorText=t,this.debug=r,e.compile(t)}function f(t,r,n){this.selectorText=t,this.pseudoClassArg=r,this.debug=n,e.compile(this.selectorText)}function p(e,t,r){var n=":xpath(//",o=e;m.startsWith(e,n)&&(o=e.replace(n,"body:xpath(//")),f.call(this,o,t,r)}function d(e,t,r){f.call(this,e,t,r)}function h(e,t,r){var n=e.indexOf(":remove()"),o=e.slice(0,n);f.call(this,o,t,r),this.isRemoveSelector=!0}function g(t,r,n,o,i){l.call(this,t,i),this.simple=r,this.relation=n,this.complex=o,e.compile(o)}return c.prototype={querySelectorAll:function(){return document.querySelectorAll(this.selectorText)},matches:function(e){return e[m.matchesPropertyName](this.selectorText)},isDebugging:u},l.prototype={querySelectorAll:function(){return e(this.selectorText)},matches:function(t){return e.matchesSelector(t,this.selectorText)},isDebugging:u},f.prototype={querySelectorAll:function(){var t,r=this,n=[];if(this.selectorText){if(!(t=e(this.selectorText))||!t.length)return n}else t=[document];return t.forEach((function(e){r.searchResultNodes(e,r.pseudoClassArg,n)})),e.uniqueSort(n)},matches:function(e){return this.querySelectorAll().indexOf(e)>-1},isDebugging:u,searchResultNodes:function(e,t,r){t&&r.push(e)}},p.prototype=Object.create(f.prototype),p.prototype.constructor=p,p.prototype.searchResultNodes=function(e,t,r){for(var n,o=document.evaluate(t,e,null,XPathResult.UNORDERED_NODE_ITERATOR_TYPE,null);n=o.iterateNext();)r.push(n)},d.prototype=Object.create(f.prototype),d.prototype.constructor=d,d.prototype.searchResultNodes=function(e,t,r){if(""!==t){var n=e.parentElement;if(null===n)return;if(null===(e=n.closest(t)))return}r.push(e)},h.prototype=Object.create(f.prototype),h.prototype.constructor=h,g.prototype=Object.create(l.prototype),g.prototype.constructor=g,g.prototype.querySelectorAll=function(){var t,r,n=this,o=[],i=this.simple;if(i){if(!(t=document.querySelectorAll(i))||!t.length)return o;r=this.relation}else t=[document],r=" ";switch(r){case" ":t.forEach((function(e){n.relativeSearch(e,o)}));break;case">":t.forEach((function(e){Object.values(e.children).forEach((function(e){n.matches(e)&&o.push(e)}))}));break;case"+":t.forEach((function(e){var t=e.parentNode;Object.values(t.children).forEach((function(t){n.matches(t)&&t.previousElementSibling===e&&o.push(t)}))}));break;case"~":t.forEach((function(e){var t=e.parentNode;Object.values(t.children).forEach((function(t){n.matches(t)&&4===e.compareDocumentPosition(t)&&o.push(t)}))}))}return e.uniqueSort(o)},g.prototype.relativeSearch=function(t,r){e(this.complex,t,r)},{createSelector:function(e,t,r){return new s(e,t,r).createSelector()},enableGlobalDebugging:function(){a=!0}}}(),q=function(){var e,t=/[;}]/g,r=/[;:}]/g,n=/\S/g;function o(e){this.cssText=e}return o.prototype={error:function(e){throw new Error("CssParser: parse error at position ".concat(this.posOffset+e))},validateSelectors:function(t){for(var r=t.length;r--;)for(var n=t[r].groups,o=n.length;o--;){var i=n[o],s=i[i.length-1];if(e.selectors.relative[s.type])return!1}return!0},parseCss:function(){this.posOffset=0,this.cssText||this.error(0);for(var t=[];this.cssText;){var r=e.tokenize(this.cssText,!1,{tolerant:!0,returnUnsorted:!0}),n=r.selectors;this.nextIndex=r.nextIndex,123===this.cssText.charCodeAt(this.nextIndex)&&this.validateSelectors(n)||this.error(this.nextIndex),this.nextIndex++;var o=this.parseNextStyle(),i=!1,s=o.debug;void 0!==s&&("global"===s&&M.enableGlobalDebugging(),i=!0,delete o.debug);for(var a=0,u=n.length;a-1)})):n,s=function(r){e=r.type,t=Date.now()};i.forEach((function(e){document.documentElement.addEventListener(e,s,!0)}));return{isIgnoredEventType:function(){return r.indexOf(e)>-1&&Date.now()-t<10}}}(),a=[],u=[],c={},l=window.addEventListener;var f={attributes:!0,attributeOldValue:!0,attributeFilter:["style"]};function p(e,t){if(!m.MutationObserver)return null;var r=t.map((function(e){return e.style})),n=new m.MutationObserver(function(e){return function(t,r){if(t.length){var n=t[0].target;r.disconnect(),e.forEach((function(e){g(n,e)})),++r.styleProtectionCount<50?r.observe(n,f):m.logError("ExtendedCss: infinite loop protection for style")}}}(r));return n.observe(e,f),n.styleProtectionCount=0,n}function d(e){var t=e.node;e.removed=!0;var r=m.getNodeSelector(t),n=c[r]||0;n>50?m.logError("ExtendedCss: infinite loop protection for SELECTOR",r):t.parentNode&&(t.parentNode.removeChild(t),c[r]=n+1)}function h(e){if(!e.protectionObserver&&(!n||(e=n(e))))for(var t=e.node,r=0;r=0?o.substring(0,s):o).trim(),e.style.setProperty(r,n,"important")}var o,i,s}))}function v(e){e.protectionObserver&&e.protectionObserver.disconnect(),e.node.style.cssText=e.originalStyle}function y(e){var t,r=e.selector.isDebugging();r&&(t=m.AsyncWrapper.now());var n=e.selector.querySelectorAll();if(n.forEach((function(t){var r=function(e){for(var t=0;t0)for(;t--;){var r=u[t];-1===e.indexOf(r.node)?(v(r),u.splice(t,1)):r.removed||r.protectionObserver||(r.protectionObserver=p(r.node,r.rules))}!function(){if(o)return;o=!0,e=w,m.MutationObserver?(i=new m.MutationObserver((function(t){t&&0!==t.length&&(s.isIgnoredEventType()&&function(e){for(var t=0;t { - /** - * Class that matches element attributes against the specified expressions - * @param {ArgData} nameArg - parsed name argument - * @param {ArgData} valueArg - parsed value argument - * @param {string} pseudoElement - * @constructor - * - * @member {string|RegExp} attrName - * @member {boolean} isRegexpName - * @member {string|RegExp} attrValue - * @member {boolean} isRegexpValue - */ - const AttrMatcher = function (nameArg, valueArg, pseudoElement) { - this.pseudoElement = pseudoElement; - ({ arg: this.attrName, isRegexp: this.isRegexpName } = nameArg); - ({ arg: this.attrValue, isRegexp: this.isRegexpValue } = valueArg); - }; - - /** - * Function to check if element attributes matches filter pattern - * @param {Element} element to check - */ - AttrMatcher.prototype.matches = function (element) { - const elAttrs = element.attributes; - if (elAttrs.length === 0 - || !this.attrName) { - return false; - } - - let i = 0; - while (i < elAttrs.length) { - const attr = elAttrs[i]; - let matched = false; - - const attrNameMatched = this.isRegexpName - ? this.attrName.test(attr.name) - : this.attrName === attr.name; - - if (!this.attrValue) { - // for :matches-attr("/regex/") or :matches-attr("attr-name") - matched = attrNameMatched; - } else { - const attrValueMatched = this.isRegexpValue - ? this.attrValue.test(attr.value) - : this.attrValue === attr.value; - matched = attrNameMatched && attrValueMatched; - } - - if (matched) { - return true; - } - i += 1; - } - }; - - /** - * Creates a new pseudo-class and registers it in Sizzle - */ - const extendSizzle = function (sizzle) { - // First of all we should prepare Sizzle engine - sizzle.selectors.pseudos['matches-attr'] = sizzle.selectors.createPseudo((attrFilter) => { - const [rawName, rawValue] = matcherUtils.parseMatcherFilter(attrFilter); - const nameArg = matcherUtils.parseRawMatcherArg(rawName); - const valueArg = matcherUtils.parseRawMatcherArg(rawValue); - - if (!attrFilter || !matcherUtils.validatePropMatcherArgs(nameArg, valueArg)) { - throw new Error(`Invalid argument of :matches-attr pseudo class: ${attrFilter}`); - } - - const matcher = new AttrMatcher(nameArg, valueArg); - return function (element) { - return matcher.matches(element); - }; - }); - }; - - // EXPOSE - return { - extendSizzle, - }; -})(); - -export default AttributesMatcher; diff --git a/lib/css-utils.js b/lib/css-utils.js deleted file mode 100644 index 4ec78104..00000000 --- a/lib/css-utils.js +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Copyright 2016 Adguard Software Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Helper class css utils - * - * @type {{normalize}} - */ -const cssUtils = (() => { - /** - * Regex that matches AdGuard's backward compatible syntaxes. - */ - const reAttrFallback = /\[-(?:ext|abp)-([a-z-_]+)=(["'])((?:(?=(\\?))\4.)*?)\2\]/g; - - /** - * Complex replacement function. - * Unescapes quote characters inside of an extended selector. - * - * @param match Whole matched string - * @param name Group 1 - * @param quoteChar Group 2 - * @param value Group 3 - */ - const evaluateMatch = function (match, name, quoteChar, value) { - // Unescape quotes - const re = new RegExp(`([^\\\\]|^)\\\\${quoteChar}`, 'g'); - value = value.replace(re, `$1${quoteChar}`); - return `:${name}(${value})`; - }; - - // Sizzle's parsing of pseudo class arguments is buggy on certain circumstances - // We support following form of arguments: - // 1. for :matches-css, those of a form {propertyName}: /.*/ - // 2. for :contains, those of a form /.*/ - // We transform such cases in a way that Sizzle has no ambiguity in parsing arguments. - const reMatchesCss = /\:(matches-css(?:-after|-before)?)\(([a-z-\s]*\:\s*\/(?:\\.|[^\/])*?\/\s*)\)/g; - const reContains = /:(?:-abp-)?(contains|has-text)\((\s*\/(?:\\.|[^\/])*?\/\s*)\)/g; - const reScope = /\(\:scope >/g; - // Note that we require `/` character in regular expressions to be escaped. - - /** - * Used for pre-processing pseudo-classes values with above two regexes. - */ - const addQuotes = function (_, c1, c2) { - return `:${c1}("${c2.replace(/["\\]/g, '\\$&')}")`; - }; - - const SCOPE_REPLACER = '(>'; - - /** - * Normalizes specified css text in a form that can be parsed by the - * Sizzle engine. - * Normalization means - * 1. transforming [-ext-*=""] attributes to pseudo classes - * 2. enclosing possibly ambiguous arguments of `:contains`, - * `:matches-css` pseudo classes with quotes. - * @param {string} cssText - * @return {string} - */ - const normalize = function (cssText) { - const normalizedCssText = cssText - .replace(reAttrFallback, evaluateMatch) - .replace(reMatchesCss, addQuotes) - .replace(reContains, addQuotes) - .replace(reScope, SCOPE_REPLACER); - return normalizedCssText; - }; - - const isSimpleSelectorValid = function (selector) { - try { - document.querySelectorAll(selector); - } catch (e) { - return false; - } - return true; - }; - - return { - normalize, - isSimpleSelectorValid, - }; -})(); - -export default cssUtils; diff --git a/lib/element-property-matcher.js b/lib/element-property-matcher.js deleted file mode 100644 index 104e1726..00000000 --- a/lib/element-property-matcher.js +++ /dev/null @@ -1,231 +0,0 @@ -/** - * Copyright 2020 Adguard Software Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import utils from './utils'; -import matcherUtils from './matcher-utils'; - -/** - * Parses raw property arg - * @param {string} input - * @returns {ArgData[]} array of objects - */ -const parseRawPropChain = (input) => { - const PROPS_DIVIDER = '.'; - const REGEXP_MARKER = '/'; - const propsArr = []; - let str = input; - - while (str.length > 0) { - if (utils.startsWith(str, PROPS_DIVIDER)) { - // for cases like '.prop.id' and 'nested..test' - throw new Error(`Invalid chain property: ${input}`); - } - - if (!utils.startsWith(str, REGEXP_MARKER)) { - const isRegexp = false; - const dividerIndex = str.indexOf(PROPS_DIVIDER); - if (str.indexOf(PROPS_DIVIDER) === -1) { - // if there is no '.' left in str - // take the rest of str as prop - propsArr.push({ arg: str, isRegexp }); - return propsArr; - } - // else take prop from str - const prop = str.slice(0, dividerIndex); - // for cases like 'asadf.?+/.test' - if (prop.indexOf(REGEXP_MARKER) > -1) { - // prop is '?+/' - throw new Error(`Invalid chain property: ${prop}`); - } - propsArr.push({ arg: prop, isRegexp }); - // delete prop from str - str = str.slice(dividerIndex); - } else { - // deal with regexp - const propChunks = []; - propChunks.push(str.slice(0, 1)); - // if str starts with '/', delete it from str and find closing regexp slash. - // note that chained property name can not include '/' or '.' - // so there is no checking for escaped characters - str = str.slice(1); - const regexEndIndex = str.indexOf(REGEXP_MARKER); - if (regexEndIndex < 1) { - // regexp should be at least === '/./' - // so we should avoid args like '/id' and 'test.//.id' - throw new Error(`Invalid regexp: ${REGEXP_MARKER}${str}`); - } - const isRegexp = true; - // take the rest regexp part - propChunks.push(str.slice(0, regexEndIndex + 1)); - const prop = utils.toRegExp(propChunks.join('')); - propsArr.push({ arg: prop, isRegexp }); - // delete prop from str - str = str.slice(regexEndIndex + 1); - } - - if (!str) { - return propsArr; - } - - // str should be like '.nextProp' now - // so 'zx.prop' or '.' is invalid - if (!utils.startsWith(str, PROPS_DIVIDER) - || (utils.startsWith(str, PROPS_DIVIDER) && str.length === 1)) { - throw new Error(`Invalid chain property: ${input}`); - } - str = str.slice(1); - } -}; - -const convertTypeFromStr = (value) => { - const numValue = Number(value); - let output; - if (!Number.isNaN(numValue)) { - output = numValue; - } else { - switch (value) { - case 'undefined': - output = undefined; - break; - case 'null': - output = null; - break; - case 'true': - output = true; - break; - case 'false': - output = false; - break; - default: - output = value; - } - } - return output; -}; - -const convertTypeIntoStr = (value) => { - let output; - switch (value) { - case undefined: - output = 'undefined'; - break; - case null: - output = 'null'; - break; - default: - output = value.toString(); - } - return output; -}; - -/** - * Class that extends Sizzle and adds support for "matches-property" pseudo element. - */ -const ElementPropertyMatcher = (() => { - /** - * Class that matches element properties against the specified expressions - * @param {ArgData[]} propsChainArg - array of parsed props chain objects - * @param {ArgData} valueArg - parsed value argument - * @param {string} pseudoElement - * @constructor - * - * @member {Array} chainedProps - * @member {boolean} isRegexpName - * @member {string|RegExp} propValue - * @member {boolean} isRegexpValue - */ - const PropMatcher = function (propsChainArg, valueArg, pseudoElement) { - this.pseudoElement = pseudoElement; - (this.chainedProps = propsChainArg); - ({ arg: this.propValue, isRegexp: this.isRegexpValue } = valueArg); - }; - - /** - * Function to check if element properties matches filter pattern - * @param {Element} element to check - */ - PropMatcher.prototype.matches = function (element) { - const ownerObjArr = matcherUtils.filterRootsByRegexpChain(element, this.chainedProps); - if (ownerObjArr.length === 0) { - return false; - } - - let matched = true; - if (this.propValue) { - for (let i = 0; i < ownerObjArr.length; i += 1) { - const realValue = ownerObjArr[i].value; - - if (this.isRegexpValue) { - matched = this.propValue.test(convertTypeIntoStr(realValue)); - } else { - // handle 'null' and 'undefined' property values set as string - if (realValue === 'null' || realValue === 'undefined') { - matched = this.propValue === realValue; - break; - } - matched = convertTypeFromStr(this.propValue) === realValue; - } - - if (matched) { - break; - } - } - } - return matched; - }; - - /** - * Creates a new pseudo-class and registers it in Sizzle - */ - const extendSizzle = function (sizzle) { - // First of all we should prepare Sizzle engine - sizzle.selectors.pseudos['matches-property'] = sizzle.selectors.createPseudo((propertyFilter) => { - if (!propertyFilter) { - throw new Error('No argument is given for :matches-property pseudo class'); - } - - const [rawProp, rawValue] = matcherUtils.parseMatcherFilter(propertyFilter); - - // chained property name can not include '/' or '.' - // so regex prop names with such escaped characters are invalid - if (rawProp.indexOf('\\/') > -1 - || rawProp.indexOf('\\.') > -1) { - throw new Error(`Invalid property name: ${rawProp}`); - } - - const propsChainArg = parseRawPropChain(rawProp); - const valueArg = matcherUtils.parseRawMatcherArg(rawValue); - - const propsToValidate = [...propsChainArg, valueArg]; - - if (!matcherUtils.validatePropMatcherArgs(propsToValidate)) { - throw new Error(`Invalid argument of :matches-property pseudo class: ${propertyFilter}`); - } - - const matcher = new PropMatcher(propsChainArg, valueArg); - return function (element) { - return matcher.matches(element); - }; - }); - }; - - // EXPOSE - return { - extendSizzle, - }; -})(); - -export default ElementPropertyMatcher; diff --git a/lib/extended-css-parser.js b/lib/extended-css-parser.js deleted file mode 100644 index 22720d2d..00000000 --- a/lib/extended-css-parser.js +++ /dev/null @@ -1,213 +0,0 @@ -/** - * Copyright 2016 Adguard Software Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import utils from './utils'; -import cssUtils from './css-utils'; -import initializeSizzle from './sizzle.patched'; -import ExtendedSelectorFactory from './extended-css-selector'; - -/** - * A helper class that parses stylesheets containing extended selectors - * into ExtendedSelector instances and key-value maps of style declarations. - * Please note, that it does not support any complex things like media queries and such. - */ -const ExtendedCssParser = (function () { - const reDeclEnd = /[;}]/g; - const reDeclDivider = /[;:}]/g; - const reNonWhitespace = /\S/g; - - let Sizzle; - - /** - * @param {string} cssText - * @constructor - */ - function Parser(cssText) { - this.cssText = cssText; - } - - Parser.prototype = { - error(position) { - throw new Error(`CssParser: parse error at position ${this.posOffset + position}`); - }, - - /** - * Validates that the tokens correspond to a valid selector. - * Sizzle is different from browsers and some selectors that it tolerates aren't actually valid. - * For instance, "div >" won't work in a browser, but it will in Sizzle (it'd be the same as "div > *"). - * - * @param {*} selectors An array of SelectorData (selector, groups) - * @returns {boolean} false if any of the groups are invalid - */ - validateSelectors(selectors) { - let iSelectors = selectors.length; - while (iSelectors--) { - const { groups } = selectors[iSelectors]; - let iGroups = groups.length; - - while (iGroups--) { - const tokens = groups[iGroups]; - const lastToken = tokens[tokens.length - 1]; - if (Sizzle.selectors.relative[lastToken.type]) { - return false; - } - } - } - - return true; - }, - - /** - * Parses a stylesheet and returns a list of pairs of an ExtendedSelector and a styles map. - * This method will throw an error in case of an obviously invalid input. - * If any of the selectors used in the stylesheet cannot be compiled into an ExtendedSelector, - * it will be ignored. - * - * @typedef {Object} ExtendedStyle - * @property {Object} selector An instance of the {@link ExtendedSelector} class - * @property {Object} styleMap A map of styles parsed - * - * @returns {Array.} An array of the styles parsed - */ - parseCss() { - this.posOffset = 0; - if (!this.cssText) { this.error(0); } - const results = []; - - while (this.cssText) { - // Apply tolerant tokenization. - const parseResult = Sizzle.tokenize(this.cssText, false, { - tolerant: true, - returnUnsorted: true, - }); - - const selectorData = parseResult.selectors; - this.nextIndex = parseResult.nextIndex; - - if (this.cssText.charCodeAt(this.nextIndex) !== 123 - || /* charCode of '{' */ !this.validateSelectors(selectorData)) { - this.error(this.nextIndex); - } - - this.nextIndex++; // Move the pointer to the start of style declaration. - const styleMap = this.parseNextStyle(); - - let debug = false; - - // If there is a style property 'debug', mark the selector - // as a debuggable selector, and delete the style declaration. - const debugPropertyValue = styleMap['debug']; - if (typeof debugPropertyValue !== 'undefined') { - if (debugPropertyValue === 'global') { - ExtendedSelectorFactory.enableGlobalDebugging(); - } - debug = true; - delete styleMap['debug']; - } - - // Creating an ExtendedSelector instance for every selector we got from Sizzle.tokenize. - // This is quite important as Sizzle does a poor job at executing selectors like "selector1, selector2". - for (let i = 0, l = selectorData.length; i < l; i++) { - const data = selectorData[i]; - try { - const extendedSelector = ExtendedSelectorFactory.createSelector(data.selectorText, data.groups, debug); - if (extendedSelector.pseudoClassArg && extendedSelector.isRemoveSelector) { - // if there is remove pseudo-class in rule, - // the element will be removed and no other styles will be applied - styleMap['remove'] = 'true'; - } - results.push({ - selector: extendedSelector, - style: styleMap, - }); - } catch (ex) { - utils.logError(`ExtendedCssParser: ignoring invalid selector ${data.selectorText}`); - } - } - } - - return results; - }, - - parseNextStyle() { - const styleMap = Object.create(null); - - const bracketPos = this.parseUntilClosingBracket(styleMap); - - // Cut out matched portion from cssText. - reNonWhitespace.lastIndex = bracketPos + 1; - const match = reNonWhitespace.exec(this.cssText); - if (match === null) { - this.cssText = ''; - return styleMap; - } - const matchPos = match.index; - - this.cssText = this.cssText.slice(matchPos); - this.posOffset += matchPos; - return styleMap; - }, - - /** - * @return {number} an index of the next '}' in `this.cssText`. - */ - parseUntilClosingBracket(styleMap) { - // Expects ":", ";", and "}". - reDeclDivider.lastIndex = this.nextIndex; - let match = reDeclDivider.exec(this.cssText); - if (match === null) { - this.error(this.nextIndex); - } - let matchPos = match.index; - let matched = match[0]; - if (matched === '}') { - return matchPos; - } - if (matched === ':') { - const colonIndex = matchPos; - // Expects ";" and "}". - reDeclEnd.lastIndex = colonIndex; - match = reDeclEnd.exec(this.cssText); - if (match === null) { - this.error(colonIndex); - } - matchPos = match.index; - matched = match[0]; - // Populates the `styleMap` key-value map. - const property = this.cssText.slice(this.nextIndex, colonIndex).trim(); - const value = this.cssText.slice(colonIndex + 1, matchPos).trim(); - styleMap[property] = value; - // If found "}", re-run the outer loop. - if (matched === '}') { - return matchPos; - } - } - // matchPos is the position of the next ';'. - // Increase 'nextIndex' and re-run the loop. - this.nextIndex = matchPos + 1; - return this.parseUntilClosingBracket(styleMap); // Should be a subject of tail-call optimization. - }, - }; - - return { - parseCss(cssText) { - Sizzle = initializeSizzle(); - return (new Parser(cssUtils.normalize(cssText))).parseCss(); - }, - }; -})(); - -export default ExtendedCssParser; diff --git a/lib/extended-css-selector.js b/lib/extended-css-selector.js deleted file mode 100644 index ffcb816c..00000000 --- a/lib/extended-css-selector.js +++ /dev/null @@ -1,741 +0,0 @@ -/** - * Copyright 2021 Adguard Software Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* eslint-disable no-use-before-define */ - -import utils from './utils'; -import cssUtils from './css-utils'; -import initializeSizzle from './sizzle.patched'; -import StylePropertyMatcher from './style-property-matcher'; -import AttributesMatcher from './attributes-matcher'; -import ElementPropertyMatcher from './element-property-matcher'; -import IsAnyMatcher from './is-any-matcher'; - -/** - * Extended selector factory module, for creating extended selector classes. - * - * Extended selection capabilities description: - * https://github.com/AdguardTeam/ExtendedCss/blob/master/README.md - */ - -const ExtendedSelectorFactory = (function () { - // while adding new markers, constants in other AdGuard repos should be corrected - // AdGuard browser extension : CssFilterRule.SUPPORTED_PSEUDO_CLASSES and CssFilterRule.EXTENDED_CSS_MARKERS - // tsurlfilter, SafariConverterLib : EXT_CSS_PSEUDO_INDICATORS - const PSEUDO_EXTENSIONS_MARKERS = [':has', ':contains', ':has-text', ':matches-css', - ':-abp-has', ':-abp-has-text', ':if', ':if-not', ':xpath', ':nth-ancestor', ':upward', - ':remove', ':matches-attr', ':matches-property', ':-abp-contains', ':is']; - let initialized = false; - - let Sizzle; - /** - * Lazy initialization of the ExtendedSelectorFactory and objects that might be necessary for creating and applying styles. - * This method extends Sizzle engine that we use under the hood with our custom pseudo-classes. - */ - function initialize() { - if (initialized) { return; } - initialized = true; - - // Our version of Sizzle is initialized lazily as well - Sizzle = initializeSizzle(); - - // Add :matches-css-*() support - StylePropertyMatcher.extendSizzle(Sizzle); - - // Add :matches-attr() support - AttributesMatcher.extendSizzle(Sizzle); - - // Add :matches-property() support - ElementPropertyMatcher.extendSizzle(Sizzle); - - // Add :is() support - IsAnyMatcher.extendSizzle(Sizzle); - - // Add :contains, :has-text, :-abp-contains support - const containsPseudo = Sizzle.selectors.createPseudo((text) => { - if (/^\s*\/.*\/[gmisuy]*\s*$/.test(text)) { - text = text.trim(); - const flagsIndex = text.lastIndexOf('/'); - const flags = text.substring(flagsIndex + 1); - text = text.substr(0, flagsIndex + 1).slice(1, -1).replace(/\\([\\"])/g, '$1'); - let regex; - try { - regex = new RegExp(text, flags); - } catch (e) { - throw new Error(`Invalid argument of :contains pseudo class: ${text}`); - } - return function (elem) { - const elemTextContent = utils.nodeTextContentGetter.apply(elem); - return regex.test(elemTextContent); - }; - } - text = text.replace(/\\([\\()[\]"])/g, '$1'); - return function (elem) { - const elemTextContent = utils.nodeTextContentGetter.apply(elem); - return elemTextContent.indexOf(text) > -1; - }; - }); - Sizzle.selectors.pseudos['contains'] = containsPseudo; - Sizzle.selectors.pseudos['has-text'] = containsPseudo; - Sizzle.selectors.pseudos['-abp-contains'] = containsPseudo; - - // Add :if, :-abp-has support - Sizzle.selectors.pseudos['if'] = Sizzle.selectors.pseudos['has']; - Sizzle.selectors.pseudos['-abp-has'] = Sizzle.selectors.pseudos['has']; - - // Add :if-not support - Sizzle.selectors.pseudos['if-not'] = Sizzle.selectors.createPseudo((selector) => { - if (typeof selector === 'string') { - Sizzle.compile(selector); - } - return function (elem) { - return Sizzle(selector, elem).length === 0; - }; - }); - - registerParserOnlyTokens(); - } - - /** - * Registrate custom tokens for parser. - * Needed for proper work of pseudos: - * for checking if the token is last and pseudo-class arguments validation - */ - function registerParserOnlyTokens() { - Sizzle.selectors.pseudos['xpath'] = Sizzle.selectors.createPseudo((selector) => { - try { - document.createExpression(selector, null); - } catch (e) { - throw new Error(`Invalid argument of :xpath pseudo class: ${selector}`); - } - return () => true; - }); - Sizzle.selectors.pseudos['nth-ancestor'] = Sizzle.selectors.createPseudo((selector) => { - const deep = Number(selector); - if (Number.isNaN(deep) || deep < 1 || deep >= 256) { - throw new Error(`Invalid argument of :nth-ancestor pseudo class: ${selector}`); - } - return () => true; - }); - Sizzle.selectors.pseudos['upward'] = Sizzle.selectors.createPseudo((input) => { - if (input === '') { - throw new Error(`Invalid argument of :upward pseudo class: ${input}`); - } else if (Number.isInteger(+input) && (+input < 1 || +input >= 256)) { - throw new Error(`Invalid argument of :upward pseudo class: ${input}`); - } - return () => true; - }); - Sizzle.selectors.pseudos['remove'] = Sizzle.selectors.createPseudo((input) => { - if (input !== '') { - throw new Error(`Invalid argument of :remove pseudo class: ${input}`); - } - return () => true; - }); - } - - /** - * Checks if specified token can be used by document.querySelectorAll. - */ - function isSimpleToken(token) { - const { type } = token; - if (type === 'ID' || type === 'CLASS' || type === 'ATTR' || type === 'TAG' || type === 'CHILD') { - // known simple tokens - return true; - } - - if (type === 'PSEUDO') { - // check if value contains any of extended pseudo classes - let i = PSEUDO_EXTENSIONS_MARKERS.length; - while (i--) { - if (token.value.indexOf(PSEUDO_EXTENSIONS_MARKERS[i]) >= 0) { - return false; - } - } - return true; - } - // all others aren't simple - return false; - } - - /** - * Checks if specified token is a combinator - */ - function isRelationToken(token) { - const { type } = token; - return type === ' ' || type === '>' || type === '+' || type === '~'; - } - - /** - * ExtendedSelectorParser is a helper class for creating various selector instances which - * all shares a method `querySelectorAll()` and `matches()` implementing different search strategies - * depending on a type of selector. - * - * Currently, there are 3 types: - * A trait-less extended selector - * - we directly feed selector strings to Sizzle. - * A splitted extended selector - * - such as #container #feedItem:has(.ads), where it is splitted to `#container` and `#feedItem:has(.ads)`. - */ - function ExtendedSelectorParser(selectorText, tokens, debug) { - initialize(); - - if (typeof tokens === 'undefined') { - this.selectorText = cssUtils.normalize(selectorText); - // Passing `returnUnsorted` in order to receive tokens in the order that's valid for the browser - // In Sizzle internally, the tokens are re-sorted: https://github.com/AdguardTeam/ExtendedCss/issues/55 - this.tokens = Sizzle.tokenize(this.selectorText, false, { returnUnsorted: true }); - } else { - this.selectorText = selectorText; - this.tokens = tokens; - } - if (debug === true) { - this.debug = true; - } - } - - ExtendedSelectorParser.prototype = { - /** - * The main method, creates a selector instance depending on the type of a selector. - * @public - */ - createSelector() { - const { debug } = this; - let { tokens } = this; - const { selectorText } = this; - if (tokens.length !== 1) { // Comma-separate selector - can't optimize further - return new TraitLessSelector(selectorText, debug); - } - - const xpathPart = this.getXpathPart(); - if (typeof xpathPart !== 'undefined') { - return new XpathSelector(selectorText, xpathPart, debug); - } - - const upwardPart = this.getUpwardPart(); - if (typeof upwardPart !== 'undefined') { - let output; - const upwardDeep = parseInt(upwardPart, 10); - // if upward parameter is not a number, we consider it as a selector - if (Number.isNaN(upwardDeep)) { - output = new UpwardSelector(selectorText, upwardPart, debug); - } else { - // upward works like nth-ancestor - const xpath = this.convertNthAncestorToken(upwardDeep); - output = new XpathSelector(selectorText, xpath, debug); - } - return output; - } - - // argument of pseudo-class remove; - // it's defined only if remove is parsed as last token - // and it's valid only if remove arg is empty string - const removePart = this.getRemovePart(); - if (typeof removePart !== 'undefined') { - const hasValidRemovePart = removePart === ''; - return new RemoveSelector(selectorText, hasValidRemovePart, debug); - } - - tokens = tokens[0]; - const l = tokens.length; - const lastRelTokenInd = this.getSplitPoint(); - if (typeof lastRelTokenInd === 'undefined') { - try { - document.querySelector(selectorText); - } catch (e) { - return new TraitLessSelector(selectorText, debug); - } - return new NotAnExtendedSelector(selectorText, debug); - } - - let simple = ''; - let relation = null; - let complex = ''; - let i = 0; - for (; i < lastRelTokenInd; i++) { // build simple part - simple += tokens[i].value; - } - if (i > 0) { // build relation part - relation = tokens[i++].type; - } - // i is pointing to the start of a complex part. - for (; i < l; i++) { - complex += tokens[i].value; - } - - return lastRelTokenInd === -1 - ? new TraitLessSelector(selectorText, debug) - : new SplittedSelector(selectorText, simple, relation, complex, debug); - }, - /** - * @private - * @return {number|undefined} An index of a token that is split point. - * returns undefined if the selector does not contain any complex tokens - * or it is not eligible for splitting. - * Otherwise returns an integer indicating the index of the last relation token. - */ - getSplitPoint() { - const tokens = this.tokens[0]; - // We split selector only when the last compound selector - // is the only extended selector. - let latestRelationTokenIndex = -1; - let haveMetComplexToken = false; - for (let i = 0, l = tokens.length; i < l; i++) { - const token = tokens[i]; - if (isRelationToken(token)) { - if (haveMetComplexToken) { - return; - } - latestRelationTokenIndex = i; - } else if (!isSimpleToken(token)) { - haveMetComplexToken = true; - } - } - if (!haveMetComplexToken) { return; } - return latestRelationTokenIndex; - }, - /** - * @private - * @return {string|undefined} xpath selector part if exists - * returns undefined if the selector does not contain xpath tokens - */ - getXpathPart() { - const tokens = this.tokens[0]; - for (let i = 0, tokensLength = tokens.length; i < tokensLength; i++) { - const token = tokens[i]; - if (token.type === 'PSEUDO') { - const { matches } = token; - if (matches && matches.length > 1) { - if (matches[0] === 'xpath') { - if (this.isLastToken(tokens, i)) { - throw new Error('Invalid pseudo: \':xpath\' should be at the end of the selector'); - } - return matches[1]; - } - if (matches[0] === 'nth-ancestor') { - if (this.isLastToken(tokens, i)) { - throw new Error('Invalid pseudo: \':nth-ancestor\' should be at the end of the selector'); - } - const deep = matches[1]; - if (deep > 0 && deep < 256) { - return this.convertNthAncestorToken(deep); - } - } - } - } - } - }, - /** - * converts nth-ancestor/upward deep value to xpath equivalent - * @param {number} deep - * @return {string} - */ - convertNthAncestorToken(deep) { - let result = '..'; - while (deep > 1) { - result += '/..'; - deep--; - } - return result; - }, - /** - * Checks if the token is last, - * except of remove pseudo-class - * @param {Array} tokens - * @param {number} i index of token - * @returns {boolean} - */ - isLastToken(tokens, i) { - // check id the next parsed token is remove pseudo - const isNextRemoveToken = tokens[i + 1] - && tokens[i + 1].type === 'PSEUDO' - && tokens[i + 1].matches - && tokens[i + 1].matches[0] === 'remove'; - - // check if the token is last - // and if it is not check if it is remove one - // which should be skipped - return i + 1 !== tokens.length && !isNextRemoveToken; - }, - /** - * @private - * @return {string|undefined} upward parameter - * or undefined if the input does not contain upward tokens - */ - getUpwardPart() { - const tokens = this.tokens[0]; - for (let i = 0, tokensLength = tokens.length; i < tokensLength; i++) { - const token = tokens[i]; - if (token.type === 'PSEUDO') { - const { matches } = token; - if (matches && matches.length > 1) { - if (matches[0] === 'upward') { - if (this.isLastToken(tokens, i)) { - throw new Error('Invalid pseudo: \':upward\' should be at the end of the selector'); - } - return matches[1]; - } - } - } - } - }, - /** - * @private - * @return {string|undefined} remove parameter - * or undefined if the input does not contain remove tokens - */ - getRemovePart() { - const tokens = this.tokens[0]; - for (let i = 0, tokensLength = tokens.length; i < tokensLength; i++) { - const token = tokens[i]; - if (token.type === 'PSEUDO') { - const { matches } = token; - if (matches && matches.length > 1) { - if (matches[0] === 'remove') { - if (i + 1 !== tokensLength) { - throw new Error('Invalid pseudo: \':remove\' should be at the end of the selector'); - } - return matches[1]; - } - } - } - } - }, - }; - - let globalDebuggingFlag = false; - function isDebugging() { - return globalDebuggingFlag || this.debug; - } - - /** - * This class represents a selector which is not an extended selector. - * @param {string} selectorText - * @param {boolean=} debug - * @final - */ - function NotAnExtendedSelector(selectorText, debug) { - this.selectorText = selectorText; - this.debug = debug; - } - - NotAnExtendedSelector.prototype = { - querySelectorAll() { - return document.querySelectorAll(this.selectorText); - }, - matches(element) { - return element[utils.matchesPropertyName](this.selectorText); - }, - isDebugging, - }; - - /** - * A trait-less extended selector class. - * @param {string} selectorText - * @param {boolean=} debug - * @constructor - */ - function TraitLessSelector(selectorText, debug) { - this.selectorText = selectorText; - this.debug = debug; - Sizzle.compile(selectorText); - } - - TraitLessSelector.prototype = { - querySelectorAll() { - return Sizzle(this.selectorText); - }, - /** @final */ - matches(element) { - return Sizzle.matchesSelector(element, this.selectorText); - }, - /** @final */ - isDebugging, - }; - - /** - * Parental class for such pseudo-classes as xpath, upward, remove - * which are limited to be the last one token in selector - * - * @param {string} selectorText - * @param {string} pseudoClassArg pseudo-class arg - * @param {boolean=} debug - * @constructor - */ - function BaseLastArgumentSelector(selectorText, pseudoClassArg, debug) { - this.selectorText = selectorText; - this.pseudoClassArg = pseudoClassArg; - this.debug = debug; - Sizzle.compile(this.selectorText); - } - - BaseLastArgumentSelector.prototype = { - querySelectorAll() { - const resultNodes = []; - let simpleNodes; - if (this.selectorText) { - simpleNodes = Sizzle(this.selectorText); - if (!simpleNodes || !simpleNodes.length) { - return resultNodes; - } - } else { - simpleNodes = [document]; - } - - simpleNodes.forEach((node) => { - this.searchResultNodes(node, this.pseudoClassArg, resultNodes); - }); - - return Sizzle.uniqueSort(resultNodes); - }, - /** @final */ - matches(element) { - const results = this.querySelectorAll(); - return results.indexOf(element) > -1; - }, - /** @final */ - isDebugging, - /** - * Primitive method that returns all nodes if pseudo-class arg is defined. - * That logic works for remove pseudo-class, - * but for others it should be overridden. - * @param {Object} node context element - * @param {string} pseudoClassArg pseudo-class argument - * @param {Array} result - */ - searchResultNodes(node, pseudoClassArg, result) { - if (pseudoClassArg) { - result.push(node); - } - }, - }; - - /** - * Xpath selector class - * Limited to support 'xpath' to be only the last one token in selector - * @param {string} selectorText - * @param {string} xpath value - * @param {boolean=} debug - * @constructor - * @augments BaseLastArgumentSelector - */ - function XpathSelector(selectorText, xpath, debug) { - const NO_SELECTOR_MARKER = ':xpath(//'; - const BODY_SELECTOR_REPLACER = 'body:xpath(//'; - - let modifiedSelectorText = selectorText; - // Normally, a pseudo-class is applied to nodes selected by a selector -- selector:xpath(...). - // However, :xpath is special as the selector can be ommited. - // For any other pseudo-class that would mean "apply to ALL DOM nodes", - // but in case of :xpath it just means "apply me to the document". - if (utils.startsWith(selectorText, NO_SELECTOR_MARKER)) { - modifiedSelectorText = selectorText.replace(NO_SELECTOR_MARKER, BODY_SELECTOR_REPLACER); - } - BaseLastArgumentSelector.call(this, modifiedSelectorText, xpath, debug); - } - XpathSelector.prototype = Object.create(BaseLastArgumentSelector.prototype); - XpathSelector.prototype.constructor = XpathSelector; - /** - * Applies xpath pseudo-class to provided context node - * @param {Object} node context element - * @param {string} pseudoClassArg xpath - * @param {Array} result - * @override - */ - XpathSelector.prototype.searchResultNodes = function (node, pseudoClassArg, result) { - const xpathResult = document.evaluate(pseudoClassArg, node, null, - XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null); - let iNode; - // eslint-disable-next-line no-cond-assign - while (iNode = xpathResult.iterateNext()) { - result.push(iNode); - } - }; - - /** - * Upward selector class - * Limited to support 'upward' to be only the last one token in selector - * @param {string} selectorText - * @param {string} upwardSelector value - * @param {boolean=} debug - * @constructor - * @augments BaseLastArgumentSelector - */ - function UpwardSelector(selectorText, upwardSelector, debug) { - BaseLastArgumentSelector.call(this, selectorText, upwardSelector, debug); - } - UpwardSelector.prototype = Object.create(BaseLastArgumentSelector.prototype); - UpwardSelector.prototype.constructor = UpwardSelector; - /** - * Applies upward pseudo-class to provided context node - * @param {Object} node context element - * @param {string} upwardSelector upward selector - * @param {Array} result - * @override - */ - UpwardSelector.prototype.searchResultNodes = function (node, upwardSelector, result) { - if (upwardSelector !== '') { - const parent = node.parentElement; - if (parent === null) { - return; - } - node = parent.closest(upwardSelector); - if (node === null) { - return; - } - } - result.push(node); - }; - - /** - * Remove selector class - * Limited to support 'remove' to be only the last one token in selector - * @param {string} selectorText - * @param {boolean} hasValidRemovePart - * @param {boolean=} debug - * @constructor - * @augments BaseLastArgumentSelector - */ - function RemoveSelector(selectorText, hasValidRemovePart, debug) { - const REMOVE_PSEUDO_MARKER = ':remove()'; - const removeMarkerIndex = selectorText.indexOf(REMOVE_PSEUDO_MARKER); - // deleting remove part of rule instead of which - // pseudo-property property 'remove' will be added by ExtendedCssParser - const modifiedSelectorText = selectorText.slice(0, removeMarkerIndex); - BaseLastArgumentSelector.call(this, modifiedSelectorText, hasValidRemovePart, debug); - // mark extendedSelector as Remove one for ExtendedCssParser - this.isRemoveSelector = true; - } - RemoveSelector.prototype = Object.create(BaseLastArgumentSelector.prototype); - RemoveSelector.prototype.constructor = RemoveSelector; - - /** - * A splitted extended selector class. - * - * #container #feedItem:has(.ads) - * +--------+ simple - * + relation - * +-----------------+ complex - * We split selector only when the last selector is complex - * @param {string} selectorText - * @param {string} simple - * @param {string} relation - * @param {string} complex - * @param {boolean=} debug - * @constructor - * @extends TraitLessSelector - */ - function SplittedSelector(selectorText, simple, relation, complex, debug) { - TraitLessSelector.call(this, selectorText, debug); - this.simple = simple; - this.relation = relation; - this.complex = complex; - Sizzle.compile(complex); - } - - SplittedSelector.prototype = Object.create(TraitLessSelector.prototype); - SplittedSelector.prototype.constructor = SplittedSelector; - /** @override */ - SplittedSelector.prototype.querySelectorAll = function () { - const resultNodes = []; - let simpleNodes; - - const { simple } = this; - let relation; - if (simple) { - // First we use simple selector to narrow our search - simpleNodes = document.querySelectorAll(simple); - if (!simpleNodes || !simpleNodes.length) { - return resultNodes; - } - ({ relation } = this); - } else { - simpleNodes = [document]; - relation = ' '; - } - - switch (relation) { - case ' ': - simpleNodes.forEach((node) => { - this.relativeSearch(node, resultNodes); - }); - break; - case '>': { - simpleNodes.forEach((node) => { - Object.values(node.children).forEach((childNode) => { - if (this.matches(childNode)) { - resultNodes.push(childNode); - } - }); - }); - - break; - } - case '+': { - simpleNodes.forEach((node) => { - const { parentNode } = node; - Object.values(parentNode.children).forEach((childNode) => { - if (this.matches(childNode) && childNode.previousElementSibling === node) { - resultNodes.push(childNode); - } - }); - }); - break; - } - case '~': { - simpleNodes.forEach((node) => { - const { parentNode } = node; - Object.values(parentNode.children).forEach((childNode) => { - if (this.matches(childNode) && node.compareDocumentPosition(childNode) === 4) { - resultNodes.push(childNode); - } - }); - }); - break; - } - default: - break; - } - - return Sizzle.uniqueSort(resultNodes); - }; - - /** - * Performs a search of "complex" part relative to results for the "simple" part. - * @param {Node} node a node matching the "simple" part. - * @param {Node[]} result an array to append search result. - */ - SplittedSelector.prototype.relativeSearch = function (node, results) { - Sizzle(this.complex, node, results); - }; - - return { - /** - * Wraps the inner class so that the instance is not exposed. - */ - createSelector(selector, tokens, debug) { - return new ExtendedSelectorParser(selector, tokens, debug).createSelector(); - }, - /** - * Mark every selector as a selector being debugged, so that timing information - * for the selector is printed to the console. - */ - enableGlobalDebugging() { - globalDebuggingFlag = true; - }, - }; -})(); - -export default ExtendedSelectorFactory; diff --git a/lib/extended-css.js b/lib/extended-css.js deleted file mode 100644 index dabfd108..00000000 --- a/lib/extended-css.js +++ /dev/null @@ -1,485 +0,0 @@ -/** - * Copyright 2016 Adguard Software Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import utils from './utils'; -import ExtendedCssParser from './extended-css-parser'; -import ExtendedSelectorFactory from './extended-css-selector'; - -/** - * This callback is used to get affected node elements and handle style properties - * before they are applied to them if it is necessary - * @callback beforeStyleApplied - * @param {object} affectedElement - Object containing DOM node and rule to be applied - * @return {object} affectedElement - Same or modified object containing DOM node and rule to be applied - */ - -/** - * Extended css class - * - * @param {Object} configuration - * @param {string} configuration.styleSheet - the CSS stylesheet text - * @param {beforeStyleApplied} [configuration.beforeStyleApplied] - the callback that handles affected elements - * @constructor - */ -function ExtendedCss(configuration) { - if (!configuration) { - throw new Error('Configuration is not provided.'); - } - - const { styleSheet } = configuration; - const { beforeStyleApplied } = configuration; - - if (beforeStyleApplied && typeof beforeStyleApplied !== 'function') { - // eslint-disable-next-line max-len - throw new Error(`Wrong configuration. Type of 'beforeStyleApplied' field should be a function, received: ${typeof beforeStyleApplied}`); - } - - // We use EventTracker to track the event that is likely to cause the mutation. - // The problem is that we cannot use `window.event` directly from the mutation observer call - // as we're not in the event handler context anymore. - const EventTracker = (function () { - const ignoredEventTypes = ['mouseover', 'mouseleave', 'mouseenter', 'mouseout']; - const LAST_EVENT_TIMEOUT_MS = 10; - - const EVENTS = [ - // keyboard events - 'keydown', 'keypress', 'keyup', - // mouse events - 'auxclick', 'click', 'contextmenu', 'dblclick', 'mousedown', 'mouseenter', - 'mouseleave', 'mousemove', 'mouseover', 'mouseout', 'mouseup', 'pointerlockchange', - 'pointerlockerror', 'select', 'wheel', - ]; - - // 'wheel' event makes scrolling in Safari twitchy - // https://github.com/AdguardTeam/ExtendedCss/issues/120 - const safariProblematicEvents = ['wheel']; - - const trackedEvents = utils.isSafariBrowser - ? EVENTS.filter((el) => !(safariProblematicEvents.indexOf(el) > -1)) - : EVENTS; - - let lastEventType; - let lastEventTime; - - const trackEvent = function (e) { - lastEventType = e.type; - lastEventTime = Date.now(); - }; - - trackedEvents.forEach((evName) => { - document.documentElement.addEventListener(evName, trackEvent, true); - }); - - const getLastEventType = function () { - return lastEventType; - }; - - const getTimeSinceLastEvent = function () { - return Date.now() - lastEventTime; - }; - - return { - isIgnoredEventType() { - return ignoredEventTypes.indexOf(getLastEventType()) > -1 && getTimeSinceLastEvent() < LAST_EVENT_TIMEOUT_MS; - }, - }; - })(); - - let rules = []; - const affectedElements = []; - const removalsStatistic = {}; - let domObserved; - const eventListenerSupported = window.addEventListener; - let domMutationObserver; - - function observeDocument(callback) { - // We are trying to limit the number of callback calls by not calling it on all kind of "hover" events. - // The rationale behind this is that "hover" events often cause attributes modification, - // but re-applying extCSS rules will be useless as these attribute changes are usually transient. - const isIgnoredMutation = function (mutations) { - for (let i = 0; i < mutations.length; i += 1) { - if (mutations.type !== 'attributes') { - return false; - } - } - return true; - }; - - if (utils.MutationObserver) { - domMutationObserver = new utils.MutationObserver(((mutations) => { - if (!mutations || mutations.length === 0) { - return; - } - - if (EventTracker.isIgnoredEventType() && isIgnoredMutation(mutations)) { - return; - } - - callback(); - })); - - domMutationObserver.observe(document, { - childList: true, - subtree: true, - attributes: true, - attributeFilter: ['id', 'class'], - }); - } else if (eventListenerSupported) { - document.addEventListener('DOMNodeInserted', callback, false); - document.addEventListener('DOMNodeRemoved', callback, false); - document.addEventListener('DOMAttrModified', callback, false); - } - } - - function disconnectDocument(callback) { - if (domMutationObserver) { - domMutationObserver.disconnect(); - } else if (eventListenerSupported) { - document.removeEventListener('DOMNodeInserted', callback, false); - document.removeEventListener('DOMNodeRemoved', callback, false); - document.removeEventListener('DOMAttrModified', callback, false); - } - } - - const MAX_STYLE_PROTECTION_COUNT = 50; - - const protectionObserverOption = { - attributes: true, - attributeOldValue: true, - attributeFilter: ['style'], - }; - - /** - * Creates MutationObserver protection function - * - * @param styles - * @return {protectionFunction} - */ - function createProtectionFunction(styles) { - function protectionFunction(mutations, observer) { - if (!mutations.length) { - return; - } - const mutation = mutations[0]; - const { target } = mutation; - observer.disconnect(); - styles.forEach((style) => { - setStyleToElement(target, style); - }); - if (++observer.styleProtectionCount < MAX_STYLE_PROTECTION_COUNT) { - observer.observe(target, protectionObserverOption); - } else { - utils.logError('ExtendedCss: infinite loop protection for style'); - } - } - - return protectionFunction; - } - - /** - * Sets up a MutationObserver which protects style attributes from changes - * @param node DOM node - * @param rules rules - * @returns Mutation observer used to protect attribute or null if there's nothing to protect - */ - function protectStyleAttribute(node, rules) { - if (!utils.MutationObserver) { - return null; - } - const styles = rules.map((r) => r.style); - const protectionObserver = new utils.MutationObserver(createProtectionFunction(styles)); - protectionObserver.observe(node, protectionObserverOption); - // Adds an expando to the observer to keep 'style fix counts'. - protectionObserver.styleProtectionCount = 0; - return protectionObserver; - } - - function removeSuffix(str, suffix) { - const index = str.indexOf(suffix, str.length - suffix.length); - if (index >= 0) { return str.substring(0, index); } - return str; - } - - /** - * Finds affectedElement object for the specified DOM node - * @param node DOM node - * @returns affectedElement found or null - */ - function findAffectedElement(node) { - for (let i = 0; i < affectedElements.length; i += 1) { - if (affectedElements[i].node === node) { - return affectedElements[i]; - } - } - return null; - } - - function removeElement(affectedElement) { - const { node } = affectedElement; - - affectedElement.removed = true; - - const elementSelector = utils.getNodeSelector(node); - - // check if the element has been already removed earlier - const elementRemovalsCounter = removalsStatistic[elementSelector] || 0; - - // if removals attempts happened more than specified we do not try to remove node again - if (elementRemovalsCounter > MAX_STYLE_PROTECTION_COUNT) { - utils.logError('ExtendedCss: infinite loop protection for SELECTOR', elementSelector); - return; - } - - if (node.parentNode) { - node.parentNode.removeChild(node); - removalsStatistic[elementSelector] = elementRemovalsCounter + 1; - } - } - - /** - * Applies style to the specified DOM node - * @param affectedElement Object containing DOM node and rule to be applied - */ - function applyStyle(affectedElement) { - if (affectedElement.protectionObserver) { - // Style is already applied and protected by the observer - return; - } - - if (beforeStyleApplied) { - affectedElement = beforeStyleApplied(affectedElement); - if (!affectedElement) { - return; - } - } - - const { node } = affectedElement; - for (let i = 0; i < affectedElement.rules.length; i++) { - const { style } = affectedElement.rules[i]; - if (style['remove'] === 'true') { - removeElement(affectedElement); - return; - } - - setStyleToElement(node, style); - } - } - - /** - * Sets style to the specified DOM node - * @param node element - * @param style style - */ - function setStyleToElement(node, style) { - Object.keys(style).forEach((prop) => { - // Apply this style only to existing properties - // We can't use hasOwnProperty here (does not work in FF) - if (typeof node.style.getPropertyValue(prop) !== 'undefined') { - let value = style[prop]; - // First we should remove !important attribute (or it won't be applied') - value = removeSuffix(value.trim(), '!important').trim(); - node.style.setProperty(prop, value, 'important'); - } - }); - } - - /** - * Reverts style for the affected object - */ - function revertStyle(affectedElement) { - if (affectedElement.protectionObserver) { - affectedElement.protectionObserver.disconnect(); - } - affectedElement.node.style.cssText = affectedElement.originalStyle; - } - - /** - * Applies specified rule and returns list of elements affected - * @param rule Rule to apply - * @returns List of elements affected by this rule - */ - function applyRule(rule) { - const debug = rule.selector.isDebugging(); - let start; - if (debug) { - start = utils.AsyncWrapper.now(); - } - - const { selector } = rule; - const nodes = selector.querySelectorAll(); - - nodes.forEach((node) => { - let affectedElement = findAffectedElement(node); - - if (affectedElement) { - affectedElement.rules.push(rule); - applyStyle(affectedElement); - } else { - // Applying style first time - const originalStyle = node.style.cssText; - affectedElement = { - node, // affected DOM node - rules: [rule], // rules to be applied - originalStyle, // original node style - protectionObserver: null, // style attribute observer - }; - applyStyle(affectedElement); - affectedElements.push(affectedElement); - } - }); - - if (debug) { - const elapsed = utils.AsyncWrapper.now() - start; - if (!('timingStats' in rule)) { - rule.timingStats = new utils.Stats(); - } - rule.timingStats.push(elapsed); - } - - return nodes; - } - - /** - * Applies filtering rules - */ - function applyRules() { - const elementsIndex = []; - // some rules could make call - selector.querySelectorAll() temporarily to change node id attribute - // this caused MutationObserver to call recursively - // https://github.com/AdguardTeam/ExtendedCss/issues/81 - stopObserve(); - rules.forEach((rule) => { - const nodes = applyRule(rule); - Array.prototype.push.apply(elementsIndex, nodes); - }); - // Now revert styles for elements which are no more affected - let l = affectedElements.length; - // do nothing if there is no elements to process - if (elementsIndex.length > 0) { - while (l--) { - const obj = affectedElements[l]; - if (elementsIndex.indexOf(obj.node) === -1) { - // Time to revert style - revertStyle(obj); - affectedElements.splice(l, 1); - } else if (!obj.removed) { - // Add style protection observer - // Protect "style" attribute from changes - if (!obj.protectionObserver) { - obj.protectionObserver = protectStyleAttribute(obj.node, obj.rules); - } - } - } - } - // After styles are applied we can start observe again - observe(); - printTimingInfo(); - } - - const APPLY_RULES_DELAY = 150; - const applyRulesScheduler = new utils.AsyncWrapper(applyRules, APPLY_RULES_DELAY); - const mainCallback = applyRulesScheduler.run.bind(applyRulesScheduler); - - function observe() { - if (domObserved) { return; } - - // Handle dynamically added elements - domObserved = true; - observeDocument(mainCallback); - } - - function stopObserve() { - if (!domObserved) { return; } - domObserved = false; - disconnectDocument(mainCallback); - } - - function apply() { - applyRules(); - - if (document.readyState !== 'complete') { - document.addEventListener('DOMContentLoaded', applyRules); - } - } - - /** - * Disposes ExtendedCss and removes our styles from matched elements - */ - function dispose() { - stopObserve(); - affectedElements.forEach((obj) => { - revertStyle(obj); - }); - } - - let timingsPrinted = false; - /** - * Prints timing information for all selectors marked as "debug" - */ - function printTimingInfo() { - if (timingsPrinted) { return; } - timingsPrinted = true; - - const timings = rules.filter((rule) => rule.selector.isDebugging()).map((rule) => ({ - selectorText: rule.selector.selectorText, - timingStats: rule.timingStats, - })); - - if (timings.length === 0) { return; } - // Add location.href to the message to distinguish frames - utils.logInfo('[ExtendedCss] Timings for %o:\n%o (in milliseconds)', window.location.href, timings); - } - - // First of all parse the stylesheet - rules = ExtendedCssParser.parseCss(styleSheet); - - // EXPOSE - this.dispose = dispose; - this.apply = apply; - - /** Exposed for testing purposes only */ - this._getAffectedElements = function () { - return affectedElements; - }; -} - -/** - * Expose querySelectorAll for debugging and validating selectors - * - * @param {string} selectorText selector text - * @param {boolean} noTiming if true -- do not print the timing to the console - * @returns {Array|NodeList} a list of elements found - * @throws Will throw an error if the argument is not a valid selector - */ -ExtendedCss.query = function (selectorText, noTiming) { - if (typeof selectorText !== 'string') { - throw new Error('Selector text is empty'); - } - - const { now } = utils.AsyncWrapper; - const start = now(); - - try { - return ExtendedSelectorFactory.createSelector(selectorText).querySelectorAll(); - } finally { - const end = now(); - if (!noTiming) { - utils.logInfo(`[ExtendedCss] Elapsed: ${Math.round((end - start) * 1000)} μs.`); - } - } -}; - -export default ExtendedCss; diff --git a/lib/is-any-matcher.js b/lib/is-any-matcher.js deleted file mode 100644 index 51467d4c..00000000 --- a/lib/is-any-matcher.js +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Copyright 2020 Adguard Software Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import utils from './utils'; -import cssUtils from './css-utils'; - -/** - * Class that extends Sizzle and adds support for :is() pseudo element. - */ -const IsAnyMatcher = (() => { - /** - * Class that matches element by one of the selectors - * https://developer.mozilla.org/en-US/docs/Web/CSS/:is - * @param {Array} selectors - * @param {string} pseudoElement - * @constructor - */ - const IsMatcher = function (selectors, pseudoElement) { - this.selectors = selectors; - this.pseudoElement = pseudoElement; - }; - - /** - * Function to check if element can be matched by any passed selector - * @param {Element} element to check - */ - IsMatcher.prototype.matches = function (element) { - const isMatched = !!this.selectors.find((selector) => { - const nodes = document.querySelectorAll(selector); - return Array.from(nodes).find((node) => node === element); - }); - return isMatched; - }; - - /** - * Creates a new pseudo-class and registers it in Sizzle - */ - const extendSizzle = function (sizzle) { - // First of all we should prepare Sizzle engine - sizzle.selectors.pseudos['is'] = sizzle.selectors.createPseudo((input) => { - if (input === '') { - throw new Error(`Invalid argument of :is pseudo-class: ${input}`); - } - - const selectors = input.split(',').map((s) => s.trim()); - - // collect valid selectors and log about invalid ones - const validSelectors = selectors - .reduce((acc, selector) => { - if (cssUtils.isSimpleSelectorValid(selector)) { - acc.push(selector); - } else { - utils.logInfo(`Invalid selector passed to :is() pseudo-class: '${selector}'`); - } - return acc; - }, []); - - const matcher = new IsMatcher(validSelectors); - return function (element) { - return matcher.matches(element); - }; - }); - }; - - return { - extendSizzle, - }; -})(); - -export default IsAnyMatcher; diff --git a/lib/matcher-utils.js b/lib/matcher-utils.js deleted file mode 100644 index fdebde3d..00000000 --- a/lib/matcher-utils.js +++ /dev/null @@ -1,158 +0,0 @@ -/** - * Copyright 2016 Adguard Software Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import utils from './utils'; - -const matcherUtils = {}; -matcherUtils.MutationObserver = window.MutationObserver || window.WebKitMutationObserver; - -/** - * Parses argument of matcher pseudo (for matches-attr and matches-property) - * @param {string} matcherFilter argument of pseudo class - * @returns {Array} - */ -matcherUtils.parseMatcherFilter = (matcherFilter) => { - const FULL_MATCH_MARKER = '"="'; - const rawArgs = []; - if (matcherFilter.indexOf(FULL_MATCH_MARKER) === -1) { - // if there is only one pseudo arg - // e.g. :matches-attr("data-name") or :matches-property("inner.prop") - // Sizzle will parse it and get rid of quotes - // so it might be valid arg already without them - rawArgs.push(matcherFilter); - } else { - matcherFilter - .split('=') - .forEach((arg) => { - if (arg[0] === '"' && arg[arg.length - 1] === '"') { - rawArgs.push(arg.slice(1, -1)); - } - }); - } - return rawArgs; -}; - -/** - * @typedef {Object} ArgData - * @property {string} arg - * @property {boolean} isRegexp - */ - -/** - * Parses raw matcher arg - * @param {string} rawArg - * @returns {ArgData} - */ -matcherUtils.parseRawMatcherArg = (rawArg) => { - let arg = rawArg; - const isRegexp = !!rawArg && rawArg[0] === '/' && rawArg[rawArg.length - 1] === '/'; - if (isRegexp) { - // to avoid at least such case — :matches-property("//") - if (rawArg.length > 2) { - arg = utils.toRegExp(rawArg); - } else { - throw new Error(`Invalid regexp: ${rawArg}`); - } - } - return { arg, isRegexp }; -}; - -/** - * @typedef Chain - * @property {Object} base - * @property {string} prop - * @property {string} value - */ - -/** - * Checks if the property exists in the base object (recursively). - * @param {Object} base - * @param {ArgData[]} chain array of objects - parsed string property chain - * @param {Array} [output=[]] result acc - * @returns {Chain[]} array of objects - */ -matcherUtils.filterRootsByRegexpChain = (base, chain, output = []) => { - const tempProp = chain[0]; - - if (chain.length === 1) { - // eslint-disable-next-line no-restricted-syntax - for (const key in base) { - if (tempProp.isRegexp) { - if (tempProp.arg.test(key)) { - output.push({ base, prop: key, value: base[key] }); - } - } else if (tempProp.arg === key) { - output.push({ base, prop: tempProp.arg, value: base[key] }); - } - } - - return output; - } - - // if there is a regexp prop in input chain - // e.g. 'unit./^ad.+/.src' for 'unit.ad-1gf2.src unit.ad-fgd34.src'), - // every base keys should be tested by regexp and it can be more that one results - if (tempProp.isRegexp) { - const nextProp = chain.slice(1); - - const baseKeys = []; - // eslint-disable-next-line no-restricted-syntax - for (const key in base) { - if (tempProp.arg.test(key)) { - baseKeys.push(key); - } - } - - baseKeys.forEach((key) => { - const item = base[key]; - matcherUtils.filterRootsByRegexpChain(item, nextProp, output); - }); - } - - // avoid TypeError while accessing to null-prop's child - if (base === null) { - return; - } - - const nextBase = base[tempProp.arg]; - chain = chain.slice(1); - if (nextBase !== undefined) { - matcherUtils.filterRootsByRegexpChain(nextBase, chain, output); - } - - return output; -}; - -/** - * Validates parsed args of matches-property pseudo - * @param {...ArgData} args - */ -matcherUtils.validatePropMatcherArgs = (...args) => { - for (let i = 0; i < args.length; i += 1) { - if (args[i].isRegexp) { - if (!utils.startsWith(args[i].arg.toString(), '/') - || !utils.endsWith(args[i].arg.toString(), '/')) { - return false; - } - // simple arg check if it is not a regexp - } else if (!(/^[\w-]+$/.test(args[i].arg))) { - return false; - } - } - return true; -}; - -export default matcherUtils; diff --git a/lib/sizzle.patched.js b/lib/sizzle.patched.js deleted file mode 100644 index 1fd8b7df..00000000 --- a/lib/sizzle.patched.js +++ /dev/null @@ -1,2507 +0,0 @@ -/*! - * Sizzle CSS Selector Engine v2.3.4-pre-adguard - * https://sizzlejs.com/ - * - * Copyright JS Foundation and other contributors - * Released under the MIT license - * https://js.foundation/ - * - * Date: 2020-08-04 - */ -/** - * Version of Sizzle patched by AdGuard in order to be used in the ExtendedCss module. - * https://github.com/AdguardTeam/sizzle-extcss - * - * Look for [AdGuard Patch] and ADGUARD_EXTCSS markers to find out what exactly was changed by us. - * - * Global changes: - * 1. Added additional parameters to the "Sizzle.tokenize" method so that it can be used for stylesheets parsing and validation. - * 2. Added tokens re-sorting mechanism forcing slow pseudos to be matched last (see sortTokenGroups). - * 3. Fix the nonnativeSelectorCache caching -- there was no value corresponding to a key. - * 4. Added Sizzle.compile call to the `:has` pseudo definition. - * - * Changes that are applied to the ADGUARD_EXTCSS build only: - * 1. Do not expose Sizzle to the global scope. Initialize it lazily via initializeSizzle(). - * 2. Removed :contains pseudo declaration -- its syntax is changed and declared outside of Sizzle. - * 3. Removed declarations for the following non-standard pseudo classes: - * :parent, :header, :input, :button, :text, :first, :last, :eq, - * :even, :odd, :lt, :gt, :nth, :radio, :checkbox, :file, - * :password, :image, :submit, :reset - * 4. Added es6 module export - */ -var Sizzle; - -/** - * Initializes Sizzle object. - * In the case of AdGuard ExtendedCss we want to avoid initializing Sizzle right away - * and exposing it to the global scope. - */ -var initializeSizzle = function() { // jshint ignore:line - if (!Sizzle) { - -//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - -Sizzle = -(function( window ) { - -var i, - support, - Expr, - getText, - isXML, - tokenize, - compile, - select, - outermostContext, - sortInput, - hasDuplicate, - - // Local document vars - setDocument, - document, - docElem, - documentIsHTML, - rbuggyQSA, - rbuggyMatches, - matches, - contains, - - // Instance-specific data - expando = "sizzle" + 1 * new Date(), - preferredDoc = window.document, - dirruns = 0, - done = 0, - classCache = createCache(), - tokenCache = createCache(), - compilerCache = createCache(), - nonnativeSelectorCache = createCache(), - sortOrder = function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - } - return 0; - }, - - // Instance methods - hasOwn = ({}).hasOwnProperty, - arr = [], - pop = arr.pop, - push_native = arr.push, - push = arr.push, - slice = arr.slice, - // Use a stripped-down indexOf as it's faster than native - // https://jsperf.com/thor-indexof-vs-for/5 - indexOf = function( list, elem ) { - var i = 0, - len = list.length; - for ( ; i < len; i++ ) { - if ( list[i] === elem ) { - return i; - } - } - return -1; - }, - - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", - - // Regular expressions - - // http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - - // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier - identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", - - // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors - attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + - // Operator (capture 2) - "*([*^$|!~]?=)" + whitespace + - // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" - "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + - "*\\]", - - pseudos = ":(" + identifier + ")(?:\\((" + - // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: - // 1. quoted (capture 3; capture 4 or capture 5) - "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + - // 2. simple (capture 6) - "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + - // 3. anything else (capture 2) - ".*" + - ")\\)|)", - - // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter - rwhitespace = new RegExp( whitespace + "+", "g" ), - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), - - rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), - - rpseudo = new RegExp( pseudos ), - ridentifier = new RegExp( "^" + identifier + "$" ), - - matchExpr = { - "ID": new RegExp( "^#(" + identifier + ")" ), - "CLASS": new RegExp( "^\\.(" + identifier + ")" ), - "TAG": new RegExp( "^(" + identifier + "|[*])" ), - "ATTR": new RegExp( "^" + attributes ), - "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + - "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + - "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), - // For use in libraries implementing .is() - // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + - whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) - }, - - rinputs = /^(?:input|select|textarea|button)$/i, - rheader = /^h\d$/i, - - rnative = /^[^{]+\{\s*\[native \w/, - - // Easily-parseable/retrievable ID or TAG or CLASS selectors - rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, - - rsibling = /[+~]/, - - // CSS escapes - // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), - funescape = function( _, escaped, escapedWhitespace ) { - var high = "0x" + escaped - 0x10000; - // NaN means non-codepoint - // Support: Firefox<24 - // Workaround erroneous numeric interpretation of +"0x" - return high !== high || escapedWhitespace ? - escaped : - high < 0 ? - // BMP codepoint - String.fromCharCode( high + 0x10000 ) : - // Supplemental Plane codepoint (surrogate pair) - String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); - }, - - // CSS string/identifier serialization - // https://drafts.csswg.org/cssom/#common-serializing-idioms - rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, - fcssescape = function( ch, asCodePoint ) { - if ( asCodePoint ) { - - // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER - if ( ch === "\0" ) { - return "\uFFFD"; - } - - // Control characters and (dependent upon position) numbers get escaped as code points - return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; - } - - // Other potentially-special ASCII characters get backslash-escaped - return "\\" + ch; - }, - - // Used for iframes - // See setDocument() - // Removing the function wrapper causes a "Permission Denied" - // error in IE - unloadHandler = function() { - setDocument(); - }, - - inDisabledFieldset = addCombinator( - function( elem ) { - return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; - }, - { dir: "parentNode", next: "legend" } - ); - -// Optimize for push.apply( _, NodeList ) -try { - push.apply( - (arr = slice.call( preferredDoc.childNodes )), - preferredDoc.childNodes - ); - // Support: Android<4.0 - // Detect silently failing push.apply - arr[ preferredDoc.childNodes.length ].nodeType; -} catch ( e ) { - push = { apply: arr.length ? - - // Leverage slice if possible - function( target, els ) { - push_native.apply( target, slice.call(els) ); - } : - - // Support: IE<9 - // Otherwise append directly - function( target, els ) { - var j = target.length, - i = 0; - // Can't trust NodeList.length - while ( (target[j++] = els[i++]) ) {} - target.length = j - 1; - } - }; -} - -function Sizzle( selector, context, results, seed ) { - var m, i, elem, nid, match, groups, newSelector, - newContext = context && context.ownerDocument, - - // nodeType defaults to 9, since context defaults to document - nodeType = context ? context.nodeType : 9; - - results = results || []; - - // Return early from calls with invalid selector or context - if ( typeof selector !== "string" || !selector || - nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { - - return results; - } - - // Try to shortcut find operations (as opposed to filters) in HTML documents - if ( !seed ) { - - if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { - setDocument( context ); - } - context = context || document; - - if ( documentIsHTML ) { - - // If the selector is sufficiently simple, try using a "get*By*" DOM method - // (excepting DocumentFragment context, where the methods don't exist) - if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { - - // ID selector - if ( (m = match[1]) ) { - - // Document context - if ( nodeType === 9 ) { - if ( (elem = context.getElementById( m )) ) { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( elem.id === m ) { - results.push( elem ); - return results; - } - } else { - return results; - } - - // Element context - } else { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( newContext && (elem = newContext.getElementById( m )) && - contains( context, elem ) && - elem.id === m ) { - - results.push( elem ); - return results; - } - } - - // Type selector - } else if ( match[2] ) { - push.apply( results, context.getElementsByTagName( selector ) ); - return results; - - // Class selector - } else if ( (m = match[3]) && support.getElementsByClassName && - context.getElementsByClassName ) { - - push.apply( results, context.getElementsByClassName( m ) ); - return results; - } - } - - // Take advantage of querySelectorAll - if ( support.qsa && - !nonnativeSelectorCache[ selector + " " ] && - (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { - - if ( nodeType !== 1 ) { - newContext = context; - newSelector = selector; - - // qSA looks outside Element context, which is not what we want - // Thanks to Andrew Dupont for this workaround technique - // Support: IE <=8 - // Exclude object elements - } else if ( context.nodeName.toLowerCase() !== "object" ) { - - // Capture the context ID, setting it first if necessary - if ( (nid = context.getAttribute( "id" )) ) { - nid = nid.replace( rcssescape, fcssescape ); - } else { - context.setAttribute( "id", (nid = expando) ); - } - - // Prefix every selector in the list - groups = tokenize( selector ); - i = groups.length; - while ( i-- ) { - groups[i] = "#" + nid + " " + toSelector( groups[i] ); - } - newSelector = groups.join( "," ); - - // Expand context for sibling selectors - newContext = rsibling.test( selector ) && testContext( context.parentNode ) || - context; - } - - if ( newSelector ) { - try { - if (newSelector.indexOf(':has(') > -1) { - // https://github.com/AdguardTeam/ExtendedCss/issues/149 - throw new Error('Do not handle :has() pseudo-class by the native method'); - } - push.apply( results, - newContext.querySelectorAll( newSelector ) - ); - return results; - } catch ( qsaError ) { - // [AdGuard Path]: Fix the cache value - nonnativeSelectorCache( selector, true ); - } finally { - if ( nid === expando ) { - context.removeAttribute( "id" ); - } - } - } - } - } - } - - // All others - return select( selector.replace( rtrim, "$1" ), context, results, seed ); -} - -/** - * Create key-value caches of limited size - * @returns {function(string, object)} Returns the Object data after storing it on itself with - * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) - * deleting the oldest entry - */ -function createCache() { - var keys = []; - - function cache( key, value ) { - // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) - if ( keys.push( key + " " ) > Expr.cacheLength ) { - // Only keep the most recent entries - delete cache[ keys.shift() ]; - } - return (cache[ key + " " ] = value); - } - return cache; -} - -/** - * Mark a function for special use by Sizzle - * @param {Function} fn The function to mark - */ -function markFunction( fn ) { - fn[ expando ] = true; - return fn; -} - -/** - * Support testing using an element - * @param {Function} fn Passed the created element and returns a boolean result - */ -function assert( fn ) { - var el = document.createElement("fieldset"); - - try { - return !!fn( el ); - } catch (e) { - return false; - } finally { - // Remove from its parent by default - if ( el.parentNode ) { - el.parentNode.removeChild( el ); - } - // release memory in IE - el = null; - } -} - -/** - * Adds the same handler for all of the specified attrs - * @param {String} attrs Pipe-separated list of attributes - * @param {Function} handler The method that will be applied - */ -function addHandle( attrs, handler ) { - var arr = attrs.split("|"), - i = arr.length; - - while ( i-- ) { - Expr.attrHandle[ arr[i] ] = handler; - } -} - -/** - * Checks document order of two siblings - * @param {Element} a - * @param {Element} b - * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b - */ -function siblingCheck( a, b ) { - var cur = b && a, - diff = cur && a.nodeType === 1 && b.nodeType === 1 && - a.sourceIndex - b.sourceIndex; - - // Use IE sourceIndex if available on both nodes - if ( diff ) { - return diff; - } - - // Check if b follows a - if ( cur ) { - while ( (cur = cur.nextSibling) ) { - if ( cur === b ) { - return -1; - } - } - } - - return a ? 1 : -1; -} - -/** - * Returns a function to use in pseudos for input types - * @param {String} type - */ -function createInputPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for buttons - * @param {String} type - */ -function createButtonPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for :enabled/:disabled - * @param {Boolean} disabled true for :disabled; false for :enabled - */ -function createDisabledPseudo( disabled ) { - - // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable - return function( elem ) { - - // Only certain elements can match :enabled or :disabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled - if ( "form" in elem ) { - - // Check for inherited disabledness on relevant non-disabled elements: - // * listed form-associated elements in a disabled fieldset - // https://html.spec.whatwg.org/multipage/forms.html#category-listed - // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled - // * option elements in a disabled optgroup - // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled - // All such elements have a "form" property. - if ( elem.parentNode && elem.disabled === false ) { - - // Option elements defer to a parent optgroup if present - if ( "label" in elem ) { - if ( "label" in elem.parentNode ) { - return elem.parentNode.disabled === disabled; - } else { - return elem.disabled === disabled; - } - } - - // Support: IE 6 - 11 - // Use the isDisabled shortcut property to check for disabled fieldset ancestors - return elem.isDisabled === disabled || - - // Where there is no isDisabled, check manually - /* jshint -W018 */ - elem.isDisabled !== !disabled && - inDisabledFieldset( elem ) === disabled; - } - - return elem.disabled === disabled; - - // Try to winnow out elements that can't be disabled before trusting the disabled property. - // Some victims get caught in our net (label, legend, menu, track), but it shouldn't - // even exist on them, let alone have a boolean value. - } else if ( "label" in elem ) { - return elem.disabled === disabled; - } - - // Remaining elements are neither :enabled nor :disabled - return false; - }; -} - -/** - * Returns a function to use in pseudos for positionals - * @param {Function} fn - */ -function createPositionalPseudo( fn ) { - return markFunction(function( argument ) { - argument = +argument; - return markFunction(function( seed, matches ) { - var j, - matchIndexes = fn( [], seed.length, argument ), - i = matchIndexes.length; - - // Match elements found at the specified indexes - while ( i-- ) { - if ( seed[ (j = matchIndexes[i]) ] ) { - seed[j] = !(matches[j] = seed[j]); - } - } - }); - }); -} - -/** - * Checks a node for validity as a Sizzle context - * @param {Element|Object=} context - * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value - */ -function testContext( context ) { - return context && typeof context.getElementsByTagName !== "undefined" && context; -} - -// Expose support vars for convenience -support = Sizzle.support = {}; - -/** - * Detects XML nodes - * @param {Element|Object} elem An element or a document - * @returns {Boolean} True iff elem is a non-HTML XML node - */ -isXML = Sizzle.isXML = function( elem ) { - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = elem && (elem.ownerDocument || elem).documentElement; - return documentElement ? documentElement.nodeName !== "HTML" : false; -}; - -/** - * Sets document-related variables once based on the current document - * @param {Element|Object} [doc] An element or document object to use to set the document - * @returns {Object} Returns the current document - */ -setDocument = Sizzle.setDocument = function( node ) { - var hasCompare, subWindow, - doc = node ? node.ownerDocument || node : preferredDoc; - - // Return early if doc is invalid or already selected - if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { - return document; - } - - // Update global variables - document = doc; - docElem = document.documentElement; - documentIsHTML = !isXML( document ); - - // Support: IE 9-11, Edge - // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) - if ( preferredDoc !== document && - (subWindow = document.defaultView) && subWindow.top !== subWindow ) { - - // Support: IE 11, Edge - if ( subWindow.addEventListener ) { - subWindow.addEventListener( "unload", unloadHandler, false ); - - // Support: IE 9 - 10 only - } else if ( subWindow.attachEvent ) { - subWindow.attachEvent( "onunload", unloadHandler ); - } - } - - /* Attributes - ---------------------------------------------------------------------- */ - - // Support: IE<8 - // Verify that getAttribute really returns attributes and not properties - // (excepting IE8 booleans) - support.attributes = assert(function( el ) { - el.className = "i"; - return !el.getAttribute("className"); - }); - - /* getElement(s)By* - ---------------------------------------------------------------------- */ - - // Check if getElementsByTagName("*") returns only elements - support.getElementsByTagName = assert(function( el ) { - el.appendChild( document.createComment("") ); - return !el.getElementsByTagName("*").length; - }); - - // Support: IE<9 - support.getElementsByClassName = rnative.test( document.getElementsByClassName ); - - // Support: IE<10 - // Check if getElementById returns elements by name - // The broken getElementById methods don't pick up programmatically-set names, - // so use a roundabout getElementsByName test - support.getById = assert(function( el ) { - docElem.appendChild( el ).id = expando; - return !document.getElementsByName || !document.getElementsByName( expando ).length; - }); - - // ID filter and find - if ( support.getById ) { - Expr.filter["ID"] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - return elem.getAttribute("id") === attrId; - }; - }; - Expr.find["ID"] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var elem = context.getElementById( id ); - return elem ? [ elem ] : []; - } - }; - } else { - Expr.filter["ID"] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - var node = typeof elem.getAttributeNode !== "undefined" && - elem.getAttributeNode("id"); - return node && node.value === attrId; - }; - }; - - // Support: IE 6 - 7 only - // getElementById is not reliable as a find shortcut - Expr.find["ID"] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var node, i, elems, - elem = context.getElementById( id ); - - if ( elem ) { - - // Verify the id attribute - node = elem.getAttributeNode("id"); - if ( node && node.value === id ) { - return [ elem ]; - } - - // Fall back on getElementsByName - elems = context.getElementsByName( id ); - i = 0; - while ( (elem = elems[i++]) ) { - node = elem.getAttributeNode("id"); - if ( node && node.value === id ) { - return [ elem ]; - } - } - } - - return []; - } - }; - } - - // Tag - Expr.find["TAG"] = support.getElementsByTagName ? - function( tag, context ) { - if ( typeof context.getElementsByTagName !== "undefined" ) { - return context.getElementsByTagName( tag ); - - // DocumentFragment nodes don't have gEBTN - } else if ( support.qsa ) { - return context.querySelectorAll( tag ); - } - } : - - function( tag, context ) { - var elem, - tmp = [], - i = 0, - // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too - results = context.getElementsByTagName( tag ); - - // Filter out possible comments - if ( tag === "*" ) { - while ( (elem = results[i++]) ) { - if ( elem.nodeType === 1 ) { - tmp.push( elem ); - } - } - - return tmp; - } - return results; - }; - - // Class - Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { - if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { - return context.getElementsByClassName( className ); - } - }; - - /* QSA/matchesSelector - ---------------------------------------------------------------------- */ - - // QSA and matchesSelector support - - // matchesSelector(:active) reports false when true (IE9/Opera 11.5) - rbuggyMatches = []; - - // qSa(:focus) reports false when true (Chrome 21) - // We allow this because of a bug in IE8/9 that throws an error - // whenever `document.activeElement` is accessed on an iframe - // So, we allow :focus to pass through QSA all the time to avoid the IE error - // See https://bugs.jquery.com/ticket/13378 - rbuggyQSA = []; - - if ( (support.qsa = rnative.test( document.querySelectorAll )) ) { - // Build QSA regex - // Regex strategy adopted from Diego Perini - assert(function( el ) { - // Select is set to empty string on purpose - // This is to test IE's treatment of not explicitly - // setting a boolean content attribute, - // since its presence should be enough - // https://bugs.jquery.com/ticket/12359 - docElem.appendChild( el ).innerHTML = AGPolicy.createHTML("" + - ""); - - // Support: IE8, Opera 11-12.16 - // Nothing should be selected when empty strings follow ^= or $= or *= - // The test attribute must be unknown in Opera but "safe" for WinRT - // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section - if ( el.querySelectorAll("[msallowcapture^='']").length ) { - rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); - } - - // Support: IE8 - // Boolean attributes and "value" are not treated correctly - if ( !el.querySelectorAll("[selected]").length ) { - rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); - } - - // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ - if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { - rbuggyQSA.push("~="); - } - - // Webkit/Opera - :checked should return selected option elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - // IE8 throws error here and will not see later tests - if ( !el.querySelectorAll(":checked").length ) { - rbuggyQSA.push(":checked"); - } - - // Support: Safari 8+, iOS 8+ - // https://bugs.webkit.org/show_bug.cgi?id=136851 - // In-page `selector#id sibling-combinator selector` fails - if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { - rbuggyQSA.push(".#.+[+~]"); - } - }); - - assert(function( el ) { - el.innerHTML = AGPolicy.createHTML("" + - ""); - - // Support: Windows 8 Native Apps - // The type and name attributes are restricted during .innerHTML assignment - var input = document.createElement("input"); - input.setAttribute( "type", "hidden" ); - el.appendChild( input ).setAttribute( "name", "D" ); - - // Support: IE8 - // Enforce case-sensitivity of name attribute - if ( el.querySelectorAll("[name=d]").length ) { - rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); - } - - // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) - // IE8 throws error here and will not see later tests - if ( el.querySelectorAll(":enabled").length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Support: IE9-11+ - // IE's :disabled selector does not pick up the children of disabled fieldsets - docElem.appendChild( el ).disabled = true; - if ( el.querySelectorAll(":disabled").length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Opera 10-11 does not throw on post-comma invalid pseudos - el.querySelectorAll("*,:x"); - rbuggyQSA.push(",.*:"); - }); - } - - if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || - docElem.webkitMatchesSelector || - docElem.mozMatchesSelector || - docElem.oMatchesSelector || - docElem.msMatchesSelector) )) ) { - - assert(function( el ) { - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9) - support.disconnectedMatch = matches.call( el, "*" ); - - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( el, "[s!='']:x" ); - rbuggyMatches.push( "!=", pseudos ); - }); - } - - rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); - rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); - - /* Contains - ---------------------------------------------------------------------- */ - hasCompare = rnative.test( docElem.compareDocumentPosition ); - - // Element contains another - // Purposefully self-exclusive - // As in, an element does not contain itself - contains = hasCompare || rnative.test( docElem.contains ) ? - function( a, b ) { - var adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!( bup && bup.nodeType === 1 && ( - adown.contains ? - adown.contains( bup ) : - a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - )); - } : - function( a, b ) { - if ( b ) { - while ( (b = b.parentNode) ) { - if ( b === a ) { - return true; - } - } - } - return false; - }; - - /* Sorting - ---------------------------------------------------------------------- */ - - // Document order sorting - sortOrder = hasCompare ? - function( a, b ) { - - // Flag for duplicate removal - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - // Sort on method existence if only one input has compareDocumentPosition - var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; - if ( compare ) { - return compare; - } - - // Calculate position if both inputs belong to the same document - compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? - a.compareDocumentPosition( b ) : - - // Otherwise we know they are disconnected - 1; - - // Disconnected nodes - if ( compare & 1 || - (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { - - // Choose the first element that is related to our preferred document - if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { - return -1; - } - if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { - return 1; - } - - // Maintain original order - return sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - } - - return compare & 4 ? -1 : 1; - } : - function( a, b ) { - // Exit early if the nodes are identical - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - var cur, - i = 0, - aup = a.parentNode, - bup = b.parentNode, - ap = [ a ], - bp = [ b ]; - - // Parentless nodes are either documents or disconnected - if ( !aup || !bup ) { - return a === document ? -1 : - b === document ? 1 : - aup ? -1 : - bup ? 1 : - sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - - // If the nodes are siblings, we can do a quick check - } else if ( aup === bup ) { - return siblingCheck( a, b ); - } - - // Otherwise we need full lists of their ancestors for comparison - cur = a; - while ( (cur = cur.parentNode) ) { - ap.unshift( cur ); - } - cur = b; - while ( (cur = cur.parentNode) ) { - bp.unshift( cur ); - } - - // Walk down the tree looking for a discrepancy - while ( ap[i] === bp[i] ) { - i++; - } - - return i ? - // Do a sibling check if the nodes have a common ancestor - siblingCheck( ap[i], bp[i] ) : - - // Otherwise nodes in our document sort first - ap[i] === preferredDoc ? -1 : - bp[i] === preferredDoc ? 1 : - 0; - }; - - return document; -}; - -Sizzle.matches = function( expr, elements ) { - return Sizzle( expr, null, null, elements ); -}; - -Sizzle.matchesSelector = function( elem, expr ) { - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - if ( support.matchesSelector && documentIsHTML && - !nonnativeSelectorCache[ expr + " " ] && - ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && - ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { - - try { - var ret = matches.call( elem, expr ); - - // IE 9's matchesSelector returns false on disconnected nodes - if ( ret || support.disconnectedMatch || - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11 ) { - return ret; - } - } catch (e) { - // [AdGuard Path]: Fix the cache value - nonnativeSelectorCache( expr, true ); - } - } - - return Sizzle( expr, document, null, [ elem ] ).length > 0; -}; - -Sizzle.contains = function( context, elem ) { - // Set document vars if needed - if ( ( context.ownerDocument || context ) !== document ) { - setDocument( context ); - } - return contains( context, elem ); -}; - -Sizzle.attr = function( elem, name ) { - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - var fn = Expr.attrHandle[ name.toLowerCase() ], - // Don't get fooled by Object.prototype properties (jQuery #13807) - val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? - fn( elem, name, !documentIsHTML ) : - undefined; - - return val !== undefined ? - val : - support.attributes || !documentIsHTML ? - elem.getAttribute( name ) : - (val = elem.getAttributeNode(name)) && val.specified ? - val.value : - null; -}; - -Sizzle.escape = function( sel ) { - return (sel + "").replace( rcssescape, fcssescape ); -}; - -Sizzle.error = function( msg ) { - throw new Error( "Syntax error, unrecognized expression: " + msg ); -}; - -/** - * Document sorting and removing duplicates - * @param {ArrayLike} results - */ -Sizzle.uniqueSort = function( results ) { - var elem, - duplicates = [], - j = 0, - i = 0; - - // Unless we *know* we can detect duplicates, assume their presence - hasDuplicate = !support.detectDuplicates; - sortInput = !support.sortStable && results.slice( 0 ); - results.sort( sortOrder ); - - if ( hasDuplicate ) { - while ( (elem = results[i++]) ) { - if ( elem === results[ i ] ) { - j = duplicates.push( i ); - } - } - while ( j-- ) { - results.splice( duplicates[ j ], 1 ); - } - } - - // Clear input after sorting to release objects - // See https://github.com/jquery/sizzle/pull/225 - sortInput = null; - - return results; -}; - -/** - * Utility function for retrieving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ -getText = Sizzle.getText = function( elem ) { - var node, - ret = "", - i = 0, - nodeType = elem.nodeType; - - if ( !nodeType ) { - // If no nodeType, this is expected to be an array - while ( (node = elem[i++]) ) { - // Do not traverse comment nodes - ret += getText( node ); - } - } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { - // Use textContent for elements - // innerText usage removed for consistency of new lines (jQuery #11153) - if ( typeof elem.textContent === "string" ) { - return elem.textContent; - } else { - // Traverse its children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - ret += getText( elem ); - } - } - } else if ( nodeType === 3 || nodeType === 4 ) { - return elem.nodeValue; - } - // Do not include comment or processing instruction nodes - - return ret; -}; - -Expr = Sizzle.selectors = { - - // Can be adjusted by the user - cacheLength: 50, - - createPseudo: markFunction, - - match: matchExpr, - - attrHandle: {}, - - find: {}, - - relative: { - ">": { dir: "parentNode", first: true }, - " ": { dir: "parentNode" }, - "+": { dir: "previousSibling", first: true }, - "~": { dir: "previousSibling" } - }, - - preFilter: { - "ATTR": function( match ) { - match[1] = match[1].replace( runescape, funescape ); - - // Move the given value to match[3] whether quoted or unquoted - match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); - - if ( match[2] === "~=" ) { - match[3] = " " + match[3] + " "; - } - - return match.slice( 0, 4 ); - }, - - "CHILD": function( match ) { - /* matches from matchExpr["CHILD"] - 1 type (only|nth|...) - 2 what (child|of-type) - 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) - 4 xn-component of xn+y argument ([+-]?\d*n|) - 5 sign of xn-component - 6 x of xn-component - 7 sign of y-component - 8 y of y-component - */ - match[1] = match[1].toLowerCase(); - - if ( match[1].slice( 0, 3 ) === "nth" ) { - // nth-* requires argument - if ( !match[3] ) { - Sizzle.error( match[0] ); - } - - // numeric x and y parameters for Expr.filter.CHILD - // remember that false/true cast respectively to 0/1 - match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); - match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); - - // other types prohibit arguments - } else if ( match[3] ) { - Sizzle.error( match[0] ); - } - - return match; - }, - - "PSEUDO": function( match ) { - var excess, - unquoted = !match[6] && match[2]; - - if ( matchExpr["CHILD"].test( match[0] ) ) { - return null; - } - - // Accept quoted arguments as-is - if ( match[3] ) { - match[2] = match[4] || match[5] || ""; - - // Strip excess characters from unquoted arguments - } else if ( unquoted && rpseudo.test( unquoted ) && - // Get excess from tokenize (recursively) - (excess = tokenize( unquoted, true )) && - // advance to the next closing parenthesis - (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { - - // excess is a negative index - match[0] = match[0].slice( 0, excess ); - match[2] = unquoted.slice( 0, excess ); - } - - // Return only captures needed by the pseudo filter method (type and argument) - return match.slice( 0, 3 ); - } - }, - - filter: { - - "TAG": function( nodeNameSelector ) { - var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); - return nodeNameSelector === "*" ? - function() { return true; } : - function( elem ) { - return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; - }; - }, - - "CLASS": function( className ) { - var pattern = classCache[ className + " " ]; - - return pattern || - (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && - classCache( className, function( elem ) { - return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); - }); - }, - - "ATTR": function( name, operator, check ) { - return function( elem ) { - var result = Sizzle.attr( elem, name ); - - if ( result == null ) { - return operator === "!="; - } - if ( !operator ) { - return true; - } - - result += ""; - - return operator === "=" ? result === check : - operator === "!=" ? result !== check : - operator === "^=" ? check && result.indexOf( check ) === 0 : - operator === "*=" ? check && result.indexOf( check ) > -1 : - operator === "$=" ? check && result.slice( -check.length ) === check : - operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : - operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : - false; - }; - }, - - "CHILD": function( type, what, argument, first, last ) { - var simple = type.slice( 0, 3 ) !== "nth", - forward = type.slice( -4 ) !== "last", - ofType = what === "of-type"; - - return first === 1 && last === 0 ? - - // Shortcut for :nth-*(n) - function( elem ) { - return !!elem.parentNode; - } : - - function( elem, context, xml ) { - var cache, uniqueCache, outerCache, node, nodeIndex, start, - dir = simple !== forward ? "nextSibling" : "previousSibling", - parent = elem.parentNode, - name = ofType && elem.nodeName.toLowerCase(), - useCache = !xml && !ofType, - diff = false; - - if ( parent ) { - - // :(first|last|only)-(child|of-type) - if ( simple ) { - while ( dir ) { - node = elem; - while ( (node = node[ dir ]) ) { - if ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) { - - return false; - } - } - // Reverse direction for :only-* (if we haven't yet done so) - start = dir = type === "only" && !start && "nextSibling"; - } - return true; - } - - start = [ forward ? parent.firstChild : parent.lastChild ]; - - // non-xml :nth-child(...) stores cache data on `parent` - if ( forward && useCache ) { - - // Seek `elem` from a previously-cached index - - // ...in a gzip-friendly way - node = parent; - outerCache = node[ expando ] || (node[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex && cache[ 2 ]; - node = nodeIndex && parent.childNodes[ nodeIndex ]; - - while ( (node = ++nodeIndex && node && node[ dir ] || - - // Fallback to seeking `elem` from the start - (diff = nodeIndex = 0) || start.pop()) ) { - - // When found, cache indexes on `parent` and break - if ( node.nodeType === 1 && ++diff && node === elem ) { - uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; - break; - } - } - - } else { - // Use previously-cached element index if available - if ( useCache ) { - // ...in a gzip-friendly way - node = elem; - outerCache = node[ expando ] || (node[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex; - } - - // xml :nth-child(...) - // or :nth-last-child(...) or :nth(-last)?-of-type(...) - if ( diff === false ) { - // Use the same loop as above to seek `elem` from the start - while ( (node = ++nodeIndex && node && node[ dir ] || - (diff = nodeIndex = 0) || start.pop()) ) { - - if ( ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) && - ++diff ) { - - // Cache the index of each encountered element - if ( useCache ) { - outerCache = node[ expando ] || (node[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); - - uniqueCache[ type ] = [ dirruns, diff ]; - } - - if ( node === elem ) { - break; - } - } - } - } - } - - // Incorporate the offset, then check against cycle size - diff -= last; - return diff === first || ( diff % first === 0 && diff / first >= 0 ); - } - }; - }, - - "PSEUDO": function( pseudo, argument ) { - // pseudo-class names are case-insensitive - // http://www.w3.org/TR/selectors/#pseudo-classes - // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters - // Remember that setFilters inherits from pseudos - var args, - fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || - Sizzle.error( "unsupported pseudo: " + pseudo ); - - // The user may use createPseudo to indicate that - // arguments are needed to create the filter function - // just as Sizzle does - if ( fn[ expando ] ) { - return fn( argument ); - } - - // But maintain support for old signatures - if ( fn.length > 1 ) { - args = [ pseudo, pseudo, "", argument ]; - return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? - markFunction(function( seed, matches ) { - var idx, - matched = fn( seed, argument ), - i = matched.length; - while ( i-- ) { - idx = indexOf( seed, matched[i] ); - seed[ idx ] = !( matches[ idx ] = matched[i] ); - } - }) : - function( elem ) { - return fn( elem, 0, args ); - }; - } - - return fn; - } - }, - - pseudos: { - // Potentially complex pseudos - "not": markFunction(function( selector ) { - // Trim the selector passed to compile - // to avoid treating leading and trailing - // spaces as combinators - var input = [], - results = [], - matcher = compile( selector.replace( rtrim, "$1" ) ); - - return matcher[ expando ] ? - markFunction(function( seed, matches, context, xml ) { - var elem, - unmatched = matcher( seed, null, xml, [] ), - i = seed.length; - - // Match elements unmatched by `matcher` - while ( i-- ) { - if ( (elem = unmatched[i]) ) { - seed[i] = !(matches[i] = elem); - } - } - }) : - function( elem, context, xml ) { - input[0] = elem; - matcher( input, null, xml, results ); - // Don't keep the element (issue #299) - input[0] = null; - return !results.pop(); - }; - }), - - "has": markFunction(function( selector ) { - if (typeof selector === "string") { - Sizzle.compile(selector); - } - return function( elem ) { - return Sizzle( selector, elem ).length > 0; - }; - }), - - // Removed :contains pseudo-class declaration - - // "Whether an element is represented by a :lang() selector - // is based solely on the element's language value - // being equal to the identifier C, - // or beginning with the identifier C immediately followed by "-". - // The matching of C against the element's language value is performed case-insensitively. - // The identifier C does not have to be a valid language name." - // http://www.w3.org/TR/selectors/#lang-pseudo - "lang": markFunction( function( lang ) { - // lang value must be a valid identifier - if ( !ridentifier.test(lang || "") ) { - Sizzle.error( "unsupported lang: " + lang ); - } - lang = lang.replace( runescape, funescape ).toLowerCase(); - return function( elem ) { - var elemLang; - do { - if ( (elemLang = documentIsHTML ? - elem.lang : - elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { - - elemLang = elemLang.toLowerCase(); - return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; - } - } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); - return false; - }; - }), - - // Miscellaneous - "target": function( elem ) { - var hash = window.location && window.location.hash; - return hash && hash.slice( 1 ) === elem.id; - }, - - "root": function( elem ) { - return elem === docElem; - }, - - "focus": function( elem ) { - return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); - }, - - // Boolean properties - "enabled": createDisabledPseudo( false ), - "disabled": createDisabledPseudo( true ), - - "checked": function( elem ) { - // In CSS3, :checked should return both checked and selected elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - var nodeName = elem.nodeName.toLowerCase(); - return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); - }, - - "selected": function( elem ) { - // Accessing this property makes selected-by-default - // options in Safari work properly - if ( elem.parentNode ) { - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - // Contents - "empty": function( elem ) { - // http://www.w3.org/TR/selectors/#empty-pseudo - // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), - // but not by others (comment: 8; processing instruction: 7; etc.) - // nodeType < 6 works because attributes (2) do not appear as children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - if ( elem.nodeType < 6 ) { - return false; - } - } - return true; - }, - - // Removed custom pseudo-classes - } -}; - -// Removed custom pseudo-classes - -// Easy API for creating new setFilters -function setFilters() {} -setFilters.prototype = Expr.filters = Expr.pseudos; -Expr.setFilters = new setFilters(); - -/** - * [AdGuard Patch]: - * Sorts the tokens in order to mitigate the performance issues caused by matching slow pseudos first: - * https://github.com/AdguardTeam/ExtendedCss/issues/55#issuecomment-364058745 - */ -var sortTokenGroups = (function() { - - /** - * Splits compound selector into a list of simple selectors - * - * @param {*} tokens Tokens to split into groups - * @returns an array consisting of token groups (arrays) and relation tokens. - */ - var splitCompoundSelector = function(tokens) { - - var groups = []; - var currentTokensGroup = []; - var maxIdx = tokens.length - 1; - for (var i = 0; i <= maxIdx; i++) { - - var token = tokens[i]; - var relative = Sizzle.selectors.relative[token.type]; - if (relative) { - groups.push(currentTokensGroup); - groups.push(token); - currentTokensGroup = []; - } else { - currentTokensGroup.push(token); - } - - if (i === maxIdx) { - groups.push(currentTokensGroup); - } - } - - return groups; - }; - - var TOKEN_TYPES_VALUES = { - // nth-child, etc, always go last - "CHILD": 100, - "ID": 90, - "CLASS": 80, - "TAG": 70, - "ATTR": 70, - "PSEUDO": 60 - }; - - var POSITIONAL_PSEUDOS = [ - "nth", "first", "last", "eq", "even", "odd", "lt", "gt", "not" - ]; - - /** - * A function that defines the sort order. - * Returns a value lesser than 0 if "left" is less than "right". - */ - var compareFunction = function(left, right) { - var leftValue = TOKEN_TYPES_VALUES[left.type]; - var rightValue = TOKEN_TYPES_VALUES[right.type]; - return leftValue - rightValue; - }; - - /** - * Checks if the specified tokens group is sortable. - * We do not re-sort tokens in case of any positional or child pseudos in the group - */ - var isSortable = function(tokens) { - - var iTokens = tokens.length; - while (iTokens--) { - var token = tokens[iTokens]; - if (token.type === "PSEUDO" && POSITIONAL_PSEUDOS.indexOf(token.matches[0]) !== -1) { - return false; - } - if (token.type === "CHILD") { - return false; - } - } - return true; - }; - - /** - * Sorts the tokens in order to mitigate the issues caused by the left-to-right matching. - * The idea is change the tokens order so that Sizzle was matching fast selectors first (id, class), - * and slow selectors after that (and here I mean our slow custom pseudo classes). - * - * @param {Array} tokens An array of tokens to sort - * @returns {Array} A new re-sorted array - */ - var sortTokens = function(tokens) { - - if (!tokens || tokens.length === 1) { - return tokens; - } - - var sortedTokens = []; - var groups = splitCompoundSelector(tokens); - for (var i = 0; i < groups.length; i++) { - var group = groups[i]; - if (group instanceof Array) { - if (isSortable(group)) { - group.sort(compareFunction); - } - sortedTokens = sortedTokens.concat(group); - } else { - sortedTokens.push(group); - } - } - - return sortedTokens; - }; - - /** - * Sorts every tokens array inside of the specified "groups" array. - * See "sortTokens" methods for more information on how tokens are sorted. - * - * @param {Array} groups An array of tokens arrays. - * @returns {Array} A new array that consists of the same tokens arrays after sorting - */ - var sortTokenGroups = function(groups) { - var sortedGroups = []; - var len = groups.length; - var i = 0; - for (; i < len; i++) { - sortedGroups.push(sortTokens(groups[i])); - } - return sortedGroups; - }; - - // Expose - return sortTokenGroups; -})(); - -/** - * Creates custom policy to use TrustedTypes CSP policy - * https://w3c.github.io/webappsec-trusted-types/dist/spec/ - */ -var AGPolicy = (function createPolicy() { - var defaultPolicy = { - createHTML: function (input) { - return input; - }, - createScript: function (input) { - return input; - }, - createScriptURL: function (input) { - return input; - } - }; - - if (window.trustedTypes && window.trustedTypes.createPolicy) { - return window.trustedTypes.createPolicy("AGPolicy", defaultPolicy); - } - - return defaultPolicy; -})(); - -/** - * [AdGuard Patch]: - * Removes trailing spaces from the tokens list - * - * @param {*} tokens An array of Sizzle tokens to post-process - */ -function removeTrailingSpaces(tokens) { - var iTokens = tokens.length; - while (iTokens--) { - var token = tokens[iTokens]; - if (token.type === " ") { - tokens.length = iTokens; - } else { - break; - } - } -} - -/** - * [AdGuard Patch]: - * An object with the information about selectors and their token representation - * @typedef {{selectorText: string, groups: Array}} SelectorData - * @property {string} selectorText A CSS selector text - * @property {Array} groups An array of token groups corresponding to that selector - */ - -/** - * [AdGuard Patch]: - * This method processes parsed token groups, divides them into a number of selectors - * and makes sure that each selector's tokens are cached properly in Sizzle. - * - * @param {*} groups Token groups (see {@link Sizzle.tokenize}) - * @returns {Array.} An array of selectors data we got from the groups - */ -function tokenGroupsToSelectors(groups) { - - // Remove trailing spaces which we can encounter in tolerant mode - // We're doing it in tolerant mode only as this is the only case when - // encountering trailing spaces is expected - removeTrailingSpaces(groups[groups.length - 1]); - - // We need sorted tokens to make cache work properly - var sortedGroups = sortTokenGroups(groups); - - var selectors = []; - for (var i = 0; i < groups.length; i++) { - var tokenGroups = groups[i]; - var selectorText = toSelector(tokenGroups); - - selectors.push({ - // Sizzle expects an array of token groups when compiling a selector - groups: [ tokenGroups ], - selectorText: selectorText - }); - - // Now make sure that selector tokens are cached - var tokensCacheItem = { - groups: tokenGroups, - sortedGroups: [ sortedGroups[i] ] - }; - tokenCache(selectorText, tokensCacheItem); - } - - return selectors; -} - -/** - * [AdGuard Patch]: - * Add an additional argument for Sizzle.tokenize which indicates that it - * should not throw on invalid tokens, and instead should return tokens - * that it has produced so far. - * - * One more additional argument that allow to choose if you want to receive sorted or unsorted tokens - * The problem is that the re-sorted selectors are valid for Sizzle, but not for the browser. - * options.returnUnsorted -- return unsorted tokens if true. - * options.cacheOnly -- return cached result only. Required for unit-tests. - * - * @param {*} options Optional configuration object with two additional flags - * (options.tolerant, options.returnUnsorted, options.cacheOnly) -- see patches #5 and #6 notes - */ -tokenize = Sizzle.tokenize = function( selector, parseOnly, options) { - var matched, match, tokens, type, - soFar, groups, preFilters, - cached = tokenCache[ selector + " " ]; - - var tolerant = options && options.tolerant; - var returnUnsorted = options && options.returnUnsorted; - var cacheOnly = options && options.cacheOnly; - - if (cached) { - if (parseOnly) { - return 0; - } else { - return (returnUnsorted ? cached.groups : cached.sortedGroups).slice(0); - } - } - - if (cacheOnly) { - return null; - } - - soFar = selector; - groups = []; - preFilters = Expr.preFilter; - - while ( soFar ) { - - // Comma and first run - if ( !matched || (match = rcomma.exec( soFar )) ) { - if ( match ) { - // Don't consume trailing commas as valid - soFar = soFar.slice( match[0].length ) || soFar; - } - groups.push( (tokens = []) ); - } - - matched = false; - - // Combinators - if ( (match = rcombinators.exec( soFar )) ) { - matched = match.shift(); - tokens.push({ - value: matched, - // Cast descendant combinators to space - type: match[0].replace( rtrim, " " ) - }); - soFar = soFar.slice( matched.length ); - } - - // Filters - for ( type in Expr.filter ) { - if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || - (match = preFilters[ type ]( match ))) ) { - matched = match.shift(); - tokens.push({ - value: matched, - type: type, - matches: match - }); - soFar = soFar.slice( matched.length ); - } - } - - if ( !matched ) { - break; - } - } - - // Return the length of the invalid excess - // if we're just parsing - // Otherwise, throw an error or return tokens - var invalidLen = soFar.length; - if (parseOnly) { - return invalidLen; - } - - if (invalidLen !== 0 && !tolerant) { - Sizzle.error( selector ); // Throws an error. - } - - if (tolerant) { - /** - * [AdGuard Patch]: - * In tolerant mode we return a special object that constists of - * an array of parsed selectors (and their tokens) and a "nextIndex" field - * that points to an index after which we're not able to parse selectors farther. - */ - var nextIndex = selector.length - invalidLen; - var selectors = tokenGroupsToSelectors(groups); - return { - selectors: selectors, - nextIndex: nextIndex - }; - } - - /** [AdGuard Patch]: Sorting tokens */ - var sortedGroups = sortTokenGroups(groups); - - /** [AdGuard Patch]: Change the way tokens are cached */ - var tokensCacheItem = { - groups: groups, - sortedGroups: sortedGroups - }; - tokensCacheItem = tokenCache(selector, tokensCacheItem); - return (returnUnsorted ? tokensCacheItem.groups : tokensCacheItem.sortedGroups).slice(0); -}; - -function toSelector( tokens ) { - var i = 0, - len = tokens.length, - selector = ""; - for ( ; i < len; i++ ) { - selector += tokens[i].value; - } - return selector; -} - -function addCombinator( matcher, combinator, base ) { - var dir = combinator.dir, - skip = combinator.next, - key = skip || dir, - checkNonElements = base && key === "parentNode", - doneName = done++; - - return combinator.first ? - // Check against closest ancestor/preceding element - function( elem, context, xml ) { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - return matcher( elem, context, xml ); - } - } - return false; - } : - - // Check against all ancestor/preceding elements - function( elem, context, xml ) { - var oldCache, uniqueCache, outerCache, - newCache = [ dirruns, doneName ]; - - // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching - if ( xml ) { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - if ( matcher( elem, context, xml ) ) { - return true; - } - } - } - } else { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - outerCache = elem[ expando ] || (elem[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {}); - - if ( skip && skip === elem.nodeName.toLowerCase() ) { - elem = elem[ dir ] || elem; - } else if ( (oldCache = uniqueCache[ key ]) && - oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { - - // Assign to newCache so results back-propagate to previous elements - return (newCache[ 2 ] = oldCache[ 2 ]); - } else { - // Reuse newcache so results back-propagate to previous elements - uniqueCache[ key ] = newCache; - - // A match means we're done; a fail means we have to keep checking - if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { - return true; - } - } - } - } - } - return false; - }; -} - -function elementMatcher( matchers ) { - return matchers.length > 1 ? - function( elem, context, xml ) { - var i = matchers.length; - while ( i-- ) { - if ( !matchers[i]( elem, context, xml ) ) { - return false; - } - } - return true; - } : - matchers[0]; -} - -function multipleContexts( selector, contexts, results ) { - var i = 0, - len = contexts.length; - for ( ; i < len; i++ ) { - Sizzle( selector, contexts[i], results ); - } - return results; -} - -function condense( unmatched, map, filter, context, xml ) { - var elem, - newUnmatched = [], - i = 0, - len = unmatched.length, - mapped = map != null; - - for ( ; i < len; i++ ) { - if ( (elem = unmatched[i]) ) { - if ( !filter || filter( elem, context, xml ) ) { - newUnmatched.push( elem ); - if ( mapped ) { - map.push( i ); - } - } - } - } - - return newUnmatched; -} - -function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { - if ( postFilter && !postFilter[ expando ] ) { - postFilter = setMatcher( postFilter ); - } - if ( postFinder && !postFinder[ expando ] ) { - postFinder = setMatcher( postFinder, postSelector ); - } - return markFunction(function( seed, results, context, xml ) { - var temp, i, elem, - preMap = [], - postMap = [], - preexisting = results.length, - - // Get initial elements from seed or context - elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), - - // Prefilter to get matcher input, preserving a map for seed-results synchronization - matcherIn = preFilter && ( seed || !selector ) ? - condense( elems, preMap, preFilter, context, xml ) : - elems, - - matcherOut = matcher ? - // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, - postFinder || ( seed ? preFilter : preexisting || postFilter ) ? - - // ...intermediate processing is necessary - [] : - - // ...otherwise use results directly - results : - matcherIn; - - // Find primary matches - if ( matcher ) { - matcher( matcherIn, matcherOut, context, xml ); - } - - // Apply postFilter - if ( postFilter ) { - temp = condense( matcherOut, postMap ); - postFilter( temp, [], context, xml ); - - // Un-match failing elements by moving them back to matcherIn - i = temp.length; - while ( i-- ) { - if ( (elem = temp[i]) ) { - matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); - } - } - } - - if ( seed ) { - if ( postFinder || preFilter ) { - if ( postFinder ) { - // Get the final matcherOut by condensing this intermediate into postFinder contexts - temp = []; - i = matcherOut.length; - while ( i-- ) { - if ( (elem = matcherOut[i]) ) { - // Restore matcherIn since elem is not yet a final match - temp.push( (matcherIn[i] = elem) ); - } - } - postFinder( null, (matcherOut = []), temp, xml ); - } - - // Move matched elements from seed to results to keep them synchronized - i = matcherOut.length; - while ( i-- ) { - if ( (elem = matcherOut[i]) && - (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { - - seed[temp] = !(results[temp] = elem); - } - } - } - - // Add elements to results, through postFinder if defined - } else { - matcherOut = condense( - matcherOut === results ? - matcherOut.splice( preexisting, matcherOut.length ) : - matcherOut - ); - if ( postFinder ) { - postFinder( null, results, matcherOut, xml ); - } else { - push.apply( results, matcherOut ); - } - } - }); -} - -function matcherFromTokens( tokens ) { - var checkContext, matcher, j, - len = tokens.length, - leadingRelative = Expr.relative[ tokens[0].type ], - implicitRelative = leadingRelative || Expr.relative[" "], - i = leadingRelative ? 1 : 0, - - // The foundational matcher ensures that elements are reachable from top-level context(s) - matchContext = addCombinator( function( elem ) { - return elem === checkContext; - }, implicitRelative, true ), - matchAnyContext = addCombinator( function( elem ) { - return indexOf( checkContext, elem ) > -1; - }, implicitRelative, true ), - matchers = [ function( elem, context, xml ) { - var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( - (checkContext = context).nodeType ? - matchContext( elem, context, xml ) : - matchAnyContext( elem, context, xml ) ); - // Avoid hanging onto element (issue #299) - checkContext = null; - return ret; - } ]; - - for ( ; i < len; i++ ) { - if ( (matcher = Expr.relative[ tokens[i].type ]) ) { - matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; - } else { - matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); - - // Return special upon seeing a positional matcher - if ( matcher[ expando ] ) { - // Find the next relative operator (if any) for proper handling - j = ++i; - for ( ; j < len; j++ ) { - if ( Expr.relative[ tokens[j].type ] ) { - break; - } - } - return setMatcher( - i > 1 && elementMatcher( matchers ), - i > 1 && toSelector( - // If the preceding token was a descendant combinator, insert an implicit any-element `*` - tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) - ).replace( rtrim, "$1" ), - matcher, - i < j && matcherFromTokens( tokens.slice( i, j ) ), - j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), - j < len && toSelector( tokens ) - ); - } - matchers.push( matcher ); - } - } - - return elementMatcher( matchers ); -} - -function matcherFromGroupMatchers( elementMatchers, setMatchers ) { - var bySet = setMatchers.length > 0, - byElement = elementMatchers.length > 0, - superMatcher = function( seed, context, xml, results, outermost ) { - var elem, j, matcher, - matchedCount = 0, - i = "0", - unmatched = seed && [], - setMatched = [], - contextBackup = outermostContext, - // We must always have either seed elements or outermost context - elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), - // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), - len = elems.length; - - if ( outermost ) { - outermostContext = context === document || context || outermost; - } - - // Add elements passing elementMatchers directly to results - // Support: IE<9, Safari - // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id - for ( ; i !== len && (elem = elems[i]) != null; i++ ) { - if ( byElement && elem ) { - j = 0; - if ( !context && elem.ownerDocument !== document ) { - setDocument( elem ); - xml = !documentIsHTML; - } - while ( (matcher = elementMatchers[j++]) ) { - if ( matcher( elem, context || document, xml) ) { - results.push( elem ); - break; - } - } - if ( outermost ) { - dirruns = dirrunsUnique; - } - } - - // Track unmatched elements for set filters - if ( bySet ) { - // They will have gone through all possible matchers - if ( (elem = !matcher && elem) ) { - matchedCount--; - } - - // Lengthen the array for every element, matched or not - if ( seed ) { - unmatched.push( elem ); - } - } - } - - // `i` is now the count of elements visited above, and adding it to `matchedCount` - // makes the latter nonnegative. - matchedCount += i; - - // Apply set filters to unmatched elements - // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` - // equals `i`), unless we didn't visit _any_ elements in the above loop because we have - // no element matchers and no seed. - // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that - // case, which will result in a "00" `matchedCount` that differs from `i` but is also - // numerically zero. - if ( bySet && i !== matchedCount ) { - j = 0; - while ( (matcher = setMatchers[j++]) ) { - matcher( unmatched, setMatched, context, xml ); - } - - if ( seed ) { - // Reintegrate element matches to eliminate the need for sorting - if ( matchedCount > 0 ) { - while ( i-- ) { - if ( !(unmatched[i] || setMatched[i]) ) { - setMatched[i] = pop.call( results ); - } - } - } - - // Discard index placeholder values to get only actual matches - setMatched = condense( setMatched ); - } - - // Add matches to results - push.apply( results, setMatched ); - - // Seedless set matches succeeding multiple successful matchers stipulate sorting - if ( outermost && !seed && setMatched.length > 0 && - ( matchedCount + setMatchers.length ) > 1 ) { - - Sizzle.uniqueSort( results ); - } - } - - // Override manipulation of globals by nested matchers - if ( outermost ) { - dirruns = dirrunsUnique; - outermostContext = contextBackup; - } - - return unmatched; - }; - - return bySet ? - markFunction( superMatcher ) : - superMatcher; -} - -compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { - var i, - setMatchers = [], - elementMatchers = [], - cached = compilerCache[ selector + " " ]; - - if ( !cached ) { - // Generate a function of recursive functions that can be used to check each element - if ( !match ) { - match = tokenize( selector ); - } - i = match.length; - while ( i-- ) { - cached = matcherFromTokens( match[i] ); - if ( cached[ expando ] ) { - setMatchers.push( cached ); - } else { - elementMatchers.push( cached ); - } - } - - // Cache the compiled function - cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); - - // Save selector and tokenization - cached.selector = selector; - } - return cached; -}; - -/** - * A low-level selection function that works with Sizzle's compiled - * selector functions - * @param {String|Function} selector A selector or a pre-compiled - * selector function built with Sizzle.compile - * @param {Element} context - * @param {Array} [results] - * @param {Array} [seed] A set of elements to match against - */ -select = Sizzle.select = function( selector, context, results, seed ) { - var i, tokens, token, type, find, - compiled = typeof selector === "function" && selector, - match = !seed && tokenize( (selector = compiled.selector || selector) ); - - results = results || []; - - // Try to minimize operations if there is only one selector in the list and no seed - // (the latter of which guarantees us context) - if ( match.length === 1 ) { - - // Reduce context if the leading compound selector is an ID - tokens = match[0] = match[0].slice( 0 ); - if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && - context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[1].type ] ) { - - context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; - if ( !context ) { - return results; - - // Precompiled matchers will still verify ancestry, so step up a level - } else if ( compiled ) { - context = context.parentNode; - } - - selector = selector.slice( tokens.shift().value.length ); - } - - // Fetch a seed set for right-to-left matching - i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; - while ( i-- ) { - token = tokens[i]; - - // Abort if we hit a combinator - if ( Expr.relative[ (type = token.type) ] ) { - break; - } - if ( (find = Expr.find[ type ]) ) { - // Search, expanding context for leading sibling combinators - if ( (seed = find( - token.matches[0].replace( runescape, funescape ), - rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context - )) ) { - - // If seed is empty or no tokens remain, we can return early - tokens.splice( i, 1 ); - selector = seed.length && toSelector( tokens ); - if ( !selector ) { - push.apply( results, seed ); - return results; - } - - break; - } - } - } - } - - // Compile and execute a filtering function if one is not provided - // Provide `match` to avoid retokenization if we modified the selector above - ( compiled || compile( selector, match ) )( - seed, - context, - !documentIsHTML, - results, - !context || rsibling.test( selector ) && testContext( context.parentNode ) || context - ); - return results; -}; - -// One-time assignments - -// Sort stability -support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; - -// Support: Chrome 14-35+ -// Always assume duplicates if they aren't passed to the comparison function -support.detectDuplicates = !!hasDuplicate; - -// Initialize against the default document -setDocument(); - -// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) -// Detached nodes confoundingly follow *each other* -support.sortDetached = assert(function( el ) { - // Should return 1, but returns 4 (following) - return el.compareDocumentPosition( document.createElement("fieldset") ) & 1; -}); - -// Support: IE<8 -// Prevent attribute/property "interpolation" -// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx -if ( !assert(function( el ) { - el.innerHTML = AGPolicy.createHTML(""); - return el.firstChild.getAttribute("href") === "#" ; -}) ) { - addHandle( "type|href|height|width", function( elem, name, isXML ) { - if ( !isXML ) { - return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); - } - }); -} - -// Support: IE<9 -// Use defaultValue in place of getAttribute("value") -if ( !support.attributes || !assert(function( el ) { - el.innerHTML = AGPolicy.createHTML(""); - el.firstChild.setAttribute( "value", "" ); - return el.firstChild.getAttribute( "value" ) === ""; -}) ) { - addHandle( "value", function( elem, name, isXML ) { - if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { - return elem.defaultValue; - } - }); -} - -// Support: IE<9 -// Use getAttributeNode to fetch booleans when getAttribute lies -if ( !assert(function( el ) { - return el.getAttribute("disabled") == null; -}) ) { - addHandle( booleans, function( elem, name, isXML ) { - var val; - if ( !isXML ) { - return elem[ name ] === true ? name.toLowerCase() : - (val = elem.getAttributeNode( name )) && val.specified ? - val.value : - null; - } - }); -} - -// EXPOSE - -// Do not expose Sizzle to the global scope in the case of AdGuard ExtendedCss build - - return Sizzle; - -// EXPOSE - -})( window ); - -//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - -} - -return Sizzle; -}; - -/* jshint ignore:start */ -// jscs:disable -export default initializeSizzle; -// jscs:enable -/* jshint ignore:end */ diff --git a/lib/style-property-matcher.js b/lib/style-property-matcher.js deleted file mode 100644 index 16c76fcb..00000000 --- a/lib/style-property-matcher.js +++ /dev/null @@ -1,170 +0,0 @@ -/** - * Copyright 2016 Adguard Software Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import utils from './utils'; -/** - * Class that extends Sizzle and adds support for "matches-css" pseudo element. - */ -const StylePropertyMatcher = (function (window) { - const isPhantom = !!window._phantom; - const useFallback = isPhantom && !!window.getMatchedCSSRules; - - /** - * Unquotes specified value - * Webkit-based browsers singlequotes content property values - * Other browsers doublequotes content property values. - */ - const removeContentQuotes = function (value) { - if (typeof value === 'string') { - return value.replace(/^(["'])([\s\S]*)\1$/, '$2'); - } - return value; - }; - - const getComputedStyle = window.getComputedStyle.bind(window); - const getMatchedCSSRules = useFallback ? window.getMatchedCSSRules.bind(window) : null; - - /** - * There is an issue in browsers based on old webkit: - * getComputedStyle(el, ":before") is empty if element is not visible. - * - * To circumvent this issue we use getMatchedCSSRules instead. - * - * It appears that getMatchedCSSRules sorts the CSS rules - * in increasing order of specifities of corresponding selectors. - * We pick the css rule that is being applied to an element based on this assumption. - * - * @param element DOM node - * @param pseudoElement Optional pseudoElement name - * @param propertyName CSS property name - */ - const getComputedStylePropertyValue = function (element, pseudoElement, propertyName) { - let value = ''; - if (useFallback && pseudoElement) { - const cssRules = getMatchedCSSRules(element, pseudoElement) || []; - let i = cssRules.length; - while (i-- > 0 && !value) { value = cssRules[i].style.getPropertyValue(propertyName); } - } else { - const style = getComputedStyle(element, pseudoElement); - if (style) { - value = style.getPropertyValue(propertyName); - // https://bugs.webkit.org/show_bug.cgi?id=93445 - if (propertyName === 'opacity' && utils.isSafariBrowser) { - value = (Math.round(parseFloat(value) * 100) / 100).toString(); - } - } - } - - if (propertyName === 'content') { - value = removeContentQuotes(value); - } - - return value; - }; - - /** - * Adds url parameter quotes for non-regex pattern - * @param {string} pattern - */ - const addUrlQuotes = function (pattern) { - // for regex patterns - if (pattern[0] === '/' - && pattern[pattern.length - 1] === '/' - && pattern.indexOf('\\"') < 10) { - // e.g. /^url\\([a-z]{4}:[a-z]{5}/ - // or /^url\\(data\\:\\image\\/gif;base64.+/ - const re = /(\^)?url(\\)?\\\((\w|\[\w)/g; - return pattern.replace(re, '$1url$2\\\(\\"?$3'); - } - // for non-regex patterns - if (pattern.indexOf('url("') === -1) { - const re = /url\((.*?)\)/g; - return pattern.replace(re, 'url("$1")'); - } - return pattern; - }; - - /** - * Class that matches element style against the specified expression - * @member {string} propertyName - * @member {string} pseudoElement - * @member {RegExp} regex - */ - const Matcher = function (propertyFilter, pseudoElement) { - this.pseudoElement = pseudoElement; - try { - const index = propertyFilter.indexOf(':'); - this.propertyName = propertyFilter.substring(0, index).trim(); - let pattern = propertyFilter.substring(index + 1).trim(); - pattern = addUrlQuotes(pattern); - - // Unescaping pattern - // For non-regex patterns, (,),[,] should be unescaped, because we require escaping them in filter rules. - // For regex patterns, ",\ should be escaped, because we manually escape those in extended-css-selector.js. - if (/^\/.*\/$/.test(pattern)) { - pattern = pattern.slice(1, -1); - this.regex = utils.pseudoArgToRegex(pattern); - } else { - pattern = pattern.replace(/\\([\\()[\]"])/g, '$1'); - this.regex = utils.createURLRegex(pattern); - } - } catch (ex) { - utils.logError(`StylePropertyMatcher: invalid match string ${propertyFilter}`); - } - }; - - /** - * Function to check if element CSS property matches filter pattern - * @param {Element} element to check - */ - Matcher.prototype.matches = function (element) { - if (!this.regex || !this.propertyName) { return false; } - const value = getComputedStylePropertyValue(element, this.pseudoElement, this.propertyName); - return value && this.regex.test(value); - }; - - /** - * Creates a new pseudo-class and registers it in Sizzle - */ - const extendSizzle = function (sizzle) { - // First of all we should prepare Sizzle engine - sizzle.selectors.pseudos['matches-css'] = sizzle.selectors.createPseudo((propertyFilter) => { - const matcher = new Matcher(propertyFilter); - return function (element) { - return matcher.matches(element); - }; - }); - sizzle.selectors.pseudos['matches-css-before'] = sizzle.selectors.createPseudo((propertyFilter) => { - const matcher = new Matcher(propertyFilter, ':before'); - return function (element) { - return matcher.matches(element); - }; - }); - sizzle.selectors.pseudos['matches-css-after'] = sizzle.selectors.createPseudo((propertyFilter) => { - const matcher = new Matcher(propertyFilter, ':after'); - return function (element) { - return matcher.matches(element); - }; - }); - }; - - // EXPOSE - return { - extendSizzle, - }; -})(window); - -export default StylePropertyMatcher; diff --git a/lib/utils.js b/lib/utils.js deleted file mode 100644 index ea1978cf..00000000 --- a/lib/utils.js +++ /dev/null @@ -1,509 +0,0 @@ -/** - * Copyright 2016 Adguard Software Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* eslint-disable no-console */ - -const utils = {}; -utils.MutationObserver = window.MutationObserver || window.WebKitMutationObserver; - -/** - * Stores native Node textContent getter to be used for contains pseudo-class - * because elements' 'textContent' and 'innerText' properties might be mocked - * https://github.com/AdguardTeam/ExtendedCss/issues/127 - */ -utils.nodeTextContentGetter = (function () { - const nativeNode = window.Node || Node; - return Object.getOwnPropertyDescriptor(nativeNode.prototype, 'textContent').get; -})(); - -utils.isSafariBrowser = (function () { - return navigator.vendor === 'Apple Computer, Inc.'; -})(); - -/** - * Converts regular expressions passed as pseudo class arguments into RegExp instances. - * Have to unescape doublequote " as well, because we escape them while enclosing such - * arguments with doublequotes, and sizzle does not automatically unescapes them. - */ -utils.pseudoArgToRegex = function (regexSrc, flag) { - flag = flag || 'i'; - regexSrc = regexSrc.trim().replace(/\\(["\\])/g, '$1'); - return new RegExp(regexSrc, flag); -}; - -/** - * Converts string to the regexp - * @param {string} str - * @returns {RegExp} - */ -utils.toRegExp = (str) => { - if (str[0] === '/' && str[str.length - 1] === '/') { - return new RegExp(str.slice(1, -1)); - } - const escaped = str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - return new RegExp(escaped); -}; - -utils.startsWith = function (str, prefix) { - // if str === '', (str && false) will return '' - // that's why it has to be !!str - return !!str && str.indexOf(prefix) === 0; -}; - -utils.endsWith = function (str, postfix) { - if (!str || !postfix) { - return false; - } - - if (str.endsWith) { - return str.endsWith(postfix); - } - const t = String(postfix); - const index = str.lastIndexOf(t); - return index >= 0 && index === str.length - t.length; -}; - -/** - * Helper function for creating regular expression from a url filter rule syntax. - */ -utils.createURLRegex = (function () { - // Constants - const regexConfiguration = { - maskStartUrl: '||', - maskPipe: '|', - maskSeparator: '^', - maskAnySymbol: '*', - - regexAnySymbol: '.*', - regexSeparator: '([^ a-zA-Z0-9.%_-]|$)', - regexStartUrl: '^(http|https|ws|wss)://([a-z0-9-_.]+\\.)?', - regexStartString: '^', - regexEndString: '$', - }; - - // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/regexp - // should be escaped . * + ? ^ $ { } ( ) | [ ] / \ - // except of * | ^ - const specials = [ - '.', '+', '?', '$', '{', '}', '(', ')', '[', ']', '\\', '/', - ]; - const specialsRegex = new RegExp(`[${specials.join('\\')}]`, 'g'); - - /** - * Escapes regular expression string - */ - const escapeRegExp = function (str) { - return str.replace(specialsRegex, '\\$&'); - }; - - const replaceAll = function (str, find, replace) { - if (!str) { - return str; - } - return str.split(find).join(replace); - }; - - /** - * Main function that converts a url filter rule string to a regex. - * @param {string} str - * @return {RegExp} - */ - const createRegexText = function (str) { - let regex = escapeRegExp(str); - - if (utils.startsWith(regex, regexConfiguration.maskStartUrl)) { - regex = regex.substring(0, regexConfiguration.maskStartUrl.length) - + replaceAll(regex.substring(regexConfiguration.maskStartUrl.length, regex.length - 1), '\|', '\\|') - + regex.substring(regex.length - 1); - } else if (utils.startsWith(regex, regexConfiguration.maskPipe)) { - regex = regex.substring(0, regexConfiguration.maskPipe.length) - + replaceAll(regex.substring(regexConfiguration.maskPipe.length, regex.length - 1), '\|', '\\|') - + regex.substring(regex.length - 1); - } else { - regex = replaceAll(regex.substring(0, regex.length - 1), '\|', '\\|') - + regex.substring(regex.length - 1); - } - - // Replacing special url masks - regex = replaceAll(regex, regexConfiguration.maskAnySymbol, regexConfiguration.regexAnySymbol); - regex = replaceAll(regex, regexConfiguration.maskSeparator, regexConfiguration.regexSeparator); - - if (utils.startsWith(regex, regexConfiguration.maskStartUrl)) { - regex = regexConfiguration.regexStartUrl + regex.substring(regexConfiguration.maskStartUrl.length); - } else if (utils.startsWith(regex, regexConfiguration.maskPipe)) { - regex = regexConfiguration.regexStartString + regex.substring(regexConfiguration.maskPipe.length); - } - if (utils.endsWith(regex, regexConfiguration.maskPipe)) { - regex = regex.substring(0, regex.length - 1) + regexConfiguration.regexEndString; - } - - return new RegExp(regex, 'i'); - }; - - return createRegexText; -})(); - -/** - * Creates an object implementing Location interface from a url. - * An alternative to URL. - * https://github.com/AdguardTeam/FingerprintingBlocker/blob/master/src/shared/url.ts#L64 - */ -utils.createLocation = function (href) { - const anchor = document.createElement('a'); - anchor.href = href; - if (anchor.host === '') { - anchor.href = anchor.href; // eslint-disable-line no-self-assign - } - return anchor; -}; - -/** - * Checks whether A has the same origin as B. - * @param {string} urlA location.href of A. - * @param {Location} locationB location of B. - * @param {string} domainB document.domain of B. - * @return {boolean} - */ -utils.isSameOrigin = function (urlA, locationB, domainB) { - const locationA = utils.createLocation(urlA); - // eslint-disable-next-line no-script-url - if (locationA.protocol === 'javascript:' || locationA.href === 'about:blank') { - return true; - } - if (locationA.protocol === 'data:' || locationA.protocol === 'file:') { - return false; - } - return locationA.hostname === domainB && locationA.port === locationB.port && locationA.protocol === locationB.protocol; -}; - -/** - * A helper class to throttle function calls with setTimeout and requestAnimationFrame. - */ -utils.AsyncWrapper = (function () { - const supported = (typeof window.requestAnimationFrame !== 'undefined'); - const rAF = supported ? requestAnimationFrame : setTimeout; - const cAF = supported ? cancelAnimationFrame : clearTimeout; - const perf = supported ? performance : Date; - - /** - * @param {Function} callback - * @param {number} throttle number, the provided callback should be executed twice - * in this time frame. - * @constructor - */ - function AsyncWrapper(callback, throttle) { - this.callback = callback; - this.throttle = throttle; - this.wrappedCallback = this.wrappedCallback.bind(this); - if (this.wrappedAsapCallback) { - this.wrappedAsapCallback = this.wrappedAsapCallback.bind(this); - } - } - /** @private */ - AsyncWrapper.prototype.wrappedCallback = function (ts) { - this.lastRun = isNumber(ts) ? ts : perf.now(); - delete this.rAFid; - delete this.timerId; - delete this.asapScheduled; - this.callback(); - }; - /** @private Indicates whether there is a scheduled callback. */ - AsyncWrapper.prototype.hasPendingCallback = function () { - return isNumber(this.rAFid) || isNumber(this.timerId); - }; - /** - * Schedules a function call before the next animation frame. - */ - AsyncWrapper.prototype.run = function () { - if (this.hasPendingCallback()) { - // There is a pending execution scheduled. - return; - } - if (typeof this.lastRun !== 'undefined') { - const elapsed = perf.now() - this.lastRun; - if (elapsed < this.throttle) { - this.timerId = setTimeout(this.wrappedCallback, this.throttle - elapsed); - return; - } - } - this.rAFid = rAF(this.wrappedCallback); - }; - /** - * Schedules a function call in the most immenent microtask. - * This cannot be canceled. - */ - AsyncWrapper.prototype.runAsap = function () { - if (this.asapScheduled) { return; } - this.asapScheduled = true; - - cAF(this.rAFid); - clearTimeout(this.timerId); - - if (utils.MutationObserver) { - /** - * Using MutationObservers to access microtask queue is a standard technique, - * used in ASAP library - * {@link https://github.com/kriskowal/asap/blob/master/browser-raw.js#L140} - */ - if (!this.mo) { - this.mo = new utils.MutationObserver(this.wrappedCallback); - this.node = document.createTextNode(1); - this.mo.observe(this.node, { characterData: true }); - } - this.node.nodeValue = -this.node.nodeValue; - } else { - setTimeout(this.wrappedCallback); - } - }; - /** - * Runs scheduled execution immediately, if there were any. - */ - AsyncWrapper.prototype.runImmediately = function () { - if (this.hasPendingCallback()) { - cAF(this.rAFid); - clearTimeout(this.timerId); - delete this.rAFid; - delete this.timerId; - this.wrappedCallback(); - } - }; - - AsyncWrapper.now = function () { - return perf.now(); - }; - - return AsyncWrapper; -})(); - -/** - * Stores native OdP to be used in WeakMap and Set polyfills. - */ -utils.defineProperty = Object.defineProperty; - -utils.WeakMap = typeof WeakMap !== 'undefined' ? WeakMap : (function () { - /** Originally based on {@link https://github.com/Polymer/WeakMap} */ - let counter = Date.now() % 1e9; - - const WeakMap = function () { - this.name = `__st${Math.random() * 1e9 >>> 0}${counter++}__`; - }; - - WeakMap.prototype = { - set(key, value) { - const entry = key[this.name]; - if (entry && entry[0] === key) { - entry[1] = value; - } else { - utils.defineProperty(key, this.name, { value: [key, value], writable: true }); - } - return this; - }, - get(key) { - const entry = key[this.name]; - return entry && entry[0] === key ? entry[1] : undefined; - }, - delete(key) { - const entry = key[this.name]; - if (!entry) { - return false; - } - const hasValue = entry[0] === key; - delete entry[0]; - delete entry[1]; - return hasValue; - }, - has(key) { - const entry = key[this.name]; - if (!entry) { - return false; - } - return entry[0] === key; - }, - }; - - return WeakMap; -})(); - -utils.Set = typeof Set !== 'undefined' ? Set : (function () { - let counter = Date.now() % 1e9; - /** - * A polyfill which covers only the basic usage. - * Only supports methods that are supported in IE11. - * {@link https://docs.microsoft.com/en-us/scripting/javascript/reference/set-object-javascript} - * Assumes that 'key's are all objects, not primitives such as a number. - * - * @param {Array} items Initial items in this set - */ - const Set = function (items) { - this.name = `__st${Math.random() * 1e9 >>> 0}${counter++}__`; - this.keys = []; - - if (items && items.length) { - let iItems = items.length; - while (iItems--) { - this.add(items[iItems]); - } - } - }; - - Set.prototype = { - add(key) { - if (!isNumber(key[this.name])) { - const index = this.keys.push(key) - 1; - utils.defineProperty(key, this.name, { value: index, writable: true }); - } - }, - delete(key) { - if (isNumber(key[this.name])) { - const index = key[this.name]; - delete this.keys[index]; - key[this.name] = undefined; - } - }, - has(key) { - return isNumber(key[this.name]); - }, - clear() { - this.keys.forEach(function (key) { - key[this.name] = undefined; - }); - this.keys.length = 0; - }, - forEach(cb) { - const that = this; - this.keys.forEach((value) => { - cb(value, value, that); - }); - }, - }; - - utils.defineProperty(Set.prototype, 'size', { - get() { - // Skips holes. - return this.keys.reduce((acc) => acc + 1, 0); - }, - }); - - return Set; -})(); - -/** - * Vendor-specific Element.prototype.matches - */ -utils.matchesPropertyName = (function () { - const props = ['matches', 'matchesSelector', 'mozMatchesSelector', - 'msMatchesSelector', 'oMatchesSelector', 'webkitMatchesSelector']; - - for (let i = 0; i < 6; i++) { - if (Element.prototype.hasOwnProperty(props[i])) { - return props[i]; - } - } -})(); - -/** - * Provides stats information - */ -utils.Stats = function () { - /** @member {Array} */ - this.array = []; - /** @member {number} */ - this.length = 0; - const zeroDescriptor = { - value: 0, - writable: true, - }; - /** @member {number} @private */ - Object.defineProperty(this, 'sum', zeroDescriptor); - /** @member {number} @private */ - Object.defineProperty(this, 'squaredSum', zeroDescriptor); -}; - -/** - * @param {number} dataPoint data point - */ -utils.Stats.prototype.push = function (dataPoint) { - this.array.push(dataPoint); - this.length++; - this.sum += dataPoint; - this.squaredSum += dataPoint * dataPoint; - /** @member {number} */ - this.mean = this.sum / this.length; - /** @member {number} */ - // eslint-disable-next-line no-restricted-properties - this.stddev = Math.sqrt((this.squaredSum / this.length) - Math.pow(this.mean, 2)); -}; - -/** Safe console.error version */ -utils.logError = ( - typeof console !== 'undefined' - && console.error - && Function.prototype.bind - && console.error.bind -) ? console.error.bind(window.console) : console.error; - -/** Safe console.info version */ -utils.logInfo = ( - typeof console !== 'undefined' - && console.info - && Function.prototype.bind - && console.info.bind -) ? console.info.bind(window.console) : console.info; - -function isNumber(obj) { - return typeof obj === 'number'; -} - -/** - * Returns path to element we will use as element identifier - * @param {Element} inputEl - * @returns {string} - path to the element - */ -utils.getNodeSelector = function (inputEl) { - if (!(inputEl instanceof Element)) { - throw new Error('Function received argument with wrong type'); - } - - let el = inputEl; - const path = []; - // we need to check '!!el' first because it is possible - // that some ancestor of the inputEl was removed before it - while (!!el && el.nodeType === Node.ELEMENT_NODE) { - let selector = el.nodeName.toLowerCase(); - if (el.id && typeof el.id === 'string') { - selector += `#${el.id}`; - path.unshift(selector); - break; - } else { - let sibling = el; - let nth = 1; - while (sibling.previousSibling) { - sibling = sibling.previousSibling; - if (sibling.nodeType === Node.ELEMENT_NODE && sibling.nodeName.toLowerCase() === selector) { - nth++; - } - } - if (nth !== 1) { - selector += `:nth-of-type(${nth})`; - } - } - path.unshift(selector); - el = el.parentNode; - } - return path.join(' > '); -}; - -export default utils; diff --git a/package.json b/package.json index 816b6452..8ac499c6 100644 --- a/package.json +++ b/package.json @@ -1,57 +1,85 @@ { - "name": "extended-css", - "version": "1.3.16", - "description": "Module for applying CSS styles with extended selection properties.", - "main": "dist/extended-css.cjs.js", + "name": "@adguard/extended-css", + "version": "2.0.0", + "description": "AdGuard's TypeScript library for non-standard element selecting and applying CSS styles with extended properties.", + "main": "dist/extended-css.umd.js", "module": "dist/extended-css.esm.js", - "types": "dist/extended-css.d.ts", - "directories": { - "test": "test" + "typings": "dist/types", + "files": [ + "dist" + ], + "exports": { + "./package.json": "./package.json", + ".": { + "default": "./dist/extended-css.umd.js" + } }, "engines": { - "node": ">=10" + "node": ">=14" }, "scripts": { - "test": "node tasks/tests.js", - "build": "node tasks/compile.js", - "browserstack": "node tasks/tests.js && node tasks/browserstack.js", + "build": "ts-node tools/build", + "test": "ts-node tools/test", + "prebuild": "rimraf dist && yarn build:types", + "build:types": "tsc --declaration --emitDeclarationOnly --outdir dist/types", "increment": "yarn version --patch --no-git-tag-version", - "lint": "eslint ." + "lint": "eslint . && tsc --noEmit", + "lint-staged": "lint-staged", + "prepare": "husky install" }, - "husky": { - "hooks": { - "pre-commit": "yarn lint; yarn test" - } + "lint-staged": { + "*.ts": [ + "eslint" + ] }, "author": "AdGuard", "license": "GPL-3.0", "repository": { "type": "git", - "url": "https://github.com/AdguardTeam/ExtendedCss.git" + "url": "git+https://github.com/AdguardTeam/ExtendedCss.git" }, "bugs": { "url": "https://github.com/AdguardTeam/ExtendedCss/issues" }, - "homepage": "https://github.com/AdguardTeam/ExtendedCss", + "homepage": "https://github.com/AdguardTeam/ExtendedCss#homepage", "devDependencies": { - "@babel/core": "^7.7.5", - "@babel/plugin-transform-runtime": "^7.9.6", - "@babel/preset-env": "^7.9.6", - "@rollup/plugin-babel": "^5.0.0", - "@rollup/plugin-commonjs": "^15.0.0", - "@rollup/plugin-node-resolve": "^9.0.0", - "browserstack-runner": "^0.9.0", - "dotenv": "^8.2.0", - "eslint": "^7.6.0", - "eslint-config-airbnb-base": "^14.2.0", - "eslint-plugin-import": "^2.16.0", - "fs-extra": "^9.0.1", - "husky": "^4.2.5", - "node-qunit-puppeteer": "^2.1.0", - "rollup": "^2.8.2", - "rollup-plugin-copy": "^3.3.0", + "@babel/core": "^7.18.10", + "@babel/plugin-transform-runtime": "^7.18.10", + "@babel/preset-env": "^7.18.10", + "@babel/preset-typescript": "^7.18.6", + "@rollup/plugin-babel": "^5.3.1", + "@rollup/plugin-commonjs": "^22.0.1", + "@rollup/plugin-node-resolve": "^13.3.0", + "@types/html-minifier": "^4.0.2", + "@types/jest": "^28.1.8", + "@types/node": "^18.7.11", + "@types/qunit": "^2.19.2", + "@typescript-eslint/eslint-plugin": "^5.32.0", + "@typescript-eslint/parser": "^5.32.0", + "browserstack-runner": "^0.9.4", + "chalk": "^4.1.2", + "commander": "^9.4.0", + "dotenv": "^16.0.1", + "eslint": "^8.25.0", + "eslint-config-airbnb-typescript": "^17.0.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jsdoc": "^39.3.6", + "fs-extra": "^10.1.0", + "husky": "^8.0.0", + "jest": "^28.1.3", + "jest-environment-jsdom": "^28.1.3", + "lint-staged": "^13.0.3", + "playwright": "1.24.2", + "qunit": "2.10.0", + "rimraf": "^3.0.2", + "rollup": "^2.77.2", + "rollup-plugin-copy": "^3.4.0", "rollup-plugin-delete": "^2.0.0", - "rollup-plugin-terser": "^7.0.0" - }, - "dependencies": {} + "rollup-plugin-html2": "^3.1.0", + "rollup-plugin-terser": "^7.0.2", + "rollup-plugin-ts": "^3.0.2", + "ts-node": "^10.9.1", + "typescript": "^4.7.4", + "user-agent-data-types": "^0.3.0" + } } diff --git a/src/common/constants.ts b/src/common/constants.ts new file mode 100644 index 00000000..3f444a4f --- /dev/null +++ b/src/common/constants.ts @@ -0,0 +1,220 @@ +const LEFT_SQUARE_BRACKET = '['; +const RIGHT_SQUARE_BRACKET = ']'; +const LEFT_PARENTHESIS = '('; +const RIGHT_PARENTHESIS = ')'; +const LEFT_CURLY_BRACKET = '{'; +const RIGHT_CURLY_BRACKET = '}'; + +export const BRACKETS = { + SQUARE: { + LEFT: LEFT_SQUARE_BRACKET, + RIGHT: RIGHT_SQUARE_BRACKET, + }, + PARENTHESES: { + LEFT: LEFT_PARENTHESIS, + RIGHT: RIGHT_PARENTHESIS, + }, + CURLY: { + LEFT: LEFT_CURLY_BRACKET, + RIGHT: RIGHT_CURLY_BRACKET, + }, +}; + +export const SLASH = '/'; +export const BACKSLASH = '\\'; + +export const SPACE = ' '; +export const COMMA = ','; +export const DOT = '.'; +export const SEMICOLON = ';'; +export const COLON = ':'; + +export const SINGLE_QUOTE = '\''; +export const DOUBLE_QUOTE = '"'; + +// do not consider hyphen `-` as separated mark +// to avoid pseudo-class names splitting +// e.g. 'matches-css' or 'if-not' + +export const CARET = '^'; +export const DOLLAR_SIGN = '$'; + +export const EQUAL_SIGN = '='; + +export const TAB = '\t'; +export const CARRIAGE_RETURN = '\r'; +export const LINE_FEED = '\n'; +export const FORM_FEED = '\f'; + +export const WHITE_SPACE_CHARACTERS = [ + SPACE, + TAB, + CARRIAGE_RETURN, + LINE_FEED, + FORM_FEED, +]; + +// for universal selector and attributes +export const ASTERISK = '*'; +export const ID_MARKER = '#'; +export const CLASS_MARKER = DOT; + +export const DESCENDANT_COMBINATOR = SPACE; +export const CHILD_COMBINATOR = '>'; +export const NEXT_SIBLING_COMBINATOR = '+'; +export const SUBSEQUENT_SIBLING_COMBINATOR = '~'; + +export const COMBINATORS = [ + DESCENDANT_COMBINATOR, + CHILD_COMBINATOR, + NEXT_SIBLING_COMBINATOR, + SUBSEQUENT_SIBLING_COMBINATOR, +]; + +export const SUPPORTED_SELECTOR_MARKS = [ + LEFT_SQUARE_BRACKET, + RIGHT_SQUARE_BRACKET, + LEFT_PARENTHESIS, + RIGHT_PARENTHESIS, + LEFT_CURLY_BRACKET, + RIGHT_CURLY_BRACKET, + SLASH, + BACKSLASH, + SEMICOLON, + COLON, + COMMA, + SINGLE_QUOTE, + DOUBLE_QUOTE, + CARET, + DOLLAR_SIGN, + ASTERISK, + ID_MARKER, + CLASS_MARKER, + DESCENDANT_COMBINATOR, + CHILD_COMBINATOR, + NEXT_SIBLING_COMBINATOR, + SUBSEQUENT_SIBLING_COMBINATOR, + TAB, + CARRIAGE_RETURN, + LINE_FEED, + FORM_FEED, +]; + +// absolute: +export const CONTAINS_PSEUDO = 'contains'; +export const HAS_TEXT_PSEUDO = 'has-text'; +export const ABP_CONTAINS_PSEUDO = '-abp-contains'; +export const MATCHES_CSS_PSEUDO = 'matches-css'; +export const MATCHES_CSS_BEFORE_PSEUDO = 'matches-css-before'; +export const MATCHES_CSS_AFTER_PSEUDO = 'matches-css-after'; +export const MATCHES_ATTR_PSEUDO_CLASS_MARKER = 'matches-attr'; +export const MATCHES_PROPERTY_PSEUDO_CLASS_MARKER = 'matches-property'; +export const XPATH_PSEUDO_CLASS_MARKER = 'xpath'; +export const NTH_ANCESTOR_PSEUDO_CLASS_MARKER = 'nth-ancestor'; + +/** + * Pseudo-class :upward() can get number or selector arg + * and if the arg is selector it should be standard, not extended + * so :upward pseudo-class is always absolute. + */ +export const UPWARD_PSEUDO_CLASS_MARKER = 'upward'; + +/** + * Pseudo-class `:remove()` and pseudo-property `remove` + * are used for element actions, not for element selecting. + * + * Selector text should not contain the pseudo-class + * so selector parser should consider it as invalid + * and both are handled by stylesheet parser. + */ +export const REMOVE_PSEUDO_MARKER = 'remove'; + +// relative: +export const HAS_PSEUDO_CLASS_MARKER = 'has'; +export const IF_PSEUDO_CLASS_MARKER = 'if'; +export const ABP_HAS_PSEUDO_CLASS_MARKER = '-abp-has'; +export const HAS_PSEUDO_CLASS_MARKERS = [ + HAS_PSEUDO_CLASS_MARKER, + IF_PSEUDO_CLASS_MARKER, + ABP_HAS_PSEUDO_CLASS_MARKER, +]; +export const IF_NOT_PSEUDO_CLASS_MARKER = 'if-not'; +export const IS_PSEUDO_CLASS_MARKER = 'is'; +export const NOT_PSEUDO_CLASS_MARKER = 'not'; + +export const ABSOLUTE_PSEUDO_CLASSES = [ + CONTAINS_PSEUDO, + HAS_TEXT_PSEUDO, + ABP_CONTAINS_PSEUDO, + MATCHES_CSS_PSEUDO, + MATCHES_CSS_BEFORE_PSEUDO, + MATCHES_CSS_AFTER_PSEUDO, + MATCHES_ATTR_PSEUDO_CLASS_MARKER, + MATCHES_PROPERTY_PSEUDO_CLASS_MARKER, + XPATH_PSEUDO_CLASS_MARKER, + NTH_ANCESTOR_PSEUDO_CLASS_MARKER, + UPWARD_PSEUDO_CLASS_MARKER, +]; + +export const RELATIVE_PSEUDO_CLASSES = [ + ...HAS_PSEUDO_CLASS_MARKERS, + IF_NOT_PSEUDO_CLASS_MARKER, + IS_PSEUDO_CLASS_MARKER, + NOT_PSEUDO_CLASS_MARKER, +]; + +export const SUPPORTED_PSEUDO_CLASSES = [ + ...ABSOLUTE_PSEUDO_CLASSES, + ...RELATIVE_PSEUDO_CLASSES, +]; + +/** + * ':scope' is used for extended pseudo-class :has(), if-not(), :is() and :not(). + * + * ':where' is needed for limitation it's using inside :has() arg. + * + * @see {@link https://bugs.chromium.org/p/chromium/issues/detail?id=669058#c54} [case 1] + */ +export const REGULAR_PSEUDO_CLASSES = { + SCOPE: 'scope', +}; + +/** + * ':after' and ':before' are needed for :matches-css() pseudo-class + * all other are needed for :has() limitation after regular pseudo-elements. + * + * @see {@link https://bugs.chromium.org/p/chromium/issues/detail?id=669058#c54} [case 3] + */ +export const REGULAR_PSEUDO_ELEMENTS = { + AFTER: 'after', + BACKDROP: 'backdrop', + BEFORE: 'before', + CUE: 'cue', + CUE_REGION: 'cue-region', + FIRST_LETTER: 'first-letter', + FIRST_LINE: 'first-line', + FILE_SELECTION_BUTTON: 'file-selector-button', + GRAMMAR_ERROR: 'grammar-error', + MARKER: 'marker', + PART: 'part', + PLACEHOLDER: 'placeholder', + SELECTION: 'selection', + SLOTTED: 'slotted', + SPELLING_ERROR: 'spelling-error', + TARGET_TEXT: 'target-text', +}; + +export const PSEUDO_PROPERTY_POSITIVE_VALUE = 'true'; +export const DEBUG_PSEUDO_PROPERTY_GLOBAL_VALUE = 'global'; + +export const STYLESHEET_ERROR_PREFIX = { + NO_STYLE: 'No style declaration at stylesheet part', + INVALID_STYLE: 'Invalid style declaration at stylesheet part', + UNCLOSED_STYLE: 'Unclosed style declaration at stylesheet part', + NO_PROPERTY: 'Missing style property in declaration at stylesheet part', + NO_VALUE: 'Missing style value in declaration at stylesheet part', + INVALID_REMOVE: 'Invalid :remove() pseudo-class in selector', + NO_STYLE_OR_REMOVE: 'Invalid stylesheet - no style declared or :remove() pseudo-class used', +}; + +export const MAX_STYLE_PROTECTION_COUNT = 50; diff --git a/src/common/utils/arrays.ts b/src/common/utils/arrays.ts new file mode 100644 index 00000000..3eecac5e --- /dev/null +++ b/src/common/utils/arrays.ts @@ -0,0 +1,40 @@ + +/** + * Some browsers do not support Array.prototype.flat() + * e.g. Opera 42 which is used for browserstack tests. + * + * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat} + * + * @param input Array needed to be flatten. + * + * @throws An error if array cannot be flatten. + */ +export const flatten = (input: Array): Array => { + const stack: Array = []; + input.forEach((el) => stack.push(el)); + const res: Array = []; + while (stack.length) { + // pop value from stack + const next = stack.pop(); + if (!next) { + throw new Error('Unable to make array flat'); + } + if (Array.isArray(next)) { + // push back array items, won't modify the original input + next.forEach((el) => stack.push(el)); + } else { + res.push(next); + } + } + // reverse to restore input order + return res.reverse(); +}; + +/** + * Returns last item from array. + * + * @param array Input array. + */ +export const getLast = (array: Array): T => { + return array[array.length - 1]; +}; diff --git a/src/common/utils/logger.ts b/src/common/utils/logger.ts new file mode 100644 index 00000000..10fa9141 --- /dev/null +++ b/src/common/utils/logger.ts @@ -0,0 +1,21 @@ +export const logger = { + /** + * Safe console.error version. + */ + error: ( + typeof console !== 'undefined' + && console.error + && console.error.bind) + ? console.error.bind(window.console) + : console.error, + + /** + * Safe console.info version. + */ + info: ( + typeof console !== 'undefined' + && console.info + && console.info.bind) + ? console.info.bind(window.console) + : console.info, +}; diff --git a/src/common/utils/natives.ts b/src/common/utils/natives.ts new file mode 100644 index 00000000..4c64f8a6 --- /dev/null +++ b/src/common/utils/natives.ts @@ -0,0 +1,20 @@ +declare global { + interface Window { + WebKitMutationObserver: MutationObserver; + } +} + +export const natives = { + MutationObserver: window.MutationObserver || window.WebKitMutationObserver, +}; + +/** + * As soon as possible stores native Node textContent getter to be used for contains pseudo-class + * because elements' 'textContent' and 'innerText' properties might be mocked. + * + * @see {@link https://github.com/AdguardTeam/ExtendedCss/issues/127} + */ +export const nodeTextContentGetter = (() => { + const nativeNode = window.Node || Node; + return Object.getOwnPropertyDescriptor(nativeNode.prototype, 'textContent')?.get; +})(); diff --git a/src/common/utils/nodes.ts b/src/common/utils/nodes.ts new file mode 100644 index 00000000..4f59961b --- /dev/null +++ b/src/common/utils/nodes.ts @@ -0,0 +1,77 @@ +import { nodeTextContentGetter } from './natives'; + +/** + * Returns textContent of passed domElement. + * + * @param domElement DOM element. + */ +export const getNodeTextContent = (domElement: Node): string => { + return nodeTextContentGetter?.apply(domElement) || ''; +}; + +/** + * Returns element selector text based on it's tagName and attributes. + * + * @param element DOM element. + */ +export const getElementSelectorDesc = (element: Element): string => { + let selectorText = element.tagName.toLowerCase(); + selectorText += Array.from(element.attributes) + .map((attr) => { + return `[${attr.name}="${element.getAttribute(attr.name)}"]`; + }) + .join(''); + return selectorText; +}; + +/** + * Returns path to a DOM element as a selector string. + * + * @param inputEl Input element. + * + * @throws An error if `inputEl` in not instance of `Element`. + */ +export const getElementSelectorPath = (inputEl: Element): string => { + if (!(inputEl instanceof Element)) { + throw new Error('Function received argument with wrong type'); + } + + let el: Element | null; + + el = inputEl; + const path: string[] = []; + // we need to check '!!el' first because it is possible + // that some ancestor of the inputEl was removed before it + while (!!el && el.nodeType === Node.ELEMENT_NODE) { + let selector: string = el.nodeName.toLowerCase(); + if (el.id && typeof el.id === 'string') { + selector += `#${el.id}`; + path.unshift(selector); + break; + } + let sibling = el; + let nth = 1; + while (sibling.previousElementSibling) { + sibling = sibling.previousElementSibling; + if (sibling.nodeType === Node.ELEMENT_NODE && sibling.nodeName.toLowerCase() === selector) { + nth += 1; + } + } + if (nth !== 1) { + selector += `:nth-of-type(${nth})`; + } + + path.unshift(selector); + el = el.parentElement; + } + return path.join(' > '); +}; + +/** + * Checks whether the element is instance of HTMLElement. + * + * @param element Element to check. + */ +export const isHtmlElement = (element: HTMLElement | Node | null): element is HTMLElement => { + return element instanceof HTMLElement; +}; diff --git a/src/common/utils/numbers.ts b/src/common/utils/numbers.ts new file mode 100644 index 00000000..5cf67deb --- /dev/null +++ b/src/common/utils/numbers.ts @@ -0,0 +1,9 @@ +/** + * Checks whether passed `arg` is number type. + * + * @param arg Value to check. + */ +export const isNumber = (arg: T): boolean => { + return typeof arg === 'number' + && !Number.isNaN(arg); +}; diff --git a/src/common/utils/objects.ts b/src/common/utils/objects.ts new file mode 100644 index 00000000..a11da1fd --- /dev/null +++ b/src/common/utils/objects.ts @@ -0,0 +1,19 @@ +/** + * Converts array of pairs to object. + * Object.fromEntries() polyfill because it is not supported by old browsers, e.g. Chrome 55. + * + * @see {@link https://caniuse.com/?search=Object.fromEntries} + * + * @param entries Array of pairs. + */ +export const getObjectFromEntries = (entries: Array>): { [key: string]: T } => { + const initAcc: { [key: string]: T } = {}; + const object = entries + .reduce((acc, el) => { + const key = el[0]; + const value = el[1]; + acc[key] = value; + return acc; + }, initAcc); + return object; +}; diff --git a/src/common/utils/strings.ts b/src/common/utils/strings.ts new file mode 100644 index 00000000..13fc79a2 --- /dev/null +++ b/src/common/utils/strings.ts @@ -0,0 +1,98 @@ +import { SLASH } from '../constants'; + +/** + * Gets string without suffix. + * + * @param str Input string. + * @param suffix Needed to remove. + */ +export const removeSuffix = (str: string, suffix: string): string => { + const index = str.indexOf(suffix, str.length - suffix.length); + if (index >= 0) { + return str.substring(0, index); + } + return str; +}; + +/** + * Replaces all `pattern`s with `replacement` in `input` string. + * String.replaceAll() polyfill because it is not supported by old browsers, e.g. Chrome 55. + * + * @see {@link https://caniuse.com/?search=String.replaceAll} + * + * @param input Input string to process. + * @param pattern Find in the input string. + * @param replacement Replace the pattern with. + */ +export const replaceAll = (input: string, pattern: string, replacement: string): string => { + if (!input) { + return input; + } + return input.split(pattern).join(replacement); +}; + +/** + * Converts string pattern to regular expression. + * + * @param str String to convert. + */ +export const toRegExp = (str: string): RegExp => { + if (str.startsWith(SLASH) && str.endsWith(SLASH)) { + return new RegExp(str.slice(1, -1)); + } + const escaped = str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + return new RegExp(escaped); +}; + +/** + * Converts any simple type value to string type, + * e.g. `undefined` -> `'undefined'`. + * + * @param value Any type value. + */ +export const convertTypeIntoString = (value?: Element[keyof Element]): string => { + let output; + switch (value) { + case undefined: + output = 'undefined'; + break; + case null: + output = 'null'; + break; + default: + output = value.toString(); + } + return output; +}; + +/** + * Converts instance of string value into other simple types, + * e.g. `'null'` -> `null`, `'true'` -> `true`. + * + * @param value String-type value. + */ +export const convertTypeFromString = (value: string): undefined | null | boolean | number | string => { + const numValue = Number(value); + let output; + if (!Number.isNaN(numValue)) { + output = numValue; + } else { + switch (value) { + case 'undefined': + output = undefined; + break; + case 'null': + output = null; + break; + case 'true': + output = true; + break; + case 'false': + output = false; + break; + default: + output = value; + } + } + return output; +}; diff --git a/src/common/utils/user-agents.ts b/src/common/utils/user-agents.ts new file mode 100644 index 00000000..66666a2a --- /dev/null +++ b/src/common/utils/user-agents.ts @@ -0,0 +1,149 @@ +enum BrowserName { + Chrome = 'Chrome', + Firefox = 'Firefox', + Edge = 'Edg', + Opera = 'Opera', + Safari = 'Safari', +} + +const CHROMIUM_BRAND_NAME = 'Chromium'; +const GOOGLE_CHROME_BRAND_NAME = 'Google Chrome'; + +/** + * Simple check for Safari browser. + */ +export const isSafariBrowser = navigator.vendor === 'Apple Computer, Inc.'; + +interface BrowserData { + MASK: RegExp; + MIN_VERSION: number; +} + +type SupportedBrowsersData = { + [key: string]: BrowserData; +}; + +const SUPPORTED_BROWSERS_DATA: SupportedBrowsersData = { + [BrowserName.Chrome]: { + // avoid Chromium-based Edge browser + MASK: /\s(Chrome)\/(\d+)\..+\s(?!.*Edg\/)/, + MIN_VERSION: 55, + }, + [BrowserName.Firefox]: { + MASK: /\s(Firefox)\/(\d+)\./, + MIN_VERSION: 52, + }, + [BrowserName.Edge]: { + MASK: /\s(Edg)\/(\d+)\./, + MIN_VERSION: 80, + }, + [BrowserName.Opera]: { + MASK: /\s(OPR)\/(\d+)\./, + MIN_VERSION: 80, + }, + [BrowserName.Safari]: { + MASK: /\sVersion\/(\d{2}\.\d)(.+\s|\s)(Safari)\//, + MIN_VERSION: 11.1, + }, +}; + +/** + * Returns chromium brand object from navigator.userAgentData.brands or null if not supported. + * Chromium because of all browsers based on it should be supported as well + * and it is universal way to check it. + * + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/NavigatorUAData/brands} + */ +const getChromiumBrand = (): NavigatorUABrandVersion | null => { + const brandsData = navigator.userAgentData?.brands; + if (!brandsData) { + return null; + } + // for chromium-based browsers + const chromiumBrand = brandsData.find((brandData) => { + return brandData.brand === CHROMIUM_BRAND_NAME + || brandData.brand === GOOGLE_CHROME_BRAND_NAME; + }); + return chromiumBrand || null; +}; + +type BrowserInfo = { + browserName: string, + currentVersion: number, +}; + +/** + * Parses userAgent string and returns the data object for supported browsers; + * otherwise returns null. + */ +const parseUserAgent = (): BrowserInfo | null => { + let browserName; + let currentVersion; + const browserNames = Object.values(BrowserName); + + for (let i = 0; i < browserNames.length; i += 1) { + const match = SUPPORTED_BROWSERS_DATA[browserNames[i]].MASK.exec(navigator.userAgent); + if (match) { + // for safari browser the order is different because of regexp + if (match[3] === browserNames[i]) { + browserName = match[3]; + currentVersion = Number(match[1]); + } else { + // for others first is name and second is version + browserName = match[1]; + currentVersion = Number(match[2]); + } + return { browserName, currentVersion }; + } + } + + return null; +}; + +/** + * Gets info about current browser. + */ +const getCurrentBrowserInfoAsSupported = (): BrowserInfo | null => { + const brandData = getChromiumBrand(); + if (!brandData) { + const uaInfo = parseUserAgent(); + if (!uaInfo) { + return null; + } + const { browserName, currentVersion } = uaInfo; + return { browserName, currentVersion }; + } + + // if navigator.userAgentData is supported + const { brand, version } = brandData; + // handle chromium-based browsers + const browserName = brand === CHROMIUM_BRAND_NAME || brand === GOOGLE_CHROME_BRAND_NAME + ? BrowserName.Chrome + : brand; + return { browserName, currentVersion: Number(version) }; +}; + +/** + * Checks whether the current browser is supported. + */ +export const isBrowserSupported = (): boolean => { + const ua = navigator.userAgent; + // do not support Internet Explorer + if (ua.includes('MSIE') || ua.includes('Trident/')) { + return false; + } + + // for local testing purposes + if (ua.includes('jsdom')) { + return true; + } + + const currentBrowserData = getCurrentBrowserInfoAsSupported(); + if (!currentBrowserData) { + return false; + } + + const { browserName, currentVersion } = currentBrowserData; + + return currentVersion >= SUPPORTED_BROWSERS_DATA[browserName].MIN_VERSION; +}; diff --git a/src/extended-css/extended-css.ts b/src/extended-css/extended-css.ts new file mode 100644 index 00000000..b613c3c2 --- /dev/null +++ b/src/extended-css/extended-css.ts @@ -0,0 +1,219 @@ +import { extCssDocument } from '../selector'; +import { parse as parseStylesheet } from '../stylesheet'; + +import { ThrottleWrapper } from './helpers/throttle-wrapper'; +import { applyRules } from './helpers/rules-applier'; +import { revertStyle } from './helpers/style-setter'; +import { mainDisconnect } from './helpers/document-observer'; +import { + AffectedElement, + BeforeStyleAppliedCallback, + Context, +} from './helpers/types'; + +import { isBrowserSupported } from '../common/utils/user-agents'; +import { logger } from '../common/utils/logger'; + +import { DEBUG_PSEUDO_PROPERTY_GLOBAL_VALUE } from '../common/constants'; + +/** + * Throttle timeout for ThrottleWrapper to execute applyRules(). + */ +const APPLY_RULES_DELAY = 150; + +/** + * Result of selector validation. + */ +type ValidationResult = { + ok: boolean, + error: string | null, +}; + +/** + * Interface for ExtendedCss constructor argument. Needed to create the instance of ExtendedCss. + * + * ExtendedCss configuration should contain: + * - CSS stylesheet of rules to apply + * - the callback for matched elements + * and also may contain a flag for global logging which is useful for selectors debugging. + * + * Stylesheet can contain not just extended selectors, and all of them will be applied by the lib. + * Stylesheet does not support CSS comments and at-rules. + */ +export interface ExtCssConfiguration { + /** + * Standard CSS stylesheet — a set of CSS rules + * which generally consists of selector and style (except `:remove()` pseudo-class). + * + * ExtendedCss is able to parse and apply almost any CSS stylesheet, not just extended selectors + * but there are some limitations - for example, CSS comments and at-rules are not supported; + * learn more about the Limitations in README.md. + */ + styleSheet: string; + + /** + * The callback that handles affected elements. + * + * Needed for getting affected node elements and handle style properties + * before they are applied to them if it is necessary. + * + * Used by AdGuard Browser extension to display rules in Filtering log + * and `collect-hits-count` (via tsurlfilter's CssHitsCounter). + */ + beforeStyleApplied?: BeforeStyleAppliedCallback; + + /** + * Optional flag for global debugging mode. + * + * Alternatively can be set by extended pseudo-property `debug: global` in styleSheet rules. + * + * Learn more about Selectors debug mode in README.md. + */ + debug?: boolean; +} + + +/** + * Main class of ExtendedCss lib. + * + * Parses css stylesheet with any selectors (passed to its argument as styleSheet), + * and guarantee its applying as mutation observer is used to prevent the restyling of needed elements by other scripts. + * This style protection is limited to 50 times to avoid infinite loop (MAX_STYLE_PROTECTION_COUNT). + * Our own ThrottleWrapper is used for styles applying to avoid too often lib reactions on page mutations. + * + * Constructor creates the instance of class which should be run be `apply()` method to apply the rules, + * and the applying can be stopped by `dispose()`. + * + * Can be used to select page elements by selector with `query()` method (similar to `Document.querySelectorAll()`), + * which does not require instance creating. + */ +export class ExtendedCss { + private context: Context; + + private applyRulesScheduler: ThrottleWrapper; + + private applyRulesCallbackListener: () => void; + + + /** + * Creates new ExtendedCss. + * + * @param configuration ExtendedCss configuration. + */ + constructor(configuration: ExtCssConfiguration) { + if (!isBrowserSupported()) { + throw new Error('Browser is not supported by ExtendedCss.'); + } + + if (!configuration) { + throw new Error('ExtendedCss configuration should be provided.'); + } + + this.context = { + beforeStyleApplied: configuration.beforeStyleApplied, + debug: false, + affectedElements: [], + isDomObserved: false, + removalsStatistic: {}, + parsedRules: parseStylesheet(configuration.styleSheet, extCssDocument), + mainCallback: () => {}, + }; + + // true if set in configuration + // or any rule in styleSheet has `debug: global` + this.context.debug = configuration.debug || this.context.parsedRules.some((ruleData) => { + return ruleData.debug === DEBUG_PSEUDO_PROPERTY_GLOBAL_VALUE; + }); + + this.applyRulesScheduler = new ThrottleWrapper(this.context, applyRules, APPLY_RULES_DELAY); + + this.context.mainCallback = this.applyRulesScheduler.run.bind(this.applyRulesScheduler); + + if (this.context.beforeStyleApplied && typeof this.context.beforeStyleApplied !== 'function') { + throw new Error(`Invalid configuration. Type of 'beforeStyleApplied' should be a function, received: '${typeof this.context.beforeStyleApplied}'`); // eslint-disable-line max-len + } + + this.applyRulesCallbackListener = () => { + applyRules(this.context); + }; + } + + /** + * Applies stylesheet rules on page. + */ + apply(): void { + applyRules(this.context); + + if (document.readyState !== 'complete') { + document.addEventListener( + 'DOMContentLoaded', + this.applyRulesCallbackListener, + false, + ); + } + } + + /** + * Disposes ExtendedCss and removes our styles from matched elements. + */ + dispose(): void { + mainDisconnect(this.context, this.context.mainCallback); + this.context.affectedElements.forEach((el) => { + revertStyle(el); + }); + document.removeEventListener( + 'DOMContentLoaded', + this.applyRulesCallbackListener, + false, + ); + } + + /** + * Exposed for testing purposes only. + */ + getAffectedElements(): AffectedElement[] { + return this.context.affectedElements; + } + + /** + * Returns a list of the document's elements that match the specified selector. + * Uses ExtCssDocument.querySelectorAll(). + * + * @param selector Selector text. + * @param [noTiming=true] If true — do not print the timings to the console. + * + * @throws An error if selector is not valid. + * @returns A list of elements that match the selector. + */ + public static query(selector: string, noTiming = true): HTMLElement[] { + if (typeof selector !== 'string') { + throw new Error('Selector should be defined as a string.'); + } + + const start = ThrottleWrapper.now(); + + try { + return extCssDocument.querySelectorAll(selector); + } finally { + const end = ThrottleWrapper.now(); + if (!noTiming) { + logger.info(`[ExtendedCss] Elapsed: ${Math.round((end - start) * 1000)} μs.`); + } + } + } + + /** + * Validates selector. + * + * @param selector Selector text. + */ + public static validate(selector: string): ValidationResult { + try { + ExtendedCss.query(selector); + return { ok: true, error: null }; + } catch (e: any) { // eslint-disable-line @typescript-eslint/no-explicit-any + const error = `Error: selector "${selector}" is invalid — ${e.message})`; + return { ok: false, error }; + } + } +} diff --git a/src/extended-css/helpers/document-observer.ts b/src/extended-css/helpers/document-observer.ts new file mode 100644 index 00000000..c7cb9df0 --- /dev/null +++ b/src/extended-css/helpers/document-observer.ts @@ -0,0 +1,70 @@ +import { EventTracker } from './event-tracker'; +import { Context, MainCallback } from './types'; +import { natives } from '../../common/utils/natives'; + +const isEventListenerSupported = typeof window.addEventListener !== 'undefined'; + +const observeDocument = (context: Context, callback: MainCallback): void => { + // We are trying to limit the number of callback calls by not calling it on all kind of "hover" events. + // The rationale behind this is that "hover" events often cause attributes modification, + // but re-applying extCSS rules will be useless as these attribute changes are usually transient. + const shouldIgnoreMutations = (mutations: MutationRecord[]) => { + // ignore if all mutations are about attributes changes + return mutations.every((m) => m.type === 'attributes'); + }; + + if (natives.MutationObserver) { + context.domMutationObserver = new natives.MutationObserver(((mutations) => { + if (!mutations || mutations.length === 0) { + return; + } + const eventTracker = new EventTracker(); + if (eventTracker.isIgnoredEventType() && shouldIgnoreMutations(mutations)) { + return; + } + // save instance of EventTracker to context + // for removing its event listeners on disconnectDocument() while mainDisconnect() + context.eventTracker = eventTracker; + callback(); + })); + context.domMutationObserver.observe(document, { + childList: true, + subtree: true, + attributes: true, + attributeFilter: ['id', 'class'], + }); + } else if (isEventListenerSupported) { + document.addEventListener('DOMNodeInserted', callback, false); + document.addEventListener('DOMNodeRemoved', callback, false); + document.addEventListener('DOMAttrModified', callback, false); + } +}; + +const disconnectDocument = (context: Context, callback: MainCallback): void => { + if (context.domMutationObserver) { + context.domMutationObserver.disconnect(); + } else if (isEventListenerSupported) { + document.removeEventListener('DOMNodeInserted', callback, false); + document.removeEventListener('DOMNodeRemoved', callback, false); + document.removeEventListener('DOMAttrModified', callback, false); + } + // clean up event listeners + context.eventTracker?.stopTracking(); +}; + +export const mainObserve = (context: Context, mainCallback: MainCallback): void => { + if (context.isDomObserved) { + return; + } + // handle dynamically added elements + context.isDomObserved = true; + observeDocument(context, mainCallback); +}; + +export const mainDisconnect = (context: Context, mainCallback: MainCallback): void => { + if (!context.isDomObserved) { + return; + } + context.isDomObserved = false; + disconnectDocument(context, mainCallback); +}; diff --git a/src/extended-css/helpers/event-tracker.ts b/src/extended-css/helpers/event-tracker.ts new file mode 100644 index 00000000..efca610b --- /dev/null +++ b/src/extended-css/helpers/event-tracker.ts @@ -0,0 +1,84 @@ +import { isSafariBrowser } from '../../common/utils/user-agents'; + +const LAST_EVENT_TIMEOUT_MS = 10; + +const IGNORED_EVENTS = ['mouseover', 'mouseleave', 'mouseenter', 'mouseout']; + +const SUPPORTED_EVENTS = [ + // keyboard events + 'keydown', 'keypress', 'keyup', + // mouse events + 'auxclick', 'click', 'contextmenu', 'dblclick', 'mousedown', 'mouseenter', + 'mouseleave', 'mousemove', 'mouseover', 'mouseout', 'mouseup', 'pointerlockchange', + 'pointerlockerror', 'select', 'wheel', +]; + +// 'wheel' event makes scrolling in Safari twitchy +// https://github.com/AdguardTeam/ExtendedCss/issues/120 +const SAFARI_PROBLEMATIC_EVENTS = ['wheel']; + +/** + * We use EventTracker to track the event that is likely to cause the mutation. + * The problem is that we cannot use `window.event` directly from the mutation observer call + * as we're not in the event handler context anymore. + */ +export class EventTracker { + private trackedEvents: string[]; + + private lastEventType?: string; + + private lastEventTime?: number; + + /** + * Creates new EventTracker. + */ + constructor() { + this.trackedEvents = isSafariBrowser + ? SUPPORTED_EVENTS.filter((event) => !SAFARI_PROBLEMATIC_EVENTS.includes(event)) + : SUPPORTED_EVENTS; + + this.trackedEvents.forEach((eventName) => { + document.documentElement.addEventListener(eventName, this.trackEvent, true); + }); + } + + /** + * Callback for event listener for events tracking. + * + * @param event Any event. + */ + private trackEvent(event: Event): void { + this.lastEventType = event.type; + this.lastEventTime = Date.now(); + } + + private getLastEventType = (): string | undefined => this.lastEventType; + + private getTimeSinceLastEvent = (): number | null => { + if (!this.lastEventTime) { + return null; + } + return Date.now() - this.lastEventTime; + }; + + /** + * Checks whether the last caught event should be ignored. + */ + isIgnoredEventType(): boolean { + const lastEventType = this.getLastEventType(); + const sinceLastEventTime = this.getTimeSinceLastEvent(); + return !!lastEventType + && IGNORED_EVENTS.includes(lastEventType) + && !!sinceLastEventTime + && sinceLastEventTime < LAST_EVENT_TIMEOUT_MS; + } + + /** + * Stops event tracking by removing event listener. + */ + stopTracking(): void { + this.trackedEvents.forEach((eventName) => { + document.documentElement.removeEventListener(eventName, this.trackEvent, true); + }); + } +} diff --git a/src/extended-css/helpers/mutation-observer.ts b/src/extended-css/helpers/mutation-observer.ts new file mode 100644 index 00000000..a6d001e5 --- /dev/null +++ b/src/extended-css/helpers/mutation-observer.ts @@ -0,0 +1,64 @@ +import { ProtectionCallback } from './types'; +import { natives } from '../../common/utils/natives'; +import { logger } from '../../common/utils/logger'; + +import { MAX_STYLE_PROTECTION_COUNT } from '../../common/constants'; + +/** + * ExtMutationObserver is a wrapper over regular MutationObserver with one additional function: + * it keeps track of the number of times we called the "ProtectionCallback". + * + * We use an instance of this to monitor styles added by ExtendedCss + * and to make sure these styles are recovered if the page script attempts to modify them. + * + * However, we want to avoid endless loops of modification if the page script repeatedly modifies the styles. + * So we keep track of the number of calls and observe() makes a decision + * whether to continue recovering the styles or not. + */ +export class ExtMutationObserver { + private observer: MutationObserver; + + /** + * Extra property for keeping 'style fix counts'. + */ + private styleProtectionCount: number; + + /** + * Creates new ExtMutationObserver. + * + * @param protectionCallback Callback which execution should be counted. + */ + constructor(protectionCallback: ProtectionCallback) { + this.styleProtectionCount = 0; + this.observer = new natives.MutationObserver((mutations: MutationRecord[]) => { + if (!mutations.length) { + return; + } + this.styleProtectionCount += 1; + protectionCallback(mutations, this); + }); + } + + /** + * Starts to observe target element, + * prevents infinite loop of observing due to the limited number of times of callback runs. + * + * @param target Target to observe. + * @param options Mutation observer options. + */ + observe(target: Node, options: MutationObserverInit): void { + if (this.styleProtectionCount < MAX_STYLE_PROTECTION_COUNT) { + this.observer.observe(target, options); + } else { + logger.error('ExtendedCss: infinite loop protection for style'); + } + } + + /** + * Stops ExtMutationObserver from observing any mutations. + * Until the `observe()` is used again, `protectionCallback` will not be invoked. + */ + disconnect(): void { + this.observer.disconnect(); + } +} diff --git a/src/extended-css/helpers/rules-applier.ts b/src/extended-css/helpers/rules-applier.ts new file mode 100644 index 00000000..c8562ea6 --- /dev/null +++ b/src/extended-css/helpers/rules-applier.ts @@ -0,0 +1,120 @@ +import { ThrottleWrapper } from './throttle-wrapper'; +import { mainDisconnect, mainObserve } from './document-observer'; +import { applyStyle, revertStyle } from './style-setter'; +import { protectStyleAttribute } from './style-protector'; +import { TimingStats, printTimingInfo } from './timing-stats'; +import { AffectedElement, Context } from './types'; + +import { ExtCssRuleData } from '../../stylesheet'; +import { selectElementsByAst } from '../../selector'; + +/** + * Finds affectedElement object for the specified DOM node. + * + * @param affElements Array of affected elements — context.affectedElements. + * @param domNode DOM node. + * @returns Found affectedElement or undefined. + */ +const findAffectedElement = (affElements: AffectedElement[], domNode: Element): AffectedElement | undefined => { + return affElements.find((affEl) => affEl.node === domNode); +}; + +/** + * Applies specified rule and returns list of elements affected. + * + * @param context ExtendedCss context. + * @param ruleData Rule to apply. + * @returns List of elements affected by the rule. + */ +const applyRule = (context: Context, ruleData: ExtCssRuleData): HTMLElement[] => { + // debugging mode can be enabled in two ways: + // 1. for separate rules - by `{ debug: true; }` + // 2. for all rules simultaneously by: + // - `{ debug: global; }` in any rule + // - positive `debug` property in ExtCssConfiguration + const isDebuggingMode = !!ruleData.debug || context.debug; + let startTime: number | undefined; + if (isDebuggingMode) { + startTime = ThrottleWrapper.now(); + } + + const { ast } = ruleData; + const nodes = selectElementsByAst(ast); + + nodes.forEach((node) => { + let affectedElement = findAffectedElement(context.affectedElements, node); + + if (affectedElement) { + affectedElement.rules.push(ruleData); + applyStyle(context, affectedElement); + } else { + // Applying style first time + const originalStyle = node.style.cssText; + affectedElement = { + node, // affected DOM node + rules: [ruleData], // rule to be applied + originalStyle, // original node style + protectionObserver: null, // style attribute observer + }; + applyStyle(context, affectedElement); + context.affectedElements.push(affectedElement); + } + }); + + if (isDebuggingMode && startTime) { + const elapsedTimeMs = ThrottleWrapper.now() - startTime; + if (!ruleData.timingStats) { + ruleData.timingStats = new TimingStats(); + } + ruleData.timingStats.push(elapsedTimeMs); + } + + return nodes; +}; + +/** + * Applies filtering rules. + * + * @param context ExtendedCss context. + */ +export const applyRules = (context: Context): void => { + const newSelectedElements: HTMLElement[] = []; + // some rules could make call - selector.querySelectorAll() temporarily to change node id attribute + // this caused MutationObserver to call recursively + // https://github.com/AdguardTeam/ExtendedCss/issues/81 + mainDisconnect(context, context.mainCallback); + context.parsedRules.forEach((ruleData) => { + const nodes = applyRule(context, ruleData); + Array.prototype.push.apply(newSelectedElements, nodes); + // save matched elements to ruleData as linked to applied rule + // only for debugging purposes + if (ruleData.debug) { + ruleData.matchedElements = nodes; + } + }); + // Now revert styles for elements which are no more affected + let affLength = context.affectedElements.length; + // do nothing if there is no elements to process + while (affLength) { + const affectedElement = context.affectedElements[affLength - 1]; + if (!newSelectedElements.includes(affectedElement.node)) { + // Time to revert style + revertStyle(affectedElement); + context.affectedElements.splice(affLength - 1, 1); + } else if (!affectedElement.removed) { + // Add style protection observer + // Protect "style" attribute from changes + if (!affectedElement.protectionObserver) { + affectedElement.protectionObserver = protectStyleAttribute( + affectedElement.node, + affectedElement.rules, + ); + } + } + affLength -= 1; + } + // After styles are applied we can start observe again + mainObserve(context, context.mainCallback); + + printTimingInfo(context); +}; diff --git a/src/extended-css/helpers/style-protector.ts b/src/extended-css/helpers/style-protector.ts new file mode 100644 index 00000000..2eb38dba --- /dev/null +++ b/src/extended-css/helpers/style-protector.ts @@ -0,0 +1,59 @@ +import { setStyleToElement } from './style-setter'; +import { ExtMutationObserver } from './mutation-observer'; +import { ProtectionCallback } from './types'; + +import { CssStyleMap, ExtCssRuleData } from '../../stylesheet'; + +import { natives } from '../../common/utils/natives'; + +const PROTECTION_OBSERVER_OPTIONS = { + attributes: true, + attributeOldValue: true, + attributeFilter: ['style'], +}; + +/** + * Creates MutationObserver protection callback. + * + * @param styles Styles data object. + */ +const createProtectionCallback = (styles: CssStyleMap[]): ProtectionCallback => { + const protectionCallback = (mutations: MutationRecord[], extObserver: ExtMutationObserver): void => { + const { target } = mutations[0]; + extObserver.disconnect(); + styles.forEach((style) => { + setStyleToElement(target, style); + }); + extObserver.observe(target, PROTECTION_OBSERVER_OPTIONS); + }; + return protectionCallback; +}; + +/** + * Sets up a MutationObserver which protects style attributes from changes. + * + * @param node DOM node. + * @param rules Rule data objects. + * @returns Mutation observer used to protect attribute or null if there's nothing to protect. + */ +export const protectStyleAttribute = ( + node: HTMLElement, + rules: ExtCssRuleData[], +): ExtMutationObserver | null => { + if (!natives.MutationObserver) { + return null; + } + const styles: CssStyleMap[] = []; + rules.forEach((ruleData) => { + const { style } = ruleData; + // some rules might have only debug property in style declaration + // e.g. 'div:has(> a) { debug: true }' -> parsed to boolean `ruleData.debug` + // so no style is fine, and here we should collect only valid styles to protect + if (style) { + styles.push(style); + } + }); + const protectionObserver = new ExtMutationObserver(createProtectionCallback(styles)); + protectionObserver.observe(node, PROTECTION_OBSERVER_OPTIONS); + return protectionObserver; +}; diff --git a/src/extended-css/helpers/style-setter.ts b/src/extended-css/helpers/style-setter.ts new file mode 100644 index 00000000..63e17205 --- /dev/null +++ b/src/extended-css/helpers/style-setter.ts @@ -0,0 +1,113 @@ +import { AffectedElement, Context, IAffectedElement } from './types'; +import { CssStyleMap } from '../../stylesheet'; + +import { getElementSelectorPath } from '../../common/utils/nodes'; +import { removeSuffix } from '../../common/utils/strings'; +import { logger } from '../../common/utils/logger'; + +import { + MAX_STYLE_PROTECTION_COUNT, + PSEUDO_PROPERTY_POSITIVE_VALUE, + REMOVE_PSEUDO_MARKER, +} from '../../common/constants'; + +/** + * Removes affectedElement.node from DOM. + * + * @param context ExtendedCss context. + * @param affectedElement Affected element. + */ +const removeElement = (context: Context, affectedElement: AffectedElement): void => { + const { node } = affectedElement; + + affectedElement.removed = true; + + const elementSelector = getElementSelectorPath(node); + + // check if the element has been already removed earlier + const elementRemovalsCounter = context.removalsStatistic[elementSelector] || 0; + + // if removals attempts happened more than specified we do not try to remove node again + if (elementRemovalsCounter > MAX_STYLE_PROTECTION_COUNT) { + logger.error(`ExtendedCss: infinite loop protection for selector: '${elementSelector}'`); + return; + } + + if (node.parentElement) { + node.parentElement.removeChild(node); + context.removalsStatistic[elementSelector] = elementRemovalsCounter + 1; + } +}; + +/** + * Sets style to the specified DOM node. + * + * @param node DOM element. + * @param style Style to set. + */ +export const setStyleToElement = (node: Node, style: CssStyleMap): void => { + if (!(node instanceof HTMLElement)) { + return; + } + Object.keys(style).forEach((prop) => { + // Apply this style only to existing properties + // We can't use hasOwnProperty here (does not work in FF) + if (typeof node.style.getPropertyValue(prop) !== 'undefined') { + let value = style[prop]; + // First we should remove !important attribute (or it won't be applied') + value = removeSuffix(value.trim(), '!important').trim(); + node.style.setProperty(prop, value, 'important'); + } + }); +}; + +/** + * Applies style to the specified DOM node. + * + * @param context ExtendedCss context. + * @param affectedElement Object containing DOM node and rule to be applied. + * + * @throws An error if affectedElement has no style to apply. + */ +export const applyStyle = (context: Context, affectedElement: AffectedElement): void => { + if (affectedElement.protectionObserver) { + // style is already applied and protected by the observer + return; + } + + if (context.beforeStyleApplied) { + affectedElement = context.beforeStyleApplied(affectedElement as IAffectedElement); + if (!affectedElement) { + return; + } + } + + const { node, rules } = affectedElement; + for (let i = 0; i < rules.length; i += 1) { + const { selector, style, debug } = rules[i]; + // rule may not have style to apply + // e.g. 'div:has(> a) { debug: true }' -> means no style to apply, and enable debug mode + if (style) { + if (style[REMOVE_PSEUDO_MARKER] === PSEUDO_PROPERTY_POSITIVE_VALUE) { + removeElement(context, affectedElement); + return; + } + setStyleToElement(node, style); + } else if (!debug) { + // but rule should not have both style and debug properties + throw new Error(`No style declaration in rule for selector: '${selector}'`); + } + } +}; + +/** + * Reverts style for the affected object. + * + * @param affectedElement Affected element. + */ +export const revertStyle = (affectedElement: AffectedElement): void => { + if (affectedElement.protectionObserver) { + affectedElement.protectionObserver.disconnect(); + } + affectedElement.node.style.cssText = affectedElement.originalStyle; +}; diff --git a/src/extended-css/helpers/throttle-wrapper.ts b/src/extended-css/helpers/throttle-wrapper.ts new file mode 100644 index 00000000..6249790a --- /dev/null +++ b/src/extended-css/helpers/throttle-wrapper.ts @@ -0,0 +1,121 @@ +import { isNumber } from '../../common/utils/numbers'; +import { Context } from './types'; + +const isSupported = (typeof window.requestAnimationFrame !== 'undefined'); +const timeout = isSupported ? requestAnimationFrame : window.setTimeout; +const deleteTimeout = isSupported ? cancelAnimationFrame : clearTimeout; +const perf = isSupported ? performance : Date; + +const DEFAULT_THROTTLE_DELAY_MS = 150; + +type WrappedCallback = (timestamp?: number) => void; + +/** + * Method for filtering rules applying. + */ +type ApplyRulesCallback = (context: Context) => void; + +/** + * The purpose of ThrottleWrapper is to throttle calls of the function + * that applies ExtendedCss rules. The reasoning here is that the function calls + * are triggered by MutationObserver and there may be many mutations in a short period of time. + * We do not want to apply rules on every mutation so we use this helper to make sure + * that there is only one call in the given amount of time. + */ +export class ThrottleWrapper { + private context: Context; + + private callback?: ApplyRulesCallback; + + /** + * The provided callback should be executed twice in this time frame: + * very first time and not more often than throttleDelayMs for further executions. + * + * @see {@link ThrottleWrapper.run} + */ + private throttleDelayMs: number; + + private wrappedCb: WrappedCallback; + + private timeoutId?: number; + + private timerId?: number; + + private lastRunTime?: number; + + /** + * Creates new ThrottleWrapper. + * + * @param context ExtendedCss context. + * @param callback The callback. + * @param throttleMs Throttle delay in ms. + */ + constructor(context: Context, callback?: ApplyRulesCallback, throttleMs?: number) { + this.context = context; + this.callback = callback; + this.throttleDelayMs = throttleMs || DEFAULT_THROTTLE_DELAY_MS; + this.wrappedCb = this.wrappedCallback.bind(this); + } + + /** + * Wraps the callback (which supposed to be `applyRules`), + * needed to update `lastRunTime` and clean previous timeouts for proper execution of the callback. + * + * @param timestamp Timestamp. + */ + private wrappedCallback(timestamp?: number): void { + this.lastRunTime = isNumber(timestamp) + ? timestamp + : perf.now(); + // `timeoutId` can be requestAnimationFrame-related + // so cancelAnimationFrame() as deleteTimeout() needs the arg to be defined + if (this.timeoutId) { + deleteTimeout(this.timeoutId); + delete this.timeoutId; + } + clearTimeout(this.timerId); + delete this.timerId; + if (this.callback) { + this.callback(this.context); + } + } + + /** + * Indicates whether there is a scheduled callback. + */ + private hasPendingCallback(): boolean { + return isNumber(this.timeoutId) || isNumber(this.timerId); + } + + /** + * Schedules the function which applies ExtendedCss rules before the next animation frame. + * + * Wraps function execution into `timeout` — requestAnimationFrame or setTimeout. + * For the first time runs the function without any condition. + * As it may be triggered by any mutation which may occur too ofter, we limit the function execution: + * 1. If `elapsedTime` since last function execution is less then set `throttleDelayMs`, + * next function call is hold till the end of throttle interval (subtracting `elapsed` from `throttleDelayMs`); + * 2. Do nothing if triggered again but function call which is on hold has not yet started its execution. + */ + run(): void { + if (this.hasPendingCallback()) { + // there is a pending execution scheduled + return; + } + if (typeof this.lastRunTime !== 'undefined') { + const elapsedTime = perf.now() - this.lastRunTime; + if (elapsedTime < this.throttleDelayMs) { + this.timerId = window.setTimeout(this.wrappedCb, this.throttleDelayMs - elapsedTime); + return; + } + } + this.timeoutId = timeout(this.wrappedCb); + } + + /** + * Returns timestamp for 'now'. + */ + public static now(): number { + return perf.now(); + } +} diff --git a/src/extended-css/helpers/timing-stats.ts b/src/extended-css/helpers/timing-stats.ts new file mode 100644 index 00000000..8144a984 --- /dev/null +++ b/src/extended-css/helpers/timing-stats.ts @@ -0,0 +1,140 @@ +import { Context } from './types'; +import { CssStyleMap } from '../../stylesheet/parser'; + +import { logger } from '../../common/utils/logger'; + +import { PSEUDO_PROPERTY_POSITIVE_VALUE, REMOVE_PSEUDO_MARKER } from '../../common/constants'; + +const STATS_DECIMAL_DIGITS_COUNT = 4; + +export interface TimingStatsInterface { + appliesTimings: number[]; + appliesCount: number; + timingsSum: number; + meanTiming: number; + standardDeviation: number; +} + +/** + * A helper class for applied rule stats. + */ +export class TimingStats implements TimingStatsInterface { + appliesTimings: number[]; + + appliesCount: number; + + timingsSum: number; + + meanTiming: number; + + private squaredSum: number; + + standardDeviation: number; + + /** + * Creates new TimingStats. + */ + constructor() { + this.appliesTimings = []; + this.appliesCount = 0; + this.timingsSum = 0; + this.meanTiming = 0; + this.squaredSum = 0; + this.standardDeviation = 0; + } + + /** + * Observe target element and mark observer as active. + * + * @param elapsedTimeMs Time in ms. + */ + push(elapsedTimeMs: number): void { + this.appliesTimings.push(elapsedTimeMs); + this.appliesCount += 1; + this.timingsSum += elapsedTimeMs; + this.meanTiming = this.timingsSum / this.appliesCount; + this.squaredSum += elapsedTimeMs * elapsedTimeMs; + this.standardDeviation = Math.sqrt((this.squaredSum / this.appliesCount) - Math.pow(this.meanTiming, 2)); + } +} + +type SelectorLogData = { + selectorParsed: string; + timings: TimingStatsInterface; + styleApplied?: CssStyleMap | null; + removed?: boolean; + matchedElements?: HTMLElement[]; +}; + +type LogStatData = { + [key: string]: SelectorLogData; +}; + +/** + * Makes the timestamps more readable. + * + * @param timestamp Raw timestamp. + */ +const beautifyTimingNumber = (timestamp: number): number => { + return Number(timestamp.toFixed(STATS_DECIMAL_DIGITS_COUNT)); +}; + +/** + * Improves timing stats readability. + * + * @param rawTimings Collected timings with raw timestamp. + */ +const beautifyTimings = (rawTimings: TimingStatsInterface): TimingStatsInterface => { + return { + appliesTimings: rawTimings.appliesTimings.map((t) => beautifyTimingNumber(t)), + appliesCount: beautifyTimingNumber(rawTimings.appliesCount), + timingsSum: beautifyTimingNumber(rawTimings.timingsSum), + meanTiming: beautifyTimingNumber(rawTimings.meanTiming), + standardDeviation: beautifyTimingNumber(rawTimings.standardDeviation), + }; +}; + +/** + * Prints timing information if debugging mode is enabled. + * + * @param context ExtendedCss context. + */ +export const printTimingInfo = (context: Context): void => { + if (context.areTimingsPrinted) { + return; + } + context.areTimingsPrinted = true; + + const timingsLogData: LogStatData = {}; + + context.parsedRules.forEach((ruleData) => { + if (ruleData.timingStats) { + const { selector, style, debug, matchedElements } = ruleData; + // style declaration for some rules is parsed to debug property and no style to apply + // e.g. 'div:has(> a) { debug: true }' + if (!style && !debug) { + throw new Error(`Rule should have style declaration for selector: '${selector}'`); + } + const selectorData: SelectorLogData = { + selectorParsed: selector, + timings: beautifyTimings(ruleData.timingStats), + }; + // `ruleData.style` may contain `remove` pseudo-property + // and make logs look better + if (style && style[REMOVE_PSEUDO_MARKER] === PSEUDO_PROPERTY_POSITIVE_VALUE) { + selectorData.removed = true; + // no matchedElements for such case as they are removed after ExtendedCss applied + } else { + selectorData.styleApplied = style || null; + selectorData.matchedElements = matchedElements; + } + timingsLogData[selector] = selectorData; + } + }); + + if (Object.keys(timingsLogData).length === 0) { + return; + } + // add location.href to the message to distinguish frames + logger.info('[ExtendedCss] Timings in milliseconds for %o:\n%o', window.location.href, timingsLogData); +}; diff --git a/src/extended-css/helpers/types.ts b/src/extended-css/helpers/types.ts new file mode 100644 index 00000000..7a427883 --- /dev/null +++ b/src/extended-css/helpers/types.ts @@ -0,0 +1,117 @@ +import { CssStyleMap, ExtCssRuleData } from '../../stylesheet'; +import { EventTracker } from './event-tracker'; +import { ExtMutationObserver } from './mutation-observer'; + +export type MainCallback = () => void; + +export type ProtectionCallback = (m: MutationRecord[], o: ExtMutationObserver) => void; + +interface AffectedElementProto { + node: HTMLElement; + originalStyle: string; + protectionObserver?: ExtMutationObserver | null; + removed?: boolean; +} + +/** + * Interface for internal lib usage. + */ +export interface AffectedElement extends AffectedElementProto { + rules: ExtCssRuleData[]; +} + +/** + * Needed for ExtCssConfiguration.beforeStyleApplied(); + * value of 'content' property is applied rule text. + */ +interface CssStyleMapWithContent extends CssStyleMap { + content: string; +} + +/** + * Rule data interface with required 'style' property defined with required 'content' property. + */ +interface ExtCssRuleDataWithContentStyle extends Partial { + style: CssStyleMapWithContent; +} + +/** + * Api interface with required 'content' style property in rules. + */ +export interface IAffectedElement extends Partial { + node: HTMLElement; + rules: ExtCssRuleDataWithContentStyle[]; +} + +/** + * Data pairs for selector and number of times the element was removed by ExtendedCss. + * Needed to avoid infinite loop of re-setting styles. + */ +interface RemovalsStatistic { + [key: string]: number; +} + +/** + * Needed for getting affected node elements and handle style properties + * before they are applied to them if it is necessary. + * + * Used by AdGuard Browser extension to display rules in Filtering log + * and `collect-hits-count` (via tsurlfilter's CssHitsCounter). + */ +export type BeforeStyleAppliedCallback = (x:IAffectedElement) => AffectedElement; + +/** + * Interface for ExtendedCss context. Needed to store affected elements, collect removal stats, etc. + */ +export interface Context { + /** + * Callback that handles affected elements. + */ + beforeStyleApplied?: BeforeStyleAppliedCallback; + + /** + * Array of data which represents parsed rules, matched dom node, etc. + */ + affectedElements: AffectedElement[]; + + /** + * Flag for mainObserve() and mainDisconnect(), used while rules applying. + */ + isDomObserved: boolean; + + /** + * Instance of EventTracker for document observing. + */ + eventTracker?: EventTracker; + + /** + * Main document mutation observer. + */ + domMutationObserver?: MutationObserver; + + /** + * Actually the main callback — applyRules() scheduled by ThrottleWrapper. + */ + mainCallback: MainCallback; + + /** + * Info about element selectors and their removing counter. + */ + removalsStatistic: RemovalsStatistic; + + /** + * Array of parsed rules data needed for elements selecting and processing by ExtendedCss. + */ + parsedRules: ExtCssRuleData[]; + + /** + * Flag for global debugging mode + * which can be set in either ExtCssConfiguration or rules. + */ + debug: boolean; + + /** + * Flag for printing information about applied rules. + */ + areTimingsPrinted?: boolean; +} diff --git a/src/extended-css/index.ts b/src/extended-css/index.ts new file mode 100644 index 00000000..3b7ecb18 --- /dev/null +++ b/src/extended-css/index.ts @@ -0,0 +1,4 @@ +export { ExtendedCss } from './extended-css'; +export type { ExtCssConfiguration } from './extended-css'; +export type { IAffectedElement } from './helpers/types'; +export type { TimingStats } from './helpers/timing-stats'; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000..e7738d8c --- /dev/null +++ b/src/index.ts @@ -0,0 +1,2 @@ +export { ExtendedCss } from './extended-css'; +export type { ExtCssConfiguration, IAffectedElement } from './extended-css'; diff --git a/src/selector/converter.ts b/src/selector/converter.ts new file mode 100644 index 00000000..d54df7b7 --- /dev/null +++ b/src/selector/converter.ts @@ -0,0 +1,75 @@ +import { BRACKETS, COMMA } from '../common/constants'; + +/** + * Regexp that matches backward compatible syntaxes. + */ +const REGEXP_VALID_OLD_SYNTAX = /\[-(?:ext)-([a-z-_]+)=(["'])((?:(?=(\\?))\4.)*?)\2\]/g; + +/** + * Marker for checking invalid selector after old-syntax normalizing by selector converter. + */ +const INVALID_OLD_SYNTAX_MARKER = '[-ext-'; + +/** + * Complex replacement function. + * Undo quote escaping inside of an extended selector. + * + * @param match Whole matched string. + * @param name Group 1. + * @param quoteChar Group 2. + * @param rawValue Group 3. + */ +const evaluateMatch = (match: string, name: string, quoteChar: string, rawValue: string): string => { + // Unescape quotes + const re = new RegExp(`([^\\\\]|^)\\\\${quoteChar}`, 'g'); + const value = rawValue.replace(re, `$1${quoteChar}`); + return `:${name}(${value})`; +}; + +// ':scope' pseudo may be at start of :has() argument +// but ExtCssDocument.querySelectorAll() already use it for selecting exact element descendants +const reScope = /\(:scope >/g; +const SCOPE_REPLACER = '(>'; + +const MATCHES_CSS_PSEUDO_ELEMENT_REGEXP = /(:matches-css)-(before|after)\(/g; +const convertMatchesCss = (match: string, extendedPseudoClass: string, regularPseudoElement: string): string => { + // ':matches-css-before(' --> ':matches-css(before, ' + // ':matches-css-after(' --> ':matches-css(after, ' + return `${extendedPseudoClass}${BRACKETS.PARENTHESES.LEFT}${regularPseudoElement}${COMMA}`; +}; + +/** + * Handles old syntax and :scope inside :has(). + * + * @param selector Trimmed selector to normalize. + * + * @throws An error on invalid old extended syntax selector. + */ +const normalize = (selector: string): string => { + const normalizedSelector = selector + .replace(REGEXP_VALID_OLD_SYNTAX, evaluateMatch) + .replace(reScope, SCOPE_REPLACER) + .replace(MATCHES_CSS_PSEUDO_ELEMENT_REGEXP, convertMatchesCss); + + // validate old syntax after normalizing + // e.g. '[-ext-matches-css-before=\'content: /^[A-Z][a-z]' + if (normalizedSelector.includes(INVALID_OLD_SYNTAX_MARKER)) { + throw new Error(`Invalid extended-css old syntax selector: '${selector}'`); + } + + return normalizedSelector; +}; + +/** + * Prepares the rawSelector before tokenization: + * 1. Trims it. + * 2. Converts old syntax `[-ext-pseudo-class="..."]` to new one `:pseudo-class(...)`. + * 3. Handles :scope pseudo inside :has() pseudo-class arg. + * + * @param rawSelector Selector with no style declaration. + * @returns Prepared selector with no style declaration. + */ +export const convert = (rawSelector: string): string => { + const trimmedSelector = rawSelector.trim(); + return normalize(trimmedSelector); +}; diff --git a/src/selector/index.ts b/src/selector/index.ts new file mode 100644 index 00000000..9d98aed6 --- /dev/null +++ b/src/selector/index.ts @@ -0,0 +1,11 @@ +export { getValidMatcherArg, parseRawPropChain } from './utils/absolute-matcher'; + +export { NodeType, AnySelectorNodeInterface } from './nodes'; + +export { convert } from './converter'; + +export { tokenize } from './tokenizer'; + +export { parse } from './parser'; + +export { extCssDocument, ExtCssDocument, selectElementsByAst } from './query'; diff --git a/src/selector/nodes.ts b/src/selector/nodes.ts new file mode 100644 index 00000000..466f68ad --- /dev/null +++ b/src/selector/nodes.ts @@ -0,0 +1,182 @@ +export enum NodeType { + SelectorList = 'SelectorList', + Selector = 'Selector', + RegularSelector = 'RegularSelector', + ExtendedSelector = 'ExtendedSelector', + AbsolutePseudoClass = 'AbsolutePseudoClass', + RelativePseudoClass = 'RelativePseudoClass', +} + +/** + * Universal interface for all node types. + */ +export interface AnySelectorNodeInterface { + type: string; + children: AnySelectorNodeInterface[]; + value?: string; + name?: string; + + addChild(child: AnySelectorNodeInterface): void; +} + +/** + * Class needed for creating ast nodes while selector parsing. + * Used for SelectorList, Selector, ExtendedSelector. + */ +export class AnySelectorNode implements AnySelectorNodeInterface { + type: string; + + children: AnySelectorNodeInterface[] = []; + + /** + * Creates new ast node. + * + * @param type Ast node type. + */ + constructor(type: NodeType) { + this.type = type; + } + + /** + * Adds child node to children array. + * + * @param child Ast node. + */ + public addChild(child: AnySelectorNodeInterface): void { + this.children.push(child); + } +} + +/** + * Class needed for creating RegularSelector ast node while selector parsing. + */ +export class RegularSelectorNode extends AnySelectorNode { + value: string; + + /** + * Creates RegularSelector ast node. + * + * @param value Value of RegularSelector node. + */ + constructor(value: string) { + super(NodeType.RegularSelector); + this.value = value; + } +} + +/** + * Class needed for creating RelativePseudoClass ast node while selector parsing. + */ +export class RelativePseudoClassNode extends AnySelectorNode { + name: string; + + /** + * Creates RegularSelector ast node. + * + * @param name Name of RelativePseudoClass node. + */ + constructor(name: string) { + super(NodeType.RelativePseudoClass); + this.name = name; + } +} + +/** + * Class needed for creating AbsolutePseudoClass ast node while selector parsing. + */ +export class AbsolutePseudoClassNode extends AnySelectorNode { + name: string; + + value = ''; + + /** + * Creates AbsolutePseudoClass ast node. + * + * @param name Name of AbsolutePseudoClass node. + */ + constructor(name: string) { + super(NodeType.AbsolutePseudoClass); + this.name = name; + } +} + +/* eslint-disable jsdoc/require-description-complete-sentence */ + +/** + * Root node. + * + * SelectorList + * : Selector + * ... + * ; + */ + +/** + * Selector node. + * + * Selector + * : RegularSelector + * | ExtendedSelector + * ... + * ; + */ + +/** + * Regular selector node. + * It can be selected by querySelectorAll(). + * + * RegularSelector + * : type + * : value + * ; + */ + +/** + * Extended selector node. + * + * ExtendedSelector + * : AbsolutePseudoClass + * | RelativePseudoClass + * ; + */ + +/** + * Absolute extended pseudo-class node, + * i.e. none-selector args. + * + * AbsolutePseudoClass + * : type + * : name + * : value + * ; + */ + +/** + * Relative extended pseudo-class node + * i.e. selector as arg. + * + * RelativePseudoClass + * : type + * : name + * : SelectorList + * ; + */ + +// +// ast example +// +// div.banner > div:has(span, p), a img.ad +// +// SelectorList - div.banner > div:has(span, p), a img.ad +// Selector - div.banner > div:has(span, p) +// RegularSelector - div.banner > div +// ExtendedSelector - :has(span, p) +// PseudoClassSelector - :has +// SelectorList - span, p +// Selector - span +// RegularSelector - span +// Selector - p +// RegularSelector - p +// Selector - a img.ad +// RegularSelector - a img.ad +// diff --git a/src/selector/parser.ts b/src/selector/parser.ts new file mode 100644 index 00000000..1ded2cee --- /dev/null +++ b/src/selector/parser.ts @@ -0,0 +1,959 @@ +import { TokenType, tokenize } from './tokenizer'; + +import { + NodeType, + AnySelectorNodeInterface, + AnySelectorNode, + RegularSelectorNode, + AbsolutePseudoClassNode, + RelativePseudoClassNode, +} from './nodes'; + +import { getLast } from '../common/utils/arrays'; + +import { + BRACKETS, + COLON, + SEMICOLON, + DESCENDANT_COMBINATOR, + CHILD_COMBINATOR, + NEXT_SIBLING_COMBINATOR, + SUBSEQUENT_SIBLING_COMBINATOR, + CLASS_MARKER, + ID_MARKER, + COMBINATORS, + SPACE, + ASTERISK, + COMMA, + BACKSLASH, + SLASH, + SINGLE_QUOTE, + DOUBLE_QUOTE, + CARET, + DOLLAR_SIGN, + TAB, + LINE_FEED, + CARRIAGE_RETURN, + FORM_FEED, + WHITE_SPACE_CHARACTERS, + SUPPORTED_PSEUDO_CLASSES, + ABSOLUTE_PSEUDO_CLASSES, + RELATIVE_PSEUDO_CLASSES, + XPATH_PSEUDO_CLASS_MARKER, + HAS_PSEUDO_CLASS_MARKERS, + IS_PSEUDO_CLASS_MARKER, + NOT_PSEUDO_CLASS_MARKER, + REMOVE_PSEUDO_MARKER, + REGULAR_PSEUDO_ELEMENTS, + UPWARD_PSEUDO_CLASS_MARKER, + NTH_ANCESTOR_PSEUDO_CLASS_MARKER, +} from '../common/constants'; + +// limit applying of wildcard :is() and :not() pseudo-class only to html children +// e.g. ':is(.page, .main) > .banner' or '*:not(span):not(p)' +const IS_OR_NOT_PSEUDO_SELECTING_ROOT = `html ${ASTERISK}`; + +// limit applying of :xpath() pseudo-class to 'any' element +// https://github.com/AdguardTeam/ExtendedCss/issues/115 +const XPATH_PSEUDO_SELECTING_ROOT = 'body'; + +/** + * Checks whether the passed token is supported extended pseudo-class. + * + * @param tokenValue Token value to check. + */ +const isSupportedExtendedPseudo = (tokenValue: string): boolean => SUPPORTED_PSEUDO_CLASSES.includes(tokenValue); + +/** + * Checks whether next token is a continuation of regular selector being processed. + * + * @param nextTokenType Type of token next to current one. + * @param nextTokenValue Value of token next to current one. + */ +const doesRegularContinueAfterSpace = (nextTokenType: string, nextTokenValue: string): boolean => { + return COMBINATORS.includes(nextTokenValue) + || nextTokenType === TokenType.Word + // e.g. '#main *:has(> .ad)' + || nextTokenValue === ASTERISK + || nextTokenValue === ID_MARKER + || nextTokenValue === CLASS_MARKER + // e.g. 'div :where(.content)' + || nextTokenValue === COLON + // e.g. "div[class*=' ']" + || nextTokenValue === SINGLE_QUOTE + // e.g. 'div[class*=" "]' + || nextTokenValue === DOUBLE_QUOTE + || nextTokenValue === BRACKETS.SQUARE.LEFT; +}; + +/** + * Interface for selector parser context. + */ +interface Context { + /** + * Collected result. + */ + ast: AnySelectorNodeInterface | null; + + /** + * Array of nodes as path to buffer node. + */ + pathToBufferNode: AnySelectorNodeInterface[]; + + /** + * Array of extended pseudo-class names; + * needed for checking while going deep into extended selector. + */ + extendedPseudoNamesStack: string[]; + + /** + * Array of brackets for proper extended selector args collecting. + */ + extendedPseudoBracketsStack: string[]; + + /** + * Array of standard pseudo-class names. + */ + standardPseudoNamesStack: string[]; + + /** + * Array of brackets for proper standard pseudo-class handling. + */ + standardPseudoBracketsStack: string[]; + + /** + * Flag for processing comma inside attribute value. + */ + isAttributeBracketsOpen: boolean; + + /** + * Flag for extended pseudo-class arg regexp values. + */ + isRegexpOpen: boolean; +} + +/** + * Gets the node which is being collected + * or null if there is no such one. + * + * @param context Selector parser context. + */ +const getBufferNode = (context: Context): AnySelectorNodeInterface | null => { + if (context.pathToBufferNode.length === 0) { + return null; + } + // buffer node is always the last in the pathToBufferNode stack + return getLast(context.pathToBufferNode); +}; + +/** + * Gets last RegularSelector ast node. + * Needed for parsing of the complex selector with extended pseudo-class inside it. + * + * @param context Selector parser context. + * + * @throws An error if: + * - bufferNode is absent; + * - type of bufferNode is unsupported; + * - no RegularSelector in bufferNode. + */ +const getLastRegularSelectorNode = (context: Context): AnySelectorNodeInterface => { + const bufferNode = getBufferNode(context); + if (!bufferNode) { + throw new Error('No bufferNode found'); + } + if (bufferNode.type !== NodeType.Selector) { + throw new Error('Unsupported bufferNode type'); + } + const selectorRegularChildren = bufferNode.children.filter((node) => node.type === NodeType.RegularSelector); + if (selectorRegularChildren.length === 0) { + throw new Error('No RegularSelector node found'); + } + const lastRegularSelectorNode = getLast(selectorRegularChildren); + context.pathToBufferNode.push(lastRegularSelectorNode); + return lastRegularSelectorNode; +}; + +/** + * Updates needed buffer node value while tokens iterating. + * + * @param context Selector parser context. + * @param tokenValue Value of current token. + * + * @throws An error if: + * - no bufferNode; + * - bufferNode.type is not RegularSelector or AbsolutePseudoClass. + */ +const updateBufferNode = (context: Context, tokenValue: string): void => { + const bufferNode = getBufferNode(context); + if (bufferNode === null) { + throw new Error('No bufferNode to update'); + } + const { type } = bufferNode; + if (type === NodeType.RegularSelector + || type === NodeType.AbsolutePseudoClass) { + bufferNode.value += tokenValue; + } else { + throw new Error(`${bufferNode.type} node can not be updated. Only RegularSelector and AbsolutePseudoClass are supported.`); // eslint-disable-line max-len + } +}; + +/** + * Adds SelectorList node to context.ast at the start of ast collecting. + * + * @param context Selector parser context. + */ +const addSelectorListNode = (context: Context): void => { + const selectorListNode = new AnySelectorNode(NodeType.SelectorList); + context.ast = selectorListNode; + context.pathToBufferNode.push(selectorListNode); +}; + +/** + * Adds new node to buffer node children. + * New added node will be considered as buffer node after it. + * + * @param context Selector parser context. + * @param type Type of node to add. + * @param tokenValue Optional, defaults to `''`, value of processing token. + * + * @throws An error if no bufferNode. + */ +const addAstNodeByType = (context: Context, type: NodeType, tokenValue = ''): void => { + const bufferNode = getBufferNode(context); + if (bufferNode === null) { + throw new Error('No buffer node'); + } + + let node: AnySelectorNodeInterface; + if (type === NodeType.RegularSelector) { + node = new RegularSelectorNode(tokenValue); + } else if (type === NodeType.AbsolutePseudoClass) { + node = new AbsolutePseudoClassNode(tokenValue); + } else if (type === NodeType.RelativePseudoClass) { + node = new RelativePseudoClassNode(tokenValue); + } else { + // SelectorList || Selector || ExtendedSelector + node = new AnySelectorNode(type); + } + + bufferNode.addChild(node); + context.pathToBufferNode.push(node); +}; + +/** + * The very beginning of ast collecting. + * + * @param context Selector parser context. + * @param tokenValue Value of regular selector. + */ +const initAst = (context: Context, tokenValue: string): void => { + addSelectorListNode(context); + addAstNodeByType(context, NodeType.Selector); + // RegularSelector node is always the first child of Selector node + addAstNodeByType(context, NodeType.RegularSelector, tokenValue); +}; + +/** + * Inits selector list subtree for relative extended pseudo-classes, e.g. :has(), :not(). + * + * @param context Selector parser context. + * @param tokenValue Optional, defaults to `''`, value of inner regular selector. + */ +const initRelativeSubtree = (context: Context, tokenValue = ''): void => { + addAstNodeByType(context, NodeType.SelectorList); + addAstNodeByType(context, NodeType.Selector); + addAstNodeByType(context, NodeType.RegularSelector, tokenValue); +}; + +/** + * Goes to closest parent specified by type. + * Actually updates path to buffer node for proper ast collecting of selectors while parsing. + * + * @param context Selector parser context. + * @param parentType Type of needed parent node in ast. + */ +const upToClosest = (context: Context, parentType: NodeType): void => { + for (let i = context.pathToBufferNode.length - 1; i >= 0; i -= 1) { + if (context.pathToBufferNode[i].type === parentType) { + context.pathToBufferNode = context.pathToBufferNode.slice(0, i + 1); + break; + } + } +}; + +/** + * Gets needed buffer node updated due to complex selector parsing. + * + * @param context Selector parser context. + * + * @throws An error if there is no upper SelectorNode is ast. + */ +const getUpdatedBufferNode = (context: Context): AnySelectorNodeInterface | null => { + upToClosest(context, NodeType.Selector); + const selectorNode = getBufferNode(context); + if (!selectorNode) { + throw new Error('No SelectorNode, impossible to continue selector parsing'); + } + const lastSelectorNodeChild = getLast(selectorNode.children); + const hasExtended = lastSelectorNodeChild.type === NodeType.ExtendedSelector + // parser position might be inside standard pseudo-class brackets which has space + // e.g. 'div:contains(/а/):nth-child(100n + 2)' + && context.standardPseudoBracketsStack.length === 0; + const lastExtendedPseudoName = hasExtended + && lastSelectorNodeChild.children[0].name; + + const isLastExtendedNameRelative = lastExtendedPseudoName + && RELATIVE_PSEUDO_CLASSES.includes(lastExtendedPseudoName); + const isLastExtendedNameAbsolute = lastExtendedPseudoName + && ABSOLUTE_PSEUDO_CLASSES.includes(lastExtendedPseudoName); + + const hasRelativeExtended = isLastExtendedNameRelative + && context.extendedPseudoBracketsStack.length > 0 + && context.extendedPseudoBracketsStack.length === context.extendedPseudoNamesStack.length; + const hasAbsoluteExtended = isLastExtendedNameAbsolute + && lastExtendedPseudoName === getLast(context.extendedPseudoNamesStack); + + let newNeededBufferNode = selectorNode; + if (hasRelativeExtended) { + // return relative selector node to update later + context.pathToBufferNode.push(lastSelectorNodeChild); + newNeededBufferNode = lastSelectorNodeChild.children[0]; + } else if (hasAbsoluteExtended) { + // return absolute selector node to update later + context.pathToBufferNode.push(lastSelectorNodeChild); + newNeededBufferNode = lastSelectorNodeChild.children[0]; + } else if (hasExtended) { + // return selector node to add new regular selector node later + newNeededBufferNode = selectorNode; + } else { + // otherwise return last regular selector node to update later + newNeededBufferNode = getLastRegularSelectorNode(context); + } + context.pathToBufferNode.push(newNeededBufferNode); + return newNeededBufferNode; +}; + +/** + * Checks values of few next tokens on colon token `:` and: + * - updates buffer node for following standard pseudo-class; + * - adds extended selector ast node for following extended pseudo-class; + * - validates some cases of `:remove()` and `:has()` usage. + * + * @param context Selector parser context. + * @param selector Selector. + * @param tokenValue Value of current token. + * @param nextTokenValue Value of token next to current one. + * @param nextToNextTokenValue Value of token next to next to current one. + * + * @throws An error on :remove() pseudo-class in selector + * or :has() inside regular pseudo limitation. + */ +const handleNextTokenOnColon = ( + context: Context, + selector: string, + tokenValue: string, + nextTokenValue: string, + nextToNextTokenValue: string, +) => { + if (!isSupportedExtendedPseudo(nextTokenValue.toLowerCase())) { + if (nextTokenValue.toLowerCase() === REMOVE_PSEUDO_MARKER) { + // :remove() pseudo-class should be handled before + // as it is not about element selecting but actions with elements + // e.g. 'body > div:empty:remove()' + throw new Error(`Selector parser error: invalid :remove() pseudo-class in selector: '${selector}'`); // eslint-disable-line max-len + } + // if following token is not an extended pseudo + // the colon should be collected to value of RegularSelector + // e.g. '.entry_text:nth-child(2)' + updateBufferNode(context, tokenValue); + // check the token after the pseudo and do balance parentheses later + // only if it is functional pseudo-class (standard with brackets, e.g. ':lang()'). + // no brackets balance needed for such case, + // parser position is on first colon after the 'div': + // e.g. 'div:last-child:has(button.privacy-policy__btn)' + if (nextToNextTokenValue === BRACKETS.PARENTHESES.LEFT) { + context.standardPseudoNamesStack.push(nextTokenValue); + } + } else { + // it is supported extended pseudo-class. + // Disallow :has() inside the pseudos accepting only compound selectors + // https://bugs.chromium.org/p/chromium/issues/detail?id=669058#c54 [2] + if (HAS_PSEUDO_CLASS_MARKERS.includes(nextTokenValue) + && context.standardPseudoNamesStack.length > 0) { + // eslint-disable-next-line max-len + throw new Error(`Usage of :${nextTokenValue}() pseudo-class is not allowed inside regular pseudo: '${getLast(context.standardPseudoNamesStack)}'`); + } else { + // stop RegularSelector value collecting + upToClosest(context, NodeType.Selector); + // add ExtendedSelector to Selector children + addAstNodeByType(context, NodeType.ExtendedSelector); + } + } +}; + +/** + * Parses selector into ast for following element selection. + * + * @param selector Selector to parse. + * + * @throws An error on invalid selector. + */ +export const parse = (selector: string): AnySelectorNodeInterface => { + const tokens = tokenize(selector); + + const context: Context = { + ast: null, + pathToBufferNode: [], + extendedPseudoNamesStack: [], + extendedPseudoBracketsStack: [], + standardPseudoNamesStack: [], + standardPseudoBracketsStack: [], + isAttributeBracketsOpen: false, + isRegexpOpen: false, + }; + + let i = 0; + while (i < tokens.length) { + const token = tokens[i]; + + // Token to process + const { type: tokenType, value: tokenValue } = token; + + // needed for SPACE and COLON tokens checking + const nextToken = tokens[i + 1] || []; + const { type: nextTokenType, value: nextTokenValue } = nextToken; + + // needed for limitations + // - :not() and :is() root element + // - :has() usage + // - white space before and after pseudo-class name + const nextToNextToken = tokens[i + 2] || []; + const { value: nextToNextTokenValue } = nextToNextToken; + + // needed for COLON token checking for none-specified regular selector before extended one + // e.g. 'p, :hover' + // or '.banner, :contains(ads)' + const previousToken = tokens[i - 1] || []; + const { type: prevTokenType, value: prevTokenValue } = previousToken; + + let bufferNode = getBufferNode(context); + + switch (tokenType) { + case TokenType.Word: + if (bufferNode === null) { + // there is no buffer node only in one case — no ast collecting has been started + initAst(context, tokenValue); + } else if (bufferNode.type === NodeType.SelectorList) { + // add new selector to selector list + addAstNodeByType(context, NodeType.Selector); + addAstNodeByType(context, NodeType.RegularSelector, tokenValue); + } else if (bufferNode.type === NodeType.RegularSelector) { + updateBufferNode(context, tokenValue); + } else if (bufferNode.type === NodeType.ExtendedSelector) { + // No white space is allowed between the name of extended pseudo-class + // and its opening parenthesis + // https://www.w3.org/TR/selectors-4/#pseudo-classes + // e.g. 'span:contains (text)' + if (WHITE_SPACE_CHARACTERS.includes(nextTokenValue) + && nextToNextTokenValue === BRACKETS.PARENTHESES.LEFT) { + throw new Error(`No white space is allowed before or after extended pseudo-class name in selector: '${selector}'`); // eslint-disable-line max-len + } + // save pseudo-class name for brackets balance checking + context.extendedPseudoNamesStack.push(tokenValue.toLowerCase()); + // extended pseudo-class name are parsed in lower case + // as they should be case-insensitive + // https://www.w3.org/TR/selectors-4/#pseudo-classes + if (ABSOLUTE_PSEUDO_CLASSES.includes(tokenValue.toLowerCase())) { + addAstNodeByType(context, NodeType.AbsolutePseudoClass, tokenValue.toLowerCase()); + } else { + // if it is not absolute pseudo-class, it must be relative one + // add RelativePseudoClass with tokenValue as pseudo-class name to ExtendedSelector children + addAstNodeByType(context, NodeType.RelativePseudoClass, tokenValue.toLowerCase()); + } + } else if (bufferNode.type === NodeType.AbsolutePseudoClass) { + // collect absolute pseudo-class arg + updateBufferNode(context, tokenValue); + } else if (bufferNode.type === NodeType.RelativePseudoClass) { + initRelativeSubtree(context, tokenValue); + } + break; + case TokenType.Mark: + switch (tokenValue) { + case COMMA: + if (!bufferNode || (typeof bufferNode !== 'undefined' && !nextTokenValue)) { + // consider the selector is invalid if there is no bufferNode yet (e.g. ', a') + // or there is nothing after the comma while bufferNode is defined (e.g. 'div, ') + throw new Error(`'${selector}' is not a valid selector`); + } else if (bufferNode.type === NodeType.RegularSelector) { + if (context.isAttributeBracketsOpen) { + // the comma might be inside element attribute value + // e.g. 'div[data-comma="0,1"]' + updateBufferNode(context, tokenValue); + } else { + // new Selector should be collected to upper SelectorList + upToClosest(context, NodeType.SelectorList); + } + } else if (bufferNode.type === NodeType.AbsolutePseudoClass) { + // the comma inside arg of absolute extended pseudo + // e.g. 'div:xpath(//h3[contains(text(),"Share it!")]/..)' + updateBufferNode(context, tokenValue); + } else if (bufferNode?.type === NodeType.Selector) { + // new Selector should be collected to upper SelectorList + // if parser position is on Selector node + upToClosest(context, NodeType.SelectorList); + } + break; + case SPACE: + // it might be complex selector with extended pseudo-class inside it + // and the space is between that complex selector and following regular selector + // parser position is on ` ` before `span` now: + // e.g. 'div:has(img).banner span' + // so we need to check whether the new ast node should be added (example above) + // or previous regular selector node should be updated + if (bufferNode?.type === NodeType.RegularSelector) { + bufferNode = getUpdatedBufferNode(context); + } + if (bufferNode?.type === NodeType.RegularSelector) { + // standard selectors with white space between colon and name of pseudo + // are invalid for native document.querySelectorAll() anyway, + // so throwing the error here is better + // than proper parsing of invalid selector and passing it further. + // first of all do not check attributes + // e.g. div[style="text-align: center"] + if (!context.isAttributeBracketsOpen + // check the space after the colon and before the pseudo + // e.g. '.block: nth-child(2) + && ((prevTokenValue === COLON && nextTokenType === TokenType.Word) + // or after the pseudo and before the opening parenthesis + // e.g. '.block:nth-child (2) + || (prevTokenType === TokenType.Word + && nextTokenValue === BRACKETS.PARENTHESES.LEFT)) + ) { + throw new Error(`'${selector}' is not a valid selector.`); + } + // collect current tokenValue to value of RegularSelector + // if it is the last token or standard selector continues after the space. + // otherwise it will be skipped + if (!nextTokenValue || doesRegularContinueAfterSpace(nextTokenType, nextTokenValue)) { + updateBufferNode(context, tokenValue); + } + } + if (bufferNode?.type === NodeType.AbsolutePseudoClass) { + // space inside extended pseudo-class arg + // e.g. 'span:contains(some text)' + updateBufferNode(context, tokenValue); + } + if (bufferNode?.type === NodeType.RelativePseudoClass) { + // init with empty value RegularSelector + // as the space is not needed for selector value + // e.g. 'p:not( .content )' + initRelativeSubtree(context); + } + if (bufferNode?.type === NodeType.Selector) { + // do NOT add RegularSelector if parser position on space BEFORE the comma in selector list + // e.g. '.block:has(> img) , .banner)' + if (nextTokenValue && doesRegularContinueAfterSpace(nextTokenType, nextTokenValue)) { + // regular selector might be after the extended one. + // extra space before combinator or selector should not be collected + // e.g. '.banner:upward(2) .block' + // '.banner:upward(2) > .block' + // so no tokenValue passed to addAnySelectorNode() + addAstNodeByType(context, NodeType.RegularSelector); + } + } + break; + case DESCENDANT_COMBINATOR: + case CHILD_COMBINATOR: + case NEXT_SIBLING_COMBINATOR: + case SUBSEQUENT_SIBLING_COMBINATOR: + case SEMICOLON: + case SLASH: + case BACKSLASH: + case SINGLE_QUOTE: + case DOUBLE_QUOTE: + case CARET: + case DOLLAR_SIGN: + case BRACKETS.CURLY.LEFT: + case BRACKETS.CURLY.RIGHT: + case ASTERISK: + case ID_MARKER: + case CLASS_MARKER: + case BRACKETS.SQUARE.LEFT: + // it might be complex selector with extended pseudo-class inside it + // and the space is between that complex selector and following regular selector + // e.g. 'div:has(img).banner' // parser position is on `.` before `banner` now + // 'div:has(img)[attr]' // parser position is on `[` before `attr` now + // so we need to check whether the new ast node should be added (example above) + // or previous regular selector node should be updated + if (COMBINATORS.includes(tokenValue)) { + bufferNode = getUpdatedBufferNode(context); + } + if (bufferNode === null) { + // no ast collecting has been started + if (tokenValue === ASTERISK + && nextTokenValue === COLON + && (nextToNextTokenValue === IS_PSEUDO_CLASS_MARKER + || nextToNextTokenValue === NOT_PSEUDO_CLASS_MARKER)) { + // limit applying of wildcard :is() and :not() pseudo-class only to html children + // as we check element parent for them and there is no parent for html, + // e.g. '*:is(.page, .main) > .banner' + // or '*:not(span):not(p)' + initAst(context, IS_OR_NOT_PSEUDO_SELECTING_ROOT); + } else { + // e.g. '.banner > p' + // or '#top > div.ad' + // or '[class][style][attr]' + initAst(context, tokenValue); + } + } else if (bufferNode.type === NodeType.RegularSelector) { + // collect the mark to the value of RegularSelector node + updateBufferNode(context, tokenValue); + if (tokenValue === BRACKETS.SQUARE.LEFT) { + // needed for proper handling element attribute value with comma + // e.g. 'div[data-comma="0,1"]' + context.isAttributeBracketsOpen = true; + } + } else if (bufferNode.type === NodeType.AbsolutePseudoClass) { + // collect the mark to the arg of AbsolutePseudoClass node + updateBufferNode(context, tokenValue); + // 'isRegexpOpen' flag is needed for brackets balancing inside extended pseudo-class arg + if (tokenValue === SLASH && prevTokenValue !== BACKSLASH) { + context.isRegexpOpen = context.extendedPseudoNamesStack.length > 0 + && !context.isRegexpOpen; + } + } else if (bufferNode.type === NodeType.RelativePseudoClass) { + // add SelectorList to children of RelativePseudoClass node + initRelativeSubtree(context, tokenValue); + } else if (bufferNode.type === NodeType.Selector) { + // after the extended pseudo closing parentheses + // parser position is on Selector node + // and regular selector can be after the extended one + // e.g. '.banner:upward(2)> .block' + // or '.inner:nth-ancestor(1)~ .banner' + if (COMBINATORS.includes(tokenValue)) { + addAstNodeByType(context, NodeType.RegularSelector, tokenValue); + } else if (!context.isRegexpOpen) { + // it might be complex selector with extended pseudo-class inside it. + // parser position is on `.` now: + // e.g. 'div:has(img).banner' + // so we need to get last regular selector node and update its value + bufferNode = getLastRegularSelectorNode(context); + updateBufferNode(context, tokenValue); + } + } else if (bufferNode.type === NodeType.SelectorList) { + // add Selector to SelectorList + addAstNodeByType(context, NodeType.Selector); + // and RegularSelector as it is always the first child of Selector + addAstNodeByType(context, NodeType.RegularSelector, tokenValue); + } + break; + case BRACKETS.SQUARE.RIGHT: + if (bufferNode?.type === NodeType.RegularSelector) { + // needed for proper parsing regular selectors after the attributes with comma + // e.g. 'div[data-comma="0,1"] > img' + context.isAttributeBracketsOpen = false; + // collect the bracket to the value of RegularSelector node + updateBufferNode(context, tokenValue); + } + if (bufferNode?.type === NodeType.AbsolutePseudoClass) { + // :xpath() expended pseudo-class arg might contain square bracket + // so it should be collected + // e.g. 'div:xpath(//h3[contains(text(),"Share it!")]/..)' + updateBufferNode(context, tokenValue); + } + break; + case COLON: + // No white space is allowed between the colon and the following name of the pseudo-class + // https://www.w3.org/TR/selectors-4/#pseudo-classes + // e.g. 'span: contains(text)' + if (WHITE_SPACE_CHARACTERS.includes(nextTokenValue) + && SUPPORTED_PSEUDO_CLASSES.includes(nextToNextTokenValue)) { + throw new Error(`No white space is allowed before or after extended pseudo-class name in selector: '${selector}'`); // eslint-disable-line max-len + } + if (bufferNode === null) { + // no ast collecting has been started + if (nextTokenValue === XPATH_PSEUDO_CLASS_MARKER) { + // limit applying of "naked" :xpath pseudo-class + // https://github.com/AdguardTeam/ExtendedCss/issues/115 + initAst(context, XPATH_PSEUDO_SELECTING_ROOT); + } else if (nextTokenValue === IS_PSEUDO_CLASS_MARKER + || nextTokenValue === NOT_PSEUDO_CLASS_MARKER) { + // parent element checking is used for extended pseudo-class :is() and :not(). + // as there is no parentNode for root element (html) + // element selection should be limited to it's children. + // e.g. ':is(.page, .main) > .banner' + // or ':not(span):not(p)' + initAst(context, IS_OR_NOT_PSEUDO_SELECTING_ROOT); + } else if (nextTokenValue === UPWARD_PSEUDO_CLASS_MARKER + || nextTokenValue === NTH_ANCESTOR_PSEUDO_CLASS_MARKER) { + // selector should be specified before :nth-ancestor() or :upward() + // e.g. ':nth-ancestor(3)' + // or ':upward(span)' + throw new Error(`Selector should be specified before :${nextTokenValue}() pseudo-class`); // eslint-disable-line max-len + } else { + // make it more obvious if selector starts with pseudo with no tag specified + // e.g. ':has(a)' -> '*:has(a)' + // or ':empty' -> '*:empty' + initAst(context, ASTERISK); + } + + // bufferNode should be updated for following checking + bufferNode = getBufferNode(context); + } + + if (!bufferNode) { + throw new Error('bufferNode has to be specified by now'); + } + + if (bufferNode.type === NodeType.SelectorList) { + // bufferNode is SelectorList after comma has been parsed. + // parser position is on colon now: + // e.g. 'img,:not(.content)' + addAstNodeByType(context, NodeType.Selector); + // add empty value RegularSelector anyway as any selector should start with it + // and check previous token on the next step + addAstNodeByType(context, NodeType.RegularSelector); + // bufferNode should be updated for following checking + bufferNode = getBufferNode(context); + } + if (bufferNode?.type === NodeType.RegularSelector) { + // it can be extended or standard pseudo + // e.g. '#share, :contains(share it)' + // or 'div,:hover' + // of 'div:has(+:contains(text))' // position is after '+' + if (COMBINATORS.includes(prevTokenValue) + || prevTokenValue === COMMA) { + // case with colon at the start of string - e.g. ':contains(text)' + // is covered by 'bufferNode === null' above at start of COLON checking + updateBufferNode(context, ASTERISK); + } + handleNextTokenOnColon(context, selector, tokenValue, nextTokenValue, nextToNextTokenValue); + } + if (bufferNode?.type === NodeType.Selector) { + // after the extended pseudo closing parentheses + // parser position is on Selector node + // and there is might be another extended selector. + // parser position is on colon before 'upward': + // e.g. 'p:contains(PR):upward(2)' + if (isSupportedExtendedPseudo(nextTokenValue.toLowerCase())) { + // if supported extended pseudo-class is next to colon + // add ExtendedSelector to Selector children + addAstNodeByType(context, NodeType.ExtendedSelector); + } else if (nextTokenValue.toLowerCase() === REMOVE_PSEUDO_MARKER) { + // :remove() pseudo-class should be handled before + // as it is not about element selecting but actions with elements + // e.g. '#banner:upward(2):remove()' + throw new Error(`Selector parser error: invalid :remove() pseudo-class in selector: '${selector}'`); // eslint-disable-line max-len + } else { + // otherwise it is standard pseudo after extended pseudo-class in complex selector + // and colon should be collected to value of previous RegularSelector + // e.g. 'body *:not(input)::selection' + // 'input:matches-css(padding: 10):checked' + bufferNode = getLastRegularSelectorNode(context); + handleNextTokenOnColon(context, selector, tokenValue, nextTokenType, nextToNextTokenValue); // eslint-disable-line max-len + } + } + if (bufferNode?.type === NodeType.AbsolutePseudoClass) { + // collecting arg for absolute pseudo-class + // e.g. 'div:matches-css(width:400px)' + updateBufferNode(context, tokenValue); + } + if (bufferNode?.type === NodeType.RelativePseudoClass) { + // make it more obvious if selector starts with pseudo with no tag specified + // parser position is on colon inside :has() arg + // e.g. 'div:has(:contains(text))' + // or 'div:not(:empty)' + initRelativeSubtree(context, ASTERISK); + if (!isSupportedExtendedPseudo(nextTokenValue.toLowerCase())) { + // collect the colon to value of RegularSelector + // e.g. 'div:not(:empty)' + updateBufferNode(context, tokenValue); + // parentheses should be balanced only for functional pseudo-classes + // e.g. '.yellow:not(:nth-child(3))' + if (nextToNextTokenValue === BRACKETS.PARENTHESES.LEFT) { + context.standardPseudoNamesStack.push(nextTokenValue); + } + } else { + // add ExtendedSelector to Selector children + // e.g. 'div:has(:contains(text))' + upToClosest(context, NodeType.Selector); + addAstNodeByType(context, NodeType.ExtendedSelector); + } + } + break; + case BRACKETS.PARENTHESES.LEFT: + // start of pseudo-class arg + if (bufferNode?.type === NodeType.AbsolutePseudoClass) { + // no brackets balancing needed inside + // 1. :xpath() extended pseudo-class arg + // 2. regexp arg for other extended pseudo-classes + if (bufferNode.name !== XPATH_PSEUDO_CLASS_MARKER && context.isRegexpOpen) { + // if the parentheses is escaped it should be part of regexp + // collect it to arg of AbsolutePseudoClass + // e.g. 'div:matches-css(background-image: /^url\\("data:image\\/gif;base64.+/)' + updateBufferNode(context, tokenValue); + } else { + // otherwise brackets should be balanced + // e.g. 'div:xpath(//h3[contains(text(),"Share it!")]/..)' + context.extendedPseudoBracketsStack.push(tokenValue); + // eslint-disable-next-line max-len + if (context.extendedPseudoBracketsStack.length > context.extendedPseudoNamesStack.length) { + updateBufferNode(context, tokenValue); + } + } + } + if (bufferNode?.type === NodeType.RegularSelector) { + // continue RegularSelector value collecting for standard pseudo-classes + // e.g. '.banner:where(div)' + if (context.standardPseudoNamesStack.length > 0) { + updateBufferNode(context, tokenValue); + context.standardPseudoBracketsStack.push(tokenValue); + } + } + if (bufferNode?.type === NodeType.RelativePseudoClass) { + // save opening bracket for balancing + // e.g. 'div:not()' // position is on `(` + context.extendedPseudoBracketsStack.push(tokenValue); + } + break; + case BRACKETS.PARENTHESES.RIGHT: + if (bufferNode?.type === NodeType.AbsolutePseudoClass) { + // no brackets balancing needed inside + // 1. :xpath() extended pseudo-class arg + // 2. regexp arg for other extended pseudo-classes + if (bufferNode.name !== XPATH_PSEUDO_CLASS_MARKER && context.isRegexpOpen) { + // if closing bracket is part of regexp + // simply save it to pseudo-class arg + updateBufferNode(context, tokenValue); + } else { + // remove stacked open parentheses for brackets balance + // e.g. 'h3:contains((Ads))' + // or 'div:xpath(//h3[contains(text(),"Share it!")]/..)' + context.extendedPseudoBracketsStack.pop(); + if (bufferNode.name !== XPATH_PSEUDO_CLASS_MARKER) { + // for all other absolute pseudo-classes except :xpath() + // remove stacked name of extended pseudo-class + context.extendedPseudoNamesStack.pop(); + if (context.extendedPseudoBracketsStack.length > context.extendedPseudoNamesStack.length) { // eslint-disable-line max-len + // if brackets stack is not empty yet, + // save tokenValue to arg of AbsolutePseudoClass + // parser position on first closing bracket after 'Ads': + // e.g. 'h3:contains((Ads))' + updateBufferNode(context, tokenValue); + } else if (context.extendedPseudoBracketsStack.length >= 0 + && context.extendedPseudoNamesStack.length >= 0) { + // assume it is combined extended pseudo-classes + // parser position on first closing bracket after 'advert': + // e.g. 'div:has(.banner, :contains(advert))' + upToClosest(context, NodeType.Selector); + } + } else { + // for :xpath() + if (context.extendedPseudoBracketsStack.length < context.extendedPseudoNamesStack.length) { // eslint-disable-line max-len + // remove stacked name of extended pseudo-class + // if there are less brackets than pseudo-class names + // with means last removes bracket was closing for pseudo-class + context.extendedPseudoNamesStack.pop(); + } else { + // otherwise the bracket is part of arg + updateBufferNode(context, tokenValue); + } + } + } + } + if (bufferNode?.type === NodeType.RegularSelector) { + if (context.standardPseudoNamesStack.length > 0 + && context.standardPseudoBracketsStack.length > 0) { + // standard pseudo-class was processing. + // collect the closing bracket to value of RegularSelector + // parser position is on bracket after 'class' now: + // e.g. 'div:where(.class)' + updateBufferNode(context, tokenValue); + // remove bracket and pseudo name from stacks + context.standardPseudoBracketsStack.pop(); + const lastStandardPseudo = context.standardPseudoNamesStack.pop(); + if (!lastStandardPseudo) { + // standard pseudo should be in standardPseudoNamesStack + // as related to standardPseudoBracketsStack + throw new Error(`Parsing error. Invalid selector: ${selector}`); + } + // Disallow :has() after regular pseudo-elements + // https://bugs.chromium.org/p/chromium/issues/detail?id=669058#c54 [3] + if (Object.values(REGULAR_PSEUDO_ELEMENTS).includes(lastStandardPseudo) + // check token which is next to closing parentheses and token after it + // parser position is on bracket after 'foo' now: + // e.g. '::part(foo):has(.a)' + && nextTokenValue === COLON + && nextToNextTokenValue + && HAS_PSEUDO_CLASS_MARKERS.includes(nextToNextTokenValue)) { + // eslint-disable-next-line max-len + throw new Error(`Usage of :${nextToNextTokenValue}() pseudo-class is not allowed after any regular pseudo-element: '${lastStandardPseudo}'`); + } + } else { + // extended pseudo-class was processing. + // e.g. 'div:has(h3)' + // remove bracket and pseudo name from stacks + context.extendedPseudoBracketsStack.pop(); + context.extendedPseudoNamesStack.pop(); + upToClosest(context, NodeType.ExtendedSelector); + // go to upper selector for possible selector continuation after extended pseudo-class + // e.g. 'div:has(h3) > img' + upToClosest(context, NodeType.Selector); + } + } + if (bufferNode?.type === NodeType.Selector) { + // after inner extended pseudo-class bufferNode is Selector. + // parser position is on last bracket now: + // e.g. 'div:has(.banner, :contains(ads))' + context.extendedPseudoBracketsStack.pop(); + context.extendedPseudoNamesStack.pop(); + upToClosest(context, NodeType.ExtendedSelector); + upToClosest(context, NodeType.Selector); + } + if (bufferNode?.type === NodeType.RelativePseudoClass) { + // save opening bracket for balancing + // e.g. 'div:not()' // position is on `)` + // context.extendedPseudoBracketsStack.push(tokenValue); + if (context.extendedPseudoNamesStack.length > 0 + && context.extendedPseudoBracketsStack.length > 0) { + context.extendedPseudoBracketsStack.pop(); + context.extendedPseudoNamesStack.pop(); + } + } + break; + case TAB: + case LINE_FEED: + case FORM_FEED: + case CARRIAGE_RETURN: + // such characters at start and end of selector should be trimmed + // so is there is one them among tokens, it is not valid selector + throw new Error(`'${selector}' is not a valid selector.`); + } + break; + // no default statement for Marks as they are limited to SUPPORTED_SELECTOR_MARKS + // and all other symbol combinations are tokenized as Word + // so error for invalid Word will be thrown later while element selecting by parsed ast + default: + throw new Error(`Unknown type of token: '${tokenValue}'.`); + } + + i += 1; + } + + if (context.ast === null) { + throw new Error(`'${selector}' is not a valid selector`); + } + + if (context.extendedPseudoNamesStack.length > 0 + || context.extendedPseudoBracketsStack.length > 0) { + // eslint-disable-next-line max-len + throw new Error(`Unbalanced brackets for extended pseudo-class: '${getLast(context.extendedPseudoNamesStack)}'`); + } + + if (context.isAttributeBracketsOpen) { + throw new Error(`Unbalanced brackets for attributes is selector: '${selector}'`); + } + + return context.ast; +}; diff --git a/src/selector/query.ts b/src/selector/query.ts new file mode 100644 index 00000000..76f0d212 --- /dev/null +++ b/src/selector/query.ts @@ -0,0 +1,101 @@ +import { parse } from './parser'; + +import { AnySelectorNodeInterface } from './nodes'; + +import { getElementsForSelectorNode } from './utils/query-helpers'; + +import { flatten } from '../common/utils/arrays'; + +/** + * Selects elements by ast. + * + * @param ast Ast of parsed selector. + * @param doc Document. + */ +export const selectElementsByAst = (ast: AnySelectorNodeInterface, doc = document): HTMLElement[] => { + const selectedElements: HTMLElement[] = []; + // ast root is SelectorList node; + // it has Selector nodes as children which should be processed separately + ast.children.forEach((selectorNode: AnySelectorNodeInterface) => { + selectedElements.push(...getElementsForSelectorNode(selectorNode, doc)); + }); + // selectedElements should be flattened as it is array of arrays with elements + const uniqueElements = [...new Set(flatten(selectedElements))]; + return uniqueElements; +}; + +/** + * Selects elements by selector. + * + * @param selector Standard or extended selector. + */ +export const querySelectorAll = (selector: string): HTMLElement[] => { + const ast = parse(selector); + return selectElementsByAst(ast); +}; + +/** + * Class of ExtCssDocument is needed for caching. + * For making cache related to each new instance of class, not global. + */ +export class ExtCssDocument { + /** + * Cache with selectors and their AST parsing results. + */ + private readonly astCache: Map; + + /** + * Creates new ExtCssDocument and inits new `astCache`. + */ + constructor() { + this.astCache = new Map(); + } + + /** + * Saves selector and it's ast to cache. + * + * @param selector Standard or extended selector. + * @param ast Selector ast. + */ + private saveAstToCache(selector: string, ast: AnySelectorNodeInterface): void { + this.astCache.set(selector, ast); + } + + /** + * Gets ast from cache for given selector. + * + * @param selector Standard or extended selector. + */ + private getAstFromCache(selector: string): AnySelectorNodeInterface | null { + const cachedAst = this.astCache.get(selector) || null; + return cachedAst; + } + + /** + * Gets selector ast: + * - if cached ast exists — returns it; + * - if no cached ast — saves newly parsed ast to cache and returns it. + * + * @param selector Standard or extended selector. + */ + getSelectorAst(selector: string): AnySelectorNodeInterface { + let ast = this.getAstFromCache(selector); + if (!ast) { + ast = parse(selector); + } + this.saveAstToCache(selector, ast); + return ast; + } + + /** + * Selects elements by selector. + * + * @param selector Standard or extended selector. + */ + querySelectorAll(selector: string): HTMLElement[] { + const ast = this.getSelectorAst(selector); + return selectElementsByAst(ast); + } +} + +export const extCssDocument = new ExtCssDocument(); diff --git a/src/selector/tokenizer.ts b/src/selector/tokenizer.ts new file mode 100644 index 00000000..ce93b400 --- /dev/null +++ b/src/selector/tokenizer.ts @@ -0,0 +1,47 @@ +import { convert } from './converter'; + +import { SUPPORTED_SELECTOR_MARKS } from '../common/constants'; + +export enum TokenType { + Mark = 'mark', + Word = 'word', +} + +export interface Token { + type: TokenType; + value: string; +} + +/** + * Splits selector string into tokens. + * + * @param rawSelector Raw css selector. + */ +export const tokenize = (rawSelector: string): Token[] => { + const selector = convert(rawSelector); + + // currently processed + let symbol; + // for words collecting while iterating + let buffer = ''; + // result collection + const tokens: Token[] = []; + + // iterate selector chars and collect tokens + for (let i = 0; i < selector.length; i += 1) { + symbol = selector[i]; + if (SUPPORTED_SELECTOR_MARKS.includes(symbol)) { + tokens.push({ type: TokenType.Mark, value: symbol }); + continue; + } + buffer += symbol; + const nextSymbol = selector[i + 1]; + // string end has been reached if nextSymbol is undefined + if (!nextSymbol || SUPPORTED_SELECTOR_MARKS.includes(nextSymbol)) { + tokens.push({ type: TokenType.Word, value: buffer }); + buffer = ''; + } + } + + return tokens; +}; diff --git a/src/selector/utils/absolute-finder.ts b/src/selector/utils/absolute-finder.ts new file mode 100644 index 00000000..58066887 --- /dev/null +++ b/src/selector/utils/absolute-finder.ts @@ -0,0 +1,54 @@ +/** + * Validates number arg for :nth-ancestor() and :upward() pseudo-classes. + * + * @param rawArg Raw arg of pseudo-class. + * @param pseudoName Pseudo-class name. + * + * @throws An error on invalid `rawArg`. + */ +export const getValidNumberAncestorArg = (rawArg: string, pseudoName: string): number => { + const deep = Number(rawArg); + if (Number.isNaN(deep) || deep < 1 || deep >= 256) { + throw new Error(`Invalid argument of :${pseudoName} pseudo-class: '${rawArg}'`); + } + return deep; +}; + +/** + * Returns nth ancestor by 'deep' number arg OR undefined if ancestor range limit exceeded. + * + * @param domElement DOM element to find ancestor for. + * @param nth Depth up to needed ancestor. + * @param pseudoName Pseudo-class name. + * + * @throws An error on invalid `nth` arg. + */ +export const getNthAncestor = (domElement: HTMLElement, nth: number, pseudoName: string): HTMLElement | null => { + let ancestor: HTMLElement | null = null; + let i = 0; + while (i < nth) { + ancestor = domElement.parentElement; + if (!ancestor) { + throw new Error(`Argument of :${pseudoName}() pseudo-class is too big — '${nth}', out of DOM elements root.`); // eslint-disable-line max-len + } + domElement = ancestor; + i += 1; + } + return ancestor; +}; + +/** + * Validates standard CSS selector. + * + * @param selector Standard selector. + */ +export const validateStandardSelector = (selector: string): boolean => { + let isValid: boolean; + try { + document.querySelectorAll(selector); + isValid = true; + } catch (e) { + isValid = false; + } + return isValid; +}; diff --git a/src/selector/utils/absolute-matcher.ts b/src/selector/utils/absolute-matcher.ts new file mode 100644 index 00000000..bc0f44ac --- /dev/null +++ b/src/selector/utils/absolute-matcher.ts @@ -0,0 +1,657 @@ +import { + convertTypeFromString, + convertTypeIntoString, + replaceAll, + toRegExp, +} from '../../common/utils/strings'; +import { logger } from '../../common/utils/logger'; +import { isSafariBrowser } from '../../common/utils/user-agents'; +import { getNodeTextContent } from '../../common/utils/nodes'; + +import { + ASTERISK, + DOT, + COLON, + DOUBLE_QUOTE, + EQUAL_SIGN, + SLASH, + COMMA, + REGULAR_PSEUDO_ELEMENTS, +} from '../../common/constants'; + +enum CssProperty { + Background = 'background', + BackgroundImage = 'background-image', + Content = 'content', + Opacity = 'opacity', +} + +const REGEXP_ANY_SYMBOL = '.*'; + +const REGEXP_WITH_FLAGS_REGEXP = /^\s*\/.*\/[gmisuy]*\s*$/; + +export interface MatcherArgsInterface { + /** + * Extended pseudo-class name. + */ + pseudoName: string; + + /** + * Extended pseudo-class arg. + */ + pseudoArg: string; + + /** + * Dom element to check. + */ + domElement: Element; +} + +/** + * Removes quotes for specified content value. + * + * For example, content style declaration with `::before` can be set as '-' (e.g. unordered list) + * which displayed as simple dash `-` with no quotes. + * But CSSStyleDeclaration.getPropertyValue('content') will return value + * wrapped into quotes, e.g. '"-"', which should be removed + * because filters maintainers does not use any quotes in real rules. + * + * @param str Input string. + */ +const removeContentQuotes = (str: string): string => { + return str.replace(/^(["'])([\s\S]*)\1$/, '$2'); +}; + +/** + * Adds quotes for specified background url value. + * + * If background-image is specified **without** quotes: + * e.g. 'background: url(data:image/gif;base64,R0lGODlhAQA7)'. + * + * CSSStyleDeclaration.getPropertyValue('background-image') may return value **with** quotes: + * e.g. 'background: url("data:image/gif;base64,R0lGODlhAQA7")'. + * + * So we add quotes for compatibility since filters maintainers might use quotes in real rules. + * + * @param str Input string. + */ +const addUrlPropertyQuotes = (str: string): string => { + if (!str.includes('url("')) { + const re = /url\((.*?)\)/g; + return str.replace(re, 'url("$1")'); + } + return str; +}; + +/** + * Adds quotes to url arg for consistent property value matching. + */ +const addUrlQuotesTo = { + regexpArg: (str: string): string => { + // e.g. /^url\\([a-z]{4}:[a-z]{5}/ + // or /^url\\(data\\:\\image\\/gif;base64.+/ + const re = /(\^)?url(\\)?\\\((\w|\[\w)/g; + return str.replace(re, '$1url$2\\(\\"?$3'); + }, + noneRegexpArg: addUrlPropertyQuotes, +}; + +/** + * Escapes regular expression string. + * + * @param str Input string. + */ +const escapeRegExp = (str: string): string => { + // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/regexp + // should be escaped . * + ? ^ $ { } ( ) | [ ] / \ + // except of * | ^ + const specials = ['.', '+', '?', '$', '{', '}', '(', ')', '[', ']', '\\', '/']; + const specialsRegex = new RegExp(`[${specials.join('\\')}]`, 'g'); + return str.replace(specialsRegex, '\\$&'); +}; + +/** + * Converts :matches-css() arg property value match to regexp. + * + * @param rawValue Style match value pattern. + */ +const convertStyleMatchValueToRegexp = (rawValue: string): RegExp => { + let value: string; + if (rawValue.startsWith(SLASH) && rawValue.endsWith(SLASH)) { + // For regex patterns double quotes `"` and backslashes `\` should be escaped + value = addUrlQuotesTo.regexpArg(rawValue); + value = value.slice(1, -1); + } else { + // For non-regex patterns parentheses `(` `)` and square brackets `[` `]` + // should be unescaped, because their escaping in filter rules is required + value = addUrlQuotesTo.noneRegexpArg(rawValue); + value = value.replace(/\\([\\()[\]"])/g, '$1'); + value = escapeRegExp(value); + // e.g. div:matches-css(background-image: url(data:*)) + value = replaceAll(value, ASTERISK, REGEXP_ANY_SYMBOL); + } + return new RegExp(value, 'i'); +}; + +/** + * Makes some properties values compatible. + * + * @param propertyName Name of style property. + * @param propertyValue Value of style property. + */ +const normalizePropertyValue = (propertyName: string, propertyValue: string): string => { + let normalized = ''; + switch (propertyName) { + case CssProperty.Background: + case CssProperty.BackgroundImage: + // sometimes url property does not have quotes + // so we add them for consistent matching + normalized = addUrlPropertyQuotes(propertyValue); + break; + case CssProperty.Content: + normalized = removeContentQuotes(propertyValue); + break; + case CssProperty.Opacity: + // https://bugs.webkit.org/show_bug.cgi?id=93445 + normalized = isSafariBrowser + ? (Math.round(parseFloat(propertyValue) * 100) / 100).toString() + : propertyValue; + break; + default: + normalized = propertyValue; + } + return normalized; +}; + +/** + * Gets domElement style property value + * by css property name and standard pseudo-element. + * + * @param domElement DOM element. + * @param propertyName CSS property name. + * @param regularPseudoElement Standard pseudo-element — :before, :after etc. + */ +const getComputedStylePropertyValue = ( + domElement: Element, + propertyName: string, + regularPseudoElement: string | null, +): string => { + const style = getComputedStyle(domElement, regularPseudoElement); + const propertyValue = style.getPropertyValue(propertyName); + return normalizePropertyValue(propertyName, propertyValue); +}; + +interface PseudoArgData { + name: string; + value?: string; +} + +/** + * Parses arg of absolute pseudo-class into 'name' and 'value' if set. + * + * Used for :matches-css() - with COLON as separator, + * for :matches-attr() and :matches-property() - with EQUAL_SIGN as separator. + * + * @param pseudoArg Arg of pseudo-class. + * @param separator Divider symbol. + */ +const getPseudoArgData = (pseudoArg: string, separator: string): PseudoArgData => { + const index = pseudoArg.indexOf(separator); + let name: string; + let value: string | undefined; + + if (index > -1) { + name = pseudoArg.substring(0, index).trim(); + value = pseudoArg.substring(index + 1).trim(); + } else { + name = pseudoArg; + } + + return { name, value }; +}; + +interface MatchesCssArgData { + regularPseudoElement: string | null; + styleMatchArg: string; +} + +/** + * Parses :matches-css() pseudo-class arg + * where regular pseudo-element can be a part of arg + * e.g. 'div:matches-css(before, color: rgb(255, 255, 255))' <-- obsolete `:matches-css-before()`. + * + * @param pseudoName Pseudo-class name. + * @param rawArg Pseudo-class arg. + * + * @throws An error on invalid `rawArg`. + */ +const parseStyleMatchArg = (pseudoName: string, rawArg: string): MatchesCssArgData => { + const { name, value } = getPseudoArgData(rawArg, COMMA); + + let regularPseudoElement: string | null = name; + let styleMatchArg: string | undefined = value; + + // check whether the string part before the separator is valid regular pseudo-element, + // otherwise `regularPseudoElement` is null, and `styleMatchArg` is rawArg + if (!Object.values(REGULAR_PSEUDO_ELEMENTS).includes(name)) { + regularPseudoElement = null; + styleMatchArg = rawArg; + } + + if (!styleMatchArg) { + throw new Error(`Required style property argument part is missing in :${pseudoName}() arg: '${rawArg}'`); + } + + return { regularPseudoElement, styleMatchArg }; +}; + +/** + * Checks whether the domElement is matched by :matches-css() arg. + * + * @param argsData Pseudo-class name, arg, and dom element to check. + * + * @throws An error on invalid pseudo-class arg. + */ +export const isStyleMatched = (argsData: MatcherArgsInterface): boolean => { + const { pseudoName, pseudoArg, domElement } = argsData; + + const { regularPseudoElement, styleMatchArg } = parseStyleMatchArg(pseudoName, pseudoArg); + + const { name: matchName, value: matchValue } = getPseudoArgData(styleMatchArg, COLON); + if (!matchName || !matchValue) { + throw new Error(`Required property name or value is missing in :${pseudoName}() arg: '${styleMatchArg}'`); + } + + let valueRegexp: RegExp; + try { + valueRegexp = convertStyleMatchValueToRegexp(matchValue); + } catch (e) { + logger.error(e); + throw new Error(`Invalid argument of :${pseudoName}() pseudo-class: '${styleMatchArg}'`); + } + + const value = getComputedStylePropertyValue(domElement, matchName, regularPseudoElement); + + return valueRegexp && valueRegexp.test(value); +}; + +/** + * Validates string arg for :matches-attr() and :matches-property(). + * + * @param arg Pseudo-class arg. + */ +const validateStrMatcherArg = (arg: string): boolean => { + if (arg.includes(SLASH)) { + return false; + } + if (!(/^[\w-]+$/.test(arg))) { + return false; + } + return true; +}; + +/** + * Returns valid arg for :matches-attr and :matcher-property. + * + * @param rawArg Arg pattern. + * @param [isWildcardAllowed=false] Flag for wildcard (`*`) using as pseudo-class arg. + * + * @throws An error on invalid `rawArg`. + */ +export const getValidMatcherArg = (rawArg: string, isWildcardAllowed = false): string | RegExp => { + // if rawArg is missing for pseudo-class + // e.g. :matches-attr() + // error will be thrown before getValidMatcherArg() is called: + // name or arg is missing in AbsolutePseudoClass + + let arg: string | RegExp; + if (rawArg.length > 1 && rawArg.startsWith(DOUBLE_QUOTE) && rawArg.endsWith(DOUBLE_QUOTE)) { + rawArg = rawArg.slice(1, -1); + } + if (rawArg === '') { + // e.g. :matches-property("") + throw new Error('Argument should be specified. Empty arg is invalid.'); + } + if (rawArg.startsWith(SLASH) && rawArg.endsWith(SLASH)) { + // e.g. :matches-property("//") + if (rawArg.length > 2) { + arg = toRegExp(rawArg); + } else { + throw new Error(`Invalid regexp: '${rawArg}'`); + } + } else if (rawArg.includes(ASTERISK)) { + if (rawArg === ASTERISK && !isWildcardAllowed) { + // e.g. :matches-attr(*) + throw new Error(`Argument should be more specific than ${rawArg}`); + } + arg = replaceAll(rawArg, ASTERISK, REGEXP_ANY_SYMBOL); + arg = new RegExp(arg); + } else { + if (!validateStrMatcherArg(rawArg)) { + throw new Error(`Invalid argument: '${rawArg}'`); + } + arg = rawArg; + } + return arg; +}; + +interface RawMatchingArgData { + rawName: string; + rawValue?: string; +} + +/** + * Parses pseudo-class argument and returns parsed data. + * + * @param pseudoName Extended pseudo-class name. + * @param pseudoArg Extended pseudo-class argument. + * + * @throws An error if attribute name is missing in pseudo-class arg. + */ +export const getRawMatchingData = (pseudoName: string, pseudoArg: string): RawMatchingArgData => { + const { name: rawName, value: rawValue } = getPseudoArgData(pseudoArg, EQUAL_SIGN); + if (!rawName) { + throw new Error(`Required attribute name is missing in :${pseudoName} arg: ${pseudoArg}`); + } + return { rawName, rawValue }; +}; + +/** + * Checks whether the domElement is matched by :matches-attr() arg. + * + * @param argsData Pseudo-class name, arg, and dom element to check. + * + * @throws An error on invalid arg of pseudo-class. + */ +export const isAttributeMatched = (argsData: MatcherArgsInterface): boolean => { + const { pseudoName, pseudoArg, domElement } = argsData; + const elementAttributes = domElement.attributes; + // no match if dom element has no attributes + if (elementAttributes.length === 0) { + return false; + } + + const { rawName: rawAttrName, rawValue: rawAttrValue } = getRawMatchingData(pseudoName, pseudoArg); + + let attrNameMatch: string | RegExp; + try { + attrNameMatch = getValidMatcherArg(rawAttrName); + } catch (e: any) { // eslint-disable-line @typescript-eslint/no-explicit-any + logger.error(e); + throw new SyntaxError(e.message); + } + + let isMatched = false; + let i = 0; + while (i < elementAttributes.length && !isMatched) { + const attr = elementAttributes[i]; + + const isNameMatched = attrNameMatch instanceof RegExp + ? attrNameMatch.test(attr.name) + : attrNameMatch === attr.name; + + if (!rawAttrValue) { + // for rules with no attribute value specified + // e.g. :matches-attr("/regex/") or :matches-attr("attr-name") + isMatched = isNameMatched; + } else { + let attrValueMatch: string | RegExp; + try { + attrValueMatch = getValidMatcherArg(rawAttrValue); + } catch (e: any) { // eslint-disable-line @typescript-eslint/no-explicit-any + logger.error(e); + throw new SyntaxError(e.message); + } + const isValueMatched = attrValueMatch instanceof RegExp + ? attrValueMatch.test(attr.value) + : attrValueMatch === attr.value; + isMatched = isNameMatched && isValueMatched; + } + + i += 1; + } + + return isMatched; +}; + +/** + * Parses raw :matches-property() arg which may be chain of properties. + * + * @param input Argument of :matches-property(). + * + * @throws An error on invalid chain. + */ +export const parseRawPropChain = (input: string): (string | RegExp)[] => { + if (input.length > 1 && input.startsWith(DOUBLE_QUOTE) && input.endsWith(DOUBLE_QUOTE)) { + input = input.slice(1, -1); + } + + const chainChunks = input.split(DOT); + + const chainPatterns: string[] = []; + let patternBuffer = ''; + let isRegexpPattern = false; + let i = 0; + while (i < chainChunks.length) { + const chunk = chainChunks[i]; + if (chunk.startsWith(SLASH) && chunk.endsWith(SLASH) && chunk.length > 2) { + // regexp pattern with no dot in it, e.g. /propName/ + chainPatterns.push(chunk); + } else if (chunk.startsWith(SLASH)) { + // if chunk is a start of regexp pattern + isRegexpPattern = true; + patternBuffer += chunk; + } else if (chunk.endsWith(SLASH)) { + isRegexpPattern = false; + // restore dot removed while splitting + // e.g. testProp./.{1,5}/ + patternBuffer += `.${chunk}`; + chainPatterns.push(patternBuffer); + patternBuffer = ''; + } else { + // if there are few dots in regexp pattern + // so chunk might be in the middle of it + if (isRegexpPattern) { + patternBuffer += chunk; + } else { + // otherwise it is string pattern + chainPatterns.push(chunk); + } + } + i += 1; + } + + if (patternBuffer.length > 0) { + throw new Error(`Invalid regexp property pattern '${input}'`); + } + + const chainMatchPatterns = chainPatterns + .map((pattern) => { + if (pattern.length === 0) { + // e.g. '.prop.id' or 'nested..test' + throw new Error(`Empty pattern '${pattern}' is invalid in chain '${input}'`); + } + let validPattern: string | RegExp; + try { + validPattern = getValidMatcherArg(pattern, true); + } catch (e) { + logger.error(e); + throw new Error(`Invalid property pattern '${pattern}' in property chain '${input}'`); + } + return validPattern; + }); + + return chainMatchPatterns; +}; + +interface Chain { + base: Element; + prop: string; + value?: Element[keyof Element]; +} + +/** + * Checks if the property exists in the base object (recursively). + * + * @param base Element to check. + * @param chain Array of objects - parsed string property chain. + * @param [output=[]] Result acc. + */ +const filterRootsByRegexpChain = (base: Element, chain: (string | RegExp)[], output: Chain[] = []): Chain[] => { + const tempProp = chain[0]; + + if (chain.length === 1) { + let key: keyof Element; + for (key in base) { + if (tempProp instanceof RegExp) { + if (tempProp.test(key)) { + output.push({ + base, + prop: key, + value: base[key], + }); + } + } else if (tempProp === key) { + output.push({ + base, + prop: tempProp, + value: base[key], + }); + } + } + return output; + } + + // if there is a regexp prop in input chain + // e.g. 'unit./^ad.+/.src' for 'unit.ad-1gf2.src unit.ad-fgd34.src'), + // every base keys should be tested by regexp and it can be more that one results + if (tempProp instanceof RegExp) { + const nextProp = chain.slice(1); + const baseKeys: string[] = []; + for (const key in base) { + if (tempProp.test(key)) { + baseKeys.push(key); + } + } + baseKeys.forEach((key) => { + const item = Object.getOwnPropertyDescriptor(base, key)?.value; + filterRootsByRegexpChain(item, nextProp, output); + }); + } + + if (base && typeof tempProp === 'string') { + const nextBase = Object.getOwnPropertyDescriptor(base, tempProp)?.value; + chain = chain.slice(1); + if (nextBase !== undefined) { + filterRootsByRegexpChain(nextBase, chain, output); + } + } + + return output; +}; + +/** + * Checks whether the domElement is matched by :matches-property() arg. + * + * @param argsData Pseudo-class name, arg, and dom element to check. + * + * @throws An error on invalid prop in chain. + */ +export const isPropertyMatched = (argsData: MatcherArgsInterface): boolean => { + const { pseudoName, pseudoArg, domElement } = argsData; + const { rawName: rawPropertyName, rawValue: rawPropertyValue } = getRawMatchingData(pseudoName, pseudoArg); + + // chained property name can not include '/' or '.' + // so regex prop names with such escaped characters are invalid + if (rawPropertyName.includes('\\/') + || rawPropertyName.includes('\\.')) { + throw new Error(`Invalid :${pseudoName} name pattern: ${rawPropertyName}`); + } + + let propChainMatches: (string | RegExp)[]; + try { + propChainMatches = parseRawPropChain(rawPropertyName); + } catch (e: any) { // eslint-disable-line @typescript-eslint/no-explicit-any + logger.error(e); + throw new SyntaxError(e.message); + } + + const ownerObjArr = filterRootsByRegexpChain(domElement, propChainMatches); + if (ownerObjArr.length === 0) { + return false; + } + + let isMatched = true; + + if (rawPropertyValue) { + let propValueMatch: string | RegExp; + try { + propValueMatch = getValidMatcherArg(rawPropertyValue); + } catch (e: any) { // eslint-disable-line @typescript-eslint/no-explicit-any + logger.error(e); + throw new SyntaxError(e.message); + } + + if (propValueMatch) { + for (let i = 0; i < ownerObjArr.length; i += 1) { + const realValue = ownerObjArr[i].value; + + if (propValueMatch instanceof RegExp) { + isMatched = propValueMatch.test(convertTypeIntoString(realValue)); + } else { + // handle 'null' and 'undefined' property values set as string + if (realValue === 'null' || realValue === 'undefined') { + isMatched = propValueMatch === realValue; + break; + } + isMatched = convertTypeFromString(propValueMatch) === realValue; + } + + if (isMatched) { + break; + } + } + } + } + + return isMatched; +}; + +/** + * Checks whether the textContent is matched by :contains arg. + * + * @param argsData Pseudo-class name, arg, and dom element to check. + * + * @throws An error on invalid arg of pseudo-class. + */ +export const isTextMatched = (argsData: MatcherArgsInterface): boolean => { + const { pseudoName, pseudoArg, domElement } = argsData; + const textContent = getNodeTextContent(domElement); + let isTextContentMatched; + + let pseudoArgToMatch = pseudoArg; + + if (pseudoArgToMatch.startsWith(SLASH) + && REGEXP_WITH_FLAGS_REGEXP.test(pseudoArgToMatch)) { + // regexp arg + const flagsIndex = pseudoArgToMatch.lastIndexOf('/'); + const flagsStr = pseudoArgToMatch.substring(flagsIndex + 1); + pseudoArgToMatch = pseudoArgToMatch + .substring(0, flagsIndex + 1) + .slice(1, -1) + .replace(/\\([\\"])/g, '$1'); + let regex: RegExp; + try { + regex = new RegExp(pseudoArgToMatch, flagsStr); + } catch (e) { + throw new Error(`Invalid argument of :${pseudoName}() pseudo-class: ${pseudoArg}`); + } + isTextContentMatched = regex.test(textContent); + } else { + // none-regexp arg + pseudoArgToMatch = pseudoArgToMatch.replace(/\\([\\()[\]"])/g, '$1'); + isTextContentMatched = textContent.includes(pseudoArgToMatch); + } + + return isTextContentMatched; +}; diff --git a/src/selector/utils/absolute-processor.ts b/src/selector/utils/absolute-processor.ts new file mode 100644 index 00000000..8615e854 --- /dev/null +++ b/src/selector/utils/absolute-processor.ts @@ -0,0 +1,185 @@ +import { isHtmlElement } from '../../common/utils/nodes'; +import { flatten } from '../../common/utils/arrays'; +import { logger } from '../../common/utils/logger'; + +import { + isTextMatched, + isStyleMatched, + isAttributeMatched, + isPropertyMatched, + MatcherArgsInterface, +} from './absolute-matcher'; + +import { + getNthAncestor, + getValidNumberAncestorArg, + validateStandardSelector, +} from './absolute-finder'; + +import { + MATCHES_CSS_BEFORE_PSEUDO, + MATCHES_CSS_AFTER_PSEUDO, + CONTAINS_PSEUDO, + HAS_TEXT_PSEUDO, + ABP_CONTAINS_PSEUDO, + MATCHES_CSS_PSEUDO, + MATCHES_ATTR_PSEUDO_CLASS_MARKER, + MATCHES_PROPERTY_PSEUDO_CLASS_MARKER, +} from '../../common/constants'; + +type MatcherCallback = (a: MatcherArgsInterface) => boolean; + +/** + * Wrapper to run matcher `callback` with `args` + * and throw error with `errorMessage` if `callback` run fails. + * + * @param callback Matcher callback. + * @param argsData Args needed for matcher callback. + * @param errorMessage Error message. + * + * @throws An error if `callback` fails. + */ +const matcherWrapper = (callback: MatcherCallback, argsData: MatcherArgsInterface, errorMessage: string): boolean => { + let isMatched: boolean; + try { + isMatched = callback(argsData); + } catch (e) { + logger.error(e); + throw new Error(errorMessage); + } + return isMatched; +}; + +/** + * Checks whether the domElement is matched by absolute extended pseudo-class argument. + * + * @param domElement Page element. + * @param pseudoName Pseudo-class name. + * @param pseudoArg Pseudo-class arg. + * + * @throws An error on unknown absolute pseudo-class. + */ +export const isMatchedByAbsolutePseudo = (domElement: Element, pseudoName: string, pseudoArg: string): boolean => { + let argsData: MatcherArgsInterface; + let errorMessage: string; + let callback: MatcherCallback; + + switch (pseudoName) { + case CONTAINS_PSEUDO: + case HAS_TEXT_PSEUDO: + case ABP_CONTAINS_PSEUDO: + callback = isTextMatched; + argsData = { pseudoName, pseudoArg, domElement }; + errorMessage = `Error while matching element text content by arg '${pseudoArg}'.`; + break; + case MATCHES_CSS_PSEUDO: + case MATCHES_CSS_AFTER_PSEUDO: + case MATCHES_CSS_BEFORE_PSEUDO: + callback = isStyleMatched; + argsData = { pseudoName, pseudoArg, domElement }; + errorMessage = `Error while matching element style by arg '${pseudoArg}'.`; + break; + case MATCHES_ATTR_PSEUDO_CLASS_MARKER: + callback = isAttributeMatched; + argsData = { domElement, pseudoName, pseudoArg }; + errorMessage = `Error while matching element attributes by arg '${pseudoArg}'.`; + break; + case MATCHES_PROPERTY_PSEUDO_CLASS_MARKER: + callback = isPropertyMatched; + argsData = { domElement, pseudoName, pseudoArg }; + errorMessage = `Error while matching element properties by arg '${pseudoArg}'.`; + break; + default: + throw new Error(`Unknown absolute pseudo-class :${pseudoName}()`); + } + + return matcherWrapper(callback, argsData, errorMessage); +}; + +export const findByAbsolutePseudoPseudo = { + /** + * Gets list of nth ancestors relative to every dom node from domElements list. + * + * @param domElements DOM elements. + * @param rawPseudoArg Number arg of :nth-ancestor() or :upward() pseudo-class. + * @param pseudoName Pseudo-class name. + */ + nthAncestor: (domElements: HTMLElement[], rawPseudoArg: string, pseudoName: string): HTMLElement[] => { + const deep = getValidNumberAncestorArg(rawPseudoArg, pseudoName); + const ancestors = domElements + .map((domElement) => { + let ancestor: HTMLElement | null = null; + try { + ancestor = getNthAncestor(domElement, deep, pseudoName); + } catch (e) { + logger.error(e); + } + return ancestor; + }) + .filter(isHtmlElement); + return ancestors; + }, + + /** + * Gets list of elements by xpath expression, evaluated on every dom node from domElements list. + * + * @param domElements DOM elements. + * @param rawPseudoArg Arg of :xpath() pseudo-class. + */ + xpath: (domElements: HTMLElement[], rawPseudoArg: string): HTMLElement[] => { + const foundElements = domElements + .map((domElement) => { + const result = []; + let xpathResult: XPathResult; + try { + xpathResult = document.evaluate( + rawPseudoArg, + domElement, + null, + XPathResult.UNORDERED_NODE_ITERATOR_TYPE, + null, + ); + } catch (e) { + logger.error(e); + throw new Error(`Invalid argument of :xpath pseudo-class: '${rawPseudoArg}'`); + } + let node = xpathResult.iterateNext(); + while (node) { + if (isHtmlElement(node)) { + result.push(node); + } + node = xpathResult.iterateNext(); + } + return result; + }); + + return flatten(foundElements); + }, + + /** + * Gets list of closest ancestors relative to every dom node from domElements list. + * + * @param domElements DOM elements. + * @param rawPseudoArg Standard selector arg of :upward() pseudo-class. + * + * @throws An error if `rawPseudoArg` is not a valid standard selector. + */ + upward: (domElements: HTMLElement[], rawPseudoArg: string): HTMLElement[] => { + if (!validateStandardSelector(rawPseudoArg)) { + throw new Error(`Invalid argument of :upward pseudo-class: '${rawPseudoArg}'`); + } + const closestAncestors = domElements + .map((domElement) => { + // closest to parent element should be found + // otherwise `.base:upward(.base)` will return itself too, not only ancestor + const parent = domElement.parentElement; + if (!parent) { + return null; + } + return parent.closest(rawPseudoArg); + }) + .filter(isHtmlElement); + + return closestAncestors; + }, +}; diff --git a/src/selector/utils/query-helpers.ts b/src/selector/utils/query-helpers.ts new file mode 100644 index 00000000..ba3db879 --- /dev/null +++ b/src/selector/utils/query-helpers.ts @@ -0,0 +1,417 @@ +import { + NodeType, + AnySelectorNodeInterface, +} from '../nodes'; + +import { + findByAbsolutePseudoPseudo, + isMatchedByAbsolutePseudo, +} from './absolute-processor'; + +import { flatten } from '../../common/utils/arrays'; +import { getElementSelectorDesc } from '../../common/utils/nodes'; + +import { + DESCENDANT_COMBINATOR, + CHILD_COMBINATOR, + NEXT_SIBLING_COMBINATOR, + SUBSEQUENT_SIBLING_COMBINATOR, + REGULAR_PSEUDO_CLASSES, + COLON, + ABSOLUTE_PSEUDO_CLASSES, + RELATIVE_PSEUDO_CLASSES, + IF_NOT_PSEUDO_CLASS_MARKER, + IS_PSEUDO_CLASS_MARKER, + NOT_PSEUDO_CLASS_MARKER, + NTH_ANCESTOR_PSEUDO_CLASS_MARKER, + UPWARD_PSEUDO_CLASS_MARKER, + XPATH_PSEUDO_CLASS_MARKER, + HAS_PSEUDO_CLASS_MARKER, + ABP_HAS_PSEUDO_CLASS_MARKER, + IF_PSEUDO_CLASS_MARKER, +} from '../../common/constants'; +import { logger } from '../../common/utils/logger'; + +/** + * Additional calculated selector part which is needed to :has(), :if-not(), :is() and :not() pseudo-classes. + * + * Native Document.querySelectorAll() does not select exact descendant elements + * but match all page elements satisfying the selector, + * so extra specification is needed for proper descendants selection + * e.g. 'div:has(> img)'. + * + * Its calculation depends on extended selector. + */ +export type Specificity = string; + +/** + * Interface for relative pseudo-class helpers args. + */ +interface RelativePredicateArgsInterface { + /** + * Dom element to check relatives. + */ + element: HTMLElement; + + /** + * SelectorList node. + */ + relativeSelectorList: AnySelectorNodeInterface; + + /** + * Extended pseudo-class name. + */ + pseudoName: string; + + /** + * Flag for error throwing on invalid selector from selectorList + * e.g. `true` for :not() pseudo-class. + */ + errorOnInvalidSelector?: boolean; +} + +/** + * Checks whether the element has all relative elements specified by pseudo-class arg. + * Used for :has() and :if-not() pseudo-classes. + * + * @param argsData Relative pseudo-class helpers args data. + */ +const hasRelativesBySelectorList = (argsData: RelativePredicateArgsInterface): boolean => { + const { element, relativeSelectorList, pseudoName } = argsData; + return relativeSelectorList.children + // Array.every() is used here as each Selector node from SelectorList should exist on page + .every((selector) => { + // selectorList.children always starts with regular selector as any selector generally + const [relativeRegularSelector] = selector.children; + if (!relativeRegularSelector) { + throw new Error(`RegularSelector is missing for :${pseudoName} pseudo-class.`); + } + + let specificity: Specificity = ''; + let rootElement: HTMLElement | null = null; + if (relativeRegularSelector.value?.startsWith(NEXT_SIBLING_COMBINATOR) + || relativeRegularSelector.value?.startsWith(SUBSEQUENT_SIBLING_COMBINATOR)) { + /** + * For matching the element by "element:has(+ next-sibling)" and "element:has(~ sibling)" + * we check whether the element's parentElement has specific direct child combination, + * e.g. 'h1:has(+ .share)' -> `h1Node.parentElement.querySelectorAll(':scope > h1 + .share')`. + * + * @see {@link https://www.w3.org/TR/selectors-4/#relational} + */ + rootElement = element.parentElement; + const elementSelectorText = element.tagName.toLowerCase(); + specificity = `${COLON}${REGULAR_PSEUDO_CLASSES.SCOPE}${CHILD_COMBINATOR}${elementSelectorText}`; + } else { + /** + * :scope specification is needed for proper descendants selection + * as native element.querySelectorAll() does not select exact element descendants + * e.g. 'a:has(> img)' -> `aNode.querySelectorAll(':scope > img')` + * OR '.block(div > span)' -> `blockClassNode.querySelectorAll(':scope div > span')`. + */ + specificity = `${COLON}${REGULAR_PSEUDO_CLASSES.SCOPE}${DESCENDANT_COMBINATOR}`; + rootElement = element; + } + + if (!rootElement) { + throw new Error(`Selection by :${pseudoName} pseudo-class is not possible.`); + } + + let relativeElements: HTMLElement[]; + try { + // eslint-disable-next-line @typescript-eslint/no-use-before-define + relativeElements = getElementsForSelectorNode(selector, rootElement, specificity); + } catch (e) { + logger.error(e); + // fail for invalid selector + throw new Error(`Invalid selector for :${pseudoName} pseudo-class: '${relativeRegularSelector.value}'`); + } + return relativeElements.length > 0; + }); +}; + +/** + * Checks whether the element is an any element specified by pseudo-class arg. + * Used for :is() and :not() pseudo-classes. + * + * @param argsData Relative pseudo-class helpers args data. + */ +const isAnyElementBySelectorList = (argsData: RelativePredicateArgsInterface): boolean => { + const { element, relativeSelectorList, pseudoName, errorOnInvalidSelector } = argsData; + return relativeSelectorList.children + // Array.some() is used here as any selector from selector list should exist on page + .some((selector) => { + // selectorList.children always starts with regular selector + const [relativeRegularSelector] = selector.children; + if (!relativeRegularSelector) { + throw new Error(`RegularSelector is missing for :${pseudoName} pseudo-class.`); + } + + /** + * For checking the element by 'div:is(.banner)' and 'div:not([data="content"]) + * we check whether the element's parentElement has any specific direct child. + */ + const rootElement = element.parentElement; + if (!rootElement) { + throw new Error(`Selection by :${pseudoName} pseudo-class is not possible.`); + } + + /** + * So we calculate the element "description" by it's tagname and attributes for targeting + * and use it to specify the selection + * e.g. `div:is(.banner)` --> `divNode.parentElement.querySelectorAll(':scope > div[class="banner"]')`. + */ + const elementSelectorText = getElementSelectorDesc(element); + const specificity = `${COLON}${REGULAR_PSEUDO_CLASSES.SCOPE}${CHILD_COMBINATOR}${elementSelectorText}`; + + let anyElements: HTMLElement[]; + try { + // eslint-disable-next-line @typescript-eslint/no-use-before-define + anyElements = getElementsForSelectorNode(selector, rootElement, specificity); + } catch (e) { + if (errorOnInvalidSelector) { + // fail on invalid selectors for :not() + logger.error(e); + throw new Error(`Invalid selector for :${pseudoName} pseudo-class: '${relativeRegularSelector.value}'`); // eslint-disable-line max-len + } else { + // do not fail on invalid selectors for :is() + return false; + } + } + return anyElements.length > 0; + }); +}; + +/** + * Selects dom elements by value of RegularSelector. + * + * @param regularSelectorNode RegularSelector node. + * @param root Root DOM element. + * @param specificity @see {@link Specificity}. + * + * @throws An error if RegularSelector has no value + * or RegularSelector.value is invalid selector. + */ +export const getByRegularSelector = ( + regularSelectorNode: AnySelectorNodeInterface, + root: Document | Element, + specificity?: Specificity, +): HTMLElement[] => { + if (!regularSelectorNode.value) { + throw new Error('RegularSelector value should be specified'); + } + const selectorText = specificity + ? `${specificity}${regularSelectorNode.value}` + : regularSelectorNode.value; + + let selectedElements: HTMLElement[] = []; + try { + selectedElements = Array.from(root.querySelectorAll(selectorText)); + } catch (e: any) { // eslint-disable-line @typescript-eslint/no-explicit-any + throw new Error(`Error: unable to select by '${selectorText}' — ${e.message}`); + } + return selectedElements; +}; + +/** + * Returns list of dom elements filtered or selected by ExtendedSelector node. + * + * @param domElements Array of DOM elements. + * @param extendedSelectorNode ExtendedSelector node. + * + * @throws An error on unknown pseudo-class, + * absent or invalid arg of extended pseudo-class, etc. + * @returns Array of DOM elements. + */ +export const getByExtendedSelector = ( + domElements: HTMLElement[], + extendedSelectorNode: AnySelectorNodeInterface, +): HTMLElement[] => { + let foundElements: HTMLElement[] = []; + const pseudoName = extendedSelectorNode.children[0].name; + if (!pseudoName) { + // extended pseudo-classes should have a name + throw new Error('Extended pseudo-class should have a name'); + } + + if (ABSOLUTE_PSEUDO_CLASSES.includes(pseudoName)) { + const absolutePseudoArg = extendedSelectorNode.children[0].value; + if (!absolutePseudoArg) { + // absolute extended pseudo-classes should have an argument + throw new Error(`Missing arg for :${pseudoName} pseudo-class`); + } + if (pseudoName === NTH_ANCESTOR_PSEUDO_CLASS_MARKER) { + // :nth-ancestor() + foundElements = findByAbsolutePseudoPseudo.nthAncestor(domElements, absolutePseudoArg, pseudoName); + } else if (pseudoName === XPATH_PSEUDO_CLASS_MARKER) { + // :xpath() + try { + document.createExpression(absolutePseudoArg, null); + } catch (e) { + throw new Error(`Invalid argument of :${pseudoName} pseudo-class: '${absolutePseudoArg}'`); + } + foundElements = findByAbsolutePseudoPseudo.xpath(domElements, absolutePseudoArg); + } else if (pseudoName === UPWARD_PSEUDO_CLASS_MARKER) { + // :upward() + if (Number.isNaN(Number(absolutePseudoArg))) { + // so arg is selector, not a number + foundElements = findByAbsolutePseudoPseudo.upward(domElements, absolutePseudoArg); + } else { + foundElements = findByAbsolutePseudoPseudo.nthAncestor(domElements, absolutePseudoArg, pseudoName); + } + } else { + // all other absolute extended pseudo-classes + // e.g. contains, matches-attr, etc. + foundElements = domElements.filter((element) => { + return isMatchedByAbsolutePseudo(element, pseudoName, absolutePseudoArg); + }); + } + } else if (RELATIVE_PSEUDO_CLASSES.includes(pseudoName)) { + const relativeSelectorNodes = extendedSelectorNode.children[0].children; + if (relativeSelectorNodes.length === 0) { + // extended relative pseudo-classes should have an argument as well + throw new Error(`Missing arg for :${pseudoName} pseudo-class`); + } + const [relativeSelectorList] = relativeSelectorNodes; + // needed for :not() + let errorOnInvalidSelector = false; + let relativePredicate: (e: HTMLElement) => boolean; + switch (pseudoName) { + case HAS_PSEUDO_CLASS_MARKER: + case IF_PSEUDO_CLASS_MARKER: + case ABP_HAS_PSEUDO_CLASS_MARKER: + relativePredicate = (element: HTMLElement) => hasRelativesBySelectorList({ + element, + relativeSelectorList, + pseudoName, + }); + break; + case IF_NOT_PSEUDO_CLASS_MARKER: + relativePredicate = (element: HTMLElement) => !hasRelativesBySelectorList({ + element, + relativeSelectorList, + pseudoName, + }); + break; + case IS_PSEUDO_CLASS_MARKER: + relativePredicate = (element: HTMLElement) => isAnyElementBySelectorList({ + element, + relativeSelectorList, + pseudoName, + }); + break; + case NOT_PSEUDO_CLASS_MARKER: + errorOnInvalidSelector = true; + relativePredicate = (element: HTMLElement) => !isAnyElementBySelectorList({ + element, + relativeSelectorList, + pseudoName, + errorOnInvalidSelector, + }); + break; + default: + throw new Error(`Unknown relative pseudo-class :${pseudoName}()`); + } + foundElements = domElements.filter(relativePredicate); + } else { + // extra check is parser missed something + throw new Error(`Unknown extended pseudo-class: ':${pseudoName}'`); + } + return foundElements; +}; + +/** + * Returns list of dom elements which is selected by RegularSelector value. + * + * @param domElements Array of DOM elements. + * @param regularSelectorNode RegularSelector node. + * + * @throws An error if RegularSelector has not value. + * @returns Array of DOM elements. + */ +export const getByFollowingRegularSelector = ( + domElements: HTMLElement[], + regularSelectorNode: AnySelectorNodeInterface, +): HTMLElement[] => { + // array of arrays because of Array.map() later + let foundElements: HTMLElement[][] = []; + const { value } = regularSelectorNode; + if (!value) { + throw new Error('RegularSelector should have a value.'); + } + + if (value.startsWith(CHILD_COMBINATOR)) { + // e.g. div:has(> img) > .banner + foundElements = domElements + .map((root) => { + const specificity = `${COLON}${REGULAR_PSEUDO_CLASSES.SCOPE}`; + return getByRegularSelector(regularSelectorNode, root, specificity); + }); + } else if (value.startsWith(NEXT_SIBLING_COMBINATOR) + || value.startsWith(SUBSEQUENT_SIBLING_COMBINATOR)) { + // e.g. div:has(> img) + .banner + // or div:has(> img) ~ .banner + foundElements = domElements + .map((element) => { + const rootElement = element.parentElement; + if (!rootElement) { + // do not throw error if there in no parent for element + // e.g. '*:contains(text)' selects `html` which has no parentElement + return []; + } + const elementSelectorText = getElementSelectorDesc(element); + const specificity = `${COLON}${REGULAR_PSEUDO_CLASSES.SCOPE}${CHILD_COMBINATOR}${elementSelectorText}`; + const selected = getByRegularSelector(regularSelectorNode, rootElement, specificity); + return selected; + }); + } else { + // space-separated regular selector after extended one + // e.g. div:has(> img) .banner + foundElements = domElements + .map((root) => { + const specificity = `${COLON}${REGULAR_PSEUDO_CLASSES.SCOPE}${DESCENDANT_COMBINATOR}`; + return getByRegularSelector(regularSelectorNode, root, specificity); + }); + } + // foundElements should be flattened + // as getByRegularSelector() returns elements array, and Array.map() collects them to array + return flatten(foundElements); +}; + +/** + * Gets elements nodes for Selector node. + * As far as any selector always starts with regular part, + * it selects by RegularSelector first and checks found elements later. + * + * Relative pseudo-classes has it's own subtree so getElementsForSelectorNode is called recursively. + * + * 'specificity' is needed for :has(), :is(), and :not() pseudo-classes + * as native querySelectorAll() does not select exact element descendants even if it is called on 'div' + * e.g. ':scope' specification is needed for proper descendants selection for 'div:has(> img)'. + * So we check `divNode.querySelectorAll(':scope > img').length > 0`. + * + * @param selectorNode Selector node. + * @param root Root DOM element. + * @param specificity Needed element specification. + */ +export const getElementsForSelectorNode = ( + selectorNode: AnySelectorNodeInterface, + root: Document | Element | HTMLElement, + specificity?: Specificity, +): HTMLElement[] => { + let selectedElements: HTMLElement[] = []; + let i = 0; + while (i < selectorNode.children.length) { + const selectorNodeChild = selectorNode.children[i]; + if (i === 0) { + // any selector always starts with regular selector + selectedElements = getByRegularSelector(selectorNodeChild, root, specificity); + } else if (selectorNodeChild.type === NodeType.ExtendedSelector) { + // filter previously selected elements by next selector nodes + selectedElements = getByExtendedSelector(selectedElements, selectorNodeChild); + } else if (selectorNodeChild.type === NodeType.RegularSelector) { + selectedElements = getByFollowingRegularSelector(selectedElements, selectorNodeChild); + } + i += 1; + } + return selectedElements; +}; diff --git a/src/stylesheet/index.ts b/src/stylesheet/index.ts new file mode 100644 index 00000000..81572064 --- /dev/null +++ b/src/stylesheet/index.ts @@ -0,0 +1,2 @@ +export { parse } from './parser'; +export type { CssStyleMap, ExtCssRuleData } from './parser'; diff --git a/src/stylesheet/parser.ts b/src/stylesheet/parser.ts new file mode 100644 index 00000000..0438c562 --- /dev/null +++ b/src/stylesheet/parser.ts @@ -0,0 +1,580 @@ +import { AnySelectorNodeInterface, ExtCssDocument } from '../selector'; + +import { TimingStats } from '../extended-css'; + +import { logger } from '../common/utils/logger'; +import { getObjectFromEntries } from '../common/utils/objects'; + +import { + BRACKETS, + COLON, + REMOVE_PSEUDO_MARKER, + PSEUDO_PROPERTY_POSITIVE_VALUE, + DEBUG_PSEUDO_PROPERTY_GLOBAL_VALUE, + STYLESHEET_ERROR_PREFIX, + SLASH, + ASTERISK, +} from '../common/constants'; + +const DEBUG_PSEUDO_PROPERTY_KEY = 'debug'; + +const REGEXP_DECLARATION_END = /[;}]/g; +const REGEXP_DECLARATION_DIVIDER = /[;:}]/g; +const REGEXP_NON_WHITESPACE = /\S/g; + +// ExtendedCss does not support at-rules +// https://developer.mozilla.org/en-US/docs/Web/CSS/At-rule +const AT_RULE_MARKER = '@'; + +interface Style { + property: string; + value: string; +} + +interface RawCssRuleData { + selector: string; + ast?: AnySelectorNodeInterface; + styles?: Style[]; +} + +interface RawResultValue { + ast: AnySelectorNodeInterface; + styles: Style[]; +} +type RawResults = Map; + +export interface CssStyleMap { + [key: string]: string; +} + +/** + * Interface for rules data parsed from passed styleSheet. + */ +export interface ExtCssRuleData { + /** + * Selector text. + */ + selector: string; + + /** + * Selector ast to query dom elements. + */ + ast: AnySelectorNodeInterface; + + /** + * Styles to apply to matched dom elements. + */ + style?: CssStyleMap; + + /** + * Flag for specific rule debugging mode. + */ + debug?: string; + + /** + * Log data, available only for debugging mode. + */ + timingStats?: TimingStats; + + /** + * Dom elements matched by rule, available only for debugging mode. + */ + matchedElements?: HTMLElement[]; +} + +/** + * Interface for storing data parsed from selector rule part. + */ +interface SelectorPartData { + /** + * Success status. + */ + success: boolean; + + /** + * Parsed selector. + */ + selector: string; + + /** + * Selector ast to query elements by, + * might be not defined if selector is not valid. + */ + ast?: AnySelectorNodeInterface; + + /** + * Styles parsed from selector rule part, + * relevant to rules with `:remove()` pseudo-class which may not have actual style declaration. + */ + stylesOfSelector?: Style[]; +} + +/** + * Interface for stylesheet parser context. + */ +interface Context { + /** + * Flag for parsing rules parts. + */ + isSelector: boolean; + + /** + * Parser position. + */ + nextIndex: number; + + /** + * Stylesheet left to parse. + */ + cssToParse: string; + + /** + * Buffer for selector text collecting. + */ + selectorBuffer: string; + + /** + * Buffer for rule data collecting. + */ + rawRuleData: RawCssRuleData; +} + +/** + * Init value for rawRuleData. + */ +const initRawRuleData = { selector: '' }; + +/** + * Resets rule data buffer to init value after rule successfully collected. + * + * @param context Stylesheet parser context. + */ +const restoreRuleAcc = (context: Context): void => { + context.rawRuleData = initRawRuleData; +}; + +interface ParsedSelectorData { + selector: string, + stylesOfSelector: Style[], +} + +/** + * Checks the presence of :remove() pseudo-class and validates it while parsing the selector part of css rule. + * + * @param rawSelector Selector which may contain :remove() pseudo-class. + * + * @throws An error on invalid :remove() position. + */ +const parseRemoveSelector = (rawSelector: string): ParsedSelectorData => { + /** + * No error will be thrown on invalid selector as it will be validated later + * so it's better to explicitly specify 'any' selector for :remove() pseudo-class by '*', + * e.g. '.banner > *:remove()' instead of '.banner > :remove()'. + */ + + // ':remove()' + const VALID_REMOVE_MARKER = `${COLON}${REMOVE_PSEUDO_MARKER}${BRACKETS.PARENTHESES.LEFT}${BRACKETS.PARENTHESES.RIGHT}`; // eslint-disable-line max-len + // ':remove(' - needed for validation rules like 'div:remove(2)' + const INVALID_REMOVE_MARKER = `${COLON}${REMOVE_PSEUDO_MARKER}${BRACKETS.PARENTHESES.LEFT}`; + + let selector: string; + let shouldRemove = false; + const firstIndex = rawSelector.indexOf(VALID_REMOVE_MARKER); + if (firstIndex === 0) { + // e.g. ':remove()' + throw new Error(`Selector should be specified before :remove() pseudo-class: '${rawSelector}'`); + } else if (firstIndex > 0) { + if (firstIndex !== rawSelector.lastIndexOf(VALID_REMOVE_MARKER)) { + // rule with more than one :remove() pseudo-class is invalid + // e.g. '.block:remove() > .banner:remove()' + throw new Error(`Pseudo-class :remove() appears more than once in selector: '${rawSelector}'`); + } else if (firstIndex + VALID_REMOVE_MARKER.length < rawSelector.length) { + // remove pseudo-class should be last in the rule + // e.g. '.block:remove():upward(2)' + throw new Error(`Pseudo-class :remove() should be at the end of selector: '${rawSelector}'`); + } else { + // valid :remove() pseudo-class position + selector = rawSelector.substring(0, firstIndex); + shouldRemove = true; + } + } else if (rawSelector.includes(INVALID_REMOVE_MARKER)) { + // it is not valid if ':remove()' is absent in rule but just ':remove(' is present + // e.g. 'div:remove(0)' + throw new Error(`${STYLESHEET_ERROR_PREFIX.INVALID_REMOVE}: '${rawSelector}'`); + } else { + // there is no :remove() pseudo-class is rule + selector = rawSelector; + } + + const stylesOfSelector = shouldRemove + ? [{ property: REMOVE_PSEUDO_MARKER, value: String(shouldRemove) }] + : []; + + return { selector, stylesOfSelector }; +}; + +/** + * Parses cropped selector part found before `{` previously. + * + * @param context Stylesheet parser context. + * @param extCssDoc Needed for caching of selector ast. + * + * @throws An error on unsupported CSS features, e.g. at-rules. + */ +const parseSelectorPart = (context: Context, extCssDoc: ExtCssDocument): SelectorPartData => { + let selector = context.selectorBuffer.trim(); + if (selector.startsWith(AT_RULE_MARKER)) { + throw new Error(`At-rules are not supported: '${selector}'.`); + } + + let removeSelectorData: ParsedSelectorData; + try { + removeSelectorData = parseRemoveSelector(selector); + } catch (e: any) { // eslint-disable-line @typescript-eslint/no-explicit-any + logger.error(e.message); + throw new Error(`${STYLESHEET_ERROR_PREFIX.INVALID_REMOVE}: '${selector}'`); + } + + if (context.nextIndex === -1) { + if (selector === removeSelectorData.selector) { + // rule should have style or pseudo-class :remove() + throw new Error(`${STYLESHEET_ERROR_PREFIX.NO_STYLE_OR_REMOVE}: '${context.cssToParse}'`); // eslint-disable-line max-len + } + // stop parsing as there is no style declaration and selector parsed fine + context.cssToParse = ''; + } + + let stylesOfSelector: Style[] = []; + let success = false; + let ast: AnySelectorNodeInterface | undefined; + + try { + selector = removeSelectorData.selector; + stylesOfSelector = removeSelectorData.stylesOfSelector; + // validate found selector by parsing it to ast + // so if it is invalid error will be thrown + ast = extCssDoc.getSelectorAst(selector); + success = true; + } catch (e: any) { // eslint-disable-line @typescript-eslint/no-explicit-any + success = false; + } + + if (context.nextIndex > 0) { + // slice found valid selector part off + // and parse rest of stylesheet later + context.cssToParse = context.cssToParse.slice(context.nextIndex); + } + + return { success, selector, ast, stylesOfSelector }; +}; + +/** + * Recursively parses style declaration string into `Style`s. + * + * @param context Stylesheet parser context. + * @param styles Array of styles. + * + * @throws An error on invalid style declaration. + * @returns A number index of the next `}` in `this.cssToParse`. + */ +const parseUntilClosingBracket = (context: Context, styles: Style[]): number => { + // Expects ":", ";", and "}". + REGEXP_DECLARATION_DIVIDER.lastIndex = context.nextIndex; + let match = REGEXP_DECLARATION_DIVIDER.exec(context.cssToParse); + if (match === null) { + throw new Error(`${STYLESHEET_ERROR_PREFIX.INVALID_STYLE}: '${context.cssToParse}'`); + } + let matchPos = match.index; + let matched = match[0]; + + if (matched === BRACKETS.CURLY.RIGHT) { + const declarationChunk = context.cssToParse.slice(context.nextIndex, matchPos); + if (declarationChunk.trim().length === 0) { + // empty style declaration + // e.g. 'div { }' + if (styles.length === 0) { + throw new Error(`${STYLESHEET_ERROR_PREFIX.NO_STYLE}: '${context.cssToParse}'`); + } + // else valid style parsed before it + // e.g. '{ display: none; }' -- position is after ';' + } else { + // closing curly bracket '}' is matched before colon ':' + // trimmed declarationChunk is not a space, between ';' and '}', + // e.g. 'visible }' in style '{ display: none; visible }' after part before ';' is parsed + throw new Error(`${STYLESHEET_ERROR_PREFIX.INVALID_STYLE}: '${context.cssToParse}'`); + } + return matchPos; + } + + if (matched === COLON) { + const colonIndex = matchPos; + // Expects ";" and "}". + REGEXP_DECLARATION_END.lastIndex = colonIndex; + match = REGEXP_DECLARATION_END.exec(context.cssToParse); + if (match === null) { + throw new Error(`${STYLESHEET_ERROR_PREFIX.UNCLOSED_STYLE}: '${context.cssToParse}'`); + } + matchPos = match.index; + matched = match[0]; + // Populates the `styleMap` key-value map. + const property = context.cssToParse.slice(context.nextIndex, colonIndex).trim(); + if (property.length === 0) { + throw new Error(`${STYLESHEET_ERROR_PREFIX.NO_PROPERTY}: '${context.cssToParse}'`); + } + const value = context.cssToParse.slice(colonIndex + 1, matchPos).trim(); + if (value.length === 0) { + throw new Error(`${STYLESHEET_ERROR_PREFIX.NO_VALUE}: '${context.cssToParse}'`); + } + styles.push({ property, value }); + // finish style parsing if '}' is found + // e.g. '{ display: none }' -- no ';' at the end of declaration + if (matched === BRACKETS.CURLY.RIGHT) { + return matchPos; + } + } + // matchPos is the position of the next ';' + // crop 'cssToParse' and re-run the loop + context.cssToParse = context.cssToParse.slice(matchPos + 1); + context.nextIndex = 0; + return parseUntilClosingBracket(context, styles); // Should be a subject of tail-call optimization. +}; + +/** + * Parses next style declaration part in stylesheet. + * + * @param context Stylesheet parser context. + */ +const parseNextStyle = (context: Context): Style[] => { + const styles: Style[] = []; + + const styleEndPos = parseUntilClosingBracket(context, styles); + + // find next rule after the style declaration + REGEXP_NON_WHITESPACE.lastIndex = styleEndPos + 1; + const match = REGEXP_NON_WHITESPACE.exec(context.cssToParse); + if (match === null) { + context.cssToParse = ''; + return styles; + } + const matchPos = match.index; + + // cut out matched style declaration for previous selector + context.cssToParse = context.cssToParse.slice(matchPos); + return styles; +}; + +/** + * Checks whether the 'remove' property positively set in styles + * with only one positive value - 'true'. + * + * @param styles Array of styles. + */ +const isRemoveSetInStyles = (styles: Style[]): boolean => { + return styles.some((s) => { + return s.property === REMOVE_PSEUDO_MARKER + && s.value === PSEUDO_PROPERTY_POSITIVE_VALUE; + }); +}; + +/** + * Gets valid 'debug' property value set in styles + * where possible values are 'true' and 'global'. + * + * @param styles Array of styles. + */ +const getDebugStyleValue = (styles: Style[]): string | undefined => { + const debugStyle = styles.find((s) => { + return s.property === DEBUG_PSEUDO_PROPERTY_KEY; + }); + return debugStyle?.value; +}; + +/** + * Prepares final RuleData. + * + * @param selector String selector. + * @param ast Parsed ast. + * @param rawStyles Array of previously collected styles which may contain 'remove' and 'debug'. + */ +export const prepareRuleData = ( + selector: string, + ast: AnySelectorNodeInterface, + rawStyles: Style[], +): ExtCssRuleData => { + const ruleData: ExtCssRuleData = { selector, ast }; + + const debugValue = getDebugStyleValue(rawStyles); + + const shouldRemove = isRemoveSetInStyles(rawStyles); + + let styles = rawStyles; + if (debugValue) { + // get rid of 'debug' from styles + styles = rawStyles.filter((s) => s.property !== DEBUG_PSEUDO_PROPERTY_KEY); + // and set it as separate property only if its value is valid + // which is 'true' or 'global' + if (debugValue === PSEUDO_PROPERTY_POSITIVE_VALUE + || debugValue === DEBUG_PSEUDO_PROPERTY_GLOBAL_VALUE) { + ruleData.debug = debugValue; + } + } + if (shouldRemove) { + // no other styles are needed to apply if 'remove' is set + ruleData.style = { + [REMOVE_PSEUDO_MARKER]: PSEUDO_PROPERTY_POSITIVE_VALUE, + }; + } else { + // otherwise all styles should be applied. + // every style property will be unique because of their converting into object + if (styles.length > 0) { + const stylesAsEntries = styles.map((style) => { + const { property, value } = style; + return [property, value]; + }); + const preparedStyleData = getObjectFromEntries(stylesAsEntries); + ruleData.style = preparedStyleData; + } + } + + return ruleData; +}; + +/** + * Saves rules data for unique selectors. + * + * @param rawResults Previously collected results of parsing. + * @param rawRuleData Parsed rule data. + * + * @throws An error if there is no rawRuleData.styles or rawRuleData.ast. + */ +const saveToRawResults = (rawResults: RawResults, rawRuleData: RawCssRuleData): void => { + const { selector, ast, styles } = rawRuleData; + + if (!styles) { + throw new Error(`No style declaration for selector: '${selector}'`); + } + if (!ast) { + throw new Error(`No ast parsed for selector: '${selector}'`); + } + + const storedRuleData = rawResults.get(selector); + if (!storedRuleData) { + rawResults.set(selector, { ast, styles }); + } else { + storedRuleData.styles.push(...styles); + } +}; + +/** + * Parses stylesheet of rules into rules data objects (non-recursively): + * 1. Iterates through stylesheet string. + * 2. Finds first `{` which can be style declaration start or part of selector. + * 3. Validates found string part via selector parser; and if: + * - it throws error — saves string part to buffer as part of selector, + * slice next stylesheet part to `{` [2] and validates again [3]; + * - no error — saves found string part as selector and starts to parse styles (recursively). + * + * @param rawStylesheet Raw stylesheet as string. + * @param extCssDoc ExtCssDocument which uses cache while selectors parsing. + * @throws An error on unsupported CSS features, e.g. comments, or invalid stylesheet syntax. + * @returns Array of rules data which contains: + * - selector as string; + * - ast to query elements by; + * - map of styles to apply. + */ +export const parse = (rawStylesheet: string, extCssDoc: ExtCssDocument): ExtCssRuleData[] => { + const stylesheet = rawStylesheet.trim(); + if (stylesheet.includes(`${SLASH}${ASTERISK}`) && stylesheet.includes(`${ASTERISK}${SLASH}`)) { + throw new Error(`Comments in stylesheet are not supported: '${stylesheet}'`); + } + + const context: Context = { + // any stylesheet should start with selector + isSelector: true, + // init value of parser position + nextIndex: 0, + // init value of cssToParse + cssToParse: stylesheet, + // buffer for collecting selector part + selectorBuffer: '', + // accumulator for rules + rawRuleData: initRawRuleData, + }; + + const rawResults: RawResults = new Map(); + + let selectorData: SelectorPartData; + + // context.cssToParse is going to be cropped while its parsing + while (context.cssToParse) { + if (context.isSelector) { + // find index of first opening curly bracket + // which may mean start of style part and end of selector one + context.nextIndex = context.cssToParse.indexOf(BRACKETS.CURLY.LEFT); + // rule should not start with style, selector required + if (context.selectorBuffer.length === 0 && context.nextIndex === 0) { + throw new Error(`Selector should be defined before style declaration in stylesheet: '${context.cssToParse}'`); // eslint-disable-line max-len + } + if (context.nextIndex === -1) { + // no style declaration in rule + // but rule still may contain :remove() pseudo-class + context.selectorBuffer = context.cssToParse; + } else { + // collect string parts before opening curly bracket + // until valid selector collected + context.selectorBuffer += context.cssToParse.slice(0, context.nextIndex); + } + + selectorData = parseSelectorPart(context, extCssDoc); + if (selectorData.success) { + // selector successfully parsed + context.rawRuleData.selector = selectorData.selector.trim(); + context.rawRuleData.ast = selectorData.ast; + context.rawRuleData.styles = selectorData.stylesOfSelector; + context.isSelector = false; + // save rule data if there is no style declaration + if (context.nextIndex === -1) { + saveToRawResults(rawResults, context.rawRuleData); + // clean up ruleContext + restoreRuleAcc(context); + } else { + // skip the opening curly bracket at the start of style declaration part + context.nextIndex = 1; + context.selectorBuffer = ''; + } + } else { + // if selector was not successfully parsed parseSelectorPart(), continue stylesheet parsing: + // save the found bracket to buffer and proceed to next loop iteration + context.selectorBuffer += BRACKETS.CURLY.LEFT; + // delete `{` from cssToParse + context.cssToParse = context.cssToParse.slice(1); + } + } else { + // style declaration should be parsed + const parsedStyles = parseNextStyle(context); + + // styles can be parsed from selector part if it has :remove() pseudo-class + // e.g. '.banner:remove() { debug: true; }' + context.rawRuleData.styles?.push(...parsedStyles); + + // save rule data to results + saveToRawResults(rawResults, context.rawRuleData); + + context.nextIndex = 0; + + // clean up ruleContext + restoreRuleAcc(context); + + // parse next rule selector after style successfully parsed + context.isSelector = true; + } + } + + const results: ExtCssRuleData[] = []; + rawResults.forEach((value, key) => { + const selector = key; + const { ast, styles: rawStyles } = value; + results.push(prepareRuleData(selector, ast, rawStyles)); + }); + return results; +}; diff --git a/tasks/browserstack.js b/tasks/browserstack.js deleted file mode 100644 index d5fc7bc1..00000000 --- a/tasks/browserstack.js +++ /dev/null @@ -1,19 +0,0 @@ -const console = require('console'); -const browserstackRunner = require('browserstack-runner'); -const config = require('./browserstack.json'); - -if (!process.env.TRAVIS) { - // eslint-disable-next-line global-require - require('dotenv').config(); -} - -config.username = process.env.BROWSERSTACK_USER; -config.key = process.env.BROWSERSTACK_KEY; - -browserstackRunner.run(config, (error) => { - if (error) { - throw error; - } - - console.log('Test Finished'); -}); diff --git a/tasks/browserstack.json b/tasks/browserstack.json deleted file mode 100644 index d5b35d28..00000000 --- a/tasks/browserstack.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "test_framework": "qunit", - "test_path": [ - "test/build/dist/dist.html" - ], - "test_server_port": "9961", - "exit_with_fail": true, - "browsers": [ - { - "browser": "Chrome", - "browser_version": "55", - "device": null, - "os": "Windows", - "os_version": "10", - "cli_key": 1 - }, - { - "browser": "Edge", - "browser_version": "18", - "device": null, - "os": "Windows", - "os_version": "10", - "cli_key": 2 - }, - { - "browser": "Firefox", - "browser_version": "52", - "device": null, - "os": "Windows", - "os_version": "10", - "cli_key": 3 - } - ] -} diff --git a/tasks/compile.js b/tasks/compile.js deleted file mode 100644 index e4787884..00000000 --- a/tasks/compile.js +++ /dev/null @@ -1,93 +0,0 @@ -/* eslint-disable no-console */ -/** - * This task compiles sources to javascript bundle - */ -const fs = require('fs-extra'); -const { rollup } = require('rollup'); -const { terser } = require('rollup-plugin-terser'); -const { babel } = require('@rollup/plugin-babel'); -const copy = require('rollup-plugin-copy'); -const resolve = require('@rollup/plugin-node-resolve').nodeResolve; -const commonjs = require('@rollup/plugin-commonjs'); - -const pkg = require('../package.json'); -const config = require('./config'); - -if (!fs.existsSync(config.outputDir)) { - fs.mkdirSync(config.outputDir); -} else { - fs.emptyDirSync(config.outputDir); -} - -const banner = `/*! ${pkg.name} - v${pkg.version} - ${new Date().toDateString()} -${pkg.homepage ? `* ${pkg.homepage}` : ''} -* Copyright (c) ${new Date().getFullYear()} ${pkg.author}. Licensed ${pkg.license} -*/`; - -const rollupConfig = { - input: './index.js', - output: [ - { - file: `${config.outputDir}/${config.fileName}.js`, - format: 'iife', - name: 'ExtendedCss', - banner, - }, - { - file: `${config.outputDir}/${config.fileName}.esm.js`, - format: 'esm', - name: 'ExtendedCss', - banner, - }, - { - file: `${config.outputDir}/${config.fileName}.cjs.js`, - format: 'cjs', - name: 'ExtendedCss', - banner, - }, - { - file: `${config.outputDir}/${config.fileName}.min.js`, - format: 'iife', - name: 'ExtendedCss', - banner, - plugins: [terser({ - output: { - comments: false, - preamble: banner, - }, - })], - }, - ], - plugins: [ - resolve(), - commonjs({ - include: 'node_modules/**', - }), - babel({ - exclude: 'node_modules/**', - babelHelpers: 'bundled', - }), - copy({ - targets: [ - { src: 'types/extended-css.d.ts', dest: 'dist/' }, - ], - }), - ], -}; - -(async () => { - try { - console.info('Start compiling sources'); - - const bundle = await rollup(rollupConfig); - console.log(bundle.watchFiles); - - rollupConfig.output.forEach((option) => { - bundle.write(option); - }); - - console.info('Finished compiling sources'); - } catch (ex) { - console.error(ex); - } -})(); diff --git a/tasks/config.js b/tasks/config.js deleted file mode 100644 index 52bd3255..00000000 --- a/tasks/config.js +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Build configuration - */ -module.exports = { - outputDir: 'dist', - fileName: 'extended-css', -}; diff --git a/tasks/server.js b/tasks/server.js deleted file mode 100644 index 476d79a2..00000000 --- a/tasks/server.js +++ /dev/null @@ -1,43 +0,0 @@ -/* eslint-disable no-console */ -const http = require('http'); -const fs = require('fs'); -const path = require('path'); - -const PORT = 8585; -const ROOT_PATH = '../'; - -const httpServer = http.createServer((req, res) => { - const indexFile = 'index.html'; - const filename = req.url === '/' || req.url.match(/\/\?/) ? indexFile : req.url; - const filePath = path.join(__dirname, ROOT_PATH, filename); - fs.readFile(filePath, (err, data) => { - if (err) { - console.log(err.message); - res.writeHead(404); - res.end(JSON.stringify(err)); - return; - } - res.writeHead(200); - res.end(data); - }); -}); - -const server = { - port: PORT, - start() { - return new Promise((resolve) => { - httpServer.listen(PORT, () => { - resolve(); - }); - }); - }, - stop() { - return new Promise((resolve) => { - httpServer.close(() => { - resolve(); - }); - }); - }, -}; - -module.exports = { server }; diff --git a/tasks/tests.js b/tasks/tests.js deleted file mode 100644 index 03120cf0..00000000 --- a/tasks/tests.js +++ /dev/null @@ -1,138 +0,0 @@ -/* eslint-disable no-console */ -/** - * This task tests the javascript bundle - */ -const { runQunitPuppeteer, printResultSummary, printFailedTests } = require('node-qunit-puppeteer'); -const { rollup } = require('rollup'); -const { babel } = require('@rollup/plugin-babel'); -const copy = require('rollup-plugin-copy'); -const resolve = require('@rollup/plugin-node-resolve').nodeResolve; -const commonjs = require('@rollup/plugin-commonjs'); -const del = require('rollup-plugin-delete'); -const { server } = require('./server'); - -const TESTS_RUN_TIMEOUT = 15000; - -const testModules = [ - 'utils', - 'css-parser', - 'extended-css', - 'performance', - 'selector', - 'dist', -]; - -const testsConfigs = testModules.map((module) => { - const inputPathPart = `${module}/${module}.test.js`; - const inputFile = `./test/${inputPathPart}`; - const outputFile = `./test/build/${inputPathPart}`; - - return { - input: inputFile, - output: { - file: outputFile, - format: 'iife', - }, - plugins: [ - copy({ - verbose: true, - targets: [{ src: `./test/${module}/${module}.html`, dest: `./test/build/${module}` }], - }), - resolve(), - commonjs({ - include: 'node_modules/**', - }), - babel({ - exclude: 'node_modules/**', - babelHelpers: 'bundled', - }), - ], - }; -}); - -const rollupConfigs = [ - { - input: './test/index.js', - output: { - file: './test/build/index.js', - format: 'iife', - name: 'exports', - }, - plugins: [ - del({ - targets: ['./test/build'], - verbose: true, - }), - copy({ - verbose: true, - targets: [ - { src: './test/qunit/**', dest: './test/build/qunit' }, - { src: './test/index.html', dest: './test/build' }, - { src: './dist/*', dest: './test/build/dist' }, - ], - }), - resolve(), - commonjs({ - include: 'node_modules/**', - }), - babel({ - exclude: 'node_modules/**', - babelHelpers: 'bundled', - }), - ], - }, - ...testsConfigs, -]; - -const runQunit = async (module) => { - const qunitArgs = { - targetUrl: `http://localhost:${server.port}/test/build/${module}/${module}.html`, - timeout: TESTS_RUN_TIMEOUT, - puppeteerArgs: ['--no-sandbox', '--allow-file-access-from-files'], - }; - - try { - await server.start(); - const result = await runQunitPuppeteer(qunitArgs); - printResultSummary(result, console); - if (result.stats.failed > 0) { - printFailedTests(result, console); - } - await server.stop(); - } catch (e) { - await server.stop(); - console.error(e); - process.exit(1); - } -}; - -(async () => { - try { - console.info('Start compiling sources'); - - /** - * As we are not able to use es6 modules in browser environment, we first compile sources. - */ - // eslint-disable-next-line no-restricted-syntax - for (const config of rollupConfigs) { - // eslint-disable-next-line no-await-in-loop - const bundle = await rollup(config); - // an array of file names this bundle depends on - console.log(bundle.watchFiles); - // eslint-disable-next-line no-await-in-loop - await bundle.write(config.output); - } - - console.info('Finished compiling sources'); - - console.log('Running tests..'); - - // eslint-disable-next-line no-restricted-syntax - for (const module of testModules) { - // eslint-disable-next-line no-await-in-loop - await runQunit(module); - } - } catch (ex) { - console.log(ex); - } -})(); diff --git a/test/dist/dist.html b/test/browserstack/browserstack.html similarity index 65% rename from test/dist/dist.html rename to test/browserstack/browserstack.html index 9b79e76b..90a12057 100644 --- a/test/dist/dist.html +++ b/test/browserstack/browserstack.html @@ -6,8 +6,8 @@ - Extended CSS tests - + Extended CSS tests in Browserstack + @@ -16,9 +16,11 @@ @@ -34,6 +36,14 @@ +
+

+
+

+ +
+

+
@@ -44,7 +54,7 @@
-
+
@@ -52,12 +62,21 @@
+
+ +
- + - - + + diff --git a/test/browserstack/browserstack.test.ts b/test/browserstack/browserstack.test.ts new file mode 100644 index 00000000..cd4b56b0 --- /dev/null +++ b/test/browserstack/browserstack.test.ts @@ -0,0 +1,160 @@ +interface TestExtCssConfig { + styleSheet: string; +} + +interface ExtendedCssInstance { + apply(): void; + dispose(): void; +} + +// Needed to avoid declare global error: +// Augmentations for the global scope can only be directly nested in external modules or ambient module declarations +export {}; + +declare global { + const BrowserstackTest: { + ExtendedCss: { + new (configuration: TestExtCssConfig): ExtendedCssInstance, + }, + }; +} + +/* Start with creating ExtendedCss */ +const cssText = document.getElementById('extendedCss')?.innerHTML; +if (!cssText) { + throw new Error('Missing extendedCss style on page'); +} + +const extCss = new BrowserstackTest.ExtendedCss({ styleSheet: cssText }); +extCss.apply(); + +interface TestExpectedStyle { + [key: string]: string; +} + +/** + * Asserts that specified function has specified expected styles. + * + * @param id ID of element to check. + * @param expectedStyle Expected style of element selected by id. + * @param assert Qunit assert instance. + */ +const assertElementStyle = function (id: string, expectedStyle: TestExpectedStyle, assert: Assert) { + const element = document.getElementById(id); + let resultOk = true; + if (!element) { + resultOk = false; + } + + Object.keys(expectedStyle).forEach((prop) => { + const left = element?.style.getPropertyValue(prop) || ''; + const right = expectedStyle[prop]; + + if (left !== right) { + resultOk = false; + } + }); + + assert.ok(resultOk, id + (resultOk ? ' ok' : ' element either does not exist or has different style.')); +}; + +type RafCallback = () => void; + +/** + * We throttle MO callbacks in ExtCss with requestAnimationFrame and setTimeout. + * Browsers postpone rAF callbacks in inactive tabs for a long time. + * It throttles setTimeout callbacks as well, but it is called within a + * relatively short time. (within several seconds) + * We apply rAF in tests as well to postpone test for similar amount of time. + * + * @param callback Callback to postpone. + * @param delay Time in ms. + */ +const rAF = (callback: RafCallback, delay: number) => { + if (typeof window.requestAnimationFrame !== 'undefined') { + requestAnimationFrame(() => { + setTimeout(callback, delay); + }); + } else { + setTimeout(callback, delay); + } +}; + +QUnit.test('Modifier -ext-has', (assert) => { + assertElementStyle('case1-blocked', { display: 'none' }, assert); +}); + +QUnit.test('Modifier -ext-has + >h3', (assert) => { + assertElementStyle('case2-blocked', { display: 'none' }, assert); +}); + +QUnit.test('Append our style', (assert) => { + assertElementStyle('case3-modified', { 'display': 'block', 'visibility': 'hidden' }, assert); +}); + +QUnit.test('Composite style', (assert) => { + assertElementStyle('case4-blocked', { 'display': 'none' }, assert); + assertElementStyle('case4-not-blocked', { 'display': '' }, assert); +}); + +QUnit.test('Reaction on DOM modification', (assert) => { + const done = assert.async(); + assertElementStyle('case5-blocked', { display: 'none' }, assert); + const el = document.getElementById('case5-blocked'); + if (!el) { + throw new Error('No needed test element #case5-blocked on page'); + } + document?.getElementById('container')?.appendChild(el); + + rAF(() => { + assertElementStyle('case5-blocked', { display: '' }, assert); + done(); + }, 200); +}); + +QUnit.test('Modifier -ext-matches-css -- opacity property', (assert) => { + assertElementStyle('case6-blocked', { display: 'none' }, assert); +}); + +QUnit.test('Protection from recurring style fixes', (assert) => { + const done = assert.async(); + + const testNodeId = 'case11'; + const testNode = document.getElementById(testNodeId); + if (!testNode) { + throw new Error('No needed test element #case11 on page'); + } + + let styleTamperCount = 0; + + const tamperStyle = () => { + if (testNode.hasAttribute('style')) { + testNode.removeAttribute('style'); + styleTamperCount += 1; + } + }; + + const tamperObserver = new MutationObserver(tamperStyle); + + tamperStyle(); + tamperObserver.observe( + testNode, + { + attributes: true, + attributeFilter: ['style'], + }, + ); + + setTimeout(() => { + tamperObserver.disconnect(); + assert.ok( + styleTamperCount >= 50 && styleTamperCount < 60, + `style should be protected >= 50 && < 60 times. actual: ${styleTamperCount}`, + ); + assert.notOk( + testNode.hasAttribute('style'), + `'style' attribute for #${testNodeId} should not be set`, + ); + done(); + }, 1000); +}); diff --git a/test/browserstack/config.ts b/test/browserstack/config.ts new file mode 100644 index 00000000..ac96f564 --- /dev/null +++ b/test/browserstack/config.ts @@ -0,0 +1,45 @@ +export const rawConfig = { + test_framework: 'qunit', + test_path: [ + 'test/dist/browserstack.html', + ], + test_server_port: '9961', + exit_with_fail: true, + browsers: [ + { + browser: 'Chrome', + browser_version: '55', + device: null, + os: 'Windows', + os_version: '10', + }, + { + browser: 'Firefox', + browser_version: '52', + device: null, + os: 'Windows', + os_version: '10', + }, + { + browser: 'Edge', + browser_version: '80', + device: null, + os: 'Windows', + os_version: '10', + }, + { + browser: 'Opera', + browser_version: '80', + device: null, + os: 'Windows', + os_version: '10', + }, + { + browser: 'Safari', + browser_version: '11.1', + device: null, + os: 'OS X', + os_version: 'High Sierra', + }, + ], +}; diff --git a/test/browserstack/index.ts b/test/browserstack/index.ts new file mode 100644 index 00000000..deacb3bb --- /dev/null +++ b/test/browserstack/index.ts @@ -0,0 +1,33 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +import browserstackRunner from 'browserstack-runner'; +import dotenv from 'dotenv'; +import { rawConfig } from './config'; + +interface BrowserConfig { + [key: string]: string | null; +} + +interface BrowserstackConfig { + [key: string]: string | number | boolean | undefined | string[] | BrowserConfig[]; +} + +dotenv.config(); + +const config: BrowserstackConfig = { + ...rawConfig, + username: process.env.BROWSERSTACK_USER, + key: process.env.BROWSERSTACK_KEY, + // limit each runner with 60 seconds + // if not set defaults to 5 min (300 s) + timeout: 60, +}; + +export const runBrowserstack = async () => { + browserstackRunner.run(config, (error: any): void => { // eslint-disable-line @typescript-eslint/no-explicit-any + if (error) { + throw error; + } + console.log('Test Finished'); + }); +}; diff --git a/test/css-parser/css-parser.html b/test/css-parser/css-parser.html deleted file mode 100644 index a8007660..00000000 --- a/test/css-parser/css-parser.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - CssParser tests - - - -
-
- - - - - diff --git a/test/css-parser/css-parser.test.js b/test/css-parser/css-parser.test.js deleted file mode 100644 index 4d792e6d..00000000 --- a/test/css-parser/css-parser.test.js +++ /dev/null @@ -1,118 +0,0 @@ -/* eslint-disable max-len,no-multi-str */ -const { ExtendedCssParser } = exports; -const { initializeSizzle } = exports; - -const Sizzle = initializeSizzle(); - -QUnit.test('Simple CSS', (assert) => { - const selector = 'body'; - const cssText = `${selector} { display:none; }`; - const cssObject = ExtendedCssParser.parseCss(cssText); - assert.ok(cssObject instanceof Array); - assert.equal(cssObject.length, 1); - assert.ok(cssObject[0]); - assert.equal(cssObject[0].selector.selectorText, selector); - assert.ok(cssObject[0].style); - assert.equal(cssObject[0].style.display, 'none'); -}); - -QUnit.test('Test Sizzle tokenize cache', (assert) => { - const selector = 'body'; - const cssText = `${selector} { display:none; }`; - ExtendedCssParser.parseCss(cssText); - - // Check the tokens cache only now - const tokens = Sizzle.tokenize(selector, false, { cacheOnly: true }); - assert.ok(tokens); - assert.equal(tokens.length, 1); - assert.equal(tokens[0].length, 1); -}); - -QUnit.test('Parse an invalid selector', (assert) => { - assert.throws(() => { - const cssText = 'div > { display:none; }'; - ExtendedCssParser.parseCss(cssText); - }, - (error) => error.toString().includes('parse error at position'), - 'Expected ExtendedCssParser to throw on an invalid selector'); -}); - -QUnit.test('Single invalid selector in a stylesheet', (assert) => { - const cssText = 'body:has(div:invalid-pseudo(1)), div { display: none }'; - const cssObject = ExtendedCssParser.parseCss(cssText); - assert.ok(cssObject instanceof Array); - assert.equal(cssObject.length, 1); -}); - -QUnit.test('Convert remove pseudo-class into remove pseudo-property', (assert) => { - const elementSelector = 'div:has(> div[class])'; - const selectorText = `${elementSelector}:remove()`; - const cssText = `${selectorText} { display:none; }`; - const cssObject = ExtendedCssParser.parseCss(cssText); - assert.ok(cssObject instanceof Array); - assert.equal(cssObject.length, 1); - assert.equal(cssObject[0].selector.selectorText, elementSelector); - assert.ok(cssObject[0].style); - assert.equal(cssObject[0].style.remove, 'true'); -}); - -QUnit.test('Scope handling', (assert) => { - const inputCssText = 'div:has(:scope > a > img[id]) { display: none }'; - const cssObject = ExtendedCssParser.parseCss(inputCssText); - const expectedOutputSelectorText = 'div:has(> a > img[id])'; - assert.ok(cssObject instanceof Array); - assert.equal(cssObject.length, 1); - assert.equal(cssObject[0].selector.selectorText, expectedOutputSelectorText); -}); - -QUnit.test('Parse stylesheet', (assert) => { - const cssText = 'body { background: none!important; }\n div.wrapper { display: block!important; position: absolute; top:-2000px; }'; - const cssObject = ExtendedCssParser.parseCss(cssText); - assert.ok(cssObject instanceof Array); - assert.equal(cssObject.length, 2); - - assert.ok(cssObject[0]); - assert.equal(cssObject[0].selector.selectorText, 'body'); - assert.ok(cssObject[0].style); - assert.equal(cssObject[0].style.background, 'none!important'); - - assert.ok(cssObject[1]); - assert.equal(cssObject[1].selector.selectorText, 'div.wrapper'); - assert.ok(cssObject[1].style); - assert.equal(cssObject[1].style.display, 'block!important'); - assert.equal(cssObject[1].style.position, 'absolute'); - assert.equal(cssObject[1].style.top, '-2000px'); -}); - -QUnit.test('Parse stylesheet with extended selectors', (assert) => { - const cssText = ':contains(/[\\w]{9,}/){display:none!important;visibility:hidden!important}\ - :matches-css( background-image: /^url\\((.)[a-z]{4}:[a-z]{2}\\1nk\\)$/ ) + [-ext-matches-css-before=\'content: /^[A-Z][a-z]{2}\\s/ \'][-ext-has=\'+:matches-css-after( content : /(\\d+\\s)*me/ ):contains(/^(?![\\s\\S])/)\'] {\ - width: 500px;height: 500px;\ - -webkit-border-radius: 30px;\ - -moz-border-radius: 30px;\ -\ - -webkit-box-shadow: 1px -1px #b2492c, 2px -2px #b2492c, 3px -3px #b2492c, 4px -4px #b2492c, 5px -5px #b2492c, 6px -6px #b2492c, 7px -7px #b2492c, 8px -8px #b2492c, 9px -9px #b2492c, 10px -10px #b2492c;\ -\ - }'; - - const cssObject = ExtendedCssParser.parseCss(cssText); - - assert.ok(cssObject instanceof Array); - assert.equal(cssObject.length, 2); - - assert.ok(cssObject[0]); - assert.equal(cssObject[0].selector.selectorText, ':contains("/[\\\\w]{9,}/")'); - assert.ok(cssObject[0].style); - assert.equal(cssObject[0].style.display, 'none!important'); - assert.equal(cssObject[0].style.visibility, 'hidden!important'); - - assert.ok(cssObject[1]); - assert.equal(cssObject[1].selector.selectorText, ':matches-css(" background-image: /^url\\\\((.)[a-z]{4}:[a-z]{2}\\\\1nk\\\\)$/ ") + :matches-css-before("content: /^[A-Z][a-z]{2}\\\\s/ "):has(+:matches-css-after(" content : /(\\\\d+\\\\s)*me/ "):contains("/^(?![\\\\s\\\\S])/"))'); - - assert.ok(cssObject[1].style); - assert.equal(cssObject[1].style.width, '500px'); - assert.equal(cssObject[1].style.height, '500px'); - assert.equal(cssObject[1].style['-webkit-border-radius'], '30px'); - assert.equal(cssObject[1].style['-moz-border-radius'], '30px'); - assert.equal(cssObject[1].style['-webkit-box-shadow'], '1px -1px #b2492c, 2px -2px #b2492c, 3px -3px #b2492c, 4px -4px #b2492c, 5px -5px #b2492c, 6px -6px #b2492c, 7px -7px #b2492c, 8px -8px #b2492c, 9px -9px #b2492c, 10px -10px #b2492c'); -}); diff --git a/test/dist/dist.test.js b/test/dist/dist.test.js deleted file mode 100644 index b20a1f29..00000000 --- a/test/dist/dist.test.js +++ /dev/null @@ -1,104 +0,0 @@ -/* global ExtendedCss */ - -/* Start with creating ExtendedCss */ -const cssText = document.getElementById('extendedCss').innerHTML; -const extCss = new ExtendedCss({ styleSheet: cssText }); -extCss.apply(); - -/** - * Asserts that specified function has specified expected styles - */ -const assertElementStyle = function (id, expectedStyle, assert) { - const element = document.getElementById(id); - let resultOk = true; - if (!element) { - resultOk = false; - } - - Object.keys(expectedStyle).forEach((prop) => { - const left = element.style.getPropertyValue(prop) || ''; - const right = expectedStyle[prop]; - - if (left !== right) { - resultOk = false; - } - }); - - assert.ok(resultOk, id + (resultOk ? ' ok' : ' element either does not exist or has different style.')); -}; - -/** - * We throttle MO callbacks in ExtCss with requestAnimationFrame and setTimeout. - * Browsers postpone rAF callbacks in inactive tabs for a long time. - * It throttles setTimeout callbacks as well, but it is called within a - * relatively short time. (within several seconds) - * We apply rAF in tests as well to postpone test for similar amount of time. - */ -const rAF = function (fn, timeout) { - if (window.requestAnimationFrame) { - requestAnimationFrame(() => { - setTimeout(fn, timeout); - }); - } else { - setTimeout(fn, timeout); - } -}; - -QUnit.test('Modifer -ext-has', (assert) => { - assertElementStyle('case1-blocked', { display: 'none' }, assert); -}); - -QUnit.test('Append our style', (assert) => { - assertElementStyle('case3-modified', { 'display': 'block', 'visibility': 'hidden' }, assert); -}); - -QUnit.test('Composite style', (assert) => { - assertElementStyle('case4-blocked', { 'display': 'none' }, assert); - assertElementStyle('case4-notblocked', { 'display': '' }, assert); -}); - -QUnit.test('Reaction on DOM modification', (assert) => { - const done = assert.async(); - assertElementStyle('case5-blocked', { display: 'none' }, assert); - const el = document.getElementById('case5-blocked'); - document.getElementById('container').appendChild(el); - - rAF(() => { - assertElementStyle('case5-blocked', { display: '' }, assert); - done(); - }, 200); -}); - -QUnit.test('Protection from recurring style fixes', (assert) => { - const done = assert.async(); - - const testNode = document.getElementById('case11'); - - let styleTamperCount = 0; - - const tamperStyle = function () { - if (testNode.hasAttribute('style')) { - testNode.removeAttribute('style'); - styleTamperCount++; - } - }; - - const tamperObserver = new MutationObserver(tamperStyle); - - tamperStyle(); - tamperObserver.observe( - testNode, - { - attributes: true, - attributeFilter: ['style'], - } - ); - - setTimeout(() => { - tamperObserver.disconnect(); - assert.ok(styleTamperCount < 60); - assert.ok(styleTamperCount >= 50); - assert.notOk(testNode.hasAttribute('style')); - done(); - }, 1000); -}); diff --git a/test/extended-css.test.ts b/test/extended-css.test.ts new file mode 100644 index 00000000..d2885255 --- /dev/null +++ b/test/extended-css.test.ts @@ -0,0 +1,709 @@ +/** + * @jest-environment jsdom + */ + +import { ExtendedCss } from '../src'; + +import { TimingStats } from '../src/extended-css'; + +import { logger } from '../src/common/utils/logger'; + +const TESTS_RUN_TIMEOUT_MS = 20 * 1000; + +interface TestPropElement extends Element { + // eslint-disable-next-line @typescript-eslint/ban-types + testProp: string | Object; +} + +/** + * Applies extended css stylesheet. + * + * @param styleSheet Extended css stylesheet. + */ +const applyExtCss = (styleSheet: string): void => { + const extendedCss = new ExtendedCss({ styleSheet }); + extendedCss.apply(); +}; + +interface TestStyleMap { + [key: string]: string; +} + +interface TestLoggedStats { + selectorParsed: string, + timings: TimingStats, +} + +/** + * Asserts that specified function has specified expected styles. + * + * @param actualId Actual element id. + * @param expectedStyle Expected style of element. + */ +export const expectElementStyle = (actualId: string, expectedStyle: TestStyleMap) => { + const element = document.getElementById(actualId); + expect(element).toBeDefined(); + Object.keys(expectedStyle) + .forEach((prop) => { + const actual = element?.style.getPropertyValue(prop); + const expected = expectedStyle[prop]; + expect(actual).toEqual(expected); + }); +}; + +/** + * We throttle MO callbacks in ExtCss with requestAnimationFrame and setTimeout. + * Browsers postpone rAF callbacks in inactive tabs for a long time. + * It throttles setTimeout callbacks as well, but it is called within a + * relatively short time. (within several seconds) + * We apply rAF in tests as well to postpone test for similar amount of time. + * + * @param callback Callback to postpone. + * @param delay Time in ms. + */ +const rAF = (callback: Function, delay: number) => { // eslint-disable-line @typescript-eslint/ban-types + if (typeof window.requestAnimationFrame !== 'undefined') { + requestAnimationFrame(() => { + setTimeout(callback, delay); + }); + } else { + setTimeout(callback, delay); + } +}; + +jest.setTimeout(TESTS_RUN_TIMEOUT_MS); + +describe('extended css library', () => { + afterEach(() => { + document.body.innerHTML = ''; + }); + + it('-ext-has', () => { + document.body.innerHTML = ` +
+
+ +
+
+ `; + const styleSheet = '#case1 > div[-ext-has=".banner"] { display:none !important; }'; + applyExtCss(styleSheet); + expectElementStyle('case1-blocked', { display: 'none' }); + }); + + it('-ext-contains', () => { + document.body.innerHTML = ` +
+ + + +
+ `; + const styleSheet = '#case2 > div[-ext-contains="Block this"] { display: none!important }'; + applyExtCss(styleSheet); + + expectElementStyle('case2-blocked1', { display: 'none' }); + expectElementStyle('case2-blocked2', { display: 'none' }); + expectElementStyle('case2-not-blocked', { display: '' }); + }); + + it('Append our style', () => { + document.body.innerHTML = ` +
+
+ +
+
+ `; + const styleSheet = '#case3>div[-ext-has=".banner"] { visibility: hidden; }'; + applyExtCss(styleSheet); + expectElementStyle('case3-modified', { display: 'block', visibility: 'hidden' }); + }); + + it('Composite style', () => { + document.body.innerHTML = ` +
+
+
+ +
+
+ +
+
+
+ `; + const styleSheet = '#case4 div[-ext-has=".banner:contains(Banner)"] { display: none; }'; + applyExtCss(styleSheet); + expectElementStyle('case4-blocked', { 'display': 'none' }); + expectElementStyle('case4-not-blocked', { 'display': '' }); + }); + + it('Reaction on DOM modification', (done) => { + document.body.innerHTML = ` +
+
+ +
+
+ `; + const styleSheet = '#case5 > div[-ext-contains="Block this"] { display: none!important }'; + applyExtCss(styleSheet); + // style should be set by rule + expectElementStyle('case5-blocked', { display: 'none' }); + const el = document.getElementById('case5-blocked'); + if (!el) { + throw new Error('No element selected by ExtendedCss for case5'); + } + const container = document.getElementById('container'); + container?.appendChild(el); + + rAF(() => { + try { + // style is not set as target element should be direct child of `#case5` by rule + expectElementStyle('case5-blocked', { display: '' }); + done(); + } catch (error) { + done(error); + } + }, 200); + }); + + it('Affected elements length - simple', (done) => { + document.body.innerHTML = ` +
+
+
+ `; + const styleSheet = '#case6>div[-ext-has=".banner"] { display: none; }'; + const extendedCss = new ExtendedCss({ styleSheet }); + extendedCss.apply(); + + let affectedLength: number; + const startLength = extendedCss.getAffectedElements().length; + // no element with 'banner' class at start + expect(startLength).toBe(0); + const toBeBlocked = document.getElementById('case6-blocked'); + expectElementStyle('case6-blocked', { display: '' }); + + const banner = document.createElement('div'); + banner.setAttribute('class', 'banner'); + toBeBlocked?.appendChild(banner); + + rAF(() => { + try { + expectElementStyle('case6-blocked', { display: 'none' }); + affectedLength = extendedCss.getAffectedElements().length; + expect(affectedLength).toBe(startLength + 1); + } catch (error) { + done(error); + } + + toBeBlocked?.removeChild(banner); + + rAF(() => { + try { + expectElementStyle('case6-blocked', { display: '' }); + affectedLength = extendedCss.getAffectedElements().length; + expect(affectedLength).toBe(startLength); + done(); + } catch (error) { + done(error); + } + }, 300); + }, 300); + }); + + it('Affected elements length - root element removal', (done) => { + document.body.innerHTML = ` +
+
Block this
+
+ `; + const styleSheet = '#case7>div[-ext-contains="Block this"] { display: none; }'; + const extendedCss = new ExtendedCss({ styleSheet }); + extendedCss.apply(); + + let affectedLength: number; + const startLength = extendedCss.getAffectedElements().length; + expect(startLength).toBe(1); + // one element at start + expectElementStyle('case7-blocked', { display: 'none' }); + + const root = document.getElementById('case7'); + root?.parentNode?.removeChild(root); + + rAF(() => { + try { + affectedLength = extendedCss.getAffectedElements().length; + // no element after root removing + expect(affectedLength).toBe(startLength - 1); + done(); + } catch (error) { + done(error); + } + }, 200); + }); + + it('has(matches-css)', () => { + document.body.innerHTML = ` +
+
+
+
+
+ + + `; + const styleSheet = '#case8>div[-ext-has=":matches-css(height: 20px)"] { display: none; }'; + applyExtCss(styleSheet); + + expectElementStyle('case8-blocked', { display: 'none' }); + }); + + it('font-size style', () => { + document.body.innerHTML = ` +
+
Another text
+
+ `; + const styleSheet = '#case9>div[-ext-contains="Another text"] { font-size: 16px; }'; + applyExtCss(styleSheet); + + expectElementStyle('case9-not-blocked', { display: '', 'font-size': '16px' }); + }); + + it('attribute protection', (done) => { + document.body.innerHTML = ` +
+
Block this
+
+ `; + const styleSheet = '#case10>div[-ext-contains="Block this"] { display: none; }'; + applyExtCss(styleSheet); + + expectElementStyle('case10-blocked', { display: 'none' }); + + rAF(() => { + const node = document.getElementById('case10-blocked'); + if (!node) { + throw new Error('No target test element selected for case10.'); + } + node.style.cssText = 'display: block!important;'; + rAF(() => { + node.style.cssText = 'display: block!important; visibility: visible!important;'; + rAF(() => { + try { + expectElementStyle('case10-blocked', { display: 'none' }); + done(); + } catch (error) { + done(error); + } + }, 100); + }, 100); + }, 100); + + }); + + it('protection from recurring style fixes', (done) => { + document.body.innerHTML = '
'; + const styleSheet = '#case11 { display: none; }'; + applyExtCss(styleSheet); + + const testNode = document.getElementById('case11'); + if (!testNode) { + throw new Error('No target test element selected for case11.'); + } + + let styleTamperCount = 0; + + const tamperStyle = (): void => { + if (testNode.hasAttribute('style')) { + testNode.removeAttribute('style'); + styleTamperCount += 1; + } + }; + + const tamperObserver = new MutationObserver(tamperStyle); + + tamperStyle(); + tamperObserver.observe( + testNode, + { + attributes: true, + attributeFilter: ['style'], + }, + ); + + setTimeout(() => { + try { + tamperObserver.disconnect(); + expect(styleTamperCount < 60).toBeTruthy(); + expect(styleTamperCount >= 50).toBeTruthy(); + expect(testNode.hasAttribute('style')).toBeFalsy(); + done(); + } catch (error) { + done(error); + } + }, 1000); + }); + + it('test ExtendedCss.query', () => { + document.body.innerHTML = ` +
+
Block me
+
+ `; + + const selector = '#case12>div:contains(Block me)'; + const elements: HTMLElement[] = ExtendedCss.query(selector); + + expect(elements).toBeDefined(); + expect(elements.length).toBe(1); + }); + + it('test ExtendedCss.validate', () => { + expect(ExtendedCss.validate('div').ok).toBeTruthy(); + expect(ExtendedCss.validate('#banner').ok).toBeTruthy(); + expect(ExtendedCss.validate('#banner:has(div) > #banner:contains(test)').ok).toBeTruthy(); + expect(ExtendedCss.validate('#banner:whatisthispseudo(div)').ok).toBeFalsy(); + }); + + it('style remove pseudo-property', (done) => { + document.body.innerHTML = '
'; + const styleSheet = '#case-remove-property { remove: true }'; + applyExtCss(styleSheet); + + let targetElement = document.querySelector('#case-remove-property'); + // no such element after rule applying + expect(targetElement).toBeNull(); + + const nodeHtml = '
'; + rAF(() => { + document.body.insertAdjacentHTML('beforeend', nodeHtml); + rAF(() => { + try { + targetElement = document.querySelector('#case-remove-property'); + // element removed again + expect(targetElement).toBeNull(); + done(); + } catch (error) { + done(error); + } + }, 100); + }, 100); + }); + + it('apply different rules to the same element', () => { + document.body.innerHTML = ` +
+
+ +
+
+ `; + const styleSheet = ` + #case15>div[-ext-has=".banner"] { color:red; } + #case15>div[-ext-has=".banner"] { background:white; } + `; + applyExtCss(styleSheet); + + expectElementStyle('case15-inner', { color: 'red', background: 'white' }); + }); + + it('protect only rule style', (done) => { + document.body.innerHTML = ` +
+
+ +
+
+ `; + const styleSheet = '#case16>div[-ext-has=".banner"] { color:red; }'; + applyExtCss(styleSheet); + + expectElementStyle('case16-inner', { color: 'red', background: 'white' }); + + rAF(() => { + const node = document.getElementById('case16-inner'); + if (!node) { + throw new Error('No target test element selected for case16.'); + } + node.style.cssText = 'background: green;'; + rAF(() => { + rAF(() => { + try { + expectElementStyle('case16-inner', { color: 'red', background: 'green' }); + done(); + } catch (error) { + done(error); + } + }, 100); + }, 100); + }, 100); + }); + + it('protected elements are removed only 50 times', (done) => { + document.body.innerHTML = ` +
+
+
+ `; + + const protectorNode = document.getElementById('protect-node-inside'); + if (!protectorNode) { + throw new Error('No protectorNode selected.'); + } + + const id = 'case-remove-property-repeatedly'; + const testNodeElement = document.createElement('div'); + testNodeElement.id = id; + + let elementAddCounter = 0; + const protectElement = () => { + const testNode = protectorNode.querySelector(`#${id}`); + if (!testNode) { + protectorNode.appendChild(testNodeElement); + elementAddCounter += 1; + } + }; + + const observer = new MutationObserver(protectElement); + observer.observe(protectorNode, { childList: true }); + + const styleSheet = `#${id} { remove: true }`; + applyExtCss(styleSheet); + + setTimeout(() => { + try { + observer.disconnect(); + expect(elementAddCounter < 60).toBeTruthy(); + expect(elementAddCounter >= 50).toBeTruthy(); + expect(protectorNode.querySelector(`#${id}`)).toBeDefined(); + done(); + } catch (error) { + done(error); + } + }, 9000); + }); + + it('strict style attribute matching', (done) => { + document.body.innerHTML = ` +
+
+ +
+
+ `; + const selector = 'div[class="test_item"][style="padding-bottom: 16px;"]:has(> a > img[width="50"])'; + const styleSheet = `${selector} { display: none!important; }`; + applyExtCss(styleSheet); + + expectElementStyle('case17-inner', { 'padding-bottom': '16px', display: 'none' }); + + rAF(() => { + try { + expectElementStyle('case17-inner', { 'padding-bottom': '16px', display: 'none' }); + done(); + } catch (error) { + done(error); + } + }, 200); + }); + + it('test removing of parent and child elements matched by style + no id attr', () => { + document.body.innerHTML = ` +
+
+
+
+
+
+
+
+
+
+
+ + + `; + let parentEl = document.querySelector('div[case18-parent]'); + let childEl = document.querySelector('div[case18-child]'); + let targetEl = document.querySelector('div[case18-target]'); + expect(parentEl).toBeDefined(); + expect(childEl).toBeDefined(); + expect(targetEl).toBeDefined(); + + const styleSheet = '#case18 div:matches-css(height:/20px/) { remove: true; }'; + applyExtCss(styleSheet); + + parentEl = document.querySelector('div[case18-parent]'); + childEl = document.querySelector('div[case18-child]'); + targetEl = document.querySelector('div[case18-target]'); + + // parentEl should be removed by rule + expect(parentEl).toBeNull(); + // childEl no longer exists because parentNode is removed + expect(childEl).toBeNull(); + // targetEl should be removed as well + expect(targetEl).toBeNull(); + }); + + it('matches-property - regexp value', () => { + document.body.innerHTML = ` +
+
+
+
+ `; + + const testEl = document.querySelector('#case19-property-match') as TestPropElement; + const testPropName = '_testProp'; + testEl[testPropName] = 'abc'; + + const styleSheet = '#case19 > div:matches-property(_testProp=/[\\w]{3}/) { display: none!important; }'; + applyExtCss(styleSheet); + + expectElementStyle('case19-property-match', { display: 'none' }); + expectElementStyle('case19-property-no-match', { display: 'block' }); + }); + + it('matches-property - chain with regexp', () => { + document.body.innerHTML = ` +
+
+
+
+ `; + + const testEl = document.querySelector('#case19-chain-property-match') as TestPropElement; + const propFirst = '_testProp'; + const propInner = { inner: null }; + testEl[propFirst] = propInner; + + const styleSheet = '#case19 > div:matches-property(/_test/.inner=null) { display: none!important; }'; + applyExtCss(styleSheet); + + expectElementStyle('case19-chain-property-match', { display: 'none' }); + expectElementStyle('case19-chain-property-no-match', { display: 'block' }); + }); + + it('matches-property - access child prop of null prop - no match and no fail', () => { + document.body.innerHTML = ` +
+
+
+ `; + const styleSheet = '#case19 > div:matches-property("firstChild.assignedSlot.test") { display: none!important; }'; // eslint-disable-line max-len + applyExtCss(styleSheet); + + expectElementStyle('case19-property-null', { display: 'block' }); + }); + + it('debugging - true', (done) => { + expect.assertions(3); + document.body.innerHTML = '
'; + const styleSheet = ` + #case13:not(with-debug) { display:none; debug: true } + #case13:not(without-debug) { display:none; } + `; + const extendedCss = new ExtendedCss({ styleSheet }); + + const loggerInfo = logger.info; + logger.info = function (...args) { + if (args.length === 3 + && typeof args[0] === 'string' && args[0].indexOf('Timings') !== -1) { + const loggedData = args[2]; + expect(loggedData).toBeDefined(); + + const selectors = Object.keys(loggedData); + expect(selectors.length).toEqual(1); + expect(selectors[0].includes('with-debug')).toBeTruthy(); + + // Cleanup + logger.info = loggerInfo; + extendedCss.dispose(); + done(); + } + return loggerInfo.apply(this, args); + }; + + extendedCss.apply(); + }); + + it('debugging - global', (done) => { + expect.assertions(5); + document.body.innerHTML = '
'; + const styleSheet = ` + #case14:not(without-debug-before-global) { display:none; } + #case14:not(with-global-debug) { display:none; debug: global } + #case14:not(without-debug-after-global) { display:none; } + `; + const extendedCss = new ExtendedCss({ styleSheet }); + + // Spy on utils.logInfo + const loggerInfo = logger.info; + logger.info = function (...args) { + if (args.length === 3 + && typeof args[0] === 'string' && args[0].indexOf('Timings') !== -1) { + const loggedData: TestLoggedStats[] = args[2]; + expect(loggedData).toBeDefined(); + + const selectors = Object.keys(loggedData); + expect(selectors.length).toEqual(3); + + expect(selectors.filter((s) => s.includes('with-global-debug')).length).toEqual(1); + expect(selectors.filter((s) => s.includes('without-debug-before-global')).length).toEqual(1); + expect(selectors.filter((s) => s.includes('without-debug-after-global')).length).toEqual(1); + + // Cleanup + logger.info = loggerInfo; + extendedCss.dispose(); + done(); + } + return loggerInfo.apply(this, args); + }; + + extendedCss.apply(); + }); + + it('debugging - only debug property for logging', (done) => { + expect.assertions(3); + document.body.innerHTML = '
'; + const styleSheet = ` + #case13:not(with-debug) { debug: true } + `; + const extendedCss = new ExtendedCss({ styleSheet }); + + const loggerInfo = logger.info; + logger.info = function (...args) { + if (args.length === 3 + && typeof args[0] === 'string' && args[0].indexOf('Timings') !== -1) { + const loggedData = args[2]; + expect(loggedData).toBeDefined(); + + const selectors = Object.keys(loggedData); + expect(selectors.length).toEqual(1); + expect(selectors[0].includes('with-debug')).toBeTruthy(); + + // Cleanup + logger.info = loggerInfo; + extendedCss.dispose(); + done(); + } + return loggerInfo.apply(this, args); + }; + + extendedCss.apply(); + }); +}); diff --git a/test/extended-css/extended-css.html b/test/extended-css/extended-css.html deleted file mode 100644 index 8c7cca9d..00000000 --- a/test/extended-css/extended-css.html +++ /dev/null @@ -1,153 +0,0 @@ - - - - - - - - Extended CSS tests - - - - -
-
- - - - -
-
-
- -
-
-
- - - -
-
-
- -
-
-
-
-
- -
-
- -
-
-
-
- -
-
-
-
-
-
Block this
-
-
-
-
-
-
-
-
-
Another text
-
-
-
Block this
-
-
-
-
Block me
-
-
-
-
-
- -
-
-
-
- -
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
- -
-
-
- - - - - - - diff --git a/test/extended-css/extended-css.test.js b/test/extended-css/extended-css.test.js deleted file mode 100644 index db7d8fc5..00000000 --- a/test/extended-css/extended-css.test.js +++ /dev/null @@ -1,441 +0,0 @@ -/* eslint-disable max-len,prefer-rest-params */ - -const { ExtendedCss } = exports; -const { utils } = exports; - -/* Start with creating ExtendedCss */ -const cssText = document.getElementById('extendedCss').innerHTML; -const extendedCss = new ExtendedCss({ styleSheet: cssText }); -extendedCss.apply(); - -/** - * Asserts that specified function has specified expected styles - */ -const assertElementStyle = function (id, expectedStyle, assert) { - const element = document.getElementById(id); - let resultOk = true; - if (!element) { - resultOk = false; - } - - Object.keys(expectedStyle) - .forEach((prop) => { - const left = element.style.getPropertyValue(prop) || ''; - const right = expectedStyle[prop]; - - if (left !== right) { - resultOk = false; - } - }); - - assert.ok(resultOk, id + (resultOk ? ' ok' : ' element either does not exist or has different style.')); -}; - -/** - * We throttle MO callbacks in ExtCss with requestAnimationFrame and setTimeout. - * Browsers postpone rAF callbacks in inactive tabs for a long time. - * It throttles setTimeout callbacks as well, but it is called within a - * relatively short time. (within several seconds) - * We apply rAF in tests as well to postpone test for similar amount of time. - */ -const rAF = function (fn, timeout) { - if (window.requestAnimationFrame) { - requestAnimationFrame(() => { - setTimeout(fn, timeout); - }); - } else { - setTimeout(fn, timeout); - } -}; - -QUnit.test('Modifer -ext-has', (assert) => { - assertElementStyle('case1-blocked', { display: 'none' }, assert); -}); - -QUnit.test('Modifer -ext-contains', (assert) => { - assertElementStyle('case2-blocked1', { display: 'none' }, assert); - assertElementStyle('case2-blocked2', { display: 'none' }, assert); - assertElementStyle('case2-notblocked', { display: '' }, assert); -}); - -QUnit.test('Append our style', (assert) => { - assertElementStyle('case3-modified', { 'display': 'block', 'visibility': 'hidden' }, assert); -}); - -QUnit.test('Composite style', (assert) => { - assertElementStyle('case4-blocked', { 'display': 'none' }, assert); - assertElementStyle('case4-notblocked', { 'display': '' }, assert); -}); - -QUnit.test('Reaction on DOM modification', (assert) => { - const done = assert.async(); - assertElementStyle('case5-blocked', { display: 'none' }, assert); - const el = document.getElementById('case5-blocked'); - document.getElementById('container').appendChild(el); - - rAF(() => { - assertElementStyle('case5-blocked', { display: '' }, assert); - done(); - }, 200); -}); - -QUnit.test('Affected elements length (simple)', (assert) => { - const done = assert.async(); - - let affectedLength; - const startLength = extendedCss._getAffectedElements().length; - assert.ok(1, `Start test: ${startLength} elements affected`); - const toBeBlocked = document.getElementById('case6-blocked'); - assertElementStyle('case6-blocked', { 'display': '' }, assert); - - const banner = document.createElement('div'); - banner.setAttribute('class', 'banner'); - toBeBlocked.appendChild(banner); - - rAF(() => { - assertElementStyle('case6-blocked', { 'display': 'none' }, assert); - affectedLength = extendedCss._getAffectedElements().length; - assert.equal(affectedLength, startLength + 1); - assert.ok(1, `Element blocked: ${affectedLength} elements affected`); - - toBeBlocked.removeChild(banner); - rAF(() => { - assertElementStyle('case6-blocked', { 'display': '' }, assert); - affectedLength = extendedCss._getAffectedElements().length; - assert.equal(affectedLength, startLength); - assert.ok(1, `Element unblocked: ${affectedLength} elements affected`); - done(); - }, 300); - }, 300); -}); - -QUnit.test('Affected elements length (root element removal)', (assert) => { - const done = assert.async(); - - let affectedLength; - const startLength = extendedCss._getAffectedElements().length; - assert.ok(1, `Start test: ${startLength} elements affected`); - assertElementStyle('case7-blocked', { 'display': 'none' }, assert); - - const root = document.getElementById('case7'); - root.parentNode.removeChild(root); - - rAF(() => { - affectedLength = extendedCss._getAffectedElements().length; - assert.equal(affectedLength, startLength - 1); - assert.ok(1, `Element blocked: ${affectedLength} elements affected`); - done(); - }, 200); -}); - -QUnit.test('Modifer -ext-matches-css-before', (assert) => { - assertElementStyle('case8-blocked', { 'display': 'none' }, assert); -}); - -QUnit.test('Font-size style', (assert) => { - assertElementStyle('case9-notblocked', { 'display': '', 'font-size': '16px' }, assert); -}); - -QUnit.test('Test attribute protection', (assert) => { - const done = assert.async(); - assertElementStyle('case10-blocked', { 'display': 'none' }, assert); - - rAF(() => { - const node = document.getElementById('case10-blocked'); - node.style.cssText = 'display: block!important;'; - rAF(() => { - node.style.cssText = 'display: block!important; visibility: visible!important;'; - rAF(() => { - assertElementStyle('case10-blocked', { 'display': 'none' }, assert); - done(); - }, 100); - }, 100); - }, 100); -}); - -QUnit.test('Protection from recurring style fixes', (assert) => { - assert.expect(3); - const done = assert.async(); - - const testNode = document.getElementById('case11'); - - let styleTamperCount = 0; - - const tamperStyle = function () { - if (testNode.hasAttribute('style')) { - testNode.removeAttribute('style'); - styleTamperCount++; - } - }; - - const tamperObserver = new MutationObserver(tamperStyle); - - tamperStyle(); - tamperObserver.observe( - testNode, - { - attributes: true, - attributeFilter: ['style'], - } - ); - - setTimeout(() => { - tamperObserver.disconnect(); - assert.ok(styleTamperCount < 60); - assert.ok(styleTamperCount >= 50); - assert.notOk(testNode.hasAttribute('style')); - done(); - }, 1000); -}); - -QUnit.test('Test ExtendedCss.query', (assert) => { - const elements = ExtendedCss.query('#case12>div:contains(Block me)'); - assert.ok(elements); - assert.ok(elements.length === 1); - assert.ok((elements instanceof Array) || (elements instanceof NodeList)); -}); - -QUnit.test('Test using ExtendedCss.query for selectors validation', (assert) => { - function isValid(selectorText) { - try { - ExtendedCss.query(selectorText, true); - return true; - } catch (ex) { - return false; - } - } - - assert.notOk(isValid()); - assert.ok(isValid('div')); - assert.ok(isValid('#banner')); - assert.ok(isValid('#banner:has(div) > #banner:contains(test)')); - assert.ok(isValid("#banner[-ext-has='test']")); - assert.notOk(isValid('#banner:whatisthispseudo(div)')); -}); - -QUnit.test('Test debugging', (assert) => { - assert.timeout(1000); - assert.expect(2); - const done = assert.async(); - - const selectors = [ - '#case13:not(with-debug) { display:none; debug:"" }', - '#case13:not(without-debug) { display:none; }', - ]; - const extendedCss = new ExtendedCss({ styleSheet: selectors.join('\n') }); - - // Spy on utils.logInfo - const utilsLogInfo = utils.logInfo; - utils.logInfo = function () { - if (arguments.length === 3 - && typeof arguments[0] === 'string' && arguments[0].indexOf('Timings for') !== -1) { - const stats = arguments[2]; - assert.ok(stats); - assert.ok(stats[0].selectorText.indexOf('with-debug') !== -1); - - // Cleanup - utils.logInfo = utilsLogInfo; - extendedCss.dispose(); - done(); - } - return utilsLogInfo.apply(this, arguments); - }; - - extendedCss.apply(); -}); - -QUnit.test('Test global debugging', (assert) => { - assert.timeout(1000); - assert.expect(5); - const done = assert.async(); - - const selectors = [ - '#case14:not(without-debug-before-global) { display:none; }', - '#case14:not(with-global-debug) { display:none; debug: global }', - '#case14:not(without-debug-after-global) { display:none; }', - ]; - - const extendedCss = new ExtendedCss({ styleSheet: selectors.join('\n') }); - - // Spy on utils.logInfo - const utilsLogInfo = utils.logInfo; - utils.logInfo = function () { - if (arguments.length === 3 - && typeof arguments[0] === 'string' && arguments[0].indexOf('Timings for') !== -1) { - const stats = arguments[2]; - - assert.ok(stats); - assert.ok(stats.length, 3); - - assert.equal(stats.filter((item) => item.selectorText.indexOf('with-global-debug') !== -1).length, 1, JSON.stringify(stats)); - assert.equal(stats.filter((item) => item.selectorText.indexOf('without-debug-before-global') !== -1).length, 1, JSON.stringify(stats)); - assert.equal(stats.filter((item) => item.selectorText.indexOf('without-debug-after-global') !== -1).length, 1, JSON.stringify(stats)); - - // Cleanup - utils.logInfo = utilsLogInfo; - extendedCss.dispose(); - done(); - } - return utilsLogInfo.apply(this, arguments); - }; - - extendedCss.apply(); -}); - -QUnit.test('Test style remove property', (assert) => { - assert.timeout(1000); - assert.expect(2); - const done = assert.async(); - - const styleSheet = '#case-remove-property { remove: true }'; - const extendedCss = new ExtendedCss({ styleSheet }); - extendedCss.apply(); - const targetElement = document.querySelector('#case-remove-property'); - assert.notOk(targetElement); - - const nodeHtml = '
'; - rAF(() => { - document.body.insertAdjacentHTML('beforeend', nodeHtml); - rAF(() => { - const targetElement = document.querySelector('#case-remove-property'); - assert.notOk(targetElement); - done(); - }, 100); - }, 100); -}); - -QUnit.test('Apply different rules to the same element', (assert) => { - assertElementStyle('case15-inner', { 'color': 'red', 'background': 'white' }, assert); -}); - -QUnit.test('Protect only rule style', (assert) => { - assert.expect(2); - const done = assert.async(); - assertElementStyle('case16-inner', { 'color': 'red', 'background': 'white' }, assert); - - rAF(() => { - const node = document.getElementById('case16-inner'); - node.style.cssText = 'background: green;'; - rAF(() => { - rAF(() => { - assertElementStyle('case16-inner', { 'color': 'red', 'background': 'green' }, assert); - done(); - }, 100); - }, 100); - }, 100); -}); - -QUnit.test('Protected elements are removed only 50 times', (assert) => { - assert.expect(3); - const done = assert.async(); - const protectorNode = document.getElementById('protect-node-inside'); - const id = 'case-remove-property-repeatedly'; - const testNodeElement = document.createElement('div'); - testNodeElement.id = id; - - let elementAddCounter = 0; - - const protectElement = () => { - const testNode = protectorNode.querySelector(`#${id}`); - if (!testNode) { - protectorNode.appendChild(testNodeElement); - elementAddCounter += 1; - } - }; - - const observer = new MutationObserver(protectElement); - observer.observe(protectorNode, { childList: true }); - - const styleSheet = `#${id} { remove: true }`; - const extendedCss = new ExtendedCss({ styleSheet }); - extendedCss.apply(); - - setTimeout(() => { - observer.disconnect(); - assert.ok(elementAddCounter < 60); - assert.ok(elementAddCounter >= 50); - assert.ok(protectorNode.querySelector(`#${id}`)); - done(); - }, 9000); -}); - -QUnit.test('Strict style attribute matching', (assert) => { - const selector = 'div[class="test_item"][style="padding-bottom: 16px;"]:has(> a > img[width="50"])'; - const styleSheet = `${selector} { display: none!important; }`; - const extendedCss = new ExtendedCss({ styleSheet }); - extendedCss.apply(); - - assert.expect(4); - const done = assert.async(); - const testNode = document.getElementById('case17-inner'); - const testNodeStyleProps = window.getComputedStyle(testNode); - assert.strictEqual(testNodeStyleProps['padding-bottom'], '16px'); - assert.strictEqual(testNodeStyleProps.display, 'none'); - - rAF(() => { - assert.strictEqual(testNodeStyleProps['padding-bottom'], '16px'); - assert.strictEqual(testNodeStyleProps.display, 'none'); - done(); - }, 200); -}); - -QUnit.test('Test removing of parent and child elements matched by style + no id attr', (assert) => { - let parentEl = document.querySelector('div[case18-parent]'); - assert.ok(parentEl, 'parentEl is present at test start'); - let childEl = document.querySelector('div[case18-child]'); - assert.ok(childEl, 'childEl is present at test start'); - let targetEl = document.querySelector('div[case18-target]'); - assert.ok(targetEl, 'targetEl is present at test start'); - - const styleSheet = '#case18 div:matches-css(height:/20px/) { remove: true; }'; - const extendedCss = new ExtendedCss({ styleSheet }); - extendedCss.apply(); - - parentEl = document.querySelector('div[case18-parent]'); - assert.notOk(parentEl, 'parentEl should be removed by rule'); - childEl = document.querySelector('div[case18-child]'); - assert.notOk(childEl, 'childEl no longer exists because parentNode is removed'); - targetEl = document.querySelector('div[case18-target]'); - assert.notOk(targetEl, 'targetEl should be removed as well'); -}); - -QUnit.test('matches-property -- regexp value', (assert) => { - const selector = '#case19 > div:matches-property("id"="/property-match/")'; - const styleSheet = `${selector} { display: none!important; }`; - const extendedCss = new ExtendedCss({ styleSheet }); - extendedCss.apply(); - - const matchEl = document.getElementById('case19-property-match'); - const matchElStyleProps = window.getComputedStyle(matchEl); - assert.strictEqual(matchElStyleProps.display, 'none'); - - const noMatchEl = document.getElementById('case19-property-no-match'); - const noMatchElStyleProps = window.getComputedStyle(noMatchEl); - assert.strictEqual(noMatchElStyleProps.display, 'block'); -}); - -QUnit.test('matches-property -- chain with regexp', (assert) => { - const selector = '#case19 > div:matches-property("/class/.value"="match")'; - const styleSheet = `${selector} { display: none!important; }`; - const extendedCss = new ExtendedCss({ styleSheet }); - extendedCss.apply(); - - const matchEl = document.getElementById('case19-chain-property-match'); - const matchElStyleProps = window.getComputedStyle(matchEl); - assert.strictEqual(matchElStyleProps.display, 'none'); - - const noMatchEl = document.getElementById('case19-chain-property-no-match'); - const noMatchElStyleProps = window.getComputedStyle(noMatchEl); - assert.strictEqual(noMatchElStyleProps.display, 'block'); -}); - -QUnit.test('matches-property -- access child prop of null prop', (assert) => { - const selector = '#case19 > div[class]:matches-property("firstChild.assignedSlot.test")'; - const styleSheet = `${selector} { display: none!important; }`; - const extendedCss = new ExtendedCss({ styleSheet }); - extendedCss.apply(); - - const matchEl = document.getElementById('case19-property-null'); - const matchElStyleProps = window.getComputedStyle(matchEl); - assert.strictEqual(matchElStyleProps.display, 'block'); -}); diff --git a/test/global-scope.test.ts b/test/global-scope.test.ts new file mode 100644 index 00000000..9e30054c --- /dev/null +++ b/test/global-scope.test.ts @@ -0,0 +1,48 @@ +/** + * @jest-environment jsdom + */ + +/** + * Store global window before any import. + */ +const windowBefore = Object.assign({}, window); + +const { ExtendedCss } = require('../src'); // eslint-disable-line @typescript-eslint/no-var-requires +const { expectElementStyle } = require('./extended-css.test'); // eslint-disable-line @typescript-eslint/no-var-requires + +/** + * Compares whether the two objects are equal. + * + * @param obj1 First object. + * @param obj2 Second object. + */ +const isEqual = (obj1: T, obj2: T): boolean => { + const keys1 = Object.keys(obj1); + const keys2 = Object.keys(obj2); + return keys1.length === keys2.length + && keys2.every((k2) => keys1.includes(k2) && keys1[k2] === keys2[k2]); +}; + +describe('global scope test', () => { + afterEach(() => { + document.body.innerHTML = ''; + }); + + it('global scope pollution check', () => { + document.body.innerHTML = ` +
+
Block me
+
+ `; + const styleSheet = '#case12>div:contains(Block me) { display: none!important; }'; + const extendedCss = new ExtendedCss({ styleSheet }); + extendedCss.apply(); + + expectElementStyle('case12-blocked', { display: 'none' }); + document.body.innerHTML = ''; + + const windowAfter = Object.assign({}, window); + + expect(isEqual(windowBefore, windowAfter)).toBeTruthy(); + }); +}); diff --git a/test/helpers/performance-checker.ts b/test/helpers/performance-checker.ts new file mode 100644 index 00000000..4fa78f0f --- /dev/null +++ b/test/helpers/performance-checker.ts @@ -0,0 +1,86 @@ +declare global { + const extCssV1: { + query(selector: string, noTiming: boolean): Element[]; + }; + + const extCssV2: { + ExtendedCss: { + query(selector: string): Element[]; + } + }; +} + +export interface PerformanceResult { + selector: string, + status: boolean, + elapsed: number, + count: number, + average: number, + result?: string, +} + +const LOOP_COUNT = 10 * 1000; +const MAX_ELAPSED_VALUE = 15 * 1000; + +/** + * Runs ExtendedCSS v2 query for selector. + * + * @param selector Css selector - standard or extended. + */ +const checkPerformanceV2 = (selector: string): PerformanceResult => { + const startTime = new Date().getTime(); + let iCount = LOOP_COUNT; + let resultOk = true; + while (iCount--) { + const nodes = extCssV2.ExtendedCss.query(selector); + if (!nodes || !nodes.length) { + resultOk = false; + } + } + const elapsed = new Date().getTime() - startTime; + if (elapsed > MAX_ELAPSED_VALUE) { + resultOk = false; + } + const resData = { + selector: selector, + status: resultOk, + elapsed: elapsed, + count: LOOP_COUNT, + average: elapsed / LOOP_COUNT, + }; + return resData; +}; + +/** + * Runs ExtendedCSS v1 query for selector. + * + * @param selector Css selector - standard or extended. + */ +const checkPerformanceV1 = (selector: string): PerformanceResult => { + const startTime = new Date().getTime(); + let iCount = LOOP_COUNT; + let resultOk = true; + while (iCount--) { + const nodes = extCssV1.query(selector, true); + if (!nodes || !nodes.length) { + resultOk = false; + } + } + const elapsed = new Date().getTime() - startTime; + if (elapsed > MAX_ELAPSED_VALUE) { + resultOk = false; + } + const resData = { + selector: selector, + status: resultOk, + elapsed: elapsed, + count: LOOP_COUNT, + average: elapsed / LOOP_COUNT, + }; + return resData; +}; + +export const checkPerformance = { + v1: checkPerformanceV1, + v2: checkPerformanceV2, +}; diff --git a/test/helpers/selector-parser.ts b/test/helpers/selector-parser.ts new file mode 100644 index 00000000..0b3cfac6 --- /dev/null +++ b/test/helpers/selector-parser.ts @@ -0,0 +1,241 @@ +import { parse } from '../../src/selector'; + +import { NodeType } from '../../src/selector'; + +interface TestAnySelectorNodeInterface { + type: string, + children: TestAnySelectorNodeInterface[], + value?: string, + name?: string, +} + +/** + * Returns RegularSelector with specified value. + * + * @param regularValue String value for RegularSelector. + */ +export const getRegularSelector = (regularValue: string): TestAnySelectorNodeInterface => { + return { + type: NodeType.RegularSelector, + value: regularValue, + children: [], + }; +}; + +/** + * Returns extended selector AbsolutePseudoClass node. + * + * @param name Extended pseudo-class name. + * @param value Value of pseudo-class. + */ +export const getAbsoluteExtendedSelector = (name: string, value: string): TestAnySelectorNodeInterface => { + return { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.AbsolutePseudoClass, + name, + value, + children: [], + }, + ], + }; +}; + +/** + * Returns Selector node with RegularSelector as single child. + * + * @param regularValue String value for RegularSelector. + */ +export const getSelectorAsRegular = (regularValue: string): TestAnySelectorNodeInterface => { + const selectorNode = { + type: NodeType.Selector, + children: [getRegularSelector(regularValue)], + }; + return selectorNode; +}; + +/** + * Returns extended selector RelativePseudoClass node with single RegularSelector. + * + * @param name Extended pseudo-class name. + * @param value Value of it's inner regular selector. + */ +export const getRelativeExtendedWithSingleRegular = (name: string, value: string): TestAnySelectorNodeInterface => { + return { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name, + children: [ + { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + { + type: NodeType.RegularSelector, + value, + children: [], + }, + ], + }, + ], + }, + ], + }, + ], + }; +}; + +/** + * Returns SelectorList with multiple Selector nodes which have single RegularSelector node with specified value. + * + * @param regularValues Array of RegularSelector values. + */ +export const getSelectorListOfRegularSelectors = (regularValues: string[]): TestAnySelectorNodeInterface => { + const selectorNodes = regularValues.map((value) => { + return getSelectorAsRegular(value); + }); + return { + type: NodeType.SelectorList, + children: selectorNodes, + }; +}; + +/** + * Returns SelectorList with single Selector node which has single RegularSelector node with specified value. + * + * @param regularValue String value for RegularSelector. + */ +export const getAstWithSingleRegularSelector = (regularValue: string): TestAnySelectorNodeInterface => { + return getSelectorListOfRegularSelectors([regularValue]); +}; + +/** + * Data object for generating Selector children nodes: + * 1. Only one of isRegular/isAbsolute/isRelative should be true. + * 2. Acceptable parameters for: + * - isRegular: value; + * - isAbsolute or isRelative: name, value. + */ +interface AnyChildOfSelectorRaw { + isRegular?: boolean, + isAbsolute?: boolean, + isRelative?: boolean + value?: string, + name?: string, +} + +/** + * Returns SelectorList with single Selector node which with any ast node. + * + * @param expected Simplified data for Selector child to expect. + */ +export const getSingleSelectorAstWithAnyChildren = ( + expected: AnyChildOfSelectorRaw[], +): TestAnySelectorNodeInterface => { + const selectorChildren = expected.map((raw) => { + const { + isRegular, + isAbsolute, + isRelative, + value, + name, + } = raw; + + if (isRegular && isAbsolute && isRelative) { + throw new Error('Just one of properties should be specified: isRegular OR isAbsolute'); + } + + let childNode; + if (isRegular && value) { + childNode = getRegularSelector(value); + } else if (isAbsolute && name && value) { + childNode = getAbsoluteExtendedSelector(name, value); + } else if (isRelative && name && value) { + childNode = getRelativeExtendedWithSingleRegular(name, value); + } + if (!childNode) { + throw new Error('Selector node child cannot be undefined. Some input param might be not set.'); + } + return childNode; + }); + + return { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: selectorChildren, + }, + ], + }; +}; + +interface SelectorListOfRegularsInput { + /** + * Selector for parsing. + */ + actual: string, + + /** + * Array of expected values for RegularSelector nodes. + */ + expected: string[], +} + +/** + * Checks whether the passed selector is parsed into proper SelectorList. + * + * @param input - { actual, expected }. + * @param input.actual Selector to parse to ast. + * @param input.expected Expected ast selector. + */ +export const expectSelectorListOfRegularSelectors = ({ actual, expected }: SelectorListOfRegularsInput): void => { + expect(parse(actual)).toEqual(getSelectorListOfRegularSelectors(expected)); +}; + +interface SelectorListOfAnyChildrenInput { + /** + * Selector for parsing. + */ + actual: string, + + /** + * Array of data for building ast. + */ + expected: AnyChildOfSelectorRaw[], +} + +/** + * Checks whether the 'actual' is parsed into AST with specified parameters. + * + * @param input - { actual, expected }. + * @param input.actual Selector to parse to ast. + * @param input.expected Expected ast selector. + */ +export const expectSingleSelectorAstWithAnyChildren = ({ actual, expected }: SelectorListOfAnyChildrenInput): void => { + expect(parse(actual)).toEqual(getSingleSelectorAstWithAnyChildren(expected)); +}; + +interface ToThrowSelectorInput { + /** + * Selector for extCss querySelectorAll(). + */ + selector: string, + + /** + * Error text to match. + */ + error: string, +} + +export const expectToThrowInput = (input: ToThrowSelectorInput): void => { + const { selector, error } = input; + expect(() => { + parse(selector); + }).toThrow(error); +}; diff --git a/test/helpers/selector-query-jsdom.ts b/test/helpers/selector-query-jsdom.ts new file mode 100644 index 00000000..6e2ecfa6 --- /dev/null +++ b/test/helpers/selector-query-jsdom.ts @@ -0,0 +1,79 @@ +import { ExtCssDocument } from '../../src/selector'; + +/** + * Checks whether selectedElements and expectedElements are the same. + * + * @param selectedElements Selected by extCss querySelectorAll(). + * @param expectedElements Expected element selected by native document.querySelectorAll(). + */ +export const expectTheSameElements = (selectedElements: HTMLElement[], expectedElements: NodeListOf) => { + expect(selectedElements.length).toEqual(expectedElements.length); + expectedElements.forEach((expectedElement, index) => { + expect(selectedElements[index]).toEqual(expectedElement); + }); +}; + +/** + * Checks whether there is no element selected. + * + * @param selectedElements Selected by extCss querySelectorAll(). + */ +export const expectNoMatch = (selectedElements: HTMLElement[]) => { + expect(selectedElements.length).toEqual(0); +}; + +interface SuccessSelectorInput { + /** + * Selector for extCss querySelectorAll(). + */ + actual: string, + + /** + * Target selector for checking. + */ + expected: string, +} +export const expectSuccessInput = (input: SuccessSelectorInput): void => { + const { actual, expected } = input; + const extCssDoc = new ExtCssDocument(); + const selectedElements = extCssDoc.querySelectorAll(actual); + const expectedElements = document.querySelectorAll(expected); + expectTheSameElements(selectedElements, expectedElements); +}; + +interface NoMatchSelectorInput { + /** + * Selector for extCss querySelectorAll(). + */ + selector: string, +} +export const expectNoMatchInput = (input: NoMatchSelectorInput): void => { + const { selector } = input; + const extCssDoc = new ExtCssDocument(); + const selectedElements = extCssDoc.querySelectorAll(selector); + expectNoMatch(selectedElements); +}; + +interface ToThrowSelectorInput { + /** + * Selector for extCss querySelectorAll(). + */ + selector: string, + + /** + * Error text to match. + */ + error: string, +} +export const expectToThrowInput = (input: ToThrowSelectorInput): void => { + const { selector, error } = input; + const extCssDoc = new ExtCssDocument(); + expect(() => { + extCssDoc.querySelectorAll(selector); + }).toThrow(error); +}; + +export interface TestPropElement extends Element { + // eslint-disable-next-line @typescript-eslint/ban-types + testProp: string | Object, +} diff --git a/test/helpers/server.ts b/test/helpers/server.ts new file mode 100644 index 00000000..43fc2765 --- /dev/null +++ b/test/helpers/server.ts @@ -0,0 +1,86 @@ +/* eslint-disable no-console */ +import { + createServer, + IncomingMessage, + ServerResponse, + Server, +} from 'http'; +import { promises as fsp } from 'fs'; +import path from 'path'; + +const QUERY_START_MARKER = '?'; +const DEFAULT_PORT = 8585; +const TEST_TEMP_DIR = '../dist'; + +const initServer = (): Server => { + return createServer(async (req: IncomingMessage, res: ServerResponse): Promise => { + if (!req || !req.url) { + throw new Error('Unable to create server'); + } + let filename = req.url; + const queryStartPosition = filename.indexOf(QUERY_START_MARKER); + if (queryStartPosition && queryStartPosition > -1) { + filename = req.url.slice(0, queryStartPosition); + } + + let data; + try { + data = await fsp.readFile(path.join(__dirname, TEST_TEMP_DIR, filename)); + res.writeHead(200); + res.end(data); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (e: any) { + console.log(e.message); + res.writeHead(404); + res.end(JSON.stringify(e)); + return; + } + }); +}; + +const startServer = (server: Server, port: number | undefined): Promise => { + let logMessage: string; + if (!port) { + port = DEFAULT_PORT; + logMessage = `Server starting on default port: ${port}`; + } else { + logMessage = `Server starting on specified port: ${port}`; + } + + return new Promise((resolve) => { + server.listen(port, () => { + console.log(logMessage); + resolve(); + }); + }); +}; + +const stopServer = (server: Server): Promise => { + return new Promise((resolve) => { + server.close(() => { + resolve(); + }); + }); +}; + +const server = (() => { + let s: Server; + + const start = async (port?: number | undefined): Promise => { + s = initServer(); + console.log(' START '); + await startServer(s, port); + }; + + const stop = async (): Promise => { + console.log('Server closing'); + await stopServer(s); + }; + + return { + start, + stop, + }; +})(); + +export default server; diff --git a/test/helpers/xpath-evaluate-counter.ts b/test/helpers/xpath-evaluate-counter.ts new file mode 100644 index 00000000..ddb8d7b3 --- /dev/null +++ b/test/helpers/xpath-evaluate-counter.ts @@ -0,0 +1,19 @@ +import { ExtCssDocument } from '../../src/selector'; + +export interface XpathEvaluationResult { + counter: number, + elements: Element[], +} + +export const checkXpathEvaluation = (selector: string, document: Document): XpathEvaluationResult => { + let counter = 0; + const nativeEvaluate = Document.prototype.evaluate; + Document.prototype.evaluate = (...args) => { + counter += 1; + return nativeEvaluate.apply(document, args); + }; + const extCssDoc = new ExtCssDocument(); + const elements = extCssDoc.querySelectorAll(selector); + + return { counter, elements }; +}; diff --git a/test/index.html b/test/index.html deleted file mode 100644 index e39bfa8b..00000000 --- a/test/index.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - ExtendedCss tests - - -
    -
  1. Utils
  2. -
  3. xpath-performance
  4. -
  5. Css parser
  6. -
  7. Performance
  8. -
  9. Selector
  10. -
  11. Extended Css
  12. -
- - - - diff --git a/test/index.js b/test/index.js deleted file mode 100644 index 90c17168..00000000 --- a/test/index.js +++ /dev/null @@ -1,10 +0,0 @@ -import utils from '../lib/utils'; -import matcherUtils from '../lib/matcher-utils'; -import ExtendedSelectorFactory from '../lib/extended-css-selector'; -import ExtendedCssParser from '../lib/extended-css-parser'; -import initializeSizzle from '../lib/sizzle.patched'; -import ExtendedCss from '../lib/extended-css'; - -export { - utils, matcherUtils, initializeSizzle, ExtendedCssParser, ExtendedSelectorFactory, ExtendedCss, -}; diff --git a/test/performance-selector/jest.config.ts b/test/performance-selector/jest.config.ts new file mode 100644 index 00000000..7c22846d --- /dev/null +++ b/test/performance-selector/jest.config.ts @@ -0,0 +1,13 @@ +import type { Config } from '@jest/types'; + +export const performanceConfig: Config.InitialOptions = { + silent: true, + testPathIgnorePatterns: [ + './test/browserstack', + './tools/test', + ], + // for manual run of performance selector tests + testRegex: ['performance-selector.test.ts'], +}; + +export default performanceConfig; diff --git a/test/performance-selector/performance-selector.test.ts b/test/performance-selector/performance-selector.test.ts new file mode 100644 index 00000000..4d0b36b3 --- /dev/null +++ b/test/performance-selector/performance-selector.test.ts @@ -0,0 +1,220 @@ +import path from 'path'; +import fs from 'fs'; +import { + chromium, + Browser, + Page, +} from 'playwright'; + +import server from '../helpers/server'; +import { PerformanceResult } from '../helpers/performance-checker'; + +let browser: Browser; +let page: Page; + +declare global { + const extCssPerformance: { + checkPerformance: { + v1(selector: string): PerformanceResult; + v2(selector: string): PerformanceResult; + } + }; +} + +/** + * Returns PerformanceResult for extCss v1. + * + * @param selectorStr Css selector - standard or extended. + */ +const getV1PerformanceResult = async (selectorStr: string): Promise => { + return page.evaluate((selector): PerformanceResult => { + return extCssPerformance.checkPerformance.v1(selector); + }, selectorStr); +}; + +/** + * Returns PerformanceResult for extCss v2. + * + * @param selectorStr Css selector - standard or extended. + */ +const getV2PerformanceResult = async (selectorStr: string): Promise => { + return page.evaluate((selector: string): PerformanceResult => { + return extCssPerformance.checkPerformance.v2(selector); + }, selectorStr); +}; + +const compareV2toV1 = (averageV1: number, averageV2: number): string => { + const ratioV2toV1 = Math.round((averageV2 / averageV1) * 100); + return ratioV2toV1 <= 100 + ? `✅ ~${100 - ratioV2toV1}% faster` + : `❗️ ~${ratioV2toV1 - 100}% slower`; +}; + +const getPerformanceComparingLog = ( + selector: string, + v1Data: PerformanceResult, + v2Data: PerformanceResult, +): string => { + let log = ''; + log += '------------------------------------------------------------------------\n'; + log += `selector: ${selector}\n`; + log += '------------------------------------------------------------------------\n'; + log += 'ExtendedCss: v1 v2\n'; + log += '------------------------------------------------------------------------\n'; + log += `elapsed: ${v1Data.elapsed} ms ${v2Data.elapsed} ms\n`; + log += `count: ${v1Data.count} ${v2Data.count}\n`; + log += `average: ${v1Data.average} ms ${v2Data.average} ms\n`; + log += `result: ${compareV2toV1(v1Data.average, v2Data.average)}\n`; + log += '------------------------------------------------------------------------\n\n'; + return log; +}; + +let resultsToSave = ''; + +/** + * Saves comparison results to file in test/test-files. + * + * @param resultsStr Performance tests results. + */ +const saveResultsToFile = (resultsStr: string): void => { + const RESULTS_FILENAME = 'performance-selector-results.txt'; + const TEST_FILES_DIR_PATH = '../test-files'; + const resultsPath = path.resolve(__dirname, TEST_FILES_DIR_PATH, RESULTS_FILENAME); + fs.writeFileSync(resultsPath, resultsStr); +}; + +const SELECTOR_PERFORMANCE_PORT = 8586; + +jest.setTimeout(10 * 1000); + +describe('performance selector tests', () => { + describe('one pre rule', () => { + beforeAll(async () => { + await server.start(SELECTOR_PERFORMANCE_PORT); + browser = await chromium.launch(); + }); + afterAll(async () => { + await browser.close(); + await server.stop(); + // save results to file + saveResultsToFile(resultsToSave); + }); + + beforeEach(async () => { + page = await browser.newPage(); + await page.goto(`http://localhost:${SELECTOR_PERFORMANCE_PORT}/performance-selector.html`); + }); + afterEach(async () => { + await page.close(); + }); + + it('simple regular selector', async () => { + const selector = '.container #case1 div div'; + + const v1Data = await getV1PerformanceResult(selector); + expect(v1Data.status).toBe(true); + + const v2Data = await getV2PerformanceResult(selector); + expect(v2Data.status).toBe(true); + + resultsToSave += getPerformanceComparingLog(selector, v1Data, v2Data); + }); + + it('extended 1 - :has', async () => { + const selector = '.container #case1 div div:has(.banner)'; + + const v1Data = await getV1PerformanceResult(selector); + expect(v1Data.status).toBe(true); + + const v2Data = await getV2PerformanceResult(selector); + expect(v2Data.status).toBe(true); + + resultsToSave += getPerformanceComparingLog(selector, v1Data, v2Data); + }); + + it('extended 2 - :contains', async () => { + const selector = '.container #case2 div div:contains(Block this)'; + + const v1Data = await getV1PerformanceResult(selector); + expect(v1Data.status).toBe(true); + + const v2Data = await getV2PerformanceResult(selector); + expect(v2Data.status).toBe(true); + + resultsToSave += getPerformanceComparingLog(selector, v1Data, v2Data); + }); + + it('extended 3 - :matches-css', async () => { + const selector = '.container #case3 div div:matches-css(background-image: data:*)'; + + const v1Data = await getV1PerformanceResult(selector); + expect(v1Data.status).toBe(true); + + const v2Data = await getV2PerformanceResult(selector); + expect(v2Data.status).toBe(true); + + resultsToSave += getPerformanceComparingLog(selector, v1Data, v2Data); + }); + + it('extended 4 - :has + :contains', async () => { + const selector = '.container #case4 div div:has(.banner:contains(Block this))'; + + const v1Data = await getV1PerformanceResult(selector); + expect(v1Data.status).toBe(true); + + const v2Data = await getV2PerformanceResult(selector); + expect(v2Data.status).toBe(true); + + resultsToSave += getPerformanceComparingLog(selector, v1Data, v2Data); + }); + + it('extended 5.1 - complicated selector', async () => { + // eslint-disable-next-line max-len + const selector = '#case5 > div:not([style^="min-height:"]) > div[id][data-id^="toolkit-"]:not([data-bem]):not([data-m]):has(a[href^="https://example."]>img)'; + + const v1Data = await getV1PerformanceResult(selector); + expect(v1Data.status).toBe(true); + + const v2Data = await getV2PerformanceResult(selector); + expect(v2Data.status).toBe(true); + + resultsToSave += getPerformanceComparingLog(selector, v1Data, v2Data); + }); + + it('extended 5.2 - split selectors with a lot of children', async () => { + const selector = '#case5 div > div:has(.target-banner)'; + + const v1Data = await getV1PerformanceResult(selector); + expect(v1Data.status).toBe(true); + + const v2Data = await getV2PerformanceResult(selector); + expect(v2Data.status).toBe(true); + + resultsToSave += getPerformanceComparingLog(selector, v1Data, v2Data); + }); + + it('extended 5.3 - split selectors with a lot of children and matches-css', async () => { + const selector = '#case5 div > div:matches-css(background-image: data:*)'; + + const v1Data = await getV1PerformanceResult(selector); + expect(v1Data.status).toBe(true); + + const v2Data = await getV2PerformanceResult(selector); + expect(v2Data.status).toBe(true); + + resultsToSave += getPerformanceComparingLog(selector, v1Data, v2Data); + }); + + it('extended 6 - :xpath ', async () => { + const selector = ':xpath(//div[@class=\'target-banner\'])'; + + const v1Data = await getV1PerformanceResult(selector); + expect(v1Data.status).toBe(true); + + const v2Data = await getV2PerformanceResult(selector); + expect(v2Data.status).toBe(true); + + resultsToSave += getPerformanceComparingLog(selector, v1Data, v2Data); + }); + }); +}); diff --git a/test/performance-xpath-evaluate.test.ts b/test/performance-xpath-evaluate.test.ts new file mode 100644 index 00000000..8b58aa5b --- /dev/null +++ b/test/performance-xpath-evaluate.test.ts @@ -0,0 +1,57 @@ +import { chromium, Browser, Page } from 'playwright'; + +import server from './helpers/server'; + +import { XpathEvaluationResult } from './helpers/xpath-evaluate-counter'; + +let browser: Browser; +let page: Page; + +declare global { + const v2ExtCssPerformanceXpath: { + checkXpathEvaluation(selector: string, document: Document): XpathEvaluationResult; + }; +} + +/** + * Returns elements ids selected by extCss.querySelectorAll. + * + * @param extSelector Selector for extended css. + */ +const getXpathEvaluationResult = async (extSelector: string): Promise<[number, number]> => { + return page.evaluate((selector: string): [number, number] => { + const res = v2ExtCssPerformanceXpath.checkXpathEvaluation(selector, document); + return [res.counter, res.elements.length]; + }, extSelector); +}; + +const XPATH_PERFORMANCE_PORT = 8587; + +describe('xpath evaluation test', () => { + beforeAll(async () => { + await server.start(XPATH_PERFORMANCE_PORT); + browser = await chromium.launch(); + // can be useful for debugging + // browser = await chromium.launch({ headless: false }); + }); + afterAll(async () => { + await browser.close(); + await server.stop(); + }); + + beforeEach(async () => { + page = await browser.newPage(); + await page.goto(`http://localhost:${XPATH_PERFORMANCE_PORT}/performance-xpath-evaluate.html`); + }); + afterEach(async () => { + await page.close(); + }); + + it('extended :xpath - document.evaluate calls count ', async () => { + const selector = ':xpath(//div[@class=\'banner\'])'; + const [evaluationCount, elementsLength] = await getXpathEvaluationResult(selector); + expect(evaluationCount).toBe(1); + expect(elementsLength).toBe(12); + + }); +}); diff --git a/test/performance/performance.html b/test/performance/performance.html deleted file mode 100644 index e4b48a6a..00000000 --- a/test/performance/performance.html +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - Performance tests - - - - -
-
- - -
- -
-
-
- -
-
-
- -
-
-
- Block this -
-
-
- -
-
-
-
-
-
- -
-
-
- -
-
-
- -
-
-
-
- -
-
- - - - - - - - -
- -
-
-
-
- -
- -
- -
-
-
-
-
- - - - - - - diff --git a/test/performance/performance.test.js b/test/performance/performance.test.js deleted file mode 100644 index ff3e2805..00000000 --- a/test/performance/performance.test.js +++ /dev/null @@ -1,131 +0,0 @@ -/* eslint-disable max-len */ - -const { ExtendedSelectorFactory } = exports; -const { initializeSizzle } = exports; - -const Sizzle = initializeSizzle(); - -const LOOP_COUNT = 10000; -const MAX_ELAPSED_VALUE = 15000; - -const performanceTest = function (selector, assert) { - const startTime = new Date().getTime(); - let iCount = LOOP_COUNT; - let resultOk = true; - while (iCount--) { - const nodes = selector.querySelectorAll(); - if (!nodes || !nodes.length) { - resultOk = false; - } - } - const elapsed = new Date().getTime() - startTime; - let msg = `Elapsed: ${elapsed} ms `; - msg += `Count: ${LOOP_COUNT} `; - msg += `Average: ${elapsed / LOOP_COUNT} ms`; - console.log(msg, assert.test.testName); // eslint-disable-line no-console - if (elapsed > MAX_ELAPSED_VALUE) { - resultOk = false; - } - assert.ok(resultOk, msg); -}; - -QUnit.test('Tokenize performance', (assert) => { - initializeSizzle(); - - const selectorText = "#case5 > div:not([style^=\"min-height:\"]) > div[id][data-uniqid^=\"toolkit-\"]:not([data-bem]):not([data-mnemo])[-ext-has='a[href^=\"https://an.yandex.\"]>img']"; - const startTime = new Date().getTime(); - - let resultOk = true; - let iCount = LOOP_COUNT; - while (iCount--) { - const tokens = Sizzle.tokenize(selectorText, false, { returnUnsorted: true }); - if (!tokens || !tokens.length) { - resultOk = false; - } - } - const elapsed = new Date().getTime() - startTime; - let msg = `Elapsed: ${elapsed} ms\n`; - msg += `Count: ${LOOP_COUNT}\n`; - msg += `Average: ${elapsed / LOOP_COUNT} ms`; - assert.ok(resultOk, msg); -}); - -QUnit.test('Test simple selector', (assert) => { - const selector = { - querySelectorAll() { - return document.querySelectorAll('.container #case1 div div'); - }, - }; - performanceTest(selector, assert); -}); - -QUnit.test('Case 1. :has performance', (assert) => { - const selectorText = '.container #case1 div div:has(.banner)'; - const selector = ExtendedSelectorFactory.createSelector(selectorText); - performanceTest(selector, assert); -}); - -QUnit.test('Case 2. :contains performance', (assert) => { - const selectorText = '.container #case2 div div:contains(Block this)'; - const selector = ExtendedSelectorFactory.createSelector(selectorText); - performanceTest(selector, assert); -}); - -QUnit.test('Case 3. :matches-css performance', (assert) => { - const selectorText = '.container #case3 div div:matches-css(background-image: data:*)'; - const selector = ExtendedSelectorFactory.createSelector(selectorText); - performanceTest(selector, assert); -}); - -QUnit.test('Case 4. :has and :contains composite performance', (assert) => { - const selectorText = '.container #case4 div div:has(.banner:contains(Block this))'; - const selector = ExtendedSelectorFactory.createSelector(selectorText); - performanceTest(selector, assert); -}); - -QUnit.test('Case 5.1 complicated selector', (assert) => { - // https://github.com/AdguardTeam/ExtendedCss/issues/25 - - const selectorText = "#case5 > div:not([style^=\"min-height:\"]) > div[id][data-uniqid^=\"toolkit-\"]:not([data-bem]):not([data-mnemo])[-ext-has='a[href^=\"https://an.yandex.\"]>img']"; - const selector = ExtendedSelectorFactory.createSelector(selectorText); - performanceTest(selector, assert); -}); - -// Previous test results: Average: 0.0665 ms -> Last test results Average: 0.0409 ms -QUnit.test('Case 5.2 split selectors with a lot of children', (assert) => { - const selectorText = '#case5 div > div:has(.target-banner)'; - const selector = ExtendedSelectorFactory.createSelector(selectorText); - performanceTest(selector, assert); -}); - -// Prev test results: Average: 0.1101 ms -> Last test results Average: 0.0601 ms -QUnit.test('Case 5.3 split selectors with a lot of children and matches-css', (assert) => { - const selectorText = '#case5 div > div:matches-css(background-image: data:*)'; - const selector = ExtendedSelectorFactory.createSelector(selectorText); - performanceTest(selector, assert); -}); - -QUnit.test('Case 6.1 :xpath performance', (assert) => { - const selectorText = ':xpath(//div[@class=\'target-banner\'])'; - const selector = ExtendedSelectorFactory.createSelector(selectorText); - performanceTest(selector, assert); -}); - -QUnit.test('Case 6.2 document.evaluate calls count', (assert) => { - let counter = 0; - const nativeEvaluate = Document.prototype.evaluate; - - Document.prototype.evaluate = (...args) => { - counter += 1; - return nativeEvaluate.apply(document, args); - }; - - const selectorText = ':xpath(//div[@class=\'banner\'])'; - const selector = ExtendedSelectorFactory.createSelector(selectorText); - const nodes = selector.querySelectorAll(); - - assert.equal(nodes.length, 12); - assert.equal(counter, 1); - - Document.prototype.evaluate = nativeEvaluate; -}); diff --git a/test/qunit/qunit-2.10.0.css b/test/qunit/qunit-2.10.0.css deleted file mode 100644 index 847fff89..00000000 --- a/test/qunit/qunit-2.10.0.css +++ /dev/null @@ -1,447 +0,0 @@ -/*! - * QUnit 2.10.0 - * https://qunitjs.com/ - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license - * https://jquery.org/license - * - * Date: 2020-05-02T22:51Z - */ - -/** Font Family and Sizes */ - - -[id^=qunit] button { - font-size: initial; - border: initial; - background-color: buttonface; -} - -#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult { - font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; -} - -#qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } -#qunit-tests { font-size: smaller; } - - -/** Resets */ - -#qunit-tests, #qunit-header, #qunit-banner, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { - margin: 0; - padding: 0; -} - - -/** Header (excluding toolbar) */ - -#qunit-header { - padding: 0.5em 0 0.5em 1em; - - color: #8699A4; - background-color: #0D3349; - - font-size: 1.5em; - line-height: 1em; - font-weight: 400; - - border-radius: 5px 5px 0 0; -} - -#qunit-header a { - text-decoration: none; - color: #C2CCD1; -} - -#qunit-header a:hover, -#qunit-header a:focus { - color: #FFF; -} - -#qunit-banner { - height: 5px; -} - -#qunit-filteredTest { - padding: 0.5em 1em 0.5em 1em; - color: #366097; - background-color: #F4FF77; -} - -#qunit-userAgent { - padding: 0.5em 1em 0.5em 1em; - color: #FFF; - background-color: #2B81AF; - text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; -} - - -/** Toolbar */ - -#qunit-testrunner-toolbar { - padding: 0.5em 1em 0.5em 1em; - color: #5E740B; - background-color: #EEE; -} - -#qunit-testrunner-toolbar .clearfix { - height: 0; - clear: both; -} - -#qunit-testrunner-toolbar label { - display: inline-block; -} - -#qunit-testrunner-toolbar input[type=checkbox], -#qunit-testrunner-toolbar input[type=radio] { - margin: 3px; - vertical-align: -2px; -} - -#qunit-testrunner-toolbar input[type=text] { - box-sizing: border-box; - height: 1.6em; -} - -#qunit-toolbar-filters { - float: right; -} - -.qunit-url-config, -.qunit-filter, -#qunit-modulefilter { - display: inline-block; - line-height: 2.1em; -} - -.qunit-filter, -#qunit-modulefilter { - position: relative; - margin-left: 1em; -} - -.qunit-url-config label { - margin-right: 0.5em; -} - -#qunit-modulefilter-search { - box-sizing: border-box; - min-width: 400px; -} - -#qunit-modulefilter-search-container:after { - position: absolute; - right: 0.3em; - content: "\25bc"; - color: black; -} - -#qunit-modulefilter-dropdown { - /* align with #qunit-modulefilter-search */ - box-sizing: border-box; - min-width: 400px; - position: absolute; - right: 0; - top: 50%; - margin-top: 0.8em; - - border: 1px solid #D3D3D3; - border-top: none; - border-radius: 0 0 .25em .25em; - color: #000; - background-color: #F5F5F5; - z-index: 99; -} - -#qunit-modulefilter-dropdown a { - color: inherit; - text-decoration: none; -} - -#qunit-modulefilter-dropdown .clickable.checked { - font-weight: bold; - color: #000; - background-color: #D2E0E6; -} - -#qunit-modulefilter-dropdown .clickable:hover { - color: #FFF; - background-color: #0D3349; -} - -#qunit-modulefilter-actions { - display: block; - overflow: auto; - - /* align with #qunit-modulefilter-dropdown-list */ - font: smaller/1.5em sans-serif; -} - -#qunit-modulefilter-dropdown #qunit-modulefilter-actions > * { - box-sizing: border-box; - max-height: 2.8em; - display: block; - padding: 0.4em; -} - -#qunit-modulefilter-dropdown #qunit-modulefilter-actions > button { - float: right; - font: inherit; -} - -#qunit-modulefilter-dropdown #qunit-modulefilter-actions > :last-child { - /* insert padding to align with checkbox margins */ - padding-left: 3px; -} - -#qunit-modulefilter-dropdown-list { - max-height: 200px; - overflow-y: auto; - margin: 0; - border-top: 2px groove threedhighlight; - padding: 0.4em 0 0; - font: smaller/1.5em sans-serif; -} - -#qunit-modulefilter-dropdown-list li { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -#qunit-modulefilter-dropdown-list .clickable { - display: block; - padding-left: 0.15em; - padding-right: 0.5em; -} - - -/** Tests: Pass/Fail */ - -#qunit-tests { - list-style-position: inside; -} - -#qunit-tests li { - padding: 0.4em 1em 0.4em 1em; - border-bottom: 1px solid #FFF; - list-style-position: inside; -} - -#qunit-tests > li { - display: none; -} - -#qunit-tests li.running, -#qunit-tests li.pass, -#qunit-tests li.fail, -#qunit-tests li.skipped, -#qunit-tests li.aborted { - display: list-item; -} - -#qunit-tests.hidepass { - position: relative; -} - -#qunit-tests.hidepass li.running, -#qunit-tests.hidepass li.pass:not(.todo) { - visibility: hidden; - position: absolute; - width: 0; - height: 0; - padding: 0; - border: 0; - margin: 0; -} - -#qunit-tests li strong { - cursor: pointer; -} - -#qunit-tests li.skipped strong { - cursor: default; -} - -#qunit-tests li a { - padding: 0.5em; - color: #C2CCD1; - text-decoration: none; -} - -#qunit-tests li p a { - padding: 0.25em; - color: #6B6464; -} -#qunit-tests li a:hover, -#qunit-tests li a:focus { - color: #000; -} - -#qunit-tests li .runtime { - float: right; - font-size: smaller; -} - -.qunit-assert-list { - margin-top: 0.5em; - padding: 0.5em; - - background-color: #FFF; - - border-radius: 5px; -} - -.qunit-source { - margin: 0.6em 0 0.3em; -} - -.qunit-collapsed { - display: none; -} - -#qunit-tests table { - border-collapse: collapse; - margin-top: 0.2em; -} - -#qunit-tests th { - text-align: right; - vertical-align: top; - padding: 0 0.5em 0 0; -} - -#qunit-tests td { - vertical-align: top; -} - -#qunit-tests pre { - margin: 0; - white-space: pre-wrap; - word-wrap: break-word; -} - -#qunit-tests del { - color: #374E0C; - background-color: #E0F2BE; - text-decoration: none; -} - -#qunit-tests ins { - color: #500; - background-color: #FFCACA; - text-decoration: none; -} - -/*** Test Counts */ - -#qunit-tests b.counts { color: #000; } -#qunit-tests b.passed { color: #5E740B; } -#qunit-tests b.failed { color: #710909; } - -#qunit-tests li li { - padding: 5px; - background-color: #FFF; - border-bottom: none; - list-style-position: inside; -} - -/*** Passing Styles */ - -#qunit-tests li li.pass { - color: #3C510C; - background-color: #FFF; - border-left: 10px solid #C6E746; -} - -#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } -#qunit-tests .pass .test-name { color: #366097; } - -#qunit-tests .pass .test-actual, -#qunit-tests .pass .test-expected { color: #999; } - -#qunit-banner.qunit-pass { background-color: #C6E746; } - -/*** Failing Styles */ - -#qunit-tests li li.fail { - color: #710909; - background-color: #FFF; - border-left: 10px solid #EE5757; - white-space: pre; -} - -#qunit-tests > li:last-child { - border-radius: 0 0 5px 5px; -} - -#qunit-tests .fail { color: #000; background-color: #EE5757; } -#qunit-tests .fail .test-name, -#qunit-tests .fail .module-name { color: #000; } - -#qunit-tests .fail .test-actual { color: #EE5757; } -#qunit-tests .fail .test-expected { color: #008000; } - -#qunit-banner.qunit-fail { background-color: #EE5757; } - - -/*** Aborted tests */ -#qunit-tests .aborted { color: #000; background-color: orange; } -/*** Skipped tests */ - -#qunit-tests .skipped { - background-color: #EBECE9; -} - -#qunit-tests .qunit-todo-label, -#qunit-tests .qunit-skipped-label { - background-color: #F4FF77; - display: inline-block; - font-style: normal; - color: #366097; - line-height: 1.8em; - padding: 0 0.5em; - margin: -0.4em 0.4em -0.4em 0; -} - -#qunit-tests .qunit-todo-label { - background-color: #EEE; -} - -/** Result */ - -#qunit-testresult { - color: #2B81AF; - background-color: #D2E0E6; - - border-bottom: 1px solid #FFF; -} -#qunit-testresult .clearfix { - height: 0; - clear: both; -} -#qunit-testresult .module-name { - font-weight: 700; -} -#qunit-testresult-display { - padding: 0.5em 1em 0.5em 1em; - width: 85%; - float:left; -} -#qunit-testresult-controls { - padding: 0.5em 1em 0.5em 1em; - width: 10%; - float:left; -} - -/** Fixture */ - -#qunit-fixture { - position: absolute; - top: -10000px; - left: -10000px; - width: 1000px; - height: 1000px; -} diff --git a/test/qunit/qunit-2.10.0.js b/test/qunit/qunit-2.10.0.js deleted file mode 100644 index b4c2eeaa..00000000 --- a/test/qunit/qunit-2.10.0.js +++ /dev/null @@ -1,6643 +0,0 @@ -/*! - * QUnit 2.10.0 - * https://qunitjs.com/ - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license - * https://jquery.org/license - * - * Date: 2020-05-02T22:51Z - */ -(function (global$1) { - 'use strict'; - - global$1 = global$1 && global$1.hasOwnProperty('default') ? global$1['default'] : global$1; - - var window$1 = global$1.window; - var self$1 = global$1.self; - var console = global$1.console; - var setTimeout$1 = global$1.setTimeout; - var clearTimeout = global$1.clearTimeout; - - var document$1 = window$1 && window$1.document; - var navigator = window$1 && window$1.navigator; - - var localSessionStorage = function () { - var x = "qunit-test-string"; - try { - global$1.sessionStorage.setItem(x, x); - global$1.sessionStorage.removeItem(x); - return global$1.sessionStorage; - } catch (e) { - return undefined; - } - }(); - - /** - * Returns a function that proxies to the given method name on the globals - * console object. The proxy will also detect if the console doesn't exist and - * will appropriately no-op. This allows support for IE9, which doesn't have a - * console if the developer tools are not open. - */ - function consoleProxy(method) { - return function () { - if (console) { - console[method].apply(console, arguments); - } - }; - } - - var Logger = { - warn: consoleProxy("warn") - }; - - var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { - return typeof obj; - } : function (obj) { - return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; - }; - - - - - - - - - - - - var classCallCheck = function (instance, Constructor) { - if (!(instance instanceof Constructor)) { - throw new TypeError("Cannot call a class as a function"); - } - }; - - var createClass = function () { - function defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if ("value" in descriptor) descriptor.writable = true; - Object.defineProperty(target, descriptor.key, descriptor); - } - } - - return function (Constructor, protoProps, staticProps) { - if (protoProps) defineProperties(Constructor.prototype, protoProps); - if (staticProps) defineProperties(Constructor, staticProps); - return Constructor; - }; - }(); - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - var toConsumableArray = function (arr) { - if (Array.isArray(arr)) { - for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; - - return arr2; - } else { - return Array.from(arr); - } - }; - - var toString = Object.prototype.toString; - var hasOwn = Object.prototype.hasOwnProperty; - var now = Date.now || function () { - return new Date().getTime(); - }; - - var hasPerformanceApi = detectPerformanceApi(); - var performance = hasPerformanceApi ? window$1.performance : undefined; - var performanceNow = hasPerformanceApi ? performance.now.bind(performance) : now; - - function detectPerformanceApi() { - return window$1 && typeof window$1.performance !== "undefined" && typeof window$1.performance.mark === "function" && typeof window$1.performance.measure === "function"; - } - - function measure(comment, startMark, endMark) { - - // `performance.measure` may fail if the mark could not be found. - // reasons a specific mark could not be found include: outside code invoking `performance.clearMarks()` - try { - performance.measure(comment, startMark, endMark); - } catch (ex) { - Logger.warn("performance.measure could not be executed because of ", ex.message); - } - } - - var defined = { - document: window$1 && window$1.document !== undefined, - setTimeout: setTimeout$1 !== undefined - }; - - // Returns a new Array with the elements that are in a but not in b - function diff(a, b) { - var i, - j, - result = a.slice(); - - for (i = 0; i < result.length; i++) { - for (j = 0; j < b.length; j++) { - if (result[i] === b[j]) { - result.splice(i, 1); - i--; - break; - } - } - } - return result; - } - - /** - * Determines whether an element exists in a given array or not. - * - * @method inArray - * @param {Any} elem - * @param {Array} array - * @return {Boolean} - */ - function inArray(elem, array) { - return array.indexOf(elem) !== -1; - } - - /** - * Makes a clone of an object using only Array or Object as base, - * and copies over the own enumerable properties. - * - * @param {Object} obj - * @return {Object} New object with only the own properties (recursively). - */ - function objectValues(obj) { - var key, - val, - vals = is("array", obj) ? [] : {}; - for (key in obj) { - if (hasOwn.call(obj, key)) { - val = obj[key]; - vals[key] = val === Object(val) ? objectValues(val) : val; - } - } - return vals; - } - - function extend(a, b, undefOnly) { - for (var prop in b) { - if (hasOwn.call(b, prop)) { - if (b[prop] === undefined) { - delete a[prop]; - } else if (!(undefOnly && typeof a[prop] !== "undefined")) { - a[prop] = b[prop]; - } - } - } - - return a; - } - - function objectType(obj) { - if (typeof obj === "undefined") { - return "undefined"; - } - - // Consider: typeof null === object - if (obj === null) { - return "null"; - } - - var match = toString.call(obj).match(/^\[object\s(.*)\]$/), - type = match && match[1]; - - switch (type) { - case "Number": - if (isNaN(obj)) { - return "nan"; - } - return "number"; - case "String": - case "Boolean": - case "Array": - case "Set": - case "Map": - case "Date": - case "RegExp": - case "Function": - case "Symbol": - return type.toLowerCase(); - default: - return typeof obj === "undefined" ? "undefined" : _typeof(obj); - } - } - - // Safe object type checking - function is(type, obj) { - return objectType(obj) === type; - } - - // Based on Java's String.hashCode, a simple but not - // rigorously collision resistant hashing function - function generateHash(module, testName) { - var str = module + "\x1C" + testName; - var hash = 0; - - for (var i = 0; i < str.length; i++) { - hash = (hash << 5) - hash + str.charCodeAt(i); - hash |= 0; - } - - // Convert the possibly negative integer hash code into an 8 character hex string, which isn't - // strictly necessary but increases user understanding that the id is a SHA-like hash - var hex = (0x100000000 + hash).toString(16); - if (hex.length < 8) { - hex = "0000000" + hex; - } - - return hex.slice(-8); - } - - // Test for equality any JavaScript type. - // Authors: Philippe Rathé , David Chan - var equiv = (function () { - - // Value pairs queued for comparison. Used for breadth-first processing order, recursion - // detection and avoiding repeated comparison (see below for details). - // Elements are { a: val, b: val }. - var pairs = []; - - var getProto = Object.getPrototypeOf || function (obj) { - return obj.__proto__; - }; - - function useStrictEquality(a, b) { - - // This only gets called if a and b are not strict equal, and is used to compare on - // the primitive values inside object wrappers. For example: - // `var i = 1;` - // `var j = new Number(1);` - // Neither a nor b can be null, as a !== b and they have the same type. - if ((typeof a === "undefined" ? "undefined" : _typeof(a)) === "object") { - a = a.valueOf(); - } - if ((typeof b === "undefined" ? "undefined" : _typeof(b)) === "object") { - b = b.valueOf(); - } - - return a === b; - } - - function compareConstructors(a, b) { - var protoA = getProto(a); - var protoB = getProto(b); - - // Comparing constructors is more strict than using `instanceof` - if (a.constructor === b.constructor) { - return true; - } - - // Ref #851 - // If the obj prototype descends from a null constructor, treat it - // as a null prototype. - if (protoA && protoA.constructor === null) { - protoA = null; - } - if (protoB && protoB.constructor === null) { - protoB = null; - } - - // Allow objects with no prototype to be equivalent to - // objects with Object as their constructor. - if (protoA === null && protoB === Object.prototype || protoB === null && protoA === Object.prototype) { - return true; - } - - return false; - } - - function getRegExpFlags(regexp) { - return "flags" in regexp ? regexp.flags : regexp.toString().match(/[gimuy]*$/)[0]; - } - - function isContainer(val) { - return ["object", "array", "map", "set"].indexOf(objectType(val)) !== -1; - } - - function breadthFirstCompareChild(a, b) { - - // If a is a container not reference-equal to b, postpone the comparison to the - // end of the pairs queue -- unless (a, b) has been seen before, in which case skip - // over the pair. - if (a === b) { - return true; - } - if (!isContainer(a)) { - return typeEquiv(a, b); - } - if (pairs.every(function (pair) { - return pair.a !== a || pair.b !== b; - })) { - - // Not yet started comparing this pair - pairs.push({ a: a, b: b }); - } - return true; - } - - var callbacks = { - "string": useStrictEquality, - "boolean": useStrictEquality, - "number": useStrictEquality, - "null": useStrictEquality, - "undefined": useStrictEquality, - "symbol": useStrictEquality, - "date": useStrictEquality, - - "nan": function nan() { - return true; - }, - - "regexp": function regexp(a, b) { - return a.source === b.source && - - // Include flags in the comparison - getRegExpFlags(a) === getRegExpFlags(b); - }, - - // abort (identical references / instance methods were skipped earlier) - "function": function _function() { - return false; - }, - - "array": function array(a, b) { - var i, len; - - len = a.length; - if (len !== b.length) { - - // Safe and faster - return false; - } - - for (i = 0; i < len; i++) { - - // Compare non-containers; queue non-reference-equal containers - if (!breadthFirstCompareChild(a[i], b[i])) { - return false; - } - } - return true; - }, - - // Define sets a and b to be equivalent if for each element aVal in a, there - // is some element bVal in b such that aVal and bVal are equivalent. Element - // repetitions are not counted, so these are equivalent: - // a = new Set( [ {}, [], [] ] ); - // b = new Set( [ {}, {}, [] ] ); - "set": function set$$1(a, b) { - var innerEq, - outerEq = true; - - if (a.size !== b.size) { - - // This optimization has certain quirks because of the lack of - // repetition counting. For instance, adding the same - // (reference-identical) element to two equivalent sets can - // make them non-equivalent. - return false; - } - - a.forEach(function (aVal) { - - // Short-circuit if the result is already known. (Using for...of - // with a break clause would be cleaner here, but it would cause - // a syntax error on older Javascript implementations even if - // Set is unused) - if (!outerEq) { - return; - } - - innerEq = false; - - b.forEach(function (bVal) { - var parentPairs; - - // Likewise, short-circuit if the result is already known - if (innerEq) { - return; - } - - // Swap out the global pairs list, as the nested call to - // innerEquiv will clobber its contents - parentPairs = pairs; - if (innerEquiv(bVal, aVal)) { - innerEq = true; - } - - // Replace the global pairs list - pairs = parentPairs; - }); - - if (!innerEq) { - outerEq = false; - } - }); - - return outerEq; - }, - - // Define maps a and b to be equivalent if for each key-value pair (aKey, aVal) - // in a, there is some key-value pair (bKey, bVal) in b such that - // [ aKey, aVal ] and [ bKey, bVal ] are equivalent. Key repetitions are not - // counted, so these are equivalent: - // a = new Map( [ [ {}, 1 ], [ {}, 1 ], [ [], 1 ] ] ); - // b = new Map( [ [ {}, 1 ], [ [], 1 ], [ [], 1 ] ] ); - "map": function map(a, b) { - var innerEq, - outerEq = true; - - if (a.size !== b.size) { - - // This optimization has certain quirks because of the lack of - // repetition counting. For instance, adding the same - // (reference-identical) key-value pair to two equivalent maps - // can make them non-equivalent. - return false; - } - - a.forEach(function (aVal, aKey) { - - // Short-circuit if the result is already known. (Using for...of - // with a break clause would be cleaner here, but it would cause - // a syntax error on older Javascript implementations even if - // Map is unused) - if (!outerEq) { - return; - } - - innerEq = false; - - b.forEach(function (bVal, bKey) { - var parentPairs; - - // Likewise, short-circuit if the result is already known - if (innerEq) { - return; - } - - // Swap out the global pairs list, as the nested call to - // innerEquiv will clobber its contents - parentPairs = pairs; - if (innerEquiv([bVal, bKey], [aVal, aKey])) { - innerEq = true; - } - - // Replace the global pairs list - pairs = parentPairs; - }); - - if (!innerEq) { - outerEq = false; - } - }); - - return outerEq; - }, - - "object": function object(a, b) { - var i, - aProperties = [], - bProperties = []; - - if (compareConstructors(a, b) === false) { - return false; - } - - // Be strict: don't ensure hasOwnProperty and go deep - for (i in a) { - - // Collect a's properties - aProperties.push(i); - - // Skip OOP methods that look the same - if (a.constructor !== Object && typeof a.constructor !== "undefined" && typeof a[i] === "function" && typeof b[i] === "function" && a[i].toString() === b[i].toString()) { - continue; - } - - // Compare non-containers; queue non-reference-equal containers - if (!breadthFirstCompareChild(a[i], b[i])) { - return false; - } - } - - for (i in b) { - - // Collect b's properties - bProperties.push(i); - } - - // Ensures identical properties name - return typeEquiv(aProperties.sort(), bProperties.sort()); - } - }; - - function typeEquiv(a, b) { - var type = objectType(a); - - // Callbacks for containers will append to the pairs queue to achieve breadth-first - // search order. The pairs queue is also used to avoid reprocessing any pair of - // containers that are reference-equal to a previously visited pair (a special case - // this being recursion detection). - // - // Because of this approach, once typeEquiv returns a false value, it should not be - // called again without clearing the pair queue else it may wrongly report a visited - // pair as being equivalent. - return objectType(b) === type && callbacks[type](a, b); - } - - function innerEquiv(a, b) { - var i, pair; - - // We're done when there's nothing more to compare - if (arguments.length < 2) { - return true; - } - - // Clear the global pair queue and add the top-level values being compared - pairs = [{ a: a, b: b }]; - - for (i = 0; i < pairs.length; i++) { - pair = pairs[i]; - - // Perform type-specific comparison on any pairs that are not strictly - // equal. For container types, that comparison will postpone comparison - // of any sub-container pair to the end of the pair queue. This gives - // breadth-first search order. It also avoids the reprocessing of - // reference-equal siblings, cousins etc, which can have a significant speed - // impact when comparing a container of small objects each of which has a - // reference to the same (singleton) large object. - if (pair.a !== pair.b && !typeEquiv(pair.a, pair.b)) { - return false; - } - } - - // ...across all consecutive argument pairs - return arguments.length === 2 || innerEquiv.apply(this, [].slice.call(arguments, 1)); - } - - return function () { - var result = innerEquiv.apply(undefined, arguments); - - // Release any retained objects - pairs.length = 0; - return result; - }; - })(); - - /** - * Config object: Maintain internal state - * Later exposed as QUnit.config - * `config` initialized at top of scope - */ - var config = { - - // The queue of tests to run - queue: [], - - // Block until document ready - blocking: true, - - // By default, run previously failed tests first - // very useful in combination with "Hide passed tests" checked - reorder: true, - - // By default, modify document.title when suite is done - altertitle: true, - - // HTML Reporter: collapse every test except the first failing test - // If false, all failing tests will be expanded - collapse: true, - - // By default, scroll to top of the page when suite is done - scrolltop: true, - - // Depth up-to which object will be dumped - maxDepth: 5, - - // When enabled, all tests must call expect() - requireExpects: false, - - // Placeholder for user-configurable form-exposed URL parameters - urlConfig: [], - - // Set of all modules. - modules: [], - - // The first unnamed module - currentModule: { - name: "", - tests: [], - childModules: [], - testsRun: 0, - unskippedTestsRun: 0, - hooks: { - before: [], - beforeEach: [], - afterEach: [], - after: [] - } - }, - - callbacks: {}, - - // The storage module to use for reordering tests - storage: localSessionStorage - }; - - // take a predefined QUnit.config and extend the defaults - var globalConfig = window$1 && window$1.QUnit && window$1.QUnit.config; - - // only extend the global config if there is no QUnit overload - if (window$1 && window$1.QUnit && !window$1.QUnit.version) { - extend(config, globalConfig); - } - - // Push a loose unnamed module to the modules collection - config.modules.push(config.currentModule); - - // Based on jsDump by Ariel Flesler - // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html - var dump = (function () { - function quote(str) { - return "\"" + str.toString().replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + "\""; - } - function literal(o) { - return o + ""; - } - function join(pre, arr, post) { - var s = dump.separator(), - base = dump.indent(), - inner = dump.indent(1); - if (arr.join) { - arr = arr.join("," + s + inner); - } - if (!arr) { - return pre + post; - } - return [pre, inner + arr, base + post].join(s); - } - function array(arr, stack) { - var i = arr.length, - ret = new Array(i); - - if (dump.maxDepth && dump.depth > dump.maxDepth) { - return "[object Array]"; - } - - this.up(); - while (i--) { - ret[i] = this.parse(arr[i], undefined, stack); - } - this.down(); - return join("[", ret, "]"); - } - - function isArray(obj) { - return ( - - //Native Arrays - toString.call(obj) === "[object Array]" || - - // NodeList objects - typeof obj.length === "number" && obj.item !== undefined && (obj.length ? obj.item(0) === obj[0] : obj.item(0) === null && obj[0] === undefined) - ); - } - - var reName = /^function (\w+)/, - dump = { - - // The objType is used mostly internally, you can fix a (custom) type in advance - parse: function parse(obj, objType, stack) { - stack = stack || []; - var res, - parser, - parserType, - objIndex = stack.indexOf(obj); - - if (objIndex !== -1) { - return "recursion(" + (objIndex - stack.length) + ")"; - } - - objType = objType || this.typeOf(obj); - parser = this.parsers[objType]; - parserType = typeof parser === "undefined" ? "undefined" : _typeof(parser); - - if (parserType === "function") { - stack.push(obj); - res = parser.call(this, obj, stack); - stack.pop(); - return res; - } - return parserType === "string" ? parser : this.parsers.error; - }, - typeOf: function typeOf(obj) { - var type; - - if (obj === null) { - type = "null"; - } else if (typeof obj === "undefined") { - type = "undefined"; - } else if (is("regexp", obj)) { - type = "regexp"; - } else if (is("date", obj)) { - type = "date"; - } else if (is("function", obj)) { - type = "function"; - } else if (obj.setInterval !== undefined && obj.document !== undefined && obj.nodeType === undefined) { - type = "window"; - } else if (obj.nodeType === 9) { - type = "document"; - } else if (obj.nodeType) { - type = "node"; - } else if (isArray(obj)) { - type = "array"; - } else if (obj.constructor === Error.prototype.constructor) { - type = "error"; - } else { - type = typeof obj === "undefined" ? "undefined" : _typeof(obj); - } - return type; - }, - - separator: function separator() { - if (this.multiline) { - return this.HTML ? "
" : "\n"; - } else { - return this.HTML ? " " : " "; - } - }, - - // Extra can be a number, shortcut for increasing-calling-decreasing - indent: function indent(extra) { - if (!this.multiline) { - return ""; - } - var chr = this.indentChar; - if (this.HTML) { - chr = chr.replace(/\t/g, " ").replace(/ /g, " "); - } - return new Array(this.depth + (extra || 0)).join(chr); - }, - up: function up(a) { - this.depth += a || 1; - }, - down: function down(a) { - this.depth -= a || 1; - }, - setParser: function setParser(name, parser) { - this.parsers[name] = parser; - }, - - // The next 3 are exposed so you can use them - quote: quote, - literal: literal, - join: join, - depth: 1, - maxDepth: config.maxDepth, - - // This is the list of parsers, to modify them, use dump.setParser - parsers: { - window: "[Window]", - document: "[Document]", - error: function error(_error) { - return "Error(\"" + _error.message + "\")"; - }, - unknown: "[Unknown]", - "null": "null", - "undefined": "undefined", - "function": function _function(fn) { - var ret = "function", - - - // Functions never have name in IE - name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1]; - - if (name) { - ret += " " + name; - } - ret += "("; - - ret = [ret, dump.parse(fn, "functionArgs"), "){"].join(""); - return join(ret, dump.parse(fn, "functionCode"), "}"); - }, - array: array, - nodelist: array, - "arguments": array, - object: function object(map, stack) { - var keys, - key, - val, - i, - nonEnumerableProperties, - ret = []; - - if (dump.maxDepth && dump.depth > dump.maxDepth) { - return "[object Object]"; - } - - dump.up(); - keys = []; - for (key in map) { - keys.push(key); - } - - // Some properties are not always enumerable on Error objects. - nonEnumerableProperties = ["message", "name"]; - for (i in nonEnumerableProperties) { - key = nonEnumerableProperties[i]; - if (key in map && !inArray(key, keys)) { - keys.push(key); - } - } - keys.sort(); - for (i = 0; i < keys.length; i++) { - key = keys[i]; - val = map[key]; - ret.push(dump.parse(key, "key") + ": " + dump.parse(val, undefined, stack)); - } - dump.down(); - return join("{", ret, "}"); - }, - node: function node(_node) { - var len, - i, - val, - open = dump.HTML ? "<" : "<", - close = dump.HTML ? ">" : ">", - tag = _node.nodeName.toLowerCase(), - ret = open + tag, - attrs = _node.attributes; - - if (attrs) { - for (i = 0, len = attrs.length; i < len; i++) { - val = attrs[i].nodeValue; - - // IE6 includes all attributes in .attributes, even ones not explicitly - // set. Those have values like undefined, null, 0, false, "" or - // "inherit". - if (val && val !== "inherit") { - ret += " " + attrs[i].nodeName + "=" + dump.parse(val, "attribute"); - } - } - } - ret += close; - - // Show content of TextNode or CDATASection - if (_node.nodeType === 3 || _node.nodeType === 4) { - ret += _node.nodeValue; - } - - return ret + open + "/" + tag + close; - }, - - // Function calls it internally, it's the arguments part of the function - functionArgs: function functionArgs(fn) { - var args, - l = fn.length; - - if (!l) { - return ""; - } - - args = new Array(l); - while (l--) { - - // 97 is 'a' - args[l] = String.fromCharCode(97 + l); - } - return " " + args.join(", ") + " "; - }, - - // Object calls it internally, the key part of an item in a map - key: quote, - - // Function calls it internally, it's the content of the function - functionCode: "[code]", - - // Node calls it internally, it's a html attribute value - attribute: quote, - string: quote, - date: quote, - regexp: literal, - number: literal, - "boolean": literal, - symbol: function symbol(sym) { - return sym.toString(); - } - }, - - // If true, entities are escaped ( <, >, \t, space and \n ) - HTML: false, - - // Indentation unit - indentChar: " ", - - // If true, items in a collection, are separated by a \n, else just a space. - multiline: true - }; - - return dump; - })(); - - var SuiteReport = function () { - function SuiteReport(name, parentSuite) { - classCallCheck(this, SuiteReport); - - this.name = name; - this.fullName = parentSuite ? parentSuite.fullName.concat(name) : []; - - this.tests = []; - this.childSuites = []; - - if (parentSuite) { - parentSuite.pushChildSuite(this); - } - } - - createClass(SuiteReport, [{ - key: "start", - value: function start(recordTime) { - if (recordTime) { - this._startTime = performanceNow(); - - if (performance) { - var suiteLevel = this.fullName.length; - performance.mark("qunit_suite_" + suiteLevel + "_start"); - } - } - - return { - name: this.name, - fullName: this.fullName.slice(), - tests: this.tests.map(function (test) { - return test.start(); - }), - childSuites: this.childSuites.map(function (suite) { - return suite.start(); - }), - testCounts: { - total: this.getTestCounts().total - } - }; - } - }, { - key: "end", - value: function end(recordTime) { - if (recordTime) { - this._endTime = performanceNow(); - - if (performance) { - var suiteLevel = this.fullName.length; - performance.mark("qunit_suite_" + suiteLevel + "_end"); - - var suiteName = this.fullName.join(" – "); - - measure(suiteLevel === 0 ? "QUnit Test Run" : "QUnit Test Suite: " + suiteName, "qunit_suite_" + suiteLevel + "_start", "qunit_suite_" + suiteLevel + "_end"); - } - } - - return { - name: this.name, - fullName: this.fullName.slice(), - tests: this.tests.map(function (test) { - return test.end(); - }), - childSuites: this.childSuites.map(function (suite) { - return suite.end(); - }), - testCounts: this.getTestCounts(), - runtime: this.getRuntime(), - status: this.getStatus() - }; - } - }, { - key: "pushChildSuite", - value: function pushChildSuite(suite) { - this.childSuites.push(suite); - } - }, { - key: "pushTest", - value: function pushTest(test) { - this.tests.push(test); - } - }, { - key: "getRuntime", - value: function getRuntime() { - return this._endTime - this._startTime; - } - }, { - key: "getTestCounts", - value: function getTestCounts() { - var counts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { passed: 0, failed: 0, skipped: 0, todo: 0, total: 0 }; - - counts = this.tests.reduce(function (counts, test) { - if (test.valid) { - counts[test.getStatus()]++; - counts.total++; - } - - return counts; - }, counts); - - return this.childSuites.reduce(function (counts, suite) { - return suite.getTestCounts(counts); - }, counts); - } - }, { - key: "getStatus", - value: function getStatus() { - var _getTestCounts = this.getTestCounts(), - total = _getTestCounts.total, - failed = _getTestCounts.failed, - skipped = _getTestCounts.skipped, - todo = _getTestCounts.todo; - - if (failed) { - return "failed"; - } else { - if (skipped === total) { - return "skipped"; - } else if (todo === total) { - return "todo"; - } else { - return "passed"; - } - } - } - }]); - return SuiteReport; - }(); - - var focused = false; - - var moduleStack = []; - - function isParentModuleInQueue() { - var modulesInQueue = config.modules.map(function (module) { - return module.moduleId; - }); - return moduleStack.some(function (module) { - return modulesInQueue.includes(module.moduleId); - }); - } - - function createModule(name, testEnvironment, modifiers) { - var parentModule = moduleStack.length ? moduleStack.slice(-1)[0] : null; - var moduleName = parentModule !== null ? [parentModule.name, name].join(" > ") : name; - var parentSuite = parentModule ? parentModule.suiteReport : globalSuite; - - var skip = parentModule !== null && parentModule.skip || modifiers.skip; - var todo = parentModule !== null && parentModule.todo || modifiers.todo; - - var module = { - name: moduleName, - parentModule: parentModule, - tests: [], - moduleId: generateHash(moduleName), - testsRun: 0, - unskippedTestsRun: 0, - childModules: [], - suiteReport: new SuiteReport(name, parentSuite), - - // Pass along `skip` and `todo` properties from parent module, in case - // there is one, to childs. And use own otherwise. - // This property will be used to mark own tests and tests of child suites - // as either `skipped` or `todo`. - skip: skip, - todo: skip ? false : todo - }; - - var env = {}; - if (parentModule) { - parentModule.childModules.push(module); - extend(env, parentModule.testEnvironment); - } - extend(env, testEnvironment); - module.testEnvironment = env; - - config.modules.push(module); - return module; - } - - function processModule(name, options, executeNow) { - var modifiers = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; - - if (objectType(options) === "function") { - executeNow = options; - options = undefined; - } - - var module = createModule(name, options, modifiers); - - // Move any hooks to a 'hooks' object - var testEnvironment = module.testEnvironment; - var hooks = module.hooks = {}; - - setHookFromEnvironment(hooks, testEnvironment, "before"); - setHookFromEnvironment(hooks, testEnvironment, "beforeEach"); - setHookFromEnvironment(hooks, testEnvironment, "afterEach"); - setHookFromEnvironment(hooks, testEnvironment, "after"); - - var moduleFns = { - before: setHookFunction(module, "before"), - beforeEach: setHookFunction(module, "beforeEach"), - afterEach: setHookFunction(module, "afterEach"), - after: setHookFunction(module, "after") - }; - - var currentModule = config.currentModule; - if (objectType(executeNow) === "function") { - moduleStack.push(module); - config.currentModule = module; - executeNow.call(module.testEnvironment, moduleFns); - moduleStack.pop(); - module = module.parentModule || currentModule; - } - - config.currentModule = module; - - function setHookFromEnvironment(hooks, environment, name) { - var potentialHook = environment[name]; - hooks[name] = typeof potentialHook === "function" ? [potentialHook] : []; - delete environment[name]; - } - - function setHookFunction(module, hookName) { - return function setHook(callback) { - module.hooks[hookName].push(callback); - }; - } - } - - function module$1(name, options, executeNow) { - if (focused && !isParentModuleInQueue()) { - return; - } - - processModule(name, options, executeNow); - } - - module$1.only = function () { - if (!focused) { - config.modules.length = 0; - config.queue.length = 0; - } - - processModule.apply(undefined, arguments); - - focused = true; - }; - - module$1.skip = function (name, options, executeNow) { - if (focused) { - return; - } - - processModule(name, options, executeNow, { skip: true }); - }; - - module$1.todo = function (name, options, executeNow) { - if (focused) { - return; - } - - processModule(name, options, executeNow, { todo: true }); - }; - - var LISTENERS = Object.create(null); - var SUPPORTED_EVENTS = ["runStart", "suiteStart", "testStart", "assertion", "testEnd", "suiteEnd", "runEnd"]; - - /** - * Emits an event with the specified data to all currently registered listeners. - * Callbacks will fire in the order in which they are registered (FIFO). This - * function is not exposed publicly; it is used by QUnit internals to emit - * logging events. - * - * @private - * @method emit - * @param {String} eventName - * @param {Object} data - * @return {Void} - */ - function emit(eventName, data) { - if (objectType(eventName) !== "string") { - throw new TypeError("eventName must be a string when emitting an event"); - } - - // Clone the callbacks in case one of them registers a new callback - var originalCallbacks = LISTENERS[eventName]; - var callbacks = originalCallbacks ? [].concat(toConsumableArray(originalCallbacks)) : []; - - for (var i = 0; i < callbacks.length; i++) { - callbacks[i](data); - } - } - - /** - * Registers a callback as a listener to the specified event. - * - * @public - * @method on - * @param {String} eventName - * @param {Function} callback - * @return {Void} - */ - function on(eventName, callback) { - if (objectType(eventName) !== "string") { - throw new TypeError("eventName must be a string when registering a listener"); - } else if (!inArray(eventName, SUPPORTED_EVENTS)) { - var events = SUPPORTED_EVENTS.join(", "); - throw new Error("\"" + eventName + "\" is not a valid event; must be one of: " + events + "."); - } else if (objectType(callback) !== "function") { - throw new TypeError("callback must be a function when registering a listener"); - } - - if (!LISTENERS[eventName]) { - LISTENERS[eventName] = []; - } - - // Don't register the same callback more than once - if (!inArray(callback, LISTENERS[eventName])) { - LISTENERS[eventName].push(callback); - } - } - - function objectOrFunction(x) { - var type = typeof x === 'undefined' ? 'undefined' : _typeof(x); - return x !== null && (type === 'object' || type === 'function'); - } - - function isFunction(x) { - return typeof x === 'function'; - } - - - - var _isArray = void 0; - if (Array.isArray) { - _isArray = Array.isArray; - } else { - _isArray = function _isArray(x) { - return Object.prototype.toString.call(x) === '[object Array]'; - }; - } - - var isArray = _isArray; - - var len = 0; - var vertxNext = void 0; - var customSchedulerFn = void 0; - - var asap = function asap(callback, arg) { - queue[len] = callback; - queue[len + 1] = arg; - len += 2; - if (len === 2) { - // If len is 2, that means that we need to schedule an async flush. - // If additional callbacks are queued before the queue is flushed, they - // will be processed by this flush that we are scheduling. - if (customSchedulerFn) { - customSchedulerFn(flush); - } else { - scheduleFlush(); - } - } - }; - - function setScheduler(scheduleFn) { - customSchedulerFn = scheduleFn; - } - - function setAsap(asapFn) { - asap = asapFn; - } - - var browserWindow = typeof window !== 'undefined' ? window : undefined; - var browserGlobal = browserWindow || {}; - var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver; - var isNode = typeof self === 'undefined' && typeof process !== 'undefined' && {}.toString.call(process) === '[object process]'; - - // test for web worker but not in IE10 - var isWorker = typeof Uint8ClampedArray !== 'undefined' && typeof importScripts !== 'undefined' && typeof MessageChannel !== 'undefined'; - - // node - function useNextTick() { - // node version 0.10.x displays a deprecation warning when nextTick is used recursively - // see https://github.com/cujojs/when/issues/410 for details - return function () { - return process.nextTick(flush); - }; - } - - // vertx - function useVertxTimer() { - if (typeof vertxNext !== 'undefined') { - return function () { - vertxNext(flush); - }; - } - - return useSetTimeout(); - } - - function useMutationObserver() { - var iterations = 0; - var observer = new BrowserMutationObserver(flush); - var node = document.createTextNode(''); - observer.observe(node, { characterData: true }); - - return function () { - node.data = iterations = ++iterations % 2; - }; - } - - // web worker - function useMessageChannel() { - var channel = new MessageChannel(); - channel.port1.onmessage = flush; - return function () { - return channel.port2.postMessage(0); - }; - } - - function useSetTimeout() { - // Store setTimeout reference so es6-promise will be unaffected by - // other code modifying setTimeout (like sinon.useFakeTimers()) - var globalSetTimeout = setTimeout; - return function () { - return globalSetTimeout(flush, 1); - }; - } - - var queue = new Array(1000); - function flush() { - for (var i = 0; i < len; i += 2) { - var callback = queue[i]; - var arg = queue[i + 1]; - - callback(arg); - - queue[i] = undefined; - queue[i + 1] = undefined; - } - - len = 0; - } - - function attemptVertx() { - try { - var vertx = Function('return this')().require('vertx'); - vertxNext = vertx.runOnLoop || vertx.runOnContext; - return useVertxTimer(); - } catch (e) { - return useSetTimeout(); - } - } - - var scheduleFlush = void 0; - // Decide what async method to use to triggering processing of queued callbacks: - if (isNode) { - scheduleFlush = useNextTick(); - } else if (BrowserMutationObserver) { - scheduleFlush = useMutationObserver(); - } else if (isWorker) { - scheduleFlush = useMessageChannel(); - } else if (browserWindow === undefined && typeof require === 'function') { - scheduleFlush = attemptVertx(); - } else { - scheduleFlush = useSetTimeout(); - } - - function then(onFulfillment, onRejection) { - var parent = this; - - var child = new this.constructor(noop); - - if (child[PROMISE_ID] === undefined) { - makePromise(child); - } - - var _state = parent._state; - - - if (_state) { - var callback = arguments[_state - 1]; - asap(function () { - return invokeCallback(_state, child, callback, parent._result); - }); - } else { - subscribe(parent, child, onFulfillment, onRejection); - } - - return child; - } - - /** - `Promise.resolve` returns a promise that will become resolved with the - passed `value`. It is shorthand for the following: - - ```javascript - let promise = new Promise(function(resolve, reject){ - resolve(1); - }); - - promise.then(function(value){ - // value === 1 - }); - ``` - - Instead of writing the above, your code now simply becomes the following: - - ```javascript - let promise = Promise.resolve(1); - - promise.then(function(value){ - // value === 1 - }); - ``` - - @method resolve - @static - @param {Any} value value that the returned promise will be resolved with - Useful for tooling. - @return {Promise} a promise that will become fulfilled with the given - `value` - */ - function resolve$1(object) { - /*jshint validthis:true */ - var Constructor = this; - - if (object && (typeof object === 'undefined' ? 'undefined' : _typeof(object)) === 'object' && object.constructor === Constructor) { - return object; - } - - var promise = new Constructor(noop); - resolve(promise, object); - return promise; - } - - var PROMISE_ID = Math.random().toString(36).substring(2); - - function noop() {} - - var PENDING = void 0; - var FULFILLED = 1; - var REJECTED = 2; - - function selfFulfillment() { - return new TypeError("You cannot resolve a promise with itself"); - } - - function cannotReturnOwn() { - return new TypeError('A promises callback cannot return that same promise.'); - } - - function tryThen(then$$1, value, fulfillmentHandler, rejectionHandler) { - try { - then$$1.call(value, fulfillmentHandler, rejectionHandler); - } catch (e) { - return e; - } - } - - function handleForeignThenable(promise, thenable, then$$1) { - asap(function (promise) { - var sealed = false; - var error = tryThen(then$$1, thenable, function (value) { - if (sealed) { - return; - } - sealed = true; - if (thenable !== value) { - resolve(promise, value); - } else { - fulfill(promise, value); - } - }, function (reason) { - if (sealed) { - return; - } - sealed = true; - - reject(promise, reason); - }, 'Settle: ' + (promise._label || ' unknown promise')); - - if (!sealed && error) { - sealed = true; - reject(promise, error); - } - }, promise); - } - - function handleOwnThenable(promise, thenable) { - if (thenable._state === FULFILLED) { - fulfill(promise, thenable._result); - } else if (thenable._state === REJECTED) { - reject(promise, thenable._result); - } else { - subscribe(thenable, undefined, function (value) { - return resolve(promise, value); - }, function (reason) { - return reject(promise, reason); - }); - } - } - - function handleMaybeThenable(promise, maybeThenable, then$$1) { - if (maybeThenable.constructor === promise.constructor && then$$1 === then && maybeThenable.constructor.resolve === resolve$1) { - handleOwnThenable(promise, maybeThenable); - } else { - if (then$$1 === undefined) { - fulfill(promise, maybeThenable); - } else if (isFunction(then$$1)) { - handleForeignThenable(promise, maybeThenable, then$$1); - } else { - fulfill(promise, maybeThenable); - } - } - } - - function resolve(promise, value) { - if (promise === value) { - reject(promise, selfFulfillment()); - } else if (objectOrFunction(value)) { - var then$$1 = void 0; - try { - then$$1 = value.then; - } catch (error) { - reject(promise, error); - return; - } - handleMaybeThenable(promise, value, then$$1); - } else { - fulfill(promise, value); - } - } - - function publishRejection(promise) { - if (promise._onerror) { - promise._onerror(promise._result); - } - - publish(promise); - } - - function fulfill(promise, value) { - if (promise._state !== PENDING) { - return; - } - - promise._result = value; - promise._state = FULFILLED; - - if (promise._subscribers.length !== 0) { - asap(publish, promise); - } - } - - function reject(promise, reason) { - if (promise._state !== PENDING) { - return; - } - promise._state = REJECTED; - promise._result = reason; - - asap(publishRejection, promise); - } - - function subscribe(parent, child, onFulfillment, onRejection) { - var _subscribers = parent._subscribers; - var length = _subscribers.length; - - - parent._onerror = null; - - _subscribers[length] = child; - _subscribers[length + FULFILLED] = onFulfillment; - _subscribers[length + REJECTED] = onRejection; - - if (length === 0 && parent._state) { - asap(publish, parent); - } - } - - function publish(promise) { - var subscribers = promise._subscribers; - var settled = promise._state; - - if (subscribers.length === 0) { - return; - } - - var child = void 0, - callback = void 0, - detail = promise._result; - - for (var i = 0; i < subscribers.length; i += 3) { - child = subscribers[i]; - callback = subscribers[i + settled]; - - if (child) { - invokeCallback(settled, child, callback, detail); - } else { - callback(detail); - } - } - - promise._subscribers.length = 0; - } - - function invokeCallback(settled, promise, callback, detail) { - var hasCallback = isFunction(callback), - value = void 0, - error = void 0, - succeeded = true; - - if (hasCallback) { - try { - value = callback(detail); - } catch (e) { - succeeded = false; - error = e; - } - - if (promise === value) { - reject(promise, cannotReturnOwn()); - return; - } - } else { - value = detail; - } - - if (promise._state !== PENDING) { - // noop - } else if (hasCallback && succeeded) { - resolve(promise, value); - } else if (succeeded === false) { - reject(promise, error); - } else if (settled === FULFILLED) { - fulfill(promise, value); - } else if (settled === REJECTED) { - reject(promise, value); - } - } - - function initializePromise(promise, resolver) { - try { - resolver(function resolvePromise(value) { - resolve(promise, value); - }, function rejectPromise(reason) { - reject(promise, reason); - }); - } catch (e) { - reject(promise, e); - } - } - - var id = 0; - function nextId() { - return id++; - } - - function makePromise(promise) { - promise[PROMISE_ID] = id++; - promise._state = undefined; - promise._result = undefined; - promise._subscribers = []; - } - - function validationError() { - return new Error('Array Methods must be provided an Array'); - } - - var Enumerator = function () { - function Enumerator(Constructor, input) { - classCallCheck(this, Enumerator); - - this._instanceConstructor = Constructor; - this.promise = new Constructor(noop); - - if (!this.promise[PROMISE_ID]) { - makePromise(this.promise); - } - - if (isArray(input)) { - this.length = input.length; - this._remaining = input.length; - - this._result = new Array(this.length); - - if (this.length === 0) { - fulfill(this.promise, this._result); - } else { - this.length = this.length || 0; - this._enumerate(input); - if (this._remaining === 0) { - fulfill(this.promise, this._result); - } - } - } else { - reject(this.promise, validationError()); - } - } - - createClass(Enumerator, [{ - key: '_enumerate', - value: function _enumerate(input) { - for (var i = 0; this._state === PENDING && i < input.length; i++) { - this._eachEntry(input[i], i); - } - } - }, { - key: '_eachEntry', - value: function _eachEntry(entry, i) { - var c = this._instanceConstructor; - var resolve$$1 = c.resolve; - - - if (resolve$$1 === resolve$1) { - var _then = void 0; - var error = void 0; - var didError = false; - try { - _then = entry.then; - } catch (e) { - didError = true; - error = e; - } - - if (_then === then && entry._state !== PENDING) { - this._settledAt(entry._state, i, entry._result); - } else if (typeof _then !== 'function') { - this._remaining--; - this._result[i] = entry; - } else if (c === Promise$2) { - var promise = new c(noop); - if (didError) { - reject(promise, error); - } else { - handleMaybeThenable(promise, entry, _then); - } - this._willSettleAt(promise, i); - } else { - this._willSettleAt(new c(function (resolve$$1) { - return resolve$$1(entry); - }), i); - } - } else { - this._willSettleAt(resolve$$1(entry), i); - } - } - }, { - key: '_settledAt', - value: function _settledAt(state, i, value) { - var promise = this.promise; - - - if (promise._state === PENDING) { - this._remaining--; - - if (state === REJECTED) { - reject(promise, value); - } else { - this._result[i] = value; - } - } - - if (this._remaining === 0) { - fulfill(promise, this._result); - } - } - }, { - key: '_willSettleAt', - value: function _willSettleAt(promise, i) { - var enumerator = this; - - subscribe(promise, undefined, function (value) { - return enumerator._settledAt(FULFILLED, i, value); - }, function (reason) { - return enumerator._settledAt(REJECTED, i, reason); - }); - } - }]); - return Enumerator; - }(); - - /** - `Promise.all` accepts an array of promises, and returns a new promise which - is fulfilled with an array of fulfillment values for the passed promises, or - rejected with the reason of the first passed promise to be rejected. It casts all - elements of the passed iterable to promises as it runs this algorithm. - - Example: - - ```javascript - let promise1 = resolve(1); - let promise2 = resolve(2); - let promise3 = resolve(3); - let promises = [ promise1, promise2, promise3 ]; - - Promise.all(promises).then(function(array){ - // The array here would be [ 1, 2, 3 ]; - }); - ``` - - If any of the `promises` given to `all` are rejected, the first promise - that is rejected will be given as an argument to the returned promises's - rejection handler. For example: - - Example: - - ```javascript - let promise1 = resolve(1); - let promise2 = reject(new Error("2")); - let promise3 = reject(new Error("3")); - let promises = [ promise1, promise2, promise3 ]; - - Promise.all(promises).then(function(array){ - // Code here never runs because there are rejected promises! - }, function(error) { - // error.message === "2" - }); - ``` - - @method all - @static - @param {Array} entries array of promises - @param {String} label optional string for labeling the promise. - Useful for tooling. - @return {Promise} promise that is fulfilled when all `promises` have been - fulfilled, or rejected if any of them become rejected. - @static - */ - function all(entries) { - return new Enumerator(this, entries).promise; - } - - /** - `Promise.race` returns a new promise which is settled in the same way as the - first passed promise to settle. - - Example: - - ```javascript - let promise1 = new Promise(function(resolve, reject){ - setTimeout(function(){ - resolve('promise 1'); - }, 200); - }); - - let promise2 = new Promise(function(resolve, reject){ - setTimeout(function(){ - resolve('promise 2'); - }, 100); - }); - - Promise.race([promise1, promise2]).then(function(result){ - // result === 'promise 2' because it was resolved before promise1 - // was resolved. - }); - ``` - - `Promise.race` is deterministic in that only the state of the first - settled promise matters. For example, even if other promises given to the - `promises` array argument are resolved, but the first settled promise has - become rejected before the other promises became fulfilled, the returned - promise will become rejected: - - ```javascript - let promise1 = new Promise(function(resolve, reject){ - setTimeout(function(){ - resolve('promise 1'); - }, 200); - }); - - let promise2 = new Promise(function(resolve, reject){ - setTimeout(function(){ - reject(new Error('promise 2')); - }, 100); - }); - - Promise.race([promise1, promise2]).then(function(result){ - // Code here never runs - }, function(reason){ - // reason.message === 'promise 2' because promise 2 became rejected before - // promise 1 became fulfilled - }); - ``` - - An example real-world use case is implementing timeouts: - - ```javascript - Promise.race([ajax('foo.json'), timeout(5000)]) - ``` - - @method race - @static - @param {Array} promises array of promises to observe - Useful for tooling. - @return {Promise} a promise which settles in the same way as the first passed - promise to settle. - */ - function race(entries) { - /*jshint validthis:true */ - var Constructor = this; - - if (!isArray(entries)) { - return new Constructor(function (_, reject) { - return reject(new TypeError('You must pass an array to race.')); - }); - } else { - return new Constructor(function (resolve, reject) { - var length = entries.length; - for (var i = 0; i < length; i++) { - Constructor.resolve(entries[i]).then(resolve, reject); - } - }); - } - } - - /** - `Promise.reject` returns a promise rejected with the passed `reason`. - It is shorthand for the following: - - ```javascript - let promise = new Promise(function(resolve, reject){ - reject(new Error('WHOOPS')); - }); - - promise.then(function(value){ - // Code here doesn't run because the promise is rejected! - }, function(reason){ - // reason.message === 'WHOOPS' - }); - ``` - - Instead of writing the above, your code now simply becomes the following: - - ```javascript - let promise = Promise.reject(new Error('WHOOPS')); - - promise.then(function(value){ - // Code here doesn't run because the promise is rejected! - }, function(reason){ - // reason.message === 'WHOOPS' - }); - ``` - - @method reject - @static - @param {Any} reason value that the returned promise will be rejected with. - Useful for tooling. - @return {Promise} a promise rejected with the given `reason`. - */ - function reject$1(reason) { - /*jshint validthis:true */ - var Constructor = this; - var promise = new Constructor(noop); - reject(promise, reason); - return promise; - } - - function needsResolver() { - throw new TypeError('You must pass a resolver function as the first argument to the promise constructor'); - } - - function needsNew() { - throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function."); - } - - /** - Promise objects represent the eventual result of an asynchronous operation. The - primary way of interacting with a promise is through its `then` method, which - registers callbacks to receive either a promise's eventual value or the reason - why the promise cannot be fulfilled. - - Terminology - ----------- - - - `promise` is an object or function with a `then` method whose behavior conforms to this specification. - - `thenable` is an object or function that defines a `then` method. - - `value` is any legal JavaScript value (including undefined, a thenable, or a promise). - - `exception` is a value that is thrown using the throw statement. - - `reason` is a value that indicates why a promise was rejected. - - `settled` the final resting state of a promise, fulfilled or rejected. - - A promise can be in one of three states: pending, fulfilled, or rejected. - - Promises that are fulfilled have a fulfillment value and are in the fulfilled - state. Promises that are rejected have a rejection reason and are in the - rejected state. A fulfillment value is never a thenable. - - Promises can also be said to *resolve* a value. If this value is also a - promise, then the original promise's settled state will match the value's - settled state. So a promise that *resolves* a promise that rejects will - itself reject, and a promise that *resolves* a promise that fulfills will - itself fulfill. - - - Basic Usage: - ------------ - - ```js - let promise = new Promise(function(resolve, reject) { - // on success - resolve(value); - - // on failure - reject(reason); - }); - - promise.then(function(value) { - // on fulfillment - }, function(reason) { - // on rejection - }); - ``` - - Advanced Usage: - --------------- - - Promises shine when abstracting away asynchronous interactions such as - `XMLHttpRequest`s. - - ```js - function getJSON(url) { - return new Promise(function(resolve, reject){ - let xhr = new XMLHttpRequest(); - - xhr.open('GET', url); - xhr.onreadystatechange = handler; - xhr.responseType = 'json'; - xhr.setRequestHeader('Accept', 'application/json'); - xhr.send(); - - function handler() { - if (this.readyState === this.DONE) { - if (this.status === 200) { - resolve(this.response); - } else { - reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']')); - } - } - }; - }); - } - - getJSON('/posts.json').then(function(json) { - // on fulfillment - }, function(reason) { - // on rejection - }); - ``` - - Unlike callbacks, promises are great composable primitives. - - ```js - Promise.all([ - getJSON('/posts'), - getJSON('/comments') - ]).then(function(values){ - values[0] // => postsJSON - values[1] // => commentsJSON - - return values; - }); - ``` - - @class Promise - @param {Function} resolver - Useful for tooling. - @constructor - */ - - var Promise$2 = function () { - function Promise(resolver) { - classCallCheck(this, Promise); - - this[PROMISE_ID] = nextId(); - this._result = this._state = undefined; - this._subscribers = []; - - if (noop !== resolver) { - typeof resolver !== 'function' && needsResolver(); - this instanceof Promise ? initializePromise(this, resolver) : needsNew(); - } - } - - /** - The primary way of interacting with a promise is through its `then` method, - which registers callbacks to receive either a promise's eventual value or the - reason why the promise cannot be fulfilled. - ```js - findUser().then(function(user){ - // user is available - }, function(reason){ - // user is unavailable, and you are given the reason why - }); - ``` - Chaining - -------- - The return value of `then` is itself a promise. This second, 'downstream' - promise is resolved with the return value of the first promise's fulfillment - or rejection handler, or rejected if the handler throws an exception. - ```js - findUser().then(function (user) { - return user.name; - }, function (reason) { - return 'default name'; - }).then(function (userName) { - // If `findUser` fulfilled, `userName` will be the user's name, otherwise it - // will be `'default name'` - }); - findUser().then(function (user) { - throw new Error('Found user, but still unhappy'); - }, function (reason) { - throw new Error('`findUser` rejected and we're unhappy'); - }).then(function (value) { - // never reached - }, function (reason) { - // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'. - // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'. - }); - ``` - If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream. - ```js - findUser().then(function (user) { - throw new PedagogicalException('Upstream error'); - }).then(function (value) { - // never reached - }).then(function (value) { - // never reached - }, function (reason) { - // The `PedgagocialException` is propagated all the way down to here - }); - ``` - Assimilation - ------------ - Sometimes the value you want to propagate to a downstream promise can only be - retrieved asynchronously. This can be achieved by returning a promise in the - fulfillment or rejection handler. The downstream promise will then be pending - until the returned promise is settled. This is called *assimilation*. - ```js - findUser().then(function (user) { - return findCommentsByAuthor(user); - }).then(function (comments) { - // The user's comments are now available - }); - ``` - If the assimliated promise rejects, then the downstream promise will also reject. - ```js - findUser().then(function (user) { - return findCommentsByAuthor(user); - }).then(function (comments) { - // If `findCommentsByAuthor` fulfills, we'll have the value here - }, function (reason) { - // If `findCommentsByAuthor` rejects, we'll have the reason here - }); - ``` - Simple Example - -------------- - Synchronous Example - ```javascript - let result; - try { - result = findResult(); - // success - } catch(reason) { - // failure - } - ``` - Errback Example - ```js - findResult(function(result, err){ - if (err) { - // failure - } else { - // success - } - }); - ``` - Promise Example; - ```javascript - findResult().then(function(result){ - // success - }, function(reason){ - // failure - }); - ``` - Advanced Example - -------------- - Synchronous Example - ```javascript - let author, books; - try { - author = findAuthor(); - books = findBooksByAuthor(author); - // success - } catch(reason) { - // failure - } - ``` - Errback Example - ```js - function foundBooks(books) { - } - function failure(reason) { - } - findAuthor(function(author, err){ - if (err) { - failure(err); - // failure - } else { - try { - findBoooksByAuthor(author, function(books, err) { - if (err) { - failure(err); - } else { - try { - foundBooks(books); - } catch(reason) { - failure(reason); - } - } - }); - } catch(error) { - failure(err); - } - // success - } - }); - ``` - Promise Example; - ```javascript - findAuthor(). - then(findBooksByAuthor). - then(function(books){ - // found books - }).catch(function(reason){ - // something went wrong - }); - ``` - @method then - @param {Function} onFulfilled - @param {Function} onRejected - Useful for tooling. - @return {Promise} - */ - - /** - `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same - as the catch block of a try/catch statement. - ```js - function findAuthor(){ - throw new Error('couldn't find that author'); - } - // synchronous - try { - findAuthor(); - } catch(reason) { - // something went wrong - } - // async with promises - findAuthor().catch(function(reason){ - // something went wrong - }); - ``` - @method catch - @param {Function} onRejection - Useful for tooling. - @return {Promise} - */ - - - createClass(Promise, [{ - key: 'catch', - value: function _catch(onRejection) { - return this.then(null, onRejection); - } - - /** - `finally` will be invoked regardless of the promise's fate just as native - try/catch/finally behaves - - Synchronous example: - - ```js - findAuthor() { - if (Math.random() > 0.5) { - throw new Error(); - } - return new Author(); - } - - try { - return findAuthor(); // succeed or fail - } catch(error) { - return findOtherAuther(); - } finally { - // always runs - // doesn't affect the return value - } - ``` - - Asynchronous example: - - ```js - findAuthor().catch(function(reason){ - return findOtherAuther(); - }).finally(function(){ - // author was either found, or not - }); - ``` - - @method finally - @param {Function} callback - @return {Promise} - */ - - }, { - key: 'finally', - value: function _finally(callback) { - var promise = this; - var constructor = promise.constructor; - - if (isFunction(callback)) { - return promise.then(function (value) { - return constructor.resolve(callback()).then(function () { - return value; - }); - }, function (reason) { - return constructor.resolve(callback()).then(function () { - throw reason; - }); - }); - } - - return promise.then(callback, callback); - } - }]); - return Promise; - }(); - - Promise$2.prototype.then = then; - Promise$2.all = all; - Promise$2.race = race; - Promise$2.resolve = resolve$1; - Promise$2.reject = reject$1; - Promise$2._setScheduler = setScheduler; - Promise$2._setAsap = setAsap; - Promise$2._asap = asap; - - /*global self*/ - function polyfill() { - var local = void 0; - - if (typeof global !== 'undefined') { - local = global; - } else if (typeof self !== 'undefined') { - local = self; - } else { - try { - local = Function('return this')(); - } catch (e) { - throw new Error('polyfill failed because global object is unavailable in this environment'); - } - } - - var P = local.Promise; - - if (P) { - var promiseToString = null; - try { - promiseToString = Object.prototype.toString.call(P.resolve()); - } catch (e) { - // silently ignored - } - - if (promiseToString === '[object Promise]' && !P.cast) { - return; - } - } - - local.Promise = Promise$2; - } - - // Strange compat.. - Promise$2.polyfill = polyfill; - Promise$2.Promise = Promise$2; - - var Promise$1 = typeof Promise !== "undefined" ? Promise : Promise$2; - - // Register logging callbacks - function registerLoggingCallbacks(obj) { - var i, - l, - key, - callbackNames = ["begin", "done", "log", "testStart", "testDone", "moduleStart", "moduleDone"]; - - function registerLoggingCallback(key) { - var loggingCallback = function loggingCallback(callback) { - if (objectType(callback) !== "function") { - throw new Error("QUnit logging methods require a callback function as their first parameters."); - } - - config.callbacks[key].push(callback); - }; - - return loggingCallback; - } - - for (i = 0, l = callbackNames.length; i < l; i++) { - key = callbackNames[i]; - - // Initialize key collection of logging callback - if (objectType(config.callbacks[key]) === "undefined") { - config.callbacks[key] = []; - } - - obj[key] = registerLoggingCallback(key); - } - } - - function runLoggingCallbacks(key, args) { - var callbacks = config.callbacks[key]; - - // Handling 'log' callbacks separately. Unlike the other callbacks, - // the log callback is not controlled by the processing queue, - // but rather used by asserts. Hence to promisfy the 'log' callback - // would mean promisfying each step of a test - if (key === "log") { - callbacks.map(function (callback) { - return callback(args); - }); - return; - } - - // ensure that each callback is executed serially - return callbacks.reduce(function (promiseChain, callback) { - return promiseChain.then(function () { - return Promise$1.resolve(callback(args)); - }); - }, Promise$1.resolve([])); - } - - // Doesn't support IE9, it will return undefined on these browsers - // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack - var fileName = (sourceFromStacktrace(0) || "").replace(/(:\d+)+\)?/, "").replace(/.+\//, ""); - - function extractStacktrace(e, offset) { - offset = offset === undefined ? 4 : offset; - - var stack, include, i; - - if (e && e.stack) { - stack = e.stack.split("\n"); - if (/^error$/i.test(stack[0])) { - stack.shift(); - } - if (fileName) { - include = []; - for (i = offset; i < stack.length; i++) { - if (stack[i].indexOf(fileName) !== -1) { - break; - } - include.push(stack[i]); - } - if (include.length) { - return include.join("\n"); - } - } - return stack[offset]; - } - } - - function sourceFromStacktrace(offset) { - var error = new Error(); - - // Support: Safari <=7 only, IE <=10 - 11 only - // Not all browsers generate the `stack` property for `new Error()`, see also #636 - if (!error.stack) { - try { - throw error; - } catch (err) { - error = err; - } - } - - return extractStacktrace(error, offset); - } - - var priorityCount = 0; - var unitSampler = void 0; - - // This is a queue of functions that are tasks within a single test. - // After tests are dequeued from config.queue they are expanded into - // a set of tasks in this queue. - var taskQueue = []; - - /** - * Advances the taskQueue to the next task. If the taskQueue is empty, - * process the testQueue - */ - function advance() { - advanceTaskQueue(); - - if (!taskQueue.length && !config.blocking && !config.current) { - advanceTestQueue(); - } - } - - /** - * Advances the taskQueue with an increased depth - */ - function advanceTaskQueue() { - var start = now(); - config.depth = (config.depth || 0) + 1; - - processTaskQueue(start); - - config.depth--; - } - - /** - * Process the first task on the taskQueue as a promise. - * Each task is a function returned by https://github.com/qunitjs/qunit/blob/master/src/test.js#L381 - */ - function processTaskQueue(start) { - if (taskQueue.length && !config.blocking) { - var elapsedTime = now() - start; - - if (!defined.setTimeout || config.updateRate <= 0 || elapsedTime < config.updateRate) { - var task = taskQueue.shift(); - Promise$1.resolve(task()).then(function () { - if (!taskQueue.length) { - advance(); - } else { - processTaskQueue(start); - } - }); - } else { - setTimeout$1(advance); - } - } - } - - /** - * Advance the testQueue to the next test to process. Call done() if testQueue completes. - */ - function advanceTestQueue() { - if (!config.blocking && !config.queue.length && config.depth === 0) { - done(); - return; - } - - var testTasks = config.queue.shift(); - addToTaskQueue(testTasks()); - - if (priorityCount > 0) { - priorityCount--; - } - - advance(); - } - - /** - * Enqueue the tasks for a test into the task queue. - * @param {Array} tasksArray - */ - function addToTaskQueue(tasksArray) { - taskQueue.push.apply(taskQueue, toConsumableArray(tasksArray)); - } - - /** - * Return the number of tasks remaining in the task queue to be processed. - * @return {Number} - */ - function taskQueueLength() { - return taskQueue.length; - } - - /** - * Adds a test to the TestQueue for execution. - * @param {Function} testTasksFunc - * @param {Boolean} prioritize - * @param {String} seed - */ - function addToTestQueue(testTasksFunc, prioritize, seed) { - if (prioritize) { - config.queue.splice(priorityCount++, 0, testTasksFunc); - } else if (seed) { - if (!unitSampler) { - unitSampler = unitSamplerGenerator(seed); - } - - // Insert into a random position after all prioritized items - var index = Math.floor(unitSampler() * (config.queue.length - priorityCount + 1)); - config.queue.splice(priorityCount + index, 0, testTasksFunc); - } else { - config.queue.push(testTasksFunc); - } - } - - /** - * Creates a seeded "sample" generator which is used for randomizing tests. - */ - function unitSamplerGenerator(seed) { - - // 32-bit xorshift, requires only a nonzero seed - // http://excamera.com/sphinx/article-xorshift.html - var sample = parseInt(generateHash(seed), 16) || -1; - return function () { - sample ^= sample << 13; - sample ^= sample >>> 17; - sample ^= sample << 5; - - // ECMAScript has no unsigned number type - if (sample < 0) { - sample += 0x100000000; - } - - return sample / 0x100000000; - }; - } - - /** - * This function is called when the ProcessingQueue is done processing all - * items. It handles emitting the final run events. - */ - function done() { - var storage = config.storage; - - ProcessingQueue.finished = true; - - var runtime = now() - config.started; - var passed = config.stats.all - config.stats.bad; - - if (config.stats.all === 0) { - - if (config.filter && config.filter.length) { - throw new Error("No tests matched the filter \"" + config.filter + "\"."); - } - - if (config.module && config.module.length) { - throw new Error("No tests matched the module \"" + config.module + "\"."); - } - - if (config.moduleId && config.moduleId.length) { - throw new Error("No tests matched the moduleId \"" + config.moduleId + "\"."); - } - - if (config.testId && config.testId.length) { - throw new Error("No tests matched the testId \"" + config.testId + "\"."); - } - - throw new Error("No tests were run."); - } - - emit("runEnd", globalSuite.end(true)); - runLoggingCallbacks("done", { - passed: passed, - failed: config.stats.bad, - total: config.stats.all, - runtime: runtime - }).then(function () { - - // Clear own storage items if all tests passed - if (storage && config.stats.bad === 0) { - for (var i = storage.length - 1; i >= 0; i--) { - var key = storage.key(i); - - if (key.indexOf("qunit-test-") === 0) { - storage.removeItem(key); - } - } - } - }); - } - - var ProcessingQueue = { - finished: false, - add: addToTestQueue, - advance: advance, - taskCount: taskQueueLength - }; - - var TestReport = function () { - function TestReport(name, suite, options) { - classCallCheck(this, TestReport); - - this.name = name; - this.suiteName = suite.name; - this.fullName = suite.fullName.concat(name); - this.runtime = 0; - this.assertions = []; - - this.skipped = !!options.skip; - this.todo = !!options.todo; - - this.valid = options.valid; - - this._startTime = 0; - this._endTime = 0; - - suite.pushTest(this); - } - - createClass(TestReport, [{ - key: "start", - value: function start(recordTime) { - if (recordTime) { - this._startTime = performanceNow(); - if (performance) { - performance.mark("qunit_test_start"); - } - } - - return { - name: this.name, - suiteName: this.suiteName, - fullName: this.fullName.slice() - }; - } - }, { - key: "end", - value: function end(recordTime) { - if (recordTime) { - this._endTime = performanceNow(); - if (performance) { - performance.mark("qunit_test_end"); - - var testName = this.fullName.join(" – "); - - measure("QUnit Test: " + testName, "qunit_test_start", "qunit_test_end"); - } - } - - return extend(this.start(), { - runtime: this.getRuntime(), - status: this.getStatus(), - errors: this.getFailedAssertions(), - assertions: this.getAssertions() - }); - } - }, { - key: "pushAssertion", - value: function pushAssertion(assertion) { - this.assertions.push(assertion); - } - }, { - key: "getRuntime", - value: function getRuntime() { - return this._endTime - this._startTime; - } - }, { - key: "getStatus", - value: function getStatus() { - if (this.skipped) { - return "skipped"; - } - - var testPassed = this.getFailedAssertions().length > 0 ? this.todo : !this.todo; - - if (!testPassed) { - return "failed"; - } else if (this.todo) { - return "todo"; - } else { - return "passed"; - } - } - }, { - key: "getFailedAssertions", - value: function getFailedAssertions() { - return this.assertions.filter(function (assertion) { - return !assertion.passed; - }); - } - }, { - key: "getAssertions", - value: function getAssertions() { - return this.assertions.slice(); - } - - // Remove actual and expected values from assertions. This is to prevent - // leaking memory throughout a test suite. - - }, { - key: "slimAssertions", - value: function slimAssertions() { - this.assertions = this.assertions.map(function (assertion) { - delete assertion.actual; - delete assertion.expected; - return assertion; - }); - } - }]); - return TestReport; - }(); - - var focused$1 = false; - - function Test(settings) { - var i, l; - - ++Test.count; - - this.expected = null; - this.assertions = []; - this.semaphore = 0; - this.module = config.currentModule; - this.steps = []; - this.timeout = undefined; - this.errorForStack = new Error(); - - // If a module is skipped, all its tests and the tests of the child suites - // should be treated as skipped even if they are defined as `only` or `todo`. - // As for `todo` module, all its tests will be treated as `todo` except for - // tests defined as `skip` which will be left intact. - // - // So, if a test is defined as `todo` and is inside a skipped module, we should - // then treat that test as if was defined as `skip`. - if (this.module.skip) { - settings.skip = true; - settings.todo = false; - - // Skipped tests should be left intact - } else if (this.module.todo && !settings.skip) { - settings.todo = true; - } - - extend(this, settings); - - this.testReport = new TestReport(settings.testName, this.module.suiteReport, { - todo: settings.todo, - skip: settings.skip, - valid: this.valid() - }); - - // Register unique strings - for (i = 0, l = this.module.tests; i < l.length; i++) { - if (this.module.tests[i].name === this.testName) { - this.testName += " "; - } - } - - this.testId = generateHash(this.module.name, this.testName); - - this.module.tests.push({ - name: this.testName, - testId: this.testId, - skip: !!settings.skip - }); - - if (settings.skip) { - - // Skipped tests will fully ignore any sent callback - this.callback = function () {}; - this.async = false; - this.expected = 0; - } else { - if (typeof this.callback !== "function") { - var method = this.todo ? "todo" : "test"; - - // eslint-disable-next-line max-len - throw new TypeError("You must provide a function as a test callback to QUnit." + method + "(\"" + settings.testName + "\")"); - } - - this.assert = new Assert(this); - } - } - - Test.count = 0; - - function getNotStartedModules(startModule) { - var module = startModule, - modules = []; - - while (module && module.testsRun === 0) { - modules.push(module); - module = module.parentModule; - } - - // The above push modules from the child to the parent - // return a reversed order with the top being the top most parent module - return modules.reverse(); - } - - Test.prototype = { - - // generating a stack trace can be expensive, so using a getter defers this until we need it - get stack() { - return extractStacktrace(this.errorForStack, 2); - }, - - before: function before() { - var _this = this; - - var module = this.module, - notStartedModules = getNotStartedModules(module); - - // ensure the callbacks are executed serially for each module - var callbackPromises = notStartedModules.reduce(function (promiseChain, startModule) { - return promiseChain.then(function () { - startModule.stats = { all: 0, bad: 0, started: now() }; - emit("suiteStart", startModule.suiteReport.start(true)); - return runLoggingCallbacks("moduleStart", { - name: startModule.name, - tests: startModule.tests - }); - }); - }, Promise$1.resolve([])); - - return callbackPromises.then(function () { - config.current = _this; - - _this.testEnvironment = extend({}, module.testEnvironment); - - _this.started = now(); - emit("testStart", _this.testReport.start(true)); - return runLoggingCallbacks("testStart", { - name: _this.testName, - module: module.name, - testId: _this.testId, - previousFailure: _this.previousFailure - }).then(function () { - if (!config.pollution) { - saveGlobal(); - } - }); - }); - }, - - run: function run() { - var promise; - - config.current = this; - - this.callbackStarted = now(); - - if (config.notrycatch) { - runTest(this); - return; - } - - try { - runTest(this); - } catch (e) { - this.pushFailure("Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + (e.message || e), extractStacktrace(e, 0)); - - // Else next test will carry the responsibility - saveGlobal(); - - // Restart the tests if they're blocking - if (config.blocking) { - internalRecover(this); - } - } - - function runTest(test) { - promise = test.callback.call(test.testEnvironment, test.assert); - test.resolvePromise(promise); - - // If the test has a "lock" on it, but the timeout is 0, then we push a - // failure as the test should be synchronous. - if (test.timeout === 0 && test.semaphore !== 0) { - pushFailure("Test did not finish synchronously even though assert.timeout( 0 ) was used.", sourceFromStacktrace(2)); - } - } - }, - - after: function after() { - checkPollution(); - }, - - queueHook: function queueHook(hook, hookName, hookOwner) { - var _this2 = this; - - var callHook = function callHook() { - var promise = hook.call(_this2.testEnvironment, _this2.assert); - _this2.resolvePromise(promise, hookName); - }; - - var runHook = function runHook() { - if (hookName === "before") { - if (hookOwner.unskippedTestsRun !== 0) { - return; - } - - _this2.preserveEnvironment = true; - } - - // The 'after' hook should only execute when there are not tests left and - // when the 'after' and 'finish' tasks are the only tasks left to process - if (hookName === "after" && hookOwner.unskippedTestsRun !== numberOfUnskippedTests(hookOwner) - 1 && (config.queue.length > 0 || ProcessingQueue.taskCount() > 2)) { - return; - } - - config.current = _this2; - if (config.notrycatch) { - callHook(); - return; - } - try { - callHook(); - } catch (error) { - _this2.pushFailure(hookName + " failed on " + _this2.testName + ": " + (error.message || error), extractStacktrace(error, 0)); - } - }; - - return runHook; - }, - - - // Currently only used for module level hooks, can be used to add global level ones - hooks: function hooks(handler) { - var hooks = []; - - function processHooks(test, module) { - if (module.parentModule) { - processHooks(test, module.parentModule); - } - - if (module.hooks[handler].length) { - for (var i = 0; i < module.hooks[handler].length; i++) { - hooks.push(test.queueHook(module.hooks[handler][i], handler, module)); - } - } - } - - // Hooks are ignored on skipped tests - if (!this.skip) { - processHooks(this, this.module); - } - - return hooks; - }, - - - finish: function finish() { - config.current = this; - - // Release the test callback to ensure that anything referenced has been - // released to be garbage collected. - this.callback = undefined; - - if (this.steps.length) { - var stepsList = this.steps.join(", "); - this.pushFailure("Expected assert.verifySteps() to be called before end of test " + ("after using assert.step(). Unverified steps: " + stepsList), this.stack); - } - - if (config.requireExpects && this.expected === null) { - this.pushFailure("Expected number of assertions to be defined, but expect() was " + "not called.", this.stack); - } else if (this.expected !== null && this.expected !== this.assertions.length) { - this.pushFailure("Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack); - } else if (this.expected === null && !this.assertions.length) { - this.pushFailure("Expected at least one assertion, but none were run - call " + "expect(0) to accept zero assertions.", this.stack); - } - - var i, - module = this.module, - moduleName = module.name, - testName = this.testName, - skipped = !!this.skip, - todo = !!this.todo, - bad = 0, - storage = config.storage; - - this.runtime = now() - this.started; - - config.stats.all += this.assertions.length; - module.stats.all += this.assertions.length; - - for (i = 0; i < this.assertions.length; i++) { - if (!this.assertions[i].result) { - bad++; - config.stats.bad++; - module.stats.bad++; - } - } - - notifyTestsRan(module, skipped); - - // Store result when possible - if (storage) { - if (bad) { - storage.setItem("qunit-test-" + moduleName + "-" + testName, bad); - } else { - storage.removeItem("qunit-test-" + moduleName + "-" + testName); - } - } - - // After emitting the js-reporters event we cleanup the assertion data to - // avoid leaking it. It is not used by the legacy testDone callbacks. - emit("testEnd", this.testReport.end(true)); - this.testReport.slimAssertions(); - var test = this; - - return runLoggingCallbacks("testDone", { - name: testName, - module: moduleName, - skipped: skipped, - todo: todo, - failed: bad, - passed: this.assertions.length - bad, - total: this.assertions.length, - runtime: skipped ? 0 : this.runtime, - - // HTML Reporter use - assertions: this.assertions, - testId: this.testId, - - // Source of Test - // generating stack trace is expensive, so using a getter will help defer this until we need it - get source() { - return test.stack; - } - }).then(function () { - if (module.testsRun === numberOfTests(module)) { - var completedModules = [module]; - - // Check if the parent modules, iteratively, are done. If that the case, - // we emit the `suiteEnd` event and trigger `moduleDone` callback. - var parent = module.parentModule; - while (parent && parent.testsRun === numberOfTests(parent)) { - completedModules.push(parent); - parent = parent.parentModule; - } - - return completedModules.reduce(function (promiseChain, completedModule) { - return promiseChain.then(function () { - return logSuiteEnd(completedModule); - }); - }, Promise$1.resolve([])); - } - }).then(function () { - config.current = undefined; - }); - - function logSuiteEnd(module) { - - // Reset `module.hooks` to ensure that anything referenced in these hooks - // has been released to be garbage collected. - module.hooks = {}; - - emit("suiteEnd", module.suiteReport.end(true)); - return runLoggingCallbacks("moduleDone", { - name: module.name, - tests: module.tests, - failed: module.stats.bad, - passed: module.stats.all - module.stats.bad, - total: module.stats.all, - runtime: now() - module.stats.started - }); - } - }, - - preserveTestEnvironment: function preserveTestEnvironment() { - if (this.preserveEnvironment) { - this.module.testEnvironment = this.testEnvironment; - this.testEnvironment = extend({}, this.module.testEnvironment); - } - }, - - queue: function queue() { - var test = this; - - if (!this.valid()) { - return; - } - - function runTest() { - return [function () { - return test.before(); - }].concat(toConsumableArray(test.hooks("before")), [function () { - test.preserveTestEnvironment(); - }], toConsumableArray(test.hooks("beforeEach")), [function () { - test.run(); - }], toConsumableArray(test.hooks("afterEach").reverse()), toConsumableArray(test.hooks("after").reverse()), [function () { - test.after(); - }, function () { - return test.finish(); - }]); - } - - var previousFailCount = config.storage && +config.storage.getItem("qunit-test-" + this.module.name + "-" + this.testName); - - // Prioritize previously failed tests, detected from storage - var prioritize = config.reorder && !!previousFailCount; - - this.previousFailure = !!previousFailCount; - - ProcessingQueue.add(runTest, prioritize, config.seed); - - // If the queue has already finished, we manually process the new test - if (ProcessingQueue.finished) { - ProcessingQueue.advance(); - } - }, - - - pushResult: function pushResult(resultInfo) { - if (this !== config.current) { - throw new Error("Assertion occurred after test had finished."); - } - - // Destructure of resultInfo = { result, actual, expected, message, negative } - var source, - details = { - module: this.module.name, - name: this.testName, - result: resultInfo.result, - message: resultInfo.message, - actual: resultInfo.actual, - testId: this.testId, - negative: resultInfo.negative || false, - runtime: now() - this.started, - todo: !!this.todo - }; - - if (hasOwn.call(resultInfo, "expected")) { - details.expected = resultInfo.expected; - } - - if (!resultInfo.result) { - source = resultInfo.source || sourceFromStacktrace(); - - if (source) { - details.source = source; - } - } - - this.logAssertion(details); - - this.assertions.push({ - result: !!resultInfo.result, - message: resultInfo.message - }); - }, - - pushFailure: function pushFailure(message, source, actual) { - if (!(this instanceof Test)) { - throw new Error("pushFailure() assertion outside test context, was " + sourceFromStacktrace(2)); - } - - this.pushResult({ - result: false, - message: message || "error", - actual: actual || null, - source: source - }); - }, - - /** - * Log assertion details using both the old QUnit.log interface and - * QUnit.on( "assertion" ) interface. - * - * @private - */ - logAssertion: function logAssertion(details) { - runLoggingCallbacks("log", details); - - var assertion = { - passed: details.result, - actual: details.actual, - expected: details.expected, - message: details.message, - stack: details.source, - todo: details.todo - }; - this.testReport.pushAssertion(assertion); - emit("assertion", assertion); - }, - - - resolvePromise: function resolvePromise(promise, phase) { - var then, - resume, - message, - test = this; - if (promise != null) { - then = promise.then; - if (objectType(then) === "function") { - resume = internalStop(test); - if (config.notrycatch) { - then.call(promise, function () { - resume(); - }); - } else { - then.call(promise, function () { - resume(); - }, function (error) { - message = "Promise rejected " + (!phase ? "during" : phase.replace(/Each$/, "")) + " \"" + test.testName + "\": " + (error && error.message || error); - test.pushFailure(message, extractStacktrace(error, 0)); - - // Else next test will carry the responsibility - saveGlobal(); - - // Unblock - internalRecover(test); - }); - } - } - } - }, - - valid: function valid() { - var filter = config.filter, - regexFilter = /^(!?)\/([\w\W]*)\/(i?$)/.exec(filter), - module = config.module && config.module.toLowerCase(), - fullName = this.module.name + ": " + this.testName; - - function moduleChainNameMatch(testModule) { - var testModuleName = testModule.name ? testModule.name.toLowerCase() : null; - if (testModuleName === module) { - return true; - } else if (testModule.parentModule) { - return moduleChainNameMatch(testModule.parentModule); - } else { - return false; - } - } - - function moduleChainIdMatch(testModule) { - return inArray(testModule.moduleId, config.moduleId) || testModule.parentModule && moduleChainIdMatch(testModule.parentModule); - } - - // Internally-generated tests are always valid - if (this.callback && this.callback.validTest) { - return true; - } - - if (config.moduleId && config.moduleId.length > 0 && !moduleChainIdMatch(this.module)) { - - return false; - } - - if (config.testId && config.testId.length > 0 && !inArray(this.testId, config.testId)) { - - return false; - } - - if (module && !moduleChainNameMatch(this.module)) { - return false; - } - - if (!filter) { - return true; - } - - return regexFilter ? this.regexFilter(!!regexFilter[1], regexFilter[2], regexFilter[3], fullName) : this.stringFilter(filter, fullName); - }, - - regexFilter: function regexFilter(exclude, pattern, flags, fullName) { - var regex = new RegExp(pattern, flags); - var match = regex.test(fullName); - - return match !== exclude; - }, - - stringFilter: function stringFilter(filter, fullName) { - filter = filter.toLowerCase(); - fullName = fullName.toLowerCase(); - - var include = filter.charAt(0) !== "!"; - if (!include) { - filter = filter.slice(1); - } - - // If the filter matches, we need to honour include - if (fullName.indexOf(filter) !== -1) { - return include; - } - - // Otherwise, do the opposite - return !include; - } - }; - - function pushFailure() { - if (!config.current) { - throw new Error("pushFailure() assertion outside test context, in " + sourceFromStacktrace(2)); - } - - // Gets current test obj - var currentTest = config.current; - - return currentTest.pushFailure.apply(currentTest, arguments); - } - - function saveGlobal() { - config.pollution = []; - - if (config.noglobals) { - for (var key in global$1) { - if (hasOwn.call(global$1, key)) { - - // In Opera sometimes DOM element ids show up here, ignore them - if (/^qunit-test-output/.test(key)) { - continue; - } - config.pollution.push(key); - } - } - } - } - - function checkPollution() { - var newGlobals, - deletedGlobals, - old = config.pollution; - - saveGlobal(); - - newGlobals = diff(config.pollution, old); - if (newGlobals.length > 0) { - pushFailure("Introduced global variable(s): " + newGlobals.join(", ")); - } - - deletedGlobals = diff(old, config.pollution); - if (deletedGlobals.length > 0) { - pushFailure("Deleted global variable(s): " + deletedGlobals.join(", ")); - } - } - - // Will be exposed as QUnit.test - function test(testName, callback) { - if (focused$1) { - return; - } - - var newTest = new Test({ - testName: testName, - callback: callback - }); - - newTest.queue(); - } - - function todo(testName, callback) { - if (focused$1) { - return; - } - - var newTest = new Test({ - testName: testName, - callback: callback, - todo: true - }); - - newTest.queue(); - } - - // Will be exposed as QUnit.skip - function skip(testName) { - if (focused$1) { - return; - } - - var test = new Test({ - testName: testName, - skip: true - }); - - test.queue(); - } - - // Will be exposed as QUnit.only - function only(testName, callback) { - if (!focused$1) { - config.queue.length = 0; - focused$1 = true; - } - - var newTest = new Test({ - testName: testName, - callback: callback - }); - - newTest.queue(); - } - - // Resets config.timeout with a new timeout duration. - function resetTestTimeout(timeoutDuration) { - clearTimeout(config.timeout); - config.timeout = setTimeout$1(config.timeoutHandler(timeoutDuration), timeoutDuration); - } - - // Put a hold on processing and return a function that will release it. - function internalStop(test) { - var released = false; - test.semaphore += 1; - config.blocking = true; - - // Set a recovery timeout, if so configured. - if (defined.setTimeout) { - var timeoutDuration = void 0; - - if (typeof test.timeout === "number") { - timeoutDuration = test.timeout; - } else if (typeof config.testTimeout === "number") { - timeoutDuration = config.testTimeout; - } - - if (typeof timeoutDuration === "number" && timeoutDuration > 0) { - clearTimeout(config.timeout); - config.timeoutHandler = function (timeout) { - return function () { - pushFailure("Test took longer than " + timeout + "ms; test timed out.", sourceFromStacktrace(2)); - released = true; - internalRecover(test); - }; - }; - config.timeout = setTimeout$1(config.timeoutHandler(timeoutDuration), timeoutDuration); - } - } - - return function resume() { - if (released) { - return; - } - - released = true; - test.semaphore -= 1; - internalStart(test); - }; - } - - // Forcefully release all processing holds. - function internalRecover(test) { - test.semaphore = 0; - internalStart(test); - } - - // Release a processing hold, scheduling a resumption attempt if no holds remain. - function internalStart(test) { - - // If semaphore is non-numeric, throw error - if (isNaN(test.semaphore)) { - test.semaphore = 0; - - pushFailure("Invalid value on test.semaphore", sourceFromStacktrace(2)); - return; - } - - // Don't start until equal number of stop-calls - if (test.semaphore > 0) { - return; - } - - // Throw an Error if start is called more often than stop - if (test.semaphore < 0) { - test.semaphore = 0; - - pushFailure("Tried to restart test while already started (test's semaphore was 0 already)", sourceFromStacktrace(2)); - return; - } - - // Add a slight delay to allow more assertions etc. - if (defined.setTimeout) { - if (config.timeout) { - clearTimeout(config.timeout); - } - config.timeout = setTimeout$1(function () { - if (test.semaphore > 0) { - return; - } - - if (config.timeout) { - clearTimeout(config.timeout); - } - - begin(); - }); - } else { - begin(); - } - } - - function collectTests(module) { - var tests = [].concat(module.tests); - var modules = [].concat(toConsumableArray(module.childModules)); - - // Do a breadth-first traversal of the child modules - while (modules.length) { - var nextModule = modules.shift(); - tests.push.apply(tests, nextModule.tests); - modules.push.apply(modules, toConsumableArray(nextModule.childModules)); - } - - return tests; - } - - function numberOfTests(module) { - return collectTests(module).length; - } - - function numberOfUnskippedTests(module) { - return collectTests(module).filter(function (test) { - return !test.skip; - }).length; - } - - function notifyTestsRan(module, skipped) { - module.testsRun++; - if (!skipped) { - module.unskippedTestsRun++; - } - while (module = module.parentModule) { - module.testsRun++; - if (!skipped) { - module.unskippedTestsRun++; - } - } - } - - var Assert = function () { - function Assert(testContext) { - classCallCheck(this, Assert); - - this.test = testContext; - } - - // Assert helpers - - createClass(Assert, [{ - key: "timeout", - value: function timeout(duration) { - if (typeof duration !== "number") { - throw new Error("You must pass a number as the duration to assert.timeout"); - } - - this.test.timeout = duration; - - // If a timeout has been set, clear it and reset with the new duration - if (config.timeout) { - clearTimeout(config.timeout); - - if (config.timeoutHandler && this.test.timeout > 0) { - resetTestTimeout(this.test.timeout); - } - } - } - - // Documents a "step", which is a string value, in a test as a passing assertion - - }, { - key: "step", - value: function step(message) { - var assertionMessage = message; - var result = !!message; - - this.test.steps.push(message); - - if (objectType(message) === "undefined" || message === "") { - assertionMessage = "You must provide a message to assert.step"; - } else if (objectType(message) !== "string") { - assertionMessage = "You must provide a string value to assert.step"; - result = false; - } - - this.pushResult({ - result: result, - message: assertionMessage - }); - } - - // Verifies the steps in a test match a given array of string values - - }, { - key: "verifySteps", - value: function verifySteps(steps, message) { - - // Since the steps array is just string values, we can clone with slice - var actualStepsClone = this.test.steps.slice(); - this.deepEqual(actualStepsClone, steps, message); - this.test.steps.length = 0; - } - - // Specify the number of expected assertions to guarantee that failed test - // (no assertions are run at all) don't slip through. - - }, { - key: "expect", - value: function expect(asserts) { - if (arguments.length === 1) { - this.test.expected = asserts; - } else { - return this.test.expected; - } - } - - // Put a hold on processing and return a function that will release it a maximum of once. - - }, { - key: "async", - value: function async(count) { - var test$$1 = this.test; - - var popped = false, - acceptCallCount = count; - - if (typeof acceptCallCount === "undefined") { - acceptCallCount = 1; - } - - var resume = internalStop(test$$1); - - return function done() { - if (config.current !== test$$1) { - throw Error("assert.async callback called after test finished."); - } - - if (popped) { - test$$1.pushFailure("Too many calls to the `assert.async` callback", sourceFromStacktrace(2)); - return; - } - - acceptCallCount -= 1; - if (acceptCallCount > 0) { - return; - } - - popped = true; - resume(); - }; - } - - // Exports test.push() to the user API - // Alias of pushResult. - - }, { - key: "push", - value: function push(result, actual, expected, message, negative) { - Logger.warn("assert.push is deprecated and will be removed in QUnit 3.0." + " Please use assert.pushResult instead (https://api.qunitjs.com/assert/pushResult)."); - - var currentAssert = this instanceof Assert ? this : config.current.assert; - return currentAssert.pushResult({ - result: result, - actual: actual, - expected: expected, - message: message, - negative: negative - }); - } - }, { - key: "pushResult", - value: function pushResult(resultInfo) { - - // Destructure of resultInfo = { result, actual, expected, message, negative } - var assert = this; - var currentTest = assert instanceof Assert && assert.test || config.current; - - // Backwards compatibility fix. - // Allows the direct use of global exported assertions and QUnit.assert.* - // Although, it's use is not recommended as it can leak assertions - // to other tests from async tests, because we only get a reference to the current test, - // not exactly the test where assertion were intended to be called. - if (!currentTest) { - throw new Error("assertion outside test context, in " + sourceFromStacktrace(2)); - } - - if (!(assert instanceof Assert)) { - assert = currentTest.assert; - } - - return assert.test.pushResult(resultInfo); - } - }, { - key: "ok", - value: function ok(result, message) { - if (!message) { - message = result ? "okay" : "failed, expected argument to be truthy, was: " + dump.parse(result); - } - - this.pushResult({ - result: !!result, - actual: result, - expected: true, - message: message - }); - } - }, { - key: "notOk", - value: function notOk(result, message) { - if (!message) { - message = !result ? "okay" : "failed, expected argument to be falsy, was: " + dump.parse(result); - } - - this.pushResult({ - result: !result, - actual: result, - expected: false, - message: message - }); - } - }, { - key: "equal", - value: function equal(actual, expected, message) { - - // eslint-disable-next-line eqeqeq - var result = expected == actual; - - this.pushResult({ - result: result, - actual: actual, - expected: expected, - message: message - }); - } - }, { - key: "notEqual", - value: function notEqual(actual, expected, message) { - - // eslint-disable-next-line eqeqeq - var result = expected != actual; - - this.pushResult({ - result: result, - actual: actual, - expected: expected, - message: message, - negative: true - }); - } - }, { - key: "propEqual", - value: function propEqual(actual, expected, message) { - actual = objectValues(actual); - expected = objectValues(expected); - - this.pushResult({ - result: equiv(actual, expected), - actual: actual, - expected: expected, - message: message - }); - } - }, { - key: "notPropEqual", - value: function notPropEqual(actual, expected, message) { - actual = objectValues(actual); - expected = objectValues(expected); - - this.pushResult({ - result: !equiv(actual, expected), - actual: actual, - expected: expected, - message: message, - negative: true - }); - } - }, { - key: "deepEqual", - value: function deepEqual(actual, expected, message) { - this.pushResult({ - result: equiv(actual, expected), - actual: actual, - expected: expected, - message: message - }); - } - }, { - key: "notDeepEqual", - value: function notDeepEqual(actual, expected, message) { - this.pushResult({ - result: !equiv(actual, expected), - actual: actual, - expected: expected, - message: message, - negative: true - }); - } - }, { - key: "strictEqual", - value: function strictEqual(actual, expected, message) { - this.pushResult({ - result: expected === actual, - actual: actual, - expected: expected, - message: message - }); - } - }, { - key: "notStrictEqual", - value: function notStrictEqual(actual, expected, message) { - this.pushResult({ - result: expected !== actual, - actual: actual, - expected: expected, - message: message, - negative: true - }); - } - }, { - key: "throws", - value: function throws(block, expected, message) { - var actual = void 0, - result = false; - - var currentTest = this instanceof Assert && this.test || config.current; - - // 'expected' is optional unless doing string comparison - if (objectType(expected) === "string") { - if (message == null) { - message = expected; - expected = null; - } else { - throw new Error("throws/raises does not accept a string value for the expected argument.\n" + "Use a non-string object value (e.g. regExp) instead if it's necessary."); - } - } - - currentTest.ignoreGlobalErrors = true; - try { - block.call(currentTest.testEnvironment); - } catch (e) { - actual = e; - } - currentTest.ignoreGlobalErrors = false; - - if (actual) { - var expectedType = objectType(expected); - - // We don't want to validate thrown error - if (!expected) { - result = true; - - // Expected is a regexp - } else if (expectedType === "regexp") { - result = expected.test(errorString(actual)); - - // Log the string form of the regexp - expected = String(expected); - - // Expected is a constructor, maybe an Error constructor - } else if (expectedType === "function" && actual instanceof expected) { - result = true; - - // Expected is an Error object - } else if (expectedType === "object") { - result = actual instanceof expected.constructor && actual.name === expected.name && actual.message === expected.message; - - // Log the string form of the Error object - expected = errorString(expected); - - // Expected is a validation function which returns true if validation passed - } else if (expectedType === "function" && expected.call({}, actual) === true) { - expected = null; - result = true; - } - } - - currentTest.assert.pushResult({ - result: result, - - // undefined if it didn't throw - actual: actual && errorString(actual), - expected: expected, - message: message - }); - } - }, { - key: "rejects", - value: function rejects(promise, expected, message) { - var result = false; - - var currentTest = this instanceof Assert && this.test || config.current; - - // 'expected' is optional unless doing string comparison - if (objectType(expected) === "string") { - if (message === undefined) { - message = expected; - expected = undefined; - } else { - message = "assert.rejects does not accept a string value for the expected " + "argument.\nUse a non-string object value (e.g. validator function) instead " + "if necessary."; - - currentTest.assert.pushResult({ - result: false, - message: message - }); - - return; - } - } - - var then = promise && promise.then; - if (objectType(then) !== "function") { - var _message = "The value provided to `assert.rejects` in " + "\"" + currentTest.testName + "\" was not a promise."; - - currentTest.assert.pushResult({ - result: false, - message: _message, - actual: promise - }); - - return; - } - - var done = this.async(); - - return then.call(promise, function handleFulfillment() { - var message = "The promise returned by the `assert.rejects` callback in " + "\"" + currentTest.testName + "\" did not reject."; - - currentTest.assert.pushResult({ - result: false, - message: message, - actual: promise - }); - - done(); - }, function handleRejection(actual) { - var expectedType = objectType(expected); - - // We don't want to validate - if (expected === undefined) { - result = true; - - // Expected is a regexp - } else if (expectedType === "regexp") { - result = expected.test(errorString(actual)); - - // Log the string form of the regexp - expected = String(expected); - - // Expected is a constructor, maybe an Error constructor - } else if (expectedType === "function" && actual instanceof expected) { - result = true; - - // Expected is an Error object - } else if (expectedType === "object") { - result = actual instanceof expected.constructor && actual.name === expected.name && actual.message === expected.message; - - // Log the string form of the Error object - expected = errorString(expected); - - // Expected is a validation function which returns true if validation passed - } else { - if (expectedType === "function") { - result = expected.call({}, actual) === true; - expected = null; - - // Expected is some other invalid type - } else { - result = false; - message = "invalid expected value provided to `assert.rejects` " + "callback in \"" + currentTest.testName + "\": " + expectedType + "."; - } - } - - currentTest.assert.pushResult({ - result: result, - - // leave rejection value of undefined as-is - actual: actual && errorString(actual), - expected: expected, - message: message - }); - - done(); - }); - } - }]); - return Assert; - }(); - - // Provide an alternative to assert.throws(), for environments that consider throws a reserved word - // Known to us are: Closure Compiler, Narwhal - // eslint-disable-next-line dot-notation - - - Assert.prototype.raises = Assert.prototype["throws"]; - - /** - * Converts an error into a simple string for comparisons. - * - * @param {Error|Object} error - * @return {String} - */ - function errorString(error) { - var resultErrorString = error.toString(); - - // If the error wasn't a subclass of Error but something like - // an object literal with name and message properties... - if (resultErrorString.substring(0, 7) === "[object") { - var name = error.name ? error.name.toString() : "Error"; - var message = error.message ? error.message.toString() : ""; - - if (name && message) { - return name + ": " + message; - } else if (name) { - return name; - } else if (message) { - return message; - } else { - return "Error"; - } - } else { - return resultErrorString; - } - } - - /* global module, exports, define */ - function exportQUnit(QUnit) { - - if (defined.document) { - - // QUnit may be defined when it is preconfigured but then only QUnit and QUnit.config may be defined. - if (window$1.QUnit && window$1.QUnit.version) { - throw new Error("QUnit has already been defined."); - } - - window$1.QUnit = QUnit; - } - - // For nodejs - if (typeof module !== "undefined" && module && module.exports) { - module.exports = QUnit; - - // For consistency with CommonJS environments' exports - module.exports.QUnit = QUnit; - } - - // For CommonJS with exports, but without module.exports, like Rhino - if (typeof exports !== "undefined" && exports) { - exports.QUnit = QUnit; - } - - if (typeof define === "function" && define.amd) { - define(function () { - return QUnit; - }); - QUnit.config.autostart = false; - } - - // For Web/Service Workers - if (self$1 && self$1.WorkerGlobalScope && self$1 instanceof self$1.WorkerGlobalScope) { - self$1.QUnit = QUnit; - } - } - - // Handle an unhandled exception. By convention, returns true if further - // error handling should be suppressed and false otherwise. - // In this case, we will only suppress further error handling if the - // "ignoreGlobalErrors" configuration option is enabled. - function onError(error) { - for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { - args[_key - 1] = arguments[_key]; - } - - if (config.current) { - if (config.current.ignoreGlobalErrors) { - return true; - } - pushFailure.apply(undefined, [error.message, error.stacktrace || error.fileName + ":" + error.lineNumber].concat(args)); - } else { - test("global failure", extend(function () { - pushFailure.apply(undefined, [error.message, error.stacktrace || error.fileName + ":" + error.lineNumber].concat(args)); - }, { validTest: true })); - } - - return false; - } - - // Handle an unhandled rejection - function onUnhandledRejection(reason) { - var resultInfo = { - result: false, - message: reason.message || "error", - actual: reason, - source: reason.stack || sourceFromStacktrace(3) - }; - - var currentTest = config.current; - if (currentTest) { - currentTest.assert.pushResult(resultInfo); - } else { - test("global failure", extend(function (assert) { - assert.pushResult(resultInfo); - }, { validTest: true })); - } - } - - var QUnit = {}; - var globalSuite = new SuiteReport(); - - // The initial "currentModule" represents the global (or top-level) module that - // is not explicitly defined by the user, therefore we add the "globalSuite" to - // it since each module has a suiteReport associated with it. - config.currentModule.suiteReport = globalSuite; - - var globalStartCalled = false; - var runStarted = false; - - // Figure out if we're running the tests from a server or not - QUnit.isLocal = !(defined.document && window$1.location.protocol !== "file:"); - - // Expose the current QUnit version - QUnit.version = "2.10.0"; - - extend(QUnit, { - on: on, - - module: module$1, - - test: test, - - todo: todo, - - skip: skip, - - only: only, - - start: function start(count) { - var globalStartAlreadyCalled = globalStartCalled; - - if (!config.current) { - globalStartCalled = true; - - if (runStarted) { - throw new Error("Called start() while test already started running"); - } else if (globalStartAlreadyCalled || count > 1) { - throw new Error("Called start() outside of a test context too many times"); - } else if (config.autostart) { - throw new Error("Called start() outside of a test context when " + "QUnit.config.autostart was true"); - } else if (!config.pageLoaded) { - - // The page isn't completely loaded yet, so we set autostart and then - // load if we're in Node or wait for the browser's load event. - config.autostart = true; - - // Starts from Node even if .load was not previously called. We still return - // early otherwise we'll wind up "beginning" twice. - if (!defined.document) { - QUnit.load(); - } - - return; - } - } else { - throw new Error("QUnit.start cannot be called inside a test context."); - } - - scheduleBegin(); - }, - - config: config, - - is: is, - - objectType: objectType, - - extend: extend, - - load: function load() { - config.pageLoaded = true; - - // Initialize the configuration options - extend(config, { - stats: { all: 0, bad: 0 }, - started: 0, - updateRate: 1000, - autostart: true, - filter: "" - }, true); - - if (!runStarted) { - config.blocking = false; - - if (config.autostart) { - scheduleBegin(); - } - } - }, - - stack: function stack(offset) { - offset = (offset || 0) + 2; - return sourceFromStacktrace(offset); - }, - - onError: onError, - - onUnhandledRejection: onUnhandledRejection - }); - - QUnit.pushFailure = pushFailure; - QUnit.assert = Assert.prototype; - QUnit.equiv = equiv; - QUnit.dump = dump; - - registerLoggingCallbacks(QUnit); - - function scheduleBegin() { - - runStarted = true; - - // Add a slight delay to allow definition of more modules and tests. - if (defined.setTimeout) { - setTimeout$1(function () { - begin(); - }); - } else { - begin(); - } - } - - function unblockAndAdvanceQueue() { - config.blocking = false; - ProcessingQueue.advance(); - } - - function begin() { - var i, - l, - modulesLog = []; - - // If the test run hasn't officially begun yet - if (!config.started) { - - // Record the time of the test run's beginning - config.started = now(); - - // Delete the loose unnamed module if unused. - if (config.modules[0].name === "" && config.modules[0].tests.length === 0) { - config.modules.shift(); - } - - // Avoid unnecessary information by not logging modules' test environments - for (i = 0, l = config.modules.length; i < l; i++) { - modulesLog.push({ - name: config.modules[i].name, - tests: config.modules[i].tests - }); - } - - // The test run is officially beginning now - emit("runStart", globalSuite.start(true)); - runLoggingCallbacks("begin", { - totalTests: Test.count, - modules: modulesLog - }).then(unblockAndAdvanceQueue); - } else { - unblockAndAdvanceQueue(); - } - } - - exportQUnit(QUnit); - - (function () { - - if (typeof window$1 === "undefined" || typeof document$1 === "undefined") { - return; - } - - var config = QUnit.config, - hasOwn = Object.prototype.hasOwnProperty; - - // Stores fixture HTML for resetting later - function storeFixture() { - - // Avoid overwriting user-defined values - if (hasOwn.call(config, "fixture")) { - return; - } - - var fixture = document$1.getElementById("qunit-fixture"); - if (fixture) { - config.fixture = fixture.cloneNode(true); - } - } - - QUnit.begin(storeFixture); - - // Resets the fixture DOM element if available. - function resetFixture() { - if (config.fixture == null) { - return; - } - - var fixture = document$1.getElementById("qunit-fixture"); - var resetFixtureType = _typeof(config.fixture); - if (resetFixtureType === "string") { - - // support user defined values for `config.fixture` - var newFixture = document$1.createElement("div"); - newFixture.setAttribute("id", "qunit-fixture"); - newFixture.innerHTML = config.fixture; - fixture.parentNode.replaceChild(newFixture, fixture); - } else { - var clonedFixture = config.fixture.cloneNode(true); - fixture.parentNode.replaceChild(clonedFixture, fixture); - } - } - - QUnit.testStart(resetFixture); - })(); - - (function () { - - // Only interact with URLs via window.location - var location = typeof window$1 !== "undefined" && window$1.location; - if (!location) { - return; - } - - var urlParams = getUrlParams(); - - QUnit.urlParams = urlParams; - - // Match module/test by inclusion in an array - QUnit.config.moduleId = [].concat(urlParams.moduleId || []); - QUnit.config.testId = [].concat(urlParams.testId || []); - - // Exact case-insensitive match of the module name - QUnit.config.module = urlParams.module; - - // Regular expression or case-insenstive substring match against "moduleName: testName" - QUnit.config.filter = urlParams.filter; - - // Test order randomization - if (urlParams.seed === true) { - - // Generate a random seed if the option is specified without a value - QUnit.config.seed = Math.random().toString(36).slice(2); - } else if (urlParams.seed) { - QUnit.config.seed = urlParams.seed; - } - - // Add URL-parameter-mapped config values with UI form rendering data - QUnit.config.urlConfig.push({ - id: "hidepassed", - label: "Hide passed tests", - tooltip: "Only show tests and assertions that fail. Stored as query-strings." - }, { - id: "noglobals", - label: "Check for Globals", - tooltip: "Enabling this will test if any test introduces new properties on the " + "global object (`window` in Browsers). Stored as query-strings." - }, { - id: "notrycatch", - label: "No try-catch", - tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " + "exceptions in IE reasonable. Stored as query-strings." - }); - - QUnit.begin(function () { - var i, - option, - urlConfig = QUnit.config.urlConfig; - - for (i = 0; i < urlConfig.length; i++) { - - // Options can be either strings or objects with nonempty "id" properties - option = QUnit.config.urlConfig[i]; - if (typeof option !== "string") { - option = option.id; - } - - if (QUnit.config[option] === undefined) { - QUnit.config[option] = urlParams[option]; - } - } - }); - - function getUrlParams() { - var i, param, name, value; - var urlParams = Object.create(null); - var params = location.search.slice(1).split("&"); - var length = params.length; - - for (i = 0; i < length; i++) { - if (params[i]) { - param = params[i].split("="); - name = decodeQueryParam(param[0]); - - // Allow just a key to turn on a flag, e.g., test.html?noglobals - value = param.length === 1 || decodeQueryParam(param.slice(1).join("=")); - if (name in urlParams) { - urlParams[name] = [].concat(urlParams[name], value); - } else { - urlParams[name] = value; - } - } - } - - return urlParams; - } - - function decodeQueryParam(param) { - return decodeURIComponent(param.replace(/\+/g, "%20")); - } - })(); - - var stats = { - passedTests: 0, - failedTests: 0, - skippedTests: 0, - todoTests: 0 - }; - - // Escape text for attribute or text content. - function escapeText(s) { - if (!s) { - return ""; - } - s = s + ""; - - // Both single quotes and double quotes (for attributes) - return s.replace(/['"<>&]/g, function (s) { - switch (s) { - case "'": - return "'"; - case "\"": - return """; - case "<": - return "<"; - case ">": - return ">"; - case "&": - return "&"; - } - }); - } - - (function () { - - // Don't load the HTML Reporter on non-browser environments - if (typeof window$1 === "undefined" || !window$1.document) { - return; - } - - var config = QUnit.config, - hiddenTests = [], - document = window$1.document, - collapseNext = false, - hasOwn$$1 = Object.prototype.hasOwnProperty, - unfilteredUrl = setUrl({ filter: undefined, module: undefined, - moduleId: undefined, testId: undefined }), - modulesList = []; - - function addEvent(elem, type, fn) { - elem.addEventListener(type, fn, false); - } - - function removeEvent(elem, type, fn) { - elem.removeEventListener(type, fn, false); - } - - function addEvents(elems, type, fn) { - var i = elems.length; - while (i--) { - addEvent(elems[i], type, fn); - } - } - - function hasClass(elem, name) { - return (" " + elem.className + " ").indexOf(" " + name + " ") >= 0; - } - - function addClass(elem, name) { - if (!hasClass(elem, name)) { - elem.className += (elem.className ? " " : "") + name; - } - } - - function toggleClass(elem, name, force) { - if (force || typeof force === "undefined" && !hasClass(elem, name)) { - addClass(elem, name); - } else { - removeClass(elem, name); - } - } - - function removeClass(elem, name) { - var set = " " + elem.className + " "; - - // Class name may appear multiple times - while (set.indexOf(" " + name + " ") >= 0) { - set = set.replace(" " + name + " ", " "); - } - - // Trim for prettiness - elem.className = typeof set.trim === "function" ? set.trim() : set.replace(/^\s+|\s+$/g, ""); - } - - function id(name) { - return document.getElementById && document.getElementById(name); - } - - function abortTests() { - var abortButton = id("qunit-abort-tests-button"); - if (abortButton) { - abortButton.disabled = true; - abortButton.innerHTML = "Aborting..."; - } - QUnit.config.queue.length = 0; - return false; - } - - function interceptNavigation(ev) { - applyUrlParams(); - - if (ev && ev.preventDefault) { - ev.preventDefault(); - } - - return false; - } - - function getUrlConfigHtml() { - var i, - j, - val, - escaped, - escapedTooltip, - selection = false, - urlConfig = config.urlConfig, - urlConfigHtml = ""; - - for (i = 0; i < urlConfig.length; i++) { - - // Options can be either strings or objects with nonempty "id" properties - val = config.urlConfig[i]; - if (typeof val === "string") { - val = { - id: val, - label: val - }; - } - - escaped = escapeText(val.id); - escapedTooltip = escapeText(val.tooltip); - - if (!val.value || typeof val.value === "string") { - urlConfigHtml += ""; - } else { - urlConfigHtml += ""; - } - } - - return urlConfigHtml; - } - - // Handle "click" events on toolbar checkboxes and "change" for select menus. - // Updates the URL with the new state of `config.urlConfig` values. - function toolbarChanged() { - var updatedUrl, - value, - tests, - field = this, - params = {}; - - // Detect if field is a select menu or a checkbox - if ("selectedIndex" in field) { - value = field.options[field.selectedIndex].value || undefined; - } else { - value = field.checked ? field.defaultValue || true : undefined; - } - - params[field.name] = value; - updatedUrl = setUrl(params); - - // Check if we can apply the change without a page refresh - if ("hidepassed" === field.name && "replaceState" in window$1.history) { - QUnit.urlParams[field.name] = value; - config[field.name] = value || false; - tests = id("qunit-tests"); - if (tests) { - var length = tests.children.length; - var children = tests.children; - - if (field.checked) { - for (var i = 0; i < length; i++) { - var test$$1 = children[i]; - - if (test$$1 && test$$1.className.indexOf("pass") > -1) { - hiddenTests.push(test$$1); - } - } - - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = undefined; - - try { - for (var _iterator = hiddenTests[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var hiddenTest = _step.value; - - tests.removeChild(hiddenTest); - } - } catch (err) { - _didIteratorError = true; - _iteratorError = err; - } finally { - try { - if (!_iteratorNormalCompletion && _iterator.return) { - _iterator.return(); - } - } finally { - if (_didIteratorError) { - throw _iteratorError; - } - } - } - } else { - while ((test$$1 = hiddenTests.pop()) != null) { - tests.appendChild(test$$1); - } - } - } - window$1.history.replaceState(null, "", updatedUrl); - } else { - window$1.location = updatedUrl; - } - } - - function setUrl(params) { - var key, - arrValue, - i, - querystring = "?", - location = window$1.location; - - params = QUnit.extend(QUnit.extend({}, QUnit.urlParams), params); - - for (key in params) { - - // Skip inherited or undefined properties - if (hasOwn$$1.call(params, key) && params[key] !== undefined) { - - // Output a parameter for each value of this key - // (but usually just one) - arrValue = [].concat(params[key]); - for (i = 0; i < arrValue.length; i++) { - querystring += encodeURIComponent(key); - if (arrValue[i] !== true) { - querystring += "=" + encodeURIComponent(arrValue[i]); - } - querystring += "&"; - } - } - } - return location.protocol + "//" + location.host + location.pathname + querystring.slice(0, -1); - } - - function applyUrlParams() { - var i, - selectedModules = [], - modulesList = id("qunit-modulefilter-dropdown-list").getElementsByTagName("input"), - filter = id("qunit-filter-input").value; - - for (i = 0; i < modulesList.length; i++) { - if (modulesList[i].checked) { - selectedModules.push(modulesList[i].value); - } - } - - window$1.location = setUrl({ - filter: filter === "" ? undefined : filter, - moduleId: selectedModules.length === 0 ? undefined : selectedModules, - - // Remove module and testId filter - module: undefined, - testId: undefined - }); - } - - function toolbarUrlConfigContainer() { - var urlConfigContainer = document.createElement("span"); - - urlConfigContainer.innerHTML = getUrlConfigHtml(); - addClass(urlConfigContainer, "qunit-url-config"); - - addEvents(urlConfigContainer.getElementsByTagName("input"), "change", toolbarChanged); - addEvents(urlConfigContainer.getElementsByTagName("select"), "change", toolbarChanged); - - return urlConfigContainer; - } - - function abortTestsButton() { - var button = document.createElement("button"); - button.id = "qunit-abort-tests-button"; - button.innerHTML = "Abort"; - addEvent(button, "click", abortTests); - return button; - } - - function toolbarLooseFilter() { - var filter = document.createElement("form"), - label = document.createElement("label"), - input = document.createElement("input"), - button = document.createElement("button"); - - addClass(filter, "qunit-filter"); - - label.innerHTML = "Filter: "; - - input.type = "text"; - input.value = config.filter || ""; - input.name = "filter"; - input.id = "qunit-filter-input"; - - button.innerHTML = "Go"; - - label.appendChild(input); - - filter.appendChild(label); - filter.appendChild(document.createTextNode(" ")); - filter.appendChild(button); - addEvent(filter, "submit", interceptNavigation); - - return filter; - } - - function moduleListHtml() { - var i, - checked, - html = ""; - - for (i = 0; i < config.modules.length; i++) { - if (config.modules[i].name !== "") { - checked = config.moduleId.indexOf(config.modules[i].moduleId) > -1; - html += "
  • "; - } - } - - return html; - } - - function toolbarModuleFilter() { - var commit, - reset, - moduleFilter = document.createElement("form"), - label = document.createElement("label"), - moduleSearch = document.createElement("input"), - dropDown = document.createElement("div"), - actions = document.createElement("span"), - applyButton = document.createElement("button"), - resetButton = document.createElement("button"), - allModulesLabel = document.createElement("label"), - allCheckbox = document.createElement("input"), - dropDownList = document.createElement("ul"), - dirty = false; - - moduleSearch.id = "qunit-modulefilter-search"; - moduleSearch.autocomplete = "off"; - addEvent(moduleSearch, "input", searchInput); - addEvent(moduleSearch, "input", searchFocus); - addEvent(moduleSearch, "focus", searchFocus); - addEvent(moduleSearch, "click", searchFocus); - - label.id = "qunit-modulefilter-search-container"; - label.innerHTML = "Module: "; - label.appendChild(moduleSearch); - - applyButton.textContent = "Apply"; - applyButton.style.display = "none"; - - resetButton.textContent = "Reset"; - resetButton.type = "reset"; - resetButton.style.display = "none"; - - allCheckbox.type = "checkbox"; - allCheckbox.checked = config.moduleId.length === 0; - - allModulesLabel.className = "clickable"; - if (config.moduleId.length) { - allModulesLabel.className = "checked"; - } - allModulesLabel.appendChild(allCheckbox); - allModulesLabel.appendChild(document.createTextNode("All modules")); - - actions.id = "qunit-modulefilter-actions"; - actions.appendChild(applyButton); - actions.appendChild(resetButton); - actions.appendChild(allModulesLabel); - commit = actions.firstChild; - reset = commit.nextSibling; - addEvent(commit, "click", applyUrlParams); - - dropDownList.id = "qunit-modulefilter-dropdown-list"; - dropDownList.innerHTML = moduleListHtml(); - - dropDown.id = "qunit-modulefilter-dropdown"; - dropDown.style.display = "none"; - dropDown.appendChild(actions); - dropDown.appendChild(dropDownList); - addEvent(dropDown, "change", selectionChange); - selectionChange(); - - moduleFilter.id = "qunit-modulefilter"; - moduleFilter.appendChild(label); - moduleFilter.appendChild(dropDown); - addEvent(moduleFilter, "submit", interceptNavigation); - addEvent(moduleFilter, "reset", function () { - - // Let the reset happen, then update styles - window$1.setTimeout(selectionChange); - }); - - // Enables show/hide for the dropdown - function searchFocus() { - if (dropDown.style.display !== "none") { - return; - } - - dropDown.style.display = "block"; - addEvent(document, "click", hideHandler); - addEvent(document, "keydown", hideHandler); - - // Hide on Escape keydown or outside-container click - function hideHandler(e) { - var inContainer = moduleFilter.contains(e.target); - - if (e.keyCode === 27 || !inContainer) { - if (e.keyCode === 27 && inContainer) { - moduleSearch.focus(); - } - dropDown.style.display = "none"; - removeEvent(document, "click", hideHandler); - removeEvent(document, "keydown", hideHandler); - moduleSearch.value = ""; - searchInput(); - } - } - } - - // Processes module search box input - function searchInput() { - var i, - item, - searchText = moduleSearch.value.toLowerCase(), - listItems = dropDownList.children; - - for (i = 0; i < listItems.length; i++) { - item = listItems[i]; - if (!searchText || item.textContent.toLowerCase().indexOf(searchText) > -1) { - item.style.display = ""; - } else { - item.style.display = "none"; - } - } - } - - // Processes selection changes - function selectionChange(evt) { - var i, - item, - checkbox = evt && evt.target || allCheckbox, - modulesList = dropDownList.getElementsByTagName("input"), - selectedNames = []; - - toggleClass(checkbox.parentNode, "checked", checkbox.checked); - - dirty = false; - if (checkbox.checked && checkbox !== allCheckbox) { - allCheckbox.checked = false; - removeClass(allCheckbox.parentNode, "checked"); - } - for (i = 0; i < modulesList.length; i++) { - item = modulesList[i]; - if (!evt) { - toggleClass(item.parentNode, "checked", item.checked); - } else if (checkbox === allCheckbox && checkbox.checked) { - item.checked = false; - removeClass(item.parentNode, "checked"); - } - dirty = dirty || item.checked !== item.defaultChecked; - if (item.checked) { - selectedNames.push(item.parentNode.textContent); - } - } - - commit.style.display = reset.style.display = dirty ? "" : "none"; - moduleSearch.placeholder = selectedNames.join(", ") || allCheckbox.parentNode.textContent; - moduleSearch.title = "Type to filter list. Current selection:\n" + (selectedNames.join("\n") || allCheckbox.parentNode.textContent); - } - - return moduleFilter; - } - - function toolbarFilters() { - var toolbarFilters = document.createElement("span"); - - toolbarFilters.id = "qunit-toolbar-filters"; - toolbarFilters.appendChild(toolbarLooseFilter()); - toolbarFilters.appendChild(toolbarModuleFilter()); - - return toolbarFilters; - } - - function appendToolbar() { - var toolbar = id("qunit-testrunner-toolbar"); - - if (toolbar) { - toolbar.appendChild(toolbarUrlConfigContainer()); - toolbar.appendChild(toolbarFilters()); - toolbar.appendChild(document.createElement("div")).className = "clearfix"; - } - } - - function appendHeader() { - var header = id("qunit-header"); - - if (header) { - header.innerHTML = "" + header.innerHTML + " "; - } - } - - function appendBanner() { - var banner = id("qunit-banner"); - - if (banner) { - banner.className = ""; - } - } - - function appendTestResults() { - var tests = id("qunit-tests"), - result = id("qunit-testresult"), - controls; - - if (result) { - result.parentNode.removeChild(result); - } - - if (tests) { - tests.innerHTML = ""; - result = document.createElement("p"); - result.id = "qunit-testresult"; - result.className = "result"; - tests.parentNode.insertBefore(result, tests); - result.innerHTML = "
    Running...
     
    " + "
    " + "
    "; - controls = id("qunit-testresult-controls"); - } - - if (controls) { - controls.appendChild(abortTestsButton()); - } - } - - function appendFilteredTest() { - var testId = QUnit.config.testId; - if (!testId || testId.length <= 0) { - return ""; - } - return "
    Rerunning selected tests: " + escapeText(testId.join(", ")) + " Run all tests
    "; - } - - function appendUserAgent() { - var userAgent = id("qunit-userAgent"); - - if (userAgent) { - userAgent.innerHTML = ""; - userAgent.appendChild(document.createTextNode("QUnit " + QUnit.version + "; " + navigator.userAgent)); - } - } - - function appendInterface() { - var qunit = id("qunit"); - - if (qunit) { - qunit.innerHTML = "

    " + escapeText(document.title) + "

    " + "

    " + "
    " + appendFilteredTest() + "

    " + "
      "; - } - - appendHeader(); - appendBanner(); - appendTestResults(); - appendUserAgent(); - appendToolbar(); - } - - function appendTest(name, testId, moduleName) { - var title, - rerunTrigger, - testBlock, - assertList, - tests = id("qunit-tests"); - - if (!tests) { - return; - } - - title = document.createElement("strong"); - title.innerHTML = getNameHtml(name, moduleName); - - rerunTrigger = document.createElement("a"); - rerunTrigger.innerHTML = "Rerun"; - rerunTrigger.href = setUrl({ testId: testId }); - - testBlock = document.createElement("li"); - testBlock.appendChild(title); - testBlock.appendChild(rerunTrigger); - testBlock.id = "qunit-test-output-" + testId; - - assertList = document.createElement("ol"); - assertList.className = "qunit-assert-list"; - - testBlock.appendChild(assertList); - - tests.appendChild(testBlock); - } - - // HTML Reporter initialization and load - QUnit.begin(function (details) { - var i, moduleObj; - - // Sort modules by name for the picker - for (i = 0; i < details.modules.length; i++) { - moduleObj = details.modules[i]; - if (moduleObj.name) { - modulesList.push(moduleObj.name); - } - } - modulesList.sort(function (a, b) { - return a.localeCompare(b); - }); - - // Initialize QUnit elements - appendInterface(); - }); - - QUnit.done(function (details) { - var banner = id("qunit-banner"), - tests = id("qunit-tests"), - abortButton = id("qunit-abort-tests-button"), - totalTests = stats.passedTests + stats.skippedTests + stats.todoTests + stats.failedTests, - html = [totalTests, " tests completed in ", details.runtime, " milliseconds, with ", stats.failedTests, " failed, ", stats.skippedTests, " skipped, and ", stats.todoTests, " todo.
      ", "", details.passed, " assertions of ", details.total, " passed, ", details.failed, " failed."].join(""), - test$$1, - assertLi, - assertList; - - // Update remaining tests to aborted - if (abortButton && abortButton.disabled) { - html = "Tests aborted after " + details.runtime + " milliseconds."; - - for (var i = 0; i < tests.children.length; i++) { - test$$1 = tests.children[i]; - if (test$$1.className === "" || test$$1.className === "running") { - test$$1.className = "aborted"; - assertList = test$$1.getElementsByTagName("ol")[0]; - assertLi = document.createElement("li"); - assertLi.className = "fail"; - assertLi.innerHTML = "Test aborted."; - assertList.appendChild(assertLi); - } - } - } - - if (banner && (!abortButton || abortButton.disabled === false)) { - banner.className = stats.failedTests ? "qunit-fail" : "qunit-pass"; - } - - if (abortButton) { - abortButton.parentNode.removeChild(abortButton); - } - - if (tests) { - id("qunit-testresult-display").innerHTML = html; - } - - if (config.altertitle && document.title) { - - // Show ✖ for good, ✔ for bad suite result in title - // use escape sequences in case file gets loaded with non-utf-8 - // charset - document.title = [stats.failedTests ? "\u2716" : "\u2714", document.title.replace(/^[\u2714\u2716] /i, "")].join(" "); - } - - // Scroll back to top to show results - if (config.scrolltop && window$1.scrollTo) { - window$1.scrollTo(0, 0); - } - }); - - function getNameHtml(name, module) { - var nameHtml = ""; - - if (module) { - nameHtml = "" + escapeText(module) + ": "; - } - - nameHtml += "" + escapeText(name) + ""; - - return nameHtml; - } - - function getProgressHtml(runtime, stats, total) { - var completed = stats.passedTests + stats.skippedTests + stats.todoTests + stats.failedTests; - - return ["
      ", completed, " / ", total, " tests completed in ", runtime, " milliseconds, with ", stats.failedTests, " failed, ", stats.skippedTests, " skipped, and ", stats.todoTests, " todo."].join(""); - } - - QUnit.testStart(function (details) { - var running, bad; - - appendTest(details.name, details.testId, details.module); - - running = id("qunit-testresult-display"); - - if (running) { - addClass(running, "running"); - - bad = QUnit.config.reorder && details.previousFailure; - - running.innerHTML = [bad ? "Rerunning previously failed test:
      " : "Running:
      ", getNameHtml(details.name, details.module), getProgressHtml(now() - config.started, stats, Test.count)].join(""); - } - }); - - function stripHtml(string) { - - // Strip tags, html entity and whitespaces - return string.replace(/<\/?[^>]+(>|$)/g, "").replace(/"/g, "").replace(/\s+/g, ""); - } - - QUnit.log(function (details) { - var assertList, - assertLi, - message, - expected, - actual, - diff$$1, - showDiff = false, - testItem = id("qunit-test-output-" + details.testId); - - if (!testItem) { - return; - } - - message = escapeText(details.message) || (details.result ? "okay" : "failed"); - message = "" + message + ""; - message += "@ " + details.runtime + " ms"; - - // The pushFailure doesn't provide details.expected - // when it calls, it's implicit to also not show expected and diff stuff - // Also, we need to check details.expected existence, as it can exist and be undefined - if (!details.result && hasOwn$$1.call(details, "expected")) { - if (details.negative) { - expected = "NOT " + QUnit.dump.parse(details.expected); - } else { - expected = QUnit.dump.parse(details.expected); - } - - actual = QUnit.dump.parse(details.actual); - message += ""; - - if (actual !== expected) { - - message += ""; - - if (typeof details.actual === "number" && typeof details.expected === "number") { - if (!isNaN(details.actual) && !isNaN(details.expected)) { - showDiff = true; - diff$$1 = details.actual - details.expected; - diff$$1 = (diff$$1 > 0 ? "+" : "") + diff$$1; - } - } else if (typeof details.actual !== "boolean" && typeof details.expected !== "boolean") { - diff$$1 = QUnit.diff(expected, actual); - - // don't show diff if there is zero overlap - showDiff = stripHtml(diff$$1).length !== stripHtml(expected).length + stripHtml(actual).length; - } - - if (showDiff) { - message += ""; - } - } else if (expected.indexOf("[object Array]") !== -1 || expected.indexOf("[object Object]") !== -1) { - message += ""; - } else { - message += ""; - } - - if (details.source) { - message += ""; - } - - message += "
      Expected:
      " + escapeText(expected) + "
      Result:
      " + escapeText(actual) + "
      Diff:
      " + diff$$1 + "
      Message: " + "Diff suppressed as the depth of object is more than current max depth (" + QUnit.config.maxDepth + ").

      Hint: Use QUnit.dump.maxDepth to " + " run with a higher max depth or " + "Rerun without max depth.

      Message: " + "Diff suppressed as the expected and actual results have an equivalent" + " serialization
      Source:
      " + escapeText(details.source) + "
      "; - - // This occurs when pushFailure is set and we have an extracted stack trace - } else if (!details.result && details.source) { - message += "" + "" + "
      Source:
      " + escapeText(details.source) + "
      "; - } - - assertList = testItem.getElementsByTagName("ol")[0]; - - assertLi = document.createElement("li"); - assertLi.className = details.result ? "pass" : "fail"; - assertLi.innerHTML = message; - assertList.appendChild(assertLi); - }); - - QUnit.testDone(function (details) { - var testTitle, - time, - testItem, - assertList, - status, - good, - bad, - testCounts, - skipped, - sourceName, - tests = id("qunit-tests"); - - if (!tests) { - return; - } - - testItem = id("qunit-test-output-" + details.testId); - - removeClass(testItem, "running"); - - if (details.failed > 0) { - status = "failed"; - } else if (details.todo) { - status = "todo"; - } else { - status = details.skipped ? "skipped" : "passed"; - } - - assertList = testItem.getElementsByTagName("ol")[0]; - - good = details.passed; - bad = details.failed; - - // This test passed if it has no unexpected failed assertions - var testPassed = details.failed > 0 ? details.todo : !details.todo; - - if (testPassed) { - - // Collapse the passing tests - addClass(assertList, "qunit-collapsed"); - } else if (config.collapse) { - if (!collapseNext) { - - // Skip collapsing the first failing test - collapseNext = true; - } else { - - // Collapse remaining tests - addClass(assertList, "qunit-collapsed"); - } - } - - // The testItem.firstChild is the test name - testTitle = testItem.firstChild; - - testCounts = bad ? "" + bad + ", " + "" + good + ", " : ""; - - testTitle.innerHTML += " (" + testCounts + details.assertions.length + ")"; - - if (details.skipped) { - stats.skippedTests++; - - testItem.className = "skipped"; - skipped = document.createElement("em"); - skipped.className = "qunit-skipped-label"; - skipped.innerHTML = "skipped"; - testItem.insertBefore(skipped, testTitle); - } else { - addEvent(testTitle, "click", function () { - toggleClass(assertList, "qunit-collapsed"); - }); - - testItem.className = testPassed ? "pass" : "fail"; - - if (details.todo) { - var todoLabel = document.createElement("em"); - todoLabel.className = "qunit-todo-label"; - todoLabel.innerHTML = "todo"; - testItem.className += " todo"; - testItem.insertBefore(todoLabel, testTitle); - } - - time = document.createElement("span"); - time.className = "runtime"; - time.innerHTML = details.runtime + " ms"; - testItem.insertBefore(time, assertList); - - if (!testPassed) { - stats.failedTests++; - } else if (details.todo) { - stats.todoTests++; - } else { - stats.passedTests++; - } - } - - // Show the source of the test when showing assertions - if (details.source) { - sourceName = document.createElement("p"); - sourceName.innerHTML = "Source: " + escapeText(details.source); - addClass(sourceName, "qunit-source"); - if (testPassed) { - addClass(sourceName, "qunit-collapsed"); - } - addEvent(testTitle, "click", function () { - toggleClass(sourceName, "qunit-collapsed"); - }); - testItem.appendChild(sourceName); - } - - if (config.hidepassed && status === "passed") { - - // use removeChild instead of remove because of support - hiddenTests.push(testItem); - - tests.removeChild(testItem); - } - }); - - // Avoid readyState issue with phantomjs - // Ref: #818 - var notPhantom = function (p) { - return !(p && p.version && p.version.major > 0); - }(window$1.phantom); - - if (notPhantom && document.readyState === "complete") { - QUnit.load(); - } else { - addEvent(window$1, "load", QUnit.load); - } - - // Wrap window.onerror. We will call the original window.onerror to see if - // the existing handler fully handles the error; if not, we will call the - // QUnit.onError function. - var originalWindowOnError = window$1.onerror; - - // Cover uncaught exceptions - // Returning true will suppress the default browser handler, - // returning false will let it run. - window$1.onerror = function (message, fileName, lineNumber, columnNumber, errorObj) { - var ret = false; - if (originalWindowOnError) { - for (var _len = arguments.length, args = Array(_len > 5 ? _len - 5 : 0), _key = 5; _key < _len; _key++) { - args[_key - 5] = arguments[_key]; - } - - ret = originalWindowOnError.call.apply(originalWindowOnError, [this, message, fileName, lineNumber, columnNumber, errorObj].concat(args)); - } - - // Treat return value as window.onerror itself does, - // Only do our handling if not suppressed. - if (ret !== true) { - var error = { - message: message, - fileName: fileName, - lineNumber: lineNumber - }; - - // According to - // https://blog.sentry.io/2016/01/04/client-javascript-reporting-window-onerror, - // most modern browsers support an errorObj argument; use that to - // get a full stack trace if it's available. - if (errorObj && errorObj.stack) { - error.stacktrace = extractStacktrace(errorObj, 0); - } - - ret = QUnit.onError(error); - } - - return ret; - }; - - // Listen for unhandled rejections, and call QUnit.onUnhandledRejection - window$1.addEventListener("unhandledrejection", function (event) { - QUnit.onUnhandledRejection(event.reason); - }); - })(); - - /* - * This file is a modified version of google-diff-match-patch's JavaScript implementation - * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js), - * modifications are licensed as more fully set forth in LICENSE.txt. - * - * The original source of google-diff-match-patch is attributable and licensed as follows: - * - * Copyright 2006 Google Inc. - * https://code.google.com/p/google-diff-match-patch/ - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * More Info: - * https://code.google.com/p/google-diff-match-patch/ - * - * Usage: QUnit.diff(expected, actual) - * - */ - QUnit.diff = function () { - function DiffMatchPatch() {} - - // DIFF FUNCTIONS - - /** - * The data structure representing a diff is an array of tuples: - * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']] - * which means: delete 'Hello', add 'Goodbye' and keep ' world.' - */ - var DIFF_DELETE = -1, - DIFF_INSERT = 1, - DIFF_EQUAL = 0; - - /** - * Find the differences between two texts. Simplifies the problem by stripping - * any common prefix or suffix off the texts before diffing. - * @param {string} text1 Old string to be diffed. - * @param {string} text2 New string to be diffed. - * @param {boolean=} optChecklines Optional speedup flag. If present and false, - * then don't run a line-level diff first to identify the changed areas. - * Defaults to true, which does a faster, slightly less optimal diff. - * @return {!Array.} Array of diff tuples. - */ - DiffMatchPatch.prototype.DiffMain = function (text1, text2, optChecklines) { - var deadline, checklines, commonlength, commonprefix, commonsuffix, diffs; - - // The diff must be complete in up to 1 second. - deadline = new Date().getTime() + 1000; - - // Check for null inputs. - if (text1 === null || text2 === null) { - throw new Error("Null input. (DiffMain)"); - } - - // Check for equality (speedup). - if (text1 === text2) { - if (text1) { - return [[DIFF_EQUAL, text1]]; - } - return []; - } - - if (typeof optChecklines === "undefined") { - optChecklines = true; - } - - checklines = optChecklines; - - // Trim off common prefix (speedup). - commonlength = this.diffCommonPrefix(text1, text2); - commonprefix = text1.substring(0, commonlength); - text1 = text1.substring(commonlength); - text2 = text2.substring(commonlength); - - // Trim off common suffix (speedup). - commonlength = this.diffCommonSuffix(text1, text2); - commonsuffix = text1.substring(text1.length - commonlength); - text1 = text1.substring(0, text1.length - commonlength); - text2 = text2.substring(0, text2.length - commonlength); - - // Compute the diff on the middle block. - diffs = this.diffCompute(text1, text2, checklines, deadline); - - // Restore the prefix and suffix. - if (commonprefix) { - diffs.unshift([DIFF_EQUAL, commonprefix]); - } - if (commonsuffix) { - diffs.push([DIFF_EQUAL, commonsuffix]); - } - this.diffCleanupMerge(diffs); - return diffs; - }; - - /** - * Reduce the number of edits by eliminating operationally trivial equalities. - * @param {!Array.} diffs Array of diff tuples. - */ - DiffMatchPatch.prototype.diffCleanupEfficiency = function (diffs) { - var changes, equalities, equalitiesLength, lastequality, pointer, preIns, preDel, postIns, postDel; - changes = false; - equalities = []; // Stack of indices where equalities are found. - equalitiesLength = 0; // Keeping our own length var is faster in JS. - /** @type {?string} */ - lastequality = null; - - // Always equal to diffs[equalities[equalitiesLength - 1]][1] - pointer = 0; // Index of current position. - - // Is there an insertion operation before the last equality. - preIns = false; - - // Is there a deletion operation before the last equality. - preDel = false; - - // Is there an insertion operation after the last equality. - postIns = false; - - // Is there a deletion operation after the last equality. - postDel = false; - while (pointer < diffs.length) { - - // Equality found. - if (diffs[pointer][0] === DIFF_EQUAL) { - if (diffs[pointer][1].length < 4 && (postIns || postDel)) { - - // Candidate found. - equalities[equalitiesLength++] = pointer; - preIns = postIns; - preDel = postDel; - lastequality = diffs[pointer][1]; - } else { - - // Not a candidate, and can never become one. - equalitiesLength = 0; - lastequality = null; - } - postIns = postDel = false; - - // An insertion or deletion. - } else { - - if (diffs[pointer][0] === DIFF_DELETE) { - postDel = true; - } else { - postIns = true; - } - - /* - * Five types to be split: - * ABXYCD - * AXCD - * ABXC - * AXCD - * ABXC - */ - if (lastequality && (preIns && preDel && postIns && postDel || lastequality.length < 2 && preIns + preDel + postIns + postDel === 3)) { - - // Duplicate record. - diffs.splice(equalities[equalitiesLength - 1], 0, [DIFF_DELETE, lastequality]); - - // Change second copy to insert. - diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT; - equalitiesLength--; // Throw away the equality we just deleted; - lastequality = null; - if (preIns && preDel) { - - // No changes made which could affect previous entry, keep going. - postIns = postDel = true; - equalitiesLength = 0; - } else { - equalitiesLength--; // Throw away the previous equality. - pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1; - postIns = postDel = false; - } - changes = true; - } - } - pointer++; - } - - if (changes) { - this.diffCleanupMerge(diffs); - } - }; - - /** - * Convert a diff array into a pretty HTML report. - * @param {!Array.} diffs Array of diff tuples. - * @param {integer} string to be beautified. - * @return {string} HTML representation. - */ - DiffMatchPatch.prototype.diffPrettyHtml = function (diffs) { - var op, - data, - x, - html = []; - for (x = 0; x < diffs.length; x++) { - op = diffs[x][0]; // Operation (insert, delete, equal) - data = diffs[x][1]; // Text of change. - switch (op) { - case DIFF_INSERT: - html[x] = "" + escapeText(data) + ""; - break; - case DIFF_DELETE: - html[x] = "" + escapeText(data) + ""; - break; - case DIFF_EQUAL: - html[x] = "" + escapeText(data) + ""; - break; - } - } - return html.join(""); - }; - - /** - * Determine the common prefix of two strings. - * @param {string} text1 First string. - * @param {string} text2 Second string. - * @return {number} The number of characters common to the start of each - * string. - */ - DiffMatchPatch.prototype.diffCommonPrefix = function (text1, text2) { - var pointermid, pointermax, pointermin, pointerstart; - - // Quick check for common null cases. - if (!text1 || !text2 || text1.charAt(0) !== text2.charAt(0)) { - return 0; - } - - // Binary search. - // Performance analysis: https://neil.fraser.name/news/2007/10/09/ - pointermin = 0; - pointermax = Math.min(text1.length, text2.length); - pointermid = pointermax; - pointerstart = 0; - while (pointermin < pointermid) { - if (text1.substring(pointerstart, pointermid) === text2.substring(pointerstart, pointermid)) { - pointermin = pointermid; - pointerstart = pointermin; - } else { - pointermax = pointermid; - } - pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin); - } - return pointermid; - }; - - /** - * Determine the common suffix of two strings. - * @param {string} text1 First string. - * @param {string} text2 Second string. - * @return {number} The number of characters common to the end of each string. - */ - DiffMatchPatch.prototype.diffCommonSuffix = function (text1, text2) { - var pointermid, pointermax, pointermin, pointerend; - - // Quick check for common null cases. - if (!text1 || !text2 || text1.charAt(text1.length - 1) !== text2.charAt(text2.length - 1)) { - return 0; - } - - // Binary search. - // Performance analysis: https://neil.fraser.name/news/2007/10/09/ - pointermin = 0; - pointermax = Math.min(text1.length, text2.length); - pointermid = pointermax; - pointerend = 0; - while (pointermin < pointermid) { - if (text1.substring(text1.length - pointermid, text1.length - pointerend) === text2.substring(text2.length - pointermid, text2.length - pointerend)) { - pointermin = pointermid; - pointerend = pointermin; - } else { - pointermax = pointermid; - } - pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin); - } - return pointermid; - }; - - /** - * Find the differences between two texts. Assumes that the texts do not - * have any common prefix or suffix. - * @param {string} text1 Old string to be diffed. - * @param {string} text2 New string to be diffed. - * @param {boolean} checklines Speedup flag. If false, then don't run a - * line-level diff first to identify the changed areas. - * If true, then run a faster, slightly less optimal diff. - * @param {number} deadline Time when the diff should be complete by. - * @return {!Array.} Array of diff tuples. - * @private - */ - DiffMatchPatch.prototype.diffCompute = function (text1, text2, checklines, deadline) { - var diffs, longtext, shorttext, i, hm, text1A, text2A, text1B, text2B, midCommon, diffsA, diffsB; - - if (!text1) { - - // Just add some text (speedup). - return [[DIFF_INSERT, text2]]; - } - - if (!text2) { - - // Just delete some text (speedup). - return [[DIFF_DELETE, text1]]; - } - - longtext = text1.length > text2.length ? text1 : text2; - shorttext = text1.length > text2.length ? text2 : text1; - i = longtext.indexOf(shorttext); - if (i !== -1) { - - // Shorter text is inside the longer text (speedup). - diffs = [[DIFF_INSERT, longtext.substring(0, i)], [DIFF_EQUAL, shorttext], [DIFF_INSERT, longtext.substring(i + shorttext.length)]]; - - // Swap insertions for deletions if diff is reversed. - if (text1.length > text2.length) { - diffs[0][0] = diffs[2][0] = DIFF_DELETE; - } - return diffs; - } - - if (shorttext.length === 1) { - - // Single character string. - // After the previous speedup, the character can't be an equality. - return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]]; - } - - // Check to see if the problem can be split in two. - hm = this.diffHalfMatch(text1, text2); - if (hm) { - - // A half-match was found, sort out the return data. - text1A = hm[0]; - text1B = hm[1]; - text2A = hm[2]; - text2B = hm[3]; - midCommon = hm[4]; - - // Send both pairs off for separate processing. - diffsA = this.DiffMain(text1A, text2A, checklines, deadline); - diffsB = this.DiffMain(text1B, text2B, checklines, deadline); - - // Merge the results. - return diffsA.concat([[DIFF_EQUAL, midCommon]], diffsB); - } - - if (checklines && text1.length > 100 && text2.length > 100) { - return this.diffLineMode(text1, text2, deadline); - } - - return this.diffBisect(text1, text2, deadline); - }; - - /** - * Do the two texts share a substring which is at least half the length of the - * longer text? - * This speedup can produce non-minimal diffs. - * @param {string} text1 First string. - * @param {string} text2 Second string. - * @return {Array.} Five element Array, containing the prefix of - * text1, the suffix of text1, the prefix of text2, the suffix of - * text2 and the common middle. Or null if there was no match. - * @private - */ - DiffMatchPatch.prototype.diffHalfMatch = function (text1, text2) { - var longtext, shorttext, dmp, text1A, text2B, text2A, text1B, midCommon, hm1, hm2, hm; - - longtext = text1.length > text2.length ? text1 : text2; - shorttext = text1.length > text2.length ? text2 : text1; - if (longtext.length < 4 || shorttext.length * 2 < longtext.length) { - return null; // Pointless. - } - dmp = this; // 'this' becomes 'window' in a closure. - - /** - * Does a substring of shorttext exist within longtext such that the substring - * is at least half the length of longtext? - * Closure, but does not reference any external variables. - * @param {string} longtext Longer string. - * @param {string} shorttext Shorter string. - * @param {number} i Start index of quarter length substring within longtext. - * @return {Array.} Five element Array, containing the prefix of - * longtext, the suffix of longtext, the prefix of shorttext, the suffix - * of shorttext and the common middle. Or null if there was no match. - * @private - */ - function diffHalfMatchI(longtext, shorttext, i) { - var seed, j, bestCommon, prefixLength, suffixLength, bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB; - - // Start with a 1/4 length substring at position i as a seed. - seed = longtext.substring(i, i + Math.floor(longtext.length / 4)); - j = -1; - bestCommon = ""; - while ((j = shorttext.indexOf(seed, j + 1)) !== -1) { - prefixLength = dmp.diffCommonPrefix(longtext.substring(i), shorttext.substring(j)); - suffixLength = dmp.diffCommonSuffix(longtext.substring(0, i), shorttext.substring(0, j)); - if (bestCommon.length < suffixLength + prefixLength) { - bestCommon = shorttext.substring(j - suffixLength, j) + shorttext.substring(j, j + prefixLength); - bestLongtextA = longtext.substring(0, i - suffixLength); - bestLongtextB = longtext.substring(i + prefixLength); - bestShorttextA = shorttext.substring(0, j - suffixLength); - bestShorttextB = shorttext.substring(j + prefixLength); - } - } - if (bestCommon.length * 2 >= longtext.length) { - return [bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB, bestCommon]; - } else { - return null; - } - } - - // First check if the second quarter is the seed for a half-match. - hm1 = diffHalfMatchI(longtext, shorttext, Math.ceil(longtext.length / 4)); - - // Check again based on the third quarter. - hm2 = diffHalfMatchI(longtext, shorttext, Math.ceil(longtext.length / 2)); - if (!hm1 && !hm2) { - return null; - } else if (!hm2) { - hm = hm1; - } else if (!hm1) { - hm = hm2; - } else { - - // Both matched. Select the longest. - hm = hm1[4].length > hm2[4].length ? hm1 : hm2; - } - - // A half-match was found, sort out the return data. - if (text1.length > text2.length) { - text1A = hm[0]; - text1B = hm[1]; - text2A = hm[2]; - text2B = hm[3]; - } else { - text2A = hm[0]; - text2B = hm[1]; - text1A = hm[2]; - text1B = hm[3]; - } - midCommon = hm[4]; - return [text1A, text1B, text2A, text2B, midCommon]; - }; - - /** - * Do a quick line-level diff on both strings, then rediff the parts for - * greater accuracy. - * This speedup can produce non-minimal diffs. - * @param {string} text1 Old string to be diffed. - * @param {string} text2 New string to be diffed. - * @param {number} deadline Time when the diff should be complete by. - * @return {!Array.} Array of diff tuples. - * @private - */ - DiffMatchPatch.prototype.diffLineMode = function (text1, text2, deadline) { - var a, diffs, linearray, pointer, countInsert, countDelete, textInsert, textDelete, j; - - // Scan the text on a line-by-line basis first. - a = this.diffLinesToChars(text1, text2); - text1 = a.chars1; - text2 = a.chars2; - linearray = a.lineArray; - - diffs = this.DiffMain(text1, text2, false, deadline); - - // Convert the diff back to original text. - this.diffCharsToLines(diffs, linearray); - - // Eliminate freak matches (e.g. blank lines) - this.diffCleanupSemantic(diffs); - - // Rediff any replacement blocks, this time character-by-character. - // Add a dummy entry at the end. - diffs.push([DIFF_EQUAL, ""]); - pointer = 0; - countDelete = 0; - countInsert = 0; - textDelete = ""; - textInsert = ""; - while (pointer < diffs.length) { - switch (diffs[pointer][0]) { - case DIFF_INSERT: - countInsert++; - textInsert += diffs[pointer][1]; - break; - case DIFF_DELETE: - countDelete++; - textDelete += diffs[pointer][1]; - break; - case DIFF_EQUAL: - - // Upon reaching an equality, check for prior redundancies. - if (countDelete >= 1 && countInsert >= 1) { - - // Delete the offending records and add the merged ones. - diffs.splice(pointer - countDelete - countInsert, countDelete + countInsert); - pointer = pointer - countDelete - countInsert; - a = this.DiffMain(textDelete, textInsert, false, deadline); - for (j = a.length - 1; j >= 0; j--) { - diffs.splice(pointer, 0, a[j]); - } - pointer = pointer + a.length; - } - countInsert = 0; - countDelete = 0; - textDelete = ""; - textInsert = ""; - break; - } - pointer++; - } - diffs.pop(); // Remove the dummy entry at the end. - - return diffs; - }; - - /** - * Find the 'middle snake' of a diff, split the problem in two - * and return the recursively constructed diff. - * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations. - * @param {string} text1 Old string to be diffed. - * @param {string} text2 New string to be diffed. - * @param {number} deadline Time at which to bail if not yet complete. - * @return {!Array.} Array of diff tuples. - * @private - */ - DiffMatchPatch.prototype.diffBisect = function (text1, text2, deadline) { - var text1Length, text2Length, maxD, vOffset, vLength, v1, v2, x, delta, front, k1start, k1end, k2start, k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2; - - // Cache the text lengths to prevent multiple calls. - text1Length = text1.length; - text2Length = text2.length; - maxD = Math.ceil((text1Length + text2Length) / 2); - vOffset = maxD; - vLength = 2 * maxD; - v1 = new Array(vLength); - v2 = new Array(vLength); - - // Setting all elements to -1 is faster in Chrome & Firefox than mixing - // integers and undefined. - for (x = 0; x < vLength; x++) { - v1[x] = -1; - v2[x] = -1; - } - v1[vOffset + 1] = 0; - v2[vOffset + 1] = 0; - delta = text1Length - text2Length; - - // If the total number of characters is odd, then the front path will collide - // with the reverse path. - front = delta % 2 !== 0; - - // Offsets for start and end of k loop. - // Prevents mapping of space beyond the grid. - k1start = 0; - k1end = 0; - k2start = 0; - k2end = 0; - for (d = 0; d < maxD; d++) { - - // Bail out if deadline is reached. - if (new Date().getTime() > deadline) { - break; - } - - // Walk the front path one step. - for (k1 = -d + k1start; k1 <= d - k1end; k1 += 2) { - k1Offset = vOffset + k1; - if (k1 === -d || k1 !== d && v1[k1Offset - 1] < v1[k1Offset + 1]) { - x1 = v1[k1Offset + 1]; - } else { - x1 = v1[k1Offset - 1] + 1; - } - y1 = x1 - k1; - while (x1 < text1Length && y1 < text2Length && text1.charAt(x1) === text2.charAt(y1)) { - x1++; - y1++; - } - v1[k1Offset] = x1; - if (x1 > text1Length) { - - // Ran off the right of the graph. - k1end += 2; - } else if (y1 > text2Length) { - - // Ran off the bottom of the graph. - k1start += 2; - } else if (front) { - k2Offset = vOffset + delta - k1; - if (k2Offset >= 0 && k2Offset < vLength && v2[k2Offset] !== -1) { - - // Mirror x2 onto top-left coordinate system. - x2 = text1Length - v2[k2Offset]; - if (x1 >= x2) { - - // Overlap detected. - return this.diffBisectSplit(text1, text2, x1, y1, deadline); - } - } - } - } - - // Walk the reverse path one step. - for (k2 = -d + k2start; k2 <= d - k2end; k2 += 2) { - k2Offset = vOffset + k2; - if (k2 === -d || k2 !== d && v2[k2Offset - 1] < v2[k2Offset + 1]) { - x2 = v2[k2Offset + 1]; - } else { - x2 = v2[k2Offset - 1] + 1; - } - y2 = x2 - k2; - while (x2 < text1Length && y2 < text2Length && text1.charAt(text1Length - x2 - 1) === text2.charAt(text2Length - y2 - 1)) { - x2++; - y2++; - } - v2[k2Offset] = x2; - if (x2 > text1Length) { - - // Ran off the left of the graph. - k2end += 2; - } else if (y2 > text2Length) { - - // Ran off the top of the graph. - k2start += 2; - } else if (!front) { - k1Offset = vOffset + delta - k2; - if (k1Offset >= 0 && k1Offset < vLength && v1[k1Offset] !== -1) { - x1 = v1[k1Offset]; - y1 = vOffset + x1 - k1Offset; - - // Mirror x2 onto top-left coordinate system. - x2 = text1Length - x2; - if (x1 >= x2) { - - // Overlap detected. - return this.diffBisectSplit(text1, text2, x1, y1, deadline); - } - } - } - } - } - - // Diff took too long and hit the deadline or - // number of diffs equals number of characters, no commonality at all. - return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]]; - }; - - /** - * Given the location of the 'middle snake', split the diff in two parts - * and recurse. - * @param {string} text1 Old string to be diffed. - * @param {string} text2 New string to be diffed. - * @param {number} x Index of split point in text1. - * @param {number} y Index of split point in text2. - * @param {number} deadline Time at which to bail if not yet complete. - * @return {!Array.} Array of diff tuples. - * @private - */ - DiffMatchPatch.prototype.diffBisectSplit = function (text1, text2, x, y, deadline) { - var text1a, text1b, text2a, text2b, diffs, diffsb; - text1a = text1.substring(0, x); - text2a = text2.substring(0, y); - text1b = text1.substring(x); - text2b = text2.substring(y); - - // Compute both diffs serially. - diffs = this.DiffMain(text1a, text2a, false, deadline); - diffsb = this.DiffMain(text1b, text2b, false, deadline); - - return diffs.concat(diffsb); - }; - - /** - * Reduce the number of edits by eliminating semantically trivial equalities. - * @param {!Array.} diffs Array of diff tuples. - */ - DiffMatchPatch.prototype.diffCleanupSemantic = function (diffs) { - var changes, equalities, equalitiesLength, lastequality, pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1, lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2; - changes = false; - equalities = []; // Stack of indices where equalities are found. - equalitiesLength = 0; // Keeping our own length var is faster in JS. - /** @type {?string} */ - lastequality = null; - - // Always equal to diffs[equalities[equalitiesLength - 1]][1] - pointer = 0; // Index of current position. - - // Number of characters that changed prior to the equality. - lengthInsertions1 = 0; - lengthDeletions1 = 0; - - // Number of characters that changed after the equality. - lengthInsertions2 = 0; - lengthDeletions2 = 0; - while (pointer < diffs.length) { - if (diffs[pointer][0] === DIFF_EQUAL) { - // Equality found. - equalities[equalitiesLength++] = pointer; - lengthInsertions1 = lengthInsertions2; - lengthDeletions1 = lengthDeletions2; - lengthInsertions2 = 0; - lengthDeletions2 = 0; - lastequality = diffs[pointer][1]; - } else { - // An insertion or deletion. - if (diffs[pointer][0] === DIFF_INSERT) { - lengthInsertions2 += diffs[pointer][1].length; - } else { - lengthDeletions2 += diffs[pointer][1].length; - } - - // Eliminate an equality that is smaller or equal to the edits on both - // sides of it. - if (lastequality && lastequality.length <= Math.max(lengthInsertions1, lengthDeletions1) && lastequality.length <= Math.max(lengthInsertions2, lengthDeletions2)) { - - // Duplicate record. - diffs.splice(equalities[equalitiesLength - 1], 0, [DIFF_DELETE, lastequality]); - - // Change second copy to insert. - diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT; - - // Throw away the equality we just deleted. - equalitiesLength--; - - // Throw away the previous equality (it needs to be reevaluated). - equalitiesLength--; - pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1; - - // Reset the counters. - lengthInsertions1 = 0; - lengthDeletions1 = 0; - lengthInsertions2 = 0; - lengthDeletions2 = 0; - lastequality = null; - changes = true; - } - } - pointer++; - } - - // Normalize the diff. - if (changes) { - this.diffCleanupMerge(diffs); - } - - // Find any overlaps between deletions and insertions. - // e.g: abcxxxxxxdef - // -> abcxxxdef - // e.g: xxxabcdefxxx - // -> defxxxabc - // Only extract an overlap if it is as big as the edit ahead or behind it. - pointer = 1; - while (pointer < diffs.length) { - if (diffs[pointer - 1][0] === DIFF_DELETE && diffs[pointer][0] === DIFF_INSERT) { - deletion = diffs[pointer - 1][1]; - insertion = diffs[pointer][1]; - overlapLength1 = this.diffCommonOverlap(deletion, insertion); - overlapLength2 = this.diffCommonOverlap(insertion, deletion); - if (overlapLength1 >= overlapLength2) { - if (overlapLength1 >= deletion.length / 2 || overlapLength1 >= insertion.length / 2) { - - // Overlap found. Insert an equality and trim the surrounding edits. - diffs.splice(pointer, 0, [DIFF_EQUAL, insertion.substring(0, overlapLength1)]); - diffs[pointer - 1][1] = deletion.substring(0, deletion.length - overlapLength1); - diffs[pointer + 1][1] = insertion.substring(overlapLength1); - pointer++; - } - } else { - if (overlapLength2 >= deletion.length / 2 || overlapLength2 >= insertion.length / 2) { - - // Reverse overlap found. - // Insert an equality and swap and trim the surrounding edits. - diffs.splice(pointer, 0, [DIFF_EQUAL, deletion.substring(0, overlapLength2)]); - - diffs[pointer - 1][0] = DIFF_INSERT; - diffs[pointer - 1][1] = insertion.substring(0, insertion.length - overlapLength2); - diffs[pointer + 1][0] = DIFF_DELETE; - diffs[pointer + 1][1] = deletion.substring(overlapLength2); - pointer++; - } - } - pointer++; - } - pointer++; - } - }; - - /** - * Determine if the suffix of one string is the prefix of another. - * @param {string} text1 First string. - * @param {string} text2 Second string. - * @return {number} The number of characters common to the end of the first - * string and the start of the second string. - * @private - */ - DiffMatchPatch.prototype.diffCommonOverlap = function (text1, text2) { - var text1Length, text2Length, textLength, best, length, pattern, found; - - // Cache the text lengths to prevent multiple calls. - text1Length = text1.length; - text2Length = text2.length; - - // Eliminate the null case. - if (text1Length === 0 || text2Length === 0) { - return 0; - } - - // Truncate the longer string. - if (text1Length > text2Length) { - text1 = text1.substring(text1Length - text2Length); - } else if (text1Length < text2Length) { - text2 = text2.substring(0, text1Length); - } - textLength = Math.min(text1Length, text2Length); - - // Quick check for the worst case. - if (text1 === text2) { - return textLength; - } - - // Start by looking for a single character match - // and increase length until no match is found. - // Performance analysis: https://neil.fraser.name/news/2010/11/04/ - best = 0; - length = 1; - while (true) { - pattern = text1.substring(textLength - length); - found = text2.indexOf(pattern); - if (found === -1) { - return best; - } - length += found; - if (found === 0 || text1.substring(textLength - length) === text2.substring(0, length)) { - best = length; - length++; - } - } - }; - - /** - * Split two texts into an array of strings. Reduce the texts to a string of - * hashes where each Unicode character represents one line. - * @param {string} text1 First string. - * @param {string} text2 Second string. - * @return {{chars1: string, chars2: string, lineArray: !Array.}} - * An object containing the encoded text1, the encoded text2 and - * the array of unique strings. - * The zeroth element of the array of unique strings is intentionally blank. - * @private - */ - DiffMatchPatch.prototype.diffLinesToChars = function (text1, text2) { - var lineArray, lineHash, chars1, chars2; - lineArray = []; // E.g. lineArray[4] === 'Hello\n' - lineHash = {}; // E.g. lineHash['Hello\n'] === 4 - - // '\x00' is a valid character, but various debuggers don't like it. - // So we'll insert a junk entry to avoid generating a null character. - lineArray[0] = ""; - - /** - * Split a text into an array of strings. Reduce the texts to a string of - * hashes where each Unicode character represents one line. - * Modifies linearray and linehash through being a closure. - * @param {string} text String to encode. - * @return {string} Encoded string. - * @private - */ - function diffLinesToCharsMunge(text) { - var chars, lineStart, lineEnd, lineArrayLength, line; - chars = ""; - - // Walk the text, pulling out a substring for each line. - // text.split('\n') would would temporarily double our memory footprint. - // Modifying text would create many large strings to garbage collect. - lineStart = 0; - lineEnd = -1; - - // Keeping our own length variable is faster than looking it up. - lineArrayLength = lineArray.length; - while (lineEnd < text.length - 1) { - lineEnd = text.indexOf("\n", lineStart); - if (lineEnd === -1) { - lineEnd = text.length - 1; - } - line = text.substring(lineStart, lineEnd + 1); - lineStart = lineEnd + 1; - - var lineHashExists = lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) : lineHash[line] !== undefined; - - if (lineHashExists) { - chars += String.fromCharCode(lineHash[line]); - } else { - chars += String.fromCharCode(lineArrayLength); - lineHash[line] = lineArrayLength; - lineArray[lineArrayLength++] = line; - } - } - return chars; - } - - chars1 = diffLinesToCharsMunge(text1); - chars2 = diffLinesToCharsMunge(text2); - return { - chars1: chars1, - chars2: chars2, - lineArray: lineArray - }; - }; - - /** - * Rehydrate the text in a diff from a string of line hashes to real lines of - * text. - * @param {!Array.} diffs Array of diff tuples. - * @param {!Array.} lineArray Array of unique strings. - * @private - */ - DiffMatchPatch.prototype.diffCharsToLines = function (diffs, lineArray) { - var x, chars, text, y; - for (x = 0; x < diffs.length; x++) { - chars = diffs[x][1]; - text = []; - for (y = 0; y < chars.length; y++) { - text[y] = lineArray[chars.charCodeAt(y)]; - } - diffs[x][1] = text.join(""); - } - }; - - /** - * Reorder and merge like edit sections. Merge equalities. - * Any edit section can move as long as it doesn't cross an equality. - * @param {!Array.} diffs Array of diff tuples. - */ - DiffMatchPatch.prototype.diffCleanupMerge = function (diffs) { - var pointer, countDelete, countInsert, textInsert, textDelete, commonlength, changes, diffPointer, position; - diffs.push([DIFF_EQUAL, ""]); // Add a dummy entry at the end. - pointer = 0; - countDelete = 0; - countInsert = 0; - textDelete = ""; - textInsert = ""; - - while (pointer < diffs.length) { - switch (diffs[pointer][0]) { - case DIFF_INSERT: - countInsert++; - textInsert += diffs[pointer][1]; - pointer++; - break; - case DIFF_DELETE: - countDelete++; - textDelete += diffs[pointer][1]; - pointer++; - break; - case DIFF_EQUAL: - - // Upon reaching an equality, check for prior redundancies. - if (countDelete + countInsert > 1) { - if (countDelete !== 0 && countInsert !== 0) { - - // Factor out any common prefixes. - commonlength = this.diffCommonPrefix(textInsert, textDelete); - if (commonlength !== 0) { - if (pointer - countDelete - countInsert > 0 && diffs[pointer - countDelete - countInsert - 1][0] === DIFF_EQUAL) { - diffs[pointer - countDelete - countInsert - 1][1] += textInsert.substring(0, commonlength); - } else { - diffs.splice(0, 0, [DIFF_EQUAL, textInsert.substring(0, commonlength)]); - pointer++; - } - textInsert = textInsert.substring(commonlength); - textDelete = textDelete.substring(commonlength); - } - - // Factor out any common suffixies. - commonlength = this.diffCommonSuffix(textInsert, textDelete); - if (commonlength !== 0) { - diffs[pointer][1] = textInsert.substring(textInsert.length - commonlength) + diffs[pointer][1]; - textInsert = textInsert.substring(0, textInsert.length - commonlength); - textDelete = textDelete.substring(0, textDelete.length - commonlength); - } - } - - // Delete the offending records and add the merged ones. - if (countDelete === 0) { - diffs.splice(pointer - countInsert, countDelete + countInsert, [DIFF_INSERT, textInsert]); - } else if (countInsert === 0) { - diffs.splice(pointer - countDelete, countDelete + countInsert, [DIFF_DELETE, textDelete]); - } else { - diffs.splice(pointer - countDelete - countInsert, countDelete + countInsert, [DIFF_DELETE, textDelete], [DIFF_INSERT, textInsert]); - } - pointer = pointer - countDelete - countInsert + (countDelete ? 1 : 0) + (countInsert ? 1 : 0) + 1; - } else if (pointer !== 0 && diffs[pointer - 1][0] === DIFF_EQUAL) { - - // Merge this equality with the previous one. - diffs[pointer - 1][1] += diffs[pointer][1]; - diffs.splice(pointer, 1); - } else { - pointer++; - } - countInsert = 0; - countDelete = 0; - textDelete = ""; - textInsert = ""; - break; - } - } - if (diffs[diffs.length - 1][1] === "") { - diffs.pop(); // Remove the dummy entry at the end. - } - - // Second pass: look for single edits surrounded on both sides by equalities - // which can be shifted sideways to eliminate an equality. - // e.g: ABAC -> ABAC - changes = false; - pointer = 1; - - // Intentionally ignore the first and last element (don't need checking). - while (pointer < diffs.length - 1) { - if (diffs[pointer - 1][0] === DIFF_EQUAL && diffs[pointer + 1][0] === DIFF_EQUAL) { - - diffPointer = diffs[pointer][1]; - position = diffPointer.substring(diffPointer.length - diffs[pointer - 1][1].length); - - // This is a single edit surrounded by equalities. - if (position === diffs[pointer - 1][1]) { - - // Shift the edit over the previous equality. - diffs[pointer][1] = diffs[pointer - 1][1] + diffs[pointer][1].substring(0, diffs[pointer][1].length - diffs[pointer - 1][1].length); - diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1]; - diffs.splice(pointer - 1, 1); - changes = true; - } else if (diffPointer.substring(0, diffs[pointer + 1][1].length) === diffs[pointer + 1][1]) { - - // Shift the edit over the next equality. - diffs[pointer - 1][1] += diffs[pointer + 1][1]; - diffs[pointer][1] = diffs[pointer][1].substring(diffs[pointer + 1][1].length) + diffs[pointer + 1][1]; - diffs.splice(pointer + 1, 1); - changes = true; - } - } - pointer++; - } - - // If shifts were made, the diff needs reordering and another shift sweep. - if (changes) { - this.diffCleanupMerge(diffs); - } - }; - - return function (o, n) { - var diff, output, text; - diff = new DiffMatchPatch(); - output = diff.DiffMain(o, n); - diff.diffCleanupEfficiency(output); - text = diff.diffPrettyHtml(output); - - return text; - }; - }(); - -}((function() { return this; }()))); diff --git a/test/selector/converter.test.ts b/test/selector/converter.test.ts new file mode 100644 index 00000000..36cea8bb --- /dev/null +++ b/test/selector/converter.test.ts @@ -0,0 +1,187 @@ +/** + * @jest-environment jsdom + */ + +import { convert } from '../../src/selector'; + +describe('converter', () => { + describe('trim selectors', () => { + const rawSelectors = [ + ' #test p', + ' #test p', + '\t#test p', + '\r#test p', + '\n#test p', + '\f#test p', + '#test p ', + '#test p ', + '#test p\t', + '#test p\r', + '#test p\n', + '#test p\f', + ]; + const expected = '#test p'; + + test.each(rawSelectors)('%s', (raw) => { + expect(convert(raw)).toEqual(expected); + }); + }); + + describe('obvious scope inside has', () => { + const testsInputs = [ + { actual: 'div:has(:scope > a > img[id])', expected: 'div:has(> a > img[id])' }, + { actual: '.block:-abp-has(:scope > .banner)', expected: '.block:-abp-has(> .banner)' }, + { actual: 'div.r-block:-abp-has(:scope > [id^=one2n-])', expected: 'div.r-block:-abp-has(> [id^=one2n-])' }, + ]; + test.each(testsInputs)('%s', ({ actual, expected }) => { + expect(convert(actual)).toEqual(expected); + }); + }); + + describe('old matches-css pseudo-classes', () => { + const testsInputs = [ + { + actual: 'span:matches-css-before(content:ad*))', + expected: 'span:matches-css(before,content:ad*))', + }, + { + actual: 'span:matches-css-after(content:ad*))', + expected: 'span:matches-css(after,content:ad*))', + }, + { + actual: 'div:matches-css-after(color: rgb(255, 255, 255))', + expected: 'div:matches-css(after,color: rgb(255, 255, 255))', + }, + ]; + test.each(testsInputs)('%s', ({ actual, expected }) => { + expect(convert(actual)).toEqual(expected); + }); + }); + + describe('old syntax', () => { + const testsInputs = [ + // has + { + actual: 'div[-ext-has=".banner"]', + expected: 'div:has(.banner)', + }, + { + actual: 'div.test-class[-ext-has="time.g-time"]', + expected: 'div.test-class:has(time.g-time)', + }, + { + actual: 'div#test-div[-ext-has="#test"]', + expected: 'div#test-div:has(#test)', + }, + { + actual: '[-ext-has="div.advert"]', + expected: ':has(div.advert)', + }, + { + actual: '[-ext-has="div.test-class-two"]', + expected: ':has(div.test-class-two)', + }, + { + actual: '.block[-ext-has=\'a[href^="https://example.net/"]\']', + expected: '.block:has(a[href^="https://example.net/"])', + }, + { + actual: 'div[style*="z-index:"][-ext-has=\'>div[id$="_content"]>iframe#overlay_iframe\']', + expected: 'div[style*="z-index:"]:has(>div[id$="_content"]>iframe#overlay_iframe)', + }, + // contains + { + actual: 'div a[-ext-contains="text"]', + expected: 'div a:contains(text)', + }, + { + actual: 'a[-ext-contains=""extra-quotes""]', + expected: 'a:contains("extra-quotes")', + }, + { + actual: 'a[target="_blank"][-ext-contains="Advertisement"]', + expected: 'a[target="_blank"]:contains(Advertisement)', + }, + { + actual: 'div[style="text-align: center"] > b[-ext-contains="Ads:"]+a[href^="http://example.com/test.html?id="]+br', // eslint-disable-line max-len + expected: 'div[style="text-align: center"] > b:contains(Ads:)+a[href^="http://example.com/test.html?id="]+br', // eslint-disable-line max-len + }, + // matches-css + { + actual: '#test-matches-css div[-ext-matches-css="background-image: url(data:*)"]', + expected: '#test-matches-css div:matches-css(background-image: url(data:*))', + }, + { + actual: '#test-opacity-property[-ext-matches-css="opacity: 0.9"]', + expected: '#test-opacity-property:matches-css(opacity: 0.9)', + }, + { + actual: '#test-matches-css div[-ext-matches-css-before="content: *find me*"]', + expected: '#test-matches-css div:matches-css(before,content: *find me*)', + }, + { + actual: '#test-matches-css div[-ext-matches-css-after="content: *find me*"]', + expected: '#test-matches-css div:matches-css(after,content: *find me*)', + }, + // combinations + { + actual: 'div[-ext-contains="adg-test"][-ext-has="div.test-class-two"]', + expected: 'div:contains(adg-test):has(div.test-class-two)', + }, + { + actual: 'div[i18n][-ext-contains="adg-test"][-ext-has="div.test-class-two"]', + expected: 'div[i18n]:contains(adg-test):has(div.test-class-two)', + }, + { + actual: 'div[-ext-has="div.test-class-two"] > .test-class[-ext-contains="test"]', + expected: 'div:has(div.test-class-two) > .test-class:contains(test)', + }, + { + actual: '#sidebar div[class^="text-"][-ext-has=">.box-inner>h2:contains(ads)"]', + expected: '#sidebar div[class^="text-"]:has(>.box-inner>h2:contains(ads))', + }, + { + actual: '.sidebar > h3[-ext-has="a:contains(Recommended)"]', + expected: '.sidebar > h3:has(a:contains(Recommended))', + }, + { + actual: '.sidebar > h3[-ext-has="a:contains(Recommended)"] + div', + expected: '.sidebar > h3:has(a:contains(Recommended)) + div', + }, + { + actual: '*[-ext-contains=\'/\\s[a-t]{8}$/\'] + *:contains(/^[^\\"\\\'"]{30}quickly/)', + expected: '*:contains(/\\s[a-t]{8}$/) + *:contains(/^[^\\"\\\'"]{30}quickly/)', + }, + { + actual: '[-ext-matches-css-before=\'content: /^[A-Z][a-z]{2}\\s/ \']', + expected: ':matches-css(before,content: /^[A-Z][a-z]{2}\\s/ )', + }, + { + actual: '[-ext-has=\'+:matches-css-after( content : /(\\d+\\s)*me/ ):contains(/^(?![\\s\\S])/)\']', + expected: ':has(+:matches-css(after, content : /(\\d+\\s)*me/ ):contains(/^(?![\\s\\S])/))', + }, + { + /* eslint-disable max-len */ + actual: ':matches-css( background-image: /^url\\((.)[a-z]{4}:[a-z]{2}\\1nk\\)$/ ) + [-ext-matches-css-before=\'content: /^[A-Z][a-z]{2}\\s/ \'][-ext-has=\'+:matches-css-after( content : /(\\d+\\s)*me/ ):contains(/^(?![\\s\\S])/)\']', + expected: ':matches-css( background-image: /^url\\((.)[a-z]{4}:[a-z]{2}\\1nk\\)$/ ) + :matches-css(before,content: /^[A-Z][a-z]{2}\\s/ ):has(+:matches-css(after, content : /(\\d+\\s)*me/ ):contains(/^(?![\\s\\S])/))', + /* eslint-enable max-len */ + }, + ]; + test.each(testsInputs)('%s', ({ actual, expected }) => { + expect(convert(actual)).toEqual(expected); + }); + }); + + describe('invalid selectors', () => { + const error = 'Invalid extended-css old syntax selector'; + const invalidSelectors = [ + '[-ext-matches-css-before=\'content: /^[A-Z][a-z]', + '[-ext-has="a[data-item^=\'{sources:\']"', + ]; + test.each(invalidSelectors)('%s', (selector) => { + expect(() => { + convert(selector); + }).toThrow(error); + }); + }); +}); diff --git a/test/selector/parser.test.ts b/test/selector/parser.test.ts new file mode 100644 index 00000000..4d78e0e9 --- /dev/null +++ b/test/selector/parser.test.ts @@ -0,0 +1,2578 @@ +/** + * @jest-environment jsdom + */ + +import { + NodeType, + parse, +} from '../../src/selector'; + +import { + getRegularSelector, + getAstWithSingleRegularSelector, + getAbsoluteExtendedSelector, + getRelativeExtendedWithSingleRegular, + getSelectorAsRegular, + getSelectorListOfRegularSelectors, + getSingleSelectorAstWithAnyChildren, + expectSelectorListOfRegularSelectors, + expectSingleSelectorAstWithAnyChildren, + expectToThrowInput, +} from '../helpers/selector-parser'; + +describe('regular selectors', () => { + it('simple', () => { + const selector = 'div'; + const expectedAst = getAstWithSingleRegularSelector(selector); + expect(parse(selector)).toEqual(expectedAst); + }); + + describe('compound', () => { + const selectors = [ + 'div.banner', + '.banner.text', + 'div.ad > a.redirect + a', + 'div[style]', + 'div#top[onclick*="redirect"]', + 'div[data-comma="0,1"]', + 'input[data-comma=\'0,1\']', + 'div[class*=" "]', + ]; + test.each(selectors)('%s', (selector) => { + const expectedAst = getAstWithSingleRegularSelector(selector); + expect(parse(selector)).toEqual(expectedAst); + }); + }); + + describe('complex', () => { + const selectors = [ + 'div > span', + '.banner + div[style="clear:both;"]', + ]; + test.each(selectors)('%s', (selector) => { + const expectedAst = getAstWithSingleRegularSelector(selector); + expect(parse(selector)).toEqual(expectedAst); + }); + }); + + describe('selector list', () => { + const testsInputs = [ + { + actual: 'div, span', + expected: ['div', 'span'], + }, + { + actual: 'div,span', + expected: ['div', 'span'], + }, + { + actual: 'div , span', + expected: ['div', 'span'], + }, + { + actual: 'div.banner, span[ad], div > a > img', + expected: ['div.banner', 'span[ad]', 'div > a > img'], + }, + { + actual: 'p, :hover', + expected: ['p', '*:hover'], + }, + { + actual: 'p,:hover', + expected: ['p', '*:hover'], + }, + { + actual: '.banner, div[data-comma="0,1"]', + expected: ['.banner', 'div[data-comma="0,1"]'], + }, + ]; + test.each(testsInputs)('%s', ({ actual, expected }) => { + expect(parse(actual)).toEqual(getSelectorListOfRegularSelectors(expected)); + }); + }); + + describe('regular selector with pseudo-class', () => { + const wildcardSelectors = [ + ':lang(en)', + ':lang(ara\\b)', + ':lang(ara\\\\b)', + // should be parsed with no error as it is invalid for querySelectorAll + ':lang(c++)', + ]; + + const testsInputs = [ + { actual: 'div:hover', expected: 'div:hover' }, + { actual: '.post-content > p:empty::before', expected: '.post-content > p:empty::before' }, + { actual: '.block:nth-child(2) .inner', expected: '.block:nth-child(2) .inner' }, + { actual: '.block:nth-child(2) > .inner', expected: '.block:nth-child(2) > .inner' }, + ...wildcardSelectors.map((actual) => ({ actual, expected: `*${actual}` })), + ]; + test.each(testsInputs)('%s', ({ actual, expected }) => { + const expectedAst = getAstWithSingleRegularSelector(expected); + expect(parse(actual)).toEqual(expectedAst); + }); + }); + + describe('not a valid selector for querySelectorAll - should not fail while parsing', () => { + const invalidSelectors = [ + 'div >', + 'div:invalid-pseudo(1)', + ]; + test.each(invalidSelectors)('%s', (selector) => { + const expectedAst = getAstWithSingleRegularSelector(selector); + expect(parse(selector)).toEqual(expectedAst); + }); + }); +}); + +describe('absolute extended selectors', () => { + describe('contains pseudo-class', () => { + const name = 'contains'; + const testsInputs = [ + { + actual: 'span:contains(text)', + expected: [ + { isRegular: true, value: 'span' }, + { isAbsolute: true, name, value: 'text' }, + ], + }, + { + actual: 'span:contains("some text")', + expected: [ + { isRegular: true, value: 'span' }, + { isAbsolute: true, name, value: '"some text"' }, + ], + }, + { + actual: 'div[id] > .row > span:contains(/^Advertising$/)', + expected: [ + { isRegular: true, value: 'div[id] > .row > span' }, + { isAbsolute: true, name, value: '/^Advertising$/' }, + ], + }, + { + actual: 'div > :contains(test)', + expected: [ + { isRegular: true, value: 'div > *' }, + { isAbsolute: true, name, value: 'test' }, + ], + }, + { + actual: ':contains((test))', + expected: [ + { isRegular: true, value: '*' }, + { isAbsolute: true, name, value: '(test)' }, + ], + }, + { + actual: 'a[class*=blog]:contains(!)', + expected: [ + { isRegular: true, value: 'a[class*=blog]' }, + { isAbsolute: true, name, value: '!' }, + ], + }, + { + actual: ':contains(/[\\w]{9,}/)', + expected: [ + { isRegular: true, value: '*' }, + { isAbsolute: true, name, value: '/[\\w]{9,}/' }, + ], + }, + ]; + test.each(testsInputs)('%s', (input) => expectSingleSelectorAstWithAnyChildren(input)); + }); + + describe('matches-css pseudo-class', () => { + const name = 'matches-css'; + const testsInputs = [ + { + actual: '*:matches-css(width:400px)', + expected: [ + { isRegular: true, value: '*' }, + { isAbsolute: true, name, value: 'width:400px' }, + ], + }, + { + actual: 'div:matches-css(background-image: /^url\\("data:image\\/gif;base64.+/)', + expected: [ + { isRegular: true, value: 'div' }, + { isAbsolute: true, name, value: 'background-image: /^url\\("data:image\\/gif;base64.+/' }, + ], + }, + { + actual: 'div:matches-css(background-image: /^url\\([a-z]{4}:[a-z]{5}/)', + expected: [ + { isRegular: true, value: 'div' }, + { isAbsolute: true, name, value: 'background-image: /^url\\([a-z]{4}:[a-z]{5}/' }, + ], + }, + { + actual: ':matches-css( background-image: /v\\.ping\\.pl\\/MjAxOTA/ )', + expected: [ + { isRegular: true, value: '*' }, + { isAbsolute: true, name, value: ' background-image: /v\\.ping\\.pl\\/MjAxOTA/ ' }, + ], + }, + { + actual: ':matches-css( background-image: /^url\\((.)[a-z]{4}:[a-z]{2}\\1nk\\)$/ )', + expected: [ + { isRegular: true, value: '*' }, + { isAbsolute: true, name, value: ' background-image: /^url\\((.)[a-z]{4}:[a-z]{2}\\1nk\\)$/ ' }, // eslint-disable-line max-len + ], + }, + ]; + test.each(testsInputs)('%s', (input) => expectSingleSelectorAstWithAnyChildren(input)); + }); + + it('matches-attr pseudo-class', () => { + const actual = 'div:matches-attr("/data-v-/")'; + expectSingleSelectorAstWithAnyChildren({ + actual, + expected: [ + { isRegular: true, value: 'div' }, + { isAbsolute: true, name: 'matches-attr', value: '"/data-v-/"' }, + ], + }); + }); + + it('nth-ancestor pseudo-class', () => { + const actual = 'a:nth-ancestor(2)'; + expectSingleSelectorAstWithAnyChildren({ + actual, + expected: [ + { isRegular: true, value: 'a' }, + { isAbsolute: true, name: 'nth-ancestor', value: '2' }, + ], + }); + }); + + describe('xpath pseudo-class', () => { + const name = 'xpath'; + /* eslint-disable max-len */ + const testsInputs = [ + { + actual: 'div:xpath(//h3[contains(text(),"Share it!")]/..)', + expected: [ + { isRegular: true, value: 'div' }, + { isAbsolute: true, name, value: '//h3[contains(text(),"Share it!")]/..' }, + ], + }, + { + actual: '*:xpath(//h3[contains(text(),"Share it!")]/..)', + expected: [ + { isRegular: true, value: '*' }, + { isAbsolute: true, name, value: '//h3[contains(text(),"Share it!")]/..' }, + ], + }, + { + actual: '[data-src^="https://example.org/"]:xpath(..)', + expected: [ + { isRegular: true, value: '[data-src^="https://example.org/"]' }, + { isAbsolute: true, name, value: '..' }, + ], + }, + { + actual: ':xpath(//div[@data-st-area=\'Advert\'][count(*)=2][not(header)])', + expected: [ + { isRegular: true, value: 'body' }, + { isAbsolute: true, name, value: '//div[@data-st-area=\'Advert\'][count(*)=2][not(header)]' }, + ], + }, + { + actual: ":xpath(//article//div[count(div[*[*[*]]])=2][count(div[*[*[*]]][1]//img[starts-with(@src,'data:image/png;base64,')])>2][div[*[*[*]]][2][count(div[@class]/div[last()][count(div)=3])>=2]])", + expected: [ + { isRegular: true, value: 'body' }, + { isAbsolute: true, name, value: "//article//div[count(div[*[*[*]]])=2][count(div[*[*[*]]][1]//img[starts-with(@src,'data:image/png;base64,')])>2][div[*[*[*]]][2][count(div[@class]/div[last()][count(div)=3])>=2]]" }, + ], + }, + { + actual: ':xpath(//article/h1/following-sibling::p[1]/following-sibling::div[1]//div[1][@class][@id][not(ancestor::div[@id]/ancestor::article)])', + expected: [ + { isRegular: true, value: 'body' }, + { isAbsolute: true, name, value: '//article/h1/following-sibling::p[1]/following-sibling::div[1]//div[1][@class][@id][not(ancestor::div[@id]/ancestor::article)]' }, + ], + }, + { + actual: ':xpath(//article/h1/following-sibling::div[1]/following-sibling::div//div[count(*)>1][not(ancestor::div[count(*)>1]/ancestor::article)]/div[1])', + expected: [ + { isRegular: true, value: 'body' }, + { isAbsolute: true, name, value: '//article/h1/following-sibling::div[1]/following-sibling::div//div[count(*)>1][not(ancestor::div[count(*)>1]/ancestor::article)]/div[1]' }, + ], + }, + { + actual: ":xpath(//article/h1/following-sibling::div[1]/following-sibling::div//div[count(*)>1]//div[count(*)>1][not(ancestor::div[count(*)>1]/ancestor::div[count(*)>1]/ancestor::article)]/div[.//ul/li|.//a[contains(@href,'/w/%EB%B6%84%EB%A5%98:')]]/following-sibling::div[.//div[contains(concat(' ',normalize-space(@class),' '),' example-toc-ad ')]|.//div[contains(concat(' ',normalize-space(@class),' '),' wiki-paragraph ')]]/following-sibling::div[count(.//*[count(img[starts-with(@src,'//w.example.la/s/')]|img[starts-with(@src,'//ww.example.la/s/')]|img[starts-with(@src,'data:image/png;base64,')])>1])>1])", + expected: [ + { isRegular: true, value: 'body' }, + { isAbsolute: true, name, value: "//article/h1/following-sibling::div[1]/following-sibling::div//div[count(*)>1]//div[count(*)>1][not(ancestor::div[count(*)>1]/ancestor::div[count(*)>1]/ancestor::article)]/div[.//ul/li|.//a[contains(@href,'/w/%EB%B6%84%EB%A5%98:')]]/following-sibling::div[.//div[contains(concat(' ',normalize-space(@class),' '),' example-toc-ad ')]|.//div[contains(concat(' ',normalize-space(@class),' '),' wiki-paragraph ')]]/following-sibling::div[count(.//*[count(img[starts-with(@src,'//w.example.la/s/')]|img[starts-with(@src,'//ww.example.la/s/')]|img[starts-with(@src,'data:image/png;base64,')])>1])>1]" }, + ], + }, + { + actual: ":xpath(//div[@class='ytp-button ytp-paid-content-overlay-text'])", + expected: [ + { isRegular: true, value: 'body' }, + { isAbsolute: true, name, value: "//div[@class='ytp-button ytp-paid-content-overlay-text']" }, + ], + }, + { + actual: ':xpath(//div[@class="user-content"]/div[@class="snippet-clear"]/following-sibling::text()[contains(.,"Advertisement")])', + expected: [ + { isRegular: true, value: 'body' }, + { isAbsolute: true, name, value: '//div[@class="user-content"]/div[@class="snippet-clear"]/following-sibling::text()[contains(.,"Advertisement")]' }, + ], + }, + ]; + /* eslint-enable max-len */ + test.each(testsInputs)('%s', (input) => expectSingleSelectorAstWithAnyChildren(input)); + }); + + describe('upward extended pseudo-class', () => { + const name = 'upward'; + const testsInputs = [ + { + actual: 'a[class][redirect]:upward(3)', + expected: [ + { isRegular: true, value: 'a[class][redirect]' }, + { isAbsolute: true, name, value: '3' }, + ], + }, + { + actual: 'div.advert:upward(.info)', + expected: [ + { isRegular: true, value: 'div.advert' }, + { isAbsolute: true, name, value: '.info' }, + ], + }, + { + actual: 'img:upward(header ~ div[class])', + expected: [ + { isRegular: true, value: 'img' }, + { isAbsolute: true, name, value: 'header ~ div[class]' }, + ], + }, + { + actual: '.ad-title + .banner:upward([id][class])', + expected: [ + { isRegular: true, value: '.ad-title + .banner' }, + { isAbsolute: true, name, value: '[id][class]' }, + ], + }, + ]; + test.each(testsInputs)('%s', (input) => expectSingleSelectorAstWithAnyChildren(input)); + }); +}); + +describe('relative extended selectors', () => { + describe('has', () => { + const name = 'has'; + const testsInputs = [ + { + actual: 'div:has(span)', + expected: [ + { isRegular: true, value: 'div' }, + { isRelative: true, name, value: 'span' }, + ], + }, + { + actual: 'div.banner > div:has(> a[class^="ad"])', + expected: [ + { isRegular: true, value: 'div.banner > div' }, + { isRelative: true, name, value: '> a[class^="ad"]' }, + ], + }, + { + actual: '.banner:has(:scope > a[class^="ad"])', + // :scope inside :has should be handled by converter before tokenization + expected: [ + { isRegular: true, value: '.banner' }, + { isRelative: true, name, value: '> a[class^="ad"]' }, + ], + }, + ]; + test.each(testsInputs)('%s', (input) => expectSingleSelectorAstWithAnyChildren(input)); + + it('selector list as arg of has', () => { + const actual = '.banner > :has(span, p)'; + const expected = { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('.banner > *'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'has', + children: [ + getSelectorListOfRegularSelectors(['span', 'p']), + ], + }, + ], + }, + ], + }, + ], + }; + expect(parse(actual)).toEqual(expected); + }); + + it('selector list: has with selector list as arg + regular selector', () => { + const actual = '.banner > :has(span, p), a img.ad'; + const expected = { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('.banner > *'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'has', + children: [ + getSelectorListOfRegularSelectors(['span', 'p']), + ], + }, + ], + }, + ], + }, + { + type: NodeType.Selector, + children: [ + getRegularSelector('a img.ad'), + ], + }, + ], + }; + expect(parse(actual)).toEqual(expected); + }); + }); + + describe('if-not', () => { + const name = 'if-not'; + const testsInputs = [ + { + actual: 'div.banner:if-not(> span)', + expected: [ + { isRegular: true, value: 'div.banner' }, + { isRelative: true, name, value: '> span' }, + ], + }, + { + // eslint-disable-next-line max-len + actual: 'header[data-test-id="header"] ~ div[class]:last-child > div[class] > div[class]:if-not(a[data-test-id="logo-link"])', + expected: [ + { + isRegular: true, + value: 'header[data-test-id="header"] ~ div[class]:last-child > div[class] > div[class]', + }, + { isRelative: true, name, value: 'a[data-test-id="logo-link"]' }, + ], + }, + ]; + test.each(testsInputs)('%s', (input) => expectSingleSelectorAstWithAnyChildren(input)); + }); + + it('is', () => { + let actual; + let expected; + + actual = ':is(.header, .footer)'; + expected = { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('html *'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'is', + children: [ + getSelectorListOfRegularSelectors(['.header', '.footer']), + ], + }, + ], + }, + ], + }, + ], + }; + expect(parse(actual)).toEqual(expected); + + actual = '#__next > :is(.header, .footer)'; + expected = { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('#__next > *'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'is', + children: [getSelectorListOfRegularSelectors(['.header', '.footer'])], + }, + ], + }, + ], + }, + ], + }; + expect(parse(actual)).toEqual(expected); + + actual = 'h3 > :is(a[href$="/netflix-premium/"], a[href$="/spotify-premium/"], a[title="Disney Premium"])'; + expected = { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('h3 > *'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'is', + children: [ + getSelectorListOfRegularSelectors([ + 'a[href$="/netflix-premium/"]', + 'a[href$="/spotify-premium/"]', + 'a[title="Disney Premium"]', + ]), + ], + }, + ], + }, + ], + }, + ], + }; + expect(parse(actual)).toEqual(expected); + }); + + describe('not', () => { + const name = 'not'; + const testsInputs = [ + { + actual: '.banner:not(.header)', + expected: [ + { isRegular: true, value: '.banner' }, + { isRelative: true, name, value: '.header' }, + ], + }, + { + actual: 'div.banner > div:not(> a[class^="ad"])', + expected: [ + { isRegular: true, value: 'div.banner > div' }, + { isRelative: true, name, value: '> a[class^="ad"]' }, + ], + }, + ]; + test.each(testsInputs)('%s', (input) => expectSingleSelectorAstWithAnyChildren(input)); + + it('selector list as arg of not', () => { + let actual; + let expected; + + actual = '.banner > :not(span, p)'; + expected = { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('.banner > *'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'not', + children: [getSelectorListOfRegularSelectors(['span', 'p'])], + }, + ], + }, + ], + }, + ], + }; + expect(parse(actual)).toEqual(expected); + + actual = '#child *:not(a, span)'; + expected = { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('#child *'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'not', + children: [getSelectorListOfRegularSelectors(['a', 'span'])], + }, + ], + }, + ], + }, + ], + }; + expect(parse(actual)).toEqual(expected); + }); + }); +}); + +describe('old syntax', () => { + const testsInputs = [ + { + actual: 'div[-ext-has=".banner"]', + expected: [ + { isRegular: true, value: 'div' }, + { isRelative: true, name: 'has', value: '.banner' }, + ], + }, + { + actual: '[-ext-has="div.advert"]', + expected: [ + { isRegular: true, value: '*' }, + { isRelative: true, name: 'has', value: 'div.advert' }, + ], + }, + { + actual: '.block[-ext-has=\'a[href^="https://example.net/"]\']', + expected: [ + { isRegular: true, value: '.block' }, + { isRelative: true, name: 'has', value: 'a[href^="https://example.net/"]' }, + ], + }, + { + actual: 'div[style*="z-index:"][-ext-has=\'>div[id$="_content"]>iframe#overlay_iframe\']', + expected: [ + { isRegular: true, value: 'div[style*="z-index:"]' }, + { isRelative: true, name: 'has', value: '>div[id$="_content"]>iframe#overlay_iframe' }, + ], + }, + { + actual: 'div a[-ext-contains="text"]', + expected: [ + { isRegular: true, value: 'div a' }, + { isAbsolute: true, name: 'contains', value: 'text' }, + ], + }, + { + actual: 'a[-ext-contains=""extra-quotes""]', + expected: [ + { isRegular: true, value: 'a' }, + { isAbsolute: true, name: 'contains', value: '"extra-quotes"' }, + ], + }, + { + actual: '#test-matches-css div[-ext-matches-css="background-image: url(data:*)"]', + expected: [ + { isRegular: true, value: '#test-matches-css div' }, + { isAbsolute: true, name: 'matches-css', value: 'background-image: url(data:*)' }, + ], + }, + { + actual: '#test-matches-css div[-ext-matches-css-before="content: *find me*"]', + expected: [ + { isRegular: true, value: '#test-matches-css div' }, + { isAbsolute: true, name: 'matches-css', value: 'before,content: *find me*' }, + ], + }, + { + actual: '[-ext-matches-css-before=\'content: /^[A-Z][a-z]{2}\\s/ \']', + expected: [ + { isRegular: true, value: '*' }, + { isAbsolute: true, name: 'matches-css', value: 'before,content: /^[A-Z][a-z]{2}\\s/ ' }, + ], + }, + { + actual: 'div[style="text-align: center"] > b[-ext-contains="Ads:"]+a[href^="http://example.com/test.html?id="]+br', // eslint-disable-line max-len + expected: [ + { isRegular: true, value: 'div[style="text-align: center"] > b' }, + { isAbsolute: true, name: 'contains', value: 'Ads:' }, + { isRegular: true, value: '+a[href^="http://example.com/test.html?id="]+br' }, + ], + }, + { + actual: 'div[-ext-contains="test"][-ext-has="div.test-class-two"]', + expected: [ + { isRegular: true, value: 'div' }, + { isAbsolute: true, name: 'contains', value: 'test' }, + { isRelative: true, name: 'has', value: 'div.test-class-two' }, + ], + }, + { + actual: 'div[i18n][-ext-contains="test"][-ext-has="div.test-class-two"]', + expected: [ + { isRegular: true, value: 'div[i18n]' }, + { isAbsolute: true, name: 'contains', value: 'test' }, + { isRelative: true, name: 'has', value: 'div.test-class-two' }, + ], + }, + { + actual: 'div[-ext-has="div.test-class-two"] > .test-class[-ext-contains="test"]', + expected: [ + { isRegular: true, value: 'div' }, + { isRelative: true, name: 'has', value: 'div.test-class-two' }, + { isRegular: true, value: '> .test-class' }, + { isAbsolute: true, name: 'contains', value: 'test' }, + ], + }, + { + actual: '*[-ext-contains=\'/\\s[a-t]{8}$/\'] + *:contains(/^[^\\"\\\'"]{30}quickly/)', + expected: [ + { isRegular: true, value: '*' }, + { isAbsolute: true, name: 'contains', value: '/\\s[a-t]{8}$/' }, + { isRegular: true, value: '+ *' }, + { isAbsolute: true, name: 'contains', value: '/^[^\\"\\\'"]{30}quickly/' }, + ], + }, + ]; + test.each(testsInputs)('%s', (input) => expectSingleSelectorAstWithAnyChildren(input)); + + it('old syntax - has(> contains)', () => { + let actual; + let expected; + + actual = '.sidebar > h3[-ext-has="a:contains(Recommended)"]'; + expected = { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('.sidebar > h3'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'has', + children: [ + getSingleSelectorAstWithAnyChildren([ + { isRegular: true, value: 'a' }, + { isAbsolute: true, name: 'contains', value: 'Recommended' }, + ]), + ], + }, + ], + }, + ], + }, + ], + }; + expect(parse(actual)).toEqual(expected); + + actual = '#sidebar div[class^="text-"][-ext-has=">.box-inner>h2:contains(ads)"]'; + expected = { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('#sidebar div[class^="text-"]'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'has', + children: [ + getSingleSelectorAstWithAnyChildren([ + { isRegular: true, value: '>.box-inner>h2' }, + { isAbsolute: true, name: 'contains', value: 'ads' }, + ]), + ], + }, + ], + }, + ], + }, + ], + }; + expect(parse(actual)).toEqual(expected); + }); + + /* eslint-disable max-len */ + it('old syntax - matches-css + matches-css-before has(matches-css-after contains)', () => { + const actual = ':matches-css( background-image: /^url\\((.)[a-z]{4}:[a-z]{2}\\1nk\\)$/ ) + [-ext-matches-css-before=\'content: /^[A-Z][a-z]{2}\\s/ \'][-ext-has=\'+:matches-css-after( content : /(\\d+\\s)*me/ ):contains(/^(?![\\s\\S])/)\']'; + const expected = { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('*'), + getAbsoluteExtendedSelector('matches-css', ' background-image: /^url\\((.)[a-z]{4}:[a-z]{2}\\1nk\\)$/ '), + getRegularSelector('+ *'), + getAbsoluteExtendedSelector('matches-css', 'before,content: /^[A-Z][a-z]{2}\\s/ '), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'has', + children: [ + getSingleSelectorAstWithAnyChildren([ + { isRegular: true, value: '+*' }, + { isAbsolute: true, name: 'matches-css', value: 'after, content : /(\\d+\\s)*me/ ' }, + { isAbsolute: true, name: 'contains', value: '/^(?![\\s\\S])/' }, + ]), + ], + }, + ], + }, + ], + }, + ], + }; + expect(parse(actual)).toEqual(expected); + }); + /* eslint-enable max-len */ +}); + +describe('combined extended selectors', () => { + it('has contains', () => { + const actual = 'div:has(span):contains(something)'; + const expected = [ + { isRegular: true, value: 'div' }, + { isRelative: true, name: 'has', value: 'span' }, + { isAbsolute: true, name: 'contains', value: 'something' }, + ]; + expectSingleSelectorAstWithAnyChildren({ actual, expected }); + }); + + it('has(> contains)', () => { + const actual = 'div:has(> p:contains(test))'; + const expected = { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('div'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'has', + children: [ + getSingleSelectorAstWithAnyChildren([ + { isRegular: true, value: '> p' }, + { isAbsolute: true, name: 'contains', value: 'test' }, + ]), + ], + }, + ], + }, + ], + }, + ], + }; + expect(parse(actual)).toEqual(expected); + }); + + it('has(contains)', () => { + const actual = 'div:has(:contains(text))'; + const expected = { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('div'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'has', + children: [ + getSingleSelectorAstWithAnyChildren([ + { isRegular: true, value: '*' }, + { isAbsolute: true, name: 'contains', value: 'text' }, + ]), + ], + }, + ], + }, + ], + }, + ], + }; + expect(parse(actual)).toEqual(expected); + }); + + it('has(has)', () => { + const actual = 'div:has(.banner:has(> a > img))'; + const expected = { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('div'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'has', + children: [ + { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('.banner'), + getRelativeExtendedWithSingleRegular('has', '> a > img'), + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }; + expect(parse(actual)).toEqual(expected); + }); + + it('has(has(contains))', () => { + const actual = 'div:has(.banner:has(> span:contains(inner text)))'; + const expected = { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('div'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'has', + children: [ + { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('.banner'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'has', + children: [ + getSingleSelectorAstWithAnyChildren([ + { isRegular: true, value: '> span' }, + { isAbsolute: true, name: 'contains', value: 'inner text' }, // eslint-disable-line max-len + ]), + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }; + expect(parse(actual)).toEqual(expected); + }); + + it('is(selector list) contains', () => { + const actual = '#__next > :is(.header, .footer):contains(ads)'; + const expected = { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('#__next > *'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'is', + children: [getSelectorListOfRegularSelectors(['.header', '.footer'])], + }, + ], + }, + getAbsoluteExtendedSelector('contains', 'ads'), + ], + }, + ], + }; + expect(parse(actual)).toEqual(expected); + }); + + it('is(has, contains)', () => { + const actual = '#__next > :is(.banner:has(> img), .block:contains(Share))'; + const expected = { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('#__next > *'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'is', + children: [ + { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('.banner'), + getRelativeExtendedWithSingleRegular('has', '> img'), + ], + }, + { + type: NodeType.Selector, + children: [ + getRegularSelector('.block'), + getAbsoluteExtendedSelector('contains', 'Share'), + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }; + expect(parse(actual)).toEqual(expected); + }); + + it('selector list with extra space - has , regular selector', () => { + const actual = '.block:has(> img) , .banner)'; + const expected = { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('.block'), + getRelativeExtendedWithSingleRegular('has', '> img'), + ], + }, + getSelectorAsRegular('.banner'), + ], + }; + expect(parse(actual)).toEqual(expected); + }); + + it('has(matches-css-before)', () => { + // eslint-disable-next-line max-len + const actual = 'body.zen .zen-lib div.feed__item:has(> div > div > div[class*="__label"] > span:matches-css-before(content:*Яндекс.Директ))'; + const expected = { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('body.zen .zen-lib div.feed__item'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'has', + children: [ + getSingleSelectorAstWithAnyChildren([ + { isRegular: true, value: '> div > div > div[class*="__label"] > span' }, + { isAbsolute: true, name: 'matches-css', value: 'before,content:*Яндекс.Директ' }, // eslint-disable-line max-len + ], + )], + }, + ], + }, + ], + }, + ], + }; + expect(parse(actual)).toEqual(expected); + }); + + it('upward not', () => { + const actual = 'div[style="width:640px;height:360px"][id="video-player"]:upward(div):not([class])'; + const expected = [ + { isRegular: true, value: 'div[style="width:640px;height:360px"][id="video-player"]' }, + { isAbsolute: true, name: 'upward', value: 'div' }, + { isRelative: true, name: 'not', value: '[class]' }, + ]; + expectSingleSelectorAstWithAnyChildren({ actual, expected }); + }); + + it('upward not not', () => { + const actual = 'a[href^="https://example."]:upward(1):not(section):not(div[class^="article"])'; + const expected = [ + { isRegular: true, value: 'a[href^="https://example."]' }, + { isAbsolute: true, name: 'upward', value: '1' }, + { isRelative: true, name: 'not', value: 'section' }, + { isRelative: true, name: 'not', value: 'div[class^="article"]' }, + ]; + expectSingleSelectorAstWithAnyChildren({ actual, expected }); + }); + + it('upward(not)', () => { + const actual = '.SocialMediaShareButton:upward(div:not([class]))'; + const expected = [ + { isRegular: true, value: '.SocialMediaShareButton' }, + { isAbsolute: true, name: 'upward', value: 'div:not([class])' }, + ]; + expectSingleSelectorAstWithAnyChildren({ actual, expected }); + }); + + it('contains upward', () => { + const actual = 'div > p:contains(PR):upward(2)'; + const expected = [ + { isRegular: true, value: 'div > p' }, + { isAbsolute: true, name: 'contains', value: 'PR' }, + { isAbsolute: true, name: 'upward', value: '2' }, + ]; + expectSingleSelectorAstWithAnyChildren({ actual, expected }); + }); + + it('upward matches-css', () => { + const actual = '[data-ad-subtype]:upward(1):matches-css(min-height:/[0-9]+/)'; + const expected = [ + { isRegular: true, value: '[data-ad-subtype]' }, + { isAbsolute: true, name: 'upward', value: '1' }, + { isAbsolute: true, name: 'matches-css', value: 'min-height:/[0-9]+/' }, + ]; + expectSingleSelectorAstWithAnyChildren({ actual, expected }); + }); + + it('has(not)', () => { + const selector = 'div:has(:not(span))'; + const expected = { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('div'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'has', + children: [ + getSingleSelectorAstWithAnyChildren([ + { isRegular: true, value: '*' }, + { isRelative: true, name: 'not', value: 'span' }, + ]), + ], + }, + ], + }, + ], + }, + ], + }; + expect(parse(selector)).toEqual(expected); + }); + + it('not(contains)', () => { + const selector = 'p:not(:contains(text))'; + const expected = { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('p'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'not', + children: [ + getSingleSelectorAstWithAnyChildren([ + { isRegular: true, value: '*' }, + { isAbsolute: true, name: 'contains', value: 'text' }, + ]), + ], + }, + ], + }, + ], + }, + ], + }; + expect(parse(selector)).toEqual(expected); + }); + + it('not(has)', () => { + const selector = 'div:not(:has(span))'; + const expected = { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('div'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'not', + children: [ + getSingleSelectorAstWithAnyChildren([ + { isRegular: true, value: '*' }, + { isRelative: true, name: 'has', value: 'span' }, + ]), + ], + }, + ], + }, + ], + }, + ], + }; + expect(parse(selector)).toEqual(expected); + }); + + it('not(has(not))', () => { + const actual = 'div:not(:has(:not(img)))'; + const expected = { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('div'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'not', + children: [ + { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('*'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'has', + children: [ + { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('*'), + getRelativeExtendedWithSingleRegular('not', 'img'), // eslint-disable-line max-len + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }; + expect(parse(actual)).toEqual(expected); + }); + + it('two not with simple selector next to each other', () => { + const actual = ':not(span):not(p)'; + const expected = [ + { isRegular: true, value: 'html *' }, + { isRelative: true, name: 'not', value: 'span' }, + { isRelative: true, name: 'not', value: 'p' }, + ]; + expectSingleSelectorAstWithAnyChildren({ actual, expected }); + }); +}); + +describe('combined selectors', () => { + describe('complex selector with extended pseudo-class inside', () => { + const testsInputs = [ + { + actual: 'div:upward(3).banner', + expected: [ + { isRegular: true, value: 'div.banner' }, + { isAbsolute: true, name: 'upward', value: '3' }, + ], + }, + { + actual: '.test:upward(3).banner', + expected: [ + { isRegular: true, value: '.test.banner' }, + { isAbsolute: true, name: 'upward', value: '3' }, + ], + }, + { + actual: '.test:upward(3)#id', + expected: [ + { isRegular: true, value: '.test#id' }, + { isAbsolute: true, name: 'upward', value: '3' }, + ], + }, + { + actual: '.test:upward(3)[attr]', + expected: [ + { isRegular: true, value: '.test[attr]' }, + { isAbsolute: true, name: 'upward', value: '3' }, + ], + }, + { + actual: 'div:upward(3).class#id[attr]', + expected: [ + { isRegular: true, value: 'div.class#id[attr]' }, + { isAbsolute: true, name: 'upward', value: '3' }, + ], + }, + { + actual: '.banner:has(img).inner', + expected: [ + { isRegular: true, value: '.banner.inner' }, + { isRelative: true, name: 'has', value: 'img' }, + ], + }, + { + actual: 'div:has(.test).class#id[attr]', + expected: [ + { isRegular: true, value: 'div.class#id[attr]' }, + { isRelative: true, name: 'has', value: '.test' }, + ], + }, + { + actual: 'div[attr].class:has(.inner)#id', + expected: [ + { isRegular: true, value: 'div[attr].class#id' }, + { isRelative: true, name: 'has', value: '.inner' }, + ], + }, + { + actual: '.test:upward(3).banner:matches-css(z-index: 10)', + expected: [ + { isRegular: true, value: '.test.banner' }, + { isAbsolute: true, name: 'upward', value: '3' }, + { isAbsolute: true, name: 'matches-css', value: 'z-index: 10' }, + ], + }, + { + actual: '.test:upward(3).banner:matches-css(z-index: 10)[attr=true]', + expected: [ + { isRegular: true, value: '.test.banner[attr=true]' }, + { isAbsolute: true, name: 'upward', value: '3' }, + { isAbsolute: true, name: 'matches-css', value: 'z-index: 10' }, + ], + }, + { + actual: '.test:upward(#top > .block)[attr]', + expected: [ + { isRegular: true, value: '.test[attr]' }, + { isAbsolute: true, name: 'upward', value: '#top > .block' }, + ], + }, + { + actual: 'div:contains(/а/):nth-child(100n + 2)', + expected: [ + { isRegular: true, value: 'div:nth-child(100n + 2)' }, + { isAbsolute: true, name: 'contains', value: '/а/' }, + ], + }, + ]; + test.each(testsInputs)('%s', (input) => expectSingleSelectorAstWithAnyChildren(input)); + }); + + describe('selectors with standard pseudos', () => { + it(':not::selection', () => { + const actual = '*:not(input)::selection'; + const expected = [ + { isRegular: true, value: 'html *::selection' }, + { isRelative: true, name: 'not', value: 'input' }, + ]; + expectSingleSelectorAstWithAnyChildren({ actual, expected }); + }); + + it(':not():not()::selection', () => { + const actual = 'html > body *:not(input):not(textarea)::selection'; + const expected = [ + { isRegular: true, value: 'html > body *::selection' }, + { isRelative: true, name: 'not', value: 'input' }, + { isRelative: true, name: 'not', value: 'textarea' }, + ]; + expectSingleSelectorAstWithAnyChildren({ actual, expected }); + }); + + it(':matches-css():checked', () => { + const actual = 'input:matches-css(padding: 10):checked'; + const expected = [ + { isRegular: true, value: 'input:checked' }, + { isAbsolute: true, name: 'matches-css', value: 'padding: 10' }, + ]; + expectSingleSelectorAstWithAnyChildren({ actual, expected }); + }); + + it(':not():has(:only-child)', () => { + // eslint-disable-next-line max-len + const actual = '#snippet-list-posts > .item:not([id]):has(> .box-responsive:only-child > div[id]:only-child)'; + const expected = [ + { isRegular: true, value: '#snippet-list-posts > .item' }, + { isRelative: true, name: 'not', value: '[id]' }, + { isRelative: true, name: 'has', value: '> .box-responsive:only-child > div[id]:only-child' }, + ]; + expectSingleSelectorAstWithAnyChildren({ actual, expected }); + }); + + it(':last-child:has()', () => { + const actual = '#__next > div:last-child:has(button.privacy-policy__btn)'; + const expected = [ + { isRegular: true, value: '#__next > div:last-child' }, + { isRelative: true, name: 'has', value: 'button.privacy-policy__btn' }, + ]; + expectSingleSelectorAstWithAnyChildren({ actual, expected }); + }); + + it(':not(:nth-child())', () => { + const actual = '.yellow:not(:nth-child(3))'; + const expected = [ + { isRegular: true, value: '.yellow' }, + { isRelative: true, name: 'not', value: '*:nth-child(3)' }, + ]; + expectSingleSelectorAstWithAnyChildren({ actual, expected }); + }); + + it(':nth-child():has()', () => { + const actual = '.entry_text:nth-child(2):has(> #ninja-blog-inactive)'; + const expected = [ + { isRegular: true, value: '.entry_text:nth-child(2)' }, + { isRelative: true, name: 'has', value: '> #ninja-blog-inactive' }, + ]; + expectSingleSelectorAstWithAnyChildren({ actual, expected }); + }); + + it('two not with standard pseudo next to each other', () => { + const actual = ':not(:empty):not(:hover)'; + const expected = [ + { isRegular: true, value: 'html *' }, + { isRelative: true, name: 'not', value: '*:empty' }, + { isRelative: true, name: 'not', value: '*:hover' }, + ]; + expectSingleSelectorAstWithAnyChildren({ actual, expected }); + }); + }); + + it('selector list with regular "any" and extended :contains', () => { + const actual = '.banner, :contains(#ad)'; + const expected = { + type: NodeType.SelectorList, + children: [ + getSelectorAsRegular('.banner'), + { + type: NodeType.Selector, + children: [ + getRegularSelector('*'), + getAbsoluteExtendedSelector('contains', '#ad'), + ], + }, + ], + }; + expect(parse(actual)).toEqual(expected); + }); + + it('has(+*:matches-css-after)', () => { + const actual = ':has(+:matches-css-after( content : /(\\d+\\s)*me/ ))'; + const expected = { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('*'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'has', + children: [ + getSingleSelectorAstWithAnyChildren([ + { isRegular: true, value: '+*' }, + { isAbsolute: true, name: 'matches-css', value: 'after, content : /(\\d+\\s)*me/ ' }, // eslint-disable-line max-len + ]), + ], + }, + ], + }, + ], + }, + ], + }; + expect(parse(actual)).toEqual(expected); + }); + + it('has with selector list - regular and extended', () => { + const actual = 'div:has(.banner, :contains(!))'; + const expected = { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('div'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'has', + children: [ + { + type: NodeType.SelectorList, + children: [ + getSelectorAsRegular('.banner'), + { + type: NodeType.Selector, + children: [ + getRegularSelector('*'), + getAbsoluteExtendedSelector('contains', '!'), + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }; + expect(parse(actual)).toEqual(expected); + }); + + it('not has with selector list - regular and extended', () => { + const actual = 'a[class]:not(:has(*, :contains(*)))'; + const expected = { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('a[class]'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'not', + children: [ + { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('*'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'has', + children: [ + { + type: NodeType.SelectorList, + children: [ + getSelectorAsRegular('*'), + { + type: NodeType.Selector, + children: [ + getRegularSelector('*'), + getAbsoluteExtendedSelector('contains', '*'), // eslint-disable-line max-len + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }; + expect(parse(actual)).toEqual(expected); + }); + + it('selector list with combined-extended and simple-extended selectors', () => { + const selector = 'div:has(.banner, :contains(!)), p:contains(text)'; + const expected = { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('div'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'has', + children: [ + { + type: NodeType.SelectorList, + children: [ + getSelectorAsRegular('.banner'), + { + type: NodeType.Selector, + children: [ + getRegularSelector('*'), + getAbsoluteExtendedSelector('contains', '!'), + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + { + type: NodeType.Selector, + children: [ + getRegularSelector('p'), + getAbsoluteExtendedSelector('contains', 'text'), + ], + }, + ], + }; + expect(parse(selector)).toEqual(expected); + }); + + it('super stressor', () => { + // eslint-disable-next-line max-len + const selector = 'a[class*=blog]:not(:has(*, :contains(!)), :contains(!)), br:contains(]), p:contains(]), :not(:empty):not(:parent)'; + const expected = { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('a[class*=blog]'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'not', + children: [ + { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('*'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'has', + children: [ + { + type: NodeType.SelectorList, + children: [ + getSelectorAsRegular('*'), + { + type: NodeType.Selector, + children: [ + getRegularSelector('*'), + getAbsoluteExtendedSelector('contains', '!'), // eslint-disable-line max-len + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + { + type: NodeType.Selector, + children: [ + getRegularSelector('*'), + getAbsoluteExtendedSelector('contains', '!'), + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + { + type: NodeType.Selector, + children: [ + getRegularSelector('br'), + getAbsoluteExtendedSelector('contains', ']'), + ], + }, + { + type: NodeType.Selector, + children: [ + getRegularSelector('p'), + getAbsoluteExtendedSelector('contains', ']'), + ], + }, + { + type: NodeType.Selector, + children: [ + getRegularSelector('*'), + getRelativeExtendedWithSingleRegular('not', '*:empty'), + getRelativeExtendedWithSingleRegular('not', '*:parent'), + ], + }, + ], + }; + expect(parse(selector)).toEqual(expected); + }); + + describe('has pseudo-class limitation', () => { + const toThrowInputs = [ + // no :has inside regular pseudos + { + selector: '::slotted(:has(.a))', + error: 'Usage of :has() pseudo-class is not allowed inside regular pseudo', + }, + // no :has after pseudo-elements + { + selector: '::part(foo):has(.a)', + error: 'Usage of :has() pseudo-class is not allowed after any regular pseudo-element', + }, + ]; + test.each(toThrowInputs)('%s', (input) => expectToThrowInput(input)); + }); + + describe('regular selector AFTER extended absolute selector', () => { + const testsInputs = [ + { + actual: '.test:upward(3) .banner', + expected: [ + { isRegular: true, value: '.test' }, + { isAbsolute: true, name: 'upward', value: '3' }, + { isRegular: true, value: '.banner' }, + ], + }, + { + actual: '.test:nth-ancestor(1) > .banner', + expected: [ + { isRegular: true, value: '.test' }, + { isAbsolute: true, name: 'nth-ancestor', value: '1' }, + { isRegular: true, value: '> .banner' }, + ], + }, + { + actual: '.test:upward(3) > .banner > *', + expected: [ + { isRegular: true, value: '.test' }, + { isAbsolute: true, name: 'upward', value: '3' }, + { isRegular: true, value: '> .banner > *' }, + ], + }, + { + actual: '.test:upward(#top .block) .banner', + expected: [ + { isRegular: true, value: '.test' }, + { isAbsolute: true, name: 'upward', value: '#top .block' }, + { isRegular: true, value: '.banner' }, + ], + }, + { + actual: '.test:upward(#top > .block) .banner', + expected: [ + { isRegular: true, value: '.test' }, + { isAbsolute: true, name: 'upward', value: '#top > .block' }, + { isRegular: true, value: '.banner' }, + ], + }, + { + actual: '.test:nth-ancestor(1)> .banner', + expected: [ + { isRegular: true, value: '.test' }, + { isAbsolute: true, name: 'nth-ancestor', value: '1' }, + { isRegular: true, value: '> .banner' }, + ], + }, + { + actual: '#main .target:nth-ancestor(2)+ .banner', + expected: [ + { isRegular: true, value: '#main .target' }, + { isAbsolute: true, name: 'nth-ancestor', value: '2' }, + { isRegular: true, value: '+ .banner' }, + ], + }, + ]; + test.each(testsInputs)('%s', (input) => expectSingleSelectorAstWithAnyChildren(input)); + }); + + describe('regular selector AFTER extended relative selector', () => { + const testsInputs = [ + { + actual: '.banner:has(img) .inner', + expected: [ + { isRegular: true, value: '.banner' }, + { isRelative: true, name: 'has', value: 'img' }, + { isRegular: true, value: '.inner' }, + ], + }, + { + actual: ':not([class^="Navigation__subscribe"]) > div[data-cm-unit="show-failsafe"]', + expected: [ + { isRegular: true, value: 'html *' }, + { isRelative: true, name: 'not', value: '[class^="Navigation__subscribe"]' }, + { isRegular: true, value: '> div[data-cm-unit="show-failsafe"]' }, + ], + }, + { + actual: '.header > header + [id*="-"]:not([class]) > a[href*="adblock"]', + expected: [ + { isRegular: true, value: '.header > header + [id*="-"]' }, + { isRelative: true, name: 'not', value: '[class]' }, + { isRegular: true, value: '> a[href*="adblock"]' }, + ], + }, + { + actual: 'html:has(> body) > body.no_scroll', + expected: [ + { isRegular: true, value: 'html' }, + { isRelative: true, name: 'has', value: '> body' }, + { isRegular: true, value: '> body.no_scroll' }, + ], + }, + { + actual: 'td[align="left"]:not([width]) > a > img', + expected: [ + { isRegular: true, value: 'td[align="left"]' }, + { isRelative: true, name: 'not', value: '[width]' }, + { isRegular: true, value: '> a > img' }, + ], + }, + { + actual: 'div:has(> #banner)> .inner', + expected: [ + { isRegular: true, value: 'div' }, + { isRelative: true, name: 'has', value: '> #banner' }, + { isRegular: true, value: '> .inner' }, + ], + }, + { + actual: 'td[align="left"]:not([width])+ a > img', + expected: [ + { isRegular: true, value: 'td[align="left"]' }, + { isRelative: true, name: 'not', value: '[width]' }, + { isRegular: true, value: '+ a > img' }, + ], + }, + ]; + test.each(testsInputs)('%s', (input) => expectSingleSelectorAstWithAnyChildren(input)); + }); + + describe('combined after combined with any combinator between', () => { + const testsInputs = [ + { + actual: 'div:contains(base) + .paragraph:contains(text)', + expected: [ + { isRegular: true, value: 'div' }, + { isAbsolute: true, name: 'contains', value: 'base' }, + { isRegular: true, value: '+ .paragraph' }, + { isAbsolute: true, name: 'contains', value: 'text' }, + ], + }, + { + actual: '#root > div:not([class]) > div:contains(PRIVACY)', + expected: [ + { isRegular: true, value: '#root > div' }, + { isRelative: true, name: 'not', value: '[class]' }, + { isRegular: true, value: '> div' }, + { isAbsolute: true, name: 'contains', value: 'PRIVACY' }, + ], + }, + { + // eslint-disable-next-line max-len + actual: '#app > div[class]:matches-attr("/data-v-/") > div > div:has(> a[href="https://example.com"])', + expected: [ + { isRegular: true, value: '#app > div[class]' }, + { isAbsolute: true, name: 'matches-attr', value: '"/data-v-/"' }, + { isRegular: true, value: '> div > div' }, + { isRelative: true, name: 'has', value: '> a[href="https://example.com"]' }, + ], + }, + { + // eslint-disable-next-line max-len + actual: 'tr[data-id]:not([class]):not([id]) > td[class] > div[class*=" "]:has(> div[class*=" "] > iframe[src^="https://example.org/"])', + expected: [ + { isRegular: true, value: 'tr[data-id]' }, + { isRelative: true, name: 'not', value: '[class]' }, + { isRelative: true, name: 'not', value: '[id]' }, + { isRegular: true, value: '> td[class] > div[class*=" "]' }, + { isRelative: true, name: 'has', value: '> div[class*=" "] > iframe[src^="https://example.org/"]' }, + ], + }, + { + // eslint-disable-next-line max-len + actual: 'div:not([style^="min-height:"]) > div[id][data-id^="toolkit-"]:not([data-bem]):not([data-m]):has(a[href^="https://example."]>img)', + expected: [ + { isRegular: true, value: 'div' }, + { isRelative: true, name: 'not', value: '[style^="min-height:"]' }, + { isRegular: true, value: '> div[id][data-id^="toolkit-"]' }, + { isRelative: true, name: 'not', value: '[data-bem]' }, + { isRelative: true, name: 'not', value: '[data-m]' }, + { isRelative: true, name: 'has', value: 'a[href^="https://example."]>img' }, + ], + }, + { + // eslint-disable-next-line max-len + actual: '#root > div:not([class])> div[class] > div[class] > span[class] + a[href="https://example.org/test"]:upward(2)', + expected: [ + { isRegular: true, value: '#root > div' }, + { isRelative: true, name: 'not', value: '[class]' }, + { isRegular: true, value: '> div[class] > div[class] > span[class] + a[href="https://example.org/test"]' }, // eslint-disable-line max-len + { isAbsolute: true, name: 'upward', value: '2' }, + ], + }, + { + actual: 'body > div[id="root"] ~ script +div:not([class]):not([id]) > div[class*=" "]', + expected: [ + { isRegular: true, value: 'body > div[id="root"] ~ script +div' }, + { isRelative: true, name: 'not', value: '[class]' }, + { isRelative: true, name: 'not', value: '[id]' }, + { isRegular: true, value: '> div[class*=" "]' }, + ], + }, + { + // eslint-disable-next-line max-len + actual: '#peek > div:not([class]):not([id]) > div[data-root][style*="position: fixed; bottom: 0px; z-index: 1000; display: flex; min-width: 50%; margin: 8px;"]:not([class]):not([id])', + expected: [ + { isRegular: true, value: '#peek > div' }, + { isRelative: true, name: 'not', value: '[class]' }, + { isRelative: true, name: 'not', value: '[id]' }, + // eslint-disable-next-line max-len + { isRegular: true, value: '> div[data-root][style*="position: fixed; bottom: 0px; z-index: 1000; display: flex; min-width: 50%; margin: 8px;"]' }, + { isRelative: true, name: 'not', value: '[class]' }, + { isRelative: true, name: 'not', value: '[id]' }, + ], + }, + { + // eslint-disable-next-line max-len + actual: '#content-container > div[class] > div[class]:matches-css(z-index: 10) > div[class] > div[class] > h4:contains(cookies):upward(4)', + expected: [ + { isRegular: true, value: '#content-container > div[class] > div[class]' }, + { isAbsolute: true, name: 'matches-css', value: 'z-index: 10' }, + { isRegular: true, value: '> div[class] > div[class] > h4' }, + { isAbsolute: true, name: 'contains', value: 'cookies' }, + { isAbsolute: true, name: 'upward', value: '4' }, + ], + }, + { + // eslint-disable-next-line max-len + actual: '.content > .block.rel ~ div[class*=" "]:not(.clear) > a[href="javascript:void(0)"]:only-child:contains(/^Open app$/):upward(1)', + expected: [ + { isRegular: true, value: '.content > .block.rel ~ div[class*=" "]' }, + { isRelative: true, name: 'not', value: '.clear' }, + { isRegular: true, value: '> a[href="javascript:void(0)"]:only-child' }, + { isAbsolute: true, name: 'contains', value: '/^Open app$/' }, + { isAbsolute: true, name: 'upward', value: '1' }, + ], + }, + { + actual: 'article>div[id]:not([class])~div[class]:not([id])', + expected: [ + { isRegular: true, value: 'article>div[id]' }, + { isRelative: true, name: 'not', value: '[class]' }, + { isRegular: true, value: '~div[class]' }, + { isRelative: true, name: 'not', value: '[id]' }, + ], + }, + { + // eslint-disable-next-line max-len + actual: 'body > script + div:empty:not([class]):not([id]) ~ div:empty:not([class]):not([id]) + div:empty:not([class]):not([id]) + [id]:not([class])', + expected: [ + { isRegular: true, value: 'body > script + div:empty' }, + { isRelative: true, name: 'not', value: '[class]' }, + { isRelative: true, name: 'not', value: '[id]' }, + { isRegular: true, value: '~ div:empty' }, + { isRelative: true, name: 'not', value: '[class]' }, + { isRelative: true, name: 'not', value: '[id]' }, + { isRegular: true, value: '+ div:empty' }, + { isRelative: true, name: 'not', value: '[class]' }, + { isRelative: true, name: 'not', value: '[id]' }, + { isRegular: true, value: '+ [id]' }, + { isRelative: true, name: 'not', value: '[class]' }, + ], + }, + // complex selectors with extended pseudo-class inside as part before combinator + { + actual: 'div:upward(3).banner .inner', + expected: [ + { isRegular: true, value: 'div.banner' }, + { isAbsolute: true, name: 'upward', value: '3' }, + { isRegular: true, value: '.inner' }, + ], + }, + { + actual: 'div:has(img).banner > .text-ad', + expected: [ + { isRegular: true, value: 'div.banner' }, + { isRelative: true, name: 'has', value: 'img' }, + { isRegular: true, value: '> .text-ad' }, + ], + }, + { + actual: 'div:has(> img).banner > .text-ad', + expected: [ + { isRegular: true, value: 'div.banner' }, + { isRelative: true, name: 'has', value: '> img' }, + { isRegular: true, value: '> .text-ad' }, + ], + }, + { + actual: '.test:has(> img).banner[attr=true] > .text-ad', + expected: [ + { isRegular: true, value: '.test.banner[attr=true]' }, + { isRelative: true, name: 'has', value: '> img' }, + { isRegular: true, value: '> .text-ad' }, + ], + }, + { + actual: '.test:has(> img).banner[attr=true] ~ .text-ad:matches-css(z-index: 10)', + expected: [ + { isRegular: true, value: '.test.banner[attr=true]' }, + { isRelative: true, name: 'has', value: '> img' }, + { isRegular: true, value: '~ .text-ad' }, + { isAbsolute: true, name: 'matches-css', value: 'z-index: 10' }, + ], + }, + { + actual: '.test:upward(#top > .block)[attr] .banner', + expected: [ + { isRegular: true, value: '.test[attr]' }, + { isAbsolute: true, name: 'upward', value: '#top > .block' }, + { isRegular: true, value: '.banner' }, + ], + }, + { + actual: '.test:upward(#top > .block)[attr] > div:upward(2).banner', + expected: [ + { isRegular: true, value: '.test[attr]' }, + { isAbsolute: true, name: 'upward', value: '#top > .block' }, + { isRegular: true, value: '> div.banner' }, + { isAbsolute: true, name: 'upward', value: '2' }, + ], + }, + ]; + test.each(testsInputs)('%s', (input) => expectSingleSelectorAstWithAnyChildren(input)); + + it('has(has) + has', () => { + const actual = 'div:has(> .banner:has(> a > img)) + .ad:has(> img)'; + const expected = { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('div'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'has', + children: [ + { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('> .banner'), + getRelativeExtendedWithSingleRegular('has', '> a > img'), + ], + }, + ], + }, + ], + }, + ], + }, + getRegularSelector('+ .ad'), + getRelativeExtendedWithSingleRegular('has', '> img'), + ], + }, + ], + }; + expect(parse(actual)).toEqual(expected); + }); + + it('not > has(not > regular)', () => { + // eslint-disable-next-line max-len + const actual = 'body > div:not([class]) > div[class]:has(> div:not([class]) > .branch-journeys-top a[target="_blank"][href^="/policy/"])'; + const expected = { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('body > div'), + getRelativeExtendedWithSingleRegular('not', '[class]'), + getRegularSelector('> div[class]'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'has', + children: [ + { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('> div'), + getRelativeExtendedWithSingleRegular('not', '[class]'), + getRegularSelector('> .branch-journeys-top a[target="_blank"][href^="/policy/"]'), // eslint-disable-line max-len + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }; + expect(parse(actual)).toEqual(expected); + }); + + it('has(matches-css+ matches-css)', () => { + // eslint-disable-next-line max-len + const actual = '.category-double-article-container:has(.half-article:matches-css(display:none)+ .half-article:matches-css(display:none))'; + const expected = { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('.category-double-article-container'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'has', + children: [ + { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('.half-article'), + getAbsoluteExtendedSelector('matches-css', 'display:none'), + getRegularSelector('+ .half-article'), + getAbsoluteExtendedSelector('matches-css', 'display:none'), + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }; + expect(parse(actual)).toEqual(expected); + }); + }); + + it('un-tokenizable complex selector testcase', () => { + let actual; + const expected = { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('*'), + getAbsoluteExtendedSelector('contains', '/absolute[\\s\\S]*-\\d{4}/'), + getRegularSelector('+ * > .banner'), + getAbsoluteExtendedSelector('contains', '/а/'), + getRegularSelector('~ #case17.banner'), + { + type: NodeType.ExtendedSelector, + children: [ + { + type: NodeType.RelativePseudoClass, + name: 'has', + children: [ + { + type: NodeType.SelectorList, + children: [ + { + type: NodeType.Selector, + children: [ + getRegularSelector('> div:nth-child(100n + 2)'), + getAbsoluteExtendedSelector('contains', '/а/'), + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }; + + // eslint-disable-next-line max-len + actual = '*:contains(/absolute[\\s\\S]*-\\d{4}/) + * > .banner:contains(/а/) ~ #case17.banner:has(> div:nth-child(100n + 2):contains(/а/))'; + expect(parse(actual)).toEqual(expected); + + // eslint-disable-next-line max-len + actual = '*:contains(/absolute[\\s\\S]*-\\d{4}/) + * > .banner:contains(/а/) ~ #case17.banner:has(> div:contains(/а/):nth-child(100n + 2))'; + expect(parse(actual)).toEqual(expected); + }); +}); + +describe('raw valid selectors', () => { + describe('should be trimmed', () => { + const rawSelectors = [ + ' #test p', + ' #test p', + '\t#test p', + '\r#test p', + '\n#test p', + '\f#test p', + '#test p ', + '#test p ', + '#test p\t', + '#test p\r', + '#test p\n', + '#test p\f', + ]; + // should be RegularSelector with value: '#test p' + const expected = '#test p'; + test.each(rawSelectors)('%s', (selector) => { + const expectedAst = getAstWithSingleRegularSelector(expected); + expect(parse(selector)).toEqual(expectedAst); + }); + }); +}); + +describe('check case-insensitive attributes parsing', () => { + // https://github.com/AdguardTeam/ExtendedCss/issues/104 + describe('regular selectors', () => { + const validSelectors = [ + 'body div[class="case" i]', + 'div[class="case" i]', + 'div[class=case i]', + 'div[class=cAsE I]', + '.plus-banner[external-event-tracking*="Banner-"][external-event-tracking*="-sticky" i]', + 'div[id*="left" i] a[href][target="_blank"]:where([href*="1001track.com"]) > img', + 'div[id*=smart-banner i]', + 'a[class=facebook i][title="Share this"]', + 'a[class^=socialButton i][onclick*="window.open"]', + 'div[class^=share i]', + 'a[data-share$=Facebook i]', + 'a[title="Share on" i]', + 'a[class=share i][title=Sharing]', + ]; + test.each(validSelectors)('%s', (selector) => { + const expectedAst = getAstWithSingleRegularSelector(selector); + expect(parse(selector)).toEqual(expectedAst); + }); + }); + + it('selector list', () => { + const actual = 'a[data-st-area*="backTo" i], a[data-st-area*="goToSG" i]'; + const expected = ['a[data-st-area*="backTo" i]', 'a[data-st-area*="goToSG" i]']; + expectSelectorListOfRegularSelectors({ actual, expected }); + }); + + describe('extended selectors', () => { + const testsInputs = [ + { + actual: 'body:has(div[class="page" i])', + expected: [ + { isRegular: true, value: 'body' }, + { isRelative: true, name: 'has', value: 'div[class="page" i]' }, + ], + }, + { + actual: 'div > .fb-page[data-href$="/link/" i]:upward(2)', + expected: [ + { isRegular: true, value: 'div > .fb-page[data-href$="/link/" i]' }, + { isAbsolute: true, name: 'upward', value: '2' }, + ], + }, + ]; + test.each(testsInputs)('%s', (input) => expectSingleSelectorAstWithAnyChildren(input)); + }); +}); + +describe('check pseudo-class names case-insensitivity', () => { + const testsInputs = [ + { + actual: 'div.base[level="3"]:UPWARD([level="0"])', + expected: [ + { isRegular: true, value: 'div.base[level="3"]' }, + { isAbsolute: true, name: 'upward', value: '[level="0"]' }, + ], + }, + { + actual: 'div.base[LEVEL="3"]:UPWARD([level="0"])', + expected: [ + { isRegular: true, value: 'div.base[LEVEL="3"]' }, + { isAbsolute: true, name: 'upward', value: '[level="0"]' }, + ], + }, + { + actual: 'div.base[LEVEL="3"]:UPWARD([LEVEL="0"])', + expected: [ + { isRegular: true, value: 'div.base[LEVEL="3"]' }, + { isAbsolute: true, name: 'upward', value: '[LEVEL="0"]' }, + ], + }, + { + actual: 'div:HAS(> #paragraph)', + expected: [ + { isRegular: true, value: 'div' }, + { isRelative: true, name: 'has', value: '> #paragraph' }, + ], + }, + { + actual: '#root p:CONTAINS(text)', + expected: [ + { isRegular: true, value: '#root p' }, + { isAbsolute: true, name: 'contains', value: 'text' }, + ], + }, + { + actual: '#root p:CONTAINS(UPPER)', + expected: [ + { isRegular: true, value: '#root p' }, + { isAbsolute: true, name: 'contains', value: 'UPPER' }, + ], + }, + { + actual: '#parent *:NOT([class])', + expected: [ + { isRegular: true, value: '#parent *' }, + { isRelative: true, name: 'not', value: '[class]' }, + ], + }, + { + actual: '#parent *:NOT([CLASS]):CONTAINS(text)', + expected: [ + { isRegular: true, value: '#parent *' }, + { isRelative: true, name: 'not', value: '[CLASS]' }, + { isAbsolute: true, name: 'contains', value: 'text' }, + ], + }, + ]; + test.each(testsInputs)('%s', (input) => expectSingleSelectorAstWithAnyChildren(input)); +}); + +describe('remove pseudo-class is invalid for selector parser', () => { + const error = 'Selector parser error: invalid :remove() pseudo-class in selector:'; + const toThrowInputs = [ + { + selector: 'div[id][class][style]:remove()', + error, + }, + { + selector: '.banner > *:remove()', + error, + }, + { + selector: 'div:upward(.ads):remove()', + error, + }, + { + selector: 'body > div:not([id]):not([class]):not([style]):empty:remove()', + error, + }, + ]; + test.each(toThrowInputs)('%s', (input) => expectToThrowInput(input)); +}); + +describe('fail on white space which is before or after extended pseudo-class name', () => { + describe('supported extended pseudo-classes', () => { + // No white space is allowed between the colon and the name of the pseudo-class, + // nor, as usual for CSS syntax, between name of extended pseudo-class and its opening parenthesis + // https://www.w3.org/TR/selectors-4/#pseudo-classes + const error = 'No white space is allowed before or after extended pseudo-class name in selector:'; + const invalidSelectors = [ + // white space before pseudo-class name + 'span: contains(text)', + 'span:\tcontains(text)', + 'div:\nmatches-attr(data=ad)', + // after + '.banner:has (> div > img)', + '.banner > *:not\r(.content)', + '.banner > :is\f(img, p)', + // before and after + 'span: not (.text)', + ]; + test.each(invalidSelectors)('%s', (selector) => expectToThrowInput({ selector, error })); + }); + + describe('standard or invalid pseudos', () => { + const error = 'is not a valid selector'; + const invalidSelectors = [ + // the same for standard pseudo + // and invalid pseudo as well as it will be validated later. + // white space before the pseudo + 'body > script + div: empty', + '.block: nth-child(2) .inner', + 'div:has(> .box:\fonly-child)', + 'div: invalid-pseudo(1)', + 'div:\finvalid-pseudo(1)', + // after the pseudo + 'div:invalid-pseudo (1)', + '.block:nth-child (2) .inner', + '.block:nth-child\t(2) .inner', + ]; + test.each(invalidSelectors)('%s', (selector) => expectToThrowInput({ selector, error })); + }); +}); + +describe('fail on invalid selector', () => { + describe('unbalanced brackets - extended pseudo-class', () => { + const error = 'Unbalanced brackets for extended pseudo-class'; + const invalidSelectors = [ + // part of 'head > style:contains(body{background: #410e13)' before opening `{` + 'head > style:contains(body{', + ]; + test.each(invalidSelectors)('%s', (selector) => expectToThrowInput({ selector, error })); + }); + + describe('unbalanced brackets - attributes is selector', () => { + const error = 'Unbalanced brackets for attributes is selector'; + const invalidSelectors = [ + // part of 'a[href][data-item^=\'{"sources":[\'][data-item*=\'Video Ad\']' before opening `{` + 'a[href][data-item^=\'{', + ]; + test.each(invalidSelectors)('%s', (selector) => expectToThrowInput({ selector, error })); + }); + + describe('non-closed old syntax', () => { + // if may happen while stylesheet parsing + const error = 'Invalid extended-css old syntax selector'; + const invalidSelectors = [ + // part of '[-ext-matches-css-before=\'content: /^[A-Z][a-z]{2}\\s/ \']' before opening `{` + '[-ext-matches-css-before=\'content: /^[A-Z][a-z]', + ]; + test.each(invalidSelectors)('%s', (selector) => expectToThrowInput({ selector, error })); + }); + + describe('upward with no specified selector before', () => { + const error = 'Selector should be specified before :upward() pseudo-class'; + const invalidSelectors = [ + ':upward(1)', + ':upward(p[class])', + ]; + test.each(invalidSelectors)('%s', (selector) => expectToThrowInput({ selector, error })); + }); + + describe('nth-ancestor with no specified selector before', () => { + const error = 'Selector should be specified before :nth-ancestor() pseudo-class'; + const invalidSelectors = [ + ':nth-ancestor(1)', + ':nth-ancestor(p[class])', + ]; + test.each(invalidSelectors)('%s', (selector) => expectToThrowInput({ selector, error })); + }); +}); diff --git a/test/selector/query-jsdom.test.ts b/test/selector/query-jsdom.test.ts new file mode 100644 index 00000000..87cd2870 --- /dev/null +++ b/test/selector/query-jsdom.test.ts @@ -0,0 +1,1796 @@ +/** + * @jest-environment jsdom + */ + +import { + expectSuccessInput, + expectNoMatchInput, + expectToThrowInput, + TestPropElement, +} from '../helpers/selector-query-jsdom'; + +describe('regular selectors', () => { + afterEach(() => { + document.body.innerHTML = ''; + }); + + it('simple -- div', () => { + document.body.innerHTML = '
      '; + const actual = 'div'; + const expected = 'div#username'; + expectSuccessInput({ actual, expected }); + }); + + it('compound -- div#banner', () => { + document.body.innerHTML = ` +
      + + `; + const actual = 'div#banner'; + const expected = 'div#banner'; + expectSuccessInput({ actual, expected }); + }); + + it('compound -- div[style]', () => { + document.body.innerHTML = ` +
      +
      + `; + const actual = 'div[style]'; + const expected = 'div#target'; + expectSuccessInput({ actual, expected }); + }); + + it('compound -- div[id][onclick*="redirect"]', () => { + document.body.innerHTML = ` +
      +
      + `; + const actual = 'div[id][onclick*="redirect"]'; + const expected = 'div#target'; + expectSuccessInput({ actual, expected }); + }); + + it('complex -- div > span', () => { + document.body.innerHTML = ` +
      + + +
      + `; + const actual = 'div > span'; + const expected = 'span#target'; + expectSuccessInput({ actual, expected }); + }); + + it('complex -- div.ad > a.redirect + a', () => { + document.body.innerHTML = ` +
      + + +
      +
      + + +
      + `; + const actual = 'div.ad > a.redirect + a'; + const expected = 'a#target'; + expectSuccessInput({ actual, expected }); + }); + + it('selector list -- div, span', () => { + document.body.innerHTML = ` +
      + + + `; + const actual = 'div, span'; + const expected = '#target0, #target1'; + expectSuccessInput({ actual, expected }); + }); + + it('selector list -- div.banner, p[ad] ~ span, div > a > img', () => { + document.body.innerHTML = ` +
      +

      + + +
      +
      + +
      +
      + + + +
      + `; + const actual = 'p[ad] ~ span, div.banner, div > a > img'; + const expected = '#target0, #target1, #target2'; + expectSuccessInput({ actual, expected }); + }); + + describe('regular selector with pseudo-class -- input:disabled', () => { + beforeEach(() => { + document.body.innerHTML = ` + + + `; + }); + afterEach(() => { + document.body.innerHTML = ''; + }); + + const successInputs = [ + { actual: 'input:disabled', expected: '#disabled' }, + { actual: 'input:enabled', expected: '#enabled' }, + { actual: 'body > *:nth-last-child(1)', expected: '#disabled' }, + { actual: 'body > *:NTH-last-child(2)', expected: '#enabled' }, + { actual: 'body > :nth-of-type(2)', expected: '#disabled' }, + ]; + test.each(successInputs)('%s', (input) => expectSuccessInput(input)); + }); + + describe('valid regular selectors - ids', () => { + beforeEach(() => { + document.body.innerHTML = ` +
      +
      +
      +
      +
      + +
      +
      +

      +
      +
      +
      +
      +
      + `; + }); + afterEach(() => { + document.body.innerHTML = ''; + }); + + const successInputs = [ + { actual: '#台北Táiběi', expected: '[target="id0"]' }, + { actual: '#台北', expected: '[target="id1"]' }, + { actual: '#foo\\:bar', expected: '[target="id2"]' }, + { actual: '#test\\.foo\\[5\\]bar', expected: '[target="id3"]' }, + { actual: '#form > #radio', expected: '[target="id4"]' }, + { actual: '#foo > *', expected: '[target="id5"]' }, + { actual: '#types_all', expected: '[target="id6"]' }, + { actual: '#dash-id', expected: '[target="id7"]' }, + { actual: '#name\\+value', expected: '[target="id8"]' }, + ]; + test.each(successInputs)('%s', (input) => expectSuccessInput(input)); + + const noMatchSelectors = [ + // all children of ID with no children + '#firstUL > *', + ]; + test.each(noMatchSelectors)('%s', (selector) => expectNoMatchInput({ selector })); + }); + + describe('valid regular selectors - classes', () => { + beforeEach(() => { + document.body.innerHTML = ` +
      +
      +
      + + `; + }); + afterEach(() => { + document.body.innerHTML = ''; + }); + + const successInputs = [ + { actual: '.台北Táiběi', expected: '[target="id0"]' }, + { actual: '.foo\\:bar', expected: '[target="id1"]' }, + { actual: '.test\\.foo\\[5\\]bar', expected: '[target="id2"]' }, + { actual: '.nav', expected: '[target="id3"]' }, + ]; + test.each(successInputs)('%s', (input) => expectSuccessInput(input)); + + const noMatchSelectors = [ + // no class name case match - no selection + '.NAV', + ]; + test.each(noMatchSelectors)('%s', (selector) => expectNoMatchInput({ selector })); + }); + + describe('valid regular selectors - attributes', () => { + beforeEach(() => { + document.body.innerHTML = ` +
      +
      + + + + + `; + }); + afterEach(() => { + document.body.innerHTML = ''; + }); + + const successInputs = [ + { actual: '[rel="bookmark"]', expected: '#target0' }, + { actual: '[rel=bookmark]', expected: '#target0' }, + { actual: 'div[name=action]', expected: '#target0' }, + { actual: 'div[name=\'action\']', expected: '#target0' }, + { actual: 'div[name="action"]', expected: '#target0' }, + { actual: 'div[data-comma="0,1"]', expected: '#target0' }, + { actual: 'div[data-comma=\'0,1\']', expected: '#target0' }, + // strict match for case sensitive value + { actual: '[name=Name1]', expected: '#target1' }, + { actual: 'a[ title ]', expected: '#target2' }, + { actual: 'a[TITLE]', expected: '#target2' }, + { actual: 'a[ title = "bookmark" ]', expected: '#target2' }, + { actual: 'a[href ^= "http://www"]', expected: '#target2' }, + // #target3 - href with hash + { actual: 'a[href^="#"]', expected: '#target3' }, + { actual: 'a[href*="#"]', expected: '#target3' }, + { actual: 'a[href *= "para"]', expected: '#target3' }, + // #target4 - value with square brackets + { actual: 'input[name^="foo["]', expected: '#target4' }, + { actual: 'input[name^="foo[bar]"]', expected: '#target4' }, + { actual: 'input[name*="[bar]"]', expected: '#target4' }, + { actual: 'input[name*="foo[bar]"]', expected: '#target4' }, + // #target4 - separated word + { actual: '[title^=space]', expected: '#target4' }, + { actual: '[title*=space]', expected: '#target4' }, + // specified for exact separated word + { actual: '[title~=space]', expected: '#target4' }, + // #target5 - exact value start + { actual: '[title|=exact]', expected: '#target5' }, + { actual: '[title*=exact]', expected: '#target5' }, + { actual: '[title^=exact]', expected: '#target5' }, + ]; + test.each(successInputs)('%s', (input) => expectSuccessInput(input)); + + const noMatchSelectors = [ + // no match for case sensitive value + '[name=name1]', + // no match because of "=|" which needs dash separated exact start of value + '[title|=space]', + // no match because of "~|" which needs space separated word in value + '[title~=exact]', + + ]; + test.each(noMatchSelectors)('%s', (selector) => expectNoMatchInput({ selector })); + }); +}); + +describe('extended pseudo-classes', () => { + describe('contains', () => { + afterEach(() => { + document.body.innerHTML = ''; + }); + + describe('simple string arg', () => { + document.body.innerHTML = ` + text example (LINK) + 123example +

      text

      + `; + const actualSelectors = [ + 'span:contains(text)', + 'span:contains( example)', + 'span:contains(example (LINK))', + 'span:contains((LINK))', + ]; + const expected = 'span#target'; + test.each(actualSelectors)('%s', (actual) => expectSuccessInput({ actual, expected })); + }); + + it('regexp arg', () => { + document.body.innerHTML = ` +
      +

      Test template

      + text for contains pseudo checking + "quotes" + 123456 +

      text123

      +
      + `; + let actual; + let expected; + + actual = '#container > :contains(/^text\\s/)'; + expected = 'span#target'; + expectSuccessInput({ actual, expected }); + + actual = '#container > :contains(/"quote[\\w]"/)'; + expected = 'span#target'; + expectSuccessInput({ actual, expected }); + }); + + describe('regexp arg with flags', () => { + document.body.innerHTML = ` +
      +

      paragraph with simple text

      + another simple text +
      + `; + + const actualMatchSelectors = [ + 'p:contains(/simple/)', + 'p:contains(/Simple/i)', + 'p:contains(/Simple/gmi)', + ]; + const expected = 'p#target'; + test.each(actualMatchSelectors)('%s', (actual) => expectSuccessInput({ actual, expected })); + + const noMatchSelectors = [ + 'p:contains(/Simple/)', + ]; + test.each(noMatchSelectors)('%s', (selector) => expectNoMatchInput({ selector })); + }); + + it('few different standard combinators + contains', () => { + document.body.innerHTML = ` +
      +

      Test template

      +

      text for contains pseudo checking

      +
      +
      + adg-test +
      +
      +
      + `; + const actual = '* > p ~ #test a:contains(adg-test)'; + const expected = 'a#target'; + expectSuccessInput({ actual, expected }); + }); + + it('old syntax', () => { + document.body.innerHTML = ` +
      + text for contains pseudo checking + 123456 +
      + `; + let actual; + let expected; + + actual = 'div[-ext-contains="text"]'; + expected = 'div#container'; + expectSuccessInput({ actual, expected }); + + actual = 'div *[-ext-contains="text"]'; + expected = 'span#target'; + expectSuccessInput({ actual, expected }); + }); + + describe('contains - invalid args', () => { + const toThrowInputs = [ + { selector: 'p:contains()', error: 'Missing arg for :contains pseudo-class' }, + { selector: 'p:contains(/ab.?+)', error: 'Unbalanced brackets for extended pseudo-class' }, + ]; + test.each(toThrowInputs)('%s', (input) => expectToThrowInput(input)); + }); + }); + + describe('matches-css pseudos', () => { + describe('matches-css - simple', () => { + document.body.innerHTML = ` + + +
      + `; + const actualMatchSelectors = [ + ':matches-css(width:20px)', + ':matches-css(content: *find me*)', + 'div:matches-css(min-height:/10/):matches-css(height:/10|15|20/)', + ]; + const expected = 'div#target'; + test.each(actualMatchSelectors)('%s', (actual) => expectSuccessInput({ actual, expected })); + + const noMatchSelectors = [ + // should NOT match because height is 15px + 'div:matches-css(min-height:/10/):matches-css(height:/10|20/)', + ]; + test.each(noMatchSelectors)('%s', (selector) => expectNoMatchInput({ selector })); + }); + + it('matches-css - opacity', () => { + document.body.innerHTML = ` + + +
      +
      + `; + const actual = 'div:matches-css(opacity: 0.9)'; + const expected = 'div#target'; + expectSuccessInput({ actual, expected }); + }); + + describe('matches-css - url', () => { + /* eslint-disable max-len */ + document.body.innerHTML = ` + + +
      +

      + `; + /* eslint-enable max-len */ + + const successInputs = [ + { + // no quotes for url + actual: 'div:matches-css(background-image: url(data:*))', + expected: 'div#divTarget', + }, + { + // quotes for url + actual: 'div:matches-css(background-image: url("data:*"))', + expected: 'div#divTarget', + }, + { + // regex + strict quotes for url + actual: 'div:matches-css(background-image: /^url\\("data:image\\/gif;base64.+/)', + expected: 'div#divTarget', + }, + { + // regex + optional quotes for url + actual: 'div:matches-css(background-image: /^url\\("?data:image\\/gif;base64.+/)', + expected: 'div#divTarget', + }, + { + // regex + no quotes for url + actual: 'div:matches-css(background-image: /^url\\([a-z]{4}:[a-z]{5}/)', + expected: 'div#divTarget', + }, + { + // different target + actual: 'p:matches-css(background-image: /^url\\("?data:image\\/gif;base64.+/)', + expected: 'p#pTarget', + }, + ]; + test.each(successInputs)('%s', (input) => expectSuccessInput(input)); + }); + + /** + * Matches-css-before and matches-css-after tests located in selector-playwright.test.ts + * because jsdom does not support pseudo-elements so it does not work. + * + * @see {@link https://github.com/jsdom/jsdom/issues/1928} + */ + }); + + describe('matches-attr pseudo', () => { + beforeEach(() => { + document.body.innerHTML = ` +
      +
      + `; + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + describe('matches-attr - ok', () => { + const actualSelectors = [ + ':matches-attr("class")', + ':matches-attr(class)', + ':matches-attr("data-*")', + ':matches-attr("/data-/")', + ':matches-attr("*-o"="banner_*")', + ':matches-attr(data-*=*240*)', + 'div:matches-attr("class"="ma*")', + 'div:matches-attr("class"="match")', + 'div:matches-attr("class"="/[\\w]{5}/")', + 'div:matches-attr(class=/[\\w]{5}/)', + 'div:matches-attr("data-*"="/^banner_.?/")', + 'div:matches-attr(/data-/=/^banner_.?/)', + 'div:matches-attr(quoted=/double\\"quote/)', + ]; + const expected = 'div#target'; + test.each(actualSelectors)('%s', (actual) => expectSuccessInput({ actual, expected })); + }); + + describe('no match', () => { + const noMatchSelectors = [ + // no match by value + 'div:matches-attr("class"="target")', + // no match by attr name + 'div:matches-attr("data")', + ]; + test.each(noMatchSelectors)('%s', (selector) => expectNoMatchInput({ selector })); + }); + + describe('matches-attr - invalid args', () => { + const matchAttrErrorText = 'Error while matching element attributes by arg'; + const toThrowInputs = [ + { selector: 'div:matches-attr()', error: 'Missing arg for :matches-attr pseudo-class' }, + { selector: 'div:matches-attr("//")', error: matchAttrErrorText }, + { selector: 'div:matches-attr(*)', error: matchAttrErrorText }, + { selector: 'div:matches-attr(".?"="/^[0-9]*$/")', error: matchAttrErrorText }, + { selector: 'div:matches-attr(")', error: matchAttrErrorText }, + { selector: 'div:matches-attr(> [track="true"])', error: matchAttrErrorText }, + { selector: 'div:matches-attr(".?"="/^[0-9]*$/")', error: matchAttrErrorText }, + { selector: 'div:matches-attr("//"="/./")', error: matchAttrErrorText }, + ]; + test.each(toThrowInputs)('%s', (input) => expectToThrowInput(input)); + }); + }); + + describe('matches-property pseudo', () => { + beforeEach(() => { + document.body.innerHTML = '
      '; + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + it('matches-property - property name with quotes', () => { + const testEl = document.querySelector('#target') as TestPropElement; + const testPropName = '_testProp'; + const testPropValue = '123'; + testEl[testPropName] = testPropValue; + + const actual = 'div:matches-property("_testProp")'; + const expected = 'div#target'; + expectSuccessInput({ actual, expected }); + }); + + it('matches-property - property name with no quotes', () => { + const testEl = document.querySelector('#target') as TestPropElement; + const testPropName = '_testProp'; + const testPropValue = '123'; + testEl[testPropName] = testPropValue; + + const actual = 'div:matches-property(_testProp)'; + const expected = 'div#target'; + expectSuccessInput({ actual, expected }); + }); + + it('matches-property - wildcard in property name pattern', () => { + const testEl = document.querySelector('#target') as TestPropElement; + const testPropName = '_testProp'; + const testPropValue = '123'; + testEl[testPropName] = testPropValue; + + const actual = 'div:matches-property(_t*)'; + const expected = 'div#target'; + expectSuccessInput({ actual, expected }); + }); + + it('matches-property - no match by property name', () => { + const testEl = document.querySelector('#target') as TestPropElement; + const testPropName = '_testProp'; + const testPropValue = '123'; + testEl[testPropName] = testPropValue; + + const selector = 'div:matches-property("test")'; + expectNoMatchInput({ selector }); + }); + + it('matches-property - regexp for property name pattern', () => { + const testEl = document.querySelector('#target') as TestPropElement; + const testPropName = '_testProp'; + const testPropValue = '123'; + testEl[testPropName] = testPropValue; + + const actual = 'div:matches-property(/_test/)'; + const expected = 'div#target'; + expectSuccessInput({ actual, expected }); + }); + + it('matches-property - string name and value', () => { + const testEl = document.querySelector('#target') as TestPropElement; + const testPropName = '_testProp'; + const testPropValue = 'abc'; + testEl[testPropName] = testPropValue; + + const actual = 'div:matches-property("_testProp"="abc")'; + const expected = 'div#target'; + expectSuccessInput({ actual, expected }); + }); + + it('matches-property - no match by value', () => { + const testEl = document.querySelector('#target') as TestPropElement; + const testPropName = '_testProp'; + const testPropValue = 'abc'; + testEl[testPropName] = testPropValue; + + const selector = 'div:matches-property("_testProp"="target")'; + expectNoMatchInput({ selector }); + }); + + it('matches-property - string name and regexp value', () => { + const testEl = document.querySelector('#target') as TestPropElement; + const testPropName = '_testProp'; + const testPropValue = 'abc'; + testEl[testPropName] = testPropValue; + + let actual; + let expected; + + actual = 'div:matches-property(_testProp=/[\\w]{3}/)'; + expected = 'div#target'; + expectSuccessInput({ actual, expected }); + + actual = 'div:matches-property("id"=/tar/)'; + expected = 'div#target'; + expectSuccessInput({ actual, expected }); + }); + + it('matches-property - string chain and null value', () => { + const testEl = document.querySelector('#target') as TestPropElement; + const propFirst = '_testProp'; + const propInner = { propInner: null }; + testEl[propFirst] = propInner; + + const actual = 'div:matches-property(_testProp.propInner=null)'; + const expected = 'div#target'; + expectSuccessInput({ actual, expected }); + }); + + it('matches-property - partially regexp chain', () => { + const testEl = document.querySelector('#target') as TestPropElement; + const propFirst = '_testProp'; + const propInner = { propInner: null }; + testEl[propFirst] = propInner; + + const actual = 'div:matches-property(/^_test/.propInner=null)'; + const expected = 'div#target'; + expectSuccessInput({ actual, expected }); + }); + + it('matches-property - access child prop of null prop', () => { + const testEl = document.querySelector('#target') as TestPropElement; + const propFirst = '_testProp'; + const propInner = { propInner: null }; + testEl[propFirst] = propInner; + + const selector = 'div:matches-property(_testProp.propInner.test)'; + expectNoMatchInput({ selector }); + }); + + it('matches-property - string chain and null value as string', () => { + const testEl = document.querySelector('#target') as TestPropElement; + const propNullAsStr = '_testProp'; + const propInnerNullStr = { propInner: 'null' }; + testEl[propNullAsStr] = propInnerNullStr; + + const actual = 'div:matches-property("_testProp.propInner"="null")'; + const expected = 'div#target'; + expectSuccessInput({ actual, expected }); + }); + + it('matches-property - string chain and true value', () => { + const testEl = document.querySelector('#target') as TestPropElement; + const propNullAsStr = '_testProp'; + const propInnerNullStr = { propInner: true }; + testEl[propNullAsStr] = propInnerNullStr; + + const actual = 'div:matches-property("_testProp.*Inner"=true)'; + const expected = 'div#target'; + expectSuccessInput({ actual, expected }); + }); + + it('matches-property - string chain and undefined value as string', () => { + const testEl = document.querySelector('#target') as TestPropElement; + const propUndefAsStr = '_testProp'; + const propInnerUndefStr = { propInner: 'undefined' }; + testEl[propUndefAsStr] = propInnerUndefStr; + + const actual = 'div:matches-property("_testProp.propInner"="undefined")'; + const expected = 'div#target'; + expectSuccessInput({ actual, expected }); + }); + + it('matches-property - property chain variants', () => { + const testEl = document.querySelector('#target') as TestPropElement; + const aProp = '_testProp'; + const aInner = { + unit123: { id: 123 }, + }; + testEl[aProp] = aInner; + + let actual; + const expected = 'div#target'; + + actual = 'div:matches-property("_testProp./[\\w]{4}123/.id")'; + expectSuccessInput({ actual, expected }); + + actual = 'div:matches-property(_testProp.unit123./.{1,5}/=123)'; + expectSuccessInput({ actual, expected }); + }); + + describe('matches-property - invalid args', () => { + const missingArgErrorText = 'Missing arg for :matches-property pseudo-class'; + const matchPropErrorText = 'Error while matching element properties by arg'; + const unbalancedBracketsErrorText = 'Unbalanced brackets for extended pseudo-class'; + const toThrowInputs = [ + { selector: 'div:matches-property()', error: missingArgErrorText }, + { selector: 'div:matches-property("//")', error: matchPropErrorText }, + { selector: 'div:matches-property(".?"="/^[0-9]*$/")', error: matchPropErrorText }, + { selector: 'div:matches-property(.prop.id)', error: matchPropErrorText }, + // due to invalid regexp, closing `)` is considered as part of pseudo-class arg + { selector: 'div:matches-property(abc./aa.?+./test/)', error: unbalancedBracketsErrorText }, + { selector: 'div:matches-property(abc..?+/.test)', error: unbalancedBracketsErrorText }, + { selector: 'div:matches-property(abcd.?+/.test)', error: unbalancedBracketsErrorText }, + ]; + test.each(toThrowInputs)('%s', (input) => expectToThrowInput(input)); + }); + }); + + describe('xpath pseudo', () => { + beforeEach(() => { + document.body.innerHTML = ` +
      +
      +
      +
      + test-xpath-content +
      +
      +
      +
      + `; + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + describe('xpath - ok', () => { + const successInputs = [ + { actual: 'div.base[level="2"]:xpath(../..)', expected: 'div#root' }, + { actual: ':xpath(//*[@class="baseInner"])', expected: 'div#inner' }, + { actual: ':xpath(//*[@class="base"]/..)', expected: 'div#parent' }, + { actual: ':xpath(//div[contains(text(),"test-xpath-content")])', expected: 'div#inner' }, + { actual: '*:xpath(//div[contains(text(),"test-xpath-content")])', expected: 'div#inner' }, + ]; + test.each(successInputs)('%s', (input) => expectSuccessInput(input)); + }); + + it('xpath - no match', () => { + // there is no such ancestor + const selector = 'div#root:xpath(../../../..)'; + expectNoMatchInput({ selector }); + }); + + describe('xpath - invalid args', () => { + const invalidArgErrorText = 'Invalid argument of :xpath pseudo-class'; + const toThrowInputs = [ + { selector: 'div:xpath()', error: 'Missing arg for :xpath pseudo-class' }, + { selector: 'div:xpath("//")', error: invalidArgErrorText }, + { selector: 'div:xpath(2)', error: invalidArgErrorText }, + { selector: 'div:xpath(300)', error: invalidArgErrorText }, + ]; + test.each(toThrowInputs)('%s', (input) => expectToThrowInput(input)); + }); + }); + + describe('nth-ancestor pseudo', () => { + beforeEach(() => { + document.body.innerHTML = ` +
      +
      +
      +
      +
      +
      +
      + `; + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + describe('nth-ancestor - ok', () => { + const actualSelectors = [ + 'div.base[level="3"]:nth-ancestor(3)', + 'div.base[level="2"]:nth-ancestor(2)', + ]; + const expected = 'div#root'; + test.each(actualSelectors)('%s', (actual) => expectSuccessInput({ actual, expected })); + }); + + it('nth-ancestor - no match', () => { + // there is no such ancestor + const selector = 'div#root:nth-ancestor(5)'; + expectNoMatchInput({ selector }); + }); + + describe('nth-ancestor - invalid args', () => { + const invalidArgErrorText = 'Invalid argument of :nth-ancestor pseudo-class'; + const toThrowInputs = [ + { selector: 'div:nth-ancestor()', error: 'Missing arg for :nth-ancestor pseudo-class' }, + { selector: 'div:nth-ancestor("//")', error: invalidArgErrorText }, + { selector: 'div:nth-ancestor(0)', error: invalidArgErrorText }, + { selector: 'div:nth-ancestor(300)', error: invalidArgErrorText }, + ]; + test.each(toThrowInputs)('%s', (input) => expectToThrowInput(input)); + }); + }); + + describe('upward pseudo - number', () => { + beforeEach(() => { + document.body.innerHTML = ` +
      +
      +
      +
      +
      +
      +
      + `; + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + + describe('upward number - ok', () => { + const expected = 'div#root'; + const actualSelectors = [ + 'div.base[level="3"]:upward(3)', + 'div.base[level="2"]:upward(2)', + ]; + test.each(actualSelectors)('%s', (actual) => expectSuccessInput({ actual, expected })); + }); + + it('upward number - no match', () => { + // there is no such ancestor + const selector = 'div#root:upward(5)'; + expectNoMatchInput({ selector }); + }); + + describe('upward number - invalid args', () => { + const invalidArgErrorText = 'Invalid argument of :upward pseudo-class'; + const selectors = [ + 'div:upward(0)', + 'div:upward(300)', + ]; + test.each(selectors)('%s', (selector) => expectToThrowInput({ selector, error: invalidArgErrorText })); + }); + }); + + describe('upward pseudo - selector', () => { + beforeEach(() => { + document.body.innerHTML = ` +
      +
      +

      text

      +
      +
      +
      +
      +
      + `; + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + describe('upward selector - ok', () => { + const successInputs = [ + { actual: 'div.base[level="3"]:upward([level="0"])', expected: 'div#root' }, + { actual: 'div.base:upward(.base)', expected: 'div#child' }, + { actual: 'div.base[level="2"]:upward(#root > div)', expected: 'div#parent' }, + { actual: 'div.base:upward(body > [level])', expected: 'div#root' }, + { actual: 'div#inner:upward(p ~ div)', expected: 'div#child' }, + ]; + test.each(successInputs)('%s', (input) => expectSuccessInput(input)); + }); + + it('upward selector - no match', () => { + // there is no such ancestor + const selector = 'div#root:upward(div)'; + expectNoMatchInput({ selector }); + }); + + describe('upward selector - invalid args', () => { + const selectors = [ + 'div:upward(//)', + 'div:upward(..class)', + ]; + const invalidArgErrorText = 'Invalid argument of :upward pseudo-class'; + test.each(selectors)('%s', (selector) => expectToThrowInput({ selector, error: invalidArgErrorText })); + }); + }); + + describe('has', () => { + beforeEach(() => { + document.body.innerHTML = ` +
      +
      +

      text

      + +
      + +
      +
      + +

      text

      +
      +
      +
      +
      +

      text

      +
      +
      +
      +
        +
      • + + +
      • +
      • +
        + +
        +
      • +
      +
      + `; + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + describe('has - ok', () => { + const successInputs = [ + { actual: 'div:has(#parent)', expected: 'div#root' }, + { actual: 'div:has(#child)', expected: '#root, #parent' }, + { actual: 'div[class]:has(div > span)', expected: 'div#child' }, + // child combinator in arg + { actual: 'div:has(> #child)', expected: 'div#parent' }, + { actual: ':has(> div > .banner)', expected: 'div#parent' }, + { actual: ':has(> p + a + div #innerParagraph)', expected: 'div#parent' }, + { actual: ':has(> p ~ div #innerParagraph)', expected: 'div#parent' }, + { actual: 'li:has(> span):has(+ li > div > img)', expected: 'li#firstLi' }, + { actual: 'li:has(+ li > div > img) > span', expected: 'span#firstSpan' }, + { actual: 'li:has(> span) + li:has(> div > img)', expected: 'li#secondLi' }, + { actual: 'li:has(> span) + li > div:has(> img)', expected: 'div#secondDiv' }, + { actual: 'li:has(> span) + li > div > img', expected: 'img#secondImg' }, + // next sibling combinator in arg + { actual: 'p:has(+ a)', expected: 'p#paragraph' }, + { actual: 'p:has(+ * + div#child)', expected: 'p#paragraph' }, + { actual: '.banner:has(+ div[id][class])', expected: 'a#anchor' }, + // subsequent-sibling combinator in arg + { actual: 'p:has(~ a)', expected: 'p#paragraph' }, + { actual: 'p:has(~ div#child)', expected: 'p#paragraph' }, + { actual: '#root p:has(~ div#child)', expected: 'p#paragraph' }, + // selector list as arg + { actual: 'div[id][class]:has(.base, p#innerParagraph)', expected: 'div#child' }, + { actual: 'div:has([id][class="base"], p)', expected: '#root, #parent, #child' }, + // complex selectors as base + { actual: '* > #paragraph ~ div:has(a[href^="/inner"])', expected: 'div#child' }, + { actual: '* > a[href*="test"] + div:has(a[href^="/inner"])', expected: 'div#child' }, + { actual: '#root div > div:has(.banner)', expected: 'div#child' }, + // obvious :scope pseudo inside :has + { actual: 'div:has(:scope > #child)', expected: 'div#parent' }, + // old syntax + { actual: 'div[-ext-has="> #child"]', expected: 'div#parent' }, + ]; + test.each(successInputs)('%s', (input) => expectSuccessInput(input)); + }); + + describe('has - no arg or invalid selectors', () => { + const invalidArgErrorText = 'Invalid selector for :has pseudo-class'; + const toThrowInputs = [ + { selector: 'div:has()', error: 'Missing arg for :has pseudo-class' }, + { selector: 'div:has(1)', error: invalidArgErrorText }, + { selector: '#parent > :has(..banner)', error: invalidArgErrorText }, + { selector: '#parent > :has(id="123") > .test-inner', error: invalidArgErrorText }, + ]; + test.each(toThrowInputs)('%s', (input) => expectToThrowInput(input)); + }); + }); + + describe('if-not pseudo-class', () => { + beforeEach(() => { + document.body.innerHTML = ` +
      +
      +

      text

      + +
      + +
      +
      + +

      text

      +
      +
      +
      +
      +

      text

      +
      +
      +
      +
      + `; + }); + afterEach(() => { + document.body.innerHTML = ''; + }); + + describe('if-not - ok', () => { + const successInputs = [ + { actual: '#parent > div:if-not(#innerParagraph)', expected: 'div#child2' }, + { actual: '#parent > div:if-not(div > span)', expected: 'div#child2' }, + // child combinator in arg + { actual: '#child div:if-not(> span)', expected: 'div#inner' }, + { actual: '#parent > div:if-not(> a + div + div)', expected: 'div#child2' }, + { actual: '#parent > div:if-not(> a ~ div[level="3"])', expected: 'div#child2' }, + { actual: '#child > :if-not(> span)', expected: '#anchor, #inner' }, + // next sibling combinator in arg + { actual: 'a:if-not(+ [level="2"])', expected: 'a#anchor' }, + { actual: 'a:if-not(+ [level="2"] + [level="2"])', expected: 'a#anchor' }, + // subsequent-sibling combinator in arg + { actual: 'a:if-not(~ .base2)', expected: 'a#anchor' }, + { actual: 'a:if-not(~ * + div[id][class] > [level="3"])', expected: 'a#anchor' }, + // selector list as arg + { actual: '#parent > div[id][class]:if-not(a, span)', expected: 'div#child2' }, + // complex selector as base + { actual: '#root div > div:if-not(*)', expected: 'div#inner' }, + { actual: '#root > * > #paragraph ~ div:if-not(a[href^="/inner"])', expected: 'div#child2' }, + ]; + test.each(successInputs)('%s', (input) => expectSuccessInput(input)); + }); + + describe('if-not - no arg or invalid selectors', () => { + const invalidArgErrorText = 'Invalid selector for :if-not pseudo-class'; + const toThrowInputs = [ + { selector: 'div:if-not()', error: 'Missing arg for :if-not pseudo-class' }, + { selector: 'div:if-not(1)', error: invalidArgErrorText }, + { selector: '#parent > :if-not(..banner)', error: invalidArgErrorText }, + { selector: '#parent > :if-not(id="123") > .test-inner', error: invalidArgErrorText }, + ]; + test.each(toThrowInputs)('%s', (input) => expectToThrowInput(input)); + }); + }); + + describe('is pseudo-class', () => { + beforeEach(() => { + document.body.innerHTML = ` +
      +
      +

      text

      + +
      + +
      +
      + +

      text

      +
      +
      +
      +
      +

      text

      +
      +
      +
      +
      + `; + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + describe('is - ok', () => { + const successInputs = [ + { actual: 'div:is(#parent, #parent2, #parent3)', expected: 'div#parent' }, + { actual: '#parent > :is(.base, .target)', expected: 'div#child' }, + { actual: 'div:is(#child, #child2)', expected: '#child, #child2' }, + ]; + test.each(successInputs)('%s', (input) => expectSuccessInput(input)); + }); + + describe('is - invalid selector — no fail, just skip', () => { + const invalidSelectors = [ + '#test-is :is(1) > .test-inner', + '#parent > :is(id="123") > .test-inner', + '#parent > :is(..banner) > .test-inner', + // ':has(..banner)' is invalid but it is wrapped in :is() so no error + '#parent > :is(div:has(..banner))', + ]; + test.each(invalidSelectors)('%s', (selector) => expectNoMatchInput({ selector })); + }); + + describe('is - invalid args', () => { + const toThrowInputs = [ + { selector: 'div:is()', error: 'Missing arg for :is pseudo-class' }, + { selector: 'html:is(.modal-active)', error: 'Selection by :is pseudo-class is not possible' }, + ]; + test.each(toThrowInputs)('%s', (input) => expectToThrowInput(input)); + }); + }); + + describe('not', () => { + beforeEach(() => { + document.body.innerHTML = ` +
      +
      +

      text

      + +
      + +
      +
      + +

      text

      +
      +
      +
      +
      +

      text

      +
      +
      +
      +
      + `; + }); + afterEach(() => { + document.body.innerHTML = ''; + }); + + describe('not - ok', () => { + const successInputs = [ + { actual: '.base:not([level="3"])', expected: 'div#child' }, + { actual: '#inner2 > :not(span)', expected: 'p#innerParagraph' }, + { actual: '#child *:not(a, span, p)', expected: '#inner, #inner2' }, + ]; + test.each(successInputs)('%s', (input) => expectSuccessInput(input)); + }); + + describe('not - no match', () => { + const noMatchSelectors = [ + '#inner2 > p:not(p)', + '#inner2 > p:not( p )', + '#inner2 > p:not([id])', + '#inner2 > p:not(#innerParagraph)', + '#inner2 > p:not(a, p)', + '#inner2 > p:not(a,p)', + '#inner2 > :not(span, p, div)', + ]; + test.each(noMatchSelectors)('%s', (selector) => expectNoMatchInput({ selector })); + }); + + describe('not - invalid args', () => { + const invalidArgErrorText = 'Invalid selector for :not pseudo-class'; + const toThrowInputs = [ + { selector: 'div:not()', error: 'Missing arg for :not pseudo-class' }, + { selector: 'div:not(1)', error: invalidArgErrorText }, + { selector: '#parent > :not(id="123") > .test-inner', error: invalidArgErrorText }, + { selector: '#parent > :not(..banner) > .test-inner', error: invalidArgErrorText }, + // there is no parentElement for html-node + { selector: 'html:not(.modal-active)', error: 'Selection by :not pseudo-class is not possible' }, + ]; + test.each(toThrowInputs)('%s', (input) => expectToThrowInput(input)); + }); + }); +}); + +describe('combined pseudo-classes', () => { + afterEach(() => { + document.body.innerHTML = ''; + }); + + describe('different combinations of pseudos', () => { + beforeEach(() => { + document.body.innerHTML = ` +
      +
      +

      text

      +
      +
      + inner span text +

      inner paragraph

      +
      +
      +
      + +
      +

      second test

      +
      +
      + span2 text +

      +
      +
      +
      + +
      +
      + `; + }); + afterEach(() => { + document.body.innerHTML = ''; + }); + + describe('all should be ok', () => { + const successInputs = [ + // selector list of has duplicates + { actual: '.base:has(> #innerSpan), .base:has(> #innerSpan)', expected: '#inner' }, + // selector list with nonexistent class + { actual: '#nonexistent:has(*), #inner:has(*)', expected: '#inner' }, + // selector list with :not() and few regular selectors + { actual: '#root > div:not(:contains(span2 text)),#deepDiv,:disabled', expected: '#parent, #deepDiv, #disabledInput' }, // eslint-disable-line max-len + // not(contains, regular selector) + { actual: '#inner2 > :not(:contains(span2 text),#deepDiv)', expected: '#innerParagraph2' }, + // not not not + { actual: '#root [id]:not([class]):not([level]):not(p)', expected: 'input#disabledInput' }, + // has(not), few targets + { actual: '#parent div[id][class]:has(:not(div))', expected: '#child, #inner' }, + // has(contains) + { actual: 'div[id^="inn"][class]:has(p:contains(inner))', expected: 'div#inner' }, + // has(contains) has contains + { actual: '#root div:has(:contains(text)):has(#paragraph):contains(inner paragraph)', expected: '#parent' }, // eslint-disable-line max-len + // has(has) + { actual: '#parent div:has(div:has(> p))', expected: '#child' }, + // has(has contains) + { actual: '#parent div:has(div:has(> p):contains(inner span text))', expected: '#child' }, + // has(has(contains)) + { actual: '#parent div:has(div:has(> p:contains(inner)))', expected: '#child' }, + // matches-attr matches-attr upward + { actual: '#root *[id^="p"][random] > *:matches-attr("/class/"="/base/"):matches-attr("/level$/"="/^[0-9]$/"):upward(1)', expected: '#parent' }, // eslint-disable-line max-len + // matches-attr contains xpath + { actual: '#parent2 div:matches-attr("/random/"):contains(text):xpath(../..)', expected: '#parent2' }, + // is(has, has) + { actual: '#parent > :is(.block:has(> #inner), .base:has(> #inner))', expected: 'div#child' }, + // is contains + { actual: ':is(#inner, #inner2):contains(inner)', expected: 'div#inner' }, + // is(not) + { actual: '#inner > :is(*:not([class]))', expected: '#innerParagraph' }, + // is(not) has + { actual: '#root > :is(div:not([class])):has(#paragraph)', expected: 'div#parent' }, + // has-text xpath + { actual: 'p:has-text(/inner/):xpath(../../..)', expected: 'div#parent' }, + // upward(number) if + { actual: 'div[level="2"]:upward(1):if(#disabledInput)', expected: 'div#parent2' }, + // upward(selector) -abp-has + { actual: 'div[level="2"]:upward(div[id]):-abp-has(#disabledInput)', expected: 'div#parent2' }, + // upward(not) + { actual: '#innerParagraph:upward(div:not([class]))', expected: 'div#parent' }, + // -abp-contains upward + { actual: 'p:-abp-contains(inner paragraph):upward(div[id])', expected: 'div#inner' }, + // -abp-contains upward not + { actual: 'p:-abp-contains(inner paragraph):upward(div[id]:not([class]))', expected: 'div#parent' }, + // not(has) + { actual: '#parent div.base:not(:has(> div > span))', expected: 'div#inner' }, + // not(has(not)) + { actual: 'div.base:not(:has(:not(span, p)))', expected: 'div#inner' }, + ]; + test.each(successInputs)('%s', (input) => expectSuccessInput(input)); + }); + + it('matches-attr + has + matches-prop', () => { + const setPropElement = document.querySelectorAll('#deepDiv')[0] as TestPropElement; + const testPropName = '_testProp'; + const testPropValue = 'abc'; + setPropElement[testPropName] = testPropValue; + + const actual = 'div:matches-attr(random):has(div:matches-property(_testProp=/[\\w]{3}/))'; + const expected = 'div#inner2'; + expectSuccessInput({ actual, expected }); + }); + }); + + it('is(has, has)', () => { + document.body.innerHTML = ` +
        +
      • + + +
      • +
      • +
        + +
        +
      • +
      • + + + +
      • +
      + `; + const actual = 'li:is(:has(> img), :has(> span > img))'; + const expected = 'li#firstLi, li#thirdLi'; + expectSuccessInput({ actual, expected }); + }); + + it('has(> not)', () => { + document.body.innerHTML = ` +
        +
      • + + +
      • +
      • +
        + +
        +
      • +
      • + + + +
      • +
      + `; + const actual = 'li:has(> :not(img))'; + const expected = 'li#secondLi, li#thirdLi'; + expectSuccessInput({ actual, expected }); + }); + + it('has(nth-child)', () => { + document.body.innerHTML = ` +
        +
      • + + +
      • +
      • +
        + +
        +
      • +
      • + + + +
      • +
      + `; + const actual = 'li:has(img:nth-child(2))'; + const expected = 'li#firstLi'; + expectSuccessInput({ actual, expected }); + }); + + describe('has limitation', () => { + const toThrowInputs = [ + // no :has inside regular pseudos + { + selector: '::slotted(:has(.a))', + error: 'Usage of :has() pseudo-class is not allowed inside regular pseudo', + }, + // no :has after pseudo-elements + { + selector: '::part(foo):has(.a)', + error: 'Usage of :has() pseudo-class is not allowed after any regular pseudo-element', + }, + ]; + test.each(toThrowInputs)('%s', (input) => expectToThrowInput(input)); + }); + + describe('super stressor', () => { + beforeEach(() => { + document.body.innerHTML = ` +
      + [test] +

      [paragraph]

      +
      + test! +
      + `; + }); + afterEach(() => { + document.body.innerHTML = ''; + }); + + it('multiple selectors combined', () => { + // eslint-disable-next-line max-len + const actual = 'div[class]:not(:has(*, :contains(!)), :contains(!)), span:contains(]), p:contains(]), body > :not(:empty)'; + const expected = '#targetDiv, #spanTarget, #pTarget, #targetNotEmpty'; + expectSuccessInput({ actual, expected }); + }); + }); + + describe('complex selector with different order of compound selector in it', () => { + beforeEach(() => { + document.body.innerHTML = ` + +
      + +
      + `; + }); + afterEach(() => { + document.body.innerHTML = ''; + }); + const testsInputs = [ + { actual: 'input:disabled', expected: '#disabledInput' }, + { actual: 'input.off:disabled', expected: '#disabledInput' }, + { actual: 'input:disabled.off', expected: '#disabledInput' }, + { actual: 'input:disabled[class]', expected: '#disabledInput' }, + { actual: 'input:disabled#disabledInput', expected: '#disabledInput' }, + { actual: 'input:disabled:matches-attr(class)', expected: '#disabledInput' }, + { actual: 'input:disabled:matches-css(width: 20px)[class]', expected: '#disabledInput' }, + { actual: 'input:matches-css(width: 20px)[class]:disabled[id]', expected: '#disabledInput' }, + ]; + test.each(testsInputs)('%s', (input) => expectSuccessInput(input)); + }); + + describe('regular selector AFTER extended absolute selector', () => { + beforeEach(() => { + document.body.innerHTML = ` +
      +
      +

      text

      +
      +
      + inner span text +

      inner paragraph

      +
      +
      + +
      + +
      + +
      +
      + `; + }); + afterEach(() => { + document.body.innerHTML = ''; + }); + + const testsInputs = [ + { actual: '#inner:upward(2) .text', expected: 'p#paragraph' }, + { actual: '#inner:nth-ancestor(2) > .text', expected: 'p#paragraph' }, + { actual: '[level="3"]:upward(1) > div > *', expected: '#innerSpan, #innerParagraph' }, + { actual: '.base:upward(#root [random]) .span', expected: '#innerSpan' }, + { actual: '.base:upward(#root > [random]) .span', expected: '#innerSpan' }, + { actual: '#innerParagraph:nth-ancestor(1)> .span', expected: '#innerSpan' }, + { actual: '#inner :contains(text)+ [id]', expected: 'p#innerParagraph' }, + { actual: '#inner :contains(text)~ [id]', expected: 'p#innerParagraph' }, + { actual: 'div:has(> script.ads) *.text', expected: 'p#paragraph' }, + { actual: 'div:has(> script) + *.advert', expected: 'iframe#frameTarget' }, + { actual: 'div:has(> script) ~ *:has(*.advert)', expected: '#divAfter' }, + ]; + test.each(testsInputs)('%s', (input) => expectSuccessInput(input)); + }); + + describe('regular selector AFTER extended relative selector', () => { + beforeEach(() => { + document.body.innerHTML = ` +
      +
      +

      text

      +
      +
      + inner span text +

      inner paragraph

      +
      +
      +
      +
      + `; + }); + afterEach(() => { + document.body.innerHTML = ''; + }); + + const successInputs = [ + { actual: 'div[random]:has(span) .text', expected: 'p#paragraph' }, + { actual: 'div[random]:has(span)[random] .text', expected: 'p#paragraph' }, + { actual: '#root :not([class^="base"]) > p[class="text"]', expected: 'p#paragraph' }, + { actual: '#parent > p + [id]:not([random]) > div', expected: 'div#inner' }, + { actual: '#parent > p + *:not([random])[id] > div', expected: 'div#inner' }, + { actual: 'div:has(> span) > span.span', expected: 'span#innerSpan' }, + { actual: 'div[id="child"]:not([level="1"]) > div > span', expected: 'span#innerSpan' }, + { actual: 'div:has(> #innerParagraph)> .span', expected: 'span#innerSpan' }, + { actual: '*[id="paragraph"]:not([level])+ div > div', expected: 'div#inner' }, + { actual: '*:not([level])[id="paragraph"]+ div > div', expected: 'div#inner' }, + { actual: '*[id="paragraph"]:not([level])~ div > div', expected: 'div#inner' }, + { actual: '#parent :is(.block, .base) .span', expected: 'span#innerSpan' }, + { actual: '#parent :is(.block, .base) > .span', expected: 'span#innerSpan' }, + { actual: '#parent :is(.base, span) + p', expected: 'p#innerParagraph' }, + { actual: '#parent :is(.base, span) ~ p', expected: 'p#innerParagraph' }, + ]; + test.each(successInputs)('%s', (input) => expectSuccessInput(input)); + }); + + describe('combined after combined with any combinator between', () => { + beforeEach(() => { + document.body.innerHTML = ` + +
      +
      +

      text

      +
      +
      + inner span text +

      inner paragraph

      +
      +
      +
      + s2222222 +

      inP2

      +
      + +
      +
      +

      p2222

      +
      +
      + `; + }); + afterEach(() => { + document.body.innerHTML = ''; + }); + + const successInputs = [ + { + actual: 'div:has(> div:matches-attr(/^level$/=2) > div:matches-attr("/class/"="/base/"))', + expected: 'div#parent', + }, + { + actual: '* > p + div:matches-css(width:20px) ~ div > *:has(> input)', + expected: 'div#deep2', + }, + { + // eslint-disable-next-line max-len + actual: ':matches-css(width:20px) + :matches-attr(class=/2/):has(+ :matches-attr(id=/child/):contains(p2222))', + expected: 'div#child2', + }, + { + actual: ':is([id^="child"]) > :is(div:not([class])) > input[id]', + expected: 'input#disabledInput', + }, + { + actual: ':is([id^="child"]) > :is(*:not([class]) > input[id])', + expected: 'div#deep2', + }, + { + actual: '#parent div:contains(inner span text) + .base2:contains(inP2)', + expected: 'div#child2', + }, + + { + actual: '#parent > *:not(p):not(.base2) > div p:contains(inner paragraph)', + expected: 'p#innerParagraph', + }, + { + actual: '#root > div[random]:matches-attr("/level/") > div > div:has(> input:disabled)', + expected: 'div#deep2', + }, + { + actual: '#parent > div:not(.base2)> div[class] > span[class] + p[id]:upward(2)', + expected: 'div#child', + }, + { + actual: '#parent > p[id][class] ~ div +p:not([class]):not([level])', + expected: 'p#childParagraph', + }, + { + actual: '#parent > .base ~ div[class*="2"]:not(.clear) > div > input:only-child:upward(2)', + expected: 'div#child2', + }, + { + actual: '#child2>p[id]:not([class])~div[id]:not([class])', + expected: 'div#deep2', + }, + ]; + test.each(successInputs)('%s', (input) => expectSuccessInput(input)); + }); + + it('un-tokenizable complex selector testcase', () => { + document.body.innerHTML = ` +

      as text { position: absolute; top: -2500px; }

      +
      + + +
      + `; + // eslint-disable-next-line max-len + const actual = '*:contains(/absolute[\\s\\S]*-\\d{4}/) + * > .banner:contains(/а/) ~ #case17.banner:has(> div:nth-child(100n + 2):contains(/а/))'; + const expected = 'div#case17'; + expectSuccessInput({ actual, expected }); + document.body.innerHTML = ''; + }); +}); + +describe('check invalid selectors', () => { + describe('not a valid selectors', () => { + const invalidInputs = [ + 'foo\nbaz', + 'foo\\\fbaz', + 'foo\\\\\rbaz', + '[\nrel = bookmark\t]', + ',a', + 'a,', + 'a:has(.ad),', + '(', + '()', + ',', + '[', + '{', + '<', + '<>', + '{}', + '[id=012345678901234567890123456789', + ':nth-child(2+0)', + ':nth-child(- 1n)', + ':first-child(n)', + ':last-child(n)', + ':only-child(n)', + 'input[name=]', + 'input[name=foo.baz]', + 'input[name=foo[baz]]', + "input[name=''double-quoted'']", + "input[name='apostrophe'd']", + ':lang(c++)', + ]; + const error = 'is not a valid selector'; + test.each(invalidInputs)('%s', (selector) => expectToThrowInput({ selector, error })); + }); + + describe('unknown pseudo-classes', () => { + const unknownPseudoInputs = [ + '*,:x', + ':visble', + ':nth-child', + ':nth-child(2n+-0)', + ':nth-child(-1 n)', + ':nth-last-last-child(1)', + ':first-last-child', + ':last-last-child', + ':only-last-child', + ]; + const error = 'unknown pseudo-class selector'; + test.each(unknownPseudoInputs)('%s', (selector) => expectToThrowInput({ selector, error })); + }); +}); + +describe('check case-insensitive attributes selecting', () => { + // https://github.com/AdguardTeam/ExtendedCss/issues/104 + beforeEach(() => { + document.body.innerHTML = ` +
      +
      +
      + + SLOT online AD abd1 +
      + `; + }); + afterEach(() => { + document.body.innerHTML = ''; + }); + + const successInputs = [ + // regular selectors + { actual: 'body div[class="case" i]', expected: '#target' }, + { actual: 'div[class="case" i]', expected: '#target' }, + { actual: 'div[class=case i]', expected: '#target' }, + { actual: 'div[class="cAsE" i]', expected: '#target' }, + { actual: '.case[tracking*="ad-"][event*="-sticky" i]', expected: '#target' }, + { actual: 'div[data-st-area*="backTo" i], div[data-st-area*="goToSG" i]', expected: '#child, #inner' }, + { actual: 'img[alt^="slot online ad" i]', expected: '#img' }, + // extended selectors + { actual: 'div:has(div[class="page" i])', expected: '#target' }, + { actual: 'div[id*="TAR" i] a[href][target="_blank"]:is([href*="example.COM" i])', expected: '#anchor' }, + ]; + test.each(successInputs)('%s', (input) => expectSuccessInput(input)); + + // jsdom does not support 'I' flag by document.querySelectorAll() + // e.g. 'div[class="cAsE" I]'; +}); + +describe('check valid regular selectors', () => { + afterEach(() => { + document.body.innerHTML = ''; + }); + + describe('should be trimmed', () => { + document.body.innerHTML = ` +
      +

      text

      +
      + `; + const actualSelectors = [ + ' #test p', + ' #test p', + '\t#test p', + '\r#test p', + '\n#test p', + '\f#test p', + '#test p ', + '#test p ', + '#test p\t', + '#test p\r', + '#test p\n', + '#test p\f', + ]; + const expected = '#test p'; + test.each(actualSelectors)('%s', (actual) => expectSuccessInput({ actual, expected })); + }); +}); + +describe('case-insensitivity for pseudo-class names', () => { + document.body.innerHTML = ` +
      +
      +

      text

      +
      +
      +
      +
      +
      + `; + + const testInputs = [ + { actual: 'div.base[level="3"]:UPWARD([level="0"])', expected: 'div#root' }, + { actual: 'div.base[LEVEL="3"]:UPWARD([level="0"])', expected: 'div#root' }, + { actual: 'div:HAS(> #paragraph)', expected: 'div#parent' }, + { actual: '#root p:CONTAINS(text)', expected: 'div#paragraph' }, + { actual: '#parent *:NOT([class])', expected: 'div#paragraph' }, + { actual: '#parent *:NOT([CLASS]):CONTAINS(text)', expected: 'div#paragraph' }, + ]; + test.each(testInputs)('%s', (input) => expectSuccessInput(input)); +}); diff --git a/test/selector/query-playwright.test.ts b/test/selector/query-playwright.test.ts new file mode 100644 index 00000000..bfaf6184 --- /dev/null +++ b/test/selector/query-playwright.test.ts @@ -0,0 +1,221 @@ +/** + * Pseudo-elements are not supported by jsdom + * so playwright is required for matches-css-before and matches-css-after selector tests. + * + * @see {@link https://github.com/jsdom/jsdom/issues/1928} + */ + +import { chromium, Browser, Page } from 'playwright'; + +import server from '../helpers/server'; + +let browser: Browser; +let page: Page; + +// sometimes default 5 seconds are not enough +const TESTS_RUN_TIMEOUT_MS = 10 * 1000; + +/** + * Sets document.body.innerHTML with passed htmlContent. + * + * @param htmlContent Inner html content. + */ +const setBodyInnerHtml = async (htmlContent: string): Promise => { + await page.evaluate((bodyInnerHtml) => { + document.body.innerHTML = bodyInnerHtml; + }, htmlContent); +}; + +declare global { + const testExtCss: { + querySelectorAll(selector: string, document: Document): HTMLElement[]; + }; +} + +/** + * Returns elements ids selected by extCss.querySelectorAll. + * + * @param extCssSelector Selector for extended css. + */ +const getIdsByExtended = async (extCssSelector: string): Promise => { + return page.evaluate((selector: string): string[] => { + return testExtCss.querySelectorAll(selector, document).map((el: Element) => el.id); + }, extCssSelector); +}; + +/** + * Returns elements ids selected by document.querySelectorAll(). + * + * @param regularSelector Standard selector. + */ +const getIdsByRegular = async (regularSelector: string): Promise => { + return page.evaluate((selector) => { + return Array.from(document.querySelectorAll(selector)).map((el) => el.id); + }, regularSelector); +}; + +/** + * Checks whether there is no elements selected by extCssSelector. + * + * @param extCssSelector Extended css selector. + */ +const expectNoMatch = async (extCssSelector: string): Promise => { + const selectedIds = await getIdsByExtended(extCssSelector); + expect(selectedIds.length).toEqual(0); +}; + +jest.setTimeout(TESTS_RUN_TIMEOUT_MS); + +describe('playwright required tests', () => { + describe('matches-css pseudos', () => { + beforeAll(async () => { + await server.start(); + browser = await chromium.launch(); + // can be useful for debugging + // browser = await chromium.launch({ headless: false }); + }); + afterAll(async () => { + await browser?.close(); + await server?.stop(); + }); + + beforeEach(async () => { + page = await browser.newPage(); + await page.goto('http://localhost:8585/empty.html'); + }); + afterEach(async () => { + await page?.close(); + }); + + it('matches-css - simple', async () => { + const bodyInnerHtml = ` + + +
      + `; + await setBodyInnerHtml(bodyInnerHtml); + + const targetSelector = 'div#target'; + let extCssSelector; + + extCssSelector = ':matches-css(width:20px)'; + expect(await getIdsByExtended(extCssSelector)).toEqual(await getIdsByRegular(targetSelector)); + + extCssSelector = ':matches-css(content: *find me*)'; + expect(await getIdsByExtended(extCssSelector)).toEqual(await getIdsByRegular(targetSelector)); + + extCssSelector = 'div:matches-css(min-height:/10/):matches-css(height:/10|15|20/)'; + expect(await getIdsByExtended(extCssSelector)).toEqual(await getIdsByRegular(targetSelector)); + + // should NOT match because height is 15px + extCssSelector = 'div:matches-css(min-height:/10/):matches-css(height:/10|20/)'; + await expectNoMatch(extCssSelector); + }); + + it('matches-css + before', async () => { + const bodyInnerHtml = ` +
      + +
      + `; + await setBodyInnerHtml(bodyInnerHtml); + + const targetSelector = 'div#target'; + let extCssSelector; + + // old syntax + extCssSelector = 'div:matches-css-before(color: rgb(255, 255, 255))'; + expect(await getIdsByExtended(extCssSelector)).toEqual(await getIdsByRegular(targetSelector)); + + // new syntax + extCssSelector = 'div:matches-css(before, color: rgb(255, 255, 255))'; + expect(await getIdsByExtended(extCssSelector)).toEqual(await getIdsByRegular(targetSelector)); + + extCssSelector = 'div:matches-css(before,content: /^Advertisement$/)'; + expect(await getIdsByExtended(extCssSelector)).toEqual(await getIdsByRegular(targetSelector)); + }); + + it('matches-css + after', async () => { + const bodyInnerHtml = ` + + +
      + `; + await setBodyInnerHtml(bodyInnerHtml); + + const targetSelector = 'div#target'; + let extCssSelector; + + // old syntax + extCssSelector = 'div:matches-css-after(color: rgb(255, 255, 255))'; + expect(await getIdsByExtended(extCssSelector)).toEqual(await getIdsByRegular(targetSelector)); + + extCssSelector = 'div:matches-css(after, content: /^Advertisement$/)'; + expect(await getIdsByExtended(extCssSelector)).toEqual(await getIdsByRegular(targetSelector)); + + extCssSelector = 'div:matches-css(after, content: advertisement)'; + expect(await getIdsByExtended(extCssSelector)).toEqual(await getIdsByRegular(targetSelector)); + + extCssSelector = 'div:matches-css(after,content: advert*)'; + expect(await getIdsByExtended(extCssSelector)).toEqual(await getIdsByRegular(targetSelector)); + }); + + it('matches-css + first-line', async () => { + const bodyInnerHtml = ` + + +

      + `; + await setBodyInnerHtml(bodyInnerHtml); + + const targetSelector = 'p#target'; + let extCssSelector; + + extCssSelector = 'p:matches-css(first-line, color: rgb(255, 255, 255))'; + expect(await getIdsByExtended(extCssSelector)).toEqual(await getIdsByRegular(targetSelector)); + + extCssSelector = 'p:matches-css(first-line, word-spacing: *px)'; + expect(await getIdsByExtended(extCssSelector)).toEqual(await getIdsByRegular(targetSelector)); + }); + }); +}); diff --git a/test/selector/selector.html b/test/selector/selector.html deleted file mode 100644 index ce4e00f0..00000000 --- a/test/selector/selector.html +++ /dev/null @@ -1,267 +0,0 @@ - - - - - - - ExtendedSelector tests - - - -
      -
      - -
      - -
      -

      Test template

      - -

      Use this document as a way to quickly start any new project.
      All you get is this text and a - mostly barebones HTML document.

      - - -
      - -
      -
      -

      some text header

      -
      -
      -
      -
      -
      - -
      -
      -
      -
      -
      -
      -
      - - -
      -
      -
      -
      -
      - -
      - -
      - - -
      -
      -
      -
      -
      -
      -
      -
      -
      - test-xpath-content -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      - the test target -
      -
      -
      -
      -
      - upward contains test -
      -
      -
      -
      -
      - -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      - remove me -
      -
      - remove me -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      - -
      -
      -
      -
      -
      -
      -
      -
      -
      - -
      -
      -
      -
      -
      -
      -
      -
      -
      - - - - -
      -
      -
      -
      -
      click here
      -
      -
      -
      - -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      - -
      -
      -
      -
      - isistest -
      -
      -
      -
      -
      - isistest -
      -
      - isistest -
      -
      -
      - -
      -
      - -
      - - - - - - diff --git a/test/selector/selector.test.js b/test/selector/selector.test.js deleted file mode 100644 index f219d750..00000000 --- a/test/selector/selector.test.js +++ /dev/null @@ -1,832 +0,0 @@ -/** - * Copyright 2016 Performix LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* eslint-disable max-len */ - -const { ExtendedSelectorFactory } = exports; - -QUnit.test('Test ExtendedSelector', (assert) => { - const checkElements = function (elements, selector) { - for (let i = 0; i < elements.length; i++) { - assert.ok(selector.matches(elements[i])); - } - }; - - let elements; - let selector; - - selector = ExtendedSelectorFactory.createSelector('div a[-ext-contains="adg-test"]'); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); - - selector = ExtendedSelectorFactory.createSelector('div.test-class[-ext-has="time.g-time"]'); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); - - selector = ExtendedSelectorFactory.createSelector('div#test-div[-ext-has="test"]'); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 0); - - elements = ExtendedSelectorFactory.createSelector('[-ext-has="div.advert"]').querySelectorAll(); - assert.equal(elements.length, 0); - - selector = ExtendedSelectorFactory.createSelector('[-ext-has="div.test-class-two"]'); - elements = selector.querySelectorAll(); - - assert.equal(elements.length, 5); - checkElements(elements, selector); - - selector = ExtendedSelectorFactory.createSelector('div[-ext-contains="adg-test"][-ext-has="div.test-class-two"]'); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 3); - checkElements(elements, selector); - - selector = ExtendedSelectorFactory.createSelector('div[-ext-contains="adg-test"][-ext-has="div.test-class-two"][i18n]'); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - checkElements(elements, selector); - - selector = ExtendedSelectorFactory.createSelector('div[-ext-has="div.test-class-two"]'); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 3); - checkElements(elements, selector); - - selector = ExtendedSelectorFactory.createSelector('div[-ext-has="div.test-class-two"] > .test-class[-ext-contains="adg-test"]'); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - checkElements(elements, selector); -}); - -QUnit.test('Test -ext-matches-css', (assert) => { - let selector; - let elements; - // Compatible syntax + no quotes for url - selector = ExtendedSelectorFactory.createSelector('#test-matches-css div[-ext-matches-css="background-image: url(data:*)"]'); - elements = selector.querySelectorAll(); - - assert.equal(elements.length, 1); - assert.equal(elements[0], document.getElementById('test-div-background')); - - // Standard syntax + quotes for url - selector = ExtendedSelectorFactory.createSelector('#test-matches-css div:matches-css(background-image: url("data:*"))'); - elements = selector.querySelectorAll(); - - assert.equal(elements.length, 1); - assert.equal(elements[0], document.getElementById('test-div-background')); - - // regex + strict quotes for url - selector = ExtendedSelectorFactory.createSelector('#test-matches-css div:matches-css(background-image: /^url\\("data:image\\/gif;base64.+/)'); - elements = selector.querySelectorAll(); - - assert.equal(elements.length, 1); - assert.equal(elements[0], document.getElementById('test-div-background')); - - // regex + optional quotes for url - selector = ExtendedSelectorFactory.createSelector('#test-matches-css div:matches-css(background-image: /^url\\("?data:image\/gif;base64.+/)'); - elements = selector.querySelectorAll(); - - assert.equal(elements.length, 1); - assert.equal(elements[0], document.getElementById('test-div-background')); - - // regex + no quotes for url - selector = ExtendedSelectorFactory.createSelector('#test-matches-css div:matches-css(background-image: /^url\\([a-z]{4}:[a-z]{5}/)'); - elements = selector.querySelectorAll(); - - assert.equal(elements.length, 1); - assert.equal(elements[0], document.getElementById('test-div-background')); -}); - -QUnit.test('Test -ext-matches-css with opacity property', (assert) => { - // Compatible syntax - let selector = ExtendedSelectorFactory.createSelector('#test-opacity-property[-ext-matches-css="opacity: 0.9"]'); - let elements = selector.querySelectorAll(); - - assert.equal(elements.length, 1); - assert.equal(elements[0], document.getElementById('test-opacity-property')); - - // Standard syntax - selector = ExtendedSelectorFactory.createSelector('#test-opacity-property:matches-css(opacity: 0.9)'); - elements = selector.querySelectorAll(); - - assert.equal(elements.length, 1); - assert.equal(elements[0], document.getElementById('test-opacity-property')); -}); - -QUnit.test('Test -ext-matches-css-before', (assert) => { - // Compatible syntax - let selector = ExtendedSelectorFactory.createSelector('#test-matches-css div[-ext-matches-css-before="content: *find me*"]'); - let elements = selector.querySelectorAll(); - - assert.equal(elements.length, 1); - assert.equal(elements[0], document.getElementById('test-div-before')); - - // Standard syntax - selector = ExtendedSelectorFactory.createSelector('#test-matches-css div:matches-css-before(content: *find me*)'); - elements = selector.querySelectorAll(); - - assert.equal(elements.length, 1); - assert.equal(elements[0], document.getElementById('test-div-before')); -}); - -QUnit.test('Test -ext-matches-css-after', (assert) => { - // Compatible syntax - let selector = ExtendedSelectorFactory.createSelector('#test-matches-css div[-ext-matches-css-after="content: *find me*"]'); - let elements = selector.querySelectorAll(); - - assert.equal(elements.length, 1); - assert.equal(elements[0], document.getElementById('test-div-after')); - - // Standard syntax - selector = ExtendedSelectorFactory.createSelector('#test-matches-css div:matches-css-after(content: *find me*)'); - elements = selector.querySelectorAll(); - - assert.equal(elements.length, 1); - assert.equal(elements[0], document.getElementById('test-div-after')); -}); - -QUnit.test('Test tokenize selector', (assert) => { - let selectorText = '#test'; - let compiled = ExtendedSelectorFactory.createSelector(selectorText); - assert.notOk(compiled.simple); - assert.notOk(compiled.relation); - assert.notOk(compiled.complex); - - selectorText = "div span.className > a[href^='http'] > #banner"; - compiled = ExtendedSelectorFactory.createSelector(selectorText); - assert.notOk(compiled.simple); - assert.notOk(compiled.relation); - assert.notOk(compiled.complex); - - selectorText = "div span.className + a[href^='http'] ~ #banner"; - compiled = ExtendedSelectorFactory.createSelector(selectorText); - assert.notOk(compiled.simple); - assert.notOk(compiled.relation); - assert.notOk(compiled.complex); - - selectorText = '#banner div:first-child > div:has(.banner)'; - compiled = ExtendedSelectorFactory.createSelector(selectorText); - assert.equal(compiled.simple, '#banner div:first-child'); - assert.equal(compiled.relation, '>'); - assert.equal(compiled.complex, 'div:has(.banner)'); - - selectorText = '#banner div:first-child ~ div:has(.banner)'; - compiled = ExtendedSelectorFactory.createSelector(selectorText); - assert.equal(compiled.simple, '#banner div:first-child'); - assert.equal(compiled.relation, '~'); - assert.equal(compiled.complex, 'div:has(.banner)'); - - selectorText = '#banner div:first-child > div > :has(.banner) > div'; - compiled = ExtendedSelectorFactory.createSelector(selectorText); - assert.notEqual(compiled.constructor.name, 'SplittedSelector'); - assert.equal(compiled.selectorText, selectorText); - - selectorText = '#banner div:first-child > div + :has(.banner) > div'; - compiled = ExtendedSelectorFactory.createSelector(selectorText); - assert.notEqual(compiled.constructor.name, 'SplittedSelector'); - assert.equal(compiled.selectorText, selectorText); - - selectorText = '#banner :not(div) div:matches-css(background: blank)'; - compiled = ExtendedSelectorFactory.createSelector(selectorText); - assert.equal(compiled.simple, '#banner :not(div)'); - assert.equal(compiled.relation, ' '); - assert.equal(compiled.complex, 'div:matches-css(background: blank)'); -}); - -QUnit.test('Test regular expressions support in :contains', (assert) => { - const selectorText = '*[-ext-contains=\'/\\s[a-t]{8}$/\'] + *:contains(/^[^\\"\\\'"]{30}quickly/)'; - const selector = ExtendedSelectorFactory.createSelector(selectorText); - const elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); -}); - -QUnit.test('Test regular expressions flags support in :contains', (assert) => { - let elements; - let selector; - let selectorText; - - selectorText = 'p:contains(/Quickly/)'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 0); - - selectorText = 'p:contains(/quickly/)'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - - selectorText = 'p:contains(/Quickly/i)'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - - selectorText = 'p:contains(/Quickly/gmi)'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); -}); - -QUnit.test('Test regular expressions support in :matches-css', (assert) => { - // var selectorText = ':matches-css( background-image: /^url\\((.)[a-z]{4}:[a-z]{2}\\1nk\\)$/ ) + [-ext-matches-css-before=\'content: /^[A-Z][a-z]{2}\\s/ \'][-ext-has=\'+:matches-css-after( content : /(\\d+\\s)*me/ ):contains(/^(?![\\s\\S])/)\']'; - const selectorText = ':matches-css( background-image: /^url\\(\\"[a-z]{4}:[a-z]{5}\\/[gif;base].*\\"\\)$/ ) + [-ext-matches-css-before=\'content: /^[A-Z][a-z]{2}\\s/ \'][-ext-has=\'+:matches-css-after( content : /(\\d+\\s)*me/ ):contains(/^(?![\\s\\S])/)\']'; - const selector = ExtendedSelectorFactory.createSelector(selectorText); - const elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); -}); - -QUnit.test('Test simple regex support in :matches-css, when ()[] characters are escaped', (assert) => { - const selectorText = ':matches-css(background-image:url\(data:*\))'; - const selector = ExtendedSelectorFactory.createSelector(selectorText); - const elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); -}); - -QUnit.test('Test -abp-has and -abp-has-text', (assert) => { - let elements; - let selector; - - selector = ExtendedSelectorFactory.createSelector('div.test-class:-abp-has(time.g-time)'); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); - - selector = ExtendedSelectorFactory.createSelector('div:-abp-has(div.test-class-two) > .test-class:-abp-contains(adg-test)'); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); -}); - -QUnit.test('Pseudo -abp-contains class', (assert) => { - const selector = ExtendedSelectorFactory.createSelector('#test-abp-pseudo div:-abp-contains(some text) div:-abp-has(div.test-class)'); - const elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); -}); - -QUnit.test('Test if and if-not', (assert) => { - let elements; - let selector; - - selector = ExtendedSelectorFactory.createSelector('div.test-class:if(time.g-time)'); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); - - selector = ExtendedSelectorFactory.createSelector('#test-if-not > *:if-not(> .test-class)'); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 2); - assert.ok(selector.matches(elements[0])); -}); - -QUnit.test('Test :is()', (assert) => { - let elements; - let selector; - - selector = ExtendedSelectorFactory.createSelector('#test-is :is(.header, .body, .footer) .test-is-inner'); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 3); - assert.equal(elements[0].id, 'test-is--header-inner'); - assert.equal(elements[1].id, 'test-is--body-inner'); - assert.equal(elements[2].id, 'test-is--footer-inner'); - - selector = ExtendedSelectorFactory.createSelector('#test-is :is(.header, .body) > .test-is-inner'); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.equal(elements[0].id, 'test-is--header-inner'); - - selector = ExtendedSelectorFactory.createSelector('#test-is :is(.header, .body) > div > .test-is-inner'); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.equal(elements[0].id, 'test-is--body-inner'); - - selector = ExtendedSelectorFactory.createSelector('#test-is :is(.header, .main) > div > .test-is-inner'); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 0); - - selector = ExtendedSelectorFactory.createSelector('#test-is :is(.main, div[id$="footer"]) .test-is-inner'); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.equal(elements[0].id, 'test-is--footer-inner'); - - selector = ExtendedSelectorFactory.createSelector(':is(.test-is-inner, .test-is-inner2):contains(isistest)'); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 3); - assert.equal(elements[0].id, 'test-is--header-inner'); - assert.equal(elements[1].id, 'test-is--body-inner'); - assert.equal(elements[2].id, 'test-is--body-inner2'); - - selector = ExtendedSelectorFactory.createSelector(':is([id^="test-is"]) > :is(div:not([class]) > .test-is-inner)'); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.equal(elements[0].id, 'test-is--body-inner'); - - // invalid selector but it should not fail, just skip - selector = ExtendedSelectorFactory.createSelector('#test-is :is(id="123") > .test-is-inner'); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 0); - - selector = ExtendedSelectorFactory.createSelector('#test-is :is(1) > .test-is-inner'); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 0); -}); - -QUnit.test('Test :is() validation', (assert) => { - let selectorText; - - assert.throws(() => { - selectorText = ':is()'; - ExtendedSelectorFactory.createSelector(selectorText); - }, 'Expected to be invalid rule -- no arg'); - - assert.throws(() => { - selectorText = 'div:is():has(> a)'; - ExtendedSelectorFactory.createSelector(selectorText); - }, 'Expected to be invalid rule -- no arg'); -}); - -QUnit.test('Test + and ~ combinators matching', (assert) => { - let selectorText; let selector; let - elements; - - selectorText = "* > p ~ #test-id-div a:contains('adg-test')"; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); - - selectorText = '* > div + style:matches-css(display:none) ~ div > *:matches-css-after(content:/y\\st/)'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); - - selectorText = "* > .lead ~ div:has(a[href^='/t'])"; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); - - selectorText = "* > .lead + div:has(a[href^='/t'])"; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); -}); - -QUnit.test('Test xpath / nth-ancestor / upward', (assert) => { - let selectorText; let selector; let elements; - - selectorText = 'div:xpath(//*[@class="test-xpath-class"])'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); - assert.equal(elements[0].id, 'test-xpath-class-div'); - - selectorText = 'div:xpath(//*[@class="test-xpath-div-inner-class"]/../..)'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); - assert.equal(elements[0].id, 'test-xpath-div'); - - selectorText = ':xpath(//div[contains(text(),"test-xpath-content")]/../..)'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); - assert.equal(elements[0].id, 'test-xpath-content-div'); - - selectorText = '.test-xpath-div-inner-class:xpath(../../..)'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); - assert.equal(elements[0].id, 'test-xpath'); - - selectorText = '.test-xpath-content-class:has-text(/test-xpath-content/):xpath(../../..)'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); - assert.equal(elements[0].id, 'test-xpath'); - - selectorText = 'div:has-text(/test-xpath-content/):xpath(../../..)'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 5); - assert.ok(selector.matches(elements[0])); - assert.ok(selector.matches(elements[1])); - assert.ok(selector.matches(elements[2])); - assert.equal(elements[4].id, 'test-xpath'); - - selectorText = 'div.test-nth-ancestor-marker:nth-ancestor(4)'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 2); - assert.ok(selector.matches(elements[0])); - assert.ok(selector.matches(elements[1])); - assert.equal(elements[0].id, 'test-nth-ancestor-div'); - - selectorText = 'div.test-upward-marker:upward(2)'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 2); - assert.ok(selector.matches(elements[0])); - assert.ok(selector.matches(elements[1])); - assert.equal(elements[0].id, 'test-upward-div'); - - selectorText = '.test-upward-selector:upward(div[id])'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); - assert.equal(elements[0].id, 'test-upward-div'); - - selectorText = '.test-upward-selector:upward(div[class^="test-upward-"])'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); - assert.equal(elements[0].className, 'test-upward-marker'); - - selectorText = 'div:contains(upward contains):upward(div[id][class])'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); - assert.equal(elements[0].id, 'test-upward-div'); -}); - -QUnit.test('Test xpath validation', (assert) => { - let selectorText; - - assert.throws(() => { - selectorText = 'div:xpath()'; - ExtendedSelectorFactory.createSelector(selectorText); - }, 'Expected to be invalid rule -- xpath should get an arg'); - - assert.throws(() => { - selectorText = 'div:xpath(../..):has-text(/test-xpath-content/)'; - ExtendedSelectorFactory.createSelector(selectorText); - }, 'Expected to be invalid rule -- xpath should be at the end of rule'); - - assert.throws(() => { - selectorText = 'div:nth-ancestor(invalid)'; - ExtendedSelectorFactory.createSelector(selectorText); - }, 'Expected to be invalid rule -- nth-ancestor should get a number arg'); - - assert.throws(() => { - selectorText = 'div:nth-ancestor(0)'; - ExtendedSelectorFactory.createSelector(selectorText); - }, 'Expected to be invalid rule -- nth-ancestor arg should be more that 0 and less that 256'); - - assert.throws(() => { - selectorText = 'div:nth-ancestor(2):has-text(/test-xpath-content/)'; - ExtendedSelectorFactory.createSelector(selectorText); - }, 'Expected to be invalid rule -- nth-ancestor should be at the end of rule'); - - assert.throws(() => { - selectorText = 'div:upward()'; - ExtendedSelectorFactory.createSelector(selectorText); - }, 'Expected to be invalid rule -- upward should get an arg'); - - assert.throws(() => { - selectorText = 'div:upward(0)'; - ExtendedSelectorFactory.createSelector(selectorText); - }, 'Expected to be invalid rule -- upward arg should be more that 0 and less that 256'); - - assert.throws(() => { - selectorText = 'div:upward(3):has-text(/test-xpath-content/)'; - ExtendedSelectorFactory.createSelector(selectorText); - }, 'Expected to be invalid rule -- upward should be at the end of rule'); -}); - -QUnit.test('Test remove pseudo-class', (assert) => { - let selectorText; let selector; let elements; - - selectorText = 'div#test-remove #test-remove-inner-id:remove()'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); - assert.equal(elements[0].id, 'test-remove-inner-id'); - - selectorText = 'div[id*="remove"]:has(> div > .test-remove-inner-class):remove()'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); - assert.equal(elements[0].id, 'test-remove-div-for-has'); - - selectorText = '#test-remove-div-for-contains div[class]:contains(remove me):remove()'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); - assert.equal(elements[0].className, 'test-remove-inner-with-text'); - - selectorText = '#test-remove-inner-for-upward:upward(div[id]):remove()'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); - assert.equal(elements[0].id, 'test-remove-with-xpath-upward'); - - selectorText = '#test-remove-inner-for-xpath-pseudo:xpath(../../..):remove()'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); - assert.equal(elements[0].id, 'test-remove-with-xpath-upward'); -}); - -QUnit.test('Test remove validation', (assert) => { - let selectorText; - - assert.throws(() => { - selectorText = 'div:remove():has-text(/test-content/)'; - ExtendedSelectorFactory.createSelector(selectorText); - }, 'Expected to be invalid rule -- remove is not in the end'); - - assert.throws(() => { - selectorText = 'div:remove(0)'; - ExtendedSelectorFactory.createSelector(selectorText); - }, 'Expected to be invalid rule -- remove should not get arg'); - - assert.throws(() => { - selectorText = 'div:not([class]):remove(invalid)'; - ExtendedSelectorFactory.createSelector(selectorText); - }, 'Expected to be invalid rule -- remove should not get arg'); -}); - -QUnit.test('Test matches-attr', (assert) => { - let selectorText; let selector; let elements; - - selectorText = '#test-matches-attr div:matches-attr("data-o")'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.equal(elements[0], document.getElementById('test-matches-attr-strict')); - - selectorText = '#test-matches-attr div:matches-attr("/data-o/")'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 3); - assert.equal(elements[0], document.getElementById('test-matches-attr-strict')); - assert.equal(elements[1], document.getElementById('test-matches-attr-one')); - assert.equal(elements[2], document.getElementById('test-matches-attr-one-test')); - - selectorText = '#test-matches-attr div:matches-attr("test-data"="no_click")'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.equal(elements[0], document.getElementById('test-matches-attr-test-data-match')); - - selectorText = '#test-matches-attr div:matches-attr("test-data"="/banner/")'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.equal(elements[0], document.getElementById('test-matches-attr-test-data')); - - selectorText = '#test-matches-attr div:matches-attr("/data-/"="/click here/")'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 5); - assert.equal(elements[0], document.getElementById('test-matches-attr-one')); - - selectorText = '#test-matches-attr div:matches-attr("/^data-.{4}$/"="/click here/")'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.equal(elements[0], document.getElementById('test-matches-attr-last')); - - selectorText = '#test-matches-attr div:matches-attr("data-one"="/^click\\shere.{1,}?banner.{1,}?$/")'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.equal(elements[0], document.getElementById('test-matches-attr-one')); - - selectorText = '#test-matches-attr div:has(> div:matches-attr("/id/"="/R-A-/") > div:matches-attr("/data-bem/"="/src:/"))'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.equal(elements[0], document.getElementById('test_matches-attr_has')); - - selectorText = '#test-matches-attr *[id^="unit-"][class] > *:matches-attr("/class/"="/^.{6,8}$/"):matches-attr("/.{5,}delay$/"="/^[0-9]*$/"):upward(3)'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.equal(elements[0], document.getElementById('test_matches-attr_upward')); - - selectorText = '#test-matches-attr div:matches-attr("/-link/"="/-banner_/"):contains(click here):xpath(../..)'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.equal(elements[0], document.getElementById('test_matches-attr_contains_xpath')); -}); - -QUnit.test('Test matches-attr validation', (assert) => { - let selectorText; - - assert.throws(() => { - selectorText = 'div:matches-attr()'; - ExtendedSelectorFactory.createSelector(selectorText); - }, 'Expected to be invalid rule -- no pseudo arg'); - - assert.throws(() => { - selectorText = 'div:matches-attr(")'; - ExtendedSelectorFactory.createSelector(selectorText); - }, 'Expected to be invalid rule -- invalid arg'); - - assert.throws(() => { - selectorText = 'div:matches-attr("")'; - ExtendedSelectorFactory.createSelector(selectorText); - }, 'Expected to be invalid rule -- invalid arg'); - - assert.throws(() => { - selectorText = 'div:matches-attr(> [track="true"])'; - ExtendedSelectorFactory.createSelector(selectorText); - }, 'Expected to be invalid rule -- invalid arg'); - - assert.throws(() => { - selectorText = 'div:matches-attr(".?"="/^[0-9]*$/")'; - ExtendedSelectorFactory.createSelector(selectorText); - }, 'Expected to be invalid rule -- first is not a regexp'); - - assert.throws(() => { - selectorText = 'div:matches-attr("//"="/./")'; - ExtendedSelectorFactory.createSelector(selectorText); - }, 'Expected to be invalid rule -- first is not a regexp'); -}); - -QUnit.test('Test matches-property pseudo-class', (assert) => { - let selectorText; let selector; let elements; - - const testEl = document.querySelector('#test-matches-property-div'); - const testElems = document.querySelectorAll('#test-matches-property div[id][class]'); - const testInnerElems = document.querySelectorAll('#test-matches-property div[id^="test-matches-property-inner"]'); - - const testPropName = '_testProp'; - const testPropValue = '123'; - testEl[testPropName] = testPropValue; - selectorText = `div#test-matches-property div:matches-property("${testPropName}")`; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); - assert.equal(elements[0].id, 'test-matches-property-div'); - - const propFirst = 'propFirst'; - const propInner = { propInner: null }; - testEl[propFirst] = propInner; - selectorText = 'div#test-matches-property div:matches-property("propFirst.propInner"="null")'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); - assert.equal(elements[0].id, 'test-matches-property-div'); - - // access child prop of null prop - selectorText = 'div#test-matches-property div:matches-property("propFirst.propInner.test")'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 0); - - const propNullAsStr = 'propNullStr'; - const propInnerNullStr = { propInner: 'null' }; - testEl[propNullAsStr] = propInnerNullStr; - selectorText = 'div#test-matches-property div:matches-property("propNullStr.propInner"="null")'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); - assert.equal(elements[0].id, 'test-matches-property-div'); - - const propUndefAsStr = 'propNullStr'; - const propInnerUndefStr = { propInner: 'undefined' }; - testEl[propUndefAsStr] = propInnerUndefStr; - selectorText = 'div#test-matches-property div:matches-property("propNullStr.propInner"="undefined")'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); - assert.equal(elements[0].id, 'test-matches-property-div'); - - const abcProp = 'abcProp'; - const propInnerOne = { propInnerOne: 111 }; - const propInnerTwo = { propInnerTwo: 222 }; - testElems[0][abcProp] = propInnerOne; - testElems[1][abcProp] = propInnerTwo; - selectorText = 'div#test-matches-property div[id][class]:matches-property("abcProp.propInnerOne")'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); - assert.equal(elements[0].id, 'test-matches-property-div'); - - const aProp = 'aProp'; - const propInnerValueOne = { val: 111 }; - const propInnerValueTwo = { val: 222 }; - testElems[0][aProp] = propInnerValueOne; - testElems[1][aProp] = propInnerValueTwo; - selectorText = 'div#test-matches-property div[id][class]:matches-property("aProp.val"="222")'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); - assert.equal(elements[0].id, 'test-matches-property-div-two'); - - const bProp = 'bProp'; - const cProp = 'cProp'; - const bPropInner = { val: 1234 }; - const cPropInner = { val: 1223 }; - testElems[0][bProp] = bPropInner; - testElems[1][cProp] = cPropInner; - selectorText = 'div#test-matches-property div[id][class]:matches-property("/^b[A-Z].+/.val")'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); - assert.equal(elements[0].id, 'test-matches-property-div'); - - selectorText = 'div#test-matches-property div[id][class]:matches-property("/^[a-z][A-Z].+/.val"="/223/")'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); - assert.equal(elements[0].id, 'test-matches-property-div-two'); - - selectorText = 'div#test-matches-property div:matches-property("/^[a-z]Prop$/./[\\w]+/"="1234")'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); - assert.equal(elements[0].id, 'test-matches-property-div'); - - const abProp = 'abProp'; - const aaInner = { - unit123: { id: 123 }, - }; - const bbInner = { - unit000: { id: 0 }, - }; - testInnerElems[0][abProp] = aaInner; - testInnerElems[1][abProp] = bbInner; - selectorText = 'div#test-matches-property div[id]:matches-property("abProp./[\\w]{4}000/.id"):upward(2)'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); - assert.equal(elements[0].id, 'test-matches-property-div-two'); - - selectorText = 'div#test-matches-property div[id]:has(div:matches-property("abProp.unit123./.{1,5}/"="123"))'; - selector = ExtendedSelectorFactory.createSelector(selectorText); - elements = selector.querySelectorAll(); - assert.equal(elements.length, 1); - assert.ok(selector.matches(elements[0])); - assert.equal(elements[0].id, 'test-matches-property-div'); -}); - -QUnit.test('Test matches-property validation', (assert) => { - let selectorText; - - assert.throws(() => { - selectorText = 'div:matches-property()'; - ExtendedSelectorFactory.createSelector(selectorText); - }, 'Expected to be invalid rule -- no arg'); - - assert.throws(() => { - selectorText = 'div:matches-property(.prop.id)'; - ExtendedSelectorFactory.createSelector(selectorText); - }, 'Expected to be invalid rule -- invalid arg'); - - assert.throws(() => { - selectorText = 'div:matches-property(asadf./aa.?+./test/)'; - ExtendedSelectorFactory.createSelector(selectorText); - }, 'Expected to be invalid rule -- invalid arg'); - - assert.throws(() => { - selectorText = 'div:matches-property(asadf..?+/.test)'; - ExtendedSelectorFactory.createSelector(selectorText); - }, 'Expected to be invalid rule -- invalid chained arg'); - - assert.throws(() => { - selectorText = 'div:matches-property(asadf.?+/.test)'; - ExtendedSelectorFactory.createSelector(selectorText); - }, 'Expected to be invalid rule -- invalid regex'); -}); diff --git a/test/selector/tokenizer.test.ts b/test/selector/tokenizer.test.ts new file mode 100644 index 00000000..be81be47 --- /dev/null +++ b/test/selector/tokenizer.test.ts @@ -0,0 +1,102 @@ +/** + * @jest-environment jsdom + */ + +import { tokenize } from '../../src/selector'; + +describe('tokenizer', () => { + it('simple', () => { + let selector = 'div.banner'; + let expected = [ + { type: 'word', value: 'div' }, + { type: 'mark', value: '.' }, + { type: 'word', value: 'banner' }, + ]; + expect(tokenize(selector)).toEqual(expected); + + selector = '.banner'; + expected = [ + { type: 'mark', value: '.' }, + { type: 'word', value: 'banner' }, + ]; + expect(tokenize(selector)).toEqual(expected); + + selector = 'div[id][class] > .banner'; + expected = [ + { type: 'word', value: 'div' }, + { type: 'mark', value: '[' }, + { type: 'word', value: 'id' }, + { type: 'mark', value: ']' }, + { type: 'mark', value: '[' }, + { type: 'word', value: 'class' }, + { type: 'mark', value: ']' }, + { type: 'mark', value: ' ' }, + { type: 'mark', value: '>' }, + { type: 'mark', value: ' ' }, + { type: 'mark', value: '.' }, + { type: 'word', value: 'banner' }, + ]; + expect(tokenize(selector)).toEqual(expected); + }); + + it('pseudo-class with regex', () => { + const selector = 'div:matches-css(background-image: /^url\\("data:image\\/gif;base64.+/)'; + const expected = [ + { type: 'word', value: 'div' }, + { type: 'mark', value: ':' }, + { type: 'word', value: 'matches-css' }, + { type: 'mark', value: '(' }, + { type: 'word', value: 'background-image' }, + { type: 'mark', value: ':' }, + { type: 'mark', value: ' ' }, + { type: 'mark', value: '/' }, + { type: 'mark', value: '^' }, + { type: 'word', value: 'url' }, + { type: 'mark', value: '\\' }, + { type: 'mark', value: '(' }, + { type: 'mark', value: '"' }, + { type: 'word', value: 'data' }, + { type: 'mark', value: ':' }, + { type: 'word', value: 'image' }, + { type: 'mark', value: '\\' }, + { type: 'mark', value: '/' }, + { type: 'word', value: 'gif' }, + { type: 'mark', value: ';' }, + { type: 'word', value: 'base64' }, + { type: 'mark', value: '.' }, + { type: 'mark', value: '+' }, + { type: 'mark', value: '/' }, + { type: 'mark', value: ')' }, + ]; + expect(tokenize(selector)).toEqual(expected); + }); + + describe('trim selectors', () => { + const rawSelectors = [ + ' #test p', + ' #test p', + '\t#test p', + '\r#test p', + '\n#test p', + '\f#test p', + '#test p ', + '#test p ', + '#test p\t', + '#test p\r', + '#test p\n', + '#test p\f', + ]; + // should be '#test p' + const expected = [ + { type: 'mark', value: '#' }, + { type: 'word', value: 'test' }, + { type: 'mark', value: ' ' }, + { type: 'word', value: 'p' }, + ]; + test.each( + rawSelectors.map((raw) => ({ raw })), + )('%s', ({ raw }) => { + expect(tokenize(raw)).toEqual(expected); + }); + }); +}); diff --git a/test/selector/utils/absolute-matcher.test.ts b/test/selector/utils/absolute-matcher.test.ts new file mode 100644 index 00000000..3e7c68ed --- /dev/null +++ b/test/selector/utils/absolute-matcher.test.ts @@ -0,0 +1,107 @@ +/** + * @jest-environment jsdom + */ + +import { + getValidMatcherArg, + parseRawPropChain, +} from '../../../src/selector'; + +describe('matcher-utils tests', () => { + describe('test getValidMatcherArg', () => { + it('valid arg pattern', () => { + let argPattern: string; + + argPattern = '"attrName"'; + expect(getValidMatcherArg(argPattern)).toStrictEqual('attrName'); + + argPattern = 'attrName'; + expect(getValidMatcherArg(argPattern)).toStrictEqual('attrName'); + + argPattern = '/attrName/'; + expect(getValidMatcherArg(argPattern)).toStrictEqual(/attrName/); + + argPattern = 'attr-*'; + expect(getValidMatcherArg(argPattern)).toStrictEqual(/attr-.*/); + }); + + it('invalid arg pattern', () => { + let argPattern: string; + + argPattern = '""'; + expect(() => { + getValidMatcherArg(argPattern); + }).toThrow('Empty arg is invalid'); + + argPattern = '"'; + expect(() => { + getValidMatcherArg(argPattern); + }).toThrow('Invalid argument'); + + argPattern = '> [track="true"]'; + expect(() => { + getValidMatcherArg(argPattern); + }).toThrow('Invalid argument'); + + argPattern = '.?'; + expect(() => { + getValidMatcherArg(argPattern); + }).toThrow('Invalid argument'); + + argPattern = '//'; + expect(() => { + getValidMatcherArg(argPattern); + }).toThrow('Invalid regexp'); + + argPattern = '*'; + expect(() => { + getValidMatcherArg(argPattern); + }).toThrow('Argument should be more specific'); + }); + }); + + describe('test parseRawPropChain', () => { + it('valid arg pattern', () => { + expect(parseRawPropChain('"propName"')).toEqual(['propName']); + expect(parseRawPropChain('propName')).toEqual(['propName']); + expect(parseRawPropChain('"prop.test"')).toEqual(['prop', 'test']); + expect(parseRawPropChain('prop./test/')).toEqual(['prop', /test/]); + expect(parseRawPropChain('prop.*.test')).toEqual(['prop', /.*/, 'test']); + expect(parseRawPropChain('aProp.unit123./.{1,5}/')).toEqual(['aProp', 'unit123', /.{1,5}/]); + }); + + it('invalid arg pattern', () => { + expect(() => { + parseRawPropChain('""'); + }).toThrow(/Empty pattern .* invalid/); + + expect(() => { + parseRawPropChain('"'); + }).toThrow('Invalid property pattern'); + + expect(() => { + parseRawPropChain('.?'); + }).toThrow(/Empty pattern .* invalid/); + + expect(() => { + parseRawPropChain('//'); + }).toThrow('Invalid regexp property pattern'); + + expect(() => { + parseRawPropChain('nested..test'); + }).toThrow(/Empty pattern .* invalid/); + + expect(() => { + parseRawPropChain('abc.?+/.test'); + }).toThrow('Invalid property pattern'); + + expect(() => { + parseRawPropChain('/id'); + }).toThrow('Invalid regexp property pattern'); + + expect(() => { + parseRawPropChain('test.//.id'); + }).toThrow('Invalid regexp property pattern'); + }); + }); +}); diff --git a/test/stylesheet/parser.test.ts b/test/stylesheet/parser.test.ts new file mode 100644 index 00000000..dba60337 --- /dev/null +++ b/test/stylesheet/parser.test.ts @@ -0,0 +1,764 @@ +/** + * @jest-environment jsdom + */ + +import { parse } from '../../src/stylesheet'; +import { ExtCssDocument } from '../../src/selector'; + +import { STYLESHEET_ERROR_PREFIX } from '../../src/common/constants'; + +interface TestRuleData { + selector: string, + style?: any, // eslint-disable-line @typescript-eslint/no-explicit-any + debug?: string, +} + +interface SingleRuleInput { + actual: string, // css rule + expected: TestRuleData, // rule data +} +const expectSingleRuleParsed = (input: SingleRuleInput): void => { + const { actual, expected } = input; + const extCssDoc = new ExtCssDocument(); + const parsed = parse(actual, extCssDoc); + expect(parsed.length).toEqual(1); + expect(parsed[0].selector).toEqual(expected.selector); + expect(parsed[0].style).toEqual(expected.style); + expect(parsed[0].debug).toEqual(expected.debug); +}; + +interface MultipleRuleInput { + actual: string, // css rule + expected: TestRuleData[], // array of rule data objects +} +const expectMultipleRulesParsed = (input: MultipleRuleInput): void => { + const { actual, expected } = input; + const extCssDoc = new ExtCssDocument(); + const parsedRules = parse(actual, extCssDoc); + parsedRules.forEach((parsed, i) => { + expect(parsed.selector).toEqual(expected[i].selector); + expect(parsed.style).toEqual(expected[i].style); + expect(parsed.debug).toEqual(expected[i].debug); + }); +}; + +interface ToThrowOnSelectorInput { + selector: string, // selector for extCss querySelectorAll() + error: string, // error text to match +} +const expectToThrowOnSelector = (input: ToThrowOnSelectorInput): void => { + const { selector, error } = input; + expect(() => { + const extCssDoc = new ExtCssDocument(); + parse(selector, extCssDoc); + }).toThrow(error); +}; + +interface ToThrowOnStylesheetInput { + stylesheet: string, // selector for extCss querySelectorAll() + error: string, // error text to match +} +const expectToThrowOnStylesheet = (input: ToThrowOnStylesheetInput): void => { + const { stylesheet, error } = input; + expect(() => { + const extCssDoc = new ExtCssDocument(); + parse(stylesheet, extCssDoc); + }).toThrow(error); +}; + +describe('stylesheet parser', () => { + describe('one rule', () => { + describe('simple selector + one style declaration', () => { + const testsInputs = [ + { + actual: 'body { display:none; }', + expected: { + selector: 'body', + style: { display: 'none' }, + }, + }, + { + actual: '.banner { display: none !important; }', + expected: { + selector: '.banner', + style: { display: 'none !important' }, + }, + }, + { + actual: '.banner { display: none!important; }', + expected: { + selector: '.banner', + style: { display: 'none!important' }, + }, + }, + { + actual: '.banner {display:none!important;}', + expected: { + selector: '.banner', + style: { display: 'none!important' }, + }, + }, + { + actual: '.banner img { margin-top: 0 !important; }', + expected: { + selector: '.banner img', + style: { 'margin-top': '0 !important' }, + }, + }, + { + actual: '#modal form#email-sign-up-form:upward(#modal) {display: none!important; }', + expected: { + selector: '#modal form#email-sign-up-form:upward(#modal)', + style: { display: 'none!important' }, + }, + }, + { + actual: '.pdp-psy > .gb_Lc.gb_g[data-ved]{display:none!important;}', + expected: { + selector: '.pdp-psy > .gb_Lc.gb_g[data-ved]', + style: { display: 'none!important' }, + }, + }, + { + actual: 'div:has(:scope > a > img[id]) { display: none; }', + expected: { + selector: 'div:has(:scope > a > img[id])', + style: { display: 'none' }, + }, + }, + ]; + test.each(testsInputs)('%s', (input) => expectSingleRuleParsed(input)); + }); + + describe('tricky selector + one style declaration', () => { + const testsInputs = [ + // '{' in selector + { + actual: 'head > style:contains(body{background: #410e13) { display: none !important; }', + expected: { + selector: 'head > style:contains(body{background: #410e13)', + style: { display: 'none !important' }, + }, + }, + { + actual: '[-ext-matches-css-before=\'content: /^[A-Z][a-z]{2}\\s/ \'] { display :none !important; }', // eslint-disable-line max-len + expected: { + selector: '[-ext-matches-css-before=\'content: /^[A-Z][a-z]{2}\\s/ \']', + style: { display: 'none !important' }, + }, + }, + { + actual: 'a[href][data-item^=\'{"sources":[\'][data-item*=\'Video Ad\'] { display: none !important; }', // eslint-disable-line max-len + expected: { + selector: 'a[href][data-item^=\'{"sources":[\'][data-item*=\'Video Ad\']', + style: { display: 'none !important' }, + }, + }, + // style without ';' at the end + { + actual: '#consent-modal { display: none !important }', + expected: { + selector: '#consent-modal', + style: { display: 'none !important' }, + }, + }, + ]; + test.each(testsInputs)('%s', (input) => expectSingleRuleParsed(input)); + }); + + describe('multiple styles', () => { + const testsInputs = [ + { + actual: '#inner { position: absolute!important; left: -3000px!important; }', + expected: { + selector: '#inner', + style: { position: 'absolute!important', left: '-3000px!important' }, + }, + }, + { + actual: `#inner { + position: absolute!important; + left: -3000px!important; + }`, + expected: { + selector: '#inner', + style: { position: 'absolute!important', left: '-3000px!important' }, + }, + }, + { + actual: '.con > .related[data-desc] > li { margin-right: 0!important; margin-left: 20px!important; }', // eslint-disable-line max-len + expected: { + selector: '.con > .related[data-desc] > li', + style: { 'margin-right': '0!important', 'margin-left': '20px!important' }, + }, + }, + { + actual: ':contains(/[\\w]{9,}/){display:none!important;visibility:hidden!important}', + expected: { + selector: ':contains(/[\\w]{9,}/)', + style: { display: 'none!important', visibility: 'hidden!important' }, + }, + }, + { + /* eslint-disable max-len */ + actual: ':matches-css( background-image: /^url\\((.)[a-z]{4}:[a-z]{2}\\1nk\\)$/ ) + [-ext-matches-css-before=\'content: /^[A-Z][a-z]{2}\\s/ \'][-ext-has=\'+:matches-css-after( content : /(\\d+\\s)*me/ ):contains(/^(?![\\s\\S])/)\'] {\ + width: 500px;height: 500px;\ + -webkit-border-radius: 30px;\ + -moz-border-radius: 30px;\ + \ + -webkit-box-shadow: 1px -1px #b2492c, 2px -2px #b2492c, 3px -3px #b2492c, 4px -4px #b2492c, 5px -5px #b2492c, 6px -6px #b2492c, 7px -7px #b2492c, 8px -8px #b2492c, 9px -9px #b2492c, 10px -10px #b2492c;\ + \ + }', + expected: { + selector: ':matches-css( background-image: /^url\\((.)[a-z]{4}:[a-z]{2}\\1nk\\)$/ ) + [-ext-matches-css-before=\'content: /^[A-Z][a-z]{2}\\s/ \'][-ext-has=\'+:matches-css-after( content : /(\\d+\\s)*me/ ):contains(/^(?![\\s\\S])/)\']', + style: { + width: '500px', + height: '500px', + '-webkit-border-radius': '30px', + '-moz-border-radius': '30px', + '-webkit-box-shadow': '1px -1px #b2492c, 2px -2px #b2492c, 3px -3px #b2492c, 4px -4px #b2492c, 5px -5px #b2492c, 6px -6px #b2492c, 7px -7px #b2492c, 8px -8px #b2492c, 9px -9px #b2492c, 10px -10px #b2492c', + }, + }, + /* eslint-enable max-len */ + }, + ]; + test.each(testsInputs)('%s', (input) => expectSingleRuleParsed(input)); + }); + }); + + describe('multiple rules', () => { + const testsInputs = [ + { + actual: '.banner {display:none!important;}\n .header { margin: 0 !important; }', + expected: [ + { + selector: '.banner', + style: { display: 'none!important' }, + }, + { + selector: '.header', + style: { margin: '0 !important' }, + }, + ], + }, + // like previous but with no line break '\n' + { + actual: '.banner {display:none!important;} .header { margin: 0 !important; }', + expected: [ + { + selector: '.banner', + style: { display: 'none!important' }, + }, + { + selector: '.header', + style: { margin: '0 !important' }, + }, + ], + }, + { + // eslint-disable-next-line max-len + actual: 'body { background: none!important; } div.wrapper { display: block!important; position: absolute; top:-2000px; }', + expected: [ + { + selector: 'body', + style: { background: 'none!important' }, + }, + { + selector: 'div.wrapper', + style: { + display: 'block!important', + position: 'absolute', + top: '-2000px', + }, + }, + ], + }, + { + // eslint-disable-next-line max-len + actual: 'body { background: none!important; }\n div.wrapper { display: block!important; position: absolute; top:-2000px; }', + expected: [ + { + selector: 'body', + style: { background: 'none!important' }, + }, + { + selector: 'div.wrapper', + style: { + display: 'block!important', + position: 'absolute', + top: '-2000px', + }, + }, + ], + }, + { + // eslint-disable-next-line max-len + actual: 'div.wrapper>div[-ext-has=".banner"] { display:none!important; } div.wrapper>div[-ext-contains="some word"] { background:none!important; }', + expected: [ + { + selector: 'div.wrapper>div[-ext-has=".banner"]', + style: { display: 'none!important' }, + }, + { + selector: 'div.wrapper>div[-ext-contains="some word"]', + style: { background: 'none!important' }, + }, + ], + }, + { + /* eslint-disable max-len */ + actual: ':contains(/[\\w]{9,}/){display:none!important;visibility:hidden!important}\ + :matches-css( background-image: /^url\\((.)[a-z]{4}:[a-z]{2}\\1nk\\)$/ ) + [-ext-matches-css-before=\'content: /^[A-Z][a-z]{2}\\s/ \'][-ext-has=\'+:matches-css-after( content : /(\\d+\\s)*me/ ):contains(/^(?![\\s\\S])/)\'] {\ + width: 500px;height: 500px;\ + -webkit-border-radius: 30px;\ + -moz-border-radius: 30px;\ + \ + -webkit-box-shadow: 1px -1px #b2492c, 2px -2px #b2492c, 3px -3px #b2492c, 4px -4px #b2492c, 5px -5px #b2492c, 6px -6px #b2492c, 7px -7px #b2492c, 8px -8px #b2492c, 9px -9px #b2492c, 10px -10px #b2492c;\ + \ + }', + expected: [ + { + selector: ':contains(/[\\w]{9,}/)', + style: { + display: 'none!important', + visibility: 'hidden!important', + }, + }, + { + selector: ':matches-css( background-image: /^url\\((.)[a-z]{4}:[a-z]{2}\\1nk\\)$/ ) + [-ext-matches-css-before=\'content: /^[A-Z][a-z]{2}\\s/ \'][-ext-has=\'+:matches-css-after( content : /(\\d+\\s)*me/ ):contains(/^(?![\\s\\S])/)\']', + style: { + width: '500px', + height: '500px', + '-webkit-border-radius': '30px', + '-moz-border-radius': '30px', + '-webkit-box-shadow': '1px -1px #b2492c, 2px -2px #b2492c, 3px -3px #b2492c, 4px -4px #b2492c, 5px -5px #b2492c, 6px -6px #b2492c, 7px -7px #b2492c, 8px -8px #b2492c, 9px -9px #b2492c, 10px -10px #b2492c', + }, + }, + ], + /* eslint-enable max-len */ + }, + ]; + test.each(testsInputs)('%s', (input) => expectMultipleRulesParsed(input)); + }); + + describe('do not fail on rule with invalid selector', () => { + // querySelectorAll() will fail on invalid selector + const testsInputs = [ + { + actual: 'body > { display:none; }', + expected: { + selector: 'body >', + style: { display: 'none' }, + }, + }, + { + actual: 'div:invalid-pseudo(1), div { display: none !important; }', + expected: { + selector: 'div:invalid-pseudo(1), div', + style: { display: 'none !important' }, + }, + }, + { + actual: 'body:has(div:invalid-pseudo(1)), div { display: none }', + expected: { + selector: 'body:has(div:invalid-pseudo(1)), div', + style: { display: 'none' }, + }, + }, + ]; + test.each(testsInputs)('%s', (input) => expectSingleRuleParsed(input)); + }); + + describe('remove pseudos', () => { + describe('remove pseudo-property', () => { + const testsInputs = [ + { + actual: '.banner { remove: true; }', + expected: { + selector: '.banner', + style: { remove: 'true' }, + }, + }, + { + actual: '.banner { remove:true; }', + expected: { + selector: '.banner', + style: { remove: 'true' }, + }, + }, + { + actual: 'head > style:contains(body{background: #410e13) { remove: true; }', + expected: { + selector: 'head > style:contains(body{background: #410e13)', + style: { remove: 'true' }, + }, + }, + ]; + test.each(testsInputs)('%s', (input) => expectSingleRuleParsed(input)); + }); + + describe('single rule with remove pseudo-class', () => { + const testsInputs = [ + { + actual: 'div[class]:remove()', + expected: { + selector: 'div[class]', + style: { remove: 'true' }, + }, + }, + { + actual: '.banner > *:remove()', + expected: { + selector: '.banner > *', + style: { remove: 'true' }, + }, + }, + { + // no fail as selector will be validated later + actual: '.banner > :remove()', + expected: { + // trimmed selector before pseudo-class + selector: '.banner >', + style: { remove: 'true' }, + }, + }, + { + actual: 'div[id][class][style]:remove()', + expected: { + selector: 'div[id][class][style]', + style: { remove: 'true' }, + }, + }, + { + actual: 'body > div:not([id]):not([class]):not([style]):empty:remove()', + expected: { + selector: 'body > div:not([id]):not([class]):not([style]):empty', + style: { remove: 'true' }, + }, + }, + { + actual: 'div#test-remove #test-remove-inner-id:remove()', + expected: { + selector: 'div#test-remove #test-remove-inner-id', + style: { remove: 'true' }, + }, + }, + { + actual: 'div[id*="remove"]:has(> div > .test-remove-inner-class):remove()', + expected: { + selector: 'div[id*="remove"]:has(> div > .test-remove-inner-class)', + style: { remove: 'true' }, + }, + }, + { + actual: 'div[class]:contains(remove):remove()', + expected: { + selector: 'div[class]:contains(remove)', + style: { remove: 'true' }, + }, + }, + { + actual: '#test-remove-inner-for-upward:upward(div[id]):remove()', + expected: { + selector: '#test-remove-inner-for-upward:upward(div[id])', + style: { remove: 'true' }, + }, + }, + { + actual: '#test-remove-inner-for-xpath-pseudo:xpath(../../..):remove()', + expected: { + selector: '#test-remove-inner-for-xpath-pseudo:xpath(../../..)', + style: { remove: 'true' }, + }, + }, + ]; + test.each(testsInputs)('%s', (input) => expectSingleRuleParsed(input)); + }); + + describe('both remove pseudo-class and pseudo-property', () => { + const testsInputs = [ + { + actual: '.banner:remove() { remove: true; }', + expected: { + selector: '.banner', + style: { remove: 'true' }, + }, + }, + ]; + test.each(testsInputs)('%s', (input) => expectSingleRuleParsed(input)); + }); + }); + + describe('debug pseudo-property', () => { + const testsInputs = [ + // 'true' value + { + actual: '.banner { debug: true; }', + expected: { + selector: '.banner', + debug: 'true', + }, + }, + { + actual: '.banner { display: none; debug: true; }', + expected: { + selector: '.banner', + debug: 'true', + style: { display: 'none' }, + }, + }, + // 'global' value + { + actual: '.banner { debug: global; }', + expected: { + selector: '.banner', + debug: 'global', + }, + }, + { + actual: '.banner { display: none; debug: global; }', + expected: { + selector: '.banner', + debug: 'global', + style: { display: 'none' }, + }, + }, + // invalid value + { + actual: '.banner { display: none; debug: false; }', + expected: { + selector: '.banner', + style: { display: 'none' }, + }, + }, + { + actual: '.banner { display: none; debug: "" }', + expected: { + selector: '.banner', + style: { display: 'none' }, + }, + }, + ]; + test.each(testsInputs)('%s', (input) => expectSingleRuleParsed(input)); + }); + + it('debug global for one rule in list', () => { + const actual = ` + #case14:not(without-debug-before-global) { display:none; } + #case14:not(with-global-debug) { display:none; debug: global } + #case14:not(without-debug-after-global) { display:none; } + `; + const expected = [ + { + selector: '#case14:not(without-debug-before-global)', + style: { display: 'none' }, + }, + { + selector: '#case14:not(with-global-debug)', + style: { display: 'none' }, + debug: 'global', + }, + { + selector: '#case14:not(without-debug-after-global)', + style: { display: 'none' }, + }, + ]; + expectMultipleRulesParsed({ actual, expected }); + }); + + describe('invalid stylesheets', () => { + describe('selector with no style declaration', () => { + const error = STYLESHEET_ERROR_PREFIX.NO_STYLE_OR_REMOVE; + const invalidSelectors = [ + '.banner', + '.block > span:contains({background: #410e13})', + '[-ext-matches-css-before=\'content: /^[A-Z][a-z]{2}\\s/ \']', + ]; + test.each(invalidSelectors)('%s', (selector) => expectToThrowOnSelector({ selector, error })); + }); + + describe('invalid remove pseudo-class', () => { + const error = STYLESHEET_ERROR_PREFIX.INVALID_REMOVE; + const invalidSelectors = [ + ':remove()', + '.block:remove() > .banner:remove()', + '.block:remove():upward(2)', + 'div:remove():contains(/test-content/)', + '.banner:has(> div[class]):remove():upward()', + 'div:remove(0)', + 'div:not([class]):remove(invalid)', + ]; + test.each(invalidSelectors)('%s', (selector) => expectToThrowOnSelector({ selector, error })); + }); + + describe('invalid style declaration', () => { + const toThrowInputs = [ + { selector: 'div { }', error: STYLESHEET_ERROR_PREFIX.NO_STYLE }, + { selector: 'div { display', error: STYLESHEET_ERROR_PREFIX.INVALID_STYLE }, + { selector: 'div { display:', error: STYLESHEET_ERROR_PREFIX.UNCLOSED_STYLE }, + { selector: 'div { display: }', error: STYLESHEET_ERROR_PREFIX.NO_VALUE }, + { selector: 'div { : none }', error: STYLESHEET_ERROR_PREFIX.NO_PROPERTY }, + { selector: 'div { display: none; visible ', error: STYLESHEET_ERROR_PREFIX.INVALID_STYLE }, + { selector: 'div { display: none; visible }', error: STYLESHEET_ERROR_PREFIX.INVALID_STYLE }, + { selector: 'div { remove }', error: STYLESHEET_ERROR_PREFIX.INVALID_STYLE }, + ]; + test.each(toThrowInputs)('%s', (input) => expectToThrowOnSelector(input)); + }); + + it('fail on comments in stylesheet', () => { + const stylesheet = ` + div:not(.header) { display: none; } + /* div:not(.header) { padding: 0; } */ + `; + const error = 'Comments in stylesheet are not supported'; + expectToThrowOnStylesheet({ stylesheet, error }); + }); + + it('fail on media query in stylesheet', () => { + const error = 'At-rules are not supported'; + let stylesheet; + + stylesheet = '@media (max-width: 768px) { body { padding-top: 50px !important; } }'; + expectToThrowOnStylesheet({ stylesheet, error }); + + stylesheet = ` + div:not(.header) { display: none; } + @media (max-width: 768px) { body { padding-top: 50px !important; } } + `; + expectToThrowOnStylesheet({ stylesheet, error }); + }); + }); + + describe('various combinations', () => { + const testsInputs = [ + { + actual: 'div:has(> div[class]):remove() { display: none !important; }', + expected: { + selector: 'div:has(> div[class])', + style: { remove: 'true' }, + }, + }, + { + actual: 'div[class]:contains(remove) { display: none !important; remove: true; }', + expected: { + selector: 'div[class]:contains(remove)', + style: { remove: 'true' }, + }, + }, + { + actual: 'div[class]:contains(remove) { display: none !important; remove: true; debug: true; }', + expected: { + selector: 'div[class]:contains(remove)', + style: { remove: 'true' }, + debug: 'true', + }, + }, + ]; + test.each(testsInputs)('%s', (input) => expectSingleRuleParsed(input)); + }); + + describe('merge styles for same selectors', () => { + describe('single rule as result', () => { + const testsInputs = [ + // selector duplicate + { + actual: '#ad { top: 0 !important; } #ad { margin: 0; }', + expected: { + selector: '#ad', + style: { top: '0 !important', margin: '0' }, + }, + }, + // style property duplicate + { + actual: 'div { display: none; } div { top: 0 !important; } div { display: none }', + expected: { + selector: 'div', + style: { display: 'none', top: '0 !important' }, + }, + }, + { + actual: 'div { display: none; } div { display: none }', + expected: { + selector: 'div', + style: { display: 'none' }, + }, + }, + // the last value is final for property + { + actual: 'div { display: none !important; } div { display: none }', + expected: { + selector: 'div', + style: { display: 'none' }, + }, + }, + { + actual: 'div { display: none; } div { display: none !important; }', + expected: { + selector: 'div', + style: { display: 'none !important' }, + }, + }, + { + actual: 'div { display: none !important }\n div { display: block !important; }', + expected: { + selector: 'div', + style: { display: 'block !important' }, + }, + }, + // remove property + { + actual: '.banner { top: 0 !important }\n .banner { remove: true; }', + expected: { + selector: '.banner', + style: { remove: 'true' }, + }, + }, + { + actual: '.banner { remove: true; } .banner { top: 0 !important }', + expected: { + selector: '.banner', + style: { remove: 'true' }, + }, + }, + // debug property + { + actual: 'div[attr] { top: 0 !important } div[attr] { debug: true; }', + expected: { + selector: 'div[attr]', + style: { top: '0 !important' }, + debug: 'true', + }, + }, + { + actual: 'div[attr] { top: 0 !important } div[attr] { debug: global; }', + expected: { + selector: 'div[attr]', + style: { top: '0 !important' }, + debug: 'global', + }, + }, + ]; + test.each(testsInputs)('%s', (input) => expectSingleRuleParsed(input)); + }); + + describe('multiple rules as result', () => { + const testsInputs = [ + { + // the last value is final for 'display' property for 'div' + actual: 'div { display: none !important; } .class { top: 0 !important; } div { display: none }', + expected: [ + { + selector: 'div', + style: { display: 'none' }, + }, + { + selector: '.class', + style: { top: '0 !important' }, + }, + ], + }, + ]; + test.each(testsInputs)('%s', (input) => expectMultipleRulesParsed(input)); + }); + }); +}); diff --git a/test/test-files/empty.html b/test/test-files/empty.html new file mode 100644 index 00000000..81f90b26 --- /dev/null +++ b/test/test-files/empty.html @@ -0,0 +1,10 @@ + + + + + + + Selector test page + + + diff --git a/dist/extended-css.js b/test/test-files/extCssV1.js similarity index 99% rename from dist/extended-css.js rename to test/test-files/extCssV1.js index 80c5d11f..9e55dc34 100644 --- a/dist/extended-css.js +++ b/test/test-files/extCssV1.js @@ -1,24 +1,15 @@ -/*! extended-css - v1.3.16 - Thu Sep 15 2022 -* https://github.com/AdguardTeam/ExtendedCss -* Copyright (c) 2022 AdGuard. Licensed GPL-3.0 -*/ -var ExtendedCss = (function () { +/* ExtendedCSS v1 for performance-selector test */ +var extCssV1 = (function () { 'use strict'; function _typeof(obj) { "@babel/helpers - typeof"; - if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { - _typeof = function (obj) { - return typeof obj; - }; - } else { - _typeof = function (obj) { - return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; - }; - } - - return _typeof(obj); + return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { + return typeof obj; + } : function (obj) { + return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; + }, _typeof(obj); } function _slicedToArray(arr, i) { @@ -38,18 +29,21 @@ var ExtendedCss = (function () { } function _iterableToArray(iter) { - if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter); + if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); } function _iterableToArrayLimit(arr, i) { - if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return; + var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; + + if (_i == null) return; var _arr = []; var _n = true; var _d = false; - var _e = undefined; + + var _s, _e; try { - for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { + for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; @@ -1034,11 +1028,6 @@ var ExtendedCss = (function () { if (newSelector) { try { - if (newSelector.indexOf(':has(') > -1) { - // https://github.com/AdguardTeam/ExtendedCss/issues/149 - throw new Error('Do not handle :has() pseudo-class by the native method'); - } - push.apply(results, newContext.querySelectorAll(newSelector)); return results; } catch (qsaError) { @@ -5292,4 +5281,4 @@ var ExtendedCss = (function () { return ExtendedCss; -}()); +})(); diff --git a/test/test-files/performance-selector-results.txt b/test/test-files/performance-selector-results.txt new file mode 100644 index 00000000..01b33777 --- /dev/null +++ b/test/test-files/performance-selector-results.txt @@ -0,0 +1,98 @@ +------------------------------------------------------------------------ +selector: .container #case1 div div +------------------------------------------------------------------------ +ExtendedCss: v1 v2 +------------------------------------------------------------------------ +elapsed: 37 ms 29 ms +count: 10000 10000 +average: 0.0037 ms 0.0029 ms +result: ✅ ~22% faster +------------------------------------------------------------------------ + +------------------------------------------------------------------------ +selector: .container #case1 div div:has(.banner) +------------------------------------------------------------------------ +ExtendedCss: v1 v2 +------------------------------------------------------------------------ +elapsed: 112 ms 53 ms +count: 10000 10000 +average: 0.0112 ms 0.0053 ms +result: ✅ ~53% faster +------------------------------------------------------------------------ + +------------------------------------------------------------------------ +selector: .container #case2 div div:contains(Block this) +------------------------------------------------------------------------ +ExtendedCss: v1 v2 +------------------------------------------------------------------------ +elapsed: 89 ms 32 ms +count: 10000 10000 +average: 0.0089 ms 0.0032 ms +result: ✅ ~64% faster +------------------------------------------------------------------------ + +------------------------------------------------------------------------ +selector: .container #case3 div div:matches-css(background-image: data:*) +------------------------------------------------------------------------ +ExtendedCss: v1 v2 +------------------------------------------------------------------------ +elapsed: 125 ms 92 ms +count: 10000 10000 +average: 0.0125 ms 0.0092 ms +result: ✅ ~26% faster +------------------------------------------------------------------------ + +------------------------------------------------------------------------ +selector: .container #case4 div div:has(.banner:contains(Block this)) +------------------------------------------------------------------------ +ExtendedCss: v1 v2 +------------------------------------------------------------------------ +elapsed: 182 ms 63 ms +count: 10000 10000 +average: 0.0182 ms 0.0063 ms +result: ✅ ~65% faster +------------------------------------------------------------------------ + +------------------------------------------------------------------------ +selector: #case5 > div:not([style^="min-height:"]) > div[id][data-id^="toolkit-"]:not([data-bem]):not([data-m]):has(a[href^="https://example."]>img) +------------------------------------------------------------------------ +ExtendedCss: v1 v2 +------------------------------------------------------------------------ +elapsed: 211 ms 171 ms +count: 10000 10000 +average: 0.0211 ms 0.0171 ms +result: ✅ ~19% faster +------------------------------------------------------------------------ + +------------------------------------------------------------------------ +selector: #case5 div > div:has(.target-banner) +------------------------------------------------------------------------ +ExtendedCss: v1 v2 +------------------------------------------------------------------------ +elapsed: 670 ms 205 ms +count: 10000 10000 +average: 0.067 ms 0.0205 ms +result: ✅ ~69% faster +------------------------------------------------------------------------ + +------------------------------------------------------------------------ +selector: #case5 div > div:matches-css(background-image: data:*) +------------------------------------------------------------------------ +ExtendedCss: v1 v2 +------------------------------------------------------------------------ +elapsed: 849 ms 673 ms +count: 10000 10000 +average: 0.0849 ms 0.0673 ms +result: ✅ ~21% faster +------------------------------------------------------------------------ + +------------------------------------------------------------------------ +selector: :xpath(//div[@class='target-banner']) +------------------------------------------------------------------------ +ExtendedCss: v1 v2 +------------------------------------------------------------------------ +elapsed: 192 ms 191 ms +count: 10000 10000 +average: 0.0192 ms 0.0191 ms +result: ✅ ~1% faster +------------------------------------------------------------------------ diff --git a/test/test-files/performance.html b/test/test-files/performance.html new file mode 100644 index 00000000..46e795ee --- /dev/null +++ b/test/test-files/performance.html @@ -0,0 +1,92 @@ + + + + + + + Performance test page + + + + +
      + +
      +
      +
      + +
      +
      +
      + +
      +
      +
      + Block this +
      +
      +
      + +
      +
      +
      +
      +
      + +
      +
      +
      + +
      +
      +
      + +
      +
      +
      +
      + +
      +
      + + + + + + + + +
      + +
      +
      +
      +
      + +
      +
      +
      +
      +
      + +
      + + diff --git a/test/utils/utils.html b/test/utils/utils.html deleted file mode 100644 index 6e15cc89..00000000 --- a/test/utils/utils.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - Utils tests - - - -
      -
      - - - - - - diff --git a/test/utils/utils.test.js b/test/utils/utils.test.js deleted file mode 100644 index 81ec6fbc..00000000 --- a/test/utils/utils.test.js +++ /dev/null @@ -1,119 +0,0 @@ -/* eslint-disable max-len */ - -const { utils, matcherUtils } = exports; - -QUnit.test('Test matcher utils', (assert) => { - let input; - let output; - - input = 'test'; - output = matcherUtils.parseMatcherFilter(input); - assert.ok(output instanceof Array); - assert.equal(output.length, 1); - assert.equal(output[0], 'test'); - - input = '"test"="123"'; - output = matcherUtils.parseMatcherFilter(input); - assert.equal(output.length, 2); - assert.equal(output[0], 'test'); - assert.equal(output[1], '123'); - - input = 'test123'; - output = matcherUtils.parseRawMatcherArg(input); - assert.ok(output instanceof Object); - assert.equal(output.arg, 'test123'); - assert.notOk(output.isRegexp); - - input = '/test(.{1,2})?/'; - output = matcherUtils.parseRawMatcherArg(input); - assert.ok(output instanceof Object); - assert.equal(output.arg, '/test(.{1,2})?/'); - assert.ok(output.isRegexp); - - let inputChain; let inputBase; - inputBase = { - id: 1, - test: { - abc123: 123, - abc456: 456, - abc4: null, - }, - }; - inputChain = [ - { - arg: 'test', - isRegexp: false, - }, - { - arg: utils.toRegExp('/^abc4.+?/'), - isRegexp: true, - }, - ]; - output = matcherUtils.filterRootsByRegexpChain(inputBase, inputChain); - assert.ok(output instanceof Array); - assert.equal(output.length, 1); - assert.ok(output[0].base instanceof Object); // due to 'test' prop in inputBase - assert.strictEqual(output[0].prop, 'abc456'); - assert.strictEqual(output[0].value, 456); - output = matcherUtils.validatePropMatcherArgs(inputChain); - assert.ok(output); - - inputBase = { - id: 1, - test1: { - id: 0, - src: null, - }, - test2: { - id: 1, - src: 'ad.com', - }, - }; - inputChain = [ - { - arg: utils.toRegExp('/test/'), - isRegexp: true, - }, - { - arg: 'id', - isRegexp: false, - }, - ]; - output = matcherUtils.filterRootsByRegexpChain(inputBase, inputChain); - assert.ok(output instanceof Array); - assert.equal(output.length, 2); - assert.strictEqual(output[0].prop, 'id'); - assert.strictEqual(output[0].value, 0); - assert.strictEqual(output[1].prop, 'id'); - assert.strictEqual(output[1].value, 1); - output = matcherUtils.validatePropMatcherArgs(inputChain); - assert.ok(output); - - input = { - arg: 'test', - isRegexp: false, - }; - output = matcherUtils.validatePropMatcherArgs(input); - assert.ok(output); - - input = { - arg: '/^abc.+?/', - isRegexp: true, - }; - output = matcherUtils.validatePropMatcherArgs(input); - assert.ok(output); - - input = { - arg: 'abc123', - isRegexp: true, - }; - output = matcherUtils.validatePropMatcherArgs(input); - assert.notOk(output); - - input = { - arg: '/abc', - isRegexp: true, - }; - output = matcherUtils.validatePropMatcherArgs(input); - assert.notOk(output); -}); diff --git a/tools/build.ts b/tools/build.ts new file mode 100644 index 00000000..5207d99a --- /dev/null +++ b/tools/build.ts @@ -0,0 +1,81 @@ +import path from 'path'; +import { writeFile } from './utils'; +import { program } from 'commander'; + +import { terser } from 'rollup-plugin-terser'; + +import { commonPlugins, libOutputBanner } from './rollup-commons'; +import { rollupRunner } from './rollup-runner'; + +import { + SRC_DIR_PATH, + SRC_FILENAME, + LIBRARY_NAME, + LIB_FILE_NAME, + DIST_DIR_PATH, + BUILD_TXT_FILENAME, + OutputFormat, +} from './constants'; + +import { version } from '../package.json'; + +const srcInputPath = path.resolve(__dirname, SRC_DIR_PATH, SRC_FILENAME); +const prodOutputDir = path.resolve(__dirname, DIST_DIR_PATH); + +const prodConfig = { + input: srcInputPath, + output: [ + { + file: `${prodOutputDir}/${LIB_FILE_NAME}.js`, + format: OutputFormat.IIFE, + name: LIBRARY_NAME, + banner: libOutputBanner, + }, + { + file: `${prodOutputDir}/${LIB_FILE_NAME}.esm.js`, + format: OutputFormat.ESM, + name: LIBRARY_NAME, + banner: libOutputBanner, + }, + { + // umd is preferred over cjs to avoid variables renaming in tsurlfilter + file: `${prodOutputDir}/${LIB_FILE_NAME}.umd.js`, + format: OutputFormat.UMD, + name: LIBRARY_NAME, + banner: libOutputBanner, + }, + { + file: `${prodOutputDir}/${LIB_FILE_NAME}.min.js`, + format: OutputFormat.IIFE, + name: LIBRARY_NAME, + banner: libOutputBanner, + plugins: [terser({ + output: { + comments: false, + preamble: libOutputBanner, + }, + })], + }, + ], + plugins: commonPlugins, +}; + +const buildLib = async (): Promise => { + const configName = 'extended-css prod build'; + await rollupRunner(prodConfig, configName); +}; + +const buildTxt = async (): Promise => { + const content = `version=${version}`; + await writeFile(path.resolve(__dirname, DIST_DIR_PATH, BUILD_TXT_FILENAME), content); +}; + +// prod extended-css build is default run with no command +program + .description('Builds extended-css') + .action(async () => { + await buildLib(); + await buildTxt(); + }); + +program.parse(process.argv); diff --git a/tools/constants.ts b/tools/constants.ts new file mode 100644 index 00000000..bc17b9f1 --- /dev/null +++ b/tools/constants.ts @@ -0,0 +1,23 @@ +export enum OutputFormat { + IIFE = 'iife', + ESM = 'esm', + UMD = 'umd', +} + +export const LIBRARY_NAME = 'ExtendedCSS'; +export const LIB_FILE_NAME = 'extended-css'; + +export const ROOT_PATH = '..'; +export const SRC_DIR_PATH = '../src'; +export const SRC_FILENAME = 'index.ts'; + +export const SELECTOR_SRC_DIR_PATH = '../src/selector'; +export const SELECTOR_QUERY_FILE_NAME = 'query.ts'; + +export const DIST_DIR_PATH = '../dist'; +export const BUILD_TXT_FILENAME = 'build.txt'; + +export const TEST_TEMP_DIR_PATH = '../test/dist'; + +export const TEST_BROWSERSTACK_DIR_PATH = '../test/browserstack'; +export const BROWSERSTACK_TEST_FILE_NAME = 'browserstack.test.ts'; diff --git a/tools/rollup-commons.ts b/tools/rollup-commons.ts new file mode 100644 index 00000000..f5231acd --- /dev/null +++ b/tools/rollup-commons.ts @@ -0,0 +1,21 @@ +import resolve from '@rollup/plugin-node-resolve'; +import commonjs from '@rollup/plugin-commonjs'; +import typescript from 'rollup-plugin-ts'; + +import * as pkg from '../package.json'; + +export const libOutputBanner = `/*! ${pkg.name} - v${pkg.version} - ${new Date().toDateString()} +${pkg.homepage ? `* ${pkg.homepage}` : ''} +* Copyright (c) ${new Date().getFullYear()} ${pkg.author}. Licensed ${pkg.license} +*/`; + +export const commonPlugins = [ + resolve(), + commonjs({ + include: 'node_modules/**', + }), + typescript({ + transpiler: 'babel', + // 'browserslist' should be set as 'targets' in babel.config.js + }), +]; diff --git a/tools/rollup-runner.ts b/tools/rollup-runner.ts new file mode 100644 index 00000000..cc5cb38e --- /dev/null +++ b/tools/rollup-runner.ts @@ -0,0 +1,46 @@ +import { rollup, RollupOptions } from 'rollup'; +import chalk from 'chalk'; + +const { log } = console; + +/** + * Runs single rollup config. + * + * @param config Rollup config. + * @param [name] Config name for better logging. + */ +const runOneConfig = async (config: RollupOptions, name?: string) => { + const { input, output } = config; + if (!output) { + throw new Error(`No 'output' set in config with such input: '${input}'`); + } + const configInfo = name + ? `${name} - ${input}` + : input; + log(`Start building... ${configInfo}`); + const bundle = await rollup(config); + if (Array.isArray(output)) { + for (const outputItem of output) { + await bundle.write(outputItem); + } + } else { + await bundle.write(output); + } + log(chalk.greenBright('Successfully built:'), configInfo); +}; + +/** + * Builds the lib. + * + * @param config May be a list of configs or just one config. + * @param [name] Config name for better logging. + */ +export const rollupRunner = async (config: RollupOptions | RollupOptions[], name?: string) => { + if (Array.isArray(config)) { + for (const oneConfig of config) { + await runOneConfig(oneConfig, name); + } + } else { + await runOneConfig(config, name); + } +}; diff --git a/tools/test.ts b/tools/test.ts new file mode 100644 index 00000000..9bd4acf1 --- /dev/null +++ b/tools/test.ts @@ -0,0 +1,289 @@ +import path from 'path'; +import chalk from 'chalk'; +import { runCLI } from 'jest'; +import { program } from 'commander'; + +import copy from 'rollup-plugin-copy'; +import del from 'rollup-plugin-delete'; +import html2 from 'rollup-plugin-html2'; + +import { rollupRunner } from './rollup-runner'; +import { commonPlugins, libOutputBanner } from './rollup-commons'; + +import type { Config } from '@jest/types'; +import commonJestConfig from '../jest.config'; +import performanceConfig from '../test/performance-selector/jest.config'; + +import { runBrowserstack } from '../test/browserstack'; + +import { + LIB_FILE_NAME, + OutputFormat, + ROOT_PATH, + SRC_DIR_PATH, + SRC_FILENAME, + SELECTOR_QUERY_FILE_NAME, + SELECTOR_SRC_DIR_PATH, + TEST_TEMP_DIR_PATH, + TEST_BROWSERSTACK_DIR_PATH, + BROWSERSTACK_TEST_FILE_NAME, +} from './constants'; + +const { log } = console; + +const PERFORMANCE_XPATH_EVALUATION_PATH = './test/helpers/xpath-evaluate-counter.ts'; +const PERFORMANCE_SELECTOR_PATH = './test/helpers/performance-checker.ts'; + +const TEST_FILES_DIR_PATH = '../test/test-files'; +const EXT_CSS_V1_BUNDLE_FILENAME = 'extCssV1.js'; +const EXT_CSS_V2_BUNDLE_FILENAME = 'extCssV2.js'; + +const projectRootPath = path.resolve(__dirname, ROOT_PATH); +const srcInputPath = path.resolve(__dirname, SRC_DIR_PATH, SRC_FILENAME); +const selectorInputPath = path.resolve(__dirname, SELECTOR_SRC_DIR_PATH, SELECTOR_QUERY_FILE_NAME); +const testTempDir = path.resolve(__dirname, TEST_TEMP_DIR_PATH); +const browserstackTestInput = path.resolve(__dirname, TEST_BROWSERSTACK_DIR_PATH, BROWSERSTACK_TEST_FILE_NAME); +const testBrowserstackDir = path.resolve(__dirname, TEST_BROWSERSTACK_DIR_PATH); +const extCssV1BundlePath = path.resolve(__dirname, TEST_FILES_DIR_PATH, EXT_CSS_V1_BUNDLE_FILENAME); + +const selectorTestConfig = { + input: selectorInputPath, + output: [ + { + file: `${testTempDir}/selector.js`, + format: OutputFormat.IIFE, + name: 'testExtCss', + banner: '/* querySelectorAll testing */', + }, + ], + plugins: [ + ...commonPlugins, + html2({ + template: 'test/test-files/empty.html', + }), + del({ + targets: [testTempDir], + hook: 'buildStart', + }), + ], +}; +const buildSelectorTests = async () => { + const name = 'selector tests for playwright'; + await rollupRunner(selectorTestConfig, name); +}; + +// independent test config for xpath evaluate +const v2XpathEvaluationPerformanceTestConfig = { + input: PERFORMANCE_XPATH_EVALUATION_PATH, + output: [ + { + file: `${testTempDir}/performance-xpath-evaluate.js`, + format: OutputFormat.IIFE, + name: 'v2ExtCssPerformanceXpath', + banner: '/* xpath evaluation performance testing */', + }, + ], + plugins: [ + ...commonPlugins, + html2({ + template: 'test/test-files/performance.html', + fileName: `${testTempDir}/performance-xpath-evaluate.html`, + }), + ], +}; +const buildXpathPerformanceTests = async () => { + const name = 'xpath evaluate tests'; + await rollupRunner(v2XpathEvaluationPerformanceTestConfig, name); +}; + +const temp1HtmlPath = `${testTempDir}/performance-selector-temp1.html`; +const tempV1SelectorPerformanceConfig = { + // bundled extCssV1 is being injected into template performance.html; + // after that use the generated html as template for selectorPerformanceTestConfig + input: extCssV1BundlePath, + output: [ + { + file: `${testTempDir}/${EXT_CSS_V1_BUNDLE_FILENAME}`, + format: OutputFormat.IIFE, + name: 'extCssV1', + banner: '/* ExtendedCSS v1 for performance comparing */', + }, + ], + plugins: [ + html2({ + // output file + fileName: temp1HtmlPath, + // initial template to inject ExtendedCSS v1 + template: 'test/test-files/performance.html', + }), + ], +}; + +const temp2HtmlPath = `${testTempDir}/performance-selector-temp2.html`; +// ExtendedCss v2 should be bundled before running selector performance comparing test +const tempV2SelectorPerformanceConfig = { + input: srcInputPath, + output: [ + { + file: `${testTempDir}/${EXT_CSS_V2_BUNDLE_FILENAME}`, + format: OutputFormat.IIFE, + name: 'extCssV2', + banner: '/* ExtendedCSS v2 for performance comparing */', + }, + ], + plugins: [ + ...commonPlugins, + html2({ + // output file + fileName: temp2HtmlPath, + // template is generated previously html with injected ExtendedCss v1 + template: temp1HtmlPath, + }), + ], +}; + +const selectorPerformanceTestConfig = { + input: PERFORMANCE_SELECTOR_PATH, + output: [ + { + file: `${testTempDir}/performance-selector.js`, + format: OutputFormat.IIFE, + name: 'extCssPerformance', + banner: '/* selector performance testing */', + }, + ], + plugins: [ + ...commonPlugins, + html2({ + template: temp2HtmlPath, + fileName: `${testTempDir}/performance-selector.html`, + }), + // bundled ExtendedCSS v1 lib code is stored in test/test-files, + // it should be copied to test/dist + copy({ + targets: [ + { src: extCssV1BundlePath, dest: testTempDir }, + ], + }), + ], +}; +const buildPerformanceSelectorTests = async () => { + const v1PreparationBuildName = 'prepare ExtendedCss v1'; + await rollupRunner(tempV1SelectorPerformanceConfig, v1PreparationBuildName); + const v2PreparationBuildName = 'prepare ExtendedCss v2'; + await rollupRunner(tempV2SelectorPerformanceConfig, v2PreparationBuildName); + const finalBuildName = 'selector performance compare tests'; + await rollupRunner(selectorPerformanceTestConfig, finalBuildName); +}; + +// needed for browserstack tests +const browserstackLibConfig = { + input: srcInputPath, + output: [ + { + file: `${testTempDir}/${LIB_FILE_NAME}.js`, + format: OutputFormat.IIFE, + name: 'BrowserstackTest', + banner: libOutputBanner, + }, + ], + plugins: commonPlugins, +}; +const buildLibForBrowserstack = async () => { + const name = 'extended-css for browserstack'; + await rollupRunner(browserstackLibConfig, name); +}; + +const browserstackTestConfig = { + input: browserstackTestInput, + output: [ + { + file: `${testTempDir}/browserstack.test.js`, + format: OutputFormat.IIFE, + name: 'BrowserstackTest', + banner: '/* browserstack qunit testing */', + }, + ], + plugins: [ + ...commonPlugins, + copy({ + verbose: true, + targets: [ + { + src: [ + `${testBrowserstackDir}/browserstack.html`, + 'node_modules/qunit/qunit/**', + ], + dest: testTempDir, + }, + ], + }), + ], +}; +const buildBrowserstackTests = async (): Promise => { + const name = 'tests for browserstack'; + await rollupRunner(browserstackTestConfig, name); +}; + +/** + * Runs jest tests. + * + * @param config Jest config. + */ +const runJest = async (config: Config.InitialOptions): Promise => { + const { results } = await runCLI(config as Config.Argv, [projectRootPath]); + if (results.success) { + log(chalk.greenBright('Tests completed')); + } else { + throw new Error(chalk.redBright.bold('Tests failed')); + } +}; + +const runTestsLocally = async (): Promise => { + await buildSelectorTests(); + await buildXpathPerformanceTests(); + await runJest(commonJestConfig); +}; + +const runTestsOnBrowserstack = async (): Promise => { + await buildLibForBrowserstack(); + await buildBrowserstackTests(); + await runBrowserstack(); +}; + +// run tests locally and on browserstack if no command specified +program + .description('full testing') + .action(async () => { + await runTestsLocally(); + await runTestsOnBrowserstack(); + }); + +program + .command('local') + .description('only local tests run, no browserstack') + .action(async () => { + await runTestsLocally(); + }); + +program + .command('browserstack') + .description('only browserstack tests run') + .action(async () => { + await runTestsOnBrowserstack(); + }); + +// run performance selector tests, should be run manually only when needed +const runSelectorPerformanceTests = async (): Promise => { + await buildPerformanceSelectorTests(); + await runJest(performanceConfig); +}; + +program + .command('performance') + .description('only performance selector tests run') + .action(async () => { + await runSelectorPerformanceTests(); + }); + +program.parse(process.argv); diff --git a/tools/utils.ts b/tools/utils.ts new file mode 100644 index 00000000..3ecea6c5 --- /dev/null +++ b/tools/utils.ts @@ -0,0 +1,14 @@ +import fs from 'fs-extra'; +import path from 'path'; + +/** + * Writes `content` to file. + * + * @param filePath File path. + * @param content Content to write. + */ +export const writeFile = async (filePath: string, content: string): Promise => { + const dirname = path.dirname(filePath); + await fs.ensureDir(dirname); + await fs.writeFile(filePath, content); +}; diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json new file mode 100644 index 00000000..b846b1b0 --- /dev/null +++ b/tsconfig.eslint.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "include": [ + "src", + "test", + "tools", + ] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..b52c7f0f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "moduleResolution": "node", + "target": "es5", + "downlevelIteration": true, + "module": "es2015", + "lib": ["esnext", "dom"], + "strict": true, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "typeRoots": [ + "node_modules/@types", + ], + "types": [ + "qunit", + "./node_modules/user-agent-data-types" + ], + }, + "ts-node": { + // override these options only for ts-node + "compilerOptions": { + "module": "commonjs" + } + }, + "include": [ + "src" + ] +} diff --git a/types/extended-css.d.ts b/types/extended-css.d.ts deleted file mode 100644 index 271203bf..00000000 --- a/types/extended-css.d.ts +++ /dev/null @@ -1,39 +0,0 @@ -declare module 'extended-css' { - export interface IAffectedElement { - rules: { style: { content: string }}[] - node: HTMLElement; - } - - export interface IConfiguration { - styleSheet: string; - beforeStyleApplied?(x:IAffectedElement): IAffectedElement; - } - - export default class ExtendedCss { - constructor(configuration: IConfiguration); - - /** - * Applies filtering rules - */ - apply(): void; - - /** - * Disposes ExtendedCss and removes our styles from matched elements - */ - dispose(): void; - - /** - * Exposes querySelectorAll for debugging and validating selectors - * @param selectorText - * @param noTiming - */ - static query(selectorText: string, noTiming: boolean): void; - - /** - * Used for testing purposes only - */ - _getAffectedElements(): IAffectedElement[]; - } -} - - diff --git a/yarn.lock b/yarn.lock index 771e7d13..2426f606 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,756 +2,847 @@ # yarn lockfile v1 -"@babel/code-frame@7.12.11": - version "7.12.11" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" - integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== - dependencies: - "@babel/highlight" "^7.10.4" - -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658" - integrity sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g== - dependencies: - "@babel/highlight" "^7.12.13" - -"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.13.12", "@babel/compat-data@^7.13.15", "@babel/compat-data@^7.13.8": - version "7.13.15" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.13.15.tgz#7e8eea42d0b64fda2b375b22d06c605222e848f4" - integrity sha512-ltnibHKR1VnrU4ymHyQ/CXtNXI6yZC0oJThyW78Hft8XndANwi+9H+UIklBDraIjFEJzw8wmcM427oDd9KS5wA== - -"@babel/core@^7.7.5": - version "7.13.15" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.13.15.tgz#a6d40917df027487b54312202a06812c4f7792d0" - integrity sha512-6GXmNYeNjS2Uz+uls5jalOemgIhnTMeaXo+yBUA72kC2uX/8VW6XyhVIo2L8/q0goKQA3EVKx0KOQpVKSeWadQ== - dependencies: - "@babel/code-frame" "^7.12.13" - "@babel/generator" "^7.13.9" - "@babel/helper-compilation-targets" "^7.13.13" - "@babel/helper-module-transforms" "^7.13.14" - "@babel/helpers" "^7.13.10" - "@babel/parser" "^7.13.15" - "@babel/template" "^7.12.13" - "@babel/traverse" "^7.13.15" - "@babel/types" "^7.13.14" +"@ampproject/remapping@^2.1.0": + version "2.2.0" + resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz" + integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== + dependencies: + "@jridgewell/gen-mapping" "^0.1.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz" + integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== + dependencies: + "@babel/highlight" "^7.18.6" + +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8": + version "7.18.8" + resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.8.tgz" + integrity sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ== + +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.18.10": + version "7.18.10" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.18.10.tgz" + integrity sha512-JQM6k6ENcBFKVtWvLavlvi/mPcpYZ3+R+2EySDEMSMbp7Mn4FexlbbJVrx2R7Ijhr01T8gyqrOaABWIOgxeUyw== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.18.10" + "@babel/helper-compilation-targets" "^7.18.9" + "@babel/helper-module-transforms" "^7.18.9" + "@babel/helpers" "^7.18.9" + "@babel/parser" "^7.18.10" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.18.10" + "@babel/types" "^7.18.10" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" - json5 "^2.1.2" + json5 "^2.2.1" semver "^6.3.0" - source-map "^0.5.0" -"@babel/generator@^7.13.9": - version "7.13.9" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.13.9.tgz#3a7aa96f9efb8e2be42d38d80e2ceb4c64d8de39" - integrity sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw== +"@babel/generator@^7.18.10", "@babel/generator@^7.7.2": + version "7.18.12" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.18.12.tgz" + integrity sha512-dfQ8ebCN98SvyL7IxNMCUtZQSq5R7kxgN+r8qYTGDmmSion1hX2C0zq2yo1bsCDhXixokv1SAWTZUMYbO/V5zg== dependencies: - "@babel/types" "^7.13.0" + "@babel/types" "^7.18.10" + "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" - source-map "^0.5.0" -"@babel/helper-annotate-as-pure@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz#0f58e86dfc4bb3b1fcd7db806570e177d439b6ab" - integrity sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw== +"@babel/helper-annotate-as-pure@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz" + integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA== dependencies: - "@babel/types" "^7.12.13" + "@babel/types" "^7.18.6" -"@babel/helper-builder-binary-assignment-operator-visitor@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.12.13.tgz#6bc20361c88b0a74d05137a65cac8d3cbf6f61fc" - integrity sha512-CZOv9tGphhDRlVjVkAgm8Nhklm9RzSmWpX2my+t7Ua/KT616pEzXsQCjinzvkRvHWJ9itO4f296efroX23XCMA== +"@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz" + integrity sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw== dependencies: - "@babel/helper-explode-assignable-expression" "^7.12.13" - "@babel/types" "^7.12.13" + "@babel/helper-explode-assignable-expression" "^7.18.6" + "@babel/types" "^7.18.9" -"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.13.13", "@babel/helper-compilation-targets@^7.13.8": - version "7.13.13" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.13.tgz#2b2972a0926474853f41e4adbc69338f520600e5" - integrity sha512-q1kcdHNZehBwD9jYPh3WyXcsFERi39X4I59I3NadciWtNDyZ6x+GboOxncFK0kXlKIv6BJm5acncehXWUjWQMQ== +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz" + integrity sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg== dependencies: - "@babel/compat-data" "^7.13.12" - "@babel/helper-validator-option" "^7.12.17" - browserslist "^4.14.5" + "@babel/compat-data" "^7.18.8" + "@babel/helper-validator-option" "^7.18.6" + browserslist "^4.20.2" semver "^6.3.0" -"@babel/helper-create-class-features-plugin@^7.13.0": - version "7.13.11" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.13.11.tgz#30d30a005bca2c953f5653fc25091a492177f4f6" - integrity sha512-ays0I7XYq9xbjCSvT+EvysLgfc3tOkwCULHjrnscGT3A9qD4sk3wXnJ3of0MAWsWGjdinFvajHU2smYuqXKMrw== - dependencies: - "@babel/helper-function-name" "^7.12.13" - "@babel/helper-member-expression-to-functions" "^7.13.0" - "@babel/helper-optimise-call-expression" "^7.12.13" - "@babel/helper-replace-supers" "^7.13.0" - "@babel/helper-split-export-declaration" "^7.12.13" - -"@babel/helper-create-regexp-features-plugin@^7.12.13": - version "7.12.17" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.17.tgz#a2ac87e9e319269ac655b8d4415e94d38d663cb7" - integrity sha512-p2VGmBu9oefLZ2nQpgnEnG0ZlRPvL8gAGvPUMQwUdaE8k49rOMuZpOwdQoy5qJf6K8jL3bcAMhVUlHAjIgJHUg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.12.13" - regexpu-core "^4.7.1" - -"@babel/helper-define-polyfill-provider@^0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.0.tgz#a640051772045fedaaecc6f0c6c69f02bdd34bf1" - integrity sha512-JT8tHuFjKBo8NnaUbblz7mIu1nnvUDiHVjXXkulZULyidvo/7P6TY7+YqpV37IfF+KUFxmlK04elKtGKXaiVgw== - dependencies: - "@babel/helper-compilation-targets" "^7.13.0" - "@babel/helper-module-imports" "^7.12.13" - "@babel/helper-plugin-utils" "^7.13.0" - "@babel/traverse" "^7.13.0" +"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.9.tgz" + integrity sha512-WvypNAYaVh23QcjpMR24CwZY2Nz6hqdOcFdPbNpV56hL5H6KiFheO7Xm1aPdlLQ7d5emYZX7VZwPp9x3z+2opw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.18.9" + "@babel/helper-member-expression-to-functions" "^7.18.9" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/helper-replace-supers" "^7.18.9" + "@babel/helper-split-export-declaration" "^7.18.6" + +"@babel/helper-create-regexp-features-plugin@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.18.6.tgz" + integrity sha512-7LcpH1wnQLGrI+4v+nPp+zUvIkF9x0ddv1Hkdue10tg3gmRnLy97DXh4STiOf1qeIInyD69Qv5kKSZzKD8B/7A== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + regexpu-core "^5.1.0" + +"@babel/helper-define-polyfill-provider@^0.3.2": + version "0.3.2" + resolved "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.2.tgz" + integrity sha512-r9QJJ+uDWrd+94BSPcP6/de67ygLtvVy6cK4luE6MOuDsZIdoaPBnfSpbO/+LTifjPckbKXRuI9BB/Z2/y3iTg== + dependencies: + "@babel/helper-compilation-targets" "^7.17.7" + "@babel/helper-plugin-utils" "^7.16.7" debug "^4.1.1" lodash.debounce "^4.0.8" resolve "^1.14.2" semver "^6.1.2" -"@babel/helper-explode-assignable-expression@^7.12.13": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.13.0.tgz#17b5c59ff473d9f956f40ef570cf3a76ca12657f" - integrity sha512-qS0peLTDP8kOisG1blKbaoBg/o9OSa1qoumMjTK5pM+KDTtpxpsiubnCGP34vK8BXGcb2M9eigwgvoJryrzwWA== +"@babel/helper-environment-visitor@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz" + integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== + +"@babel/helper-explode-assignable-expression@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz" + integrity sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg== dependencies: - "@babel/types" "^7.13.0" - -"@babel/helper-function-name@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz#93ad656db3c3c2232559fd7b2c3dbdcbe0eb377a" - integrity sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA== - dependencies: - "@babel/helper-get-function-arity" "^7.12.13" - "@babel/template" "^7.12.13" - "@babel/types" "^7.12.13" - -"@babel/helper-get-function-arity@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz#bc63451d403a3b3082b97e1d8b3fe5bd4091e583" - integrity sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg== - dependencies: - "@babel/types" "^7.12.13" - -"@babel/helper-hoist-variables@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.13.0.tgz#5d5882e855b5c5eda91e0cadc26c6e7a2c8593d8" - integrity sha512-0kBzvXiIKfsCA0y6cFEIJf4OdzfpRuNk4+YTeHZpGGc666SATFKTz6sRncwFnQk7/ugJ4dSrCj6iJuvW4Qwr2g== - dependencies: - "@babel/traverse" "^7.13.0" - "@babel/types" "^7.13.0" - -"@babel/helper-member-expression-to-functions@^7.13.0", "@babel/helper-member-expression-to-functions@^7.13.12": - version "7.13.12" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz#dfe368f26d426a07299d8d6513821768216e6d72" - integrity sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw== - dependencies: - "@babel/types" "^7.13.12" - -"@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.13.12": - version "7.13.12" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz#c6a369a6f3621cb25da014078684da9196b61977" - integrity sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA== - dependencies: - "@babel/types" "^7.13.12" - -"@babel/helper-module-transforms@^7.13.0", "@babel/helper-module-transforms@^7.13.14": - version "7.13.14" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.13.14.tgz#e600652ba48ccb1641775413cb32cfa4e8b495ef" - integrity sha512-QuU/OJ0iAOSIatyVZmfqB0lbkVP0kDRiKj34xy+QNsnVZi/PA6BoSoreeqnxxa9EHFAIL0R9XOaAR/G9WlIy5g== - dependencies: - "@babel/helper-module-imports" "^7.13.12" - "@babel/helper-replace-supers" "^7.13.12" - "@babel/helper-simple-access" "^7.13.12" - "@babel/helper-split-export-declaration" "^7.12.13" - "@babel/helper-validator-identifier" "^7.12.11" - "@babel/template" "^7.12.13" - "@babel/traverse" "^7.13.13" - "@babel/types" "^7.13.14" - -"@babel/helper-optimise-call-expression@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz#5c02d171b4c8615b1e7163f888c1c81c30a2aaea" - integrity sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA== - dependencies: - "@babel/types" "^7.12.13" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz#806526ce125aed03373bc416a828321e3a6a33af" - integrity sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ== - -"@babel/helper-remap-async-to-generator@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.13.0.tgz#376a760d9f7b4b2077a9dd05aa9c3927cadb2209" - integrity sha512-pUQpFBE9JvC9lrQbpX0TmeNIy5s7GnZjna2lhhcHC7DzgBs6fWn722Y5cfwgrtrqc7NAJwMvOa0mKhq6XaE4jg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.12.13" - "@babel/helper-wrap-function" "^7.13.0" - "@babel/types" "^7.13.0" + "@babel/types" "^7.18.6" + +"@babel/helper-function-name@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz" + integrity sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A== + dependencies: + "@babel/template" "^7.18.6" + "@babel/types" "^7.18.9" + +"@babel/helper-hoist-variables@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz" + integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-member-expression-to-functions@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz" + integrity sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg== + dependencies: + "@babel/types" "^7.18.9" + +"@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz" + integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.9.tgz" + integrity sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-simple-access" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-validator-identifier" "^7.18.6" + "@babel/template" "^7.18.6" + "@babel/traverse" "^7.18.9" + "@babel/types" "^7.18.9" + +"@babel/helper-optimise-call-expression@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz" + integrity sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz" + integrity sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w== + +"@babel/helper-remap-async-to-generator@^7.18.6", "@babel/helper-remap-async-to-generator@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz" + integrity sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-wrap-function" "^7.18.9" + "@babel/types" "^7.18.9" + +"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.18.9.tgz" + integrity sha512-dNsWibVI4lNT6HiuOIBr1oyxo40HvIVmbwPUm3XZ7wMh4k2WxrxTqZwSqw/eEmXDS9np0ey5M2bz9tBmO9c+YQ== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-member-expression-to-functions" "^7.18.9" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/traverse" "^7.18.9" + "@babel/types" "^7.18.9" + +"@babel/helper-simple-access@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz" + integrity sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-skip-transparent-expression-wrappers@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz" + integrity sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw== + dependencies: + "@babel/types" "^7.18.9" + +"@babel/helper-split-export-declaration@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz" + integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-string-parser@^7.18.10": + version "7.18.10" + resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz" + integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw== + +"@babel/helper-validator-identifier@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz" + integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== + +"@babel/helper-validator-option@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz" + integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== + +"@babel/helper-wrap-function@^7.18.9": + version "7.18.11" + resolved "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.18.11.tgz" + integrity sha512-oBUlbv+rjZLh2Ks9SKi4aL7eKaAXBWleHzU89mP0G6BMUlRxSckk9tSIkgDGydhgFxHuGSlBQZfnaD47oBEB7w== + dependencies: + "@babel/helper-function-name" "^7.18.9" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.18.11" + "@babel/types" "^7.18.10" + +"@babel/helpers@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.9.tgz" + integrity sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ== + dependencies: + "@babel/template" "^7.18.6" + "@babel/traverse" "^7.18.9" + "@babel/types" "^7.18.9" + +"@babel/highlight@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz" + integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + chalk "^2.0.0" + js-tokens "^4.0.0" -"@babel/helper-replace-supers@^7.12.13", "@babel/helper-replace-supers@^7.13.0", "@babel/helper-replace-supers@^7.13.12": - version "7.13.12" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.13.12.tgz#6442f4c1ad912502481a564a7386de0c77ff3804" - integrity sha512-Gz1eiX+4yDO8mT+heB94aLVNCL+rbuT2xy4YfyNqu8F+OI6vMvJK891qGBTqL9Uc8wxEvRW92Id6G7sDen3fFw== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.13.12" - "@babel/helper-optimise-call-expression" "^7.12.13" - "@babel/traverse" "^7.13.0" - "@babel/types" "^7.13.12" +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.18.11": + version "7.18.11" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.18.11.tgz" + integrity sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ== -"@babel/helper-simple-access@^7.12.13", "@babel/helper-simple-access@^7.13.12": - version "7.13.12" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz#dd6c538afb61819d205a012c31792a39c7a5eaf6" - integrity sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA== +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz" + integrity sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ== dependencies: - "@babel/types" "^7.13.12" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/helper-skip-transparent-expression-wrappers@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz#462dc63a7e435ade8468385c63d2b84cce4b3cbf" - integrity sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA== +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz" + integrity sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg== dependencies: - "@babel/types" "^7.12.1" - -"@babel/helper-split-export-declaration@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz#e9430be00baf3e88b0e13e6f9d4eaf2136372b05" - integrity sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg== - dependencies: - "@babel/types" "^7.12.13" - -"@babel/helper-validator-identifier@^7.12.11": - version "7.12.11" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed" - integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw== - -"@babel/helper-validator-option@^7.12.17": - version "7.12.17" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz#d1fbf012e1a79b7eebbfdc6d270baaf8d9eb9831" - integrity sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw== - -"@babel/helper-wrap-function@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.13.0.tgz#bdb5c66fda8526ec235ab894ad53a1235c79fcc4" - integrity sha512-1UX9F7K3BS42fI6qd2A4BjKzgGjToscyZTdp1DjknHLCIvpgne6918io+aL5LXFcER/8QWiwpoY902pVEqgTXA== - dependencies: - "@babel/helper-function-name" "^7.12.13" - "@babel/template" "^7.12.13" - "@babel/traverse" "^7.13.0" - "@babel/types" "^7.13.0" - -"@babel/helpers@^7.13.10": - version "7.13.10" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.13.10.tgz#fd8e2ba7488533cdeac45cc158e9ebca5e3c7df8" - integrity sha512-4VO883+MWPDUVRF3PhiLBUFHoX/bsLTGFpFK/HqvvfBZz2D57u9XzPVNFVBTc0PW/CWR9BXTOKt8NF4DInUHcQ== - dependencies: - "@babel/template" "^7.12.13" - "@babel/traverse" "^7.13.0" - "@babel/types" "^7.13.0" - -"@babel/highlight@^7.10.4", "@babel/highlight@^7.12.13": - version "7.13.10" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.13.10.tgz#a8b2a66148f5b27d666b15d81774347a731d52d1" - integrity sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg== - dependencies: - "@babel/helper-validator-identifier" "^7.12.11" - chalk "^2.0.0" - js-tokens "^4.0.0" - -"@babel/parser@^7.12.13", "@babel/parser@^7.13.15": - version "7.13.15" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.13.15.tgz#8e66775fb523599acb6a289e12929fa5ab0954d8" - integrity sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ== + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" + "@babel/plugin-proposal-optional-chaining" "^7.18.9" -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.13.12": - version "7.13.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.13.12.tgz#a3484d84d0b549f3fc916b99ee4783f26fabad2a" - integrity sha512-d0u3zWKcoZf379fOeJdr1a5WPDny4aOFZ6hlfKivgK0LY7ZxNfoaHL2fWwdGtHyVvra38FC+HVYkO+byfSA8AQ== +"@babel/plugin-proposal-async-generator-functions@^7.18.10": + version "7.18.10" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.18.10.tgz" + integrity sha512-1mFuY2TOsR1hxbjCo4QL+qlIjV07p4H4EUYw2J/WCqsvFV6V9X9z9YhXbWndc/4fw+hYGlDT7egYxliMp5O6Ew== dependencies: - "@babel/helper-plugin-utils" "^7.13.0" - "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1" - "@babel/plugin-proposal-optional-chaining" "^7.13.12" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-remap-async-to-generator" "^7.18.9" + "@babel/plugin-syntax-async-generators" "^7.8.4" -"@babel/plugin-proposal-async-generator-functions@^7.13.15": - version "7.13.15" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.13.15.tgz#80e549df273a3b3050431b148c892491df1bcc5b" - integrity sha512-VapibkWzFeoa6ubXy/NgV5U2U4MVnUlvnx6wo1XhlsaTrLYWE0UFpDQsVrmn22q5CzeloqJ8gEMHSKxuee6ZdA== +"@babel/plugin-proposal-class-properties@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz" + integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ== dependencies: - "@babel/helper-plugin-utils" "^7.13.0" - "@babel/helper-remap-async-to-generator" "^7.13.0" - "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-proposal-class-properties@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.13.0.tgz#146376000b94efd001e57a40a88a525afaab9f37" - integrity sha512-KnTDjFNC1g+45ka0myZNvSBFLhNCLN+GeGYLDEA8Oq7MZ6yMgfLoIRh86GRT0FjtJhZw8JyUskP9uvj5pHM9Zg== +"@babel/plugin-proposal-class-static-block@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz" + integrity sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw== dependencies: - "@babel/helper-create-class-features-plugin" "^7.13.0" - "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-class-static-block" "^7.14.5" -"@babel/plugin-proposal-dynamic-import@^7.13.8": - version "7.13.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.13.8.tgz#876a1f6966e1dec332e8c9451afda3bebcdf2e1d" - integrity sha512-ONWKj0H6+wIRCkZi9zSbZtE/r73uOhMVHh256ys0UzfM7I3d4n+spZNWjOnJv2gzopumP2Wxi186vI8N0Y2JyQ== +"@babel/plugin-proposal-dynamic-import@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz" + integrity sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw== dependencies: - "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-dynamic-import" "^7.8.3" -"@babel/plugin-proposal-export-namespace-from@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.13.tgz#393be47a4acd03fa2af6e3cde9b06e33de1b446d" - integrity sha512-INAgtFo4OnLN3Y/j0VwAgw3HDXcDtX+C/erMvWzuV9v71r7urb6iyMXu7eM9IgLr1ElLlOkaHjJ0SbCmdOQ3Iw== +"@babel/plugin-proposal-export-namespace-from@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz" + integrity sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-plugin-utils" "^7.18.9" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" -"@babel/plugin-proposal-json-strings@^7.13.8": - version "7.13.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.13.8.tgz#bf1fb362547075afda3634ed31571c5901afef7b" - integrity sha512-w4zOPKUFPX1mgvTmL/fcEqy34hrQ1CRcGxdphBc6snDnnqJ47EZDIyop6IwXzAC8G916hsIuXB2ZMBCExC5k7Q== +"@babel/plugin-proposal-json-strings@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz" + integrity sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ== dependencies: - "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-json-strings" "^7.8.3" -"@babel/plugin-proposal-logical-assignment-operators@^7.13.8": - version "7.13.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.13.8.tgz#93fa78d63857c40ce3c8c3315220fd00bfbb4e1a" - integrity sha512-aul6znYB4N4HGweImqKn59Su9RS8lbUIqxtXTOcAGtNIDczoEFv+l1EhmX8rUBp3G1jMjKJm8m0jXVp63ZpS4A== +"@babel/plugin-proposal-logical-assignment-operators@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz" + integrity sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q== dependencies: - "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-plugin-utils" "^7.18.9" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" -"@babel/plugin-proposal-nullish-coalescing-operator@^7.13.8": - version "7.13.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.13.8.tgz#3730a31dafd3c10d8ccd10648ed80a2ac5472ef3" - integrity sha512-iePlDPBn//UhxExyS9KyeYU7RM9WScAG+D3Hhno0PLJebAEpDZMocbDe64eqynhNAnwz/vZoL/q/QB2T1OH39A== +"@babel/plugin-proposal-nullish-coalescing-operator@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz" + integrity sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA== dependencies: - "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" -"@babel/plugin-proposal-numeric-separator@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.13.tgz#bd9da3188e787b5120b4f9d465a8261ce67ed1db" - integrity sha512-O1jFia9R8BUCl3ZGB7eitaAPu62TXJRHn7rh+ojNERCFyqRwJMTmhz+tJ+k0CwI6CLjX/ee4qW74FSqlq9I35w== +"@babel/plugin-proposal-numeric-separator@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz" + integrity sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-numeric-separator" "^7.10.4" -"@babel/plugin-proposal-object-rest-spread@^7.13.8": - version "7.13.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.13.8.tgz#5d210a4d727d6ce3b18f9de82cc99a3964eed60a" - integrity sha512-DhB2EuB1Ih7S3/IRX5AFVgZ16k3EzfRbq97CxAVI1KSYcW+lexV8VZb7G7L8zuPVSdQMRn0kiBpf/Yzu9ZKH0g== +"@babel/plugin-proposal-object-rest-spread@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz" + integrity sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q== dependencies: - "@babel/compat-data" "^7.13.8" - "@babel/helper-compilation-targets" "^7.13.8" - "@babel/helper-plugin-utils" "^7.13.0" + "@babel/compat-data" "^7.18.8" + "@babel/helper-compilation-targets" "^7.18.9" + "@babel/helper-plugin-utils" "^7.18.9" "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.13.0" + "@babel/plugin-transform-parameters" "^7.18.8" -"@babel/plugin-proposal-optional-catch-binding@^7.13.8": - version "7.13.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.13.8.tgz#3ad6bd5901506ea996fc31bdcf3ccfa2bed71107" - integrity sha512-0wS/4DUF1CuTmGo+NiaHfHcVSeSLj5S3e6RivPTg/2k3wOv3jO35tZ6/ZWsQhQMvdgI7CwphjQa/ccarLymHVA== +"@babel/plugin-proposal-optional-catch-binding@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz" + integrity sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw== dependencies: - "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -"@babel/plugin-proposal-optional-chaining@^7.13.12": - version "7.13.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.13.12.tgz#ba9feb601d422e0adea6760c2bd6bbb7bfec4866" - integrity sha512-fcEdKOkIB7Tf4IxrgEVeFC4zeJSTr78no9wTdBuZZbqF64kzllU0ybo2zrzm7gUQfxGhBgq4E39oRs8Zx/RMYQ== +"@babel/plugin-proposal-optional-chaining@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz" + integrity sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w== dependencies: - "@babel/helper-plugin-utils" "^7.13.0" - "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1" + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" "@babel/plugin-syntax-optional-chaining" "^7.8.3" -"@babel/plugin-proposal-private-methods@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.13.0.tgz#04bd4c6d40f6e6bbfa2f57e2d8094bad900ef787" - integrity sha512-MXyyKQd9inhx1kDYPkFRVOBXQ20ES8Pto3T7UZ92xj2mY0EVD8oAVzeyYuVfy/mxAdTSIayOvg+aVzcHV2bn6Q== +"@babel/plugin-proposal-private-methods@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz" + integrity sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA== dependencies: - "@babel/helper-create-class-features-plugin" "^7.13.0" - "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-proposal-unicode-property-regex@^7.12.13", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.13.tgz#bebde51339be829c17aaaaced18641deb62b39ba" - integrity sha512-XyJmZidNfofEkqFV5VC/bLabGmO5QzenPO/YOfGuEbgU+2sSwMmio3YLb4WtBgcmmdwZHyVyv8on77IUjQ5Gvg== +"@babel/plugin-proposal-private-property-in-object@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz" + integrity sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.12.13" - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + +"@babel/plugin-proposal-unicode-property-regex@^7.18.6", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz" + integrity sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz" integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-class-properties@^7.12.13": +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13", "@babel/plugin-syntax-class-properties@^7.8.3": version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz" integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== dependencies: "@babel/helper-plugin-utils" "^7.12.13" +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/plugin-syntax-dynamic-import@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz" integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-export-namespace-from@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz" integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-syntax-import-assertions@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.18.6.tgz" + integrity sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-syntax-import-meta@^7.8.3": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-json-strings@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz" integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz" integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz" integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-numeric-separator@^7.10.4": +"@babel/plugin-syntax-numeric-separator@^7.10.4", "@babel/plugin-syntax-numeric-separator@^7.8.3": version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz" integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-object-rest-spread@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz" integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-optional-catch-binding@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz" integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-optional-chaining@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz" integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-top-level-await@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.13.tgz#c5f0fa6e249f5b739727f923540cf7a806130178" - integrity sha512-A81F9pDwyS7yM//KwbCSDqy3Uj4NMIurtplxphWxoYtNPov7cJsDkAFNNyVlIZ3jwGycVsurZ+LtOA8gZ376iQ== +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-arrow-functions@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.13.0.tgz#10a59bebad52d637a027afa692e8d5ceff5e3dae" - integrity sha512-96lgJagobeVmazXFaDrbmCLQxBysKu7U6Do3mLsx27gf5Dk85ezysrs2BZUpXD703U/Su1xTBDxxar2oa4jAGg== +"@babel/plugin-syntax-top-level-await@^7.14.5", "@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== dependencies: - "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-async-to-generator@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.13.0.tgz#8e112bf6771b82bf1e974e5e26806c5c99aa516f" - integrity sha512-3j6E004Dx0K3eGmhxVJxwwI89CTJrce7lg3UrtFuDAVQ/2+SJ/h/aSFOeE6/n0WB1GsOffsJp6MnPQNQ8nmwhg== +"@babel/plugin-syntax-typescript@^7.18.6", "@babel/plugin-syntax-typescript@^7.7.2": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz" + integrity sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA== dependencies: - "@babel/helper-module-imports" "^7.12.13" - "@babel/helper-plugin-utils" "^7.13.0" - "@babel/helper-remap-async-to-generator" "^7.13.0" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-block-scoped-functions@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.13.tgz#a9bf1836f2a39b4eb6cf09967739de29ea4bf4c4" - integrity sha512-zNyFqbc3kI/fVpqwfqkg6RvBgFpC4J18aKKMmv7KdQ/1GgREapSJAykLMVNwfRGO3BtHj3YQZl8kxCXPcVMVeg== +"@babel/plugin-transform-arrow-functions@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz" + integrity sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-block-scoping@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.13.tgz#f36e55076d06f41dfd78557ea039c1b581642e61" - integrity sha512-Pxwe0iqWJX4fOOM2kEZeUuAxHMWb9nK+9oh5d11bsLoB0xMg+mkDpt0eYuDZB7ETrY9bbcVlKUGTOGWy7BHsMQ== - dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - -"@babel/plugin-transform-classes@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.13.0.tgz#0265155075c42918bf4d3a4053134176ad9b533b" - integrity sha512-9BtHCPUARyVH1oXGcSJD3YpsqRLROJx5ZNP6tN5vnk17N0SVf9WCtf8Nuh1CFmgByKKAIMstitKduoCmsaDK5g== - dependencies: - "@babel/helper-annotate-as-pure" "^7.12.13" - "@babel/helper-function-name" "^7.12.13" - "@babel/helper-optimise-call-expression" "^7.12.13" - "@babel/helper-plugin-utils" "^7.13.0" - "@babel/helper-replace-supers" "^7.13.0" - "@babel/helper-split-export-declaration" "^7.12.13" - globals "^11.1.0" - -"@babel/plugin-transform-computed-properties@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.13.0.tgz#845c6e8b9bb55376b1fa0b92ef0bdc8ea06644ed" - integrity sha512-RRqTYTeZkZAz8WbieLTvKUEUxZlUTdmL5KGMyZj7FnMfLNKV4+r5549aORG/mgojRmFlQMJDUupwAMiF2Q7OUg== - dependencies: - "@babel/helper-plugin-utils" "^7.13.0" - -"@babel/plugin-transform-destructuring@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.13.0.tgz#c5dce270014d4e1ebb1d806116694c12b7028963" - integrity sha512-zym5em7tePoNT9s964c0/KU3JPPnuq7VhIxPRefJ4/s82cD+q1mgKfuGRDMCPL0HTyKz4dISuQlCusfgCJ86HA== - dependencies: - "@babel/helper-plugin-utils" "^7.13.0" - -"@babel/plugin-transform-dotall-regex@^7.12.13", "@babel/plugin-transform-dotall-regex@^7.4.4": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.13.tgz#3f1601cc29905bfcb67f53910f197aeafebb25ad" - integrity sha512-foDrozE65ZFdUC2OfgeOCrEPTxdB3yjqxpXh8CH+ipd9CHd4s/iq81kcUpyH8ACGNEPdFqbtzfgzbT/ZGlbDeQ== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.12.13" - "@babel/helper-plugin-utils" "^7.12.13" - -"@babel/plugin-transform-duplicate-keys@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.13.tgz#6f06b87a8b803fd928e54b81c258f0a0033904de" - integrity sha512-NfADJiiHdhLBW3pulJlJI2NB0t4cci4WTZ8FtdIuNc2+8pslXdPtRRAEWqUY+m9kNOk2eRYbTAOipAxlrOcwwQ== +"@babel/plugin-transform-async-to-generator@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz" + integrity sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-remap-async-to-generator" "^7.18.6" -"@babel/plugin-transform-exponentiation-operator@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.13.tgz#4d52390b9a273e651e4aba6aee49ef40e80cd0a1" - integrity sha512-fbUelkM1apvqez/yYx1/oICVnGo2KM5s63mhGylrmXUxK/IAXSIf87QIxVfZldWf4QsOafY6vV3bX8aMHSvNrA== +"@babel/plugin-transform-block-scoped-functions@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz" + integrity sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ== dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.12.13" - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-for-of@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.13.0.tgz#c799f881a8091ac26b54867a845c3e97d2696062" - integrity sha512-IHKT00mwUVYE0zzbkDgNRP6SRzvfGCYsOxIRz8KsiaaHCcT9BWIkO+H9QRJseHBLOGBZkHUdHiqj6r0POsdytg== +"@babel/plugin-transform-block-scoping@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.9.tgz" + integrity sha512-5sDIJRV1KtQVEbt/EIBwGy4T01uYIo4KRB3VUqzkhrAIOGx7AoctL9+Ux88btY0zXdDyPJ9mW+bg+v+XEkGmtw== dependencies: - "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-function-name@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.13.tgz#bb024452f9aaed861d374c8e7a24252ce3a50051" - integrity sha512-6K7gZycG0cmIwwF7uMK/ZqeCikCGVBdyP2J5SKNCXO5EOHcqi+z7Jwf8AmyDNcBgxET8DrEtCt/mPKPyAzXyqQ== +"@babel/plugin-transform-classes@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.9.tgz" + integrity sha512-EkRQxsxoytpTlKJmSPYrsOMjCILacAjtSVkd4gChEe2kXjFCun3yohhW5I7plXJhCemM0gKsaGMcO8tinvCA5g== dependencies: - "@babel/helper-function-name" "^7.12.13" - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.18.9" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-replace-supers" "^7.18.9" + "@babel/helper-split-export-declaration" "^7.18.6" + globals "^11.1.0" -"@babel/plugin-transform-literals@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.13.tgz#2ca45bafe4a820197cf315794a4d26560fe4bdb9" - integrity sha512-FW+WPjSR7hiUxMcKqyNjP05tQ2kmBCdpEpZHY1ARm96tGQCCBvXKnpjILtDplUnJ/eHZ0lALLM+d2lMFSpYJrQ== +"@babel/plugin-transform-computed-properties@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz" + integrity sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-member-expression-literals@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.13.tgz#5ffa66cd59b9e191314c9f1f803b938e8c081e40" - integrity sha512-kxLkOsg8yir4YeEPHLuO2tXP9R/gTjpuTOjshqSpELUN3ZAg2jfDnKUvzzJxObun38sw3wm4Uu69sX/zA7iRvg== +"@babel/plugin-transform-destructuring@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.9.tgz" + integrity sha512-p5VCYNddPLkZTq4XymQIaIfZNJwT9YsjkPOhkVEqt6QIpQFZVM9IltqqYpOEkJoN1DPznmxUDyZ5CTZs/ZCuHA== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-modules-amd@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.13.0.tgz#19f511d60e3d8753cc5a6d4e775d3a5184866cc3" - integrity sha512-EKy/E2NHhY/6Vw5d1k3rgoobftcNUmp9fGjb9XZwQLtTctsRBOTRO7RHHxfIky1ogMN5BxN7p9uMA3SzPfotMQ== +"@babel/plugin-transform-dotall-regex@^7.18.6", "@babel/plugin-transform-dotall-regex@^7.4.4": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz" + integrity sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg== dependencies: - "@babel/helper-module-transforms" "^7.13.0" - "@babel/helper-plugin-utils" "^7.13.0" - babel-plugin-dynamic-import-node "^2.3.3" + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-modules-commonjs@^7.13.8": - version "7.13.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.13.8.tgz#7b01ad7c2dcf2275b06fa1781e00d13d420b3e1b" - integrity sha512-9QiOx4MEGglfYZ4XOnU79OHr6vIWUakIj9b4mioN8eQIoEh+pf5p/zEB36JpDFWA12nNMiRf7bfoRvl9Rn79Bw== +"@babel/plugin-transform-duplicate-keys@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz" + integrity sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw== dependencies: - "@babel/helper-module-transforms" "^7.13.0" - "@babel/helper-plugin-utils" "^7.13.0" - "@babel/helper-simple-access" "^7.12.13" - babel-plugin-dynamic-import-node "^2.3.3" + "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-modules-systemjs@^7.13.8": - version "7.13.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.13.8.tgz#6d066ee2bff3c7b3d60bf28dec169ad993831ae3" - integrity sha512-hwqctPYjhM6cWvVIlOIe27jCIBgHCsdH2xCJVAYQm7V5yTMoilbVMi9f6wKg0rpQAOn6ZG4AOyvCqFF/hUh6+A== +"@babel/plugin-transform-exponentiation-operator@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz" + integrity sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw== dependencies: - "@babel/helper-hoist-variables" "^7.13.0" - "@babel/helper-module-transforms" "^7.13.0" - "@babel/helper-plugin-utils" "^7.13.0" - "@babel/helper-validator-identifier" "^7.12.11" - babel-plugin-dynamic-import-node "^2.3.3" + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-modules-umd@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.13.0.tgz#8a3d96a97d199705b9fd021580082af81c06e70b" - integrity sha512-D/ILzAh6uyvkWjKKyFE/W0FzWwasv6vPTSqPcjxFqn6QpX3u8DjRVliq4F2BamO2Wee/om06Vyy+vPkNrd4wxw== +"@babel/plugin-transform-for-of@^7.18.8": + version "7.18.8" + resolved "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz" + integrity sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ== dependencies: - "@babel/helper-module-transforms" "^7.13.0" - "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-named-capturing-groups-regex@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.13.tgz#2213725a5f5bbbe364b50c3ba5998c9599c5c9d9" - integrity sha512-Xsm8P2hr5hAxyYblrfACXpQKdQbx4m2df9/ZZSQ8MAhsadw06+jW7s9zsSw6he+mJZXRlVMyEnVktJo4zjk1WA== +"@babel/plugin-transform-function-name@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz" + integrity sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.12.13" + "@babel/helper-compilation-targets" "^7.18.9" + "@babel/helper-function-name" "^7.18.9" + "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-new-target@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.13.tgz#e22d8c3af24b150dd528cbd6e685e799bf1c351c" - integrity sha512-/KY2hbLxrG5GTQ9zzZSc3xWiOy379pIETEhbtzwZcw9rvuaVV4Fqy7BYGYOWZnaoXIQYbbJ0ziXLa/sKcGCYEQ== +"@babel/plugin-transform-literals@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz" + integrity sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-object-super@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.13.tgz#b4416a2d63b8f7be314f3d349bd55a9c1b5171f7" - integrity sha512-JzYIcj3XtYspZDV8j9ulnoMPZZnF/Cj0LUxPOjR89BdBVx+zYJI9MdMIlUZjbXDX+6YVeS6I3e8op+qQ3BYBoQ== +"@babel/plugin-transform-member-expression-literals@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz" + integrity sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - "@babel/helper-replace-supers" "^7.12.13" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-parameters@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.13.0.tgz#8fa7603e3097f9c0b7ca1a4821bc2fb52e9e5007" - integrity sha512-Jt8k/h/mIwE2JFEOb3lURoY5C85ETcYPnbuAJ96zRBzh1XHtQZfs62ChZ6EP22QlC8c7Xqr9q+e1SU5qttwwjw== +"@babel/plugin-transform-modules-amd@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.6.tgz" + integrity sha512-Pra5aXsmTsOnjM3IajS8rTaLCy++nGM4v3YR4esk5PCsyg9z8NA5oQLwxzMUtDBd8F+UmVza3VxoAaWCbzH1rg== dependencies: - "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-module-transforms" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-property-literals@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.13.tgz#4e6a9e37864d8f1b3bc0e2dce7bf8857db8b1a81" - integrity sha512-nqVigwVan+lR+g8Fj8Exl0UQX2kymtjcWfMOYM1vTYEKujeyv2SkMgazf2qNcK7l4SDiKyTA/nHCPqL4e2zo1A== +"@babel/plugin-transform-modules-commonjs@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz" + integrity sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-module-transforms" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-simple-access" "^7.18.6" + babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-regenerator@^7.13.15": - version "7.13.15" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.13.15.tgz#e5eb28945bf8b6563e7f818945f966a8d2997f39" - integrity sha512-Bk9cOLSz8DiurcMETZ8E2YtIVJbFCPGW28DJWUakmyVWtQSm6Wsf0p4B4BfEr/eL2Nkhe/CICiUiMOCi1TPhuQ== +"@babel/plugin-transform-modules-systemjs@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.18.9.tgz" + integrity sha512-zY/VSIbbqtoRoJKo2cDTewL364jSlZGvn0LKOf9ntbfxOvjfmyrdtEEOAdswOswhZEb8UH3jDkCKHd1sPgsS0A== dependencies: - regenerator-transform "^0.14.2" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-module-transforms" "^7.18.9" + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-validator-identifier" "^7.18.6" + babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-reserved-words@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.13.tgz#7d9988d4f06e0fe697ea1d9803188aa18b472695" - integrity sha512-xhUPzDXxZN1QfiOy/I5tyye+TRz6lA7z6xaT4CLOjPRMVg1ldRf0LHw0TDBpYL4vG78556WuHdyO9oi5UmzZBg== +"@babel/plugin-transform-modules-umd@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz" + integrity sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-module-transforms" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-runtime@^7.9.6": - version "7.13.15" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.13.15.tgz#2eddf585dd066b84102517e10a577f24f76a9cd7" - integrity sha512-d+ezl76gx6Jal08XngJUkXM4lFXK/5Ikl9Mh4HKDxSfGJXmZ9xG64XT2oivBzfxb/eQ62VfvoMkaCZUKJMVrBA== +"@babel/plugin-transform-named-capturing-groups-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.18.6.tgz" + integrity sha512-UmEOGF8XgaIqD74bC8g7iV3RYj8lMf0Bw7NJzvnS9qQhM4mg+1WHKotUIdjxgD2RGrgFLZZPCFPFj3P/kVDYhg== dependencies: - "@babel/helper-module-imports" "^7.13.12" - "@babel/helper-plugin-utils" "^7.13.0" - babel-plugin-polyfill-corejs2 "^0.2.0" - babel-plugin-polyfill-corejs3 "^0.2.0" - babel-plugin-polyfill-regenerator "^0.2.0" - semver "^6.3.0" + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-shorthand-properties@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.13.tgz#db755732b70c539d504c6390d9ce90fe64aff7ad" - integrity sha512-xpL49pqPnLtf0tVluuqvzWIgLEhuPpZzvs2yabUHSKRNlN7ScYU7aMlmavOeyXJZKgZKQRBlh8rHbKiJDraTSw== +"@babel/plugin-transform-new-target@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz" + integrity sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-spread@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.13.0.tgz#84887710e273c1815ace7ae459f6f42a5d31d5fd" - integrity sha512-V6vkiXijjzYeFmQTr3dBxPtZYLPcUfY34DebOU27jIl2M/Y8Egm52Hw82CSjjPqd54GTlJs5x+CR7HeNr24ckg== +"@babel/plugin-transform-object-super@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz" + integrity sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA== dependencies: - "@babel/helper-plugin-utils" "^7.13.0" - "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-replace-supers" "^7.18.6" -"@babel/plugin-transform-sticky-regex@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.13.tgz#760ffd936face73f860ae646fb86ee82f3d06d1f" - integrity sha512-Jc3JSaaWT8+fr7GRvQP02fKDsYk4K/lYwWq38r/UGfaxo89ajud321NH28KRQ7xy1Ybc0VUE5Pz8psjNNDUglg== +"@babel/plugin-transform-parameters@^7.18.8": + version "7.18.8" + resolved "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz" + integrity sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-template-literals@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.13.0.tgz#a36049127977ad94438dee7443598d1cefdf409d" - integrity sha512-d67umW6nlfmr1iehCcBv69eSUSySk1EsIS8aTDX4Xo9qajAh6mYtcl4kJrBkGXuxZPEgVr7RVfAvNW6YQkd4Mw== +"@babel/plugin-transform-property-literals@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz" + integrity sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg== dependencies: - "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-typeof-symbol@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.13.tgz#785dd67a1f2ea579d9c2be722de8c84cb85f5a7f" - integrity sha512-eKv/LmUJpMnu4npgfvs3LiHhJua5fo/CysENxa45YCQXZwKnGCQKAg87bvoqSW1fFT+HA32l03Qxsm8ouTY3ZQ== +"@babel/plugin-transform-regenerator@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz" + integrity sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-plugin-utils" "^7.18.6" + regenerator-transform "^0.15.0" -"@babel/plugin-transform-unicode-escapes@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.13.tgz#840ced3b816d3b5127dd1d12dcedc5dead1a5e74" - integrity sha512-0bHEkdwJ/sN/ikBHfSmOXPypN/beiGqjo+o4/5K+vxEFNPRPdImhviPakMKG4x96l85emoa0Z6cDflsdBusZbw== +"@babel/plugin-transform-reserved-words@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz" + integrity sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-unicode-regex@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.13.tgz#b52521685804e155b1202e83fc188d34bb70f5ac" - integrity sha512-mDRzSNY7/zopwisPZ5kM9XKCfhchqIYwAKRERtEnhYscZB79VRekuRSoYbN0+KVe3y8+q1h6A4svXtP7N+UoCA== +"@babel/plugin-transform-runtime@^7.18.10": + version "7.18.10" + resolved "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.10.tgz" + integrity sha512-q5mMeYAdfEbpBAgzl7tBre/la3LeCxmDO1+wMXRdPWbcoMjR3GiXlCLk7JBZVVye0bqTGNMbt0yYVXX1B1jEWQ== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.12.13" - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.9" + babel-plugin-polyfill-corejs2 "^0.3.2" + babel-plugin-polyfill-corejs3 "^0.5.3" + babel-plugin-polyfill-regenerator "^0.4.0" + semver "^6.3.0" -"@babel/preset-env@^7.9.6": - version "7.13.15" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.13.15.tgz#c8a6eb584f96ecba183d3d414a83553a599f478f" - integrity sha512-D4JAPMXcxk69PKe81jRJ21/fP/uYdcTZ3hJDF5QX2HSI9bBxxYw/dumdR6dGumhjxlprHPE4XWoPaqzZUVy2MA== - dependencies: - "@babel/compat-data" "^7.13.15" - "@babel/helper-compilation-targets" "^7.13.13" - "@babel/helper-plugin-utils" "^7.13.0" - "@babel/helper-validator-option" "^7.12.17" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.13.12" - "@babel/plugin-proposal-async-generator-functions" "^7.13.15" - "@babel/plugin-proposal-class-properties" "^7.13.0" - "@babel/plugin-proposal-dynamic-import" "^7.13.8" - "@babel/plugin-proposal-export-namespace-from" "^7.12.13" - "@babel/plugin-proposal-json-strings" "^7.13.8" - "@babel/plugin-proposal-logical-assignment-operators" "^7.13.8" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.13.8" - "@babel/plugin-proposal-numeric-separator" "^7.12.13" - "@babel/plugin-proposal-object-rest-spread" "^7.13.8" - "@babel/plugin-proposal-optional-catch-binding" "^7.13.8" - "@babel/plugin-proposal-optional-chaining" "^7.13.12" - "@babel/plugin-proposal-private-methods" "^7.13.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.12.13" +"@babel/plugin-transform-shorthand-properties@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz" + integrity sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-spread@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.18.9.tgz" + integrity sha512-39Q814wyoOPtIB/qGopNIL9xDChOE1pNU0ZY5dO0owhiVt/5kFm4li+/bBtwc7QotG0u5EPzqhZdjMtmqBqyQA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" + +"@babel/plugin-transform-sticky-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz" + integrity sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-template-literals@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz" + integrity sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/plugin-transform-typeof-symbol@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz" + integrity sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/plugin-transform-typescript@^7.18.6": + version "7.18.12" + resolved "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.18.12.tgz" + integrity sha512-2vjjam0cum0miPkenUbQswKowuxs/NjMwIKEq0zwegRxXk12C9YOF9STXnaUptITOtOJHKHpzvvWYOjbm6tc0w== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.18.9" + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-syntax-typescript" "^7.18.6" + +"@babel/plugin-transform-unicode-escapes@^7.18.10": + version "7.18.10" + resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz" + integrity sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/plugin-transform-unicode-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz" + integrity sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/preset-env@^7.18.10": + version "7.18.10" + resolved "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.18.10.tgz" + integrity sha512-wVxs1yjFdW3Z/XkNfXKoblxoHgbtUF7/l3PvvP4m02Qz9TZ6uZGxRVYjSQeR87oQmHco9zWitW5J82DJ7sCjvA== + dependencies: + "@babel/compat-data" "^7.18.8" + "@babel/helper-compilation-targets" "^7.18.9" + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-validator-option" "^7.18.6" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.18.9" + "@babel/plugin-proposal-async-generator-functions" "^7.18.10" + "@babel/plugin-proposal-class-properties" "^7.18.6" + "@babel/plugin-proposal-class-static-block" "^7.18.6" + "@babel/plugin-proposal-dynamic-import" "^7.18.6" + "@babel/plugin-proposal-export-namespace-from" "^7.18.9" + "@babel/plugin-proposal-json-strings" "^7.18.6" + "@babel/plugin-proposal-logical-assignment-operators" "^7.18.9" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.6" + "@babel/plugin-proposal-numeric-separator" "^7.18.6" + "@babel/plugin-proposal-object-rest-spread" "^7.18.9" + "@babel/plugin-proposal-optional-catch-binding" "^7.18.6" + "@babel/plugin-proposal-optional-chaining" "^7.18.9" + "@babel/plugin-proposal-private-methods" "^7.18.6" + "@babel/plugin-proposal-private-property-in-object" "^7.18.6" + "@babel/plugin-proposal-unicode-property-regex" "^7.18.6" "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" "@babel/plugin-syntax-dynamic-import" "^7.8.3" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-import-assertions" "^7.18.6" "@babel/plugin-syntax-json-strings" "^7.8.3" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" @@ -759,51 +850,52 @@ "@babel/plugin-syntax-object-rest-spread" "^7.8.3" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-top-level-await" "^7.12.13" - "@babel/plugin-transform-arrow-functions" "^7.13.0" - "@babel/plugin-transform-async-to-generator" "^7.13.0" - "@babel/plugin-transform-block-scoped-functions" "^7.12.13" - "@babel/plugin-transform-block-scoping" "^7.12.13" - "@babel/plugin-transform-classes" "^7.13.0" - "@babel/plugin-transform-computed-properties" "^7.13.0" - "@babel/plugin-transform-destructuring" "^7.13.0" - "@babel/plugin-transform-dotall-regex" "^7.12.13" - "@babel/plugin-transform-duplicate-keys" "^7.12.13" - "@babel/plugin-transform-exponentiation-operator" "^7.12.13" - "@babel/plugin-transform-for-of" "^7.13.0" - "@babel/plugin-transform-function-name" "^7.12.13" - "@babel/plugin-transform-literals" "^7.12.13" - "@babel/plugin-transform-member-expression-literals" "^7.12.13" - "@babel/plugin-transform-modules-amd" "^7.13.0" - "@babel/plugin-transform-modules-commonjs" "^7.13.8" - "@babel/plugin-transform-modules-systemjs" "^7.13.8" - "@babel/plugin-transform-modules-umd" "^7.13.0" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.12.13" - "@babel/plugin-transform-new-target" "^7.12.13" - "@babel/plugin-transform-object-super" "^7.12.13" - "@babel/plugin-transform-parameters" "^7.13.0" - "@babel/plugin-transform-property-literals" "^7.12.13" - "@babel/plugin-transform-regenerator" "^7.13.15" - "@babel/plugin-transform-reserved-words" "^7.12.13" - "@babel/plugin-transform-shorthand-properties" "^7.12.13" - "@babel/plugin-transform-spread" "^7.13.0" - "@babel/plugin-transform-sticky-regex" "^7.12.13" - "@babel/plugin-transform-template-literals" "^7.13.0" - "@babel/plugin-transform-typeof-symbol" "^7.12.13" - "@babel/plugin-transform-unicode-escapes" "^7.12.13" - "@babel/plugin-transform-unicode-regex" "^7.12.13" - "@babel/preset-modules" "^0.1.4" - "@babel/types" "^7.13.14" - babel-plugin-polyfill-corejs2 "^0.2.0" - babel-plugin-polyfill-corejs3 "^0.2.0" - babel-plugin-polyfill-regenerator "^0.2.0" - core-js-compat "^3.9.0" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + "@babel/plugin-transform-arrow-functions" "^7.18.6" + "@babel/plugin-transform-async-to-generator" "^7.18.6" + "@babel/plugin-transform-block-scoped-functions" "^7.18.6" + "@babel/plugin-transform-block-scoping" "^7.18.9" + "@babel/plugin-transform-classes" "^7.18.9" + "@babel/plugin-transform-computed-properties" "^7.18.9" + "@babel/plugin-transform-destructuring" "^7.18.9" + "@babel/plugin-transform-dotall-regex" "^7.18.6" + "@babel/plugin-transform-duplicate-keys" "^7.18.9" + "@babel/plugin-transform-exponentiation-operator" "^7.18.6" + "@babel/plugin-transform-for-of" "^7.18.8" + "@babel/plugin-transform-function-name" "^7.18.9" + "@babel/plugin-transform-literals" "^7.18.9" + "@babel/plugin-transform-member-expression-literals" "^7.18.6" + "@babel/plugin-transform-modules-amd" "^7.18.6" + "@babel/plugin-transform-modules-commonjs" "^7.18.6" + "@babel/plugin-transform-modules-systemjs" "^7.18.9" + "@babel/plugin-transform-modules-umd" "^7.18.6" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.18.6" + "@babel/plugin-transform-new-target" "^7.18.6" + "@babel/plugin-transform-object-super" "^7.18.6" + "@babel/plugin-transform-parameters" "^7.18.8" + "@babel/plugin-transform-property-literals" "^7.18.6" + "@babel/plugin-transform-regenerator" "^7.18.6" + "@babel/plugin-transform-reserved-words" "^7.18.6" + "@babel/plugin-transform-shorthand-properties" "^7.18.6" + "@babel/plugin-transform-spread" "^7.18.9" + "@babel/plugin-transform-sticky-regex" "^7.18.6" + "@babel/plugin-transform-template-literals" "^7.18.9" + "@babel/plugin-transform-typeof-symbol" "^7.18.9" + "@babel/plugin-transform-unicode-escapes" "^7.18.10" + "@babel/plugin-transform-unicode-regex" "^7.18.6" + "@babel/preset-modules" "^0.1.5" + "@babel/types" "^7.18.10" + babel-plugin-polyfill-corejs2 "^0.3.2" + babel-plugin-polyfill-corejs3 "^0.5.3" + babel-plugin-polyfill-regenerator "^0.4.0" + core-js-compat "^3.22.1" semver "^6.3.0" -"@babel/preset-modules@^0.1.4": - version "0.1.4" - resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.4.tgz#362f2b68c662842970fdb5e254ffc8fc1c2e415e" - integrity sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg== +"@babel/preset-modules@^0.1.5": + version "0.1.5" + resolved "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz" + integrity sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" @@ -811,93 +903,414 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" -"@babel/runtime@^7.8.4": - version "7.13.10" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.10.tgz#47d42a57b6095f4468da440388fdbad8bebf0d7d" - integrity sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw== +"@babel/preset-typescript@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz" + integrity sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ== dependencies: - regenerator-runtime "^0.13.4" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-validator-option" "^7.18.6" + "@babel/plugin-transform-typescript" "^7.18.6" -"@babel/template@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.13.tgz#530265be8a2589dbb37523844c5bcb55947fb327" - integrity sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA== +"@babel/runtime@^7.8.4": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz" + integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw== dependencies: - "@babel/code-frame" "^7.12.13" - "@babel/parser" "^7.12.13" - "@babel/types" "^7.12.13" + regenerator-runtime "^0.13.4" -"@babel/traverse@^7.13.0", "@babel/traverse@^7.13.13", "@babel/traverse@^7.13.15": - version "7.13.15" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.13.15.tgz#c38bf7679334ddd4028e8e1f7b3aa5019f0dada7" - integrity sha512-/mpZMNvj6bce59Qzl09fHEs8Bt8NnpEDQYleHUPZQ3wXUMvXi+HJPLars68oAbmp839fGoOkv2pSL2z9ajCIaQ== - dependencies: - "@babel/code-frame" "^7.12.13" - "@babel/generator" "^7.13.9" - "@babel/helper-function-name" "^7.12.13" - "@babel/helper-split-export-declaration" "^7.12.13" - "@babel/parser" "^7.13.15" - "@babel/types" "^7.13.14" +"@babel/template@^7.18.10", "@babel/template@^7.18.6", "@babel/template@^7.3.3": + version "7.18.10" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz" + integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/parser" "^7.18.10" + "@babel/types" "^7.18.10" + +"@babel/traverse@^7.18.10", "@babel/traverse@^7.18.11", "@babel/traverse@^7.18.9", "@babel/traverse@^7.7.2": + version "7.18.11" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.11.tgz" + integrity sha512-TG9PiM2R/cWCAy6BPJKeHzNbu4lPzOSZpeMfeNErskGpTJx6trEvFaVCbDvpcxwy49BKWmEPwiW8mrysNiDvIQ== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.18.10" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.18.9" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.18.11" + "@babel/types" "^7.18.10" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.12.1", "@babel/types@^7.12.13", "@babel/types@^7.13.0", "@babel/types@^7.13.12", "@babel/types@^7.13.14", "@babel/types@^7.4.4": - version "7.13.14" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.13.14.tgz#c35a4abb15c7cd45a2746d78ab328e362cbace0d" - integrity sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ== +"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": + version "7.18.10" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.18.10.tgz" + integrity sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ== dependencies: - "@babel/helper-validator-identifier" "^7.12.11" - lodash "^4.17.19" + "@babel/helper-string-parser" "^7.18.10" + "@babel/helper-validator-identifier" "^7.18.6" to-fast-properties "^2.0.0" -"@eslint/eslintrc@^0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.0.tgz#99cc0a0584d72f1df38b900fb062ba995f395547" - integrity sha512-2ZPCc+uNbjV5ERJr+aKSPRwZgKd2z11x0EgLvb1PURmUrn9QNRXFqje0Ldq454PfAVyaJYyrDvvIKSFP4NnBog== +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@es-joy/jsdoccomment@~0.31.0": + version "0.31.0" + resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.31.0.tgz#dbc342cc38eb6878c12727985e693eaef34302bc" + integrity sha512-tc1/iuQcnaiSIUVad72PBierDFpsxdUHtEF/OrfqvM1CBAsIoMP51j52jTMb3dXriwhieTo289InzZj72jL3EQ== + dependencies: + comment-parser "1.3.1" + esquery "^1.4.0" + jsdoc-type-pratt-parser "~3.1.0" + +"@eslint/eslintrc@^1.3.3": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.3.tgz#2b044ab39fdfa75b4688184f9e573ce3c5b0ff95" + integrity sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg== dependencies: ajv "^6.12.4" - debug "^4.1.1" - espree "^7.3.0" - globals "^12.1.0" - ignore "^4.0.6" + debug "^4.3.2" + espree "^9.4.0" + globals "^13.15.0" + ignore "^5.2.0" import-fresh "^3.2.1" - js-yaml "^3.13.1" - minimatch "^3.0.4" + js-yaml "^4.1.0" + minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@nodelib/fs.scandir@2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69" - integrity sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA== +"@humanwhocodes/config-array@^0.10.5": + version "0.10.7" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.10.7.tgz#6d53769fd0c222767e6452e8ebda825c22e9f0dc" + integrity sha512-MDl6D6sBsaV452/QSdX+4CXIjZhIcI0PELsxUjk4U828yd58vk3bTIvk/6w5FY+4hIy9sLW0sfrV7K7Kc++j/w== + dependencies: + "@humanwhocodes/object-schema" "^1.2.1" + debug "^4.1.1" + minimatch "^3.0.4" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^1.2.1": + version "1.2.1" + resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.3" + resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^28.1.3": + version "28.1.3" + resolved "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz" + integrity sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw== + dependencies: + "@jest/types" "^28.1.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^28.1.3" + jest-util "^28.1.3" + slash "^3.0.0" + +"@jest/core@^28.1.3": + version "28.1.3" + resolved "https://registry.npmjs.org/@jest/core/-/core-28.1.3.tgz" + integrity sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA== + dependencies: + "@jest/console" "^28.1.3" + "@jest/reporters" "^28.1.3" + "@jest/test-result" "^28.1.3" + "@jest/transform" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^28.1.3" + jest-config "^28.1.3" + jest-haste-map "^28.1.3" + jest-message-util "^28.1.3" + jest-regex-util "^28.0.2" + jest-resolve "^28.1.3" + jest-resolve-dependencies "^28.1.3" + jest-runner "^28.1.3" + jest-runtime "^28.1.3" + jest-snapshot "^28.1.3" + jest-util "^28.1.3" + jest-validate "^28.1.3" + jest-watcher "^28.1.3" + micromatch "^4.0.4" + pretty-format "^28.1.3" + rimraf "^3.0.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^28.1.3": + version "28.1.3" + resolved "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz" + integrity sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA== + dependencies: + "@jest/fake-timers" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/node" "*" + jest-mock "^28.1.3" + +"@jest/expect-utils@^28.1.3": + version "28.1.3" + resolved "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz" + integrity sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA== + dependencies: + jest-get-type "^28.0.2" + +"@jest/expect@^28.1.3": + version "28.1.3" + resolved "https://registry.npmjs.org/@jest/expect/-/expect-28.1.3.tgz" + integrity sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw== + dependencies: + expect "^28.1.3" + jest-snapshot "^28.1.3" + +"@jest/fake-timers@^28.1.3": + version "28.1.3" + resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz" + integrity sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw== + dependencies: + "@jest/types" "^28.1.3" + "@sinonjs/fake-timers" "^9.1.2" + "@types/node" "*" + jest-message-util "^28.1.3" + jest-mock "^28.1.3" + jest-util "^28.1.3" + +"@jest/globals@^28.1.3": + version "28.1.3" + resolved "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz" + integrity sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA== + dependencies: + "@jest/environment" "^28.1.3" + "@jest/expect" "^28.1.3" + "@jest/types" "^28.1.3" + +"@jest/reporters@^28.1.3": + version "28.1.3" + resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz" + integrity sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^28.1.3" + "@jest/test-result" "^28.1.3" + "@jest/transform" "^28.1.3" + "@jest/types" "^28.1.3" + "@jridgewell/trace-mapping" "^0.3.13" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^5.1.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^28.1.3" + jest-util "^28.1.3" + jest-worker "^28.1.3" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + terminal-link "^2.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@^28.1.3": + version "28.1.3" + resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz" + integrity sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg== + dependencies: + "@sinclair/typebox" "^0.24.1" + +"@jest/source-map@^28.1.2": + version "28.1.2" + resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-28.1.2.tgz" + integrity sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww== + dependencies: + "@jridgewell/trace-mapping" "^0.3.13" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^28.1.3": + version "28.1.3" + resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz" + integrity sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg== + dependencies: + "@jest/console" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^28.1.3": + version "28.1.3" + resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz" + integrity sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw== + dependencies: + "@jest/test-result" "^28.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^28.1.3" + slash "^3.0.0" + +"@jest/transform@^28.1.3": + version "28.1.3" + resolved "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz" + integrity sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^28.1.3" + "@jridgewell/trace-mapping" "^0.3.13" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^28.1.3" + jest-regex-util "^28.0.2" + jest-util "^28.1.3" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.1" + +"@jest/types@^28.1.3": + version "28.1.3" + resolved "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz" + integrity sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ== + dependencies: + "@jest/schemas" "^28.1.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.1.0": + version "0.1.1" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz" + integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== + dependencies: + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": + version "0.3.2" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.0" + resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + +"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/source-map@^0.3.2": + version "0.3.2" + resolved "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz" + integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.14" + resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.13", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.15" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz" + integrity sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@mdn/browser-compat-data@^4.1.16": + version "4.2.1" + resolved "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-4.2.1.tgz" + integrity sha512-EWUguj2kd7ldmrF9F+vI5hUOralPd+sdsUnYbRy33vZTuZkduC1shE9TtEMEjAQwyfyMb4ole5KtjF8MsnQOlA== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== dependencies: - "@nodelib/fs.stat" "2.0.4" + "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@2.0.4", "@nodelib/fs.stat@^2.0.2": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz#a3f2dd61bab43b8db8fa108a121cfffe4c676655" - integrity sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q== +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== "@nodelib/fs.walk@^1.2.3": - version "1.2.6" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz#cce9396b30aa5afe9e3756608f5831adcb53d063" - integrity sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow== + version "1.2.8" + resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== dependencies: - "@nodelib/fs.scandir" "2.1.4" + "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@rollup/plugin-babel@^5.0.0": - version "5.3.0" - resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz#9cb1c5146ddd6a4968ad96f209c50c62f92f9879" - integrity sha512-9uIC8HZOnVLrLHxayq/PTzw+uS25E14KPUBh5ktF+18Mjo5yK0ToMMx6epY0uEgkjwJw0aBW4x2horYXh8juWw== +"@rollup/plugin-babel@^5.3.1": + version "5.3.1" + resolved "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz" + integrity sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q== dependencies: "@babel/helper-module-imports" "^7.10.4" "@rollup/pluginutils" "^3.1.0" -"@rollup/plugin-commonjs@^15.0.0": - version "15.1.0" - resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-15.1.0.tgz#1e7d076c4f1b2abf7e65248570e555defc37c238" - integrity sha512-xCQqz4z/o0h2syQ7d9LskIMvBSH4PX5PjYdpSSvgS+pQik3WahkQVNWg3D8XJeYjZoVWnIUQYDghuEMRGrmQYQ== +"@rollup/plugin-commonjs@^22.0.1": + version "22.0.2" + resolved "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-22.0.2.tgz" + integrity sha512-//NdP6iIwPbMTcazYsiBMbJW7gfmpHom33u1beiIoHDEM0Q9clvtQB1T0efvMqHeKsGohiHo97BCPCkBXdscwg== dependencies: "@rollup/pluginutils" "^3.1.0" commondir "^1.0.1" @@ -907,102 +1320,426 @@ magic-string "^0.25.7" resolve "^1.17.0" -"@rollup/plugin-node-resolve@^9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-9.0.0.tgz#39bd0034ce9126b39c1699695f440b4b7d2b62e6" - integrity sha512-gPz+utFHLRrd41WMP13Jq5mqqzHL3OXrfj3/MkSyB6UBIcuNt9j60GCbarzMzdf1VHFpOxfQh/ez7wyadLMqkg== +"@rollup/plugin-node-resolve@^13.3.0": + version "13.3.0" + resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz" + integrity sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw== dependencies: "@rollup/pluginutils" "^3.1.0" "@types/resolve" "1.17.1" - builtin-modules "^3.1.0" deepmerge "^4.2.2" + is-builtin-module "^3.1.0" is-module "^1.0.0" - resolve "^1.17.0" + resolve "^1.19.0" "@rollup/pluginutils@^3.1.0": version "3.1.0" - resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" + resolved "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz" integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== dependencies: "@types/estree" "0.0.39" estree-walker "^1.0.1" picomatch "^2.2.2" +"@rollup/pluginutils@^4.2.1": + version "4.2.1" + resolved "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz" + integrity sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ== + dependencies: + estree-walker "^2.0.1" + picomatch "^2.2.2" + +"@sinclair/typebox@^0.24.1": + version "0.24.28" + resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.28.tgz" + integrity sha512-dgJd3HLOkLmz4Bw50eZx/zJwtBq65nms3N9VBYu5LTjJ883oBFkTyXRlCB/ZGGwqYpJJHA5zW2Ibhl5ngITfow== + +"@sinonjs/commons@^1.7.0": + version "1.8.3" + resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz" + integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^9.1.2": + version "9.1.2" + resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz" + integrity sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw== + dependencies: + "@sinonjs/commons" "^1.7.0" + +"@tootallnate/once@2": + version "2.0.0" + resolved "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz" + integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== + +"@tsconfig/node10@^1.0.7": + version "1.0.9" + resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz" + integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.3" + resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz" + integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== + +"@types/babel__core@^7.1.14": + version "7.1.19" + resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz" + integrity sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.4" + resolved "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz" + integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.1" + resolved "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz" + integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.18.0" + resolved "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.0.tgz" + integrity sha512-v4Vwdko+pgymgS+A2UIaJru93zQd85vIGWObM5ekZNdXCKtDYqATlEYnWgfo86Q6I1Lh0oXnksDnMU1cwmlPDw== + dependencies: + "@babel/types" "^7.3.0" + +"@types/clean-css@*": + version "4.2.5" + resolved "https://registry.yarnpkg.com/@types/clean-css/-/clean-css-4.2.5.tgz#69ce62cc13557c90ca40460133f672dc52ceaf89" + integrity sha512-NEzjkGGpbs9S9fgC4abuBvTpVwE3i+Acu9BBod3PUyjDVZcNsGx61b8r2PphR61QGPnn0JHVs5ey6/I4eTrkxw== + dependencies: + "@types/node" "*" + source-map "^0.6.0" + "@types/estree@*": - version "0.0.47" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.47.tgz#d7a51db20f0650efec24cd04994f523d93172ed4" - integrity sha512-c5ciR06jK8u9BstrmJyO97m+klJrrhCf9u3rLu3DEAJBirxRqSCvDQoYKmxuYwQI5SZChAWu+tq9oVlGRuzPAg== + version "1.0.0" + resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz" + integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== "@types/estree@0.0.39": version "0.0.39" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" + resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== "@types/fs-extra@^8.0.1": - version "8.1.1" - resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-8.1.1.tgz#1e49f22d09aa46e19b51c0b013cb63d0d923a068" - integrity sha512-TcUlBem321DFQzBNuz8p0CLLKp0VvF/XH9E4KHNmgwyp4E3AfgI5cjiIVZWlbfThBop2qxFIh4+LeY6hVWWZ2w== + version "8.1.2" + resolved "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.2.tgz" + integrity sha512-SvSrYXfWSc7R4eqnOzbQF4TZmfpNSM9FrSWLU3EUnWBuyZqNBOrv1B1JA3byUDPUl9z4Ab3jeZG2eDdySlgNMg== dependencies: "@types/node" "*" "@types/glob@^7.1.1": - version "7.1.3" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" - integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w== + version "7.2.0" + resolved "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz" + integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA== dependencies: "@types/minimatch" "*" "@types/node" "*" -"@types/json5@^0.0.29": - version "0.0.29" - resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" - integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= +"@types/graceful-fs@^4.1.3": + version "4.1.5" + resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz" + integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== + dependencies: + "@types/node" "*" -"@types/mime-types@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@types/mime-types/-/mime-types-2.1.0.tgz#9ca52cda363f699c69466c2a6ccdaad913ea7a73" - integrity sha1-nKUs2jY/aZxpRmwqbM2q2RPqenM= +"@types/html-minifier@^4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/html-minifier/-/html-minifier-4.0.2.tgz#ea0b927ad0019821a2e9d14ba9c57d105b63cecc" + integrity sha512-4IkmkXJP/25R2fZsCHDX2abztXuQRzUAZq39PfCMz2loLFj8vS9y7aF6vDl58koXSTpsF+eL4Lc5Y4Aww/GCTQ== + dependencies: + "@types/clean-css" "*" + "@types/relateurl" "*" + "@types/uglify-js" "*" -"@types/minimatch@*": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.4.tgz#f0ec25dbf2f0e4b18647313ac031134ca5b24b21" - integrity sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA== +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.4" + resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz" + integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== -"@types/node@*": - version "14.14.40" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.40.tgz#05a7cd31154487f357ca0bec4334ed1b1ab825a0" - integrity sha512-2HoZZGylcnz19ZSbvWhgWHqvprw1ZGHanxIrDWYykPD4CauLW4gcyLzCVfUN2kv/1t1F3CurQIdi+s1l9+XgEA== +"@types/istanbul-lib-report@*": + version "3.0.0" + resolved "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz" + integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== + dependencies: + "@types/istanbul-lib-coverage" "*" -"@types/parse-json@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" - integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== +"@types/istanbul-reports@^3.0.0": + version "3.0.1" + resolved "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz" + integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^28.1.8": + version "28.1.8" + resolved "https://registry.npmjs.org/@types/jest/-/jest-28.1.8.tgz" + integrity sha512-8TJkV++s7B6XqnDrzR1m/TT0A0h948Pnl/097veySPN67VRAgQ4gZ7n2KfJo2rVq6njQjdxU3GCCyDvAeuHoiw== + dependencies: + expect "^28.0.0" + pretty-format "^28.0.0" + +"@types/jsdom@^16.2.4": + version "16.2.15" + resolved "https://registry.npmjs.org/@types/jsdom/-/jsdom-16.2.15.tgz" + integrity sha512-nwF87yjBKuX/roqGYerZZM0Nv1pZDMAT5YhOHYeM/72Fic+VEqJh4nyoqoapzJnW3pUlfxPY5FhgsJtM+dRnQQ== + dependencies: + "@types/node" "*" + "@types/parse5" "^6.0.3" + "@types/tough-cookie" "*" + +"@types/json-schema@^7.0.9": + version "7.0.11" + resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz" + integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" + integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== + +"@types/minimatch@*": + version "3.0.5" + resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz" + integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== + +"@types/node@*", "@types/node@^18.7.11": + version "18.7.13" + resolved "https://registry.npmjs.org/@types/node/-/node-18.7.13.tgz" + integrity sha512-46yIhxSe5xEaJZXWdIBP7GU4HDTG8/eo0qd9atdiL+lFpA03y8KS+lkTN834TWJj5767GbWv4n/P6efyTFt1Dw== + +"@types/node@^17.0.36": + version "17.0.45" + resolved "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz" + integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw== + +"@types/object-path@^0.11.1": + version "0.11.1" + resolved "https://registry.npmjs.org/@types/object-path/-/object-path-0.11.1.tgz" + integrity sha512-219LSCO9HPcoXcRTC6DbCs0FRhZgBnEMzf16RRqkT40WbkKx3mOeQuz3e2XqbfhOz/AHfbru0kzB1n1RCAsIIg== + +"@types/parse5@^6.0.3": + version "6.0.3" + resolved "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz" + integrity sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g== + +"@types/prettier@^2.1.5": + version "2.7.0" + resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.0.tgz" + integrity sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A== + +"@types/qunit@^2.19.2": + version "2.19.2" + resolved "https://registry.npmjs.org/@types/qunit/-/qunit-2.19.2.tgz" + integrity sha512-hVOSrAcRtRY2R2iCQopzkiSy6BowuSFuR5llHE9R3FDxBCNP4bMdrPpxkE0jZTknn+cmFMT31RfbZWAdu5Qa1w== + +"@types/relateurl@*": + version "0.2.29" + resolved "https://registry.yarnpkg.com/@types/relateurl/-/relateurl-0.2.29.tgz#68ccecec3d4ffdafb9c577fe764f912afc050fe6" + integrity sha512-QSvevZ+IRww2ldtfv1QskYsqVVVwCKQf1XbwtcyyoRvLIQzfyPhj/C+3+PKzSDRdiyejaiLgnq//XTkleorpLg== "@types/resolve@1.17.1": version "1.17.1" - resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" + resolved "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz" integrity sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw== dependencies: "@types/node" "*" -acorn-jsx@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" - integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== +"@types/semver@^7.3.9": + version "7.3.12" + resolved "https://registry.npmjs.org/@types/semver/-/semver-7.3.12.tgz" + integrity sha512-WwA1MW0++RfXmCr12xeYOOC5baSC9mSb0ZqCquFzKhcoF4TvHu5MKOuXsncgZcpVFhB1pXd5hZmM0ryAoCp12A== + +"@types/stack-utils@^2.0.0": + version "2.0.1" + resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz" + integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== + +"@types/tough-cookie@*": + version "4.0.2" + resolved "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz" + integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw== + +"@types/ua-parser-js@^0.7.36": + version "0.7.36" + resolved "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.36.tgz" + integrity sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ== + +"@types/uglify-js@*": + version "3.17.0" + resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.17.0.tgz#95271e7abe0bf7094c60284f76ee43232aef43b9" + integrity sha512-3HO6rm0y+/cqvOyA8xcYLweF0TKXlAxmQASjbOi49Co51A1N4nR4bEwBgRoD9kNM+rqFGArjKr654SLp2CoGmQ== + dependencies: + source-map "^0.6.1" + +"@types/yargs-parser@*": + version "21.0.0" + resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz" + integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== + +"@types/yargs@^17.0.8": + version "17.0.11" + resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.11.tgz" + integrity sha512-aB4y9UDUXTSMxmM4MH+YnuR0g5Cph3FLQBoWoMB21DSvFVAxRVEHEMx3TLh+zUZYMCQtKiqazz0Q4Rre31f/OA== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@^5.32.0": + version "5.33.1" + resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.33.1.tgz" + integrity sha512-S1iZIxrTvKkU3+m63YUOxYPKaP+yWDQrdhxTglVDVEVBf+aCSw85+BmJnyUaQQsk5TXFG/LpBu9fa+LrAQ91fQ== + dependencies: + "@typescript-eslint/scope-manager" "5.33.1" + "@typescript-eslint/type-utils" "5.33.1" + "@typescript-eslint/utils" "5.33.1" + debug "^4.3.4" + functional-red-black-tree "^1.0.1" + ignore "^5.2.0" + regexpp "^3.2.0" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/parser@^5.32.0": + version "5.33.1" + resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.33.1.tgz" + integrity sha512-IgLLtW7FOzoDlmaMoXdxG8HOCByTBXrB1V2ZQYSEV1ggMmJfAkMWTwUjjzagS6OkfpySyhKFkBw7A9jYmcHpZA== + dependencies: + "@typescript-eslint/scope-manager" "5.33.1" + "@typescript-eslint/types" "5.33.1" + "@typescript-eslint/typescript-estree" "5.33.1" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@5.33.1": + version "5.33.1" + resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.33.1.tgz" + integrity sha512-8ibcZSqy4c5m69QpzJn8XQq9NnqAToC8OdH/W6IXPXv83vRyEDPYLdjAlUx8h/rbusq6MkW4YdQzURGOqsn3CA== + dependencies: + "@typescript-eslint/types" "5.33.1" + "@typescript-eslint/visitor-keys" "5.33.1" + +"@typescript-eslint/type-utils@5.33.1": + version "5.33.1" + resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.33.1.tgz" + integrity sha512-X3pGsJsD8OiqhNa5fim41YtlnyiWMF/eKsEZGsHID2HcDqeSC5yr/uLOeph8rNF2/utwuI0IQoAK3fpoxcLl2g== + dependencies: + "@typescript-eslint/utils" "5.33.1" + debug "^4.3.4" + tsutils "^3.21.0" + +"@typescript-eslint/types@5.33.1": + version "5.33.1" + resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.33.1.tgz" + integrity sha512-7K6MoQPQh6WVEkMrMW5QOA5FO+BOwzHSNd0j3+BlBwd6vtzfZceJ8xJ7Um2XDi/O3umS8/qDX6jdy2i7CijkwQ== + +"@typescript-eslint/typescript-estree@5.33.1": + version "5.33.1" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.33.1.tgz" + integrity sha512-JOAzJ4pJ+tHzA2pgsWQi4804XisPHOtbvwUyqsuuq8+y5B5GMZs7lI1xDWs6V2d7gE/Ez5bTGojSK12+IIPtXA== + dependencies: + "@typescript-eslint/types" "5.33.1" + "@typescript-eslint/visitor-keys" "5.33.1" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/utils@5.33.1": + version "5.33.1" + resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.33.1.tgz" + integrity sha512-uphZjkMaZ4fE8CR4dU7BquOV6u0doeQAr8n6cQenl/poMaIyJtBu8eys5uk6u5HiDH01Mj5lzbJ5SfeDz7oqMQ== + dependencies: + "@types/json-schema" "^7.0.9" + "@typescript-eslint/scope-manager" "5.33.1" + "@typescript-eslint/types" "5.33.1" + "@typescript-eslint/typescript-estree" "5.33.1" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + +"@typescript-eslint/visitor-keys@5.33.1": + version "5.33.1" + resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.33.1.tgz" + integrity sha512-nwIxOK8Z2MPWltLKMLOEZwmfBZReqUdbEoHQXeCpa+sRVARe5twpJGHCB4dk9903Yaf0nMAlGbQfaAH92F60eg== + dependencies: + "@typescript-eslint/types" "5.33.1" + eslint-visitor-keys "^3.3.0" + +"@wessberg/stringutil@^1.0.19": + version "1.0.19" + resolved "https://registry.npmjs.org/@wessberg/stringutil/-/stringutil-1.0.19.tgz" + integrity sha512-9AZHVXWlpN8Cn9k5BC/O0Dzb9E9xfEMXzYrNunwvkUTvuK7xgQPVRZpLo+jWCOZ5r8oBa8NIrHuPEu1hzbb6bg== + +abab@^2.0.5, abab@^2.0.6: + version "2.0.6" + resolved "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz" + integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== -acorn@^7.4.0: +acorn-globals@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz" + integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== + dependencies: + acorn "^7.1.1" + acorn-walk "^7.1.1" + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-walk@^7.1.1: + version "7.2.0" + resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz" + integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== + +acorn-walk@^8.1.1: + version "8.2.0" + resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + +acorn@^7.1.1: version "7.4.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + resolved "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -agent-base@5: - version "5.1.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c" - integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g== +acorn@^8.4.1, acorn@^8.5.0, acorn@^8.8.0: + version "8.8.0" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz" + integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== + +agent-base@6: + version "6.0.2" + resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" aggregate-error@^3.0.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + resolved "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz" integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== dependencies: clean-stack "^2.0.0" @@ -1010,7 +1747,7 @@ aggregate-error@^3.0.0: ajv@^6.10.0, ajv@^6.12.4: version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== dependencies: fast-deep-equal "^3.1.1" @@ -1018,157 +1755,279 @@ ajv@^6.10.0, ajv@^6.12.4: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.1: - version "8.1.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.1.0.tgz#45d5d3d36c7cdd808930cc3e603cf6200dbeb736" - integrity sha512-B/Sk2Ix7A36fs/ZkuGLIR86EdjbgR6fsAcbx9lOP/QBSXujDNbVmIS/U4Itz5k8fPFDeVZl/zQ/gJW4Jrq6XjQ== +ansi-colors@^4.1.3: + version "4.1.3" + resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== + +ansi-escapes@^4.2.1, ansi-escapes@^4.3.0: + version "4.3.2" + resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" + type-fest "^0.21.3" -ansi-colors@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" - integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-regex@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" - integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== +ansi-regex@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== ansi-styles@^3.2.1: version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== dependencies: color-convert "^1.9.0" ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== dependencies: color-convert "^2.0.1" +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +ansi-styles@^6.0.0: + version "6.1.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.1.0.tgz" + integrity sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ== + ansi-styles@~1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.0.0.tgz#cb102df1c56f5123eab8b67cd7b98027a0279178" - integrity sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg= + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz" + integrity sha512-3iF4FIKdxaVYT3JqQuY3Wat/T2t7TRbbQ94Fu50ZUCbLy4TFbTzr90NOHQodQkNqmeEGCw8WbeP78WNi6SKYUA== + +anymatch@^3.0.3: + version "3.1.2" + resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== argparse@^1.0.7: version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz" integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== dependencies: sprintf-js "~1.0.2" -array-includes@^3.1.1: - version "3.1.3" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.3.tgz#c7f619b382ad2afaf5326cddfdc0afc61af7690a" - integrity sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A== +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-includes@^3.1.4: + version "3.1.5" + resolved "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz" + integrity sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.18.0-next.2" + define-properties "^1.1.4" + es-abstract "^1.19.5" get-intrinsic "^1.1.1" - is-string "^1.0.5" + is-string "^1.0.7" array-union@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== -array.prototype.flat@^1.2.3: - version "1.2.4" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz#6ef638b43312bd401b4c6199fdec7e2dc9e9a123" - integrity sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg== +array.prototype.flat@^1.2.5: + version "1.3.0" + resolved "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz" + integrity sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw== dependencies: - call-bind "^1.0.0" + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.18.0-next.1" + es-abstract "^1.19.2" + es-shim-unscopables "^1.0.0" astral-regex@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + resolved "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz" integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== -async-limiter@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" - integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== - -at-least-node@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" - integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +babel-jest@^28.1.3: + version "28.1.3" + resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.3.tgz" + integrity sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q== + dependencies: + "@jest/transform" "^28.1.3" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^28.1.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" babel-plugin-dynamic-import-node@^2.3.3: version "2.3.3" - resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" + resolved "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz" integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== dependencies: object.assign "^4.1.0" -babel-plugin-polyfill-corejs2@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.0.tgz#686775bf9a5aa757e10520903675e3889caeedc4" - integrity sha512-9bNwiR0dS881c5SHnzCmmGlMkJLl0OUZvxrxHo9w/iNoRuqaPjqlvBf4HrovXtQs/au5yKkpcdgfT1cC5PAZwg== +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== dependencies: - "@babel/compat-data" "^7.13.11" - "@babel/helper-define-polyfill-provider" "^0.2.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^28.1.3: + version "28.1.3" + resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.3.tgz" + integrity sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-plugin-polyfill-corejs2@^0.3.2: + version "0.3.2" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.2.tgz" + integrity sha512-LPnodUl3lS0/4wN3Rb+m+UK8s7lj2jcLRrjho4gLw+OJs+I4bvGXshINesY5xx/apM+biTnQ9reDI8yj+0M5+Q== + dependencies: + "@babel/compat-data" "^7.17.7" + "@babel/helper-define-polyfill-provider" "^0.3.2" semver "^6.1.1" -babel-plugin-polyfill-corejs3@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.0.tgz#f4b4bb7b19329827df36ff56f6e6d367026cb7a2" - integrity sha512-zZyi7p3BCUyzNxLx8KV61zTINkkV65zVkDAFNZmrTCRVhjo1jAS+YLvDJ9Jgd/w2tsAviCwFHReYfxO3Iql8Yg== +babel-plugin-polyfill-corejs3@^0.5.3: + version "0.5.3" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz" + integrity sha512-zKsXDh0XjnrUEW0mxIHLfjBfnXSMr5Q/goMe/fxpQnLm07mcOZiIZHBNWCMx60HmdvjxfXcalac0tfFg0wqxyw== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.3.2" + core-js-compat "^3.21.0" + +babel-plugin-polyfill-regenerator@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.0.tgz" + integrity sha512-RW1cnryiADFeHmfLS+WW/G431p1PsW5qdRdz0SDRi7TKcUgc7Oh/uXkT7MZ/+tGsT1BkczEAmD5XjUyJ5SWDTw== dependencies: - "@babel/helper-define-polyfill-provider" "^0.2.0" - core-js-compat "^3.9.1" + "@babel/helper-define-polyfill-provider" "^0.3.2" + +babel-preset-current-node-syntax@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz" + integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.8.3" + "@babel/plugin-syntax-import-meta" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-top-level-await" "^7.8.3" -babel-plugin-polyfill-regenerator@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.0.tgz#853f5f5716f4691d98c84f8069c7636ea8da7ab8" - integrity sha512-J7vKbCuD2Xi/eEHxquHN14bXAW9CXtecwuLrOIDJtcZzTaPzV1VdEfoUf9AzcRBMolKUQKM9/GVojeh0hFiqMg== +babel-preset-jest@^28.1.3: + version "28.1.3" + resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-28.1.3.tgz" + integrity sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A== dependencies: - "@babel/helper-define-polyfill-provider" "^0.2.0" + babel-plugin-jest-hoist "^28.1.3" + babel-preset-current-node-syntax "^1.0.0" balanced-match@^1.0.0: version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +boolbase@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz" + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== + brace-expansion@^1.1.7: version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== dependencies: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^3.0.1: +braces@^3.0.2: version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== dependencies: fill-range "^7.0.1" -browserslist@^4.14.5, browserslist@^4.16.3: - version "4.16.4" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.4.tgz#7ebf913487f40caf4637b892b268069951c35d58" - integrity sha512-d7rCxYV8I9kj41RH8UKYnvDYCRENUlHRgyXy/Rhr/1BaeLGfiCptEdFE8MIrvGfWbBFNjVYx76SQWvNX1j+/cQ== - dependencies: - caniuse-lite "^1.0.30001208" - colorette "^1.2.2" - electron-to-chromium "^1.3.712" +browser-process-hrtime@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz" + integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== + +browserslist-generator@^1.0.66: + version "1.0.66" + resolved "https://registry.npmjs.org/browserslist-generator/-/browserslist-generator-1.0.66.tgz" + integrity sha512-aFDax4Qzh29DdyhHQBD2Yu2L5OvaDnvYFMbmpLrLwwaNK4H6dHEhC/Nxv93/+mfAA+a/t94ln0P2JZvHO6LZDA== + dependencies: + "@mdn/browser-compat-data" "^4.1.16" + "@types/object-path" "^0.11.1" + "@types/semver" "^7.3.9" + "@types/ua-parser-js" "^0.7.36" + browserslist "4.20.2" + caniuse-lite "^1.0.30001328" + isbot "3.4.5" + object-path "^0.11.8" + semver "^7.3.7" + ua-parser-js "^1.0.2" + +browserslist@4.20.2: + version "4.20.2" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.20.2.tgz" + integrity sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA== + dependencies: + caniuse-lite "^1.0.30001317" + electron-to-chromium "^1.4.84" escalade "^3.1.1" - node-releases "^1.1.71" + node-releases "^2.0.2" + picocolors "^1.0.0" + +browserslist@^4.20.2, browserslist@^4.20.4, browserslist@^4.21.3: + version "4.21.3" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz" + integrity sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ== + dependencies: + caniuse-lite "^1.0.30001370" + electron-to-chromium "^1.4.202" + node-releases "^2.0.6" + update-browserslist-db "^1.0.5" -browserstack-runner@^0.9.0: +browserstack-runner@^0.9.4: version "0.9.4" - resolved "https://registry.yarnpkg.com/browserstack-runner/-/browserstack-runner-0.9.4.tgz#f9c167f69f79a96abc9320e6f34b697f4d26d13a" + resolved "https://registry.npmjs.org/browserstack-runner/-/browserstack-runner-0.9.4.tgz" integrity sha512-M588zrfpd8nIGG0GBpgi5bcNxKNs4+Essnhcebl0Hm68t8cx5wLcnr23M7DFFAjGABc6D8BfrnKKdhpfrHHjBg== dependencies: browserstack "1.3.0" @@ -1183,27 +2042,29 @@ browserstack-runner@^0.9.0: browserstack@1.3.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/browserstack/-/browserstack-1.3.0.tgz#8438053ef6acbb844dc6b291510c194339eb50df" - integrity sha1-hDgFPvasu4RNxrKRUQwZQznrUN8= + resolved "https://registry.npmjs.org/browserstack/-/browserstack-1.3.0.tgz" + integrity sha512-jBTvd/nLpTZmMnAEF6SDutTjYlcLx+iqmhmo4PBzTvQnts5MazkkT7EUWK39BBAZvvH3m5o2IcFfDoyZq3kFMg== -buffer-crc32@~0.2.3: - version "0.2.13" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= +bser@2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" buffer-from@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + version "1.1.2" + resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -builtin-modules@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887" - integrity sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA== +builtin-modules@^3.3.0: + version "3.3.0" + resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz" + integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz" integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== dependencies: function-bind "^1.1.1" @@ -1211,23 +2072,41 @@ call-bind@^1.0.0, call-bind@^1.0.2: callsites@^3.0.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -camelcase@^5.0.0: +camel-case@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz" + integrity sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w== + dependencies: + no-case "^2.2.0" + upper-case "^1.1.1" + +camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -caniuse-lite@^1.0.30001208: - version "1.0.30001208" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001208.tgz#a999014a35cebd4f98c405930a057a0d75352eb9" - integrity sha512-OE5UE4+nBOro8Dyvv0lfx+SRtfVIOM9uhKqFmJeUbGriqhhStgp1A0OyBpgy3OUF8AhYCT+PVwPC1gMl2ZcQMA== +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001317, caniuse-lite@^1.0.30001328: + version "1.0.30001378" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001378.tgz" + integrity sha512-JVQnfoO7FK7WvU4ZkBRbPjaot4+YqxogSDosHv0Hv5mWpUESmN+UubMU6L/hGz8QlQ2aY5U0vR6MOs6j/CXpNA== + +caniuse-lite@^1.0.30001370: + version "1.0.30001376" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001376.tgz" + integrity sha512-I27WhtOQ3X3v3it9gNs/oTpoE5KpwmqKR5oKPA8M0G7uMXh9Ty81Q904HpKUrM30ei7zfcL5jE7AXefgbOfMig== chalk@0.4.0: version "0.4.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.4.0.tgz#5199a3ddcd0c1efe23bc08c1b027b06176e0c64f" - integrity sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8= + resolved "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz" + integrity sha512-sQfYDlfv2DGVtjdoQqxS0cEZDroyG8h6TamA6rvxwlrU5BaSLDx9xhatBYl2pxZ7gmpNaPFVwBtdGdu5rQ+tYQ== dependencies: ansi-styles "~1.0.0" has-color "~0.1.0" @@ -1235,198 +2114,322 @@ chalk@0.4.0: chalk@^2.0.0: version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== dependencies: ansi-styles "^3.2.1" escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" - integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== +chalk@^4.0.0, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== dependencies: ansi-styles "^4.1.0" supports-color "^7.1.0" -ci-info@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" - integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +ci-info@^3.2.0: + version "3.3.2" + resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.3.2.tgz" + integrity sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg== circular-json@0.3.1: version "0.3.1" - resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d" - integrity sha1-vos2rvzN6LPKeqLWr8B6NyQsDS0= + resolved "https://registry.npmjs.org/circular-json/-/circular-json-0.3.1.tgz" + integrity sha512-MTc6ffiOuzmPfRWVHjRscjzTQSYq16oouOebk6iHn/Tvp1mKBwQ/x33Trh7oZwI0e7wZyMV9KzDBWalzxjoIGQ== + +cjs-module-lexer@^1.0.0: + version "1.2.2" + resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz" + integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== + +clean-css@^4.2.1: + version "4.2.4" + resolved "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz" + integrity sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A== + dependencies: + source-map "~0.6.0" clean-stack@^2.0.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + resolved "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-truncate@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz" + integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== + dependencies: + slice-ansi "^3.0.0" + string-width "^4.2.0" + +cli-truncate@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz" + integrity sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA== + dependencies: + slice-ansi "^5.0.0" + string-width "^5.0.0" + cliui@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + resolved "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz" integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== dependencies: string-width "^4.2.0" strip-ansi "^6.0.0" wrap-ansi "^6.2.0" +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +collect-v8-coverage@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz" + integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== + color-convert@^1.9.0: version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== dependencies: color-name "1.1.3" color-convert@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== dependencies: color-name "~1.1.4" color-name@1.1.3: version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== color-name@~1.1.4: version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -colorette@^1.1.0, colorette@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" - integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== - -colors@^1.4.0: +colorette@^1.1.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" - integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== + resolved "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz" + integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g== + +colorette@^2.0.16, colorette@^2.0.17: + version "2.0.19" + resolved "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz" + integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== -commander@^2.20.0: +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@2.12.2: + version "2.12.2" + resolved "https://registry.npmjs.org/commander/-/commander-2.12.2.tgz" + integrity sha512-BFnaq5ZOGcDN7FlrtBT4xxkgIToalIIxwjxLWVJ8bGTpe1LroqMiqQXdA7ygc7CRvaYS+9zfPGFnJqFSayx+AA== + +commander@^2.19.0, commander@^2.20.0: version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== +commander@^9.3.0, commander@^9.4.0: + version "9.4.0" + resolved "https://registry.npmjs.org/commander/-/commander-9.4.0.tgz" + integrity sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw== + +comment-parser@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.3.1.tgz#3d7ea3adaf9345594aedee6563f422348f165c1b" + integrity sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA== + commondir@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" - integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= + resolved "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz" + integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== -compare-versions@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62" - integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== +compatfactory@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/compatfactory/-/compatfactory-1.0.1.tgz" + integrity sha512-hR9u0HSZTKDNNchPtMHg6myeNx0XO+av7UZIJPsi4rPALJBHi/W5Mbwi19hC/xm6y3JkYpxVYjTqnSGsU5X/iw== + dependencies: + helpertypes "^0.0.18" concat-map@0.0.1: version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -concat-stream@^1.6.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" - integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== confusing-browser-globals@^1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.10.tgz#30d1e7f3d1b882b25ec4933d1d1adac353d20a59" - integrity sha512-gNld/3lySHwuhaVluJUKLePYirM3QNCKzVxqAdhJII9/WXKVX5PURzMVJspS1jTslSqjeuG4KMVTSouit5YPHA== - -contains-path@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" - integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= + version "1.0.11" + resolved "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz" + integrity sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA== -convert-source-map@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" - integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== +convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: + version "1.8.0" + resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz" + integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== dependencies: safe-buffer "~5.1.1" -core-js-compat@^3.9.0, core-js-compat@^3.9.1: - version "3.10.1" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.10.1.tgz#62183a3a77ceeffcc420d907a3e6fc67d9b27f1c" - integrity sha512-ZHQTdTPkqvw2CeHiZC970NNJcnwzT6YIueDMASKt+p3WbZsLXOcoD392SkcWhkC0wBBHhlfhqGKKsNCQUozYtg== +core-js-compat@^3.21.0, core-js-compat@^3.22.1: + version "3.24.1" + resolved "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.24.1.tgz" + integrity sha512-XhdNAGeRnTpp8xbD+sR/HFDK9CbeeeqXT6TuofXh3urqEevzkWmLRgrVoykodsw8okqo2pu1BOmuCKrHx63zdw== dependencies: - browserslist "^4.16.3" + browserslist "^4.21.3" semver "7.0.0" -core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= - -cosmiconfig@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3" - integrity sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA== - dependencies: - "@types/parse-json" "^4.0.0" - import-fresh "^3.2.1" - parse-json "^5.0.0" - path-type "^4.0.0" - yaml "^1.10.0" +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -cross-spawn@^7.0.2: +cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== dependencies: path-key "^3.1.0" shebang-command "^2.0.0" which "^2.0.1" +crosspath@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/crosspath/-/crosspath-2.0.0.tgz" + integrity sha512-ju88BYCQ2uvjO2bR+SsgLSTwTSctU+6Vp2ePbKPgSCZyy4MWZxYsT738DlKVRE5utUjobjPRm1MkTYKJxCmpTA== + dependencies: + "@types/node" "^17.0.36" + +css-select@^4.1.3: + version "4.3.0" + resolved "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz" + integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ== + dependencies: + boolbase "^1.0.0" + css-what "^6.0.1" + domhandler "^4.3.1" + domutils "^2.8.0" + nth-check "^2.0.1" + +css-what@^6.0.1: + version "6.1.0" + resolved "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz" + integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== + +cssom@^0.5.0: + version "0.5.0" + resolved "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz" + integrity sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw== + +cssom@~0.3.6: + version "0.3.8" + resolved "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssstyle@^2.3.0: + version "2.3.0" + resolved "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz" + integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== + dependencies: + cssom "~0.3.6" + +data-urls@^3.0.1: + version "3.0.2" + resolved "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz" + integrity sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ== + dependencies: + abab "^2.0.6" + whatwg-mimetype "^3.0.0" + whatwg-url "^11.0.0" + debug@2.6.9, debug@^2.6.9: version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" -debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" - integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + decamelize@^1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" + integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== -deep-is@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" - integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= +decimal.js@^10.3.1: + version "10.4.0" + resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.0.tgz" + integrity sha512-Nv6ENEzyPQ6AItkGwLE2PGKinZZ9g59vSh2BeH6NqPu0OTKZ5ruJsVqh/orbAnqXc9pBbgXAIrc2EyaCj8NpGg== + +dedent@^0.7.0: + version "0.7.0" + resolved "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz" + integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== + +deep-is@^0.1.3, deep-is@~0.1.3: + version "0.1.4" + resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== deepmerge@^4.2.2: version "4.2.2" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== -define-properties@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" - integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== +define-properties@^1.1.3, define-properties@^1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz" + integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== dependencies: - object-keys "^1.0.12" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" del@^5.1.0: version "5.1.0" - resolved "https://registry.yarnpkg.com/del/-/del-5.1.0.tgz#d9487c94e367410e6eff2925ee58c0c84a75b3a7" + resolved "https://registry.npmjs.org/del/-/del-5.1.0.tgz" integrity sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA== dependencies: globby "^10.0.1" @@ -1438,102 +2441,190 @@ del@^5.1.0: rimraf "^3.0.0" slash "^3.0.0" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + depd@~1.1.2: version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz" + integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== destroy@~1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" - integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= + resolved "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz" + integrity sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg== + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^28.1.1: + version "28.1.1" + resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz" + integrity sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== dir-glob@^3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== dependencies: path-type "^4.0.0" -doctrine@1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" - integrity sha1-N53Ocw9hZvds76TmcHoVmwLFpvo= +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== dependencies: esutils "^2.0.2" - isarray "^1.0.0" doctrine@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + resolved "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz" integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== dependencies: esutils "^2.0.2" -dotenv@^8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" - integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== +dom-serializer@^1.0.1: + version "1.4.1" + resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz" + integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.2.0" + entities "^2.0.0" + +domelementtype@^2.0.1, domelementtype@^2.2.0: + version "2.3.0" + resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + +domexception@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz" + integrity sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw== + dependencies: + webidl-conversions "^7.0.0" + +domhandler@^4.2.0, domhandler@^4.3.1: + version "4.3.1" + resolved "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz" + integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== + dependencies: + domelementtype "^2.2.0" + +domutils@^2.8.0: + version "2.8.0" + resolved "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz" + integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== + dependencies: + dom-serializer "^1.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + +dotenv@^16.0.1: + version "16.0.1" + resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz" + integrity sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ== + +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== ee-first@1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +electron-to-chromium@^1.4.202: + version "1.4.220" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.220.tgz" + integrity sha512-RA6Mn51gj2lyeAt/MiuVV+85tN8vt1jbWP+kx7S84htJ0MbIHGHdnVm+j+HzZQp69O6gWmhLBJ+Wpk2c445+ZA== -electron-to-chromium@^1.3.712: - version "1.3.717" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.717.tgz#78d4c857070755fb58ab64bcc173db1d51cbc25f" - integrity sha512-OfzVPIqD1MkJ7fX+yTl2nKyOE4FReeVfMCzzxQS+Kp43hZYwHwThlGP+EGIZRXJsxCM7dqo8Y65NOX/HP12iXQ== +electron-to-chromium@^1.4.84: + version "1.4.224" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.224.tgz" + integrity sha512-dOujC5Yzj0nOVE23iD5HKqrRSDj2SD7RazpZS/b/WX85MtO6/LzKDF4TlYZTBteB+7fvSg5JpWh0sN7fImNF8w== + +emittery@^0.10.2: + version "0.10.2" + resolved "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz" + integrity sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw== emoji-regex@^8.0.0: version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + encodeurl@~1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== -enquirer@^2.3.5: - version "2.3.6" - resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" - integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== - dependencies: - ansi-colors "^4.1.1" +entities@^2.0.0: + version "2.2.0" + resolved "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== -error-ex@^1.2.0, error-ex@^1.3.1: +error-ex@^1.3.1: version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz" integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== dependencies: is-arrayish "^0.2.1" -es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2: - version "1.18.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0.tgz#ab80b359eecb7ede4c298000390bc5ac3ec7b5a4" - integrity sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw== +es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.2, es-abstract@^1.19.5: + version "1.20.1" + resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz" + integrity sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA== dependencies: call-bind "^1.0.2" es-to-primitive "^1.2.1" function-bind "^1.1.1" + function.prototype.name "^1.1.5" get-intrinsic "^1.1.1" + get-symbol-description "^1.0.0" has "^1.0.3" - has-symbols "^1.0.2" - is-callable "^1.2.3" - is-negative-zero "^2.0.1" - is-regex "^1.1.2" - is-string "^1.0.5" - object-inspect "^1.9.0" + has-property-descriptors "^1.0.0" + has-symbols "^1.0.3" + internal-slot "^1.0.3" + is-callable "^1.2.4" + is-negative-zero "^2.0.2" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + is-string "^1.0.7" + is-weakref "^1.0.2" + object-inspect "^1.12.0" object-keys "^1.1.1" object.assign "^4.1.2" - string.prototype.trimend "^1.0.4" - string.prototype.trimstart "^1.0.4" - unbox-primitive "^1.0.0" + regexp.prototype.flags "^1.4.3" + string.prototype.trimend "^1.0.5" + string.prototype.trimstart "^1.0.5" + unbox-primitive "^1.0.2" + +es-shim-unscopables@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz" + integrity sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w== + dependencies: + has "^1.0.3" es-to-primitive@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + resolved "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz" integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== dependencies: is-callable "^1.1.4" @@ -1542,264 +2633,343 @@ es-to-primitive@^1.2.1: escalade@^3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== escape-html@~1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== escape-string-regexp@^1.0.5: version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +escodegen@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz" + integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== + dependencies: + esprima "^4.0.1" + estraverse "^5.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" -eslint-config-airbnb-base@^14.2.0: - version "14.2.1" - resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.1.tgz#8a2eb38455dc5a312550193b319cdaeef042cd1e" - integrity sha512-GOrQyDtVEc1Xy20U7vsB2yAoB4nBlfH5HZJeatRXHleO+OS5Ot+MWij4Dpltw4/DyIkqUfqz1epfhVR5XWWQPA== +eslint-config-airbnb-base@^15.0.0: + version "15.0.0" + resolved "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz" + integrity sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig== dependencies: confusing-browser-globals "^1.0.10" object.assign "^4.1.2" - object.entries "^1.1.2" + object.entries "^1.1.5" + semver "^6.3.0" -eslint-import-resolver-node@^0.3.4: - version "0.3.4" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717" - integrity sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA== +eslint-config-airbnb-typescript@^17.0.0: + version "17.0.0" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-17.0.0.tgz#360dbcf810b26bbcf2ff716198465775f1c49a07" + integrity sha512-elNiuzD0kPAPTXjFWg+lE24nMdHMtuxgYoD30OyMD6yrW1AhFZPAg27VX7d3tzOErw+dgJTNWfRSDqEcXb4V0g== dependencies: - debug "^2.6.9" - resolve "^1.13.1" + eslint-config-airbnb-base "^15.0.0" -eslint-module-utils@^2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz#579ebd094f56af7797d19c9866c9c9486629bfa6" - integrity sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA== +eslint-import-resolver-node@^0.3.6: + version "0.3.6" + resolved "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz" + integrity sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw== dependencies: - debug "^2.6.9" - pkg-dir "^2.0.0" + debug "^3.2.7" + resolve "^1.20.0" -eslint-plugin-import@^2.16.0: - version "2.22.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz#0896c7e6a0cf44109a2d97b95903c2bb689d7702" - integrity sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw== +eslint-module-utils@^2.7.3: + version "2.7.4" + resolved "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz" + integrity sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA== dependencies: - array-includes "^3.1.1" - array.prototype.flat "^1.2.3" - contains-path "^0.1.0" + debug "^3.2.7" + +eslint-plugin-import@^2.26.0: + version "2.26.0" + resolved "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz" + integrity sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA== + dependencies: + array-includes "^3.1.4" + array.prototype.flat "^1.2.5" debug "^2.6.9" - doctrine "1.5.0" - eslint-import-resolver-node "^0.3.4" - eslint-module-utils "^2.6.0" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.6" + eslint-module-utils "^2.7.3" has "^1.0.3" - minimatch "^3.0.4" - object.values "^1.1.1" - read-pkg-up "^2.0.0" - resolve "^1.17.0" - tsconfig-paths "^3.9.0" + is-core-module "^2.8.1" + is-glob "^4.0.3" + minimatch "^3.1.2" + object.values "^1.1.5" + resolve "^1.22.0" + tsconfig-paths "^3.14.1" + +eslint-plugin-jsdoc@^39.3.6: + version "39.3.6" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-39.3.6.tgz#6ba29f32368d72a51335a3dc9ccd22ad0437665d" + integrity sha512-R6dZ4t83qPdMhIOGr7g2QII2pwCjYyKP+z0tPOfO1bbAbQyKC20Y2Rd6z1te86Lq3T7uM8bNo+VD9YFpE8HU/g== + dependencies: + "@es-joy/jsdoccomment" "~0.31.0" + comment-parser "1.3.1" + debug "^4.3.4" + escape-string-regexp "^4.0.0" + esquery "^1.4.0" + semver "^7.3.7" + spdx-expression-parse "^3.0.1" eslint-scope@^5.1.1: version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz" integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== dependencies: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-utils@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" - integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== +eslint-scope@^7.1.1: + version "7.1.1" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz" + integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== dependencies: - eslint-visitor-keys "^1.1.0" + esrecurse "^4.3.0" + estraverse "^5.2.0" -eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" - integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== +eslint-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz" + integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + dependencies: + eslint-visitor-keys "^2.0.0" eslint-visitor-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" - integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== + version "2.1.0" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + +eslint-visitor-keys@^3.3.0: + version "3.3.0" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz" + integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint@^7.6.0: - version "7.24.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.24.0.tgz#2e44fa62d93892bfdb100521f17345ba54b8513a" - integrity sha512-k9gaHeHiFmGCDQ2rEfvULlSLruz6tgfA8DEn+rY9/oYPFFTlz55mM/Q/Rij1b2Y42jwZiK3lXvNTw6w6TXzcKQ== +eslint@^8.25.0: + version "8.25.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.25.0.tgz#00eb962f50962165d0c4ee3327708315eaa8058b" + integrity sha512-DVlJOZ4Pn50zcKW5bYH7GQK/9MsoQG2d5eDH0ebEkE8PbgzTTmtt/VTH9GGJ4BfeZCpBLqFfvsjX35UacUL83A== dependencies: - "@babel/code-frame" "7.12.11" - "@eslint/eslintrc" "^0.4.0" + "@eslint/eslintrc" "^1.3.3" + "@humanwhocodes/config-array" "^0.10.5" + "@humanwhocodes/module-importer" "^1.0.1" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" - debug "^4.0.1" + debug "^4.3.2" doctrine "^3.0.0" - enquirer "^2.3.5" - eslint-scope "^5.1.1" - eslint-utils "^2.1.0" - eslint-visitor-keys "^2.0.0" - espree "^7.3.1" + escape-string-regexp "^4.0.0" + eslint-scope "^7.1.1" + eslint-utils "^3.0.0" + eslint-visitor-keys "^3.3.0" + espree "^9.4.0" esquery "^1.4.0" esutils "^2.0.2" + fast-deep-equal "^3.1.3" file-entry-cache "^6.0.1" - functional-red-black-tree "^1.0.1" - glob-parent "^5.0.0" - globals "^13.6.0" - ignore "^4.0.6" + find-up "^5.0.0" + glob-parent "^6.0.1" + globals "^13.15.0" + globby "^11.1.0" + grapheme-splitter "^1.0.4" + ignore "^5.2.0" import-fresh "^3.0.0" imurmurhash "^0.1.4" is-glob "^4.0.0" - js-yaml "^3.13.1" + js-sdsl "^4.1.4" + js-yaml "^4.1.0" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" - lodash "^4.17.21" - minimatch "^3.0.4" + lodash.merge "^4.6.2" + minimatch "^3.1.2" natural-compare "^1.4.0" optionator "^0.9.1" - progress "^2.0.0" - regexpp "^3.1.0" - semver "^7.2.1" - strip-ansi "^6.0.0" + regexpp "^3.2.0" + strip-ansi "^6.0.1" strip-json-comments "^3.1.0" - table "^6.0.4" text-table "^0.2.0" - v8-compile-cache "^2.0.3" -espree@^7.3.0, espree@^7.3.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" - integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== +espree@^9.4.0: + version "9.4.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.4.0.tgz#cd4bc3d6e9336c433265fc0aa016fc1aaf182f8a" + integrity sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw== dependencies: - acorn "^7.4.0" - acorn-jsx "^5.3.1" - eslint-visitor-keys "^1.3.0" + acorn "^8.8.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.3.0" -esprima@^4.0.0: +esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== esquery@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + resolved "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz" integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== dependencies: estraverse "^5.1.0" esrecurse@^4.3.0: version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz" integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== dependencies: estraverse "^5.2.0" estraverse@^4.1.1: version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== estraverse@^5.1.0, estraverse@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" - integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== + version "5.3.0" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== estree-walker@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" + resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz" integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== estree-walker@^2.0.1: version "2.0.2" - resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" + resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz" integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== esutils@^2.0.2: version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== etag@~1.8.1: version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== -extract-zip@^1.6.6: - version "1.7.0" - resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.7.0.tgz#556cc3ae9df7f452c493a0cfb51cc30277940927" - integrity sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA== +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== dependencies: - concat-stream "^1.6.2" - debug "^2.6.9" - mkdirp "^0.5.4" - yauzl "^2.10.0" + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" -fast-deep-equal@^3.1.1: +execa@^6.1.0: + version "6.1.0" + resolved "https://registry.npmjs.org/execa/-/execa-6.1.0.tgz" + integrity sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.1" + human-signals "^3.0.1" + is-stream "^3.0.0" + merge-stream "^2.0.0" + npm-run-path "^5.1.0" + onetime "^6.0.0" + signal-exit "^3.0.7" + strip-final-newline "^3.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expect@^28.0.0, expect@^28.1.3: + version "28.1.3" + resolved "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz" + integrity sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g== + dependencies: + "@jest/expect-utils" "^28.1.3" + jest-get-type "^28.0.2" + jest-matcher-utils "^28.1.3" + jest-message-util "^28.1.3" + jest-util "^28.1.3" + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.0.3: - version "3.2.5" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.5.tgz#7939af2a656de79a4f1901903ee8adcaa7cb9661" - integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg== +fast-glob@^3.0.3, fast-glob@^3.2.9: + version "3.2.11" + resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz" + integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.0" + glob-parent "^5.1.2" merge2 "^1.3.0" - micromatch "^4.0.2" - picomatch "^2.2.1" + micromatch "^4.0.4" fast-json-stable-stringify@^2.0.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fast-levenshtein@^2.0.6: +fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fastq@^1.6.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.0.tgz#bb9fb955a07130a918eb63c1f5161cc32a5d0858" - integrity sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g== + version "1.13.0" + resolved "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz" + integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== dependencies: reusify "^1.0.4" -fd-slicer@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" - integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= +fb-watchman@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz" + integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== dependencies: - pend "~1.2.0" + bser "2.1.1" file-entry-cache@^6.0.1: version "6.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz" integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== dependencies: flat-cache "^3.0.4" fill-range@^7.0.1: version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz" integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== dependencies: to-regex-range "^5.0.1" -find-up@^2.0.0, find-up@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" - integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= - dependencies: - locate-path "^2.0.0" - -find-up@^4.1.0: +find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== dependencies: locate-path "^5.0.0" @@ -1807,136 +2977,170 @@ find-up@^4.1.0: find-up@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz" integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== dependencies: locate-path "^6.0.0" path-exists "^4.0.0" -find-versions@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-4.0.0.tgz#3c57e573bf97769b8cb8df16934b627915da4965" - integrity sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ== - dependencies: - semver-regex "^3.1.2" - flat-cache@^3.0.4: version "3.0.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz" integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== dependencies: flatted "^3.1.0" rimraf "^3.0.2" flatted@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469" - integrity sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA== + version "3.2.6" + resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz" + integrity sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ== + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" fresh@0.5.2: version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +fs-extra@^10.1.0: + version "10.1.0" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz" + integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" fs-extra@^8.1.0: version "8.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz" integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== dependencies: graceful-fs "^4.2.0" jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@^9.0.1: - version "9.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" - integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== - dependencies: - at-least-node "^1.0.0" - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - fs.realpath@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@~2.3.1: +fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== function-bind@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +function.prototype.name@^1.1.5: + version "1.1.5" + resolved "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz" + integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.0" + functions-have-names "^1.2.2" + functional-red-black-tree@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + resolved "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz" + integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== + +functions-have-names@^1.2.2: + version "1.2.3" + resolved "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== gensync@^1.0.0-beta.2: version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== -get-caller-file@^2.0.1: +get-caller-file@^2.0.1, get-caller-file@^2.0.5: version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.0.2, get-intrinsic@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" - integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== +get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: + version "1.1.2" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz" + integrity sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA== dependencies: function-bind "^1.1.1" has "^1.0.3" - has-symbols "^1.0.1" + has-symbols "^1.0.3" + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -glob-parent@^5.0.0, glob-parent@^5.1.0: +get-stream@^6.0.0, get-stream@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +get-symbol-description@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz" + integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" + +glob-parent@^5.1.2: version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" -glob@^7.1.3, glob@^7.1.6: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== +glob-parent@^6.0.1: + version "6.0.2" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: + version "7.2.3" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "^3.0.4" + minimatch "^3.1.1" once "^1.3.0" path-is-absolute "^1.0.0" globals@^11.1.0: version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^12.1.0: - version "12.4.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" - integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== - dependencies: - type-fest "^0.8.1" - -globals@^13.6.0: - version "13.8.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.8.0.tgz#3e20f504810ce87a8d72e55aecf8435b50f4c1b3" - integrity sha512-rHtdA6+PDBIjeEvA91rpqzEvk/k3/i7EeNQiryiWuJH0Hw9cpyJMAt2jtbAwUaRdhD+573X4vWw6IcjKPasi9Q== +globals@^13.15.0: + version "13.17.0" + resolved "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz" + integrity sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw== dependencies: type-fest "^0.20.2" globby@10.0.1: version "10.0.1" - resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.1.tgz#4782c34cb75dd683351335c5829cc3420e606b22" + resolved "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz" integrity sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A== dependencies: "@types/glob" "^7.1.1" @@ -1950,7 +3154,7 @@ globby@10.0.1: globby@^10.0.1: version "10.0.2" - resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.2.tgz#277593e745acaa4646c3ab411289ec47a0392543" + resolved "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz" integrity sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg== dependencies: "@types/glob" "^7.1.1" @@ -1962,586 +3166,1327 @@ globby@^10.0.1: merge2 "^1.2.3" slash "^3.0.0" -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2: - version "4.2.6" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" - integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.9: + version "4.2.10" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== -has-bigints@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" - integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== +grapheme-splitter@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz" + integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== + +has-bigints@^1.0.1, has-bigints@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== has-color@~0.1.0: version "0.1.7" - resolved "https://registry.yarnpkg.com/has-color/-/has-color-0.1.7.tgz#67144a5260c34fc3cca677d041daf52fe7b78b2f" - integrity sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8= + resolved "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz" + integrity sha512-kaNz5OTAYYmt646Hkqw50/qyxP2vFnTVu5AQ1Zmk22Kk5+4Qx6BpO8+u7IKsML5fOsFk0ZT0AcCJNYwcvaLBvw== has-flag@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== has-flag@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.0.1, has-symbols@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" - integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== +has-property-descriptors@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz" + integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== + dependencies: + get-intrinsic "^1.1.1" + +has-symbols@^1.0.2, has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" has@^1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== dependencies: function-bind "^1.1.1" -hosted-git-info@^2.1.4: - version "2.8.9" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" - integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== +he@1.2.0, he@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +helpertypes@^0.0.18: + version "0.0.18" + resolved "https://registry.npmjs.org/helpertypes/-/helpertypes-0.0.18.tgz" + integrity sha512-XRhfbSEmR+poXUC5/8AbmYNJb2riOT6qPzjGJZr0S9YedHiaY+/tzPYzWMUclYMEdCYo/1l8PDYrQFCj02v97w== + +html-encoding-sniffer@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz" + integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA== + dependencies: + whatwg-encoding "^2.0.0" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +html-minifier@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/html-minifier/-/html-minifier-4.0.0.tgz" + integrity sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig== + dependencies: + camel-case "^3.0.0" + clean-css "^4.2.1" + commander "^2.19.0" + he "^1.2.0" + param-case "^2.1.1" + relateurl "^0.2.7" + uglify-js "^3.5.1" http-errors@~1.6.2: version "1.6.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" - integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= + resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz" + integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== dependencies: depd "~1.1.2" inherits "2.0.3" setprototypeof "1.1.0" statuses ">= 1.4.0 < 2" -https-proxy-agent@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b" - integrity sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg== +http-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz" + integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== dependencies: - agent-base "5" + "@tootallnate/once" "2" + agent-base "6" debug "4" -husky@^4.2.5: - version "4.3.8" - resolved "https://registry.yarnpkg.com/husky/-/husky-4.3.8.tgz#31144060be963fd6850e5cc8f019a1dfe194296d" - integrity sha512-LCqqsB0PzJQ/AlCgfrfzRe3e3+NvmefAdKQhRYpxS4u6clblBoDdzzvHi8fmxKRzvMxPY/1WZWzomPZww0Anow== +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== dependencies: - chalk "^4.0.0" - ci-info "^2.0.0" - compare-versions "^3.6.0" - cosmiconfig "^7.0.0" - find-versions "^4.0.0" - opencollective-postinstall "^2.0.2" - pkg-dir "^5.0.0" - please-upgrade-node "^3.2.0" - slash "^3.0.0" - which-pm-runs "^1.0.0" + agent-base "6" + debug "4" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +human-signals@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/human-signals/-/human-signals-3.0.1.tgz" + integrity sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ== + +husky@^8.0.0: + version "8.0.1" + resolved "https://registry.npmjs.org/husky/-/husky-8.0.1.tgz" + integrity sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw== -ignore@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" - integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== +iconv-lite@0.6.3: + version "0.6.3" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" -ignore@^5.1.1: - version "5.1.8" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" - integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== +ignore@^5.1.1, ignore@^5.2.0: + version "5.2.0" + resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== import-fresh@^3.0.0, import-fresh@^3.2.1: version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== dependencies: parent-module "^1.0.0" resolve-from "^4.0.0" +import-local@^3.0.2: + version "3.1.0" + resolved "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz" + integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + imurmurhash@^0.1.4: version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== indent-string@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + resolved "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== inflight@^1.0.4: version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== dependencies: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.3, inherits@~2.0.3: +inherits@2: version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== inherits@2.0.3: version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" + integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== + +internal-slot@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz" + integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== + dependencies: + get-intrinsic "^1.1.0" + has "^1.0.3" + side-channel "^1.0.4" is-arrayish@^0.2.1: version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== is-bigint@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.1.tgz#6923051dfcbc764278540b9ce0e6b3213aa5ebc2" - integrity sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg== + version "1.0.4" + resolved "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" is-boolean-object@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.0.tgz#e2aaad3a3a8fca34c28f6eee135b156ed2587ff0" - integrity sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA== + version "1.1.2" + resolved "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== dependencies: - call-bind "^1.0.0" + call-bind "^1.0.2" + has-tostringtag "^1.0.0" -is-callable@^1.1.4, is-callable@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e" - integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ== +is-builtin-module@^3.1.0: + version "3.2.0" + resolved "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.0.tgz" + integrity sha512-phDA4oSGt7vl1n5tJvTWooWWAsXLY+2xCnxNqvKhGEzujg+A43wPlPOyDg3C8XQHN+6k/JTQWJ/j0dQh/qr+Hw== + dependencies: + builtin-modules "^3.3.0" -is-core-module@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" - integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ== +is-callable@^1.1.4, is-callable@^1.2.4: + version "1.2.4" + resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz" + integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== + +is-core-module@^2.8.1, is-core-module@^2.9.0: + version "2.10.0" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz" + integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg== dependencies: has "^1.0.3" is-date-object@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" - integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== + version "1.0.5" + resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" is-extglob@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== is-fullwidth-code-point@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== -is-glob@^4.0.0, is-glob@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== +is-fullwidth-code-point@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz" + integrity sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== dependencies: is-extglob "^2.1.1" is-module@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" - integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= + resolved "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz" + integrity sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g== -is-negative-zero@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" - integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== +is-negative-zero@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz" + integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== is-number-object@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" - integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw== + version "1.0.7" + resolved "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz" + integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== + dependencies: + has-tostringtag "^1.0.0" is-number@^7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== is-path-cwd@^2.2.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" + resolved "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz" integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== is-path-inside@^3.0.1: version "3.0.3" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== is-plain-object@^3.0.0: version "3.0.1" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-3.0.1.tgz#662d92d24c0aa4302407b0d45d21f2251c85f85b" + resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz" integrity sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g== +is-potential-custom-element-name@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz" + integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== + is-reference@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7" + resolved "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz" integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ== dependencies: "@types/estree" "*" -is-regex@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.2.tgz#81c8ebde4db142f2cf1c53fc86d6a45788266251" - integrity sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg== +is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== dependencies: call-bind "^1.0.2" - has-symbols "^1.0.1" + has-tostringtag "^1.0.0" -is-string@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" - integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== +is-shared-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz" + integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== + dependencies: + call-bind "^1.0.2" + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz" + integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== + +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" is-symbol@^1.0.2, is-symbol@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" - integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== + version "1.0.4" + resolved "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== dependencies: - has-symbols "^1.0.1" + has-symbols "^1.0.2" -isarray@^1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= +is-weakref@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" + +isbot@3.4.5: + version "3.4.5" + resolved "https://registry.npmjs.org/isbot/-/isbot-3.4.5.tgz" + integrity sha512-+KD6q1BBtw0iK9aGBGSfxJ31/ZgizKRjhm8ebgJUBMx0aeeQuIJ1I72beCoIrltIZGrSm4vmrxRxrG5n1aUTtw== isexe@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz" + integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== + +istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: + version "5.2.0" + resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz" + integrity sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-report@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz" + integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^3.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.1.5" + resolved "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz" + integrity sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jest-changed-files@^28.1.3: + version "28.1.3" + resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-28.1.3.tgz" + integrity sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA== + dependencies: + execa "^5.0.0" + p-limit "^3.1.0" + +jest-circus@^28.1.3: + version "28.1.3" + resolved "https://registry.npmjs.org/jest-circus/-/jest-circus-28.1.3.tgz" + integrity sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow== + dependencies: + "@jest/environment" "^28.1.3" + "@jest/expect" "^28.1.3" + "@jest/test-result" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^0.7.0" + is-generator-fn "^2.0.0" + jest-each "^28.1.3" + jest-matcher-utils "^28.1.3" + jest-message-util "^28.1.3" + jest-runtime "^28.1.3" + jest-snapshot "^28.1.3" + jest-util "^28.1.3" + p-limit "^3.1.0" + pretty-format "^28.1.3" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^28.1.3: + version "28.1.3" + resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.3.tgz" + integrity sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ== + dependencies: + "@jest/core" "^28.1.3" + "@jest/test-result" "^28.1.3" + "@jest/types" "^28.1.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + import-local "^3.0.2" + jest-config "^28.1.3" + jest-util "^28.1.3" + jest-validate "^28.1.3" + prompts "^2.0.1" + yargs "^17.3.1" + +jest-config@^28.1.3: + version "28.1.3" + resolved "https://registry.npmjs.org/jest-config/-/jest-config-28.1.3.tgz" + integrity sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^28.1.3" + "@jest/types" "^28.1.3" + babel-jest "^28.1.3" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^28.1.3" + jest-environment-node "^28.1.3" + jest-get-type "^28.0.2" + jest-regex-util "^28.0.2" + jest-resolve "^28.1.3" + jest-runner "^28.1.3" + jest-util "^28.1.3" + jest-validate "^28.1.3" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^28.1.3" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^28.1.3: + version "28.1.3" + resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz" + integrity sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw== + dependencies: + chalk "^4.0.0" + diff-sequences "^28.1.1" + jest-get-type "^28.0.2" + pretty-format "^28.1.3" + +jest-docblock@^28.1.1: + version "28.1.1" + resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-28.1.1.tgz" + integrity sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA== + dependencies: + detect-newline "^3.0.0" + +jest-each@^28.1.3: + version "28.1.3" + resolved "https://registry.npmjs.org/jest-each/-/jest-each-28.1.3.tgz" + integrity sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g== + dependencies: + "@jest/types" "^28.1.3" + chalk "^4.0.0" + jest-get-type "^28.0.2" + jest-util "^28.1.3" + pretty-format "^28.1.3" + +jest-environment-jsdom@^28.1.3: + version "28.1.3" + resolved "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-28.1.3.tgz" + integrity sha512-HnlGUmZRdxfCByd3GM2F100DgQOajUBzEitjGqIREcb45kGjZvRrKUdlaF6escXBdcXNl0OBh+1ZrfeZT3GnAg== + dependencies: + "@jest/environment" "^28.1.3" + "@jest/fake-timers" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/jsdom" "^16.2.4" + "@types/node" "*" + jest-mock "^28.1.3" + jest-util "^28.1.3" + jsdom "^19.0.0" + +jest-environment-node@^28.1.3: + version "28.1.3" + resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.3.tgz" + integrity sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A== + dependencies: + "@jest/environment" "^28.1.3" + "@jest/fake-timers" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/node" "*" + jest-mock "^28.1.3" + jest-util "^28.1.3" + +jest-get-type@^28.0.2: + version "28.0.2" + resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz" + integrity sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA== + +jest-haste-map@^28.1.3: + version "28.1.3" + resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz" + integrity sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA== + dependencies: + "@jest/types" "^28.1.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^28.0.2" + jest-util "^28.1.3" + jest-worker "^28.1.3" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^28.1.3: + version "28.1.3" + resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz" + integrity sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA== + dependencies: + jest-get-type "^28.0.2" + pretty-format "^28.1.3" + +jest-matcher-utils@^28.1.3: + version "28.1.3" + resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz" + integrity sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw== + dependencies: + chalk "^4.0.0" + jest-diff "^28.1.3" + jest-get-type "^28.0.2" + pretty-format "^28.1.3" + +jest-message-util@^28.1.3: + version "28.1.3" + resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz" + integrity sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^28.1.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^28.1.3" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^28.1.3: + version "28.1.3" + resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz" + integrity sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA== + dependencies: + "@jest/types" "^28.1.3" + "@types/node" "*" + +jest-pnp-resolver@^1.2.2: + version "1.2.2" + resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz" + integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== + +jest-regex-util@^28.0.2: + version "28.0.2" + resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz" + integrity sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw== + +jest-resolve-dependencies@^28.1.3: + version "28.1.3" + resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.3.tgz" + integrity sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA== + dependencies: + jest-regex-util "^28.0.2" + jest-snapshot "^28.1.3" + +jest-resolve@^28.1.3: + version "28.1.3" + resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-28.1.3.tgz" + integrity sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^28.1.3" + jest-pnp-resolver "^1.2.2" + jest-util "^28.1.3" + jest-validate "^28.1.3" + resolve "^1.20.0" + resolve.exports "^1.1.0" + slash "^3.0.0" + +jest-runner@^28.1.3: + version "28.1.3" + resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-28.1.3.tgz" + integrity sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA== + dependencies: + "@jest/console" "^28.1.3" + "@jest/environment" "^28.1.3" + "@jest/test-result" "^28.1.3" + "@jest/transform" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.10.2" + graceful-fs "^4.2.9" + jest-docblock "^28.1.1" + jest-environment-node "^28.1.3" + jest-haste-map "^28.1.3" + jest-leak-detector "^28.1.3" + jest-message-util "^28.1.3" + jest-resolve "^28.1.3" + jest-runtime "^28.1.3" + jest-util "^28.1.3" + jest-watcher "^28.1.3" + jest-worker "^28.1.3" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^28.1.3: + version "28.1.3" + resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.3.tgz" + integrity sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw== + dependencies: + "@jest/environment" "^28.1.3" + "@jest/fake-timers" "^28.1.3" + "@jest/globals" "^28.1.3" + "@jest/source-map" "^28.1.2" + "@jest/test-result" "^28.1.3" + "@jest/transform" "^28.1.3" + "@jest/types" "^28.1.3" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + execa "^5.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^28.1.3" + jest-message-util "^28.1.3" + jest-mock "^28.1.3" + jest-regex-util "^28.0.2" + jest-resolve "^28.1.3" + jest-snapshot "^28.1.3" + jest-util "^28.1.3" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@^28.1.3: + version "28.1.3" + resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.3.tgz" + integrity sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/traverse" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^28.1.3" + "@jest/transform" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/babel__traverse" "^7.0.6" + "@types/prettier" "^2.1.5" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^28.1.3" + graceful-fs "^4.2.9" + jest-diff "^28.1.3" + jest-get-type "^28.0.2" + jest-haste-map "^28.1.3" + jest-matcher-utils "^28.1.3" + jest-message-util "^28.1.3" + jest-util "^28.1.3" + natural-compare "^1.4.0" + pretty-format "^28.1.3" + semver "^7.3.5" + +jest-util@^28.1.3: + version "28.1.3" + resolved "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz" + integrity sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ== + dependencies: + "@jest/types" "^28.1.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^28.1.3: + version "28.1.3" + resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.3.tgz" + integrity sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA== + dependencies: + "@jest/types" "^28.1.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^28.0.2" + leven "^3.1.0" + pretty-format "^28.1.3" + +jest-watcher@^28.1.3: + version "28.1.3" + resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz" + integrity sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g== + dependencies: + "@jest/test-result" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.10.2" + jest-util "^28.1.3" + string-length "^4.0.1" jest-worker@^26.2.1: version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz" integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== dependencies: "@types/node" "*" merge-stream "^2.0.0" supports-color "^7.0.0" +jest-worker@^28.1.3: + version "28.1.3" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz" + integrity sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^28.1.3: + version "28.1.3" + resolved "https://registry.npmjs.org/jest/-/jest-28.1.3.tgz" + integrity sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA== + dependencies: + "@jest/core" "^28.1.3" + "@jest/types" "^28.1.3" + import-local "^3.0.2" + jest-cli "^28.1.3" + js-reporters@1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/js-reporters/-/js-reporters-1.1.0.tgz#c83c00fe0d4c9f67f944b4edd5f3b2957497cd62" - integrity sha1-yDwA/g1Mn2f5RLTt1fOylXSXzWI= + resolved "https://registry.npmjs.org/js-reporters/-/js-reporters-1.1.0.tgz" + integrity sha512-+hsAd3ijND8m4sYeEMOWCylj2kAXFLrCVETeICtyEhkLVuHRm1d5mfKpUZvaSZo+EjoQt+K0mbrzZRmozmLoQw== + +js-reporters@1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/js-reporters/-/js-reporters-1.2.1.tgz" + integrity sha512-E4/TBelYYApx/lszChawx4+4MxEAZzL2hNYjQfHsIuu/vlYHkNRrlhTwaeABe5QhK546XmmAvqnCKHgawZs50g== + +js-sdsl@^4.1.4: + version "4.1.5" + resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.1.5.tgz#1ff1645e6b4d1b028cd3f862db88c9d887f26e2a" + integrity sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q== js-tokens@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== js-yaml@^3.13.1: version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== dependencies: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsdoc-type-pratt-parser@~3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-3.1.0.tgz#a4a56bdc6e82e5865ffd9febc5b1a227ff28e67e" + integrity sha512-MgtD0ZiCDk9B+eI73BextfRrVQl0oyzRG8B2BjORts6jbunj4ScKPcyXGTbB6eXL4y9TzxCm6hyeLq/2ASzNdw== + +jsdom@^19.0.0: + version "19.0.0" + resolved "https://registry.npmjs.org/jsdom/-/jsdom-19.0.0.tgz" + integrity sha512-RYAyjCbxy/vri/CfnjUWJQQtZ3LKlLnDqj+9XLNnJPgEGeirZs3hllKR20re8LUZ6o1b1X4Jat+Qd26zmP41+A== + dependencies: + abab "^2.0.5" + acorn "^8.5.0" + acorn-globals "^6.0.0" + cssom "^0.5.0" + cssstyle "^2.3.0" + data-urls "^3.0.1" + decimal.js "^10.3.1" + domexception "^4.0.0" + escodegen "^2.0.0" + form-data "^4.0.0" + html-encoding-sniffer "^3.0.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.0" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.0" + parse5 "6.0.1" + saxes "^5.0.1" + symbol-tree "^3.2.4" + tough-cookie "^4.0.0" + w3c-hr-time "^1.0.2" + w3c-xmlserializer "^3.0.0" + webidl-conversions "^7.0.0" + whatwg-encoding "^2.0.0" + whatwg-mimetype "^3.0.0" + whatwg-url "^10.0.0" + ws "^8.2.3" + xml-name-validator "^4.0.0" + jsesc@^2.5.1: version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== jsesc@~0.5.0: version "0.5.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" - integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= + resolved "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz" + integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== json-parse-even-better-errors@^2.3.0: version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== json-schema-traverse@^0.4.1: version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== -json-schema-traverse@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" - integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== - json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== json5@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" + resolved "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz" integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== dependencies: minimist "^1.2.0" -json5@^2.1.2: - version "2.2.0" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" - integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== - dependencies: - minimist "^1.2.5" +json5@^2.2.1: + version "2.2.1" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz" + integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== jsonfile@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz" + integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== optionalDependencies: graceful-fs "^4.1.6" jsonfile@^6.0.1: version "6.1.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz" integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== dependencies: universalify "^2.0.0" optionalDependencies: graceful-fs "^4.1.6" +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + levn@^0.4.1: version "0.4.1" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz" integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== dependencies: prelude-ls "^1.2.1" type-check "~0.4.0" -lines-and-columns@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" - integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= - -load-json-file@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" - integrity sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg= +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz" + integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA== dependencies: - graceful-fs "^4.1.2" - parse-json "^2.2.0" - pify "^2.0.0" - strip-bom "^3.0.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" -locate-path@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" - integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= - dependencies: - p-locate "^2.0.0" - path-exists "^3.0.0" +lilconfig@2.0.5: + version "2.0.5" + resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.5.tgz" + integrity sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +lint-staged@^13.0.3: + version "13.0.3" + resolved "https://registry.npmjs.org/lint-staged/-/lint-staged-13.0.3.tgz" + integrity sha512-9hmrwSCFroTSYLjflGI8Uk+GWAwMB4OlpU4bMJEAT5d/llQwtYKoim4bLOyLCuWFAhWEupE0vkIFqtw/WIsPug== + dependencies: + cli-truncate "^3.1.0" + colorette "^2.0.17" + commander "^9.3.0" + debug "^4.3.4" + execa "^6.1.0" + lilconfig "2.0.5" + listr2 "^4.0.5" + micromatch "^4.0.5" + normalize-path "^3.0.0" + object-inspect "^1.12.2" + pidtree "^0.6.0" + string-argv "^0.3.1" + yaml "^2.1.1" + +listr2@^4.0.5: + version "4.0.5" + resolved "https://registry.npmjs.org/listr2/-/listr2-4.0.5.tgz" + integrity sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA== + dependencies: + cli-truncate "^2.1.0" + colorette "^2.0.16" + log-update "^4.0.0" + p-map "^4.0.0" + rfdc "^1.3.0" + rxjs "^7.5.5" + through "^2.3.8" + wrap-ansi "^7.0.0" locate-path@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz" integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== dependencies: p-locate "^4.1.0" locate-path@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz" integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== dependencies: p-locate "^5.0.0" -lodash.clonedeep@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" - integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= - lodash.debounce@^4.0.8: version "4.0.8" - resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" - integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= + resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz" + integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== -lodash.flatten@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" - integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -lodash.truncate@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" - integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= +log-update@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz" + integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== + dependencies: + ansi-escapes "^4.3.0" + cli-cursor "^3.1.0" + slice-ansi "^4.0.0" + wrap-ansi "^6.2.0" -lodash@^4.17.19, lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +lower-case@^1.1.1: + version "1.1.4" + resolved "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz" + integrity sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA== lru-cache@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== dependencies: yallist "^4.0.0" magic-string@^0.25.7: - version "0.25.7" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" - integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== + version "0.25.9" + resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz" + integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ== dependencies: - sourcemap-codec "^1.4.4" + sourcemap-codec "^1.4.8" + +magic-string@^0.26.2: + version "0.26.2" + resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.26.2.tgz" + integrity sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A== + dependencies: + sourcemap-codec "^1.4.8" + +make-dir@^3.0.0: + version "3.1.0" + resolved "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" merge-stream@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.2.3, merge2@^1.3.0: +merge2@^1.2.3, merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -micromatch@^4.0.2: - version "4.0.4" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" - integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== +micromatch@^4.0.4, micromatch@^4.0.5: + version "4.0.5" + resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== dependencies: - braces "^3.0.1" - picomatch "^2.2.3" + braces "^3.0.2" + picomatch "^2.3.1" -mime-db@1.47.0: - version "1.47.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.47.0.tgz#8cb313e59965d3c05cfbf898915a267af46a335c" - integrity sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw== +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.25: - version "2.1.30" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.30.tgz#6e7be8b4c479825f85ed6326695db73f9305d62d" - integrity sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg== +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: - mime-db "1.47.0" + mime-db "1.52.0" mime@1.4.1: version "1.4.1" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" + resolved "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz" integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ== mime@1.6.0: version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mime@^2.0.3: - version "2.5.2" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" - integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg== +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +mimic-fn@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz" + integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== -minimatch@^3.0.4: +minimatch@3.0.4: version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== dependencies: brace-expansion "^1.1.7" -minimist@^1.2.0, minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== - -mkdirp@^0.5.4: - version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== +minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: - minimist "^1.2.5" + brace-expansion "^1.1.7" + +minimist@^1.2.0, minimist@^1.2.6: + version "1.2.6" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== ms@2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== ms@2.1.2: version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + natural-compare@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== -node-qunit-puppeteer@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/node-qunit-puppeteer/-/node-qunit-puppeteer-2.1.0.tgz#72e36a240e3981b127fa84d175f7186acf86c1f9" - integrity sha512-53ytjfu+t51r9qbOsek5D9biAlGO808Pp6b1gGdg/fcGJGFZIJNHoVpFF3OLRe7IUXA9QjhsQL9IkK4NGenHCA== +no-case@^2.2.0: + version "2.3.2" + resolved "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz" + integrity sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ== + dependencies: + lower-case "^1.1.1" + +node-html-parser@^4.1.5: + version "4.1.5" + resolved "https://registry.npmjs.org/node-html-parser/-/node-html-parser-4.1.5.tgz" + integrity sha512-NLgqUXtftqnBqIjlRjYSaApaqE7TTxfTiH4VqKCjdUJKFOtUzRwney83EHz2qYc0XoxXAkYdmLjENCuZHvsIFg== dependencies: - colors "^1.4.0" - puppeteer "^2.1.0" + css-select "^4.1.3" + he "1.2.0" + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.2, node-releases@^2.0.6: + version "2.0.6" + resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz" + integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== + +node-watch@0.6.1: + version "0.6.1" + resolved "https://registry.npmjs.org/node-watch/-/node-watch-0.6.1.tgz" + integrity sha512-gwQiR7weFRV8mAtT0x0kXkZ18dfRLB45xH7q0hCOVQMLfLb2f1ZaSvR57q4/b/Vj6B0RwMNJYbvb69e1yM7qEA== -node-releases@^1.1.71: - version "1.1.71" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.71.tgz#cb1334b179896b1c89ecfdd4b725fb7bbdfc7dbb" - integrity sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg== +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -normalize-package-data@^2.3.2: - version "2.5.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" - integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== dependencies: - hosted-git-info "^2.1.4" - resolve "^1.10.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" + path-key "^3.0.0" -object-inspect@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a" - integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw== +npm-run-path@^5.1.0: + version "5.1.0" + resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz" + integrity sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q== + dependencies: + path-key "^4.0.0" + +nth-check@^2.0.1: + version "2.1.1" + resolved "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz" + integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== + dependencies: + boolbase "^1.0.0" + +nwsapi@^2.2.0: + version "2.2.1" + resolved "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.1.tgz" + integrity sha512-JYOWTeFoS0Z93587vRJgASD5Ut11fYl5NyihP3KrYBvMe1FRRs6RN7m20SA/16GM4P6hTnZjT+UmDOt38UeXNg== -object-keys@^1.0.12, object-keys@^1.1.1: +object-inspect@^1.12.0, object-inspect@^1.12.2, object-inspect@^1.9.0: + version "1.12.2" + resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz" + integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== + +object-keys@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== +object-path@^0.11.8: + version "0.11.8" + resolved "https://registry.npmjs.org/object-path/-/object-path-0.11.8.tgz" + integrity sha512-YJjNZrlXJFM42wTBn6zgOJVar9KFJvzx6sTWDte8sWZF//cnjl0BxHNpfZx+ZffXX63A9q0b1zsFiBX4g4X5KA== + object.assign@^4.1.0, object.assign@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" - integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== + version "4.1.3" + resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.3.tgz" + integrity sha512-ZFJnX3zltyjcYJL0RoCJuzb+11zWGyaDbjgxZbdV7rFEcHQuYxrZqhow67aA7xpes6LhojyFDaBKAFfogQrikA== dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - has-symbols "^1.0.1" + call-bind "^1.0.2" + define-properties "^1.1.4" + has-symbols "^1.0.3" object-keys "^1.1.1" -object.entries@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.3.tgz#c601c7f168b62374541a07ddbd3e2d5e4f7711a6" - integrity sha512-ym7h7OZebNS96hn5IJeyUmaWhaSM4SVtAPPfNLQEI2MYWCO2egsITb9nab2+i/Pwibx+R0mtn+ltKJXRSeTMGg== +object.entries@^1.1.5: + version "1.1.5" + resolved "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz" + integrity sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g== dependencies: - call-bind "^1.0.0" + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.18.0-next.1" - has "^1.0.3" + es-abstract "^1.19.1" -object.values@^1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.3.tgz#eaa8b1e17589f02f698db093f7c62ee1699742ee" - integrity sha512-nkF6PfDB9alkOUxpf1HNm/QlkeW3SReqL5WXeBLpEJJnlPSvRaDQpW3gQTksTN3fgJX4hL42RzKyOin6ff3tyw== +object.values@^1.1.5: + version "1.1.5" + resolved "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz" + integrity sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg== dependencies: call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.18.0-next.2" - has "^1.0.3" + es-abstract "^1.19.1" on-finished@~2.3.0: version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= + resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz" + integrity sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww== dependencies: ee-first "1.1.1" once@^1.3.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" -opencollective-postinstall@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259" - integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q== +onetime@^5.1.0, onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +onetime@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz" + integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== + dependencies: + mimic-fn "^4.0.0" + +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" optionator@^0.9.1: version "0.9.1" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz" integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== dependencies: deep-is "^0.1.3" @@ -2551,82 +4496,70 @@ optionator@^0.9.1: type-check "^0.4.0" word-wrap "^1.2.3" -p-limit@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" - integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== - dependencies: - p-try "^1.0.0" - p-limit@^2.2.0: version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== dependencies: p-try "^2.0.0" -p-limit@^3.0.2: +p-limit@^3.0.2, p-limit@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== dependencies: yocto-queue "^0.1.0" -p-locate@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" - integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= - dependencies: - p-limit "^1.1.0" - p-locate@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz" integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== dependencies: p-limit "^2.2.0" p-locate@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz" integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== dependencies: p-limit "^3.0.2" p-map@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d" + resolved "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz" integrity sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ== dependencies: aggregate-error "^3.0.0" -p-try@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" - integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" p-try@^2.0.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +param-case@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz" + integrity sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w== + dependencies: + no-case "^2.2.0" + parent-module@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== dependencies: callsites "^3.0.0" -parse-json@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" - integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= - dependencies: - error-ex "^1.2.0" - -parse-json@^5.0.0: +parse-json@^5.2.0: version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz" integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== dependencies: "@babel/code-frame" "^7.0.0" @@ -2634,275 +4567,299 @@ parse-json@^5.0.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= +parse5@6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== path-exists@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== path-is-absolute@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== -path-key@^3.1.0: +path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" - integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== +path-key@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz" + integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== -path-type@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" - integrity sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM= - dependencies: - pify "^2.0.0" +path-parse@^1.0.6, path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-type@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -pend@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" - integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.3.tgz#465547f359ccc206d3c48e46a1bcb89bf7ee619d" - integrity sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg== +picomatch@^2.0.4, picomatch@^2.2.2, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -pify@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" - integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= +pidtree@^0.6.0: + version "0.6.0" + resolved "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz" + integrity sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g== -pkg-dir@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" - integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s= - dependencies: - find-up "^2.1.0" +pirates@^4.0.4: + version "4.0.5" + resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz" + integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== -pkg-dir@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-5.0.0.tgz#a02d6aebe6ba133a928f74aec20bafdfe6b8e760" - integrity sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA== +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== dependencies: - find-up "^5.0.0" + find-up "^4.0.0" -please-upgrade-node@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" - integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg== +playwright-core@1.24.2: + version "1.24.2" + resolved "https://registry.npmjs.org/playwright-core/-/playwright-core-1.24.2.tgz" + integrity sha512-zfAoDoPY/0sDLsgSgLZwWmSCevIg1ym7CppBwllguVBNiHeixZkc1AdMuYUPZC6AdEYc4CxWEyLMBTw2YcmRrA== + +playwright@1.24.2: + version "1.24.2" + resolved "https://registry.npmjs.org/playwright/-/playwright-1.24.2.tgz" + integrity sha512-iMWDLgaFRT+7dXsNeYwgl8nhLHsUrzFyaRVC+ftr++P1dVs70mPrFKBZrGp1fOKigHV9d1syC03IpPbqLKlPsg== dependencies: - semver-compare "^1.0.0" + playwright-core "1.24.2" prelude-ls@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz" + integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== -progress@^2.0.0, progress@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== +pretty-format@^28.0.0, pretty-format@^28.1.3: + version "28.1.3" + resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz" + integrity sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q== + dependencies: + "@jest/schemas" "^28.1.3" + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^18.0.0" -proxy-from-env@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" -punycode@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +psl@^1.1.33: + version "1.9.0" + resolved "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz" + integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== -puppeteer@^2.1.0: +punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-2.1.1.tgz#ccde47c2a688f131883b50f2d697bd25189da27e" - integrity sha512-LWzaDVQkk1EPiuYeTOj+CZRIjda4k2s5w4MK4xoH2+kgWV/SDlkYHmxatDdtYrciHUKSXTsGgPgPP8ILVdBsxg== - dependencies: - "@types/mime-types" "^2.1.0" - debug "^4.1.0" - extract-zip "^1.6.6" - https-proxy-agent "^4.0.0" - mime "^2.0.3" - mime-types "^2.1.25" - progress "^2.0.1" - proxy-from-env "^1.0.0" - rimraf "^2.6.1" - ws "^6.1.0" + resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== queue-microtask@^1.2.2: version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +qunit@2.10.0: + version "2.10.0" + resolved "https://registry.npmjs.org/qunit/-/qunit-2.10.0.tgz" + integrity sha512-EP9Q9Kf45z4l/X02ZJtyTQU9DBc82pEWAncSNx7Weo/73BDpX71xqbsdDAQrtEeeilK70cib7CY/lniJV6Cwwg== + dependencies: + commander "2.12.2" + js-reporters "1.2.1" + minimatch "3.0.4" + node-watch "0.6.1" + resolve "1.9.0" + randombytes@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + resolved "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz" integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== dependencies: safe-buffer "^5.1.0" range-parser@~1.2.0: version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -read-pkg-up@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" - integrity sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4= - dependencies: - find-up "^2.0.0" - read-pkg "^2.0.0" - -read-pkg@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" - integrity sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg= - dependencies: - load-json-file "^2.0.0" - normalize-package-data "^2.3.2" - path-type "^2.0.0" - -readable-stream@^2.2.2: - version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" +react-is@^18.0.0: + version "18.2.0" + resolved "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz" + integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== -regenerate-unicode-properties@^8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" - integrity sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA== +regenerate-unicode-properties@^10.0.1: + version "10.0.1" + resolved "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz" + integrity sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw== dependencies: - regenerate "^1.4.0" + regenerate "^1.4.2" -regenerate@^1.4.0: +regenerate@^1.4.2: version "1.4.2" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" + resolved "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz" integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== regenerator-runtime@^0.13.4: - version "0.13.7" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" - integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== + version "0.13.9" + resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz" + integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== -regenerator-transform@^0.14.2: - version "0.14.5" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.5.tgz#c98da154683671c9c4dcb16ece736517e1b7feb4" - integrity sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw== +regenerator-transform@^0.15.0: + version "0.15.0" + resolved "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz" + integrity sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg== dependencies: "@babel/runtime" "^7.8.4" -regexpp@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" - integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== - -regexpu-core@^4.7.1: - version "4.7.1" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.1.tgz#2dea5a9a07233298fbf0db91fa9abc4c6e0f8ad6" - integrity sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ== - dependencies: - regenerate "^1.4.0" - regenerate-unicode-properties "^8.2.0" - regjsgen "^0.5.1" - regjsparser "^0.6.4" - unicode-match-property-ecmascript "^1.0.4" - unicode-match-property-value-ecmascript "^1.2.0" - -regjsgen@^0.5.1: - version "0.5.2" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733" - integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A== +regexp.prototype.flags@^1.4.3: + version "1.4.3" + resolved "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz" + integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + functions-have-names "^1.2.2" + +regexpp@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== -regjsparser@^0.6.4: - version "0.6.9" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.9.tgz#b489eef7c9a2ce43727627011429cf833a7183e6" - integrity sha512-ZqbNRz1SNjLAiYuwY0zoXW8Ne675IX5q+YHioAGbCw4X96Mjl2+dcX9B2ciaeyYjViDAfvIjFpQjJgLttTEERQ== +regexpu-core@^5.1.0: + version "5.1.0" + resolved "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.1.0.tgz" + integrity sha512-bb6hk+xWd2PEOkj5It46A16zFMs2mv86Iwpdu94la4S3sJ7C973h2dHpYKwIBGaWSO7cIRJ+UX0IeMaWcO4qwA== + dependencies: + regenerate "^1.4.2" + regenerate-unicode-properties "^10.0.1" + regjsgen "^0.6.0" + regjsparser "^0.8.2" + unicode-match-property-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.0.0" + +regjsgen@^0.6.0: + version "0.6.0" + resolved "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz" + integrity sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA== + +regjsparser@^0.8.2: + version "0.8.4" + resolved "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz" + integrity sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA== dependencies: jsesc "~0.5.0" +relateurl@^0.2.7: + version "0.2.7" + resolved "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz" + integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog== + require-directory@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= - -require-from-string@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== require-main-filename@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + resolved "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + resolve-from@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve.exports@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz" + integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== + resolve@1.1.7: version "1.1.7" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" - integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= + resolved "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz" + integrity sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg== -resolve@^1.10.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.17.0: - version "1.20.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" - integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== +resolve@1.9.0: + version "1.9.0" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.9.0.tgz" + integrity sha512-TZNye00tI67lwYvzxCxHGjwTNlUV70io54/Ed4j6PscB8xVfuBJpRenI/o6dVk0cY0PYTY27AgCoGGxRnYuItQ== dependencies: - is-core-module "^2.2.0" path-parse "^1.0.6" +resolve@^1.14.2, resolve@^1.17.0, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.0: + version "1.22.1" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz" + integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== + dependencies: + is-core-module "^2.9.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + reusify@^1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rimraf@^2.6.1: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" +rfdc@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz" + integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== dependencies: glob "^7.1.3" -rollup-plugin-copy@^3.3.0: +rollup-plugin-copy@^3.4.0: version "3.4.0" - resolved "https://registry.yarnpkg.com/rollup-plugin-copy/-/rollup-plugin-copy-3.4.0.tgz#f1228a3ffb66ffad8606e2f3fb7ff23141ed3286" + resolved "https://registry.npmjs.org/rollup-plugin-copy/-/rollup-plugin-copy-3.4.0.tgz" integrity sha512-rGUmYYsYsceRJRqLVlE9FivJMxJ7X6jDlP79fmFkL8sJs7VVMSVyA2yfyL+PGyO/vJs4A87hwhgVfz61njI+uQ== dependencies: "@types/fs-extra" "^8.0.1" @@ -2918,7 +4875,15 @@ rollup-plugin-delete@^2.0.0: dependencies: del "^5.1.0" -rollup-plugin-terser@^7.0.0: +rollup-plugin-html2@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/rollup-plugin-html2/-/rollup-plugin-html2-3.1.0.tgz#01146d744e7571eefe90665d90f7f8d6e8d9e15f" + integrity sha512-j9Gr4diZRVVj+A8Msi6HoPEY29m6z13PMkPI9pWhHn1FBZeimGmKcI/qQVyswyYsSvVvUUh9Cy/HJwUiykf12Q== + dependencies: + html-minifier "^4.0.0" + node-html-parser "^4.1.5" + +rollup-plugin-terser@^7.0.2: version "7.0.2" resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz#e8fbba4869981b2dc35ae7e8a502d5c6c04d324d" integrity sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ== @@ -2928,65 +4893,85 @@ rollup-plugin-terser@^7.0.0: serialize-javascript "^4.0.0" terser "^5.0.0" -rollup@^2.8.2: - version "2.45.2" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.45.2.tgz#8fb85917c9f35605720e92328f3ccbfba6f78b48" - integrity sha512-kRRU7wXzFHUzBIv0GfoFFIN3m9oteY4uAsKllIpQDId5cfnkWF2J130l+27dzDju0E6MScKiV0ZM5Bw8m4blYQ== +rollup-plugin-ts@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rollup-plugin-ts/-/rollup-plugin-ts-3.0.2.tgz#ee1a3f9ffe202ceff0b4d2f725fa268fa0c921bf" + integrity sha512-67qi2QTHewhLyKDG6fX3jpohWpmUPPIT/xJ7rsYK46X6MqmoWy64Ti0y8ygPfLv8mXDCdRZUofM3mTxDfCswRA== + dependencies: + "@rollup/pluginutils" "^4.2.1" + "@wessberg/stringutil" "^1.0.19" + ansi-colors "^4.1.3" + browserslist "^4.20.4" + browserslist-generator "^1.0.66" + compatfactory "^1.0.1" + crosspath "^2.0.0" + magic-string "^0.26.2" + ts-clone-node "^1.0.0" + tslib "^2.4.0" + +rollup@^2.77.2: + version "2.78.0" + resolved "https://registry.npmjs.org/rollup/-/rollup-2.78.0.tgz" + integrity sha512-4+YfbQC9QEVvKTanHhIAFVUFSRsezvQF8vFOJwtGfb9Bb+r014S+qryr9PSmw8x6sMnPkmFBGAvIFVQxvJxjtg== optionalDependencies: - fsevents "~2.3.1" + fsevents "~2.3.2" run-parallel@^1.1.9: version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== dependencies: queue-microtask "^1.2.2" +rxjs@^7.5.5: + version "7.5.6" + resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.5.6.tgz" + integrity sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw== + dependencies: + tslib "^2.1.0" + safe-buffer@^5.1.0: version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@~5.1.1: version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -semver-compare@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" - integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= - -semver-regex@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-3.1.2.tgz#34b4c0d361eef262e07199dbef316d0f2ab11807" - integrity sha512-bXWyL6EAKOJa81XG1OZ/Yyuq+oT0b2YLlxx7c+mrdYPaPbnj6WgVULXhinMIeZGufuUBu/eVRqXEhiv4imfwxA== +"safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -"semver@2 || 3 || 4 || 5": - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== +saxes@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz" + integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== + dependencies: + xmlchars "^2.2.0" semver@7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" + resolved "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== -semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: +semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.2.1: - version "7.3.5" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" - integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== +semver@^7.3.5, semver@^7.3.7: + version "7.3.7" + resolved "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== dependencies: lru-cache "^6.0.0" send@0.16.2: version "0.16.2" - resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1" + resolved "https://registry.npmjs.org/send/-/send-0.16.2.tgz" integrity sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw== dependencies: debug "2.6.9" @@ -3005,89 +4990,115 @@ send@0.16.2: serialize-javascript@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa" + resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz" integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw== dependencies: randombytes "^2.1.0" set-blocking@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== setprototypeof@1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" + resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz" integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== shebang-command@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== dependencies: shebang-regex "^3.0.0" shebang-regex@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + slash@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +slice-ansi@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz" + integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + slice-ansi@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz" integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== dependencies: ansi-styles "^4.0.0" astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" -source-map-support@~0.5.19: - version "0.5.19" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" - integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== +slice-ansi@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz" + integrity sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ== + dependencies: + ansi-styles "^6.0.0" + is-fullwidth-code-point "^4.0.0" + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" -source-map@^0.5.0: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= +source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" -source-map@^0.6.0: +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@~0.7.2: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== - -sourcemap-codec@^1.4.4: +sourcemap-codec@^1.4.8: version "1.4.8" - resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" + resolved "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz" integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== -spdx-correct@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" - integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== - dependencies: - spdx-expression-parse "^3.0.0" - spdx-license-ids "^3.0.0" - spdx-exceptions@^2.1.0: version "2.3.0" resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== -spdx-expression-parse@^3.0.0: +spdx-expression-parse@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== @@ -3096,242 +5107,475 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.7" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz#e9c18a410e5ed7e12442a549fbd8afa767038d65" - integrity sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ== + version "3.0.12" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz#69077835abe2710b65f03969898b6637b505a779" + integrity sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA== sprintf-js@~1.0.2: version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stack-utils@^2.0.3: + version "2.0.5" + resolved "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz" + integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== + dependencies: + escape-string-regexp "^2.0.0" "statuses@>= 1.4.0 < 2": version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz" + integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== statuses@~1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" + resolved "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz" integrity sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew== -string-width@^4.1.0, string-width@^4.2.0: - version "4.2.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5" - integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA== +string-argv@^0.3.1: + version "0.3.1" + resolved "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz" + integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== dependencies: emoji-regex "^8.0.0" is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.0" + strip-ansi "^6.0.1" -string.prototype.trimend@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" - integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== +string-width@^5.0.0: + version "5.1.2" + resolved "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +string.prototype.trimend@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz" + integrity sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" + define-properties "^1.1.4" + es-abstract "^1.19.5" -string.prototype.trimstart@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" - integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== +string.prototype.trimstart@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz" + integrity sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" + define-properties "^1.1.4" + es-abstract "^1.19.5" -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: - safe-buffer "~5.1.0" + ansi-regex "^5.0.1" -strip-ansi@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" - integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== +strip-ansi@^7.0.1: + version "7.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz" + integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw== dependencies: - ansi-regex "^5.0.0" + ansi-regex "^6.0.1" strip-ansi@~0.1.0: version "0.1.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.1.1.tgz#39e8a98d044d150660abe4a6808acf70bb7bc991" - integrity sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE= + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz" + integrity sha512-behete+3uqxecWlDAm5lmskaSaISA+ThQ4oNNBDTBJt0x2ppR6IPqfZNuj6BLaLJ/Sji4TPZlcRyOis8wXQTLg== strip-bom@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= + resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-final-newline@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz" + integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== supports-color@^5.3.0: version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== dependencies: has-flag "^3.0.0" supports-color@^7.0.0, supports-color@^7.1.0: version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: has-flag "^4.0.0" -table@^6.0.4: - version "6.1.0" - resolved "https://registry.yarnpkg.com/table/-/table-6.1.0.tgz#676a0cfb206008b59e783fcd94ef8ba7d67d966c" - integrity sha512-T4G5KMmqIk6X87gLKWyU5exPpTjLjY5KyrFWaIjv3SvgaIUGXV7UEzGEnZJdTA38/yUS6f9PlKezQ0bYXG3iIQ== +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== dependencies: - ajv "^8.0.1" - is-boolean-object "^1.1.0" - is-number-object "^1.0.4" - is-string "^1.0.5" - lodash.clonedeep "^4.5.0" - lodash.flatten "^4.4.0" - lodash.truncate "^4.4.2" - slice-ansi "^4.0.0" - string-width "^4.2.0" + has-flag "^4.0.0" + +supports-hyperlinks@^2.0.0: + version "2.2.0" + resolved "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz" + integrity sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + +terminal-link@^2.0.0: + version "2.1.1" + resolved "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz" + integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== + dependencies: + ansi-escapes "^4.2.1" + supports-hyperlinks "^2.0.0" terser@^5.0.0: - version "5.6.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.6.1.tgz#a48eeac5300c0a09b36854bf90d9c26fb201973c" - integrity sha512-yv9YLFQQ+3ZqgWCUk+pvNJwgUTdlIxUk1WTN+RnaFJe2L7ipG2csPT0ra2XRm7Cs8cxN7QXmK1rFzEwYEQkzXw== + version "5.14.2" + resolved "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz" + integrity sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA== dependencies: + "@jridgewell/source-map" "^0.3.2" + acorn "^8.5.0" commander "^2.20.0" - source-map "~0.7.2" - source-map-support "~0.5.19" + source-map-support "~0.5.20" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" text-table@^0.2.0: version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +through@^2.3.8: + version "2.3.8" + resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== to-fast-properties@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== to-regex-range@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== dependencies: is-number "^7.0.0" -tsconfig-paths@^3.9.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" - integrity sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw== +tough-cookie@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz" + integrity sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.1.2" + +tr46@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz" + integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA== + dependencies: + punycode "^2.1.1" + +ts-clone-node@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/ts-clone-node/-/ts-clone-node-1.0.0.tgz" + integrity sha512-/cDYbr2HAXxFNeTT41c/xs/2bhLJjqnYheHsmA3AoHSt+n4JA4t0FL9Lk5O8kWnJ6jeB3kPcUoXIFtwERNzv6Q== + dependencies: + compatfactory "^1.0.1" + +ts-node@^10.9.1: + version "10.9.1" + resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz" + integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +tsconfig-paths@^3.14.1: + version "3.14.1" + resolved "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz" + integrity sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ== dependencies: "@types/json5" "^0.0.29" json5 "^1.0.1" - minimist "^1.2.0" + minimist "^1.2.6" strip-bom "^3.0.0" +tslib@^1.8.1: + version "1.14.1" + resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslib@^2.1.0, tslib@^2.4.0: + version "2.4.0" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz" + integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== + +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + tunnel@0.0.3: version "0.0.3" - resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.3.tgz#e8f988115ca7be9d076c7a1fae4788be708f0cf1" - integrity sha1-6PmIEVynvp0HbHofrkeIvnCPDPE= + resolved "https://registry.npmjs.org/tunnel/-/tunnel-0.0.3.tgz" + integrity sha512-YohLhrnpZrbzloZ4AOHh+zKgGrkNO5LDw72UMhfGPC4ccHnNAyimzEb0RMZgqAIuR+PqXO4BzRhaqsv8UBw13Q== type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== dependencies: prelude-ls "^1.2.1" +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz" + integrity sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg== + dependencies: + prelude-ls "~1.1.2" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + type-fest@^0.20.2: version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== -type-fest@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +typescript@^4.7.4: + version "4.7.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" + integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== -unbox-primitive@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" - integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== +ua-parser-js@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.2.tgz" + integrity sha512-00y/AXhx0/SsnI51fTc0rLRmafiGOM4/O+ny10Ps7f+j/b8p/ZY11ytMgznXkOVo4GQ+KwQG5UQLkLGirsACRg== + +uglify-js@^3.5.1: + version "3.17.0" + resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.0.tgz" + integrity sha512-aTeNPVmgIMPpm1cxXr2Q/nEbvkmV8yq66F3om7X3P/cvOXQ0TMQ64Wk63iyT1gPlmdmGzjGpyLh1f3y8MZWXGg== + +unbox-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz" + integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== dependencies: - function-bind "^1.1.1" - has-bigints "^1.0.1" - has-symbols "^1.0.2" + call-bind "^1.0.2" + has-bigints "^1.0.2" + has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" -unicode-canonical-property-names-ecmascript@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" - integrity sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ== +unicode-canonical-property-names-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz" + integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== -unicode-match-property-ecmascript@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c" - integrity sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg== +unicode-match-property-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz" + integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== dependencies: - unicode-canonical-property-names-ecmascript "^1.0.4" - unicode-property-aliases-ecmascript "^1.0.4" + unicode-canonical-property-names-ecmascript "^2.0.0" + unicode-property-aliases-ecmascript "^2.0.0" -unicode-match-property-value-ecmascript@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz#0d91f600eeeb3096aa962b1d6fc88876e64ea531" - integrity sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ== +unicode-match-property-value-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz" + integrity sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw== -unicode-property-aliases-ecmascript@^1.0.4: - version "1.1.0" - resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4" - integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg== +unicode-property-aliases-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz" + integrity sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ== -universalify@^0.1.0: +universalify@^0.1.0, universalify@^0.1.2: version "0.1.2" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + resolved "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== universalify@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== +update-browserslist-db@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz" + integrity sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +upper-case@^1.1.1: + version "1.1.3" + resolved "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz" + integrity sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA== + uri-js@^4.2.2: version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== dependencies: punycode "^2.1.0" -util-deprecate@~1.0.1: +user-agent-data-types@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/user-agent-data-types/-/user-agent-data-types-0.3.0.tgz#6a3ad1eabce306af62ab82ffbcd1ab747857f1b3" + integrity sha512-AI3vPwmafXd4r/mSbubOu6S1ngO4vdvJFAJdP0MUc9Y4SwXm/Pqpno0R/O4Zt26vZYzWorV+4BgVcntxhJlMPw== + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +v8-to-istanbul@^9.0.1: + version "9.0.1" + resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz" + integrity sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^1.6.0" + +w3c-hr-time@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + resolved "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz" + integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== + dependencies: + browser-process-hrtime "^1.0.0" -v8-compile-cache@^2.0.3: - version "2.3.0" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" - integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== +w3c-xmlserializer@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz" + integrity sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg== + dependencies: + xml-name-validator "^4.0.0" -validate-npm-package-license@^3.0.1: - version "3.0.4" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" - integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== + +whatwg-encoding@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz" + integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg== + dependencies: + iconv-lite "0.6.3" + +whatwg-mimetype@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz" + integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== + +whatwg-url@^10.0.0: + version "10.0.0" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-10.0.0.tgz" + integrity sha512-CLxxCmdUby142H5FZzn4D8ikO1cmypvXVQktsgosNy4a4BHrDHeciBBGZhb0bNoR5/MltoCatso+vFjjGx8t0w== + dependencies: + tr46 "^3.0.0" + webidl-conversions "^7.0.0" + +whatwg-url@^11.0.0: + version "11.0.0" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz" + integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ== dependencies: - spdx-correct "^3.0.0" - spdx-expression-parse "^3.0.0" + tr46 "^3.0.0" + webidl-conversions "^7.0.0" which-boxed-primitive@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz" integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== dependencies: is-bigint "^1.0.1" @@ -3342,73 +5586,103 @@ which-boxed-primitive@^1.0.2: which-module@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= - -which-pm-runs@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" - integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= + resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz" + integrity sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q== which@^2.0.1: version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" -word-wrap@^1.2.3: +word-wrap@^1.2.3, word-wrap@~1.2.3: version "1.2.3" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== wrap-ansi@^6.2.0: version "6.2.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz" integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== dependencies: ansi-styles "^4.0.0" string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrappy@1: version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -ws@^6.1.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" - integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== +write-file-atomic@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.1.tgz" + integrity sha512-nSKUxgAbyioruk6hU87QzVbY279oYT6uiwgDoujth2ju4mJ+TZau7SQBhtbTmUyuNYTuXnSyRn66FV0+eCgcrQ== dependencies: - async-limiter "~1.0.0" + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +ws@^8.2.3: + version "8.8.1" + resolved "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz" + integrity sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA== + +xml-name-validator@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz" + integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw== + +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== y18n@^4.0.0: version "4.0.3" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" + resolved "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz" integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + yallist@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@^1.10.0: - version "1.10.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" - integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== +yaml@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/yaml/-/yaml-2.1.1.tgz" + integrity sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw== yargs-parser@^18.1.1: version "18.1.3" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz" integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== dependencies: camelcase "^5.0.0" decamelize "^1.2.0" +yargs-parser@^21.0.0: + version "21.1.1" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + yargs@15.3.1: version "15.3.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.3.1.tgz#9505b472763963e54afe60148ad27a330818e98b" + resolved "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz" integrity sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA== dependencies: cliui "^6.0.0" @@ -3423,15 +5697,25 @@ yargs@15.3.1: y18n "^4.0.0" yargs-parser "^18.1.1" -yauzl@^2.10.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" - integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= +yargs@^17.3.1: + version "17.5.1" + resolved "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz" + integrity sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA== dependencies: - buffer-crc32 "~0.2.3" - fd-slicer "~1.1.0" + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.0.0" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== yocto-queue@^0.1.0: version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==