Commit 224e1d18 authored by wittman@chromium.org's avatar wittman@chromium.org

Set metainfo ID for enhanced bookmarks extension

This is a temporary workaround to allow for reliable creation of a
durable unique ID for the extension. It will go away once we are able to
provide control of all bookmark creation paths via the extension API.

BUG=383557

Review URL: https://codereview.chromium.org/338593002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@277635 0039d316-1c4b-4281-b951-d872f2087c98
parent b6af1a22
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include <algorithm>
#include "chrome/browser/extensions/api/bookmarks/bookmarks_api.h" #include "chrome/browser/extensions/api/bookmarks/bookmarks_api.h"
#include "base/bind.h" #include "base/bind.h"
...@@ -13,6 +15,7 @@ ...@@ -13,6 +15,7 @@
#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_ptr.h"
#include "base/path_service.h" #include "base/path_service.h"
#include "base/prefs/pref_service.h" #include "base/prefs/pref_service.h"
#include "base/rand_util.h"
#include "base/sha1.h" #include "base/sha1.h"
#include "base/stl_util.h" #include "base/stl_util.h"
#include "base/strings/string16.h" #include "base/strings/string16.h"
...@@ -45,7 +48,9 @@ ...@@ -45,7 +48,9 @@
#include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents.h"
#include "extensions/browser/event_router.h" #include "extensions/browser/event_router.h"
#include "extensions/browser/extension_function_dispatcher.h" #include "extensions/browser/extension_function_dispatcher.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/quota_service.h" #include "extensions/browser/quota_service.h"
#include "extensions/common/permissions/permissions_data.h"
#include "grit/generated_resources.h" #include "grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h" #include "ui/base/l10n/l10n_util.h"
...@@ -97,6 +102,56 @@ base::FilePath GetDefaultFilepathForBookmarkExport() { ...@@ -97,6 +102,56 @@ base::FilePath GetDefaultFilepathForBookmarkExport() {
return default_path.Append(filename); return default_path.Append(filename);
} }
bool IsEnhancedBookmarksExtensionActive(Profile* profile) {
static const char *enhanced_extension_hashes[] = {
"D5736E4B5CF695CB93A2FB57E4FDC6E5AFAB6FE2", // http://crbug.com/312900
"D57DE394F36DC1C3220E7604C575D29C51A6C495", // http://crbug.com/319444
"3F65507A3B39259B38C8173C6FFA3D12DF64CCE9" // http://crbug.com/371562
};
const ExtensionSet& extensions =
ExtensionRegistry::Get(profile)->enabled_extensions();
for (ExtensionSet::const_iterator it = extensions.begin();
it != extensions.end(); ++it) {
const Extension* extension = *it;
if (extension->permissions_data()->HasAPIPermission(
APIPermission::kBookmarkManagerPrivate)) {
std::string hash = base::SHA1HashString(extension->id());
hash = base::HexEncode(hash.c_str(), hash.length());
for (size_t i = 0; i < arraysize(enhanced_extension_hashes); i++)
if (hash == enhanced_extension_hashes[i])
return true;
}
}
return false;
}
std::string ToBase36(int64 value) {
DCHECK(value >= 0);
std::string str;
while (value > 0) {
int digit = value % 36;
value /= 36;
str += (digit < 10 ? '0' + digit : 'a' + digit - 10);
}
std::reverse(str.begin(), str.end());
return str;
}
// Generate a metadata ID based on a the current time and a random number for
// enhanced bookmarks, to be assigned pre-sync.
std::string GenerateEnhancedBookmarksID(bool is_folder) {
static const char bookmark_prefix[] = "cc_";
static const char folder_prefix[] = "cf_";
// Use [0..range_mid) for bookmarks, [range_mid..2*range_mid) for folders.
int range_mid = 36*36*36*36 / 2;
int rand = base::RandInt(0, range_mid - 1);
int64 unix_epoch_time_in_ms =
(base::Time::Now() - base::Time::UnixEpoch()).InMilliseconds();
return std::string(is_folder ? folder_prefix : bookmark_prefix) +
ToBase36(is_folder ? range_mid + rand : rand) +
ToBase36(unix_epoch_time_in_ms);
}
} // namespace } // namespace
bool BookmarksFunction::RunAsync() { bool BookmarksFunction::RunAsync() {
...@@ -292,6 +347,18 @@ void BookmarkEventRouter::BookmarkNodeMoved(BookmarkModel* model, ...@@ -292,6 +347,18 @@ void BookmarkEventRouter::BookmarkNodeMoved(BookmarkModel* model,
bookmarks::OnMoved::Create(base::Int64ToString(node->id()), move_info)); bookmarks::OnMoved::Create(base::Int64ToString(node->id()), move_info));
} }
void BookmarkEventRouter::OnWillAddBookmarkNode(BookmarkModel* model,
BookmarkNode* node) {
// TODO(wittman): Remove this once extension hooks are in place to allow the
// enhanced bookmarks extension to manage all bookmark creation code
// paths. See http://crbug.com/383557.
if (IsEnhancedBookmarksExtensionActive(Profile::FromBrowserContext(
browser_context_))) {
static const char key[] = "stars.id";
node->SetMetaInfo(key, GenerateEnhancedBookmarksID(node->is_folder()));
}
}
void BookmarkEventRouter::BookmarkNodeAdded(BookmarkModel* model, void BookmarkEventRouter::BookmarkNodeAdded(BookmarkModel* model,
const BookmarkNode* parent, const BookmarkNode* parent,
int index) { int index) {
......
...@@ -53,6 +53,8 @@ class BookmarkEventRouter : public BookmarkModelObserver { ...@@ -53,6 +53,8 @@ class BookmarkEventRouter : public BookmarkModelObserver {
int old_index, int old_index,
const BookmarkNode* new_parent, const BookmarkNode* new_parent,
int new_index) OVERRIDE; int new_index) OVERRIDE;
virtual void OnWillAddBookmarkNode(BookmarkModel* model,
BookmarkNode* node) OVERRIDE;
virtual void BookmarkNodeAdded(BookmarkModel* model, virtual void BookmarkNodeAdded(BookmarkModel* model,
const BookmarkNode* parent, const BookmarkNode* parent,
int index) OVERRIDE; int index) OVERRIDE;
......
...@@ -877,6 +877,9 @@ void BookmarkModel::RemoveNodeAndGetRemovedUrls(BookmarkNode* node, ...@@ -877,6 +877,9 @@ void BookmarkModel::RemoveNodeAndGetRemovedUrls(BookmarkNode* node,
BookmarkNode* BookmarkModel::AddNode(BookmarkNode* parent, BookmarkNode* BookmarkModel::AddNode(BookmarkNode* parent,
int index, int index,
BookmarkNode* node) { BookmarkNode* node) {
FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
OnWillAddBookmarkNode(this, node));
parent->Add(node, index); parent->Add(node, index);
if (store_.get()) if (store_.get())
......
...@@ -29,6 +29,12 @@ class BookmarkModelObserver { ...@@ -29,6 +29,12 @@ class BookmarkModelObserver {
const BookmarkNode* new_parent, const BookmarkNode* new_parent,
int new_index) = 0; int new_index) = 0;
// Invoked prior to adding a bookmark node, and in particular, prior to adding
// it to the parent. This function can be used to alter the contents of the
// node before BookmarkNodeAdded listeners know about it.
virtual void OnWillAddBookmarkNode(BookmarkModel* model,
BookmarkNode* node) {}
// Invoked when a node has been added. // Invoked when a node has been added.
virtual void BookmarkNodeAdded(BookmarkModel* model, virtual void BookmarkNodeAdded(BookmarkModel* model,
const BookmarkNode* parent, const BookmarkNode* parent,
......
...@@ -158,6 +158,12 @@ class BookmarkModelTest : public testing::Test, ...@@ -158,6 +158,12 @@ class BookmarkModelTest : public testing::Test,
observer_details_.Set(old_parent, new_parent, old_index, new_index); observer_details_.Set(old_parent, new_parent, old_index, new_index);
} }
virtual void OnWillAddBookmarkNode(BookmarkModel* model,
BookmarkNode* node) OVERRIDE {
++will_add_count_;
EXPECT_TRUE(node->parent() == NULL);
}
virtual void BookmarkNodeAdded(BookmarkModel* model, virtual void BookmarkNodeAdded(BookmarkModel* model,
const BookmarkNode* parent, const BookmarkNode* parent,
int index) OVERRIDE { int index) OVERRIDE {
...@@ -230,14 +236,15 @@ class BookmarkModelTest : public testing::Test, ...@@ -230,14 +236,15 @@ class BookmarkModelTest : public testing::Test,
} }
void ClearCounts() { void ClearCounts() {
added_count_ = moved_count_ = removed_count_ = changed_count_ = will_add_count_ = added_count_ = moved_count_ = removed_count_ =
reordered_count_ = extensive_changes_beginning_count_ = changed_count_ = reordered_count_ = extensive_changes_beginning_count_ =
extensive_changes_ended_count_ = all_bookmarks_removed_ = extensive_changes_ended_count_ = all_bookmarks_removed_ =
before_remove_count_ = before_change_count_ = before_reorder_count_ = before_remove_count_ = before_change_count_ = before_reorder_count_ =
before_remove_all_count_ = 0; before_remove_all_count_ = 0;
} }
void AssertObserverCount(int added_count, void AssertObserverCount(int will_add_count,
int added_count,
int moved_count, int moved_count,
int removed_count, int removed_count,
int changed_count, int changed_count,
...@@ -246,6 +253,7 @@ class BookmarkModelTest : public testing::Test, ...@@ -246,6 +253,7 @@ class BookmarkModelTest : public testing::Test,
int before_change_count, int before_change_count,
int before_reorder_count, int before_reorder_count,
int before_remove_all_count) { int before_remove_all_count) {
EXPECT_EQ(will_add_count_, will_add_count);
EXPECT_EQ(added_count_, added_count); EXPECT_EQ(added_count_, added_count);
EXPECT_EQ(moved_count_, moved_count); EXPECT_EQ(moved_count_, moved_count);
EXPECT_EQ(removed_count_, removed_count); EXPECT_EQ(removed_count_, removed_count);
...@@ -290,6 +298,7 @@ class BookmarkModelTest : public testing::Test, ...@@ -290,6 +298,7 @@ class BookmarkModelTest : public testing::Test,
ObserverDetails observer_details_; ObserverDetails observer_details_;
private: private:
int will_add_count_;
int added_count_; int added_count_;
int moved_count_; int moved_count_;
int removed_count_; int removed_count_;
...@@ -333,7 +342,7 @@ TEST_F(BookmarkModelTest, AddURL) { ...@@ -333,7 +342,7 @@ TEST_F(BookmarkModelTest, AddURL) {
const GURL url("http://foo.com"); const GURL url("http://foo.com");
const BookmarkNode* new_node = model_->AddURL(root, 0, title, url); const BookmarkNode* new_node = model_->AddURL(root, 0, title, url);
AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0); AssertObserverCount(1, 1, 0, 0, 0, 0, 0, 0, 0, 0);
observer_details_.ExpectEquals(root, NULL, 0, -1); observer_details_.ExpectEquals(root, NULL, 0, -1);
ASSERT_EQ(1, root->child_count()); ASSERT_EQ(1, root->child_count());
...@@ -354,7 +363,7 @@ TEST_F(BookmarkModelTest, AddURLWithUnicodeTitle) { ...@@ -354,7 +363,7 @@ TEST_F(BookmarkModelTest, AddURLWithUnicodeTitle) {
const GURL url("https://www.baidu.com/"); const GURL url("https://www.baidu.com/");
const BookmarkNode* new_node = model_->AddURL(root, 0, title, url); const BookmarkNode* new_node = model_->AddURL(root, 0, title, url);
AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0); AssertObserverCount(1, 1, 0, 0, 0, 0, 0, 0, 0, 0);
observer_details_.ExpectEquals(root, NULL, 0, -1); observer_details_.ExpectEquals(root, NULL, 0, -1);
ASSERT_EQ(1, root->child_count()); ASSERT_EQ(1, root->child_count());
...@@ -395,7 +404,7 @@ TEST_F(BookmarkModelTest, AddURLWithCreationTimeAndMetaInfo) { ...@@ -395,7 +404,7 @@ TEST_F(BookmarkModelTest, AddURLWithCreationTimeAndMetaInfo) {
const BookmarkNode* new_node = model_->AddURLWithCreationTimeAndMetaInfo( const BookmarkNode* new_node = model_->AddURLWithCreationTimeAndMetaInfo(
root, 0, title, url, time, &meta_info); root, 0, title, url, time, &meta_info);
AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0); AssertObserverCount(1, 1, 0, 0, 0, 0, 0, 0, 0, 0);
observer_details_.ExpectEquals(root, NULL, 0, -1); observer_details_.ExpectEquals(root, NULL, 0, -1);
ASSERT_EQ(1, root->child_count()); ASSERT_EQ(1, root->child_count());
...@@ -418,7 +427,7 @@ TEST_F(BookmarkModelTest, AddURLToMobileBookmarks) { ...@@ -418,7 +427,7 @@ TEST_F(BookmarkModelTest, AddURLToMobileBookmarks) {
const GURL url("http://foo.com"); const GURL url("http://foo.com");
const BookmarkNode* new_node = model_->AddURL(root, 0, title, url); const BookmarkNode* new_node = model_->AddURL(root, 0, title, url);
AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0); AssertObserverCount(1, 1, 0, 0, 0, 0, 0, 0, 0, 0);
observer_details_.ExpectEquals(root, NULL, 0, -1); observer_details_.ExpectEquals(root, NULL, 0, -1);
ASSERT_EQ(1, root->child_count()); ASSERT_EQ(1, root->child_count());
...@@ -437,7 +446,7 @@ TEST_F(BookmarkModelTest, AddFolder) { ...@@ -437,7 +446,7 @@ TEST_F(BookmarkModelTest, AddFolder) {
const base::string16 title(ASCIIToUTF16("foo")); const base::string16 title(ASCIIToUTF16("foo"));
const BookmarkNode* new_node = model_->AddFolder(root, 0, title); const BookmarkNode* new_node = model_->AddFolder(root, 0, title);
AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0); AssertObserverCount(1, 1, 0, 0, 0, 0, 0, 0, 0, 0);
observer_details_.ExpectEquals(root, NULL, 0, -1); observer_details_.ExpectEquals(root, NULL, 0, -1);
ASSERT_EQ(1, root->child_count()); ASSERT_EQ(1, root->child_count());
...@@ -451,7 +460,7 @@ TEST_F(BookmarkModelTest, AddFolder) { ...@@ -451,7 +460,7 @@ TEST_F(BookmarkModelTest, AddFolder) {
// Add another folder, just to make sure folder_ids are incremented correctly. // Add another folder, just to make sure folder_ids are incremented correctly.
ClearCounts(); ClearCounts();
model_->AddFolder(root, 0, title); model_->AddFolder(root, 0, title);
AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0); AssertObserverCount(1, 1, 0, 0, 0, 0, 0, 0, 0, 0);
observer_details_.ExpectEquals(root, NULL, 0, -1); observer_details_.ExpectEquals(root, NULL, 0, -1);
} }
...@@ -480,7 +489,7 @@ TEST_F(BookmarkModelTest, RemoveURL) { ...@@ -480,7 +489,7 @@ TEST_F(BookmarkModelTest, RemoveURL) {
model_->Remove(root, 0); model_->Remove(root, 0);
ASSERT_EQ(0, root->child_count()); ASSERT_EQ(0, root->child_count());
AssertObserverCount(0, 0, 1, 0, 0, 1, 0, 0, 0); AssertObserverCount(0, 0, 0, 1, 0, 0, 1, 0, 0, 0);
observer_details_.ExpectEquals(root, NULL, 0, -1); observer_details_.ExpectEquals(root, NULL, 0, -1);
// Make sure there is no mapping for the URL. // Make sure there is no mapping for the URL.
...@@ -503,7 +512,7 @@ TEST_F(BookmarkModelTest, RemoveFolder) { ...@@ -503,7 +512,7 @@ TEST_F(BookmarkModelTest, RemoveFolder) {
// Now remove the folder. // Now remove the folder.
model_->Remove(root, 0); model_->Remove(root, 0);
ASSERT_EQ(0, root->child_count()); ASSERT_EQ(0, root->child_count());
AssertObserverCount(0, 0, 1, 0, 0, 1, 0, 0, 0); AssertObserverCount(0, 0, 0, 1, 0, 0, 1, 0, 0, 0);
observer_details_.ExpectEquals(root, NULL, 0, -1); observer_details_.ExpectEquals(root, NULL, 0, -1);
// Make sure there is no mapping for the URL. // Make sure there is no mapping for the URL.
...@@ -524,7 +533,7 @@ TEST_F(BookmarkModelTest, RemoveAllUserBookmarks) { ...@@ -524,7 +533,7 @@ TEST_F(BookmarkModelTest, RemoveAllUserBookmarks) {
const BookmarkNode* folder = model_->AddFolder(bookmark_bar_node, 0, title); const BookmarkNode* folder = model_->AddFolder(bookmark_bar_node, 0, title);
model_->AddURL(folder, 0, title, url); model_->AddURL(folder, 0, title, url);
AssertObserverCount(3, 0, 0, 0, 0, 0, 0, 0, 0); AssertObserverCount(3, 3, 0, 0, 0, 0, 0, 0, 0, 0);
ClearCounts(); ClearCounts();
model_->RemoveAllUserBookmarks(); model_->RemoveAllUserBookmarks();
...@@ -532,7 +541,7 @@ TEST_F(BookmarkModelTest, RemoveAllUserBookmarks) { ...@@ -532,7 +541,7 @@ TEST_F(BookmarkModelTest, RemoveAllUserBookmarks) {
EXPECT_EQ(0, bookmark_bar_node->child_count()); EXPECT_EQ(0, bookmark_bar_node->child_count());
// No individual BookmarkNodeRemoved events are fired, so removed count // No individual BookmarkNodeRemoved events are fired, so removed count
// should be 0. // should be 0.
AssertObserverCount(0, 0, 0, 0, 0, 0, 0, 0, 1); AssertObserverCount(0, 0, 0, 0, 0, 0, 0, 0, 0, 1);
AssertExtensiveChangesObserverCount(1, 1); AssertExtensiveChangesObserverCount(1, 1);
EXPECT_EQ(1, AllNodesRemovedObserverCount()); EXPECT_EQ(1, AllNodesRemovedObserverCount());
} }
...@@ -547,7 +556,7 @@ TEST_F(BookmarkModelTest, SetTitle) { ...@@ -547,7 +556,7 @@ TEST_F(BookmarkModelTest, SetTitle) {
title = ASCIIToUTF16("foo2"); title = ASCIIToUTF16("foo2");
model_->SetTitle(node, title); model_->SetTitle(node, title);
AssertObserverCount(0, 0, 0, 1, 0, 0, 1, 0, 0); AssertObserverCount(0, 0, 0, 0, 1, 0, 0, 1, 0, 0);
observer_details_.ExpectEquals(node, NULL, -1, -1); observer_details_.ExpectEquals(node, NULL, -1, -1);
EXPECT_EQ(title, node->GetTitle()); EXPECT_EQ(title, node->GetTitle());
} }
...@@ -576,7 +585,7 @@ TEST_F(BookmarkModelTest, SetURL) { ...@@ -576,7 +585,7 @@ TEST_F(BookmarkModelTest, SetURL) {
url = GURL("http://foo2.com"); url = GURL("http://foo2.com");
model_->SetURL(node, url); model_->SetURL(node, url);
AssertObserverCount(0, 0, 0, 1, 0, 0, 1, 0, 0); AssertObserverCount(0, 0, 0, 0, 1, 0, 0, 1, 0, 0);
observer_details_.ExpectEquals(node, NULL, -1, -1); observer_details_.ExpectEquals(node, NULL, -1, -1);
EXPECT_EQ(url, node->url()); EXPECT_EQ(url, node->url());
} }
...@@ -591,7 +600,7 @@ TEST_F(BookmarkModelTest, SetDateAdded) { ...@@ -591,7 +600,7 @@ TEST_F(BookmarkModelTest, SetDateAdded) {
base::Time new_time = base::Time::Now() + base::TimeDelta::FromMinutes(20); base::Time new_time = base::Time::Now() + base::TimeDelta::FromMinutes(20);
model_->SetDateAdded(node, new_time); model_->SetDateAdded(node, new_time);
AssertObserverCount(0, 0, 0, 0, 0, 0, 0, 0, 0); AssertObserverCount(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
EXPECT_EQ(new_time, node->date_added()); EXPECT_EQ(new_time, node->date_added());
EXPECT_EQ(new_time, model_->bookmark_bar_node()->date_folder_modified()); EXPECT_EQ(new_time, model_->bookmark_bar_node()->date_folder_modified());
} }
...@@ -606,7 +615,7 @@ TEST_F(BookmarkModelTest, Move) { ...@@ -606,7 +615,7 @@ TEST_F(BookmarkModelTest, Move) {
model_->Move(node, folder1, 0); model_->Move(node, folder1, 0);
AssertObserverCount(0, 1, 0, 0, 0, 0, 0, 0, 0); AssertObserverCount(0, 0, 1, 0, 0, 0, 0, 0, 0, 0);
observer_details_.ExpectEquals(root, folder1, 1, 0); observer_details_.ExpectEquals(root, folder1, 1, 0);
EXPECT_TRUE(folder1 == node->parent()); EXPECT_TRUE(folder1 == node->parent());
EXPECT_EQ(1, root->child_count()); EXPECT_EQ(1, root->child_count());
...@@ -617,7 +626,7 @@ TEST_F(BookmarkModelTest, Move) { ...@@ -617,7 +626,7 @@ TEST_F(BookmarkModelTest, Move) {
// And remove the folder. // And remove the folder.
ClearCounts(); ClearCounts();
model_->Remove(root, 0); model_->Remove(root, 0);
AssertObserverCount(0, 0, 1, 0, 0, 1, 0, 0, 0); AssertObserverCount(0, 0, 0, 1, 0, 0, 1, 0, 0, 0);
observer_details_.ExpectEquals(root, NULL, 0, -1); observer_details_.ExpectEquals(root, NULL, 0, -1);
EXPECT_TRUE(model_->GetMostRecentlyAddedUserNodeForURL(url) == NULL); EXPECT_TRUE(model_->GetMostRecentlyAddedUserNodeForURL(url) == NULL);
EXPECT_EQ(0, root->child_count()); EXPECT_EQ(0, root->child_count());
...@@ -1011,7 +1020,7 @@ TEST_F(BookmarkModelTest, Sort) { ...@@ -1011,7 +1020,7 @@ TEST_F(BookmarkModelTest, Sort) {
model_->SortChildren(parent); model_->SortChildren(parent);
// Make sure we were notified. // Make sure we were notified.
AssertObserverCount(0, 0, 0, 0, 1, 0, 0, 1, 0); AssertObserverCount(0, 0, 0, 0, 0, 1, 0, 0, 1, 0);
// Make sure the order matches (remember, 'a' and 'C' are folders and // Make sure the order matches (remember, 'a' and 'C' are folders and
// come first). // come first).
...@@ -1039,7 +1048,7 @@ TEST_F(BookmarkModelTest, Reorder) { ...@@ -1039,7 +1048,7 @@ TEST_F(BookmarkModelTest, Reorder) {
model_->ReorderChildren(parent, new_order); model_->ReorderChildren(parent, new_order);
// Make sure we were notified. // Make sure we were notified.
AssertObserverCount(0, 0, 0, 0, 1, 0, 0, 1, 0); AssertObserverCount(0, 0, 0, 0, 0, 1, 0, 0, 1, 0);
// Make sure the order matches is correct (it should be reversed). // Make sure the order matches is correct (it should be reversed).
ASSERT_EQ(4, parent->child_count()); ASSERT_EQ(4, parent->child_count());
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment