summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordfalcantara <dfalcantara@chromium.org>2014-11-05 10:59:43 -0800
committerCommit bot <commit-bot@chromium.org>2014-11-05 19:00:06 +0000
commitea1e8afa9e0b32e48aac4bfe1df0a6ebedbff2b9 (patch)
tree87a1da5984933e13167bb8bd7685ee1784e1e5a1
parentcec083d3e7038a5b26e5fdc2e514d404890805cb (diff)
downloadchromium_src-ea1e8afa9e0b32e48aac4bfe1df0a6ebedbff2b9.zip
chromium_src-ea1e8afa9e0b32e48aac4bfe1df0a6ebedbff2b9.tar.gz
chromium_src-ea1e8afa9e0b32e48aac4bfe1df0a6ebedbff2b9.tar.bz2
Upstream TabState classes
Simply moving the TabState class upstream. The test had to be adjusted to that it would depend on ChromeShellTestBase instead of a downstream InstrumentationTestCase subclass. BUG=415747 TEST=TabStateTest NOTRY=true Review URL: https://codereview.chromium.org/690803003 Cr-Commit-Position: refs/heads/master@{#302842}
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/TabState.java345
-rw-r--r--chrome/android/javatests/src/org/chromium/chrome/browser/TabStateTest.java169
-rw-r--r--chrome/browser/android/chrome_jni_registrar.cc2
-rw-r--r--chrome/browser/android/tab_state.cc556
-rw-r--r--chrome/browser/android/tab_state.h73
-rw-r--r--chrome/chrome_browser.gypi3
6 files changed, 1148 insertions, 0 deletions
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/TabState.java b/chrome/android/java/src/org/chromium/chrome/browser/TabState.java
new file mode 100644
index 0000000..72df8ec
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/TabState.java
@@ -0,0 +1,345 @@
+// Copyright 2014 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;
+
+import android.os.Handler;
+import android.util.Log;
+
+import org.chromium.base.VisibleForTesting;
+import org.chromium.chrome.browser.util.StreamUtil;
+import org.chromium.content.browser.crypto.CipherFactory;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileChannel.MapMode;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.CipherOutputStream;
+
+/**
+ * Object that contains the state of a tab, including its navigation history.
+ */
+public class TabState {
+ private static final String TAG = "TabState";
+
+ /**
+ * Version number of the format used to save the WebContents navigation history, as returned by
+ * nativeGetContentsStateAsByteBuffer(). Version labels:
+ * 0 - Chrome m18
+ * 1 - Chrome m25
+ * 2 - Chrome m26+
+ */
+ public static final int CONTENTS_STATE_CURRENT_VERSION = 2;
+
+ /** Special value for mTimestampMillis. */
+ private static final long TIMESTAMP_NOT_SET = -1;
+
+ /** Checks if the TabState header is loaded properly. */
+ private static final long KEY_CHECKER = 0;
+
+ /** Overrides the Chrome channel/package name to test a variant channel-specific behaviour. */
+ @VisibleForTesting
+ static String sChannelNameOverrideForTest;
+
+ /** Contains the state for a WebContents. */
+ public static class WebContentsState {
+ public final ByteBuffer mBuffer;
+ private int mVersion;
+
+ public WebContentsState(ByteBuffer buffer) {
+ this.mBuffer = buffer;
+ }
+
+ public ByteBuffer buffer() {
+ return mBuffer;
+ }
+
+ public int version() {
+ return mVersion;
+ }
+
+ public void setVersion(int version) {
+ mVersion = version;
+ }
+
+ /**
+ * Creates a WebContents from the buffer.
+ * @param isHidden Whether or not the tab initially starts hidden.
+ * @return Pointer to the native WebContents.
+ */
+ public long restoreContentsFromByteBuffer(boolean isHidden) {
+ return nativeRestoreContentsFromByteBuffer(mBuffer, mVersion, isHidden);
+ }
+ }
+
+ /** Deletes the native-side portion of the buffer. */
+ public static class WebContentsStateNative extends WebContentsState {
+ private final Handler mHandler;
+
+ public WebContentsStateNative(ByteBuffer buffer) {
+ super(buffer);
+ this.mHandler = new Handler();
+ }
+
+ @Override
+ protected void finalize() {
+ assert mHandler != null;
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ nativeFreeWebContentsStateBuffer(mBuffer);
+ }
+ });
+ }
+ }
+
+ public long timestampMillis = TIMESTAMP_NOT_SET;
+ public WebContentsState contentsState; // Navigation history of the WebContents.
+ public int parentId = Tab.INVALID_TAB_ID;
+ public String openerAppId;
+ public boolean isIncognito;
+ public long syncId;
+ public boolean shouldPreserve;
+
+ /** @return Whether a Stable channel build of Chrome is being used. */
+ private static boolean isStableChannelBuild() {
+ if ("stable".equals(sChannelNameOverrideForTest)) return true;
+ return ChromeVersionInfo.isStableBuild();
+ }
+
+ /**
+ * Restores a particular TabState file from storage.
+ * @param tabFile Location of the TabState file.
+ * @param isIncognito Whether the Tab is incognito or not.
+ * @return TabState that has been restored, or null if it failed.
+ */
+ public static TabState restoreTabState(File tabFile, boolean isIncognito) {
+ FileInputStream stream = null;
+ TabState tabState = null;
+ try {
+ stream = new FileInputStream(tabFile);
+ tabState = TabState.readState(stream, isIncognito);
+ } catch (FileNotFoundException exception) {
+ Log.e(TAG, "Failed to restore tab state for tab: " + tabFile);
+ } catch (IOException exception) {
+ Log.e(TAG, "Failed to restore tab state.", exception);
+ } finally {
+ StreamUtil.closeQuietly(stream);
+ }
+ return tabState;
+ }
+
+ /**
+ * Restores a particular TabState file from storage.
+ * @param input Location of the TabState file.
+ * @param encrypted Whether the file is encrypted or not.
+ * @return TabState that has been restored, or null if it failed.
+ */
+ public static TabState readState(FileInputStream input, boolean encrypted) throws IOException {
+ return readStateInternal(input, encrypted);
+ }
+
+ private static TabState readStateInternal(FileInputStream input, boolean encrypted)
+ throws IOException {
+ DataInputStream stream = null;
+ if (encrypted) {
+ Cipher cipher = CipherFactory.getInstance().getCipher(Cipher.DECRYPT_MODE);
+ if (cipher != null) {
+ stream = new DataInputStream(new CipherInputStream(input, cipher));
+ }
+ }
+ if (stream == null) {
+ stream = new DataInputStream(input);
+ }
+ try {
+ if (encrypted && stream.readLong() != KEY_CHECKER) {
+ // Got the wrong key, skip the file
+ return null;
+ }
+ TabState tabState = new TabState();
+ tabState.timestampMillis = stream.readLong();
+ int size = stream.readInt();
+ if (encrypted) {
+ // If it's encrypted, we have to read the stream normally to apply the cipher.
+ byte[] state = new byte[size];
+ stream.readFully(state);
+ tabState.contentsState = new WebContentsState(ByteBuffer.allocateDirect(size));
+ tabState.contentsState.buffer().put(state);
+ } else {
+ // If not, we can mmap the file directly, saving time and copies into the java heap.
+ FileChannel channel = input.getChannel();
+ tabState.contentsState = new WebContentsState(
+ channel.map(MapMode.READ_ONLY, channel.position(), size));
+ // Skip ahead to avoid re-reading data that mmap'd.
+ long skipped = input.skip(size);
+ if (skipped != size) {
+ Log.e(TAG, "Only skipped " + skipped + " bytes when " + size + " should've "
+ + "been skipped. Tab restore may fail.");
+ }
+ }
+ tabState.parentId = stream.readInt();
+ try {
+ tabState.openerAppId = stream.readUTF();
+ if ("".equals(tabState.openerAppId)) tabState.openerAppId = null;
+ } catch (EOFException eof) {
+ // Could happen if reading a version of a TabState that does not include the app id.
+ Log.w(TAG, "Failed to read opener app id state from tab state");
+ }
+ try {
+ tabState.contentsState.setVersion(stream.readInt());
+ } catch (EOFException eof) {
+ // On the stable channel, the first release is version 18. For all other channels,
+ // chrome 25 is the first release.
+ tabState.contentsState.setVersion(isStableChannelBuild() ? 0 : 1);
+
+ // Could happen if reading a version of a TabState that does not include the
+ // version id.
+ Log.w(TAG, "Failed to read saved state version id from tab state. Assuming "
+ + "version " + tabState.contentsState.version());
+ }
+ try {
+ tabState.syncId = stream.readLong();
+ } catch (EOFException eof) {
+ tabState.syncId = 0;
+ // Could happen if reading a version of TabState without syncId.
+ Log.w(TAG, "Failed to read syncId from tab state. Assuming syncId is: 0");
+ }
+ try {
+ tabState.shouldPreserve = stream.readBoolean();
+ } catch (EOFException eof) {
+ // Could happen if reading a version of TabState without this flag set.
+ tabState.shouldPreserve = false;
+ Log.w(TAG, "Failed to read shouldPreserve flag from tab state. "
+ + "Assuming shouldPreserve is false");
+ }
+ tabState.isIncognito = encrypted;
+ return tabState;
+ } finally {
+ stream.close();
+ }
+ }
+
+ /**
+ * Writes the TabState to disk. This method may be called on either the UI or background thread.
+ * @param foutput Stream to write the tab's state to.
+ * @param state State object obtained from from {@link ChromeTab#getState()}.
+ * @param encrypted Whether or not the TabState should be encrypted.
+ */
+ public static void saveState(FileOutputStream foutput, TabState state, boolean encrypted)
+ throws IOException {
+ if (state == null || state.contentsState == null) {
+ return;
+ }
+ saveStateInternal(foutput, state, encrypted);
+ }
+
+ private static void saveStateInternal(FileOutputStream output, TabState state,
+ boolean encrypted) throws IOException {
+ DataOutputStream stream;
+ if (encrypted) {
+ Cipher cipher = CipherFactory.getInstance().getCipher(Cipher.ENCRYPT_MODE);
+ if (cipher != null) {
+ stream = new DataOutputStream(new CipherOutputStream(output, cipher));
+ } else {
+ // If cipher is null, getRandomBytes failed, which means encryption is meaningless.
+ // Therefore, do not save anything. This will cause users to lose Incognito state in
+ // certain cases. That is annoying, but is better than failing to provide the
+ // guarantee of Incognito Mode.
+ return;
+ }
+ } else {
+ stream = new DataOutputStream(output);
+ }
+
+ try {
+ if (encrypted) {
+ stream.writeLong(KEY_CHECKER);
+ }
+ stream.writeLong(state.timestampMillis);
+ state.contentsState.buffer().rewind();
+ stream.writeInt(state.contentsState.buffer().remaining());
+ if (encrypted) {
+ byte[] bytes = new byte[state.contentsState.buffer().remaining()];
+ state.contentsState.buffer().get(bytes);
+ stream.write(bytes);
+ } else {
+ output.getChannel().write(state.contentsState.buffer());
+ }
+ stream.writeInt(state.parentId);
+ stream.writeUTF(state.openerAppId != null ? state.openerAppId : "");
+ stream.writeInt(state.contentsState.version());
+ stream.writeLong(state.syncId);
+ stream.writeBoolean(state.shouldPreserve);
+ } finally {
+ StreamUtil.closeQuietly(stream);
+ }
+ }
+
+ /** @return Title currently being displayed in the saved state's current entry. */
+ public String getDisplayTitleFromState() {
+ return nativeGetDisplayTitleFromByteBuffer(contentsState.buffer(), contentsState.version());
+ }
+
+ /** @return URL currently being displayed in the saved state's current entry. */
+ public String getVirtualUrlFromState() {
+ return nativeGetVirtualUrlFromByteBuffer(contentsState.buffer(), contentsState.version());
+ }
+
+ /**
+ * Creates a WebContentsState for a tab that will be loaded lazily.
+ * @param url URL that is pending.
+ * @param referrerUrl URL for the referrer.
+ * @param referrerPolicy Policy for the referrer.
+ * @param isIncognito Whether or not the state is meant to be incognito (e.g. encrypted).
+ * @return ByteBuffer that represents a state representing a single pending URL.
+ */
+ public static ByteBuffer createSingleNavigationStateAsByteBuffer(
+ String url, String referrerUrl, int referrerPolicy, boolean isIncognito) {
+ return nativeCreateSingleNavigationStateAsByteBuffer(
+ url, referrerUrl, referrerPolicy, isIncognito);
+ }
+
+ /**
+ * Returns the WebContents' state as a ByteBuffer.
+ * @param tab Tab to pickle.
+ * @return ByteBuffer containing the state of the WebContents.
+ */
+ public static ByteBuffer getContentsStateAsByteBuffer(Tab tab) {
+ return nativeGetContentsStateAsByteBuffer(tab);
+ }
+
+ /**
+ * Overrides the channel name for testing.
+ * @param name Channel to use.
+ */
+ public static void setChannelNameOverrideForTest(String name) {
+ sChannelNameOverrideForTest = name;
+ }
+
+ private static native long nativeRestoreContentsFromByteBuffer(
+ ByteBuffer buffer, int savedStateVersion, boolean initiallyHidden);
+
+ private static native ByteBuffer nativeGetContentsStateAsByteBuffer(Tab tab);
+
+ private static native ByteBuffer nativeCreateSingleNavigationStateAsByteBuffer(
+ String url, String referrerUrl, int referrerPolicy, boolean isIncognito);
+
+ private static native String nativeGetDisplayTitleFromByteBuffer(
+ ByteBuffer state, int savedStateVersion);
+
+ private static native String nativeGetVirtualUrlFromByteBuffer(
+ ByteBuffer state, int savedStateVersion);
+
+ private static native void nativeFreeWebContentsStateBuffer(ByteBuffer buffer);
+} \ No newline at end of file
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/TabStateTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/TabStateTest.java
new file mode 100644
index 0000000..aeea33b
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/TabStateTest.java
@@ -0,0 +1,169 @@
+// Copyright 2014 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;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Base64;
+
+import org.chromium.chrome.browser.util.StreamUtil;
+import org.chromium.chrome.shell.ChromeShellTestBase;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Tests whether TabState can be saved and restored to disk properly. Also checks to see if
+ * TabStates from previous versions of Chrome can still be loaded and upgraded.
+ *
+ * This test suite explicitly stores the tab states in this file to get around an infrastructure bug
+ * with setting file permissions. Ideally there wouldn't be any real I/O in this function, but we
+ * need it because TabState functions require FileInputStreams.
+ */
+public class TabStateTest extends ChromeShellTestBase {
+ private static final String M18_TAB0 =
+ "AAABPLD4wNkAAALk4AIAAAAAAAACAAAAAQAAAB0AAABjaHJvbWU6Ly9uZXd0YWIvI21vc3Rfdmlz"
+ + "aXRlZAAAAAAAAAAHAAAATgBlAHcAIAB0AGEAYgAAADQBAAAwAQAACwAAADoAAABjAGgAcgBvAG0A"
+ + "ZQA6AC8ALwBuAGUAdwB0AGEAYgAvACMAbQBvAHMAdABfAHYAaQBzAGkAdABlAGQAAAA6AAAAYwBo"
+ + "AHIAbwBtAGUAOgAvAC8AbgBlAHcAdABhAGIALwAjAG0AbwBzAHQAXwB2AGkAcwBpAHQAZQBkAAAA"
+ + "/////wAAAAD//////////wgAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAD/////AAAAAAgAAAAA"
+ + "AAAAAAAAQHuHvz0F1QQAfIe/PQXVBAABAAAAMgAAAP8BPwBvPwFTCGZvbGRlcklkPwFTATM/AVMR"
+ + "c2VsZWN0ZWRQYW5lSW5kZXg/AUkCewIAAAAAAAAA//////////8IAAAAAAAAAAAAAEABAAAAAAAA"
+ + "AAgAAAAWAAAAaHR0cDovL3d3dy5nb29nbGUuY29tLwAAAAAAAAYAAABHAG8AbwBnAGwAZQDcAAAA"
+ + "2AAAAAsAAAAsAAAAaAB0AHQAcAA6AC8ALwB3AHcAdwAuAGcAbwBvAGcAbABlAC4AYwBvAG0ALwAs"
+ + "AAAAaAB0AHQAcAA6AC8ALwB3AHcAdwAuAGcAbwBvAGcAbABlAC4AYwBvAG0ALwD/////AAAAAP//"
+ + "////////CAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAP////8AAAAACAAAAAAAAMCcguc/o8Iq"
+ + "PgXVBACkwio+BdUEAAAAAAAAAAAA//////////8IAAAAAAAAwJyC5z8AAAAAAAAAAAgAAAAdAAAA"
+ + "Y2hyb21lOi8vbmV3dGFiLyNtb3N0X3Zpc2l0ZWQAAAAAAAAAFgAAAGh0dHA6Ly93d3cuZ29vZ2xl"
+ + "LmNvbS8AAAAAAAD/////AAA=";
+
+ private static final String M18_TAB1 =
+ "AAABPK1gIpkAAAQYFAQAAAAAAAACAAAAAAAAAB0AAABjaHJvbWU6Ly9uZXd0YWIvI21vc3Rfdmlz"
+ + "aXRlZAAAAAAAAAAHAAAATgBlAHcAIAB0AGEAYgAAADQBAAAwAQAACwAAADoAAABjAGgAcgBvAG0A"
+ + "ZQA6AC8ALwBuAGUAdwB0AGEAYgAvACMAbQBvAHMAdABfAHYAaQBzAGkAdABlAGQAAAA6AAAAYwBo"
+ + "AHIAbwBtAGUAOgAvAC8AbgBlAHcAdABhAGIALwAjAG0AbwBzAHQAXwB2AGkAcwBpAHQAZQBkAAAA"
+ + "/////wAAAAD//////////wgAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAD/////AAAAAAgAAAAA"
+ + "AAAAAAAAQMYvST4F1QQAxy9JPgXVBAABAAAAMgAAAP8BPwBvPwFTCGZvbGRlcklkPwFTATM/AVMR"
+ + "c2VsZWN0ZWRQYW5lSW5kZXg/AUkCewIAAAAAAAAA//////////8IAAAAAAAAAAAAAEABAAAAAAAA"
+ + "AAYAAAEVAAAAaHR0cDovL3d3dy5nb29nbGUuY2EvAAAAAAAAAAYAAABHAG8AbwBnAGwAZQAUAgAA"
+ + "EAIAAAsAAAAqAAAAaAB0AHQAcAA6AC8ALwB3AHcAdwAuAGcAbwBvAGcAbABlAC4AYwBhAC8AAAAi"
+ + "AAAAaAB0AHQAcAA6AC8ALwBnAG8AbwBnAGwAZQAuAGMAYQAvAAAA/////wAAAAAMAAAARwBvAG8A"
+ + "ZwBsAGUA/////wgAAAAAAAAAAAAAAAAAAAACAAAAAQAAAAAAAAD/////AwAAAAYAAABjAHMAaQAA"
+ + "ABAAAAB0AGUAeAB0AGEAcgBlAGEA/////wgAAAAAAAAAAAAAQHu+oD4F1QQAfL6gPgXVBAAAAAAA"
+ + "AAAAAP//////////CAAAAAAAAAAAAABAAQAAAAEAAAALAAAAFgAAAGEAYgBvAHUAdAA6AGIAbABh"
+ + "AG4AawAAABYAAABhAGIAbwB1AHQAOgBiAGwAYQBuAGsAAAAIAAAAdwBnAGoAZgD/////////////"
+ + "//8IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAAGgAdAB0AHAAOgAvAC8AdwB3AHcALgBn"
+ + "AG8AbwBnAGwAZQAuAGMAYQAvAAAAAAAAAAgAAAAAAAAAAADwP32+oD4F1QQAfr6gPgXVBAAAAAAA"
+ + "AAAAAP////8qAAAAaAB0AHQAcAA6AC8ALwB3AHcAdwAuAGcAbwBvAGcAbABlAC4AYwBhAC8AAAAI"
+ + "AAAAAAAAAAAA8D8BAAAAAAAAAAEAAAAdAAAAY2hyb21lOi8vbmV3dGFiLyNtb3N0X3Zpc2l0ZWQA"
+ + "AAAAAAAAEQAAAGh0dHA6Ly9nb29nbGUuY2EvAAAAAAAAAP////8AAA==";
+
+ private static final String M26_TAB0 =
+ "AAABPK2JhPQAAALg3AIAAAAAAAACAAAAAQAAAAAAAAAdAAAAY2hyb21lOi8vbmV3dGFiLyNtb3N0"
+ + "X3Zpc2l0ZWQAAAAHAAAATgBlAHcAIAB0AGEAYgAAACQBAAAgAQAADQAAADoAAABjAGgAcgBvAG0A"
+ + "ZQA6AC8ALwBuAGUAdwB0AGEAYgAvACMAbQBvAHMAdABfAHYAaQBzAGkAdABlAGQAAAA6AAAAYwBo"
+ + "AHIAbwBtAGUAOgAvAC8AbgBlAHcAdABhAGIALwAjAG0AbwBzAHQAXwB2AGkAcwBpAHQAZQBkAAAA"
+ + "/////wAAAAD//////////wgAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAD/////AAAAAAgAAAAA"
+ + "AAAAAADwP2hSNuEF1QQAaVI24QXVBAABAAAAMgAAAP8CPwBvPwFTCGZvbGRlcklkPwFTATM/AVMR"
+ + "c2VsZWN0ZWRQYW5lSW5kZXg/AUkCewIAAAAAAAAA//////////8AAAAABgAAAAAAAAAAAAAAAQAA"
+ + "AB0AAABjaHJvbWU6Ly9uZXd0YWIvI21vc3RfdmlzaXRlZAAAAAAAAABaa9YpnDMuAAEAAAAWAAAA"
+ + "aHR0cDovL3d3dy5nb29nbGUuY29tLwAABgAAAEcAbwBvAGcAbABlAMQAAADAAAAADQAAACwAAABo"
+ + "AHQAdABwADoALwAvAHcAdwB3AC4AZwBvAG8AZwBsAGUALgBjAG8AbQAvACQAAABoAHQAdABwADoA"
+ + "LwAvAGcAbwBvAGcAbABlAC4AYwBvAG0ALwD/////AAAAAP//////////CAAAAAAAAAAAAAAAAAAA"
+ + "AAAAAAABAAAAAAAAAP////8AAAAACAAAAAAAAMCcgtc/XIjK4gXVBABdiMriBdUEAAAAAAAAAAAA"
+ + "//////////8AAAAAAQAAAgAAAAAAAAAAAQAAABIAAABodHRwOi8vZ29vZ2xlLmNvbS8AAAAAAABI"
+ + "AVIrnDMuAP////8AAA==";
+
+ private static final String M26_TAB1 =
+ "AAABPK2J90YAAALs6AIAAAAAAAACAAAAAQAAAAAAAAAdAAAAY2hyb21lOi8vbmV3dGFiLyNtb3N0"
+ + "X3Zpc2l0ZWQAAAAHAAAATgBlAHcAIAB0AGEAYgAAACQBAAAgAQAADQAAADoAAABjAGgAcgBvAG0A"
+ + "ZQA6AC8ALwBuAGUAdwB0AGEAYgAvACMAbQBvAHMAdABfAHYAaQBzAGkAdABlAGQAAAA6AAAAYwBo"
+ + "AHIAbwBtAGUAOgAvAC8AbgBlAHcAdABhAGIALwAjAG0AbwBzAHQAXwB2AGkAcwBpAHQAZQBkAAAA"
+ + "/////wAAAAD//////////wgAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAD/////AAAAAAgAAAAA"
+ + "AAAAAADwP9eU9OIF1QQA2JT04gXVBAABAAAAMgAAAP8CPwBvPwFTCGZvbGRlcklkPwFTATM/AVMR"
+ + "c2VsZWN0ZWRQYW5lSW5kZXg/AUkCewIAAAAAAAAA//////////8AAAAABgAAAAAAAAAAAAAAAQAA"
+ + "AB0AAABjaHJvbWU6Ly9uZXd0YWIvI21vc3RfdmlzaXRlZAAAAAAAAADl8oQrnDMuAAEAAAAVAAAA"
+ + "aHR0cDovL3d3dy5nb29nbGUuY2EvAAAABgAAAEcAbwBvAGcAbABlAMwAAADIAAAADQAAACoAAABo"
+ + "AHQAdABwADoALwAvAHcAdwB3AC4AZwBvAG8AZwBsAGUALgBjAGEALwAAACoAAABoAHQAdABwADoA"
+ + "LwAvAHcAdwB3AC4AZwBvAG8AZwBsAGUALgBjAGEALwAAAP////8AAAAA//////////8IAAAAAAAA"
+ + "AAAAAAAAAAAAAAAAAAEAAAAAAAAA/////wAAAAAIAAAAAAAAAAAA8D9VtDjjBdUEAFa0OOMF1QQA"
+ + "AAAAAAAAAAD//////////wAAAAABAAAAAAAAAAAAAAABAAAAFQAAAGh0dHA6Ly93d3cuZ29vZ2xl"
+ + "LmNhLwAAAAAAAAD8oBUsnDMuAP////8AAA==";
+
+ private static final String M38_TAB =
+ "AAABSPI9OA8AAALs6AIAAAAAAAACAAAAAQAAACABAAAcAQAAAAAAABcAAABjaHJvbWUtbmF0aXZl"
+ + "Oi8vbmV3dGFiLwAHAAAATgBlAHcAIAB0AGEAYgAAAKQAAACgAAAAFQAAAAAAAAAuAAAAYwBoAHIA"
+ + "bwBtAGUALQBuAGEAdABpAHYAZQA6AC8ALwBuAGUAdwB0AGEAYgAvAAAA/////wAAAAAAAAAA////"
+ + "/wAAAAAIAAAAAAAAAAAA8D8EkSY/8gQFAAWRJj/yBAUABpEmP/IEBQABAAAACAAAAAAAAAAAAPC/"
+ + "CAAAAAAAAAAAAPC/AAAAAAAAAAD/////AAAAAAYAAAAAAAAAAAAAAAEAAAAXAAAAY2hyb21lLW5h"
+ + "dGl2ZTovL25ld3RhYi8AAAAAAMnUrIeIYy4AAAAAAAAAAAC0AQAAsAEAAAEAAAAUAAAAaHR0cDov"
+ + "L3RleHRhcmVhLm9yZy8IAAAAdABlAHgAdABhAHIAZQBhAEABAAA8AQAAFQAAAAAAAAAoAAAAaAB0"
+ + "AHQAcAA6AC8ALwB0AGUAeAB0AGEAcgBlAGEALgBvAHIAZwAvAP////8AAAAAAAAAAP////8HAAAA"
+ + "YAAAAAoADQA/ACUAIABXAGUAYgBLAGkAdAAgAHMAZQByAGkAYQBsAGkAegBlAGQAIABmAG8AcgBt"
+ + "ACAAcwB0AGEAdABlACAAdgBlAHIAcwBpAG8AbgAgADgAIAAKAA0APQAmABAAAABOAG8AIABvAHcA"
+ + "bgBlAHIAAgAAADEAAAAAAAAAEAAAAHQAZQB4AHQAYQByAGUAYQACAAAAMQAAAAAAAAAIAAAAAAAA"
+ + "AAAAAAAHkSY/8gQFAAiRJj/yBAUABpEmP/IEBQABAAAACAAAAAAAAAAAAAAACAAAAAAAAAAAAAAA"
+ + "AAAAAAAAAAD/////AAAAAAEAAAIAAAAAAAAAAAEAAAAUAAAAaHR0cDovL3RleHRhcmVhLm9yZy8A"
+ + "AAAANKvVh4hjLgAAAAAAyAAAAP////8AAAAAAAIAAAAAAAAAAwE=";
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ clearAppData();
+ startChromeBrowserProcessSync(getInstrumentation().getTargetContext());
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ TabState.setChannelNameOverrideForTest(null);
+ super.tearDown();
+ }
+
+ private void doTest(String encodedFile, String url,
+ String title, int expectedVersion) throws IOException {
+ String filename = "tab_temp";
+ File directory = getInstrumentation().getTargetContext().getCacheDir();
+ File tabStateFile = new File(directory, filename);
+ FileOutputStream outputStream = null;
+ try {
+ outputStream = new FileOutputStream(tabStateFile);
+ outputStream.write(Base64.decode(encodedFile, 0));
+ } catch (FileNotFoundException e) {
+ assert false : "Failed to create " + filename;
+ } finally {
+ StreamUtil.closeQuietly(outputStream);
+ }
+
+ TabState tabState = TabState.restoreTabState(tabStateFile, false);
+ if (!tabStateFile.delete()) {
+ assert false : "Failed to delete " + filename;
+ }
+ assertNotNull(tabState);
+ assertEquals(url, tabState.getVirtualUrlFromState());
+ assertEquals(title, tabState.getDisplayTitleFromState());
+ assertEquals(expectedVersion, tabState.contentsState.version());
+ }
+
+ @SmallTest
+ public void testLoadM18Tabs() throws Exception {
+ TabState.setChannelNameOverrideForTest("stable");
+ doTest(M18_TAB0, "http://www.google.com/", "Google", 0);
+ doTest(M18_TAB1, "chrome://newtab/#most_visited", "New tab", 0);
+ }
+
+ @SmallTest
+ public void testLoadM26Tabs() throws Exception {
+ TabState.setChannelNameOverrideForTest(null);
+ doTest(M26_TAB0, "http://www.google.com/", "Google", 1);
+ doTest(M26_TAB1, "http://www.google.ca/", "Google", 1);
+ }
+
+ @SmallTest
+ public void testLoadM38Tab() throws Exception {
+ TabState.setChannelNameOverrideForTest(null);
+ doTest(M38_TAB, "http://textarea.org/", "textarea", 2);
+ }
+}
diff --git a/chrome/browser/android/chrome_jni_registrar.cc b/chrome/browser/android/chrome_jni_registrar.cc
index ef0d49e..df01efc 100644
--- a/chrome/browser/android/chrome_jni_registrar.cc
+++ b/chrome/browser/android/chrome_jni_registrar.cc
@@ -37,6 +37,7 @@
#include "chrome/browser/android/signin/account_management_screen_helper.h"
#include "chrome/browser/android/signin/signin_manager_android.h"
#include "chrome/browser/android/tab_android.h"
+#include "chrome/browser/android/tab_state.h"
#include "chrome/browser/android/uma_bridge.h"
#include "chrome/browser/android/uma_utils.h"
#include "chrome/browser/android/url_utilities.h"
@@ -187,6 +188,7 @@ static base::android::RegistrationMethod kChromeRegisteredMethods[] = {
{ "StartupMetricUtils", RegisterStartupMetricUtils },
{ "TabAndroid", TabAndroid::RegisterTabAndroid },
{ "TabModelJniBridge", TabModelJniBridge::Register},
+ { "TabState", RegisterTabState },
{ "TemplateUrlServiceAndroid", TemplateUrlServiceAndroid::Register },
{ "ToolbarModelAndroid", ToolbarModelAndroid::RegisterToolbarModelAndroid },
{ "TranslateInfoBarDelegate", RegisterTranslateInfoBarDelegate },
diff --git a/chrome/browser/android/tab_state.cc b/chrome/browser/android/tab_state.cc
new file mode 100644
index 0000000..4a8c107
--- /dev/null
+++ b/chrome/browser/android/tab_state.cc
@@ -0,0 +1,556 @@
+// Copyright 2014 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/android/tab_state.h"
+
+#include <jni.h>
+#include <limits>
+#include <vector>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/pickle.h"
+#include "chrome/browser/android/tab_android.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/sessions/session_command.h"
+#include "components/sessions/content/content_serialized_navigation_builder.h"
+#include "components/sessions/serialized_navigation_entry.h"
+#include "content/public/browser/navigation_controller.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/web_contents.h"
+#include "jni/TabState_jni.h"
+
+using base::android::ConvertUTF16ToJavaString;
+using base::android::ConvertUTF8ToJavaString;
+using base::android::ScopedJavaLocalRef;
+using content::NavigationController;
+using content::WebContents;
+
+namespace {
+
+bool WriteStateHeaderToPickle(bool off_the_record, int entry_count,
+ int current_entry_index, Pickle* pickle) {
+ return pickle->WriteBool(off_the_record) &&
+ pickle->WriteInt(entry_count) &&
+ pickle->WriteInt(current_entry_index);
+}
+
+// Migrates a pickled SerializedNavigationEntry from Android tab version 0 to
+// 2 or (Chrome 18->26).
+//
+// Due to the fact that all SerializedNavigationEntrys were previously stored
+// in a single pickle on Android, this function has to read the fields exactly
+// how they were written on m18 which is a custom format and different other
+// chromes.
+//
+// This uses the fields from SerializedNavigationEntry/TabNavigation from:
+// https://gerrit-int.chromium.org/gitweb?p=clank/internal/apps.git;
+// a=blob;f=native/framework/chrome/tab.cc;hb=refs/heads/m18
+//
+// 1. For each tab navigation:
+// virtual_url
+// title
+// content_state
+// transition_type
+// type_mask
+//
+// 2. For each tab navigation:
+// referrer
+// is_overriding_user_agent
+//
+void UpgradeNavigationFromV0ToV2(
+ std::vector<sessions::SerializedNavigationEntry>* navigations,
+ int entry_count,
+ PickleIterator* iterator) {
+
+ for (int i = 0; i < entry_count; ++i) {
+ Pickle v2_pickle;
+ std::string virtual_url_spec;
+ std::string str_referrer;
+ base::string16 title;
+ std::string content_state;
+ int transition_type_int;
+ if (!iterator->ReadString(&virtual_url_spec) ||
+ !iterator->ReadString(&str_referrer) ||
+ !iterator->ReadString16(&title) ||
+ !iterator->ReadString(&content_state) ||
+ !iterator->ReadInt(&transition_type_int))
+ return;
+
+ // Write back the fields that were just read.
+ v2_pickle.WriteInt(i);
+ v2_pickle.WriteString(virtual_url_spec);
+ v2_pickle.WriteString16(title);
+ v2_pickle.WriteString(content_state);
+ v2_pickle.WriteInt(transition_type_int);
+
+ // type_mask
+ v2_pickle.WriteInt(0);
+ // referrer_spec
+ v2_pickle.WriteString(str_referrer);
+ // policy_int
+ v2_pickle.WriteInt(0);
+ // original_request_url_spec
+ v2_pickle.WriteString(std::string());
+ // is_overriding_user_agent
+ v2_pickle.WriteBool(false);
+ // timestamp_internal_value
+ v2_pickle.WriteInt64(0);
+ // search_terms
+ v2_pickle.WriteString16(base::string16());
+
+ PickleIterator tab_navigation_pickle_iterator(v2_pickle);
+ sessions::SerializedNavigationEntry nav;
+ if (nav.ReadFromPickle(&tab_navigation_pickle_iterator)) {
+ navigations->push_back(nav);
+ } else {
+ LOG(ERROR) << "Failed to read SerializedNavigationEntry from pickle "
+ << "(index=" << i << ", url=" << virtual_url_spec;
+ }
+
+ }
+
+ for (int i = 0; i < entry_count; ++i) {
+ std::string initial_url;
+ bool user_agent_overridden;
+ if (!iterator->ReadString(&initial_url) ||
+ !iterator->ReadBool(&user_agent_overridden)) {
+ break;
+ }
+ }
+}
+
+// Migrates a pickled SerializedNavigationEntry from Android tab version 0 to 1
+// (or Chrome 25->26)
+//
+// Due to the fact that all SerializedNavigationEntrys were previously stored in
+// a single pickle on Android, this function reads all the old fields,
+// re-outputs them and appends an empty string16, representing the new
+// search_terms field, and ensures that reading a v0 SerializedNavigationEntry
+// won't consume bytes from a subsequent SerializedNavigationEntry.
+//
+// This uses the fields from SerializedNavigationEntry/TabNavigation prior to
+// https://chromiumcodereview.appspot.com/11876045 which are:
+//
+// index
+// virtual_url
+// title
+// content_state
+// transition_type
+// type_mask
+// referrer
+// original_request_url
+// is_overriding_user_agent
+// timestamp
+//
+// And finally search_terms was added and this function appends it.
+void UpgradeNavigationFromV1ToV2(
+ std::vector<sessions::SerializedNavigationEntry>* navigations,
+ int entry_count,
+ PickleIterator* iterator) {
+ for (int i = 0; i < entry_count; ++i) {
+ Pickle v2_pickle;
+
+ int index;
+ std::string virtual_url_spec;
+ base::string16 title;
+ std::string content_state;
+ int transition_type_int;
+ if (!iterator->ReadInt(&index) ||
+ !iterator->ReadString(&virtual_url_spec) ||
+ !iterator->ReadString16(&title) ||
+ !iterator->ReadString(&content_state) ||
+ !iterator->ReadInt(&transition_type_int))
+ return;
+
+ // Write back the fields that were just read.
+ v2_pickle.WriteInt(index);
+ v2_pickle.WriteString(virtual_url_spec);
+ v2_pickle.WriteString16(title);
+ v2_pickle.WriteString(content_state);
+ v2_pickle.WriteInt(transition_type_int);
+
+ int type_mask = 0;
+ if (!iterator->ReadInt(&type_mask))
+ continue;
+ v2_pickle.WriteInt(type_mask);
+
+ std::string referrer_spec;
+ if (iterator->ReadString(&referrer_spec))
+ v2_pickle.WriteString(referrer_spec);
+
+ int policy_int;
+ if (iterator->ReadInt(&policy_int))
+ v2_pickle.WriteInt(policy_int);
+
+ std::string original_request_url_spec;
+ if (iterator->ReadString(&original_request_url_spec))
+ v2_pickle.WriteString(original_request_url_spec);
+
+ bool is_overriding_user_agent;
+ if (iterator->ReadBool(&is_overriding_user_agent))
+ v2_pickle.WriteBool(is_overriding_user_agent);
+
+ int64 timestamp_internal_value = 0;
+ if (iterator->ReadInt64(&timestamp_internal_value))
+ v2_pickle.WriteInt64(timestamp_internal_value);
+
+ // Force output of search_terms
+ v2_pickle.WriteString16(base::string16());
+
+ PickleIterator tab_navigation_pickle_iterator(v2_pickle);
+ sessions::SerializedNavigationEntry nav;
+ if (nav.ReadFromPickle(&tab_navigation_pickle_iterator)) {
+ navigations->push_back(nav);
+ } else {
+ LOG(ERROR) << "Failed to read SerializedNavigationEntry from pickle "
+ << "(index=" << i << ", url=" << virtual_url_spec;
+ }
+ }
+}
+
+// Extracts state and navigation entries from the given Pickle data and returns
+// whether un-pickling the data succeeded
+bool ExtractNavigationEntries(
+ void* data,
+ int size,
+ int saved_state_version,
+ bool* is_off_the_record,
+ int* current_entry_index,
+ std::vector<sessions::SerializedNavigationEntry>* navigations) {
+ int entry_count;
+ Pickle pickle(static_cast<char*>(data), size);
+ PickleIterator iter(pickle);
+ if (!iter.ReadBool(is_off_the_record) || !iter.ReadInt(&entry_count) ||
+ !iter.ReadInt(current_entry_index)) {
+ LOG(ERROR) << "Failed to restore state from byte array (length=" << size
+ << ").";
+ return false;
+ }
+
+ if (!saved_state_version) {
+ // When |saved_state_version| is 0, it predates our notion of each tab
+ // having a saved version id. For that version of tab serialization, we
+ // used a single pickle for all |SerializedNavigationEntry|s.
+ UpgradeNavigationFromV0ToV2(navigations, entry_count, &iter);
+ } else if (saved_state_version == 1) {
+ // When |saved_state_version| is 1, it predates our notion of each tab
+ // having a saved version id. For that version of tab serialization, we
+ // used a single pickle for all |SerializedNavigationEntry|s.
+ UpgradeNavigationFromV1ToV2(navigations, entry_count, &iter);
+ } else {
+ // |saved_state_version| == 2 and greater.
+ for (int i = 0; i < entry_count; ++i) {
+ // Read each SerializedNavigationEntry as a separate pickle to avoid
+ // optional reads of one tab bleeding into the next tab's data.
+ int tab_navigation_data_length = 0;
+ const char* tab_navigation_data = NULL;
+ if (!iter.ReadInt(&tab_navigation_data_length) ||
+ !iter.ReadBytes(&tab_navigation_data, tab_navigation_data_length)) {
+ LOG(ERROR)
+ << "Failed to restore tab entry from byte array. "
+ << "(SerializedNavigationEntry size=" << tab_navigation_data_length
+ << ").";
+ return false; // It's dangerous to keep deserializing now, give up.
+ }
+ Pickle tab_navigation_pickle(tab_navigation_data,
+ tab_navigation_data_length);
+ PickleIterator tab_navigation_pickle_iterator(tab_navigation_pickle);
+ sessions::SerializedNavigationEntry nav;
+ if (!nav.ReadFromPickle(&tab_navigation_pickle_iterator))
+ return false; // If we failed to read a navigation, give up on others.
+
+ navigations->push_back(nav);
+ }
+ }
+
+ // Validate the data.
+ if (*current_entry_index < 0 ||
+ *current_entry_index >= static_cast<int>(navigations->size()))
+ return false;
+
+ return true;
+}
+
+}; // anonymous namespace
+
+ScopedJavaLocalRef<jobject> WebContentsState::GetContentsStateAsByteBuffer(
+ JNIEnv* env, TabAndroid* tab) {
+ Profile* profile = tab->GetProfile();
+ if (!profile)
+ return ScopedJavaLocalRef<jobject>();
+
+ content::NavigationController& controller =
+ tab->web_contents()->GetController();
+ const int pending_index = controller.GetPendingEntryIndex();
+ int entry_count = controller.GetEntryCount();
+ if (entry_count == 0 && pending_index == 0)
+ entry_count++;
+
+ if (entry_count == 0)
+ return ScopedJavaLocalRef<jobject>();
+
+ int current_entry = controller.GetLastCommittedEntryIndex();
+ if (current_entry == -1 && entry_count > 0)
+ current_entry = 0;
+
+ std::vector<content::NavigationEntry*> navigations(entry_count);
+ for (int i = 0; i < entry_count; ++i) {
+ content::NavigationEntry* entry = (i == pending_index) ?
+ controller.GetPendingEntry() : controller.GetEntryAtIndex(i);
+ navigations[i] = entry;
+ }
+
+ return WebContentsState::WriteNavigationsAsByteBuffer(
+ env,
+ profile->IsOffTheRecord(),
+ navigations,
+ current_entry);
+}
+
+// Common implementation for GetContentsStateAsByteBuffer() and
+// CreateContentsStateAsByteBuffer(). Does not assume ownership of the
+// navigations.
+ScopedJavaLocalRef<jobject> WebContentsState::WriteNavigationsAsByteBuffer(
+ JNIEnv* env,
+ bool is_off_the_record,
+ const std::vector<content::NavigationEntry*>& navigations,
+ int current_entry) {
+ Pickle pickle;
+ if (!WriteStateHeaderToPickle(is_off_the_record, navigations.size(),
+ current_entry, &pickle)) {
+ LOG(ERROR) << "Failed to serialize tab state (entry count=" <<
+ navigations.size() << ").";
+ return ScopedJavaLocalRef<jobject>();
+ }
+
+ // Write out all of the NavigationEntrys.
+ for (size_t i = 0; i < navigations.size(); ++i) {
+ // Write each SerializedNavigationEntry as a separate pickle to avoid
+ // optional reads of one tab bleeding into the next tab's data.
+ Pickle tab_navigation_pickle;
+ // Max size taken from BaseSessionService::CreateUpdateTabNavigationCommand.
+ static const size_t max_state_size =
+ std::numeric_limits<SessionCommand::size_type>::max() - 1024;
+ sessions::ContentSerializedNavigationBuilder::FromNavigationEntry(
+ i, *navigations[i])
+ .WriteToPickle(max_state_size, &tab_navigation_pickle);
+ pickle.WriteInt(tab_navigation_pickle.size());
+ pickle.WriteBytes(tab_navigation_pickle.data(),
+ tab_navigation_pickle.size());
+ }
+
+ void* buffer = malloc(pickle.size());
+ if (buffer == NULL) {
+ // We can run out of memory allocating a large enough buffer.
+ // In that case we'll only save the current entry.
+ // TODO(jcivelli): http://b/issue?id=5869635 we should save more entries.
+ // more TODO(jcivelli): Make this work
+ return ScopedJavaLocalRef<jobject>();
+ }
+ // TODO(yfriedman): Add a |release| to Pickle and save the copy.
+ memcpy(buffer, pickle.data(), pickle.size());
+ ScopedJavaLocalRef<jobject> jb(env, env->NewDirectByteBuffer(buffer,
+ pickle.size()));
+ if (base::android::ClearException(env) || jb.is_null())
+ free(buffer);
+ return jb;
+}
+
+ScopedJavaLocalRef<jstring>
+WebContentsState::GetDisplayTitleFromByteBuffer(JNIEnv* env,
+ void* data,
+ int size,
+ int saved_state_version) {
+ bool is_off_the_record;
+ int current_entry_index;
+ std::vector<sessions::SerializedNavigationEntry> navigations;
+ bool success = ExtractNavigationEntries(data,
+ size,
+ saved_state_version,
+ &is_off_the_record,
+ &current_entry_index,
+ &navigations);
+ if (!success)
+ return ScopedJavaLocalRef<jstring>();
+
+ sessions::SerializedNavigationEntry nav_entry =
+ navigations.at(current_entry_index);
+ return ConvertUTF16ToJavaString(env, nav_entry.title());
+}
+
+ScopedJavaLocalRef<jstring>
+WebContentsState::GetVirtualUrlFromByteBuffer(JNIEnv* env,
+ void* data,
+ int size,
+ int saved_state_version) {
+ bool is_off_the_record;
+ int current_entry_index;
+ std::vector<sessions::SerializedNavigationEntry> navigations;
+ bool success = ExtractNavigationEntries(data,
+ size,
+ saved_state_version,
+ &is_off_the_record,
+ &current_entry_index,
+ &navigations);
+ if (!success)
+ return ScopedJavaLocalRef<jstring>();
+
+ sessions::SerializedNavigationEntry nav_entry =
+ navigations.at(current_entry_index);
+ return ConvertUTF8ToJavaString(env, nav_entry.virtual_url().spec());
+}
+
+WebContents* WebContentsState::RestoreContentsFromByteBuffer(
+ void* data,
+ int size,
+ int saved_state_version,
+ bool initially_hidden) {
+ bool is_off_the_record;
+ int current_entry_index;
+ std::vector<sessions::SerializedNavigationEntry> navigations;
+ bool success = ExtractNavigationEntries(data,
+ size,
+ saved_state_version,
+ &is_off_the_record,
+ &current_entry_index,
+ &navigations);
+ if (!success)
+ return NULL;
+
+ Profile* profile = ProfileManager::GetActiveUserProfile();
+ ScopedVector<content::NavigationEntry> scoped_entries =
+ sessions::ContentSerializedNavigationBuilder::ToNavigationEntries(
+ navigations, profile);
+ std::vector<content::NavigationEntry*> entries;
+ scoped_entries.release(&entries);
+
+ if (is_off_the_record)
+ profile = profile->GetOffTheRecordProfile();
+ WebContents::CreateParams params(profile);
+ params.initially_hidden = initially_hidden;
+ scoped_ptr<WebContents> web_contents(WebContents::Create(params));
+ web_contents->GetController().Restore(
+ current_entry_index,
+ NavigationController::RESTORE_CURRENT_SESSION,
+ &entries);
+ return web_contents.release();
+}
+
+WebContents* WebContentsState::RestoreContentsFromByteBuffer(
+ JNIEnv* env,
+ jclass clazz,
+ jobject state,
+ jint saved_state_version,
+ jboolean initially_hidden) {
+ void* data = env->GetDirectBufferAddress(state);
+ int size = env->GetDirectBufferCapacity(state);
+
+ return WebContentsState::RestoreContentsFromByteBuffer(data,
+ size,
+ saved_state_version,
+ initially_hidden);
+}
+
+ScopedJavaLocalRef<jobject>
+ WebContentsState::CreateSingleNavigationStateAsByteBuffer(
+ JNIEnv* env,
+ jstring url,
+ jstring referrer_url,
+ jint referrer_policy,
+ jboolean is_off_the_record) {
+ content::Referrer referrer;
+ if (referrer_url) {
+ referrer = content::Referrer(
+ GURL(base::android::ConvertJavaStringToUTF8(env, referrer_url)),
+ static_cast<blink::WebReferrerPolicy>(referrer_policy));
+ }
+ scoped_ptr<content::NavigationEntry> entry(
+ content::NavigationController::CreateNavigationEntry(
+ GURL(base::android::ConvertJavaStringToUTF8(env, url)),
+ referrer,
+ ui::PAGE_TRANSITION_LINK,
+ true, // is_renderer_initiated
+ "", // extra_headers
+ ProfileManager::GetActiveUserProfile()));
+
+ std::vector<content::NavigationEntry*> navigations(1);
+ navigations[0] = entry.get();
+
+ return WebContentsState::WriteNavigationsAsByteBuffer(env,
+ is_off_the_record,
+ navigations,
+ 0);
+}
+
+// Static JNI methods.
+
+static void FreeWebContentsStateBuffer(JNIEnv* env, jclass clazz, jobject obj) {
+ void* data = env->GetDirectBufferAddress(obj);
+ free(data);
+}
+
+static jlong RestoreContentsFromByteBuffer(JNIEnv* env,
+ jclass clazz,
+ jobject state,
+ jint saved_state_version,
+ jboolean initially_hidden) {
+ return reinterpret_cast<intptr_t>(
+ WebContentsState::RestoreContentsFromByteBuffer(env,
+ clazz,
+ state,
+ saved_state_version,
+ initially_hidden));
+}
+
+static jobject GetContentsStateAsByteBuffer(
+ JNIEnv* env, jclass clazz, jobject jtab) {
+ TabAndroid* tab_android = TabAndroid::GetNativeTab(env, jtab);
+ return WebContentsState::GetContentsStateAsByteBuffer(
+ env, tab_android).Release();
+}
+
+static jobject CreateSingleNavigationStateAsByteBuffer(
+ JNIEnv* env,
+ jclass clazz,
+ jstring url,
+ jstring referrer_url,
+ jint referrer_policy,
+ jboolean is_off_the_record) {
+ return WebContentsState::CreateSingleNavigationStateAsByteBuffer(
+ env, url, referrer_url, referrer_policy, is_off_the_record).Release();
+}
+
+static jstring GetDisplayTitleFromByteBuffer(JNIEnv* env,
+ jclass clazz,
+ jobject state,
+ jint saved_state_version) {
+ void* data = env->GetDirectBufferAddress(state);
+ int size = env->GetDirectBufferCapacity(state);
+
+ ScopedJavaLocalRef<jstring> result =
+ WebContentsState::GetDisplayTitleFromByteBuffer(
+ env, data, size, saved_state_version);
+ return result.Release();
+}
+
+static jstring GetVirtualUrlFromByteBuffer(JNIEnv* env,
+ jclass clazz,
+ jobject state,
+ jint saved_state_version) {
+ void* data = env->GetDirectBufferAddress(state);
+ int size = env->GetDirectBufferCapacity(state);
+ ScopedJavaLocalRef<jstring> result =
+ WebContentsState::GetVirtualUrlFromByteBuffer(
+ env, data, size, saved_state_version);
+ return result.Release();
+}
+
+bool RegisterTabState(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
diff --git a/chrome/browser/android/tab_state.h b/chrome/browser/android/tab_state.h
new file mode 100644
index 0000000..4e33315
--- /dev/null
+++ b/chrome/browser/android/tab_state.h
@@ -0,0 +1,73 @@
+// Copyright 2014 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 CHROME_BROWSER_ANDROID_TAB_STATE_H_
+#define CHROME_BROWSER_ANDROID_TAB_STATE_H_
+
+#include <jni.h>
+#include <vector>
+
+#include "base/android/scoped_java_ref.h"
+
+namespace content {
+class NavigationEntry;
+class WebContents;
+}
+
+class TabAndroid;
+
+// Stores state for a WebContents, including its navigation history.
+class WebContentsState {
+ public:
+ static base::android::ScopedJavaLocalRef<jobject>
+ GetContentsStateAsByteBuffer(JNIEnv* env, TabAndroid* tab);
+
+ // Common implementation for GetContentsStateAsByteBuffer() and
+ // CreateContentsStateAsByteBuffer(). Does not assume ownership of the
+ // navigations.
+ static base::android::ScopedJavaLocalRef<jobject>
+ WriteNavigationsAsByteBuffer(
+ JNIEnv* env,
+ bool is_off_the_record,
+ const std::vector<content::NavigationEntry*>& navigations,
+ int current_entry);
+
+ // Extracts display title from serialized tab data on restore
+ static base::android::ScopedJavaLocalRef<jstring>
+ GetDisplayTitleFromByteBuffer(JNIEnv* env, void* data,
+ int size, int saved_state_version);
+
+ // Extracts virtual url from serialized tab data on restore
+ static base::android::ScopedJavaLocalRef<jstring>
+ GetVirtualUrlFromByteBuffer(JNIEnv* env, void* data,
+ int size, int saved_state_version);
+
+ // Restores a WebContents from the passed in state.
+ static content::WebContents* RestoreContentsFromByteBuffer(
+ void* data,
+ int size,
+ int saved_state_version,
+ bool initially_hidden);
+
+ // Restores a WebContents from the passed in state.
+ static content::WebContents* RestoreContentsFromByteBuffer(
+ JNIEnv* env,
+ jclass clazz,
+ jobject state,
+ jint saved_state_version,
+ jboolean initially_hidden);
+
+ // Synthesizes a stub, single-navigation state for a tab that will be loaded
+ // lazily.
+ static base::android::ScopedJavaLocalRef<jobject>
+ CreateSingleNavigationStateAsByteBuffer(JNIEnv* env, jstring url,
+ jstring referrer_url,
+ jint referrer_policy,
+ jboolean is_off_the_record);
+};
+
+// Registers methods for JNI.
+bool RegisterTabState(JNIEnv* env);
+
+#endif // CHROME_BROWSER_ANDROID_TAB_STATE_H_
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index f4424ca..4e52eef 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -107,6 +107,8 @@
'browser/android/tab_android.cc',
'browser/android/tab_android.h',
'browser/android/tab_load_status.h',
+ 'browser/android/tab_state.cc',
+ 'browser/android/tab_state.h',
'browser/android/thumbnail/thumbnail.cc',
'browser/android/thumbnail/thumbnail.h',
'browser/android/thumbnail/thumbnail_store.cc',
@@ -2781,6 +2783,7 @@
'android/java/src/org/chromium/chrome/browser/sync/ProfileSyncService.java',
'android/java/src/org/chromium/chrome/browser/tabmodel/TabModelJniBridge.java',
'android/java/src/org/chromium/chrome/browser/Tab.java',
+ 'android/java/src/org/chromium/chrome/browser/TabState.java',
'android/java/src/org/chromium/chrome/browser/TtsPlatformImpl.java',
'android/java/src/org/chromium/chrome/browser/UmaBridge.java',
'android/java/src/org/chromium/chrome/browser/UmaUtils.java',