├── .eslintignore ├── .gitignore ├── .prettierignore ├── tests ├── red.png ├── green.png ├── simple_link.css ├── empty_href.html ├── relative_urls.css ├── @supports.html ├── unsupported-selectors.html ├── @layer.html ├── simple_link.html ├── cdn_link.html ├── cdn_link_iife.html ├── simple_style.html ├── parsing_error1.html ├── simple.html ├── layout_query.html ├── simple_new_syntax.html ├── simple_new_long_syntax.html ├── parsing_error2.html ├── dynamic.html ├── or.html ├── not.html ├── dynamic_container.html ├── dynamic_style.html ├── nested.html ├── index.html ├── and.html ├── dynamic_link.html ├── realworldstyles1.html ├── test-utils.js ├── relative_urls.html ├── runner.html ├── pseudo_element.html ├── named_shorthand.html ├── named.html ├── diff.ts └── wpt.ts ├── .prettierrc.json ├── superstatic.json ├── .eslintrc.json ├── tsconfig.json ├── src ├── globals.d.ts ├── index.ts ├── utils │ ├── ast.ts │ ├── parse-media-query.ts │ └── parse-media-feature.ts ├── constants.ts ├── wpt.ts ├── memo.ts ├── parser.ts ├── evaluate.ts ├── transform.ts └── engine.ts ├── CHANGELOG.md ├── CONTRIBUTING.md ├── .github └── workflows │ ├── release-please.yml │ ├── build_test.yml │ └── update_baseline.yml ├── package.json ├── README.md └── LICENSE /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | wpt/ -------------------------------------------------------------------------------- /tests/red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/container-query-polyfill/HEAD/tests/red.png -------------------------------------------------------------------------------- /tests/green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/container-query-polyfill/HEAD/tests/green.png -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": false, 3 | "singleQuote": true, 4 | "trailingComma": "es5", 5 | "arrowParens": "avoid" 6 | } 7 | -------------------------------------------------------------------------------- /superstatic.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": [ 3 | { 4 | "source": "**/*", 5 | "headers": [ 6 | { 7 | "key": "Access-Control-Allow-Origin", 8 | "value": "*" 9 | } 10 | ] 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "browser": true, 5 | "es6": true, 6 | "node": true 7 | }, 8 | "parser": "@typescript-eslint/parser", 9 | "plugins": ["@typescript-eslint"], 10 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"] 11 | } 12 | -------------------------------------------------------------------------------- /tests/simple_link.css: -------------------------------------------------------------------------------- 1 | div { 2 | outline: 1px solid red; 3 | resize: both; 4 | overflow: auto; 5 | container-type: size; 6 | height: 100px; 7 | } 8 | h1 { 9 | font-size: 10px; 10 | } 11 | @container (min-width: 200px) { 12 | h1 { 13 | font-size: 30px; 14 | } 15 | } 16 | @container (max-width: 200px) { 17 | h1 { 18 | font-size: 20px; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": ".", 4 | "outDir": "dist", 5 | "lib": ["esnext", "DOM", "DOM.Iterable"], 6 | "strict": true, 7 | "target": "esnext", 8 | "module": "esnext", 9 | "moduleResolution": "node", 10 | "types": [], 11 | "esModuleInterop": true 12 | }, 13 | "include": ["src/**/*.ts", "tests/**/*.ts"] 14 | } 15 | -------------------------------------------------------------------------------- /tests/empty_href.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 |
11 | 17 | -------------------------------------------------------------------------------- /tests/relative_urls.css: -------------------------------------------------------------------------------- 1 | div { 2 | outline: 1px solid red; 3 | resize: both; 4 | overflow: auto; 5 | container-type: size; 6 | height: 100px; 7 | display: grid; 8 | } 9 | #noquotes span { 10 | grid-area: 1/1; 11 | background: url(/tests/red.png); 12 | } 13 | #doublequotes span { 14 | grid-area: 1/1; 15 | background: url('/tests/red.png'); 16 | } 17 | #singlequotes span { 18 | grid-area: 1/1; 19 | background: url('/tests/red.png'); 20 | } 21 | @container (min-width: 200px) { 22 | #noquotes span { 23 | background: url(/tests/green.png); 24 | } 25 | #doublequotes span { 26 | background: url('/tests/green.png'); 27 | } 28 | #singlequotes span { 29 | background: url('/tests/green.png'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/globals.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | declare const IS_WPT_BUILD: boolean; 15 | declare const PACKAGE_VERSION: string; 16 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [1.0.2](https://github.com/GoogleChromeLabs/container-query-polyfill/compare/v1.0.1...v1.0.2) (2022-11-08) 4 | 5 | 6 | ### Bug Fixes 7 | 8 | * improve :where supports check ([#65](https://github.com/GoogleChromeLabs/container-query-polyfill/issues/65)) ([1dab190](https://github.com/GoogleChromeLabs/container-query-polyfill/commit/1dab190dbd640f2ad1a1535c69a7143182729cee)), closes [#64](https://github.com/GoogleChromeLabs/container-query-polyfill/issues/64) 9 | 10 | ## [1.0.1](https://github.com/GoogleChromeLabs/container-query-polyfill/compare/v1.0.0...v1.0.1) (2022-10-19) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * Improve performance on lower end devices ([48dfef8](https://github.com/GoogleChromeLabs/container-query-polyfill/commit/48dfef88f8eb037cd38ad8d43410950694504497)) 16 | -------------------------------------------------------------------------------- /tests/@supports.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 19 |
20 |

