Implement container-progress() function

As introduced in https://drafts.csswg.org/css-values-5/#container-progress-func
The container-progress() functional notation returns a <number> value
representing current value of the size-feature of some container, that
can be either named or unnamed, as a progress value between two explicit
calc values.

Note: for now we only support width and height features.

Note: now it's an editor's draft with a very strong chances to be accepted.
Bug: 40944203
Change-Id: I4664ad8d01174bfdc9621ca304caecc9db37c9ca
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5391308
Reviewed-by: Anders Hartvoll Ruud <andruud@chromium.org>
Commit-Queue: Daniil Sakhapov <sakhapov@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1279994}
diff --git a/third_party/blink/renderer/core/css/css_math_expression_node.cc b/third_party/blink/renderer/core/css/css_math_expression_node.cc
index 971966d..f4b031e 100644
--- a/third_party/blink/renderer/core/css/css_math_expression_node.cc
+++ b/third_party/blink/renderer/core/css/css_math_expression_node.cc
@@ -151,6 +151,22 @@
   }
 }
 
+CSSMathOperator CSSValueIDToCSSMathOperator(CSSValueID id) {
+  switch (id) {
+#define CONVERSION_CASE(value_id) \
+  case CSSValueID::value_id:      \
+    return CSSMathOperator::value_id;
+
+    CONVERSION_CASE(kProgress)
+    CONVERSION_CASE(kMediaProgress)
+    CONVERSION_CASE(kContainerProgress)
+
+#undef CONVERSION_CASE
+    default:
+      NOTREACHED_NORETURN();
+  }
+}
+
 static bool HasDoubleValue(CSSPrimitiveValue::UnitType type) {
   switch (type) {
     case CSSPrimitiveValue::UnitType::kNumber:
@@ -270,6 +286,12 @@
   return id == CSSValueID::kWidth || id == CSSValueID::kHeight;
 }
 
+// TODO(crbug.com/40944203): For now we only support width and height
+// size features.
+bool IsAllowedContainerFeature(const CSSValueID& id) {
+  return id == CSSValueID::kWidth || id == CSSValueID::kHeight;
+}
+
 bool CheckProgressFunctionTypes(
     CSSValueID function_id,
     const CSSMathExpressionOperation::Operands& nodes) {
@@ -296,6 +318,17 @@
       }
       break;
     }
+    case CSSValueID::kContainerProgress: {
+      if (!IsAllowedContainerFeature(
+              To<CSSMathExpressionContainerFeature>(*nodes[0]).GetValue())) {
+        return false;
+      }
+      if (nodes[1]->Category() != CalculationResultCategory::kCalcLength ||
+          nodes[2]->Category() != CalculationResultCategory::kCalcLength) {
+        return false;
+      }
+      break;
+    }
     default:
       NOTREACHED();
       break;
@@ -1599,6 +1632,12 @@
   return operation;
 }
 
+inline bool CanArithmeticOperationBeSimplified(
+    const CSSMathExpressionNode* left_side,
+    const CSSMathExpressionNode* right_side) {
+  return !left_side->IsOperation() && !right_side->IsOperation();
+}
+
 // static
 CSSMathExpressionNode*
 CSSMathExpressionOperation::CreateArithmeticOperationSimplified(
@@ -1613,7 +1652,7 @@
     return result;
   }
 
-  if (left_side->IsOperation() || right_side->IsOperation()) {
+  if (!CanArithmeticOperationBeSimplified(left_side, right_side)) {
     return CreateArithmeticOperation(left_side, right_side, op);
   }
 
@@ -1933,6 +1972,7 @@
     case CSSMathOperator::kSign:
     case CSSMathOperator::kProgress:
     case CSSMathOperator::kMediaProgress:
+    case CSSMathOperator::kContainerProgress:
       return std::nullopt;
     case CSSMathOperator::kInvalid:
       NOTREACHED();
@@ -2006,6 +2046,7 @@
     case CSSMathOperator::kSign:
     case CSSMathOperator::kProgress:
     case CSSMathOperator::kMediaProgress:
+    case CSSMathOperator::kContainerProgress:
     case CSSMathOperator::kCalcSize: {
       Vector<scoped_refptr<const CalculationExpressionNode>> operands;
       operands.reserve(operands_.size());
@@ -2035,6 +2076,8 @@
         op = CalculationOperator::kProgress;
       } else if (operator_ == CSSMathOperator::kMediaProgress) {
         op = CalculationOperator::kMediaProgress;
+      } else if (operator_ == CSSMathOperator::kContainerProgress) {
+        op = CalculationOperator::kContainerProgress;
       } else {
         CHECK(operator_ == CSSMathOperator::kCalcSize);
         op = CalculationOperator::kCalcSize;
@@ -2158,6 +2201,7 @@
     case CSSMathOperator::kProgress:
     case CSSMathOperator::kCalcSize:
     case CSSMathOperator::kMediaProgress:
+    case CSSMathOperator::kContainerProgress:
       return false;
     case CSSMathOperator::kInvalid:
       NOTREACHED();
@@ -2279,7 +2323,8 @@
       return result.ReleaseString();
     }
     case CSSMathOperator::kProgress:
-    case CSSMathOperator::kMediaProgress: {
+    case CSSMathOperator::kMediaProgress:
+    case CSSMathOperator::kContainerProgress: {
       CHECK_EQ(operands_.size(), 3u);
       StringBuilder result;
       result.Append(ToString(operator_));
@@ -2374,6 +2419,7 @@
         case CSSMathOperator::kSign:
         case CSSMathOperator::kProgress:
         case CSSMathOperator::kMediaProgress:
+        case CSSMathOperator::kContainerProgress:
           return CSSPrimitiveValue::UnitType::kNumber;
         case CSSMathOperator::kCalcSize: {
           DCHECK_EQ(operands_.size(), 2u);
@@ -2507,7 +2553,8 @@
       return signum;
     }
     case CSSMathOperator::kProgress:
-    case CSSMathOperator::kMediaProgress: {
+    case CSSMathOperator::kMediaProgress:
+    case CSSMathOperator::kContainerProgress: {
       CHECK_EQ(operands.size(), 3u);
       return (operands[0] - operands[1]) / (operands[2] - operands[1]);
     }
@@ -2569,6 +2616,81 @@
 
 // ------ End of CSSMathExpressionOperation member functions ------
 
+// ------ Start of CSSMathExpressionContainerProgress member functions ----
+
+namespace {
+
+double EvaluateContainerSize(const CSSIdentifierValue* size_feature,
+                             const CSSCustomIdentValue* container_name,
+                             const CSSLengthResolver& length_resolver) {
+  if (container_name) {
+    ScopedCSSName* name = MakeGarbageCollected<ScopedCSSName>(
+        container_name->Value(), container_name->GetTreeScope());
+    switch (size_feature->GetValueID()) {
+      case CSSValueID::kWidth:
+        return length_resolver.ContainerWidth(*name);
+      case CSSValueID::kHeight:
+        return length_resolver.ContainerHeight(*name);
+      default:
+        NOTREACHED_NORETURN();
+    }
+  } else {
+    switch (size_feature->GetValueID()) {
+      case CSSValueID::kWidth:
+        return length_resolver.ContainerWidth();
+      case CSSValueID::kHeight:
+        return length_resolver.ContainerHeight();
+      default:
+        NOTREACHED_NORETURN();
+    }
+  }
+}
+
+}  // namespace
+
+CSSMathExpressionContainerFeature::CSSMathExpressionContainerFeature(
+    const CSSIdentifierValue* size_feature,
+    const CSSCustomIdentValue* container_name)
+    : CSSMathExpressionNode(CalculationResultCategory::kCalcLength,
+                            /*has_comparisons =*/false,
+                            /*needs_tree_scope_population =*/true),
+      size_feature_(size_feature),
+      container_name_(container_name) {
+  CHECK(size_feature);
+}
+
+String CSSMathExpressionContainerFeature::CustomCSSText() const {
+  StringBuilder builder;
+  builder.Append(size_feature_->CustomCSSText());
+  if (container_name_ && !container_name_->Value().empty()) {
+    builder.Append(" of ");
+    builder.Append(container_name_->CustomCSSText());
+  }
+  return builder.ToString();
+}
+
+scoped_refptr<const CalculationExpressionNode>
+CSSMathExpressionContainerFeature::ToCalculationExpression(
+    const CSSLengthResolver& length_resolver) const {
+  double progress =
+      EvaluateContainerSize(size_feature_, container_name_, length_resolver);
+  return base::MakeRefCounted<CalculationExpressionPixelsAndPercentNode>(
+      PixelsAndPercent(progress));
+}
+
+std::optional<PixelsAndPercent>
+CSSMathExpressionContainerFeature::ToPixelsAndPercent(
+    const CSSLengthResolver& length_resolver) const {
+  return PixelsAndPercent(ComputeDouble(length_resolver));
+}
+
+double CSSMathExpressionContainerFeature::ComputeDouble(
+    const CSSLengthResolver& length_resolver) const {
+  return EvaluateContainerSize(size_feature_, container_name_, length_resolver);
+}
+
+// ------ End of CSSMathExpressionContainerProgress member functions ------
+
 // ------ Start of CSSMathExpressionAnchorQuery member functions ------
 
 namespace {
@@ -2979,6 +3101,7 @@
         return RuntimeEnabledFeatures::CSSAnchorPositioningEnabled();
       case CSSValueID::kProgress:
       case CSSValueID::kMediaProgress:
+      case CSSValueID::kContainerProgress:
         return RuntimeEnabledFeatures::CSSProgressNotationEnabled();
       case CSSValueID::kCalcSize:
         return RuntimeEnabledFeatures::CSSCalcSizeFunctionEnabled();
@@ -3080,11 +3203,13 @@
 
   // https://drafts.csswg.org/css-values-5/#progress-func
   // https://drafts.csswg.org/css-values-5/#media-progress-func
+  // https://drafts.csswg.org/css-values-5/#container-progress-func
   CSSMathExpressionNode* ParseProgressNotation(CSSValueID function_id,
                                                CSSParserTokenRange& tokens,
                                                State state) {
     if (function_id != CSSValueID::kProgress &&
-        function_id != CSSValueID::kMediaProgress) {
+        function_id != CSSValueID::kMediaProgress &&
+        function_id != CSSValueID::kContainerProgress) {
       return nullptr;
     }
     // <media-progress()> = media-progress(<media-feature> from <calc-sum> to
@@ -3096,6 +3221,27 @@
               ParseKeywordLiteral(tokens, CSSMathOperator::kMediaProgress)) {
         nodes.push_back(node);
       }
+    } else if (function_id == CSSValueID::kContainerProgress) {
+      // <container-progress()> = container-progress(<size-feature> [ of
+      // <container-name> ]? from <calc-sum> to <calc-sum>)
+      const CSSIdentifierValue* size_feature =
+          css_parsing_utils::ConsumeIdent(tokens);
+      if (!size_feature) {
+        return nullptr;
+      }
+      if (tokens.Peek().Id() == CSSValueID::kOf) {
+        tokens.ConsumeIncludingWhitespace();
+        const CSSCustomIdentValue* container_name =
+            css_parsing_utils::ConsumeCustomIdent(tokens, context_);
+        if (!container_name) {
+          return nullptr;
+        }
+        nodes.push_back(MakeGarbageCollected<CSSMathExpressionContainerFeature>(
+            size_feature, container_name));
+      } else {
+        nodes.push_back(MakeGarbageCollected<CSSMathExpressionContainerFeature>(
+            size_feature, nullptr));
+      }
     } else if (CSSMathExpressionNode* node =
                    ParseValueExpression(tokens, state)) {
       // <progress()> = progress(<calc-sum> from <calc-sum> to <calc-sum>)
@@ -3137,8 +3283,7 @@
     }
     return MakeGarbageCollected<CSSMathExpressionOperation>(
         CalculationResultCategory::kCalcNumber, std::move(nodes),
-        function_id == CSSValueID::kProgress ? CSSMathOperator::kProgress
-                                             : CSSMathOperator::kMediaProgress);
+        CSSValueIDToCSSMathOperator(function_id));
   }
 
   CSSMathExpressionNode* ParseCalcSize(CSSValueID function_id,
@@ -3813,7 +3958,8 @@
           std::move(operands), op);
     }
     case CalculationOperator::kProgress:
-    case CalculationOperator::kMediaProgress: {
+    case CalculationOperator::kMediaProgress:
+    case CalculationOperator::kContainerProgress: {
       CHECK_EQ(children.size(), 3u);
       CSSMathExpressionOperation::Operands operands;
       operands.push_back(Create(*children.front()));
diff --git a/third_party/blink/renderer/core/css/css_math_expression_node.h b/third_party/blink/renderer/core/css/css_math_expression_node.h
index 51ff844..8430dcba 100644
--- a/third_party/blink/renderer/core/css/css_math_expression_node.h
+++ b/third_party/blink/renderer/core/css/css_math_expression_node.h
@@ -38,12 +38,15 @@
 #include "base/dcheck_is_on.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/css/css_anchor_query_enums.h"
+#include "third_party/blink/renderer/core/css/css_custom_ident_value.h"
+#include "third_party/blink/renderer/core/css/css_identifier_value.h"
 #include "third_party/blink/renderer/core/css/css_length_resolver.h"
 #include "third_party/blink/renderer/core/css/css_math_operator.h"
 #include "third_party/blink/renderer/core/css/css_primitive_value.h"
 #include "third_party/blink/renderer/core/css/css_value.h"
 #include "third_party/blink/renderer/core/css/parser/css_parser_token_range.h"
 #include "third_party/blink/renderer/core/css_value_keywords.h"
+#include "third_party/blink/renderer/core/dom/tree_scope.h"
 #include "third_party/blink/renderer/core/layout/geometry/axis.h"
 #include "third_party/blink/renderer/platform/geometry/calculation_value.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
@@ -118,6 +121,7 @@
   virtual bool IsAnchorQuery() const { return false; }
   virtual bool IsIdentifierLiteral() const { return false; }
   virtual bool IsKeywordLiteral() const { return false; }
+  virtual bool IsContainerFeature() const { return false; }
 
   virtual bool IsMathFunction() const { return false; }
 
@@ -600,7 +604,8 @@
   bool IsCalcSize() const { return operator_ == CSSMathOperator::kCalcSize; }
   bool IsProgressNotation() const {
     return operator_ == CSSMathOperator::kProgress ||
-           operator_ == CSSMathOperator::kMediaProgress;
+           operator_ == CSSMathOperator::kMediaProgress ||
+           operator_ == CSSMathOperator::kContainerProgress;
   }
 
   // TODO(crbug.com/1284199): Check other math functions too.
@@ -676,6 +681,96 @@
   }
 };
 
+class CORE_EXPORT CSSMathExpressionContainerFeature final
+    : public CSSMathExpressionNode {
+ public:
+  CSSMathExpressionContainerFeature(const CSSIdentifierValue* size_feature,
+                                    const CSSCustomIdentValue* container_name);
+
+  CSSMathExpressionNode* Copy() const final {
+    return MakeGarbageCollected<CSSMathExpressionContainerFeature>(
+        size_feature_, container_name_);
+  }
+
+  bool IsContainerFeature() const final { return true; }
+
+  const CSSMathExpressionNode& PopulateWithTreeScope(
+      const TreeScope* tree_scope) const final {
+    const CSSCustomIdentValue* container_name =
+        container_name_ ? &container_name_->PopulateWithTreeScope(tree_scope)
+                        : nullptr;
+    return *MakeGarbageCollected<CSSMathExpressionContainerFeature>(
+        size_feature_, container_name);
+  }
+  const CSSMathExpressionNode* TransformAnchors(
+      LogicalAxis axis,
+      const TryTacticTransform& transform,
+      const WritingDirectionMode& mode) const final {
+    return this;
+  }
+
+  CSSValueID GetValue() const { return size_feature_->GetValueID(); }
+
+  bool IsZero() const final { return false; }
+  String CustomCSSText() const final;
+  scoped_refptr<const CalculationExpressionNode> ToCalculationExpression(
+      const CSSLengthResolver&) const final;
+  std::optional<PixelsAndPercent> ToPixelsAndPercent(
+      const CSSLengthResolver&) const final;
+  double DoubleValue() const final {
+    NOTREACHED();
+    return 0;
+  }
+  std::optional<double> ComputeValueInCanonicalUnit() const final {
+    return std::nullopt;
+  }
+  double ComputeLengthPx(const CSSLengthResolver& length_resolver) const final {
+    NOTREACHED();
+    return 0;
+  }
+  bool AccumulateLengthArray(CSSLengthArray& length_array,
+                             double multiplier) const final {
+    return false;
+  }
+  void AccumulateLengthUnitTypes(
+      CSSPrimitiveValue::LengthTypeFlags& types) const final {}
+  bool IsComputationallyIndependent() const final { return true; }
+  bool operator==(const CSSMathExpressionNode& other) const final {
+    auto* other_progress = DynamicTo<CSSMathExpressionContainerFeature>(other);
+    return other_progress &&
+           base::ValuesEquivalent(other_progress->size_feature_,
+                                  size_feature_) &&
+           base::ValuesEquivalent(other_progress->container_name_,
+                                  container_name_);
+  }
+  CSSPrimitiveValue::UnitType ResolvedUnitType() const final {
+    return CSSPrimitiveValue::UnitType::kNumber;
+  }
+  void Trace(Visitor* visitor) const final {
+    visitor->Trace(size_feature_);
+    visitor->Trace(container_name_);
+    CSSMathExpressionNode::Trace(visitor);
+  }
+
+#if DCHECK_IS_ON()
+  bool InvolvesPercentageComparisons() const final { return false; }
+#endif
+
+ protected:
+  double ComputeDouble(const CSSLengthResolver& length_resolver) const final;
+
+ private:
+  Member<const CSSIdentifierValue> size_feature_;
+  Member<const CSSCustomIdentValue> container_name_;
+};
+
+template <>
+struct DowncastTraits<CSSMathExpressionContainerFeature> {
+  static bool AllowFrom(const CSSMathExpressionNode& node) {
+    return node.IsContainerFeature();
+  }
+};
+
 // anchor() and anchor-size()
 class CORE_EXPORT CSSMathExpressionAnchorQuery final
     : public CSSMathExpressionNode {
diff --git a/third_party/blink/renderer/core/css/css_math_operator.cc b/third_party/blink/renderer/core/css/css_math_operator.cc
index e7d841d..c375466 100644
--- a/third_party/blink/renderer/core/css/css_math_operator.cc
+++ b/third_party/blink/renderer/core/css/css_math_operator.cc
@@ -67,6 +67,8 @@
       return "calc-size";
     case CSSMathOperator::kMediaProgress:
       return "media-progress";
+    case CSSMathOperator::kContainerProgress:
+      return "container-progress";
     default:
       NOTREACHED();
       return String();
diff --git a/third_party/blink/renderer/core/css/css_math_operator.h b/third_party/blink/renderer/core/css/css_math_operator.h
index cbb820c..a5b9c6d 100644
--- a/third_party/blink/renderer/core/css/css_math_operator.h
+++ b/third_party/blink/renderer/core/css/css_math_operator.h
@@ -33,6 +33,7 @@
   kProgress,
   kCalcSize,
   kMediaProgress,
+  kContainerProgress,
   kInvalid
 };
 
diff --git a/third_party/blink/renderer/core/css/css_value_keywords.json5 b/third_party/blink/renderer/core/css/css_value_keywords.json5
index 3bd8ff9d2..35daa46 100644
--- a/third_party/blink/renderer/core/css/css_value_keywords.json5
+++ b/third_party/blink/renderer/core/css/css_value_keywords.json5
@@ -1875,5 +1875,9 @@
 
     // media-progress()
     "media-progress",
+
+    // container-progress()
+    "container-progress",
+    "of",
   ],
 }
