diff --git a/experimental/css-has-pseudo/.tape.mjs b/experimental/css-has-pseudo/.tape.mjs index ba1c9f37e..f3b59acde 100644 --- a/experimental/css-has-pseudo/.tape.mjs +++ b/experimental/css-has-pseudo/.tape.mjs @@ -1,68 +1,9 @@ import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; import plugin from '@csstools/css-has-pseudo-experimental'; -import postcssLogical from 'postcss-logical'; -import postcssNesting from 'postcss-nesting'; -import postcssDirPseudoClass from 'postcss-dir-pseudo-class'; postcssTape(plugin)({ 'basic': { - message: 'supports basic usage' + message: 'supports basic usage', + warnings: 1 }, - 'basic:preserve': { - message: 'supports { preserve: false } usage', - options: { - preserve: false - } - }, - 'basic:specificity-matching-name': { - message: 'supports { specificityMatchingName: "other-thing-that-does-not-exist" } usage', - options: { - specificityMatchingName: 'other-thing-that-does-not-exist' - } - }, - 'generated-selector-cases': { - message: 'correctly handles generated cases', - warnings: 1, - options: { - preserve: false - } - }, - 'browser': { - message: 'prepare CSS for chrome test', - options: { - preserve: false - } - }, - 'plugin-order-logical:before': { - message: 'works with other plugins that modify selectors', - plugins: [postcssLogical({preserve: false}), postcssDirPseudoClass({preserve: false}), plugin({preserve: false})], - }, - 'plugin-order-logical:after': { - message: 'works with other plugins that modify selectors', - plugins: [plugin({ preserve: false }), postcssLogical({ preserve: false }), postcssDirPseudoClass({ preserve: false })], - }, - 'plugin-order-logical:before:preserve': { - message: 'works with other plugins that modify selectors', - plugins: [postcssLogical({preserve: true}), postcssDirPseudoClass({preserve: true}), plugin({preserve: true})], - }, - 'plugin-order-logical:after:preserve': { - message: 'works with other plugins that modify selectors', - plugins: [plugin({ preserve: true }), postcssLogical({ preserve: true }), postcssDirPseudoClass({ preserve: true })], - }, - 'plugin-order-nesting:before': { - message: 'works with other plugins that modify selectors', - plugins: [postcssNesting({preserve: false}), plugin({preserve: false})], - }, - 'plugin-order-nesting:after': { - message: 'works with other plugins that modify selectors', - plugins: [postcssNesting({preserve: false}), plugin({preserve: false})], - }, - 'plugin-order-nesting:before:preserve': { - message: 'works with other plugins that modify selectors', - plugins: [plugin({preserve: true}), postcssNesting({preserve: true})], - }, - 'plugin-order-nesting:after:preserve': { - message: 'works with other plugins that modify selectors', - plugins: [plugin({preserve: true}), postcssNesting({preserve: true})], - } }); diff --git a/experimental/css-has-pseudo/CHANGELOG.md b/experimental/css-has-pseudo/CHANGELOG.md index 5ad138f67..88f5f19fd 100644 --- a/experimental/css-has-pseudo/CHANGELOG.md +++ b/experimental/css-has-pseudo/CHANGELOG.md @@ -1,5 +1,12 @@ # Changes to CSS Has Pseudo +### Unreleased + +- `@csstools/css-has-pseudo-experimental` is no longer supported. Please use `css-has-pseudo` instead. +All issues have been resolved in the main plugin and the experimental plugin is no longer maintained. + +⚠️ This experimental plugin no longer has any effect on the output of your CSS. + ### 0.5.2 (June 4, 2022) - Update `@csstools/selector-specificity` (major) diff --git a/experimental/css-has-pseudo/INSTALL-POSTCSS.md b/experimental/css-has-pseudo/INSTALL-POSTCSS.md deleted file mode 100644 index f1f0052ce..000000000 --- a/experimental/css-has-pseudo/INSTALL-POSTCSS.md +++ /dev/null @@ -1,165 +0,0 @@ -# Installing PostCSS - -[EXPERIMENTAL CSS Has Pseudo] runs in all Node environments, with special instructions for: - -⚠️ Experimental version of [CSS Has Pseudo](https://github.com/csstools/postcss-plugins/tree/main/plugins/css-has-pseudo) - -| [Node](#node) | [PostCSS CLI](#postcss-cli) | [Webpack](#webpack) | [Create React App](#create-react-app) | [Gulp](#gulp) | [Grunt](#grunt) | -| --- | --- | --- | --- | --- | --- | - -## Node - -Add [CSS Has Pseudo] to your project: - -```bash -npm install css-has-pseudo --save-dev -``` - -Use it as a [PostCSS] plugin: - -```js -const postcss = require('postcss'); -const cssHasPseudoExperimental = require('@csstools/css-has-pseudo-experimental'); - -postcss([ - cssHasPseudoExperimental(/* pluginOptions */) -]).process(YOUR_CSS /*, processOptions */); -``` - -## PostCSS CLI - -Add [PostCSS CLI] to your project: - -```bash -npm install postcss-cli --save-dev -``` - -Use [CSS Has Pseudo] in your `postcss.config.js` configuration file: - -```js -const cssHasPseudoExperimental = require('@csstools/css-has-pseudo-experimental'); - -module.exports = { - plugins: [ - cssHasPseudoExperimental(/* pluginOptions */) - ] -} -``` - -## Webpack - -Add [PostCSS Loader] to your project: - -```bash -npm install postcss-loader --save-dev -``` - -Use [CSS Has Pseudo] in your Webpack configuration: - -```js -const cssHasPseudoExperimental = require('@csstools/css-has-pseudo-experimental'); - -module.exports = { - module: { - rules: [ - { - test: /\.css$/, - use: [ - 'style-loader', - { loader: 'css-loader', options: { importLoaders: 1 } }, - { loader: 'postcss-loader', options: { - ident: 'postcss', - plugins: () => [ - cssHasPseudoExperimental(/* pluginOptions */) - ] - } } - ] - } - ] - } -} -``` - -## Create React App - -Add [React App Rewired] and [React App Rewire PostCSS] to your project: - -```bash -npm install react-app-rewired react-app-rewire-postcss --save-dev -``` - -Use [React App Rewire PostCSS] and [CSS Has Pseudo] in your -`config-overrides.js` -file: - -```js -const reactAppRewirePostcss = require('react-app-rewire-postcss'); -const cssHasPseudoExperimental = require('@csstools/css-has-pseudo-experimental'); - -module.exports = config => reactAppRewirePostcss(config, { - plugins: () => [ - cssHasPseudoExperimental(/* pluginOptions */) - ] -}); -``` - -## Gulp - -Add [Gulp PostCSS] to your project: - -```bash -npm install gulp-postcss --save-dev -``` - -Use [CSS Has Pseudo] in your Gulpfile: - -```js -const postcss = require('gulp-postcss'); -const cssHasPseudoExperimental = require('@csstools/css-has-pseudo-experimental'); - -gulp.task('css', () => gulp.src('./src/*.css').pipe( - postcss([ - cssHasPseudoExperimental(/* pluginOptions */) - ]) -).pipe( - gulp.dest('.') -)); -``` - -## Grunt - -Add [Grunt PostCSS] to your project: - -```bash -npm install grunt-postcss --save-dev -``` - -Use [CSS Has Pseudo] in your Gruntfile: - -```js -const cssHasPseudoExperimental = require('@csstools/css-has-pseudo-experimental'); - -grunt.loadNpmTasks('grunt-postcss'); - -grunt.initConfig({ - postcss: { - options: { - use: [ - cssHasPseudoExperimental(/* pluginOptions */) - ] - }, - dist: { - src: '*.css' - } - } -}); -``` - -[EXPERIMENTAL CSS Has Pseudo]: https://github.com/csstools/postcss-plugins/tree/main/experimental/css-has-pseudo -[Gulp PostCSS]: https://github.com/postcss/gulp-postcss -[Grunt PostCSS]: https://github.com/nDmitry/grunt-postcss -[PostCSS]: https://github.com/postcss/postcss -[PostCSS CLI]: https://github.com/postcss/postcss-cli -[PostCSS Loader]: https://github.com/postcss/postcss-loader -[React App Rewire PostCSS]: https://github.com/csstools/react-app-rewire-postcss -[React App Rewired]: https://github.com/timarney/react-app-rewired diff --git a/experimental/css-has-pseudo/README-BROWSER.md b/experimental/css-has-pseudo/README-BROWSER.md deleted file mode 100644 index ad2664c9d..000000000 --- a/experimental/css-has-pseudo/README-BROWSER.md +++ /dev/null @@ -1,152 +0,0 @@ -# EXPERIMENTAL : CSS Has Pseudo for Browsers [][EXPERIMENTAL CSS Has Pseudo] - -[![NPM Version][npm-img]][npm-url] -[Discord][discord] - -⚠️ Experimental version of [CSS Has Pseudo](https://github.com/csstools/postcss-plugins/tree/main/plugins/css-has-pseudo) - -[EXPERIMENTAL CSS Has Pseudo] lets you style elements relative to other elements in CSS, -following the [Selectors Level 4] specification. - -## Usage - -Add [EXPERIMENTAL CSS Has Pseudo] to your build tool: - -```bash -npm install @csstools/css-has-pseudo-experimental -``` - -Then include and initialize it on your document: - -```js -const cssHasPseudo = require('@csstools/css-has-pseudo-experimental/browser'); - -cssHasPseudo(document); -``` - -```html - - - -``` - -⚠️ Please use a versioned url, like this : `https://unpkg.com/@csstools/css-has-pseudo-experimental@0.2.0/dist/browser-global.js` -Without the version, you might unexpectedly get a new major version of the library with breaking changes. - -## CORS - -⚠️ Applies to you if you load CSS from a different domain than the page. -In this case the CSS is treated as untrusted and will not be made available to the Javascript polyfill. - -Example : - -| page | css | CORS applies | -| --- | --- | --- | -| https://example.com/ | https://example.com/style.css | no | -| https://example.com/ | https://other.com/style.css | yes | - -**You might see one of these error messages :** - -```html - - -``` - -Chrome : - -> DOMException: Failed to read the 'cssRules' property from 'CSSStyleSheet': Cannot access rules - -Safari : - -> SecurityError: Not allowed to access cross-origin stylesheet - -Firefox : - -> DOMException: CSSStyleSheet.cssRules getter: Not allowed to access cross-origin stylesheet - -To resolve CORS errors you need to take two steps : - -- add HTTP header `Access-Control-Allow-Origin: ` to your CSS file. -- add `crossorigin="anonymous"` to the `` tage for your CSS file. - -In a node server setting the HTTP header might look like this : - -```js -// http://localhost:8080 is the domain of your page! -res.setHeader('Access-Control-Allow-Origin', 'https://example.com'); -``` - -You can also configure a wildcard but please be aware that this might be a security risk. -It is better to only set the header for the domain you want to allow and only on the responses you want to allow. - -HTML might look like this : - -```html - -``` - -## Options - -### debug - -The `debug` option determines if errors are emitted to the console in browser. -By default the polyfill will not emit errors or warnings. - -```js -cssHasPseudo(document, { debug: true }); -``` - -### hover - -The `hover` option determines if `:hover` pseudo-class should be tracked. -This is disabled by default because it is an expensive operation. - -```js -cssHasPseudo(document, { hover: true }); -``` - -### observedAttributes - -The `observedAttributes` option determines which html attributes are observed. -If you do any client side modification of non-standard attributes and use these in combination with `:has()` you want to add these here. - -```js -cssHasPseudo(document, { observedAttributes: ['something-not-standard'] }); -``` - -### forcePolyfill - -The `forcePolyfill` option determines if the polyfill is used even when the browser has native support. -This is needed when you set `preserve: false` in the PostCSS plugin config. - -```js -cssHasPseudo(document, { forcePolyfill: true }); -``` - -## Dependencies - -Web API's: - -- [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) -- [requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) -- [querySelectorAll](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll) with support for post CSS 2.1 selectors and `:scope` selectors. - -ECMA Script: - -- `Array.prototype.filter` -- `Array.prototype.forEach` -- `Array.prototype.indexOf` -- `Array.prototype.join` -- `Array.prototype.map` -- `Array.prototype.splice` -- `RegExp.prototype.exec` -- `String.prototype.match` -- `String.prototype.replace` -- `String.prototype.split` - -[discord]: https://discord.gg/bUadyRwkJS -[npm-img]: https://img.shields.io/npm/v/@csstools/css-has-pseudo-experimental.svg -[npm-url]: https://www.npmjs.com/package/@csstools/css-has-pseudo-experimental - -[EXPERIMENTAL CSS Has Pseudo]: https://github.com/csstools/postcss-plugins/tree/main/experimental/css-has-pseudo -[Selectors Level 4]: https://drafts.csswg.org/selectors-4/#has diff --git a/experimental/css-has-pseudo/README-POSTCSS.md b/experimental/css-has-pseudo/README-POSTCSS.md deleted file mode 100644 index 4e2b87bcd..000000000 --- a/experimental/css-has-pseudo/README-POSTCSS.md +++ /dev/null @@ -1,172 +0,0 @@ -# EXPERIMENTAL : CSS Has Pseudo for PostCSS [][EXPERIMENTAL CSS Has Pseudo] - -[![NPM Version][npm-img]][npm-url] -[Discord][discord] - -⚠️ Experimental version of [CSS Has Pseudo](https://github.com/csstools/postcss-plugins/tree/main/plugins/css-has-pseudo) - -[EXPERIMENTAL CSS Has Pseudo] lets you style elements relative to other elements in CSS, -following the [Selectors Level 4] specification. - -```css -body:has(:focus) { - background-color: yellow; -} - -/* becomes */ - -[csstools-has-2q-33-2s-3d-1m-2w-2p-37-14-1m-2u-33-2r-39-37-15]:not(does-not-exist) { - background-color: yellow; -} - -body:has(:focus) { - background-color: yellow; -} -``` - -[EXPERIMENTAL CSS Has Pseudo] duplicates rules using the `:has` pseudo-class with a `[has]` -attribute selector. The preservation of the original `:has` rule can be -disabled using the `preserve` option. - -## Usage - -Add [EXPERIMENTAL CSS Has Pseudo] to your project: - -```bash -npm install @csstools/css-has-pseudo-experimental --save-dev -``` - -Use [EXPERIMENTAL CSS Has Pseudo] as a [PostCSS] plugin: - -```js -const postcss = require('postcss'); -const cssHasPseudoExperimental = require('@csstools/css-has-pseudo-experimental'); - -postcss([ - cssHasPseudoExperimental(/* pluginOptions */) -]).process(YOUR_CSS /*, processOptions */); -``` - -[EXPERIMENTAL CSS Has Pseudo] runs in all Node environments, with special -instructions for: - -| [Node](INSTALL-POSTCSS.md#node) | [PostCSS CLI](INSTALL-POSTCSS.md#postcss-cli) | [Webpack](INSTALL-POSTCSS.md#webpack) | [Create React App](INSTALL-POSTCSS.md#create-react-app) | [Gulp](INSTALL-POSTCSS.md#gulp) | [Grunt](INSTALL-POSTCSS.md#grunt) | -| --- | --- | --- | --- | --- | --- | - -## Options - -### preserve - -The `preserve` option defines whether the original selector should remain. By -default, the original selector is preserved. - -```js -cssHasPseudoExperimental({ preserve: false }); -``` - -```css -body:has(:focus) { - background-color: yellow; -} - -/* becomes */ - -[csstools-has-2q-33-2s-3d-1m-2w-2p-37-14-1m-2u-33-2r-39-37-15]:not(does-not-exist) { - background-color: yellow; -} -``` - -### specificityMatchingName - -The `specificityMatchingName` option allows you to change to selector that is used to adjust specificity. -The default value is `does-not-exist`. -If this is an actual class, id or tag name in your code, you will need to set a different option here. - -See how `:not` is used to modify [specificity](#specificity). - -```js -postcss([ - cssHasPseudoExperimental({ specificityMatchingName: 'something-random' }) -]).process(YOUR_CSS /*, processOptions */); -``` - -[specificity 1, 2, 0](https://polypane.app/css-specificity-calculator/#selector=.x%3Ahas(%3E%20%23a%3Ahover)) - -Before : - -```css -.x:has(> #a:hover) { - order: 11; -} -``` - -After : - -[specificity 1, 2, 0](https://polypane.app/css-specificity-calculator/#selector=%5Bcsstools-has-1a-3c-1m-2w-2p-37-14-1q-w-z-2p-1m-2w-33-3a-2t-36-15%5D%3Anot(%23does-not-exist)%3Anot(.does-not-exist)) - -```css -[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-z-2p-1m-2w-33-3a-2t-36-15]:not(#does-not-exist):not(.does-not-exist) { - order: 11; -} -``` - -## ⚠️ Known shortcomings - -### Specificity - -`:has` transforms will result in at least one tag selector with specificity `0, 1, 0`. -If your selector has only tags we won't be able to match the original specificity. - -Before : - -[specificity 0, 0, 2](https://polypane.app/css-specificity-calculator/#selector=figure%3Ahas(%3E%20img)) - -```css -figure:has(> img) -``` - -After : - -[specificity 0, 1, 2](https://polypane.app/css-specificity-calculator/#selector=%5Bcsstools-has-2u-2x-2v-39-36-2t-1m-2w-2p-37-14-1q-w-2x-31-2v-15%5D%3Anot(does-not-exist)%3Anot(does-not-exist)) - -```css -[csstools-has-2u-2x-2v-39-36-2t-1m-2w-2p-37-14-1q-w-2x-31-2v-15]:not(does-not-exist):not(does-not-exist) -``` - -### Plugin order - -As selectors are encoded this plugin (or `postcss-preset-env`) must be run after any other plugin that transforms selectors. - -For `postcss-preset-env` we take care to handle this for you. - -If other plugins are used you need to place these in your config before `postcss-preset-env` or `css-has-pseudo`. - -Please let us know if you have issues with plugins that transform selectors. -Then we can investigate and maybe fix these. - -## PostCSS Preset Env - -When you use `postcss-preset-env` you must disable the regular plugin. - -The experimental plugin must be added after any other plugin that modifies selectors. - -```js -plugins: [ - // other plugins - postcssPresetEnv({ - features: { - 'css-has-pseudo': false - } - }), - // other plugins - cssHasPseudoExperimental(), // last -] -``` - -[discord]: https://discord.gg/bUadyRwkJS -[npm-img]: https://img.shields.io/npm/v/@csstools/css-has-pseudo-experimental.svg -[npm-url]: https://www.npmjs.com/package/@csstools/css-has-pseudo-experimental - -[PostCSS]: https://github.com/postcss/postcss -[EXPERIMENTAL CSS Has Pseudo]: https://github.com/csstools/postcss-plugins/tree/main/experimental/css-has-pseudo -[Selectors Level 4]: https://drafts.csswg.org/selectors-4/#has-pseudo diff --git a/experimental/css-has-pseudo/README.md b/experimental/css-has-pseudo/README.md index e267b6cb1..d32523ec7 100644 --- a/experimental/css-has-pseudo/README.md +++ b/experimental/css-has-pseudo/README.md @@ -3,111 +3,10 @@ [![NPM Version][npm-img]][npm-url] [Discord][discord] -⚠️ Experimental version of [CSS Has Pseudo](https://github.com/csstools/postcss-plugins/tree/main/plugins/css-has-pseudo) +`@csstools/css-has-pseudo-experimental` is no longer supported. Please use `css-has-pseudo` instead. +All issues have been resolved in the main plugin and the experimental plugin is no longer maintained. -[EXPERIMENTAL CSS Has Pseudo] lets you style elements relative to other elements in CSS, -following the [Selectors Level 4] specification. - -[!['Can I use' table](https://caniuse.bitsofco.de/image/css-has.png)](https://caniuse.com/#feat=css-has) - -```css -a:has(> img) { - /* style links that contain an image */ -} - -h1:has(+ p) { - /* style level 1 headings that are followed by a paragraph */ -} - -section:not(:has(h1, h2, h3, h4, h5, h6)) { - /* style sections that don’t contain any heading elements */ -} - -body:has(:focus) { - /* style the body if it contains a focused element */ -} -``` - -Next, use your transformed CSS with this script: - -```html - - - -``` - -⚠️ Please use a versioned url, like this : `https://unpkg.com/@csstools/css-has-pseudo-experimental@0.1.0/dist/browser-global.js` -Without the version, you might unexpectedly get a new major version of the library with breaking changes. - -⚠️ If you were using an older version via a CDN, please update the entire url. -The old URL will no longer work. - -That’s it. The script works in most browser versions, including -Internet Explorer 11. With a [Mutation Observer polyfill], the script will work -down to Internet Explorer 9. - -See [README BROWSER](README-BROWSER.md) for more information. - -## How it works - -The [PostCSS plugin](README-POSTCSS.md) clones rules containing `:has`, -replacing them with an alternative `[:has]` selector. - -```css -body:has(:focus) { - background-color: yellow; -} - -section:not(:has(h1, h2, h3, h4, h5, h6)) { - background-color: gray; -} - -/* becomes */ - -[csstools-has-2q-33-2s-3d-1m-2w-2p-37-14-1m-2u-33-2r-39-37-15]:not(does-not-exist) { - background-color: yellow; -} - -body:has(:focus) { - background-color: yellow; -} - -[csstools-has-37-2t-2r-38-2x-33-32-1m-32-33-38-14-1m-2w-2p-37-14-2w-1d-18-w-2w-1e-18-w-2w-1f-18-w-2w-1g-18-w-2w-1h-18-w-2w-1i-15-15]:not(does-not-exist):not(does-not-exist) { - background-color: gray; -} - -section:not(:has(h1, h2, h3, h4, h5, h6)) { - background-color: gray; -} -``` - -Next, the [JavaScript library](README-BROWSER.md) adds a `[:has]` attribute to -elements otherwise matching `:has` natively. - -```html - - - -``` - -## PostCSS Preset Env - -When you use `postcss-preset-env` you must disable the regular plugin. - -The experimental plugin must be added after any other plugin that modifies selectors. - -```js -plugins: [ - // other plugins - postcssPresetEnv({ - features: { - 'css-has-pseudo': false - } - }), - // other plugins - cssHasPseudoExperimental(), // last -] -``` +⚠️ This experimental plugin no longer has any effect on the output of your CSS. [discord]: https://discord.gg/bUadyRwkJS [npm-img]: https://img.shields.io/npm/v/@csstools/css-has-pseudo-experimental.svg diff --git a/experimental/css-has-pseudo/package.json b/experimental/css-has-pseudo/package.json index cbd705593..c8fdb2890 100644 --- a/experimental/css-has-pseudo/package.json +++ b/experimental/css-has-pseudo/package.json @@ -35,17 +35,9 @@ "README.md", "dist" ], - "dependencies": { - "@csstools/selector-specificity": "^2.0.1", - "postcss-selector-parser": "^6.0.10" - }, "peerDependencies": { "postcss": "^8.2" }, - "devDependencies": { - "@mrhenry/core-web": "^0.7.2", - "puppeteer": "^13.6.0" - }, "scripts": { "build": "rollup -c ../../rollup/default.js", "clean": "node -e \"fs.rmSync('./dist', { recursive: true, force: true });\"", @@ -54,11 +46,9 @@ "lint:package-json": "node ../../.github/bin/format-package-json.mjs", "prepublishOnly": "npm run clean && npm run build && npm run test", "stryker": "stryker run --logLevel error", - "test": "node .tape.mjs && npm run test:unit && npm run test:exports", - "test:browser": "node ./test/_browser.mjs", + "test": "node .tape.mjs && npm run test:exports", "test:exports": "node ./test/_import.mjs && node ./test/_require.cjs", - "test:rewrite-expects": "REWRITE_EXPECTS=true node .tape.mjs", - "test:unit": "node ./src/encode/test.mjs" + "test:rewrite-expects": "REWRITE_EXPECTS=true node .tape.mjs" }, "homepage": "https://github.com/csstools/postcss-plugins/tree/main/experimental/css-has-pseudo#readme", "repository": { diff --git a/experimental/css-has-pseudo/src/browser.js b/experimental/css-has-pseudo/src/browser.js index 398156d8c..14a87b9a8 100644 --- a/experimental/css-has-pseudo/src/browser.js +++ b/experimental/css-has-pseudo/src/browser.js @@ -1,262 +1,3 @@ -/* global MutationObserver,requestAnimationFrame,cancelAnimationFrame,self,HTMLElement */ - -import '@mrhenry/core-web/modules/~element-qsa-has.js'; -import extractEncodedSelectors from './encode/extract.mjs'; -import encodeCSS from './encode/encode.mjs'; - -export default function cssHasPseudo(document, options) { - // OPTIONS - { - if (!options) { - options = {}; - } - - options = { - hover: (!!options.hover) || false, - debug: (!!options.debug) || false, - observedAttributes: options.observedAttributes || [], - forcePolyfill: (!!options.forcePolyfill) || false, - }; - - if (!options.forcePolyfill) { - try { - // Chrome does not support forgiving selector lists in :has() - document.querySelector(':has(*, :does-not-exist, > *)'); - - // Safari incorrectly returns the html element with this query - if (!document.querySelector(':has(:scope *)')) { - // Native support detected. - // Doing early return. - return; - } - - // fallthrough to polyfill - } catch (_) { - // fallthrough to polyfill - } - } - - if (!Array.isArray(options.observedAttributes)) { - options.observedAttributes = []; - } - - options.observedAttributes = options.observedAttributes.filter((x) => { - return (typeof x === 'string'); - }); - - // https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes - // `data-*` and `style` were omitted - options.observedAttributes = options.observedAttributes.concat(['accept', 'accept-charset', 'accesskey', 'action', 'align', 'allow', 'alt', 'async', 'autocapitalize', 'autocomplete', 'autofocus', 'autoplay', 'buffered', 'capture', 'challenge', 'charset', 'checked', 'cite', 'class', 'code', 'codebase', 'cols', 'colspan', 'content', 'contenteditable', 'contextmenu', 'controls', 'coords', 'crossorigin', 'csp', 'data', 'datetime', 'decoding', 'default', 'defer', 'dir', 'dirname', 'disabled', 'download', 'draggable', 'enctype', 'enterkeyhint', 'for', 'form', 'formaction', 'formenctype', 'formmethod', 'formnovalidate', 'formtarget', 'headers', 'hidden', 'high', 'href', 'hreflang', 'http-equiv', 'icon', 'id', 'importance', 'integrity', 'intrinsicsize', 'inputmode', 'ismap', 'itemprop', 'keytype', 'kind', 'label', 'lang', 'language', 'list', 'loop', 'low', 'manifest', 'max', 'maxlength', 'minlength', 'media', 'method', 'min', 'multiple', 'muted', 'name', 'novalidate', 'open', 'optimum', 'pattern', 'ping', 'placeholder', 'poster', 'preload', 'radiogroup', 'readonly', 'referrerpolicy', 'rel', 'required', 'reversed', 'rows', 'rowspan', 'sandbox', 'scope', 'scoped', 'selected', 'shape', 'size', 'sizes', 'slot', 'span', 'spellcheck', 'src', 'srcdoc', 'srclang', 'srcset', 'start', 'step', 'summary', 'tabindex', 'target', 'title', 'translate', 'type', 'usemap', 'value', 'width', 'wrap']); - } - - const observedItems = []; - - // document.createAttribute() doesn't support `:` in the name. innerHTML does - const attributeElement = document.createElement('x'); - - // walk all stylesheets to collect observed css rules - [].forEach.call(document.styleSheets, walkStyleSheet); - transformObservedItemsThrottled(); - - // observe DOM modifications that affect selectors - if ('MutationObserver' in self) { - const mutationObserver = new MutationObserver((mutationsList) => { - mutationsList.forEach(mutation => { - [].forEach.call(mutation.addedNodes || [], node => { - // walk stylesheets to collect observed css rules - if (node.nodeType === 1 && node.sheet) { - walkStyleSheet(node.sheet); - } - }); - - // transform observed css rules - cleanupObservedCssRules(); - transformObservedItemsThrottled(); - }); - }); - - mutationObserver.observe(document, { childList: true, subtree: true, attributes: true, attributeFilter: options.observedAttributes }); - } - - // observe DOM events that affect pseudo-selectors - document.addEventListener('focus', transformObservedItemsThrottled, true); - document.addEventListener('blur', transformObservedItemsThrottled, true); - document.addEventListener('input', transformObservedItemsThrottled); - document.addEventListener('change', transformObservedItemsThrottled, true); - - if (options.hover) { - if ('onpointerenter' in document) { - document.addEventListener('pointerenter', transformObservedItemsThrottled, true); - document.addEventListener('pointerleave', transformObservedItemsThrottled, true); - } else { - document.addEventListener('mouseover', transformObservedItemsThrottled, true); - document.addEventListener('mouseout', transformObservedItemsThrottled, true); - } - } - - // observe Javascript setters that effect pseudo-selectors - if ('defineProperty' in Object && 'getOwnPropertyDescriptor' in Object && 'hasOwnProperty' in Object) { - try { - // eslint-disable-next-line no-inner-declarations - function observeProperty(proto, property) { - // eslint-disable-next-line no-prototype-builtins - if (proto.hasOwnProperty(property)) { - const descriptor = Object.getOwnPropertyDescriptor(proto, property); - if (descriptor && descriptor.configurable && 'set' in descriptor) { - Object.defineProperty(proto, property, { - configurable: descriptor.configurable, - enumerable: descriptor.enumerable, - get: function () { - return descriptor.get.apply(this, arguments); - }, - set: function () { - descriptor.set.apply(this, arguments); - - try { - transformObservedItemsThrottled(); - } catch (_) { - // should never happen as there is an inner try/catch - // but just in case - } - }, - }); - } - } - } - - if ('HTMLElement' in self && HTMLElement.prototype) { - observeProperty(HTMLElement.prototype, 'disabled'); - } - - // Not all of these elements have all of these properties. - // But the code above checks if they exist first. - ['checked', 'selected', 'readOnly', 'required'].forEach((property) => { - [ - 'HTMLButtonElement', - 'HTMLFieldSetElement', - 'HTMLInputElement', - 'HTMLMeterElement', - 'HTMLOptGroupElement', - 'HTMLOptionElement', - 'HTMLOutputElement', - 'HTMLProgressElement', - 'HTMLSelectElement', - 'HTMLTextAreaElement', - ].forEach((elementName) => { - if (elementName in self && self[elementName].prototype) { - observeProperty(self[elementName].prototype, property); - } - }); - }); - } catch (e) { - if (options.debug) { - console.error(e); - } - } - } - - let transformObservedItemsThrottledBusy = false; - function transformObservedItemsThrottled() { - if (transformObservedItemsThrottledBusy) { - cancelAnimationFrame(transformObservedItemsThrottledBusy); - } - - transformObservedItemsThrottledBusy = requestAnimationFrame(() => { - transformObservedItems(); - }); - } - - // transform observed css rules - function transformObservedItems() { - observedItems.forEach((item) => { - const nodes = []; - - let matches = []; - try { - matches = document.querySelectorAll(item.selector); - } catch (e) { - if (options.debug) { - console.error(e); - } - return; - } - - [].forEach.call(matches, (element) => { - // memorize the node - nodes.push(element); - - // set an attribute with an irregular attribute name - // document.createAttribute() doesn't support special characters - attributeElement.innerHTML = ''; - - element.setAttributeNode(attributeElement.children[0].attributes[0].cloneNode()); - - // trigger a style refresh in IE and Edge - document.documentElement.style.zoom = 1; document.documentElement.style.zoom = null; - }); - - // remove the encoded attribute from all nodes that no longer match them - item.nodes.forEach(node => { - if (nodes.indexOf(node) === -1) { - node.removeAttribute(item.attributeName); - - // trigger a style refresh in IE and Edge - document.documentElement.style.zoom = 1; document.documentElement.style.zoom = null; - } - }); - - // update the - item.nodes = nodes; - }); - } - - // remove any observed cssrules that no longer apply - function cleanupObservedCssRules() { - [].push.apply( - observedItems, - observedItems.splice(0).filter((item) => { - return item.rule.parentStyleSheet && - item.rule.parentStyleSheet.ownerNode && - document.documentElement.contains(item.rule.parentStyleSheet.ownerNode); - }), - ); - } - - // walk a stylesheet to collect observed css rules - function walkStyleSheet(styleSheet) { - try { - // walk a css rule to collect observed css rules - [].forEach.call(styleSheet.cssRules || [], (rule) => { - if (rule.selectorText) { - try { - // decode the selector text in all browsers to: - const hasSelectors = extractEncodedSelectors(rule.selectorText.toString()); - if (hasSelectors.length === 0) { - return; - } - - for (let i = 0; i < hasSelectors.length; i++) { - const hasSelector = hasSelectors[i]; - observedItems.push({ - rule: rule, - selector: hasSelector, - attributeName: encodeCSS(hasSelector), - nodes: [], - }); - } - } catch (e) { - if (options.debug) { - console.error(e); - } - } - } else { - walkStyleSheet(rule); - } - }); - } catch (e) { - if (options.debug) { - console.error(e); - } - } - } +export default function cssHasPseudo() { + console.log('"@csstools/css-has-pseudo-experimental" is no longer supported. Please use "css-has-pseudo" instead.'); } diff --git a/experimental/css-has-pseudo/src/encode/decode.mjs b/experimental/css-has-pseudo/src/encode/decode.mjs deleted file mode 100644 index a24302530..000000000 --- a/experimental/css-has-pseudo/src/encode/decode.mjs +++ /dev/null @@ -1,17 +0,0 @@ - -/** Decodes an identifier back into a CSS selector */ -export default function decodeCSS(value) { - if (value.slice(0, 13) !== 'csstools-has-') { - return ''; - } - - value = value.slice(13); - let values = value.split('-'); - - let result = ''; - for (let i = 0; i < values.length; i++) { - result += String.fromCharCode(parseInt(values[i], 36)); - } - - return result; -} diff --git a/experimental/css-has-pseudo/src/encode/encode.mjs b/experimental/css-has-pseudo/src/encode/encode.mjs deleted file mode 100644 index 36d0a4ae8..000000000 --- a/experimental/css-has-pseudo/src/encode/encode.mjs +++ /dev/null @@ -1,20 +0,0 @@ - -/** Returns the string as an encoded CSS identifier. */ -export default function encodeCSS(value) { - if (value === '') { - return ''; - } - - let hex; - let result = ''; - for (let i = 0; i < value.length; i++) { - hex = value.charCodeAt(i).toString(36); - if (i === 0) { - result += hex; - } else { - result += '-' + hex; - } - } - - return 'csstools-has-' + result; -} diff --git a/experimental/css-has-pseudo/src/encode/extract.mjs b/experimental/css-has-pseudo/src/encode/extract.mjs deleted file mode 100644 index 366ab150c..000000000 --- a/experimental/css-has-pseudo/src/encode/extract.mjs +++ /dev/null @@ -1,91 +0,0 @@ -import decodeCSS from './decode.mjs'; - -/** Extract encoded selectors out of attribute selectors */ -export default function extractEncodedSelectors(value) { - let out = []; - - let depth = 0; - let candidate; - - let quoted = false; - let quotedMark; - - let containsUnescapedUnquotedHasAtDepth1 = false; - - // Stryker disable next-line EqualityOperator - for (let i = 0; i < value.length; i++) { - const char = value[i]; - - switch (char) { - case '[': - if (quoted) { - candidate += char; - continue; - } - - if (depth === 0) { - candidate = ''; - } else { - candidate += char; - } - - depth++; - continue; - case ']': - if (quoted) { - candidate += char; - continue; - } - - { - depth--; - if (depth === 0) { - const decoded = decodeCSS(candidate); - if (containsUnescapedUnquotedHasAtDepth1) { - out.push(decoded); - } - } else { - candidate += char; - } - } - - continue; - case '\\': - candidate += value[i]; - candidate += value[i+1]; - i++; - continue; - - case '"': - case '\'': - if (quoted && char === quotedMark) { - quoted = false; - continue; - } else if (quoted) { - candidate += char; - continue; - } - - quoted = true; - quotedMark = char; - continue; - - default: - if (candidate === '' && depth === 1 && (value.slice(i, i + 13) === 'csstools-has-')) { - containsUnescapedUnquotedHasAtDepth1 = true; - } - - candidate += char; - continue; - } - } - - const unique = []; - for (let i = 0; i < out.length; i++) { - if (unique.indexOf(out[i]) === -1) { - unique.push(out[i]); - } - } - - return unique; -} diff --git a/experimental/css-has-pseudo/src/encode/test.mjs b/experimental/css-has-pseudo/src/encode/test.mjs deleted file mode 100644 index 7ff593799..000000000 --- a/experimental/css-has-pseudo/src/encode/test.mjs +++ /dev/null @@ -1,155 +0,0 @@ -import { strict as assert } from 'assert'; -import encodeCSS from './encode.mjs'; -import decodeCSS from './decode.mjs'; -import extractEncodedSelectors from './extract.mjs'; - -function testEncoderDecoder(decoded, encoded) { - assert.strictEqual(encodeCSS(decoded), encoded); - assert.strictEqual(decodeCSS(encoded), decoded); - - assert.strictEqual(decodeCSS(encodeCSS(decoded)), decoded); - assert.strictEqual(encodeCSS(decodeCSS(encoded)), encoded); - - assert.strictEqual(decodeCSS(encodeCSS(decodeCSS(encodeCSS(decoded)))), decoded); - - assert.strictEqual(decodeCSS(decodeCSS(encodeCSS(encodeCSS(decoded)))), decoded); -} - -testEncoderDecoder( - '', - '', -); - -testEncoderDecoder( - ':has()', - 'csstools-has-1m-2w-2p-37-14-15', -); - -testEncoderDecoder( - ':has( )', - 'csstools-has-1m-2w-2p-37-14-w-15', -); - -testEncoderDecoder( - ':has(*)', - 'csstools-has-1m-2w-2p-37-14-16-15', -); - -testEncoderDecoder( - ':has(:focus)', - 'csstools-has-1m-2w-2p-37-14-1m-2u-33-2r-39-37-15', -); - -testEncoderDecoder( - ':has(~ p)', - 'csstools-has-1m-2w-2p-37-14-3i-w-34-15', -); - -testEncoderDecoder( - ':has(> p)', - 'csstools-has-1m-2w-2p-37-14-1q-w-34-15', -); - -testEncoderDecoder( - ':has(+ p)', - 'csstools-has-1m-2w-2p-37-14-17-w-34-15', -); - -testEncoderDecoder( - ':has(\\~ p)', - 'csstools-has-1m-2w-2p-37-14-2k-3i-w-34-15', -); - -testEncoderDecoder( - ':has(\\> p)', - 'csstools-has-1m-2w-2p-37-14-2k-1q-w-34-15', -); - -testEncoderDecoder( - ':has(\\+ p)', - 'csstools-has-1m-2w-2p-37-14-2k-17-w-34-15', -); - -testEncoderDecoder( - ':has(\\,\\(\\)\\[\\]\\:\\. p)\\', - 'csstools-has-1m-2w-2p-37-14-2k-18-2k-14-2k-15-2k-2j-2k-2l-2k-1m-2k-1a-w-34-15-2k', -); - -testEncoderDecoder( - ':has(.esc\\\\\\:aped)', - 'csstools-has-1m-2w-2p-37-14-1a-2t-37-2r-2k-2k-2k-1m-2p-34-2t-2s-15', -); - -testEncoderDecoder( - ':has(> [a=":has(.x)"]:hover)', - 'csstools-has-1m-2w-2p-37-14-1q-w-2j-2p-1p-y-1m-2w-2p-37-14-1a-3c-15-y-2l-1m-2w-33-3a-2t-36-15', -); - -testEncoderDecoder( - ':has(h1, h2, h3, h4, h5, h6)', - 'csstools-has-1m-2w-2p-37-14-2w-1d-18-w-2w-1e-18-w-2w-1f-18-w-2w-1g-18-w-2w-1h-18-w-2w-1i-15', -); - -testEncoderDecoder( - ':has(> [\\:has\\(\\%3E\\%20\\.a\\:hover\\)', - 'csstools-has-1m-2w-2p-37-14-1q-w-2j-2k-1m-2w-2p-37-2k-14-2k-11-1f-1x-2k-11-1e-1c-2k-1a-2p-2k-1m-2w-33-3a-2t-36-2k-15', -); - -testEncoderDecoder( - ':has(\\%perc)', - 'csstools-has-1m-2w-2p-37-14-2k-11-34-2t-36-2r-15', -); - -testEncoderDecoder( - 'foo', - 'csstools-has-2u-33-33', -); - -testEncoderDecoder( - ':has(> [foo="some"])', - 'csstools-has-1m-2w-2p-37-14-1q-w-2j-2u-33-33-1p-y-37-33-31-2t-y-2l-15', -); - -testEncoderDecoder( - '#d_main:has(#d_checkbox:checked)>#d_subject', - 'csstools-has-z-2s-2n-31-2p-2x-32-1m-2w-2p-37-14-z-2s-2n-2r-2w-2t-2r-2z-2q-33-3c-1m-2r-2w-2t-2r-2z-2t-2s-15-1q-z-2s-2n-37-39-2q-2y-2t-2r-38', -); - -testEncoderDecoder( - '#something-complex:has(> #d_checkbox:checked [foo="some"] * + [bar^="[baz]"] ~ a[class~="logo"] :has(~ .foo:is(button, input)))', - 'csstools-has-z-37-33-31-2t-38-2w-2x-32-2v-19-2r-33-31-34-30-2t-3c-1m-2w-2p-37-14-1q-w-z-2s-2n-2r-2w-2t-2r-2z-2q-33-3c-1m-2r-2w-2t-2r-2z-2t-2s-w-2j-2u-33-33-1p-y-37-33-31-2t-y-2l-w-16-w-17-w-2j-2q-2p-36-2m-1p-y-2j-2q-2p-3e-2l-y-2l-w-3i-w-2p-2j-2r-30-2p-37-37-3i-1p-y-30-33-2v-33-y-2l-w-1m-2w-2p-37-14-3i-w-1a-2u-33-33-1m-2x-37-14-2q-39-38-38-33-32-18-w-2x-32-34-39-38-15-15-15', -); - -function testExtract(encoded, rules) { - assert.deepStrictEqual(extractEncodedSelectors(encoded), rules); -} - -testExtract( - '', - [], -); - -testExtract( - '.a, [csstools-has-1m-2w-2p-37-14-1q-w-2j-2u-33-33-1p-y-37-33-31-2t-y-2l-15]', - [':has(> [foo="some"])'], -); - -testExtract( - '[csstools-has-1m-2w-2p-37-14-1q-w-2j-2u-33-33-1p-y-37-33-31-2t-y-2l-15], a', - [':has(> [foo="some"])'], -); - -testExtract( - '[csstools-has-1m-2w-2p-37-14-1q-w-2j-2u-33-33-1p-y-37-33-31-2t-y-2l-15][csstools-has-1m-2w-2p-37-14-1q-w-2j-2u-33-33-1p-y-37-33-31-2t-y-2l-15][csstools-has-1m-2w-2p-37-14-1q-w-2j-2u-33-33-1p-y-37-33-31-2t-y-2l-15]', - [':has(> [foo="some"])'], -); - -testExtract( - '[' + encodeCSS('.x:has(> .🧑🏾‍🎤)') +']', - ['.x:has(> .🧑🏾‍🎤)'], -); - -testExtract( - '[' + encodeCSS('.x:has(> .a:has( + .b))') + ']', - ['.x:has(> .a:has( + .b))'], -); diff --git a/experimental/css-has-pseudo/src/index.ts b/experimental/css-has-pseudo/src/index.ts index cabc31012..627c63d34 100644 --- a/experimental/css-has-pseudo/src/index.ts +++ b/experimental/css-has-pseudo/src/index.ts @@ -1,100 +1,10 @@ -import parser from 'postcss-selector-parser'; -import { selectorSpecificity } from '@csstools/selector-specificity'; -import encodeCSS from './encode/encode.mjs'; import type { PluginCreator } from 'postcss'; -const creator: PluginCreator<{ preserve?: boolean, specificityMatchingName?: string }> = (opts?: { preserve?: boolean, specificityMatchingName?: string }) => { - const options = { - preserve: true, - specificityMatchingName: 'does-not-exist', - ...(opts || {}), - }; - - const specificityMatchingNameId = ':not(#' + options.specificityMatchingName + ')'; - const specificityMatchingNameClass = ':not(.' + options.specificityMatchingName + ')'; - const specificityMatchingNameTag = ':not(' + options.specificityMatchingName + ')'; - +const creator: PluginCreator = () => { return { postcssPlugin: 'css-has-pseudo-experimental', - RuleExit: (rule, { result }) => { - if (!rule.selector.includes(':has(') || isWithinSupportCheck(rule)) { - return; - } - - const selectors = rule.selectors.map((selector) => { - if (!selector.includes(':has(')) { - return selector; - } - - let selectorAST; - try { - selectorAST = parser().astSync(selector); - } catch (_) { - rule.warn(result, `Failed to parse selector : ${selector}`); - return selector; - } - - if (typeof selectorAST === 'undefined') { - return selector; - } - - let containsHasPseudo = false; - selectorAST.walkPseudos((node) => { - containsHasPseudo = containsHasPseudo || (node.value === ':has' && node.nodes); - - // see : https://bugs.chromium.org/p/chromium/issues/detail?id=669058#c34 - // When we have ':has(:visited) {...}', the subject elements of the rule - // are the ancestors of the visited link element. - - // To prevent leaking visitedness to the link's ancestors, the ':visited' - // selector does not match if it is inside the ':has()' argument selector. - // So if a ':has()' argument selector requires a matching ':visited', the - // style rule are not applied. - if (node.value === ':visited') { - // We can't leave `:has` untouched as that might cause broken selector lists. - // Replacing with the specificity matching name as this should never match anything without `:not()`. - node.replaceWith(parser.className({ - value: options.specificityMatchingName, - })); - } - - if (node.value === ':any-link') { - // we can transform `:any-link` to `:link` as this is allowed - node.value = ':link'; - } - }); - - if (!containsHasPseudo) { - return selector; - } - - const encodedSelector = '[' + encodeCSS(selectorAST.toString()) + ']'; - const abcSpecificity = selectorSpecificity(selectorAST); - - let encodedSelectorWithSpecificity = encodedSelector; - for (let i = 0; i < abcSpecificity.a; i++) { - encodedSelectorWithSpecificity += specificityMatchingNameId; - } - const bSpecificity = Math.max(1, abcSpecificity.b) - 1; - for (let i = 0; i < bSpecificity; i++) { - encodedSelectorWithSpecificity += specificityMatchingNameClass; - } - for (let i = 0; i < abcSpecificity.c; i++) { - encodedSelectorWithSpecificity += specificityMatchingNameTag; - } - - return encodedSelectorWithSpecificity; - }); - - if (selectors.join(',') === rule.selectors.join(',')) { - return; - } - - if (options.preserve) { - rule.cloneBefore({ selectors: selectors }); - } else { - rule.selectors = selectors; - } + Once(root, { result }) { + root.warn(result, '"@csstools/css-has-pseudo-experimental" is no longer supported. Please use "css-has-pseudo" instead.'); }, }; }; @@ -102,19 +12,3 @@ const creator: PluginCreator<{ preserve?: boolean, specificityMatchingName?: str creator.postcss = true; export default creator; - -function isWithinSupportCheck(rule) { - let isSupportCheck = false; - let ruleParent = rule.parent; - - while (!isSupportCheck && ruleParent) { - if (ruleParent.type === 'atrule') { - - isSupportCheck = ruleParent.params.includes(':has(') && ruleParent.params.startsWith('selector('); - } - - ruleParent = ruleParent.parent; - } - - return isSupportCheck; -} diff --git a/experimental/css-has-pseudo/test/_browser.html b/experimental/css-has-pseudo/test/_browser.html deleted file mode 100644 index 4d8c007f9..000000000 --- a/experimental/css-has-pseudo/test/_browser.html +++ /dev/null @@ -1,1189 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/experimental/css-has-pseudo/test/_browser.mjs b/experimental/css-has-pseudo/test/_browser.mjs deleted file mode 100644 index a8220d97c..000000000 --- a/experimental/css-has-pseudo/test/_browser.mjs +++ /dev/null @@ -1,83 +0,0 @@ -import puppeteer from 'puppeteer'; -import http from 'http'; -import { promises as fsp } from 'fs'; - -(async () => { - const requestListener = async function (req, res) { - - const parsedUrl = new URL(req.url, 'http://localhost:8080'); - const pathname = parsedUrl.pathname; - - switch (pathname) { - case '': - case '/': - res.setHeader('Content-type', 'text/html'); - res.writeHead(200); - res.end(await fsp.readFile('test/_browser.html', 'utf8')); - break; - case '/test/basic.expect.css': - // Stylesheet WITHOUT CORS headers - res.setHeader('Content-type', 'text/css'); - res.writeHead(200); - res.end(await fsp.readFile('test/basic.expect.css', 'utf8')); - break; - case '/test/browser.expect.css': - // Stylesheet WITH CORS headers - res.setHeader('Content-type', 'text/css'); - res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8080'); - res.writeHead(200); - res.end(await fsp.readFile('test/browser.expect.css', 'utf8')); - break; - case '/dist/browser-global.js': - res.setHeader('Content-type', 'text/javascript'); - res.writeHead(200); - res.end(await fsp.readFile('dist/browser-global.js', 'utf8')); - break; - - default: - res.setHeader('Content-type', 'text/plain' ); - res.writeHead(404); - res.end('Not found'); - break; - } - }; - - // Use different servers for HTML/CSS/JS to trigger CORS - // - // HTML: - const serverA = http.createServer(requestListener); - serverA.listen(8080); - // CSS: - const serverB = http.createServer(requestListener); - serverB.listen(8081); - // JS: - const serverC = http.createServer(requestListener); - serverC.listen(8082); - - if (!process.env.DEBUG) { - const browser = await puppeteer.launch({ - headless: true, - }); - - const page = await browser.newPage(); - page.on('pageerror', (msg) => { - throw msg; - }); - await page.goto('http://localhost:8080'); - const result = await page.evaluate(async() => { - // eslint-disable-next-line no-undef - return await window.runTest(); - }); - if (!result) { - throw new Error('Test failed, expected "window.runTest()" to return true'); - } - - await browser.close(); - - await serverA.close(); - await serverB.close(); - await serverC.close(); - } else { - console.log('visit : http://localhost:8080'); - } -})(); diff --git a/experimental/css-has-pseudo/test/basic.expect.css b/experimental/css-has-pseudo/test/basic.expect.css index 052c39c82..e37868806 100644 --- a/experimental/css-has-pseudo/test/basic.expect.css +++ b/experimental/css-has-pseudo/test/basic.expect.css @@ -1,55 +1,27 @@ -[csstools-has-1m-2w-2p-37-14-1m-2u-33-2r-39-37-15] { - order: 1; -} - :has(:focus) { order: 1; } -[csstools-has-2p-1m-2w-2p-37-14-1q-w-2x-31-2v-15]:not(does-not-exist):not(does-not-exist) { - order: 2; -} - a:has(> img) { order: 2; } -[csstools-has-2w-1d-1m-2w-2p-37-14-17-w-34-15]:not(does-not-exist):not(does-not-exist) { - order: 3; -} - h1:has(+ p) { order: 3; } -[csstools-has-2w-1d-1m-2w-2p-37-14-3i-w-34-15]:not(does-not-exist):not(does-not-exist) { - order: 4; -} - h1:has(~ p) { order: 4; } -[csstools-has-37-2t-2r-38-2x-33-32-1m-32-33-38-14-1m-2w-2p-37-14-2w-1d-18-w-2w-1e-18-w-2w-1f-18-w-2w-1g-18-w-2w-1h-18-w-2w-1i-15-15]:not(does-not-exist):not(does-not-exist) { - order: 5; -} - section:not(:has(h1, h2, h3, h4, h5, h6)) { order: 5; } -[csstools-has-2q-33-2s-3d-1m-2w-2p-37-14-1m-2u-33-2r-39-37-15]:not(does-not-exist) { - order: 6; -} - body:has(:focus) { order: 6; } -[csstools-has-2q-33-2s-3d-1m-32-33-38-14-1m-2w-2p-37-14-1m-2u-33-2r-39-37-15-15]:not(does-not-exist) { - order: 7; -} - body:not(:has(:focus)) { order: 7; } @@ -59,202 +31,102 @@ body:not(:has(:focus)) { order: 8; } -[csstools-has-1m-2w-2p-37-14-1a-2t-37-2r-2k-2k-2k-1m-2p-34-2t-2s-15] { - order: 9; -} - :has(.esc\\\:aped) { order: 9; } -[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2p-1m-2w-33-3a-2t-36-15]:not(.does-not-exist):not(.does-not-exist) { - order: 10; -} - .x:has(> .a:hover) { order: 10; } -[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-z-2p-1m-2w-33-3a-2t-36-15]:not(#does-not-exist):not(.does-not-exist) { - order: 11; -} - .x:has(> #a:hover) { order: 11; } -[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-2j-2p-2l-1m-2w-33-3a-2t-36-15]:not(.does-not-exist):not(.does-not-exist) { - order: 12; -} - .x:has(> [a]:hover) { order: 12; } -[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-2j-2p-1p-y-2q-y-2l-1m-2w-33-3a-2t-36-15]:not(.does-not-exist):not(.does-not-exist) { - order: 13; -} - .x:has(> [a="b"]:hover) { order: 13; } -[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-2j-2p-1p-y-1m-2w-2p-37-14-1a-3c-15-y-2l-1m-2w-33-3a-2t-36-15]:not(.does-not-exist):not(.does-not-exist) { - order: 14; -} - .x:has(> [a=":has(.x)"]:hover) { order: 14; } -[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-2j-2k-1m-2w-2p-37-2k-14-2k-11-1f-1x-2k-11-1e-1c-2k-1a-2p-2k-1m-2w-33-3a-2t-36-2k-15-2l-1m-2w-33-3a-2t-36-15]:not(.does-not-exist):not(.does-not-exist) { - order: 15; -} - .x:has(> [\:has\(\%3E\%20\.a\:hover\)]:hover) { order: 15; } -[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1m-1m-2q-2t-2u-33-36-2t-1m-2w-33-3a-2t-36-15]:not(.does-not-exist):not(does-not-exist) { - order: 16; /* not allowed by spec but encoding should work */ -} - .x:has(> ::before:hover) { order: 16; /* not allowed by spec but encoding should work */ } -[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2p-1m-2w-2p-37-14-w-17-w-1a-2q-15-15]:not(.does-not-exist):not(.does-not-exist) { - order: 17; -} - .x:has(> .a:has( + .b)) { order: 17; } -[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-2n-2n-2u-33-33-15]:not(does-not-exist) { - order: 18; -} - .x:has(> __foo) { order: 18; } -[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1m-19-19-2u-33-33-15]:not(.does-not-exist) { - order: 19; -} - .x:has(> :--foo) { order: 19; } -[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-16-15] { - order: 20; -} - .x:has(> *) { order: 20; } -[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-3d-w-16-15]:not(.does-not-exist) { - order: 21; -} - .x:has(> .y *) { order: 21; } -[csstools-has-1a-2p-1m-32-33-38-14-1m-2w-2p-37-14-1q-w-1a-2q-15-15]:not(.does-not-exist) { - order: 22; -} - .a:not(:has(> .b)) { order: 22; } -[csstools-has-1a-3c-1m-2w-2p-37-14-3i-w-1a-3d-1m-2w-2p-37-14-1a-2v-w-1a-2w-15-w-1a-2x-15]:not(.does-not-exist):not(.does-not-exist):not(.does-not-exist):not(.does-not-exist) { - order: 23; -} - .x:has(~ .y:has(.g .h) .i) { order: 23; } -[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2p-15-w-3i-w-1a-3c-1m-2w-2p-37-14-1q-w-1a-2q-15]:not(.does-not-exist):not(.does-not-exist):not(.does-not-exist) { - order: 24; -} - .x:has(> .a) ~ .x:has(> .b) { order: 24; } -[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2p-15-w-1a-2q]:not(.does-not-exist):not(.does-not-exist) { - order: 24; -} - .x:has(> .a) .b { order: 24; } -[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-16pq-17td-16po-188u-6bx-16po-186c-15]:not(.does-not-exist) { - order: 25; -} - .x:has(> .🧑🏾‍🎤) { order: 25; } -[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2p-15]:not(.does-not-exist), [csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2q-15]:not(.does-not-exist) { - order: 26; -} - .x:has(> .a), .x:has(> .b) { order: 26; } -[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2p-15-w-3i-w-1a-3c-1m-2w-2p-37-14-1q-w-1a-2q-15]:not(.does-not-exist):not(.does-not-exist):not(.does-not-exist) { - order: 27; -} - .x:has(> .a) ~ .x:has(> .b) { order: 27; } -[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2p-15]:not(.does-not-exist), .b { - order: 28; -} - .x:has(> .a), .b { order: 28; } -.a, [csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2q-15]:not(.does-not-exist) { - order: 29; -} - .a, .x:has(> .b) { order: 29; } -[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2q-w-16-15]:not(.does-not-exist) { - order: 30; -} - .x:has(> .b *) { order: 30; } -[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1a-2s-33-2t-37-19-32-33-38-19-2t-3c-2x-37-38-15]:not(.does-not-exist) { - order: 31; -} - .x:has(> :visited) { order: 31; } -[csstools-has-1a-3c-1m-2w-2p-37-14-1q-w-1m-30-2x-32-2z-15]:not(.does-not-exist) { - order: 31; -} - .x:has(> :any-link) { order: 31; } @@ -266,9 +138,6 @@ body:not(:has(:focus)) { } @supports (display: grid) { - [csstools-has-1m-2w-2p-37-14-1m-2u-33-2r-39-37-15] { - order: 33; - } :has(:focus) { order: 33; }