Commit 4092bcf4 authored by Wei-Yin Chen (陳威尹)'s avatar Wei-Yin Chen (陳威尹) Committed by Commit Bot

[Instant Start] Add TabAttributeCache

TabAttributeCache stores URL, title, and root ID for tabs so that they
are available before native initialization.

Bug: 1016952
Change-Id: I45365dff66c89d859562c116e61a0a9ea75f2bd3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1874707Reviewed-by: default avatarYusuf Ozuysal <yusufo@chromium.org>
Commit-Queue: Wei-Yin Chen (陳威尹) <wychen@chromium.org>
Cr-Commit-Position: refs/heads/master@{#708704}
parent 380d58d8
...@@ -83,6 +83,7 @@ android_library("java") { ...@@ -83,6 +83,7 @@ android_library("java") {
"java/src/org/chromium/chrome/browser/tasks/TasksSurfaceCoordinator.java", "java/src/org/chromium/chrome/browser/tasks/TasksSurfaceCoordinator.java",
"java/src/org/chromium/chrome/browser/tasks/TasksView.java", "java/src/org/chromium/chrome/browser/tasks/TasksView.java",
"java/src/org/chromium/chrome/browser/tasks/TasksViewBinder.java", "java/src/org/chromium/chrome/browser/tasks/TasksViewBinder.java",
"java/src/org/chromium/chrome/browser/tasks/pseudotab/TabAttributeCache.java",
"java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupUtils.java", "java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupUtils.java",
"java/src/org/chromium/chrome/browser/tasks/tab_management/ClosableTabGridView.java", "java/src/org/chromium/chrome/browser/tasks/tab_management/ClosableTabGridView.java",
"java/src/org/chromium/chrome/browser/tasks/tab_management/MultiThumbnailCardProvider.java", "java/src/org/chromium/chrome/browser/tasks/tab_management/MultiThumbnailCardProvider.java",
......
// Copyright 2019 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.
package org.chromium.chrome.browser.tasks.pseudotab;
import android.content.Context;
import android.content.SharedPreferences;
import org.chromium.base.ContextUtils;
import org.chromium.base.LifetimeAssert;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.EmptyTabModelObserver;
import org.chromium.chrome.browser.tabmodel.EmptyTabModelSelectorObserver;
import org.chromium.chrome.browser.tabmodel.TabModelFilter;
import org.chromium.chrome.browser.tabmodel.TabModelObserver;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabObserver;
/**
* Cache for attributes of {@link PseudoTab} to be available before native is ready.
*/
public class TabAttributeCache {
private static final String PREFERENCES_NAME = "tab_attribute_cache";
private static SharedPreferences sPref;
private final TabModelSelector mTabModelSelector;
private final TabModelObserver mTabModelObserver;
private final TabModelSelectorTabObserver mTabModelSelectorTabObserver;
private final TabModelSelectorObserver mTabModelSelectorObserver;
private final LifetimeAssert mLifetimeAssert = LifetimeAssert.create(this);
private static SharedPreferences getSharedPreferences() {
if (sPref == null) {
sPref = ContextUtils.getApplicationContext().getSharedPreferences(
PREFERENCES_NAME, Context.MODE_PRIVATE);
}
return sPref;
}
/**
* Create a TabAttributeCache instance to observe tab attribute changes.
*
* Note that querying tab attributes doesn't rely on having an instance.
* @param tabModelSelector The {@link TabModelSelector} to observe.
*/
public TabAttributeCache(TabModelSelector tabModelSelector) {
mTabModelSelector = tabModelSelector;
mTabModelSelectorTabObserver = new TabModelSelectorTabObserver(mTabModelSelector) {
@Override
public void onUrlUpdated(Tab tab) {
if (tab.isIncognito()) return;
String url = tab.getUrl();
cacheUrl(tab.getId(), url);
}
@Override
public void onTitleUpdated(Tab tab) {
if (tab.isIncognito()) return;
String title = tab.getTitle();
cacheTitle(tab.getId(), title);
}
@Override
public void onRootIdChanged(Tab tab, int newRootId) {
if (tab.isIncognito()) return;
assert newRootId == tab.getRootId();
cacheRootId(tab.getId(), newRootId);
}
};
mTabModelObserver = new EmptyTabModelObserver() {
@Override
public void tabClosureCommitted(Tab tab) {
int id = tab.getId();
getSharedPreferences()
.edit()
.remove(getUrlKey(id))
.remove(getTitleKey(id))
.remove(getRootIdKey(id))
.apply();
}
};
mTabModelSelectorObserver = new EmptyTabModelSelectorObserver() {
@Override
public void onTabStateInitialized() {
// TODO(wychen): after this cache is enabled by default, we only need to populate it
// once.
TabModelFilter filter =
mTabModelSelector.getTabModelFilterProvider().getTabModelFilter(false);
for (int i = 0; i < filter.getCount(); i++) {
Tab tab = filter.getTabAt(i);
cacheUrl(tab.getId(), tab.getUrl());
cacheTitle(tab.getId(), tab.getTitle());
cacheRootId(tab.getId(), tab.getRootId());
}
filter.addObserver(mTabModelObserver);
}
};
mTabModelSelector.addObserver(mTabModelSelectorObserver);
}
private static String getTitleKey(int id) {
return id + "_title";
}
/**
* Get the title of a {@link PseudoTab}.
* @param id The ID of the {@link PseudoTab}.
* @return The title
*/
public static String getTitle(int id) {
return getSharedPreferences().getString(getTitleKey(id), "");
}
private static void cacheTitle(int id, String title) {
getSharedPreferences().edit().putString(getTitleKey(id), title).apply();
}
/**
* Set the title of a {@link PseudoTab}. Only for testing.
* @param id The ID of the {@link PseudoTab}.
* @param title The title
*/
static void setTitleForTesting(int id, String title) {
cacheTitle(id, title);
}
private static String getUrlKey(int id) {
return id + "_url";
}
/**
* Get the URL of a {@link PseudoTab}.
* @param id The ID of the {@link PseudoTab}.
* @return The URL
*/
public static String getUrl(int id) {
return getSharedPreferences().getString(getUrlKey(id), "");
}
private static void cacheUrl(int id, String url) {
getSharedPreferences().edit().putString(getUrlKey(id), url).apply();
}
/**
* Set the URL of a {@link PseudoTab}.
* @param id The ID of the {@link PseudoTab}.
* @param url The URL
*/
static void setUrlForTesting(int id, String url) {
cacheUrl(id, url);
}
private static String getRootIdKey(int id) {
return id + "_rootID";
}
/**
* Get the root ID of a {@link PseudoTab}.
* @param id The ID of the {@link PseudoTab}.
* @return The root ID
*/
public static int getRootId(int id) {
return getSharedPreferences().getInt(getRootIdKey(id), Tab.INVALID_TAB_ID);
}
private static void cacheRootId(int id, int rootId) {
getSharedPreferences().edit().putInt(getRootIdKey(id), rootId).apply();
}
/**
* Set the root ID for a {@link PseudoTab}.
* @param id The ID of the {@link PseudoTab}.
* @param rootId The root ID
*/
static void setRootIdForTesting(int id, int rootId) {
cacheRootId(id, rootId);
}
/**
* Clear everything in the storage.
*/
static void clearAllForTesting() {
getSharedPreferences().edit().clear().apply();
}
/**
* Remove all the observers.
*/
public void destroy() {
mTabModelSelectorTabObserver.destroy();
mTabModelSelector.getTabModelFilterProvider().getTabModelFilter(false).removeObserver(
mTabModelObserver);
mTabModelSelector.removeObserver(mTabModelSelectorObserver);
LifetimeAssert.setSafeToGc(mLifetimeAssert, true);
}
}
// Copyright 2019 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.
package org.chromium.chrome.browser.tasks.pseudotab;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabObserver;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelFilter;
import org.chromium.chrome.browser.tabmodel.TabModelFilterProvider;
import org.chromium.chrome.browser.tabmodel.TabModelObserver;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorImpl;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
import org.chromium.chrome.test.util.browser.Features;
import java.util.ArrayList;
import java.util.List;
/**
* Unit tests for {@link TabAttributeCache}.
*/
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class TabAttributeCacheUnitTest {
@Rule
public TestRule mProcessor = new Features.JUnitProcessor();
private static final int TAB1_ID = 456;
private static final int TAB2_ID = 789;
private static final int POSITION1 = 0;
private static final int POSITION2 = 1;
@Mock
TabModelSelectorImpl mTabModelSelector;
@Mock
TabModelFilterProvider mTabModelFilterProvider;
@Mock
TabModelFilter mTabModelFilter;
@Mock
TabModel mTabModel;
@Captor
ArgumentCaptor<TabModelObserver> mTabModelObserverCaptor;
@Captor
ArgumentCaptor<TabModelSelectorObserver> mTabModelSelectorObserverCaptor;
@Captor
ArgumentCaptor<TabObserver> mTabObserverCaptor;
private Tab mTab1;
private Tab mTab2;
private TabAttributeCache mCache;
@Before
public void setUp() {
RecordUserAction.setDisabledForTests(true);
RecordHistogram.setDisabledForTests(true);
MockitoAnnotations.initMocks(this);
mTab1 = prepareTab(TAB1_ID);
mTab2 = prepareTab(TAB2_ID);
List<TabModel> tabModelList = new ArrayList<>();
tabModelList.add(mTabModel);
doReturn(tabModelList).when(mTabModelSelector).getModels();
doReturn(mTabModelFilterProvider).when(mTabModelSelector).getTabModelFilterProvider();
doReturn(mTabModelFilter).when(mTabModelFilterProvider).getTabModelFilter(eq(false));
doNothing().when(mTabModelFilter).addObserver(mTabModelObserverCaptor.capture());
doNothing().when(mTabModelSelector).addObserver(mTabModelSelectorObserverCaptor.capture());
doReturn(mTab1).when(mTabModel).getTabAt(POSITION1);
doReturn(mTab2).when(mTabModel).getTabAt(POSITION2);
doReturn(POSITION1).when(mTabModel).indexOf(mTab1);
doReturn(POSITION2).when(mTabModel).indexOf(mTab2);
doNothing().when(mTab1).addObserver(mTabObserverCaptor.capture());
doNothing().when(mTab2).addObserver(mTabObserverCaptor.capture());
doReturn(0).when(mTabModel).index();
doReturn(2).when(mTabModel).getCount();
doReturn(mTabModel).when(mTabModel).getComprehensiveModel();
mCache = new TabAttributeCache(mTabModelSelector);
}
@After
public void tearDown() {
RecordUserAction.setDisabledForTests(false);
RecordHistogram.setDisabledForTests(false);
mCache.destroy();
TabAttributeCache.clearAllForTesting();
}
@Test
public void updateUrl() {
String url = "url 1";
doReturn(url).when(mTab1).getUrl();
Assert.assertNotEquals(url, TabAttributeCache.getUrl(TAB1_ID));
mTabObserverCaptor.getValue().onUrlUpdated(mTab1);
Assert.assertEquals(url, TabAttributeCache.getUrl(TAB1_ID));
mTabModelSelectorObserverCaptor.getValue().onTabStateInitialized();
mTabModelObserverCaptor.getValue().tabClosureCommitted(mTab1);
Assert.assertNotEquals(url, TabAttributeCache.getUrl(TAB1_ID));
}
@Test
public void updateUrl_incognito() {
String url = "url 1";
doReturn(url).when(mTab1).getUrl();
doReturn(true).when(mTab1).isIncognito();
mTabObserverCaptor.getValue().onUrlUpdated(mTab1);
Assert.assertNotEquals(url, TabAttributeCache.getUrl(TAB1_ID));
}
@Test
public void updateTitle() {
String title = "title 1";
doReturn(title).when(mTab1).getTitle();
Assert.assertNotEquals(title, TabAttributeCache.getTitle(TAB1_ID));
mTabObserverCaptor.getValue().onTitleUpdated(mTab1);
Assert.assertEquals(title, TabAttributeCache.getTitle(TAB1_ID));
mTabModelSelectorObserverCaptor.getValue().onTabStateInitialized();
mTabModelObserverCaptor.getValue().tabClosureCommitted(mTab1);
Assert.assertNotEquals(title, TabAttributeCache.getTitle(TAB1_ID));
}
@Test
public void updateTitle_incognito() {
String title = "title 1";
doReturn(title).when(mTab1).getTitle();
doReturn(true).when(mTab1).isIncognito();
mTabObserverCaptor.getValue().onTitleUpdated(mTab1);
Assert.assertNotEquals(title, TabAttributeCache.getTitle(TAB1_ID));
}
@Test
public void updateRootId() {
int rootId = 1337;
doReturn(rootId).when(mTab1).getRootId();
Assert.assertNotEquals(rootId, TabAttributeCache.getRootId(TAB1_ID));
mTabObserverCaptor.getValue().onRootIdChanged(mTab1, rootId);
Assert.assertEquals(rootId, TabAttributeCache.getRootId(TAB1_ID));
mTabModelSelectorObserverCaptor.getValue().onTabStateInitialized();
mTabModelObserverCaptor.getValue().tabClosureCommitted(mTab1);
Assert.assertNotEquals(rootId, TabAttributeCache.getRootId(TAB1_ID));
}
@Test
public void updateRootId_incognito() {
int rootId = 1337;
doReturn(rootId).when(mTab1).getRootId();
doReturn(true).when(mTab1).isIncognito();
mTabObserverCaptor.getValue().onRootIdChanged(mTab1, rootId);
Assert.assertNotEquals(rootId, TabAttributeCache.getRootId(TAB1_ID));
}
@Test
public void onTabStateInitialized() {
String url1 = "url 1";
doReturn(url1).when(mTab1).getUrl();
String title1 = "title 1";
doReturn(title1).when(mTab1).getTitle();
int rootId1 = 1337;
doReturn(rootId1).when(mTab1).getRootId();
String url2 = "url 2";
doReturn(url2).when(mTab2).getUrl();
String title2 = "title 2";
doReturn(title2).when(mTab2).getTitle();
int rootId2 = 42;
doReturn(rootId2).when(mTab2).getRootId();
doReturn(mTab1).when(mTabModelFilter).getTabAt(0);
doReturn(mTab2).when(mTabModelFilter).getTabAt(1);
doReturn(2).when(mTabModelFilter).getCount();
Assert.assertNotEquals(url1, TabAttributeCache.getUrl(TAB1_ID));
Assert.assertNotEquals(title1, TabAttributeCache.getTitle(TAB1_ID));
Assert.assertNotEquals(rootId1, TabAttributeCache.getRootId(TAB1_ID));
Assert.assertNotEquals(url2, TabAttributeCache.getUrl(TAB2_ID));
Assert.assertNotEquals(title2, TabAttributeCache.getTitle(TAB2_ID));
Assert.assertNotEquals(rootId2, TabAttributeCache.getRootId(TAB2_ID));
mTabModelSelectorObserverCaptor.getValue().onTabStateInitialized();
Assert.assertEquals(url1, TabAttributeCache.getUrl(TAB1_ID));
Assert.assertEquals(title1, TabAttributeCache.getTitle(TAB1_ID));
Assert.assertEquals(rootId1, TabAttributeCache.getRootId(TAB1_ID));
Assert.assertEquals(url2, TabAttributeCache.getUrl(TAB2_ID));
Assert.assertEquals(title2, TabAttributeCache.getTitle(TAB2_ID));
Assert.assertEquals(rootId2, TabAttributeCache.getRootId(TAB2_ID));
}
private Tab prepareTab(int id) {
Tab tab = mock(Tab.class);
doReturn(id).when(tab).getId();
return tab;
}
}
...@@ -35,6 +35,7 @@ tab_management_test_java_sources = [ ...@@ -35,6 +35,7 @@ tab_management_test_java_sources = [
tab_management_junit_java_sources = [ tab_management_junit_java_sources = [
"//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/MostVisitedListViewBinderUnitTest.java", "//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/MostVisitedListViewBinderUnitTest.java",
"//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/pseudotab/TabAttributeCacheUnitTest.java",
"//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupModelFilterUnitTest.java", "//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupModelFilterUnitTest.java",
"//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupUtilsUnitTest.java", "//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupUtilsUnitTest.java",
"//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediatorUnitTest.java", "//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediatorUnitTest.java",
......
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