Ohai

21 |
22 | 35 | -------------------------------------------------------------------------------- /tests/unsupported-selectors.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 19 |
20 |

Ohai

21 |
22 | 35 | -------------------------------------------------------------------------------- /tests/@layer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 |
22 |

Ohai

23 |
24 | 37 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution, 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | name: release-please 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | release-please: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: google-github-actions/release-please-action@v3 13 | id: release 14 | with: 15 | release-type: node 16 | package-name: container-query-polyfill 17 | 18 | - uses: actions/checkout@v3 19 | - uses: actions/setup-node@v3 20 | with: 21 | node-version: '16.x' 22 | if: ${{ steps.release.outputs.release_created }} 23 | - run: npm install 24 | if: ${{ steps.release.outputs.release_created }} 25 | - run: npm run build 26 | if: ${{ steps.release.outputs.release_created }} 27 | 28 | - uses: actions/setup-node@v3 29 | with: 30 | node-version: '16.x' 31 | registry-url: 'https://wombat-dressing-room.appspot.com/' 32 | if: ${{ steps.release.outputs.release_created }} 33 | - run: npm publish 34 | env: 35 | NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 36 | if: ${{ steps.release.outputs.release_created }} 37 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | import {initializePolyfill} from './engine'; 15 | import {initializeForWPT, handleUpdate} from './wpt'; 16 | 17 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 18 | (window as any).CQPolyfill = { 19 | version: PACKAGE_VERSION, 20 | }; 21 | 22 | if (!('container' in document.documentElement.style)) { 23 | let updateCallback = () => { 24 | /* noop */ 25 | }; 26 | if (IS_WPT_BUILD) { 27 | initializeForWPT(); 28 | updateCallback = handleUpdate; 29 | } 30 | 31 | initializePolyfill(updateCallback); 32 | } 33 | -------------------------------------------------------------------------------- /tests/simple_link.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

Ohai

5 |
6 | 41 | -------------------------------------------------------------------------------- /tests/cdn_link.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

Ohai

5 |
6 | 41 | -------------------------------------------------------------------------------- /tests/cdn_link_iife.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

Ohai

5 |
6 | 7 | 40 | -------------------------------------------------------------------------------- /tests/simple_style.html: -------------------------------------------------------------------------------- 1 | 2 | 24 |
25 |

Ohai

