Skip to content

Commit

Permalink
[Merge M58] arc: Handle position conflict in app list items.
Browse files Browse the repository at this point in the history
It is not guaranteed that sync items have unique positions. This may
cause crash in case web store app has conflicting pos with some other
app.

TEST=Unit test added
BUG=692802

NOTRY=true
NOPRESUBMIT=true
TBR=stevenjb@chromium.org

Review-Url: https://codereview.chromium.org/2732633003
Cr-Commit-Position: refs/heads/master@{#455195}
(cherry picked from commit 11f9ee6)

Review-Url: https://codereview.chromium.org/2740603002
Cr-Commit-Position: refs/branch-heads/3029@{#51}
Cr-Branched-From: 939b32e-refs/heads/master@{#454471}
  • Loading branch information
khmel authored and Commit bot committed Mar 7, 2017
1 parent c1080fb commit 651a2d0
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 21 deletions.
63 changes: 42 additions & 21 deletions chrome/browser/ui/app_list/app_list_syncable_service.cc
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,6 @@ namespace app_list {

namespace {

const char kOemFolderId[] = "ddb1da55-d478-4243-8642-56d3041f0263";

const char kNameKey[] = "name";
const char kParentIdKey[] = "parent_id";
const char kPositionKey[] = "position";
Expand Down Expand Up @@ -288,6 +286,10 @@ class AppListSyncableService::ModelObserver : public AppListModelObserver {

// AppListSyncableService

// static
const char AppListSyncableService::kOemFolderId[] =
"ddb1da55-d478-4243-8642-56d3041f0263";

// static
void AppListSyncableService::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
Expand Down Expand Up @@ -1143,18 +1145,21 @@ std::string AppListSyncableService::FindOrCreateOemFolder() {
if (!oem_folder) {
std::unique_ptr<AppListFolderItem> new_folder(new AppListFolderItem(
kOemFolderId, AppListFolderItem::FOLDER_TYPE_OEM));
oem_folder =
static_cast<AppListFolderItem*>(model_->AddItem(std::move(new_folder)));
SyncItem* oem_sync_item = FindSyncItem(kOemFolderId);
syncer::StringOrdinal oem_position;
if (oem_sync_item) {
DCHECK(oem_sync_item->item_ordinal.IsValid());
VLOG(1) << "Creating OEM folder from existing sync item: "
<< oem_sync_item->item_ordinal.ToDebugString();
model_->SetItemPosition(oem_folder, oem_sync_item->item_ordinal);
oem_position = oem_sync_item->item_ordinal;
} else {
model_->SetItemPosition(oem_folder, GetOemFolderPos());
oem_position = GetOemFolderPos();
// Do not create a sync item for the OEM folder here, do it in
// ResolveFolderPositions() when the item position is finalized.
}
oem_folder =
static_cast<AppListFolderItem*>(model_->AddItem(std::move(new_folder)));
model_->SetItemPosition(oem_folder, oem_position);
}
model_->SetItemName(oem_folder, oem_folder_name_);
return oem_folder->id();
Expand All @@ -1181,24 +1186,40 @@ syncer::StringOrdinal AppListSyncableService::GetOemFolderPos() {
// stable. TODO(stevenjb): consider explicitly setting the OEM folder location
// along with the name in ServicesCustomizationDocument::SetOemFolderName().
AppListItemList* item_list = model_->top_level_item_list();
if (item_list->item_count() == 0)
return syncer::StringOrdinal();
if (!item_list->item_count()) {
LOG(ERROR) << "No top level item was found. "
<< "Placing OEM folder at the beginning.";
return syncer::StringOrdinal::CreateInitialOrdinal();
}

size_t oem_index = 0;
for (; oem_index < item_list->item_count() - 1; ++oem_index) {
AppListItem* cur_item = item_list->item_at(oem_index);
if (cur_item->id() == extensions::kWebStoreAppId)
break;
size_t web_store_app_index;
if (!item_list->FindItemIndex(extensions::kWebStoreAppId,
&web_store_app_index)) {
LOG(ERROR) << "Web store position is not found it top items. "
<< "Placing OEM folder at the end.";
return item_list->item_at(item_list->item_count() - 1)
->position()
.CreateAfter();
}
syncer::StringOrdinal oem_ordinal;
AppListItem* prev = item_list->item_at(oem_index);
if (oem_index + 1 < item_list->item_count()) {
AppListItem* next = item_list->item_at(oem_index + 1);
oem_ordinal = prev->position().CreateBetween(next->position());
} else {
oem_ordinal = prev->position().CreateAfter();

// Skip items with the same position.
const AppListItem* web_store_app_item =
item_list->item_at(web_store_app_index);
for (size_t j = web_store_app_index + 1; j < item_list->item_count(); ++j) {
const AppListItem* next_item = item_list->item_at(j);
DCHECK(next_item->position().IsValid());
if (!next_item->position().Equals(web_store_app_item->position())) {
const syncer::StringOrdinal oem_ordinal =
web_store_app_item->position().CreateBetween(next_item->position());
VLOG(1) << "Placing OEM Folder at: " << j
<< " position: " << oem_ordinal.ToDebugString();
return oem_ordinal;
}
}
VLOG(1) << "Placing OEM Folder at: " << oem_index

const syncer::StringOrdinal oem_ordinal =
web_store_app_item->position().CreateAfter();
VLOG(1) << "Placing OEM Folder at: " << item_list->item_count()
<< " position: " << oem_ordinal.ToDebugString();
return oem_ordinal;
}
Expand Down
2 changes: 2 additions & 0 deletions chrome/browser/ui/app_list/app_list_syncable_service.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ class AppListSyncableService : public syncer::SyncableService,

~AppListSyncableService() override;

static const char kOemFolderId[];

// Registers prefs to support local storage.
static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);

Expand Down
133 changes: 133 additions & 0 deletions chrome/browser/ui/app_list/app_list_syncable_service_unittest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// 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 "chrome/browser/ui/app_list/app_list_syncable_service.h"
#include "base/files/scoped_temp_dir.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/app_list/app_list_test_util.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "components/crx_file/id_util.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/constants.h"
#include "ui/app_list/app_list_item.h"
#include "ui/app_list/app_list_model.h"

namespace {

scoped_refptr<extensions::Extension> MakeApp(
const std::string& name,
const std::string& id,
extensions::Extension::InitFromValueFlags flags) {
std::string err;
base::DictionaryValue value;
value.SetString("name", name);
value.SetString("version", "0.0");
value.SetString("app.launch.web_url", "http://google.com");
scoped_refptr<extensions::Extension> app = extensions::Extension::Create(
base::FilePath(), extensions::Manifest::INTERNAL, value, flags, id, &err);
EXPECT_EQ(err, "");
return app;
}

// Creates next by natural sort ordering application id. Application id has to
// have 32 chars each in range 'a' to 'p' inclusively.
std::string CreateNextAppId(const std::string& app_id) {
DCHECK(crx_file::id_util::IdIsValid(app_id));
std::string next_app_id = app_id;
size_t index = next_app_id.length() - 1;
while (index > 0 && next_app_id[index] == 'p')
next_app_id[index--] = 'a';
DCHECK(next_app_id[index] != 'p');
next_app_id[index]++;
DCHECK(crx_file::id_util::IdIsValid(next_app_id));
return next_app_id;
}

} // namespace

class AppListSyncableServiceTest : public AppListTestBase {
public:
AppListSyncableServiceTest() = default;
~AppListSyncableServiceTest() override = default;

void SetUp() override {
AppListTestBase::SetUp();

// Make sure we have a Profile Manager.
DCHECK(temp_dir_.CreateUniqueTempDir());
TestingBrowserProcess::GetGlobal()->SetProfileManager(
new ProfileManagerWithoutInit(temp_dir_.GetPath()));

extensions::ExtensionSystem* extension_system =
extensions::ExtensionSystem::Get(profile_.get());
DCHECK(extension_system);
app_list_syncable_service_.reset(
new app_list::AppListSyncableService(profile_.get(), extension_system));
}

void TearDown() override { app_list_syncable_service_.reset(); }

app_list::AppListModel* model() {
return app_list_syncable_service_->GetModel();
}

private:
base::ScopedTempDir temp_dir_;
std::unique_ptr<app_list::AppListSyncableService> app_list_syncable_service_;

DISALLOW_COPY_AND_ASSIGN(AppListSyncableServiceTest);
};

TEST_F(AppListSyncableServiceTest, OEMFolderForConflictingPos) {
// Create a "web store" app.
const std::string web_store_app_id(extensions::kWebStoreAppId);
scoped_refptr<extensions::Extension> store =
MakeApp("webstore", web_store_app_id,
extensions::Extension::WAS_INSTALLED_BY_DEFAULT);
service_->AddExtension(store.get());

// Create some app. Note its id should be greater than web store app id in
// order to move app in case of conflicting pos after web store app.
const std::string some_app_id = CreateNextAppId(extensions::kWebStoreAppId);
scoped_refptr<extensions::Extension> some_app =
MakeApp("some_app", some_app_id,
extensions ::Extension::WAS_INSTALLED_BY_DEFAULT);
service_->AddExtension(some_app.get());

app_list::AppListItem* web_store_item = model()->FindItem(web_store_app_id);
ASSERT_TRUE(web_store_item);
app_list::AppListItem* some_app_item = model()->FindItem(some_app_id);
ASSERT_TRUE(some_app_item);

// Simulate position conflict.
model()->SetItemPosition(web_store_item, some_app_item->position());

// Install an OEM app. It must be placed by default after web store app but in
// case of app of the same position should be shifted next.
const std::string oem_app_id = CreateNextAppId(some_app_id);
scoped_refptr<extensions::Extension> oem_app = MakeApp(
"oem_app", oem_app_id, extensions::Extension::WAS_INSTALLED_BY_OEM);
service_->AddExtension(oem_app.get());

size_t web_store_app_index;
size_t some_app_index;
size_t oem_app_index;
size_t oem_folder_index;
EXPECT_TRUE(model()->top_level_item_list()->FindItemIndex(
web_store_app_id, &web_store_app_index));
EXPECT_TRUE(model()->top_level_item_list()->FindItemIndex(some_app_id,
&some_app_index));
// OEM item is not top level element.
EXPECT_FALSE(model()->top_level_item_list()->FindItemIndex(oem_app_id,
&oem_app_index));
// But OEM folder is.
EXPECT_TRUE(model()->top_level_item_list()->FindItemIndex(
app_list::AppListSyncableService::kOemFolderId, &oem_folder_index));

// Ensure right item sequence.
EXPECT_EQ(some_app_index, web_store_app_index + 1);
EXPECT_EQ(oem_folder_index, web_store_app_index + 2);
}
1 change: 1 addition & 0 deletions chrome/test/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -4914,6 +4914,7 @@ test("unit_tests") {
"../browser/apps/drive/drive_app_mapping_unittest.cc",
"../browser/ui/app_list/app_context_menu_unittest.cc",
"../browser/ui/app_list/app_list_service_unittest.cc",
"../browser/ui/app_list/app_list_syncable_service_unittest.cc",
"../browser/ui/app_list/app_list_test_util.cc",
"../browser/ui/app_list/app_list_test_util.h",
"../browser/ui/app_list/arc/arc_app_test.cc",
Expand Down

0 comments on commit 651a2d0

Please sign in to comment.