diff --git a/third_party/blink/renderer/platform/geometry/calculation_expression_node.cc b/third_party/blink/renderer/platform/geometry/calculation_expression_node.cc
index 20d9460..cdbed37b 100644
--- a/third_party/blink/renderer/platform/geometry/calculation_expression_node.cc
+++ b/third_party/blink/renderer/platform/geometry/calculation_expression_node.cc
@@ -326,7 +326,8 @@
       }
     }
     case CalculationOperator::kProgress:
-    case CalculationOperator::kMediaProgress: {
+    case CalculationOperator::kMediaProgress:
+    case CalculationOperator::kContainerProgress: {
       DCHECK_EQ(children.size(), 3u);
       Vector<float, 3> operand_pixels;
       bool can_simplify = true;
@@ -490,7 +491,8 @@
       return children_[1]->Evaluate(max_value, calculation_input);
     }
     case CalculationOperator::kProgress:
-    case CalculationOperator::kMediaProgress: {
+    case CalculationOperator::kMediaProgress:
+    case CalculationOperator::kContainerProgress: {
       DCHECK(!children_.empty());
       float progress = children_[0]->Evaluate(max_value, input);
       float from = children_[1]->Evaluate(max_value, input);
@@ -553,7 +555,8 @@
     case CalculationOperator::kAbs:
     case CalculationOperator::kSign:
     case CalculationOperator::kProgress:
-    case CalculationOperator::kMediaProgress: {
+    case CalculationOperator::kMediaProgress:
+    case CalculationOperator::kContainerProgress: {
       DCHECK(children_.size());
       Vector<scoped_refptr<const CalculationExpressionNode>> cloned_operands;
       cloned_operands.reserve(children_.size());
@@ -631,6 +634,7 @@
       return first_child_type;
     }
     case CalculationOperator::kSign:
+    case CalculationOperator::kContainerProgress:
     case CalculationOperator::kProgress:
     case CalculationOperator::kMediaProgress:
       return ResultType::kNumber;
diff --git a/third_party/blink/renderer/platform/geometry/calculation_expression_node.h b/third_party/blink/renderer/platform/geometry/calculation_expression_node.h
index 328ddec..6b3d694 100644
--- a/third_party/blink/renderer/platform/geometry/calculation_expression_node.h
+++ b/third_party/blink/renderer/platform/geometry/calculation_expression_node.h
@@ -31,6 +31,7 @@
   kAbs,
   kSign,
   kProgress,
+  kContainerProgress,
   kCalcSize,
   kMediaProgress,
   kInvalid
diff --git a/third_party/blink/web_tests/external/wpt/css/css-values/container-progress-computed.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-values/container-progress-computed.tentative.html
new file mode 100644
index 0000000..9ab537c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-values/container-progress-computed.tentative.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-values-5/#container-progress-func">
+<link rel="author" title="sakhapov@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../support/numeric-testcommon.js"></script>
+<div id="out-of-scope-container"></div>
+<div id="extra-container">
+  <div id="outer-container">
+    <div id="inner-container">
+      <div id=target></div>
+    </div>
+  </div>
+</div>
+<style>
+:root {
+  font-size: 10px;
+}
+#out-of-scope-container {
+  container: my-container-3 / size;
+  width: 1px;
+  height: 1px;
+}
+#extra-container {
+  container: my-container-2 / size;
+  width: 5051px;
+  height: 1337px;
+}
+#outer-container {
+  container: my-container / size;
+  width: 322px;
+  height: 228px;
+}
+#inner-container {
+  container-type: size;
+  width: 228px;
+  height: 322px;
+}
+#target {
+  font-size: 10px;
+}
+</style>
+<script>
+
+let width = window.innerWidth;
+let height = window.innerHeight;
+
+let extraWidth = 5051;
+let extraHeight = 1337;
+let innerWidth = 228;
+let innerHeight = 322;
+let outerWidth = 322;
+let outerHeight = 228;
+
+// Identity tests
+test_math_used('container-progress(height from 0px to 1px)', innerHeight, {type:'number'});
+test_math_used('container-progress(width of my-container from 0px to 1px)', outerWidth, {type:'number'});
+
+// Nestings
+test_math_used('container-progress(height from container-progress(height from 0px to 1px) * 1px to container-progress(height from 0px to 1px) * 1px)', '0', {type:'number'});
+test_math_used('container-progress(height from container-progress(height from 0px to 1px) * 0.5px to container-progress(height from 0px to 1px) * 1px)', '1', {type:'number'});
+test_math_used('container-progress(height from container-progress(width of my-container from 0px to 1px) * 1px to container-progress(height of my-container-2 from 0px to 1px) * 1px)', (innerHeight - outerWidth) / (extraHeight - outerWidth), {type:'number'});
+
+// General calculations
+test_math_used('calc(container-progress(width from 0px to 50px) * 10px + 100px)', (innerWidth / 50 * 10 + 100) + 'px');
+test_math_used('calc(container-progress(height from 10px to sign(50px - 500em) * 10px))', (innerHeight - 10) / (-10 - 10), {type:'number'});
+test_math_used('calc(container-progress(width of my-container from 0px to 50px) * 10px + 100px)', (outerWidth / 50 * 10 + 100) + 'px');
+test_math_used('calc(container-progress(height of my-container from 10px to sign(50px - 500em) * 10px))', (outerHeight - 10) / (-10 - 10), {type:'number'});
+
+// Fallback
+test_math_used('container-progress(width of non-existing-container from 0px to 1px)', width, {type:'number'});
+test_math_used('container-progress(height of non-existing-container from 0px to 1px)', height, {type:'number'});
+test_math_used('container-progress(width of out-of-scope-container from 0px to 1px)', width, {type:'number'});
+test_math_used('container-progress(height of out-of-scope-container from 0px to 1px)', height, {type:'number'});
+
+// Type checking
+test_math_used('calc(container-progress(width from 0px to 1px) * 1px)', innerWidth + 'px');
+test_math_used('calc(container-progress(height of my-container from 0px to 1px) * 1s)', outerHeight + 's', {type:'time'});
+test_math_used('calc(container-progress(width of my-container-2 from 0px to 1px) * 1deg)', extraWidth + 'deg', {type:'angle', approx:0.001});
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-values/container-progress-invalid.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-values/container-progress-invalid.tentative.html
new file mode 100644
index 0000000..a78fd344
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-values/container-progress-invalid.tentative.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-values-5/#container-progress-func">
+<link rel="author" title="sakhapov@chromuim.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../support/parsing-testcommon.js"></script>
+<script>
+function test_invalid_number(value) {
+  test_invalid_value('opacity', value);
+}
+function test_invalid_length(value) {
+  // 'letter-spacing' accepts <length> only, not <percentage> or any mixes.
+  test_invalid_value('letter-spacing', value);
+}
+
+// Syntax checking
+test_invalid_number('container-progress()');
+test_invalid_number('container-progress( )');
+test_invalid_number('container-progress(,)');
+test_invalid_number('container-progress(1 from )');
+test_invalid_number('container-progress(1)');
+test_invalid_number('container-progress(50% to 0)');
+test_invalid_number('container-progress(0 from 1 to)');
+test_invalid_number('container-progress(from to)');
+test_invalid_number('container-progress(from 1 to 0)');
+test_invalid_number('container-progress(3 of 2 from 1 to 0)');
+test_invalid_number('container-progress(width of 2 from 1 to 0)');
+test_invalid_number('container-progress(from 1 to 0 1)');
+test_invalid_number('container-progress(from 1 0)');
+test_invalid_number('container-progress(0 from to 0)');
+test_invalid_number('container-progress(to to to to to)');
+test_invalid_number('container-progress(0, from, 10, to 200)');
+test_invalid_number('container-progress(0, from, 10, to, 200)');
+test_invalid_number('container-progress(0, from 10, to 200)');
+test_invalid_number('container-progress(0, 10, 200)');
+
+// General tests
+test_invalid_number('container-progress(height from 0 to 8');
+test_invalid_number('container-progress(height container from 0 to 8');
+test_invalid_number('container-progress(height of from 0 to 8');
+test_invalid_number('container-progress(depth from 0px to 8px');
+test_invalid_number('container-progress(width of 10 from 0px to 8px');
+test_invalid_number('container-progress(height of 10 from 0px to 8px');
+test_invalid_number('container-progress(height of name from 0deg to 8deg');
+test_invalid_number('container-progress(height of name from 0 to 8px');
+test_invalid_number('container-progress(10px from 0px to 8px');
+test_invalid_number('container-progress(depth of name from 0px to 8px');
+test_invalid_number('container-progress(width from 0deg to 8deg');
+test_invalid_number('container-progress(5 from 0deg to 8deg');
+test_invalid_number('container-progress(5 from 0% to 8deg');
+test_invalid_number('container-progress(height from 0% to sign(10px)');
+test_invalid_number('container-progress(5% from 0px to 10px');
+test_invalid_length('calc(1px * container-progress(10deg from 0 to 10))');
+test_invalid_length('calc(1px * container-progress(10 from 0px to 10))');
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-values/container-progress-serialize.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-values/container-progress-serialize.tentative.html
new file mode 100644
index 0000000..181054c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-values/container-progress-serialize.tentative.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-values-5/#container-progress-func">
+<link rel="author" title="sakhapov@chromuim.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../support/serialize-testcommon.js"></script>
+<div id="outer-container">
+  <div id="inner-container">
+    <div id=target></div>
+  </div>
+</div>
+<style>
+:root {
+  font-size: 10px;
+}
+#outer-container {
+  container: my-container / size;
+  width: 322px;
+  height: 228px;
+}
+#inner-container {
+  container-type: size;
+  width: 228px;
+  height: 322px;
+}
+#target {
+  font-size: 10px;
+}
+</style>
+<script>
+function test_serialization(t,s,c) {
+    test_specified_serialization('opacity', t, s);
+    test_specified_serialization('transform', `scale(${t})`, `scale(${s})`);
+    test_computed_serialization('opacity', t, c);
+    test_computed_serialization('transform', `scale(${t})`, `matrix(${c}, 0, 0, ${c}, 0, 0)`);
+}
+
+test_serialization(
+    'calc(container-progress(width from 0px to 1px) / 1000)',
+    'calc(container-progress(width from 0px to 1px) / 1000)',
+    '0.228',
+);
+test_serialization(
+    'calc(0.1 * container-progress(height of my-container from 0px to 10em))',
+    'calc(0.1 * container-progress(height of my-container from 0px to 10em))',
+    '0.228',
+);
+</script>