19 Commits 67848a8c51 ... 28d5d982ce

Tác giả SHA1 Thông báo Ngày
  R-Goc 28d5d982ce Everywhere: Remove unused private fields 1 tuần trước cách đây
  Sam Atkins e43bb1410c LibWeb/CSS: Reject non-grouping-rules as descendants of style rules 1 tuần trước cách đây
  Sam Atkins 9cce791424 LibWeb/CSS: Only attempt to load valid `@font-face` fonts 1 tuần trước cách đây
  Sam Atkins f87b454fa9 LibWeb/CSS: Parse `@font-face` descriptors as style values 1 tuần trước cách đây
  Sam Atkins 3c9685ff1a LibWeb/CSS: Support creating ParsedFontFace from CSSFontFaceDescriptors 1 tuần trước cách đây
  Sam Atkins cb8511772d LibWeb/CSS: Add CSSFontFaceDescriptors type 1 tuần trước cách đây
  Sam Atkins 1bc73ed4a8 LibWeb/CSS: Add missing include to StringStyleValue.h 1 tuần trước cách đây
  Sam Atkins fd45c53c11 LibWeb: Parse descriptors as style values, using the JSON data 1 tuần trước cách đây
  Sam Atkins 60c536bdd5 LibWeb/CSS: Add FontSourceStyleValue 1 tuần trước cách đây
  Sam Atkins 79093291b5 LibWeb/CSS: Un-template parse_comma_separated_value_list() 1 tuần trước cách đây
  Sam Atkins fd4f4f425d LibWeb: Generate DescriptorID enum 2 tuần trước cách đây
  Timothy Flynn a3ea4881e7 LibWeb+LibWebView+UI: Migrate to LibWebView's language settings 1 tuần trước cách đây
  Timothy Flynn f242920cc9 LibWebView: Add language settings to about:settings 1 tuần trước cách đây
  Timothy Flynn 26ec01068f LibWebView: Fire all observers when settings are restored to default 1 tuần trước cách đây
  Timothy Flynn aad95d8d9d Base: Generalize some about:settings CSS classes 1 tuần trước cách đây
  Timothy Flynn c8d6890ba2 Base: Write "websites" as a single word on about:settings 1 tuần trước cách đây
  Tim Ledbetter c941170e5a LibWeb: Invalidate document style when media rules are changed 1 tuần trước cách đây
  Tim Ledbetter 1659381362 Meta: Download WPT support files used in iframes 1 tuần trước cách đây
  R-Goc 8f1a7934e1 WebContent: Remove unused private member 1 tuần trước cách đây
80 tập tin đã thay đổi với 4452 bổ sung627 xóa
  1. 0 2
      AK/StringImpl.h
  2. 230 30
      Base/res/ladybird/about-pages/settings.html
  3. 177 0
      Base/res/ladybird/about-pages/settings/languages.js
  4. 4 0
      Base/res/ladybird/ladybird.css
  5. 0 1
      Libraries/LibIPC/Message.h
  6. 2 6
      Libraries/LibJS/AST.h
  7. 2 4
      Libraries/LibJS/Parser.cpp
  8. 1 1
      Libraries/LibMedia/Color/ColorConverter.cpp
  9. 2 4
      Libraries/LibMedia/Color/ColorConverter.h
  10. 0 1
      Libraries/LibMedia/PlaybackManager.h
  11. 2 3
      Libraries/LibTLS/TLSv12.cpp
  12. 1 2
      Libraries/LibTLS/TLSv12.h
  13. 5 0
      Libraries/LibWeb/CMakeLists.txt
  14. 406 0
      Libraries/LibWeb/CSS/CSSFontFaceDescriptors.cpp
  15. 93 0
      Libraries/LibWeb/CSS/CSSFontFaceDescriptors.h
  16. 35 0
      Libraries/LibWeb/CSS/CSSFontFaceDescriptors.idl
  17. 35 20
      Libraries/LibWeb/CSS/CSSFontFaceRule.cpp
  18. 9 6
      Libraries/LibWeb/CSS/CSSFontFaceRule.h
  19. 1 0
      Libraries/LibWeb/CSS/CSSStyleSheet.cpp
  20. 7 0
      Libraries/LibWeb/CSS/CSSStyleValue.cpp
  21. 5 0
      Libraries/LibWeb/CSS/CSSStyleValue.h
  22. 14 0
      Libraries/LibWeb/CSS/Descriptor.cpp
  23. 22 0
      Libraries/LibWeb/CSS/Descriptor.h
  24. 129 0
      Libraries/LibWeb/CSS/Descriptors.json
  25. 2 0
      Libraries/LibWeb/CSS/Keywords.json
  26. 175 1
      Libraries/LibWeb/CSS/ParsedFontFace.cpp
  27. 3 1
      Libraries/LibWeb/CSS/ParsedFontFace.h
  28. 135 0
      Libraries/LibWeb/CSS/Parser/DescriptorParsing.cpp
  29. 14 0
      Libraries/LibWeb/CSS/Parser/Helpers.cpp
  30. 49 4
      Libraries/LibWeb/CSS/Parser/Parser.cpp
  31. 16 2
      Libraries/LibWeb/CSS/Parser/Parser.h
  32. 0 24
      Libraries/LibWeb/CSS/Parser/PropertyParsing.cpp
  33. 10 284
      Libraries/LibWeb/CSS/Parser/RuleParsing.cpp
  34. 95 4
      Libraries/LibWeb/CSS/Parser/ValueParsing.cpp
  35. 4 2
      Libraries/LibWeb/CSS/StyleComputer.cpp
  36. 67 0
      Libraries/LibWeb/CSS/StyleValues/FontSourceStyleValue.cpp
  37. 41 0
      Libraries/LibWeb/CSS/StyleValues/FontSourceStyleValue.h
  38. 1 0
      Libraries/LibWeb/CSS/StyleValues/StringStyleValue.h
  39. 4 1
      Libraries/LibWeb/Dump.cpp
  40. 2 0
      Libraries/LibWeb/Forward.h
  41. 3 3
      Libraries/LibWeb/HTML/NavigatorLanguage.h
  42. 2 1
      Libraries/LibWeb/IndexedDB/Internal/KeyGenerator.h
  43. 3 7
      Libraries/LibWeb/Loader/ResourceLoader.h
  44. 3 2
      Libraries/LibWeb/ServiceWorker/Registration.h
  45. 1 0
      Libraries/LibWeb/idl_files.cmake
  46. 52 1
      Libraries/LibWebView/Settings.cpp
  47. 6 0
      Libraries/LibWebView/Settings.h
  48. 7 5
      Libraries/LibWebView/ViewImplementation.cpp
  49. 1 2
      Libraries/LibWebView/ViewImplementation.h
  50. 11 0
      Libraries/LibWebView/WebUI/SettingsUI.cpp
  51. 1 0
      Libraries/LibWebView/WebUI/SettingsUI.h
  52. 10 0
      Meta/CMake/libweb_generators.cmake
  53. 0 4
      Meta/Lagom/CMakeLists.txt
  54. 1 0
      Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt
  55. 361 0
      Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSDescriptors.cpp
  56. 0 1
      Meta/gn/build/BUILD.gn
  57. 1 1
      Meta/import-wpt-test.py
  58. 1 2
      Services/WebContent/ConnectionFromClient.cpp
  59. 1 2
      Services/WebContent/ConnectionFromClient.h
  60. 1 1
      Services/WebContent/main.cpp
  61. 1 0
      Tests/LibWeb/Text/expected/all-window-properties.txt
  62. 1 0
      Tests/LibWeb/Text/expected/css/CSSRule-type.txt
  63. 4 3
      Tests/LibWeb/Text/expected/wpt-import/css/css-fonts/parsing/font-face-size-adjust.txt
  64. 23 22
      Tests/LibWeb/Text/expected/wpt-import/css/css-fonts/parsing/font-face-src-format.txt
  65. 7 6
      Tests/LibWeb/Text/expected/wpt-import/css/css-fonts/parsing/font-face-src-list.txt
  66. 8 7
      Tests/LibWeb/Text/expected/wpt-import/css/css-fonts/parsing/font-face-src-local.txt
  67. 24 23
      Tests/LibWeb/Text/expected/wpt-import/css/css-fonts/parsing/font-face-src-tech.txt
  68. 1353 0
      Tests/LibWeb/Text/expected/wpt-import/css/mediaqueries/test_media_queries.txt
  69. 16 0
      Tests/LibWeb/Text/input/wpt-import/css/mediaqueries/support/media_queries_iframe.html
  70. 740 0
      Tests/LibWeb/Text/input/wpt-import/css/mediaqueries/test_media_queries.html
  71. 0 32
      UI/Qt/BrowserWindow.cpp
  72. 0 3
      UI/Qt/BrowserWindow.h
  73. 0 1
      UI/Qt/CMakeLists.txt
  74. 0 11
      UI/Qt/Settings.cpp
  75. 0 4
      UI/Qt/Settings.h
  76. 0 42
      UI/Qt/SettingsDialog.cpp
  77. 0 31
      UI/Qt/SettingsDialog.h
  78. 0 5
      UI/Qt/Tab.cpp
  79. 0 2
      UI/Qt/Tab.h
  80. 9 0
      UI/cmake/ResourceFiles.cmake

+ 0 - 2
AK/StringImpl.h

@@ -82,7 +82,6 @@ private:
         ConstructTheEmptyStringImpl
     };
     explicit StringImpl(ConstructTheEmptyStringImplTag)
-        : m_fly(true)
     {
         m_inline_buffer[0] = '\0';
     }
@@ -97,7 +96,6 @@ private:
     size_t m_length { 0 };
     mutable unsigned m_hash { 0 };
     mutable bool m_has_hash { false };
-    mutable bool m_fly { false };
     char m_inline_buffer[0];
 };
 

+ 230 - 30
Base/res/ladybird/about-pages/settings.html

@@ -168,7 +168,6 @@
                 height: 450px;
 
                 border-top: 1px solid var(--border-color);
-                border-bottom: 1px solid var(--border-color);
 
                 display: flex;
                 flex-direction: column;
@@ -188,6 +187,8 @@
                 display: flex;
                 justify-content: flex-end;
 
+                border-top: 1px solid var(--border-color);
+
                 padding: 15px 20px;
                 gap: 10px;
             }
@@ -197,7 +198,7 @@
                 font-weight: 500;
             }
 