26 |
27 | 51 | -------------------------------------------------------------------------------- /tests/parsing_error1.html: -------------------------------------------------------------------------------- 1 | 2 | 27 |
28 |

Ohai

29 |
30 | 54 | -------------------------------------------------------------------------------- /tests/simple.html: -------------------------------------------------------------------------------- 1 | 2 | 27 |
28 |

Ohai

29 |
30 | 54 | -------------------------------------------------------------------------------- /tests/layout_query.html: -------------------------------------------------------------------------------- 1 | 2 | 27 |
28 |

Ohai

29 |
30 | 54 | -------------------------------------------------------------------------------- /tests/simple_new_syntax.html: -------------------------------------------------------------------------------- 1 | 2 | 27 |
28 |

Ohai

29 |
30 | 54 | -------------------------------------------------------------------------------- /tests/simple_new_long_syntax.html: -------------------------------------------------------------------------------- 1 | 2 | 27 |
28 |

Ohai

29 |
30 | 54 | -------------------------------------------------------------------------------- /tests/parsing_error2.html: -------------------------------------------------------------------------------- 1 | 2 | 29 |
30 |

Ohai

31 |
32 | 56 | -------------------------------------------------------------------------------- /src/utils/ast.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | import {DeclarationNode, Node, Type} from './css'; 15 | 16 | export function ws(): Node { 17 | return {type: Type.WhitespaceToken}; 18 | } 19 | 20 | export function delim(value: string): Node { 21 | return {type: Type.DelimToken, value}; 22 | } 23 | 24 | export function decl(name: string, value: Node[]): DeclarationNode { 25 | return { 26 | type: Type.DeclarationNode, 27 | name, 28 | value, 29 | important: false, 30 | }; 31 | } 32 | 33 | export function ident(value: string): Node { 34 | return {type: Type.IdentToken, value}; 35 | } 36 | 37 | export function func(name: string, value: Node[]): Node { 38 | return {type: Type.FunctionNode, name, value}; 39 | } 40 | 41 | export function customVar(name: string) { 42 | return func('var', [ident(name)]); 43 | } 44 | -------------------------------------------------------------------------------- /tests/dynamic.html: -------------------------------------------------------------------------------- 1 | 2 | 27 | 61 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | export const PER_RUN_UID = Array.from({length: 4}, () => 15 | Math.floor(Math.random() * 256).toString(16) 16 | ).join(''); 17 | 18 | export const INTERNAL_KEYWORD_PREFIX = 'cq-'; 19 | export const CUSTOM_PROPERTY_SHORTHAND = getCustomVariableName('container'); 20 | export const CUSTOM_PROPERTY_TYPE = getCustomVariableName('container-type'); 21 | export const CUSTOM_PROPERTY_NAME = getCustomVariableName('container-name'); 22 | 23 | export const DATA_ATTRIBUTE_SELF = `data-cqs-${PER_RUN_UID}`; 24 | export const DATA_ATTRIBUTE_CHILD = `data-cqc-${PER_RUN_UID}`; 25 | 26 | export const CUSTOM_UNIT_VARIABLE_CQW = getCustomVariableName('cqw'); 27 | export const CUSTOM_UNIT_VARIABLE_CQH = getCustomVariableName('cqh'); 28 | export const CUSTOM_UNIT_VARIABLE_CQI = getCustomVariableName('cqi'); 29 | export const CUSTOM_UNIT_VARIABLE_CQB = getCustomVariableName('cqb'); 30 | 31 | function getCustomVariableName(name: string): string { 32 | return `--cq-${name}-${PER_RUN_UID}`; 33 | } 34 | -------------------------------------------------------------------------------- /tests/or.html: -------------------------------------------------------------------------------- 1 | 2 | 21 |
22 |
23 |

H1

24 |
25 |
26 | 51 | -------------------------------------------------------------------------------- /tests/not.html: -------------------------------------------------------------------------------- 1 | 2 | 21 |
22 |
23 |

H1

