| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/tab/web_contents_state.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <limits> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/android/jni_android.h" |
| #include "base/android/jni_bytebuffer.h" |
| #include "base/android/jni_string.h" |
| #include "base/compiler_specific.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/pickle.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "components/sessions/content/content_serialized_navigation_builder.h" |
| #include "components/sessions/core/serialized_navigation_entry.h" |
| #include "components/sessions/core/session_command.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/navigation_controller.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/restore_type.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/referrer.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| |
| // Must come after all headers that specialize FromJniType() / ToJniType(). |
| #include "chrome/browser/tab/jni_headers/WebContentsState_jni.h" |
| |
| using base::android::ConvertUTF16ToJavaString; |
| using base::android::ConvertUTF8ToJavaString; |
| using base::android::JavaParamRef; |
| using base::android::MethodID; |
| using base::android::ScopedJavaLocalRef; |
| using content::BrowserContext; |
| using content::NavigationController; |
| using content::WebContents; |
| |
| namespace { |
| |
| ScopedJavaLocalRef<jobject> CreateByteBufferDirect(JNIEnv* env, int size) { |
| ScopedJavaLocalRef<jclass> clazz = |
| base::android::GetClass(env, "java/nio/ByteBuffer"); |
| jmethodID method = MethodID::Get<MethodID::TYPE_STATIC>( |
| env, clazz.obj(), "allocateDirect", "(I)Ljava/nio/ByteBuffer;"); |
| jobject ret = env->CallStaticObjectMethod(clazz.obj(), method, size); |
| if (base::android::ClearException(env)) { |
| return {}; |
| } |
| return base::android::ScopedJavaLocalRef<jobject>::Adopt(env, ret); |
| } |
| |
| void WriteStateHeaderToPickle(bool off_the_record, |
| int entry_count, |
| int current_entry_index, |
| base::Pickle* pickle) { |
| pickle->WriteBool(off_the_record); |
| pickle->WriteInt(entry_count); |
| pickle->WriteInt(current_entry_index); |
| } |
| |
| base::Pickle WriteSerializedNavigationsAsPickle( |
| bool is_off_the_record, |
| const std::vector<sessions::SerializedNavigationEntry>& navigations, |
| int current_entry) { |
| base::Pickle pickle; |
| WriteStateHeaderToPickle(is_off_the_record, navigations.size(), current_entry, |
| &pickle); |
| |
| // Write out all of the NavigationEntrys. |
| for (const auto& navigation : navigations) { |
| // Write each SerializedNavigationEntry as a separate pickle to avoid |
| // optional reads of one tab bleeding into the next tab's data. |
| base::Pickle tab_navigation_pickle; |
| // Max size taken from |
| // CommandStorageManager::CreateUpdateTabNavigationCommand. |
| static const size_t max_state_size = |
| std::numeric_limits<sessions::SessionCommand::size_type>::max() - 1024; |
| navigation.WriteToPickle(max_state_size, &tab_navigation_pickle); |
| pickle.WriteInt(tab_navigation_pickle.size()); |
| pickle.WriteBytes(tab_navigation_pickle.data(), |
| tab_navigation_pickle.size()); |
| } |
| return pickle; |
| } |
| |
| ScopedJavaLocalRef<jobject> WriteSerializedNavigationsAsByteBuffer( |
| JNIEnv* env, |
| bool is_off_the_record, |
| const std::vector<sessions::SerializedNavigationEntry>& navigations, |
| int current_entry) { |
| base::Pickle pickle = WriteSerializedNavigationsAsPickle( |
| is_off_the_record, navigations, current_entry); |
| ScopedJavaLocalRef<jobject> buffer = |
| CreateByteBufferDirect(env, static_cast<int>(pickle.size())); |
| if (buffer) { |
| UNSAFE_TODO(memcpy(env->GetDirectBufferAddress(buffer.obj()), pickle.data(), |
| pickle.size())); |
| } |
| return buffer; |
| } |
| |
| std::vector<sessions::SerializedNavigationEntry> SerializeNavigations( |
| const std::vector<content::NavigationEntry*>& navigations) { |
| std::vector<sessions::SerializedNavigationEntry> serialized; |
| serialized.reserve(navigations.size()); |
| for (size_t i = 0; i < navigations.size(); ++i) { |
| serialized.push_back( |
| sessions::ContentSerializedNavigationBuilder::FromNavigationEntry( |
| i, navigations[i])); |
| } |
| return serialized; |
| } |
| |
| // Common implementation for GetContentsStateAsByteBuffer() and |
| // CreateContentsStateAsByteBuffer(). Does not assume ownership of the |
| // navigations. |
| ScopedJavaLocalRef<jobject> WriteNavigationsAsByteBuffer( |
| JNIEnv* env, |
| bool is_off_the_record, |
| const std::vector<content::NavigationEntry*>& navigations, |
| int current_entry) { |
| std::vector<sessions::SerializedNavigationEntry> serialized = |
| SerializeNavigations(navigations); |
| return WriteSerializedNavigationsAsByteBuffer(env, is_off_the_record, |
| serialized, current_entry); |
| } |
| |
| std::unique_ptr<content::NavigationEntry> CreatePendingNavigationEntry( |
| BrowserContext* browser_context, |
| const std::optional<std::u16string>& title, |
| const std::string& url, |
| const std::optional<std::string>& referrer_url, |
| int referrer_policy, |
| const std::optional<url::Origin>& optional_initiator_origin) { |
| content::Referrer referrer; |
| if (referrer_url.has_value()) { |
| referrer = |
| content::Referrer(GURL(*referrer_url), |
| content::Referrer::ConvertToPolicy(referrer_policy)); |
| } |
| |
| url::Origin initiator_origin = |
| optional_initiator_origin.value_or(url::Origin()); |
| // TODO(crbug.com/40062134): Deal with getting initiator_base_url |
| // plumbed here too. |
| auto navigation_entry = content::NavigationController::CreateNavigationEntry( |
| GURL(url), referrer, initiator_origin, |
| /* initiator_base_url= */ std::nullopt, ui::PAGE_TRANSITION_LINK, |
| /* is_renderer_initiated= */ true, |
| /* extra_headers= */ "", browser_context, |
| /* blob_url_loader_factory= */ nullptr); |
| if (title.has_value()) { |
| navigation_entry->SetTitle(*title); |
| } |
| return navigation_entry; |
| } |
| |
| } // namespace |
| |
| WebContentsStateByteBuffer::WebContentsStateByteBuffer( |
| base::android::ScopedJavaLocalRef<jobject> web_contents_byte_buffer_result, |
| int saved_state_version) |
| : state_version(saved_state_version) { |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| java_buffer.Reset(web_contents_byte_buffer_result); |
| backing_buffer = base::android::JavaByteBufferToSpan(env, java_buffer); |
| } |
| |
| WebContentsStateByteBuffer::WebContentsStateByteBuffer( |
| base::raw_span<const uint8_t> raw_data, |
| int saved_state_version) |
| : backing_buffer(raw_data), state_version(saved_state_version) {} |
| |
| WebContentsStateByteBuffer::~WebContentsStateByteBuffer() = default; |
| |
| WebContentsStateByteBuffer& WebContentsStateByteBuffer::operator=( |
| WebContentsStateByteBuffer&& other) noexcept = default; |
| WebContentsStateByteBuffer::WebContentsStateByteBuffer( |
| WebContentsStateByteBuffer&& other) noexcept = default; |
| |
| WebContentsStateUnpacked::WebContentsStateUnpacked( |
| bool is_off_the_record, |
| int current_entry_index, |
| std::vector<sessions::SerializedNavigationEntry> navigations) |
| : is_off_the_record_(is_off_the_record), |
| current_entry_index_(current_entry_index), |
| navigations_(std::move(navigations)) {} |
| WebContentsStateUnpacked::~WebContentsStateUnpacked() = default; |
| |
| ScopedJavaLocalRef<jobject> WebContentsStateUnpacked::Pack(JNIEnv* env) const { |
| return WriteSerializedNavigationsAsByteBuffer( |
| env, is_off_the_record_, navigations_, current_entry_index_); |
| } |
| |
| ScopedJavaLocalRef<jobject> WebContentsState::GetContentsStateAsByteBuffer( |
| JNIEnv* env, |
| content::WebContents* web_contents) { |
| if (!web_contents) { |
| return ScopedJavaLocalRef<jobject>(); |
| } |
| |
| content::NavigationController& controller = web_contents->GetController(); |
| const int entry_count = controller.GetEntryCount(); |
| // Don't try to persist initial NavigationEntry, as it is not actually |
| // associated with any navigation and will just result in about:blank on |
| // session restore. |
| if (controller.GetLastCommittedEntry()->IsInitialEntry()) { |
| return ScopedJavaLocalRef<jobject>(); |
| } |
| |
| std::vector<content::NavigationEntry*> navigations(entry_count); |
| for (int i = 0; i < entry_count; ++i) { |
| navigations[i] = controller.GetEntryAtIndex(i); |
| } |
| |
| return WriteNavigationsAsByteBuffer( |
| env, web_contents->GetBrowserContext()->IsOffTheRecord(), navigations, |
| controller.GetLastCommittedEntryIndex()); |
| } |
| |
| std::unique_ptr<WebContentsStateUnpacked> WebContentsState::Unpack( |
| base::span<const uint8_t> buffer, |
| int saved_state_version) { |
| bool is_off_the_record; |
| int current_entry_index; |
| std::vector<sessions::SerializedNavigationEntry> navigations; |
| bool success = WebContentsState::ExtractNavigationEntries( |
| buffer, saved_state_version, &is_off_the_record, ¤t_entry_index, |
| &navigations); |
| if (!success) { |
| return nullptr; |
| } |
| |
| return std::make_unique<WebContentsStateUnpacked>( |
| is_off_the_record, current_entry_index, std::move(navigations)); |
| } |
| |
| ScopedJavaLocalRef<jobject> |
| WebContentsState::DeleteNavigationEntriesFromByteBuffer( |
| JNIEnv* env, |
| base::span<const uint8_t> buffer, |
| int saved_state_version, |
| const DeletionPredicate& predicate) { |
| bool is_off_the_record; |
| int current_entry_index; |
| std::vector<sessions::SerializedNavigationEntry> navigations; |
| bool success = WebContentsState::ExtractNavigationEntries( |
| buffer, saved_state_version, &is_off_the_record, ¤t_entry_index, |
| &navigations); |
| if (!success) { |
| return ScopedJavaLocalRef<jobject>(); |
| } |
| |
| size_t original_size = navigations.size(); |
| int deleted_navigations = 0; |
| size_t write_index = 0; |
| for (size_t read_index = 0; read_index < original_size; ++read_index) { |
| sessions::SerializedNavigationEntry& navigation = navigations[read_index]; |
| if (current_entry_index != navigation.index() && |
| predicate.Run(navigations[read_index])) { |
| deleted_navigations++; |
| } else { |
| // Adjust indices according to number of deleted navigations. |
| if (current_entry_index == navigation.index()) { |
| current_entry_index -= deleted_navigations; |
| } |
| navigation.set_index(navigation.index() - |
| deleted_navigations); |
| if (write_index != read_index) { |
| navigations[write_index] = std::move(navigation); |
| } |
| write_index++; |
| } |
| } |
| |
| if (write_index == original_size) { |
| return ScopedJavaLocalRef<jobject>(); |
| } |
| |
| navigations.resize(write_index); |
| |
| return WriteSerializedNavigationsAsByteBuffer( |
| env, is_off_the_record, navigations, current_entry_index); |
| } |
| |
| std::optional<std::u16string> WebContentsState::GetDisplayTitleFromByteBuffer( |
| JNIEnv* env, |
| base::span<const uint8_t> buffer, |
| int saved_state_version) { |
| bool is_off_the_record; |
| int current_entry_index; |
| std::vector<sessions::SerializedNavigationEntry> navigations; |
| bool success = WebContentsState::ExtractNavigationEntries( |
| buffer, saved_state_version, &is_off_the_record, ¤t_entry_index, |
| &navigations); |
| if (!success) { |
| return std::nullopt; |
| } |
| |
| sessions::SerializedNavigationEntry nav_entry = |
| navigations.at(current_entry_index); |
| return nav_entry.title(); |
| } |
| |
| std::optional<std::string> WebContentsState::GetVirtualUrlFromByteBuffer( |
| JNIEnv* env, |
| base::span<const uint8_t> buffer, |
| int saved_state_version) { |
| bool is_off_the_record; |
| int current_entry_index; |
| std::vector<sessions::SerializedNavigationEntry> navigations; |
| bool success = WebContentsState::ExtractNavigationEntries( |
| buffer, saved_state_version, &is_off_the_record, ¤t_entry_index, |
| &navigations); |
| if (!success) { |
| return std::nullopt; |
| } |
| |
| sessions::SerializedNavigationEntry nav_entry = |
| navigations.at(current_entry_index); |
| return nav_entry.virtual_url().spec(); |
| } |
| |
| ScopedJavaLocalRef<jobject> WebContentsState::RestoreContentsFromByteBuffer( |
| JNIEnv* env, |
| const base::android::JavaRef<jobject>& state, |
| BrowserContext* browser_context, |
| int saved_state_version, |
| jboolean initially_hidden, |
| jboolean no_renderer) { |
| base::span<const uint8_t> span = |
| base::android::JavaByteBufferToSpan(env, state); |
| |
| WebContents* web_contents = |
| WebContentsState::RestoreContentsFromByteBufferImpl( |
| browser_context, span, saved_state_version, initially_hidden, |
| no_renderer) |
| .release(); |
| |
| if (web_contents) { |
| return web_contents->GetJavaWebContents(); |
| } else { |
| return ScopedJavaLocalRef<jobject>(); |
| } |
| } |
| |
| std::unique_ptr<WebContents> WebContentsState::RestoreContentsFromByteBuffer( |
| BrowserContext* browser_context, |
| const WebContentsStateByteBuffer* byte_buffer, |
| bool initially_hidden, |
| bool no_renderer) { |
| return WebContentsState::RestoreContentsFromByteBufferImpl( |
| browser_context, byte_buffer->backing_buffer, byte_buffer->state_version, |
| initially_hidden, no_renderer); |
| } |
| |
| std::unique_ptr<WebContents> |
| WebContentsState::RestoreContentsFromByteBufferImpl( |
| BrowserContext* browser_context, |
| base::span<const uint8_t> buffer, |
| int saved_state_version, |
| bool initially_hidden, |
| bool no_renderer) { |
| bool is_off_the_record = browser_context->IsOffTheRecord(); |
| int current_entry_index; |
| std::vector<sessions::SerializedNavigationEntry> navigations; |
| bool success = WebContentsState::ExtractNavigationEntries( |
| buffer, saved_state_version, &is_off_the_record, ¤t_entry_index, |
| &navigations); |
| if (!success) { |
| return nullptr; |
| } |
| |
| std::vector<std::unique_ptr<content::NavigationEntry>> entries = |
| sessions::ContentSerializedNavigationBuilder::ToNavigationEntries( |
| navigations, browser_context); |
| |
| WebContents::CreateParams params(browser_context); |
| |
| params.initially_hidden = initially_hidden; |
| if (no_renderer) { |
| params.desired_renderer_state = |
| WebContents::CreateParams::kNoRendererProcess; |
| } |
| std::unique_ptr<WebContents> web_contents(WebContents::Create(params)); |
| web_contents->GetController().Restore( |
| current_entry_index, content::RestoreType::kRestored, &entries); |
| return web_contents; |
| } |
| |
| bool WebContentsState::ExtractNavigationEntries( |
| base::span<const uint8_t> buffer, |
| int saved_state_version, |
| bool* is_off_the_record, |
| int* current_entry_index, |
| std::vector<sessions::SerializedNavigationEntry>* navigations) { |
| int entry_count; |
| base::Pickle pickle = base::Pickle::WithUnownedBuffer(buffer); |
| base::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=" |
| << buffer.size() << ")."; |
| return false; |
| } |
| |
| // Support for versions 0 and 1 is removed in M142/M143. Metrics suggests |
| // in-the-wild usage is virtually non-existent (see crbug.com/41493935). |
| if (saved_state_version < 2) { |
| LOG(ERROR) << "Unsupported saved_state_version: " << saved_state_version; |
| return false; |
| } |
| |
| // `saved_state_version` == 2 and greater. |
| navigations->reserve(entry_count); |
| 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. |
| std::optional<base::span<const uint8_t>> tab_entry = iter.ReadData(); |
| if (!tab_entry.has_value()) { |
| LOG(ERROR) << "Failed to restore tab entry from byte array."; |
| return false; // It's dangerous to keep deserializing now, give up. |
| } |
| base::Pickle tab_navigation_pickle = |
| base::Pickle::WithUnownedBuffer(*tab_entry); |
| base::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; |
| } |
| |
| ScopedJavaLocalRef<jobject> |
| WebContentsState::CreateSingleNavigationStateAsByteBuffer( |
| JNIEnv* env, |
| BrowserContext* browser_context, |
| const std::optional<std::u16string>& title, |
| const std::string& url, |
| const std::optional<std::string>& referrer_url, |
| int referrer_policy, |
| const std::optional<url::Origin>& initiator_origin) { |
| bool is_off_the_record = browser_context->IsOffTheRecord(); |
| std::unique_ptr<content::NavigationEntry> entry = |
| CreatePendingNavigationEntry(browser_context, title, url, referrer_url, |
| referrer_policy, initiator_origin); |
| |
| std::vector<content::NavigationEntry*> navigations(1); |
| navigations[0] = entry.get(); |
| |
| return WriteNavigationsAsByteBuffer(env, is_off_the_record, navigations, 0); |
| } |
| |
| base::Pickle WebContentsState::CreateSingleNavigationStateAsPickle( |
| BrowserContext* browser_context, |
| std::u16string title, |
| const GURL& url, |
| content::Referrer referrer, |
| url::Origin initiator_origin) { |
| base::Pickle pickle; |
| std::unique_ptr<content::NavigationEntry> navigation_entry = |
| content::NavigationController::CreateNavigationEntry( |
| url, referrer, initiator_origin, |
| /* initiator_base_url= */ std::nullopt, ui::PAGE_TRANSITION_LINK, |
| /* is_renderer_initiated= */ true, |
| /* extra_headers= */ "", browser_context, |
| /* blob_url_loader_factory= */ nullptr); |
| navigation_entry->SetTitle(std::move(title)); |
| |
| std::vector<sessions::SerializedNavigationEntry> serialized = |
| SerializeNavigations({navigation_entry.get()}); |
| |
| return WriteSerializedNavigationsAsPickle(browser_context->IsOffTheRecord(), |
| serialized, 0); |
| } |
| |
| ScopedJavaLocalRef<jobject> WebContentsState::AppendPendingNavigation( |
| JNIEnv* env, |
| BrowserContext* browser_context, |
| base::span<const uint8_t> buffer, |
| int saved_state_version, |
| const std::optional<std::u16string>& title, |
| const std::string& url, |
| const std::optional<std::string>& referrer_url, |
| int referrer_policy, |
| const std::optional<url::Origin>& initiator_origin) { |
| bool is_off_the_record; |
| bool is_context_off_the_record = browser_context->IsOffTheRecord(); |
| int current_entry_index; |
| std::vector<sessions::SerializedNavigationEntry> navigations; |
| bool success = WebContentsState::ExtractNavigationEntries( |
| buffer, saved_state_version, &is_off_the_record, ¤t_entry_index, |
| &navigations); |
| |
| bool safeToAppend = success && is_context_off_the_record == is_off_the_record; |
| base::UmaHistogramBoolean( |
| "Tabs.DeserializationResultForAppendPendingNavigation", safeToAppend); |
| if (!safeToAppend) { |
| LOG(WARNING) << "Failed to deserialize navigation entries, clobbering " |
| "previous navigation state."; |
| return CreateSingleNavigationStateAsByteBuffer( |
| env, browser_context, title, url, referrer_url, referrer_policy, |
| initiator_origin); |
| } |
| |
| int new_entry_index = current_entry_index + 1; |
| navigations.erase(std::next(navigations.begin(), new_entry_index), navigations.end()); |
| std::unique_ptr<content::NavigationEntry> new_entry = |
| CreatePendingNavigationEntry(browser_context, title, url, referrer_url, |
| referrer_policy, initiator_origin); |
| navigations.push_back( |
| sessions::ContentSerializedNavigationBuilder::FromNavigationEntry( |
| new_entry_index, new_entry.get())); |
| |
| return WriteSerializedNavigationsAsByteBuffer( |
| env, is_off_the_record, navigations, new_entry_index); |
| } |
| |
| // Static JNI methods. |
| |
| static ScopedJavaLocalRef<jobject> |
| JNI_WebContentsState_RestoreContentsFromByteBuffer( |
| JNIEnv* env, |
| Profile* profile, |
| const JavaParamRef<jobject>& state, |
| int saved_state_version, |
| jboolean initially_hidden, |
| jboolean no_renderer) { |
| return WebContentsState::RestoreContentsFromByteBuffer( |
| env, state, profile, saved_state_version, initially_hidden, no_renderer); |
| } |
| |
| static ScopedJavaLocalRef<jobject> |
| JNI_WebContentsState_GetContentsStateAsByteBuffer( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& jweb_contents) { |
| WebContents* web_contents = |
| content::WebContents::FromJavaWebContents(jweb_contents); |
| return WebContentsState::GetContentsStateAsByteBuffer(env, web_contents); |
| } |
| |
| static base::android::ScopedJavaLocalRef<jobject> |
| JNI_WebContentsState_DeleteNavigationEntries( |
| JNIEnv* env, |
| const base::android::JavaParamRef<jobject>& state, |
| int saved_state_version, |
| jlong predicate_ptr) { |
| base::span<const uint8_t> span = |
| base::android::JavaByteBufferToSpan(env, state); |
| |
| const auto* predicate = |
| reinterpret_cast<WebContentsState::DeletionPredicate*>(predicate_ptr); |
| |
| return WebContentsState::DeleteNavigationEntriesFromByteBuffer( |
| env, span, saved_state_version, *predicate); |
| } |
| |
| static ScopedJavaLocalRef<jobject> |
| JNI_WebContentsState_CreateSingleNavigationStateAsByteBuffer( |
| JNIEnv* env, |
| Profile* profile, |
| std::optional<std::u16string>& title, |
| std::string& url, |
| std::optional<std::string>& referrer_url, |
| int referrer_policy, |
| std::optional<url::Origin>& initiator_origin) { |
| return WebContentsState::CreateSingleNavigationStateAsByteBuffer( |
| env, profile, title, url, referrer_url, referrer_policy, |
| initiator_origin); |
| } |
| |
| static ScopedJavaLocalRef<jobject> JNI_WebContentsState_AppendPendingNavigation( |
| JNIEnv* env, |
| Profile* profile, |
| const JavaParamRef<jobject>& state, |
| int saved_state_version, |
| std::optional<std::u16string>& title, |
| std::string& url, |
| std::optional<std::string>& referrer_url, |
| int referrer_policy, |
| std::optional<url::Origin>& initiator_origin) { |
| base::span<const uint8_t> span = |
| base::android::JavaByteBufferToSpan(env, state); |
| |
| return WebContentsState::AppendPendingNavigation( |
| env, profile, span, saved_state_version, title, url, referrer_url, |
| referrer_policy, initiator_origin); |
| } |
| |
| static std::optional<std::u16string> |
| JNI_WebContentsState_GetDisplayTitleFromByteBuffer( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& state, |
| int saved_state_version) { |
| base::span<const uint8_t> span = |
| base::android::JavaByteBufferToSpan(env, state); |
| |
| return WebContentsState::GetDisplayTitleFromByteBuffer(env, span, |
| saved_state_version); |
| } |
| |
| static std::optional<std::string> |
| JNI_WebContentsState_GetVirtualUrlFromByteBuffer( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& state, |
| int saved_state_version) { |
| base::span<const uint8_t> span = |
| base::android::JavaByteBufferToSpan(env, state); |
| |
| return WebContentsState::GetVirtualUrlFromByteBuffer(env, span, |
| saved_state_version); |
| } |
| |
| static void JNI_WebContentsState_FreeStringPointer(JNIEnv* env, |
| jlong string_pointer) { |
| delete reinterpret_cast<std::string*>(string_pointer); |
| } |