Commit 5db42678 authored by David Maunder's avatar David Maunder Committed by Commit Bot

Separate out TabState fields to separate class and change

serialization method to protobufs.

Currently in TabState fields are persisted by storing in a file one after
another. This approach does not scale well - fields are not well organized
and this approach makes it difficult to deprecate fields.

PersistedTabData enables persisted fields to be better organized and
protobufs offer better support for adding and deprecating fields.

PersistedTabData also enables different storage methods - in an
upcoming CL a PersistedTabData which uses a Level DB backend will
be used for data for building new features but is not pertinent
to the running of the app.

Bug: 1046367
Change-Id: Id73074303641babb24e2f6109254c53d0d763e2c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2025317
Commit-Queue: David Maunder <davidjm@chromium.org>
Reviewed-by: default avatarDavid Trainor <dtrainor@chromium.org>
Reviewed-by: default avatarTommy Nyquist <nyquist@chromium.org>
Cr-Commit-Position: refs/heads/master@{#750611}
parent d1bdf26f
......@@ -225,6 +225,7 @@ android_library("chrome_java") {
":chrome_public_android_manifest",
":chrome_public_apk_template_resources",
":chrome_version_constants",
":critical_persisted_tab_data_proto_java",
":partner_location_descriptor_proto_java",
":update_proto_java",
":usage_stats_proto_java",
......@@ -630,6 +631,11 @@ proto_java_library("partner_location_descriptor_proto_java") {
sources = [ "$proto_path/partner_location_descriptor.proto" ]
}
proto_java_library("critical_persisted_tab_data_proto_java") {
proto_path = "java/src/org/chromium/chrome/browser/tab/state/proto"
sources = [ "$proto_path/critical_persisted_tab_data.proto" ]
}
proto_java_library("update_proto_java") {
proto_path = "java/src/org/chromium/chrome/browser/omaha/metrics"
sources = [ "$proto_path/update_success_tracking.proto" ]
......
......@@ -1601,6 +1601,12 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/tab/TabWebContentsObserver.java",
"java/src/org/chromium/chrome/browser/tab/TabWebContentsUserData.java",
"java/src/org/chromium/chrome/browser/tab/TrustedCdn.java",
"java/src/org/chromium/chrome/browser/tab/state/CriticalPersistedTabData.java",
"java/src/org/chromium/chrome/browser/tab/state/FilePersistedTabDataStorage.java",
"java/src/org/chromium/chrome/browser/tab/state/PersistedTabData.java",
"java/src/org/chromium/chrome/browser/tab/state/PersistedTabDataConfiguration.java",
"java/src/org/chromium/chrome/browser/tab/state/PersistedTabDataFactory.java",
"java/src/org/chromium/chrome/browser/tab/state/PersistedTabDataStorage.java",
"java/src/org/chromium/chrome/browser/tab_activity_glue/ActivityTabWebContentsDelegateAndroid.java",
"java/src/org/chromium/chrome/browser/tab_activity_glue/ReparentingDelegateFactory.java",
"java/src/org/chromium/chrome/browser/tab_activity_glue/ReparentingTask.java",
......
......@@ -482,6 +482,8 @@ chrome_test_java_sources = [
"javatests/src/org/chromium/chrome/browser/tab/TabStateTest.java",
"javatests/src/org/chromium/chrome/browser/tab/TabUmaTest.java",
"javatests/src/org/chromium/chrome/browser/tab/UndoIntegrationTest.java",
"javatests/src/org/chromium/chrome/browser/tab/state/CriticalPersistedTabDataTest.java",
"javatests/src/org/chromium/chrome/browser/tab/state/FilePersistedTabDataStorageTest.java",
"javatests/src/org/chromium/chrome/browser/tabbed_mode/TabbedNavigationBarColorControllerTest.java",
"javatests/src/org/chromium/chrome/browser/tabmodel/AsyncTabCreationParamsManagerTest.java",
"javatests/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreatorTest.java",
......
......@@ -18,6 +18,9 @@ include_rules = [
"+components/browser_ui/styles/android",
"+components/browser_ui/widget/android",
"+content/public/android/java/src/org/chromium/content_public",
"+chrome/android/public/crypto/java/src/org/chromium/chrome/browser/crypto/CipherFactory.java",
"+chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabbedModeTabPersistencePolicy.java",
"+chrome/android/public/profiles/java/src/org/chromium/chrome/browser/profiles/Profile.java",
]
specific_include_rules = {
......
......@@ -299,7 +299,7 @@ public class TabState {
}
}
private static byte[] getContentStateByteArray(ByteBuffer buffer) {
public static byte[] getContentStateByteArray(ByteBuffer buffer) {
byte[] contentsStateBytes = new byte[buffer.limit()];
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
buffer.rewind();
......@@ -333,7 +333,7 @@ public class TabState {
}
/** Returns an object representing the state of the Tab's WebContents. */
private static WebContentsState getWebContentsState(TabImpl tab) {
public static WebContentsState getWebContentsState(TabImpl tab) {
if (tab.getFrozenContentsState() != null) return tab.getFrozenContentsState();
// Native call returns null when buffer allocation needed to serialize the state failed.
......@@ -346,7 +346,7 @@ public class TabState {
}
/** Returns an ByteBuffer representing the state of the Tab's WebContents. */
private static ByteBuffer getWebContentsStateAsByteBuffer(TabImpl tab) {
protected static ByteBuffer getWebContentsStateAsByteBuffer(TabImpl tab) {
LoadUrlParams pendingLoadParams = tab.getPendingLoadParams();
if (pendingLoadParams == null) {
return getContentsStateAsByteBuffer(tab);
......
// Copyright 2020 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.tab.state;
import android.support.v4.util.AtomicFile;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.Callback;
import org.chromium.base.Log;
import org.chromium.base.StreamUtil;
import org.chromium.chrome.browser.crypto.CipherFactory;
import org.chromium.chrome.browser.tabmodel.TabbedModeTabPersistencePolicy;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Locale;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
/**
* {@link PersistedTabDataStorage} which uses a file for the storage
*/
public class FilePersistedTabDataStorage implements PersistedTabDataStorage {
private static final String TAG = "FilePTDS";
@Override
public void save(int tabId, boolean isEncrypted, String dataId, byte[] data) {
// TODO(crbug.com/1059636) these should be queued and executed on a background thread
// TODO(crbug.com/1059637) we should introduce a retry mechanisms
// TODO(crbug.com/1059638) abstract out encrypt/decrypt
if (isEncrypted) {
Cipher cipher = CipherFactory.getInstance().getCipher(Cipher.ENCRYPT_MODE);
try {
data = cipher.doFinal(data);
} catch (BadPaddingException | IllegalBlockSizeException e) {
Log.e(TAG,
String.format(Locale.ENGLISH, "Problem encrypting data. Details: %s",
e.getMessage()));
return;
}
}
FileOutputStream outputStream = null;
try {
outputStream = new FileOutputStream(getFile(tabId, isEncrypted, dataId));
outputStream.write(data);
} catch (FileNotFoundException e) {
Log.e(TAG,
String.format(Locale.ENGLISH,
"FileNotFoundException while attempting to save for Tab %d "
+ "Details: %s",
tabId, e.getMessage()));
} catch (IOException e) {
Log.e(TAG,
String.format(Locale.ENGLISH,
"IOException while attempting to save for Tab %d. "
+ " Details: %s",
tabId, e.getMessage()));
} finally {
StreamUtil.closeQuietly(outputStream);
}
}
@Override
public void restore(int tabId, boolean isEncrypted, String dataId, Callback<byte[]> callback) {
File file = getFile(tabId, isEncrypted, dataId);
try {
AtomicFile atomicFile = new AtomicFile(file);
byte[] res = atomicFile.readFully();
if (isEncrypted) {
Cipher cipher = CipherFactory.getInstance().getCipher(Cipher.DECRYPT_MODE);
res = cipher.doFinal(res);
}
callback.onResult(res);
} catch (FileNotFoundException e) {
Log.e(TAG,
String.format(Locale.ENGLISH,
"FileNotFoundException while attempting to restore "
+ " for Tab %d. Details: %s",
tabId, e.getMessage()));
callback.onResult(null);
} catch (IOException e) {
Log.e(TAG,
String.format(Locale.ENGLISH,
"IOException while attempting to restore for Tab "
+ "%d. Details: %s",
tabId, e.getMessage()));
callback.onResult(null);
} catch (BadPaddingException | IllegalBlockSizeException e) {
Log.e(TAG,
String.format(
Locale.ENGLISH, "Error encrypting data. Details: %s", e.getMessage()));
callback.onResult(null);
}
}
@Override
public void delete(int tabId, boolean isEncrypted, String dataId) {
File file = getFile(tabId, isEncrypted, dataId);
if (!file.exists()) {
return;
}
boolean res = file.delete();
if (!res) {
Log.e(TAG, String.format(Locale.ENGLISH, "Error deleting file %s", file));
}
}
@VisibleForTesting
protected File getFile(int tabId, boolean isEncrypted, String dataId) {
String encryptedId = isEncrypted ? "Encrypted" : "NotEncrypted";
return new File(TabbedModeTabPersistencePolicy.getOrCreateTabbedModeStateDirectory(),
String.format(Locale.ENGLISH, "%d%s%s", tabId, encryptedId, dataId));
}
}
// Copyright 2020 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.tab.state;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.Callback;
import org.chromium.base.UserData;
import org.chromium.base.supplier.Supplier;
import org.chromium.chrome.browser.tab.Tab;
/**
* PersistedTabData is Tab data persisted across restarts
* A constructor of taking a Tab and a byte[] (serialized
* {@link PersistedTabData}, PersistedTabDataStorage and
* PersistedTabDataID (identifier for {@link PersistedTabData}
* in storage) is required as reflection is used to build
* the object after acquiring the serialized object from storage.
*/
public abstract class PersistedTabData implements UserData {
private static final String TAG = "PTD";
protected final Tab mTab;
private final PersistedTabDataStorage mPersistedTabDataStorage;
private final String mPersistedTabDataId;
/**
* @param tab {@link Tab} {@link PersistedTabData} is being stored for
* @param data serialized {@link Tab} metadata
* @param persistedTabDataStorage storage for {@link PersistedTabData}
* @param persistedTabDataId identifier for {@link PersistedTabData} in storage
*/
PersistedTabData(Tab tab, byte[] data, PersistedTabDataStorage persistedTabDataStorage,
String persistedTabDataId) {
this(tab, persistedTabDataStorage, persistedTabDataId);
deserialize(data);
}
/**
* @param tab {@link Tab} {@link PersistedTabData} is being stored for
* @param persistedTabDataStorage storage for {@link PersistedTabData}
* @param persistedTabDataId identifier for {@link PersistedTabData} in storage
*/
PersistedTabData(
Tab tab, PersistedTabDataStorage persistedTabDataStorage, String persistedTabDataId) {
mTab = tab;
mPersistedTabDataStorage = persistedTabDataStorage;
mPersistedTabDataId = persistedTabDataId;
}
/**
* Asynchronously acquire a {@link PersistedTabData}
* for a {@link Tab}
* @param tab {@link Tab} {@link PersistedTabData} is being acquired for.
* At a minimum, a frozen tab with an identifier and isIncognito fields set
* is required.
* @param factory {@link PersistedTabDataFactory} which will create {@link PersistedTabData}
* @param supplier for constructing a {@link PersistedTabData} from a
* {@link Tab}. This will be used as a fallback in the event that the {@link PersistedTabData}
* cannot be found in storage.
* @param clazz class of the {@link PersistedTabData}
* @param callback callback to pass the {@link PersistedTabData} in
* @return {@link PersistedTabData} from storage
*/
protected static <T extends PersistedTabData> void from(Tab tab,
PersistedTabDataFactory<T> factory, Supplier<T> supplier, Class<T> clazz,
Callback<T> callback) {
// TODO(crbug.com/1059602) cache callbacks
T persistedTabDataFromTab = getUserData(tab, clazz);
if (persistedTabDataFromTab != null) {
// TODO(crbug.com/1060181) post the task
callback.onResult(persistedTabDataFromTab);
return;
}
PersistedTabDataConfiguration config = PersistedTabDataConfiguration.get(clazz);
config.storage.restore(tab.getId(), tab.isIncognito(), config.id, (data) -> {
T persistedTabData;
if (data == null) {
persistedTabData = supplier.get();
} else {
persistedTabData = factory.create(data, config.storage, config.id);
}
if (persistedTabData != null) {
setUserData(tab, clazz, persistedTabData);
}
callback.onResult(persistedTabData);
});
}
/**
* Acquire {@link PersistedTabData} stored in {@link UserData} on a {@link Tab}
* @param tab the {@link Tab}
* @param clazz {@link PersistedTabData} class
*/
private static <T extends PersistedTabData> T getUserData(Tab tab, Class<T> clazz) {
return clazz.cast(tab.getUserDataHost().getUserData(clazz));
}
/**
* Set {@link PersistedTabData} on a {@link Tab} object using {@link UserDataHost}
* @param tab the {@link Tab}
* @param clazz {@link PersistedTabData} class - they key for {@link UserDataHost}
* @param persistedTabData {@link PersistedTabData} stored on the {@link UserDataHost}
* associated with the {@link Tab}
*/
private static <T extends PersistedTabData> T setUserData(
Tab tab, Class<T> clazz, T persistedTabData) {
return tab.getUserDataHost().setUserData(clazz, persistedTabData);
}
/**
* Save {@link PersistedTabData} to storage
* @param callback callback indicating success/failure
*/
@VisibleForTesting
protected void save() {
mPersistedTabDataStorage.save(
mTab.getId(), mTab.isIncognito(), mPersistedTabDataId, serialize());
}
/**
* @return {@link PersistedTabData} in serialized form.
*/
abstract byte[] serialize();
/**
* Deserialize serialized {@link PersistedTabData} and
* assign to fields in {@link PersistedTabData}
* @param bytes serialized PersistedTabData
*/
abstract void deserialize(@Nullable byte[] bytes);
/**
* Delete {@link PersistedTabData} for a tab id
* @param tabId tab identifier
* @param profile profile
*/
protected void delete() {
mPersistedTabDataStorage.delete(mTab.getId(), mTab.isIncognito(), mPersistedTabDataId);
}
/**
* Destroy the object. This will clean up any {@link PersistedTabData}
* in memory. It will not delete the stored data on a file or database.
*/
@Override
public abstract void destroy();
}
// Copyright 2020 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.tab.state;
import java.util.HashMap;
import java.util.Map;
/**
* Contains configuration values such as data storage methods and unique identifiers
* for {@link PersistedTabData}
*/
public enum PersistedTabDataConfiguration {
// TODO(crbug.com/1059650) investigate should this go in the app code?
// Also investigate if the storage instance should be shared.
CRITICAL_PERSISTED_TAB_DATA("CPTD", new FilePersistedTabDataStorage());
private static final Map<Class<? extends PersistedTabData>, PersistedTabDataConfiguration>
sLookup = new HashMap<>();
static {
// TODO(crbug.com/1060187) remove static initializer and initialization lazy
sLookup.put(CriticalPersistedTabData.class, CRITICAL_PERSISTED_TAB_DATA);
}
public final String id;
public final PersistedTabDataStorage storage;
/**
* @param id identifier for {@link PersistedTabData}
* @param storage {@link PersistedTabDataStorage} associated with {@link PersistedTabData}
*/
PersistedTabDataConfiguration(String id, PersistedTabDataStorage storage) {
this.id = id;
this.storage = storage;
}
/**
* Acquire {@link PersistedTabDataConfiguration} for a given {@link PersistedTabData} class
*/
public static PersistedTabDataConfiguration get(Class<? extends PersistedTabData> clazz) {
return sLookup.get(clazz);
}
}
// Copyright 2020 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.tab.state;
/**
* Creates a {@link PersistedTabData}
* @param <T> {@link PersistedTabData} being created
*/
public interface PersistedTabDataFactory<T extends PersistedTabData> {
/**
* @param data serialized {@link PersistedTabData}
* @param storage storage method {@link PersistedTabDataStorage} for {@link PersistedTabData}
* @param id identifier for {@link PersistedTabData} in storage
*/
T create(byte[] data, PersistedTabDataStorage storage, String id);
}
// Copyright 2020 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.tab.state;
import org.chromium.base.Callback;
/**
* Storage for {@link PersistedTabData}
*/
public interface PersistedTabDataStorage {
/**
* @param tabId identifier for the {@link Tab}
* @param isEncrypted true if the data should be encrypted (i.e. for incognito Tabs)
* @param tabDataId unique identifier representing the type {@link PersistedTabData}
* @param data serialized {@link PersistedTabData}
*/
void save(int tabId, boolean isEncrypted, String tabDataId, byte[] data);
/**
* @param tabId identifier for the {@link Tab}
* @param isEncrypted true if the stored data is encrypted (i.e. for incognito Tabs)
* @param tabDataId unique identifier representing the type of {@link PersistedTabData}
* @param callback to pass back the seraizliaed {@link PersistedTabData} in
*/
void restore(int tabId, boolean isEncrypted, String tabDataId, Callback<byte[]> callback);
/**
* @param tabId identifier for the {@link Tab}
* @param isEncrypted true if the stored data is encrypted (i.e. for incognito Tabs)
* @param tabDataId unique identifier representing the type of {@link PersistedTabData}
*/
void delete(int tabId, boolean isEncrypted, String tabDataId);
}
// Copyright 2020 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.
syntax = "proto2";
package org.chromium.chrome.browser.tab.proto;
option java_package = "org.chromium.chrome.browser.tab.proto";
message CriticalPersistedTabDataProto {
// Parent Tab identifier.
optional int32 parent_id = 1;
// Root Tab identifier.
optional int32 root_id = 2;
// Timestamp when Tab was created.
optional int64 timestamp_millis = 3;
// Content State.
optional bytes content_state_bytes = 4;
// Content State version.
optional int32 content_state_version = 5;
// Identifier for app which opened the Tab.
optional string opener_app_id = 6;
// Theme color.
optional int32 theme_color = 7;
// Launch type at creation.
// See TabLaunchType.java for full descriptions
enum LaunchTypeAtCreation {
FROM_LINK = 0;
FROM_EXTERNAL_APP = 1;
FROM_CHROME_UI = 2;
FROM_RESTORE = 3;
FROM_LONGPRESS_FOREGROUND = 4;
FROM_LONGPRESS_BACKGROUND = 5;
FROM_REPARENTING = 6;
FROM_LAUNCHER_SHORTCUT = 7;
FROM_SPECULATIVE_BACKGROUND_CREATION = 8;
FROM_BROWSER_ACTIONS = 9;
FROM_LAUNCH_NEW_INCOGNITO_TAB = 10;
FROM_STARTUP = 11;
FROM_START_SURFACE = 12;
SIZE = 13;
UNKNOWN = 14;
}
optional LaunchTypeAtCreation launch_type_at_creation = 8;
}
// Copyright 2020 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.tab.state;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.support.test.filters.SmallTest;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.chromium.base.Callback;
import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.chrome.browser.tab.MockTab;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.test.ChromeBrowserTestRule;
/**
* Test relating to {@link CriticalPersistedTabData}
*/
@RunWith(BaseJUnit4ClassRunner.class)
public class CriticalPersistedTabDataTest {
@Rule
public final ChromeBrowserTestRule mBrowserTestRule = new ChromeBrowserTestRule();
private static final int TAB_ID = 1;
private static final int PARENT_ID = 2;
private static final int ROOT_ID = 3;
private static final int CONTENT_STATE_VERSION = 42;
private static final byte[] CONTENT_STATE = {9, 10};
private static final long TIMESTAMP = 203847028374L;
private static final String APP_ID = "AppId";
private static final String OPENER_APP_ID = "OpenerAppId";
private static final int THEME_COLOR = 5;
private static final int LAUNCH_TYPE_AT_CREATION = 3;
@Mock
private Callback<Boolean> mBooleanCallback;
@Mock
private Callback<CriticalPersistedTabData> mRestoreCallback;
@Mock
private Callback<CriticalPersistedTabData> mDeleteCallback;
private static Tab mockTab(int id, boolean isEncrypted) {
return new MockTab(id, isEncrypted);
}
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
@SmallTest
@Test
public void testNonEncryptedSaveRestore() {
testSaveRestoreDelete(false);
}
@SmallTest
@Test
public void testEncryptedSaveRestoreDelete() {
testSaveRestoreDelete(true);
}
private void testSaveRestoreDelete(boolean isEncrypted) {
PersistedTabDataConfiguration config =
PersistedTabDataConfiguration.get(CriticalPersistedTabData.class);
CriticalPersistedTabData criticalPersistedTabData =
new CriticalPersistedTabData(mockTab(TAB_ID, isEncrypted), PARENT_ID, ROOT_ID,
TIMESTAMP, CONTENT_STATE, CONTENT_STATE_VERSION, OPENER_APP_ID, THEME_COLOR,
LAUNCH_TYPE_AT_CREATION, config.storage, config.id);
criticalPersistedTabData.save();
CriticalPersistedTabData.from(mockTab(TAB_ID, isEncrypted), mRestoreCallback);
ArgumentCaptor<CriticalPersistedTabData> cptdCaptor =
ArgumentCaptor.forClass(CriticalPersistedTabData.class);
verify(mRestoreCallback, times(1)).onResult(cptdCaptor.capture());
CriticalPersistedTabData restored = cptdCaptor.getValue();
Assert.assertNotNull(restored);
Assert.assertEquals(restored.getParentId(), PARENT_ID);
Assert.assertEquals(restored.getRootId(), ROOT_ID);
Assert.assertEquals(restored.getTimestampMillis(), TIMESTAMP);
Assert.assertEquals(restored.getContentStateVersion(), CONTENT_STATE_VERSION);
Assert.assertEquals(restored.getOpenerAppId(), OPENER_APP_ID);
Assert.assertEquals(restored.getThemeColor(), THEME_COLOR);
Assert.assertEquals(restored.getTabLaunchTypeAtCreation(), LAUNCH_TYPE_AT_CREATION);
Assert.assertArrayEquals(restored.getContentStateBytes(), CONTENT_STATE);
restored.delete();
CriticalPersistedTabData.from(mockTab(TAB_ID, isEncrypted), mDeleteCallback);
ArgumentCaptor<CriticalPersistedTabData> cptdDeletedCaptor =
ArgumentCaptor.forClass(CriticalPersistedTabData.class);
verify(mDeleteCallback, times(1)).onResult(cptdDeletedCaptor.capture());
Assert.assertNull(cptdDeletedCaptor.getValue());
// TODO(crbug.com/1060232) test restored.save() after restored.delete()
// Also cover
// - Multiple (different) TAB_IDs being stored in the same storage.
// - Tests for doing multiple operations on the same TAB_ID:
// - save() multiple times
// - restore() multiple times
// - delete() multiple times
}
}
// Copyright 2020 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.tab.state;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.support.test.filters.SmallTest;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.chromium.base.Callback;
import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeBrowserTestRule;
import java.io.File;
/**
* Tests relating to {@link FilePersistedTabDataStorage}
*/
@RunWith(BaseJUnit4ClassRunner.class)
public class FilePersistedTabDataStorageTest {
@Rule
public final ChromeBrowserTestRule mBrowserTestRule = new ChromeBrowserTestRule();
@Mock
private Callback<byte[]> mByteArrayCallback;
private static final int TAB_ID = 1;
private static final byte[] DATA = {13, 14};
private static final String DATA_ID = "DataId";
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
@SmallTest
@Test
public void testFilePersistedDataStorageNonEncrypted() {
testFilePersistedDataStorage(false);
}
@SmallTest
@Test
public void testFilePersistedDataStorageEncrypted() {
testFilePersistedDataStorage(true);
}
private void testFilePersistedDataStorage(boolean isEncrypted) {
FilePersistedTabDataStorage filePersistedTabDataStorage = new FilePersistedTabDataStorage();
filePersistedTabDataStorage.save(TAB_ID, isEncrypted, DATA_ID, DATA);
ArgumentCaptor<byte[]> byteArrayCaptor = ArgumentCaptor.forClass(byte[].class);
filePersistedTabDataStorage.restore(TAB_ID, isEncrypted, DATA_ID, mByteArrayCallback);
verify(mByteArrayCallback, times(1)).onResult(byteArrayCaptor.capture());
Assert.assertEquals(byteArrayCaptor.getValue().length, 2);
Assert.assertArrayEquals(byteArrayCaptor.getValue(), DATA);
File file = filePersistedTabDataStorage.getFile(TAB_ID, isEncrypted, DATA_ID);
Assert.assertTrue(file.exists());
filePersistedTabDataStorage.delete(TAB_ID, isEncrypted, DATA_ID);
Assert.assertFalse(file.exists());
}
}
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