Allow setting class properties by value for assignable types.

Most of our Views class properties are of type ClassProperty<T*> where
T is a value type, but we're forced to use pointers because T can be of
any type and must therefore be allocated on the heap.

This leads to a lot of code like:
my_view->SetProperty(kMarginsKey, new Insets(kMyViewDefaultInsets));
...
*my_view->GetProperty(kMarginsKey) = new_insets;

(The latter pattern is to prevent a second heap allocation - but only
works if the initial allocation happens; otherwise it crashes.)

This CL shortcuts this behavior so that it behaves in the way we
actually want to use the system 90% of the time:

// Allocates a copy of kMyViewDefaultInsets for the property.
my_view->SetProperty(kMarginsKey, kMyViewDefaultInsets);

// Updates the value of the existing property.
my_view->SetProperty(kMarginsKey, new_insets);

// De-allocates the existing property value.
my_view->ClearProperty(kMarginsKey);

In order to use this new functionality:
 - The property must be an owned property of pointer type.
 - The property type behind the pointer must be copy- or
   move-assignable.

Change-Id: Idec1d5b9c104814f234270214b615774fa5a7084
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1657288
Reviewed-by: Scott Violet <sky@chromium.org>
Commit-Queue: Dana Fried <dfried@chromium.org>
Cr-Commit-Position: refs/heads/master@{#670329}
diff --git a/ui/base/class_property.h b/ui/base/class_property.h
index 40bcdeb..d937c57 100644
--- a/ui/base/class_property.h
+++ b/ui/base/class_property.h
@@ -70,11 +70,26 @@
   ~PropertyHandler();
 
   // Sets the |value| of the given class |property|. Setting to the default
-  // value (e.g., NULL) removes the property. The caller is responsible for the
-  // lifetime of any object set as a property on the class.
+  // value (e.g., NULL) removes the property. The lifetime of objects set as
+  // values of unowned properties is managed by the caller (owned properties are
+  // freed when they are overwritten or cleared).
   template<typename T>
   void SetProperty(const ClassProperty<T>* property, T value);
 
+  // Sets the |value| of the given class |property|, which must be an owned
+  // property of pointer type. The property will be assigned a copy of |value|;
+  // if no property object exists one will be allocated. T must support copy
+  // construction and assignment.
+  template <typename T>
+  void SetProperty(const ClassProperty<T*>* property, const T& value);
+
+  // Sets the |value| of the given class |property|, which must be an owned
+  // property and of pointer type. The property will be move-assigned or move-
+  // constructed from |value|; if no property object exists one will be
+  // allocated. T must support at least copy (and ideally move) semantics.
+  template <typename T>
+  void SetProperty(const ClassProperty<T*>* property, T&& value);
+
   // Returns the value of the given class |property|.  Returns the
   // property-specific default value if the property was not previously set.
   template<typename T>
@@ -182,6 +197,40 @@
 
 }  // namespace subtle
 
+// Template implementation is necessary in the .h file unless we want to break
+// [DECLARE|DEFINE]_EXPORTED_UI_CLASS_PROPERTY_TYPE() below into different
+// macros for owned and unowned properties; implementing them as pure templates
+// makes them nearly impossible to implement or use incorrectly at the cost of a
+// small amount of code duplication across libraries.
+
+template <typename T>
+void PropertyHandler::SetProperty(const ClassProperty<T*>* property,
+                                  const T& value) {
+  // Prevent additional heap allocation if possible.
+  T* const old = GetProperty(property);
+  if (old) {
+    T temp(*old);
+    *old = value;
+    AfterPropertyChange(property, reinterpret_cast<int64_t>(&temp));
+  } else {
+    SetProperty(property, new T(value));
+  }
+}
+
+template <typename T>
+void PropertyHandler::SetProperty(const ClassProperty<T*>* property,
+                                  T&& value) {
+  // Prevent additional heap allocation if possible.
+  T* const old = GetProperty(property);
+  if (old) {
+    T temp(std::move(*old));
+    *old = std::forward<T>(value);
+    AfterPropertyChange(property, reinterpret_cast<int64_t>(&temp));
+  } else {
+    SetProperty(property, new T(std::forward<T>(value)));
+  }
+}
+
 }  // namespace ui
 
 // Macros to declare the property getter/setter template functions.