24 |
25 |
26 | 51 | -------------------------------------------------------------------------------- /tests/dynamic_container.html: -------------------------------------------------------------------------------- 1 | 2 | 27 | 61 | -------------------------------------------------------------------------------- /tests/dynamic_style.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "container-query-polyfill", 3 | "version": "1.0.2", 4 | "description": "", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/GoogleChromeLabs/container-query-polyfill.git" 8 | }, 9 | "type": "module", 10 | "module": "dist/container-query-polyfill.modern.js", 11 | "unpkg": "dist/container-query-polyfill.modern.js", 12 | "publishConfig": { 13 | "source": "src/index.ts" 14 | }, 15 | "exports": { 16 | "default": "./dist/container-query-polyfill.modern.js" 17 | }, 18 | "files": [ 19 | "README.md", 20 | "LICENSE", 21 | "dist/*.js", 22 | "package.json" 23 | ], 24 | "scripts": { 25 | "build:clean": "rimraf ./dist/", 26 | "build:wpt": "microbundle -f modern --no-compress --define PACKAGE_VERSION=${npm_package_version},IS_WPT_BUILD=1", 27 | "build": "microbundle -f modern --no-sourcemap --define PACKAGE_VERSION=${npm_package_version},IS_WPT_BUILD=0", 28 | "lint": "eslint '**/*.ts'", 29 | "prettier:fix": "prettier --write .", 30 | "serve": "superstatic -p 9606 .", 31 | "test": "node --loader ts-node/esm ./tests/wpt.ts" 32 | }, 33 | "author": "Google Chrome Developers ", 34 | "contributors": [ 35 | "Surma " 36 | ], 37 | "license": "Apache-2.0", 38 | "devDependencies": { 39 | "@types/async": "^3.2.14", 40 | "@types/selenium-webdriver": "^4.1.1", 41 | "@typescript-eslint/eslint-plugin": "^5.18.0", 42 | "@typescript-eslint/parser": "^5.18.0", 43 | "async": "^3.2.4", 44 | "browserstack-local": "^1.5.1", 45 | "eslint": "^8.12.0", 46 | "microbundle": "^0.15.0", 47 | "prettier": "^2.6.2", 48 | "rimraf": "^3.0.2", 49 | "selenium-webdriver": "^4.3.0", 50 | "superstatic": "^8.0.0", 51 | "ts-node": "^10.9.1" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/nested.html: -------------------------------------------------------------------------------- 1 | 2 | 29 |
30 |
31 |

H1

32 |
33 |
34 | 61 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 9 |
10 | 11 | 72 | -------------------------------------------------------------------------------- /tests/and.html: -------------------------------------------------------------------------------- 1 | 2 | 33 |
34 |
35 |

H1

36 |
37 |
38 | 64 | -------------------------------------------------------------------------------- /tests/dynamic_link.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64 | -------------------------------------------------------------------------------- /tests/realworldstyles1.html: -------------------------------------------------------------------------------- 1 | 2 | 44 |
45 |

Ohai

