// Copyright 2012 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/android/tab_android.h"
#include <stddef.h>
#include <string>
#include <utility>
#include <vector>
#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/user_metrics.h"
#include "base/trace_event/trace_event.h"
#include "cc/slim/layer.h"
#include "chrome/browser/android/background_tab_manager.h"
#include "chrome/browser/android/compositor/tab_content_manager.h"
#include "chrome/browser/android/tab_web_contents_delegate_android.h"
#include "chrome/browser/browser_about_handler.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/notifications/notification_permission_context.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/resource_coordinator/tab_helper.h"
#include "chrome/browser/resource_coordinator/tab_load_tracker.h"
#include "chrome/browser/sync/glue/synced_tab_delegate_android.h"
#include "chrome/browser/tab_contents/tab_util.h"
#include "chrome/browser/ui/android/context_menu_helper.h"
#include "chrome/browser/ui/android/tab_model/tab_model.h"
#include "chrome/browser/ui/android/tab_model/tab_model_list.h"
#include "chrome/browser/ui/autofill/chrome_autofill_client.h"
#include "chrome/browser/ui/browser_navigator_params.h"
#include "chrome/browser/ui/startup/bad_flags_prompt.h"
#include "chrome/browser/ui/tab_helpers.h"
#include "components/android_autofill/browser/android_autofill_client.h"
#include "components/android_autofill/browser/android_autofill_manager.h"
#include "components/android_autofill/browser/android_autofill_provider.h"
#include "components/android_autofill/browser/autofill_provider.h"
#include "components/autofill/content/browser/content_autofill_driver.h"
#include "components/infobars/content/content_infobar_manager.h"
#include "components/no_state_prefetch/browser/no_state_prefetch_manager.h"
#include "components/sessions/content/session_tab_helper.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/devtools_agent_host.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_user_data.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "ui/android/view_android.h"
#include "url/android/gurl_android.h"
#include "url/gurl.h"
// Must come after all headers that specialize FromJniType() / ToJniType().
#include "chrome/android/chrome_jni_headers/TabImpl_jni.h"
using base::android::AttachCurrentThread;
using base::android::ConvertUTF8ToJavaString;
using base::android::JavaParamRef;
using base::android::JavaRef;
using chrome::android::BackgroundTabManager;
using content::GlobalRequestID;
using content::NavigationController;
using content::WebContents;
namespace {
class TabAndroidHelper : public content::WebContentsUserData<TabAndroidHelper> {
public:
static void SetTabForWebContents(WebContents* contents,
TabAndroid* tab_android) {
content::WebContentsUserData<TabAndroidHelper>::CreateForWebContents(
contents);
content::WebContentsUserData<TabAndroidHelper>::FromWebContents(contents)
->tab_android_ = tab_android;
}
static TabAndroid* FromWebContents(const WebContents* contents) {
TabAndroidHelper* helper =
static_cast<TabAndroidHelper*>(contents->GetUserData(UserDataKey()));
return helper ? helper->tab_android_.get() : nullptr;
}
explicit TabAndroidHelper(content::WebContents* web_contents)
: content::WebContentsUserData<TabAndroidHelper>(*web_contents) {}
private:
friend class content::WebContentsUserData<TabAndroidHelper>;
raw_ptr<TabAndroid> tab_android_;
WEB_CONTENTS_USER_DATA_KEY_DECL();
};
WEB_CONTENTS_USER_DATA_KEY_IMPL(TabAndroidHelper);
} // namespace
TabAndroid* TabAndroid::FromWebContents(
const content::WebContents* web_contents) {
return TabAndroidHelper::FromWebContents(web_contents);
}
TabAndroid* TabAndroid::GetNativeTab(JNIEnv* env, const JavaRef<jobject>& obj) {
return reinterpret_cast<TabAndroid*>(Java_TabImpl_getNativePtr(env, obj));
}
std::vector<raw_ptr<TabAndroid, VectorExperimental>>
TabAndroid::GetAllNativeTabs(
JNIEnv* env,
const ScopedJavaLocalRef<jobjectArray>& obj_array) {
std::vector<raw_ptr<TabAndroid, VectorExperimental>> tab_native_ptrs;
ScopedJavaLocalRef<jlongArray> j_tabs_ptr =
Java_TabImpl_getAllNativePtrs(env, obj_array);
if (j_tabs_ptr.is_null())
return tab_native_ptrs;
std::vector<jlong> tab_ptr;
base::android::JavaLongArrayToLongVector(env, j_tabs_ptr, &tab_ptr);
for (size_t i = 0; i < tab_ptr.size(); ++i) {
tab_native_ptrs.push_back(reinterpret_cast<TabAndroid*>(tab_ptr[i]));
}
return tab_native_ptrs;
}
void TabAndroid::AttachTabHelpers(content::WebContents* web_contents) {
DCHECK(web_contents);
TabHelpers::AttachTabHelpers(web_contents);
}
TabAndroid::TabAndroid(JNIEnv* env,
const JavaRef<jobject>& obj,
Profile* profile,
int tab_id)
: weak_java_tab_(env, obj),
tab_id_(tab_id),
session_window_id_(SessionID::InvalidValue()),
content_layer_(cc::slim::Layer::Create()),
synced_tab_delegate_(new browser_sync::SyncedTabDelegateAndroid(this)),
profile_(profile->GetWeakPtr()) {
Java_TabImpl_setNativePtr(env, obj, reinterpret_cast<intptr_t>(this));
}
TabAndroid::~TabAndroid() {
GetContentLayer()->RemoveAllChildren();
JNIEnv* env = base::android::AttachCurrentThread();
Java_TabImpl_clearNativePtr(env, weak_java_tab_.get(env));
}
SessionID TabAndroid::GetWindowId() const {
return session_window_id_;
}
int TabAndroid::GetAndroidId() const {
return tab_id_;
}
std::unique_ptr<WebContentsStateByteBuffer>
TabAndroid::GetWebContentsByteBuffer() {
JNIEnv* env = base::android::AttachCurrentThread();
ScopedJavaLocalRef<jobject> state =
Java_TabImpl_getWebContentsStateByteBuffer(env, weak_java_tab_.get(env));
int version = Java_TabImpl_getWebContentsStateSavedStateVersion(
env, weak_java_tab_.get(env));
// If the web contents is null (denoted by saved_state_version being -1),
// return a nullptr.
if (version == -1) {
return nullptr;
}
return std::make_unique<WebContentsStateByteBuffer>(state, version);
}
base::android::ScopedJavaLocalRef<jobject> TabAndroid::GetJavaObject() {
JNIEnv* env = base::android::AttachCurrentThread();
return weak_java_tab_.get(env);
}
scoped_refptr<cc::slim::Layer> TabAndroid::GetContentLayer() const {
return content_layer_;
}
base::WeakPtr<TabAndroid> TabAndroid::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
int TabAndroid::GetLaunchType() const {
JNIEnv* env = base::android::AttachCurrentThread();
return Java_TabImpl_getLaunchType(env, weak_java_tab_.get(env));
}
int TabAndroid::GetUserAgent() const {
JNIEnv* env = base::android::AttachCurrentThread();
return Java_TabImpl_getUserAgent(env, weak_java_tab_.get(env));
}
bool TabAndroid::IsNativePage() const {
JNIEnv* env = base::android::AttachCurrentThread();
return Java_TabImpl_isNativePage(env, weak_java_tab_.get(env));
}
std::u16string TabAndroid::GetTitle() const {
JNIEnv* env = base::android::AttachCurrentThread();
ScopedJavaLocalRef<jstring> java_title =
Java_TabImpl_getTitle(env, weak_java_tab_.get(env));
return java_title ? base::android::ConvertJavaStringToUTF16(java_title)
: std::u16string();
}
GURL TabAndroid::GetURL() const {
JNIEnv* env = base::android::AttachCurrentThread();
return url::GURLAndroid::ToNativeGURL(
env, Java_TabImpl_getUrl(env, weak_java_tab_.get(env)));
}
bool TabAndroid::IsUserInteractable() const {
JNIEnv* env = base::android::AttachCurrentThread();
return Java_TabImpl_isUserInteractable(env, weak_java_tab_.get(env));
}
sync_sessions::SyncedTabDelegate* TabAndroid::GetSyncedTabDelegate() const {
return synced_tab_delegate_.get();
}
bool TabAndroid::IsIncognito() const {
JNIEnv* env = base::android::AttachCurrentThread();
const bool is_incognito =
Java_TabImpl_isIncognito(env, weak_java_tab_.get(env));
if (Profile* p = profile()) {
CHECK_EQ(is_incognito, p->IsOffTheRecord());
}
return is_incognito;
}
base::Time TabAndroid::GetLastShownTimestamp() const {
JNIEnv* env = base::android::AttachCurrentThread();
const int64_t timestamp =
Java_TabImpl_getLastShownTimestamp(env, weak_java_tab_.get(env));
return (timestamp == -1)
? base::Time()
: base::Time::FromMillisecondsSinceUnixEpoch(timestamp);
}
void TabAndroid::DeleteFrozenNavigationEntries(
const WebContentsState::DeletionPredicate& predicate) {
JNIEnv* env = base::android::AttachCurrentThread();
Java_TabImpl_deleteNavigationEntriesFromFrozenState(
env, weak_java_tab_.get(env), reinterpret_cast<intptr_t>(&predicate));
}
void TabAndroid::SetWindowSessionID(SessionID window_id) {
session_window_id_ = window_id;
if (!web_contents())
return;
sessions::SessionTabHelper* session_tab_helper =
sessions::SessionTabHelper::FromWebContents(web_contents());
session_tab_helper->SetWindowID(session_window_id_);
}
std::unique_ptr<content::WebContents> TabAndroid::SwapWebContents(
std::unique_ptr<content::WebContents> new_contents,
bool did_start_load,
bool did_finish_load) {
content::WebContents* old_contents = web_contents_.get();
JNIEnv* env = base::android::AttachCurrentThread();
Java_TabImpl_swapWebContents(env, weak_java_tab_.get(env),
new_contents->GetJavaWebContents(),
did_start_load, did_finish_load);
DCHECK_EQ(web_contents_, new_contents);
new_contents.release();
return base::WrapUnique(old_contents);
}
bool TabAndroid::IsCustomTab() {
JNIEnv* env = base::android::AttachCurrentThread();
return Java_TabImpl_isCustomTab(env, weak_java_tab_.get(env));
}
bool TabAndroid::IsHidden() {
JNIEnv* env = base::android::AttachCurrentThread();
return Java_TabImpl_isHidden(env, weak_java_tab_.get(env));
}
void TabAndroid::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void TabAndroid::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
void TabAndroid::Destroy(JNIEnv* env) {
delete this;
}
void TabAndroid::InitWebContents(
JNIEnv* env,
jboolean incognito,
jboolean is_background_tab,
const JavaParamRef<jobject>& jweb_contents,
const JavaParamRef<jobject>& jweb_contents_delegate,
const JavaParamRef<jobject>& jcontext_menu_populator_factory) {
web_contents_.reset(content::WebContents::FromJavaWebContents(jweb_contents));
DCHECK(web_contents_.get());
web_contents_->SetOwnerLocationForDebug(FROM_HERE);
TabAndroidHelper::SetTabForWebContents(web_contents(), this);
web_contents_delegate_ =
std::make_unique<android::TabWebContentsDelegateAndroid>(
env, jweb_contents_delegate);
web_contents()->SetDelegate(web_contents_delegate_.get());
AttachTabHelpers(web_contents_.get());
// When restoring a frame that was unloaded we re-create the TabAndroid and
// its host. This triggers visibility changes in both the Browser and
// Renderer. We need to start tracking the content-to-visible time now. On
// Android the tab controller does not send a visibility change until later
// on, at which point it is too late to attempt to track tab changes for
// unloaded frames.
web_contents_->SetTabSwitchStartTime(
base::TimeTicks::Now(),
resource_coordinator::ResourceCoordinatorTabHelper::IsLoaded(
web_contents_.get()));
SetWindowSessionID(session_window_id_);
ContextMenuHelper::FromWebContents(web_contents())
->SetPopulatorFactory(jcontext_menu_populator_factory);
synced_tab_delegate_->SetWebContents(web_contents());
// Verify that the WebContents this tab represents matches the expected
// off the record state.
CHECK_EQ(profile()->IsOffTheRecord(), incognito);
if (is_background_tab) {
BackgroundTabManager::CreateForWebContents(web_contents(), profile());
}
content_layer_->InsertChild(web_contents_->GetNativeView()->GetLayer(), 0);
// Shows a warning notification for dangerous flags in about:flags.
chrome::ShowBadFlagsPrompt(web_contents());
for (Observer& observer : observers_)
observer.OnInitWebContents(this);
}
void TabAndroid::InitializeAutofillIfNecessary(JNIEnv* env) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(autofill::AutofillProvider::FromWebContents(web_contents_.get()));
if (autofill::ContentAutofillClient::FromWebContents(web_contents_.get())) {
// We need to initialize the keyboard suppressor before creating any
// AutofillManagers and after the autofill client is available.
autofill::AutofillProvider::FromWebContents(web_contents_.get())
->MaybeInitKeyboardSuppressor();
return;
}
android_autofill::AndroidAutofillClient::CreateForWebContents(
web_contents_.get());
// We need to initialize the keyboard suppressor before creating any
// AutofillManagers and after the autofill client is available.
autofill::AutofillProvider::FromWebContents(web_contents_.get())
->MaybeInitKeyboardSuppressor();
autofill::ContentAutofillDriver::GetForRenderFrameHost(
web_contents_->GetPrimaryMainFrame());
}
void TabAndroid::UpdateDelegates(
JNIEnv* env,
const JavaParamRef<jobject>& jweb_contents_delegate,
const JavaParamRef<jobject>& jcontext_menu_populator_factory) {
ContextMenuHelper::FromWebContents(web_contents())
->SetPopulatorFactory(jcontext_menu_populator_factory);
web_contents_delegate_ =
std::make_unique<android::TabWebContentsDelegateAndroid>(
env, jweb_contents_delegate);
web_contents()->SetDelegate(web_contents_delegate_.get());
}
namespace {
void WillRemoveWebContentsFromTab(content::WebContents* contents) {
DCHECK(contents);
if (contents->GetNativeView())
contents->GetNativeView()->GetLayer()->RemoveFromParent();
contents->SetDelegate(nullptr);
}
} // namespace
void TabAndroid::DestroyWebContents(JNIEnv* env) {
WillRemoveWebContentsFromTab(web_contents());
// Terminate the renderer process if this is the last tab.
// If there's no unload listener, FastShutdownIfPossible kills the
// renderer process. Otherwise, we go with the slow path where renderer
// process shuts down itself when ref count becomes 0.
// This helps the render process exit quickly which avoids some issues
// during shutdown. See https://codereview.chromium.org/146693011/
// and http://crbug.com/338709 for details.
content::RenderProcessHost* process =
web_contents()->GetPrimaryMainFrame()->GetProcess();
if (process)
process->FastShutdownIfPossible(1, false);
web_contents_.reset();
synced_tab_delegate_->ResetWebContents();
}
void TabAndroid::ReleaseWebContents(JNIEnv* env) {
WillRemoveWebContentsFromTab(web_contents());
// Ownership of |released_contents| is assumed by the code that initiated the
// release.
content::WebContents* released_contents = web_contents_.release();
if (released_contents) {
released_contents->SetOwnerLocationForDebug(std::nullopt);
}
// Remove the link from the native WebContents to |this|, since the
// lifetimes of the two objects are no longer intertwined.
TabAndroidHelper::SetTabForWebContents(released_contents, nullptr);
synced_tab_delegate_->ResetWebContents();
}
void TabAndroid::OnPhysicalBackingSizeChanged(
JNIEnv* env,
const JavaParamRef<jobject>& jweb_contents,
jint width,
jint height) {
content::WebContents* web_contents =
content::WebContents::FromJavaWebContents(jweb_contents);
gfx::Size size(width, height);
web_contents->GetNativeView()->OnPhysicalBackingSizeChanged(size);
}
void TabAndroid::SetActiveNavigationEntryTitleForUrl(
JNIEnv* env,
const JavaParamRef<jstring>& jurl,
const JavaParamRef<jstring>& jtitle) {
DCHECK(web_contents());
std::u16string title;
if (jtitle)
title = base::android::ConvertJavaStringToUTF16(env, jtitle);
std::string url;
if (jurl)
url = base::android::ConvertJavaStringToUTF8(env, jurl);
content::NavigationEntry* entry =
web_contents()->GetController().GetVisibleEntry();
if (entry && url == entry->GetVirtualURL().spec())
entry->SetTitle(std::move(title));
}
void TabAndroid::LoadOriginalImage(JNIEnv* env) {
content::RenderFrameHost* render_frame_host =
web_contents()->GetFocusedFrame();
mojo::AssociatedRemote<chrome::mojom::ChromeRenderFrame> renderer;
render_frame_host->GetRemoteAssociatedInterfaces()->GetInterface(&renderer);
renderer->RequestReloadImageForContextNode();
}
void TabAndroid::OnShow(JNIEnv* env) {
// When changing tabs to one that is unloaded, the tab change notification
// arrives before the request to InitWebContents. In that case do nothing and
// allow initialization to record timing.
//
// Similarly if we are already visible do not enqueue a timing request.
if (!web_contents_ ||
web_contents_->GetVisibility() != content::Visibility::HIDDEN) {
return;
}
// TODO(crbug.com/40868330): When a tab is backgrounded, and then brought
// again to the foreground it's TabLoadTracker state gets stuck in LOADING.
// This disagrees with the WebContents internal state. So for now we can only
// trust UNLOADED. TabLoadTracker::DidStopLoading is not being called
// correctly except for the initial load in InitWebContents.
bool loaded =
resource_coordinator::TabLoadTracker::Get()->GetLoadingState(
web_contents_.get()) != mojom::LifecycleUnitLoadingState::UNLOADED &&
!web_contents_->IsLoading();
web_contents_->SetTabSwitchStartTime(base::TimeTicks::Now(), loaded);
}
scoped_refptr<content::DevToolsAgentHost> TabAndroid::GetDevToolsAgentHost() {
return devtools_host_;
}
void TabAndroid::SetDevToolsAgentHost(
scoped_refptr<content::DevToolsAgentHost> host) {
devtools_host_ = std::move(host);
}
bool TabAndroid::IsTrustedWebActivity() {
JNIEnv* env = base::android::AttachCurrentThread();
return Java_TabImpl_isTrustedWebActivity(env, weak_java_tab_.get(env));
}
base::android::ScopedJavaLocalRef<jobject> JNI_TabImpl_FromWebContents(
JNIEnv* env,
const JavaParamRef<jobject>& jweb_contents) {
base::android::ScopedJavaLocalRef<jobject> jtab;
content::WebContents* web_contents =
content::WebContents::FromJavaWebContents(jweb_contents);
TabAndroid* tab =
web_contents ? TabAndroid::FromWebContents(web_contents) : nullptr;
if (tab)
jtab = tab->GetJavaObject();
return jtab;
}
static jboolean JNI_TabImpl_HandleNonNavigationAboutURL(
JNIEnv* env,
const JavaParamRef<jobject>& jurl) {
GURL url = url::GURLAndroid::ToNativeGURL(env, jurl);
return HandleNonNavigationAboutURL(url);
}
static void JNI_TabImpl_Init(JNIEnv* env,
const JavaParamRef<jobject>& obj,
Profile* profile,
jint id) {
TRACE_EVENT0("native", "TabAndroid::Init");
// This will automatically bind to the Java object and pass ownership there.
new TabAndroid(env, obj, profile, id);
}