diff --git a/ui/base/class_property_unittest.cc b/ui/base/class_property_unittest.cc
index 870a40f..05c7137 100644
--- a/ui/base/class_property_unittest.cc
+++ b/ui/base/class_property_unittest.cc
@@ -35,17 +35,61 @@
 
 void* TestProperty::last_deleted_ = nullptr;
 
-DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(TestProperty, kOwnedKey, NULL)
+class AssignableTestProperty {
+ public:
+  AssignableTestProperty() {}
+  AssignableTestProperty(int value) : value_(value) {}
+  AssignableTestProperty(const AssignableTestProperty& other)
+      : value_(other.value_) {}
+  AssignableTestProperty(AssignableTestProperty&& other)
+      : value_(std::move(other.value_)), was_move_assigned_(true) {}
+  AssignableTestProperty& operator=(const AssignableTestProperty& other) {
+    value_ = other.value_;
+    was_move_assigned_ = false;
+    return *this;
+  }
+  AssignableTestProperty& operator=(AssignableTestProperty&& other) {
+    value_ = std::move(other.value_);
+    was_move_assigned_ = true;
+    return *this;
+  }
+
+  int value() const { return value_; }
+  bool was_move_assigned() const { return was_move_assigned_; }
+
+ private:
+  int value_ = 0;
+  bool was_move_assigned_ = false;
+};
+
+DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(TestProperty, kOwnedKey, nullptr)
+DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(AssignableTestProperty,
+                                   kAssignableKey,
+                                   nullptr)
 
 }  // namespace
 
 DEFINE_UI_CLASS_PROPERTY_TYPE(TestProperty*)