46 |
47 | 77 | -------------------------------------------------------------------------------- /tests/test-utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | export function doubleRaf() { 15 | return new Promise(resolve => { 16 | requestAnimationFrame(() => { 17 | requestAnimationFrame(() => { 18 | resolve(); 19 | }); 20 | }); 21 | }); 22 | } 23 | 24 | export function fail(msg) { 25 | window.parent?.postMessage(msg, '*'); 26 | } 27 | 28 | export function success() { 29 | window.parent?.postMessage(true, '*'); 30 | } 31 | 32 | export function nextEvent(el, name) { 33 | return new Promise(resolve => 34 | el.addEventListener(name, resolve, {once: true}) 35 | ); 36 | } 37 | 38 | export function assert(bool, msg) { 39 | if (!bool) { 40 | throw Error(msg); 41 | } 42 | } 43 | 44 | export function assertEquals(a, b, msg) { 45 | if (a !== b) { 46 | throw Error(`Expected ${a} == ${b}. ${msg}`); 47 | } 48 | } 49 | 50 | export function timeout(ms) { 51 | return new Promise(resolve => setTimeout(resolve, ms)); 52 | } 53 | 54 | export async function testSuite(name, cb) { 55 | try { 56 | await Promise.race([ 57 | cb(), 58 | // timeout(2000).then(() => { 59 | // throw Error(`Timeout`); 60 | // }), 61 | ]); 62 | } catch (e) { 63 | console.error(e); 64 | fail(`${name}: ${e}`); 65 | return; 66 | } 67 | success(); 68 | console.log('Test passed successfully'); 69 | } 70 | -------------------------------------------------------------------------------- /tests/relative_urls.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
6 |
7 | 8 |
9 |
10 | 11 |
12 | 61 | -------------------------------------------------------------------------------- /.github/workflows/build_test.yml: -------------------------------------------------------------------------------- 1 | name: Build & Test 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | types: [opened, synchronize] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | env: 13 | BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} 14 | BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} 15 | WPT_MANIFEST: ${{ github.workspace }}/wpt/MANIFEST.json 16 | steps: 17 | - uses: actions/checkout@v3 18 | - uses: actions/setup-node@v3 19 | with: 20 | node-version: '16' 21 | - uses: actions/setup-python@v3 22 | with: 23 | python-version: '3.x' 24 | - uses: actions/checkout@v3 25 | with: 26 | repository: devknoll/wpt 27 | path: wpt 28 | ref: x-polyfill-all-tests 29 | 30 | - name: Build 31 | run: | 32 | npm install 33 | npm run build:wpt 34 | 35 | - name: Setup WPT 36 | run: | 37 | cd wpt 38 | pip install virtualenv 39 | ./wpt make-hosts-file | sudo tee -a /etc/hosts 40 | - name: Run Tests 41 | run: | 42 | npm run serve & 43 | ./wpt/wpt manifest 44 | ./wpt/wpt serve --inject-script=${{ github.workspace }}/dist/container-query-polyfill.modern.js & 45 | npm test 46 | ret=$(node --loader ts-node/esm ./tests/diff.ts) 47 | npm run prettier:fix 48 | cat ./tests/pr.txt >> $GITHUB_STEP_SUMMARY 49 | if [ $ret == "changed" ]; then 50 | exit 1 51 | fi 52 | - uses: actions/upload-artifact@v3 53 | with: 54 | name: baseline.json 55 | path: ./tests/baseline.json 56 | if: failure() 57 | - uses: actions/upload-artifact@v3 58 | with: 59 | name: results.json 60 | path: ./tests/results.json 61 | if: failure() 62 | - uses: actions/upload-artifact@v3 63 | with: 64 | name: raw.json 65 | path: ./tests/*.raw.json 66 | if: failure() 67 | -------------------------------------------------------------------------------- /.github/workflows/update_baseline.yml: -------------------------------------------------------------------------------- 1 | name: Update WPT Baseline 2 | 3 | on: 4 | workflow_dispatch: 5 | branches: [main] 6 | schedule: 7 | - cron: '0 0 * * 1' 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | env: 13 | BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} 14 | BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} 15 | WPT_MANIFEST: ${{ github.workspace }}/wpt/MANIFEST.json 16 | SCHEDULED_BASELINE_DIFF: true 17 | steps: 18 | - uses: actions/checkout@v3 19 | - uses: actions/setup-node@v3 20 | with: 21 | node-version: '16' 22 | - uses: actions/setup-python@v3 23 | with: 24 | python-version: '3.x' 25 | - uses: actions/checkout@v3 26 | with: 27 | repository: devknoll/wpt 28 | path: wpt 29 | ref: x-polyfill-all-tests 30 | 31 | - name: Build 32 | run: | 33 | npm install 34 | npm run build:wpt 35 | 36 | - name: Setup WPT 37 | run: | 38 | cd wpt 39 | pip install virtualenv 40 | ./wpt make-hosts-file | sudo tee -a /etc/hosts 41 | - name: Run Tests 42 | run: | 43 | npm run serve & 44 | ./wpt/wpt manifest 45 | ./wpt/wpt serve --inject-script=${{ github.workspace }}/dist/container-query-polyfill.modern.js & 46 | npm test 47 | 48 | - name: Open Pull Request 49 | env: 50 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 51 | run: | 52 | ret=(node --loader ts-node/esm ./tests/diff.ts) 53 | if [ $ret == "changed" ]; then 54 | npm run prettier:fix 55 | git config user.name github-actions[bot] 56 | git config user.email 41898282+github-actions[bot]@users.noreply.github.com 57 | git checkout -b update-wpt-baseline-$(date +"%Y-%m-%d") 58 | git add ./tests/baseline.json 59 | git commit -m "Update Web Platform Test baseline" 60 | git push -u origin HEAD 61 | gh pr create --title "[Automated] Update Web Platform Tests" --body-file ./tests/pr.txt --label "update-baseline" 62 | fi 63 | -------------------------------------------------------------------------------- /src/wpt.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | let resolveFirstRender: (() => void) | null; 15 | const firstRenderPromise = new Promise(resolve => { 16 | resolveFirstRender = resolve; 17 | }); 18 | 19 | export function handleUpdate() { 20 | if (resolveFirstRender) { 21 | resolveFirstRender(); 22 | resolveFirstRender = null; 23 | } 24 | } 25 | 26 | export function initializeForWPT() { 27 | // HACK: Make WPT's testharnessreport.js think that we 28 | // are running in the default test environment, so that 29 | // we can configure it. 30 | if (!window.opener) { 31 | window.opener = window; 32 | } 33 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 34 | (window as any).testharness_properties = { 35 | output: true, 36 | timeout_multiplier: 100, 37 | }; 38 | 39 | window.addEventListener('error', e => { 40 | e.stopImmediatePropagation(); 41 | }); 42 | 43 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 44 | (window as any).waitForPolyfill = async function () { 45 | await firstRenderPromise; 46 | return await new Promise(resolve => { 47 | requestAnimationFrame(() => { 48 | requestAnimationFrame(() => { 49 | requestAnimationFrame(() => { 50 | requestAnimationFrame(() => { 51 | requestAnimationFrame(() => { 52 | resolve(); 53 | }); 54 | }); 55 | }); 56 | }); 57 | }); 58 | }); 59 | }; 60 | 61 | const oldSupports = CSS.supports; 62 | CSS.supports = (ident: string) => { 63 | if (ident === 'container-type:size') { 64 | return true; 65 | } 66 | return oldSupports(ident); 67 | }; 68 | } 69 | -------------------------------------------------------------------------------- /tests/runner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 13 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /tests/pseudo_element.html: -------------------------------------------------------------------------------- 1 | 2 | 30 |
31 |

Ohai

32 |
33 | 108 | -------------------------------------------------------------------------------- /src/memo.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | export class Reference { 15 | value: T; 16 | 17 | constructor(value: T) { 18 | this.value = value; 19 | } 20 | } 21 | 22 | export type MemoizableValue = 23 | | number 24 | | string 25 | | boolean 26 | | symbol 27 | | null 28 | | Reference 29 | | MemoizableValue[] 30 | | {[key: number | string | symbol]: MemoizableValue}; 31 | 32 | export function memoizeAndReuse< 33 | TArgs extends MemoizableValue[], 34 | TResult extends MemoizableValue 35 | >(fn: (...args: TArgs) => TResult) { 36 | type Result = [TArgs, TResult]; 37 | let previousResult: Result | null = null; 38 | 39 | return (...args: TArgs) => { 40 | if (previousResult == null || !areEqual(previousResult[0], args)) { 41 | const currentResult = fn(...args); 42 | if ( 43 | previousResult == null || 44 | !areEqual(previousResult[1], currentResult) 45 | ) { 46 | previousResult = [args, currentResult]; 47 | } 48 | } 49 | return previousResult[1]; 50 | }; 51 | } 52 | 53 | function areEqual(lhs: MemoizableValue, rhs: MemoizableValue) { 54 | if (lhs === rhs) { 55 | return true; 56 | } 57 | 58 | if (typeof lhs === typeof rhs) { 59 | if (lhs !== null && rhs !== null && typeof lhs === 'object') { 60 | if (Array.isArray(lhs)) { 61 | if (!Array.isArray(rhs) || rhs.length !== lhs.length) { 62 | return false; 63 | } 64 | 65 | for (let i = 0, length = lhs.length; i < length; i++) { 66 | if (!areEqual(lhs[i], rhs[i])) { 67 | return false; 68 | } 69 | } 70 | 71 | return true; 72 | } else if (lhs instanceof Reference) { 73 | if (!(rhs instanceof Reference) || lhs.value !== rhs.value) { 74 | return false; 75 | } 76 | return true; 77 | } else { 78 | const leftKeys = Object.keys(lhs); 79 | if (leftKeys.length !== Object.keys(rhs).length) { 80 | return false; 81 | } 82 | 83 | for (let i = 0, length = leftKeys.length; i < length; i++) { 84 | const key = leftKeys[i]; 85 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 86 | if (!areEqual(lhs[key], (rhs as any)[key])) { 87 | return false; 88 | } 89 | } 90 | 91 | return true; 92 | } 93 | } 94 | } 95 | 96 | return false; 97 | } 98 | -------------------------------------------------------------------------------- /tests/named_shorthand.html: -------------------------------------------------------------------------------- 1 | 2 | 48 |
49 |
50 |

H1

51 |
52 |
53 | 121 | -------------------------------------------------------------------------------- /tests/named.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 51 |
52 |
53 |

H1

54 |
55 |
56 | 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Container Query Polyfill 2 | 3 | *Please note that this polyfill is now in maintenance mode, as of Nov, 2022. We are not planning to add more features or enhancements.* 4 | ____________________________ 5 | 6 | A small (9 kB compressed) polyfill for CSS Container Queries using [`ResizeObserver`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver) and [`MutationObserver`](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) supporting the full [`@container`](https://drafts.csswg.org/css-contain-3/) query syntax: 7 | 8 | - Discrete queries (`width: 300` and `min-width: 300px`) 9 | - Range queries (`200px < width < 400px` and `width < 400px`) 10 | - Container relative length units (`cqw`, `cqh`, `cqi`, `cqb`, `cqmin`, and `cqmax`) in properties and keyframes 11 | 12 | ## Browser Support 13 | 14 | - Firefox 69+ 15 | - Chrome 79+ 16 | - Edge 79+ 17 | - Safari 13.4+ 18 | 19 | ## Getting Started 20 | 21 | ### Installation 22 | 23 | ```bash 24 | npm install --save container-query-polyfill 25 | ``` 26 | 27 | Alternatively, you can use it directly from a CDN: 28 | 29 | ```js 30 | 31 | ``` 32 | 33 | For the best user experience, it's recommended that you initially only use the polyfill for content below-the-fold and use `@supports` queries to temporarily replace it with a loading indicator until the polyfill is ready to display it: 34 | 35 | ```css 36 | @supports not (container-type: inline-size) { 37 | .container, 38 | footer { 39 | display: none; 40 | } 41 | 42 | .loader { 43 | display: flex; 44 | } 45 | } 46 | ``` 47 | 48 | You can view a more complete demo [here](https://codesandbox.io/s/smoosh-glitter-m2ub4w?file=/index.html). On sufficiently fast networks and devices, or devices that natively support Container Queries, this loading indicator will never be displayed. 49 | 50 | > **Note** 51 | > Keep in mind that this technique effectively limits impact on FID and CLS, potentially at the expense of LCP. You may see regressions in the latter as a result, particularly on lower end devices or in poor network conditions. 52 | 53 | ## Limitations 54 | 55 | - **CSS first**: The polyfill currently only supports `