diff options
author | dfalcantara <dfalcantara@chromium.org> | 2014-11-05 10:59:43 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-11-05 19:00:06 +0000 |
commit | ea1e8afa9e0b32e48aac4bfe1df0a6ebedbff2b9 (patch) | |
tree | 87a1da5984933e13167bb8bd7685ee1784e1e5a1 | |
parent | cec083d3e7038a5b26e5fdc2e514d404890805cb (diff) | |
download | chromium_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.java | 345 | ||||
-rw-r--r-- | chrome/android/javatests/src/org/chromium/chrome/browser/TabStateTest.java | 169 | ||||
-rw-r--r-- | chrome/browser/android/chrome_jni_registrar.cc | 2 | ||||
-rw-r--r-- | chrome/browser/android/tab_state.cc | 556 | ||||
-rw-r--r-- | chrome/browser/android/tab_state.h | 73 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 3 |
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(×tamp_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, + ¤t_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, + ¤t_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, + ¤t_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', |