-            dialog .dialog-close {
+            dialog .dialog-button {
                 background: none;
                 border: none;
                 outline: none;
@@ -210,11 +211,22 @@
                 cursor: pointer;
             }
 
-            dialog .dialog-close:hover {
+            dialog .dialog-button:hover {
                 opacity: 1;
             }
 
-            dialog .dialog-site-list {
+            dialog .dialog-button:disabled {
+                opacity: 0.3;
+            }
+
+            dialog .dialog-description {
+                margin-bottom: 10px;
+
+                font-size: 14px;
+                opacity: 0.7;
+            }
+
+            dialog .dialog-list {
                 outline: 1px solid var(--border-color);
                 border-radius: 4px;
 
@@ -226,7 +238,7 @@
                 flex-grow: 1;
             }
 
-            dialog .dialog-site-item {
+            dialog .dialog-list-item {
                 padding: 10px;
 
                 display: flex;
@@ -234,20 +246,20 @@
                 align-items: center;
             }
 
-            dialog .dialog-site-item:hover {
+            dialog .dialog-list-item:hover {
                 background-color: var(--card-header-background-color);
             }
 
-            dialog .dialog-site-item-placeholder {
+            dialog .dialog-list-item-placeholder {
                 padding: 10px;
                 opacity: 0.6;
             }
 
-            dialog .dialog-site-item .filter {
+            dialog .dialog-list-item-label {
                 flex-grow: 1;
             }
 
-            dialog .dialog-add-site {
+            dialog .dialog-controls {
                 display: flex;
                 justify-content: flex-end;
 
@@ -255,7 +267,13 @@
                 gap: 10px;
             }
 
-            dialog .dialog-add-site input {
+            dialog .dialog-controls button svg {
+                width: 16px;
+                height: 16px;
+            }
+
+            dialog .dialog-controls input,
+            dialog .dialog-controls select {
                 flex-grow: 1;
             }
 
@@ -287,6 +305,13 @@
                     <label for="new-tab-page-url">New Tab Page URL</label>
                     <input id="new-tab-page-url" type="url" placeholder="about:newtab" />
                 </div>
+
+                <hr />
+
+                <div class="card-group permission-container">
+                    <span>Languages</span>
+                    <button id="languages-settings" class="secondary-button">Settings...</button>
+                </div>
             </div>
         </div>
 
@@ -338,7 +363,7 @@
             <div class="card-header">Privacy</div>
             <div class="card-body">
                 <div class="card-group toggle-container">
-                    <label for="do-not-track-toggle">Send web sites a "Do Not Track" request</label>
+                    <label for="do-not-track-toggle">Send websites a "Do Not Track" request</label>
                     <input id="do-not-track-toggle" type="checkbox" switch />
                 </div>
             </div>
@@ -348,10 +373,29 @@
             <button id="restore-defaults" class="primary-button">Restore&nbsp;Defaults</button>
         </div>
 
+        <dialog id="languages-dialog">
+            <div class="dialog-header">
+                <h3 class="dialog-title">Languages</h3>
+                <button id="languages-close" class="close-button dialog-button">&times;</button>
+            </div>
+            <div class="dialog-body">
+                <p class="dialog-description">
+                    Choose languages for websites in order of preference
+                </p>
+                <div id="languages-list" class="dialog-list"></div>
+                <div class="dialog-controls">
+                    <select id="languages-select">
+                        <option value="">Select a language to add...</option>
+                    </select>
+                    <button id="languages-add" class="primary-button" disabled>Add</button>
+                </div>
+            </div>
+        </dialog>
+
         <dialog id="site-settings">
             <div class="dialog-header">
                 <h3 id="site-settings-title" class="dialog-title"></h3>
-                <button id="site-settings-close" class="close-button dialog-close">&times;</button>
+                <button id="site-settings-close" class="close-button dialog-button">&times;</button>
             </div>
             <div class="dialog-body">
                 <div class="toggle-container">
@@ -360,8 +404,8 @@
                 </div>
                 <hr />
                 <label>Allowlist</label>
-                <div id="site-settings-list" class="dialog-site-list"></div>
-                <div class="dialog-add-site">
+                <div id="site-settings-list" class="dialog-list"></div>
+                <div class="dialog-controls">
                     <input id="site-settings-input" type="text" placeholder="example.com" />
                     <button id="site-settings-add" class="primary-button">Add&nbsp;Site</button>
                 </div>
@@ -373,8 +417,16 @@
             </div>
         </dialog>
 
+        <script src="resource://ladybird/about-pages/settings/languages.js"></script>
+
         <script>
             const newTabPageURL = document.querySelector("#new-tab-page-url");
+            const languagesAdd = document.querySelector("#languages-add");
+            const languagesClose = document.querySelector("#languages-close");
+            const languagesDialog = document.querySelector("#languages-dialog");
+            const languagesList = document.querySelector("#languages-list");
+            const languagesSelect = document.querySelector("#languages-select");
+            const languagesSettings = document.querySelector("#languages-settings");
             const searchToggle = document.querySelector("#search-toggle");
             const searchEngine = document.querySelector("#search-engine");
             const autocompleteToggle = document.querySelector("#autocomplete-toggle");
@@ -391,6 +443,13 @@
             const doNotTrackToggle = document.querySelector("#do-not-track-toggle");
             const restoreDefaults = document.querySelector("#restore-defaults");
 
+            // FIXME: When we support per-glyph font fallbacks, replace these SVGs with analogous code points.
+            //        https://github.com/LadybirdBrowser/ladybird/issues/864
+            const upwardArrowSVG =
+                '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"></polyline></svg>';
+            const downwardArrowSVG =
+                '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>';
+
             window.settings = {};
 
             const loadSettings = settings => {
@@ -402,6 +461,10 @@
                 newTabPageURL.classList.remove("error");
                 newTabPageURL.value = window.settings.newTabPageURL;
 
+                if (languagesDialog.open) {
+                    showLanguages();
+                }
+
                 const renderEngineSettings = (type, setting) => {
                     const [name, toggle, engine] = engineForType(type);
 
@@ -444,6 +507,137 @@
                 }, 1000);
             });
 
+            const languageDisplayName = language => {
+                const item = window.languages.find(item => item.language === language);
+                return item.displayName;
+            };
+
+            const saveLanguages = () => {
+                ladybird.sendMessage("setLanguages", window.settings.languages);
+            };
+
+            const moveLanguage = (from, to) => {
+                [window.settings.languages[from], window.settings.languages[to]] = [
+                    window.settings.languages[to],
+                    window.settings.languages[from],
+                ];
+
+                saveLanguages();
+            };
+
+            const removeLanguage = index => {
+                window.settings.languages.splice(index, 1);
+                saveLanguages();
+            };
+
+            const loadLanguages = () => {
+                for (const language of window.languages) {
+                    const option = document.createElement("option");
+                    option.text = language.displayName;
+                    option.value = language.language;
+
+                    languagesSelect.add(option);
+                }
+            };
+
+            const showLanguages = () => {
+                languagesList.innerHTML = "";
+
+                window.settings.languages.forEach((language, index) => {
+                    const name = document.createElement("span");
+                    name.className = "dialog-list-item-label";
+                    name.textContent = languageDisplayName(language);
+
+                    const moveUp = document.createElement("button");
+                    moveUp.className = "dialog-button";
+                    moveUp.innerHTML = upwardArrowSVG;
+                    moveUp.title = "Move up";
+
+                    if (index === 0) {
+                        moveUp.disabled = true;
+                    } else {
+                        moveUp.addEventListener("click", () => {
+                            moveLanguage(index, index - 1);
+                        });
+                    }
+
+                    const moveDown = document.createElement("button");
+                    moveDown.className = "dialog-button";
+                    moveDown.innerHTML = downwardArrowSVG;
+                    moveDown.title = "Move down";
+
+                    if (index === window.settings.languages.length - 1) {
+                        moveDown.disabled = true;
+                    } else {
+                        moveDown.addEventListener("click", () => {
+                            moveLanguage(index, index + 1);
+                        });
+                    }
+
+                    const remove = document.createElement("button");
+                    remove.className = "dialog-button";
+                    remove.innerHTML = "&times;";
+                    remove.title = "Remove";
+
+                    if (window.settings.languages.length <= 1) {
+                        remove.disabled = true;
+                    } else {
+                        remove.addEventListener("click", () => {
+                            removeLanguage(index);
+                        });
+                    }
+
+                    const controls = document.createElement("div");
+                    controls.className = "dialog-controls";
+                    controls.appendChild(moveUp);
+                    controls.appendChild(moveDown);
+                    controls.appendChild(remove);
+
+                    const item = document.createElement("div");
+                    item.className = "dialog-list-item";
+                    item.appendChild(name);
+                    item.appendChild(controls);
+
+                    languagesList.appendChild(item);
+                });
+
+                for (const language of languagesSelect.options) {
+                    language.disabled = window.settings.languages.includes(language.value);
+                }
+
+                if (!languagesDialog.open) {
+                    setTimeout(() => languagesSelect.focus());
+                    languagesDialog.showModal();
+                }
+            };
+
+            languagesAdd.addEventListener("click", () => {
+                const language = languagesSelect.value;
+
+                languagesAdd.disabled = true;
+                languagesSelect.selectedIndex = 0;
+
+                if (!language || window.settings.languages.includes(language)) {
+                    return;
+                }
+
+                window.settings.languages.push(language);
+                saveLanguages();
+            });
+
+            languagesClose.addEventListener("click", () => {
+                languagesDialog.close();
+            });
+
+            languagesSelect.addEventListener("change", () => {
+                languagesAdd.disabled = !languagesSelect.value;
+            });
+
+            languagesSettings.addEventListener("click", event => {
+                showLanguages();
+                event.stopPropagation();
+            });
+
             const Engine = Object.freeze({
                 search: 1,
                 autocomplete: 2,
@@ -546,7 +740,7 @@
 
                 if (settings.siteFilters.length === 0) {
                     const placeholder = document.createElement("div");
-                    placeholder.className = "dialog-site-item-placeholder";
+                    placeholder.className = "dialog-list-item-placeholder";
                     placeholder.textContent = "No sites added";
 
                     siteSettingsList.appendChild(placeholder);
@@ -554,11 +748,11 @@
 
                 settings.siteFilters.forEach(site => {
                     const filter = document.createElement("span");
-                    filter.className = "filter";
+                    filter.className = "dialog-list-item-label";
                     filter.textContent = site;
 
                     const remove = document.createElement("button");
-                    remove.className = "dialog-close";
+                    remove.className = "dialog-button";
                     remove.innerHTML = "&times;";
                     remove.title = `Remove ${site}`;
 
@@ -570,7 +764,7 @@
                     });
 
                     const item = document.createElement("div");
-                    item.className = "dialog-site-item";
+                    item.className = "dialog-list-item";
                     item.appendChild(filter);
                     item.appendChild(remove);
 
@@ -632,26 +826,32 @@
 
             // FIXME: Once we support `dialog::backdrop`, this event listener should be on `siteSettings`.
             document.addEventListener("click", event => {
-                if (!siteSettings.open) {
-                    return;
-                }
+                const close = dialog => {
+                    if (!dialog.open) {
+                        return;
+                    }
 
-                const rect = siteSettings.getBoundingClientRect();
+                    const rect = dialog.getBoundingClientRect();
 
-                if (
-                    event.clientX < rect.left ||
-                    event.clientX > rect.right ||
-                    event.clientY < rect.top ||
-                    event.clientY > rect.bottom
-                ) {
-                    siteSettings.close();
-                }
+                    if (
+                        event.clientX < rect.left ||
+                        event.clientX > rect.right ||
+                        event.clientY < rect.top ||
+                        event.clientY > rect.bottom
+                    ) {
+                        dialog.close();
+                    }
+                };
+
+                close(languagesDialog);
+                close(siteSettings);
             });
 
             document.addEventListener("WebUILoaded", () => {
                 ladybird.sendMessage("loadAvailableEngines");
                 ladybird.sendMessage("loadCurrentSettings");
                 ladybird.sendMessage("loadForciblyEnabledSiteSettings");
+                loadLanguages();
             });
 
             document.addEventListener("WebUIMessage", event => {

+ 177 - 0
Base/res/ladybird/about-pages/settings/languages.js

@@ -0,0 +1,177 @@
+// Rather than creating a list of all languages supported by ICU (of which there are on the order of a thousand), we
+// create a list of languages that are supported by both Chrome and Firefox. We can extend this list as needed.
+//
+// https://github.com/chromium/chromium/blob/main/ui/base/l10n/l10n_util.cc (see kAcceptLanguageList)
+// https://github.com/mozilla/gecko-dev/blob/master/intl/locale/language.properties
+window.languages = (() => {
+    const display = new Intl.DisplayNames([], { type: "language", languageDisplay: "standard" });
+
+    const language = languageID => {
+        return {
+            language: languageID,
+            displayName: display.of(languageID),
+        };
+    };
+
+    const languages = [
+        language("af"),
+        language("ak"),
+        language("am"),
+        language("ar"),
+        language("as"),
+        language("ast"),
+        language("az"),
+        language("be"),
+        language("bg"),
+        language("bm"),
+        language("bn"),
+        language("br"),
+        language("bs"),
+        language("ca"),
+        language("cs"),
+        language("cy"),
+        language("da"),
+        language("de"),
+        language("de-AT"),
+        language("de-CH"),
+        language("de-DE"),
+        language("de-LI"),
+        language("ee"),
+        language("el"),
+        language("en"),
+        language("en-AU"),
+        language("en-CA"),
+        language("en-GB"),
+        language("en-IE"),
+        language("en-NZ"),
+        language("en-US"),
+        language("en-ZA"),
+        language("eo"),
+        language("es"),
+        language("es-AR"),
+        language("es-CL"),
+        language("es-CO"),
+        language("es-CR"),
+        language("es-ES"),
+        language("es-HN"),
+        language("es-MX"),
+        language("es-PE"),
+        language("es-UY"),
+        language("es-VE"),
+        language("et"),
+        language("eu"),
+        language("fa"),
+        language("fi"),
+        language("fo"),
+        language("fr"),
+        language("fr-CA"),
+        language("fr-CH"),
+        language("fr-FR"),
+        language("fy"),
+        language("ga"),
+        language("gd"),
+        language("gl"),
+        language("gu"),
+        language("ha"),
+        language("haw"),
+        language("he"),
+        language("hi"),
+        language("hr"),
+        language("hu"),
+        language("hy"),
+        language("ia"),
+        language("id"),
+        language("ig"),
+        language("is"),
+        language("it"),
+        language("it-CH"),
+        language("ja"),
+        language("jv"),
+        language("ka"),
+        language("kk"),
+        language("km"),
+        language("kn"),
+        language("ko"),
+        language("kok"),
+        language("ku"),
+        language("ky"),
+        language("lb"),
+        language("lg"),
+        language("ln"),
+        language("lo"),
+        language("lt"),
+        language("lv"),
+        language("mai"),
+        language("mg"),
+        language("mi"),
+        language("mk"),
+        language("ml"),
+        language("mn"),
+        language("mr"),
+        language("ms"),
+        language("mt"),
+        language("my"),
+        language("nb"),
+        language("ne"),
+        language("nl"),
+        language("nn"),
+        language("no"),
+        language("nso"),
+        language("oc"),
+        language("om"),
+        language("or"),
+        language("pa"),
+        language("pl"),
+        language("ps"),
+        language("pt"),
+        language("pt-BR"),
+        language("pt-PT"),
+        language("qu"),
+        language("rm"),
+        language("ro"),
+        language("ru"),
+        language("rw"),
+        language("sa"),
+        language("sd"),
+        language("si"),
+        language("sk"),
+        language("sl"),
+        language("so"),
+        language("sq"),
+        language("sr"),
+        language("st"),
+        language("su"),
+        language("sv"),
+        language("sw"),
+        language("ta"),
+        language("te"),
+        language("tg"),
+        language("th"),
+        language("ti"),
+        language("tk"),
+        language("tn"),
+        language("to"),
+        language("tr"),
+        language("tt"),
+        language("ug"),
+        language("uk"),
+        language("ur"),
+        language("uz"),
+        language("vi"),
+        language("wo"),
+        language("xh"),
+        language("yi"),
+        language("yo"),
+        language("zh"),
+        language("zh-CN"),
+        language("zh-HK"),
+        language("zh-TW"),
+        language("zu"),
+    ];
+
+    languages.sort((lhs, rhs) => {
+        return lhs.displayName.localeCompare(rhs.displayName);
+    });
+
+    return languages;
+})();

+ 4 - 0
Base/res/ladybird/ladybird.css

@@ -114,6 +114,10 @@ button.primary-button:active {
     background-color: var(--violet-900);
 }
 
+button.primary-button:disabled {
+    background-color: var(--violet-60);
+}
+
 button.secondary-button {
     background-color: var(--secondary-button-color);
 }

+ 0 - 1
Libraries/LibIPC/Message.h

@@ -67,7 +67,6 @@ public:
 
 private:
     Vector<u8, 1024> m_data;
-    bool m_fds_taken { false };
     Vector<NonnullRefPtr<AutoCloseFileDescriptor>, 1> m_fds;
 #ifdef AK_OS_WINDOWS
     Vector<size_t> m_handle_offsets;

+ 2 - 6
Libraries/LibJS/AST.h

@@ -1397,11 +1397,10 @@ private:
 
 class ClassField final : public ClassElement {
 public:
-    ClassField(SourceRange source_range, NonnullRefPtr<Expression const> key, RefPtr<Expression const> init, bool contains_direct_call_to_eval, bool is_static)
+    ClassField(SourceRange source_range, NonnullRefPtr<Expression const> key, RefPtr<Expression const> init, bool is_static)
         : ClassElement(move(source_range), is_static)
         , m_key(move(key))
         , m_initializer(move(init))
-        , m_contains_direct_call_to_eval(contains_direct_call_to_eval)
     {
     }
 
@@ -1418,15 +1417,13 @@ public:
 private:
     NonnullRefPtr<Expression const> m_key;
     RefPtr<Expression const> m_initializer;
-    bool m_contains_direct_call_to_eval { false };
 };
 
 class StaticInitializer final : public ClassElement {
 public:
-    StaticInitializer(SourceRange source_range, NonnullRefPtr<FunctionBody> function_body, bool contains_direct_call_to_eval)
+    StaticInitializer(SourceRange source_range, NonnullRefPtr<FunctionBody> function_body)
         : ClassElement(move(source_range), true)
         , m_function_body(move(function_body))
-        , m_contains_direct_call_to_eval(contains_direct_call_to_eval)
     {
     }
 
@@ -1437,7 +1434,6 @@ public:
 
 private:
     NonnullRefPtr<FunctionBody> m_function_body;
-    bool m_contains_direct_call_to_eval { false };
 };
 
 class SuperExpression final : public Expression {

+ 2 - 4
Libraries/LibJS/Parser.cpp

@@ -1539,7 +1539,7 @@ NonnullRefPtr<ClassExpression const> Parser::parse_class_expression(bool expect_
                 parse_statement_list(static_init_block);
 
                 consume(TokenType::CurlyClose);
-                elements.append(create_ast_node<StaticInitializer>({ m_source_code, static_start.position(), position() }, move(static_init_block), static_init_scope.contains_direct_call_to_eval()));
+                elements.append(create_ast_node<StaticInitializer>({ m_source_code, static_start.position(), position() }, move(static_init_block)));
                 continue;
             } else {
                 expected("property key");
@@ -1591,7 +1591,6 @@ NonnullRefPtr<ClassExpression const> Parser::parse_class_expression(bool expect_
                 syntax_error("Class cannot have field named 'constructor'"_string);
 
             RefPtr<Expression const> initializer;
-            bool contains_direct_call_to_eval = false;
 
             if (match(TokenType::Equals)) {
                 consume();
@@ -1602,10 +1601,9 @@ NonnullRefPtr<ClassExpression const> Parser::parse_class_expression(bool expect_
                 auto class_scope_node = create_ast_node<BlockStatement>({ m_source_code, rule_start.position(), position() });
                 auto class_field_scope = ScopePusher::class_field_scope(*this, *class_scope_node);
                 initializer = parse_expression(2);
-                contains_direct_call_to_eval = class_field_scope.contains_direct_call_to_eval();
             }
 
-            elements.append(create_ast_node<ClassField>({ m_source_code, rule_start.position(), position() }, property_key.release_nonnull(), move(initializer), contains_direct_call_to_eval, is_static));
+            elements.append(create_ast_node<ClassField>({ m_source_code, rule_start.position(), position() }, property_key.release_nonnull(), move(initializer), is_static));
             consume_or_insert_semicolon();
         }
     }

+ 1 - 1
Libraries/LibMedia/Color/ColorConverter.cpp

@@ -152,7 +152,7 @@ DecoderErrorOr<ColorConverter> ColorConverter::create(u8 bit_depth, CodingIndepe
     bool should_skip_color_remapping = output_cicp.color_primaries() == input_cicp.color_primaries() && output_cicp.transfer_characteristics() == input_cicp.transfer_characteristics();
     FloatMatrix4x4 input_conversion_matrix = color_conversion_matrix * range_scaling_matrix * integer_scaling_matrix;
 
-    return ColorConverter(bit_depth, input_cicp, should_skip_color_remapping, should_tonemap, input_conversion_matrix, to_linear_lookup_table, color_primaries_matrix_4x4, to_non_linear_lookup_table);
+    return ColorConverter(input_cicp, should_skip_color_remapping, should_tonemap, input_conversion_matrix, to_linear_lookup_table, color_primaries_matrix_4x4, to_non_linear_lookup_table);
 }
 
 }

+ 2 - 4
Libraries/LibMedia/Color/ColorConverter.h

@@ -247,9 +247,8 @@ private:
     static constexpr size_t to_linear_size = 64;
     static constexpr size_t to_non_linear_size = 64;
 
-    ColorConverter(u8 bit_depth, CodingIndependentCodePoints cicp, bool should_skip_color_remapping, bool should_tonemap, FloatMatrix4x4 input_conversion_matrix, InterpolatedLookupTable<to_linear_size> to_linear_lookup, FloatMatrix4x4 color_space_conversion_matrix, InterpolatedLookupTable<to_non_linear_size> to_non_linear_lookup)
-        : m_bit_depth(bit_depth)
-        , m_cicp(cicp)
+    ColorConverter(CodingIndependentCodePoints cicp, bool should_skip_color_remapping, bool should_tonemap, FloatMatrix4x4 input_conversion_matrix, InterpolatedLookupTable<to_linear_size> to_linear_lookup, FloatMatrix4x4 color_space_conversion_matrix, InterpolatedLookupTable<to_non_linear_size> to_non_linear_lookup)
+        : m_cicp(cicp)
         , m_should_skip_color_remapping(should_skip_color_remapping)
         , m_should_tonemap(should_tonemap)
         , m_input_conversion_matrix(input_conversion_matrix)
@@ -259,7 +258,6 @@ private:
     {
     }
 
-    u8 m_bit_depth;
     CodingIndependentCodePoints m_cicp;
     bool m_should_skip_color_remapping;
     bool m_should_tonemap;

+ 0 - 1
Libraries/LibMedia/PlaybackManager.h

@@ -179,7 +179,6 @@ private:
     VideoFrameQueue m_frame_queue;
 
     RefPtr<Core::Timer> m_state_update_timer;
-    unsigned m_decoding_buffer_time_ms = 16;
 
     RefPtr<Threading::Thread> m_decode_thread;
     NonnullOwnPtr<VideoDecoder> m_decoder;

+ 2 - 3
Libraries/LibTLS/TLSv12.cpp

@@ -147,10 +147,9 @@ ErrorOr<void> TLSv12::set_close_on_exec(bool enabled)
     return m_socket->set_close_on_exec(enabled);
 }
 
-TLSv12::TLSv12(NonnullOwnPtr<Core::TCPSocket> socket, SSL_CTX* ssl_ctx, SSL* ssl, BIO* bio)
+TLSv12::TLSv12(NonnullOwnPtr<Core::TCPSocket> socket, SSL_CTX* ssl_ctx, SSL* ssl)
     : m_ssl_ctx(ssl_ctx)
     , m_ssl(ssl)
-    , m_bio(bio)
     , m_socket(move(socket))
 {
     m_socket->on_ready_to_read = [this] {
@@ -244,7 +243,7 @@ ErrorOr<NonnullOwnPtr<TLSv12>> TLSv12::connect_internal(NonnullOwnPtr<Core::TCPS
     free_ssl.disarm();
     free_ssl_ctx.disarm();
 
-    return adopt_own(*new TLSv12(move(socket), ssl_ctx, ssl, bio));
+    return adopt_own(*new TLSv12(move(socket), ssl_ctx, ssl));
 }
 
 }

+ 1 - 2
Libraries/LibTLS/TLSv12.h

@@ -65,7 +65,7 @@ public:
     ~TLSv12() override;
 
 private:
-    explicit TLSv12(NonnullOwnPtr<Core::TCPSocket>, SSL_CTX*, SSL*, BIO*);
+    explicit TLSv12(NonnullOwnPtr<Core::TCPSocket>, SSL_CTX*, SSL*);
 
     static ErrorOr<NonnullOwnPtr<TLSv12>> connect_internal(NonnullOwnPtr<Core::TCPSocket>, ByteString const&, Options);
 
@@ -73,7 +73,6 @@ private:
 
     SSL_CTX* m_ssl_ctx { nullptr };
     SSL* m_ssl { nullptr };
-    BIO* m_bio { nullptr };
 
     // Keep this around or the socket will be closed
     NonnullOwnPtr<Core::TCPSocket> m_socket;

+ 5 - 0
Libraries/LibWeb/CMakeLists.txt

@@ -70,6 +70,7 @@ set(SOURCES
     CSS/CSSImportRule.cpp
     CSS/CSSKeyframeRule.cpp
     CSS/CSSKeyframesRule.cpp
+    CSS/CSSFontFaceDescriptors.cpp
     CSS/CSSFontFaceRule.cpp
     CSS/CSSLayerBlockRule.cpp
     CSS/CSSLayerStatementRule.cpp
@@ -89,6 +90,7 @@ set(SOURCES
     CSS/CSSTransition.cpp
     CSS/CascadedProperties.cpp
     CSS/ComputedProperties.cpp
+    CSS/Descriptor.cpp
     CSS/Display.cpp
     CSS/EdgeRect.cpp
     CSS/Fetch.cpp
@@ -108,6 +110,7 @@ set(SOURCES
     CSS/MediaQueryListEvent.cpp
     CSS/Number.cpp
     CSS/Parser/ComponentValue.cpp
+    CSS/Parser/DescriptorParsing.cpp
     CSS/Parser/GradientParsing.cpp
     CSS/Parser/Helpers.cpp
     CSS/Parser/MediaParsing.cpp
@@ -164,6 +167,7 @@ set(SOURCES
     CSS/StyleValues/EasingStyleValue.cpp
     CSS/StyleValues/EdgeStyleValue.cpp
     CSS/StyleValues/FilterValueListStyleValue.cpp
+    CSS/StyleValues/FontSourceStyleValue.cpp
     CSS/StyleValues/GridAutoFlowStyleValue.cpp
     CSS/StyleValues/GridTemplateAreaStyleValue.cpp
     CSS/StyleValues/GridTrackPlacementStyleValue.cpp
@@ -952,6 +956,7 @@ invoke_generator(
 set(GENERATED_SOURCES
     ARIA/AriaRoles.cpp
     CSS/DefaultStyleSheetSource.cpp
+    CSS/DescriptorID.cpp
     CSS/Enums.cpp
     CSS/GeneratedCSSStyleProperties.cpp
     CSS/GeneratedCSSStyleProperties.idl

+ 406 - 0
Libraries/LibWeb/CSS/CSSFontFaceDescriptors.cpp

@@ -0,0 +1,406 @@
+/*
+ * Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "Parser/Parser.h"
+
+#include <LibWeb/Bindings/CSSFontFaceDescriptorsPrototype.h>
+#include <LibWeb/CSS/CSSFontFaceDescriptors.h>
+#include <LibWeb/CSS/CSSStyleValue.h>
+#include <LibWeb/CSS/Serialize.h>
+#include <LibWeb/Infra/Strings.h>
+
+namespace Web::CSS {
+
+GC_DEFINE_ALLOCATOR(CSSFontFaceDescriptors);
+
+GC::Ref<CSSFontFaceDescriptors> CSSFontFaceDescriptors::create(JS::Realm& realm, Vector<Descriptor> descriptors)
+{
+    return realm.create<CSSFontFaceDescriptors>(realm, move(descriptors));
+}
+
+CSSFontFaceDescriptors::CSSFontFaceDescriptors(JS::Realm& realm, Vector<Descriptor> descriptors)
+    : CSSStyleDeclaration(realm, Computed::No, Readonly::No)
+    , m_descriptors(move(descriptors))
+{
+}
+
+CSSFontFaceDescriptors::~CSSFontFaceDescriptors() = default;
+
+void CSSFontFaceDescriptors::initialize(JS::Realm& realm)
+{
+    Base::initialize(realm);
+    WEB_SET_PROTOTYPE_FOR_INTERFACE(CSSFontFaceDescriptors);
+}
+
+// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-length
+size_t CSSFontFaceDescriptors::length() const
+{
+    // The length attribute must return the number of CSS declarations in the declarations.
+    return m_descriptors.size();
+}
+
+// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-item
+String CSSFontFaceDescriptors::item(size_t index) const
+{
+    // The item(index) method must return the property name of the CSS declaration at position index.
+    if (index >= length())
+        return {};
+
+    return to_string(m_descriptors[index].descriptor_id).to_string();
+}
+
+// https://drafts.csswg.org/cssom/#set-a-css-declaration
+bool CSSFontFaceDescriptors::set_a_css_declaration(DescriptorID descriptor_id, NonnullRefPtr<CSSStyleValue const> value, Important)
+{
+    VERIFY(!is_computed());
+
+    for (auto& descriptor : m_descriptors) {
+        if (descriptor.descriptor_id == descriptor_id) {
+            if (*descriptor.value == *value)
+                return false;
+            descriptor.value = move(value);
+            return true;
+        }
+    }
+
+    m_descriptors.append(Descriptor {
+        .descriptor_id = descriptor_id,
+        .value = move(value),
+    });
+    return true;
+}
+
+// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-setproperty
+WebIDL::ExceptionOr<void> CSSFontFaceDescriptors::set_property(StringView property, StringView value, StringView priority)
+{
+    // 1. If the readonly flag is set, then throw a NoModificationAllowedError exception.
+    if (is_readonly())
+        return WebIDL::NoModificationAllowedError::create(realm(), "Cannot modify properties of readonly CSSFontFaceDescriptors"_string);
+
+    // 2. If property is not a custom property, follow these substeps:
+    Optional<DescriptorID> descriptor_id;
+    {
+        // 1. Let property be property converted to ASCII lowercase.
+        // 2. If property is not a case-sensitive match for a supported CSS property, then return.
+        descriptor_id = descriptor_id_from_string(AtRuleID::FontFace, property);
+        if (!descriptor_id.has_value())
+            return {};
+    }
+
+    // 3. If value is the empty string, invoke removeProperty() with property as argument and return.
+    if (value.is_empty()) {
+        MUST(remove_property(property));
+        return {};
+    }
+
+    // 4. If priority is not the empty string and is not an ASCII case-insensitive match for the string "important", then return.
+    if (!priority.is_empty() && !Infra::is_ascii_case_insensitive_match(priority, "important"sv))
+        return {};
+
+    // 5. Let component value list be the result of parsing value for property property.
+    RefPtr<CSSStyleValue> component_value_list = parse_css_descriptor(Parser::ParsingParams {}, AtRuleID::FontFace, *descriptor_id, value);
+
+    // 6. If component value list is null, then return.
+    if (!component_value_list)
+        return {};
+
+    // 7. Let updated be false.
+    auto updated = false;
+
+    // 8. If property is a shorthand property...
+    // NB: Descriptors can't be shorthands.
+    // 9. Otherwise, let updated be the result of set the CSS declaration property with value component value list,
+    //    with the important flag set if priority is not the empty string, and unset otherwise, and with the list of
+    //    declarations being the declarations.
+    updated = set_a_css_declaration(*descriptor_id, *component_value_list, !priority.is_empty() ? Important::Yes : Important::No);
+
+    // 10. If updated is true, update style attribute for the CSS declaration block.
+    if (updated)
+        update_style_attribute();
+
+    return {};
+}
+
+// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-removeproperty
+WebIDL::ExceptionOr<String> CSSFontFaceDescriptors::remove_property(StringView property)
+{
+    // 1. If the readonly flag is set, then throw a NoModificationAllowedError exception.
+    if (is_readonly())
+        return WebIDL::NoModificationAllowedError::create(realm(), "Cannot modify properties of readonly CSSFontFaceDescriptors"_string);
+
+    // 2. If property is not a custom property, let property be property converted to ASCII lowercase.
+    // AD-HOC: We compare names case-insensitively instead.
+
+    // 3. Let value be the return value of invoking getPropertyValue() with property as argument.
+    auto value = get_property_value(property);
+
+    // 4. Let removed be false.
+    bool removed = false;
+
+    // 5. If property is a shorthand property...
+    // NB: Descriptors can't be shorthands.
+    // 6. Otherwise, if property is a case-sensitive match for a property name of a CSS declaration in the
+    //    declarations, remove that CSS declaration and let removed be true.
+    // auto descriptor_id = descriptor_from_string()
+    auto descriptor_id = descriptor_id_from_string(AtRuleID::FontFace, property);
+    if (descriptor_id.has_value()) {
+        removed = m_descriptors.remove_first_matching([descriptor_id](auto& entry) { return entry.descriptor_id == *descriptor_id; });
+    }
+
+    // 7. If removed is true, Update style attribute for the CSS declaration block.
+    if (removed)
+        update_style_attribute();
+
+    // 8. Return value.
+    return value;
+}
+
+// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertyvalue
+String CSSFontFaceDescriptors::get_property_value(StringView property) const
+{
+    // 1. If property is not a custom property, follow these substeps: ...
+    // NB: These substeps only apply to shorthands, and descriptors cannot be shorthands.
+
+    // 2. If property is a case-sensitive match for a property name of a CSS declaration in the declarations, then
+    //    return the result of invoking serialize a CSS value of that declaration.
+    auto descriptor_id = descriptor_id_from_string(AtRuleID::FontFace, property);
+    if (descriptor_id.has_value()) {
+        auto match = m_descriptors.first_matching([descriptor_id](auto& entry) { return entry.descriptor_id == *descriptor_id; });
+        if (match.has_value())
+            return match->value->to_string(CSSStyleValue::SerializationMode::Normal);
+    }
+
+    // 3. Return the empty string.
+    return {};
+}
+
+// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertypriority
+StringView CSSFontFaceDescriptors::get_property_priority(StringView) const
+{
+    // AD-HOC: It's not valid for descriptors to be !important.
+    return {};
+}
+
+// https://drafts.csswg.org/cssom/#serialize-a-css-declaration-block
+String CSSFontFaceDescriptors::serialized() const
+{
+    // 1. Let list be an empty array.
+    Vector<String> list;
+    list.ensure_capacity(m_descriptors.size());
+
+    // 2. Let already serialized be an empty array.
+    // AD-HOC: Not needed as we don't have shorthands.
+
+    // 3. Declaration loop: For each CSS declaration declaration in declaration block’s declarations, follow these substeps:
+    for (auto const& descriptor : m_descriptors) {
+        // 1. Let property be declaration’s property name.
+        auto property = to_string(descriptor.descriptor_id);
+
+        // 2. If property is in already serialized, continue with the steps labeled declaration loop.
+        // AD-HOC: Not needed as we don't have shorthands.
+
+        // 3. If property maps to one or more shorthand properties, let shorthands be an array of those shorthand properties, in preferred order.
+        // 4. Shorthand loop: For each shorthand in shorthands, follow these substeps: ...
+        // NB: Descriptors can't be shorthands.
+
+        // 5. Let value be the result of invoking serialize a CSS value of declaration.
+        auto value = descriptor.value->to_string(CSSStyleValue::SerializationMode::Normal);
+
+        // 6. Let serialized declaration be the result of invoking serialize a CSS declaration with property name property, value value, and the important flag set if declaration has its important flag set.
+        auto serialized_declaration = serialize_a_css_declaration(property, value, Important::No);
+
+        // 7. Append serialized declaration to list.
+        list.append(serialized_declaration);
+
+        // 8. Append property to already serialized.
+        // AD-HOC: Not needed as we don't have shorthands.
+    }
+
+    // 4. Return list joined with " " (U+0020).
+    return MUST(String::join(' ', list));
+}
+
+// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-csstext
+WebIDL::ExceptionOr<void> CSSFontFaceDescriptors::set_css_text(StringView value)
+{
+    // 1. If the readonly flag is set, then throw a NoModificationAllowedError exception.
+    if (is_readonly())
+        return WebIDL::NoModificationAllowedError::create(realm(), "Cannot modify properties of readonly CSSFontFaceDescriptors"_string);
+
+    // 2. Empty the declarations.
+    m_descriptors.clear();
+
+    // 3. Parse the given value and, if the return value is not the empty list, insert the items in the list into the
+    //    declarations, in specified order.
+    auto descriptors = parse_css_list_of_descriptors(Parser::ParsingParams {}, AtRuleID::FontFace, value);
+    if (!descriptors.is_empty())
+        m_descriptors = move(descriptors);
+
+    // 4. Update style attribute for the CSS declaration block.
+    update_style_attribute();
+
+    return {};
+}
+
+void CSSFontFaceDescriptors::visit_edges(Visitor& visitor)
+{
+    Base::visit_edges(visitor);
+    for (auto& descriptor : m_descriptors) {
+        descriptor.value->visit_edges(visitor);
+    }
+}
+
+RefPtr<CSSStyleValue const> CSSFontFaceDescriptors::descriptor(DescriptorID descriptor_id) const
+{
+    auto match = m_descriptors.first_matching([descriptor_id](Descriptor const& descriptor) {
+        return descriptor.descriptor_id == descriptor_id;
+    });
+    if (match.has_value())
+        return match->value;
+    return nullptr;
+}
+
+WebIDL::ExceptionOr<void> CSSFontFaceDescriptors::set_ascent_override(StringView value)
+{
+    return set_property("ascent-override"sv, value, ""sv);
+}
+
+String CSSFontFaceDescriptors::ascent_override() const
+{
+    return get_property_value("ascent-override"sv);
+}
+
+WebIDL::ExceptionOr<void> CSSFontFaceDescriptors::set_descent_override(StringView value)
+{
+    return set_property("descent-override"sv, value, ""sv);
+}
+
+String CSSFontFaceDescriptors::descent_override() const
+{
+    return get_property_value("descent-override"sv);
+}
+
+WebIDL::ExceptionOr<void> CSSFontFaceDescriptors::set_font_display(StringView value)
+{
+    return set_property("font-display"sv, value, ""sv);
+}
+
+String CSSFontFaceDescriptors::font_display() const
+{
+    return get_property_value("font-display"sv);
+}
+
+WebIDL::ExceptionOr<void> CSSFontFaceDescriptors::set_font_family(StringView value)
+{
+    return set_property("font-family"sv, value, ""sv);
+}
+
+String CSSFontFaceDescriptors::font_family() const
+{
+    return get_property_value("font-family"sv);
+}
+
+WebIDL::ExceptionOr<void> CSSFontFaceDescriptors::set_font_feature_settings(StringView value)
+{
+    return set_property("font-feature-settings"sv, value, ""sv);
+}
+
+String CSSFontFaceDescriptors::font_feature_settings() const
+{
+    return get_property_value("font-feature-settings"sv);
+}
+
+WebIDL::ExceptionOr<void> CSSFontFaceDescriptors::set_font_language_override(StringView value)
+{
+    return set_property("font-language-override"sv, value, ""sv);
+}
+
+String CSSFontFaceDescriptors::font_language_override() const
+{
+    return get_property_value("font-language-override"sv);
+}
+
+WebIDL::ExceptionOr<void> CSSFontFaceDescriptors::set_font_named_instance(StringView value)
+{
+    return set_property("font-named-instance"sv, value, ""sv);
+}
+
+String CSSFontFaceDescriptors::font_named_instance() const
+{
+    return get_property_value("font-named-instance"sv);
+}
+
+WebIDL::ExceptionOr<void> CSSFontFaceDescriptors::set_font_style(StringView value)
+{
+    return set_property("font-style"sv, value, ""sv);
+}
+
+String CSSFontFaceDescriptors::font_style() const
+{
+    return get_property_value("font-style"sv);
+}
+
+WebIDL::ExceptionOr<void> CSSFontFaceDescriptors::set_font_variation_settings(StringView value)
+{
+    return set_property("font-variation-settings"sv, value, ""sv);
+}
+
+String CSSFontFaceDescriptors::font_variation_settings() const
+{
+    return get_property_value("font-variation-settings"sv);
+}
+
+WebIDL::ExceptionOr<void> CSSFontFaceDescriptors::set_font_weight(StringView value)
+{
+    return set_property("font-weight"sv, value, ""sv);
+}
+
+String CSSFontFaceDescriptors::font_weight() const
+{
+    return get_property_value("font-weight"sv);
+}
+
+WebIDL::ExceptionOr<void> CSSFontFaceDescriptors::set_font_width(StringView value)
+{
+    return set_property("font-width"sv, value, ""sv);
+}
+
+String CSSFontFaceDescriptors::font_width() const
+{
+    return get_property_value("font-width"sv);
+}
+
+WebIDL::ExceptionOr<void> CSSFontFaceDescriptors::set_line_gap_override(StringView value)
+{
+    return set_property("line-gap-override"sv, value, ""sv);
+}
+
+String CSSFontFaceDescriptors::line_gap_override() const
+{
+    return get_property_value("line-gap-override"sv);
+}
+
+WebIDL::ExceptionOr<void> CSSFontFaceDescriptors::set_src(StringView value)
+{
+    return set_property("src"sv, value, ""sv);
+}
+
+String CSSFontFaceDescriptors::src() const
+{
+    return get_property_value("src"sv);
+}
+
+WebIDL::ExceptionOr<void> CSSFontFaceDescriptors::set_unicode_range(StringView value)
+{
+    return set_property("unicode-range"sv, value, ""sv);
+}
+
+String CSSFontFaceDescriptors::unicode_range() const
+{
+    return get_property_value("unicode-range"sv);
+}
+
+}

+ 93 - 0
Libraries/LibWeb/CSS/CSSFontFaceDescriptors.h

@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibWeb/CSS/CSSStyleDeclaration.h>
+#include <LibWeb/CSS/Descriptor.h>
+
+namespace Web::CSS {
+
+// https://drafts.csswg.org/css-fonts-4/#cssfontfacedescriptors
+class CSSFontFaceDescriptors final
+    : public CSSStyleDeclaration {
+    WEB_PLATFORM_OBJECT(CSSFontFaceDescriptors, CSSStyleDeclaration);
+    GC_DECLARE_ALLOCATOR(CSSFontFaceDescriptors);
+
+public:
+    [[nodiscard]] static GC::Ref<CSSFontFaceDescriptors> create(JS::Realm&, Vector<Descriptor>);
+
+    virtual ~CSSFontFaceDescriptors() override;
+
+    virtual void initialize(JS::Realm&) override;
+
+    virtual size_t length() const override;
+    virtual String item(size_t index) const override;
+
+    virtual WebIDL::ExceptionOr<void> set_property(StringView property, StringView value, StringView priority) override;
+    virtual WebIDL::ExceptionOr<String> remove_property(StringView property) override;
+    virtual String get_property_value(StringView property) const override;
+    virtual StringView get_property_priority(StringView property) const override;
+
+    RefPtr<CSSStyleValue const> descriptor(DescriptorID) const;
+
+    WebIDL::ExceptionOr<void> set_ascent_override(StringView value);
+    String ascent_override() const;
+
+    WebIDL::ExceptionOr<void> set_descent_override(StringView value);
+    String descent_override() const;
+
+    WebIDL::ExceptionOr<void> set_font_display(StringView value);
+    String font_display() const;
+
+    WebIDL::ExceptionOr<void> set_font_family(StringView value);
+    String font_family() const;
+
+    WebIDL::ExceptionOr<void> set_font_feature_settings(StringView value);
+    String font_feature_settings() const;
+
+    WebIDL::ExceptionOr<void> set_font_language_override(StringView value);
+    String font_language_override() const;
+
+    WebIDL::ExceptionOr<void> set_font_named_instance(StringView value);
+    String font_named_instance() const;
+
+    WebIDL::ExceptionOr<void> set_font_style(StringView value);
+    String font_style() const;
+
+    WebIDL::ExceptionOr<void> set_font_variation_settings(StringView value);
+    String font_variation_settings() const;
+
+    WebIDL::ExceptionOr<void> set_font_weight(StringView value);
+    String font_weight() const;
+
+    WebIDL::ExceptionOr<void> set_font_width(StringView value);
+    String font_width() const;
+
+    WebIDL::ExceptionOr<void> set_line_gap_override(StringView value);
+    String line_gap_override() const;
+
+    WebIDL::ExceptionOr<void> set_src(StringView value);
+    String src() const;
+
+    WebIDL::ExceptionOr<void> set_unicode_range(StringView value);
+    String unicode_range() const;
+
+    virtual String serialized() const override;
+
+    virtual WebIDL::ExceptionOr<void> set_css_text(StringView) override;
+
+private:
+    CSSFontFaceDescriptors(JS::Realm&, Vector<Descriptor>);
+
+    bool set_a_css_declaration(DescriptorID, NonnullRefPtr<CSSStyleValue const>, Important);
+
+    virtual void visit_edges(Visitor&) override;
+
+    Vector<Descriptor> m_descriptors;
+};
+
+}

+ 35 - 0
Libraries/LibWeb/CSS/CSSFontFaceDescriptors.idl

@@ -0,0 +1,35 @@
+#import <CSS/CSSStyleDeclaration.idl>
+
+// https://drafts.csswg.org/css-fonts-4/#cssfontfacedescriptors
+[Exposed=Window]
+interface CSSFontFaceDescriptors : CSSStyleDeclaration {
+    [LegacyNullToEmptyString] attribute CSSOMString src;
+    [LegacyNullToEmptyString, AttributeCallbackName=font_family_regular, ImplementedAs=font_family] attribute CSSOMString fontFamily;
+    [LegacyNullToEmptyString, AttributeCallbackName=font_family_dashed, ImplementedAs=font_family] attribute CSSOMString font-family;
+    [LegacyNullToEmptyString, AttributeCallbackName=font_style_regular, ImplementedAs=font_style] attribute CSSOMString fontStyle;
+    [LegacyNullToEmptyString, AttributeCallbackName=font_style_dashed, ImplementedAs=font_style] attribute CSSOMString font-style;
+    [LegacyNullToEmptyString, AttributeCallbackName=font_weight_regular, ImplementedAs=font_weight] attribute CSSOMString fontWeight;
+    [LegacyNullToEmptyString, AttributeCallbackName=font_weight_dashed, ImplementedAs=font_weight] attribute CSSOMString font-weight;
+    [LegacyNullToEmptyString, AttributeCallbackName=font_stretch_regular, ImplementedAs=font_width] attribute CSSOMString fontStretch;
+    [LegacyNullToEmptyString, AttributeCallbackName=font_stretch_dashed, ImplementedAs=font_width] attribute CSSOMString font-stretch;
+    [LegacyNullToEmptyString, AttributeCallbackName=font_width_regular, ImplementedAs=font_width] attribute CSSOMString fontWidth;
+    [LegacyNullToEmptyString, AttributeCallbackName=font_width_dashed, ImplementedAs=font_width] attribute CSSOMString font-width;
+    [LegacyNullToEmptyString, AttributeCallbackName=unicode_range_regular, ImplementedAs=unicode_range] attribute CSSOMString unicodeRange;
+    [LegacyNullToEmptyString, AttributeCallbackName=unicode_range_dashed, ImplementedAs=unicode_range] attribute CSSOMString unicode-range;
+    [LegacyNullToEmptyString, AttributeCallbackName=font_feature_settings_regular, ImplementedAs=font_feature_settings] attribute CSSOMString fontFeatureSettings;
+    [LegacyNullToEmptyString, AttributeCallbackName=font_feature_settings_dashed, ImplementedAs=font_feature_settings] attribute CSSOMString font-feature-settings;
+    [LegacyNullToEmptyString, AttributeCallbackName=font_variation_settings_regular, ImplementedAs=font_variation_settings] attribute CSSOMString fontVariationSettings;
+    [LegacyNullToEmptyString, AttributeCallbackName=font_variation_settings_dashed, ImplementedAs=font_variation_settings] attribute CSSOMString font-variation-settings;
+    [LegacyNullToEmptyString, AttributeCallbackName=font_named_instance_regular, ImplementedAs=font_named_instance] attribute CSSOMString fontNamedInstance;
+    [LegacyNullToEmptyString, AttributeCallbackName=font_named_instance_dashed, ImplementedAs=font_named_instance] attribute CSSOMString font-named-instance;
+    [LegacyNullToEmptyString, AttributeCallbackName=font_display_regular, ImplementedAs=font_display] attribute CSSOMString fontDisplay;
+    [LegacyNullToEmptyString, AttributeCallbackName=font_display_dashed, ImplementedAs=font_display] attribute CSSOMString font-display;
+    [LegacyNullToEmptyString, AttributeCallbackName=font_language_override_regular, ImplementedAs=font_language_override] attribute CSSOMString fontLanguageOverride;
+    [LegacyNullToEmptyString, AttributeCallbackName=font_language_override_dashed, ImplementedAs=font_language_override] attribute CSSOMString font-language-override;
+    [LegacyNullToEmptyString, AttributeCallbackName=ascent_override_regular, ImplementedAs=ascent_override] attribute CSSOMString ascentOverride;
+    [LegacyNullToEmptyString, AttributeCallbackName=ascent_override_dashed, ImplementedAs=ascent_override] attribute CSSOMString ascent-override;
+    [LegacyNullToEmptyString, AttributeCallbackName=descent_override_regular, ImplementedAs=descent_override] attribute CSSOMString descentOverride;
+    [LegacyNullToEmptyString, AttributeCallbackName=descent_override_dashed, ImplementedAs=descent_override] attribute CSSOMString descent-override;
+    [LegacyNullToEmptyString, AttributeCallbackName=line_gap_override_regular, ImplementedAs=line_gap_override] attribute CSSOMString lineGapOverride;
+    [LegacyNullToEmptyString, AttributeCallbackName=line_gap_override_dashed, ImplementedAs=line_gap_override] attribute CSSOMString line-gap-override;
+};

+ 35 - 20
Libraries/LibWeb/CSS/CSSFontFaceRule.cpp

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022-2023, Sam Atkins <atkinssj@serenityos.org>
+ * Copyright (c) 2022-2025, Sam Atkins <sam@ladybird.org>
  * Copyright (c) 2022-2023, Andreas Kling <andreas@ladybird.org>
  *
  * SPDX-License-Identifier: BSD-2-Clause
@@ -17,14 +17,14 @@ namespace Web::CSS {
 
 GC_DEFINE_ALLOCATOR(CSSFontFaceRule);
 
-GC::Ref<CSSFontFaceRule> CSSFontFaceRule::create(JS::Realm& realm, ParsedFontFace&& font_face)
+GC::Ref<CSSFontFaceRule> CSSFontFaceRule::create(JS::Realm& realm, GC::Ref<CSSFontFaceDescriptors> style)
 {
-    return realm.create<CSSFontFaceRule>(realm, move(font_face));
+    return realm.create<CSSFontFaceRule>(realm, style);
 }
 
-CSSFontFaceRule::CSSFontFaceRule(JS::Realm& realm, ParsedFontFace&& font_face)
+CSSFontFaceRule::CSSFontFaceRule(JS::Realm& realm, GC::Ref<CSSFontFaceDescriptors> style)
     : CSSRule(realm, Type::FontFace)
-    , m_font_face(move(font_face))
+    , m_style(style)
 {
 }
 
@@ -34,15 +34,24 @@ void CSSFontFaceRule::initialize(JS::Realm& realm)
     WEB_SET_PROTOTYPE_FOR_INTERFACE(CSSFontFaceRule);
 }
 
-CSSStyleDeclaration* CSSFontFaceRule::style()
+bool CSSFontFaceRule::is_valid() const
 {
-    // FIXME: Return a CSSStyleDeclaration subclass that directs changes to the ParsedFontFace.
-    return nullptr;
+    // @font-face rules require a font-family and src descriptor; if either of these are missing, the @font-face rule
+    // must not be considered when performing the font matching algorithm.
+    // https://drafts.csswg.org/css-fonts-4/#font-face-rule
+    return !m_style->descriptor(DescriptorID::FontFamily).is_null()
+        && !m_style->descriptor(DescriptorID::Src).is_null();
+}
+
+ParsedFontFace CSSFontFaceRule::font_face() const
+{
+    return ParsedFontFace::from_descriptors(m_style);
 }
 
 // https://www.w3.org/TR/cssom/#ref-for-cssfontfacerule
 String CSSFontFaceRule::serialized() const
 {
+    auto font_face = this->font_face();
     StringBuilder builder;
     // The result of concatenating the following:
 
@@ -53,18 +62,18 @@ String CSSFontFaceRule::serialized() const
     builder.append("font-family: "sv);
 
     // 3. The result of performing serialize a string on the rule’s font family name.
-    serialize_a_string(builder, m_font_face.font_family());
+    serialize_a_string(builder, font_face.font_family());
 
     // 4. The string ";", i.e., SEMICOLON (U+003B).
     builder.append(';');
 
     // 5. If the rule’s associated source list is not empty, follow these substeps:
-    if (!m_font_face.sources().is_empty()) {
+    if (!font_face.sources().is_empty()) {
         // 1. A single SPACE (U+0020), followed by the string "src:", followed by a single SPACE (U+0020).
         builder.append(" src: "sv);
 
         // 2. The result of invoking serialize a comma-separated list on performing serialize a URL or serialize a LOCAL for each source on the source list.
-        serialize_a_comma_separated_list(builder, m_font_face.sources(), [&](StringBuilder& builder, ParsedFontFace::Source source) -> void {
+        serialize_a_comma_separated_list(builder, font_face.sources(), [&](StringBuilder& builder, ParsedFontFace::Source source) -> void {
             source.local_or_url.visit(
                 [&builder](URL::URL const& url) {
                     serialize_a_url(builder, url.to_string());
@@ -89,7 +98,7 @@ String CSSFontFaceRule::serialized() const
 
     // 6. If rule’s associated unicode-range descriptor is present, a single SPACE (U+0020), followed by the string "unicode-range:", followed by a single SPACE (U+0020), followed by the result of performing serialize a <'unicode-range'>, followed by the string ";", i.e., SEMICOLON (U+003B).
     builder.append(" unicode-range: "sv);
-    serialize_unicode_ranges(builder, m_font_face.unicode_ranges());
+    serialize_unicode_ranges(builder, font_face.unicode_ranges());
     builder.append(';');
 
     // FIXME: 7. If rule’s associated font-variant descriptor is present, a single SPACE (U+0020),
@@ -101,8 +110,8 @@ String CSSFontFaceRule::serialized() const
     //    followed by the string "font-feature-settings:", followed by a single SPACE (U+0020),
     //    followed by the result of performing serialize a <'font-feature-settings'>,
     //    followed by the string ";", i.e., SEMICOLON (U+003B).
-    if (m_font_face.font_feature_settings().has_value()) {
-        auto const& feature_settings = m_font_face.font_feature_settings().value();
+    if (font_face.font_feature_settings().has_value()) {
+        auto const& feature_settings = font_face.font_feature_settings().value();
         builder.append(" font-feature-settings: "sv);
         // NOTE: We sort the tags during parsing, so they're already in the correct order.
         bool first = true;
@@ -126,12 +135,12 @@ String CSSFontFaceRule::serialized() const
     //    followed by the result of performing serialize a <'font-stretch'>,
     //    followed by the string ";", i.e., SEMICOLON (U+003B).
     // NOTE: font-stretch is now an alias for font-width, so we use that instead.
-    if (m_font_face.width().has_value()) {
+    if (font_face.width().has_value()) {
         builder.append(" font-width: "sv);
         // NOTE: font-width is supposed to always be serialized as a percentage.
         //       Right now, it's stored as a Gfx::FontWidth value, so we have to lossily convert it back.
         float percentage = 100.0f;
-        switch (m_font_face.width().value()) {
+        switch (font_face.width().value()) {
         case Gfx::FontWidth::UltraCondensed:
             percentage = 50.0f;
             break;
@@ -170,8 +179,8 @@ String CSSFontFaceRule::serialized() const
     //     followed by the string "font-weight:", followed by a single SPACE (U+0020),
     //     followed by the result of performing serialize a <'font-weight'>,
     //     followed by the string ";", i.e., SEMICOLON (U+003B).
-    if (m_font_face.weight().has_value()) {
-        auto weight = m_font_face.weight().value();
+    if (font_face.weight().has_value()) {
+        auto weight = font_face.weight().value();
         builder.append(" font-weight: "sv);
         if (weight == 400)
             builder.append("normal"sv);
@@ -186,8 +195,8 @@ String CSSFontFaceRule::serialized() const
     //     followed by the string "font-style:", followed by a single SPACE (U+0020),
     //     followed by the result of performing serialize a <'font-style'>,
     //     followed by the string ";", i.e., SEMICOLON (U+003B).
-    if (m_font_face.slope().has_value()) {
-        auto slope = m_font_face.slope().value();
+    if (font_face.slope().has_value()) {
+        auto slope = font_face.slope().value();
         builder.append(" font-style: "sv);
         if (slope == Gfx::name_to_slope("Normal"sv))
             builder.append("normal"sv);
@@ -206,4 +215,10 @@ String CSSFontFaceRule::serialized() const
     return MUST(builder.to_string());
 }
 
+void CSSFontFaceRule::visit_edges(Visitor& visitor)
+{
+    Base::visit_edges(visitor);
+    visitor.visit(m_style);
+}
+
 }

+ 9 - 6
Libraries/LibWeb/CSS/CSSFontFaceRule.h

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
+ * Copyright (c) 2022-2025, Sam Atkins <sam@ladybird.org>
  * Copyright (c) 2022, Andreas Kling <andreas@ladybird.org>
  *
  * SPDX-License-Identifier: BSD-2-Clause
@@ -7,6 +7,7 @@
 
 #pragma once
 
+#include <LibWeb/CSS/CSSFontFaceDescriptors.h>
 #include <LibWeb/CSS/CSSRule.h>
 #include <LibWeb/CSS/ParsedFontFace.h>
 
@@ -17,20 +18,22 @@ class CSSFontFaceRule final : public CSSRule {
     GC_DECLARE_ALLOCATOR(CSSFontFaceRule);
 
 public:
-    [[nodiscard]] static GC::Ref<CSSFontFaceRule> create(JS::Realm&, ParsedFontFace&&);
+    [[nodiscard]] static GC::Ref<CSSFontFaceRule> create(JS::Realm&, GC::Ref<CSSFontFaceDescriptors>);
 
     virtual ~CSSFontFaceRule() override = default;
 
-    ParsedFontFace const& font_face() const { return m_font_face; }
-    CSSStyleDeclaration* style();
+    bool is_valid() const;
+    ParsedFontFace font_face() const;
+    CSSStyleDeclaration* style() { return m_style; }
 
 private:
-    CSSFontFaceRule(JS::Realm&, ParsedFontFace&&);
+    CSSFontFaceRule(JS::Realm&, GC::Ref<CSSFontFaceDescriptors>);
 
     virtual void initialize(JS::Realm&) override;
     virtual String serialized() const override;
+    virtual void visit_edges(Visitor&) override;
 
-    ParsedFontFace m_font_face;
+    GC::Ref<CSSFontFaceDescriptors> m_style;
 };
 
 template<>

+ 1 - 0
Libraries/LibWeb/CSS/CSSStyleSheet.cpp

@@ -329,6 +329,7 @@ void CSSStyleSheet::remove_owning_document_or_shadow_root(DOM::Node& document_or
 
 void CSSStyleSheet::invalidate_owners(DOM::StyleInvalidationReason reason)
 {
+    m_did_match = {};
     for (auto& document_or_shadow_root : m_owning_documents_or_shadow_roots) {
         document_or_shadow_root->invalidate_style(reason);
         document_or_shadow_root->document().style_computer().invalidate_rule_cache();

+ 7 - 0
Libraries/LibWeb/CSS/CSSStyleValue.cpp

@@ -33,6 +33,7 @@
 #include <LibWeb/CSS/StyleValues/FilterValueListStyleValue.h>
 #include <LibWeb/CSS/StyleValues/FitContentStyleValue.h>
 #include <LibWeb/CSS/StyleValues/FlexStyleValue.h>
+#include <LibWeb/CSS/StyleValues/FontSourceStyleValue.h>
 #include <LibWeb/CSS/StyleValues/FrequencyStyleValue.h>
 #include <LibWeb/CSS/StyleValues/GridAutoFlowStyleValue.h>
 #include <LibWeb/CSS/StyleValues/GridTemplateAreaStyleValue.h>
@@ -196,6 +197,12 @@ FlexStyleValue const& CSSStyleValue::as_flex() const
     return static_cast<FlexStyleValue const&>(*this);
 }
 
+FontSourceStyleValue const& CSSStyleValue::as_font_source() const
+{
+    VERIFY(is_font_source());
+    return static_cast<FontSourceStyleValue const&>(*this);
+}
+
 FrequencyStyleValue const& CSSStyleValue::as_frequency() const
 {
     VERIFY(is_frequency());

+ 5 - 0
Libraries/LibWeb/CSS/CSSStyleValue.h

@@ -106,6 +106,7 @@ public:
         FilterValueList,
         FitContent,
         Flex,
+        FontSource,
         FontVariant,
         Frequency,
         GridAutoFlow,
@@ -228,6 +229,10 @@ public:
     FlexStyleValue const& as_flex() const;
     FlexStyleValue& as_flex() { return const_cast<FlexStyleValue&>(const_cast<CSSStyleValue const&>(*this).as_flex()); }
 
+    bool is_font_source() const { return type() == Type::FontSource; }
+    FontSourceStyleValue const& as_font_source() const;
+    FontSourceStyleValue& as_font_source() { return const_cast<FontSourceStyleValue&>(const_cast<CSSStyleValue const&>(*this).as_font_source()); }
+
     bool is_frequency() const { return type() == Type::Frequency; }
     FrequencyStyleValue const& as_frequency() const;
     FrequencyStyleValue& as_frequency() { return const_cast<FrequencyStyleValue&>(const_cast<CSSStyleValue const&>(*this).as_frequency()); }

+ 14 - 0
Libraries/LibWeb/CSS/Descriptor.cpp

@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibWeb/CSS/CSSStyleValue.h>
+#include <LibWeb/CSS/Descriptor.h>
+
+namespace Web::CSS {
+
+Descriptor::~Descriptor() = default;
+
+}

+ 22 - 0
Libraries/LibWeb/CSS/Descriptor.h

@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/NonnullRefPtr.h>
+#include <LibWeb/CSS/DescriptorID.h>
+#include <LibWeb/Forward.h>
+
+namespace Web::CSS {
+
+struct Descriptor {
+    ~Descriptor();
+
+    DescriptorID descriptor_id;
+    NonnullRefPtr<CSSStyleValue const> value;
+};
+
+}

+ 129 - 0
Libraries/LibWeb/CSS/Descriptors.json

@@ -0,0 +1,129 @@
+{
+  "font-face": {
+    "spec": "https://drafts.csswg.org/css-fonts-4/#at-font-face-rule",
+    "descriptors": {
+      "ascent-override": {
+        "initial": "normal",
+        "syntax": [
+          "normal",
+          "<percentage [0,∞]>"
+        ]
+      },
+      "descent-override": {
+        "initial": "normal",
+        "syntax": [
+          "normal",
+          "<percentage [0,∞]>"
+        ]
+      },
+      "font-display": {
+        "initial": "auto",
+        "syntax": [
+          "auto",
+          "block",
+          "swap",
+          "fallback",
+          "optional"
+        ]
+      },
+      "font-family": {
+        "syntax": [
+          "<family-name>"
+        ]
+      },
+      "font-feature-settings": {
+        "initial": "normal",
+        "syntax": [
+          "<'font-feature-settings'>"
+        ]
+      },
+      "font-language-override": {
+        "initial": "normal",
+        "syntax": [
+          "normal",
+          "<string>"
+        ]
+      },
+      "font-named-instance": {
+        "initial": "auto",
+        "syntax": [
+          "auto",
+          "<string>"
+        ]
+      },
+      "font-stretch": {
+        "legacy-alias-for": "font-width"
+      },
+      "font-style": {
+        "initial": "auto",
+        "FIXME": "Support angles for oblique",
+        "syntax": [
+          "auto",
+          "<'font-style'>"
+        ]
+      },
+      "font-variation-settings": {
+        "initial": "normal",
+        "syntax": [
+          "<'font-variation-settings'>"
+        ]
+      },
+      "font-weight": {
+        "initial": "auto",
+        "FIXME": "Support multiple font-weight values; disallow relative font-weights",
+        "syntax": [
+          "auto",
+          "<'font-weight'>"
+        ]
+      },
+      "font-width": {
+        "initial": "auto",
+        "FIXME": "Support multiple font-width values",
+        "syntax": [
+          "auto",
+          "<'font-width'>"
+        ]
+      },
+      "line-gap-override": {
+        "initial": "normal",
+        "syntax": [
+          "normal",
+          "<percentage [0,∞]>"
+        ]
+      },
+      "src": {
+        "syntax": [
+          "<font-src-list>"
+        ]
+      },
+      "unicode-range": {
+        "initial": "U+0-10FFFF",
+        "NOTE": "unicode-range has special parsing rules so it's parsed manually.",
+        "syntax": [
+          "<unicode-range-token>#"
+        ]
+      }
+    }
+  },
+  "property": {
+    "spec": "https://drafts.css-houdini.org/css-properties-values-api/#at-property-rule",
+    "descriptors": {
+      "inherits": {
+        "syntax": [
+          "true",
+          "false"
+        ]
+      },
+      "initial-value": {
+        "syntax": [
+          "<declaration-value>?"
+        ]
+      },
+      "syntax": {
+        "syntax": [
+          "<string>"
+        ]
+      }
+    }
+  }
+}

+ 2 - 0
Libraries/LibWeb/CSS/Keywords.json

@@ -170,6 +170,7 @@
   "extra-condensed",
   "extra-expanded",
   "fallback",
+  "false",
   "fantasy",
   "fast",
   "field",
@@ -451,6 +452,7 @@
   "to-zero",
   "top",
   "traditional",
+  "true",
   "ui-monospace",
   "ui-rounded",
   "ui-sans-serif",

+ 175 - 1
Libraries/LibWeb/CSS/ParsedFontFace.cpp

@@ -1,14 +1,188 @@
 /*
- * Copyright (c) 2022-2024, Sam Atkins <sam@ladybird.org>
+ * Copyright (c) 2022-2025, Sam Atkins <sam@ladybird.org>
  * Copyright (c) 2023, Andreas Kling <andreas@ladybird.org>
  *
  * SPDX-License-Identifier: BSD-2-Clause
  */
 
+#include <LibWeb/CSS/CSSFontFaceDescriptors.h>
 #include <LibWeb/CSS/ParsedFontFace.h>
+#include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
+#include <LibWeb/CSS/StyleValues/CalculatedStyleValue.h>
+#include <LibWeb/CSS/StyleValues/CustomIdentStyleValue.h>
+#include <LibWeb/CSS/StyleValues/FontSourceStyleValue.h>
+#include <LibWeb/CSS/StyleValues/IntegerStyleValue.h>
+#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
+#include <LibWeb/CSS/StyleValues/OpenTypeTaggedStyleValue.h>
+#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
+#include <LibWeb/CSS/StyleValues/StringStyleValue.h>
+#include <LibWeb/CSS/StyleValues/StyleValueList.h>
+#include <LibWeb/CSS/StyleValues/UnicodeRangeStyleValue.h>
 
 namespace Web::CSS {
 
+ParsedFontFace ParsedFontFace::from_descriptors(CSSFontFaceDescriptors const& descriptors)
+{
+    auto extract_font_name = [](CSSStyleValue const& value) {
+        if (value.is_string())
+            return value.as_string().string_value();
+        if (value.is_custom_ident())
+            return value.as_custom_ident().custom_ident();
+        return FlyString {};
+    };
+
+    auto extract_percentage_or_normal = [](CSSStyleValue const& value) -> Optional<Percentage> {
+        if (value.is_percentage())
+            return value.as_percentage().percentage();
+        if (value.is_calculated()) {
+            // FIXME: These should probably be simplified already?
+            return value.as_calculated().resolve_percentage({});
+        }
+        if (value.to_keyword() == Keyword::Normal)
+            return {};
+
+        return {};
+    };
+
+    FlyString font_family;
+    if (auto value = descriptors.descriptor(DescriptorID::FontFamily))
+        font_family = extract_font_name(*value);
+
+    Optional<int> weight;
+    if (auto value = descriptors.descriptor(DescriptorID::FontWeight))
+        weight = value->to_font_weight();
+
+    Optional<int> slope;
+    if (auto value = descriptors.descriptor(DescriptorID::FontStyle))
+        slope = value->to_font_slope();
+
+    Optional<int> width;
+    if (auto value = descriptors.descriptor(DescriptorID::FontWidth))
+        width = value->to_font_width();
+
+    Vector<Source> sources;
+    if (auto value = descriptors.descriptor(DescriptorID::Src)) {
+        auto add_source = [&sources, extract_font_name](FontSourceStyleValue const& font_source) {
+            font_source.source().visit(
+                [&](FontSourceStyleValue::Local const& local) {
+                    sources.empend(extract_font_name(local.name), OptionalNone {});
+                },
+                [&](URL::URL const& url) {
+                    // FIXME: tech()
+                    sources.empend(url, font_source.format());
+                });
+        };
+
+        if (value->is_font_source()) {
+            add_source(value->as_font_source());
+        } else if (value->is_value_list()) {
+            for (auto const& source : value->as_value_list().values())
+                add_source(source->as_font_source());
+        }
+    }
+
+    Vector<Gfx::UnicodeRange> unicode_ranges;
+    if (auto value = descriptors.descriptor(DescriptorID::UnicodeRange)) {
+        if (value->is_unicode_range()) {
+            unicode_ranges.append(value->as_unicode_range().unicode_range());
+        } else if (value->is_value_list()) {
+            for (auto const& range : value->as_value_list().values())
+                unicode_ranges.append(range->as_unicode_range().unicode_range());
+        }
+    }
+    if (unicode_ranges.is_empty())
+        unicode_ranges.empend(0x0u, 0x10FFFFu);
+
+    Optional<Percentage> ascent_override;
+    if (auto value = descriptors.descriptor(DescriptorID::AscentOverride))
+        ascent_override = extract_percentage_or_normal(*value);
+
+    Optional<Percentage> descent_override;
+    if (auto value = descriptors.descriptor(DescriptorID::DescentOverride))
+        descent_override = extract_percentage_or_normal(*value);
+
+    Optional<Percentage> line_gap_override;
+    if (auto value = descriptors.descriptor(DescriptorID::LineGapOverride))
+        line_gap_override = extract_percentage_or_normal(*value);
+
+    FontDisplay font_display;
+    if (auto value = descriptors.descriptor(DescriptorID::FontDisplay))
+        font_display = keyword_to_font_display(value->to_keyword()).value_or(FontDisplay::Auto);
+
+    Optional<FlyString> font_named_instance;
+    if (auto value = descriptors.descriptor(DescriptorID::FontNamedInstance)) {
+        if (value->is_string())
+            font_named_instance = value->as_string().string_value();
+    }
+
+    Optional<FlyString> font_language_override;
+    if (auto value = descriptors.descriptor(DescriptorID::FontLanguageOverride)) {
+        if (value->is_string())
+            font_language_override = value->as_string().string_value();
+    }
+
+    Optional<OrderedHashMap<FlyString, i64>> font_feature_settings;
+    if (auto value = descriptors.descriptor(DescriptorID::FontFeatureSettings)) {
+        if (value->to_keyword() == Keyword::Normal) {
+            font_feature_settings.clear();
+        } else if (value->is_value_list()) {
+            auto const& feature_tags = value->as_value_list().values();
+            OrderedHashMap<FlyString, i64> settings;
+            settings.ensure_capacity(feature_tags.size());
+            for (auto const& feature_tag : feature_tags) {
+                auto const& setting_value = feature_tag->as_open_type_tagged().value();
+                if (setting_value->is_integer()) {
+                    settings.set(feature_tag->as_open_type_tagged().tag(), setting_value->as_integer().integer());
+                } else if (setting_value->is_calculated() && setting_value->as_calculated().resolves_to_number()) {
+                    if (auto integer = setting_value->as_calculated().resolve_integer({}); integer.has_value()) {
+                        settings.set(feature_tag->as_open_type_tagged().tag(), *integer);
+                    }
+                }
+            }
+            font_feature_settings = move(settings);
+        }
+    }
+
+    Optional<OrderedHashMap<FlyString, double>> font_variation_settings;
+    if (auto value = descriptors.descriptor(DescriptorID::FontVariationSettings)) {
+        if (value->to_keyword() == Keyword::Normal) {
+            font_variation_settings.clear();
+        } else if (value->is_value_list()) {
+            auto const& variation_tags = value->as_value_list().values();
+            OrderedHashMap<FlyString, double> settings;
+            settings.ensure_capacity(variation_tags.size());
+            for (auto const& variation_tag : variation_tags) {
+                auto const& setting_value = variation_tag->as_open_type_tagged().value();
+                if (setting_value->is_number()) {
+                    settings.set(variation_tag->as_open_type_tagged().tag(), setting_value->as_number().number());
+                } else if (setting_value->is_calculated() && setting_value->as_calculated().resolves_to_number()) {
+                    if (auto number = setting_value->as_calculated().resolve_number({}); number.has_value()) {
+                        settings.set(variation_tag->as_open_type_tagged().tag(), *number);
+                    }
+                }
+            }
+            font_variation_settings = move(settings);
+        }
+    }
+
+    return ParsedFontFace {
+        move(font_family),
+        move(weight),
+        move(slope),
+        move(width),
+        move(sources),
+        move(unicode_ranges),
+        move(ascent_override),
+        move(descent_override),
+        move(line_gap_override),
+        move(font_display),
+        move(font_named_instance),
+        move(font_language_override),
+        move(font_feature_settings),
+        move(font_variation_settings)
+    };
+}
+
 ParsedFontFace::ParsedFontFace(FlyString font_family, Optional<int> weight, Optional<int> slope, Optional<int> width, Vector<Source> sources, Vector<Gfx::UnicodeRange> unicode_ranges, Optional<Percentage> ascent_override, Optional<Percentage> descent_override, Optional<Percentage> line_gap_override, FontDisplay font_display, Optional<FlyString> font_named_instance, Optional<FlyString> font_language_override, Optional<OrderedHashMap<FlyString, i64>> font_feature_settings, Optional<OrderedHashMap<FlyString, double>> font_variation_settings)
     : m_font_family(move(font_family))
     , m_font_named_instance(move(font_named_instance))

+ 3 - 1
Libraries/LibWeb/CSS/ParsedFontFace.h

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022-2024, Sam Atkins <sam@ladybird.org>
+ * Copyright (c) 2022-2025, Sam Atkins <sam@ladybird.org>
  * Copyright (c) 2023, Andreas Kling <andreas@ladybird.org>
  *
  * SPDX-License-Identifier: BSD-2-Clause
@@ -24,6 +24,8 @@ public:
         Optional<FlyString> format;
     };
 
+    static ParsedFontFace from_descriptors(CSSFontFaceDescriptors const&);
+
     ParsedFontFace(FlyString font_family, Optional<int> weight, Optional<int> slope, Optional<int> width, Vector<Source> sources, Vector<Gfx::UnicodeRange> unicode_ranges, Optional<Percentage> ascent_override, Optional<Percentage> descent_override, Optional<Percentage> line_gap_override, FontDisplay font_display, Optional<FlyString> font_named_instance, Optional<FlyString> font_language_override, Optional<OrderedHashMap<FlyString, i64>> font_feature_settings, Optional<OrderedHashMap<FlyString, double>> font_variation_settings);
     ~ParsedFontFace() = default;
 

+ 135 - 0
Libraries/LibWeb/CSS/Parser/DescriptorParsing.cpp

@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibWeb/CSS/Parser/Parser.h>
+#include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
+#include <LibWeb/CSS/StyleValues/FontSourceStyleValue.h>
+#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
+#include <LibWeb/CSS/StyleValues/StringStyleValue.h>
+#include <LibWeb/CSS/StyleValues/UnicodeRangeStyleValue.h>
+#include <LibWeb/CSS/StyleValues/UnresolvedStyleValue.h>
+
+namespace Web::CSS::Parser {
+
+Parser::ParseErrorOr<NonnullRefPtr<CSSStyleValue>> Parser::parse_descriptor_value(AtRuleID at_rule_id, DescriptorID descriptor_id, TokenStream<ComponentValue>& unprocessed_tokens)
+{
+    if (!at_rule_supports_descriptor(at_rule_id, descriptor_id)) {
+        dbgln_if(CSS_PARSER_DEBUG, "Unsupported descriptor '{}' in '{}'", to_string(descriptor_id), to_string(at_rule_id));
+        return ParseError::SyntaxError;
+    }
+
+    auto context_guard = push_temporary_value_parsing_context(DescriptorContext { at_rule_id, descriptor_id });
+
+    Vector<ComponentValue> component_values;
+    while (unprocessed_tokens.has_next_token()) {
+        if (unprocessed_tokens.peek_token().is(Token::Type::Semicolon))
+            break;
+
+        // FIXME: Stop removing whitespace here. It's just for compatibility with the property-parsing code.
+        auto const& token = unprocessed_tokens.consume_a_token();
+        if (token.is(Token::Type::Whitespace))
+            continue;
+
+        component_values.append(token);
+    }
+
+    TokenStream tokens { component_values };
+    auto metadata = get_descriptor_metadata(at_rule_id, descriptor_id);
+    for (auto const& option : metadata.syntax) {
+        auto transaction = tokens.begin_transaction();
+        auto parsed_style_value = option.visit(
+            [&](Keyword keyword) {
+                return parse_all_as_single_keyword_value(tokens, keyword);
+            },
+            [&](PropertyID property_id) -> RefPtr<CSSStyleValue> {
+                auto value_or_error = parse_css_value(property_id, tokens);
+                if (value_or_error.is_error())
+                    return nullptr;
+                auto value_for_property = value_or_error.release_value();
+                // Descriptors don't accept the following, which properties do:
+                // - CSS-wide keywords
+                // - Shorthands
+                // - Arbitrary substitution functions (so, UnresolvedStyleValue)
+                if (value_for_property->is_css_wide_keyword() || value_for_property->is_shorthand() || value_for_property->is_unresolved())
+                    return nullptr;
+                return value_for_property;
+            },
+            [&](DescriptorMetadata::ValueType value_type) -> RefPtr<CSSStyleValue> {
+                switch (value_type) {
+                case DescriptorMetadata::ValueType::FamilyName:
+                    return parse_family_name_value(tokens);
+                case DescriptorMetadata::ValueType::FontSrcList:
+                    return parse_comma_separated_value_list(tokens, [this](auto& tokens) -> RefPtr<CSSStyleValue> {
+                        return parse_font_source_value(tokens);
+                    });
+                case DescriptorMetadata::ValueType::OptionalDeclarationValue: {
+                    // FIXME: This is for an @property's initial value. Figure out what this should actually do once we need it.
+                    StringBuilder initial_value_sb;
+                    while (tokens.has_next_token())
+                        initial_value_sb.append(tokens.consume_a_token().to_string());
+                    return StringStyleValue::create(initial_value_sb.to_fly_string_without_validation());
+                }
+                case DescriptorMetadata::ValueType::PositivePercentage:
+                    if (auto percentage_value = parse_percentage_value(tokens)) {
+                        if (percentage_value->is_percentage()) {
+                            if (percentage_value->as_percentage().value() < 0)
+                                return nullptr;
+                            return percentage_value.release_nonnull();
+                        }
+                        // All calculations in descriptors must be resolvable at parse-time.
+                        if (percentage_value->is_calculated()) {
+                            auto percentage = percentage_value->as_calculated().resolve_percentage({});
+                            if (percentage.has_value() && percentage->value() >= 0)
+                                return PercentageStyleValue::create(percentage.release_value());
+                            return nullptr;
+                        }
+                    }
+                    return nullptr;
+                case DescriptorMetadata::ValueType::String:
+                    return parse_string_value(tokens);
+                case DescriptorMetadata::ValueType::UnicodeRangeTokens:
+                    return parse_comma_separated_value_list(tokens, [this](auto& tokens) -> RefPtr<CSSStyleValue> {
+                        return parse_unicode_range_value(tokens);
+                    });
+                }
+                return nullptr;
+            });
+        if (!parsed_style_value || tokens.has_next_token())
+            continue;
+        transaction.commit();
+        return parsed_style_value.release_nonnull();
+    }
+
+    if constexpr (CSS_PARSER_DEBUG) {
+        dbgln("Failed to parse descriptor '{}' in '{}'", to_string(descriptor_id), to_string(at_rule_id));
+        tokens.dump_all_tokens();
+    }
+
+    return ParseError::SyntaxError;
+}
+
+Optional<Descriptor> Parser::convert_to_descriptor(AtRuleID at_rule_id, Declaration const& declaration)
+{
+    auto descriptor_id = descriptor_id_from_string(at_rule_id, declaration.name);
+    if (!descriptor_id.has_value())
+        return {};
+
+    auto value_token_stream = TokenStream(declaration.value);
+    auto value = parse_descriptor_value(at_rule_id, descriptor_id.value(), value_token_stream);
+    if (value.is_error()) {
+        if (value.error() == ParseError::SyntaxError) {
+            if constexpr (CSS_PARSER_DEBUG) {
+                dbgln("Unable to parse value for CSS @{} descriptor '{}'.", to_string(at_rule_id), declaration.name);
+                value_token_stream.dump_all_tokens();
+            }
+        }
+        return {};
+    }
+
+    return Descriptor { *descriptor_id, value.release_value() };
+}
+
+}

+ 14 - 0
Libraries/LibWeb/CSS/Parser/Helpers.cpp

@@ -64,6 +64,13 @@ CSS::Parser::Parser::PropertiesAndCustomProperties parse_css_style_attribute(CSS
     return CSS::Parser::Parser::create(context, css).parse_as_style_attribute();
 }
 
+Vector<CSS::Descriptor> parse_css_list_of_descriptors(CSS::Parser::ParsingParams const& parsing_params, CSS::AtRuleID at_rule_id, StringView css)
+{
+    if (css.is_empty())
+        return {};
+    return CSS::Parser::Parser::create(parsing_params, css).parse_as_list_of_descriptors(at_rule_id);
+}
+
 RefPtr<CSS::CSSStyleValue> parse_css_value(CSS::Parser::ParsingParams const& context, StringView string, CSS::PropertyID property_id)
 {
     if (string.is_empty())
@@ -71,6 +78,13 @@ RefPtr<CSS::CSSStyleValue> parse_css_value(CSS::Parser::ParsingParams const& con
     return CSS::Parser::Parser::create(context, string).parse_as_css_value(property_id);
 }
 
+RefPtr<CSS::CSSStyleValue> parse_css_descriptor(CSS::Parser::ParsingParams const& parsing_params, CSS::AtRuleID at_rule_id, CSS::DescriptorID descriptor_id, StringView string)
+{
+    if (string.is_empty())
+        return nullptr;
+    return CSS::Parser::Parser::create(parsing_params, string).parse_as_descriptor_value(at_rule_id, descriptor_id);
+}
+
 CSS::CSSRule* parse_css_rule(CSS::Parser::ParsingParams const& context, StringView css_text)
 {
     return CSS::Parser::Parser::create(context, css_text).parse_as_css_rule();

+ 49 - 4
Libraries/LibWeb/CSS/Parser/Parser.cpp

@@ -1350,6 +1350,36 @@ Parser::PropertiesAndCustomProperties Parser::parse_as_style_attribute()
     return properties;
 }
 
+Vector<Descriptor> Parser::parse_as_list_of_descriptors(AtRuleID at_rule_id)
+{
+    auto context_type = [at_rule_id] {
+        switch (at_rule_id) {
+        case AtRuleID::FontFace:
+            return ContextType::AtFontFace;
+        case AtRuleID::Property:
+            return ContextType::AtProperty;
+        }
+        VERIFY_NOT_REACHED();
+    }();
+
+    m_rule_context.append(context_type);
+    auto declarations_and_at_rules = parse_a_blocks_contents(m_token_stream);
+    m_rule_context.take_last();
+
+    Vector<Descriptor> descriptors;
+    for (auto const& rule_or_list : declarations_and_at_rules) {
+        if (rule_or_list.has<Rule>())
+            continue;
+
+        auto& declarations = rule_or_list.get<Vector<Declaration>>();
+        for (auto const& declaration : declarations) {
+            if (auto descriptor = convert_to_descriptor(at_rule_id, declaration); descriptor.has_value())
+                descriptors.append(descriptor.release_value());
+        }
+    }
+    return descriptors;
+}
+
 bool Parser::is_valid_in_the_current_context(Declaration const&) const
 {
     // TODO: Determine if this *particular* declaration is valid here, not just declarations in general.
@@ -1397,14 +1427,18 @@ bool Parser::is_valid_in_the_current_context(AtRule const& at_rule) const
     if (m_rule_context.is_empty())
         return true;
 
+    // Only grouping rules can be nested within style rules
+    if (m_rule_context.contains_slow(ContextType::Style))
+        return first_is_one_of(at_rule.name, "layer", "media", "supports");
+
     switch (m_rule_context.last()) {
     case ContextType::Unknown:
         // If the context is an unknown type, we don't accept anything.
         return false;
 
     case ContextType::Style:
-        // Style rules can contain grouping rules
-        return first_is_one_of(at_rule.name, "layer", "media", "supports");
+        // Already handled above
+        VERIFY_NOT_REACHED();
 
     case ContextType::AtLayer:
     case ContextType::AtMedia:
@@ -1574,9 +1608,10 @@ bool Parser::context_allows_quirky_length() const
     for (auto i = 1u; i < m_value_context.size() && unitless_length_allowed; i++) {
         unitless_length_allowed = m_value_context[i].visit(
             [](PropertyID const& property_id) { return property_has_quirk(property_id, Quirk::UnitlessLength); },
-            [top_level_property](Parser::FunctionContext const& function_context) {
+            [top_level_property](FunctionContext const& function_context) {
                 return function_context.name == "rect"sv && top_level_property == PropertyID::Clip;
-            });
+            },
+            [](DescriptorContext const&) { return false; });
     }
 
     return unitless_length_allowed;
@@ -1602,6 +1637,16 @@ RefPtr<CSSStyleValue> Parser::parse_as_css_value(PropertyID property_id)
     return parsed_value.release_value();
 }
 
+RefPtr<CSSStyleValue> Parser::parse_as_descriptor_value(AtRuleID at_rule_id, DescriptorID descriptor_id)
+{
+    auto component_values = parse_a_list_of_component_values(m_token_stream);
+    auto tokens = TokenStream(component_values);
+    auto parsed_value = parse_descriptor_value(at_rule_id, descriptor_id, tokens);
+    if (parsed_value.is_error())
+        return nullptr;
+    return parsed_value.release_value();
+}
+
 // https://html.spec.whatwg.org/multipage/images.html#parsing-a-sizes-attribute
 LengthOrCalculated Parser::parse_as_sizes_attribute(DOM::Element const& element, HTML::HTMLImageElement const* img)
 {

+ 16 - 2
Libraries/LibWeb/CSS/Parser/Parser.h

@@ -16,6 +16,8 @@
 #include <LibWeb/CSS/BooleanExpression.h>
 #include <LibWeb/CSS/CSSStyleDeclaration.h>
 #include <LibWeb/CSS/CSSStyleValue.h>
+#include <LibWeb/CSS/Descriptor.h>
+#include <LibWeb/CSS/DescriptorID.h>
 #include <LibWeb/CSS/MediaQuery.h>
 #include <LibWeb/CSS/ParsedFontFace.h>
 #include <LibWeb/CSS/Parser/ComponentValue.h>
@@ -94,6 +96,7 @@ public:
         HashMap<FlyString, StyleProperty> custom_properties;
     };
     PropertiesAndCustomProperties parse_as_style_attribute();
+    Vector<Descriptor> parse_as_list_of_descriptors(AtRuleID);
     CSSRule* parse_as_css_rule();
     Optional<StyleProperty> parse_as_supports_condition();
 
@@ -116,6 +119,7 @@ public:
     RefPtr<Supports> parse_as_supports();
 
     RefPtr<CSSStyleValue> parse_as_css_value(PropertyID);
+    RefPtr<CSSStyleValue> parse_as_descriptor_value(AtRuleID, DescriptorID);
 
     Optional<ComponentValue> parse_as_component_value();
 
@@ -248,6 +252,8 @@ private:
     GC::Ref<CSSStyleProperties> convert_to_style_declaration(Vector<Declaration> const&);
     Optional<StyleProperty> convert_to_style_property(Declaration const&);
 
+    Optional<Descriptor> convert_to_descriptor(AtRuleID, Declaration const&);
+
     Optional<Dimension> parse_dimension(ComponentValue const&);
     Optional<AngleOrCalculated> parse_angle(TokenStream<ComponentValue>&);
     Optional<AnglePercentage> parse_angle_percentage(TokenStream<ComponentValue>&);
@@ -294,6 +300,7 @@ private:
     RefPtr<RadialGradientStyleValue> parse_radial_gradient_function(TokenStream<ComponentValue>&);
 
     ParseErrorOr<NonnullRefPtr<CSSStyleValue>> parse_css_value(PropertyID, TokenStream<ComponentValue>&, Optional<String> original_source_text = {});
+    ParseErrorOr<NonnullRefPtr<CSSStyleValue>> parse_descriptor_value(AtRuleID, DescriptorID, TokenStream<ComponentValue>&);
     RefPtr<CSSStyleValue> parse_css_value_for_property(PropertyID, TokenStream<ComponentValue>&);
     struct PropertyAndValue {
         PropertyID property;
@@ -341,6 +348,7 @@ private:
     RefPtr<PositionStyleValue> parse_position_value(TokenStream<ComponentValue>&, PositionParsingMode = PositionParsingMode::Normal);
     RefPtr<CSSStyleValue> parse_filter_value_list_value(TokenStream<ComponentValue>&);
     RefPtr<StringStyleValue> parse_opentype_tag_value(TokenStream<ComponentValue>&);
+    RefPtr<FontSourceStyleValue> parse_font_source_value(TokenStream<ComponentValue>&);
 
     RefPtr<CSSStyleValue> parse_angle_value(TokenStream<ComponentValue>&);
     RefPtr<CSSStyleValue> parse_angle_percentage_value(TokenStream<ComponentValue>&);
@@ -358,7 +366,7 @@ private:
     RefPtr<CSSStyleValue> parse_time_value(TokenStream<ComponentValue>&);
     RefPtr<CSSStyleValue> parse_time_percentage_value(TokenStream<ComponentValue>&);
 
-    template<typename ParseFunction>
+    using ParseFunction = AK::Function<RefPtr<CSSStyleValue>(TokenStream<ComponentValue>&)>;
     RefPtr<CSSStyleValue> parse_comma_separated_value_list(TokenStream<ComponentValue>&, ParseFunction);
     RefPtr<CSSStyleValue> parse_simple_comma_separated_value_list(PropertyID, TokenStream<ComponentValue>&);
     RefPtr<CSSStyleValue> parse_all_as_single_keyword_value(TokenStream<ComponentValue>&, Keyword);
@@ -481,7 +489,11 @@ private:
     struct FunctionContext {
         StringView name;
     };
-    using ValueParsingContext = Variant<PropertyID, FunctionContext>;
+    struct DescriptorContext {
+        AtRuleID at_rule;
+        DescriptorID descriptor;
+    };
+    using ValueParsingContext = Variant<PropertyID, FunctionContext, DescriptorContext>;
     Vector<ValueParsingContext> m_value_context;
     auto push_temporary_value_parsing_context(ValueParsingContext&& context)
     {
@@ -514,7 +526,9 @@ namespace Web {
 
 CSS::CSSStyleSheet* parse_css_stylesheet(CSS::Parser::ParsingParams const&, StringView, Optional<URL::URL> location = {}, Vector<NonnullRefPtr<CSS::MediaQuery>> = {});
 CSS::Parser::Parser::PropertiesAndCustomProperties parse_css_style_attribute(CSS::Parser::ParsingParams const&, StringView);
+Vector<CSS::Descriptor> parse_css_list_of_descriptors(CSS::Parser::ParsingParams const&, CSS::AtRuleID, StringView);
 RefPtr<CSS::CSSStyleValue> parse_css_value(CSS::Parser::ParsingParams const&, StringView, CSS::PropertyID property_id = CSS::PropertyID::Invalid);
+RefPtr<CSS::CSSStyleValue> parse_css_descriptor(CSS::Parser::ParsingParams const&, CSS::AtRuleID, CSS::DescriptorID, StringView);
 Optional<CSS::SelectorList> parse_selector(CSS::Parser::ParsingParams const&, StringView);
 Optional<CSS::SelectorList> parse_selector_for_nested_style_rule(CSS::Parser::ParsingParams const&, StringView);
 Optional<CSS::Selector::PseudoElementSelector> parse_pseudo_element_selector(CSS::Parser::ParsingParams const&, StringView);

+ 0 - 24
Libraries/LibWeb/CSS/Parser/PropertyParsing.cpp

@@ -80,30 +80,6 @@ RefPtr<CSSStyleValue> Parser::parse_all_as_single_keyword_value(TokenStream<Comp
     return keyword_value;
 }
 
-template<typename ParseFunction>
-RefPtr<CSSStyleValue> Parser::parse_comma_separated_value_list(TokenStream<ComponentValue>& tokens, ParseFunction parse_one_value)
-{
-    auto first = parse_one_value(tokens);
-    if (!first || !tokens.has_next_token())
-        return first;
-
-    StyleValueVector values;
-    values.append(first.release_nonnull());
-
-    while (tokens.has_next_token()) {
-        if (!tokens.consume_a_token().is(Token::Type::Comma))
-            return nullptr;
-
-        if (auto maybe_value = parse_one_value(tokens)) {
-            values.append(maybe_value.release_nonnull());
-            continue;
-        }
-        return nullptr;
-    }
-
-    return StyleValueList::create(move(values), StyleValueList::Separator::Comma);
-}
-
 RefPtr<CSSStyleValue> Parser::parse_simple_comma_separated_value_list(PropertyID property_id, TokenStream<ComponentValue>& tokens)
 {
     return parse_comma_separated_value_list(tokens, [this, property_id](auto& tokens) -> RefPtr<CSSStyleValue> {

+ 10 - 284
Libraries/LibWeb/CSS/Parser/RuleParsing.cpp

@@ -699,296 +699,22 @@ template Vector<ParsedFontFace::Source> Parser::parse_font_face_src(TokenStream<
 GC::Ptr<CSSFontFaceRule> Parser::convert_to_font_face_rule(AtRule const& rule)
 {
     // https://drafts.csswg.org/css-fonts/#font-face-rule
-
-    Optional<FlyString> font_family;
-    Optional<FlyString> font_named_instance;
-    Vector<ParsedFontFace::Source> src;
-    Vector<Gfx::UnicodeRange> unicode_range;
-    Optional<int> weight;
-    Optional<int> slope;
-    Optional<int> width;
-    Optional<Percentage> ascent_override;
-    Optional<Percentage> descent_override;
-    Optional<Percentage> line_gap_override;
-    FontDisplay font_display = FontDisplay::Auto;
-    Optional<FlyString> language_override;
-    Optional<OrderedHashMap<FlyString, i64>> font_feature_settings;
-    Optional<OrderedHashMap<FlyString, double>> font_variation_settings;
-
-    // "normal" is returned as nullptr
-    auto parse_as_percentage_or_normal = [&](Vector<ComponentValue> const& values) -> ErrorOr<Optional<Percentage>> {
-        // normal | <percentage [0,∞]>
-        TokenStream tokens { values };
-        if (auto percentage_value = parse_percentage_value(tokens)) {
-            tokens.discard_whitespace();
-            if (tokens.has_next_token())
-                return Error::from_string_literal("Unexpected trailing tokens");
-
-            if (percentage_value->is_percentage() && percentage_value->as_percentage().percentage().value() >= 0)
-                return percentage_value->as_percentage().percentage();
-
-            // TODO: Once we implement calc-simplification in the parser, we should no longer see math values here,
-            //       unless they're impossible to resolve and thus invalid.
-            if (percentage_value->is_calculated()) {
-                if (auto result = percentage_value->as_calculated().resolve_percentage({}); result.has_value())
-                    return result.value();
-            }
-
-            return Error::from_string_literal("Invalid percentage");
-        }
-
-        tokens.discard_whitespace();
-        if (!tokens.consume_a_token().is_ident("normal"sv))
-            return Error::from_string_literal("Expected `normal | <percentage [0,∞]>`");
-        tokens.discard_whitespace();
-        if (tokens.has_next_token())
-            return Error::from_string_literal("Unexpected trailing tokens");
-
-        return OptionalNone {};
-    };
-
+    Vector<Descriptor> descriptors;
+    HashTable<DescriptorID> seen_descriptor_ids;
     rule.for_each_as_declaration_list([&](auto& declaration) {
-        if (declaration.name.equals_ignoring_ascii_case("ascent-override"sv)) {
-            auto value = parse_as_percentage_or_normal(declaration.value);
-            if (value.is_error()) {
-                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse @font-face ascent-override: {}", value.error());
-            } else {
-                ascent_override = value.release_value();
-            }
-            return;
-        }
-        if (declaration.name.equals_ignoring_ascii_case("descent-override"sv)) {
-            auto value = parse_as_percentage_or_normal(declaration.value);
-            if (value.is_error()) {
-                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse @font-face descent-override: {}", value.error());
-            } else {
-                descent_override = value.release_value();
-            }
-            return;
-        }
-        if (declaration.name.equals_ignoring_ascii_case("font-display"sv)) {
-            TokenStream token_stream { declaration.value };
-            if (auto keyword_value = parse_keyword_value(token_stream)) {
-                token_stream.discard_whitespace();
-                if (token_stream.has_next_token()) {
-                    dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Unexpected trailing tokens in font-display");
-                } else {
-                    auto value = keyword_to_font_display(keyword_value->to_keyword());
-                    if (value.has_value()) {
-                        font_display = *value;
-                    } else {
-                        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: `{}` is not a valid value for font-display", keyword_value->to_string(CSSStyleValue::SerializationMode::Normal));
-                    }
-                }
-            }
-            return;
-        }
-        if (declaration.name.equals_ignoring_ascii_case("font-family"sv)) {
-            // FIXME: This is very similar to, but different from, the logic in parse_font_family_value().
-            //        Ideally they could share code.
-            Vector<FlyString> font_family_parts;
-            bool had_syntax_error = false;
-            for (size_t i = 0; i < declaration.value.size(); ++i) {
-                auto const& part = declaration.value[i];
-                if (part.is(Token::Type::Whitespace))
-                    continue;
-                if (part.is(Token::Type::String)) {
-                    if (!font_family_parts.is_empty()) {
-                        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face font-family format invalid; discarding.");
-                        had_syntax_error = true;
-                        break;
-                    }
-                    font_family_parts.append(part.token().string());
-                    continue;
-                }
-                if (part.is(Token::Type::Ident)) {
-                    if (is_css_wide_keyword(part.token().ident())) {
-                        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face font-family format invalid; discarding.");
-                        had_syntax_error = true;
-                        break;
-                    }
-                    auto keyword = keyword_from_string(part.token().ident());
-                    if (keyword.has_value() && keyword_to_generic_font_family(keyword.value()).has_value()) {
-                        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face font-family format invalid; discarding.");
-                        had_syntax_error = true;
-                        break;
-                    }
-                    font_family_parts.append(part.token().ident());
-                    continue;
-                }
-
-                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-face font-family format invalid; discarding.");
-                had_syntax_error = true;
-                break;
-            }
-            if (had_syntax_error || font_family_parts.is_empty())
-                return;
-
-            font_family = String::join(' ', font_family_parts).release_value_but_fixme_should_propagate_errors();
-            return;
-        }
-        if (declaration.name.equals_ignoring_ascii_case("font-feature-settings"sv)) {
-            TokenStream token_stream { declaration.value };
-            if (auto value = parse_css_value(CSS::PropertyID::FontFeatureSettings, token_stream); !value.is_error()) {
-                if (value.value()->to_keyword() == Keyword::Normal) {
-                    font_feature_settings.clear();
-                } else if (value.value()->is_value_list()) {
-                    auto const& feature_tags = value.value()->as_value_list().values();
-                    OrderedHashMap<FlyString, i64> settings;
-                    settings.ensure_capacity(feature_tags.size());
-                    for (auto const& feature_tag : feature_tags) {
-                        if (!feature_tag->is_open_type_tagged()) {
-                            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Value in font-feature-settings descriptor is not an OpenTypeTaggedStyleValue; skipping");
-                            continue;
-                        }
-                        auto const& setting_value = feature_tag->as_open_type_tagged().value();
-                        if (setting_value->is_integer()) {
-                            settings.set(feature_tag->as_open_type_tagged().tag(), setting_value->as_integer().integer());
-                        } else if (setting_value->is_calculated() && setting_value->as_calculated().resolves_to_number()) {
-                            if (auto integer = setting_value->as_calculated().resolve_integer({}); integer.has_value()) {
-                                settings.set(feature_tag->as_open_type_tagged().tag(), *integer);
-                            } else {
-                                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Calculated value in font-feature-settings descriptor cannot be resolved at parse time; skipping");
-                            }
-                        } else {
-                            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Value in font-feature-settings descriptor is not an OpenTypeTaggedStyleValue holding a <integer>; skipping");
-                        }
-                    }
-                    font_feature_settings = move(settings);
-                } else {
-                    dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse font-feature-settings descriptor, not compatible with value returned from parsing font-feature-settings property: {}", value.value()->to_string(CSSStyleValue::SerializationMode::Normal));
-                }
-            }
-            return;
-        }
-        if (declaration.name.equals_ignoring_ascii_case("font-language-override"sv)) {
-            TokenStream token_stream { declaration.value };
-            if (auto maybe_value = parse_css_value(CSS::PropertyID::FontLanguageOverride, token_stream); !maybe_value.is_error()) {
-                auto& value = maybe_value.value();
-                if (value->is_string()) {
-                    language_override = value->as_string().string_value();
-                } else {
-                    language_override.clear();
-                }
-            }
-            return;
-        }
-        if (declaration.name.equals_ignoring_ascii_case("font-named-instance"sv)) {
-            // auto | <string>
-            TokenStream token_stream { declaration.value };
-            token_stream.discard_whitespace();
-            auto& token = token_stream.consume_a_token();
-            token_stream.discard_whitespace();
-            if (token_stream.has_next_token()) {
-                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Unexpected trailing tokens in font-named-instance");
-                return;
-            }
-
-            if (token.is_ident("auto"sv)) {
-                font_named_instance.clear();
-            } else if (token.is(Token::Type::String)) {
-                font_named_instance = token.token().string();
-            } else {
-                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse font-named-instance from {}", token.to_debug_string());
-            }
-
-            return;
-        }
-        if (declaration.name.equals_ignoring_ascii_case("font-style"sv)) {
-            TokenStream token_stream { declaration.value };
-            if (auto value = parse_css_value(CSS::PropertyID::FontStyle, token_stream); !value.is_error()) {
-                slope = value.value()->to_font_slope();
-            }
-            return;
-        }
-        if (declaration.name.equals_ignoring_ascii_case("font-variation-settings"sv)) {
-            TokenStream token_stream { declaration.value };
-            if (auto value = parse_css_value(CSS::PropertyID::FontVariationSettings, token_stream); !value.is_error()) {
-                if (value.value()->to_keyword() == Keyword::Normal) {
-                    font_variation_settings.clear();
-                } else if (value.value()->is_value_list()) {
-                    auto const& variation_tags = value.value()->as_value_list().values();
-                    OrderedHashMap<FlyString, double> settings;
-                    settings.ensure_capacity(variation_tags.size());
-                    for (auto const& variation_tag : variation_tags) {
-                        if (!variation_tag->is_open_type_tagged()) {
-                            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Value in font-variation-settings descriptor is not an OpenTypeTaggedStyleValue; skipping");
-                            continue;
-                        }
-                        auto const& setting_value = variation_tag->as_open_type_tagged().value();
-                        if (setting_value->is_number()) {
-                            settings.set(variation_tag->as_open_type_tagged().tag(), setting_value->as_number().number());
-                        } else if (setting_value->is_calculated() && setting_value->as_calculated().resolves_to_number()) {
-                            if (auto number = setting_value->as_calculated().resolve_number({}); number.has_value()) {
-                                settings.set(variation_tag->as_open_type_tagged().tag(), *number);
-                            } else {
-                                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Calculated value in font-variation-settings descriptor cannot be resolved at parse time; skipping");
-                            }
-                        } else {
-                            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Value in font-variation-settings descriptor is not an OpenTypeTaggedStyleValue holding a <number>; skipping");
-                        }
-                    }
-                    font_variation_settings = move(settings);
-                } else {
-                    dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse font-variation-settings descriptor, not compatible with value returned from parsing font-variation-settings property: {}", value.value()->to_string(CSSStyleValue::SerializationMode::Normal));
-                }
-            }
-            return;
-        }
-        if (declaration.name.equals_ignoring_ascii_case("font-weight"sv)) {
-            TokenStream token_stream { declaration.value };
-            if (auto value = parse_css_value(CSS::PropertyID::FontWeight, token_stream); !value.is_error()) {
-                weight = value.value()->to_font_weight();
-            }
-            return;
-        }
-        if (declaration.name.equals_ignoring_ascii_case("font-width"sv)
-            || declaration.name.equals_ignoring_ascii_case("font-stretch"sv)) {
-            TokenStream token_stream { declaration.value };
-            if (auto value = parse_css_value(CSS::PropertyID::FontWidth, token_stream); !value.is_error()) {
-                width = value.value()->to_font_width();
-            }
-            return;
-        }
-        if (declaration.name.equals_ignoring_ascii_case("line-gap-override"sv)) {
-            auto value = parse_as_percentage_or_normal(declaration.value);
-            if (value.is_error()) {
-                dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse @font-face line-gap-override: {}", value.error());
+        if (auto descriptor = convert_to_descriptor(AtRuleID::FontFace, declaration); descriptor.has_value()) {
+            if (seen_descriptor_ids.contains(descriptor->descriptor_id)) {
+                descriptors.remove_first_matching([&descriptor](Descriptor const& existing) {
+                    return existing.descriptor_id == descriptor->descriptor_id;
+                });
             } else {
-                line_gap_override = value.release_value();
+                seen_descriptor_ids.set(descriptor->descriptor_id);
             }
-            return;
+            descriptors.append(descriptor.release_value());
         }
-        if (declaration.name.equals_ignoring_ascii_case("src"sv)) {
-            TokenStream token_stream { declaration.value };
-            Vector<ParsedFontFace::Source> supported_sources = parse_font_face_src(token_stream);
-            if (!supported_sources.is_empty())
-                src = move(supported_sources);
-            return;
-        }
-        if (declaration.name.equals_ignoring_ascii_case("unicode-range"sv)) {
-            TokenStream token_stream { declaration.value };
-            auto unicode_ranges = parse_unicode_ranges(token_stream);
-            if (unicode_ranges.is_empty())
-                return;
-
-            unicode_range = move(unicode_ranges);
-            return;
-        }
-
-        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Unrecognized descriptor '{}' in @font-face; discarding.", declaration.name);
     });
 
-    if (!font_family.has_value()) {
-        dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse @font-face: no font-family!");
-        return {};
-    }
-
-    if (unicode_range.is_empty()) {
-        unicode_range.empend(0x0u, 0x10FFFFu);
-    }
-
-    return CSSFontFaceRule::create(realm(), ParsedFontFace { font_family.release_value(), move(weight), move(slope), move(width), move(src), move(unicode_range), move(ascent_override), move(descent_override), move(line_gap_override), font_display, move(font_named_instance), move(language_override), move(font_feature_settings), move(font_variation_settings) });
+    return CSSFontFaceRule::create(realm(), CSSFontFaceDescriptors::create(realm(), move(descriptors)));
 }
 
 }

+ 95 - 4
Libraries/LibWeb/CSS/Parser/ValueParsing.cpp

@@ -16,6 +16,7 @@
 #include <AK/GenericLexer.h>
 #include <AK/TemporaryChange.h>
 #include <LibURL/URL.h>
+#include <LibWeb/CSS/FontFace.h>
 #include <LibWeb/CSS/Parser/Parser.h>
 #include <LibWeb/CSS/PropertyName.h>
 #include <LibWeb/CSS/StyleValues/AngleStyleValue.h>
@@ -39,6 +40,7 @@
 #include <LibWeb/CSS/StyleValues/EdgeStyleValue.h>
 #include <LibWeb/CSS/StyleValues/FitContentStyleValue.h>
 #include <LibWeb/CSS/StyleValues/FlexStyleValue.h>
+#include <LibWeb/CSS/StyleValues/FontSourceStyleValue.h>
 #include <LibWeb/CSS/StyleValues/FrequencyStyleValue.h>
 #include <LibWeb/CSS/StyleValues/GridTrackPlacementStyleValue.h>
 #include <LibWeb/CSS/StyleValues/GridTrackSizeListStyleValue.h>
@@ -54,6 +56,7 @@
 #include <LibWeb/CSS/StyleValues/RectStyleValue.h>
 #include <LibWeb/CSS/StyleValues/ResolutionStyleValue.h>
 #include <LibWeb/CSS/StyleValues/StringStyleValue.h>
+#include <LibWeb/CSS/StyleValues/StyleValueList.h>
 #include <LibWeb/CSS/StyleValues/TimeStyleValue.h>
 #include <LibWeb/CSS/StyleValues/URLStyleValue.h>
 #include <LibWeb/CSS/StyleValues/UnicodeRangeStyleValue.h>
@@ -64,6 +67,29 @@
 
 namespace Web::CSS::Parser {
 
+RefPtr<CSSStyleValue> Parser::parse_comma_separated_value_list(TokenStream<ComponentValue>& tokens, ParseFunction parse_one_value)
+{
+    auto first = parse_one_value(tokens);
+    if (!first || !tokens.has_next_token())
+        return first;
+
+    StyleValueVector values;
+    values.append(first.release_nonnull());
+
+    while (tokens.has_next_token()) {
+        if (!tokens.consume_a_token().is(Token::Type::Comma))
+            return nullptr;
+
+        if (auto maybe_value = parse_one_value(tokens)) {
+            values.append(maybe_value.release_nonnull());
+            continue;
+        }
+        return nullptr;
+    }
+
+    return StyleValueList::create(move(values), StyleValueList::Separator::Comma);
+}
+
 Optional<Dimension> Parser::parse_dimension(ComponentValue const& component_value)
 {
     if (component_value.is(Token::Type::Dimension)) {
@@ -1769,14 +1795,14 @@ RefPtr<CSSStyleValue> Parser::parse_color_value(TokenStream<ComponentValue>& tok
         if (!m_value_context.is_empty()) {
             quirky_color_allowed = m_value_context.first().visit(
                 [](PropertyID const& property_id) { return property_has_quirk(property_id, Quirk::HashlessHexColor); },
-                [](FunctionContext const&) { return false; });
+                [](FunctionContext const&) { return false; },
+                [](DescriptorContext const&) { return false; });
         }
         for (auto i = 1u; i < m_value_context.size() && quirky_color_allowed; i++) {
             quirky_color_allowed = m_value_context[i].visit(
                 [](PropertyID const& property_id) { return property_has_quirk(property_id, Quirk::UnitlessLength); },
-                [](FunctionContext const&) {
-                    return false;
-                });
+                [](FunctionContext const&) { return false; },
+                [](DescriptorContext const&) { return false; });
         }
         if (quirky_color_allowed) {
             // NOTE: This algorithm is no longer in the spec, since the concept got moved and renamed. However, it works,
@@ -3329,6 +3355,10 @@ RefPtr<CSSStyleValue> Parser::parse_calculated_value(ComponentValue const& compo
                 }
                 // FIXME: Add other functions that provide a context for resolving values
                 return {};
+            },
+            [](DescriptorContext const&) -> Optional<CalculationContext> {
+                // FIXME: If any descriptors have `<*-percentage>` or `<integer>` types, add them here.
+                return CalculationContext {};
             });
         if (maybe_context.has_value()) {
             context = maybe_context.release_value();
@@ -3631,6 +3661,67 @@ RefPtr<StringStyleValue> Parser::parse_opentype_tag_value(TokenStream<ComponentV
     return string_value;
 }
 
+RefPtr<FontSourceStyleValue> Parser::parse_font_source_value(TokenStream<ComponentValue>& tokens)
+{
+    // <font-src> = <url> [ format(<font-format>)]? [ tech( <font-tech>#)]? | local(<family-name>)
+    auto transaction = tokens.begin_transaction();
+
+    tokens.discard_whitespace();
+
+    // local(<family-name>)
+    if (tokens.next_token().is_function("local"sv)) {
+        auto const& function = tokens.consume_a_token().function();
+        TokenStream function_tokens { function.value };
+        if (auto family_name = parse_family_name_value(function_tokens)) {
+            transaction.commit();
+            return FontSourceStyleValue::create(FontSourceStyleValue::Local { family_name.release_nonnull() }, {});
+        }
+        return nullptr;
+    }
+
+    // <url> [ format(<font-format>)]? [ tech( <font-tech>#)]?
+    auto url = parse_url_function(tokens);
+    if (!url.has_value() || !url->is_valid())
+        return nullptr;
+
+    Optional<FlyString> format;
+
+    tokens.discard_whitespace();
+
+    // [ format(<font-format>)]?
+    if (tokens.next_token().is_function("format"sv)) {
+        auto const& function = tokens.consume_a_token().function();
+        auto context_guard = push_temporary_value_parsing_context(FunctionContext { function.name });
+
+        TokenStream format_tokens { function.value };
+        format_tokens.discard_whitespace();
+        auto const& format_name_token = format_tokens.consume_a_token();
+        FlyString format_name;
+        if (format_name_token.is(Token::Type::Ident)) {
+            format_name = format_name_token.token().ident();
+        } else if (format_name_token.is(Token::Type::String)) {
+            format_name = format_name_token.token().string();
+        } else {
+            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: font source invalid (`format()` parameter not an ident or string; is: {}); discarding.", format_name_token.to_debug_string());
+            return nullptr;
+        }
+
+        if (!font_format_is_supported(format_name)) {
+            dbgln_if(CSS_PARSER_DEBUG, "CSSParser: font source format({}) not supported; skipping.", format_name);
+            return nullptr;
+        }
+
+        format = move(format_name);
+    }
+
+    tokens.discard_whitespace();
+
+    // FIXME: [ tech( <font-tech>#)]?
+
+    transaction.commit();
+    return FontSourceStyleValue::create(url.release_value(), move(format));
+}
+
 NonnullRefPtr<CSSStyleValue> Parser::resolve_unresolved_style_value(ParsingParams const& context, DOM::Element& element, Optional<PseudoElement> pseudo_element, PropertyID property_id, UnresolvedStyleValue const& unresolved)
 {
     // Unresolved always contains a var() or attr(), unless it is a custom property's value, in which case we shouldn't be trying

+ 4 - 2
Libraries/LibWeb/CSS/StyleComputer.cpp

@@ -3061,8 +3061,10 @@ void StyleComputer::load_fonts_from_sheet(CSSStyleSheet& sheet)
     for (auto const& rule : sheet.rules()) {
         if (!is<CSSFontFaceRule>(*rule))
             continue;
-        auto font_loader = load_font_face(static_cast<CSSFontFaceRule const&>(*rule).font_face());
-        if (font_loader.has_value()) {
+        auto const& font_face_rule = static_cast<CSSFontFaceRule const&>(*rule);
+        if (!font_face_rule.is_valid())
+            continue;
+        if (auto font_loader = load_font_face(font_face_rule.font_face()); font_loader.has_value()) {
             sheet.add_associated_font_loader(font_loader.value());
         }
     }

+ 67 - 0
Libraries/LibWeb/CSS/StyleValues/FontSourceStyleValue.cpp

@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibWeb/CSS/Serialize.h>
+#include <LibWeb/CSS/StyleValues/FontSourceStyleValue.h>
+
+namespace Web::CSS {
+
+FontSourceStyleValue::FontSourceStyleValue(Source source, Optional<FlyString> format)
+    : StyleValueWithDefaultOperators(Type::FontSource)
+    , m_source(move(source))
+    , m_format(move(format))
+{
+}
+
+FontSourceStyleValue::~FontSourceStyleValue() = default;
+
+String FontSourceStyleValue::to_string(SerializationMode) const
+{
+    // <font-src> = <url> [ format(<font-format>)]? [ tech( <font-tech>#)]? | local(<family-name>)
+    return m_source.visit(
+        [](Local const& local) {
+            // local(<family-name>)
+            StringBuilder builder;
+            serialize_a_local(builder, local.name->to_string(SerializationMode::Normal));
+            return builder.to_string_without_validation();
+        },
+        [this](URL::URL const& url) {
+            // <url> [ format(<font-format>)]? [ tech( <font-tech>#)]?
+            // FIXME: tech()
+            StringBuilder builder;
+            serialize_a_url(builder, url.to_string());
+
+            if (m_format.has_value()) {
+                builder.append(" format("sv);
+                serialize_an_identifier(builder, *m_format);
+                builder.append(")"sv);
+            }
+
+            return builder.to_string_without_validation();
+        });
+}
+
+bool FontSourceStyleValue::properties_equal(FontSourceStyleValue const& other) const
+{
+    bool sources_equal = m_source.visit(
+        [&other](Local const& local) {
+            if (auto* other_local = other.m_source.get_pointer<Local>()) {
+                return local.name == other_local->name;
+            }
+            return false;
+        },
+        [&other](URL::URL const& url) {
+            if (auto* other_url = other.m_source.get_pointer<URL::URL>()) {
+                return url == *other_url;
+            }
+            return false;
+        });
+
+    return sources_equal
+        && m_format == other.m_format;
+}
+
+}

+ 41 - 0
Libraries/LibWeb/CSS/StyleValues/FontSourceStyleValue.h

@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/FlyString.h>
+#include <LibWeb/CSS/CSSStyleValue.h>
+
+namespace Web::CSS {
+
+class FontSourceStyleValue final : public StyleValueWithDefaultOperators<FontSourceStyleValue> {
+public:
+    struct Local {
+        NonnullRefPtr<CSSStyleValue> name;
+    };
+    using Source = Variant<Local, URL::URL>;
+
+    static ValueComparingNonnullRefPtr<FontSourceStyleValue> create(Source source, Optional<FlyString> format)
+    {
+        return adopt_ref(*new (nothrow) FontSourceStyleValue(move(source), move(format)));
+    }
+    virtual ~FontSourceStyleValue() override;
+
+    Source const& source() const { return m_source; }
+    Optional<FlyString> const& format() const { return m_format; }
+
+    virtual String to_string(SerializationMode) const override;
+
+    bool properties_equal(FontSourceStyleValue const&) const;
+
+private:
+    FontSourceStyleValue(Source source, Optional<FlyString> format);
+
+    Source m_source;
+    Optional<FlyString> m_format;
+};
+
+}

+ 1 - 0
Libraries/LibWeb/CSS/StyleValues/StringStyleValue.h

@@ -8,6 +8,7 @@
 
 #include <AK/FlyString.h>
 #include <LibWeb/CSS/CSSStyleValue.h>
+#include <LibWeb/CSS/Serialize.h>
 
 namespace Web::CSS {
 

+ 4 - 1
Libraries/LibWeb/Dump.cpp

@@ -701,7 +701,10 @@ void dump_rule(StringBuilder& builder, CSS::CSSRule const& rule, int indent_leve
 
 void dump_font_face_rule(StringBuilder& builder, CSS::CSSFontFaceRule const& rule, int indent_levels)
 {
-    auto& font_face = rule.font_face();
+    auto const font_face = rule.font_face();
+    indent(builder, indent_levels + 1);
+    builder.appendff("VALID: {}\n", rule.is_valid());
+
     indent(builder, indent_levels + 1);
     builder.appendff("font-family: {}\n", font_face.font_family());
 

+ 2 - 0
Libraries/LibWeb/Forward.h

@@ -159,6 +159,7 @@ class CounterStyleValue;
 class CSSAnimation;
 class CSSColorValue;
 class CSSConditionRule;
+class CSSFontFaceDescriptors;
 class CSSFontFaceRule;
 class CSSGroupingRule;
 class CSSHSL;
@@ -198,6 +199,7 @@ class FlexOrCalculated;
 class FlexStyleValue;
 class FontFace;
 class FontFaceSet;
+class FontSourceStyleValue;
 class Frequency;
 class FrequencyOrCalculated;
 class FrequencyPercentage;

+ 3 - 3
Libraries/LibWeb/HTML/NavigatorLanguage.h

@@ -7,8 +7,8 @@
 
 #pragma once
 
+#include <AK/Span.h>
 #include <AK/String.h>
-#include <AK/Vector.h>
 #include <LibWeb/Loader/ResourceLoader.h>
 
 namespace Web::HTML {
@@ -16,10 +16,10 @@ namespace Web::HTML {
 class NavigatorLanguageMixin {
 public:
     // https://html.spec.whatwg.org/multipage/system-state.html#dom-navigator-language
-    String language() const { return ResourceLoader::the().preferred_languages()[0]; }
+    String const& language() const { return ResourceLoader::the().preferred_languages()[0]; }
 
     // https://html.spec.whatwg.org/multipage/system-state.html#dom-navigator-languages
-    Vector<String> languages() const { return ResourceLoader::the().preferred_languages(); }
+    ReadonlySpan<String> languages() const { return ResourceLoader::the().preferred_languages(); }
 };
 
 }

+ 2 - 1
Libraries/LibWeb/IndexedDB/Internal/KeyGenerator.h

@@ -18,7 +18,8 @@ private:
     // The current number is always a positive integer less than or equal to 2^53 (9007199254740992) + 1.
     // The initial value of a key generator's current number is 1, set when the associated object store is created.
     // The current number is incremented as keys are generated, and may be updated to a specific value by using explicit keys.
-    u64 current_number { 1 };
+    // FIXME: Implement support for KeyGenerator in ObjectStore.
+    [[maybe_unused]] u64 current_number { 1 };
 };
 
 }

+ 3 - 7
Libraries/LibWeb/Loader/ResourceLoader.h

@@ -53,15 +53,11 @@ public:
     String const& platform() const { return m_platform; }
     void set_platform(String platform) { m_platform = move(platform); }
 
-    Vector<String> preferred_languages() const { return m_preferred_languages; }
+    Vector<String> const& preferred_languages() const { return m_preferred_languages; }
     void set_preferred_languages(Vector<String> preferred_languages)
     {
-        // Default to "en" if no preferred languages are specified.
-        if (preferred_languages.is_empty() || (preferred_languages.size() == 1 && preferred_languages[0].is_empty())) {
-            m_preferred_languages = { "en"_string };
-        } else {
-            m_preferred_languages = move(preferred_languages);
-        }
+        m_preferred_languages = move(preferred_languages);
+        VERIFY(!m_preferred_languages.is_empty());
     }
 
     NavigatorCompatibilityMode navigator_compatibility_mode() { return m_navigator_compatibility_mode; }

+ 3 - 2
Libraries/LibWeb/ServiceWorker/Registration.h

@@ -68,8 +68,9 @@ private:
     // FIXME: Spec bug: A service worker registration has an associated NavigationPreloadManager object.
     //        This can't possibly be true. The association is the other way around.
 
-    bool m_navigation_preload_enabled = { false }; // https://w3c.github.io/ServiceWorker/#service-worker-registration-navigation-preload-enabled-flag
-    ByteString m_navigation_preload_header_value;  // https://w3c.github.io/ServiceWorker/#service-worker-registration-navigation-preload-header-value
+    // FIXME: Investigate if this is implemented.
+    [[maybe_unused]] bool m_navigation_preload_enabled = { false }; // https://w3c.github.io/ServiceWorker/#service-worker-registration-navigation-preload-enabled-flag
+    ByteString m_navigation_preload_header_value;                   // https://w3c.github.io/ServiceWorker/#service-worker-registration-navigation-preload-header-value
 };
 
 struct RegistrationKey {

+ 1 - 0
Libraries/LibWeb/idl_files.cmake

@@ -23,6 +23,7 @@ libweb_js_bindings(Crypto/SubtleCrypto)
 libweb_js_bindings(CSS/AnimationEvent)
 libweb_js_bindings(CSS/CSSAnimation)
 libweb_js_bindings(CSS/CSSConditionRule)
+libweb_js_bindings(CSS/CSSFontFaceDescriptors)
 libweb_js_bindings(CSS/CSSFontFaceRule)
 libweb_js_bindings(CSS/CSSGroupingRule)
 libweb_js_bindings(CSS/CSSImportRule)

+ 52 - 1
Libraries/LibWebView/Settings.cpp

@@ -14,6 +14,7 @@
 #include <LibCore/File.h>
 #include <LibCore/StandardPaths.h>
 #include <LibURL/Parser.h>
+#include <LibUnicode/Locale.h>
 #include <LibWebView/Application.h>
 #include <LibWebView/Settings.h>
 
@@ -21,6 +22,9 @@ namespace WebView {
 
 static constexpr auto new_tab_page_url_key = "newTabPageURL"sv;
 
+static constexpr auto languages_key = "languages"sv;
+static auto default_language = "en"_string;
+
 static constexpr auto search_engine_key = "searchEngine"sv;
 static constexpr auto search_engine_name_key = "name"sv;
 
@@ -81,6 +85,9 @@ Settings Settings::create(Badge<Application>)
             settings.m_new_tab_page_url = parsed_new_tab_page_url.release_value();
     }
 
+    if (auto languages = settings_json.value().get(languages_key); languages.has_value())
+        settings.m_languages = parse_json_languages(*languages);
+
     if (auto search_engine = settings_json.value().get_object(search_engine_key); search_engine.has_value()) {
         if (auto search_engine_name = search_engine->get_string(search_engine_name_key); search_engine_name.has_value())
             settings.m_search_engine = find_search_engine_by_name(*search_engine_name);
@@ -120,6 +127,7 @@ Settings Settings::create(Badge<Application>)
 Settings::Settings(ByteString settings_path)
     : m_settings_path(move(settings_path))
     , m_new_tab_page_url(URL::about_newtab())
+    , m_languages({ default_language })
 {
 }
 
@@ -128,6 +136,14 @@ JsonValue Settings::serialize_json() const
     JsonObject settings;
     settings.set(new_tab_page_url_key, m_new_tab_page_url.serialize());
 
+    JsonArray languages;
+    languages.ensure_capacity(m_languages.size());
+
+    for (auto const& language : m_languages)
+        languages.must_append(language);
+
+    settings.set(languages_key, move(languages));
+
     if (m_search_engine.has_value()) {
         JsonObject search_engine;
         search_engine.set(search_engine_name_key, m_search_engine->name);
@@ -166,6 +182,7 @@ JsonValue Settings::serialize_json() const
 void Settings::restore_defaults()
 {
     m_new_tab_page_url = URL::about_newtab();
+    m_languages = { default_language };
     m_search_engine.clear();
     m_autocomplete_engine.clear();
     m_autoplay = SiteSetting {};
@@ -173,8 +190,14 @@ void Settings::restore_defaults()
 
     persist_settings();
 
-    for (auto& observer : m_observers)
+    for (auto& observer : m_observers) {
         observer.new_tab_page_url_changed();
+        observer.languages_changed();
+        observer.search_engine_changed();
+        observer.autocomplete_engine_changed();
+        observer.autoplay_settings_changed();
+        observer.do_not_track_changed();
+    }
 }
 
 void Settings::set_new_tab_page_url(URL::URL new_tab_page_url)
@@ -186,6 +209,34 @@ void Settings::set_new_tab_page_url(URL::URL new_tab_page_url)
         observer.new_tab_page_url_changed();
 }
 
+Vector<String> Settings::parse_json_languages(JsonValue const& languages)
+{
+    if (!languages.is_array())
+        return { default_language };
+
+    Vector<String> parsed_languages;
+    parsed_languages.ensure_capacity(languages.as_array().size());
+
+    languages.as_array().for_each([&](JsonValue const& language) {
+        if (language.is_string() && Unicode::is_locale_available(language.as_string()))
+            parsed_languages.append(language.as_string());
+    });
+
+    if (parsed_languages.is_empty())
+        return { default_language };
+
+    return parsed_languages;
+}
+
+void Settings::set_languages(Vector<String> languages)
+{
+    m_languages = move(languages);
+    persist_settings();
+
+    for (auto& observer : m_observers)
+        observer.languages_changed();
+}
+
 void Settings::set_search_engine(Optional<StringView> search_engine_name)
 {
     if (search_engine_name.has_value())

+ 6 - 0
Libraries/LibWebView/Settings.h

@@ -35,6 +35,7 @@ public:
     virtual ~SettingsObserver();
 
     virtual void new_tab_page_url_changed() { }
+    virtual void languages_changed() { }
     virtual void search_engine_changed() { }
     virtual void autocomplete_engine_changed() { }
     virtual void autoplay_settings_changed() { }
@@ -52,6 +53,10 @@ public:
     URL::URL const& new_tab_page_url() const { return m_new_tab_page_url; }
     void set_new_tab_page_url(URL::URL);
 
+    static Vector<String> parse_json_languages(JsonValue const&);
+    Vector<String> const& languages() const { return m_languages; }
+    void set_languages(Vector<String>);
+
     Optional<SearchEngine> const& search_engine() const { return m_search_engine; }
     void set_search_engine(Optional<StringView> search_engine_name);
 
@@ -78,6 +83,7 @@ private:
     ByteString m_settings_path;
 
     URL::URL m_new_tab_page_url;
+    Vector<String> m_languages;
     Optional<SearchEngine> m_search_engine;
     Optional<AutocompleteEngine> m_autocomplete_engine;
     SiteSetting m_autoplay;

+ 7 - 5
Libraries/LibWebView/ViewImplementation.cpp

@@ -252,11 +252,6 @@ void ViewImplementation::set_preferred_motion(Web::CSS::PreferredMotion motion)
     client().async_set_preferred_motion(page_id(), motion);
 }
 
-void ViewImplementation::set_preferred_languages(ReadonlySpan<String> preferred_languages)
-{
-    client().async_set_preferred_languages(page_id(), preferred_languages);
-}
-
 ByteString ViewImplementation::selected_text()
 {
     return client().get_selected_text(page_id());
@@ -599,6 +594,7 @@ void ViewImplementation::initialize_client(CreateNewClient create_new_client)
     if (auto const& user_agent_preset = Application::web_content_options().user_agent_preset; user_agent_preset.has_value())
         client().async_debug_request(m_client_state.page_index, "spoof-user-agent"sv, *user_agents.get(*user_agent_preset));
 
+    languages_changed();
     autoplay_settings_changed();
     do_not_track_changed();
 }
@@ -648,6 +644,12 @@ void ViewImplementation::handle_web_content_process_crash(LoadErrorPage load_err
     }
 }
 
+void ViewImplementation::languages_changed()
+{
+    auto const& languages = Application::settings().languages();
+    client().async_set_preferred_languages(page_id(), languages);
+}
+
 void ViewImplementation::autoplay_settings_changed()
 {
     auto const& autoplay_settings = Application::settings().autoplay_settings();

+ 1 - 2
Libraries/LibWebView/ViewImplementation.h

@@ -80,8 +80,6 @@ public:
     void set_preferred_contrast(Web::CSS::PreferredContrast);
     void set_preferred_motion(Web::CSS::PreferredMotion);
 
-    void set_preferred_languages(ReadonlySpan<String>);
-
     ByteString selected_text();
     Optional<String> selected_text_with_whitespace_collapsed();
     void select_all();
@@ -262,6 +260,7 @@ protected:
     };
     void handle_web_content_process_crash(LoadErrorPage = LoadErrorPage::Yes);
 
+    virtual void languages_changed() override;
     virtual void autoplay_settings_changed() override;
     virtual void do_not_track_changed() override;
 

+ 11 - 0
Libraries/LibWebView/WebUI/SettingsUI.cpp

@@ -24,6 +24,9 @@ void SettingsUI::register_interfaces()
     register_interface("setNewTabPageURL"sv, [this](auto const& data) {
         set_new_tab_page_url(data);
     });
+    register_interface("setLanguages"sv, [this](auto const& data) {
+        set_languages(data);
+    });
 
     register_interface("loadAvailableEngines"sv, [this](auto const&) {
         load_available_engines();
@@ -80,6 +83,14 @@ void SettingsUI::set_new_tab_page_url(JsonValue const& new_tab_page_url)
     WebView::Application::settings().set_new_tab_page_url(parsed_new_tab_page_url.release_value());
 }
 
+void SettingsUI::set_languages(JsonValue const& languages)
+{
+    auto parsed_languages = Settings::parse_json_languages(languages);
+    WebView::Application::settings().set_languages(move(parsed_languages));
+
+    load_current_settings();
+}
+
 void SettingsUI::load_available_engines()
 {
     JsonArray search_engines;

+ 1 - 0
Libraries/LibWebView/WebUI/SettingsUI.h

@@ -20,6 +20,7 @@ private:
     void restore_default_settings();
 
     void set_new_tab_page_url(JsonValue const&);
+    void set_languages(JsonValue const&);
 
     void load_available_engines();
     void set_search_engine(JsonValue const&);

+ 10 - 0
Meta/CMake/libweb_generators.cmake

@@ -1,6 +1,16 @@
 function (generate_css_implementation)
     set(LIBWEB_INPUT_FOLDER "${CMAKE_CURRENT_SOURCE_DIR}")
 
+
+    invoke_generator(
+        "DescriptorID.cpp"
+        Lagom::GenerateCSSDescriptors
+        "${LIBWEB_INPUT_FOLDER}/CSS/Descriptors.json"
+        "CSS/DescriptorID.h"
+        "CSS/DescriptorID.cpp"
+        arguments -j "${LIBWEB_INPUT_FOLDER}/CSS/Descriptors.json"
+    )
+
     invoke_generator(
         "Enums.cpp"
         Lagom::GenerateCSSEnums

+ 0 - 4
Meta/Lagom/CMakeLists.txt

@@ -136,10 +136,6 @@ add_library(GenericClangPlugin INTERFACE)
 
 if (CMAKE_CXX_COMPILER_ID MATCHES "Clang$")
     add_cxx_compile_options(-Wno-overloaded-virtual)
-    # FIXME: Re-enable this check when the warning stops triggering, or document why we can't stop it from triggering.
-    # For now, there is a lot of unused private fields in LibWeb that trigger this that could be removed.
-    # See issue #14137 for details
-    add_cxx_compile_options(-Wno-unused-private-field)
 
     if (ENABLE_FUZZERS_LIBFUZZER)
         add_cxx_compile_options(-fsanitize=fuzzer)

+ 1 - 0
Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt

@@ -1,5 +1,6 @@
 set(SOURCES "") # avoid pulling SOURCES from parent scope
 
+lagom_tool(GenerateCSSDescriptors           SOURCES GenerateCSSDescriptors.cpp LIBS LibMain)
 lagom_tool(GenerateCSSEnums                 SOURCES GenerateCSSEnums.cpp LIBS LibMain)
 lagom_tool(GenerateCSSKeyword               SOURCES GenerateCSSKeyword.cpp LIBS LibMain)
 lagom_tool(GenerateCSSMathFunctions         SOURCES GenerateCSSMathFunctions.cpp LIBS LibMain)

+ 361 - 0
Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSDescriptors.cpp

@@ -0,0 +1,361 @@
+/*
+ * Copyright (c) 2022-2025, Sam Atkins <sam@ladybird.org>
+ * Copyright (c) 2024, Luke Wilde <luke@ladybird.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "GeneratorUtil.h"
+
+#include <AK/QuickSort.h>
+#include <AK/SourceGenerator.h>
+#include <LibCore/ArgsParser.h>
+#include <LibMain/Main.h>
+
+ErrorOr<void> generate_header_file(JsonObject const& at_rules_data, Core::File& file);
+ErrorOr<void> generate_implementation_file(JsonObject const& at_rules_data, Core::File& file);
+
+static bool is_legacy_alias(JsonObject const& descriptor)
+{
+    return descriptor.has_string("legacy-alias-for"sv);
+}
+
+ErrorOr<int> serenity_main(Main::Arguments arguments)
+{
+    StringView generated_header_path;
+    StringView generated_implementation_path;
+    StringView json_path;
+
+    Core::ArgsParser args_parser;
+    args_parser.add_option(generated_header_path, "Path to the DescriptorID header file to generate", "generated-header-path", 'h', "generated-header-path");
+    args_parser.add_option(generated_implementation_path, "Path to the DescriptorID implementation file to generate", "generated-implementation-path", 'c', "generated-implementation-path");
+    args_parser.add_option(json_path, "Path to the JSON file to read from", "json-path", 'j', "json-path");
+    args_parser.parse(arguments);
+
+    auto json = TRY(read_entire_file_as_json(json_path));
+    VERIFY(json.is_object());
+    auto data = json.as_object();
+
+    auto generated_header_file = TRY(Core::File::open(generated_header_path, Core::File::OpenMode::Write));
+    auto generated_implementation_file = TRY(Core::File::open(generated_implementation_path, Core::File::OpenMode::Write));
+
+    TRY(generate_header_file(data, *generated_header_file));
+    TRY(generate_implementation_file(data, *generated_implementation_file));
+
+    return 0;
+}
+
+Vector<StringView> all_descriptors;
+
+ErrorOr<void> generate_header_file(JsonObject const& at_rules_data, Core::File& file)
+{
+    StringBuilder builder;
+    SourceGenerator generator { builder };
+
+    // DescriptorID is a set of all descriptor names used by any at-rules, so gather them up.
+    auto at_rule_count = 0u;
+    HashTable<StringView> descriptors_set;
+    at_rules_data.for_each_member([&](auto const&, JsonValue const& value) {
+        auto const& at_rule = value.as_object();
+        ++at_rule_count;
+
+        if (auto descriptors = at_rule.get_object("descriptors"sv); descriptors.has_value()) {
+            descriptors.value().for_each_member([&](auto const& descriptor_name, JsonValue const& descriptor_value) {
+                if (is_legacy_alias(descriptor_value.as_object()))
+                    return;
+                descriptors_set.set(descriptor_name);
+            });
+        }
+    });
+
+    all_descriptors.ensure_capacity(descriptors_set.size());
+    for (auto name : descriptors_set)
+        all_descriptors.unchecked_append(name);
+    quick_sort(all_descriptors);
+
+    generator.set("at_rule_id_underlying_type", underlying_type_for_enum(at_rule_count));
+    generator.set("descriptor_id_underlying_type", underlying_type_for_enum(all_descriptors.size()));
+
+    generator.append(R"~~~(
+#pragma once
+
+#include <AK/FlyString.h>
+#include <AK/Optional.h>
+#include <AK/Types.h>
+#include <LibWeb/CSS/Keyword.h>
+#include <LibWeb/CSS/PropertyID.h>
+
+namespace Web::CSS {
+
+enum class AtRuleID : @at_rule_id_underlying_type@ {
+)~~~");
+    at_rules_data.for_each_member([&](auto const& name, auto const&) {
+        auto member_generator = generator.fork();
+        member_generator.set("name:titlecase", title_casify(name));
+        member_generator.appendln("    @name:titlecase@,");
+    });
+    generator.append(R"~~~(
+};
+
+FlyString to_string(AtRuleID);
+
+enum class DescriptorID : @descriptor_id_underlying_type@ {
+)~~~");
+    for (auto const& descriptor_name : all_descriptors) {
+        auto member_generator = generator.fork();
+        member_generator.set("name:titlecase", title_casify(descriptor_name));
+        member_generator.appendln("    @name:titlecase@,");
+    }
+    generator.append(R"~~~(
+};
+
+Optional<DescriptorID> descriptor_id_from_string(AtRuleID, StringView);
+FlyString to_string(DescriptorID);
+
+bool at_rule_supports_descriptor(AtRuleID, DescriptorID);
+
+struct DescriptorMetadata {
+    enum class ValueType {
+        // FIXME: Parse the grammar instead of hard-coding all the options!
+        FamilyName,
+        FontSrcList,
+        OptionalDeclarationValue,
+        PositivePercentage,
+        String,
+        UnicodeRangeTokens,
+    };
+    Vector<Variant<Keyword, PropertyID, ValueType>> syntax;
+};
+
+DescriptorMetadata get_descriptor_metadata(AtRuleID, DescriptorID);
+
+}
+)~~~");
+
+    TRY(file.write_until_depleted(generator.as_string_view().bytes()));
+    return {};
+}
+
+ErrorOr<void> generate_implementation_file(JsonObject const& at_rules_data, Core::File& file)
+{
+    StringBuilder builder;
+    SourceGenerator generator { builder };
+
+    generator.append(R"~~~(
+#include <LibWeb/CSS/DescriptorID.h>
+
+namespace Web::CSS {
+
+FlyString to_string(AtRuleID at_rule_id)
+{
+    switch (at_rule_id) {
+)~~~");
+
+    at_rules_data.for_each_member([&](auto const& at_rule_name, JsonValue const&) {
+        auto at_rule_generator = generator.fork();
+        at_rule_generator.set("at_rule", at_rule_name);
+        at_rule_generator.set("at_rule:titlecase", title_casify(at_rule_name));
+        at_rule_generator.append(R"~~~(
+    case AtRuleID::@at_rule:titlecase@:
+        return "\@@at_rule@"_fly_string;
+)~~~");
+    });
+
+    generator.append(R"~~~(
+    }
+    VERIFY_NOT_REACHED();
+}
+
+Optional<DescriptorID> descriptor_id_from_string(AtRuleID at_rule_id, StringView string)
+{
+    switch (at_rule_id) {
+)~~~");
+    at_rules_data.for_each_member([&](auto const& at_rule_name, JsonValue const& value) {
+        auto const& at_rule = value.as_object();
+
+        auto at_rule_generator = generator.fork();
+        at_rule_generator.set("at_rule:titlecase", title_casify(at_rule_name));
+        at_rule_generator.append(R"~~~(
+    case AtRuleID::@at_rule:titlecase@:
+)~~~");
+
+        auto const& descriptors = at_rule.get_object("descriptors"sv).value();
+
+        descriptors.for_each_member([&](auto const& descriptor_name, JsonValue const& descriptor_value) {
+            auto const& descriptor = descriptor_value.as_object();
+            auto descriptor_generator = at_rule_generator.fork();
+
+            descriptor_generator.set("descriptor", descriptor_name);
+            if (auto alias_for = descriptor.get_string("legacy-alias-for"sv); alias_for.has_value()) {
+                descriptor_generator.set("result:titlecase", title_casify(alias_for.value()));
+            } else {
+                descriptor_generator.set("result:titlecase", title_casify(descriptor_name));
+            }
+            descriptor_generator.append(R"~~~(
+        if (string.equals_ignoring_ascii_case("@descriptor@"sv))
+            return DescriptorID::@result:titlecase@;
+)~~~");
+        });
+
+        at_rule_generator.append(R"~~~(
+        break;
+)~~~");
+    });
+
+    generator.append(R"~~~(
+    }
+    return {};
+}
+
+FlyString to_string(DescriptorID descriptor_id)
+{
+    switch (descriptor_id) {
+)~~~");
+
+    for (auto const& descriptor_name : all_descriptors) {
+        auto member_generator = generator.fork();
+        member_generator.set("name", descriptor_name);
+        member_generator.set("name:titlecase", title_casify(descriptor_name));
+
+        member_generator.append(R"~~~(
+    case DescriptorID::@name:titlecase@:
+        return "@name@"_fly_string;
+)~~~");
+    }
+
+    generator.append(R"~~~(
+    }
+    VERIFY_NOT_REACHED();
+}
+
+bool at_rule_supports_descriptor(AtRuleID at_rule_id, DescriptorID descriptor_id)
+{
+    switch (at_rule_id) {
+)~~~");
+
+    at_rules_data.for_each_member([&](auto const& at_rule_name, JsonValue const& value) {
+        auto const& at_rule = value.as_object();
+
+        auto at_rule_generator = generator.fork();
+        at_rule_generator.set("at_rule:titlecase", title_casify(at_rule_name));
+        at_rule_generator.append(R"~~~(
+    case AtRuleID::@at_rule:titlecase@:
+        switch (descriptor_id) {
+)~~~");
+
+        auto const& descriptors = at_rule.get_object("descriptors"sv).value();
+        descriptors.for_each_member([&](auto const& descriptor_name, JsonValue const& descriptor_value) {
+            if (is_legacy_alias(descriptor_value.as_object()))
+                return;
+
+            auto descriptor_generator = at_rule_generator.fork();
+            descriptor_generator.set("descriptor:titlecase", title_casify(descriptor_name));
+            descriptor_generator.appendln("        case DescriptorID::@descriptor:titlecase@:");
+        });
+
+        at_rule_generator.append(R"~~~(
+            return true;
+        default:
+            return false;
+        }
+)~~~");
+    });
+
+    generator.append(R"~~~(
+    }
+    VERIFY_NOT_REACHED();
+}
+
+DescriptorMetadata get_descriptor_metadata(AtRuleID at_rule_id, DescriptorID descriptor_id)
+{
+    switch (at_rule_id) {
+)~~~");
+
+    at_rules_data.for_each_member([&](auto const& at_rule_name, JsonValue const& value) {
+        auto const& at_rule = value.as_object();
+
+        auto at_rule_generator = generator.fork();
+        at_rule_generator.set("at_rule:titlecase", title_casify(at_rule_name));
+        at_rule_generator.append(R"~~~(
+    case AtRuleID::@at_rule:titlecase@:
+        switch (descriptor_id) {
+)~~~");
+
+        auto const& descriptors = at_rule.get_object("descriptors"sv).value();
+        descriptors.for_each_member([&](auto const& descriptor_name, JsonValue const& descriptor_value) {
+            auto const& descriptor = descriptor_value.as_object();
+            if (is_legacy_alias(descriptor))
+                return;
+
+            auto descriptor_generator = at_rule_generator.fork();
+            descriptor_generator.set("descriptor:titlecase", title_casify(descriptor_name));
+            descriptor_generator.append(R"~~~(
+        case DescriptorID::@descriptor:titlecase@: {
+            DescriptorMetadata metadata;
+)~~~");
+            auto const& syntax = descriptor.get_array("syntax"sv).value();
+            for (auto const& entry : syntax.values()) {
+                auto option_generator = descriptor_generator.fork();
+                auto const& syntax_string = entry.as_string();
+
+                if (syntax_string.starts_with_bytes("<'"sv)) {
+                    // Property
+                    option_generator.set("property:titlecase"sv, title_casify(MUST(syntax_string.substring_from_byte_offset_with_shared_superstring(2, syntax_string.byte_count() - 4))));
+                    option_generator.append(R"~~~(
+            metadata.syntax.empend(PropertyID::@property:titlecase@);
+)~~~");
+                } else if (syntax_string.starts_with('<')) {
+                    // Value type
+                    // FIXME: Actually parse the grammar, instead of hard-coding the options!
+                    auto value_type = [&syntax_string] {
+                        if (syntax_string == "<family-name>"sv)
+                            return "FamilyName"_string;
+                        if (syntax_string == "<font-src-list>"sv)
+                            return "FontSrcList"_string;
+                        if (syntax_string == "<declaration-value>?"sv)
+                            return "OptionalDeclarationValue"_string;
+                        if (syntax_string == "<percentage [0,∞]>"sv)
+                            return "PositivePercentage"_string;
+                        if (syntax_string == "<string>"sv)
+                            return "String"_string;
+                        if (syntax_string == "<unicode-range-token>#"sv)
+                            return "UnicodeRangeTokens"_string;
+                        VERIFY_NOT_REACHED();
+                    }();
+                    option_generator.set("value_type"sv, value_type);
+                    option_generator.append(R"~~~(
+            metadata.syntax.empend(DescriptorMetadata::ValueType::@value_type@);
+)~~~");
+
+                } else {
+                    // Keyword
+                    option_generator.set("keyword:titlecase"sv, title_casify(syntax_string));
+                    option_generator.append(R"~~~(
+            metadata.syntax.empend(Keyword::@keyword:titlecase@);
+)~~~");
+                }
+            }
+            descriptor_generator.append(R"~~~(
+            return metadata;
+        }
+)~~~");
+        });
+
+        at_rule_generator.append(R"~~~(
+        default:
+            VERIFY_NOT_REACHED();
+        }
+)~~~");
+    });
+
+    generator.append(R"~~~(
+    }
+    VERIFY_NOT_REACHED();
+}
+
+}
+)~~~");
+
+    TRY(file.write_until_depleted(generator.as_string_view().bytes()));
+    return {};
+}

+ 0 - 1
Meta/gn/build/BUILD.gn

@@ -126,7 +126,6 @@ config("compiler_defaults") {
       "-Wstring-conversion",
       "-Wno-user-defined-literals",
       "-fconstexpr-steps=16777216",
-      "-Wno-unused-private-field",
       "-Wno-implicit-const-int-float-conversion",
       "-Wno-vla-cxx-extension",
       "-Wno-unqualified-std-cast-call",

+ 1 - 1
Meta/import-wpt-test.py

@@ -62,7 +62,7 @@ class LinkedResourceFinder(HTMLParser):
 
     def handle_starttag(self, tag, attrs):
         self._tag_stack_.append(tag)
-        if tag in ["script", "img"]:
+        if tag in ["script", "img", "iframe"]:
             attr_dict = dict(attrs)
             if "src" in attr_dict:
                 self._resources.append(attr_dict["src"])

+ 1 - 2
Services/WebContent/ConnectionFromClient.cpp

@@ -56,9 +56,8 @@
 
 namespace WebContent {
 
-ConnectionFromClient::ConnectionFromClient(GC::Heap& heap, IPC::Transport transport)
+ConnectionFromClient::ConnectionFromClient(IPC::Transport transport)
     : IPC::ConnectionFromClient<WebContentClientEndpoint, WebContentServerEndpoint>(*this, move(transport), 1)
-    , m_heap(heap)
     , m_page_host(PageHost::create(*this))
 {
 }

+ 1 - 2
Services/WebContent/ConnectionFromClient.h

@@ -51,7 +51,7 @@ public:
     Queue<Web::QueuedInputEvent>& input_event_queue() { return m_input_event_queue; }
 
 private:
-    explicit ConnectionFromClient(GC::Heap&, IPC::Transport);
+    explicit ConnectionFromClient(IPC::Transport);
 
     Optional<PageClient&> page(u64 index, SourceLocation = SourceLocation::current());
     Optional<PageClient const&> page(u64 index, SourceLocation = SourceLocation::current()) const;
@@ -157,7 +157,6 @@ private:
 
     virtual void system_time_zone_changed() override;
 
-    GC::Heap& m_heap;
     NonnullOwnPtr<PageHost> m_page_host;
 
     HashMap<int, Web::FileRequest> m_requested_files {};

+ 1 - 1
Services/WebContent/main.cpp

@@ -213,7 +213,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
     static_assert(IsSame<IPC::Transport, IPC::TransportSocket>, "Need to handle other IPC transports here");
 
     auto webcontent_socket = TRY(Core::take_over_socket_from_system_server("WebContent"sv));
-    auto webcontent_client = TRY(WebContent::ConnectionFromClient::try_create(Web::Bindings::main_thread_vm().heap(), IPC::Transport(move(webcontent_socket))));
+    auto webcontent_client = TRY(WebContent::ConnectionFromClient::try_create(IPC::Transport(move(webcontent_socket))));
 
     webcontent_client->on_image_decoder_connection = [&](auto& socket_file) {
         auto maybe_error = reinitialize_image_decoder(socket_file);

+ 1 - 0
Tests/LibWeb/Text/expected/all-window-properties.txt

@@ -38,6 +38,7 @@ ByteLengthQueuingStrategy
 CDATASection
 CSSAnimation
 CSSConditionRule
+CSSFontFaceDescriptors
 CSSFontFaceRule
 CSSGroupingRule
 CSSImportRule

+ 1 - 0
Tests/LibWeb/Text/expected/css/CSSRule-type.txt

@@ -2,6 +2,7 @@ CSSImportRule type = 3
 CSSNamespaceRule type = 10
 CSSStyleRule type = 1
 CSSMediaRule type = 4
+CSSFontFaceRule type = 5
 CSSKeyframesRule type = 7
 CSSSupportsRule type = 12
 CSSLayerStatementRule type = 0

+ 4 - 3
Tests/LibWeb/Text/expected/wpt-import/css/css-fonts/parsing/font-face-size-adjust.txt

@@ -2,10 +2,11 @@ Harness status: OK
 
 Found 6 tests
 
-6 Fail
+2 Pass
+4 Fail
 Fail	Check that size-adjust: 100% is valid
 Fail	Check that size-adjust: 0% is valid
 Fail	Check that size-adjust: 110% is valid
 Fail	Check that size-adjust: 100000000000% is valid
-Fail	Check that size-adjust: -100% is invalid
-Fail	Check that size-adjust: -1% is invalid
+Pass	Check that size-adjust: -100% is invalid
+Pass	Check that size-adjust: -1% is invalid

+ 23 - 22
Tests/LibWeb/Text/expected/wpt-import/css/css-fonts/parsing/font-face-src-format.txt

@@ -2,33 +2,34 @@ Harness status: OK
 
 Found 35 tests
 
-35 Fail
-Fail	Check that src: url("foo.ttf") is valid
-Fail	Check that src: url("foo.ttf"), url("bar.ttf") is valid
-Fail	Check that src: url("foo.ttf") format() is invalid
-Fail	Check that src: url("foo.ttf") dummy() is invalid
-Fail	Check that src: url("foo.ttf") format("woff") dummy() is invalid
-Fail	Check that src: url("foo.ttf") dummy() format("woff") is invalid
+21 Pass
+14 Fail
+Pass	Check that src: url("foo.ttf") is valid
+Pass	Check that src: url("foo.ttf"), url("bar.ttf") is valid
+Pass	Check that src: url("foo.ttf") format() is invalid
+Pass	Check that src: url("foo.ttf") dummy() is invalid
+Pass	Check that src: url("foo.ttf") format("woff") dummy() is invalid
+Pass	Check that src: url("foo.ttf") dummy() format("woff") is invalid
 Fail	Check that src: url("foo.ttf") format("collection") is valid
-Fail	Check that src: url("foo.ttf") format("opentype") is valid
-Fail	Check that src: url("foo.ttf") format("truetype") is valid
-Fail	Check that src: url("foo.ttf") format("woff") is valid
-Fail	Check that src: url("foo.ttf") format("woff2") is valid
+Pass	Check that src: url("foo.ttf") format("opentype") is valid
+Pass	Check that src: url("foo.ttf") format("truetype") is valid
+Pass	Check that src: url("foo.ttf") format("woff") is valid
+Pass	Check that src: url("foo.ttf") format("woff2") is valid
 Fail	Check that src: url("foo.ttf") format("opentype", "truetype") is invalid
 Fail	Check that src: url("foo.ttf") format(collection) is valid
-Fail	Check that src: url("foo.ttf") format(opentype) is valid
-Fail	Check that src: url("foo.ttf") format(truetype) is valid
-Fail	Check that src: url("foo.ttf") format(woff) is valid
-Fail	Check that src: url("foo.ttf") format(woff2) is valid
+Pass	Check that src: url("foo.ttf") format(opentype) is valid
+Pass	Check that src: url("foo.ttf") format(truetype) is valid
+Pass	Check that src: url("foo.ttf") format(woff) is valid
+Pass	Check that src: url("foo.ttf") format(woff2) is valid
 Fail	Check that src: url("foo.ttf") format(opentype, truetype) is invalid
 Fail	Check that src: url("foo.ttf") format(opentype truetype) is invalid
-Fail	Check that src: url("foo.ttf") format(auto) is invalid
-Fail	Check that src: url("foo.ttf") format(default) is invalid
-Fail	Check that src: url("foo.ttf") format(inherit) is invalid
-Fail	Check that src: url("foo.ttf") format(initial) is invalid
-Fail	Check that src: url("foo.ttf") format(none) is invalid
-Fail	Check that src: url("foo.ttf") format(normal) is invalid
-Fail	Check that src: url("foo.ttf") format(xyzzy) is invalid
+Pass	Check that src: url("foo.ttf") format(auto) is invalid
+Pass	Check that src: url("foo.ttf") format(default) is invalid
+Pass	Check that src: url("foo.ttf") format(inherit) is invalid
+Pass	Check that src: url("foo.ttf") format(initial) is invalid
+Pass	Check that src: url("foo.ttf") format(none) is invalid
+Pass	Check that src: url("foo.ttf") format(normal) is invalid
+Pass	Check that src: url("foo.ttf") format(xyzzy) is invalid
 Fail	Check that src: url("foo.ttf") format("embedded-opentype"), url("bar.html") is valid
 Fail	Check that src: url("foo.ttf") format(embedded-opentype), url("bar.html") is valid
 Fail	Check that src: url("foo.ttf") format("svg"), url("bar.html") is valid

+ 7 - 6
Tests/LibWeb/Text/expected/wpt-import/css/css-fonts/parsing/font-face-src-list.txt

@@ -2,7 +2,8 @@ Harness status: OK
 
 Found 17 tests
 
-17 Fail
+5 Pass
+12 Fail
 Fail	Check that src: local(inherit), url(foo.ttf) is valid
 Fail	Check that src: local("myfont"), local(unset) is valid
 Fail	Check that src: local(), url(foo.ttf) is valid
@@ -15,8 +16,8 @@ Fail	Check that src: url(foo.ttf) tech(color-COLRv0) otherfunc(othervalue), url(
 Fail	Check that src: url(foo.ttf), url(something.ttf) format(broken) is valid
 Fail	Check that src: /* an empty component */, url(foo.ttf) is valid
 Fail	Check that src: local(""), url(foo.ttf), unparseable-garbage, local("another font name") is valid
-Fail	Check that src: local(), local(initial) is invalid
-Fail	Check that src: local("textfont") format(opentype), local("emoji") tech(color-COLRv0) is invalid
-Fail	Check that src: local(), /*empty*/, url(should be quoted.ttf), junk is invalid
-Fail	Check that src: url(foo.ttf) format(unknown), url(bar.ttf) tech(broken) is invalid
-Fail	Check that src: url(foo.ttf) tech(color-COLRv0) otherfunc(othervalue), junk is invalid
+Pass	Check that src: local(), local(initial) is invalid
+Pass	Check that src: local("textfont") format(opentype), local("emoji") tech(color-COLRv0) is invalid
+Pass	Check that src: local(), /*empty*/, url(should be quoted.ttf), junk is invalid
+Pass	Check that src: url(foo.ttf) format(unknown), url(bar.ttf) tech(broken) is invalid
+Pass	Check that src: url(foo.ttf) tech(color-COLRv0) otherfunc(othervalue), junk is invalid

+ 8 - 7
Tests/LibWeb/Text/expected/wpt-import/css/css-fonts/parsing/font-face-src-local.txt

@@ -2,13 +2,14 @@ Harness status: OK
 
 Found 18 tests
 
-18 Fail
-Fail	Check that src: local(A) dummy() is invalid
-Fail	Check that src: dummy() local(A) is invalid
-Fail	Check that src: local(  A  ) is valid
-Fail	Check that src: local(A B) is valid
-Fail	Check that src: local(A    B) is valid
-Fail	Check that src: local(   A  B   ) is valid
+6 Pass
+12 Fail
+Pass	Check that src: local(A) dummy() is invalid
+Pass	Check that src: dummy() local(A) is invalid
+Pass	Check that src: local(  A  ) is valid
+Pass	Check that src: local(A B) is valid
+Pass	Check that src: local(A    B) is valid
+Pass	Check that src: local(   A  B   ) is valid
 Fail	Check that src: local(default) is invalid
 Fail	Check that src: local(inherit) is invalid
 Fail	Check that src: local(revert) is invalid

+ 24 - 23
Tests/LibWeb/Text/expected/wpt-import/css/css-fonts/parsing/font-face-src-tech.txt

@@ -2,9 +2,10 @@ Harness status: OK
 
 Found 39 tests
 
-39 Fail
-Fail	Check that src: url("foo.ttf") is valid
-Fail	Check that src: url("foo.ttf") tech() is invalid
+22 Pass
+17 Fail
+Pass	Check that src: url("foo.ttf") is valid
+Pass	Check that src: url("foo.ttf") tech() is invalid
 Fail	Check that src: url("foo.ttf") tech(features-opentype) is valid
 Fail	Check that src: url("foo.ttf") tech(features-aat) is valid
 Fail	Check that src: url("foo.ttf") tech(color-COLRv0) is valid
@@ -13,32 +14,32 @@ Fail	Check that src: url("foo.ttf") tech(color-sbix) is valid
 Fail	Check that src: url("foo.ttf") tech(color-CBDT) is valid
 Fail	Check that src: url("foo.ttf") tech(variations) is valid
 Fail	Check that src: url("foo.ttf") tech(palettes) is valid
-Fail	Check that src: url("foo.ttf") tech("features-opentype") is invalid
-Fail	Check that src: url("foo.ttf") tech("color-COLRv0") is invalid
-Fail	Check that src: url("foo.ttf") tech("variations") is invalid
+Pass	Check that src: url("foo.ttf") tech("features-opentype") is invalid
+Pass	Check that src: url("foo.ttf") tech("color-COLRv0") is invalid
+Pass	Check that src: url("foo.ttf") tech("variations") is invalid
 Fail	Check that src: url("foo.ttf") tech(features-opentype, color-COLRv0, variations, palettes) is valid
-Fail	Check that src: url("foo.ttf") tech(features-opentype color-COLRv0 variations palettes) is invalid
-Fail	Check that src: url("foo.ttf") tech(feature-opentype) is invalid
-Fail	Check that src: url("foo.ttf") tech(feature-aat) is invalid
-Fail	Check that src: url("foo.ttf") tech(feature-graphite) is invalid
-Fail	Check that src: url("foo.ttf") tech(auto) is invalid
-Fail	Check that src: url("foo.ttf") tech(default) is invalid
-Fail	Check that src: url("foo.ttf") tech(inherit) is invalid
-Fail	Check that src: url("foo.ttf") tech(initial) is invalid
-Fail	Check that src: url("foo.ttf") tech(none) is invalid
-Fail	Check that src: url("foo.ttf") tech(normal) is invalid
-Fail	Check that src: url("foo.ttf") tech(xyzzy) is invalid
-Fail	Check that src: url("foo.ttf") tech(xyzzy, features-opentype) is invalid
-Fail	Check that src: url("foo.ttf") tech(features-opentype, xyzzy) is invalid
+Pass	Check that src: url("foo.ttf") tech(features-opentype color-COLRv0 variations palettes) is invalid
+Pass	Check that src: url("foo.ttf") tech(feature-opentype) is invalid
+Pass	Check that src: url("foo.ttf") tech(feature-aat) is invalid
+Pass	Check that src: url("foo.ttf") tech(feature-graphite) is invalid
+Pass	Check that src: url("foo.ttf") tech(auto) is invalid
+Pass	Check that src: url("foo.ttf") tech(default) is invalid
+Pass	Check that src: url("foo.ttf") tech(inherit) is invalid
+Pass	Check that src: url("foo.ttf") tech(initial) is invalid
+Pass	Check that src: url("foo.ttf") tech(none) is invalid
+Pass	Check that src: url("foo.ttf") tech(normal) is invalid
+Pass	Check that src: url("foo.ttf") tech(xyzzy) is invalid
+Pass	Check that src: url("foo.ttf") tech(xyzzy, features-opentype) is invalid
+Pass	Check that src: url("foo.ttf") tech(features-opentype, xyzzy) is invalid
 Fail	Check that src: url("foo.ttf") format(opentype) tech(features-opentype) is valid
-Fail	Check that src: url("foo.ttf") tech(features-opentype) format(opentype) is invalid
+Pass	Check that src: url("foo.ttf") tech(features-opentype) format(opentype) is invalid
 Fail	Check that src: url("foo.ttf") tech(incremental), url("bar.html") is valid
 Fail	Check that src: url("foo.ttf") tech(incremental, color-SVG, features-graphite, features-aat), url("bar.html") is valid
 Fail	Check that src: url("foo.ttf") tech(color-SVG, features-graphite), url("bar.html") is valid
 Fail	Check that src: url("foo.ttf") tech(color-SVG), url("bar.html") is valid
 Fail	Check that src: url("foo.ttf") tech(features-graphite), url("bar.html") is valid
-Fail	Check that src: url("foo.ttf") dummy("opentype") tech(variations) is invalid
-Fail	Check that src: url("foo.ttf") dummy("opentype") dummy(variations) is invalid
-Fail	Check that src: url("foo.ttf") format(opentype) tech(features-opentype) dummy(something) is invalid
+Pass	Check that src: url("foo.ttf") dummy("opentype") tech(variations) is invalid
+Pass	Check that src: url("foo.ttf") dummy("opentype") dummy(variations) is invalid
+Pass	Check that src: url("foo.ttf") format(opentype) tech(features-opentype) dummy(something) is invalid
 Fail	Check that src: url("foo.ttf") format(dummy), url("foo.ttf") tech(variations) is valid
 Fail	Check that src: url("foo.ttf") tech(color), url("bar.html") is valid

+ 1353 - 0
Tests/LibWeb/Text/expected/wpt-import/css/mediaqueries/test_media_queries.txt

@@ -0,0 +1,1353 @@
+Harness status: OK
+
+Found 1345 tests
+
+1257 Pass
+88 Fail
+Pass	query_should_be_parseable: (orientation)
+Pass	query_should_be_parseable: not (orientation)
+Pass	expression_should_be_known: (orientation)
+Pass	expression_should_be_known: not (orientation)
+Pass	query_should_not_be_parseable: only (orientation)
+Pass	query_should_be_parseable: all and (orientation)
+Pass	query_should_be_parseable: not all and (orientation)
+Pass	query_should_be_parseable: only all and (orientation)
+Pass	query_should_not_be_parseable: not not (orientation)
+Pass	query_should_be_parseable: (orientation) and (orientation)
+Pass	query_should_be_parseable: (orientation) or (orientation)
+Pass	query_should_be_parseable: (orientation) or ((orientation) and ((orientation) or (orientation) or (not (orientation))))
+Pass	expression_should_be_known: (orientation) and (orientation)
+Pass	expression_should_be_known: (orientation) or (orientation)
+Pass	expression_should_be_known: (orientation) or ((orientation) and ((orientation) or (orientation) or (not (orientation))))
+Pass	query_should_not_be_parseable: all and (orientation) or (orientation)
+Pass	query_should_be_parseable: all and (orientation) and (orientation)
+Pass	query_should_not_be_parseable: (orientation) and (orientation) or (orientation)
+Pass	query_should_not_be_parseable: (orientation) and not (orientation)
+Pass	expression_should_be_known: width
+Pass	expression_should_be_parseable: min-width
+Pass	expression_should_be_unknown: min-width
+Pass	expression_should_be_parseable: max-width
+Pass	expression_should_be_unknown: max-width
+Pass	expression_should_be_known: width : 0
+Pass	expression_should_be_known: width : 0px
+Pass	expression_should_be_known: width : 0em
+Pass	expression_should_be_known: width : -0
+Pass	expression_should_be_known: width : -0cm
+Pass	expression_should_be_known: width : 1px
+Pass	expression_should_be_known: width : 0.001mm
+Pass	expression_should_be_known: width : 100000px
+Pass	expression_should_be_known: min-width : -0
+Pass	expression_should_be_known: max-width : -0
+Pass	expression_should_be_known: width : -1px
+Pass	expression_should_be_known: min-width : -1px
+Pass	expression_should_be_known: max-width : -1px
+Pass	expression_should_be_known: width : -0.00001mm
+Pass	expression_should_be_known: width : -100000em
+Pass	expression_should_be_parseable: 0px : width : 0px
+Pass	expression_should_be_unknown: 0px : width : 0px
+Pass	expression_should_be_parseable: 0px : width > 0px
+Pass	expression_should_be_unknown: 0px : width > 0px
+Pass	expression_should_be_parseable: 0px : width >= 0px
+Pass	expression_should_be_unknown: 0px : width >= 0px
+Pass	expression_should_be_parseable: 0px : width = 0px
+Pass	expression_should_be_unknown: 0px : width = 0px
+Pass	expression_should_be_parseable: 0px : width <= 0px
+Pass	expression_should_be_unknown: 0px : width <= 0px
+Pass	expression_should_be_parseable: 0px : width < 0px
+Pass	expression_should_be_unknown: 0px : width < 0px
+Pass	expression_should_be_known: width > 0
+Pass	expression_should_be_known: width > 0px
+Pass	expression_should_be_known: width > 0em
+Pass	expression_should_be_known: width > -0
+Pass	expression_should_be_known: width > -0cm
+Pass	expression_should_be_known: width > 1px
+Pass	expression_should_be_known: width > 0.001mm
+Pass	expression_should_be_known: width > 100000px
+Pass	expression_should_be_parseable: min-width > -0
+Pass	expression_should_be_unknown: min-width > -0
+Pass	expression_should_be_parseable: max-width > -0
+Pass	expression_should_be_unknown: max-width > -0
+Pass	expression_should_be_known: 0px > width > 100000px
+Pass	expression_should_be_known: width > -1px
+Pass	expression_should_be_parseable: min-width > -1px
+Pass	expression_should_be_unknown: min-width > -1px
+Pass	expression_should_be_parseable: max-width > -1px
+Pass	expression_should_be_unknown: max-width > -1px
+Pass	expression_should_be_known: width > -0.00001mm
+Pass	expression_should_be_known: width > -100000em
+Pass	expression_should_be_parseable: 0px > width : 0px
+Pass	expression_should_be_unknown: 0px > width : 0px
+Pass	expression_should_be_known: 0px > width > 0px
+Pass	expression_should_be_known: 0px > width >= 0px
+Pass	expression_should_be_parseable: 0px > width = 0px
+Pass	expression_should_be_unknown: 0px > width = 0px
+Pass	expression_should_be_parseable: 0px > width <= 0px
+Pass	expression_should_be_unknown: 0px > width <= 0px
+Pass	expression_should_be_parseable: 0px > width < 0px
+Pass	expression_should_be_unknown: 0px > width < 0px
+Pass	expression_should_be_known: width >= 0
+Pass	expression_should_be_known: width >= 0px
+Pass	expression_should_be_known: width >= 0em
+Pass	expression_should_be_known: width >= -0
+Pass	expression_should_be_known: width >= -0cm
+Pass	expression_should_be_known: width >= 1px
+Pass	expression_should_be_known: width >= 0.001mm
+Pass	expression_should_be_known: width >= 100000px
+Pass	expression_should_be_parseable: min-width >= -0
+Pass	expression_should_be_unknown: min-width >= -0
+Pass	expression_should_be_parseable: max-width >= -0
+Pass	expression_should_be_unknown: max-width >= -0
+Pass	expression_should_be_known: 0px >= width >= 100000px
+Pass	expression_should_be_parseable: width > = 0px
+Pass	expression_should_be_unknown: width > = 0px
+Pass	expression_should_be_known: width >= -1px
+Pass	expression_should_be_parseable: min-width >= -1px
+Pass	expression_should_be_unknown: min-width >= -1px
+Pass	expression_should_be_parseable: max-width >= -1px
+Pass	expression_should_be_unknown: max-width >= -1px
+Pass	expression_should_be_known: width >= -0.00001mm
+Pass	expression_should_be_known: width >= -100000em
+Pass	expression_should_be_parseable: 0px >= width : 0px
+Pass	expression_should_be_unknown: 0px >= width : 0px
+Pass	expression_should_be_known: 0px >= width > 0px
+Pass	expression_should_be_known: 0px >= width >= 0px
+Pass	expression_should_be_parseable: 0px >= width = 0px
+Pass	expression_should_be_unknown: 0px >= width = 0px
+Pass	expression_should_be_parseable: 0px >= width <= 0px
+Pass	expression_should_be_unknown: 0px >= width <= 0px
+Pass	expression_should_be_parseable: 0px >= width < 0px
+Pass	expression_should_be_unknown: 0px >= width < 0px
+Pass	expression_should_be_known: width = 0
+Pass	expression_should_be_known: width = 0px
+Pass	expression_should_be_known: width = 0em
+Pass	expression_should_be_known: width = -0
+Pass	expression_should_be_known: width = -0cm
+Pass	expression_should_be_known: width = 1px
+Pass	expression_should_be_known: width = 0.001mm
+Pass	expression_should_be_known: width = 100000px
+Pass	expression_should_be_parseable: min-width = -0
+Pass	expression_should_be_unknown: min-width = -0
+Pass	expression_should_be_parseable: max-width = -0
+Pass	expression_should_be_unknown: max-width = -0
+Pass	expression_should_be_parseable: 0px = width = 100000px
+Pass	expression_should_be_unknown: 0px = width = 100000px
+Pass	expression_should_be_known: width = -1px
+Pass	expression_should_be_parseable: min-width = -1px
+Pass	expression_should_be_unknown: min-width = -1px
+Pass	expression_should_be_parseable: max-width = -1px
+Pass	expression_should_be_unknown: max-width = -1px
+Pass	expression_should_be_known: width = -0.00001mm
+Pass	expression_should_be_known: width = -100000em
+Pass	expression_should_be_parseable: 0px = width : 0px
+Pass	expression_should_be_unknown: 0px = width : 0px
+Pass	expression_should_be_parseable: 0px = width > 0px
+Pass	expression_should_be_unknown: 0px = width > 0px
+Pass	expression_should_be_parseable: 0px = width >= 0px
+Pass	expression_should_be_unknown: 0px = width >= 0px
+Pass	expression_should_be_parseable: 0px = width = 0px
+Pass	expression_should_be_unknown: 0px = width = 0px
+Pass	expression_should_be_parseable: 0px = width <= 0px
+Pass	expression_should_be_unknown: 0px = width <= 0px
+Pass	expression_should_be_parseable: 0px = width < 0px
+Pass	expression_should_be_unknown: 0px = width < 0px
+Pass	expression_should_be_known: width <= 0
+Pass	expression_should_be_known: width <= 0px
+Pass	expression_should_be_known: width <= 0em
+Pass	expression_should_be_known: width <= -0
+Pass	expression_should_be_known: width <= -0cm
+Pass	expression_should_be_known: width <= 1px
+Pass	expression_should_be_known: width <= 0.001mm
+Pass	expression_should_be_known: width <= 100000px
+Pass	expression_should_be_parseable: min-width <= -0
+Pass	expression_should_be_unknown: min-width <= -0
+Pass	expression_should_be_parseable: max-width <= -0
+Pass	expression_should_be_unknown: max-width <= -0
+Pass	expression_should_be_known: 0px <= width <= 100000px
+Pass	expression_should_be_parseable: width < = 0px
+Pass	expression_should_be_unknown: width < = 0px
+Pass	expression_should_be_known: width <= -1px
+Pass	expression_should_be_parseable: min-width <= -1px
+Pass	expression_should_be_unknown: min-width <= -1px
+Pass	expression_should_be_parseable: max-width <= -1px
+Pass	expression_should_be_unknown: max-width <= -1px
+Pass	expression_should_be_known: width <= -0.00001mm
+Pass	expression_should_be_known: width <= -100000em
+Pass	expression_should_be_parseable: 0px <= width : 0px
+Pass	expression_should_be_unknown: 0px <= width : 0px
+Pass	expression_should_be_parseable: 0px <= width > 0px
+Pass	expression_should_be_unknown: 0px <= width > 0px
+Pass	expression_should_be_parseable: 0px <= width >= 0px
+Pass	expression_should_be_unknown: 0px <= width >= 0px
+Pass	expression_should_be_parseable: 0px <= width = 0px
+Pass	expression_should_be_unknown: 0px <= width = 0px
+Pass	expression_should_be_known: 0px <= width <= 0px
+Pass	expression_should_be_known: 0px <= width < 0px
+Pass	expression_should_be_known: width < 0
+Pass	expression_should_be_known: width < 0px
+Pass	expression_should_be_known: width < 0em
+Pass	expression_should_be_known: width < -0
+Pass	expression_should_be_known: width < -0cm
+Pass	expression_should_be_known: width < 1px
+Pass	expression_should_be_known: width < 0.001mm
+Pass	expression_should_be_known: width < 100000px
+Pass	expression_should_be_parseable: min-width < -0
+Pass	expression_should_be_unknown: min-width < -0
+Pass	expression_should_be_parseable: max-width < -0
+Pass	expression_should_be_unknown: max-width < -0
+Pass	expression_should_be_known: 0px < width < 100000px
+Pass	expression_should_be_known: width < -1px
+Pass	expression_should_be_parseable: min-width < -1px
+Pass	expression_should_be_unknown: min-width < -1px
+Pass	expression_should_be_parseable: max-width < -1px
+Pass	expression_should_be_unknown: max-width < -1px
+Pass	expression_should_be_known: width < -0.00001mm
+Pass	expression_should_be_known: width < -100000em
+Pass	expression_should_be_parseable: 0px < width : 0px
+Pass	expression_should_be_unknown: 0px < width : 0px
+Pass	expression_should_be_parseable: 0px < width > 0px
+Pass	expression_should_be_unknown: 0px < width > 0px
+Pass	expression_should_be_parseable: 0px < width >= 0px
+Pass	expression_should_be_unknown: 0px < width >= 0px
+Pass	expression_should_be_parseable: 0px < width = 0px
+Pass	expression_should_be_unknown: 0px < width = 0px
+Pass	expression_should_be_known: 0px < width <= 0px
+Pass	expression_should_be_known: 0px < width < 0px
+Pass	expression_should_be_known: height
+Pass	expression_should_be_parseable: min-height
+Pass	expression_should_be_unknown: min-height
+Pass	expression_should_be_parseable: max-height
+Pass	expression_should_be_unknown: max-height
+Pass	expression_should_be_known: height : 0
+Pass	expression_should_be_known: height : 0px
+Pass	expression_should_be_known: height : 0em
+Pass	expression_should_be_known: height : -0
+Pass	expression_should_be_known: height : -0cm
+Pass	expression_should_be_known: height : 1px
+Pass	expression_should_be_known: height : 0.001mm
+Pass	expression_should_be_known: height : 100000px
+Pass	expression_should_be_known: min-height : -0
+Pass	expression_should_be_known: max-height : -0
+Pass	expression_should_be_known: height : -1px
+Pass	expression_should_be_known: min-height : -1px
+Pass	expression_should_be_known: max-height : -1px
+Pass	expression_should_be_known: height : -0.00001mm
+Pass	expression_should_be_known: height : -100000em
+Pass	expression_should_be_parseable: 0px : height : 0px
+Pass	expression_should_be_unknown: 0px : height : 0px
+Pass	expression_should_be_parseable: 0px : height > 0px
+Pass	expression_should_be_unknown: 0px : height > 0px
+Pass	expression_should_be_parseable: 0px : height >= 0px
+Pass	expression_should_be_unknown: 0px : height >= 0px
+Pass	expression_should_be_parseable: 0px : height = 0px
+Pass	expression_should_be_unknown: 0px : height = 0px
+Pass	expression_should_be_parseable: 0px : height <= 0px
+Pass	expression_should_be_unknown: 0px : height <= 0px
+Pass	expression_should_be_parseable: 0px : height < 0px
+Pass	expression_should_be_unknown: 0px : height < 0px
+Pass	expression_should_be_known: height > 0
+Pass	expression_should_be_known: height > 0px
+Pass	expression_should_be_known: height > 0em
+Pass	expression_should_be_known: height > -0
+Pass	expression_should_be_known: height > -0cm
+Pass	expression_should_be_known: height > 1px
+Pass	expression_should_be_known: height > 0.001mm
+Pass	expression_should_be_known: height > 100000px
+Pass	expression_should_be_parseable: min-height > -0
+Pass	expression_should_be_unknown: min-height > -0
+Pass	expression_should_be_parseable: max-height > -0
+Pass	expression_should_be_unknown: max-height > -0
+Pass	expression_should_be_known: 0px > height > 100000px
+Pass	expression_should_be_known: height > -1px
+Pass	expression_should_be_parseable: min-height > -1px
+Pass	expression_should_be_unknown: min-height > -1px
+Pass	expression_should_be_parseable: max-height > -1px
+Pass	expression_should_be_unknown: max-height > -1px
+Pass	expression_should_be_known: height > -0.00001mm
+Pass	expression_should_be_known: height > -100000em
+Pass	expression_should_be_parseable: 0px > height : 0px
+Pass	expression_should_be_unknown: 0px > height : 0px
+Pass	expression_should_be_known: 0px > height > 0px
+Pass	expression_should_be_known: 0px > height >= 0px
+Pass	expression_should_be_parseable: 0px > height = 0px
+Pass	expression_should_be_unknown: 0px > height = 0px
+Pass	expression_should_be_parseable: 0px > height <= 0px
+Pass	expression_should_be_unknown: 0px > height <= 0px
+Pass	expression_should_be_parseable: 0px > height < 0px
+Pass	expression_should_be_unknown: 0px > height < 0px
+Pass	expression_should_be_known: height >= 0
+Pass	expression_should_be_known: height >= 0px
+Pass	expression_should_be_known: height >= 0em
+Pass	expression_should_be_known: height >= -0
+Pass	expression_should_be_known: height >= -0cm
+Pass	expression_should_be_known: height >= 1px
+Pass	expression_should_be_known: height >= 0.001mm
+Pass	expression_should_be_known: height >= 100000px
+Pass	expression_should_be_parseable: min-height >= -0
+Pass	expression_should_be_unknown: min-height >= -0
+Pass	expression_should_be_parseable: max-height >= -0
+Pass	expression_should_be_unknown: max-height >= -0
+Pass	expression_should_be_known: 0px >= height >= 100000px
+Pass	expression_should_be_parseable: height > = 0px
+Pass	expression_should_be_unknown: height > = 0px
+Pass	expression_should_be_known: height >= -1px
+Pass	expression_should_be_parseable: min-height >= -1px
+Pass	expression_should_be_unknown: min-height >= -1px
+Pass	expression_should_be_parseable: max-height >= -1px
+Pass	expression_should_be_unknown: max-height >= -1px
+Pass	expression_should_be_known: height >= -0.00001mm
+Pass	expression_should_be_known: height >= -100000em
+Pass	expression_should_be_parseable: 0px >= height : 0px
+Pass	expression_should_be_unknown: 0px >= height : 0px
+Pass	expression_should_be_known: 0px >= height > 0px
+Pass	expression_should_be_known: 0px >= height >= 0px
+Pass	expression_should_be_parseable: 0px >= height = 0px
+Pass	expression_should_be_unknown: 0px >= height = 0px
+Pass	expression_should_be_parseable: 0px >= height <= 0px
+Pass	expression_should_be_unknown: 0px >= height <= 0px
+Pass	expression_should_be_parseable: 0px >= height < 0px
+Pass	expression_should_be_unknown: 0px >= height < 0px
+Pass	expression_should_be_known: height = 0
+Pass	expression_should_be_known: height = 0px
+Pass	expression_should_be_known: height = 0em
+Pass	expression_should_be_known: height = -0
+Pass	expression_should_be_known: height = -0cm
+Pass	expression_should_be_known: height = 1px
+Pass	expression_should_be_known: height = 0.001mm
+Pass	expression_should_be_known: height = 100000px
+Pass	expression_should_be_parseable: min-height = -0
+Pass	expression_should_be_unknown: min-height = -0
+Pass	expression_should_be_parseable: max-height = -0
+Pass	expression_should_be_unknown: max-height = -0
+Pass	expression_should_be_parseable: 0px = height = 100000px
+Pass	expression_should_be_unknown: 0px = height = 100000px
+Pass	expression_should_be_known: height = -1px
+Pass	expression_should_be_parseable: min-height = -1px
+Pass	expression_should_be_unknown: min-height = -1px
+Pass	expression_should_be_parseable: max-height = -1px
+Pass	expression_should_be_unknown: max-height = -1px
+Pass	expression_should_be_known: height = -0.00001mm
+Pass	expression_should_be_known: height = -100000em
+Pass	expression_should_be_parseable: 0px = height : 0px
+Pass	expression_should_be_unknown: 0px = height : 0px
+Pass	expression_should_be_parseable: 0px = height > 0px
+Pass	expression_should_be_unknown: 0px = height > 0px
+Pass	expression_should_be_parseable: 0px = height >= 0px
+Pass	expression_should_be_unknown: 0px = height >= 0px
+Pass	expression_should_be_parseable: 0px = height = 0px
+Pass	expression_should_be_unknown: 0px = height = 0px
+Pass	expression_should_be_parseable: 0px = height <= 0px
+Pass	expression_should_be_unknown: 0px = height <= 0px
+Pass	expression_should_be_parseable: 0px = height < 0px
+Pass	expression_should_be_unknown: 0px = height < 0px
+Pass	expression_should_be_known: height <= 0
+Pass	expression_should_be_known: height <= 0px
+Pass	expression_should_be_known: height <= 0em
+Pass	expression_should_be_known: height <= -0
+Pass	expression_should_be_known: height <= -0cm
+Pass	expression_should_be_known: height <= 1px
+Pass	expression_should_be_known: height <= 0.001mm
+Pass	expression_should_be_known: height <= 100000px
+Pass	expression_should_be_parseable: min-height <= -0
+Pass	expression_should_be_unknown: min-height <= -0
+Pass	expression_should_be_parseable: max-height <= -0
+Pass	expression_should_be_unknown: max-height <= -0
+Pass	expression_should_be_known: 0px <= height <= 100000px
+Pass	expression_should_be_parseable: height < = 0px
+Pass	expression_should_be_unknown: height < = 0px
+Pass	expression_should_be_known: height <= -1px
+Pass	expression_should_be_parseable: min-height <= -1px
+Pass	expression_should_be_unknown: min-height <= -1px
+Pass	expression_should_be_parseable: max-height <= -1px
+Pass	expression_should_be_unknown: max-height <= -1px
+Pass	expression_should_be_known: height <= -0.00001mm
+Pass	expression_should_be_known: height <= -100000em
+Pass	expression_should_be_parseable: 0px <= height : 0px
+Pass	expression_should_be_unknown: 0px <= height : 0px
+Pass	expression_should_be_parseable: 0px <= height > 0px
+Pass	expression_should_be_unknown: 0px <= height > 0px
+Pass	expression_should_be_parseable: 0px <= height >= 0px
+Pass	expression_should_be_unknown: 0px <= height >= 0px
+Pass	expression_should_be_parseable: 0px <= height = 0px
+Pass	expression_should_be_unknown: 0px <= height = 0px
+Pass	expression_should_be_known: 0px <= height <= 0px
+Pass	expression_should_be_known: 0px <= height < 0px
+Pass	expression_should_be_known: height < 0
+Pass	expression_should_be_known: height < 0px
+Pass	expression_should_be_known: height < 0em
+Pass	expression_should_be_known: height < -0
+Pass	expression_should_be_known: height < -0cm
+Pass	expression_should_be_known: height < 1px
+Pass	expression_should_be_known: height < 0.001mm
+Pass	expression_should_be_known: height < 100000px
+Pass	expression_should_be_parseable: min-height < -0
+Pass	expression_should_be_unknown: min-height < -0
+Pass	expression_should_be_parseable: max-height < -0
+Pass	expression_should_be_unknown: max-height < -0
+Pass	expression_should_be_known: 0px < height < 100000px
+Pass	expression_should_be_known: height < -1px
+Pass	expression_should_be_parseable: min-height < -1px
+Pass	expression_should_be_unknown: min-height < -1px
+Pass	expression_should_be_parseable: max-height < -1px
+Pass	expression_should_be_unknown: max-height < -1px
+Pass	expression_should_be_known: height < -0.00001mm
+Pass	expression_should_be_known: height < -100000em
+Pass	expression_should_be_parseable: 0px < height : 0px
+Pass	expression_should_be_unknown: 0px < height : 0px
+Pass	expression_should_be_parseable: 0px < height > 0px
+Pass	expression_should_be_unknown: 0px < height > 0px
+Pass	expression_should_be_parseable: 0px < height >= 0px
+Pass	expression_should_be_unknown: 0px < height >= 0px
+Pass	expression_should_be_parseable: 0px < height = 0px
+Pass	expression_should_be_unknown: 0px < height = 0px
+Pass	expression_should_be_known: 0px < height <= 0px
+Pass	expression_should_be_known: 0px < height < 0px
+Pass	expression_should_be_known: device-width
+Pass	expression_should_be_parseable: min-device-width
+Pass	expression_should_be_unknown: min-device-width
+Pass	expression_should_be_parseable: max-device-width
+Pass	expression_should_be_unknown: max-device-width
+Pass	expression_should_be_known: device-width : 0
+Pass	expression_should_be_known: device-width : 0px
+Pass	expression_should_be_known: device-width : 0em
+Pass	expression_should_be_known: device-width : -0
+Pass	expression_should_be_known: device-width : -0cm
+Pass	expression_should_be_known: device-width : 1px
+Pass	expression_should_be_known: device-width : 0.001mm
+Pass	expression_should_be_known: device-width : 100000px
+Pass	expression_should_be_known: min-device-width : -0
+Pass	expression_should_be_known: max-device-width : -0
+Pass	expression_should_be_known: device-width : -1px
+Pass	expression_should_be_known: min-device-width : -1px
+Pass	expression_should_be_known: max-device-width : -1px
+Pass	expression_should_be_known: device-width : -0.00001mm
+Pass	expression_should_be_known: device-width : -100000em
+Pass	expression_should_be_parseable: 0px : device-width : 0px
+Pass	expression_should_be_unknown: 0px : device-width : 0px
+Pass	expression_should_be_parseable: 0px : device-width > 0px
+Pass	expression_should_be_unknown: 0px : device-width > 0px
+Pass	expression_should_be_parseable: 0px : device-width >= 0px
+Pass	expression_should_be_unknown: 0px : device-width >= 0px
+Pass	expression_should_be_parseable: 0px : device-width = 0px
+Pass	expression_should_be_unknown: 0px : device-width = 0px
+Pass	expression_should_be_parseable: 0px : device-width <= 0px
+Pass	expression_should_be_unknown: 0px : device-width <= 0px
+Pass	expression_should_be_parseable: 0px : device-width < 0px
+Pass	expression_should_be_unknown: 0px : device-width < 0px
+Pass	expression_should_be_known: device-width > 0
+Pass	expression_should_be_known: device-width > 0px
+Pass	expression_should_be_known: device-width > 0em
+Pass	expression_should_be_known: device-width > -0
+Pass	expression_should_be_known: device-width > -0cm
+Pass	expression_should_be_known: device-width > 1px
+Pass	expression_should_be_known: device-width > 0.001mm
+Pass	expression_should_be_known: device-width > 100000px
+Pass	expression_should_be_parseable: min-device-width > -0
+Pass	expression_should_be_unknown: min-device-width > -0
+Pass	expression_should_be_parseable: max-device-width > -0
+Pass	expression_should_be_unknown: max-device-width > -0
+Pass	expression_should_be_known: 0px > device-width > 100000px
+Pass	expression_should_be_known: device-width > -1px
+Pass	expression_should_be_parseable: min-device-width > -1px
+Pass	expression_should_be_unknown: min-device-width > -1px
+Pass	expression_should_be_parseable: max-device-width > -1px
+Pass	expression_should_be_unknown: max-device-width > -1px
+Pass	expression_should_be_known: device-width > -0.00001mm
+Pass	expression_should_be_known: device-width > -100000em
+Pass	expression_should_be_parseable: 0px > device-width : 0px
+Pass	expression_should_be_unknown: 0px > device-width : 0px
+Pass	expression_should_be_known: 0px > device-width > 0px
+Pass	expression_should_be_known: 0px > device-width >= 0px
+Pass	expression_should_be_parseable: 0px > device-width = 0px
+Pass	expression_should_be_unknown: 0px > device-width = 0px
+Pass	expression_should_be_parseable: 0px > device-width <= 0px
+Pass	expression_should_be_unknown: 0px > device-width <= 0px
+Pass	expression_should_be_parseable: 0px > device-width < 0px
+Pass	expression_should_be_unknown: 0px > device-width < 0px
+Pass	expression_should_be_known: device-width >= 0
+Pass	expression_should_be_known: device-width >= 0px
+Pass	expression_should_be_known: device-width >= 0em
+Pass	expression_should_be_known: device-width >= -0
+Pass	expression_should_be_known: device-width >= -0cm
+Pass	expression_should_be_known: device-width >= 1px
+Pass	expression_should_be_known: device-width >= 0.001mm
+Pass	expression_should_be_known: device-width >= 100000px
+Pass	expression_should_be_parseable: min-device-width >= -0
+Pass	expression_should_be_unknown: min-device-width >= -0
+Pass	expression_should_be_parseable: max-device-width >= -0
+Pass	expression_should_be_unknown: max-device-width >= -0
+Pass	expression_should_be_known: 0px >= device-width >= 100000px
+Pass	expression_should_be_parseable: device-width > = 0px
+Pass	expression_should_be_unknown: device-width > = 0px
+Pass	expression_should_be_known: device-width >= -1px
+Pass	expression_should_be_parseable: min-device-width >= -1px
+Pass	expression_should_be_unknown: min-device-width >= -1px
+Pass	expression_should_be_parseable: max-device-width >= -1px
+Pass	expression_should_be_unknown: max-device-width >= -1px
+Pass	expression_should_be_known: device-width >= -0.00001mm
+Pass	expression_should_be_known: device-width >= -100000em
+Pass	expression_should_be_parseable: 0px >= device-width : 0px
+Pass	expression_should_be_unknown: 0px >= device-width : 0px
+Pass	expression_should_be_known: 0px >= device-width > 0px
+Pass	expression_should_be_known: 0px >= device-width >= 0px
+Pass	expression_should_be_parseable: 0px >= device-width = 0px
+Pass	expression_should_be_unknown: 0px >= device-width = 0px
+Pass	expression_should_be_parseable: 0px >= device-width <= 0px
+Pass	expression_should_be_unknown: 0px >= device-width <= 0px
+Pass	expression_should_be_parseable: 0px >= device-width < 0px
+Pass	expression_should_be_unknown: 0px >= device-width < 0px
+Pass	expression_should_be_known: device-width = 0
+Pass	expression_should_be_known: device-width = 0px
+Pass	expression_should_be_known: device-width = 0em
+Pass	expression_should_be_known: device-width = -0
+Pass	expression_should_be_known: device-width = -0cm
+Pass	expression_should_be_known: device-width = 1px
+Pass	expression_should_be_known: device-width = 0.001mm
+Pass	expression_should_be_known: device-width = 100000px
+Pass	expression_should_be_parseable: min-device-width = -0
+Pass	expression_should_be_unknown: min-device-width = -0
+Pass	expression_should_be_parseable: max-device-width = -0
+Pass	expression_should_be_unknown: max-device-width = -0
+Pass	expression_should_be_parseable: 0px = device-width = 100000px
+Pass	expression_should_be_unknown: 0px = device-width = 100000px
+Pass	expression_should_be_known: device-width = -1px
+Pass	expression_should_be_parseable: min-device-width = -1px
+Pass	expression_should_be_unknown: min-device-width = -1px
+Pass	expression_should_be_parseable: max-device-width = -1px
+Pass	expression_should_be_unknown: max-device-width = -1px
+Pass	expression_should_be_known: device-width = -0.00001mm
+Pass	expression_should_be_known: device-width = -100000em
+Pass	expression_should_be_parseable: 0px = device-width : 0px
+Pass	expression_should_be_unknown: 0px = device-width : 0px
+Pass	expression_should_be_parseable: 0px = device-width > 0px
+Pass	expression_should_be_unknown: 0px = device-width > 0px
+Pass	expression_should_be_parseable: 0px = device-width >= 0px
+Pass	expression_should_be_unknown: 0px = device-width >= 0px
+Pass	expression_should_be_parseable: 0px = device-width = 0px
+Pass	expression_should_be_unknown: 0px = device-width = 0px
+Pass	expression_should_be_parseable: 0px = device-width <= 0px
+Pass	expression_should_be_unknown: 0px = device-width <= 0px
+Pass	expression_should_be_parseable: 0px = device-width < 0px
+Pass	expression_should_be_unknown: 0px = device-width < 0px
+Pass	expression_should_be_known: device-width <= 0
+Pass	expression_should_be_known: device-width <= 0px
+Pass	expression_should_be_known: device-width <= 0em
+Pass	expression_should_be_known: device-width <= -0
+Pass	expression_should_be_known: device-width <= -0cm
+Pass	expression_should_be_known: device-width <= 1px
+Pass	expression_should_be_known: device-width <= 0.001mm
+Pass	expression_should_be_known: device-width <= 100000px
+Pass	expression_should_be_parseable: min-device-width <= -0
+Pass	expression_should_be_unknown: min-device-width <= -0
+Pass	expression_should_be_parseable: max-device-width <= -0
+Pass	expression_should_be_unknown: max-device-width <= -0
+Pass	expression_should_be_known: 0px <= device-width <= 100000px
+Pass	expression_should_be_parseable: device-width < = 0px
+Pass	expression_should_be_unknown: device-width < = 0px
+Pass	expression_should_be_known: device-width <= -1px
+Pass	expression_should_be_parseable: min-device-width <= -1px
+Pass	expression_should_be_unknown: min-device-width <= -1px
+Pass	expression_should_be_parseable: max-device-width <= -1px
+Pass	expression_should_be_unknown: max-device-width <= -1px
+Pass	expression_should_be_known: device-width <= -0.00001mm
+Pass	expression_should_be_known: device-width <= -100000em
+Pass	expression_should_be_parseable: 0px <= device-width : 0px
+Pass	expression_should_be_unknown: 0px <= device-width : 0px
+Pass	expression_should_be_parseable: 0px <= device-width > 0px
+Pass	expression_should_be_unknown: 0px <= device-width > 0px
+Pass	expression_should_be_parseable: 0px <= device-width >= 0px
+Pass	expression_should_be_unknown: 0px <= device-width >= 0px
+Pass	expression_should_be_parseable: 0px <= device-width = 0px
+Pass	expression_should_be_unknown: 0px <= device-width = 0px
+Pass	expression_should_be_known: 0px <= device-width <= 0px
+Pass	expression_should_be_known: 0px <= device-width < 0px
+Pass	expression_should_be_known: device-width < 0
+Pass	expression_should_be_known: device-width < 0px
+Pass	expression_should_be_known: device-width < 0em
+Pass	expression_should_be_known: device-width < -0
+Pass	expression_should_be_known: device-width < -0cm
+Pass	expression_should_be_known: device-width < 1px
+Pass	expression_should_be_known: device-width < 0.001mm
+Pass	expression_should_be_known: device-width < 100000px
+Pass	expression_should_be_parseable: min-device-width < -0
+Pass	expression_should_be_unknown: min-device-width < -0
+Pass	expression_should_be_parseable: max-device-width < -0
+Pass	expression_should_be_unknown: max-device-width < -0
+Pass	expression_should_be_known: 0px < device-width < 100000px
+Pass	expression_should_be_known: device-width < -1px
+Pass	expression_should_be_parseable: min-device-width < -1px
+Pass	expression_should_be_unknown: min-device-width < -1px
+Pass	expression_should_be_parseable: max-device-width < -1px
+Pass	expression_should_be_unknown: max-device-width < -1px
+Pass	expression_should_be_known: device-width < -0.00001mm
+Pass	expression_should_be_known: device-width < -100000em
+Pass	expression_should_be_parseable: 0px < device-width : 0px
+Pass	expression_should_be_unknown: 0px < device-width : 0px
+Pass	expression_should_be_parseable: 0px < device-width > 0px
+Pass	expression_should_be_unknown: 0px < device-width > 0px
+Pass	expression_should_be_parseable: 0px < device-width >= 0px
+Pass	expression_should_be_unknown: 0px < device-width >= 0px
+Pass	expression_should_be_parseable: 0px < device-width = 0px
+Pass	expression_should_be_unknown: 0px < device-width = 0px
+Pass	expression_should_be_known: 0px < device-width <= 0px
+Pass	expression_should_be_known: 0px < device-width < 0px
+Pass	expression_should_be_known: device-height
+Pass	expression_should_be_parseable: min-device-height
+Pass	expression_should_be_unknown: min-device-height
+Pass	expression_should_be_parseable: max-device-height
+Pass	expression_should_be_unknown: max-device-height
+Pass	expression_should_be_known: device-height : 0
+Pass	expression_should_be_known: device-height : 0px
+Pass	expression_should_be_known: device-height : 0em
+Pass	expression_should_be_known: device-height : -0
+Pass	expression_should_be_known: device-height : -0cm
+Pass	expression_should_be_known: device-height : 1px
+Pass	expression_should_be_known: device-height : 0.001mm
+Pass	expression_should_be_known: device-height : 100000px
+Pass	expression_should_be_known: min-device-height : -0
+Pass	expression_should_be_known: max-device-height : -0
+Pass	expression_should_be_known: device-height : -1px
+Pass	expression_should_be_known: min-device-height : -1px
+Pass	expression_should_be_known: max-device-height : -1px
+Pass	expression_should_be_known: device-height : -0.00001mm
+Pass	expression_should_be_known: device-height : -100000em
+Pass	expression_should_be_parseable: 0px : device-height : 0px
+Pass	expression_should_be_unknown: 0px : device-height : 0px
+Pass	expression_should_be_parseable: 0px : device-height > 0px
+Pass	expression_should_be_unknown: 0px : device-height > 0px
+Pass	expression_should_be_parseable: 0px : device-height >= 0px
+Pass	expression_should_be_unknown: 0px : device-height >= 0px
+Pass	expression_should_be_parseable: 0px : device-height = 0px
+Pass	expression_should_be_unknown: 0px : device-height = 0px
+Pass	expression_should_be_parseable: 0px : device-height <= 0px
+Pass	expression_should_be_unknown: 0px : device-height <= 0px
+Pass	expression_should_be_parseable: 0px : device-height < 0px
+Pass	expression_should_be_unknown: 0px : device-height < 0px
+Pass	expression_should_be_known: device-height > 0
+Pass	expression_should_be_known: device-height > 0px
+Pass	expression_should_be_known: device-height > 0em
+Pass	expression_should_be_known: device-height > -0
+Pass	expression_should_be_known: device-height > -0cm
+Pass	expression_should_be_known: device-height > 1px
+Pass	expression_should_be_known: device-height > 0.001mm
+Pass	expression_should_be_known: device-height > 100000px
+Pass	expression_should_be_parseable: min-device-height > -0
+Pass	expression_should_be_unknown: min-device-height > -0
+Pass	expression_should_be_parseable: max-device-height > -0
+Pass	expression_should_be_unknown: max-device-height > -0
+Pass	expression_should_be_known: 0px > device-height > 100000px
+Pass	expression_should_be_known: device-height > -1px
+Pass	expression_should_be_parseable: min-device-height > -1px
+Pass	expression_should_be_unknown: min-device-height > -1px
+Pass	expression_should_be_parseable: max-device-height > -1px
+Pass	expression_should_be_unknown: max-device-height > -1px
+Pass	expression_should_be_known: device-height > -0.00001mm
+Pass	expression_should_be_known: device-height > -100000em
+Pass	expression_should_be_parseable: 0px > device-height : 0px
+Pass	expression_should_be_unknown: 0px > device-height : 0px
+Pass	expression_should_be_known: 0px > device-height > 0px
+Pass	expression_should_be_known: 0px > device-height >= 0px
+Pass	expression_should_be_parseable: 0px > device-height = 0px
+Pass	expression_should_be_unknown: 0px > device-height = 0px
+Pass	expression_should_be_parseable: 0px > device-height <= 0px
+Pass	expression_should_be_unknown: 0px > device-height <= 0px
+Pass	expression_should_be_parseable: 0px > device-height < 0px
+Pass	expression_should_be_unknown: 0px > device-height < 0px
+Pass	expression_should_be_known: device-height >= 0
+Pass	expression_should_be_known: device-height >= 0px
+Pass	expression_should_be_known: device-height >= 0em
+Pass	expression_should_be_known: device-height >= -0
+Pass	expression_should_be_known: device-height >= -0cm
+Pass	expression_should_be_known: device-height >= 1px
+Pass	expression_should_be_known: device-height >= 0.001mm
+Pass	expression_should_be_known: device-height >= 100000px
+Pass	expression_should_be_parseable: min-device-height >= -0
+Pass	expression_should_be_unknown: min-device-height >= -0
+Pass	expression_should_be_parseable: max-device-height >= -0
+Pass	expression_should_be_unknown: max-device-height >= -0
+Pass	expression_should_be_known: 0px >= device-height >= 100000px
+Pass	expression_should_be_parseable: device-height > = 0px
+Pass	expression_should_be_unknown: device-height > = 0px
+Pass	expression_should_be_known: device-height >= -1px
+Pass	expression_should_be_parseable: min-device-height >= -1px
+Pass	expression_should_be_unknown: min-device-height >= -1px
+Pass	expression_should_be_parseable: max-device-height >= -1px
+Pass	expression_should_be_unknown: max-device-height >= -1px
+Pass	expression_should_be_known: device-height >= -0.00001mm
+Pass	expression_should_be_known: device-height >= -100000em
+Pass	expression_should_be_parseable: 0px >= device-height : 0px
+Pass	expression_should_be_unknown: 0px >= device-height : 0px
+Pass	expression_should_be_known: 0px >= device-height > 0px
+Pass	expression_should_be_known: 0px >= device-height >= 0px
+Pass	expression_should_be_parseable: 0px >= device-height = 0px
+Pass	expression_should_be_unknown: 0px >= device-height = 0px
+Pass	expression_should_be_parseable: 0px >= device-height <= 0px
+Pass	expression_should_be_unknown: 0px >= device-height <= 0px
+Pass	expression_should_be_parseable: 0px >= device-height < 0px
+Pass	expression_should_be_unknown: 0px >= device-height < 0px
+Pass	expression_should_be_known: device-height = 0
+Pass	expression_should_be_known: device-height = 0px
+Pass	expression_should_be_known: device-height = 0em
+Pass	expression_should_be_known: device-height = -0
+Pass	expression_should_be_known: device-height = -0cm
+Pass	expression_should_be_known: device-height = 1px
+Pass	expression_should_be_known: device-height = 0.001mm
+Pass	expression_should_be_known: device-height = 100000px
+Pass	expression_should_be_parseable: min-device-height = -0
+Pass	expression_should_be_unknown: min-device-height = -0
+Pass	expression_should_be_parseable: max-device-height = -0
+Pass	expression_should_be_unknown: max-device-height = -0
+Pass	expression_should_be_parseable: 0px = device-height = 100000px
+Pass	expression_should_be_unknown: 0px = device-height = 100000px
+Pass	expression_should_be_known: device-height = -1px
+Pass	expression_should_be_parseable: min-device-height = -1px
+Pass	expression_should_be_unknown: min-device-height = -1px
+Pass	expression_should_be_parseable: max-device-height = -1px
+Pass	expression_should_be_unknown: max-device-height = -1px
+Pass	expression_should_be_known: device-height = -0.00001mm
+Pass	expression_should_be_known: device-height = -100000em
+Pass	expression_should_be_parseable: 0px = device-height : 0px
+Pass	expression_should_be_unknown: 0px = device-height : 0px
+Pass	expression_should_be_parseable: 0px = device-height > 0px
+Pass	expression_should_be_unknown: 0px = device-height > 0px
+Pass	expression_should_be_parseable: 0px = device-height >= 0px
+Pass	expression_should_be_unknown: 0px = device-height >= 0px
+Pass	expression_should_be_parseable: 0px = device-height = 0px
+Pass	expression_should_be_unknown: 0px = device-height = 0px
+Pass	expression_should_be_parseable: 0px = device-height <= 0px
+Pass	expression_should_be_unknown: 0px = device-height <= 0px
+Pass	expression_should_be_parseable: 0px = device-height < 0px
+Pass	expression_should_be_unknown: 0px = device-height < 0px
+Pass	expression_should_be_known: device-height <= 0
+Pass	expression_should_be_known: device-height <= 0px
+Pass	expression_should_be_known: device-height <= 0em
+Pass	expression_should_be_known: device-height <= -0
+Pass	expression_should_be_known: device-height <= -0cm
+Pass	expression_should_be_known: device-height <= 1px
+Pass	expression_should_be_known: device-height <= 0.001mm
+Pass	expression_should_be_known: device-height <= 100000px
+Pass	expression_should_be_parseable: min-device-height <= -0
+Pass	expression_should_be_unknown: min-device-height <= -0
+Pass	expression_should_be_parseable: max-device-height <= -0
+Pass	expression_should_be_unknown: max-device-height <= -0
+Pass	expression_should_be_known: 0px <= device-height <= 100000px
+Pass	expression_should_be_parseable: device-height < = 0px
+Pass	expression_should_be_unknown: device-height < = 0px
+Pass	expression_should_be_known: device-height <= -1px
+Pass	expression_should_be_parseable: min-device-height <= -1px
+Pass	expression_should_be_unknown: min-device-height <= -1px
+Pass	expression_should_be_parseable: max-device-height <= -1px
+Pass	expression_should_be_unknown: max-device-height <= -1px
+Pass	expression_should_be_known: device-height <= -0.00001mm
+Pass	expression_should_be_known: device-height <= -100000em
+Pass	expression_should_be_parseable: 0px <= device-height : 0px
+Pass	expression_should_be_unknown: 0px <= device-height : 0px
+Pass	expression_should_be_parseable: 0px <= device-height > 0px
+Pass	expression_should_be_unknown: 0px <= device-height > 0px
+Pass	expression_should_be_parseable: 0px <= device-height >= 0px
+Pass	expression_should_be_unknown: 0px <= device-height >= 0px
+Pass	expression_should_be_parseable: 0px <= device-height = 0px
+Pass	expression_should_be_unknown: 0px <= device-height = 0px
+Pass	expression_should_be_known: 0px <= device-height <= 0px
+Pass	expression_should_be_known: 0px <= device-height < 0px
+Pass	expression_should_be_known: device-height < 0
+Pass	expression_should_be_known: device-height < 0px
+Pass	expression_should_be_known: device-height < 0em
+Pass	expression_should_be_known: device-height < -0
+Pass	expression_should_be_known: device-height < -0cm
+Pass	expression_should_be_known: device-height < 1px
+Pass	expression_should_be_known: device-height < 0.001mm
+Pass	expression_should_be_known: device-height < 100000px
+Pass	expression_should_be_parseable: min-device-height < -0
+Pass	expression_should_be_unknown: min-device-height < -0
+Pass	expression_should_be_parseable: max-device-height < -0
+Pass	expression_should_be_unknown: max-device-height < -0
+Pass	expression_should_be_known: 0px < device-height < 100000px
+Pass	expression_should_be_known: device-height < -1px
+Pass	expression_should_be_parseable: min-device-height < -1px
+Pass	expression_should_be_unknown: min-device-height < -1px
+Pass	expression_should_be_parseable: max-device-height < -1px
+Pass	expression_should_be_unknown: max-device-height < -1px
+Pass	expression_should_be_known: device-height < -0.00001mm
+Pass	expression_should_be_known: device-height < -100000em
+Pass	expression_should_be_parseable: 0px < device-height : 0px
+Pass	expression_should_be_unknown: 0px < device-height : 0px
+Pass	expression_should_be_parseable: 0px < device-height > 0px
+Pass	expression_should_be_unknown: 0px < device-height > 0px
+Pass	expression_should_be_parseable: 0px < device-height >= 0px
+Pass	expression_should_be_unknown: 0px < device-height >= 0px
+Pass	expression_should_be_parseable: 0px < device-height = 0px
+Pass	expression_should_be_unknown: 0px < device-height = 0px
+Pass	expression_should_be_known: 0px < device-height <= 0px
+Pass	expression_should_be_known: 0px < device-height < 0px
+Fail	should_apply: all and (width: ${value}px)
+Fail	should_apply: all and (width = ${value}px)
+Pass	should_not_apply: all and (width: ${value + 1}px)
+Pass	should_not_apply: all and (width: ${value - 1}px)
+Pass	should_not_apply: all and (width = ${value + 1}px)
+Pass	should_not_apply: all and (width = ${value - 1}px)
+Pass	should_apply: all and (min-width: ${value}px)
+Fail	should_not_apply: all and (min-width: ${value + 1}px)
+Pass	should_apply: all and (min-width: ${value - 1}px)
+Fail	should_apply: all and (max-width: ${value}px)
+Fail	should_apply: all and (max-width: ${value + 1}px)
+Pass	should_not_apply: all and (max-width: ${value - 1}px)
+Fail	should_not_apply: all and (min-width: ${Math.ceil(value/em_size) + 1}em)
+Pass	should_apply: all and (min-width: ${Math.floor(value/em_size) - 1}em)
+Fail	should_apply: all and (max-width: ${Math.ceil(value/em_size) + 1}em)
+Pass	should_not_apply: all and (max-width: ${Math.floor(value/em_size) - 1}em)
+Fail	should_apply: (width <= ${value}px)
+Pass	should_apply: (width >= ${value}px)
+Fail	should_apply: (0px < width <= ${value}px)
+Fail	should_apply: (${value}px >= width > 0px)
+Pass	should_not_apply: (0px < width < ${value}px)
+Pass	should_not_apply: (${value}px > width > 0px)
+Pass	should_not_apply: (width < ${value}px)
+Fail	should_not_apply: (width > ${value}px)
+Fail	should_apply: (width < ${value + 1}px)
+Fail	should_apply: (width <= ${value + 1}px)
+Fail	should_not_apply: (width > ${value + 1}px)
+Fail	should_not_apply: (width >= ${value + 1}px)
+Pass	should_apply: (width > ${value - 1}px)
+Pass	should_apply: (width >= ${value - 1}px)
+Pass	should_not_apply: (width < ${value - 1}px)
+Pass	should_not_apply: (width <= ${value - 1}px)
+Pass	should_apply: (${value - 1}px < width)
+Pass	should_apply: (${value - 1}px <= width)
+Pass	should_not_apply: (${value - 1}px > width)
+Pass	should_not_apply: (${value - 1}px >= width)
+Fail	should_apply: (${value - 1}px < width < ${value + 1}px)
+Fail	should_apply: (${value - 1}px < width <= ${value}px)
+Fail	should_apply: (${value}px <= width < ${value + 1}px)
+Fail	should_apply: (${value + 1}px > width > ${value - 1}px)
+Fail	should_apply: (${value + 1}px > width >= ${value}px)
+Fail	should_apply: (${value}px >= width > ${value - 1}px)
+Pass	should_not_apply: (${value}px > width > ${value - 1}px)
+Pass	should_not_apply: (${value + 1}px > width > ${value}px)
+Fail	should_apply: all and (height: ${value}px)
+Fail	should_apply: all and (height = ${value}px)
+Pass	should_not_apply: all and (height: ${value + 1}px)
+Pass	should_not_apply: all and (height: ${value - 1}px)
+Pass	should_not_apply: all and (height = ${value + 1}px)
+Pass	should_not_apply: all and (height = ${value - 1}px)
+Pass	should_apply: all and (min-height: ${value}px)
+Fail	should_not_apply: all and (min-height: ${value + 1}px)
+Pass	should_apply: all and (min-height: ${value - 1}px)
+Fail	should_apply: all and (max-height: ${value}px)
+Fail	should_apply: all and (max-height: ${value + 1}px)
+Pass	should_not_apply: all and (max-height: ${value - 1}px)
+Fail	should_not_apply: all and (min-height: ${Math.ceil(value/em_size) + 1}em)
+Pass	should_apply: all and (min-height: ${Math.floor(value/em_size) - 1}em)
+Fail	should_apply: all and (max-height: ${Math.ceil(value/em_size) + 1}em)
+Pass	should_not_apply: all and (max-height: ${Math.floor(value/em_size) - 1}em)
+Fail	should_apply: (height <= ${value}px)
+Pass	should_apply: (height >= ${value}px)
+Fail	should_apply: (0px < height <= ${value}px)
+Fail	should_apply: (${value}px >= height > 0px)
+Pass	should_not_apply: (0px < height < ${value}px)
+Pass	should_not_apply: (${value}px > height > 0px)
+Pass	should_not_apply: (height < ${value}px)
+Fail	should_not_apply: (height > ${value}px)
+Fail	should_apply: (height < ${value + 1}px)
+Fail	should_apply: (height <= ${value + 1}px)
+Fail	should_not_apply: (height > ${value + 1}px)
+Fail	should_not_apply: (height >= ${value + 1}px)
+Pass	should_apply: (height > ${value - 1}px)
+Pass	should_apply: (height >= ${value - 1}px)
+Pass	should_not_apply: (height < ${value - 1}px)
+Pass	should_not_apply: (height <= ${value - 1}px)
+Pass	should_apply: (${value - 1}px < height)
+Pass	should_apply: (${value - 1}px <= height)
+Pass	should_not_apply: (${value - 1}px > height)
+Pass	should_not_apply: (${value - 1}px >= height)
+Fail	should_apply: (${value - 1}px < height < ${value + 1}px)
+Fail	should_apply: (${value - 1}px < height <= ${value}px)
+Fail	should_apply: (${value}px <= height < ${value + 1}px)
+Fail	should_apply: (${value + 1}px > height > ${value - 1}px)
+Fail	should_apply: (${value + 1}px > height >= ${value}px)
+Fail	should_apply: (${value}px >= height > ${value - 1}px)
+Pass	should_not_apply: (${value}px > height > ${value - 1}px)
+Pass	should_not_apply: (${value + 1}px > height > ${value}px)
+Pass	should_apply: all and (device-width: ${value}px)
+Pass	should_apply: all and (device-width = ${value}px)
+Pass	should_not_apply: all and (device-width: ${value + 1}px)
+Pass	should_not_apply: all and (device-width: ${value - 1}px)
+Pass	should_not_apply: all and (device-width = ${value + 1}px)
+Pass	should_not_apply: all and (device-width = ${value - 1}px)
+Pass	should_apply: all and (min-device-width: ${value}px)
+Pass	should_not_apply: all and (min-device-width: ${value + 1}px)
+Pass	should_apply: all and (min-device-width: ${value - 1}px)
+Pass	should_apply: all and (max-device-width: ${value}px)
+Pass	should_apply: all and (max-device-width: ${value + 1}px)
+Pass	should_not_apply: all and (max-device-width: ${value - 1}px)
+Fail	should_not_apply: all and (min-device-width: ${Math.ceil(value/em_size) + 1}em)
+Pass	should_apply: all and (min-device-width: ${Math.floor(value/em_size) - 1}em)
+Fail	should_apply: all and (max-device-width: ${Math.ceil(value/em_size) + 1}em)
+Pass	should_not_apply: all and (max-device-width: ${Math.floor(value/em_size) - 1}em)
+Pass	should_apply: (device-width <= ${value}px)
+Pass	should_apply: (device-width >= ${value}px)
+Pass	should_apply: (0px < device-width <= ${value}px)
+Pass	should_apply: (${value}px >= device-width > 0px)
+Pass	should_not_apply: (0px < device-width < ${value}px)
+Pass	should_not_apply: (${value}px > device-width > 0px)
+Pass	should_not_apply: (device-width < ${value}px)
+Pass	should_not_apply: (device-width > ${value}px)
+Pass	should_apply: (device-width < ${value + 1}px)
+Pass	should_apply: (device-width <= ${value + 1}px)
+Pass	should_not_apply: (device-width > ${value + 1}px)
+Pass	should_not_apply: (device-width >= ${value + 1}px)
+Pass	should_apply: (device-width > ${value - 1}px)
+Pass	should_apply: (device-width >= ${value - 1}px)
+Pass	should_not_apply: (device-width < ${value - 1}px)
+Pass	should_not_apply: (device-width <= ${value - 1}px)
+Pass	should_apply: (${value - 1}px < device-width)
+Pass	should_apply: (${value - 1}px <= device-width)
+Pass	should_not_apply: (${value - 1}px > device-width)
+Pass	should_not_apply: (${value - 1}px >= device-width)
+Pass	should_apply: (${value - 1}px < device-width < ${value + 1}px)
+Pass	should_apply: (${value - 1}px < device-width <= ${value}px)
+Pass	should_apply: (${value}px <= device-width < ${value + 1}px)
+Pass	should_apply: (${value + 1}px > device-width > ${value - 1}px)
+Pass	should_apply: (${value + 1}px > device-width >= ${value}px)
+Pass	should_apply: (${value}px >= device-width > ${value - 1}px)
+Pass	should_not_apply: (${value}px > device-width > ${value - 1}px)
+Pass	should_not_apply: (${value + 1}px > device-width > ${value}px)
+Pass	should_apply: all and (device-height: ${value}px)
+Pass	should_apply: all and (device-height = ${value}px)
+Pass	should_not_apply: all and (device-height: ${value + 1}px)
+Pass	should_not_apply: all and (device-height: ${value - 1}px)
+Pass	should_not_apply: all and (device-height = ${value + 1}px)
+Pass	should_not_apply: all and (device-height = ${value - 1}px)
+Pass	should_apply: all and (min-device-height: ${value}px)
+Pass	should_not_apply: all and (min-device-height: ${value + 1}px)
+Pass	should_apply: all and (min-device-height: ${value - 1}px)
+Pass	should_apply: all and (max-device-height: ${value}px)
+Pass	should_apply: all and (max-device-height: ${value + 1}px)
+Pass	should_not_apply: all and (max-device-height: ${value - 1}px)
+Fail	should_not_apply: all and (min-device-height: ${Math.ceil(value/em_size) + 1}em)
+Pass	should_apply: all and (min-device-height: ${Math.floor(value/em_size) - 1}em)
+Fail	should_apply: all and (max-device-height: ${Math.ceil(value/em_size) + 1}em)
+Pass	should_not_apply: all and (max-device-height: ${Math.floor(value/em_size) - 1}em)
+Pass	should_apply: (device-height <= ${value}px)
+Pass	should_apply: (device-height >= ${value}px)
+Pass	should_apply: (0px < device-height <= ${value}px)
+Pass	should_apply: (${value}px >= device-height > 0px)
+Pass	should_not_apply: (0px < device-height < ${value}px)
+Pass	should_not_apply: (${value}px > device-height > 0px)
+Pass	should_not_apply: (device-height < ${value}px)
+Pass	should_not_apply: (device-height > ${value}px)
+Pass	should_apply: (device-height < ${value + 1}px)
+Pass	should_apply: (device-height <= ${value + 1}px)
+Pass	should_not_apply: (device-height > ${value + 1}px)
+Pass	should_not_apply: (device-height >= ${value + 1}px)
+Pass	should_apply: (device-height > ${value - 1}px)
+Pass	should_apply: (device-height >= ${value - 1}px)
+Pass	should_not_apply: (device-height < ${value - 1}px)
+Pass	should_not_apply: (device-height <= ${value - 1}px)
+Pass	should_apply: (${value - 1}px < device-height)
+Pass	should_apply: (${value - 1}px <= device-height)
+Pass	should_not_apply: (${value - 1}px > device-height)
+Pass	should_not_apply: (${value - 1}px >= device-height)
+Pass	should_apply: (${value - 1}px < device-height < ${value + 1}px)
+Pass	should_apply: (${value - 1}px < device-height <= ${value}px)
+Pass	should_apply: (${value}px <= device-height < ${value + 1}px)
+Pass	should_apply: (${value + 1}px > device-height > ${value - 1}px)
+Pass	should_apply: (${value + 1}px > device-height >= ${value}px)
+Pass	should_apply: (${value}px >= device-height > ${value - 1}px)
+Pass	should_not_apply: (${value}px > device-height > ${value - 1}px)
+Pass	should_not_apply: (${value + 1}px > device-height > ${value}px)
+Pass	width = 0, height != 0: should_apply: all and (height)
+Fail	width = 0, height != 0: should_not_apply: all and (width)
+Fail	width = 0, height = 0: should_not_apply: all and (height)
+Fail	width = 0, height = 0: should_not_apply: all and (width)
+Pass	width = 0, height = 0: should_apply: all and (device-height)
+Pass	width = 0, height = 0: should_apply: all and (device-width)
+Fail	width != 0, height = 0: should_not_apply: all and (height)
+Pass	width != 0, height = 0: should_apply: all and (width)
+Pass	width != 0, height != 0: should_apply: all and (height)
+Pass	width != 0, height != 0: should_apply: all and (width)
+Pass	ratio that reduces to 59/40: expression_should_be_known: orientation
+Pass	ratio that reduces to 59/40: expression_should_be_known: orientation: portrait
+Pass	ratio that reduces to 59/40: expression_should_be_known: orientation: landscape
+Pass	ratio that reduces to 59/40: expression_should_be_parseable: min-orientation
+Pass	ratio that reduces to 59/40: expression_should_be_unknown: min-orientation
+Pass	ratio that reduces to 59/40: expression_should_be_parseable: min-orientation: portrait
+Pass	ratio that reduces to 59/40: expression_should_be_unknown: min-orientation: portrait
+Pass	ratio that reduces to 59/40: expression_should_be_parseable: min-orientation: landscape
+Pass	ratio that reduces to 59/40: expression_should_be_unknown: min-orientation: landscape
+Pass	ratio that reduces to 59/40: expression_should_be_parseable: max-orientation
+Pass	ratio that reduces to 59/40: expression_should_be_unknown: max-orientation
+Pass	ratio that reduces to 59/40: expression_should_be_parseable: max-orientation: portrait
+Pass	ratio that reduces to 59/40: expression_should_be_unknown: max-orientation: portrait
+Pass	ratio that reduces to 59/40: expression_should_be_parseable: max-orientation: landscape
+Pass	ratio that reduces to 59/40: expression_should_be_unknown: max-orientation: landscape
+Pass	ratio that reduces to 59/40: should_apply: (orientation)
+Pass	ratio that reduces to 59/40: should_apply: (orientation: landscape)
+Pass	ratio that reduces to 59/40: should_not_apply: (orientation: portrait)
+Pass	ratio that reduces to 59/40: should_apply: not all and (orientation: portrait)
+Pass	ratio that reduces to 59/80: should_apply: (orientation)
+Fail	ratio that reduces to 59/80: should_not_apply: (orientation: landscape)
+Fail	ratio that reduces to 59/80: should_apply: not all and (orientation: landscape)
+Fail	ratio that reduces to 59/80: should_apply: (orientation: portrait)
+Fail	should_apply: (aspect-ratio: 59/80)
+Pass	should_not_apply: (aspect-ratio: 58/80)
+Pass	should_not_apply: (aspect-ratio: 59/81)
+Pass	should_not_apply: (aspect-ratio: 60/80)
+Pass	should_not_apply: (aspect-ratio: 59/79)
+Fail	should_apply: (aspect-ratio: 177/240)
+Fail	should_apply: (aspect-ratio: 413/560)
+Fail	should_apply: (aspect-ratio: 5900/8000)
+Pass	should_not_apply: (aspect-ratio: 5901/8000)
+Pass	should_not_apply: (aspect-ratio: 5899/8000)
+Pass	should_not_apply: (aspect-ratio: 5900/8001)
+Pass	should_not_apply: (aspect-ratio: 5900/7999)
+Pass	should_apply: (aspect-ratio)
+Pass	should_apply: (min-aspect-ratio: 59/80)
+Pass	should_apply: (min-aspect-ratio: 58/80)
+Pass	should_apply: (min-aspect-ratio: 59/81)
+Fail	should_not_apply: (min-aspect-ratio: 60/80)
+Fail	should_not_apply: (min-aspect-ratio: 59/79)
+Pass	expression_should_be_parseable: min-aspect-ratio
+Pass	expression_should_be_unknown: min-aspect-ratio
+Fail	should_apply: (max-aspect-ratio: 59/80)
+Pass	should_not_apply: (max-aspect-ratio: 58/80)
+Pass	should_not_apply: (max-aspect-ratio: 59/81)
+Fail	should_apply: (max-aspect-ratio: 60/80)
+Fail	should_apply: (max-aspect-ratio: 59/79)
+Pass	expression_should_be_parseable: max-aspect-ratio
+Pass	expression_should_be_unknown: max-aspect-ratio
+Fail	should_apply: (device-aspect-ratio: ${real_dar})
+Pass	should_apply: not all and (device-aspect-ratio: ${high_dar_1})
+Pass	should_not_apply: all and (device-aspect-ratio: ${high_dar_2})
+Pass	should_not_apply: all and (device-aspect-ratio: ${low_dar_1})
+Pass	should_apply: not all and (device-aspect-ratio: ${low_dar_2})
+Fail	should_apply: (device-aspect-ratio)
+Fail	should_apply: (min-device-aspect-ratio: ${real_dar})
+Pass	should_not_apply: all and (min-device-aspect-ratio: ${high_dar_1})
+Pass	should_apply: not all and (min-device-aspect-ratio: ${high_dar_2})
+Fail	should_not_apply: not all and (min-device-aspect-ratio: ${low_dar_1})
+Fail	should_apply: all and (min-device-aspect-ratio: ${low_dar_2})
+Pass	expression_should_be_parseable: min-device-aspect-ratio
+Pass	expression_should_be_unknown: min-device-aspect-ratio
+Fail	should_apply: all and (max-device-aspect-ratio: ${real_dar})
+Fail	should_apply: (max-device-aspect-ratio: ${high_dar_1})
+Fail	should_apply: (max-device-aspect-ratio: ${high_dar_2})
+Pass	should_not_apply: all and (max-device-aspect-ratio: ${low_dar_1})
+Pass	should_apply: not all and (max-device-aspect-ratio: ${low_dar_2})
+Pass	expression_should_be_parseable: max-device-aspect-ratio
+Pass	expression_should_be_unknown: max-device-aspect-ratio
+Pass	expression_should_be_known: max-aspect-ratio: 1/1
+Pass	expression_should_be_known: max-aspect-ratio: 1  /1
+Pass	expression_should_be_known: max-aspect-ratio: 1  / 	
+1
+Pass	expression_should_be_known: max-aspect-ratio: 1/
1
+Pass	expression_should_be_known: max-aspect-ratio: 1
+Pass	expression_should_be_known: max-aspect-ratio: 0.5
+Pass	expression_should_be_known: max-aspect-ratio: 1.0/1
+Pass	expression_should_be_known: max-aspect-ratio: 1/1.0
+Pass	expression_should_be_known: max-aspect-ratio: 1.0/1.0
+Pass	expression_should_be_known: max-aspect-ratio: 0/1
+Pass	expression_should_be_known: max-aspect-ratio: 1/0
+Pass	expression_should_be_known: max-aspect-ratio: 0/0
+Pass	expression_should_be_parseable: max-aspect-ratio: -1/1
+Pass	expression_should_be_unknown: max-aspect-ratio: -1/1
+Pass	expression_should_be_parseable: max-aspect-ratio: 1/-1
+Pass	expression_should_be_unknown: max-aspect-ratio: 1/-1
+Pass	expression_should_be_parseable: max-aspect-ratio: -1/-1
+Pass	expression_should_be_unknown: max-aspect-ratio: -1/-1
+Pass	expression_should_be_parseable: max-aspect-ratio: invalid
+Pass	expression_should_be_unknown: max-aspect-ratio: invalid
+Pass	expression_should_be_parseable: max-aspect-ratio: 1 / invalid
+Pass	expression_should_be_unknown: max-aspect-ratio: 1 / invalid
+Pass	expression_should_be_parseable: max-aspect-ratio: 1 invalid
+Pass	expression_should_be_unknown: max-aspect-ratio: 1 invalid
+Pass	expression_should_be_known: device-aspect-ratio: 1/1
+Pass	expression_should_be_known: device-aspect-ratio: 1  /1
+Pass	expression_should_be_known: device-aspect-ratio: 1  / 	
+1
+Pass	expression_should_be_known: device-aspect-ratio: 1/
1
+Pass	expression_should_be_known: device-aspect-ratio: 1
+Pass	expression_should_be_known: device-aspect-ratio: 0.5
+Pass	expression_should_be_known: device-aspect-ratio: 1.0/1
+Pass	expression_should_be_known: device-aspect-ratio: 1/1.0
+Pass	expression_should_be_known: device-aspect-ratio: 1.0/1.0
+Pass	expression_should_be_known: device-aspect-ratio: 0/1
+Pass	expression_should_be_known: device-aspect-ratio: 1/0
+Pass	expression_should_be_known: device-aspect-ratio: 0/0
+Pass	expression_should_be_parseable: device-aspect-ratio: -1/1
+Pass	expression_should_be_unknown: device-aspect-ratio: -1/1
+Pass	expression_should_be_parseable: device-aspect-ratio: 1/-1
+Pass	expression_should_be_unknown: device-aspect-ratio: 1/-1
+Pass	expression_should_be_parseable: device-aspect-ratio: -1/-1
+Pass	expression_should_be_unknown: device-aspect-ratio: -1/-1
+Pass	expression_should_be_parseable: device-aspect-ratio: invalid
+Pass	expression_should_be_unknown: device-aspect-ratio: invalid
+Pass	expression_should_be_parseable: device-aspect-ratio: 1 / invalid
+Pass	expression_should_be_unknown: device-aspect-ratio: 1 / invalid
+Pass	expression_should_be_parseable: device-aspect-ratio: 1 invalid
+Pass	expression_should_be_unknown: device-aspect-ratio: 1 invalid
+Pass	monochrome_and_color
+Pass	find_depth
+Pass	should_apply: all and (color:8)
+Pass	should_not_apply: all and (color:7)
+Pass	should_not_apply: all and (color:9)
+Pass	should_apply: all and (max-color:8)
+Pass	should_not_apply: all and (max-color:7)
+Pass	should_apply: all and (max-color:9)
+Pass	should_apply: all and (color)
+Pass	expression_should_be_parseable: max-color
+Pass	expression_should_be_unknown: max-color
+Pass	expression_should_be_parseable: min-color
+Pass	expression_should_be_unknown: min-color
+Pass	should_not_apply: all and (monochrome)
+Pass	expression_should_be_parseable: max-monochrome
+Pass	expression_should_be_unknown: max-monochrome
+Pass	expression_should_be_parseable: min-monochrome
+Pass	expression_should_be_unknown: min-monochrome
+Pass	should_apply: not all and (monochrome)
+Pass	should_not_apply: not all and (color)
+Pass	should_apply: only all and (color)
+Pass	should_not_apply: only all and (monochrome)
+Pass	expression_should_be_known: color: 1
+Pass	expression_should_be_known: color: 327
+Pass	expression_should_be_known: color: 0
+Pass	expression_should_be_parseable: color: 1.0
+Pass	expression_should_be_unknown: color: 1.0
+Pass	expression_should_be_known: color: -1
+Pass	expression_should_be_parseable: color: 1/1
+Pass	expression_should_be_unknown: color: 1/1
+Pass	expression_should_be_known: min-monochrome: 1
+Pass	expression_should_be_known: min-monochrome: 327
+Pass	expression_should_be_known: min-monochrome: 0
+Pass	expression_should_be_parseable: min-monochrome: 1.0
+Pass	expression_should_be_unknown: min-monochrome: 1.0
+Pass	expression_should_be_known: min-monochrome: -1
+Pass	expression_should_be_parseable: min-monochrome: 1/1
+Pass	expression_should_be_unknown: min-monochrome: 1/1
+Pass	expression_should_be_known: max-color-index: 1
+Pass	expression_should_be_known: max-color-index: 327
+Pass	expression_should_be_known: max-color-index: 0
+Pass	expression_should_be_parseable: max-color-index: 1.0
+Pass	expression_should_be_unknown: max-color-index: 1.0
+Pass	expression_should_be_known: max-color-index: -1
+Pass	expression_should_be_parseable: max-color-index: 1/1
+Pass	expression_should_be_unknown: max-color-index: 1/1
+Pass	should_apply: (color-index: 0)
+Pass	should_not_apply: (color-index: 1)
+Pass	should_apply: (min-color-index: 0)
+Pass	should_not_apply: (min-color-index: 1)
+Pass	should_apply: (max-color-index: 0)
+Pass	should_apply: (max-color-index: 1)
+Pass	should_apply: (max-color-index: 157)
+Pass	expression_should_be_known: resolution: 3dpi
+Pass	expression_should_be_known: resolution:3dpi
+Pass	expression_should_be_known: resolution: 3.0dpi
+Pass	expression_should_be_known: resolution: 3.4dpi
+Pass	expression_should_be_known: resolution	: 120dpcm
+Pass	expression_should_be_known: resolution: 1dppx
+Pass	expression_should_be_known: resolution: 1x
+Pass	expression_should_be_known: resolution: 1.5dppx
+Pass	expression_should_be_known: resolution: 1.5x
+Pass	expression_should_be_known: resolution: 2.0dppx
+Pass	expression_should_be_known: resolution: 0dpi
+Pass	expression_should_be_known: resolution: 0dppx
+Pass	expression_should_be_known: resolution: 0x
+Pass	expression_should_be_known: resolution: calc(6x / 2)
+Pass	expression_should_be_parseable: resolution: -3dpi
+Fail	expression_should_be_unknown: resolution: -3dpi
+Pass	expression_should_be_known: min-resolution: 3dpi
+Pass	expression_should_be_known: min-resolution:3dpi
+Pass	expression_should_be_known: min-resolution: 3.0dpi
+Pass	expression_should_be_known: min-resolution: 3.4dpi
+Pass	expression_should_be_known: min-resolution	: 120dpcm
+Pass	expression_should_be_known: min-resolution: 1dppx
+Pass	expression_should_be_known: min-resolution: 1x
+Pass	expression_should_be_known: min-resolution: 1.5dppx
+Pass	expression_should_be_known: min-resolution: 1.5x
+Pass	expression_should_be_known: min-resolution: 2.0dppx
+Pass	expression_should_be_known: min-resolution: 0dpi
+Pass	expression_should_be_known: min-resolution: 0dppx
+Pass	expression_should_be_known: min-resolution: 0x
+Pass	expression_should_be_known: min-resolution: calc(6x / 2)
+Pass	expression_should_be_parseable: min-resolution: -3dpi
+Fail	expression_should_be_unknown: min-resolution: -3dpi
+Pass	expression_should_be_known: max-resolution: 3dpi
+Pass	expression_should_be_known: max-resolution:3dpi
+Pass	expression_should_be_known: max-resolution: 3.0dpi
+Pass	expression_should_be_known: max-resolution: 3.4dpi
+Pass	expression_should_be_known: max-resolution	: 120dpcm
+Pass	expression_should_be_known: max-resolution: 1dppx
+Pass	expression_should_be_known: max-resolution: 1x
+Pass	expression_should_be_known: max-resolution: 1.5dppx
+Pass	expression_should_be_known: max-resolution: 1.5x
+Pass	expression_should_be_known: max-resolution: 2.0dppx
+Pass	expression_should_be_known: max-resolution: 0dpi
+Pass	expression_should_be_known: max-resolution: 0dppx
+Pass	expression_should_be_known: max-resolution: 0x
+Pass	expression_should_be_known: max-resolution: calc(6x / 2)
+Pass	expression_should_be_parseable: max-resolution: -3dpi
+Fail	expression_should_be_unknown: max-resolution: -3dpi
+Pass	find_resolution
+Pass	resolution is exact: should_apply: (resolution: ${resolution}dpi)
+Pass	resolution is exact: should_apply: (resolution: ${Math.floor(resolution/96)}dppx)
+Pass	resolution is exact: should_apply: (resolution: ${Math.floor(resolution/96)}x)
+Pass	resolution is exact: should_not_apply: (resolution: ${resolution + 1}dpi)
+Pass	resolution is exact: should_not_apply: (resolution: ${resolution - 1}dpi)
+Pass	should_apply: (min-resolution: ${dpi_low}dpi)
+Pass	should_not_apply: not all and (min-resolution: ${dpi_low}dpi)
+Pass	should_apply: not all and (min-resolution: ${dpi_high}dpi)
+Pass	should_not_apply: all and (min-resolution: ${dpi_high}dpi)
+Pass	should_apply: (min-resolution: ${dpcm_low}dpcm)
+Pass	should_apply: (max-resolution: ${dpcm_high}dpcm)
+Pass	should_not_apply: (max-resolution: ${dpcm_low}dpcm)
+Pass	should_apply: not all and (min-resolution: ${dpcm_high}dpcm)
+Pass	expression_should_be_known: scan
+Pass	expression_should_be_known: scan: progressive
+Pass	expression_should_be_known: scan:interlace
+Pass	expression_should_be_parseable: min-scan:interlace
+Pass	expression_should_be_unknown: min-scan:interlace
+Pass	expression_should_be_parseable: scan: 1
+Pass	expression_should_be_unknown: scan: 1
+Pass	expression_should_be_parseable: max-scan
+Pass	expression_should_be_unknown: max-scan
+Pass	expression_should_be_parseable: max-scan: progressive
+Pass	expression_should_be_unknown: max-scan: progressive
+Fail	should_not_apply: (scan)
+Fail	should_not_apply: (scan: progressive)
+Pass	should_not_apply: (scan: interlace)
+Fail	should_apply: not all and (scan)
+Fail	should_apply: not all and (scan: progressive)
+Pass	should_apply: not all and (scan: interlace)
+Pass	expression_should_be_known: grid
+Pass	expression_should_be_known: grid: 0
+Pass	expression_should_be_known: grid: 1
+Pass	expression_should_be_parseable: min-grid
+Pass	expression_should_be_unknown: min-grid
+Pass	expression_should_be_parseable: min-grid:0
+Pass	expression_should_be_unknown: min-grid:0
+Pass	expression_should_be_parseable: max-grid: 1
+Pass	expression_should_be_unknown: max-grid: 1
+Pass	expression_should_be_parseable: grid: 2
+Pass	expression_should_be_unknown: grid: 2
+Pass	expression_should_be_parseable: grid: -1
+Pass	expression_should_be_unknown: grid: -1
+Pass	should_not_apply: (grid)
+Pass	should_apply: (grid: 0)
+Pass	should_not_apply: (grid: 1)
+Pass	should_not_apply: (grid: 2)
+Pass	should_not_apply: (grid: -1)
+Pass	should_apply: (orientation
+Pass	should_not_apply: not all and (orientation
+Pass	should_not_apply: (orientation:
+Pass	should_not_apply: (orientation:)
+Pass	should_not_apply: (orientation:  )
+Pass	should_apply: all,(orientation:
+Pass	should_not_apply: (orientation:,all
+Pass	should_apply: not all and (grid
+Pass	should_not_apply: only all and (grid
+Pass	should_not_apply: (grid
+Pass	should_apply: all,(grid
+Pass	should_not_apply: (grid,all
+Pass	should_apply: ,all
+Pass	should_apply: all,
+Pass	should_apply: ,all,
+Pass	should_apply: all,badmedium
+Pass	should_apply: badmedium,all
+Pass	should_not_apply: ,badmedium,
+Pass	should_apply: all,(badexpression)
+Pass	should_apply: (badexpression),all
+Pass	should_not_apply: (badexpression),badmedium
+Pass	should_not_apply: badmedium,(badexpression)
+Pass	should_apply: all,[badsyntax]
+Pass	should_apply: [badsyntax],all
+Pass	should_not_apply: badmedium,[badsyntax]
+Pass	should_not_apply: [badsyntax],badmedium
+Pass	query_should_not_be_parseable: all and color :
+Fail	query_should_not_be_parseable: all and color : 1
+Fail	should_not_apply: all and min-color : 1
+Pass	should_not_apply: (bogus)
+Pass	should_not_apply: not all and (bogus)
+Pass	should_not_apply: only all and (bogus)
+Pass	expression_should_be_known: overflow-block
+Pass	expression_should_be_known: overflow-block: none
+Pass	expression_should_be_known: overflow-block: paged
+Pass	expression_should_be_known: overflow-block: scroll
+Pass	expression_should_be_parseable: overflow-block: optional-paged
+Pass	expression_should_be_unknown: overflow-block: optional-paged
+Pass	expression_should_be_parseable: overflow-block: some-random-invalid-thing
+Pass	expression_should_be_unknown: overflow-block: some-random-invalid-thing
+Pass	Sanity check for overflow-block
+Pass	expression_should_be_known: overflow-inline
+Pass	expression_should_be_known: overflow-inline: none
+Pass	expression_should_be_known: overflow-inline: scroll
+Pass	expression_should_be_parseable: overflow-inline: some-random-invalid-thing
+Pass	expression_should_be_unknown: overflow-inline: some-random-invalid-thing
+Pass	Sanity check for overflow-inline
+Pass	expression_should_be_known: update
+Pass	expression_should_be_known: update: none
+Pass	expression_should_be_known: update: slow
+Pass	expression_should_be_known: update: fast
+Pass	expression_should_be_parseable: update: some-random-invalid-thing
+Pass	expression_should_be_unknown: update: some-random-invalid-thing
+Pass	Sanity check for update
+Pass	expression_should_be_known: hover
+Pass	expression_should_be_known: hover: hover
+Pass	expression_should_be_known: hover: none
+Pass	expression_should_be_known: any-hover
+Pass	expression_should_be_known: any-hover: hover
+Pass	expression_should_be_known: any-hover: none
+Pass	(hover) == (hover: hover)
+Pass	(hover) == not (hover: none)
+Pass	(any-hover) == (any-hover: hover)
+Pass	(any-hover) == not (any-hover: none)
+Pass	expression_should_be_known: pointer
+Pass	expression_should_be_known: pointer: coarse
+Pass	expression_should_be_known: pointer: fine
+Pass	expression_should_be_known: pointer: none
+Pass	expression_should_be_known: any-pointer
+Pass	expression_should_be_known: any-pointer: coarse
+Pass	expression_should_be_known: any-pointer: fine
+Pass	expression_should_be_known: any-pointer: none
+Pass	(pointer) == (pointer: coarse) or (pointer: fine)
+Pass	(pointer) == not (pointer: none)
+Pass	(any-pointer) == (any-pointer: coarse) or (any-pointer: fine)
+Pass	(any-pointer) == not (any-pointer: none)
+Fail	'or' keyword: should_not_apply: (height) or (height)
+Pass	'or' keyword: should_apply: (width) or (height)
+Pass	'or' keyword: should_apply: (height) or (width)
+Pass	'or' keyword: should_apply: (height) or (width) or (height)
+Pass	'or' keyword: query_should_not_be_parseable: screen or (width)
+Pass	'or' keyword: query_should_not_be_parseable: screen and (width) or (height)
+Fail	nesting: should_not_apply: ((height))
+Pass	nesting: should_apply: ((width))
+Pass	nesting: should_apply: (((((width)))))
+Pass	nesting: should_apply: (((((width
+Pass	'not' keyword: should_not_apply: not (width)
+Fail	'not' keyword: should_apply: not (height)
+Fail	'not' keyword: should_apply: not ((width) and (height))
+Pass	'not' keyword: should_not_apply: not ((width) or (height))
+Fail	'not' keyword: should_not_apply: not ((width) and (not (height)))
+Pass	'not' keyword: query_should_not_be_parseable: not (width) and not (height)
+Pass	'not' keyword: query_should_not_be_parseable: not not (width)
+Pass	'not' keyword: query_should_be_parseable: not unknown(width) 
+Pass	three-valued logic: should_not_apply: (unknown)
+Pass	three-valued logic: should_not_apply: not (unknown)
+Pass	three-valued logic: should_not_apply: ((unknown) and (width))
+Pass	three-valued logic: should_not_apply: not ((unknown) and (width))
+Pass	three-valued logic: should_not_apply: ((unknown) and (height))
+Fail	three-valued logic: should_apply: not ((unknown) and (height))
+Pass	three-valued logic: should_apply: ((unknown) or (width))
+Pass	three-valued logic: should_not_apply: not ((unknown) or (width))
+Fail	three-valued logic: should_not_apply: ((unknown) or (height))
+Pass	three-valued logic: should_not_apply: not ((unknown) or (height))
+Pass	three-valued logic: should_apply: (width) or (not ((unknown) and (width)))
+Pass	three-valued logic: should_not_apply: (width) and (not ((unknown) and (width)))
+Pass	three-valued logic: should_apply: (width) or (not ((unknown) or (width)))
+Pass	three-valued logic: should_not_apply: (width) and (not ((unknown) or (width)))
+Pass	three-valued logic: should_apply: (width) or (not ((unknown) and (height)))
+Fail	three-valued logic: should_apply: (width) and (not ((unknown) and (height)))
+Pass	three-valued logic: should_apply: (width) or (not ((unknown) or (height)))
+Pass	three-valued logic: should_not_apply: (width) and (not ((unknown) or (height)))
+Pass	three-valued logic: should_not_apply: unknown(width)
+Pass	three-valued logic: should_not_apply: not unknown(width)
+Fail	three-valued logic: should_apply: not (unknown(width) and (height))
+Pass	three-valued logic: should_not_apply: not (unknown(width) or (height))

+ 16 - 0
Tests/LibWeb/Text/input/wpt-import/css/mediaqueries/support/media_queries_iframe.html

@@ -0,0 +1,16 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+	"http://www.w3.org/TR/html4/strict.dtd">
+<html lang="en-US">
+<head>
+	<title>Media Queries Test inner frame</title>
+	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+	<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+	<meta http-equiv="Content-Style-Type" content="text/css">
+	<style type="text/css" id="style" media="all">
+	body { text-decoration: underline; }
+	</style>
+</head>
+<body>
+
+</body>
+</html>

+ 740 - 0
Tests/LibWeb/Text/input/wpt-import/css/mediaqueries/test_media_queries.html

@@ -0,0 +1,740 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Media Queries Self-Contained Test Suite</title>
+  <link rel="author" title="L. David Baron" href="https://dbaron.org/">
+  <link rel="author" title="Anne van Kesteren" href="http://annevankesteren.nl/">
+  <link rel="author" title="Ms2ger" href="mailto:Ms2ger@gmail.com">
+  <link rel="help" href="https://www.w3.org/TR/css3-mediaqueries/#media0">
+  <script type="text/javascript" src="../../resources/testharness.js"></script>
+  <script type="text/javascript" src="../../resources/testharnessreport.js"></script>
+</head>
+<body onload="run()">
+<div id=log></div>
+<iframe id="subdoc" src="support/media_queries_iframe.html"></iframe>
+<div id="content" style="display: none"></div>
+
+<script type="text/javascript">
+setup({ "explicit_done": true });
+
+  function run() {
+    var subdoc = document.getElementById("subdoc").contentDocument;
+    var subwin = document.getElementById("subdoc").contentWindow;
+    var style = subdoc.getElementById("style");
+    var iframe_style = document.getElementById("subdoc").style;
+    var body_cs = subdoc.defaultView.getComputedStyle(subdoc.body, "");
+    var testGroup = "";
+
+    function query_applies(q) {
+      style.setAttribute("media", q);
+      return body_cs.getPropertyValue("text-decoration-line") == "underline";
+    }
+
+    function not(predicate) {
+      return (...args) => !predicate(...args);
+    }
+
+    function test_predicate(input, predicate, name) {
+      test(() => {
+        // lazily evaluate template string to avoid including device-specific data in test name
+        var escaped = JSON.stringify(input);
+        var evaled = eval("`" + escaped.substring(1, escaped.length - 1) + "`");
+        // Also avoid an assert message for the same reason. (Relevant for
+        // failing tests).
+        assert_true(predicate(evaled))
+      }, `${testGroup ? testGroup + ": " : ""}${name}: ${input}`);
+    }
+
+    function should_apply(q) {
+      test_predicate(q, query_applies, "should_apply");
+    }
+
+    function should_not_apply(q) {
+      test_predicate(q, not(query_applies), "should_not_apply");
+    }
+
+    /*
+     * Functions to test whether a query is parseable at all.  (Should not
+     * be used for parse errors within expressions.)
+     */
+    var parse_test_style_element = document.createElement("style");
+    parse_test_style_element.type = "text/css";
+    parse_test_style_element.disabled = true; // for performance, hopefully
+    var parse_test_style_text = document.createTextNode("");
+    parse_test_style_element.appendChild(parse_test_style_text);
+    document.getElementsByTagName("head")[0]
+      .appendChild(parse_test_style_element);
+
+    function query_is_parseable(q) {
+      parse_test_style_text.data = "@media screen, " + q + " {}";
+      var sheet = parse_test_style_element.sheet; // XXX yikes, not live!
+      if (sheet.cssRules.length == 1 &&
+          sheet.cssRules[0].type == CSSRule.MEDIA_RULE)
+        return sheet.cssRules[0].media.mediaText != "screen, not all";
+
+      assert_unreached(
+        "unexpected result testing whether query " + q + " is parseable");
+    }
+
+    function query_should_be_parseable(q) {
+      test_predicate(q, query_is_parseable, "query_should_be_parseable");
+    }
+
+    function query_should_not_be_parseable(q) {
+      test_predicate(q, not(query_is_parseable), "query_should_not_be_parseable");
+    }
+
+    /*
+     * Functions to test whether a single media expression is "unknown" or not.
+     *
+     * https://drafts.csswg.org/mediaqueries-4/#evaluating
+     */
+
+    function expression_is_parseable(e) {
+      return query_is_parseable(`(${e})`);
+    }
+
+    function expression_is_known(e) {
+      return query_applies(`(${e}), not all and (${e})`);
+    }
+
+    function expression_should_be_known(e) {
+      // We don't bother with expression_is_parseable here, because it must be parseable to be known
+      test_predicate(e, expression_is_known, "expression_should_be_known");
+    }
+
+    function expression_should_be_unknown(e) {
+      test_predicate(e, expression_is_parseable, "expression_should_be_parseable");
+      test_predicate(e, not(expression_is_known), "expression_should_be_unknown");
+    }
+
+    // The no-type syntax doesn't mix with the not and only keywords.
+    query_should_be_parseable("(orientation)");
+    query_should_be_parseable("not (orientation)");
+    expression_should_be_known("(orientation)");
+    expression_should_be_known("not (orientation)");
+    query_should_not_be_parseable("only (orientation)");
+    query_should_be_parseable("all and (orientation)");
+    query_should_be_parseable("not all and (orientation)");
+    query_should_be_parseable("only all and (orientation)");
+
+    query_should_not_be_parseable("not not (orientation)");
+    query_should_be_parseable("(orientation) and (orientation)");
+    query_should_be_parseable("(orientation) or (orientation)");
+    query_should_be_parseable("(orientation) or ((orientation) and ((orientation) or (orientation) or (not (orientation))))");
+    expression_should_be_known("(orientation) and (orientation)");
+    expression_should_be_known("(orientation) or (orientation)");
+    expression_should_be_known("(orientation) or ((orientation) and ((orientation) or (orientation) or (not (orientation))))");
+
+    query_should_not_be_parseable("all and (orientation) or (orientation)");
+    query_should_be_parseable("all and (orientation) and (orientation)");
+
+    query_should_not_be_parseable("(orientation) and (orientation) or (orientation)");
+    query_should_not_be_parseable("(orientation) and not (orientation)");
+
+    var features = [ "width", "height", "device-width", "device-height" ];
+    var separators = [ ":", ">", ">=", "=", "<=", "<" ];
+    var feature;
+    var i;
+    for (i in features) {
+      feature = features[i];
+      expression_should_be_known(feature);
+      expression_should_be_unknown("min-" + feature);
+      expression_should_be_unknown("max-" + feature);
+      for (let separator of separators) {
+        expression_should_be_known(feature + " " + separator + " 0");
+        expression_should_be_known(feature + " " + separator + " 0px");
+        expression_should_be_known(feature + " " + separator + " 0em");
+        expression_should_be_known(feature + " " + separator + " -0");
+        expression_should_be_known(feature + " " + separator + " -0cm");
+        expression_should_be_known(feature + " " + separator + " 1px");
+        expression_should_be_known(feature + " " + separator + " 0.001mm");
+        expression_should_be_known(feature + " " + separator + " 100000px");
+        if (separator == ":") {
+          expression_should_be_known("min-" + feature + " " + separator + " -0");
+          expression_should_be_known("max-" + feature + " " + separator + " -0");
+        } else {
+          expression_should_be_unknown("min-" + feature + " " + separator + " -0");
+          expression_should_be_unknown("max-" + feature + " " + separator + " -0");
+          let multi_range = "0px " + separator + " " + feature + " " + separator + " 100000px"
+          if (separator == "=") {
+            expression_should_be_unknown(multi_range);
+          } else {
+            expression_should_be_known(multi_range);
+          }
+        }
+        if (separator == ">=") {
+          expression_should_be_unknown(feature + " > = 0px");
+        } else if (separator == "<=") {
+          expression_should_be_unknown(feature + " < = 0px");
+        }
+
+        expression_should_be_known(feature + " " + separator + " -1px");
+        if (separator == ":") {
+          expression_should_be_known("min-" + feature + " " + separator + " -1px");
+          expression_should_be_known("max-" + feature + " " + separator + " -1px");
+        } else {
+          expression_should_be_unknown("min-" + feature + " " + separator + " -1px");
+          expression_should_be_unknown("max-" + feature + " " + separator + " -1px");
+        }
+        expression_should_be_known(feature + " " + separator + " -0.00001mm");
+        expression_should_be_known(feature + " " + separator + " -100000em");
+
+        for (let separator2 of separators) {
+          // https://drafts.csswg.org/mediaqueries-4/#typedef-mf-range
+          if (separator[0] == separator2[0] && (separator[0] == '<' || separator[0] == '>')) {
+            expression_should_be_known(`0px ${separator} ${feature} ${separator2} 0px`);
+          } else {
+            expression_should_be_unknown(`0px ${separator} ${feature} ${separator2} 0px`);
+          }
+        }
+      }
+    }
+
+    var content_div = document.getElementById("content");
+    content_div.style.font = "medium sans-serif";
+    var em_size =
+      getComputedStyle(content_div, "").fontSize.match(/^(\d+)px$/)[1];
+
+    // in this test, assume the common underlying implementation is correct
+    var width_val = 117; // pick two not-too-round numbers
+    var height_val = 76;
+    iframe_style.width = width_val + "px";
+    iframe_style.height = height_val + "px";
+    var device_width = window.screen.width;
+    var device_height = window.screen.height;
+    features = {
+      "width": width_val,
+      "height": height_val,
+      "device-width": device_width,
+      "device-height": device_height
+    };
+    for (feature in features) {
+      var value = features[feature];
+      should_apply("all and (" + feature + ": ${value}px)");
+      should_apply("all and (" + feature + " = ${value}px)");
+      should_not_apply("all and (" + feature + ": ${value + 1}px)");
+      should_not_apply("all and (" + feature + ": ${value - 1}px)");
+      should_not_apply("all and (" + feature + " = ${value + 1}px)");
+      should_not_apply("all and (" + feature + " = ${value - 1}px)");
+
+      should_apply("all and (min-" + feature + ": ${value}px)");
+      should_not_apply("all and (min-" + feature + ": ${value + 1}px)");
+      should_apply("all and (min-" + feature + ": ${value - 1}px)");
+      should_apply("all and (max-" + feature + ": ${value}px)");
+      should_apply("all and (max-" + feature + ": ${value + 1}px)");
+      should_not_apply("all and (max-" + feature + ": ${value - 1}px)");
+      should_not_apply("all and (min-" + feature + ": ${Math.ceil(value/em_size) + 1}em)");
+      should_apply("all and (min-" + feature + ": ${Math.floor(value/em_size) - 1}em)");
+      should_apply("all and (max-" + feature + ": ${Math.ceil(value/em_size) + 1}em)");
+      should_not_apply("all and (max-" + feature + ": ${Math.floor(value/em_size) - 1}em)");
+
+      should_apply("(" + feature + " <= ${value}px)");
+      should_apply("(" + feature + " >= ${value}px)");
+
+      should_apply("(0px < " + feature + " <= ${value}px)");
+      should_apply("(${value}px >= " + feature + " > 0px)");
+
+      should_not_apply("(0px < " + feature + " < ${value}px)");
+      should_not_apply("(${value}px > " + feature + " > 0px)");
+
+      should_not_apply("(" + feature + " < ${value}px)");
+      should_not_apply("(" + feature + " > ${value}px)");
+
+      should_apply("(" + feature + " < ${value + 1}px)");
+      should_apply("(" + feature + " <= ${value + 1}px)");
+      should_not_apply("(" + feature + " > ${value + 1}px)");
+      should_not_apply("(" + feature + " >= ${value + 1}px)");
+
+      should_apply("(" + feature + " > ${value - 1}px)");
+      should_apply("(" + feature + " >= ${value - 1}px)");
+      should_not_apply("(" + feature + " < ${value - 1}px)");
+      should_not_apply("(" + feature + " <= ${value - 1}px)");
+
+      should_apply("(${value - 1}px < " + feature + ")");
+      should_apply("(${value - 1}px <= " + feature + ")");
+      should_not_apply("(${value - 1}px > " + feature + ")");
+      should_not_apply("(${value - 1}px >= " + feature + ")");
+
+      should_apply("(${value - 1}px < " + feature + " < ${value + 1}px)");
+      should_apply("(${value - 1}px < " + feature + " <= ${value}px)");
+      should_apply("(${value}px <= " + feature + " < ${value + 1}px)");
+      should_apply("(${value + 1}px > " + feature + " > ${value - 1}px)");
+      should_apply("(${value + 1}px > " + feature + " >= ${value}px)");
+      should_apply("(${value}px >= " + feature + " > ${value - 1}px)");
+      should_not_apply("(${value}px > " + feature + " > ${value - 1}px)");
+      should_not_apply("(${value + 1}px > " + feature + " > ${value}px)");
+    }
+
+    iframe_style.width = "0";
+    testGroup = "width = 0, height != 0";
+    should_apply("all and (height)");
+    should_not_apply("all and (width)");
+    iframe_style.height = "0";
+    testGroup = "width = 0, height = 0"
+    should_not_apply("all and (height)");
+    should_not_apply("all and (width)");
+    should_apply("all and (device-height)");
+    should_apply("all and (device-width)");
+    iframe_style.width = width_val + "px";
+    testGroup = "width != 0, height = 0";
+    should_not_apply("all and (height)");
+    should_apply("all and (width)");
+    iframe_style.height = height_val + "px";
+    testGroup = "width != 0, height != 0";
+    should_apply("all and (height)");
+    should_apply("all and (width)");
+    testGroup = "";
+
+    testGroup = "ratio that reduces to 59/40";
+    iframe_style.width = "236px";
+    iframe_style.height = "160px";
+    expression_should_be_known("orientation");
+    expression_should_be_known("orientation: portrait");
+    expression_should_be_known("orientation: landscape");
+    expression_should_be_unknown("min-orientation");
+    expression_should_be_unknown("min-orientation: portrait");
+    expression_should_be_unknown("min-orientation: landscape");
+    expression_should_be_unknown("max-orientation");
+    expression_should_be_unknown("max-orientation: portrait");
+    expression_should_be_unknown("max-orientation: landscape");
+    should_apply("(orientation)");
+    should_apply("(orientation: landscape)");
+    should_not_apply("(orientation: portrait)");
+    should_apply("not all and (orientation: portrait)");
+
+    testGroup = "ratio that reduces to 59/80";
+    iframe_style.height = "320px";
+    should_apply("(orientation)");
+    should_not_apply("(orientation: landscape)");
+    should_apply("not all and (orientation: landscape)");
+    should_apply("(orientation: portrait)");
+    testGroup = "";
+
+    should_apply("(aspect-ratio: 59/80)");
+    should_not_apply("(aspect-ratio: 58/80)");
+    should_not_apply("(aspect-ratio: 59/81)");
+    should_not_apply("(aspect-ratio: 60/80)");
+    should_not_apply("(aspect-ratio: 59/79)");
+    should_apply("(aspect-ratio: 177/240)");
+    should_apply("(aspect-ratio: 413/560)");
+    should_apply("(aspect-ratio: 5900/8000)");
+    should_not_apply("(aspect-ratio: 5901/8000)");
+    should_not_apply("(aspect-ratio: 5899/8000)");
+    should_not_apply("(aspect-ratio: 5900/8001)");
+    should_not_apply("(aspect-ratio: 5900/7999)");
+    should_apply("(aspect-ratio)");
+
+    should_apply("(min-aspect-ratio: 59/80)");
+    should_apply("(min-aspect-ratio: 58/80)");
+    should_apply("(min-aspect-ratio: 59/81)");
+    should_not_apply("(min-aspect-ratio: 60/80)");
+    should_not_apply("(min-aspect-ratio: 59/79)");
+    expression_should_be_unknown("min-aspect-ratio");
+
+    should_apply("(max-aspect-ratio: 59/80)");
+    should_not_apply("(max-aspect-ratio: 58/80)");
+    should_not_apply("(max-aspect-ratio: 59/81)");
+    should_apply("(max-aspect-ratio: 60/80)");
+    should_apply("(max-aspect-ratio: 59/79)");
+    expression_should_be_unknown("max-aspect-ratio");
+
+    var real_dar = device_width + "/" + device_height;
+    var high_dar_1 = (device_width + 1) + "/" + device_height;
+    var high_dar_2 = device_width + "/" + (device_height - 1);
+    var low_dar_1 = (device_width - 1) + "/" + device_height;
+    var low_dar_2 = device_width + "/" + (device_height + 1);
+    should_apply("(device-aspect-ratio: ${real_dar})");
+    should_apply("not all and (device-aspect-ratio: ${high_dar_1})");
+    should_not_apply("all and (device-aspect-ratio: ${high_dar_2})");
+    should_not_apply("all and (device-aspect-ratio: ${low_dar_1})");
+    should_apply("not all and (device-aspect-ratio: ${low_dar_2})");
+    should_apply("(device-aspect-ratio)");
+
+    should_apply("(min-device-aspect-ratio: ${real_dar})");
+    should_not_apply("all and (min-device-aspect-ratio: ${high_dar_1})");
+    should_apply("not all and (min-device-aspect-ratio: ${high_dar_2})");
+    should_not_apply("not all and (min-device-aspect-ratio: ${low_dar_1})");
+    should_apply("all and (min-device-aspect-ratio: ${low_dar_2})");
+    expression_should_be_unknown("min-device-aspect-ratio");
+
+    should_apply("all and (max-device-aspect-ratio: ${real_dar})");
+    should_apply("(max-device-aspect-ratio: ${high_dar_1})");
+    should_apply("(max-device-aspect-ratio: ${high_dar_2})");
+    should_not_apply("all and (max-device-aspect-ratio: ${low_dar_1})");
+    should_apply("not all and (max-device-aspect-ratio: ${low_dar_2})");
+    expression_should_be_unknown("max-device-aspect-ratio");
+
+    features = [ "max-aspect-ratio", "device-aspect-ratio" ];
+    for (i in features) {
+      feature = features[i];
+      expression_should_be_known(feature + ": 1/1");
+      expression_should_be_known(feature + ": 1  /1");
+      expression_should_be_known(feature + ": 1  / \t\n1");
+      expression_should_be_known(feature + ": 1/\r1");
+      expression_should_be_known(feature + ": 1");
+      expression_should_be_known(feature + ": 0.5");
+      expression_should_be_known(feature + ": 1.0/1");
+      expression_should_be_known(feature + ": 1/1.0");
+      expression_should_be_known(feature + ": 1.0/1.0");
+      expression_should_be_known(feature + ": 0/1");
+      expression_should_be_known(feature + ": 1/0");
+      expression_should_be_known(feature + ": 0/0");
+      expression_should_be_unknown(feature + ": -1/1");
+      expression_should_be_unknown(feature + ": 1/-1");
+      expression_should_be_unknown(feature + ": -1/-1");
+      expression_should_be_unknown(feature + ": invalid");
+      expression_should_be_unknown(feature + ": 1 / invalid");
+      expression_should_be_unknown(feature + ": 1 invalid");
+    }
+
+    var is_monochrome = query_applies("all and (min-monochrome: 1)");
+    var is_color = query_applies("all and (min-color: 1)");
+    test(function() {
+      assert_not_equals(is_monochrome, is_color, "should be either monochrome or color");
+    }, "monochrome_and_color");
+
+    function depth_query(prefix, depth) {
+      return "all and (" + prefix + (is_color ? "color" : "monochrome") +
+             ":" + depth + ")";
+    }
+
+    var depth = 0;
+    do {
+      if (depth > 50) {
+        break;
+      }
+    } while (query_applies(depth_query("min-", ++depth)));
+    test(function() {
+      assert_false(50 < depth);
+    }, "find_depth");
+    --depth;
+
+    should_apply(depth_query("", depth));
+    should_not_apply(depth_query("", depth - 1));
+    should_not_apply(depth_query("", depth + 1));
+    should_apply(depth_query("max-", depth));
+    should_not_apply(depth_query("max-", depth - 1));
+    should_apply(depth_query("max-", depth + 1));
+
+    (is_color ? should_apply : should_not_apply)("all and (color)");
+    expression_should_be_unknown("max-color");
+    expression_should_be_unknown("min-color");
+    (is_color ? should_not_apply : should_apply)("all and (monochrome)");
+    expression_should_be_unknown("max-monochrome");
+    expression_should_be_unknown("min-monochrome");
+    (is_color ? should_apply : should_not_apply)("not all and (monochrome)");
+    (is_color ? should_not_apply : should_apply)("not all and (color)");
+    (is_color ? should_apply : should_not_apply)("only all and (color)");
+    (is_color ? should_not_apply : should_apply)("only all and (monochrome)");
+
+    features = [ "color", "min-monochrome", "max-color-index" ];
+    for (i in features) {
+      feature = features[i];
+      expression_should_be_known(feature + ": 1");
+      expression_should_be_known(feature + ": 327");
+      expression_should_be_known(feature + ": 0");
+      expression_should_be_unknown(feature + ": 1.0");
+      expression_should_be_known(feature + ": -1");
+      expression_should_be_unknown(feature + ": 1/1");
+    }
+
+    // Presume that we never support indexed color (at least not usefully
+    // enough to call it indexed color).
+    should_apply("(color-index: 0)");
+    should_not_apply("(color-index: 1)");
+    should_apply("(min-color-index: 0)");
+    should_not_apply("(min-color-index: 1)");
+    should_apply("(max-color-index: 0)");
+    should_apply("(max-color-index: 1)");
+    should_apply("(max-color-index: 157)");
+
+    features = [ "resolution", "min-resolution", "max-resolution" ];
+    for (i in features) {
+      feature = features[i];
+      expression_should_be_known(feature + ": 3dpi");
+      expression_should_be_known(feature + ":3dpi");
+      expression_should_be_known(feature + ": 3.0dpi");
+      expression_should_be_known(feature + ": 3.4dpi");
+      expression_should_be_known(feature + "\t: 120dpcm");
+      expression_should_be_known(feature + ": 1dppx");
+      expression_should_be_known(feature + ": 1x");
+      expression_should_be_known(feature + ": 1.5dppx");
+      expression_should_be_known(feature + ": 1.5x");
+      expression_should_be_known(feature + ": 2.0dppx");
+      expression_should_be_known(feature + ": 0dpi");
+      expression_should_be_known(feature + ": 0dppx");
+      expression_should_be_known(feature + ": 0x");
+      expression_should_be_known(feature + ": calc(6x / 2)");
+      expression_should_be_unknown(feature + ": -3dpi");
+    }
+
+    // Find the resolution using max-resolution
+    var resolution = 0;
+    do {
+      ++resolution;
+      if (resolution > 10000) {
+        break;
+      }
+    } while (!query_applies("(max-resolution: " + resolution + "dpi)"));
+    test(function() {
+      assert_false(10000 < resolution);
+    }, "find_resolution");
+
+    // resolution should now be Math.ceil() of the actual resolution.
+    var dpi_high;
+    var dpi_low = resolution - 1;
+    if (query_applies(`(min-resolution: ${resolution}dpi)`)) { // query_applies, so template literal!
+      // It's exact!
+      testGroup = "resolution is exact";
+      should_apply("(resolution: ${resolution}dpi)");
+      should_apply("(resolution: ${Math.floor(resolution/96)}dppx)");
+      should_apply("(resolution: ${Math.floor(resolution/96)}x)");
+      should_not_apply("(resolution: ${resolution + 1}dpi)");
+      should_not_apply("(resolution: ${resolution - 1}dpi)");
+      dpi_high = resolution + 1;
+    } else {
+      // We have no way to test resolution applying since it need not be
+      // an integer.
+      testGroup = "resolution is inexact";
+      should_not_apply("(resolution: ${resolution}dpi)");
+      should_not_apply("(resolution: ${resolution - 1}dpi)");
+      dpi_high = resolution;
+    }
+    testGroup = "";
+
+    should_apply("(min-resolution: ${dpi_low}dpi)");
+    should_not_apply("not all and (min-resolution: ${dpi_low}dpi)");
+    should_apply("not all and (min-resolution: ${dpi_high}dpi)");
+    should_not_apply("all and (min-resolution: ${dpi_high}dpi)");
+
+    // Test dpcm units based on what we computed in dpi.
+    var dpcm_high = Math.ceil(dpi_high / 2.54);
+    var dpcm_low = Math.floor(dpi_low / 2.54);
+    should_apply("(min-resolution: ${dpcm_low}dpcm)");
+    should_apply("(max-resolution: ${dpcm_high}dpcm)");
+    should_not_apply("(max-resolution: ${dpcm_low}dpcm)");
+    should_apply("not all and (min-resolution: ${dpcm_high}dpcm)");
+
+    expression_should_be_known("scan");
+    expression_should_be_known("scan: progressive");
+    expression_should_be_known("scan:interlace");
+    expression_should_be_unknown("min-scan:interlace");
+    expression_should_be_unknown("scan: 1");
+    expression_should_be_unknown("max-scan");
+    expression_should_be_unknown("max-scan: progressive");
+    // Assume we don't support tv devices.
+    should_not_apply("(scan)");
+    should_not_apply("(scan: progressive)");
+    should_not_apply("(scan: interlace)");
+    should_apply("not all and (scan)");
+    should_apply("not all and (scan: progressive)");
+    should_apply("not all and (scan: interlace)");
+
+    expression_should_be_known("grid");
+    expression_should_be_known("grid: 0");
+    expression_should_be_known("grid: 1");
+    expression_should_be_unknown("min-grid");
+    expression_should_be_unknown("min-grid:0");
+    expression_should_be_unknown("max-grid: 1");
+    expression_should_be_unknown("grid: 2");
+    expression_should_be_unknown("grid: -1");
+
+    // Assume we don't support grid devices
+    should_not_apply("(grid)");
+    should_apply("(grid: 0)");
+    should_not_apply("(grid: 1)");
+    should_not_apply("(grid: 2)");
+    should_not_apply("(grid: -1)");
+
+    // Parsing tests
+    // bug 454227
+    should_apply("(orientation");
+    should_not_apply("not all and (orientation");
+    should_not_apply("(orientation:");
+    should_not_apply("(orientation:)");
+    should_not_apply("(orientation:  )");
+    should_apply("all,(orientation:");
+    should_not_apply("(orientation:,all");
+    should_apply("not all and (grid");
+    should_not_apply("only all and (grid");
+    should_not_apply("(grid");
+    should_apply("all,(grid");
+    should_not_apply("(grid,all");
+    // bug 454226
+    should_apply(",all");
+    should_apply("all,");
+    should_apply(",all,");
+    should_apply("all,badmedium");
+    should_apply("badmedium,all");
+    should_not_apply(",badmedium,");
+    should_apply("all,(badexpression)");
+    should_apply("(badexpression),all");
+    should_not_apply("(badexpression),badmedium");
+    should_not_apply("badmedium,(badexpression)");
+    should_apply("all,[badsyntax]");
+    should_apply("[badsyntax],all");
+    should_not_apply("badmedium,[badsyntax]");
+    should_not_apply("[badsyntax],badmedium");
+
+    // Parsing tests based on Acid3
+    query_should_not_be_parseable("all and color :");
+    query_should_not_be_parseable("all and color : 1");
+    should_not_apply("all and min-color : 1");
+    should_not_apply("(bogus)");
+    should_not_apply("not all and (bogus)")
+    should_not_apply("only all and (bogus)")
+
+    // Parsing tests for overflow-block from mediaqueries-4
+    expression_should_be_known("overflow-block")
+    expression_should_be_known("overflow-block: none")
+    expression_should_be_known("overflow-block: paged")
+    expression_should_be_known("overflow-block: scroll")
+    expression_should_be_unknown("overflow-block: optional-paged")
+    expression_should_be_unknown("overflow-block: some-random-invalid-thing")
+
+    // Sanity check for overflow-block
+    test(function() {
+      var any_overflow_block = query_applies("(overflow-block)");
+      var overflow_block_none = query_applies("(overflow-block: none)");
+      assert_not_equals(any_overflow_block, overflow_block_none, "overflow-block should be equivalent to not (overflow-block: none)");
+    }, "Sanity check for overflow-block");
+
+    // Parsing tests for overflow-inline from mediaqueries-4
+    expression_should_be_known("overflow-inline")
+    expression_should_be_known("overflow-inline: none")
+    expression_should_be_known("overflow-inline: scroll")
+    expression_should_be_unknown("overflow-inline: some-random-invalid-thing")
+
+    // Sanity check for overflow-inline
+    test(function() {
+      var any_overflow_inline = query_applies("(overflow-inline)");
+      var overflow_inline_none = query_applies("(overflow-inline: none)");
+      assert_not_equals(any_overflow_inline, overflow_inline_none, "overflow-inline should be equivalent to not (overflow-inline: none)");
+    }, "Sanity check for overflow-inline");
+
+    // Parsing tests for update from mediaqueries-4
+    expression_should_be_known("update")
+    expression_should_be_known("update: none")
+    expression_should_be_known("update: slow")
+    expression_should_be_known("update: fast")
+    expression_should_be_unknown("update: some-random-invalid-thing")
+
+    // Sanity check for update
+    test(function() {
+      var any_update = query_applies("(update)");
+      var update_none = query_applies("(update: none)");
+      assert_not_equals(any_update, update_none, "update should be equivalent to not (update: none)");
+    }, "Sanity check for update");
+
+    // Parsing tests for interaction media features.
+    expression_should_be_known("hover")
+    expression_should_be_known("hover: hover")
+    expression_should_be_known("hover: none")
+    expression_should_be_known("any-hover")
+    expression_should_be_known("any-hover: hover")
+    expression_should_be_known("any-hover: none")
+
+    test(function() {
+      assert_equals(query_applies("(hover)"), query_applies("(hover: hover)"));
+    } , "(hover) == (hover: hover)");
+
+    test(function() {
+      assert_not_equals(query_applies("(hover)"), query_applies("(hover: none)"));
+    }, "(hover) == not (hover: none)");
+
+    test(function() {
+      assert_equals(query_applies("(any-hover)"), query_applies("(any-hover: hover)"));
+    }, "(any-hover) == (any-hover: hover)");
+
+    test(function() {
+      assert_not_equals(query_applies("(any-hover)"), query_applies("(any-hover: none)"));
+    }, "(any-hover) == not (any-hover: none)");
+
+    expression_should_be_known("pointer")
+    expression_should_be_known("pointer: coarse")
+    expression_should_be_known("pointer: fine")
+    expression_should_be_known("pointer: none")
+    expression_should_be_known("any-pointer")
+    expression_should_be_known("any-pointer: coarse")
+    expression_should_be_known("any-pointer: fine")
+    expression_should_be_known("any-pointer: none")
+
+    test(function() {
+      assert_equals(query_applies("(pointer)"),
+                    query_applies("(pointer: coarse)") || query_applies("(pointer: fine)"));
+    }, "(pointer) == (pointer: coarse) or (pointer: fine)");
+
+    test(function() {
+      assert_not_equals(query_applies("(pointer)"),
+                        query_applies("(pointer: none)"));
+    }, "(pointer) == not (pointer: none)");
+
+    test(function() {
+      assert_equals(query_applies("(any-pointer)"),
+                    query_applies("(any-pointer: coarse)") || query_applies("(any-pointer: fine)"));
+    }, "(any-pointer) == (any-pointer: coarse) or (any-pointer: fine)");
+
+    test(function() {
+      assert_not_equals(query_applies("(any-pointer)"),
+                        query_applies("(any-pointer: none)"));
+    }, "(any-pointer) == not (any-pointer: none)");
+
+    iframe_style.width = '100px';
+    iframe_style.height = '0px';
+
+    testGroup = "'or' keyword"
+    should_not_apply("(height) or (height)");
+    should_apply("(width) or (height)");
+    should_apply("(height) or (width)");
+    should_apply("(height) or (width) or (height)");
+    query_should_not_be_parseable("screen or (width)");
+    query_should_not_be_parseable("screen and (width) or (height)");
+
+    testGroup = "nesting"
+    should_not_apply("((height))");
+    should_apply("((width))");
+    should_apply("(((((width)))))");
+    should_apply("(((((width");
+
+    testGroup = "'not' keyword"
+    should_not_apply("not (width)");
+    should_apply("not (height)");
+    should_apply("not ((width) and (height))");
+    should_not_apply("not ((width) or (height))");
+    should_not_apply("not ((width) and (not (height)))");
+    query_should_not_be_parseable("not (width) and not (height)");
+    query_should_not_be_parseable("not not (width)");
+    query_should_be_parseable("not unknown(width) ");
+
+    testGroup = "three-valued logic"
+    should_not_apply("(unknown)");
+    should_not_apply("not (unknown)");
+    should_not_apply("((unknown) and (width))");
+    should_not_apply("not ((unknown) and (width))");
+    should_not_apply("((unknown) and (height))");
+    should_apply("not ((unknown) and (height))");
+    should_apply("((unknown) or (width))");
+    should_not_apply("not ((unknown) or (width))");
+    should_not_apply("((unknown) or (height))");
+    should_not_apply("not ((unknown) or (height))");
+    should_apply("(width) or (not ((unknown) and (width)))");
+    should_not_apply("(width) and (not ((unknown) and (width)))");
+    should_apply("(width) or (not ((unknown) or (width)))");
+    should_not_apply("(width) and (not ((unknown) or (width)))");
+    should_apply("(width) or (not ((unknown) and (height)))");
+    should_apply("(width) and (not ((unknown) and (height)))");
+    should_apply("(width) or (not ((unknown) or (height)))");
+    should_not_apply("(width) and (not ((unknown) or (height)))");
+    should_not_apply("unknown(width)");
+    should_not_apply("not unknown(width)");
+    should_apply("not (unknown(width) and (height))");
+    should_not_apply("not (unknown(width) or (height))");
+
+    testGroup = ""
+    done();
+}
+
+</script>
+</body>
+</html>

+ 0 - 32
UI/Qt/BrowserWindow.cpp

@@ -20,7 +20,6 @@
 #include <UI/Qt/BrowserWindow.h>
 #include <UI/Qt/Icon.h>
 #include <UI/Qt/Settings.h>
-#include <UI/Qt/SettingsDialog.h>
 #include <UI/Qt/StringUtils.h>
 #include <UI/Qt/TabBar.h>
 #include <UI/Qt/WebContentView.h>
@@ -97,18 +96,6 @@ BrowserWindow::BrowserWindow(Vector<URL::URL> const& initial_urls, IsPopupWindow
         });
     }
 
-    QObject::connect(Settings::the(), &Settings::preferred_languages_changed, this, [this](QStringList languages) {
-        Vector<String> preferred_languages;
-        preferred_languages.ensure_capacity(languages.length());
-        for (auto& language : languages) {
-            preferred_languages.append(ak_string_from_qstring(language));
-        }
-
-        for_each_tab([preferred_languages](auto& tab) {
-            tab.set_preferred_languages(preferred_languages);
-        });
-    });
-
     m_hamburger_menu = new HamburgerMenu(this);
 
     if (!Settings::the()->show_menubar())
@@ -198,18 +185,6 @@ BrowserWindow::BrowserWindow(Vector<URL::URL> const& initial_urls, IsPopupWindow
         new_tab_from_url(URL::URL::about("settings"_string), Web::HTML::ActivateTab::Yes);
     });
 
-    auto* deprecated_settings_action = new QAction("Qt Settings", this);
-    deprecated_settings_action->setIcon(load_icon_from_uri("resource://icons/16x16/settings.png"sv));
-    edit_menu->addAction(deprecated_settings_action);
-    QObject::connect(deprecated_settings_action, &QAction::triggered, this, [this] {
-        if (!m_settings_dialog) {
-            m_settings_dialog = new SettingsDialog(this);
-        }
-
-        m_settings_dialog->show();
-        m_settings_dialog->setFocus();
-    });
-
     auto* view_menu = m_hamburger_menu->addMenu("&View");
     menuBar()->addMenu(view_menu);
 
@@ -853,19 +828,12 @@ void BrowserWindow::initialize_tab(Tab* tab)
     m_tabs_container->setTabIcon(m_tabs_container->indexOf(tab), tab->favicon());
     create_close_button_for_tab(tab);
 
-    Vector<String> preferred_languages;
-    preferred_languages.ensure_capacity(Settings::the()->preferred_languages().length());
-    for (auto& language : Settings::the()->preferred_languages()) {
-        preferred_languages.append(ak_string_from_qstring(language));
-    }
-
     tab->set_line_box_borders(m_show_line_box_borders_action->isChecked());
     tab->set_scripting(m_enable_scripting_action->isChecked());
     tab->set_content_filtering(m_enable_content_filtering_action->isChecked());
     tab->set_block_popups(m_block_pop_ups_action->isChecked());
     tab->set_same_origin_policy(m_enable_same_origin_policy_action->isChecked());
     tab->set_user_agent_string(user_agent_string());
-    tab->set_preferred_languages(preferred_languages);
     tab->set_navigator_compatibility_mode(navigator_compatibility_mode());
     tab->view().set_preferred_color_scheme(m_preferred_color_scheme);
 }

+ 0 - 3
UI/Qt/BrowserWindow.h

@@ -24,7 +24,6 @@
 
 namespace Ladybird {
 
-class SettingsDialog;
 class WebContentView;
 
 class BrowserWindow : public QMainWindow {
@@ -210,8 +209,6 @@ private:
     ByteString m_user_agent_string {};
     ByteString m_navigator_compatibility_mode {};
 
-    SettingsDialog* m_settings_dialog { nullptr };
-
     IsPopupWindow m_is_popup_window { IsPopupWindow::No };
 };
 

+ 0 - 1
UI/Qt/CMakeLists.txt

@@ -7,7 +7,6 @@ target_sources(ladybird PRIVATE
     Icon.cpp
     LocationEdit.cpp
     Settings.cpp
-    SettingsDialog.cpp
     Tab.cpp
     TabBar.cpp
     TVGIconEngine.cpp

+ 0 - 11
UI/Qt/Settings.cpp

@@ -54,17 +54,6 @@ void Settings::set_is_maximized(bool is_maximized)
     m_qsettings->setValue("is_maximized", is_maximized);
 }
 
-QStringList Settings::preferred_languages()
-{
-    return m_qsettings->value("preferred_languages").toStringList();
-}
-
-void Settings::set_preferred_languages(QStringList const& languages)
-{
-    m_qsettings->setValue("preferred_languages", languages);
-    emit preferred_languages_changed(languages);
-}
-
 bool Settings::show_menubar()
 {
     return m_qsettings->value("show_menubar", false).toBool();

+ 0 - 4
UI/Qt/Settings.h

@@ -41,15 +41,11 @@ public:
     bool is_maximized();
     void set_is_maximized(bool is_maximized);
 
-    QStringList preferred_languages();
-    void set_preferred_languages(QStringList const& languages);
-
     bool show_menubar();
     void set_show_menubar(bool show_menubar);
 
 signals:
     void show_menubar_changed(bool show_menubar);
-    void preferred_languages_changed(QStringList const& languages);
     void enable_do_not_track_changed(bool enable);
 
 protected:

+ 0 - 42
UI/Qt/SettingsDialog.cpp

@@ -1,42 +0,0 @@
-/*
- * Copyright (c) 2022, Filiph Sandström <filiph.sandstrom@filfatstudios.com>
- * Copyright (c) 2023, Cameron Youell <cameronyouell@gmail.com>
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
-
-#include <LibURL/Parser.h>
-#include <LibURL/URL.h>
-#include <LibWebView/Application.h>
-#include <UI/Qt/Settings.h>
-#include <UI/Qt/SettingsDialog.h>
-#include <UI/Qt/StringUtils.h>
-
-#include <QLabel>
-#include <QMenu>
-
-namespace Ladybird {
-
-SettingsDialog::SettingsDialog(QMainWindow* window)
-    : QDialog(window)
-    , m_window(window)
-{
-    m_layout = new QFormLayout(this);
-
-    m_preferred_languages = new QLineEdit(this);
-    m_preferred_languages->setText(Settings::the()->preferred_languages().join(","));
-    QObject::connect(m_preferred_languages, &QLineEdit::editingFinished, this, [this] {
-        Settings::the()->set_preferred_languages(m_preferred_languages->text().split(","));
-    });
-    QObject::connect(m_preferred_languages, &QLineEdit::returnPressed, this, [this] {
-        close();
-    });
-
-    m_layout->addRow(new QLabel("Preferred Language(s)", this), m_preferred_languages);
-
-    setWindowTitle("Settings");
-    setLayout(m_layout);
-    resize(600, 250);
-}
-
-}

+ 0 - 31
UI/Qt/SettingsDialog.h

@@ -1,31 +0,0 @@
-/*
- * Copyright (c) 2022, Filiph Sandström <filiph.sandstrom@filfatstudios.com>
- * Copyright (c) 2023, Cameron Youell <cameronyouell@gmail.com>
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
-
-#pragma once
-
-#include <QCheckBox>
-#include <QDialog>
-#include <QFormLayout>
-#include <QLineEdit>
-#include <QMainWindow>
-#include <QPushButton>
-
-namespace Ladybird {
-
-class SettingsDialog : public QDialog {
-    Q_OBJECT
-
-public:
-    explicit SettingsDialog(QMainWindow* window);
-
-private:
-    QFormLayout* m_layout;
-    QMainWindow* m_window { nullptr };
-    QLineEdit* m_preferred_languages { nullptr };
-};
-
-}

+ 0 - 5
UI/Qt/Tab.cpp

@@ -938,9 +938,4 @@ void Tab::set_navigator_compatibility_mode(ByteString const& compatibility_mode)
     debug_request("navigator-compatibility-mode", compatibility_mode);
 }
 
-void Tab::set_preferred_languages(ReadonlySpan<String> preferred_languages)
-{
-    m_view->set_preferred_languages(preferred_languages);
-}
-
 }

+ 0 - 2
UI/Qt/Tab.h

@@ -88,8 +88,6 @@ public:
     void set_user_agent_string(ByteString const&);
     void set_navigator_compatibility_mode(ByteString const&);
 
-    void set_preferred_languages(ReadonlySpan<String> preferred_languages);
-
     bool url_is_hidden() const { return m_location_edit->url_is_hidden(); }
     void set_url_is_hidden(bool url_is_hidden) { m_location_edit->set_url_is_hidden(url_is_hidden); }
 

+ 9 - 0
UI/cmake/ResourceFiles.cmake

@@ -74,6 +74,11 @@ set(ABOUT_PAGES
 )
 list(TRANSFORM ABOUT_PAGES PREPEND "${LADYBIRD_SOURCE_DIR}/Base/res/ladybird/about-pages/")
 
+set(ABOUT_SETTINGS_RESOURCES
+    languages.js
+)
+list(TRANSFORM ABOUT_SETTINGS_RESOURCES PREPEND "${LADYBIRD_SOURCE_DIR}/Base/res/ladybird/about-pages/settings/")
+
 set(WEB_TEMPLATES
     directory.html
     error.html
@@ -170,6 +175,10 @@ function(copy_resources_to_build base_directory bundle_target)
         DESTINATION ${base_directory} TARGET ${bundle_target}
     )
 
+    copy_resource_set(ladybird/about-pages/settings RESOURCES ${ABOUT_SETTINGS_RESOURCES}
+        DESTINATION ${base_directory} TARGET ${bundle_target}
+    )
+
     copy_resource_set(ladybird/templates RESOURCES ${WEB_TEMPLATES}
         DESTINATION ${base_directory} TARGET ${bundle_target}
     )