From 5b7a8891a67cd855ea364ddfc1921f7d522200bd Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Tue, 9 Jan 2024 12:41:43 +0100 Subject: [PATCH] LibWeb: Implement calc() value equality check in a more efficient way Instead of serializing two calc() values to String and then comparing those strings, we can now compare calc() values by actually traversing their internal CalculationNode tree. This makes style recomputation faster on pages with lots of calc() values since it's now much cheaper to check whether a property with some calc() value actually changed. --- .../Libraries/LibWeb/CSS/Parser/Parser.cpp | 1 + .../CSS/StyleValues/CalculatedStyleValue.cpp | 267 +++++++++++++++++- .../CSS/StyleValues/CalculatedStyleValue.h | 29 ++ 3 files changed, 295 insertions(+), 2 deletions(-) diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index 9932b07ce1..9b691d84cd 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -6552,6 +6552,7 @@ public: { builder.appendff("{: >{}}UNPARSED({})\n", "", indent, m_component_value.to_debug_string()); } + virtual bool equals(CalculationNode const&) const override { return false; } private: UnparsedCalculationNode(ComponentValue component_value) diff --git a/Userland/Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.cpp b/Userland/Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.cpp index f77baaf931..5dacf8e27a 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.cpp @@ -237,6 +237,15 @@ void NumericCalculationNode::dump(StringBuilder& builder, int indent) const builder.appendff("{: >{}}NUMERIC({})\n", "", indent, m_value.visit([](auto& it) { return it.to_string(); })); } +bool NumericCalculationNode::equals(CalculationNode const& other) const +{ + if (this == &other) + return true; + if (type() != other.type()) + return false; + return m_value == static_cast(other).m_value; +} + NonnullOwnPtr SumCalculationNode::create(Vector> values) { return adopt_own(*new (nothrow) SumCalculationNode(move(values))); @@ -355,6 +364,19 @@ void SumCalculationNode::dump(StringBuilder& builder, int indent) const item->dump(builder, indent + 2); } +bool SumCalculationNode::equals(CalculationNode const& other) const +{ + if (this == &other) + return true; + if (type() != other.type()) + return false; + for (size_t i = 0; i < m_values.size(); ++i) { + if (!m_values[i]->equals(*static_cast(other).m_values[i])) + return false; + } + return true; +} + NonnullOwnPtr ProductCalculationNode::create(Vector> values) { return adopt_own(*new (nothrow) ProductCalculationNode(move(values))); @@ -478,6 +500,19 @@ void ProductCalculationNode::dump(StringBuilder& builder, int indent) const item->dump(builder, indent + 2); } +bool ProductCalculationNode::equals(CalculationNode const& other) const +{ + if (this == &other) + return true; + if (type() != other.type()) + return false; + for (size_t i = 0; i < m_values.size(); ++i) { + if (!m_values[i]->equals(*static_cast(other).m_values[i])) + return false; + } + return true; +} + NonnullOwnPtr NegateCalculationNode::create(NonnullOwnPtr value) { return adopt_own(*new (nothrow) NegateCalculationNode(move(value))); @@ -532,6 +567,15 @@ void NegateCalculationNode::dump(StringBuilder& builder, int indent) const m_value->dump(builder, indent + 2); } +bool NegateCalculationNode::equals(CalculationNode const& other) const +{ + if (this == &other) + return true; + if (type() != other.type()) + return false; + return m_value->equals(*static_cast(other).m_value); +} + NonnullOwnPtr InvertCalculationNode::create(NonnullOwnPtr value) { return adopt_own(*new (nothrow) InvertCalculationNode(move(value))); @@ -593,6 +637,15 @@ void InvertCalculationNode::dump(StringBuilder& builder, int indent) const m_value->dump(builder, indent + 2); } +bool InvertCalculationNode::equals(CalculationNode const& other) const +{ + if (this == &other) + return true; + if (type() != other.type()) + return false; + return m_value->equals(*static_cast(other).m_value); +} + NonnullOwnPtr MinCalculationNode::create(Vector> values) { return adopt_own(*new (nothrow) MinCalculationNode(move(values))); @@ -675,6 +728,19 @@ void MinCalculationNode::dump(StringBuilder& builder, int indent) const value->dump(builder, indent + 2); } +bool MinCalculationNode::equals(CalculationNode const& other) const +{ + if (this == &other) + return true; + if (type() != other.type()) + return false; + for (size_t i = 0; i < m_values.size(); ++i) { + if (!m_values[i]->equals(*static_cast(other).m_values[i])) + return false; + } + return true; +} + NonnullOwnPtr MaxCalculationNode::create(Vector> values) { return adopt_own(*new (nothrow) MaxCalculationNode(move(values))); @@ -757,6 +823,19 @@ void MaxCalculationNode::dump(StringBuilder& builder, int indent) const value->dump(builder, indent + 2); } +bool MaxCalculationNode::equals(CalculationNode const& other) const +{ + if (this == &other) + return true; + if (type() != other.type()) + return false; + for (size_t i = 0; i < m_values.size(); ++i) { + if (!m_values[i]->equals(*static_cast(other).m_values[i])) + return false; + } + return true; +} + NonnullOwnPtr ClampCalculationNode::create(NonnullOwnPtr min, NonnullOwnPtr center, NonnullOwnPtr max) { return adopt_own(*new (nothrow) ClampCalculationNode(move(min), move(center), move(max))); @@ -853,6 +932,17 @@ void ClampCalculationNode::dump(StringBuilder& builder, int indent) const m_max_value->dump(builder, indent + 2); } +bool ClampCalculationNode::equals(CalculationNode const& other) const +{ + if (this == &other) + return true; + if (type() != other.type()) + return false; + return m_min_value->equals(*static_cast(other).m_min_value) + && m_center_value->equals(*static_cast(other).m_center_value) + && m_max_value->equals(*static_cast(other).m_max_value); +} + NonnullOwnPtr AbsCalculationNode::create(NonnullOwnPtr value) { return adopt_own(*new (nothrow) AbsCalculationNode(move(value))); @@ -915,6 +1005,15 @@ void AbsCalculationNode::dump(StringBuilder& builder, int indent) const builder.appendff("{: >{}}ABS: {}\n", "", indent, to_string()); } +bool AbsCalculationNode::equals(CalculationNode const& other) const +{ + if (this == &other) + return true; + if (type() != other.type()) + return false; + return m_value->equals(*static_cast(other).m_value); +} + NonnullOwnPtr SignCalculationNode::create(NonnullOwnPtr value) { return adopt_own(*new (nothrow) SignCalculationNode(move(value))); @@ -979,6 +1078,15 @@ void SignCalculationNode::dump(StringBuilder& builder, int indent) const builder.appendff("{: >{}}SIGN: {}\n", "", indent, to_string()); } +bool SignCalculationNode::equals(CalculationNode const& other) const +{ + if (this == &other) + return true; + if (type() != other.type()) + return false; + return m_value->equals(*static_cast(other).m_value); +} + NonnullOwnPtr ConstantCalculationNode::create(ConstantType constant) { return adopt_own(*new (nothrow) ConstantCalculationNode(constant)); @@ -1047,6 +1155,15 @@ void ConstantCalculationNode::dump(StringBuilder& builder, int indent) const builder.appendff("{: >{}}CONSTANT: {}\n", "", indent, to_string()); } +bool ConstantCalculationNode::equals(CalculationNode const& other) const +{ + if (this == &other) + return true; + if (type() != other.type()) + return false; + return m_constant == static_cast(other).m_constant; +} + NonnullOwnPtr SinCalculationNode::create(NonnullOwnPtr value) { return adopt_own(*new (nothrow) SinCalculationNode(move(value))); @@ -1106,6 +1223,15 @@ void SinCalculationNode::dump(StringBuilder& builder, int indent) const builder.appendff("{: >{}}SIN: {}\n", "", indent, to_string()); } +bool SinCalculationNode::equals(CalculationNode const& other) const +{ + if (this == &other) + return true; + if (type() != other.type()) + return false; + return m_value->equals(*static_cast(other).m_value); +} + NonnullOwnPtr CosCalculationNode::create(NonnullOwnPtr value) { return adopt_own(*new (nothrow) CosCalculationNode(move(value))); @@ -1165,6 +1291,15 @@ void CosCalculationNode::dump(StringBuilder& builder, int indent) const builder.appendff("{: >{}}COS: {}\n", "", indent, to_string()); } +bool CosCalculationNode::equals(CalculationNode const& other) const +{ + if (this == &other) + return true; + if (type() != other.type()) + return false; + return m_value->equals(*static_cast(other).m_value); +} + NonnullOwnPtr TanCalculationNode::create(NonnullOwnPtr value) { return adopt_own(*new (nothrow) TanCalculationNode(move(value))); @@ -1224,6 +1359,15 @@ void TanCalculationNode::dump(StringBuilder& builder, int indent) const builder.appendff("{: >{}}TAN: {}\n", "", indent, to_string()); } +bool TanCalculationNode::equals(CalculationNode const& other) const +{ + if (this == &other) + return true; + if (type() != other.type()) + return false; + return m_value->equals(*static_cast(other).m_value); +} + NonnullOwnPtr AsinCalculationNode::create(NonnullOwnPtr value) { return adopt_own(*new (nothrow) AsinCalculationNode(move(value))); @@ -1283,6 +1427,15 @@ void AsinCalculationNode::dump(StringBuilder& builder, int indent) const builder.appendff("{: >{}}ASIN: {}\n", "", indent, to_string()); } +bool AsinCalculationNode::equals(CalculationNode const& other) const +{ + if (this == &other) + return true; + if (type() != other.type()) + return false; + return m_value->equals(*static_cast(other).m_value); +} + NonnullOwnPtr AcosCalculationNode::create(NonnullOwnPtr value) { return adopt_own(*new (nothrow) AcosCalculationNode(move(value))); @@ -1342,6 +1495,15 @@ void AcosCalculationNode::dump(StringBuilder& builder, int indent) const builder.appendff("{: >{}}ACOS: {}\n", "", indent, to_string()); } +bool AcosCalculationNode::equals(CalculationNode const& other) const +{ + if (this == &other) + return true; + if (type() != other.type()) + return false; + return m_value->equals(*static_cast(other).m_value); +} + NonnullOwnPtr AtanCalculationNode::create(NonnullOwnPtr value) { return adopt_own(*new (nothrow) AtanCalculationNode(move(value))); @@ -1401,6 +1563,15 @@ void AtanCalculationNode::dump(StringBuilder& builder, int indent) const builder.appendff("{: >{}}ATAN: {}\n", "", indent, to_string()); } +bool AtanCalculationNode::equals(CalculationNode const& other) const +{ + if (this == &other) + return true; + if (type() != other.type()) + return false; + return m_value->equals(*static_cast(other).m_value); +} + NonnullOwnPtr Atan2CalculationNode::create(NonnullOwnPtr y, NonnullOwnPtr x) { return adopt_own(*new (nothrow) Atan2CalculationNode(move(y), move(x))); @@ -1469,6 +1640,16 @@ void Atan2CalculationNode::dump(StringBuilder& builder, int indent) const builder.appendff("{: >{}}ATAN2: {}\n", "", indent, to_string()); } +bool Atan2CalculationNode::equals(CalculationNode const& other) const +{ + if (this == &other) + return true; + if (type() != other.type()) + return false; + return m_x->equals(*static_cast(other).m_x) + && m_y->equals(*static_cast(other).m_y); +} + NonnullOwnPtr PowCalculationNode::create(NonnullOwnPtr x, NonnullOwnPtr y) { return adopt_own(*new (nothrow) PowCalculationNode(move(x), move(y))); @@ -1532,6 +1713,16 @@ void PowCalculationNode::dump(StringBuilder& builder, int indent) const builder.appendff("{: >{}}POW: {}\n", "", indent, to_string()); } +bool PowCalculationNode::equals(CalculationNode const& other) const +{ + if (this == &other) + return true; + if (type() != other.type()) + return false; + return m_x->equals(*static_cast(other).m_x) + && m_y->equals(*static_cast(other).m_y); +} + NonnullOwnPtr SqrtCalculationNode::create(NonnullOwnPtr value) { return adopt_own(*new (nothrow) SqrtCalculationNode(move(value))); @@ -1586,6 +1777,15 @@ void SqrtCalculationNode::dump(StringBuilder& builder, int indent) const builder.appendff("{: >{}}SQRT: {}\n", "", indent, to_string()); } +bool SqrtCalculationNode::equals(CalculationNode const& other) const +{ + if (this == &other) + return true; + if (type() != other.type()) + return false; + return m_value->equals(*static_cast(other).m_value); +} + NonnullOwnPtr HypotCalculationNode::create(Vector> values) { return adopt_own(*new (nothrow) HypotCalculationNode(move(values))); @@ -1666,6 +1866,19 @@ void HypotCalculationNode::dump(StringBuilder& builder, int indent) const value->dump(builder, indent + 2); } +bool HypotCalculationNode::equals(CalculationNode const& other) const +{ + if (this == &other) + return true; + if (type() != other.type()) + return false; + for (size_t i = 0; i < m_values.size(); ++i) { + if (!m_values[i]->equals(*static_cast(other).m_values[i])) + return false; + } + return true; +} + NonnullOwnPtr LogCalculationNode::create(NonnullOwnPtr x, NonnullOwnPtr y) { return adopt_own(*new (nothrow) LogCalculationNode(move(x), move(y))); @@ -1729,6 +1942,16 @@ void LogCalculationNode::dump(StringBuilder& builder, int indent) const builder.appendff("{: >{}}LOG: {}\n", "", indent, to_string()); } +bool LogCalculationNode::equals(CalculationNode const& other) const +{ + if (this == &other) + return true; + if (type() != other.type()) + return false; + return m_x->equals(*static_cast(other).m_x) + && m_y->equals(*static_cast(other).m_y); +} + NonnullOwnPtr ExpCalculationNode::create(NonnullOwnPtr value) { return adopt_own(*new (nothrow) ExpCalculationNode(move(value))); @@ -1783,6 +2006,15 @@ void ExpCalculationNode::dump(StringBuilder& builder, int indent) const builder.appendff("{: >{}}EXP: {}\n", "", indent, to_string()); } +bool ExpCalculationNode::equals(CalculationNode const& other) const +{ + if (this == &other) + return true; + if (type() != other.type()) + return false; + return m_value->equals(*static_cast(other).m_value); +} + NonnullOwnPtr RoundCalculationNode::create(RoundingStrategy strategy, NonnullOwnPtr x, NonnullOwnPtr y) { return adopt_own(*new (nothrow) RoundCalculationNode(strategy, move(x), move(y))); @@ -1885,6 +2117,17 @@ void RoundCalculationNode::dump(StringBuilder& builder, int indent) const builder.appendff("{: >{}}ROUND: {}\n", "", indent, to_string()); } +bool RoundCalculationNode::equals(CalculationNode const& other) const +{ + if (this == &other) + return true; + if (type() != other.type()) + return false; + return m_strategy == static_cast(other).m_strategy + && m_x->equals(*static_cast(other).m_x) + && m_y->equals(*static_cast(other).m_y); +} + NonnullOwnPtr ModCalculationNode::create(NonnullOwnPtr x, NonnullOwnPtr y) { return adopt_own(*new (nothrow) ModCalculationNode(move(x), move(y))); @@ -1961,6 +2204,16 @@ void ModCalculationNode::dump(StringBuilder& builder, int indent) const builder.appendff("{: >{}}MOD: {}\n", "", indent, to_string()); } +bool ModCalculationNode::equals(CalculationNode const& other) const +{ + if (this == &other) + return true; + if (type() != other.type()) + return false; + return m_x->equals(*static_cast(other).m_x) + && m_y->equals(*static_cast(other).m_y); +} + NonnullOwnPtr RemCalculationNode::create(NonnullOwnPtr x, NonnullOwnPtr y) { return adopt_own(*new (nothrow) RemCalculationNode(move(x), move(y))); @@ -2036,6 +2289,16 @@ void RemCalculationNode::dump(StringBuilder& builder, int indent) const builder.appendff("{: >{}}REM: {}\n", "", indent, to_string()); } +bool RemCalculationNode::equals(CalculationNode const& other) const +{ + if (this == &other) + return true; + if (type() != other.type()) + return false; + return m_x->equals(*static_cast(other).m_x) + && m_y->equals(*static_cast(other).m_y); +} + void CalculatedStyleValue::CalculationResult::add(CalculationResult const& other, Optional context, PercentageBasis const& percentage_basis) { add_or_subtract_internal(SumOperation::Add, other, context, percentage_basis); @@ -2331,8 +2594,8 @@ bool CalculatedStyleValue::equals(StyleValue const& other) const { if (type() != other.type()) return false; - // This is a case where comparing the strings actually makes sense. - return to_string() == other.to_string(); + + return m_calculation->equals(*static_cast(other).m_calculation); } Optional CalculatedStyleValue::resolve_angle() const diff --git a/Userland/Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.h b/Userland/Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.h index a9f1339d0d..f3a64a0826 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.h +++ b/Userland/Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.h @@ -65,6 +65,8 @@ public: Value const& value() const { return m_value; } + [[nodiscard]] bool operator==(CalculationResult const&) const = default; + private: void add_or_subtract_internal(SumOperation op, CalculationResult const& other, Optional, PercentageBasis const& percentage_basis); Value m_value; @@ -253,6 +255,7 @@ public: virtual void for_each_child_node(Function&)> const&) = 0; virtual void dump(StringBuilder&, int indent) const = 0; + virtual bool equals(CalculationNode const&) const = 0; protected: explicit CalculationNode(Type); @@ -274,6 +277,7 @@ public: virtual void for_each_child_node(Function&)> const&) override { } virtual void dump(StringBuilder&, int indent) const override; + virtual bool equals(CalculationNode const&) const override; private: explicit NumericCalculationNode(NumericValue); @@ -293,6 +297,7 @@ public: virtual void for_each_child_node(Function&)> const&) override; virtual void dump(StringBuilder&, int indent) const override; + virtual bool equals(CalculationNode const&) const override; private: explicit SumCalculationNode(Vector>); @@ -312,6 +317,7 @@ public: virtual void for_each_child_node(Function&)> const&) override; virtual void dump(StringBuilder&, int indent) const override; + virtual bool equals(CalculationNode const&) const override; private: explicit ProductCalculationNode(Vector>); @@ -331,6 +337,7 @@ public: virtual void for_each_child_node(Function&)> const&) override; virtual void dump(StringBuilder&, int indent) const override; + virtual bool equals(CalculationNode const&) const override; private: explicit NegateCalculationNode(NonnullOwnPtr); @@ -350,6 +357,7 @@ public: virtual void for_each_child_node(Function&)> const&) override; virtual void dump(StringBuilder&, int indent) const override; + virtual bool equals(CalculationNode const&) const override; private: explicit InvertCalculationNode(NonnullOwnPtr); @@ -369,6 +377,7 @@ public: virtual void for_each_child_node(Function&)> const&) override; virtual void dump(StringBuilder&, int indent) const override; + virtual bool equals(CalculationNode const&) const override; private: explicit MinCalculationNode(Vector>); @@ -388,6 +397,7 @@ public: virtual void for_each_child_node(Function&)> const&) override; virtual void dump(StringBuilder&, int indent) const override; + virtual bool equals(CalculationNode const&) const override; private: explicit MaxCalculationNode(Vector>); @@ -407,6 +417,7 @@ public: virtual void for_each_child_node(Function&)> const&) override; virtual void dump(StringBuilder&, int indent) const override; + virtual bool equals(CalculationNode const&) const override; private: explicit ClampCalculationNode(NonnullOwnPtr, NonnullOwnPtr, NonnullOwnPtr); @@ -428,6 +439,7 @@ public: virtual void for_each_child_node(Function&)> const&) override; virtual void dump(StringBuilder&, int indent) const override; + virtual bool equals(CalculationNode const&) const override; private: AbsCalculationNode(NonnullOwnPtr); @@ -447,6 +459,7 @@ public: virtual void for_each_child_node(Function&)> const&) override; virtual void dump(StringBuilder&, int indent) const override; + virtual bool equals(CalculationNode const&) const override; private: SignCalculationNode(NonnullOwnPtr); @@ -466,6 +479,7 @@ public: virtual void for_each_child_node(Function&)> const&) override { } virtual void dump(StringBuilder&, int indent) const override; + virtual bool equals(CalculationNode const&) const override; private: ConstantCalculationNode(ConstantType); @@ -485,6 +499,7 @@ public: virtual void for_each_child_node(Function&)> const&) override; virtual void dump(StringBuilder&, int indent) const override; + virtual bool equals(CalculationNode const&) const override; private: SinCalculationNode(NonnullOwnPtr); @@ -504,6 +519,7 @@ public: virtual void for_each_child_node(Function&)> const&) override; virtual void dump(StringBuilder&, int indent) const override; + virtual bool equals(CalculationNode const&) const override; private: CosCalculationNode(NonnullOwnPtr); @@ -523,6 +539,7 @@ public: virtual void for_each_child_node(Function&)> const&) override; virtual void dump(StringBuilder&, int indent) const override; + virtual bool equals(CalculationNode const&) const override; private: TanCalculationNode(NonnullOwnPtr); @@ -542,6 +559,7 @@ public: virtual void for_each_child_node(Function&)> const&) override; virtual void dump(StringBuilder&, int indent) const override; + virtual bool equals(CalculationNode const&) const override; private: AsinCalculationNode(NonnullOwnPtr); @@ -561,6 +579,7 @@ public: virtual void for_each_child_node(Function&)> const&) override; virtual void dump(StringBuilder&, int indent) const override; + virtual bool equals(CalculationNode const&) const override; private: AcosCalculationNode(NonnullOwnPtr); @@ -580,6 +599,7 @@ public: virtual void for_each_child_node(Function&)> const&) override; virtual void dump(StringBuilder&, int indent) const override; + virtual bool equals(CalculationNode const&) const override; private: AtanCalculationNode(NonnullOwnPtr); @@ -599,6 +619,7 @@ public: virtual void for_each_child_node(Function&)> const&) override; virtual void dump(StringBuilder&, int indent) const override; + virtual bool equals(CalculationNode const&) const override; private: Atan2CalculationNode(NonnullOwnPtr, NonnullOwnPtr); @@ -619,6 +640,7 @@ public: virtual void for_each_child_node(Function&)> const&) override; virtual void dump(StringBuilder&, int indent) const override; + virtual bool equals(CalculationNode const&) const override; private: explicit PowCalculationNode(NonnullOwnPtr, NonnullOwnPtr); @@ -639,6 +661,7 @@ public: virtual void for_each_child_node(Function&)> const&) override; virtual void dump(StringBuilder&, int indent) const override; + virtual bool equals(CalculationNode const&) const override; private: SqrtCalculationNode(NonnullOwnPtr); @@ -658,6 +681,7 @@ public: virtual void for_each_child_node(Function&)> const&) override; virtual void dump(StringBuilder&, int indent) const override; + virtual bool equals(CalculationNode const&) const override; private: explicit HypotCalculationNode(Vector>); @@ -677,6 +701,7 @@ public: virtual void for_each_child_node(Function&)> const&) override; virtual void dump(StringBuilder&, int indent) const override; + virtual bool equals(CalculationNode const&) const override; private: LogCalculationNode(NonnullOwnPtr, NonnullOwnPtr); @@ -697,6 +722,7 @@ public: virtual void for_each_child_node(Function&)> const&) override; virtual void dump(StringBuilder&, int indent) const override; + virtual bool equals(CalculationNode const&) const override; private: ExpCalculationNode(NonnullOwnPtr); @@ -716,6 +742,7 @@ public: virtual void for_each_child_node(Function&)> const&) override; virtual void dump(StringBuilder&, int indent) const override; + virtual bool equals(CalculationNode const&) const override; private: RoundCalculationNode(RoundingStrategy, NonnullOwnPtr, NonnullOwnPtr); @@ -737,6 +764,7 @@ public: virtual void for_each_child_node(Function&)> const&) override; virtual void dump(StringBuilder&, int indent) const override; + virtual bool equals(CalculationNode const&) const override; private: ModCalculationNode(NonnullOwnPtr, NonnullOwnPtr); @@ -757,6 +785,7 @@ public: virtual void for_each_child_node(Function&)> const&) override; virtual void dump(StringBuilder&, int indent) const override; + virtual bool equals(CalculationNode const&) const override; private: RemCalculationNode(NonnullOwnPtr, NonnullOwnPtr);