+DEFINE_UI_CLASS_PROPERTY_TYPE(AssignableTestProperty*)
 
 namespace ui {
 namespace test {
 
 namespace {
 
+class TestPropertyHandler : public PropertyHandler {
+ public:
+  int num_events() const { return num_events_; }
+
+ protected:
+  void AfterPropertyChange(const void* key, int64_t old_value) override {
+    ++num_events_;
+  }
+
+ private:
+  int num_events_ = 0;
+};
+
 const int kDefaultIntValue = -2;
 const char* kDefaultStringValue = "squeamish";
 const char* kTestStringValue = "ossifrage";
@@ -113,5 +157,110 @@
   EXPECT_EQ(p3, TestProperty::last_deleted());
 }
 
+TEST(PropertyTest, AssignableProperty) {
+  PropertyHandler h;
+
+  // Verify that assigning a property by value allocates a value.
+  EXPECT_EQ(nullptr, h.GetProperty(kAssignableKey));
+  const AssignableTestProperty kTestProperty{1};
+  h.SetProperty(kAssignableKey, kTestProperty);
+  AssignableTestProperty* p = h.GetProperty(kAssignableKey);
+  EXPECT_NE(nullptr, p);
+  EXPECT_EQ(1, p->value());
+
+  // Verify that assigning by value updates the existing value without
+  // additional allocation.
+  h.SetProperty(kAssignableKey, AssignableTestProperty{2});
+  EXPECT_EQ(p, h.GetProperty(kAssignableKey));
+  EXPECT_EQ(2, p->value());
+
+  // Same as the above case, but with a const reference instead of a move.
+  const AssignableTestProperty kTestProperty2{3};
+  h.SetProperty(kAssignableKey, kTestProperty2);
+  EXPECT_EQ(p, h.GetProperty(kAssignableKey));
+  EXPECT_EQ(3, p->value());
+
+  // Verify that clearing the property deallocates the value.
+  h.ClearProperty(kAssignableKey);
+  EXPECT_EQ(nullptr, h.GetProperty(kAssignableKey));
+
+  // Verify that setting by value after clearing allocates a new value.
+  h.SetProperty(kAssignableKey, AssignableTestProperty{4});
+  EXPECT_EQ(4, h.GetProperty(kAssignableKey)->value());
+}
+
+TEST(PropertyTest, SetProperty_ForwardsParametersCorrectly) {
+  PropertyHandler h;
+
+  // New property from a const ref.
+  const AssignableTestProperty kTestProperty{1};
+  h.SetProperty(kAssignableKey, kTestProperty);
+  EXPECT_FALSE(h.GetProperty(kAssignableKey)->was_move_assigned());
+
+  // Set property from inline rvalue ref.
+  h.SetProperty(kAssignableKey, AssignableTestProperty{2});
+  EXPECT_TRUE(h.GetProperty(kAssignableKey)->was_move_assigned());
+
+  // Set property from lvalue.
+  AssignableTestProperty test_property{3};
+  h.SetProperty(kAssignableKey, test_property);
+  EXPECT_FALSE(h.GetProperty(kAssignableKey)->was_move_assigned());
+
+  // Set property from lvalue ref.
+  AssignableTestProperty& ref = test_property;
+  h.SetProperty(kAssignableKey, ref);
+  EXPECT_FALSE(h.GetProperty(kAssignableKey)->was_move_assigned());
+
+  // Set property from moved rvalue ref.
+  h.SetProperty(kAssignableKey, std::move(test_property));
+  EXPECT_TRUE(h.GetProperty(kAssignableKey)->was_move_assigned());
+
+  // Set property from const ref.
+  const AssignableTestProperty& const_ref = kTestProperty;
+  h.SetProperty(kAssignableKey, const_ref);
+  EXPECT_FALSE(h.GetProperty(kAssignableKey)->was_move_assigned());
+
+  // New property from rvalue ref.
+  h.ClearProperty(kAssignableKey);
+  h.SetProperty(kAssignableKey, AssignableTestProperty{4});
+  EXPECT_TRUE(h.GetProperty(kAssignableKey)->was_move_assigned());
+
+  // New property from lvalue.
+  h.ClearProperty(kAssignableKey);
+  test_property = AssignableTestProperty{5};
+  h.SetProperty(kAssignableKey, test_property);
+  EXPECT_FALSE(h.GetProperty(kAssignableKey)->was_move_assigned());
+}
+
+TEST(PropertyTest, PropertyChangedEvent) {
+  TestPropertyHandler h;
+
+  // Verify that initially setting the value creates an event.
+  const AssignableTestProperty kTestProperty{1};
+  h.SetProperty(kAssignableKey, kTestProperty);
+  EXPECT_EQ(1, h.num_events());
+
+  // Verify that assigning by value sends an event.
+  h.SetProperty(kAssignableKey, AssignableTestProperty{2});
+  EXPECT_EQ(2, h.num_events());
+
+  // Same as the above case, but with a const reference instead of a move.
+  const AssignableTestProperty kTestProperty2{3};
+  h.SetProperty(kAssignableKey, kTestProperty2);
+  EXPECT_EQ(3, h.num_events());
+
+  // Verify that clearing the property creates an event.
+  h.ClearProperty(kAssignableKey);
+  EXPECT_EQ(4, h.num_events());
+
+  // Verify that setting a heap-allocated value also ticks the event counter.
+  h.SetProperty(kAssignableKey, new AssignableTestProperty{4});
+  EXPECT_EQ(5, h.num_events());
+
+  // Verify that overwriting a heap-allocated value ticks the event counter.
+  h.SetProperty(kAssignableKey, new AssignableTestProperty{5});
+  EXPECT_EQ(6, h.num_events());
+}
+
 } // namespace test
 } // namespace ui