diff --git a/base/BUILD.gn b/base/BUILD.gn index 9a62e82..b1f6bcc 100644 --- a/base/BUILD.gn +++ b/base/BUILD.gn
@@ -1025,6 +1025,8 @@ "unguessable_token.h", "value_conversions.cc", "value_conversions.h", + "value_iterators.cc", + "value_iterators.h", "values.cc", "values.h", "version.cc", @@ -2249,6 +2251,7 @@ "tracked_objects_unittest.cc", "tuple_unittest.cc", "unguessable_token_unittest.cc", + "value_iterators_unittest.cc", "values_unittest.cc", "version_unittest.cc", "vlog_unittest.cc",
diff --git a/base/allocator/partition_allocator/partition_alloc.cc b/base/allocator/partition_allocator/partition_alloc.cc index 7541eed..1c23f10 100644 --- a/base/allocator/partition_allocator/partition_alloc.cc +++ b/base/allocator/partition_allocator/partition_alloc.cc
@@ -1100,7 +1100,11 @@ DCHECK(page->num_unprovisioned_slots < bucket_num_slots); size_t num_slots = bucket_num_slots - page->num_unprovisioned_slots; char slot_usage[max_slot_count]; +#if !defined(OS_WIN) + // The last freelist entry should not be discarded when using OS_WIN. + // DiscardVirtualMemory makes the contents of discarded memory undefined. size_t last_slot = static_cast<size_t>(-1); +#endif memset(slot_usage, 1, num_slots); char* ptr = reinterpret_cast<char*>(PartitionPageToPointer(page)); PartitionFreelistEntry* entry = page->freelist_head; @@ -1111,6 +1115,7 @@ DCHECK(slotIndex < num_slots); slot_usage[slotIndex] = 0; entry = PartitionFreelistMask(entry->next); +#if !defined(OS_WIN) // If we have a slot where the masked freelist entry is 0, we can // actually discard that freelist entry because touching a discarded // page is guaranteed to return original content or 0. @@ -1118,6 +1123,7 @@ // because the masking function is negation.) if (!PartitionFreelistMask(entry)) last_slot = slotIndex; +#endif } // If the slot(s) at the end of the slot span are not in used, we can @@ -1162,7 +1168,9 @@ *entry_ptr = PartitionFreelistMask(entry); entry_ptr = reinterpret_cast<PartitionFreelistEntry**>(entry); num_new_entries++; +#if !defined(OS_WIN) last_slot = slotIndex; +#endif } // Terminate the freelist chain. *entry_ptr = nullptr; @@ -1185,8 +1193,12 @@ // null, we can discard that pointer value too. char* begin_ptr = ptr + (i * slot_size); char* end_ptr = begin_ptr + slot_size; +#if !defined(OS_WIN) if (i != last_slot) begin_ptr += sizeof(PartitionFreelistEntry); +#else + begin_ptr += sizeof(PartitionFreelistEntry); +#endif begin_ptr = reinterpret_cast<char*>( RoundUpToSystemPage(reinterpret_cast<size_t>(begin_ptr))); end_ptr = reinterpret_cast<char*>(
diff --git a/base/allocator/partition_allocator/partition_alloc_unittest.cc b/base/allocator/partition_allocator/partition_alloc_unittest.cc index 77c3823..740e984 100644 --- a/base/allocator/partition_allocator/partition_alloc_unittest.cc +++ b/base/allocator/partition_allocator/partition_alloc_unittest.cc
@@ -1839,14 +1839,22 @@ EXPECT_TRUE(stats); EXPECT_TRUE(stats->is_valid); EXPECT_EQ(0u, stats->decommittable_bytes); +#if defined(OS_WIN) + EXPECT_EQ(0u, stats->discardable_bytes); +#else EXPECT_EQ(kSystemPageSize, stats->discardable_bytes); +#endif EXPECT_EQ(kSystemPageSize, stats->active_bytes); EXPECT_EQ(2 * kSystemPageSize, stats->resident_bytes); } CheckPageInCore(ptr1 - kPointerOffset, true); PartitionPurgeMemoryGeneric(generic_allocator.root(), PartitionPurgeDiscardUnusedSystemPages); +#if defined(OS_WIN) + CheckPageInCore(ptr1 - kPointerOffset, true); +#else CheckPageInCore(ptr1 - kPointerOffset, false); +#endif PartitionFreeGeneric(generic_allocator.root(), ptr2); } @@ -1969,7 +1977,11 @@ EXPECT_TRUE(stats); EXPECT_TRUE(stats->is_valid); EXPECT_EQ(0u, stats->decommittable_bytes); +#if defined(OS_WIN) + EXPECT_EQ(kSystemPageSize, stats->discardable_bytes); +#else EXPECT_EQ(2 * kSystemPageSize, stats->discardable_bytes); +#endif EXPECT_EQ(kSystemPageSize, stats->active_bytes); EXPECT_EQ(4 * kSystemPageSize, stats->resident_bytes); } @@ -1981,7 +1993,11 @@ PartitionPurgeDiscardUnusedSystemPages); EXPECT_EQ(1u, page->num_unprovisioned_slots); CheckPageInCore(ptr1 - kPointerOffset, true); +#if defined(OS_WIN) + CheckPageInCore(ptr1 - kPointerOffset + kSystemPageSize, true); +#else CheckPageInCore(ptr1 - kPointerOffset + kSystemPageSize, false); +#endif CheckPageInCore(ptr1 - kPointerOffset + (kSystemPageSize * 2), true); CheckPageInCore(ptr1 - kPointerOffset + (kSystemPageSize * 3), false);
diff --git a/base/value_iterators.cc b/base/value_iterators.cc new file mode 100644 index 0000000..ba9c730 --- /dev/null +++ b/base/value_iterators.cc
@@ -0,0 +1,228 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/value_iterators.h" + +namespace base { + +namespace detail { + +// ---------------------------------------------------------------------------- +// dict_iterator. + +dict_iterator::pointer::pointer(const reference& ref) : ref_(ref) {} + +dict_iterator::pointer::pointer(const pointer& ptr) = default; + +dict_iterator::dict_iterator(DictStorage::iterator dict_iter) + : dict_iter_(dict_iter) {} + +dict_iterator::dict_iterator(const dict_iterator& dict_iter) = default; + +dict_iterator& dict_iterator::operator=(const dict_iterator& dict_iter) = + default; + +dict_iterator::~dict_iterator() = default; + +dict_iterator::reference dict_iterator::operator*() { + return {dict_iter_->first, *dict_iter_->second}; +} + +dict_iterator::pointer dict_iterator::operator->() { + return pointer(operator*()); +} + +dict_iterator& dict_iterator::operator++() { + ++dict_iter_; + return *this; +} + +dict_iterator dict_iterator::operator++(int) { + dict_iterator tmp(*this); + ++dict_iter_; + return tmp; +} + +dict_iterator& dict_iterator::operator--() { + --dict_iter_; + return *this; +} + +dict_iterator dict_iterator::operator--(int) { + dict_iterator tmp(*this); + --dict_iter_; + return tmp; +} + +bool operator==(const dict_iterator& lhs, const dict_iterator& rhs) { + return lhs.dict_iter_ == rhs.dict_iter_; +} + +bool operator!=(const dict_iterator& lhs, const dict_iterator& rhs) { + return !(lhs == rhs); +} + +// ---------------------------------------------------------------------------- +// const_dict_iterator. + +const_dict_iterator::pointer::pointer(const reference& ref) : ref_(ref) {} + +const_dict_iterator::pointer::pointer(const pointer& ptr) = default; + +const_dict_iterator::const_dict_iterator(DictStorage::const_iterator dict_iter) + : dict_iter_(dict_iter) {} + +const_dict_iterator::const_dict_iterator(const const_dict_iterator& dict_iter) = + default; + +const_dict_iterator& const_dict_iterator::operator=( + const const_dict_iterator& dict_iter) = default; + +const_dict_iterator::~const_dict_iterator() = default; + +const_dict_iterator::reference const_dict_iterator::operator*() const { + return {dict_iter_->first, *dict_iter_->second}; +} + +const_dict_iterator::pointer const_dict_iterator::operator->() const { + return pointer(operator*()); +} + +const_dict_iterator& const_dict_iterator::operator++() { + ++dict_iter_; + return *this; +} + +const_dict_iterator const_dict_iterator::operator++(int) { + const_dict_iterator tmp(*this); + ++dict_iter_; + return tmp; +} + +const_dict_iterator& const_dict_iterator::operator--() { + --dict_iter_; + return *this; +} + +const_dict_iterator const_dict_iterator::operator--(int) { + const_dict_iterator tmp(*this); + --dict_iter_; + return tmp; +} + +bool operator==(const const_dict_iterator& lhs, + const const_dict_iterator& rhs) { + return lhs.dict_iter_ == rhs.dict_iter_; +} + +bool operator!=(const const_dict_iterator& lhs, + const const_dict_iterator& rhs) { + return !(lhs == rhs); +} + +// ---------------------------------------------------------------------------- +// dict_iterator_proxy. + +dict_iterator_proxy::dict_iterator_proxy(DictStorage* storage) + : storage_(storage) {} + +dict_iterator_proxy::iterator dict_iterator_proxy::begin() { + return iterator(storage_->begin()); +} + +dict_iterator_proxy::const_iterator dict_iterator_proxy::begin() const { + return const_iterator(storage_->begin()); +} + +dict_iterator_proxy::iterator dict_iterator_proxy::end() { + return iterator(storage_->end()); +} + +dict_iterator_proxy::const_iterator dict_iterator_proxy::end() const { + return const_iterator(storage_->end()); +} + +dict_iterator_proxy::reverse_iterator dict_iterator_proxy::rbegin() { + return reverse_iterator(end()); +} + +dict_iterator_proxy::const_reverse_iterator dict_iterator_proxy::rbegin() + const { + return const_reverse_iterator(end()); +} + +dict_iterator_proxy::reverse_iterator dict_iterator_proxy::rend() { + return reverse_iterator(begin()); +} + +dict_iterator_proxy::const_reverse_iterator dict_iterator_proxy::rend() const { + return const_reverse_iterator(begin()); +} + +dict_iterator_proxy::const_iterator dict_iterator_proxy::cbegin() const { + return const_iterator(begin()); +} + +dict_iterator_proxy::const_iterator dict_iterator_proxy::cend() const { + return const_iterator(end()); +} + +dict_iterator_proxy::const_reverse_iterator dict_iterator_proxy::crbegin() + const { + return const_reverse_iterator(rbegin()); +} + +dict_iterator_proxy::const_reverse_iterator dict_iterator_proxy::crend() const { + return const_reverse_iterator(rend()); +} + +// ---------------------------------------------------------------------------- +// const_dict_iterator_proxy. + +const_dict_iterator_proxy::const_dict_iterator_proxy(const DictStorage* storage) + : storage_(storage) {} + +const_dict_iterator_proxy::const_iterator const_dict_iterator_proxy::begin() + const { + return const_iterator(storage_->begin()); +} + +const_dict_iterator_proxy::const_iterator const_dict_iterator_proxy::end() + const { + return const_iterator(storage_->end()); +} + +const_dict_iterator_proxy::const_reverse_iterator +const_dict_iterator_proxy::rbegin() const { + return const_reverse_iterator(end()); +} + +const_dict_iterator_proxy::const_reverse_iterator +const_dict_iterator_proxy::rend() const { + return const_reverse_iterator(begin()); +} + +const_dict_iterator_proxy::const_iterator const_dict_iterator_proxy::cbegin() + const { + return const_iterator(begin()); +} + +const_dict_iterator_proxy::const_iterator const_dict_iterator_proxy::cend() + const { + return const_iterator(end()); +} + +const_dict_iterator_proxy::const_reverse_iterator +const_dict_iterator_proxy::crbegin() const { + return const_reverse_iterator(rbegin()); +} + +const_dict_iterator_proxy::const_reverse_iterator +const_dict_iterator_proxy::crend() const { + return const_reverse_iterator(rend()); +} + +} // namespace detail + +} // namespace base
diff --git a/base/value_iterators.h b/base/value_iterators.h new file mode 100644 index 0000000..2e05127b --- /dev/null +++ b/base/value_iterators.h
@@ -0,0 +1,194 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_VALUE_ITERATORS_H_ +#define BASE_VALUE_ITERATORS_H_ + +#include <memory> +#include <string> +#include <utility> + +#include "base/base_export.h" +#include "base/containers/flat_map.h" +#include "base/macros.h" + +namespace base { + +class Value; + +namespace detail { + +using DictStorage = base::flat_map<std::string, std::unique_ptr<Value>>; + +// This iterator closely resembles DictStorage::iterator, with one +// important exception. It abstracts the underlying unique_ptr away, meaning its +// value_type is std::pair<const std::string, Value>. It's reference type is a +// std::pair<const std::string&, Value&>, so that callers have read-write +// access without incurring a copy. +class BASE_EXPORT dict_iterator { + public: + using difference_type = DictStorage::iterator::difference_type; + using value_type = std::pair<const std::string, Value>; + using reference = std::pair<const std::string&, Value&>; + using iterator_category = std::bidirectional_iterator_tag; + + class pointer { + public: + explicit pointer(const reference& ref); + pointer(const pointer& ptr); + pointer& operator=(const pointer& ptr) = delete; + + reference* operator->() { return &ref_; } + + private: + reference ref_; + }; + + explicit dict_iterator(DictStorage::iterator dict_iter); + dict_iterator(const dict_iterator& dict_iter); + dict_iterator& operator=(const dict_iterator& dict_iter); + ~dict_iterator(); + + reference operator*(); + pointer operator->(); + + dict_iterator& operator++(); + dict_iterator operator++(int); + dict_iterator& operator--(); + dict_iterator operator--(int); + + BASE_EXPORT friend bool operator==(const dict_iterator& lhs, + const dict_iterator& rhs); + BASE_EXPORT friend bool operator!=(const dict_iterator& lhs, + const dict_iterator& rhs); + + private: + DictStorage::iterator dict_iter_; +}; + +// This iterator closely resembles DictStorage::const_iterator, with one +// important exception. It abstracts the underlying unique_ptr away, meaning its +// value_type is std::pair<const std::string, Value>. It's reference type is a +// std::pair<const std::string&, const Value&>, so that callers have read-only +// access without incurring a copy. +class BASE_EXPORT const_dict_iterator { + public: + using difference_type = DictStorage::const_iterator::difference_type; + using value_type = std::pair<const std::string, Value>; + using reference = std::pair<const std::string&, const Value&>; + using iterator_category = std::bidirectional_iterator_tag; + + class pointer { + public: + explicit pointer(const reference& ref); + pointer(const pointer& ptr); + pointer& operator=(const pointer& ptr) = delete; + + const reference* operator->() const { return &ref_; } + + private: + const reference ref_; + }; + + explicit const_dict_iterator(DictStorage::const_iterator dict_iter); + const_dict_iterator(const const_dict_iterator& dict_iter); + const_dict_iterator& operator=(const const_dict_iterator& dict_iter); + ~const_dict_iterator(); + + reference operator*() const; + pointer operator->() const; + + const_dict_iterator& operator++(); + const_dict_iterator operator++(int); + const_dict_iterator& operator--(); + const_dict_iterator operator--(int); + + BASE_EXPORT friend bool operator==(const const_dict_iterator& lhs, + const const_dict_iterator& rhs); + BASE_EXPORT friend bool operator!=(const const_dict_iterator& lhs, + const const_dict_iterator& rhs); + + private: + DictStorage::const_iterator dict_iter_; +}; + +// This class wraps the various |begin| and |end| methods of the underlying +// DictStorage in dict_iterators and const_dict_iterators. This allows callers +// to use this class for easy iteration over the underlying values, granting +// them either read-only or read-write access, depending on the +// const-qualification. +class BASE_EXPORT dict_iterator_proxy { + public: + using key_type = DictStorage::key_type; + using mapped_type = DictStorage::mapped_type::element_type; + using value_type = std::pair<key_type, mapped_type>; + using key_compare = DictStorage::key_compare; + using size_type = DictStorage::size_type; + using difference_type = DictStorage::difference_type; + + using iterator = dict_iterator; + using const_iterator = const_dict_iterator; + using reverse_iterator = std::reverse_iterator<iterator>; + using const_reverse_iterator = std::reverse_iterator<const_iterator>; + + explicit dict_iterator_proxy(DictStorage* storage); + + iterator begin(); + const_iterator begin() const; + iterator end(); + const_iterator end() const; + + reverse_iterator rbegin(); + const_reverse_iterator rbegin() const; + reverse_iterator rend(); + const_reverse_iterator rend() const; + + const_dict_iterator cbegin() const; + const_dict_iterator cend() const; + const_reverse_iterator crbegin() const; + const_reverse_iterator crend() const; + + private: + DictStorage* storage_; +}; + +// This class wraps the various const |begin| and |end| methods of the +// underlying DictStorage in const_dict_iterators. This allows callers to use +// this class for easy iteration over the underlying values, granting them +// either read-only access. +class BASE_EXPORT const_dict_iterator_proxy { + public: + using key_type = const DictStorage::key_type; + using mapped_type = const DictStorage::mapped_type::element_type; + using value_type = std::pair<key_type, mapped_type>; + using key_compare = DictStorage::key_compare; + using size_type = DictStorage::size_type; + using difference_type = DictStorage::difference_type; + + using iterator = const_dict_iterator; + using const_iterator = const_dict_iterator; + using reverse_iterator = std::reverse_iterator<iterator>; + using const_reverse_iterator = std::reverse_iterator<const_iterator>; + + explicit const_dict_iterator_proxy(const DictStorage* storage); + + const_iterator begin() const; + const_iterator end() const; + + const_reverse_iterator rbegin() const; + const_reverse_iterator rend() const; + + const_iterator cbegin() const; + const_iterator cend() const; + const_reverse_iterator crbegin() const; + const_reverse_iterator crend() const; + + private: + const DictStorage* storage_; +}; +} // namespace detail + +} // namespace base + +#endif // BASE_VALUE_ITERATORS_H_
diff --git a/base/value_iterators_unittest.cc b/base/value_iterators_unittest.cc new file mode 100644 index 0000000..63b4ec45 --- /dev/null +++ b/base/value_iterators_unittest.cc
@@ -0,0 +1,345 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/value_iterators.h" + +#include <type_traits> + +#include "base/memory/ptr_util.h" +#include "base/values.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace detail { + +namespace { + +// Implementation of std::equal variant that is missing in C++11. +template <class BinaryPredicate, class InputIterator1, class InputIterator2> +bool are_equal(InputIterator1 first1, + InputIterator1 last1, + InputIterator2 first2, + InputIterator2 last2, + BinaryPredicate pred) { + for (; first1 != last1 && first2 != last2; ++first1, ++first2) { + if (!pred(*first1, *first2)) + return false; + } + return first1 == last1 && first2 == last2; +} + +} // namespace + +TEST(ValueIteratorsTest, SameDictStorage) { + static_assert(std::is_same<Value::DictStorage, DictStorage>::value, + "DictStorage differs between Value and Value Iterators."); +} + +TEST(ValueIteratorsTest, IsAssignable) { + static_assert( + !std::is_assignable<dict_iterator::reference::first_type, std::string>(), + "Can assign strings to dict_iterator"); + + static_assert( + std::is_assignable<dict_iterator::reference::second_type, Value>(), + "Can't assign Values to dict_iterator"); + + static_assert(!std::is_assignable<const_dict_iterator::reference::first_type, + std::string>(), + "Can assign strings to const_dict_iterator"); + + static_assert( + !std::is_assignable<const_dict_iterator::reference::second_type, Value>(), + "Can assign Values to const_dict_iterator"); +} + +TEST(ValueIteratorsTest, DictIteratorOperatorStar) { + DictStorage storage; + storage.emplace("0", MakeUnique<Value>(0)); + + using iterator = dict_iterator; + iterator iter(storage.begin()); + EXPECT_EQ("0", (*iter).first); + EXPECT_EQ(Value(0), (*iter).second); + + (*iter).second = Value(1); + EXPECT_EQ(Value(1), *storage["0"]); +} + +TEST(ValueIteratorsTest, DictIteratorOperatorArrow) { + DictStorage storage; + storage.emplace("0", MakeUnique<Value>(0)); + + using iterator = dict_iterator; + iterator iter(storage.begin()); + EXPECT_EQ("0", iter->first); + EXPECT_EQ(Value(0), iter->second); + + iter->second = Value(1); + EXPECT_EQ(Value(1), *storage["0"]); +} + +TEST(ValueIteratorsTest, DictIteratorPreIncrement) { + DictStorage storage; + storage.emplace("0", MakeUnique<Value>(0)); + storage.emplace("1", MakeUnique<Value>(1)); + + using iterator = dict_iterator; + iterator iter(storage.begin()); + EXPECT_EQ("0", iter->first); + EXPECT_EQ(Value(0), iter->second); + + iterator& iter_ref = ++iter; + EXPECT_EQ(&iter, &iter_ref); + + EXPECT_EQ("1", iter_ref->first); + EXPECT_EQ(Value(1), iter_ref->second); +} + +TEST(ValueIteratorsTest, DictIteratorPostIncrement) { + DictStorage storage; + storage.emplace("0", MakeUnique<Value>(0)); + storage.emplace("1", MakeUnique<Value>(1)); + + using iterator = dict_iterator; + iterator iter(storage.begin()); + iterator iter_old = iter++; + + EXPECT_EQ("0", iter_old->first); + EXPECT_EQ(Value(0), iter_old->second); + + EXPECT_EQ("1", iter->first); + EXPECT_EQ(Value(1), iter->second); +} + +TEST(ValueIteratorsTest, DictIteratorPreDecrement) { + DictStorage storage; + storage.emplace("0", MakeUnique<Value>(0)); + storage.emplace("1", MakeUnique<Value>(1)); + + using iterator = dict_iterator; + iterator iter(++storage.begin()); + EXPECT_EQ("1", iter->first); + EXPECT_EQ(Value(1), iter->second); + + iterator& iter_ref = --iter; + EXPECT_EQ(&iter, &iter_ref); + + EXPECT_EQ("0", iter_ref->first); + EXPECT_EQ(Value(0), iter_ref->second); +} + +TEST(ValueIteratorsTest, DictIteratorPostDecrement) { + DictStorage storage; + storage.emplace("0", MakeUnique<Value>(0)); + storage.emplace("1", MakeUnique<Value>(1)); + + using iterator = dict_iterator; + iterator iter(++storage.begin()); + iterator iter_old = iter--; + + EXPECT_EQ("1", iter_old->first); + EXPECT_EQ(Value(1), iter_old->second); + + EXPECT_EQ("0", iter->first); + EXPECT_EQ(Value(0), iter->second); +} + +TEST(ValueIteratorsTest, DictIteratorOperatorEQ) { + DictStorage storage; + using iterator = dict_iterator; + EXPECT_EQ(iterator(storage.begin()), iterator(storage.begin())); + EXPECT_EQ(iterator(storage.end()), iterator(storage.end())); +} + +TEST(ValueIteratorsTest, DictIteratorOperatorNE) { + DictStorage storage_0; + storage_0.emplace("0", MakeUnique<Value>(0)); + + DictStorage storage_1; + storage_1.emplace("0", MakeUnique<Value>(0)); + + using iterator = dict_iterator; + EXPECT_NE(iterator(storage_0.begin()), iterator(storage_0.end())); + EXPECT_NE(iterator(storage_0.begin()), iterator(storage_1.begin())); + EXPECT_NE(iterator(storage_0.end()), iterator(storage_1.end())); + EXPECT_NE(iterator(storage_1.begin()), iterator(storage_1.end())); +} + +TEST(ValueIteratorsTest, ConstDictIteratorOperatorStar) { + DictStorage storage; + storage.emplace("0", MakeUnique<Value>(0)); + + using iterator = const_dict_iterator; + iterator iter(storage.begin()); + EXPECT_EQ("0", (*iter).first); + EXPECT_EQ(Value(0), (*iter).second); +} + +TEST(ValueIteratorsTest, ConstDictIteratorOperatorArrow) { + DictStorage storage; + storage.emplace("0", MakeUnique<Value>(0)); + + using iterator = const_dict_iterator; + iterator iter(storage.begin()); + EXPECT_EQ("0", iter->first); + EXPECT_EQ(Value(0), iter->second); +} + +TEST(ValueIteratorsTest, ConstDictIteratorPreIncrement) { + DictStorage storage; + storage.emplace("0", MakeUnique<Value>(0)); + storage.emplace("1", MakeUnique<Value>(1)); + + using iterator = const_dict_iterator; + iterator iter(storage.begin()); + EXPECT_EQ("0", iter->first); + EXPECT_EQ(Value(0), iter->second); + + iterator& iter_ref = ++iter; + EXPECT_EQ(&iter, &iter_ref); + + EXPECT_EQ("1", iter_ref->first); + EXPECT_EQ(Value(1), iter_ref->second); +} + +TEST(ValueIteratorsTest, ConstDictIteratorPostIncrement) { + DictStorage storage; + storage.emplace("0", MakeUnique<Value>(0)); + storage.emplace("1", MakeUnique<Value>(1)); + + using iterator = const_dict_iterator; + iterator iter(storage.begin()); + iterator iter_old = iter++; + + EXPECT_EQ("0", iter_old->first); + EXPECT_EQ(Value(0), iter_old->second); + + EXPECT_EQ("1", iter->first); + EXPECT_EQ(Value(1), iter->second); +} + +TEST(ValueIteratorsTest, ConstDictIteratorPreDecrement) { + DictStorage storage; + storage.emplace("0", MakeUnique<Value>(0)); + storage.emplace("1", MakeUnique<Value>(1)); + + using iterator = const_dict_iterator; + iterator iter(++storage.begin()); + EXPECT_EQ("1", iter->first); + EXPECT_EQ(Value(1), iter->second); + + iterator& iter_ref = --iter; + EXPECT_EQ(&iter, &iter_ref); + + EXPECT_EQ("0", iter_ref->first); + EXPECT_EQ(Value(0), iter_ref->second); +} + +TEST(ValueIteratorsTest, ConstDictIteratorPostDecrement) { + DictStorage storage; + storage.emplace("0", MakeUnique<Value>(0)); + storage.emplace("1", MakeUnique<Value>(1)); + + using iterator = const_dict_iterator; + iterator iter(++storage.begin()); + iterator iter_old = iter--; + + EXPECT_EQ("1", iter_old->first); + EXPECT_EQ(Value(1), iter_old->second); + + EXPECT_EQ("0", iter->first); + EXPECT_EQ(Value(0), iter->second); +} + +TEST(ValueIteratorsTest, ConstDictIteratorOperatorEQ) { + DictStorage storage; + using iterator = const_dict_iterator; + EXPECT_EQ(iterator(storage.begin()), iterator(storage.begin())); + EXPECT_EQ(iterator(storage.end()), iterator(storage.end())); +} + +TEST(ValueIteratorsTest, ConstDictIteratorOperatorNE) { + DictStorage storage_0; + storage_0.emplace("0", MakeUnique<Value>(0)); + + DictStorage storage_1; + storage_1.emplace("0", MakeUnique<Value>(0)); + + using iterator = const_dict_iterator; + EXPECT_NE(iterator(storage_0.begin()), iterator(storage_0.end())); + EXPECT_NE(iterator(storage_0.begin()), iterator(storage_1.begin())); + EXPECT_NE(iterator(storage_0.end()), iterator(storage_1.end())); + EXPECT_NE(iterator(storage_1.begin()), iterator(storage_1.end())); +} + +TEST(ValueIteratorsTest, DictIteratorProxy) { + DictStorage storage; + storage.emplace("null", MakeUnique<Value>(Value::Type::NONE)); + storage.emplace("bool", MakeUnique<Value>(Value::Type::BOOLEAN)); + storage.emplace("int", MakeUnique<Value>(Value::Type::INTEGER)); + storage.emplace("double", MakeUnique<Value>(Value::Type::DOUBLE)); + storage.emplace("string", MakeUnique<Value>(Value::Type::STRING)); + storage.emplace("blob", MakeUnique<Value>(Value::Type::BINARY)); + storage.emplace("dict", MakeUnique<Value>(Value::Type::DICTIONARY)); + storage.emplace("list", MakeUnique<Value>(Value::Type::LIST)); + + using iterator_proxy = dict_iterator_proxy; + iterator_proxy proxy(&storage); + + auto equal_to = [](const DictStorage::value_type& lhs, + const iterator_proxy::value_type& rhs) { + return std::tie(lhs.first, *lhs.second) == std::tie(rhs.first, rhs.second); + }; + + EXPECT_TRUE(are_equal(storage.begin(), storage.end(), proxy.begin(), + proxy.end(), equal_to)); + + EXPECT_TRUE(are_equal(storage.rbegin(), storage.rend(), proxy.rbegin(), + proxy.rend(), equal_to)); + + EXPECT_TRUE(are_equal(storage.cbegin(), storage.cend(), proxy.cbegin(), + proxy.cend(), equal_to)); + + EXPECT_TRUE(are_equal(storage.crbegin(), storage.crend(), proxy.crbegin(), + proxy.crend(), equal_to)); +} + +TEST(ValueIteratorsTest, ConstDictIteratorProxy) { + DictStorage storage; + storage.emplace("null", MakeUnique<Value>(Value::Type::NONE)); + storage.emplace("bool", MakeUnique<Value>(Value::Type::BOOLEAN)); + storage.emplace("int", MakeUnique<Value>(Value::Type::INTEGER)); + storage.emplace("double", MakeUnique<Value>(Value::Type::DOUBLE)); + storage.emplace("string", MakeUnique<Value>(Value::Type::STRING)); + storage.emplace("blob", MakeUnique<Value>(Value::Type::BINARY)); + storage.emplace("dict", MakeUnique<Value>(Value::Type::DICTIONARY)); + storage.emplace("list", MakeUnique<Value>(Value::Type::LIST)); + + using iterator_proxy = const_dict_iterator_proxy; + iterator_proxy proxy(&storage); + + auto equal_to = [](const DictStorage::value_type& lhs, + const iterator_proxy::value_type& rhs) { + return std::tie(lhs.first, *lhs.second) == std::tie(rhs.first, rhs.second); + }; + + EXPECT_TRUE(are_equal(storage.begin(), storage.end(), proxy.begin(), + proxy.end(), equal_to)); + + EXPECT_TRUE(are_equal(storage.rbegin(), storage.rend(), proxy.rbegin(), + proxy.rend(), equal_to)); + + EXPECT_TRUE(are_equal(storage.cbegin(), storage.cend(), proxy.cbegin(), + proxy.cend(), equal_to)); + + EXPECT_TRUE(are_equal(storage.crbegin(), storage.crend(), proxy.crbegin(), + proxy.crend(), equal_to)); +} + +} // namespace detail + +} // namespace base
diff --git a/base/values.cc b/base/values.cc index 8e2bc88..37a4e94 100644 --- a/base/values.cc +++ b/base/values.cc
@@ -244,6 +244,99 @@ return *list_; } +Value::dict_iterator Value::FindKey(const char* key) { + return FindKey(std::string(key)); +} + +Value::dict_iterator Value::FindKey(const std::string& key) { + CHECK(is_dict()); + return dict_iterator(dict_->find(key)); +} + +Value::const_dict_iterator Value::FindKey(const char* key) const { + return FindKey(std::string(key)); +} + +Value::const_dict_iterator Value::FindKey(const std::string& key) const { + CHECK(is_dict()); + return const_dict_iterator(dict_->find(key)); +} + +Value::dict_iterator Value::FindKeyOfType(const char* key, Type type) { + return FindKeyOfType(std::string(key), type); +} + +Value::dict_iterator Value::FindKeyOfType(const std::string& key, Type type) { + CHECK(is_dict()); + auto iter = dict_->find(key); + return dict_iterator((iter != dict_->end() && iter->second->IsType(type)) + ? iter + : dict_->end()); +} + +Value::const_dict_iterator Value::FindKeyOfType(const char* key, + Type type) const { + return FindKeyOfType(std::string(key), type); +} + +Value::const_dict_iterator Value::FindKeyOfType(const std::string& key, + Type type) const { + CHECK(is_dict()); + auto iter = dict_->find(key); + return const_dict_iterator( + (iter != dict_->end() && iter->second->IsType(type)) ? iter + : dict_->end()); +} + +Value::dict_iterator Value::SetKey(const char* key, Value value) { + return SetKey(std::string(key), std::move(value)); +} + +Value::dict_iterator Value::SetKey(const std::string& key, Value value) { + CHECK(is_dict()); + auto iter = dict_->find(key); + if (iter != dict_->end()) { + *iter->second = std::move(value); + return dict_iterator(iter); + } + + return dict_iterator( + dict_->emplace(key, MakeUnique<Value>(std::move(value))).first); +} + +Value::dict_iterator Value::SetKey(std::string&& key, Value value) { + CHECK(is_dict()); + auto iter = dict_->find(key); + if (iter != dict_->end()) { + *iter->second = std::move(value); + return dict_iterator(iter); + } + + return dict_iterator( + dict_->emplace(std::move(key), MakeUnique<Value>(std::move(value))) + .first); +} + +Value::dict_iterator Value::DictEnd() { + CHECK(is_dict()); + return dict_iterator(dict_->end()); +} + +Value::const_dict_iterator Value::DictEnd() const { + CHECK(is_dict()); + return const_dict_iterator(dict_->end()); +} + +Value::dict_iterator_proxy Value::DictItems() { + CHECK(is_dict()); + return dict_iterator_proxy(&*dict_); +} + +Value::const_dict_iterator_proxy Value::DictItems() const { + CHECK(is_dict()); + return const_dict_iterator_proxy(&*dict_); +} + bool Value::GetAsBoolean(bool* out_value) const { if (out_value && is_bool()) { *out_value = bool_value_;
diff --git a/base/values.h b/base/values.h index 6e75540..2dfae42 100644 --- a/base/values.h +++ b/base/values.h
@@ -34,6 +34,7 @@ #include "base/memory/manual_constructor.h" #include "base/strings/string16.h" #include "base/strings/string_piece.h" +#include "base/value_iterators.h" namespace base { @@ -137,6 +138,59 @@ ListStorage& GetList(); const ListStorage& GetList() const; + using dict_iterator = detail::dict_iterator; + using const_dict_iterator = detail::const_dict_iterator; + using dict_iterator_proxy = detail::dict_iterator_proxy; + using const_dict_iterator_proxy = detail::const_dict_iterator_proxy; + + // TODO(crbug.com/646113): Replace the following const char* and const + // std::string& overloads with a single base::StringPiece overload when + // base::less becomes available. + + // |FindKey| looks up |key| in the underlying dictionary. If found, it returns + // an iterator to the element. Otherwise the end iterator of the dictionary is + // returned. Callers are expected to compare the returned iterator against + // |DictEnd()| in order to determine whether |key| was present. + // Note: This fatally asserts if type() is not Type::DICTIONARY. + dict_iterator FindKey(const char* key); + dict_iterator FindKey(const std::string& key); + const_dict_iterator FindKey(const char* key) const; + const_dict_iterator FindKey(const std::string& key) const; + + // |FindKeyOfType| is similar to |FindKey|, but it also requires the found + // value to have type |type|. If no type is found, or the found value is of a + // different type the end iterator of the dictionary is returned. + // Callers are expected to compare the returned iterator against |DictEnd()| + // in order to determine whether |key| was present and of the correct |type|. + // Note: This fatally asserts if type() is not Type::DICTIONARY. + dict_iterator FindKeyOfType(const char* key, Type type); + dict_iterator FindKeyOfType(const std::string& key, Type type); + const_dict_iterator FindKeyOfType(const char* key, Type type) const; + const_dict_iterator FindKeyOfType(const std::string& key, Type type) const; + + // |SetKey| looks up |key| in the underlying dictionary and sets the mapped + // value to |value|. If |key| could not be found, a new element is inserted. + // An iterator to the modifed item is returned. + // Note: This fatally asserts if type() is not Type::DICTIONARY. + dict_iterator SetKey(const char* key, Value value); + dict_iterator SetKey(const std::string& key, Value value); + dict_iterator SetKey(std::string&& key, Value value); + + // |DictEnd| returns the end iterator of the underlying dictionary. It is + // intended to be used with |FindKey| in order to determine whether a given + // key is present in the dictionary. + // Note: This fatally asserts if type() is not Type::DICTIONARY. + dict_iterator DictEnd(); + const_dict_iterator DictEnd() const; + + // |DictItems| returns a proxy object that exposes iterators to the underlying + // dictionary. These are intended for iteration over all items in the + // dictionary and are compatible with for-each loops and standard library + // algorithms. + // Note: This fatally asserts if type() is not Type::DICTIONARY. + dict_iterator_proxy DictItems(); + const_dict_iterator_proxy DictItems() const; + // These methods allow the convenient retrieval of the contents of the Value. // If the current object can be converted into the given type, the value is // returned through the |out_value| parameter and true is returned;
diff --git a/base/values_unittest.cc b/base/values_unittest.cc index 38200868..3e680c9 100644 --- a/base/values_unittest.cc +++ b/base/values_unittest.cc
@@ -6,6 +6,7 @@ #include <stddef.h> +#include <functional> #include <limits> #include <memory> #include <string> @@ -13,9 +14,11 @@ #include <utility> #include <vector> +#include "base/containers/adapters.h" #include "base/memory/ptr_util.h" #include "base/strings/string16.h" #include "base/strings/utf_string_conversions.h" +#include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace base { @@ -222,33 +225,16 @@ } TEST(ValuesTest, CopyDictionary) { - // TODO(crbug.com/646113): Clean this up once DictionaryValue switched to - // value semantics. - int copy; - DictionaryValue value; - value.SetInteger("Int", 123); + Value::DictStorage storage; + storage.emplace("Int", MakeUnique<Value>(123)); + Value value(std::move(storage)); - DictionaryValue copied_value(value); - copied_value.GetInteger("Int", ©); + Value copied_value(value); + EXPECT_EQ(value, copied_value); - EXPECT_EQ(value.type(), copied_value.type()); - EXPECT_EQ(123, copy); - - copied_value.Clear(); - copied_value = value; - - copied_value.GetInteger("Int", ©); - - EXPECT_EQ(value.type(), copied_value.type()); - EXPECT_EQ(123, copy); - - auto blank = MakeUnique<Value>(); - - *blank = value; - EXPECT_EQ(Value::Type::DICTIONARY, blank->type()); - - static_cast<DictionaryValue*>(blank.get())->GetInteger("Int", ©); - EXPECT_EQ(123, copy); + Value blank; + blank = value; + EXPECT_EQ(value, blank); } TEST(ValuesTest, CopyList) { @@ -338,23 +324,24 @@ EXPECT_EQ(buffer, blank.GetBlob()); } -TEST(ValuesTest, MoveDictionary) { - // TODO(crbug.com/646113): Clean this up once DictionaryValue switched to - // value semantics. - int move; - DictionaryValue value; - value.SetInteger("Int", 123); +TEST(ValuesTest, MoveConstructDictionary) { + Value::DictStorage storage; + storage.emplace("Int", MakeUnique<Value>(123)); - DictionaryValue moved_value(std::move(value)); - moved_value.GetInteger("Int", &move); - + Value value(std::move(storage)); + Value moved_value(std::move(value)); EXPECT_EQ(Value::Type::DICTIONARY, moved_value.type()); - EXPECT_EQ(123, move); + EXPECT_EQ(123, moved_value.FindKey("Int")->second.GetInt()); +} + +TEST(ValuesTest, MoveAssignDictionary) { + Value::DictStorage storage; + storage.emplace("Int", MakeUnique<Value>(123)); Value blank; - - blank = DictionaryValue(); + blank = Value(std::move(storage)); EXPECT_EQ(Value::Type::DICTIONARY, blank.type()); + EXPECT_EQ(123, blank.FindKey("Int")->second.GetInt()); } TEST(ValuesTest, MoveList) { @@ -371,6 +358,254 @@ EXPECT_EQ(123, blank.GetList().back().GetInt()); } +TEST(ValuesTest, FindKey) { + Value::DictStorage storage; + storage.emplace("foo", MakeUnique<Value>("bar")); + Value dict(std::move(storage)); + EXPECT_NE(dict.FindKey("foo"), dict.DictEnd()); + EXPECT_EQ(dict.FindKey("baz"), dict.DictEnd()); +} + +TEST(ValuesTest, FindKeyChangeValue) { + Value::DictStorage storage; + storage.emplace("foo", MakeUnique<Value>("bar")); + Value dict(std::move(storage)); + auto it = dict.FindKey("foo"); + EXPECT_NE(it, dict.DictEnd()); + const std::string& key = it->first; + EXPECT_EQ("foo", key); + EXPECT_EQ("bar", it->second.GetString()); + + it->second = Value(123); + EXPECT_EQ(123, dict.FindKey("foo")->second.GetInt()); +} + +TEST(ValuesTest, FindKeyConst) { + Value::DictStorage storage; + storage.emplace("foo", MakeUnique<Value>("bar")); + const Value dict(std::move(storage)); + EXPECT_NE(dict.FindKey("foo"), dict.DictEnd()); + EXPECT_EQ(dict.FindKey("baz"), dict.DictEnd()); +} + +TEST(ValuesTest, FindKeyOfType) { + Value::DictStorage storage; + storage.emplace("null", MakeUnique<Value>(Value::Type::NONE)); + storage.emplace("bool", MakeUnique<Value>(Value::Type::BOOLEAN)); + storage.emplace("int", MakeUnique<Value>(Value::Type::INTEGER)); + storage.emplace("double", MakeUnique<Value>(Value::Type::DOUBLE)); + storage.emplace("string", MakeUnique<Value>(Value::Type::STRING)); + storage.emplace("blob", MakeUnique<Value>(Value::Type::BINARY)); + storage.emplace("list", MakeUnique<Value>(Value::Type::LIST)); + storage.emplace("dict", MakeUnique<Value>(Value::Type::DICTIONARY)); + + Value dict(std::move(storage)); + EXPECT_NE(dict.FindKeyOfType("null", Value::Type::NONE), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("null", Value::Type::BOOLEAN), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("null", Value::Type::INTEGER), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("null", Value::Type::DOUBLE), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("null", Value::Type::STRING), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("null", Value::Type::BINARY), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("null", Value::Type::LIST), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("null", Value::Type::DICTIONARY), + dict.DictEnd()); + + EXPECT_EQ(dict.FindKeyOfType("bool", Value::Type::NONE), dict.DictEnd()); + EXPECT_NE(dict.FindKeyOfType("bool", Value::Type::BOOLEAN), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("bool", Value::Type::INTEGER), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("bool", Value::Type::DOUBLE), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("bool", Value::Type::STRING), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("bool", Value::Type::BINARY), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("bool", Value::Type::LIST), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("bool", Value::Type::DICTIONARY), + dict.DictEnd()); + + EXPECT_EQ(dict.FindKeyOfType("int", Value::Type::NONE), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("int", Value::Type::BOOLEAN), dict.DictEnd()); + EXPECT_NE(dict.FindKeyOfType("int", Value::Type::INTEGER), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("int", Value::Type::DOUBLE), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("int", Value::Type::STRING), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("int", Value::Type::BINARY), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("int", Value::Type::LIST), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("int", Value::Type::DICTIONARY), dict.DictEnd()); + + EXPECT_EQ(dict.FindKeyOfType("double", Value::Type::NONE), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("double", Value::Type::BOOLEAN), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("double", Value::Type::INTEGER), dict.DictEnd()); + EXPECT_NE(dict.FindKeyOfType("double", Value::Type::DOUBLE), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("double", Value::Type::STRING), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("double", Value::Type::BINARY), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("double", Value::Type::LIST), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("double", Value::Type::DICTIONARY), + dict.DictEnd()); + + EXPECT_EQ(dict.FindKeyOfType("string", Value::Type::NONE), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("string", Value::Type::BOOLEAN), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("string", Value::Type::INTEGER), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("string", Value::Type::DOUBLE), dict.DictEnd()); + EXPECT_NE(dict.FindKeyOfType("string", Value::Type::STRING), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("string", Value::Type::BINARY), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("string", Value::Type::LIST), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("string", Value::Type::DICTIONARY), + dict.DictEnd()); + + EXPECT_EQ(dict.FindKeyOfType("blob", Value::Type::NONE), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("blob", Value::Type::BOOLEAN), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("blob", Value::Type::INTEGER), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("blob", Value::Type::DOUBLE), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("blob", Value::Type::STRING), dict.DictEnd()); + EXPECT_NE(dict.FindKeyOfType("blob", Value::Type::BINARY), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("blob", Value::Type::LIST), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("blob", Value::Type::DICTIONARY), + dict.DictEnd()); + + EXPECT_EQ(dict.FindKeyOfType("list", Value::Type::NONE), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("list", Value::Type::BOOLEAN), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("list", Value::Type::INTEGER), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("list", Value::Type::DOUBLE), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("list", Value::Type::STRING), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("list", Value::Type::BINARY), dict.DictEnd()); + EXPECT_NE(dict.FindKeyOfType("list", Value::Type::LIST), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("list", Value::Type::DICTIONARY), + dict.DictEnd()); + + EXPECT_EQ(dict.FindKeyOfType("dict", Value::Type::NONE), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("dict", Value::Type::BOOLEAN), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("dict", Value::Type::INTEGER), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("dict", Value::Type::DOUBLE), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("dict", Value::Type::STRING), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("dict", Value::Type::BINARY), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("dict", Value::Type::LIST), dict.DictEnd()); + EXPECT_NE(dict.FindKeyOfType("dict", Value::Type::DICTIONARY), + dict.DictEnd()); +} + +TEST(ValuesTest, FindKeyOfTypeConst) { + Value::DictStorage storage; + storage.emplace("null", MakeUnique<Value>(Value::Type::NONE)); + storage.emplace("bool", MakeUnique<Value>(Value::Type::BOOLEAN)); + storage.emplace("int", MakeUnique<Value>(Value::Type::INTEGER)); + storage.emplace("double", MakeUnique<Value>(Value::Type::DOUBLE)); + storage.emplace("string", MakeUnique<Value>(Value::Type::STRING)); + storage.emplace("blob", MakeUnique<Value>(Value::Type::BINARY)); + storage.emplace("list", MakeUnique<Value>(Value::Type::LIST)); + storage.emplace("dict", MakeUnique<Value>(Value::Type::DICTIONARY)); + + const Value dict(std::move(storage)); + EXPECT_NE(dict.FindKeyOfType("null", Value::Type::NONE), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("null", Value::Type::BOOLEAN), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("null", Value::Type::INTEGER), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("null", Value::Type::DOUBLE), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("null", Value::Type::STRING), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("null", Value::Type::BINARY), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("null", Value::Type::LIST), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("null", Value::Type::DICTIONARY), + dict.DictEnd()); + + EXPECT_EQ(dict.FindKeyOfType("bool", Value::Type::NONE), dict.DictEnd()); + EXPECT_NE(dict.FindKeyOfType("bool", Value::Type::BOOLEAN), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("bool", Value::Type::INTEGER), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("bool", Value::Type::DOUBLE), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("bool", Value::Type::STRING), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("bool", Value::Type::BINARY), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("bool", Value::Type::LIST), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("bool", Value::Type::DICTIONARY), + dict.DictEnd()); + + EXPECT_EQ(dict.FindKeyOfType("int", Value::Type::NONE), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("int", Value::Type::BOOLEAN), dict.DictEnd()); + EXPECT_NE(dict.FindKeyOfType("int", Value::Type::INTEGER), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("int", Value::Type::DOUBLE), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("int", Value::Type::STRING), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("int", Value::Type::BINARY), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("int", Value::Type::LIST), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("int", Value::Type::DICTIONARY), dict.DictEnd()); + + EXPECT_EQ(dict.FindKeyOfType("double", Value::Type::NONE), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("double", Value::Type::BOOLEAN), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("double", Value::Type::INTEGER), dict.DictEnd()); + EXPECT_NE(dict.FindKeyOfType("double", Value::Type::DOUBLE), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("double", Value::Type::STRING), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("double", Value::Type::BINARY), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("double", Value::Type::LIST), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("double", Value::Type::DICTIONARY), + dict.DictEnd()); + + EXPECT_EQ(dict.FindKeyOfType("string", Value::Type::NONE), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("string", Value::Type::BOOLEAN), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("string", Value::Type::INTEGER), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("string", Value::Type::DOUBLE), dict.DictEnd()); + EXPECT_NE(dict.FindKeyOfType("string", Value::Type::STRING), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("string", Value::Type::BINARY), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("string", Value::Type::LIST), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("string", Value::Type::DICTIONARY), + dict.DictEnd()); + + EXPECT_EQ(dict.FindKeyOfType("blob", Value::Type::NONE), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("blob", Value::Type::BOOLEAN), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("blob", Value::Type::INTEGER), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("blob", Value::Type::DOUBLE), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("blob", Value::Type::STRING), dict.DictEnd()); + EXPECT_NE(dict.FindKeyOfType("blob", Value::Type::BINARY), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("blob", Value::Type::LIST), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("blob", Value::Type::DICTIONARY), + dict.DictEnd()); + + EXPECT_EQ(dict.FindKeyOfType("list", Value::Type::NONE), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("list", Value::Type::BOOLEAN), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("list", Value::Type::INTEGER), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("list", Value::Type::DOUBLE), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("list", Value::Type::STRING), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("list", Value::Type::BINARY), dict.DictEnd()); + EXPECT_NE(dict.FindKeyOfType("list", Value::Type::LIST), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("list", Value::Type::DICTIONARY), + dict.DictEnd()); + + EXPECT_EQ(dict.FindKeyOfType("dict", Value::Type::NONE), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("dict", Value::Type::BOOLEAN), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("dict", Value::Type::INTEGER), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("dict", Value::Type::DOUBLE), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("dict", Value::Type::STRING), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("dict", Value::Type::BINARY), dict.DictEnd()); + EXPECT_EQ(dict.FindKeyOfType("dict", Value::Type::LIST), dict.DictEnd()); + EXPECT_NE(dict.FindKeyOfType("dict", Value::Type::DICTIONARY), + dict.DictEnd()); +} + +TEST(ValuesTest, SetKey) { + Value::DictStorage storage; + storage.emplace("null", MakeUnique<Value>(Value::Type::NONE)); + storage.emplace("bool", MakeUnique<Value>(Value::Type::BOOLEAN)); + storage.emplace("int", MakeUnique<Value>(Value::Type::INTEGER)); + storage.emplace("double", MakeUnique<Value>(Value::Type::DOUBLE)); + storage.emplace("string", MakeUnique<Value>(Value::Type::STRING)); + storage.emplace("blob", MakeUnique<Value>(Value::Type::BINARY)); + storage.emplace("list", MakeUnique<Value>(Value::Type::LIST)); + storage.emplace("dict", MakeUnique<Value>(Value::Type::DICTIONARY)); + + Value dict(Value::Type::DICTIONARY); + dict.SetKey("null", Value(Value::Type::NONE)); + dict.SetKey("bool", Value(Value::Type::BOOLEAN)); + dict.SetKey("int", Value(Value::Type::INTEGER)); + dict.SetKey("double", Value(Value::Type::DOUBLE)); + dict.SetKey("string", Value(Value::Type::STRING)); + dict.SetKey("blob", Value(Value::Type::BINARY)); + dict.SetKey("list", Value(Value::Type::LIST)); + dict.SetKey("dict", Value(Value::Type::DICTIONARY)); + + EXPECT_EQ(Value(std::move(storage)), dict); +} + +TEST(ValuesTest, DictEnd) { + Value dict(Value::Type::DICTIONARY); + EXPECT_EQ(dict.DictItems().end(), dict.DictEnd()); +} + +TEST(ValuesTest, DictEndConst) { + const Value dict(Value::Type::DICTIONARY); + EXPECT_EQ(dict.DictItems().end(), dict.DictEnd()); +} + TEST(ValuesTest, Basic) { // Test basic dictionary getting/setting DictionaryValue settings;
diff --git a/build/config/merge_for_jumbo.py b/build/config/merge_for_jumbo.py index b3146ba..ae435d0 100755 --- a/build/config/merge_for_jumbo.py +++ b/build/config/merge_for_jumbo.py
@@ -83,7 +83,8 @@ header_files = set([x for x in all_inputs if x.endswith(".h")]) assert set(args.outputs) == written_output_set, "Did not fill all outputs" files_not_included = set(all_inputs) - written_input_set - header_files - assert not files_not_included, "Did not include files: " + files_not_included + assert not files_not_included, ("Jumbo build left out files: %s" % + files_not_included) if args.verbose: print("Generated %s (%d files) based on %s" % ( str(args.outputs), len(written_input_set), args.file_list))
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp index f0552d6..67583ff 100644 --- a/chrome/app/chromeos_strings.grdp +++ b/chrome/app/chromeos_strings.grdp
@@ -130,6 +130,9 @@ <message name="IDS_FILE_BROWSER_MEDIA_VIEW_AUDIO_ROOT_LABEL" desc="A label for the 'Audio' root of media views."> Audio </message> + <message name="IDS_FILE_BROWSER_RECENT_ROOT_LABEL" desc="A label for the 'Recent' root."> + Recent + </message> <message name="IDS_FILE_BROWSER_DOWNLOADS_DIRECTORY_WARNING" desc="Warning displayed to user when viewing downloads folder."> <ph name="BEGIN_BOLD"><strong></ph>Caution:<ph name="END_BOLD"></strong></ph> These files are temporary and may be automatically deleted to free up disk space. <ph name="BEGIN_LINK"><a href="javascript://"></ph>Learn More<ph name="END_LINK"></a><ex></a></ex></ph> </message>
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc index 060bf0c..bb36ce9c 100644 --- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc +++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
@@ -744,7 +744,7 @@ for (const content_settings::ContentSettingsInfo* info : *registry) { map->ClearSettingsForOneTypeWithPredicate( info->website_settings_info()->type(), delete_begin_, - base::Bind(&WebsiteSettingsFilterAdapter, filter)); + HostContentSettingsMap::PatternSourcePredicate()); } }
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.h b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.h index 5691617..37cf3b0 100644 --- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.h +++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.h
@@ -78,17 +78,16 @@ // "Site data" includes storage backend accessible to websites and some // additional metadata kept by the browser (e.g. site usage data). - DATA_TYPE_SITE_DATA = - content::BrowsingDataRemover::DATA_TYPE_COOKIES | - content::BrowsingDataRemover::DATA_TYPE_CHANNEL_IDS | - content::BrowsingDataRemover::DATA_TYPE_DOM_STORAGE | - DATA_TYPE_PLUGIN_DATA | + DATA_TYPE_SITE_DATA = content::BrowsingDataRemover::DATA_TYPE_COOKIES | + content::BrowsingDataRemover::DATA_TYPE_CHANNEL_IDS | + content::BrowsingDataRemover::DATA_TYPE_DOM_STORAGE | + DATA_TYPE_PLUGIN_DATA | #if defined(OS_ANDROID) - DATA_TYPE_WEB_APP_DATA | + DATA_TYPE_WEB_APP_DATA | #endif - DATA_TYPE_SITE_USAGE_DATA | - DATA_TYPE_DURABLE_PERMISSION | - DATA_TYPE_EXTERNAL_PROTOCOL_DATA, + DATA_TYPE_SITE_USAGE_DATA | + DATA_TYPE_DURABLE_PERMISSION | + DATA_TYPE_EXTERNAL_PROTOCOL_DATA, // Datatypes protected by Important Sites. IMPORTANT_SITES_DATA_TYPES = @@ -98,17 +97,16 @@ // whichever makes sense. FILTERABLE_DATA_TYPES = DATA_TYPE_SITE_DATA | content::BrowsingDataRemover::DATA_TYPE_CACHE | - content::BrowsingDataRemover::DATA_TYPE_DOWNLOADS | - DATA_TYPE_CONTENT_SETTINGS, + content::BrowsingDataRemover::DATA_TYPE_DOWNLOADS, // Includes all the available remove options. Meant to be used by clients // that wish to wipe as much data as possible from a Profile, to make it // look like a new Profile. - ALL_DATA_TYPES = DATA_TYPE_SITE_DATA | + ALL_DATA_TYPES = DATA_TYPE_SITE_DATA | // content::BrowsingDataRemover::DATA_TYPE_CACHE | content::BrowsingDataRemover::DATA_TYPE_DOWNLOADS | - DATA_TYPE_FORM_DATA | - DATA_TYPE_HISTORY | + DATA_TYPE_FORM_DATA | // + DATA_TYPE_HISTORY | // DATA_TYPE_PASSWORDS | content::BrowsingDataRemover::DATA_TYPE_MEDIA_LICENSES | DATA_TYPE_CONTENT_SETTINGS,
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc index 73473ec..4f9e43d 100644 --- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc +++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
@@ -1681,67 +1681,24 @@ CONTENT_SETTINGS_TYPE_GEOLOCATION, std::string(), CONTENT_SETTING_ALLOW); map->SetContentSettingDefaultScope(kOrigin2, kOrigin2, - CONTENT_SETTINGS_TYPE_GEOLOCATION, - std::string(), CONTENT_SETTING_ALLOW); - map->SetContentSettingDefaultScope(kOrigin2, kOrigin2, CONTENT_SETTINGS_TYPE_NOTIFICATIONS, std::string(), CONTENT_SETTING_ALLOW); - map->SetContentSettingDefaultScope(kOrigin3, kOrigin3, - CONTENT_SETTINGS_TYPE_NOTIFICATIONS, - std::string(), CONTENT_SETTING_ALLOW); - - // Clear all except for origin1 and origin3. - std::unique_ptr<BrowsingDataFilterBuilder> filter( - BrowsingDataFilterBuilder::Create(BrowsingDataFilterBuilder::BLACKLIST)); - filter->AddRegisterableDomain(kTestRegisterableDomain1); - filter->AddRegisterableDomain(kTestRegisterableDomain3); - BlockUntilOriginDataRemoved( + map->SetContentSettingDefaultScope(kOrigin3, GURL(), + CONTENT_SETTINGS_TYPE_COOKIES, + std::string(), CONTENT_SETTING_BLOCK); + ContentSettingsPattern pattern = + ContentSettingsPattern::FromString("[*.]example.com"); + map->SetContentSettingCustomScope(pattern, ContentSettingsPattern::Wildcard(), + CONTENT_SETTINGS_TYPE_COOKIES, + std::string(), CONTENT_SETTING_BLOCK); + BlockUntilBrowsingDataRemoved( base::Time(), base::Time::Max(), - ChromeBrowsingDataRemoverDelegate::DATA_TYPE_CONTENT_SETTINGS, - std::move(filter)); + ChromeBrowsingDataRemoverDelegate::DATA_TYPE_CONTENT_SETTINGS, false); - EXPECT_EQ(ChromeBrowsingDataRemoverDelegate::DATA_TYPE_CONTENT_SETTINGS, - GetRemovalMask()); - EXPECT_EQ(content::BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB, - GetOriginTypeMask()); - - // Verify we still have the allow setting for origin1. + // Everything except the default settings should be deleted now. ContentSettingsForOneType host_settings; map->GetSettingsForOneType(CONTENT_SETTINGS_TYPE_GEOLOCATION, std::string(), &host_settings); - ASSERT_EQ(2u, host_settings.size()); - EXPECT_EQ(ContentSettingsPattern::FromURLNoWildcard(kOrigin1), - host_settings[0].primary_pattern) - << host_settings[0].primary_pattern.ToString(); - EXPECT_EQ(CONTENT_SETTING_ALLOW, host_settings[0].GetContentSetting()); - // And the wildcard. - EXPECT_EQ(ContentSettingsPattern::Wildcard(), - host_settings[1].primary_pattern) - << host_settings[1].primary_pattern.ToString(); - EXPECT_EQ(CONTENT_SETTING_ASK, host_settings[1].GetContentSetting()); - - // There should also only be one setting for origin3 - map->GetSettingsForOneType(CONTENT_SETTINGS_TYPE_NOTIFICATIONS, std::string(), - &host_settings); - ASSERT_EQ(2u, host_settings.size()); - EXPECT_EQ(ContentSettingsPattern::FromURLNoWildcard(kOrigin3), - host_settings[0].primary_pattern) - << host_settings[0].primary_pattern.ToString(); - EXPECT_EQ(CONTENT_SETTING_ALLOW, host_settings[0].GetContentSetting()); - // And the wildcard. - EXPECT_EQ(ContentSettingsPattern::Wildcard(), - host_settings[1].primary_pattern) - << host_settings[1].primary_pattern.ToString(); - EXPECT_EQ(CONTENT_SETTING_ASK, host_settings[1].GetContentSetting()); - - BlockUntilOriginDataRemoved( - base::Time(), base::Time::Max(), - ChromeBrowsingDataRemoverDelegate::DATA_TYPE_CONTENT_SETTINGS, - BrowsingDataFilterBuilder::Create(BrowsingDataFilterBuilder::BLACKLIST)); - - // Everything except the wildcard should be deleted now. - map->GetSettingsForOneType(CONTENT_SETTINGS_TYPE_GEOLOCATION, std::string(), - &host_settings); ASSERT_EQ(1u, host_settings.size()); EXPECT_EQ(ContentSettingsPattern::Wildcard(), host_settings[0].primary_pattern) @@ -1755,6 +1712,14 @@ host_settings[0].primary_pattern) << host_settings[0].primary_pattern.ToString(); EXPECT_EQ(CONTENT_SETTING_ASK, host_settings[0].GetContentSetting()); + + map->GetSettingsForOneType(CONTENT_SETTINGS_TYPE_COOKIES, std::string(), + &host_settings); + ASSERT_EQ(1u, host_settings.size()); + EXPECT_EQ(ContentSettingsPattern::Wildcard(), + host_settings[0].primary_pattern) + << host_settings[0].primary_pattern.ToString(); + EXPECT_EQ(CONTENT_SETTING_ALLOW, host_settings[0].GetContentSetting()); } TEST_F(ChromeBrowsingDataRemoverDelegateTest, RemoveDurablePermission) {
diff --git a/chrome/browser/password_manager/chrome_password_manager_client.cc b/chrome/browser/password_manager/chrome_password_manager_client.cc index f93d6e5..5e479c2d 100644 --- a/chrome/browser/password_manager/chrome_password_manager_client.cc +++ b/chrome/browser/password_manager/chrome_password_manager_client.cc
@@ -288,10 +288,10 @@ if (update_password) { UpdatePasswordInfoBarDelegate::Create(web_contents(), std::move(form_to_save)); - return true; + } else { + SavePasswordInfoBarDelegate::Create(web_contents(), + std::move(form_to_save)); } - SavePasswordInfoBarDelegate::Create(web_contents(), - std::move(form_to_save)); #endif // !defined(OS_ANDROID) return true; }
diff --git a/chrome/browser/password_manager/save_password_infobar_delegate_android.cc b/chrome/browser/password_manager/save_password_infobar_delegate_android.cc index 9e98b2a..4c9cba8 100644 --- a/chrome/browser/password_manager/save_password_infobar_delegate_android.cc +++ b/chrome/browser/password_manager/save_password_infobar_delegate_android.cc
@@ -17,6 +17,7 @@ #include "components/infobars/core/infobar.h" #include "components/infobars/core/infobar_manager.h" #include "components/password_manager/core/browser/password_bubble_experiment.h" +#include "components/password_manager/core/browser/password_form_metrics_recorder.h" #include "content/public/browser/web_contents.h" #include "ui/base/l10n/l10n_util.h" #include "url/origin.h" @@ -41,6 +42,7 @@ SavePasswordInfoBarDelegate::~SavePasswordInfoBarDelegate() { password_manager::metrics_util::LogUIDismissalReason(infobar_response_); + form_to_save_->metrics_recorder()->RecordUIDismissalReason(infobar_response_); } SavePasswordInfoBarDelegate::SavePasswordInfoBarDelegate( @@ -62,6 +64,10 @@ &message, &message_link_range); SetMessage(message); SetMessageLinkRange(message_link_range); + + form_to_save_->metrics_recorder()->RecordPasswordBubbleShown( + form_to_save_->GetCredentialSource(), + password_manager::metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING); } infobars::InfoBarDelegate::InfoBarIdentifier
diff --git a/chrome/browser/password_manager/save_password_infobar_delegate_android_unittest.cc b/chrome/browser/password_manager/save_password_infobar_delegate_android_unittest.cc index 311162fd..a00ed800 100644 --- a/chrome/browser/password_manager/save_password_infobar_delegate_android_unittest.cc +++ b/chrome/browser/password_manager/save_password_infobar_delegate_android_unittest.cc
@@ -21,10 +21,14 @@ #include "components/password_manager/core/browser/stub_password_manager_driver.h" #include "components/password_manager/core/common/password_manager_pref_names.h" #include "components/prefs/pref_service.h" +#include "components/ukm/test_ukm_recorder.h" +#include "components/ukm/ukm_source.h" #include "content/public/browser/web_contents.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" +using password_manager::PasswordFormMetricsRecorder; + namespace { class MockPasswordFormManager : public password_manager::PasswordFormManager { @@ -75,7 +79,8 @@ PrefService* prefs(); const autofill::PasswordForm& test_form() { return test_form_; } - std::unique_ptr<MockPasswordFormManager> CreateMockFormManager(); + std::unique_ptr<MockPasswordFormManager> CreateMockFormManager( + scoped_refptr<PasswordFormMetricsRecorder> metrics_recorder); protected: std::unique_ptr<ConfirmInfoBarDelegate> CreateDelegate( @@ -99,7 +104,6 @@ test_form_.origin = GURL("http://example.com"); test_form_.username_value = base::ASCIIToUTF16("username"); test_form_.password_value = base::ASCIIToUTF16("12345"); - fetcher_.Fetch(); } PrefService* SavePasswordInfoBarDelegateTest::prefs() { @@ -109,11 +113,15 @@ } std::unique_ptr<MockPasswordFormManager> -SavePasswordInfoBarDelegateTest::CreateMockFormManager() { +SavePasswordInfoBarDelegateTest::CreateMockFormManager( + scoped_refptr<PasswordFormMetricsRecorder> metrics_recorder) { auto manager = base::MakeUnique<MockPasswordFormManager>( &password_manager_, &client_, driver_.AsWeakPtr(), test_form(), &fetcher_); - manager->Init(nullptr); + manager->Init(metrics_recorder); + manager->ProvisionallySave( + test_form(), + password_manager::PasswordFormManager::ALLOW_OTHER_POSSIBLE_USERNAMES); return manager; } @@ -137,7 +145,7 @@ TEST_F(SavePasswordInfoBarDelegateTest, CancelTestCredentialSourceAPI) { std::unique_ptr<MockPasswordFormManager> password_form_manager( - CreateMockFormManager()); + CreateMockFormManager(nullptr)); EXPECT_CALL(*password_form_manager.get(), PermanentlyBlacklist()); std::unique_ptr<ConfirmInfoBarDelegate> infobar( CreateDelegate(std::move(password_form_manager))); @@ -147,9 +155,85 @@ TEST_F(SavePasswordInfoBarDelegateTest, CancelTestCredentialSourcePasswordManager) { std::unique_ptr<MockPasswordFormManager> password_form_manager( - CreateMockFormManager()); + CreateMockFormManager(nullptr)); EXPECT_CALL(*password_form_manager.get(), PermanentlyBlacklist()); std::unique_ptr<ConfirmInfoBarDelegate> infobar( CreateDelegate(std::move(password_form_manager))); EXPECT_TRUE(infobar->Cancel()); } + +class SavePasswordInfoBarDelegateTestForUKMs + : public SavePasswordInfoBarDelegateTest, + public ::testing::WithParamInterface< + PasswordFormMetricsRecorder::BubbleDismissalReason> { + public: + SavePasswordInfoBarDelegateTestForUKMs() = default; + ~SavePasswordInfoBarDelegateTestForUKMs() = default; +}; + +// Verify that URL keyed metrics are recorded for showing and interacting with +// the password save prompt. +TEST_P(SavePasswordInfoBarDelegateTestForUKMs, VerifyUKMRecording) { + using BubbleTrigger = PasswordFormMetricsRecorder::BubbleTrigger; + using BubbleDismissalReason = + PasswordFormMetricsRecorder::BubbleDismissalReason; + + BubbleDismissalReason dismissal_reason = GetParam(); + SCOPED_TRACE(::testing::Message() << "dismissal_reason = " + << static_cast<int64_t>(dismissal_reason)); + + ukm::TestUkmRecorder test_ukm_recorder; + { + // Setup metrics recorder + ukm::SourceId source_id = test_ukm_recorder.GetNewSourceID(); + static_cast<ukm::UkmRecorder*>(&test_ukm_recorder) + ->UpdateSourceURL(source_id, GURL("https://www.example.com/")); + auto recorder = base::MakeRefCounted<PasswordFormMetricsRecorder>( + true /*is_main_frame_secure*/, + PasswordFormMetricsRecorder::CreateUkmEntryBuilder(&test_ukm_recorder, + source_id)); + + // Exercise delegate. + std::unique_ptr<MockPasswordFormManager> password_form_manager( + CreateMockFormManager(recorder)); + if (dismissal_reason == BubbleDismissalReason::kDeclined) + EXPECT_CALL(*password_form_manager.get(), PermanentlyBlacklist()); + std::unique_ptr<ConfirmInfoBarDelegate> infobar( + CreateDelegate(std::move(password_form_manager))); + switch (dismissal_reason) { + case BubbleDismissalReason::kAccepted: + EXPECT_TRUE(infobar->Accept()); + break; + case BubbleDismissalReason::kDeclined: + EXPECT_TRUE(infobar->Cancel()); + break; + case BubbleDismissalReason::kIgnored: + // Do nothing. + break; + case BubbleDismissalReason::kUnknown: + NOTREACHED(); + break; + } + } + + // Verify metrics. + const ukm::UkmSource* source = + test_ukm_recorder.GetSourceForUrl("https://www.example.com/"); + ASSERT_TRUE(source); + test_ukm_recorder.ExpectMetric(*source, "PasswordForm", + password_manager::kUkmSavingPromptShown, 1); + test_ukm_recorder.ExpectMetric( + *source, "PasswordForm", password_manager::kUkmSavingPromptTrigger, + static_cast<int64_t>(BubbleTrigger::kPasswordManagerSuggestionAutomatic)); + test_ukm_recorder.ExpectMetric(*source, "PasswordForm", + password_manager::kUkmSavingPromptInteraction, + static_cast<int64_t>(dismissal_reason)); +} + +INSTANTIATE_TEST_CASE_P( + /*no extra name*/, + SavePasswordInfoBarDelegateTestForUKMs, + ::testing::Values( + PasswordFormMetricsRecorder::BubbleDismissalReason::kAccepted, + PasswordFormMetricsRecorder::BubbleDismissalReason::kDeclined, + PasswordFormMetricsRecorder::BubbleDismissalReason::kIgnored));
diff --git a/chrome/browser/password_manager/update_password_infobar_delegate_android.cc b/chrome/browser/password_manager/update_password_infobar_delegate_android.cc index ebc381ad..8e894f0 100644 --- a/chrome/browser/password_manager/update_password_infobar_delegate_android.cc +++ b/chrome/browser/password_manager/update_password_infobar_delegate_android.cc
@@ -17,6 +17,7 @@ #include "components/browser_sync/profile_sync_service.h" #include "components/infobars/core/infobar.h" #include "components/password_manager/core/browser/password_bubble_experiment.h" +#include "components/password_manager/core/browser/password_form_metrics_recorder.h" #include "components/strings/grit/components_strings.h" #include "content/public/browser/web_contents.h" #include "ui/base/l10n/l10n_util.h" @@ -36,7 +37,10 @@ is_smartlock_branding_enabled)))); } -UpdatePasswordInfoBarDelegate::~UpdatePasswordInfoBarDelegate() {} +UpdatePasswordInfoBarDelegate::~UpdatePasswordInfoBarDelegate() { + passwords_state_.form_manager()->metrics_recorder()->RecordUIDismissalReason( + infobar_response_); +} base::string16 UpdatePasswordInfoBarDelegate::GetBranding() const { return l10n_util::GetStringUTF16(is_smartlock_branding_enabled_ @@ -61,7 +65,8 @@ content::WebContents* web_contents, std::unique_ptr<password_manager::PasswordFormManager> form_to_update, bool is_smartlock_branding_enabled) - : is_smartlock_branding_enabled_(is_smartlock_branding_enabled) { + : infobar_response_(password_manager::metrics_util::NO_DIRECT_INTERACTION), + is_smartlock_branding_enabled_(is_smartlock_branding_enabled) { base::string16 message; gfx::Range message_link_range = gfx::Range(); GetSavePasswordDialogTitleTextAndLinkRange( @@ -72,6 +77,10 @@ SetMessageLinkRange(message_link_range); // TODO(melandory): Add histograms, crbug.com/577129 + form_to_update->metrics_recorder()->RecordPasswordBubbleShown( + form_to_update->GetCredentialSource(), + password_manager::metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING_UPDATE); + passwords_state_.set_client( ChromePasswordManagerClient::FromWebContents(web_contents)); passwords_state_.OnUpdatePassword(std::move(form_to_update)); @@ -91,7 +100,12 @@ return l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_UPDATE_BUTTON); } +void UpdatePasswordInfoBarDelegate::InfoBarDismissed() { + infobar_response_ = password_manager::metrics_util::CLICKED_CANCEL; +} + bool UpdatePasswordInfoBarDelegate::Accept() { + infobar_response_ = password_manager::metrics_util::CLICKED_SAVE; UpdatePasswordInfoBar* update_password_infobar = static_cast<UpdatePasswordInfoBar*>(infobar()); password_manager::PasswordFormManager* form_manager = @@ -106,3 +120,8 @@ } return true; } + +bool UpdatePasswordInfoBarDelegate::Cancel() { + infobar_response_ = password_manager::metrics_util::CLICKED_CANCEL; + return true; +}
diff --git a/chrome/browser/password_manager/update_password_infobar_delegate_android.h b/chrome/browser/password_manager/update_password_infobar_delegate_android.h index 7bdf489..7e8b345 100644 --- a/chrome/browser/password_manager/update_password_infobar_delegate_android.h +++ b/chrome/browser/password_manager/update_password_infobar_delegate_android.h
@@ -12,6 +12,7 @@ #include "chrome/browser/password_manager/password_manager_infobar_delegate_android.h" #include "chrome/browser/ui/passwords/manage_passwords_state.h" #include "components/password_manager/core/browser/password_form_manager.h" +#include "components/password_manager/core/browser/password_manager_metrics_util.h" namespace content { class WebContents; @@ -57,11 +58,16 @@ std::unique_ptr<password_manager::PasswordFormManager> form_to_update, bool is_smartlock_branding_enabled); + // Used to track the results we get from the info bar. + password_manager::metrics_util::UIDismissalReason infobar_response_; + // ConfirmInfoBarDelegate: infobars::InfoBarDelegate::InfoBarIdentifier GetIdentifier() const override; int GetButtons() const override; base::string16 GetButtonLabel(InfoBarButton button) const override; bool Accept() override; + void InfoBarDismissed() override; + bool Cancel() override; ManagePasswordsState passwords_state_; base::string16 branding_;
diff --git a/chrome/browser/resources/chromeos/login/active_directory_password_change.html b/chrome/browser/resources/chromeos/login/active_directory_password_change.html index 3f21f19..194c1a5 100644 --- a/chrome/browser/resources/chromeos/login/active_directory_password_change.html +++ b/chrome/browser/resources/chromeos/login/active_directory_password_change.html
@@ -2,7 +2,6 @@ Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> <link rel="import" href="chrome://resources/polymer/v1_0/neon-animation/animations/fade-in-animation.html"> <link rel="import" href="chrome://resources/polymer/v1_0/neon-animation/animations/fade-out-animation.html"> <link rel="import" href="chrome://resources/polymer/v1_0/neon-animation/neon-animatable.html"> @@ -27,6 +26,7 @@ --> <dom-module id="active-directory-password-change"> <link rel="stylesheet" href="gaia_password_changed.css"> + <link rel="stylesheet" href="oobe_flex_layout.css"> <template> <neon-animated-pages id="animatedPages" class="fit" entry-animation="fade-in-animation" exit-animation="fade-out-animation"
diff --git a/chrome/browser/resources/chromeos/login/arc_terms_of_service.html b/chrome/browser/resources/chromeos/login/arc_terms_of_service.html index e859848d..13c31aa 100644 --- a/chrome/browser/resources/chromeos/login/arc_terms_of_service.html +++ b/chrome/browser/resources/chromeos/login/arc_terms_of_service.html
@@ -2,7 +2,6 @@ Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-checkbox/paper-checkbox.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html"> @@ -12,6 +11,7 @@ <template> <link rel="stylesheet" href="arc_terms_of_service.css"> <link rel="stylesheet" href="oobe_dialog_parameters.css"> + <link rel="stylesheet" href="oobe_flex_layout.css"> <oobe-dialog id="arc-tos-dialog-md" class="arc-tos-loading" has-buttons> <iron-icon src="chrome://oobe/playstore.svg" class="oobe-icon"> </iron-icon>
diff --git a/chrome/browser/resources/chromeos/login/controller-pairing-screen.html b/chrome/browser/resources/chromeos/login/controller-pairing-screen.html index 7613bfd6..4976681 100644 --- a/chrome/browser/resources/chromeos/login/controller-pairing-screen.html +++ b/chrome/browser/resources/chromeos/login/controller-pairing-screen.html
@@ -2,7 +2,6 @@ Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-iconset-svg/iron-iconset-svg.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-selector/iron-selector.html"> @@ -28,6 +27,7 @@ user actions and a spinner is shown near selected device. --> <dom-module id="pairing-device-list"> + <link rel="stylesheet" href="oobe_flex_layout.css"> <link rel="stylesheet" href="pairing_device_list.css"> <iron-iconset-svg name="pairing-device-list-icons">
diff --git a/chrome/browser/resources/chromeos/login/encryption_migration.html b/chrome/browser/resources/chromeos/login/encryption_migration.html index 14afc2a..540e238 100644 --- a/chrome/browser/resources/chromeos/login/encryption_migration.html +++ b/chrome/browser/resources/chromeos/login/encryption_migration.html
@@ -4,7 +4,6 @@ <link rel="import" href="chrome://resources/cr_elements/icons.html"> <link rel="import" href="chrome://resources/html/i18n_behavior.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-progress/paper-progress.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html"> <link rel="import" href="chrome://oobe/custom_elements.html"> @@ -13,6 +12,7 @@ <template> <link rel="stylesheet" href="encryption_migration.css"> <link rel="stylesheet" href="oobe_dialog_parameters.css"> + <link rel="stylesheet" href="oobe_flex_layout.css"> <template is="dom-if" if="[[isInitial_(uiState)]]"> <oobe-dialog tabindex="0" has-buttons></oobe-dialog> </template>
diff --git a/chrome/browser/resources/chromeos/login/gaia_card.html b/chrome/browser/resources/chromeos/login/gaia_card.html index 6315118..f1c8494 100644 --- a/chrome/browser/resources/chromeos/login/gaia_card.html +++ b/chrome/browser/resources/chromeos/login/gaia_card.html
@@ -2,7 +2,6 @@ Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-progress/paper-progress.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html"> <link rel="import" href="chrome://resources/polymer/v1_0/polymer/polymer.html"> @@ -25,6 +24,7 @@ --> <dom-module id="gaia-card"> <link rel="stylesheet" href="gaia_card.css"> + <link rel="stylesheet" href="oobe_flex_layout.css"> <template> <div class="gaia-header vertical layout relative">
diff --git a/chrome/browser/resources/chromeos/login/gaia_header.html b/chrome/browser/resources/chromeos/login/gaia_header.html index 6318e22..2fd9beb 100644 --- a/chrome/browser/resources/chromeos/login/gaia_header.html +++ b/chrome/browser/resources/chromeos/login/gaia_header.html
@@ -2,7 +2,6 @@ Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> <link rel="import" href="chrome://resources/polymer/v1_0/polymer/polymer.html"> <!-- @@ -17,6 +16,7 @@ --> <dom-module id="gaia-header"> <link rel="stylesheet" href="gaia_header.css"> + <link rel="stylesheet" href="oobe_flex_layout.css"> <template> <img src="chrome://theme/IDR_LOGO_AVATAR_CIRCLE_BLUE_COLOR" alt
diff --git a/chrome/browser/resources/chromeos/login/gaia_input.html b/chrome/browser/resources/chromeos/login/gaia_input.html index 67cac86..4f1e66d6 100644 --- a/chrome/browser/resources/chromeos/login/gaia_input.html +++ b/chrome/browser/resources/chromeos/login/gaia_input.html
@@ -2,7 +2,6 @@ Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-input/iron-input.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-input/paper-input-container.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-input/paper-input-error.html"> @@ -36,6 +35,7 @@ --> <dom-module id="gaia-input"> <link rel="stylesheet" href="gaia_input.css"> + <link rel="stylesheet" href="oobe_flex_layout.css"> <template> <paper-input-container id="decorator" on-tap="onTap"
diff --git a/chrome/browser/resources/chromeos/login/gaia_input_form.html b/chrome/browser/resources/chromeos/login/gaia_input_form.html index cad99e230..8378295 100644 --- a/chrome/browser/resources/chromeos/login/gaia_input_form.html +++ b/chrome/browser/resources/chromeos/login/gaia_input_form.html
@@ -2,7 +2,6 @@ Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> <link rel="import" href="chrome://resources/polymer/v1_0/polymer/polymer.html"> <!-- @@ -25,6 +24,7 @@ --> <dom-module id="gaia-input-form"> <link rel="stylesheet" href="gaia_input_form.css"> + <link rel="stylesheet" href="oobe_flex_layout.css"> <template> <div on-keydown="onKeyDown_">
diff --git a/chrome/browser/resources/chromeos/login/gaia_password_changed.html b/chrome/browser/resources/chromeos/login/gaia_password_changed.html index bcb6d33..5457b44 100644 --- a/chrome/browser/resources/chromeos/login/gaia_password_changed.html +++ b/chrome/browser/resources/chromeos/login/gaia_password_changed.html
@@ -2,7 +2,6 @@ Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-icons/iron-icons.html"> <link rel="import" href="chrome://resources/polymer/v1_0/neon-animation/animations/fade-in-animation.html"> <link rel="import" href="chrome://resources/polymer/v1_0/neon-animation/animations/fade-out-animation.html"> @@ -41,6 +40,7 @@ --> <dom-module id="gaia-password-changed"> <link rel="stylesheet" href="gaia_password_changed.css"> + <link rel="stylesheet" href="oobe_flex_layout.css"> <template> <neon-animated-pages id="animatedPages" class="fit"
diff --git a/chrome/browser/resources/chromeos/login/header_bar.html b/chrome/browser/resources/chromeos/login/header_bar.html index c58d052..b5c4b23 100644 --- a/chrome/browser/resources/chromeos/login/header_bar.html +++ b/chrome/browser/resources/chromeos/login/header_bar.html
@@ -1,5 +1,4 @@ -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> - +<link rel="stylesheet" href="oobe_flex_layout.css"> <div id="login-header-bar" hidden class="login-header-bar-hidden layout horizontal"> <div id="shutdown-header-bar-item" class="header-bar-item">
diff --git a/chrome/browser/resources/chromeos/login/host-pairing-screen.html b/chrome/browser/resources/chromeos/login/host-pairing-screen.html index 4f973744..48cee7d 100644 --- a/chrome/browser/resources/chromeos/login/host-pairing-screen.html +++ b/chrome/browser/resources/chromeos/login/host-pairing-screen.html
@@ -2,7 +2,6 @@ Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-icons/device-icons.html"> <link rel="import" href="chrome://resources/polymer/v1_0/neon-animation/animations/fade-in-animation.html"> @@ -28,7 +27,6 @@ </dom-module> <dom-module id="host-pairing-screen"> - <link rel="stylesheet" href="oobe_screen_host_pairing.css"> <template>
diff --git a/chrome/browser/resources/chromeos/login/md_header_bar.html b/chrome/browser/resources/chromeos/login/md_header_bar.html index 04df3a9..9b25573 100644 --- a/chrome/browser/resources/chromeos/login/md_header_bar.html +++ b/chrome/browser/resources/chromeos/login/md_header_bar.html
@@ -1,5 +1,4 @@ -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> - +<link rel="stylesheet" href="oobe_flex_layout.css"> <div id="login-header-bar" hidden class="login-header-bar-hidden layout horizontal"> <div id="shutdown-header-bar-item" class="header-bar-item">
diff --git a/chrome/browser/resources/chromeos/login/navigation_bar.html b/chrome/browser/resources/chromeos/login/navigation_bar.html index 60a3ab3c..cdf416e 100644 --- a/chrome/browser/resources/chromeos/login/navigation_bar.html +++ b/chrome/browser/resources/chromeos/login/navigation_bar.html
@@ -2,9 +2,6 @@ Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> - - <!-- A navigation panel with back, refresh, and/or close buttons. ┌─────────────────────────────────┐ @@ -25,6 +22,7 @@ --> <dom-module id="navigation-bar"> <link rel="stylesheet" href="navigation_bar.css"> + <link rel="stylesheet" href="oobe_flex_layout.css"> <template> <div class="fit layout horizontal center">
diff --git a/chrome/browser/resources/chromeos/login/notification_card.html b/chrome/browser/resources/chromeos/login/notification_card.html index a3dee9f..dbb77b7ba 100644 --- a/chrome/browser/resources/chromeos/login/notification_card.html +++ b/chrome/browser/resources/chromeos/login/notification_card.html
@@ -2,7 +2,6 @@ Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-icons/iron-icons.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html"> <link rel="import" href="chrome://resources/polymer/v1_0/polymer/polymer.html"> @@ -28,6 +27,7 @@ --> <dom-module id="notification-card"> <link rel="stylesheet" href="notification_card.css"> + <link rel="stylesheet" href="oobe_flex_layout.css"> <template> <div id="container" class="vertical layout center fit">
diff --git a/chrome/browser/resources/chromeos/login/offline_ad_login.html b/chrome/browser/resources/chromeos/login/offline_ad_login.html index 4c26792..2a51d97 100644 --- a/chrome/browser/resources/chromeos/login/offline_ad_login.html +++ b/chrome/browser/resources/chromeos/login/offline_ad_login.html
@@ -2,7 +2,6 @@ Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> <link rel="import" href="chrome://resources/polymer/v1_0/polymer/polymer.html"> <!-- @@ -34,6 +33,7 @@ --> <dom-module id="offline-ad-login"> <link rel="stylesheet" href="offline_gaia.css"> + <link rel="stylesheet" href="oobe_flex_layout.css"> <template> <gaia-card id="gaiaCard" class="fit"> <div class="header flex vertical layout end-justified start">
diff --git a/chrome/browser/resources/chromeos/login/offline_gaia.html b/chrome/browser/resources/chromeos/login/offline_gaia.html index fb30b000..7d95d1f 100644 --- a/chrome/browser/resources/chromeos/login/offline_gaia.html +++ b/chrome/browser/resources/chromeos/login/offline_gaia.html
@@ -4,7 +4,6 @@ <link rel="import" href="chrome://resources/cr_elements/shared_style_css.html"> <link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> <link rel="import" href="chrome://resources/polymer/v1_0/neon-animation/animations/slide-from-left-animation.html"> <link rel="import" href="chrome://resources/polymer/v1_0/neon-animation/animations/slide-from-right-animation.html"> <link rel="import" href="chrome://resources/polymer/v1_0/neon-animation/animations/slide-left-animation.html"> @@ -49,6 +48,7 @@ <template> <style include="cr-shared-style"></style> + <link rel="stylesheet" href="oobe_flex_layout.css"> <link rel="stylesheet" href="oobe_dialog_host.css"> <link rel="stylesheet" href="oobe_dialog_parameters.css"> <template is="dom-if" if="[[glifMode]]" restamp>
diff --git a/chrome/browser/resources/chromeos/login/oobe_a11y_option.html b/chrome/browser/resources/chromeos/login/oobe_a11y_option.html index 81f5e01..925b5760 100644 --- a/chrome/browser/resources/chromeos/login/oobe_a11y_option.html +++ b/chrome/browser/resources/chromeos/login/oobe_a11y_option.html
@@ -1,5 +1,4 @@ <link rel="import" href="chrome://resources/html/polymer.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-toggle-button/paper-toggle-button.html"> <dom-module id="oobe-a11y-option"> @@ -31,6 +30,7 @@ var(--oobe-toggle-button-size); } </style> + <link rel="stylesheet" href="oobe_flex_layout.css"> <link rel="stylesheet" href="oobe_a11y_option.css"> <div id="elementBox" class="layout horizontal"> <div class="flex layout vertical center-justified">
diff --git a/chrome/browser/resources/chromeos/login/oobe_buttons.html b/chrome/browser/resources/chromeos/login/oobe_buttons.html index 36bbe31f..9a7d998 100644 --- a/chrome/browser/resources/chromeos/login/oobe_buttons.html +++ b/chrome/browser/resources/chromeos/login/oobe_buttons.html
@@ -40,6 +40,7 @@ 'border' - adds border to the button. --> <dom-module id="oobe-text-button"> + <link rel="stylesheet" href="oobe_flex_layout.css"> <link rel="stylesheet" href="oobe_text_button.css"> <template> <style> @@ -76,6 +77,7 @@ 'label-for-aria' - accessibility label. --> <dom-module id="oobe-back-button"> + <link rel="stylesheet" href="oobe_flex_layout.css"> <link rel="stylesheet" href="oobe_nav_button.css"> <template> <paper-button id="button" on-tap="onClick_" disabled="[[disabled]]" @@ -89,6 +91,7 @@ </dom-module> <dom-module id="oobe-next-button"> + <link rel="stylesheet" href="oobe_flex_layout.css"> <link rel="stylesheet" href="oobe_nav_button.css"> <template> <paper-button id="button" on-tap="onClick_" disabled="[[disabled]]"> @@ -115,6 +118,7 @@ 'label-for-aria' - accessibility label. --> <dom-module id="oobe-welcome-secondary-button"> + <link rel="stylesheet" href="oobe_flex_layout.css"> <link rel="stylesheet" href="oobe_welcome_secondary_button.css"> <template> <paper-button id="button" aria-label$="[[labelForAria]]">
diff --git a/chrome/browser/resources/chromeos/login/oobe_dialog.html b/chrome/browser/resources/chromeos/login/oobe_dialog.html index 4b1d8e5b..74855bcd 100644 --- a/chrome/browser/resources/chromeos/login/oobe_dialog.html +++ b/chrome/browser/resources/chromeos/login/oobe_dialog.html
@@ -2,7 +2,6 @@ Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html"> <!-- @@ -57,6 +56,7 @@ <template> <link rel="stylesheet" href="oobe_dialog_host.css"> <link rel="stylesheet" href="oobe_dialog.css"> + <link rel="stylesheet" href="oobe_flex_layout.css"> <div id="header-container" hidden="[[noHeader]]" android$="[[android]]"> <div class="oobe-icon-div"> <content select=".oobe-icon"></content>
diff --git a/chrome/browser/resources/chromeos/login/oobe_eula.html b/chrome/browser/resources/chromeos/login/oobe_eula.html index 908926c..1a446fb 100644 --- a/chrome/browser/resources/chromeos/login/oobe_eula.html +++ b/chrome/browser/resources/chromeos/login/oobe_eula.html
@@ -2,7 +2,6 @@ Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-iconset-svg/iron-iconset-svg.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-checkbox/paper-checkbox.html"> @@ -42,6 +41,7 @@ <template> <link rel="stylesheet" href="oobe_eula.css"> <link rel="stylesheet" href="oobe_dialog_parameters.css"> + <link rel="stylesheet" href="oobe_flex_layout.css"> <oobe-dialog id="eulaLoadingDialog" hidden="[[!eulaLoadingScreenShown]]" role="dialog" i18n-values="aria-label:termsOfServiceLoading" has-buttons>
diff --git a/chrome/browser/resources/chromeos/login/oobe_flex_layout.css b/chrome/browser/resources/chromeos/login/oobe_flex_layout.css new file mode 100644 index 0000000..e6cd4ca2 --- /dev/null +++ b/chrome/browser/resources/chromeos/login/oobe_flex_layout.css
@@ -0,0 +1,112 @@ +/* Copyright 2017 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. */ + +/****************************************************** + OOBE Flex Layout (based on polymer iron-flex-layout) +*******************************************************/ + +.layout.horizontal, +.layout.horizontal-reverse, +.layout.vertical, +.layout.vertical-reverse { + display: flex; +} + +.layout.inline { + display: inline-flex; +} + +.layout.horizontal { + flex-direction: row; +} + +.layout.horizontal-reverse { + flex-direction: row-reverse; +} + +.layout.vertical { + flex-direction: column; +} + +.layout.vertical-reverse { + flex-direction: column-reverse; +} + +.flex { + flex: 1; +} + +/* alignment in cross axis */ + +.layout.start { + align-items: flex-start; +} + +.layout.center { + align-items: center; +} + +.layout.end { + align-items: flex-end; +} + +/* alignment in main axis */ + +.layout.start-justified { + justify-content: flex-start; +} + +.layout.center-justified { + justify-content: center; +} + +.layout.end-justified { + justify-content: flex-end; +} + +.layout.around-justified { + justify-content: space-around; +} + +.layout.justified { + justify-content: space-between; +} + +/* self alignment */ + +.self-start { + align-self: flex-start; +} + +.self-center { + align-self: center; +} + +.self-end { + align-self: flex-end; +} + +.self-stretch { + align-self: stretch; +} + +/******************************* + Other Layout +*******************************/ + +[hidden] { + display: none !important; +} + +.relative { + position: relative; +} + +.fit { + bottom: 0; + left: 0; + position: absolute; + right: 0; + top: 0; +}
diff --git a/chrome/browser/resources/chromeos/login/oobe_hid_detection.html b/chrome/browser/resources/chromeos/login/oobe_hid_detection.html index acb628c..3f744ee4 100644 --- a/chrome/browser/resources/chromeos/login/oobe_hid_detection.html +++ b/chrome/browser/resources/chromeos/login/oobe_hid_detection.html
@@ -1,4 +1,3 @@ -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-iconset-svg/iron-iconset-svg.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-checkbox/paper-checkbox.html"> @@ -17,6 +16,7 @@ <link rel="stylesheet" href="oobe_hid_detection.css"> <link rel="stylesheet" href="oobe_dialog_parameters.css"> <link rel="stylesheet" href="../../options/chromeos/bluetooth.css"> + <link rel="stylesheet" href="oobe_flex_layout.css"> <oobe-dialog has-buttons> <iron-icon icon="oobe-hid-detection:bluetooth" class="oobe-icon"> </iron-icon>
diff --git a/chrome/browser/resources/chromeos/login/oobe_reset.html b/chrome/browser/resources/chromeos/login/oobe_reset.html index 3afc401..b15e1988 100644 --- a/chrome/browser/resources/chromeos/login/oobe_reset.html +++ b/chrome/browser/resources/chromeos/login/oobe_reset.html
@@ -2,7 +2,6 @@ Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-iconset-svg/iron-iconset-svg.html"> @@ -28,9 +27,10 @@ <dom-module id="oobe-reset-md"> <template> - <link rel="stylesheet" href="oobe_reset.css"> - <link rel="stylesheet" href="oobe_dialog_parameters.css"> <link rel="stylesheet" href="chrome://resources/css/throbber.css"> + <link rel="stylesheet" href="oobe_dialog_parameters.css"> + <link rel="stylesheet" href="oobe_flex_layout.css"> + <link rel="stylesheet" href="oobe_reset.css"> <oobe-dialog id="resetDialog" role="dialog" i18n-values="aria-label:resetScreenAccessibleTitle" has-buttons> <hd-iron-icon class="oobe-icon"
diff --git a/chrome/browser/resources/chromeos/login/oobe_reset_confirmation_overlay.html b/chrome/browser/resources/chromeos/login/oobe_reset_confirmation_overlay.html index a1b2e35..520885e0 100644 --- a/chrome/browser/resources/chromeos/login/oobe_reset_confirmation_overlay.html +++ b/chrome/browser/resources/chromeos/login/oobe_reset_confirmation_overlay.html
@@ -4,6 +4,7 @@ <dom-module id="reset-confirm-overlay-md"> <template> + <link rel="stylesheet" href="oobe_flex_layout.css"> <link rel="stylesheet" href="oobe_reset_confirmation_overlay.css"> <div class="reset-popup not-resizable"> <h1 i18n-content="confirmPowerwashTitle" hidden="[[!isPowerwashView_]]">
diff --git a/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.html b/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.html index 231f18c..0f71a3fa 100644 --- a/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.html +++ b/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.html
@@ -6,6 +6,7 @@ <div id="oauth-enrollment" class="step no-logo hidden" hidden> <div id="oauth-enroll-step-contents"> + <link rel="stylesheet" href="oobe_flex_layout.css"> <div id="oauth-enroll-step-signin"> <webview id="oauth-enroll-auth-view" name="oauth-enroll-auth-view"> </webview>
diff --git a/chrome/browser/resources/chromeos/login/oobe_screen_user_image.html b/chrome/browser/resources/chromeos/login/oobe_screen_user_image.html index 5bf5e2c..893d47f 100644 --- a/chrome/browser/resources/chromeos/login/oobe_screen_user_image.html +++ b/chrome/browser/resources/chromeos/login/oobe_screen_user_image.html
@@ -35,6 +35,7 @@ </div> <div id="user-image-controls" class="step-controls"></div> <div id="user-images-loading" class="step-loading"> + <link rel="stylesheet" href="oobe_flex_layout.css"> <throbber-notice i18n-values="text:syncingPreferences" class="fit"> </throbber-notice> </div>
diff --git a/chrome/browser/resources/chromeos/login/oobe_update.html b/chrome/browser/resources/chromeos/login/oobe_update.html index 2f01183d..9ebec60d 100644 --- a/chrome/browser/resources/chromeos/login/oobe_update.html +++ b/chrome/browser/resources/chromeos/login/oobe_update.html
@@ -2,7 +2,6 @@ Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-iconset-svg/iron-iconset-svg.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-progress/paper-progress.html"> @@ -25,8 +24,9 @@ <dom-module id="oobe-update-md"> <template> - <link rel="stylesheet" href="oobe_update.css"> <link rel="stylesheet" href="oobe_dialog_parameters.css"> + <link rel="stylesheet" href="oobe_flex_layout.css"> + <link rel="stylesheet" href="oobe_update.css"> <oobe-dialog hidden="[[!checkingForUpdate]]" tabindex="0" aria-live="polite"> <iron-icon icon="oobe-update:googleg" class="oobe-icon"></iron-icon>
diff --git a/chrome/browser/resources/chromeos/login/oobe_voice_interaction_value_prop.html b/chrome/browser/resources/chromeos/login/oobe_voice_interaction_value_prop.html index 06630f0..0f638d6 100644 --- a/chrome/browser/resources/chromeos/login/oobe_voice_interaction_value_prop.html +++ b/chrome/browser/resources/chromeos/login/oobe_voice_interaction_value_prop.html
@@ -2,12 +2,11 @@ Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> - <dom-module id="voice-interaction-value-prop-md"> <template> - <link rel="stylesheet" href="oobe_voice_interaction_value_prop.css"> <link rel="stylesheet" href="oobe_dialog_parameters.css"> + <link rel="stylesheet" href="oobe_flex_layout.css"> + <link rel="stylesheet" href="oobe_voice_interaction_value_prop.css"> <oobe-dialog id="voice-dialog" class="value-prop-loading" role="dialog" hide-shadow has-buttons no-footer-padding android> <div class = "header">
diff --git a/chrome/browser/resources/chromeos/login/oobe_wait_for_container_ready.html b/chrome/browser/resources/chromeos/login/oobe_wait_for_container_ready.html index 4ecc2048..f4828c2 100644 --- a/chrome/browser/resources/chromeos/login/oobe_wait_for_container_ready.html +++ b/chrome/browser/resources/chromeos/login/oobe_wait_for_container_ready.html
@@ -2,12 +2,11 @@ Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> - <dom-module id="wait-for-container-ready-md"> <template> - <link rel="stylesheet" href="oobe_wait_for_container_ready.css"> <link rel="stylesheet" href="oobe_dialog_parameters.css"> + <link rel="stylesheet" href="oobe_flex_layout.css"> + <link rel="stylesheet" href="oobe_wait_for_container_ready.css"> <oobe-dialog id="waitForContainerReadyDialog" role="dialog" hide-shadow no-footer android> <iron-icon src="https://www.gstatic.com/opa-chromeos/oobe/images/assistant_logo.png" class="oobe-icon">
diff --git a/chrome/browser/resources/chromeos/login/oobe_welcome.html b/chrome/browser/resources/chromeos/login/oobe_welcome.html index b101431..40b2ffc 100644 --- a/chrome/browser/resources/chromeos/login/oobe_welcome.html +++ b/chrome/browser/resources/chromeos/login/oobe_welcome.html
@@ -6,7 +6,6 @@ <link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_network_select.html"> <link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_onc_types.html"> <link rel="import" href="chrome://resources/html/i18n_behavior.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-iconset-svg/iron-iconset-svg.html"> @@ -83,6 +82,7 @@ <link rel="stylesheet" href="oobe_dialog_host.css"> <link rel="stylesheet" href="oobe_welcome.css"> <link rel="stylesheet" href="oobe_dialog_parameters.css"> + <link rel="stylesheet" href="oobe_flex_layout.css"> <oobe-welcome-dialog id="welcomeScreen" role="dialog" i18n-values="aria-label:networkScreenGreeting" current-language="[[currentLanguage]]"
diff --git a/chrome/browser/resources/chromeos/login/oobe_welcome_dialog.html b/chrome/browser/resources/chromeos/login/oobe_welcome_dialog.html index 125a476b..980ee6ec 100644 --- a/chrome/browser/resources/chromeos/login/oobe_welcome_dialog.html +++ b/chrome/browser/resources/chromeos/login/oobe_welcome_dialog.html
@@ -2,7 +2,6 @@ Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html"> <!-- @@ -11,6 +10,7 @@ <dom-module id="oobe-welcome-dialog"> <template> <link rel="stylesheet" href="oobe_dialog_host.css"> + <link rel="stylesheet" href="oobe_flex_layout.css"> <link rel="stylesheet" href="oobe_welcome_dialog.css"> <div class="layout horizontal flex"> <div class="vertical-margin"></div>
diff --git a/chrome/browser/resources/chromeos/login/saml_confirm_password.html b/chrome/browser/resources/chromeos/login/saml_confirm_password.html index 50d94aa..f03a62b 100644 --- a/chrome/browser/resources/chromeos/login/saml_confirm_password.html +++ b/chrome/browser/resources/chromeos/login/saml_confirm_password.html
@@ -4,7 +4,6 @@ <link rel="import" href="chrome://resources/cr_elements/shared_style_css.html"> <link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> <link rel="import" href="chrome://resources/polymer/v1_0/neon-animation/animations/fade-in-animation.html"> <link rel="import" href="chrome://resources/polymer/v1_0/neon-animation/animations/fade-out-animation.html"> <link rel="import" href="chrome://resources/polymer/v1_0/neon-animation/neon-animatable.html"> @@ -39,6 +38,7 @@ 'focus' - If the current card is the first one, focuses password input. --> <dom-module id="saml-confirm-password"> + <link rel="stylesheet" href="oobe_flex_layout.css"> <link rel="stylesheet" href="saml_confirm_password.css"> <template>
diff --git a/chrome/browser/resources/chromeos/login/unrecoverable_cryptohome_error_card.html b/chrome/browser/resources/chromeos/login/unrecoverable_cryptohome_error_card.html index 0c98c25..54bffd26 100644 --- a/chrome/browser/resources/chromeos/login/unrecoverable_cryptohome_error_card.html +++ b/chrome/browser/resources/chromeos/login/unrecoverable_cryptohome_error_card.html
@@ -2,7 +2,6 @@ Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> <link rel="import" href="chrome://resources/polymer/v1_0/polymer/polymer.html"> <!-- @@ -15,6 +14,7 @@ 'done' - fired when user clicks on continue button. --> <dom-module id="unrecoverable-cryptohome-error-card"> + <link rel="stylesheet" href="oobe_flex_layout.css"> <link rel="stylesheet" href="unrecoverable_cryptohome_error_card.css"> <template>
diff --git a/chrome/browser/ui/cocoa/passwords/base_passwords_controller_test.mm b/chrome/browser/ui/cocoa/passwords/base_passwords_controller_test.mm index 33a4334..08a9ab7 100644 --- a/chrome/browser/ui/cocoa/passwords/base_passwords_controller_test.mm +++ b/chrome/browser/ui/cocoa/passwords/base_passwords_controller_test.mm
@@ -35,6 +35,8 @@ ui_controller_.reset(new testing::NiceMock<PasswordsModelDelegateMock>); ON_CALL(*ui_controller_, GetWebContents()) .WillByDefault(Return(test_web_contents_.get())); + ON_CALL(*ui_controller_, GetPasswordFormMetricsRecorder()) + .WillByDefault(Return(nullptr)); PasswordStoreFactory::GetInstance()->SetTestingFactoryAndUse( profile(), password_manager::BuildPasswordStore< content::BrowserContext,
diff --git a/chrome/browser/ui/passwords/manage_passwords_bubble_model.cc b/chrome/browser/ui/passwords/manage_passwords_bubble_model.cc index eab9341..63df6d8 100644 --- a/chrome/browser/ui/passwords/manage_passwords_bubble_model.cc +++ b/chrome/browser/ui/passwords/manage_passwords_bubble_model.cc
@@ -22,6 +22,7 @@ #include "chrome/grit/generated_resources.h" #include "components/browser_sync/profile_sync_service.h" #include "components/password_manager/core/browser/password_bubble_experiment.h" +#include "components/password_manager/core/browser/password_form_metrics_recorder.h" #include "components/password_manager/core/browser/password_manager_constants.h" #include "components/password_manager/core/browser/password_store.h" #include "components/password_manager/core/common/password_manager_pref_names.h" @@ -110,11 +111,16 @@ } private: + static password_manager::metrics_util::UIDismissalReason + ToNearestUIDismissalReason( + password_manager::metrics_util::UpdatePasswordSubmissionEvent + update_password_submission_event); + // The way the bubble appeared. const password_manager::metrics_util::UIDisplayDisposition display_disposition_; - // Dismissal reason for a bubble. + // Dismissal reason for a save bubble. Not filled for update bubbles. password_manager::metrics_util::UIDismissalReason dismissal_reason_; // Dismissal reason for the update bubble. @@ -216,6 +222,51 @@ if (update_password_submission_event_ != metrics_util::NO_UPDATE_SUBMISSION) LogUpdatePasswordSubmissionEvent(update_password_submission_event_); } + + // Record UKM statistics on dismissal reason. + if (model->delegate_ && model->delegate_->GetPasswordFormMetricsRecorder()) { + if (model->state() != password_manager::ui::PENDING_PASSWORD_UPDATE_STATE) { + model->delegate_->GetPasswordFormMetricsRecorder() + ->RecordUIDismissalReason(dismissal_reason_); + } else { + model->delegate_->GetPasswordFormMetricsRecorder() + ->RecordUIDismissalReason( + ToNearestUIDismissalReason(update_password_submission_event_)); + } + } +} + +// static +password_manager::metrics_util::UIDismissalReason +ManagePasswordsBubbleModel::InteractionKeeper::ToNearestUIDismissalReason( + password_manager::metrics_util::UpdatePasswordSubmissionEvent + update_password_submission_event) { + switch (update_password_submission_event) { + case password_manager::metrics_util::NO_ACCOUNTS_CLICKED_UPDATE: + case password_manager::metrics_util::ONE_ACCOUNT_CLICKED_UPDATE: + case password_manager::metrics_util::MULTIPLE_ACCOUNTS_CLICKED_UPDATE: + case password_manager::metrics_util::PASSWORD_OVERRIDDEN_CLICKED_UPDATE: + return password_manager::metrics_util::CLICKED_SAVE; + + case password_manager::metrics_util::NO_ACCOUNTS_CLICKED_NOPE: + case password_manager::metrics_util::ONE_ACCOUNT_CLICKED_NOPE: + case password_manager::metrics_util::MULTIPLE_ACCOUNTS_CLICKED_NOPE: + case password_manager::metrics_util::PASSWORD_OVERRIDDEN_CLICKED_NOPE: + return password_manager::metrics_util::CLICKED_CANCEL; + + case password_manager::metrics_util::NO_ACCOUNTS_NO_INTERACTION: + case password_manager::metrics_util::ONE_ACCOUNT_NO_INTERACTION: + case password_manager::metrics_util::MULTIPLE_ACCOUNTS_NO_INTERACTION: + case password_manager::metrics_util::PASSWORD_OVERRIDDEN_NO_INTERACTION: + case password_manager::metrics_util::NO_UPDATE_SUBMISSION: + return password_manager::metrics_util::NO_DIRECT_INTERACTION; + + case password_manager::metrics_util::UPDATE_PASSWORD_EVENT_COUNT: + // Not reached. + break; + } + NOTREACHED(); + return password_manager::metrics_util::NO_DIRECT_INTERACTION; } ManagePasswordsBubbleModel::ManagePasswordsBubbleModel( @@ -318,6 +369,11 @@ break; } } + + if (delegate_ && delegate_->GetPasswordFormMetricsRecorder()) { + delegate_->GetPasswordFormMetricsRecorder()->RecordPasswordBubbleShown( + delegate_->GetCredentialSource(), display_disposition); + } metrics_util::LogUIDisplayDisposition(display_disposition); interaction_keeper_.reset(new InteractionKeeper(std::move(interaction_stats), display_disposition));
diff --git a/chrome/browser/ui/passwords/manage_passwords_bubble_model_unittest.cc b/chrome/browser/ui/passwords/manage_passwords_bubble_model_unittest.cc index a93866b0..b3e03e2 100644 --- a/chrome/browser/ui/passwords/manage_passwords_bubble_model_unittest.cc +++ b/chrome/browser/ui/passwords/manage_passwords_bubble_model_unittest.cc
@@ -9,6 +9,7 @@ #include "base/memory/ptr_util.h" #include "base/metrics/histogram_samples.h" +#include "base/run_loop.h" #include "base/strings/utf_string_conversions.h" #include "base/test/histogram_tester.h" #include "base/test/simple_test_clock.h" @@ -19,6 +20,7 @@ #include "chrome/test/base/testing_profile.h" #include "components/browser_sync/profile_sync_service_mock.h" #include "components/password_manager/core/browser/mock_password_store.h" +#include "components/password_manager/core/browser/password_form_metrics_recorder.h" #include "components/password_manager/core/browser/password_manager_metrics_util.h" #include "components/password_manager/core/browser/password_manager_test_utils.h" #include "components/password_manager/core/browser/statistics_table.h" @@ -26,9 +28,12 @@ #include "components/password_manager/core/common/password_manager_pref_names.h" #include "components/password_manager/core/common/password_manager_ui.h" #include "components/prefs/pref_service.h" +#include "components/ukm/test_ukm_recorder.h" +#include "components/ukm/ukm_source.h" #include "content/public/browser/web_contents.h" #include "content/public/test/test_browser_thread_bundle.h" #include "content/public/test/web_contents_tester.h" +#include "services/metrics/public/cpp/ukm_recorder.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -104,7 +109,9 @@ void SetUp() override { test_web_contents_.reset( content::WebContentsTester::CreateTestWebContents(&profile_, nullptr)); - mock_delegate_.reset(new testing::StrictMock<PasswordsModelDelegateMock>); + mock_delegate_.reset(new PasswordsModelDelegateMock); + ON_CALL(*mock_delegate_, GetPasswordFormMetricsRecorder()) + .WillByDefault(Return(nullptr)); PasswordStoreFactory::GetInstance()->SetTestingFactoryAndUse( profile(), password_manager::BuildPasswordStore< @@ -279,7 +286,7 @@ EXPECT_CALL(*controller(), SavePassword()).Times(0); EXPECT_CALL(*controller(), NeverSavePassword()); model()->OnNeverForThisSiteClicked(); - EXPECT_EQ(password_manager::ui::PENDING_PASSWORD_STATE, model()->state()); + EXPECT_EQ(password_manager::ui::PENDING_PASSWORD_STATE, model()->state()); DestroyModelExpectReason(password_manager::metrics_util::CLICKED_NEVER); } @@ -473,3 +480,117 @@ ManagePasswordsBubbleModelManageLinkTest, ::testing::Values(TestSyncService::SyncedTypes::ALL, TestSyncService::SyncedTypes::NONE)); + +// Verify that URL keyed metrics are properly recorded. +TEST_F(ManagePasswordsBubbleModelTest, RecordUKMs) { + using BubbleDismissalReason = + password_manager::PasswordFormMetricsRecorder::BubbleDismissalReason; + using BubbleTrigger = + password_manager::PasswordFormMetricsRecorder::BubbleTrigger; + using password_manager::metrics_util::CredentialSourceType; + + // |credential_management_api| defines whether credentials originate from the + // credential management API. + for (const bool credential_management_api : {false, true}) { + // |update| defines whether this is an update or a save bubble. + for (const bool update : {false, true}) { + for (const auto interaction : + {BubbleDismissalReason::kAccepted, BubbleDismissalReason::kDeclined, + BubbleDismissalReason::kIgnored}) { + SCOPED_TRACE(testing::Message() + << "update = " << update + << ", interaction = " << static_cast<int64_t>(interaction) + << ", credential management api =" + << credential_management_api); + ukm::TestUkmRecorder test_ukm_recorder; + { + // Setup metrics recorder + ukm::SourceId source_id = test_ukm_recorder.GetNewSourceID(); + static_cast<ukm::UkmRecorder*>(&test_ukm_recorder) + ->UpdateSourceURL(source_id, GURL("https://www.example.com/")); + auto recorder = base::MakeRefCounted< + password_manager::PasswordFormMetricsRecorder>( + true /*is_main_frame_secure*/, + password_manager::PasswordFormMetricsRecorder:: + CreateUkmEntryBuilder(&test_ukm_recorder, source_id)); + + // Exercise bubble. + ON_CALL(*controller(), GetPasswordFormMetricsRecorder()) + .WillByDefault(Return(recorder.get())); + ON_CALL(*controller(), GetCredentialSource()) + .WillByDefault( + Return(credential_management_api + ? CredentialSourceType::kCredentialManagementAPI + : CredentialSourceType::kPasswordManager)); + + if (update) + PretendUpdatePasswordWaiting(); + else + PretendPasswordWaiting(); + + if (interaction == BubbleDismissalReason::kAccepted && update) { + autofill::PasswordForm form; + EXPECT_CALL(*controller(), UpdatePassword(form)); + model()->OnUpdateClicked(form); + } else if (interaction == BubbleDismissalReason::kAccepted && + !update) { + EXPECT_CALL(*GetStore(), + RemoveSiteStatsImpl(GURL(kSiteOrigin).GetOrigin())); + EXPECT_CALL(*controller(), SavePassword()); + model()->OnSaveClicked(); + } else if (interaction == BubbleDismissalReason::kDeclined && + update) { + EXPECT_CALL(*controller(), SavePassword()).Times(0); + model()->OnNopeUpdateClicked(); + } else if (interaction == BubbleDismissalReason::kDeclined && + !update) { + EXPECT_CALL(*GetStore(), + RemoveSiteStatsImpl(GURL(kSiteOrigin).GetOrigin())); + EXPECT_CALL(*controller(), SavePassword()).Times(0); + EXPECT_CALL(*controller(), NeverSavePassword()); + model()->OnNeverForThisSiteClicked(); + } else if (interaction == BubbleDismissalReason::kIgnored && update) { + EXPECT_CALL(*controller(), SavePassword()).Times(0); + EXPECT_CALL(*controller(), NeverSavePassword()).Times(0); + } else if (interaction == BubbleDismissalReason::kIgnored && + !update) { + EXPECT_CALL(*GetStore(), AddSiteStatsImpl(testing::_)); + EXPECT_CALL(*controller(), OnNoInteraction()); + EXPECT_CALL(*controller(), SavePassword()).Times(0); + EXPECT_CALL(*controller(), NeverSavePassword()).Times(0); + } else { + NOTREACHED(); + } + DestroyModel(); + } + // Flush async calls on password store. + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(controller())); + ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(GetStore())); + + // Verify metrics. + const ukm::UkmSource* source = + test_ukm_recorder.GetSourceForUrl("https://www.example.com/"); + ASSERT_TRUE(source); + test_ukm_recorder.ExpectMetric( + *source, "PasswordForm", + update ? password_manager::kUkmUpdatingPromptShown + : password_manager::kUkmSavingPromptShown, + 1); + test_ukm_recorder.ExpectMetric( + *source, "PasswordForm", + update ? password_manager::kUkmUpdatingPromptTrigger + : password_manager::kUkmSavingPromptTrigger, + static_cast<int64_t>( + credential_management_api + ? BubbleTrigger::kCredentialManagementAPIAutomatic + : BubbleTrigger::kPasswordManagerSuggestionAutomatic)); + test_ukm_recorder.ExpectMetric( + *source, "PasswordForm", + update ? password_manager::kUkmUpdatingPromptInteraction + : password_manager::kUkmSavingPromptInteraction, + static_cast<int64_t>(interaction)); + } + } + } +}
diff --git a/chrome/browser/ui/passwords/manage_passwords_ui_controller.cc b/chrome/browser/ui/passwords/manage_passwords_ui_controller.cc index 25c5cf770..bc4c761 100644 --- a/chrome/browser/ui/passwords/manage_passwords_ui_controller.cc +++ b/chrome/browser/ui/passwords/manage_passwords_ui_controller.cc
@@ -208,6 +208,15 @@ return passwords_data_.origin(); } +password_manager::PasswordFormMetricsRecorder* +ManagePasswordsUIController::GetPasswordFormMetricsRecorder() { + // The form manager may be null for example for auto sign-in toasts of the + // credential manager API. + password_manager::PasswordFormManager* form_manager = + passwords_data_.form_manager(); + return form_manager ? form_manager->metrics_recorder() : nullptr; +} + password_manager::ui::State ManagePasswordsUIController::GetState() const { return passwords_data_.state(); } @@ -226,6 +235,15 @@ return form_manager->pending_credentials(); } +password_manager::metrics_util::CredentialSourceType +ManagePasswordsUIController::GetCredentialSource() const { + password_manager::PasswordFormManager* form_manager = + passwords_data_.form_manager(); + return form_manager + ? form_manager->GetCredentialSource() + : password_manager::metrics_util::CredentialSourceType::kUnknown; +} + bool ManagePasswordsUIController::IsPasswordOverridden() const { const password_manager::PasswordFormManager* form_manager = passwords_data_.form_manager();
diff --git a/chrome/browser/ui/passwords/manage_passwords_ui_controller.h b/chrome/browser/ui/passwords/manage_passwords_ui_controller.h index dfcfa146..f325b3f 100644 --- a/chrome/browser/ui/passwords/manage_passwords_ui_controller.h +++ b/chrome/browser/ui/passwords/manage_passwords_ui_controller.h
@@ -85,8 +85,12 @@ // PasswordsModelDelegate: content::WebContents* GetWebContents() const override; const GURL& GetOrigin() const override; + password_manager::PasswordFormMetricsRecorder* + GetPasswordFormMetricsRecorder() override; password_manager::ui::State GetState() const override; const autofill::PasswordForm& GetPendingPassword() const override; + password_manager::metrics_util::CredentialSourceType GetCredentialSource() + const override; bool IsPasswordOverridden() const override; const std::vector<std::unique_ptr<autofill::PasswordForm>>& GetCurrentForms() const override;
diff --git a/chrome/browser/ui/passwords/passwords_model_delegate.h b/chrome/browser/ui/passwords/passwords_model_delegate.h index c904b2b..c8339c93 100644 --- a/chrome/browser/ui/passwords/passwords_model_delegate.h +++ b/chrome/browser/ui/passwords/passwords_model_delegate.h
@@ -19,7 +19,11 @@ } namespace password_manager { struct InteractionsStats; -} +class PasswordFormMetricsRecorder; +namespace metrics_util { +enum class CredentialSourceType; +} // namespace metrics_util +} // namespace password_manager class GURL; // An interface for ManagePasswordsBubbleModel implemented by @@ -30,6 +34,12 @@ // Returns WebContents* the model is attached to. virtual content::WebContents* GetWebContents() const = 0; + // Returns the password_manager::PasswordFormMetricsRecorder that is + // associated with the PasswordFormManager that governs the password being + // submitted. + virtual password_manager::PasswordFormMetricsRecorder* + GetPasswordFormMetricsRecorder() = 0; + // Returns the URL of the site the current forms are retrieved for. virtual const GURL& GetOrigin() const = 0; @@ -41,6 +51,10 @@ // the returned credential in AUTO_SIGNIN_STATE. virtual const autofill::PasswordForm& GetPendingPassword() const = 0; + // Returns the source of the credential to be saved. + virtual password_manager::metrics_util::CredentialSourceType + GetCredentialSource() const = 0; + // True if the password for previously stored account was overridden, i.e. in // newly submitted form the password is different from stored one. virtual bool IsPasswordOverridden() const = 0;
diff --git a/chrome/browser/ui/passwords/passwords_model_delegate_mock.h b/chrome/browser/ui/passwords/passwords_model_delegate_mock.h index 87bd2a54..6f999293 100644 --- a/chrome/browser/ui/passwords/passwords_model_delegate_mock.h +++ b/chrome/browser/ui/passwords/passwords_model_delegate_mock.h
@@ -18,9 +18,13 @@ ~PasswordsModelDelegateMock() override; MOCK_CONST_METHOD0(GetWebContents, content::WebContents*()); + MOCK_METHOD0(GetPasswordFormMetricsRecorder, + password_manager::PasswordFormMetricsRecorder*()); MOCK_CONST_METHOD0(GetOrigin, const GURL&()); MOCK_CONST_METHOD0(GetState, password_manager::ui::State()); MOCK_CONST_METHOD0(GetPendingPassword, const autofill::PasswordForm&()); + MOCK_CONST_METHOD0(GetCredentialSource, + password_manager::metrics_util::CredentialSourceType()); MOCK_CONST_METHOD0(IsPasswordOverridden, bool()); MOCK_CONST_METHOD0( GetCurrentForms,
diff --git a/components/exo/BUILD.gn b/components/exo/BUILD.gn index 4a4fc560..a496d19d 100644 --- a/components/exo/BUILD.gn +++ b/components/exo/BUILD.gn
@@ -17,6 +17,7 @@ "keyboard.h", "keyboard_delegate.h", "keyboard_device_configuration_delegate.h", + "keyboard_observer.h", "layer_tree_frame_sink_holder.cc", "layer_tree_frame_sink_holder.h", "notification_surface.cc",
diff --git a/components/exo/keyboard.cc b/components/exo/keyboard.cc index fd261e1..f45174b4 100644 --- a/components/exo/keyboard.cc +++ b/components/exo/keyboard.cc
@@ -102,9 +102,8 @@ } Keyboard::~Keyboard() { - delegate_->OnKeyboardDestroying(this); - if (device_configuration_delegate_) - device_configuration_delegate_->OnKeyboardDestroying(this); + for (KeyboardObserver& observer : observer_list_) + observer.OnKeyboardDestroying(this); if (focus_) focus_->RemoveSurfaceObserver(this); auto* helper = WMHelper::GetInstance(); @@ -124,6 +123,30 @@ OnKeyboardDeviceConfigurationChanged(); } +void Keyboard::AddObserver(KeyboardObserver* observer) { + observer_list_.AddObserver(observer); +} + +bool Keyboard::HasObserver(KeyboardObserver* observer) const { + return observer_list_.HasObserver(observer); +} + +void Keyboard::RemoveObserver(KeyboardObserver* observer) { + observer_list_.HasObserver(observer); +} + +void Keyboard::SetNeedKeyboardKeyAcks(bool need_acks) { + are_keyboard_key_acks_needed = need_acks; +} + +bool Keyboard::AreKeyboardKeyAcksNeeded() const { + return are_keyboard_key_acks_needed; +} + +void Keyboard::AckKeyboardKey(uint32_t serial, bool handled) { + // TODO(yhanada): Implement this method. See http://b/28104183. +} + //////////////////////////////////////////////////////////////////////////////// // ui::EventHandler overrides:
diff --git a/components/exo/keyboard.h b/components/exo/keyboard.h index 98c4a4a..86e9ff0 100644 --- a/components/exo/keyboard.h +++ b/components/exo/keyboard.h
@@ -8,6 +8,7 @@ #include <vector> #include "base/macros.h" +#include "components/exo/keyboard_observer.h" #include "components/exo/surface_observer.h" #include "components/exo/wm_helper.h" #include "ui/events/event_handler.h" @@ -37,6 +38,16 @@ void SetDeviceConfigurationDelegate( KeyboardDeviceConfigurationDelegate* delegate); + // Management of the observer list. + void AddObserver(KeyboardObserver* observer); + bool HasObserver(KeyboardObserver* observer) const; + void RemoveObserver(KeyboardObserver* observer); + + void SetNeedKeyboardKeyAcks(bool need_acks); + bool AreKeyboardKeyAcksNeeded() const; + + void AckKeyboardKey(uint32_t serial, bool handled); + // Overridden from ui::EventHandler: void OnKeyEvent(ui::KeyEvent* event) override; @@ -67,6 +78,8 @@ // to. KeyboardDeviceConfigurationDelegate* device_configuration_delegate_ = nullptr; + bool are_keyboard_key_acks_needed = false; + // The current focus surface for the keyboard. Surface* focus_ = nullptr; @@ -76,6 +89,8 @@ // Current set of modifier flags. int modifier_flags_ = 0; + base::ObserverList<KeyboardObserver> observer_list_; + DISALLOW_COPY_AND_ASSIGN(Keyboard); };
diff --git a/components/exo/keyboard_delegate.h b/components/exo/keyboard_delegate.h index 619f56a8..9e73cae 100644 --- a/components/exo/keyboard_delegate.h +++ b/components/exo/keyboard_delegate.h
@@ -14,16 +14,11 @@ } namespace exo { -class Keyboard; class Surface; // Handles events on keyboards in context-specific ways. class KeyboardDelegate { public: - // Called at the top of the keyboard's destructor, to give observers a - // chance to remove themselves. - virtual void OnKeyboardDestroying(Keyboard* keyboard) = 0; - // This should return true if |surface| is a valid target for this keyboard. // E.g. the surface is owned by the same client as the keyboard. virtual bool CanAcceptKeyboardEventsForSurface(Surface* surface) const = 0;
diff --git a/components/exo/keyboard_device_configuration_delegate.h b/components/exo/keyboard_device_configuration_delegate.h index 263be3d6..aa80e4c 100644 --- a/components/exo/keyboard_device_configuration_delegate.h +++ b/components/exo/keyboard_device_configuration_delegate.h
@@ -6,15 +6,10 @@ #define COMPONENTS_EXO_KEYBOARD_DEVICE_CONFIGURATION_H_ namespace exo { -class Keyboard; // Used as an extension to the KeyboardDelegate. class KeyboardDeviceConfigurationDelegate { public: - // Called at the top of the keyboard's destructor, to give observers a chance - // to remove themselves. - virtual void OnKeyboardDestroying(Keyboard* keyboard) = 0; - // Called when used keyboard type changed. virtual void OnKeyboardTypeChanged(bool is_physical) = 0;
diff --git a/components/exo/keyboard_observer.h b/components/exo/keyboard_observer.h new file mode 100644 index 0000000..1264132 --- /dev/null +++ b/components/exo/keyboard_observer.h
@@ -0,0 +1,23 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_EXO_KEYBOARD_OBSERVER_H_ +#define COMPONENTS_EXO_KEYBOARD_OBSERVER_H_ + +namespace exo { +class Keyboard; + +// Observers to the Keyboard are notified when the Keyboard destructs. +class KeyboardObserver { + public: + virtual ~KeyboardObserver() {} + + // Called at the top of the keyboard's destructor, to give observers a change + // to remove themselves. + virtual void OnKeyboardDestroying(Keyboard* keyboard) = 0; +}; + +} // namespace exo + +#endif // COMPONENTS_EXO_KEYBOARD_OBSERVER_H_
diff --git a/components/exo/keyboard_unittest.cc b/components/exo/keyboard_unittest.cc index 74fed6ea..cc00b4e2 100644 --- a/components/exo/keyboard_unittest.cc +++ b/components/exo/keyboard_unittest.cc
@@ -10,6 +10,7 @@ #include "components/exo/buffer.h" #include "components/exo/keyboard_delegate.h" #include "components/exo/keyboard_device_configuration_delegate.h" +#include "components/exo/keyboard_observer.h" #include "components/exo/shell_surface.h" #include "components/exo/surface.h" #include "components/exo/test/exo_test_base.h" @@ -49,6 +50,14 @@ MOCK_METHOD1(OnKeyboardTypeChanged, void(bool)); }; +class MockKeyboardObserver : public KeyboardObserver { + public: + MockKeyboardObserver() {} + + // Overridden from KeyboardObserver: + MOCK_METHOD1(OnKeyboardDestroying, void(Keyboard*)); +}; + TEST_F(KeyboardTest, OnKeyboardEnter) { std::unique_ptr<Surface> surface(new Surface); std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get())); @@ -86,7 +95,6 @@ // Surface should maintain keyboard focus when moved to top-level window. focus_client->FocusWindow(surface->window()->GetToplevelWindow()); - EXPECT_CALL(delegate, OnKeyboardDestroying(keyboard.get())); keyboard.reset(); } @@ -116,7 +124,6 @@ EXPECT_CALL(delegate, OnKeyboardLeave(surface.get())); focus_client->FocusWindow(nullptr); - EXPECT_CALL(delegate, OnKeyboardDestroying(keyboard.get())); keyboard.reset(); } @@ -155,7 +162,6 @@ generator.ReleaseKey(ui::VKEY_A, 0); generator.ReleaseKey(ui::VKEY_A, 0); - EXPECT_CALL(delegate, OnKeyboardDestroying(keyboard.get())); keyboard.reset(); } @@ -199,7 +205,6 @@ EXPECT_CALL(delegate, OnKeyboardModifiers(0)); generator.ReleaseKey(ui::VKEY_B, 0); - EXPECT_CALL(delegate, OnKeyboardDestroying(keyboard.get())); keyboard.reset(); } @@ -245,12 +250,57 @@ static_cast<ui::DeviceHotplugEventObserver*>(device_data_manager) ->OnKeyboardDevicesUpdated(keyboards); - EXPECT_CALL(delegate, OnKeyboardDestroying(keyboard.get())); - EXPECT_CALL(configuration_delegate, OnKeyboardDestroying(keyboard.get())); keyboard.reset(); maximize_mode_controller->EnableMaximizeModeWindowManager(false); } +TEST_F(KeyboardTest, KeyboardObserver) { + std::unique_ptr<Surface> surface(new Surface); + std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get())); + gfx::Size buffer_size(10, 10); + std::unique_ptr<Buffer> buffer( + new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size))); + surface->Attach(buffer.get()); + surface->Commit(); + + aura::client::FocusClient* focus_client = + aura::client::GetFocusClient(ash::Shell::GetPrimaryRootWindow()); + focus_client->FocusWindow(nullptr); + + MockKeyboardDelegate delegate; + auto keyboard = base::MakeUnique<Keyboard>(&delegate); + MockKeyboardObserver observer; + keyboard->AddObserver(&observer); + + EXPECT_CALL(observer, OnKeyboardDestroying(keyboard.get())); + keyboard.reset(); +} + +TEST_F(KeyboardTest, NeedKeyboardKeyAcks) { + std::unique_ptr<Surface> surface(new Surface); + std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get())); + gfx::Size buffer_size(10, 10); + std::unique_ptr<Buffer> buffer( + new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size))); + surface->Attach(buffer.get()); + surface->Commit(); + + aura::client::FocusClient* focus_client = + aura::client::GetFocusClient(ash::Shell::GetPrimaryRootWindow()); + focus_client->FocusWindow(nullptr); + + MockKeyboardDelegate delegate; + auto keyboard = base::MakeUnique<Keyboard>(&delegate); + + EXPECT_FALSE(keyboard->AreKeyboardKeyAcksNeeded()); + keyboard->SetNeedKeyboardKeyAcks(true); + EXPECT_TRUE(keyboard->AreKeyboardKeyAcksNeeded()); + keyboard->SetNeedKeyboardKeyAcks(false); + EXPECT_FALSE(keyboard->AreKeyboardKeyAcksNeeded()); + + keyboard.reset(); +} + } // namespace } // namespace exo
diff --git a/components/exo/wayland/BUILD.gn b/components/exo/wayland/BUILD.gn index 55aae92..dc01f94 100644 --- a/components/exo/wayland/BUILD.gn +++ b/components/exo/wayland/BUILD.gn
@@ -40,6 +40,7 @@ "//third_party/wayland-protocols:alpha_compositing_protocol", "//third_party/wayland-protocols:gaming_input_protocol", "//third_party/wayland-protocols:keyboard_configuration_protocol", + "//third_party/wayland-protocols:keyboard_extension_protocol", "//third_party/wayland-protocols:presentation_time_protocol", "//third_party/wayland-protocols:remote_shell_protocol", "//third_party/wayland-protocols:secure_output_protocol",
diff --git a/components/exo/wayland/server.cc b/components/exo/wayland/server.cc index d477e07c..20880dc 100644 --- a/components/exo/wayland/server.cc +++ b/components/exo/wayland/server.cc
@@ -9,6 +9,7 @@ #include <gaming-input-unstable-v2-server-protocol.h> #include <grp.h> #include <keyboard-configuration-unstable-v1-server-protocol.h> +#include <keyboard-extension-unstable-v1-server-protocol.h> #include <linux/input.h> #include <presentation-time-server-protocol.h> #include <remote-shell-unstable-v1-server-protocol.h> @@ -54,6 +55,7 @@ #include "components/exo/keyboard.h" #include "components/exo/keyboard_delegate.h" #include "components/exo/keyboard_device_configuration_delegate.h" +#include "components/exo/keyboard_observer.h" #include "components/exo/notification_surface.h" #include "components/exo/notification_surface_manager.h" #include "components/exo/pointer.h" @@ -2843,11 +2845,13 @@ // Keyboard delegate class that accepts events for surfaces owned by the same // client as a keyboard resource. class WaylandKeyboardDelegate - : public KeyboardDelegate + : public KeyboardDelegate, + public KeyboardObserver #if defined(USE_OZONE) && defined(OS_CHROMEOS) - , public chromeos::input_method::ImeKeyboard::Observer + , + public chromeos::input_method::ImeKeyboard::Observer #endif - { +{ public: explicit WaylandKeyboardDelegate(wl_resource* keyboard_resource) : keyboard_resource_(keyboard_resource), @@ -3133,9 +3137,12 @@ wl_resource* keyboard_resource = wl_resource_create(client, &wl_keyboard_interface, version, id); + WaylandKeyboardDelegate* delegate = + new WaylandKeyboardDelegate(keyboard_resource); + std::unique_ptr<Keyboard> keyboard = base::MakeUnique<Keyboard>(delegate); + keyboard->AddObserver(delegate); SetImplementation(keyboard_resource, &keyboard_implementation, - base::MakeUnique<Keyboard>( - new WaylandKeyboardDelegate(keyboard_resource))); + std::move(keyboard)); // TODO(reveman): Keep repeat info synchronized with chromium and the host OS. if (version >= WL_KEYBOARD_REPEAT_INFO_SINCE_VERSION) @@ -3921,16 +3928,20 @@ // keyboard_device_configuration interface: class WaylandKeyboardDeviceConfigurationDelegate - : public KeyboardDeviceConfigurationDelegate { + : public KeyboardDeviceConfigurationDelegate, + public KeyboardObserver { public: WaylandKeyboardDeviceConfigurationDelegate(wl_resource* resource, Keyboard* keyboard) : resource_(resource), keyboard_(keyboard) { keyboard_->SetDeviceConfigurationDelegate(this); + keyboard_->AddObserver(this); } ~WaylandKeyboardDeviceConfigurationDelegate() override { - if (keyboard_) + if (keyboard_) { keyboard_->SetDeviceConfigurationDelegate(nullptr); + keyboard_->RemoveObserver(this); + } } void OnKeyboardDestroying(Keyboard* keyboard) override { @@ -4083,6 +4094,93 @@ nullptr); } +//////////////////////////////////////////////////////////////////////////////// +// extended_keyboard interface: + +class WaylandExtendedKeyboardImpl : public KeyboardObserver { + public: + WaylandExtendedKeyboardImpl(Keyboard* keyboard) : keyboard_(keyboard) { + keyboard_->AddObserver(this); + keyboard_->SetNeedKeyboardKeyAcks(true); + } + ~WaylandExtendedKeyboardImpl() override { + if (keyboard_) { + keyboard_->RemoveObserver(this); + keyboard_->SetNeedKeyboardKeyAcks(false); + } + } + + // Overridden from KeyboardObserver: + void OnKeyboardDestroying(Keyboard* keyboard) override { + DCHECK(keyboard_ == keyboard); + keyboard_ = nullptr; + } + + void AckKeyboardKey(uint32_t serial, bool handled) { + if (keyboard_) + keyboard_->AckKeyboardKey(serial, handled); + } + + private: + Keyboard* keyboard_; + + DISALLOW_COPY_AND_ASSIGN(WaylandExtendedKeyboardImpl); +}; + +void extended_keyboard_destroy(wl_client* client, wl_resource* resource) { + wl_resource_destroy(resource); +} + +void extended_keyboard_ack_key(wl_client* client, + wl_resource* resource, + uint32_t serial, + uint32_t handled_state) { + GetUserDataAs<WaylandExtendedKeyboardImpl>(resource)->AckKeyboardKey( + serial, handled_state == ZCR_EXTENDED_KEYBOARD_V1_HANDLED_STATE_HANDLED); +} + +const struct zcr_extended_keyboard_v1_interface + extended_keyboard_implementation = {extended_keyboard_destroy, + extended_keyboard_ack_key}; + +//////////////////////////////////////////////////////////////////////////////// +// keyboard_extension interface: + +void keyboard_extension_get_extended_keyboard(wl_client* client, + wl_resource* resource, + uint32_t id, + wl_resource* keyboard_resource) { + Keyboard* keyboard = GetUserDataAs<Keyboard>(keyboard_resource); + if (keyboard->AreKeyboardKeyAcksNeeded()) { + wl_resource_post_error( + resource, ZCR_KEYBOARD_EXTENSION_V1_ERROR_EXTENDED_KEYBOARD_EXISTS, + "keyboard has already been associated with a extended_keyboard object"); + return; + } + + wl_resource* extended_keyboard_resource = + wl_resource_create(client, &zcr_extended_keyboard_v1_interface, 1, id); + + SetImplementation(extended_keyboard_resource, + &extended_keyboard_implementation, + base::MakeUnique<WaylandExtendedKeyboardImpl>(keyboard)); +} + +const struct zcr_keyboard_extension_v1_interface + keyboard_extension_implementation = { + keyboard_extension_get_extended_keyboard}; + +void bind_keyboard_extension(wl_client* client, + void* data, + uint32_t version, + uint32_t id) { + wl_resource* resource = wl_resource_create( + client, &zcr_keyboard_extension_v1_interface, version, id); + + wl_resource_set_implementation(resource, &keyboard_extension_implementation, + data, nullptr); +} + } // namespace //////////////////////////////////////////////////////////////////////////////// @@ -4137,6 +4235,8 @@ 2, display_, bind_keyboard_configuration); wl_global_create(wl_display_.get(), &zcr_stylus_tools_v1_interface, 1, display_, bind_stylus_tools); + wl_global_create(wl_display_.get(), &zcr_keyboard_extension_v1_interface, 1, + display_, bind_keyboard_extension); } Server::~Server() {}
diff --git a/components/password_manager/core/browser/credential_manager_password_form_manager.cc b/components/password_manager/core/browser/credential_manager_password_form_manager.cc index 4406d19..ee3dd044 100644 --- a/components/password_manager/core/browser/credential_manager_password_form_manager.cc +++ b/components/password_manager/core/browser/credential_manager_password_form_manager.cc
@@ -68,6 +68,11 @@ weak_factory_.GetWeakPtr())); } +metrics_util::CredentialSourceType +CredentialManagerPasswordFormManager::GetCredentialSource() { + return metrics_util::CredentialSourceType::kCredentialManagementAPI; +} + void CredentialManagerPasswordFormManager::NotifyDelegate() { delegate_->OnProvisionalSaveComplete(); }
diff --git a/components/password_manager/core/browser/credential_manager_password_form_manager.h b/components/password_manager/core/browser/credential_manager_password_form_manager.h index 4816e17..6f052bb 100644 --- a/components/password_manager/core/browser/credential_manager_password_form_manager.h +++ b/components/password_manager/core/browser/credential_manager_password_form_manager.h
@@ -52,6 +52,8 @@ const std::vector<const autofill::PasswordForm*>& non_federated, size_t filtered_count) override; + metrics_util::CredentialSourceType GetCredentialSource() override; + private: // Calls OnProvisionalSaveComplete on |delegate_|. void NotifyDelegate();
diff --git a/components/password_manager/core/browser/credential_manager_password_form_manager_unittest.cc b/components/password_manager/core/browser/credential_manager_password_form_manager_unittest.cc index 403f8ae..585d88e0 100644 --- a/components/password_manager/core/browser/credential_manager_password_form_manager_unittest.cc +++ b/components/password_manager/core/browser/credential_manager_password_form_manager_unittest.cc
@@ -75,4 +75,18 @@ EXPECT_FALSE(form_manager); } +// Ensure that GetCredentialSource is actually overriden and returns the proper +// value. +TEST_F(CredentialManagerPasswordFormManagerTest, GetCredentialSource) { + PasswordForm observed_form; + MockDelegate delegate; + auto form_manager = base::MakeUnique<CredentialManagerPasswordFormManager>( + &client_, driver_.AsWeakPtr(), observed_form, + base::MakeUnique<PasswordForm>(observed_form), &delegate, + base::MakeUnique<StubFormSaver>(), base::MakeUnique<FakeFormFetcher>()); + form_manager->Init(nullptr); + ASSERT_EQ(metrics_util::CredentialSourceType::kCredentialManagementAPI, + form_manager->GetCredentialSource()); +} + } // namespace password_manager
diff --git a/components/password_manager/core/browser/password_form_manager.cc b/components/password_manager/core/browser/password_form_manager.cc index 3f4c74a..e6677737 100644 --- a/components/password_manager/core/browser/password_form_manager.cc +++ b/components/password_manager/core/browser/password_form_manager.cc
@@ -1361,6 +1361,10 @@ return result; } +metrics_util::CredentialSourceType PasswordFormManager::GetCredentialSource() { + return metrics_util::CredentialSourceType::kPasswordManager; +} + void PasswordFormManager::SendVotesOnSave() { if (observed_form_.IsPossibleChangePasswordFormWithoutUsername()) return;
diff --git a/components/password_manager/core/browser/password_form_manager.h b/components/password_manager/core/browser/password_form_manager.h index 32c6a5ef..2cf48e7 100644 --- a/components/password_manager/core/browser/password_form_manager.h +++ b/components/password_manager/core/browser/password_form_manager.h
@@ -24,6 +24,7 @@ #include "components/password_manager/core/browser/password_form_metrics_recorder.h" #include "components/password_manager/core/browser/password_form_user_action.h" #include "components/password_manager/core/browser/password_manager_driver.h" +#include "components/password_manager/core/browser/password_manager_metrics_util.h" #include "components/password_manager/core/browser/password_store.h" using autofill::FormData; @@ -275,6 +276,10 @@ // adds itself as a consumer of the new one. void GrabFetcher(std::unique_ptr<FormFetcher> fetcher); + PasswordFormMetricsRecorder* metrics_recorder() { + return metrics_recorder_.get(); + } + // Create a copy of |*this| which can be passed to the code handling // save-password related UI. This omits some parts of the internal data, so // the result is not identical to the original. @@ -282,6 +287,11 @@ // another one. std::unique_ptr<PasswordFormManager> Clone(); + // Returns who created this PasswordFormManager. The Credential Management API + // uses a derived class of the PasswordFormManager that can indicate its + // origin. + virtual metrics_util::CredentialSourceType GetCredentialSource(); + protected: // FormFetcher::Consumer: void ProcessMatches(
diff --git a/components/password_manager/core/browser/password_form_manager_unittest.cc b/components/password_manager/core/browser/password_form_manager_unittest.cc index b474e5f..de82490 100644 --- a/components/password_manager/core/browser/password_form_manager_unittest.cc +++ b/components/password_manager/core/browser/password_form_manager_unittest.cc
@@ -3759,8 +3759,7 @@ const auto* source = test_ukm_recorder.GetSourceForUrl(form_to_fill.origin.spec().c_str()); test_ukm_recorder.ExpectMetric(*source, "PasswordForm", - internal::kUkmManagerFillEvent, - test.expected_event); + kUkmManagerFillEvent, test.expected_event); } }
diff --git a/components/password_manager/core/browser/password_form_metrics_recorder.cc b/components/password_manager/core/browser/password_form_metrics_recorder.cc index 60bc42b..3f0ff46f 100644 --- a/components/password_manager/core/browser/password_form_metrics_recorder.cc +++ b/components/password_manager/core/browser/password_form_metrics_recorder.cc
@@ -43,7 +43,7 @@ metrics_util::LogPasswordGenerationAvailableSubmissionEvent( metrics_util::PASSWORD_NOT_SUBMITTED); } - RecordUkmMetric(internal::kUkmSubmissionObserved, 0 /*false*/); + RecordUkmMetric(kUkmSubmissionObserved, 0 /*false*/); } if (submitted_form_type_ != kSubmittedFormTypeUnspecified) { @@ -54,8 +54,11 @@ submitted_form_type_, kSubmittedFormTypeMax); } - RecordUkmMetric(internal::kUkmSubmissionFormType, submitted_form_type_); + RecordUkmMetric(kUkmSubmissionFormType, submitted_form_type_); } + + RecordUkmMetric(kUkmUpdatingPromptShown, update_prompt_shown_); + RecordUkmMetric(kUkmSavingPromptShown, save_prompt_shown_); } // static @@ -112,8 +115,8 @@ } } base::RecordAction(base::UserMetricsAction("PasswordManager_LoginPassed")); - RecordUkmMetric(internal::kUkmSubmissionObserved, 1 /*true*/); - RecordUkmMetric(internal::kUkmSubmissionResult, kSubmitResultPassed); + RecordUkmMetric(kUkmSubmissionObserved, 1 /*true*/); + RecordUkmMetric(kUkmSubmissionResult, kSubmitResultPassed); submit_result_ = kSubmitResultPassed; } @@ -126,8 +129,8 @@ metrics_util::PASSWORD_SUBMISSION_FAILED); } base::RecordAction(base::UserMetricsAction("PasswordManager_LoginFailed")); - RecordUkmMetric(internal::kUkmSubmissionObserved, 1 /*true*/); - RecordUkmMetric(internal::kUkmSubmissionResult, kSubmitResultFailed); + RecordUkmMetric(kUkmSubmissionObserved, 1 /*true*/); + RecordUkmMetric(kUkmSubmissionResult, kSubmitResultFailed); submit_result_ = kSubmitResultFailed; } @@ -223,8 +226,111 @@ RecordUkmMetric("SuppressedAccount.Manual.SameOrganizationName", best_match); } +void PasswordFormMetricsRecorder::RecordPasswordBubbleShown( + metrics_util::CredentialSourceType credential_source_type, + metrics_util::UIDisplayDisposition display_disposition) { + if (credential_source_type == metrics_util::CredentialSourceType::kUnknown) + return; + BubbleTrigger automatic_trigger_type = + credential_source_type == + metrics_util::CredentialSourceType::kPasswordManager + ? BubbleTrigger::kPasswordManagerSuggestionAutomatic + : BubbleTrigger::kCredentialManagementAPIAutomatic; + BubbleTrigger manual_trigger_type = + credential_source_type == + metrics_util::CredentialSourceType::kPasswordManager + ? BubbleTrigger::kPasswordManagerSuggestionManual + : BubbleTrigger::kCredentialManagementAPIManual; + + switch (display_disposition) { + // New credential cases: + case metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING: + save_prompt_shown_ = true; + RecordUkmMetric(kUkmSavingPromptTrigger, + static_cast<int64_t>(automatic_trigger_type)); + break; + case metrics_util::MANUAL_WITH_PASSWORD_PENDING: + save_prompt_shown_ = true; + RecordUkmMetric(kUkmSavingPromptTrigger, + static_cast<int64_t>(manual_trigger_type)); + break; + + // Update cases: + case metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING_UPDATE: + update_prompt_shown_ = true; + RecordUkmMetric(kUkmUpdatingPromptTrigger, + static_cast<int64_t>(automatic_trigger_type)); + break; + case metrics_util::MANUAL_WITH_PASSWORD_PENDING_UPDATE: + update_prompt_shown_ = true; + RecordUkmMetric(kUkmUpdatingPromptTrigger, + static_cast<int64_t>(manual_trigger_type)); + break; + + // Other reasons to show a bubble: + case metrics_util::MANUAL_MANAGE_PASSWORDS: + case metrics_util::AUTOMATIC_GENERATED_PASSWORD_CONFIRMATION: + case metrics_util::AUTOMATIC_SIGNIN_TOAST: + // Do nothing. + return; + + // Obsolte display dispositions: + case metrics_util::MANUAL_BLACKLISTED_OBSOLETE: + case metrics_util::AUTOMATIC_CREDENTIAL_REQUEST_OBSOLETE: + case metrics_util::NUM_DISPLAY_DISPOSITIONS: + NOTREACHED(); + return; + } +} + +void PasswordFormMetricsRecorder::RecordUIDismissalReason( + metrics_util::UIDismissalReason ui_dismissal_reason) { + DCHECK(!(update_prompt_shown_ && save_prompt_shown_)); + if (!(update_prompt_shown_ || save_prompt_shown_)) + return; + const char* metric = update_prompt_shown_ ? kUkmUpdatingPromptInteraction + : kUkmSavingPromptInteraction; + switch (ui_dismissal_reason) { + // Accepted by user. + case metrics_util::CLICKED_SAVE: + RecordUkmMetric(metric, + static_cast<int64_t>(BubbleDismissalReason::kAccepted)); + break; + + // Declined by user. + case metrics_util::CLICKED_CANCEL: + case metrics_util::CLICKED_NEVER: + RecordUkmMetric(metric, + static_cast<int64_t>(BubbleDismissalReason::kDeclined)); + break; + + // Ignored by user. + case metrics_util::NO_DIRECT_INTERACTION: + RecordUkmMetric(metric, + static_cast<int64_t>(BubbleDismissalReason::kIgnored)); + break; + + // Ignore these for metrics collection: + case metrics_util::CLICKED_MANAGE: + case metrics_util::CLICKED_DONE: + case metrics_util::CLICKED_OK: + case metrics_util::CLICKED_BRAND_NAME: + case metrics_util::CLICKED_PASSWORDS_DASHBOARD: + case metrics_util::AUTO_SIGNIN_TOAST_TIMEOUT: + break; + + // These should not reach here: + case metrics_util::CLICKED_UNBLACKLIST_OBSOLETE: + case metrics_util::CLICKED_CREDENTIAL_OBSOLETE: + case metrics_util::AUTO_SIGNIN_TOAST_CLICKED_OBSOLETE: + case metrics_util::NUM_UI_RESPONSES: + NOTREACHED(); + break; + } +} + void PasswordFormMetricsRecorder::RecordFillEvent(ManagerAutofillEvent event) { - RecordUkmMetric(internal::kUkmManagerFillEvent, event); + RecordUkmMetric(kUkmManagerFillEvent, event); } PasswordFormMetricsRecorder::SuppressedAccountExistence
diff --git a/components/password_manager/core/browser/password_form_metrics_recorder.h b/components/password_manager/core/browser/password_form_metrics_recorder.h index f1c5ceb..63896bc 100644 --- a/components/password_manager/core/browser/password_form_metrics_recorder.h +++ b/components/password_manager/core/browser/password_form_metrics_recorder.h
@@ -14,13 +14,12 @@ #include "base/memory/ref_counted.h" #include "components/autofill/core/common/password_form.h" #include "components/password_manager/core/browser/password_form_user_action.h" +#include "components/password_manager/core/browser/password_manager_metrics_util.h" #include "services/metrics/public/cpp/ukm_recorder.h" namespace password_manager { -// Internal namespace is intended for component wide access only. -namespace internal { -// UKM Metric names. Exposed in internal namespace for unittesting. +// URL Keyed Metrics. // This metric records whether a submission of a password form has been // observed. The values 0 and 1 correspond to false and true respectively. @@ -36,10 +35,36 @@ // Note that no metric is recorded for kSubmittedFormTypeUnspecified. constexpr char kUkmSubmissionFormType[] = "Submission.SubmittedFormType"; +// This metric records the boolean value indicating whether a password update +// prompt was shown, which asked the user for permission to update a password. +constexpr char kUkmUpdatingPromptShown[] = "Updating.Prompt.Shown"; + +// This metric records the reason why a password update prompt was shown to ask +// the user for permission to update a password. The values correspond to +// PasswordFormMetricsRecorder::BubbleTrigger. +constexpr char kUkmUpdatingPromptTrigger[] = "Updating.Prompt.Trigger"; + +// This metric records how a user interacted with an updating prompt. The values +// correspond to PasswordFormMetricsRecorder::BubbleDismissalReason. +constexpr char kUkmUpdatingPromptInteraction[] = "Updating.Prompt.Interaction"; + +// This metric records the boolean value indicating whether a password save +// prompt was shown, which asked the user for permission to save a new +// credential. +constexpr char kUkmSavingPromptShown[] = "Saving.Prompt.Shown"; + +// This metric records the reason why a password save prompt was shown to ask +// the user for permission to save a new credential. The values correspond to +// PasswordFormMetricsRecorder::BubbleTrigger. +constexpr char kUkmSavingPromptTrigger[] = "Saving.Prompt.Trigger"; + +// This metric records how a user interacted with a saving prompt. The values +// correspond to PasswordFormMetricsRecorder::BubbleDismissalReason. +constexpr char kUkmSavingPromptInteraction[] = "Saving.Prompt.Interaction"; + // This metric records attempts to fill a password form. Values correspond to // PasswordFormMetricsRecorder::ManagerFillEvent. constexpr char kUkmManagerFillEvent[] = "ManagerFill.Action"; -} // namespace internal class FormFetcher; @@ -147,6 +172,28 @@ kSubmittedFormTypeMax }; + // The reason why a password bubble was shown on the screen. + enum class BubbleTrigger { + kUnknown = 0, + // The password manager suggests the user to save a password and asks for + // confirmation. + kPasswordManagerSuggestionAutomatic, + kPasswordManagerSuggestionManual, + // The site asked the user to save a password via the credential management + // API. + kCredentialManagementAPIAutomatic, + kCredentialManagementAPIManual, + kMax, + }; + + // The reason why a password bubble was dismissed. + enum class BubbleDismissalReason { + kUnknown = 0, + kAccepted = 1, + kDeclined = 2, + kIgnored = 3 + }; + // The maximum number of combinations of the ManagerAction, UserAction and // SubmitResult enums. // This is used when recording the actions taken by the form in UMA. @@ -194,6 +241,15 @@ const FormFetcher& form_fetcher, const autofill::PasswordForm& pending_credentials); + // Records the event that a password bubble was shown. + void RecordPasswordBubbleShown( + metrics_util::CredentialSourceType credential_source_type, + metrics_util::UIDisplayDisposition display_disposition); + + // Records the dismissal of a password bubble. + void RecordUIDismissalReason( + metrics_util::UIDismissalReason ui_dismissal_reason); + // Records that the password manager managed or failed to fill a form. void RecordFillEvent(ManagerAutofillEvent event); @@ -249,6 +305,12 @@ // Whether this form has an auto generated password. bool has_generated_password_ = false; + // Whether the user was shown a prompt to update a password. + bool update_prompt_shown_ = false; + + // Whether the user was shown a prompt to save a new credential. + bool save_prompt_shown_ = false; + // These three fields record the "ActionsTaken" by the browser and // the user with this form, and the result. They are combined and // recorded in UMA when the PasswordFormMetricsRecorder is destroyed.
diff --git a/components/password_manager/core/browser/password_form_metrics_recorder_unittest.cc b/components/password_manager/core/browser/password_form_metrics_recorder_unittest.cc index b93956e8..e97174c 100644 --- a/components/password_manager/core/browser/password_form_metrics_recorder_unittest.cc +++ b/components/password_manager/core/browser/password_form_metrics_recorder_unittest.cc
@@ -31,6 +31,7 @@ source_id); } +// TODO(crbug.com/738921) Replace this with generalized infrastructure. // Verifies that the metric |metric_name| was recorded with value |value| in the // single entry of |test_ukm_recorder_| exactly |expected_count| times. void ExpectUkmValueCount(ukm::TestUkmRecorder* test_ukm_recorder, @@ -108,7 +109,7 @@ } ExpectUkmValueCount( - &test_ukm_recorder, internal::kUkmSubmissionObserved, + &test_ukm_recorder, kUkmSubmissionObserved, test.submission != PasswordFormMetricsRecorder::kSubmitResultNotSubmitted ? 1 @@ -120,7 +121,7 @@ : 0; EXPECT_EQ(expected_login_failed, user_action_tester.GetActionCount("PasswordManager_LoginFailed")); - ExpectUkmValueCount(&test_ukm_recorder, internal::kUkmSubmissionResult, + ExpectUkmValueCount(&test_ukm_recorder, kUkmSubmissionResult, PasswordFormMetricsRecorder::kSubmitResultFailed, expected_login_failed); @@ -129,7 +130,7 @@ : 0; EXPECT_EQ(expected_login_passed, user_action_tester.GetActionCount("PasswordManager_LoginPassed")); - ExpectUkmValueCount(&test_ukm_recorder, internal::kUkmSubmissionResult, + ExpectUkmValueCount(&test_ukm_recorder, kUkmSubmissionResult, PasswordFormMetricsRecorder::kSubmitResultPassed, expected_login_passed); @@ -359,7 +360,7 @@ if (test.form_type != PasswordFormMetricsRecorder::kSubmittedFormTypeUnspecified) { - ExpectUkmValueCount(&test_ukm_recorder, internal::kUkmSubmissionFormType, + ExpectUkmValueCount(&test_ukm_recorder, kUkmSubmissionFormType, test.form_type, 1); } @@ -382,4 +383,136 @@ } } +TEST(PasswordFormMetricsRecorder, RecordPasswordBubbleShown) { + using Trigger = PasswordFormMetricsRecorder::BubbleTrigger; + static constexpr struct { + // Stimuli: + metrics_util::CredentialSourceType credential_source_type; + metrics_util::UIDisplayDisposition display_disposition; + // Expectations: + const char* expected_trigger_metric; + Trigger expected_trigger_value; + bool expected_save_prompt_shown; + bool expected_update_prompt_shown; + } kTests[] = { + // Source = PasswordManager, Saving. + {metrics_util::CredentialSourceType::kPasswordManager, + metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING, kUkmSavingPromptTrigger, + Trigger::kPasswordManagerSuggestionAutomatic, true, false}, + {metrics_util::CredentialSourceType::kPasswordManager, + metrics_util::MANUAL_WITH_PASSWORD_PENDING, kUkmSavingPromptTrigger, + Trigger::kPasswordManagerSuggestionManual, true, false}, + // Source = PasswordManager, Updating. + {metrics_util::CredentialSourceType::kPasswordManager, + metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING_UPDATE, + kUkmUpdatingPromptTrigger, Trigger::kPasswordManagerSuggestionAutomatic, + false, true}, + {metrics_util::CredentialSourceType::kPasswordManager, + metrics_util::MANUAL_WITH_PASSWORD_PENDING_UPDATE, + kUkmUpdatingPromptTrigger, Trigger::kPasswordManagerSuggestionManual, + false, true}, + // Source = Credential Management API, Saving. + {metrics_util::CredentialSourceType::kCredentialManagementAPI, + metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING, kUkmSavingPromptTrigger, + Trigger::kCredentialManagementAPIAutomatic, true, false}, + {metrics_util::CredentialSourceType::kCredentialManagementAPI, + metrics_util::MANUAL_WITH_PASSWORD_PENDING, kUkmSavingPromptTrigger, + Trigger::kCredentialManagementAPIManual, true, false}, + // Source = Credential Management API, Updating. + {metrics_util::CredentialSourceType::kCredentialManagementAPI, + metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING_UPDATE, + kUkmUpdatingPromptTrigger, Trigger::kCredentialManagementAPIAutomatic, + false, true}, + {metrics_util::CredentialSourceType::kCredentialManagementAPI, + metrics_util::MANUAL_WITH_PASSWORD_PENDING_UPDATE, + kUkmUpdatingPromptTrigger, Trigger::kCredentialManagementAPIManual, + false, true}, + // Source = Unknown, Saving. + {metrics_util::CredentialSourceType::kUnknown, + metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING, kUkmSavingPromptTrigger, + Trigger::kPasswordManagerSuggestionAutomatic, false, false}, + }; + + for (const auto& test : kTests) { + SCOPED_TRACE(testing::Message() + << "credential_source_type = " + << static_cast<int64_t>(test.credential_source_type) + << ", display_disposition = " << test.display_disposition); + ukm::TestUkmRecorder test_ukm_recorder; + { + auto recorder = base::MakeRefCounted<PasswordFormMetricsRecorder>( + true /*is_main_frame_secure*/, + CreateUkmEntryBuilder(&test_ukm_recorder)); + recorder->RecordPasswordBubbleShown(test.credential_source_type, + test.display_disposition); + } + // Verify data + const ukm::UkmSource* source = test_ukm_recorder.GetSourceForUrl(kTestUrl); + ASSERT_TRUE(source); + if (test.credential_source_type != + metrics_util::CredentialSourceType::kUnknown) { + test_ukm_recorder.ExpectMetric( + *source, "PasswordForm", test.expected_trigger_metric, + static_cast<int64_t>(test.expected_trigger_value)); + } else { + EXPECT_FALSE(test_ukm_recorder.HasMetric(*source, "PasswordForm", + kUkmSavingPromptTrigger)); + EXPECT_FALSE(test_ukm_recorder.HasMetric(*source, "PasswordForm", + kUkmUpdatingPromptTrigger)); + } + test_ukm_recorder.ExpectMetric(*source, "PasswordForm", + kUkmSavingPromptShown, + test.expected_save_prompt_shown); + test_ukm_recorder.ExpectMetric(*source, "PasswordForm", + kUkmUpdatingPromptShown, + test.expected_update_prompt_shown); + } +} + +TEST(PasswordFormMetricsRecorder, RecordUIDismissalReason) { + static constexpr struct { + // Stimuli: + metrics_util::UIDisplayDisposition display_disposition; + metrics_util::UIDismissalReason dismissal_reason; + // Expectations: + const char* expected_trigger_metric; + PasswordFormMetricsRecorder::BubbleDismissalReason expected_metric_value; + } kTests[] = { + {metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING, + metrics_util::CLICKED_SAVE, kUkmSavingPromptInteraction, + PasswordFormMetricsRecorder::BubbleDismissalReason::kAccepted}, + {metrics_util::MANUAL_WITH_PASSWORD_PENDING, metrics_util::CLICKED_CANCEL, + kUkmSavingPromptInteraction, + PasswordFormMetricsRecorder::BubbleDismissalReason::kDeclined}, + {metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING_UPDATE, + metrics_util::CLICKED_NEVER, kUkmUpdatingPromptInteraction, + PasswordFormMetricsRecorder::BubbleDismissalReason::kDeclined}, + {metrics_util::MANUAL_WITH_PASSWORD_PENDING_UPDATE, + metrics_util::NO_DIRECT_INTERACTION, kUkmUpdatingPromptInteraction, + PasswordFormMetricsRecorder::BubbleDismissalReason::kIgnored}, + }; + + for (const auto& test : kTests) { + SCOPED_TRACE(testing::Message() + << "display_disposition = " << test.display_disposition + << ", dismissal_reason = " << test.dismissal_reason); + ukm::TestUkmRecorder test_ukm_recorder; + { + auto recorder = base::MakeRefCounted<PasswordFormMetricsRecorder>( + true /*is_main_frame_secure*/, + CreateUkmEntryBuilder(&test_ukm_recorder)); + recorder->RecordPasswordBubbleShown( + metrics_util::CredentialSourceType::kPasswordManager, + test.display_disposition); + recorder->RecordUIDismissalReason(test.dismissal_reason); + } + // Verify data + const ukm::UkmSource* source = test_ukm_recorder.GetSourceForUrl(kTestUrl); + ASSERT_TRUE(source); + test_ukm_recorder.ExpectMetric( + *source, "PasswordForm", test.expected_trigger_metric, + static_cast<int64_t>(test.expected_metric_value)); + } +} + } // namespace password_manager
diff --git a/components/password_manager/core/browser/password_manager_metrics_recorder.h b/components/password_manager/core/browser/password_manager_metrics_recorder.h index ea9b816..4d2a7f70 100644 --- a/components/password_manager/core/browser/password_manager_metrics_recorder.h +++ b/components/password_manager/core/browser/password_manager_metrics_recorder.h
@@ -16,13 +16,13 @@ namespace password_manager { -// Internal namespace is intended for component wide access only. -namespace internal { +// URL Keyed Metrics. +// Records a 1 for every page on which a user has modified the content of a +// password field - regardless of how many password fields a page contains or +// the user modifies. constexpr char kUkmUserModifiedPasswordField[] = "UserModifiedPasswordField"; -} // namespace internal - class BrowserSavePasswordProgressLogger; // The pupose of this class is to record various types of metrics about the
diff --git a/components/password_manager/core/browser/password_manager_metrics_recorder_unittest.cc b/components/password_manager/core/browser/password_manager_metrics_recorder_unittest.cc index 89874c40..9cb4e2f 100644 --- a/components/password_manager/core/browser/password_manager_metrics_recorder_unittest.cc +++ b/components/password_manager/core/browser/password_manager_metrics_recorder_unittest.cc
@@ -27,6 +27,7 @@ test_ukm_recorder, source_id); } +// TODO(crbug.com/738921) Replace this with generalized infrastructure. // Verifies that the metric |metric_name| was recorded with value |value| in the // single entry of |test_ukm_recorder| exactly |expected_count| times. void ExpectUkmValueCount(ukm::TestUkmRecorder* test_ukm_recorder, @@ -57,8 +58,7 @@ CreateUkmEntryBuilder(&test_ukm_recorder)); recorder.RecordUserModifiedPasswordField(); } - ExpectUkmValueCount(&test_ukm_recorder, - internal::kUkmUserModifiedPasswordField, 1, 1); + ExpectUkmValueCount(&test_ukm_recorder, kUkmUserModifiedPasswordField, 1, 1); } TEST(PasswordManagerMetricsRecorder, UserModifiedPasswordFieldMultipleTimes) { @@ -70,8 +70,7 @@ recorder.RecordUserModifiedPasswordField(); recorder.RecordUserModifiedPasswordField(); } - ExpectUkmValueCount(&test_ukm_recorder, - internal::kUkmUserModifiedPasswordField, 1, 1); + ExpectUkmValueCount(&test_ukm_recorder, kUkmUserModifiedPasswordField, 1, 1); } TEST(PasswordManagerMetricsRecorder, UserModifiedPasswordFieldNotCalled) { @@ -80,8 +79,7 @@ PasswordManagerMetricsRecorder recorder( CreateUkmEntryBuilder(&test_ukm_recorder)); } - ExpectUkmValueCount(&test_ukm_recorder, - internal::kUkmUserModifiedPasswordField, 1, 0); + ExpectUkmValueCount(&test_ukm_recorder, kUkmUserModifiedPasswordField, 1, 0); } } // namespace password_manager
diff --git a/components/password_manager/core/browser/password_manager_metrics_util.h b/components/password_manager/core/browser/password_manager_metrics_util.h index 909a914..2f2ff18 100644 --- a/components/password_manager/core/browser/password_manager_metrics_util.h +++ b/components/password_manager/core/browser/password_manager_metrics_util.h
@@ -208,6 +208,17 @@ REAUTH_COUNT }; +// Specifies the type of PasswordFormManagers and derived classes to distinguish +// the context in which a PasswordFormManager is being created and used. +enum class CredentialSourceType { + kUnknown, + // This is used for form based credential management (PasswordFormManager). + kPasswordManager, + // This is used for credential management API based credential management + // (CredentialManagerPasswordFormManager). + kCredentialManagementAPI +}; + #if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) || \ (defined(OS_LINUX) && !defined(OS_CHROMEOS)) enum class SyncPasswordHashChange {
diff --git a/headless/lib/browser/devtools_api/devtools_connection.js b/headless/lib/browser/devtools_api/devtools_connection.js index 3061c196..07b9741a 100644 --- a/headless/lib/browser/devtools_api/devtools_connection.js +++ b/headless/lib/browser/devtools_api/devtools_connection.js
@@ -56,6 +56,14 @@ */ this.eventListenerIdToEventName_ = new Map(); + /** + * An object containing listeners for all DevTools protocol events keyed + * by listener id. + * + * @private {!Map<number, !chromium.DevTools.Connection.AllEventsFunction>} + */ + this.allEventsListeners_ = new Map(); + this.transport_.onmessage = this.onJsonMessage_.bind(this); } @@ -97,6 +105,31 @@ return this.eventListeners_.get(eventName).delete(id); } + /** + * Listens for all DevTools protocol events and issues the + * callback upon reception. + * + * @param {!chromium.DevTools.Connection.AllEventsFunction} listener The + * callback issued when we receive a DevTools protocol event. + * @return {number} The id of this event listener. + */ + addAllEventsListener(listener) { + let id = this.nextListenerId_++; + this.allEventsListeners_.set(id, listener); + return id; + } + + /** + * Removes an event listener previously added by + * <code>addAllEventsListener</code>. + * + * @param {number} id The id of the event listener to remove. + * @return {boolean} Whether the event listener was actually removed. + */ + removeAllEventsListener(id) { + if (!this.allEventsListeners_.has(id)) return false; + return this.allEventsListeners_.delete(id); + } /** * Issues a DevTools protocol command and returns a promise for the results. @@ -124,15 +157,12 @@ } /** - * If a subclass needs to customize message handling it should override this - * method. - * - * @param {*} message A parsed DevTools protocol message. * @param {string} jsonMessage A string containing a JSON DevTools protocol * message. - * @protected + * @private */ - onMessage_(message, jsonMessage) { + onJsonMessage_(jsonMessage) { + let message = JSON.parse(jsonMessage); if (message.hasOwnProperty('id')) { if (!this.pendingCommands_.has(message.id)) throw new Error('Unrecognized id:' + jsonMessage); @@ -148,6 +178,9 @@ } const method = message['method']; const params = message['params']; + this.allEventsListeners_.forEach(function(listener) { + listener({method, params}); + }); if (this.eventListeners_.has(method)) { this.eventListeners_.get(method).forEach(function(listener) { listener(params); @@ -155,16 +188,6 @@ } } } - - - /** - * @param {string} jsonMessage A string containing a JSON DevTools protocol - * message. - * @private - */ - onJsonMessage_(jsonMessage) { - this.onMessage_(JSON.parse(jsonMessage), jsonMessage); - } } /** @@ -173,6 +196,11 @@ chromium.DevTools.Connection.EventFunction; /** + * @typedef {function(Object): undefined} + */ +chromium.DevTools.Connection.AllEventsFunction; + +/** * @typedef {{ * resolve: function(!Object), * reject: function(!Object)
diff --git a/ios/chrome/browser/passwords/ios_chrome_password_manager_infobar_delegate.h b/ios/chrome/browser/passwords/ios_chrome_password_manager_infobar_delegate.h index afc444a..7a0035ea 100644 --- a/ios/chrome/browser/passwords/ios_chrome_password_manager_infobar_delegate.h +++ b/ios/chrome/browser/passwords/ios_chrome_password_manager_infobar_delegate.h
@@ -41,6 +41,10 @@ infobar_response_ = response; } + password_manager::metrics_util::UIDismissalReason infobar_response() const { + return infobar_response_; + } + private: // ConfirmInfoBarDelegate implementation. Type GetInfoBarType() const override;
diff --git a/ios/chrome/browser/passwords/ios_chrome_save_password_infobar_delegate.mm b/ios/chrome/browser/passwords/ios_chrome_save_password_infobar_delegate.mm index c0e55e5..e9838a9 100644 --- a/ios/chrome/browser/passwords/ios_chrome_save_password_infobar_delegate.mm +++ b/ios/chrome/browser/passwords/ios_chrome_save_password_infobar_delegate.mm
@@ -34,13 +34,20 @@ infobar_manager->CreateConfirmInfoBar(std::move(delegate))); } -IOSChromeSavePasswordInfoBarDelegate::~IOSChromeSavePasswordInfoBarDelegate() {} +IOSChromeSavePasswordInfoBarDelegate::~IOSChromeSavePasswordInfoBarDelegate() { + form_to_save()->metrics_recorder()->RecordUIDismissalReason( + infobar_response()); +} IOSChromeSavePasswordInfoBarDelegate::IOSChromeSavePasswordInfoBarDelegate( bool is_smart_lock_branding_enabled, - std::unique_ptr<PasswordFormManager> form_to_save) + std::unique_ptr<PasswordFormManager> form_manager) : IOSChromePasswordManagerInfoBarDelegate(is_smart_lock_branding_enabled, - std::move(form_to_save)) {} + std::move(form_manager)) { + form_to_save()->metrics_recorder()->RecordPasswordBubbleShown( + form_to_save()->GetCredentialSource(), + password_manager::metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING); +} infobars::InfoBarDelegate::InfoBarIdentifier IOSChromeSavePasswordInfoBarDelegate::GetIdentifier() const {
diff --git a/ios/chrome/browser/passwords/ios_chrome_update_password_infobar_delegate.h b/ios/chrome/browser/passwords/ios_chrome_update_password_infobar_delegate.h index d720ba2..bf5e790 100644 --- a/ios/chrome/browser/passwords/ios_chrome_update_password_infobar_delegate.h +++ b/ios/chrome/browser/passwords/ios_chrome_update_password_infobar_delegate.h
@@ -61,6 +61,7 @@ int GetButtons() const override; base::string16 GetButtonLabel(InfoBarButton button) const override; bool Accept() override; + bool Cancel() override; // The credential that should be displayed in the infobar, and for which the // password will be updated.
diff --git a/ios/chrome/browser/passwords/ios_chrome_update_password_infobar_delegate.mm b/ios/chrome/browser/passwords/ios_chrome_update_password_infobar_delegate.mm index 44a786bafd..e015593 100644 --- a/ios/chrome/browser/passwords/ios_chrome_update_password_infobar_delegate.mm +++ b/ios/chrome/browser/passwords/ios_chrome_update_password_infobar_delegate.mm
@@ -42,7 +42,10 @@ } IOSChromeUpdatePasswordInfoBarDelegate:: - ~IOSChromeUpdatePasswordInfoBarDelegate() {} + ~IOSChromeUpdatePasswordInfoBarDelegate() { + form_to_save()->metrics_recorder()->RecordUIDismissalReason( + infobar_response()); +} IOSChromeUpdatePasswordInfoBarDelegate::IOSChromeUpdatePasswordInfoBarDelegate( bool is_smart_lock_branding_enabled, @@ -50,6 +53,9 @@ : IOSChromePasswordManagerInfoBarDelegate(is_smart_lock_branding_enabled, std::move(form_manager)) { selected_account_ = form_to_save()->preferred_match()->username_value; + form_to_save()->metrics_recorder()->RecordPasswordBubbleShown( + form_to_save()->GetCredentialSource(), + password_manager::metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING_UPDATE); } bool IOSChromeUpdatePasswordInfoBarDelegate::ShowMultipleAccounts() const { @@ -106,5 +112,12 @@ } else { form_to_save()->Update(form_to_save()->pending_credentials()); } + set_infobar_response(password_manager::metrics_util::CLICKED_SAVE); + return true; +} + +bool IOSChromeUpdatePasswordInfoBarDelegate::Cancel() { + DCHECK(form_to_save()); + set_infobar_response(password_manager::metrics_util::CLICKED_CANCEL); return true; }
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations index 636b2a9..e235ff5 100644 --- a/third_party/WebKit/LayoutTests/TestExpectations +++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -2478,11 +2478,6 @@ crbug.com/595993 virtual/off-main-thread-fetch/external/wpt/service-workers/service-worker/request-end-to-end.https.html [ Failure ] crbug.com/595993 virtual/service-worker-script-streaming/external/wpt/service-workers/service-worker/request-end-to-end.https.html [ Failure ] -crbug.com/735883 external/wpt/service-workers/service-worker/fetch-canvas-tainting.https.html [ Pass Failure ] -crbug.com/735883 virtual/off-main-thread-fetch/external/wpt/service-workers/service-worker/fetch-canvas-tainting.https.html [ Pass Failure ] -crbug.com/735883 external/wpt/service-workers/service-worker/fetch-canvas-tainting-cache.https.html [ Pass Failure ] -crbug.com/735883 virtual/off-main-thread-fetch/external/wpt/service-workers/service-worker/fetch-canvas-tainting-cache.https.html [ Pass Failure ] - crbug.com/619427 [ Mac Linux ] fast/overflow/overflow-height-float-not-removed-crash3.html [ Pass Failure ] crbug.com/667371 inspector/elements/styles-1/color-aware-property-value-edit.html [ Pass Failure ]
diff --git a/third_party/WebKit/LayoutTests/editing/undo/undo-combined-delete-expected.txt b/third_party/WebKit/LayoutTests/editing/undo/undo-combined-delete-expected.txt deleted file mode 100644 index 750acc7..0000000 --- a/third_party/WebKit/LayoutTests/editing/undo/undo-combined-delete-expected.txt +++ /dev/null
@@ -1,28 +0,0 @@ -mac -PASS selection.anchorNode.data is "This word should be selected only on mac." -PASS selection.anchorOffset is 5 -PASS selection.focusNode.data is "This word should be selected only on mac." -PASS selection.focusOffset is 9 -PASS selection.toString() is "word" -win -PASS selection.anchorNode.data is "This word should be selected only on mac." -PASS selection.anchorOffset is 7 -PASS selection.focusNode.data is "This word should be selected only on mac." -PASS selection.focusOffset is 7 -PASS selection.toString() is "" -unix -PASS selection.anchorNode.data is "This word should be selected only on mac." -PASS selection.anchorOffset is 7 -PASS selection.focusNode.data is "This word should be selected only on mac." -PASS selection.focusOffset is 7 -PASS selection.toString() is "" -android -PASS selection.anchorNode.data is "This word should be selected only on mac." -PASS selection.anchorOffset is 7 -PASS selection.focusNode.data is "This word should be selected only on mac." -PASS selection.focusOffset is 7 -PASS selection.toString() is "" -PASS successfullyParsed is true - -TEST COMPLETE -
diff --git a/third_party/WebKit/LayoutTests/editing/undo/undo-combined-delete.html b/third_party/WebKit/LayoutTests/editing/undo/undo-combined-delete.html deleted file mode 100644 index 0a6af3c..0000000 --- a/third_party/WebKit/LayoutTests/editing/undo/undo-combined-delete.html +++ /dev/null
@@ -1,47 +0,0 @@ -<!DOCTYPE html> -<html> -<body> -<div id="container"> -<p id="description"></p> -<p>To test manually, place the cursor after 'o' in 'word' and delete it completely character by character. Do ctrl+z. On Mac, 'word' should be selected. On other platforms 'word' should not be selected and the cursor should be placed after 'o' in 'word'.</p> -<div id="test" style="border: 2px solid red;" contenteditable>This word should be selected only on mac.</div> -</div> -<div id="console"></div> -<script src="../../resources/js-test.js"></script> -<script> -description('Verifies the selection behavior on undoing a text deletion.'); -var selectionNode = document.getElementById('test').firstChild; -var selectionOffset = selectionNode.data.indexOf('o') + 1; -var startOffsetMac = selectionNode.data.indexOf(' ') + 1; -var endOffsetMac = selectionNode.data.indexOf('d') + 1; -var selection = window.getSelection(); - -function undoTest(platform, expectedStartNode, expectedStartOffset, expectedEndNode, expectedEndOffset, selectedText) { - debug(platform); - internals.settings.setEditingBehavior(platform); - - selection.collapse(selectionNode, selectionOffset); - for (var i = 0; i < 2; i++) - document.execCommand('delete'); - for (var i = 0; i < 2; i++) - document.execCommand('forwarddelete'); - document.execCommand('undo'); - - shouldBeEqualToString('selection.anchorNode.data', expectedStartNode.data); - shouldBe('selection.anchorOffset', expectedStartOffset + ''); - shouldBeEqualToString('selection.focusNode.data', expectedEndNode.data); - shouldBe('selection.focusOffset', expectedEndOffset + ''); - shouldBeEqualToString('selection.toString()', selectedText); -} - -if (window.internals) { - undoTest('mac', selectionNode, startOffsetMac, selectionNode, endOffsetMac, 'word'); - undoTest('win', selectionNode, selectionOffset, selectionNode, selectionOffset, ''); - undoTest('unix', selectionNode, selectionOffset, selectionNode, selectionOffset, ''); - undoTest('android', selectionNode, selectionOffset, selectionNode, selectionOffset, ''); -} -if (window.testRunner) - document.getElementById('container').outerHTML = ''; -</script> -</body> -</html>
diff --git a/third_party/WebKit/LayoutTests/editing/undo/undo-delete-expected.txt b/third_party/WebKit/LayoutTests/editing/undo/undo-delete-expected.txt deleted file mode 100644 index ea7359b..0000000 --- a/third_party/WebKit/LayoutTests/editing/undo/undo-delete-expected.txt +++ /dev/null
@@ -1,28 +0,0 @@ -mac -PASS selection.anchorNode.data is "This word should be selected only on mac." -PASS selection.anchorOffset is 9 -PASS selection.focusNode.data is "This word should be selected only on mac." -PASS selection.focusOffset is 5 -PASS selection.toString() is "word" -win -PASS selection.anchorNode.data is "This word should be selected only on mac." -PASS selection.anchorOffset is 9 -PASS selection.focusNode.data is "This word should be selected only on mac." -PASS selection.focusOffset is 9 -PASS selection.toString() is "" -unix -PASS selection.anchorNode.data is "This word should be selected only on mac." -PASS selection.anchorOffset is 9 -PASS selection.focusNode.data is "This word should be selected only on mac." -PASS selection.focusOffset is 9 -PASS selection.toString() is "" -android -PASS selection.anchorNode.data is "This word should be selected only on mac." -PASS selection.anchorOffset is 9 -PASS selection.focusNode.data is "This word should be selected only on mac." -PASS selection.focusOffset is 9 -PASS selection.toString() is "" -PASS successfullyParsed is true - -TEST COMPLETE -
diff --git a/third_party/WebKit/LayoutTests/editing/undo/undo-delete.html b/third_party/WebKit/LayoutTests/editing/undo/undo-delete.html index c5ccf4d..a56a382 100644 --- a/third_party/WebKit/LayoutTests/editing/undo/undo-delete.html +++ b/third_party/WebKit/LayoutTests/editing/undo/undo-delete.html
@@ -1,44 +1,73 @@ -<!DOCTYPE html> -<html> -<body> -<div id="container"> -<p id="description"></p> -<p>To test manually, place the cursor at the end of 'word' and delete it completely character by character. Do ctrl+z. On Mac, 'word' should be selected. On other platforms 'word' should not be selected and the cursor should be placed at the end of 'word'.</p> -<div id="test" style="border: 2px solid red;" contenteditable>This word should be selected only on mac.</div> -</div> -<div id="console"></div> -<script src="../../resources/js-test.js"></script> +<!doctype html> +<script src="../../resources/testharness.js"></script> +<script src="../../resources/testharnessreport.js"></script> +<script src="../assert_selection.js"></script> <script> -description('Verifies the selection behavior on undoing a text deletion.'); -var selectionNode = document.getElementById('test').firstChild; -var selectionOffset = selectionNode.data.indexOf('d') + 1; -var endOffsetMac = selectionNode.data.indexOf(' ') + 1; -var selection = window.getSelection(); +function undoTest(behavior) { + if (window.internals) + internals.settings.setEditingBehavior(behavior); + test(() => assert_selection( + [ + '<div contenteditable>', + 'This word| should be selected only on mac.', + '</div>' + ].join(''), + selection => { + for (let i = 0; i < 4; ++i) + selection.document.execCommand('delete'); + selection.document.execCommand('undo'); + }, + [ + '<div contenteditable>', + behavior === 'mac' + ? 'This |word^ should be selected only on mac.' + : 'This word| should be selected only on mac.', + '</div>', + ].join('')), + `${behavior}: backward delete`); -function undoTest(platform, expectedStartNode, expectedStartOffset, expectedEndNode, expectedEndOffset, selectedText) { - debug(platform); - internals.settings.setEditingBehavior(platform); + test(() => assert_selection( + [ + '<div contenteditable>', + 'This |word should be selected only on mac.', + '</div>' + ].join(''), + selection => { + for (let i = 0; i < 4; ++i) + selection.document.execCommand('forwardDelete'); + selection.document.execCommand('undo'); + }, + [ + '<div contenteditable>', + behavior === 'mac' + ? 'This ^word| should be selected only on mac.' + : 'This |word should be selected only on mac.', + '</div>', + ].join('')), + `${behavior}: forward delete`); - selection.collapse(selectionNode, selectionOffset); - for (var i = 0; i < 4; i++) - document.execCommand('delete'); - document.execCommand('undo'); - - shouldBeEqualToString('selection.anchorNode.data', expectedStartNode.data); - shouldBe('selection.anchorOffset', expectedStartOffset + ''); - shouldBeEqualToString('selection.focusNode.data', expectedEndNode.data); - shouldBe('selection.focusOffset', expectedEndOffset + ''); - shouldBeEqualToString('selection.toString()', selectedText); + test(() => assert_selection( + [ + '<div contenteditable>', + 'This wo|rd should be selected only on mac.', + '</div>' + ].join(''), + selection => { + for (let i = 0; i < 2; ++i) + selection.document.execCommand('delete'); + for (let i = 0; i < 2; ++i) + selection.document.execCommand('forwardDelete'); + selection.document.execCommand('undo'); + }, + [ + '<div contenteditable>', + behavior === 'mac' + ? 'This ^word| should be selected only on mac.' + : 'This wo|rd should be selected only on mac.', + '</div>', + ].join('')), + `${behavior}: backward and forward delete`); } - -if (window.internals) { - undoTest('mac', selectionNode, selectionOffset, selectionNode, endOffsetMac, 'word'); - undoTest('win', selectionNode, selectionOffset, selectionNode, selectionOffset, ''); - undoTest('unix', selectionNode, selectionOffset, selectionNode, selectionOffset, ''); - undoTest('android', selectionNode, selectionOffset, selectionNode, selectionOffset, ''); -} -if (window.testRunner) - document.getElementById('container').outerHTML = ''; +for (let behavior of ['android', 'mac', 'unix', 'win']) + undoTest(behavior); </script> -</body> -</html>
diff --git a/third_party/WebKit/LayoutTests/editing/undo/undo-forward-delete-expected.txt b/third_party/WebKit/LayoutTests/editing/undo/undo-forward-delete-expected.txt deleted file mode 100644 index a695a17..0000000 --- a/third_party/WebKit/LayoutTests/editing/undo/undo-forward-delete-expected.txt +++ /dev/null
@@ -1,28 +0,0 @@ -mac -PASS selection.anchorNode.data is "This word should be selected only on mac." -PASS selection.anchorOffset is 5 -PASS selection.focusNode.data is "This word should be selected only on mac." -PASS selection.focusOffset is 9 -PASS selection.toString() is "word" -win -PASS selection.anchorNode.data is "This word should be selected only on mac." -PASS selection.anchorOffset is 5 -PASS selection.focusNode.data is "This word should be selected only on mac." -PASS selection.focusOffset is 5 -PASS selection.toString() is "" -unix -PASS selection.anchorNode.data is "This word should be selected only on mac." -PASS selection.anchorOffset is 5 -PASS selection.focusNode.data is "This word should be selected only on mac." -PASS selection.focusOffset is 5 -PASS selection.toString() is "" -android -PASS selection.anchorNode.data is "This word should be selected only on mac." -PASS selection.anchorOffset is 5 -PASS selection.focusNode.data is "This word should be selected only on mac." -PASS selection.focusOffset is 5 -PASS selection.toString() is "" -PASS successfullyParsed is true - -TEST COMPLETE -
diff --git a/third_party/WebKit/LayoutTests/editing/undo/undo-forward-delete.html b/third_party/WebKit/LayoutTests/editing/undo/undo-forward-delete.html deleted file mode 100644 index fcd2e4f1..0000000 --- a/third_party/WebKit/LayoutTests/editing/undo/undo-forward-delete.html +++ /dev/null
@@ -1,44 +0,0 @@ -<!DOCTYPE html> -<html> -<body> -<div id="container"> -<p id="description"></p> -<p>To test manually, place the cursor before 'w' of 'word' and delete it completely character by character. Do ctrl+z. On Mac, 'word' should be selected. On other platforms 'word' should not be selected and the cursor should be placed before 'w' of 'word'.</p> -<div id="test" style="border: 2px solid red;" contenteditable>This word should be selected only on mac.</div> -</div> -<div id="console"></div> -<script src="../../resources/js-test.js"></script> -<script> -description('Verifies the selection behavior on undoing a text deletion.'); -var selectionNode = document.getElementById('test').firstChild; -var selectionOffset = selectionNode.data.indexOf(' ') + 1; -var endOffsetMac = selectionNode.data.indexOf('d') + 1; -var selection = window.getSelection(); - -function undoTest(platform, expectedStartNode, expectedStartOffset, expectedEndNode, expectedEndOffset, selectedText) { - debug(platform); - internals.settings.setEditingBehavior(platform); - - selection.collapse(selectionNode, selectionOffset); - for (var i = 0; i < 4; i++) - document.execCommand('forwarddelete'); - document.execCommand('undo'); - - shouldBeEqualToString('selection.anchorNode.data', expectedStartNode.data); - shouldBe('selection.anchorOffset', expectedStartOffset + ''); - shouldBeEqualToString('selection.focusNode.data', expectedEndNode.data); - shouldBe('selection.focusOffset', expectedEndOffset + ''); - shouldBeEqualToString('selection.toString()', selectedText); -} - -if (window.internals) { - undoTest('mac', selectionNode, selectionOffset, selectionNode, endOffsetMac, 'word'); - undoTest('win', selectionNode, selectionOffset, selectionNode, selectionOffset, ''); - undoTest('unix', selectionNode, selectionOffset, selectionNode, selectionOffset, ''); - undoTest('android', selectionNode, selectionOffset, selectionNode, selectionOffset, ''); -} -if (window.testRunner) - document.getElementById('container').outerHTML = ''; -</script> -</body> -</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/fetch-canvas-tainting-cache.https-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/fetch-canvas-tainting-cache.https-expected.txt deleted file mode 100644 index d6af7056..0000000 --- a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/fetch-canvas-tainting-cache.https-expected.txt +++ /dev/null
@@ -1,4 +0,0 @@ -This is a testharness.js-based test. -FAIL Verify canvas tainting of fetched image in a Service Worker assert_equals: expected "finish" but got "failure:Result of url:https://web-platform.test:8444/service-workers/service-worker/resources/fetch-access-control.py?PNGIMAGE&mode=cors&url=https%3A%2F%2Fwww1.web-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Ffetch-access-control.py%3FPNGIMAGE%26ACAOrigin%3Dhttps%3A%2F%2Fweb-platform.test%3A8444&cache cross_origin: use-credentials must be LOAD_ERROR but NOT_TAINTED" -Harness: the test ran to completion. -
diff --git a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/fetch-canvas-tainting.https-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/fetch-canvas-tainting.https-expected.txt deleted file mode 100644 index d639d286..0000000 --- a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/fetch-canvas-tainting.https-expected.txt +++ /dev/null
@@ -1,4 +0,0 @@ -This is a testharness.js-based test. -FAIL Verify canvas tainting of fetched image in a Service Worker assert_equals: expected "finish" but got "failure:Result of url:https://web-platform.test:8444/service-workers/service-worker/resources/fetch-access-control.py?PNGIMAGE&mode=cors&url=https%3A%2F%2Fwww1.web-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Ffetch-access-control.py%3FPNGIMAGE%26ACAOrigin%3Dhttps%3A%2F%2Fweb-platform.test%3A8444 cross_origin: use-credentials must be LOAD_ERROR but NOT_TAINTED" -Harness: the test ran to completion. -
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.11/virtual/off-main-thread-fetch/external/wpt/service-workers/service-worker/fetch-canvas-tainting.https-expected.txt b/third_party/WebKit/LayoutTests/platform/mac-mac10.11/virtual/off-main-thread-fetch/external/wpt/service-workers/service-worker/fetch-canvas-tainting.https-expected.txt deleted file mode 100644 index d639d286..0000000 --- a/third_party/WebKit/LayoutTests/platform/mac-mac10.11/virtual/off-main-thread-fetch/external/wpt/service-workers/service-worker/fetch-canvas-tainting.https-expected.txt +++ /dev/null
@@ -1,4 +0,0 @@ -This is a testharness.js-based test. -FAIL Verify canvas tainting of fetched image in a Service Worker assert_equals: expected "finish" but got "failure:Result of url:https://web-platform.test:8444/service-workers/service-worker/resources/fetch-access-control.py?PNGIMAGE&mode=cors&url=https%3A%2F%2Fwww1.web-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Ffetch-access-control.py%3FPNGIMAGE%26ACAOrigin%3Dhttps%3A%2F%2Fweb-platform.test%3A8444 cross_origin: use-credentials must be LOAD_ERROR but NOT_TAINTED" -Harness: the test ran to completion. -
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/virtual/off-main-thread-fetch/external/wpt/service-workers/service-worker/fetch-canvas-tainting-cache.https-expected.txt b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/virtual/off-main-thread-fetch/external/wpt/service-workers/service-worker/fetch-canvas-tainting-cache.https-expected.txt deleted file mode 100644 index cf2caad..0000000 --- a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/virtual/off-main-thread-fetch/external/wpt/service-workers/service-worker/fetch-canvas-tainting-cache.https-expected.txt +++ /dev/null
@@ -1,4 +0,0 @@ -This is a testharness.js-based test. -FAIL Verify canvas tainting of fetched image in a Service Worker assert_equals: expected "finish" but got "failure:Result of url:https://www1.web-platform.test:8444/service-workers/service-worker/resources/fetch-access-control.py?PNGIMAGE&mode=cors&credentials=same-origin&url=https%3A%2F%2Fwww1.web-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Ffetch-access-control.py%3FPNGIMAGE%26ACAOrigin%3Dhttps%3A%2F%2Fweb-platform.test%3A8444&cache cross_origin: must be TAINTED but NOT_TAINTED" -Harness: the test ran to completion. -
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/virtual/off-main-thread-fetch/external/wpt/service-workers/service-worker/fetch-canvas-tainting.https-expected.txt b/third_party/WebKit/LayoutTests/platform/mac-retina/virtual/off-main-thread-fetch/external/wpt/service-workers/service-worker/fetch-canvas-tainting.https-expected.txt deleted file mode 100644 index d639d286..0000000 --- a/third_party/WebKit/LayoutTests/platform/mac-retina/virtual/off-main-thread-fetch/external/wpt/service-workers/service-worker/fetch-canvas-tainting.https-expected.txt +++ /dev/null
@@ -1,4 +0,0 @@ -This is a testharness.js-based test. -FAIL Verify canvas tainting of fetched image in a Service Worker assert_equals: expected "finish" but got "failure:Result of url:https://web-platform.test:8444/service-workers/service-worker/resources/fetch-access-control.py?PNGIMAGE&mode=cors&url=https%3A%2F%2Fwww1.web-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Ffetch-access-control.py%3FPNGIMAGE%26ACAOrigin%3Dhttps%3A%2F%2Fweb-platform.test%3A8444 cross_origin: use-credentials must be LOAD_ERROR but NOT_TAINTED" -Harness: the test ran to completion. -
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/off-main-thread-fetch/external/wpt/service-workers/service-worker/fetch-canvas-tainting.https-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/virtual/off-main-thread-fetch/external/wpt/service-workers/service-worker/fetch-canvas-tainting.https-expected.txt deleted file mode 100644 index a4b6320..0000000 --- a/third_party/WebKit/LayoutTests/platform/mac/virtual/off-main-thread-fetch/external/wpt/service-workers/service-worker/fetch-canvas-tainting.https-expected.txt +++ /dev/null
@@ -1,4 +0,0 @@ -This is a testharness.js-based test. -FAIL Verify canvas tainting of fetched image in a Service Worker assert_equals: expected "finish" but got "failure:Result of url:https://www1.web-platform.test:8444/service-workers/service-worker/resources/fetch-access-control.py?PNGIMAGE&mode=cors&credentials=same-origin&url=https%3A%2F%2Fwww1.web-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Ffetch-access-control.py%3FPNGIMAGE%26ACAOrigin%3Dhttps%3A%2F%2Fweb-platform.test%3A8444 cross_origin: must be TAINTED but NOT_TAINTED" -Harness: the test ran to completion. -
diff --git a/third_party/WebKit/Source/core/BUILD.gn b/third_party/WebKit/Source/core/BUILD.gn index 68a39a16..2087d79 100644 --- a/third_party/WebKit/Source/core/BUILD.gn +++ b/third_party/WebKit/Source/core/BUILD.gn
@@ -471,6 +471,7 @@ "$blink_core_output_dir/css/properties/CSSPropertyAPIAnimationName.h", "$blink_core_output_dir/css/properties/CSSPropertyAPIAnimationPlayState.h", "$blink_core_output_dir/css/properties/CSSPropertyAPIBaselineShift.h", + "$blink_core_output_dir/css/properties/CSSPropertyAPIBorderColor.h", "$blink_core_output_dir/css/properties/CSSPropertyAPIBorderImageOutset.h", "$blink_core_output_dir/css/properties/CSSPropertyAPIBorderImageRepeat.h", "$blink_core_output_dir/css/properties/CSSPropertyAPIBorderImageSlice.h", @@ -603,6 +604,9 @@ "$blink_core_output_dir/css/properties/CSSShorthandPropertyAPIOverflow.h", "$blink_core_output_dir/css/properties/CSSShorthandPropertyAPIOffset.h", "$blink_core_output_dir/css/properties/CSSShorthandPropertyAPIScrollPaddingBlock.h", + "$blink_core_output_dir/css/properties/CSSShorthandPropertyAPIScrollPaddingInline.h", + "$blink_core_output_dir/css/properties/CSSShorthandPropertyAPIScrollSnapMarginBlock.h", + "$blink_core_output_dir/css/properties/CSSShorthandPropertyAPIScrollSnapMarginInline.h", "$blink_core_output_dir/css/properties/CSSShorthandPropertyAPITextDecoration.h", "$blink_core_output_dir/css/properties/CSSShorthandPropertyAPIWebkitBorderAfter.h", "$blink_core_output_dir/css/properties/CSSShorthandPropertyAPIWebkitBorderBefore.h",
diff --git a/third_party/WebKit/Source/core/css/BUILD.gn b/third_party/WebKit/Source/core/css/BUILD.gn index 309458f..ec78751 100644 --- a/third_party/WebKit/Source/core/css/BUILD.gn +++ b/third_party/WebKit/Source/core/css/BUILD.gn
@@ -381,6 +381,7 @@ "properties/CSSPropertyAPIAnimationName.cpp", "properties/CSSPropertyAPIAnimationPlayState.cpp", "properties/CSSPropertyAPIBaselineShift.cpp", + "properties/CSSPropertyAPIBorderColor.cpp", "properties/CSSPropertyAPIBorderImageOutset.cpp", "properties/CSSPropertyAPIBorderImageRepeat.cpp", "properties/CSSPropertyAPIBorderImageSlice.cpp", @@ -546,6 +547,9 @@ "properties/CSSShorthandPropertyAPIOutline.cpp", "properties/CSSShorthandPropertyAPIOverflow.cpp", "properties/CSSShorthandPropertyAPIScrollPaddingBlock.cpp", + "properties/CSSShorthandPropertyAPIScrollPaddingInline.cpp", + "properties/CSSShorthandPropertyAPIScrollSnapMarginBlock.cpp", + "properties/CSSShorthandPropertyAPIScrollSnapMarginInline.cpp", "properties/CSSShorthandPropertyAPITextDecoration.cpp", "properties/CSSShorthandPropertyAPIWebkitBorderAfter.cpp", "properties/CSSShorthandPropertyAPIWebkitBorderBefore.cpp",
diff --git a/third_party/WebKit/Source/core/css/CSSProperties.json5 b/third_party/WebKit/Source/core/css/CSSProperties.json5 index 968d9a4..9e6bfdac 100644 --- a/third_party/WebKit/Source/core/css/CSSProperties.json5 +++ b/third_party/WebKit/Source/core/css/CSSProperties.json5
@@ -748,6 +748,8 @@ }, { name: "border-bottom-color", + api_class: "CSSPropertyAPIBorderColor", + api_methods: ["parseSingleValue"], custom_all: true, interpolable: true, type_name: "Color", @@ -792,6 +794,7 @@ { name: "border-bottom-width", api_class: "CSSPropertyAPIBorderWidth", + api_methods: ["parseSingleValue"], converter: "ConvertBorderWidth", interpolable: true, keywords: ["thin", "medium", "thick"], @@ -844,6 +847,8 @@ }, { name: "border-left-color", + api_class: "CSSPropertyAPIBorderColor", + api_methods: ["parseSingleValue"], custom_all: true, interpolable: true, field_template: "storage_only", @@ -864,6 +869,7 @@ { name: "border-left-width", api_class: "CSSPropertyAPIBorderWidth", + api_methods: ["parseSingleValue"], converter: "ConvertBorderWidth", interpolable: true, keywords: ["thin", "medium", "thick"], @@ -876,6 +882,8 @@ }, { name: "border-right-color", + api_class: "CSSPropertyAPIBorderColor", + api_methods: ["parseSingleValue"], custom_all: true, interpolable: true, field_template: "storage_only", @@ -896,6 +904,7 @@ { name: "border-right-width", api_class: "CSSPropertyAPIBorderWidth", + api_methods: ["parseSingleValue"], converter: "ConvertBorderWidth", interpolable: true, keywords: ["thin", "medium", "thick"], @@ -908,6 +917,8 @@ }, { name: "border-top-color", + api_class: "CSSPropertyAPIBorderColor", + api_methods: ["parseSingleValue"], custom_all: true, interpolable: true, field_template: "storage_only", @@ -952,6 +963,7 @@ { name: "border-top-width", api_class: "CSSPropertyAPIBorderWidth", + api_methods: ["parseSingleValue"], converter: "ConvertBorderWidth", interpolable: true, keywords: ["thin", "medium", "thick"], @@ -3847,6 +3859,8 @@ { name: "scroll-padding-inline", longhands: ["scroll-padding-inline-start", "scroll-padding-inline-end"], + api_class: true, + api_methods: ["parseShorthand"], runtime_flag: "CSSScrollSnapPoints", }, { @@ -3857,11 +3871,15 @@ { name: "scroll-snap-margin-block", longhands: ["scroll-snap-margin-block-start", "scroll-snap-margin-block-end"], + api_class: true, + api_methods: ["parseShorthand"], runtime_flag: "CSSScrollSnapPoints", }, { name: "scroll-snap-margin-inline", longhands: ["scroll-snap-margin-inline-start", "scroll-snap-margin-inline-end"], + api_class: true, + api_methods: ["parseShorthand"], runtime_flag: "CSSScrollSnapPoints", }, {
diff --git a/third_party/WebKit/Source/core/css/parser/CSSPropertyParser.cpp b/third_party/WebKit/Source/core/css/parser/CSSPropertyParser.cpp index efd3f16..9ee80dc4 100644 --- a/third_party/WebKit/Source/core/css/parser/CSSPropertyParser.cpp +++ b/third_party/WebKit/Source/core/css/parser/CSSPropertyParser.cpp
@@ -47,7 +47,6 @@ #include "core/css/properties/CSSPropertyPositionUtils.h" #include "core/css/properties/CSSPropertyTextDecorationLineUtils.h" #include "core/css/properties/CSSPropertyTransitionPropertyUtils.h" -#include "core/css/properties/CSSPropertyWebkitBorderWidthUtils.h" #include "core/frame/UseCounter.h" #include "core/layout/LayoutTheme.h" #include "platform/wtf/text/StringBuilder.h" @@ -1222,27 +1221,6 @@ case CSSPropertyGridRowGap: return ConsumeLengthOrPercent(range_, context_->Mode(), kValueRangeNonNegative); - case CSSPropertyBorderBottomColor: - case CSSPropertyBorderLeftColor: - case CSSPropertyBorderRightColor: - case CSSPropertyBorderTopColor: { - bool allow_quirky_colors = - InQuirksMode() && (current_shorthand == CSSPropertyInvalid || - current_shorthand == CSSPropertyBorderColor); - return ConsumeColor(range_, context_->Mode(), allow_quirky_colors); - } - case CSSPropertyBorderBottomWidth: - case CSSPropertyBorderLeftWidth: - case CSSPropertyBorderRightWidth: - case CSSPropertyBorderTopWidth: { - bool allow_quirky_lengths = - InQuirksMode() && (current_shorthand == CSSPropertyInvalid || - current_shorthand == CSSPropertyBorderWidth); - UnitlessQuirk unitless = - allow_quirky_lengths ? UnitlessQuirk::kAllow : UnitlessQuirk::kForbid; - return CSSPropertyWebkitBorderWidthUtils::ConsumeBorderWidth( - range_, context_->Mode(), unitless); - } case CSSPropertyFilter: case CSSPropertyBackdropFilter: return ConsumeFilter(range_, context_); @@ -2470,14 +2448,8 @@ return ConsumePlaceSelfShorthand(important); case CSSPropertyScrollPadding: return Consume4Values(scrollPaddingShorthand(), important); - case CSSPropertyScrollPaddingInline: - return Consume2Values(scrollPaddingInlineShorthand(), important); case CSSPropertyScrollSnapMargin: return Consume4Values(scrollSnapMarginShorthand(), important); - case CSSPropertyScrollSnapMarginBlock: - return Consume2Values(scrollSnapMarginBlockShorthand(), important); - case CSSPropertyScrollSnapMarginInline: - return Consume2Values(scrollSnapMarginInlineShorthand(), important); default: return false; }
diff --git a/third_party/WebKit/Source/core/css/properties/CSSPropertyAPIBorderColor.cpp b/third_party/WebKit/Source/core/css/properties/CSSPropertyAPIBorderColor.cpp new file mode 100644 index 0000000..9154894 --- /dev/null +++ b/third_party/WebKit/Source/core/css/properties/CSSPropertyAPIBorderColor.cpp
@@ -0,0 +1,27 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "core/css/properties/CSSPropertyAPIBorderColor.h" + +#include "core/CSSPropertyNames.h" +#include "core/css/parser/CSSParserContext.h" +#include "core/css/parser/CSSParserLocalContext.h" +#include "core/css/parser/CSSParserMode.h" +#include "core/css/parser/CSSPropertyParserHelpers.h" + +namespace blink { + +const CSSValue* CSSPropertyAPIBorderColor::parseSingleValue( + CSSParserTokenRange& range, + const CSSParserContext& context, + const CSSParserLocalContext& local_context) { + CSSPropertyID shorthand = local_context.CurrentShorthand(); + bool allow_quirky_colors = + IsQuirksModeBehavior(context.Mode()) && + (shorthand == CSSPropertyInvalid || shorthand == CSSPropertyBorderColor); + return CSSPropertyParserHelpers::ConsumeColor(range, context.Mode(), + allow_quirky_colors); +} + +} // namespace blink
diff --git a/third_party/WebKit/Source/core/css/properties/CSSPropertyAPIBorderWidth.cpp b/third_party/WebKit/Source/core/css/properties/CSSPropertyAPIBorderWidth.cpp index de961cb..cf1f4898 100644 --- a/third_party/WebKit/Source/core/css/properties/CSSPropertyAPIBorderWidth.cpp +++ b/third_party/WebKit/Source/core/css/properties/CSSPropertyAPIBorderWidth.cpp
@@ -4,4 +4,27 @@ #include "core/css/properties/CSSPropertyAPIBorderWidth.h" -namespace blink {} // namespace blink +#include "core/css/parser/CSSParserContext.h" +#include "core/css/parser/CSSParserLocalContext.h" +#include "core/css/parser/CSSParserMode.h" +#include "core/css/parser/CSSPropertyParserHelpers.h" +#include "core/css/properties/CSSPropertyWebkitBorderWidthUtils.h" + +namespace blink { + +const CSSValue* CSSPropertyAPIBorderWidth::parseSingleValue( + CSSParserTokenRange& range, + const CSSParserContext& context, + const CSSParserLocalContext& local_context) { + CSSPropertyID shorthand = local_context.CurrentShorthand(); + bool allow_quirky_lengths = + IsQuirksModeBehavior(context.Mode()) && + (shorthand == CSSPropertyInvalid || shorthand == CSSPropertyBorderWidth); + CSSPropertyParserHelpers::UnitlessQuirk unitless = + allow_quirky_lengths ? CSSPropertyParserHelpers::UnitlessQuirk::kAllow + : CSSPropertyParserHelpers::UnitlessQuirk::kForbid; + return CSSPropertyWebkitBorderWidthUtils::ConsumeBorderWidth( + range, context.Mode(), unitless); +} + +} // namespace blink
diff --git a/third_party/WebKit/Source/core/css/properties/CSSShorthandPropertyAPIScrollPaddingInline.cpp b/third_party/WebKit/Source/core/css/properties/CSSShorthandPropertyAPIScrollPaddingInline.cpp new file mode 100644 index 0000000..80591a0 --- /dev/null +++ b/third_party/WebKit/Source/core/css/properties/CSSShorthandPropertyAPIScrollPaddingInline.cpp
@@ -0,0 +1,21 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "core/css/properties/CSSShorthandPropertyAPIScrollPaddingInline.h" + +#include "core/StylePropertyShorthand.h" +#include "core/css/parser/CSSPropertyParserHelpers.h" + +namespace blink { + +bool CSSShorthandPropertyAPIScrollPaddingInline::parseShorthand( + bool important, + CSSParserTokenRange& range, + const CSSParserContext& context, + bool, + HeapVector<CSSProperty, 256>& properties) { + return CSSPropertyParserHelpers::ConsumeShorthandVia2LonghandAPIs( + scrollPaddingInlineShorthand(), important, context, range, properties); +} +} // namespace blink
diff --git a/third_party/WebKit/Source/core/css/properties/CSSShorthandPropertyAPIScrollSnapMarginBlock.cpp b/third_party/WebKit/Source/core/css/properties/CSSShorthandPropertyAPIScrollSnapMarginBlock.cpp new file mode 100644 index 0000000..ac315e25 --- /dev/null +++ b/third_party/WebKit/Source/core/css/properties/CSSShorthandPropertyAPIScrollSnapMarginBlock.cpp
@@ -0,0 +1,21 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "core/css/properties/CSSShorthandPropertyAPIScrollSnapMarginBlock.h" + +#include "core/StylePropertyShorthand.h" +#include "core/css/parser/CSSPropertyParserHelpers.h" + +namespace blink { + +bool CSSShorthandPropertyAPIScrollSnapMarginBlock::parseShorthand( + bool important, + CSSParserTokenRange& range, + const CSSParserContext& context, + bool, + HeapVector<CSSProperty, 256>& properties) { + return CSSPropertyParserHelpers::ConsumeShorthandVia2LonghandAPIs( + scrollSnapMarginBlockShorthand(), important, context, range, properties); +} +} // namespace blink
diff --git a/third_party/WebKit/Source/core/css/properties/CSSShorthandPropertyAPIScrollSnapMarginInline.cpp b/third_party/WebKit/Source/core/css/properties/CSSShorthandPropertyAPIScrollSnapMarginInline.cpp new file mode 100644 index 0000000..c76ec0f9 --- /dev/null +++ b/third_party/WebKit/Source/core/css/properties/CSSShorthandPropertyAPIScrollSnapMarginInline.cpp
@@ -0,0 +1,21 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "core/css/properties/CSSShorthandPropertyAPIScrollSnapMarginInline.h" + +#include "core/StylePropertyShorthand.h" +#include "core/css/parser/CSSPropertyParserHelpers.h" + +namespace blink { + +bool CSSShorthandPropertyAPIScrollSnapMarginInline::parseShorthand( + bool important, + CSSParserTokenRange& range, + const CSSParserContext& context, + bool, + HeapVector<CSSProperty, 256>& properties) { + return CSSPropertyParserHelpers::ConsumeShorthandVia2LonghandAPIs( + scrollSnapMarginInlineShorthand(), important, context, range, properties); +} +} // namespace blink
diff --git a/third_party/WebKit/Source/core/editing/FrameSelection.cpp b/third_party/WebKit/Source/core/editing/FrameSelection.cpp index 20af29d4..1a781f93 100644 --- a/third_party/WebKit/Source/core/editing/FrameSelection.cpp +++ b/third_party/WebKit/Source/core/editing/FrameSelection.cpp
@@ -1163,6 +1163,13 @@ return layout_selection_->SelectionStartEnd(); } +base::Optional<int> FrameSelection::LayoutSelectionStart() const { + return layout_selection_->SelectionStart(); +} +base::Optional<int> FrameSelection::LayoutSelectionEnd() const { + return layout_selection_->SelectionEnd(); +} + void FrameSelection::ClearLayoutSelection() { layout_selection_->ClearSelection(); }
diff --git a/third_party/WebKit/Source/core/editing/FrameSelection.h b/third_party/WebKit/Source/core/editing/FrameSelection.h index 17e3f40d..4f0b8c95 100644 --- a/third_party/WebKit/Source/core/editing/FrameSelection.h +++ b/third_party/WebKit/Source/core/editing/FrameSelection.h
@@ -244,6 +244,8 @@ FrameCaret& FrameCaretForTesting() const { return *frame_caret_; } std::pair<int, int> LayoutSelectionStartEnd(); + base::Optional<int> LayoutSelectionStart() const; + base::Optional<int> LayoutSelectionEnd() const; void ClearLayoutSelection(); DECLARE_TRACE();
diff --git a/third_party/WebKit/Source/core/editing/LayoutSelection.cpp b/third_party/WebKit/Source/core/editing/LayoutSelection.cpp index f23f783..2606def 100644 --- a/third_party/WebKit/Source/core/editing/LayoutSelection.cpp +++ b/third_party/WebKit/Source/core/editing/LayoutSelection.cpp
@@ -297,6 +297,20 @@ return std::make_pair(paint_range_.StartOffset(), paint_range_.EndOffset()); } +base::Optional<int> LayoutSelection::SelectionStart() const { + DCHECK(!HasPendingSelection()); + if (paint_range_.IsNull()) + return {}; + return paint_range_.StartOffset(); +} + +base::Optional<int> LayoutSelection::SelectionEnd() const { + DCHECK(!HasPendingSelection()); + if (paint_range_.IsNull()) + return {}; + return paint_range_.EndOffset(); +} + void LayoutSelection::ClearSelection() { // For querying Layer::compositingState() // This is correct, since destroying layout objects needs to cause eager paint
diff --git a/third_party/WebKit/Source/core/editing/LayoutSelection.h b/third_party/WebKit/Source/core/editing/LayoutSelection.h index 84b27263..f56967b 100644 --- a/third_party/WebKit/Source/core/editing/LayoutSelection.h +++ b/third_party/WebKit/Source/core/editing/LayoutSelection.h
@@ -100,7 +100,10 @@ void InvalidatePaintForSelection(); void ClearSelection(); + // TODO(yoiciho): Replace SelectionStartEnd with SelectionStart/End. std::pair<int, int> SelectionStartEnd(); + base::Optional<int> SelectionStart() const; + base::Optional<int> SelectionEnd() const; void OnDocumentShutdown(); DECLARE_TRACE();
diff --git a/third_party/WebKit/Source/core/page/TouchAdjustment.cpp b/third_party/WebKit/Source/core/page/TouchAdjustment.cpp index d3656cc..2963e93 100644 --- a/third_party/WebKit/Source/core/page/TouchAdjustment.cpp +++ b/third_party/WebKit/Source/core/page/TouchAdjustment.cpp
@@ -204,6 +204,8 @@ } else { if (text_layout_object->GetSelectionState() == SelectionState::kNone) return AppendBasicSubtargetsForNode(node, subtargets); + const FrameSelection& frame_selection = + text_layout_object->GetFrame()->Selection(); // If selected, make subtargets out of only the selected part of the text. int start_pos, end_pos; switch (text_layout_object->GetSelectionState()) { @@ -212,15 +214,16 @@ end_pos = text_layout_object->TextLength(); break; case SelectionState::kStart: - std::tie(start_pos, end_pos) = text_layout_object->SelectionStartEnd(); + start_pos = frame_selection.LayoutSelectionStart().value(); end_pos = text_layout_object->TextLength(); break; case SelectionState::kEnd: - std::tie(start_pos, end_pos) = text_layout_object->SelectionStartEnd(); start_pos = 0; + end_pos = frame_selection.LayoutSelectionEnd().value(); break; case SelectionState::kStartAndEnd: - std::tie(start_pos, end_pos) = text_layout_object->SelectionStartEnd(); + start_pos = frame_selection.LayoutSelectionStart().value(); + end_pos = frame_selection.LayoutSelectionEnd().value(); break; default: NOTREACHED();
diff --git a/third_party/WebKit/Source/core/streams/README.md b/third_party/WebKit/Source/core/streams/README.md index 44a89f51..78e10fc 100644 --- a/third_party/WebKit/Source/core/streams/README.md +++ b/third_party/WebKit/Source/core/streams/README.md
@@ -1,26 +1,8 @@ -This directory contains the Blink implementation of the WHATWG Streams standard: -https://streams.spec.whatwg.org/. There is also a legacy streams implementation. +# core/streams/ -## V8 Extras ReadableStream Implementation +This directory contains the Blink implementation of [the WHATWG Streams standard][1]. -- ByteLengthQueuingStrategy.js -- CountQueuingStrategy.js -- ReadableStream.js -- ReadableStreamExperimentalPipeTo.js -- ReadableStreamController.h -- ReadableStreamOperations.{cpp,h} -- UnderlyingSourceBase.{cpp,h,idl} -- WritableStream.js +We use [V8 extras][2] to implement it. -These files implement ReadableStream using [V8 extras][1]. All new code should -use this implementation. - -[1]: https://docs.google.com/document/d/1AT5-T0aHGp7Lt29vPWFr2-qG8r3l9CByyvKwEuA8Ec0 - -## Old streams - -- Stream.{cpp,h,idl} - -These files support an old streams spec. They should eventually be removed, but -right now XMLHttpRequest and Media Streams Extension code in Blink still -depends on them. +[1]: https://streams.spec.whatwg.org/ +[2]: https://docs.google.com/document/d/1AT5-T0aHGp7Lt29vPWFr2-qG8r3l9CByyvKwEuA8Ec0
diff --git a/third_party/WebKit/Source/modules/serviceworkers/ServiceWorkerGlobalScope.cpp b/third_party/WebKit/Source/modules/serviceworkers/ServiceWorkerGlobalScope.cpp index d3aeb17..d9832983 100644 --- a/third_party/WebKit/Source/modules/serviceworkers/ServiceWorkerGlobalScope.cpp +++ b/third_party/WebKit/Source/modules/serviceworkers/ServiceWorkerGlobalScope.cpp
@@ -84,6 +84,7 @@ startup_data->worker_v8_settings_.v8_cache_options_); context->ApplyContentSecurityPolicyFromVector( *startup_data->content_security_policy_headers_); + context->SetWorkerSettings(std::move(startup_data->worker_settings_)); if (!startup_data->referrer_policy_.IsNull()) context->ParseAndSetReferrerPolicy(startup_data->referrer_policy_); context->SetAddressSpace(startup_data->address_space_);
diff --git a/third_party/WebKit/Source/platform/loader/fetch/MemoryCacheCorrectnessTest.cpp b/third_party/WebKit/Source/platform/loader/fetch/MemoryCacheCorrectnessTest.cpp index 36ed404..e5b4f76b 100644 --- a/third_party/WebKit/Source/platform/loader/fetch/MemoryCacheCorrectnessTest.cpp +++ b/third_party/WebKit/Source/platform/loader/fetch/MemoryCacheCorrectnessTest.cpp
@@ -61,7 +61,6 @@ if (response.Url().IsNull()) response.SetURL(KURL(kParsedURLString, kResourceURL)); ResourceRequest request(response.Url()); - request.SetFetchCredentialsMode(WebURLRequest::kFetchCredentialsModeOmit); MockResource* resource = MockResource::Create(request); resource->SetResponse(response); resource->Finish(); @@ -353,7 +352,6 @@ KURL redirect_target_url(kParsedURLString, kRedirectTargetUrlString); ResourceRequest request(redirect_url); - request.SetFetchCredentialsMode(WebURLRequest::kFetchCredentialsModeOmit); MockResource* first_resource = MockResource::Create(request); ResourceResponse fresh301_response; @@ -431,7 +429,7 @@ TEST_F(MemoryCacheCorrectnessTest, PostToSameURLTwice) { ResourceRequest request1(KURL(kParsedURLString, kResourceURL)); request1.SetHTTPMethod(HTTPNames::POST); - RawResource* resource1 = RawResource::Create(request1, Resource::kRaw); + RawResource* resource1 = RawResource::CreateForTest(request1, Resource::kRaw); resource1->SetStatus(ResourceStatus::kPending); GetMemoryCache()->Add(resource1); @@ -491,7 +489,6 @@ KURL redirect_target_url(kParsedURLString, kRedirectTargetUrlString); ResourceRequest request(redirect_url); - request.SetFetchCredentialsMode(WebURLRequest::kFetchCredentialsModeOmit); MockResource* first_resource = MockResource::Create(request); ResourceResponse fresh302_response; @@ -532,7 +529,6 @@ KURL redirect_target_url(kParsedURLString, kRedirectTargetUrlString); ResourceRequest request(redirect_url); - request.SetFetchCredentialsMode(WebURLRequest::kFetchCredentialsModeOmit); MockResource* first_resource = MockResource::Create(request); ResourceResponse fresh302_response;
diff --git a/third_party/WebKit/Source/platform/loader/fetch/RawResource.h b/third_party/WebKit/Source/platform/loader/fetch/RawResource.h index f80ae88..189e65c0 100644 --- a/third_party/WebKit/Source/platform/loader/fetch/RawResource.h +++ b/third_party/WebKit/Source/platform/loader/fetch/RawResource.h
@@ -52,14 +52,13 @@ static RawResource* FetchManifest(FetchParameters&, ResourceFetcher*); // Exposed for testing - static RawResource* Create(ResourceRequest request, Type type) { - request.SetFetchCredentialsMode(WebURLRequest::kFetchCredentialsModeOmit); + static RawResource* CreateForTest(ResourceRequest request, Type type) { ResourceLoaderOptions options; return new RawResource(request, type, options); } static RawResource* CreateForTest(const KURL& url, Type type) { ResourceRequest request(url); - return Create(request, type); + return CreateForTest(request, type); } static RawResource* CreateForTest(const char* url, Type type) { return CreateForTest(KURL(kParsedURLString, url), type);
diff --git a/third_party/WebKit/Source/platform/loader/fetch/RawResourceTest.cpp b/third_party/WebKit/Source/platform/loader/fetch/RawResourceTest.cpp index 5e1fc108..f925f9fe 100644 --- a/third_party/WebKit/Source/platform/loader/fetch/RawResourceTest.cpp +++ b/third_party/WebKit/Source/platform/loader/fetch/RawResourceTest.cpp
@@ -111,7 +111,8 @@ ResourceRequest jpeg_request; jpeg_request.SetHTTPAccept("image/jpeg"); - RawResource* jpeg_resource(RawResource::Create(jpeg_request, Resource::kRaw)); + RawResource* jpeg_resource( + RawResource::CreateForTest(jpeg_request, Resource::kRaw)); ResourceRequest png_request; png_request.SetHTTPAccept("image/png"); @@ -579,7 +580,7 @@ ResourceRequest request("data:text/html,"); request.SetHTTPHeaderField( HTTPNames::X_DevTools_Emulate_Network_Conditions_Client_Id, "Foo"); - Resource* raw = RawResource::Create(request, Resource::kRaw); + Resource* raw = RawResource::CreateForTest(request, Resource::kRaw); EXPECT_TRUE( raw->CanReuse(FetchParameters(ResourceRequest("data:text/html,")))); }
diff --git a/third_party/WebKit/Source/platform/loader/fetch/Resource.cpp b/third_party/WebKit/Source/platform/loader/fetch/Resource.cpp index 88bf5f93..ef87feef 100644 --- a/third_party/WebKit/Source/platform/loader/fetch/Resource.cpp +++ b/third_party/WebKit/Source/platform/loader/fetch/Resource.cpp
@@ -893,7 +893,9 @@ if (existing_was_with_fetcher_cors_suppressed) return new_mode != WebURLRequest::kFetchRequestModeCORS; - return existing_mode == new_mode; + return existing_mode == new_mode && + new_request.GetFetchCredentialsMode() == + resource_request_.GetFetchCredentialsMode(); } void Resource::Prune() {
diff --git a/third_party/WebKit/Source/platform/loader/fetch/ResourceFetcher.cpp b/third_party/WebKit/Source/platform/loader/fetch/ResourceFetcher.cpp index e7ea140e..4fd85f2 100644 --- a/third_party/WebKit/Source/platform/loader/fetch/ResourceFetcher.cpp +++ b/third_party/WebKit/Source/platform/loader/fetch/ResourceFetcher.cpp
@@ -62,14 +62,6 @@ namespace { -// Events for UMA. Do not reorder or delete. Add new events at the end, but -// before SriResourceIntegrityMismatchEventCount. -enum SriResourceIntegrityMismatchEvent { - kCheckingForIntegrityMismatch = 0, - kRefetchDueToIntegrityMismatch = 1, - kSriResourceIntegrityMismatchEventCount -}; - #define DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, name) \ case Resource::k##name: { \ DEFINE_THREAD_SAFE_STATIC_LOCAL( \ @@ -110,14 +102,6 @@ } } -void RecordSriResourceIntegrityMismatchEvent( - SriResourceIntegrityMismatchEvent event) { - DEFINE_THREAD_SAFE_STATIC_LOCAL(EnumerationHistogram, integrity_histogram, - ("sri.resource_integrity_mismatch_event", - kSriResourceIntegrityMismatchEventCount)); - integrity_histogram.Count(event); -} - ResourceLoadPriority TypeToPriority(Resource::Type type) { switch (type) { case Resource::kMainResource: @@ -911,7 +895,6 @@ Resource* resource = it->value; - RecordSriResourceIntegrityMismatchEvent(kCheckingForIntegrityMismatch); if (resource->MustRefetchDueToIntegrityMetadata(params)) return nullptr; @@ -1029,9 +1012,7 @@ // resource must be made to get the raw data. This is expected to be an // uncommon case, however, as it implies two same-origin requests to the same // resource, but with different integrity metadata. - RecordSriResourceIntegrityMismatchEvent(kCheckingForIntegrityMismatch); if (existing_resource->MustRefetchDueToIntegrityMetadata(fetch_params)) { - RecordSriResourceIntegrityMismatchEvent(kRefetchDueToIntegrityMismatch); return kReload; }
diff --git a/third_party/WebKit/Source/platform/loader/fetch/ResourceTest.cpp b/third_party/WebKit/Source/platform/loader/fetch/ResourceTest.cpp index 0c46634..2b233ec 100644 --- a/third_party/WebKit/Source/platform/loader/fetch/ResourceTest.cpp +++ b/third_party/WebKit/Source/platform/loader/fetch/ResourceTest.cpp
@@ -125,7 +125,7 @@ ResourceRequest old_request(url); old_request.SetHTTPHeaderField(HTTPNames::User_Agent, "something"); old_request.SetHTTPHeaderField(HTTPNames::Referer, "http://foo.com"); - resource = RawResource::Create(old_request, Resource::kRaw); + resource = RawResource::CreateForTest(old_request, Resource::kRaw); resource->ResponseReceived(response, nullptr); resource->Finish();
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml index f320c9f..66ef55a 100644 --- a/tools/metrics/histograms/histograms.xml +++ b/tools/metrics/histograms/histograms.xml
@@ -74169,6 +74169,9 @@ <histogram name="sri.resource_integrity_mismatch_event" enum="SRIResourceIntegrityMismatchEvent"> + <obsolete> + Removed 07/2017. Reference: crbug.com/708041 + </obsolete> <owner>jww@chromium.org</owner> <summary> When resources are checked for mismatching integrity and whether the
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml index 07d6aeb..0e5863b 100644 --- a/tools/metrics/ukm/ukm.xml +++ b/tools/metrics/ukm/ukm.xml
@@ -708,6 +708,28 @@ enum ManagerAutofillEvent. </summary> </metric> + <metric name="Saving.Prompt.Interaction"> + <summary> + Records how the user interacted with a saving prompt. Recorded values + correspond to the enum PasswordFormMetricsRecorder::BubbleDismissalReason. + </summary> + </metric> + <metric name="Saving.Prompt.Shown"> + <summary> + Records, for each password form seen by the password manager, whether the + user was shown a prompt that asked for permission to save new credentials. + In case a store() via the Credential Management API, a virtual password + form is created, for which this metric is recorded. Recorded values are 0 + and 1 for false and true. + </summary> + </metric> + <metric name="Saving.Prompt.Trigger"> + <summary> + Records the trigger of each password save bubble that was shown to the + user to ask for permission to save new credentials. Recorded values + correspond to the enum PasswordFormMetricsRecorder::BubbleTrigger. + </summary> + </metric> <metric name="Submission.Observed"> <summary> Records whether a submission of a password form has been observered. The @@ -808,6 +830,26 @@ enum SuppressedAccountExistence. </summary> </metric> + <metric name="Updating.Prompt.Interaction"> + <summary> + Records how the user interacted with an updating prompt. Recorded values + correspond to the enum PasswordFormMetricsRecorder::BubbleDismissalReason. + </summary> + </metric> + <metric name="Updating.Prompt.Shown"> + <summary> + Records, for each password form seen by the password manager, whether the + user was shown a prompt that asked for permission to update existing + credentials. Recorded values are 0 and 1 for false and true. + </summary> + </metric> + <metric name="Updating.Prompt.Trigger"> + <summary> + Records the trigger of each password update bubble that was shown to the + user to ask for permission to update existing credentials. Recorded values + correspond to the enum PasswordFormMetricsRecorder::BubbleTrigger. + </summary> + </metric> </event> <event name="PageWithPassword">
diff --git a/tools/traffic_annotation/auditor/tests/extractor_outputs/bad_call.txt b/tools/traffic_annotation/auditor/tests/extractor_outputs/bad_call.txt new file mode 100644 index 0000000..ea130c4 --- /dev/null +++ b/tools/traffic_annotation/auditor/tests/extractor_outputs/bad_call.txt
@@ -0,0 +1,4 @@ +headless/public/util/http_url_fetcher.cc +headless::HttpURLFetcher::Delegate::Delegate +net::URLRequestContext::CreateRequest +1 \ No newline at end of file
diff --git a/tools/traffic_annotation/auditor/tests/extractor_outputs/bad_syntax_annotation1.txt b/tools/traffic_annotation/auditor/tests/extractor_outputs/bad_syntax_annotation1.txt new file mode 100644 index 0000000..8be2614 --- /dev/null +++ b/tools/traffic_annotation/auditor/tests/extractor_outputs/bad_syntax_annotation1.txt
@@ -0,0 +1,36 @@ +chrome/browser/supervised_user/legacy/supervised_user_refresh_token_fetcher.cc +OnGetTokenSuccess +166 +Definition +supervised_user_refresh_token_fetcher + + + semantics + sender: "Supervised Users" + description: + "Fetches an OAuth2 refresh token scoped down to the Supervised " + "User Sync scope and tied to the given Supervised User ID, " + "identifying the Supervised User Profile to be created." + trigger: + "Called when creating a new Supervised User profile in Chromium " + "to fetch OAuth credentials for using Sync with the new profile." + data: + "The request is authenticated with an OAuth2 access token " + "identifying the Google account and contains the following " + "information:\n* The Supervised User ID, a randomly generated " + "64-bit identifier for the profile.\n* The device name, to " + "identify the refresh token in account management." + destination: GOOGLE_OWNED_SERVICE + } + policy { + cookies_allowed: false + setting: + "Users can disable this feature by toggling 'Let anyone add a " + "person to Chrome' in Chromium settings, under People." + chrome_policy { + SupervisedUserCreationEnabled { + policy_options {mode: MANDATORY} + SupervisedUserCreationEnabled: false + } + } + } \ No newline at end of file
diff --git a/tools/traffic_annotation/auditor/tests/extractor_outputs/bad_syntax_annotation2.txt b/tools/traffic_annotation/auditor/tests/extractor_outputs/bad_syntax_annotation2.txt new file mode 100644 index 0000000..dbb0d6f --- /dev/null +++ b/tools/traffic_annotation/auditor/tests/extractor_outputs/bad_syntax_annotation2.txt
@@ -0,0 +1,36 @@ +chrome/browser/supervised_user/legacy/supervised_user_refresh_token_fetcher.cc +OnGetTokenSuccess +166 +Definition +supervised_user_refresh_token_fetcher + + + Semantics { + sender: "Supervised Users" + description: + "Fetches an OAuth2 refresh token scoped down to the Supervised " + "User Sync scope and tied to the given Supervised User ID, " + "identifying the Supervised User Profile to be created." + trigger: + "Called when creating a new Supervised User profile in Chromium " + "to fetch OAuth credentials for using Sync with the new profile." + data: + "The request is authenticated with an OAuth2 access token " + "identifying the Google account and contains the following " + "information:\n* The Supervised User ID, a randomly generated " + "64-bit identifier for the profile.\n* The device name, to " + "identify the refresh token in account management." + destination: GOOGLE_OWNED_SERVICE + } + policy { + cookies_allowed: false + setting: + "Users can disable this feature by toggling 'Let anyone add a " + "person to Chrome' in Chromium settings, under People." + chrome_policy { + SupervisedUserCreationEnabled { + policy_options {mode: MANDATORY} + SupervisedUserCreationEnabled: false + } + } + } \ No newline at end of file
diff --git a/tools/traffic_annotation/auditor/tests/extractor_outputs/bad_syntax_annotation3.txt b/tools/traffic_annotation/auditor/tests/extractor_outputs/bad_syntax_annotation3.txt new file mode 100644 index 0000000..bb6ee7b --- /dev/null +++ b/tools/traffic_annotation/auditor/tests/extractor_outputs/bad_syntax_annotation3.txt
@@ -0,0 +1,36 @@ +chrome/browser/supervised_user/legacy/supervised_user_refresh_token_fetcher.cc +OnGetTokenSuccess +166 +Definition +supervised_user_refresh_token_fetcher + + + Semantics { + sender: "Supervised Users + description: + "Fetches an OAuth2 refresh token scoped down to the Supervised " + "User Sync scope and tied to the given Supervised User ID, " + "identifying the Supervised User Profile to be created." + trigger: + "Called when creating a new Supervised User profile in Chromium " + "to fetch OAuth credentials for using Sync with the new profile." + data: + "The request is authenticated with an OAuth2 access token " + "identifying the Google account and contains the following " + "information:\n* The Supervised User ID, a randomly generated " + "64-bit identifier for the profile.\n* The device name, to " + "identify the refresh token in account management." + destination: GOOGLE_OWNED_SERVICE + } + policy { + cookies_allowed: false + setting: + "Users can disable this feature by toggling 'Let anyone add a " + "person to Chrome' in Chromium settings, under People." + chrome_policy { + SupervisedUserCreationEnabled { + policy_options {mode: MANDATORY} + SupervisedUserCreationEnabled: false + } + } + } \ No newline at end of file
diff --git a/tools/traffic_annotation/auditor/tests/extractor_outputs/bad_syntax_annotation4.txt b/tools/traffic_annotation/auditor/tests/extractor_outputs/bad_syntax_annotation4.txt new file mode 100644 index 0000000..8133f72 --- /dev/null +++ b/tools/traffic_annotation/auditor/tests/extractor_outputs/bad_syntax_annotation4.txt
@@ -0,0 +1,34 @@ +chrome/browser/supervised_user/legacy/supervised_user_refresh_token_fetcher.cc +OnGetTokenSuccess +166 +Definition +supervised_user_refresh_token_fetcher + + + Semantics { + sender: "Supervised Users + description: + "Fetches an OAuth2 refresh token scoped down to the Supervised " + "User Sync scope and tied to the given Supervised User ID, " + "identifying the Supervised User Profile to be created." + trigger: + "Called when creating a new Supervised User profile in Chromium " + "to fetch OAuth credentials for using Sync with the new profile." + data: + "The request is authenticated with an OAuth2 access token " + "identifying the Google account and contains the following " + "information:\n* The Supervised User ID, a randomly generated " + "64-bit identifier for the profile.\n* The device name, to " + "identify the refresh token in account management." + destination: GOOGLE_OWNED_SERVICE + cookies_allowed: false + setting: + "Users can disable this feature by toggling 'Let anyone add a " + "person to Chrome' in Chromium settings, under People." + chrome_policy { + SupervisedUserCreationEnabled { + policy_options {mode: MANDATORY} + SupervisedUserCreationEnabled: false + } + } + } \ No newline at end of file
diff --git a/tools/traffic_annotation/auditor/tests/extractor_outputs/fatal_annotation1.txt b/tools/traffic_annotation/auditor/tests/extractor_outputs/fatal_annotation1.txt new file mode 100644 index 0000000..b4999cb --- /dev/null +++ b/tools/traffic_annotation/auditor/tests/extractor_outputs/fatal_annotation1.txt
@@ -0,0 +1,35 @@ +chrome/browser/supervised_user/legacy/supervised_user_refresh_token_fetcher.cc +OnGetTokenSuccess +Definition +supervised_user_refresh_token_fetcher + + + semantics { + sender: "Supervised Users" + description: + "Fetches an OAuth2 refresh token scoped down to the Supervised " + "User Sync scope and tied to the given Supervised User ID, " + "identifying the Supervised User Profile to be created." + trigger: + "Called when creating a new Supervised User profile in Chromium " + "to fetch OAuth credentials for using Sync with the new profile." + data: + "The request is authenticated with an OAuth2 access token " + "identifying the Google account and contains the following " + "information:\n* The Supervised User ID, a randomly generated " + "64-bit identifier for the profile.\n* The device name, to " + "identify the refresh token in account management." + destination: GOOGLE_OWNED_SERVICE + } + policy { + cookies_allowed: false + setting: + "Users can disable this feature by toggling 'Let anyone add a " + "person to Chrome' in Chromium settings, under People." + chrome_policy { + SupervisedUserCreationEnabled { + policy_options {mode: MANDATORY} + SupervisedUserCreationEnabled: false + } + } + } \ No newline at end of file
diff --git a/tools/traffic_annotation/auditor/tests/extractor_outputs/fatal_annotation2.txt b/tools/traffic_annotation/auditor/tests/extractor_outputs/fatal_annotation2.txt new file mode 100644 index 0000000..501d3b8fd --- /dev/null +++ b/tools/traffic_annotation/auditor/tests/extractor_outputs/fatal_annotation2.txt
@@ -0,0 +1,36 @@ +chrome/browser/supervised_user/legacy/supervised_user_refresh_token_fetcher.cc +OnGetTokenSuccess +122 +definition +supervised_user_refresh_token_fetcher + + + semantics { + sender: "Supervised Users" + description: + "Fetches an OAuth2 refresh token scoped down to the Supervised " + "User Sync scope and tied to the given Supervised User ID, " + "identifying the Supervised User Profile to be created." + trigger: + "Called when creating a new Supervised User profile in Chromium " + "to fetch OAuth credentials for using Sync with the new profile." + data: + "The request is authenticated with an OAuth2 access token " + "identifying the Google account and contains the following " + "information:\n* The Supervised User ID, a randomly generated " + "64-bit identifier for the profile.\n* The device name, to " + "identify the refresh token in account management." + destination: GOOGLE_OWNED_SERVICE + } + policy { + cookies_allowed: false + setting: + "Users can disable this feature by toggling 'Let anyone add a " + "person to Chrome' in Chromium settings, under People." + chrome_policy { + SupervisedUserCreationEnabled { + policy_options {mode: MANDATORY} + SupervisedUserCreationEnabled: false + } + } + } \ No newline at end of file
diff --git a/tools/traffic_annotation/auditor/tests/extractor_outputs/fatal_annotation3.txt b/tools/traffic_annotation/auditor/tests/extractor_outputs/fatal_annotation3.txt new file mode 100644 index 0000000..7e89f6f --- /dev/null +++ b/tools/traffic_annotation/auditor/tests/extractor_outputs/fatal_annotation3.txt
@@ -0,0 +1,5 @@ +chrome/browser/supervised_user/legacy/supervised_user_refresh_token_fetcher.cc +OnGetTokenSuccess +122 +definition +supervised_user_refresh_token_fetcher
diff --git a/tools/traffic_annotation/auditor/tests/extractor_outputs/good_branched_completing_annotation.txt b/tools/traffic_annotation/auditor/tests/extractor_outputs/good_branched_completing_annotation.txt new file mode 100644 index 0000000..c1c8ad2 --- /dev/null +++ b/tools/traffic_annotation/auditor/tests/extractor_outputs/good_branched_completing_annotation.txt
@@ -0,0 +1,22 @@ +chrome/service/cloud_print/cloud_print_url_fetcher.cc +cloud_print::CloudPrintURLFetcher::StartRequestHelper +265 +BranchedCompleting +cloud_print + + + semantics { + sender: "Cloud Print" + destination: GOOGLE_OWNED_SERVICE + } + policy { + cookies_allowed: false + setting: + "This feature cannot be disabled by settings." + chrome_policy { + CloudPrintProxyEnabled { + policy_options {mode: MANDATORY} + CloudPrintProxyEnabled: false + } + } + } \ No newline at end of file
diff --git a/tools/traffic_annotation/auditor/tests/extractor_outputs/good_call.txt b/tools/traffic_annotation/auditor/tests/extractor_outputs/good_call.txt new file mode 100644 index 0000000..e74c34d2 --- /dev/null +++ b/tools/traffic_annotation/auditor/tests/extractor_outputs/good_call.txt
@@ -0,0 +1,5 @@ +headless/public/util/http_url_fetcher.cc +headless::HttpURLFetcher::Delegate::Delegate +100 +net::URLRequestContext::CreateRequest +1 \ No newline at end of file
diff --git a/tools/traffic_annotation/auditor/tests/extractor_outputs/good_complete_annotation.txt b/tools/traffic_annotation/auditor/tests/extractor_outputs/good_complete_annotation.txt new file mode 100644 index 0000000..ba7ab2c --- /dev/null +++ b/tools/traffic_annotation/auditor/tests/extractor_outputs/good_complete_annotation.txt
@@ -0,0 +1,36 @@ +chrome/browser/supervised_user/legacy/supervised_user_refresh_token_fetcher.cc +OnGetTokenSuccess +166 +Definition +supervised_user_refresh_token_fetcher + + + semantics { + sender: "Supervised Users" + description: + "Fetches an OAuth2 refresh token scoped down to the Supervised " + "User Sync scope and tied to the given Supervised User ID, " + "identifying the Supervised User Profile to be created." + trigger: + "Called when creating a new Supervised User profile in Chromium " + "to fetch OAuth credentials for using Sync with the new profile." + data: + "The request is authenticated with an OAuth2 access token " + "identifying the Google account and contains the following " + "information:\n* The Supervised User ID, a randomly generated " + "64-bit identifier for the profile.\n* The device name, to " + "identify the refresh token in account management." + destination: GOOGLE_OWNED_SERVICE + } + policy { + cookies_allowed: false + setting: + "Users can disable this feature by toggling 'Let anyone add a " + "person to Chrome' in Chromium settings, under People." + chrome_policy { + SupervisedUserCreationEnabled { + policy_options {mode: MANDATORY} + SupervisedUserCreationEnabled: false + } + } + } \ No newline at end of file
diff --git a/tools/traffic_annotation/auditor/tests/extractor_outputs/good_completing_annotation.txt b/tools/traffic_annotation/auditor/tests/extractor_outputs/good_completing_annotation.txt new file mode 100644 index 0000000..a29327e --- /dev/null +++ b/tools/traffic_annotation/auditor/tests/extractor_outputs/good_completing_annotation.txt
@@ -0,0 +1,22 @@ +chrome/service/cloud_print/cloud_print_url_fetcher.cc +cloud_print::CloudPrintURLFetcher::StartRequestHelper +265 +Completing +cloud_print + + + semantics { + sender: "Cloud Print" + destination: GOOGLE_OWNED_SERVICE + } + policy { + cookies_allowed: false + setting: + "This feature cannot be disabled by settings." + chrome_policy { + CloudPrintProxyEnabled { + policy_options {mode: MANDATORY} + CloudPrintProxyEnabled: false + } + } + } \ No newline at end of file
diff --git a/tools/traffic_annotation/auditor/tests/extractor_outputs/good_partial_annotation.txt b/tools/traffic_annotation/auditor/tests/extractor_outputs/good_partial_annotation.txt new file mode 100644 index 0000000..07422ab --- /dev/null +++ b/tools/traffic_annotation/auditor/tests/extractor_outputs/good_partial_annotation.txt
@@ -0,0 +1,26 @@ +components/browsing_data/core/counters/history_counter.cc +browsing_data::HistoryCounter::Count +98 +Partial +web_history_counter +web_history_service + + semantics { + description: + "If history sync is enabled, this queries history.google.com to " + "determine if there is any synced history. This information is " + "displayed in the Clear Browsing Data dialog." + trigger: + "Checking the 'Browsing history' option in the Clear Browsing Data " + "dialog, or enabling history sync while the dialog is open." + data: + "A version info token to resolve transaction conflicts, and an " + "OAuth2 token authenticating the user." + } + policy { + chrome_policy { + SyncDisabled { + SyncDisabled: true + } + } + } \ No newline at end of file
diff --git a/tools/traffic_annotation/auditor/tests/extractor_outputs/good_test_annotation.txt b/tools/traffic_annotation/auditor/tests/extractor_outputs/good_test_annotation.txt new file mode 100644 index 0000000..07bd917 --- /dev/null +++ b/tools/traffic_annotation/auditor/tests/extractor_outputs/good_test_annotation.txt
@@ -0,0 +1,7 @@ +google_apis/drive/request_sender_unittest.cc +google_apis::(anonymous namespace)::RequestSenderTest::RequestSenderTest +67 +Definition +test + +Traffic annotation for unit, browser and other tests \ No newline at end of file
diff --git a/tools/traffic_annotation/auditor/tests/extractor_outputs/missing_annotation.txt b/tools/traffic_annotation/auditor/tests/extractor_outputs/missing_annotation.txt new file mode 100644 index 0000000..2efb3c3 --- /dev/null +++ b/tools/traffic_annotation/auditor/tests/extractor_outputs/missing_annotation.txt
@@ -0,0 +1,7 @@ +net/url_request/url_request_context.cc +net::URLRequestContext::CreateRequest +120 +Definition +missing + +Function called without traffic annotation.
diff --git a/tools/traffic_annotation/auditor/tests/extractor_outputs/no_annotation.txt b/tools/traffic_annotation/auditor/tests/extractor_outputs/no_annotation.txt new file mode 100644 index 0000000..e8b2838 --- /dev/null +++ b/tools/traffic_annotation/auditor/tests/extractor_outputs/no_annotation.txt
@@ -0,0 +1,7 @@ +components/download/internal/controller_impl.cc +download::ControllerImpl::UpdateDriverState +656 +Definition +undefined + +Nothing here yet. \ No newline at end of file
diff --git a/tools/traffic_annotation/auditor/traffic_annotation_auditor.cc b/tools/traffic_annotation/auditor/traffic_annotation_auditor.cc index bc965a59..cbe0120d 100644 --- a/tools/traffic_annotation/auditor/traffic_annotation_auditor.cc +++ b/tools/traffic_annotation/auditor/traffic_annotation_auditor.cc
@@ -506,6 +506,7 @@ return false; } +// static const std::map<int, std::string>& TrafficAnnotationAuditor::GetReservedUniqueIDs() { return kReservedAnnotations;
diff --git a/tools/traffic_annotation/auditor/traffic_annotation_auditor.h b/tools/traffic_annotation/auditor/traffic_annotation_auditor.h index f46b781..17b3516 100644 --- a/tools/traffic_annotation/auditor/traffic_annotation_auditor.h +++ b/tools/traffic_annotation/auditor/traffic_annotation_auditor.h
@@ -83,9 +83,20 @@ int line_; }; +// Base class for Annotation and Call instances. +class InstanceBase { + public: + InstanceBase(){}; + virtual ~InstanceBase(){}; + virtual AuditorResult Deserialize( + const std::vector<std::string>& serialized_lines, + int start_line, + int end_line) = 0; +}; + // Holds an instance of network traffic annotation. // TODO(rhalavati): Check if this class can also be reused in clang tool. -class AnnotationInstance { +class AnnotationInstance : public InstanceBase { public: // Annotation Type. enum class AnnotationType { @@ -115,7 +126,7 @@ // FATAL, furthur processing of the text should be stopped. AuditorResult Deserialize(const std::vector<std::string>& serialized_lines, int start_line, - int end_line); + int end_line) override; // Protobuf of the annotation. traffic_annotation::NetworkTrafficAnnotation proto; @@ -134,7 +145,7 @@ // Holds an instance of calling a function that might have a network traffic // annotation argument. // TODO(rhalavati): Check if this class can also be reused in clang tool. -class CallInstance { +class CallInstance : public InstanceBase { public: CallInstance(); CallInstance(const CallInstance& other); @@ -152,7 +163,7 @@ // FATAL, further processing of the text should be stopped. AuditorResult Deserialize(const std::vector<std::string>& serialized_lines, int start_line, - int end_line); + int end_line) override; std::string file_path; uint32_t line_number; @@ -205,7 +216,7 @@ // texts. This list includes all unique ids that are defined in // net/traffic_annotation/network_traffic_annotation.h and // net/traffic_annotation/network_traffic_annotation_test_helper.h - const std::map<int, std::string>& GetReservedUniqueIDs(); + static const std::map<int, std::string>& GetReservedUniqueIDs(); std::string clang_tool_raw_output() const { return clang_tool_raw_output_; }; @@ -217,14 +228,20 @@ return extracted_annotations_; } + void SetExtractedAnnotationsForTest( + const std::vector<AnnotationInstance>& annotations) { + extracted_annotations_ = annotations; + } + const std::vector<CallInstance>& extracted_calls() const { return extracted_calls_; } const std::vector<AuditorResult>& errors() const { return errors_; } - private: + void ClearErrorsForTest() { errors_.clear(); } + private: const base::FilePath source_path_; const base::FilePath build_path_;
diff --git a/tools/traffic_annotation/auditor/traffic_annotation_auditor_ui.cc b/tools/traffic_annotation/auditor/traffic_annotation_auditor_ui.cc index 1cfc8d5..c1eb252 100644 --- a/tools/traffic_annotation/auditor/traffic_annotation_auditor_ui.cc +++ b/tools/traffic_annotation/auditor/traffic_annotation_auditor_ui.cc
@@ -183,7 +183,7 @@ } const std::map<int, std::string> reserved_ids = - auditor.GetReservedUniqueIDs(); + TrafficAnnotationAuditor::GetReservedUniqueIDs(); for (const auto& item : reserved_ids) items.push_back(item);
diff --git a/tools/traffic_annotation/auditor/traffic_annotation_auditor_unittest.cc b/tools/traffic_annotation/auditor/traffic_annotation_auditor_unittest.cc index ee350ab..5241b1e 100644 --- a/tools/traffic_annotation/auditor/traffic_annotation_auditor_unittest.cc +++ b/tools/traffic_annotation/auditor/traffic_annotation_auditor_unittest.cc
@@ -6,6 +6,8 @@ #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/path_service.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" #include "net/traffic_annotation/network_traffic_annotation.h" #include "testing/gtest/include/gtest/gtest.h" #include "tools/traffic_annotation/auditor/traffic_annotation_file_filter.h" @@ -37,21 +39,45 @@ return; } - git_list_mock_file_ = source_path_.Append(FILE_PATH_LITERAL("tools")) - .Append(FILE_PATH_LITERAL("traffic_annotation")) - .Append(FILE_PATH_LITERAL("auditor")) - .Append(FILE_PATH_LITERAL("tests")) - .Append(FILE_PATH_LITERAL("git_list.txt")); + tests_folder_ = source_path_.Append(FILE_PATH_LITERAL("tools")) + .Append(FILE_PATH_LITERAL("traffic_annotation")) + .Append(FILE_PATH_LITERAL("auditor")) + .Append(FILE_PATH_LITERAL("tests")); } + const base::FilePath source_path() const { return source_path_; } + const base::FilePath build_path() const { return build_path_; } + const base::FilePath tests_folder() const { return tests_folder_; }; + protected: + // Deserializes an annotation or a call instance from a sample file similar to + // clang tool outputs. + AuditorResult::ResultType Deserialize(const std::string& file_name, + InstanceBase* instance); + + private: base::FilePath source_path_; base::FilePath build_path_; // Currently stays empty. Will be set if access // to a compiled build directory would be // granted. - base::FilePath git_list_mock_file_; + base::FilePath tests_folder_; }; +AuditorResult::ResultType TrafficAnnotationAuditorTest::Deserialize( + const std::string& file_name, + InstanceBase* instance) { + std::string file_content; + EXPECT_TRUE(base::ReadFileToString( + tests_folder_.Append(FILE_PATH_LITERAL("extractor_outputs")) + .AppendASCII(file_name), + &file_content)); + base::RemoveChars(file_content, "\r", &file_content); + std::vector<std::string> lines = base::SplitString( + file_content, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); + + return instance->Deserialize(lines, 0, static_cast<int>(lines.size())).type(); +} + // Tests if the two hash computation functions have the same result. TEST_F(TrafficAnnotationAuditorTest, HashFunctionCheck) { TEST_HASH_CODE("test"); @@ -67,8 +93,9 @@ // TrafficAnnotationFileFilter::IsFileRelevant. TEST_F(TrafficAnnotationAuditorTest, GetFilesFromGit) { TrafficAnnotationFileFilter filter; - filter.SetGitMockFileForTesting(git_list_mock_file_); - filter.GetFilesFromGit(source_path_); + filter.SetGitMockFileForTesting( + tests_folder().Append(FILE_PATH_LITERAL("git_list.txt"))); + filter.GetFilesFromGit(source_path()); const std::vector<std::string> git_files = filter.git_files(); @@ -88,8 +115,9 @@ // of files, given a mock git list file. TEST_F(TrafficAnnotationAuditorTest, RelevantFilesReceived) { TrafficAnnotationFileFilter filter; - filter.SetGitMockFileForTesting(git_list_mock_file_); - filter.GetFilesFromGit(source_path_); + filter.SetGitMockFileForTesting( + tests_folder().Append(FILE_PATH_LITERAL("git_list.txt"))); + filter.GetFilesFromGit(source_path()); unsigned int git_files_count = filter.git_files().size(); @@ -124,7 +152,7 @@ // Inherently checks if TrafficAnnotationFileFilter::LoadWhiteList works and // AuditorException rules are correctly deserialized. TEST_F(TrafficAnnotationAuditorTest, IsWhitelisted) { - TrafficAnnotationAuditor auditor(source_path_, build_path_); + TrafficAnnotationAuditor auditor(source_path(), build_path()); for (unsigned int i = 0; i < static_cast<unsigned int>( @@ -147,4 +175,149 @@ AuditorException::ExceptionType::MISSING)); EXPECT_TRUE(auditor.IsWhitelisted("net/url_request/url_request_context.cc", AuditorException::ExceptionType::MISSING)); -} \ No newline at end of file +} + +// Tests if annotation instances are corrrectly deserialized. +TEST_F(TrafficAnnotationAuditorTest, AnnotationDeserialization) { + struct AnnotationSample { + std::string file_name; + AuditorResult::ResultType result_type; + AnnotationInstance::AnnotationType annotation_type; + }; + + AnnotationSample test_cases[] = { + {"good_complete_annotation.txt", AuditorResult::ResultType::RESULT_OK, + AnnotationInstance::AnnotationType::ANNOTATION_COMPLETE}, + {"good_branched_completing_annotation.txt", + AuditorResult::ResultType::RESULT_OK, + AnnotationInstance::AnnotationType::ANNOTATION_BRANCHED_COMPLETING}, + {"good_completing_annotation.txt", AuditorResult::ResultType::RESULT_OK, + AnnotationInstance::AnnotationType::ANNOTATION_COMPLETENG}, + {"good_partial_annotation.txt", AuditorResult::ResultType::RESULT_OK, + AnnotationInstance::AnnotationType::ANNOTATION_PARTIAL}, + {"good_test_annotation.txt", AuditorResult::ResultType::RESULT_IGNORE}, + {"missing_annotation.txt", AuditorResult::ResultType::ERROR_MISSING}, + {"no_annotation.txt", AuditorResult::ResultType::ERROR_NO_ANNOTATION}, + {"fatal_annotation1.txt", AuditorResult::ResultType::ERROR_FATAL}, + {"fatal_annotation2.txt", AuditorResult::ResultType::ERROR_FATAL}, + {"fatal_annotation3.txt", AuditorResult::ResultType::ERROR_FATAL}, + {"bad_syntax_annotation1.txt", AuditorResult::ResultType::ERROR_SYNTAX}, + {"bad_syntax_annotation2.txt", AuditorResult::ResultType::ERROR_SYNTAX}, + {"bad_syntax_annotation3.txt", AuditorResult::ResultType::ERROR_SYNTAX}, + {"bad_syntax_annotation4.txt", AuditorResult::ResultType::ERROR_SYNTAX}, + }; + + for (const auto& test_case : test_cases) { + // Check if deserialization result is as expected. + AnnotationInstance annotation; + AuditorResult::ResultType result_type = + Deserialize(test_case.file_name, &annotation); + EXPECT_EQ(result_type, test_case.result_type); + + if (result_type == AuditorResult::ResultType::RESULT_OK) + EXPECT_EQ(annotation.annotation_type, test_case.annotation_type); + + // Content checks for one complete sample. + if (test_case.file_name != "good_complete_annotation.txt") + continue; + + EXPECT_EQ(annotation.proto.unique_id(), + "supervised_user_refresh_token_fetcher"); + EXPECT_EQ(annotation.proto.source().file(), + "chrome/browser/supervised_user/legacy/" + "supervised_user_refresh_token_fetcher.cc"); + EXPECT_EQ(annotation.proto.source().function(), "OnGetTokenSuccess"); + EXPECT_EQ(annotation.proto.source().line(), 166); + EXPECT_EQ(annotation.proto.semantics().sender(), "Supervised Users"); + EXPECT_EQ(annotation.proto.policy().cookies_allowed(), false); + } +} + +// Tests if call instances are corrrectly deserialized. +TEST_F(TrafficAnnotationAuditorTest, CallDeserialization) { + struct CallSample { + std::string file_name; + AuditorResult::ResultType result_type; + }; + + CallSample test_cases[] = { + {"good_call.txt", AuditorResult::ResultType::RESULT_OK}, + {"bad_call.txt", AuditorResult::ResultType::ERROR_FATAL}, + }; + + for (const auto& test_case : test_cases) { + // Check if deserialization result is as expected. + CallInstance call; + AuditorResult::ResultType result_type = + Deserialize(test_case.file_name, &call); + EXPECT_EQ(result_type, test_case.result_type); + + // Content checks for one complete sample. + if (test_case.file_name != "good_call.txt") + continue; + + EXPECT_EQ(call.file_path, "headless/public/util/http_url_fetcher.cc"); + EXPECT_EQ(call.line_number, 100u); + EXPECT_EQ(call.function_context, + "headless::HttpURLFetcher::Delegate::Delegate"); + EXPECT_EQ(call.function_name, "net::URLRequestContext::CreateRequest"); + EXPECT_EQ(call.is_annotated, true); + } +} + +// Tests if TrafficAnnotationAuditor::CheckDuplicateHashes works as expected. +TEST_F(TrafficAnnotationAuditorTest, CheckDuplicateHashes) { + // Load a valid annotation. + AnnotationInstance test_case; + EXPECT_EQ(Deserialize("good_complete_annotation.txt", &test_case), + AuditorResult::ResultType::RESULT_OK); + + const std::map<int, std::string>& reserved_words = + TrafficAnnotationAuditor::GetReservedUniqueIDs(); + + TrafficAnnotationAuditor auditor(source_path(), build_path()); + std::vector<AnnotationInstance> annotations; + + // Check for reserved words hash code duplication errors. + for (const auto& reserved_word : reserved_words) { + test_case.unique_id_hash_code = reserved_word.first; + annotations.push_back(test_case); + } + + auditor.SetExtractedAnnotationsForTest(annotations); + auditor.CheckDuplicateHashes(); + EXPECT_EQ(auditor.errors().size(), reserved_words.size()); + for (const auto& error : auditor.errors()) { + EXPECT_EQ(error.type(), + AuditorResult::ResultType::ERROR_RESERVED_UNIQUE_ID_HASH_CODE); + } + + // Check if several different hash codes result in no error. + annotations.clear(); + for (int i = 0; i < 10; i++) { + // Ensure that the test id is not a reserved hash code. + EXPECT_EQ(reserved_words.find(i), reserved_words.end()); + test_case.unique_id_hash_code = i; + annotations.push_back(test_case); + } + auditor.SetExtractedAnnotationsForTest(annotations); + auditor.ClearErrorsForTest(); + auditor.CheckDuplicateHashes(); + EXPECT_EQ(auditor.errors().size(), 0u); + + // Check if repeating the same hash codes results in errors. + annotations.clear(); + for (int i = 0; i < 10; i++) { + test_case.unique_id_hash_code = i; + annotations.push_back(test_case); + annotations.push_back(test_case); + } + auditor.SetExtractedAnnotationsForTest(annotations); + auditor.ClearErrorsForTest(); + auditor.CheckDuplicateHashes(); + EXPECT_EQ(auditor.errors().size(), 10u); + for (const auto& error : auditor.errors()) { + EXPECT_EQ(error.type(), + AuditorResult::ResultType::ERROR_DUPLICATE_UNIQUE_ID_HASH_CODE); + } +}
diff --git a/ui/file_manager/file_manager/foreground/css/file_manager.css b/ui/file_manager/file_manager/foreground/css/file_manager.css index f45347e..46259a7 100644 --- a/ui/file_manager/file_manager/foreground/css/file_manager.css +++ b/ui/file_manager/file_manager/foreground/css/file_manager.css
@@ -433,6 +433,16 @@ display: none; } +#selection-menu-button > .icon { + background-image: -webkit-image-set( + url(../images/files/ui/menu.png) 1x, + url(../images/files/ui/2x/menu.png) 2x); +} + +body:not(.check-select) #selection-menu-button { + display: none; +} + .cloud-import-combo-button { height: 32px; margin: 8px; @@ -1723,6 +1733,10 @@ -webkit-padding-end: 8px; } +cr-menu#file-context-menu.toolbar-menu > .hide-on-toolbar { + display: none; +} + cr-menu.chrome-menu hr { color: transparent; font-size: 0;
diff --git a/ui/file_manager/file_manager/foreground/images/files/ui/2x/menu.png b/ui/file_manager/file_manager/foreground/images/files/ui/2x/menu.png new file mode 100644 index 0000000..2b34db4 --- /dev/null +++ b/ui/file_manager/file_manager/foreground/images/files/ui/2x/menu.png Binary files differ
diff --git a/ui/file_manager/file_manager/foreground/images/files/ui/menu.png b/ui/file_manager/file_manager/foreground/images/files/ui/menu.png new file mode 100644 index 0000000..5fe650f --- /dev/null +++ b/ui/file_manager/file_manager/foreground/images/files/ui/menu.png Binary files differ
diff --git a/ui/file_manager/file_manager/foreground/js/compiled_resources2.gyp b/ui/file_manager/file_manager/foreground/js/compiled_resources2.gyp index b762652..a06d604 100644 --- a/ui/file_manager/file_manager/foreground/js/compiled_resources2.gyp +++ b/ui/file_manager/file_manager/foreground/js/compiled_resources2.gyp
@@ -189,6 +189,7 @@ 'quick_view_uma', 'scan_controller', 'search_controller', + 'selection_menu_controller', 'sort_menu_controller', 'spinner_controller', 'task_controller', @@ -504,6 +505,15 @@ 'includes': ['../../../compile_js2.gypi'], }, { + 'target_name': 'selection_menu_controller', + 'dependencies': [ + '../elements/compiled_resources2.gyp:files_toggle_ripple', + 'directory_model', + 'file_manager_commands', + ], + 'includes': ['../../../compile_js2.gypi'], + }, + { 'target_name': 'share_client', 'dependencies': [ '../../../externs/compiled_resources2.gyp:entry_location',
diff --git a/ui/file_manager/file_manager/foreground/js/file_manager.js b/ui/file_manager/file_manager/foreground/js/file_manager.js index e111901b..b83139c 100644 --- a/ui/file_manager/file_manager/foreground/js/file_manager.js +++ b/ui/file_manager/file_manager/foreground/js/file_manager.js
@@ -217,6 +217,14 @@ this.gearMenuController_ = null; /** + * Controller for the context menu opened by the action bar button in the + * check-select mode. + * @type {SelectionMenuController} + * @private + */ + this.selectionMenuController_ = null; + + /** * Toolbar controller. * @type {ToolbarController} * @private @@ -554,6 +562,9 @@ this.ui_.gearMenu, this.directoryModel_, this.commandHandler_); + this.selectionMenuController_ = new SelectionMenuController( + this.ui_.selectionMenuButton, + util.queryDecoratedElement('#file-context-menu', cr.ui.Menu)); this.toolbarController_ = new ToolbarController( this.ui_.toolbar, this.ui_.dialogNavigationList,
diff --git a/ui/file_manager/file_manager/foreground/js/main_scripts.js b/ui/file_manager/file_manager/foreground/js/main_scripts.js index 747437e9..4ca7f2a 100644 --- a/ui/file_manager/file_manager/foreground/js/main_scripts.js +++ b/ui/file_manager/file_manager/foreground/js/main_scripts.js
@@ -145,6 +145,7 @@ // <include src="quick_view_uma.js"> // <include src="scan_controller.js"> // <include src="search_controller.js"> +// <include src="selection_menu_controller.js"> // <include src="share_client.js"> // <include src="spinner_controller.js"> // <include src="task_controller.js">
diff --git a/ui/file_manager/file_manager/foreground/js/selection_menu_controller.js b/ui/file_manager/file_manager/foreground/js/selection_menu_controller.js new file mode 100644 index 0000000..7a613c4 --- /dev/null +++ b/ui/file_manager/file_manager/foreground/js/selection_menu_controller.js
@@ -0,0 +1,52 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @param {!cr.ui.MenuButton} selectionMenuButton + * @param {!cr.ui.Menu} menu + * @constructor + * @struct + */ +function SelectionMenuController(selectionMenuButton, menu) { + /** + * @type {!FilesToggleRipple} + * @const + * @private + */ + this.toggleRipple_ = + /** @type {!FilesToggleRipple} */ ( + queryRequiredElement('files-toggle-ripple', selectionMenuButton)); + + /** + * @type {!cr.ui.Menu} + * @const + */ + this.menu_ = menu; + + selectionMenuButton.addEventListener('menushow', this.onShowMenu_.bind(this)); + selectionMenuButton.addEventListener('menuhide', this.onHideMenu_.bind(this)); +} + +/** + * Class name to indicate if the menu was opened by the toolbar button or not. + * @type {string} + * @const + */ +SelectionMenuController.TOOLBAR_MENU = 'toolbar-menu'; + +/** + * @private + */ +SelectionMenuController.prototype.onShowMenu_ = function() { + this.menu_.classList.toggle(SelectionMenuController.TOOLBAR_MENU, true); + this.toggleRipple_.activated = true; +}; + +/** + * @private + */ +SelectionMenuController.prototype.onHideMenu_ = function() { + this.menu_.classList.toggle(SelectionMenuController.TOOLBAR_MENU, false); + this.toggleRipple_.activated = false; +};
diff --git a/ui/file_manager/file_manager/foreground/js/ui/actions_submenu.js b/ui/file_manager/file_manager/foreground/js/ui/actions_submenu.js index 1657f21c7..98238a95 100644 --- a/ui/file_manager/file_manager/foreground/js/ui/actions_submenu.js +++ b/ui/file_manager/file_manager/foreground/js/ui/actions_submenu.js
@@ -62,6 +62,7 @@ if (shareAction) { var menuItem = this.addMenuItem_({}); menuItem.command = '#share'; + menuItem.classList.toggle('hide-on-toolbar', true); delete remainingActions[ActionsModel.CommonActionId.SHARE]; } util.queryDecoratedElement('#share', cr.ui.Command).canExecuteChange();
diff --git a/ui/file_manager/file_manager/foreground/js/ui/file_manager_ui.js b/ui/file_manager/file_manager/foreground/js/ui/file_manager_ui.js index 1369f73..b445455 100644 --- a/ui/file_manager/file_manager/foreground/js/ui/file_manager_ui.js +++ b/ui/file_manager/file_manager/foreground/js/ui/file_manager_ui.js
@@ -220,6 +220,14 @@ this.gearMenu = new GearMenu(this.gearButton.menu); /** + * The button to open context menu in the check-select mode. + * @type {!cr.ui.MenuButton} + * @const + */ + this.selectionMenuButton = + util.queryDecoratedElement('#selection-menu-button', cr.ui.MenuButton); + + /** * Directory tree. * @type {DirectoryTree} */
diff --git a/ui/file_manager/file_manager/main.html b/ui/file_manager/file_manager/main.html index ca9b12b..0075dea 100644 --- a/ui/file_manager/file_manager/main.html +++ b/ui/file_manager/file_manager/main.html
@@ -173,10 +173,10 @@ <cr-menu id="file-context-menu" class="chrome-menu files-menu" menu-item-selector="cr-menu-item,hr" showShortcuts> <cr-menu-item id="default-task-menu-item" command="#default-task" - visibleif="full-page" hidden></cr-menu-item> + visibleif="full-page" class="hide-on-toolbar" hidden></cr-menu-item> <cr-menu-item command="#open-with" - visibleif="full-page" hidden></cr-menu-item> - <hr id="tasks-separator" visibleif="full-page" hidden> + visibleif="full-page" class="hide-on-toolbar" hidden></cr-menu-item> + <hr id="tasks-separator" visibleif="full-page" class="hide-on-toolbar" hidden> <hr id="actions-separator" hidden> <cr-menu-item command="#cut" visibleif="full-page"></cr-menu-item> <cr-menu-item command="#copy" visibleif="full-page"></cr-menu-item> @@ -185,12 +185,13 @@ <hr visibleif="full-page"> <cr-menu-item command="#get-info"></cr-menu-item> <cr-menu-item command="#rename"></cr-menu-item> - <cr-menu-item command="#delete" i18n-content="DELETE_BUTTON_LABEL"></cr-menu-item> + <cr-menu-item command="#delete" i18n-content="DELETE_BUTTON_LABEL" + class="hide-on-toolbar"></cr-menu-item> <cr-menu-item command="#zip-selection"></cr-menu-item> <cr-menu-item command="#set-wallpaper"></cr-menu-item> - <hr visibleif="saveas-file full-page"> - <cr-menu-item command="#new-folder" - visibleif="saveas-file full-page"></cr-menu-item> + <hr visibleif="saveas-file full-page" class="hide-on-toolbar"> + <cr-menu-item command="#new-folder" visibleif="saveas-file full-page" + class="hide-on-toolbar"></cr-menu-item> </cr-menu> <cr-menu id="roots-context-menu" class="chrome-menu files-menu" @@ -380,6 +381,12 @@ <files-toggle-ripple></files-toggle-ripple> <div class="icon"></div> </button> + <!-- TODO(yamaguchi): Set a proper tooltip. --> + <button id="selection-menu-button" class="icon-button" tabindex="19" + menu="#file-context-menu" aria-activedescendant="file-context-menu"> + <files-toggle-ripple></files-toggle-ripple> + <div class="icon"></div> + </button> </div> <div id="cloud-import-details" class="hidden" hidden>
diff --git a/ui/keyboard/keyboard_util.cc b/ui/keyboard/keyboard_util.cc index 0df61bca..15b70b0 100644 --- a/ui/keyboard/keyboard_util.cc +++ b/ui/keyboard/keyboard_util.cc
@@ -44,6 +44,7 @@ CHECK(!details.dispatcher_destroyed); } +bool g_keyboard_load_time_logged = false; base::LazyInstance<base::Time>::DestructorAtExit g_keyboard_load_time_start = LAZY_INSTANCE_INITIALIZER; @@ -282,7 +283,7 @@ } void MarkKeyboardLoadStarted() { - if (g_keyboard_load_time_start.Get().is_null()) + if (!g_keyboard_load_time_logged) g_keyboard_load_time_start.Get() = base::Time::Now(); } @@ -292,13 +293,12 @@ if (g_keyboard_load_time_start.Get().is_null()) return; - static bool logged = false; - if (!logged) { + if (!g_keyboard_load_time_logged) { // Log the delta only once. UMA_HISTOGRAM_TIMES( "VirtualKeyboard.InitLatency.FirstLoad", base::Time::Now() - g_keyboard_load_time_start.Get()); - logged = true; + g_keyboard_load_time_logged = true; } }