blob: e2a60e686314d8958bf9061edebd7488d1aa649c [file] [log] [blame]
// Copyright 2012 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_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/bind.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/trace_event/trace_event.h"
#include "cc/layers/layer.h"
#include "chrome/android/chrome_jni_headers/TabImpl_jni.h"
#include "chrome/browser/android/background_tab_manager.h"
#include "chrome/browser/android/compositor/tab_content_manager.h"
#include "chrome/browser/android/metrics/uma_utils.h"
#include "chrome/browser/android/tab_printer.h"
#include "chrome/browser/android/tab_web_contents_delegate_android.h"
#include "chrome/browser/browser_about_handler.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/history/history_tab_helper.h"
#include "chrome/browser/notifications/notification_permission_context.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_android.h"
#include "chrome/browser/profiles/profile_manager.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/infobars/infobar_container_android.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/browser_navigator_params.h"
#include "chrome/browser/ui/startup/bad_flags_prompt.h"
#include "chrome/browser/ui/tab_contents/core_tab_helper.h"
#include "chrome/browser/ui/tab_helpers.h"
#include "chrome/common/url_constants.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 "components/url_formatter/url_fixer.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 "content/public/common/referrer.h"
#include "content/public/common/resource_request_body_android.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "ui/android/view_android.h"
#include "ui/android/window_android.h"
#include "ui/base/resource/resource_bundle.h"
#include "url/android/gurl_android.h"
#include "url/gurl.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_ : nullptr;
}
explicit TabAndroidHelper(content::WebContents*) {}
private:
friend class content::WebContentsUserData<TabAndroidHelper>;
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<TabAndroid*> TabAndroid::GetAllNativeTabs(
JNIEnv* env,
const ScopedJavaLocalRef<jobjectArray>& obj_array) {
std::vector<TabAndroid*> 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)
: weak_java_tab_(env, obj),
session_window_id_(SessionID::InvalidValue()),
content_layer_(cc::Layer::Create()),
synced_tab_delegate_(new browser_sync::SyncedTabDelegateAndroid(this)) {
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));
}
base::android::ScopedJavaLocalRef<jobject> TabAndroid::GetJavaObject() {
JNIEnv* env = base::android::AttachCurrentThread();
return weak_java_tab_.get(env);
}
scoped_refptr<cc::Layer> TabAndroid::GetContentLayer() const {
return content_layer_;
}
int TabAndroid::GetAndroidId() const {
JNIEnv* env = base::android::AttachCurrentThread();
return Java_TabImpl_getId(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();
std::unique_ptr<GURL> gurl = url::GURLAndroid::ToNativeGURL(
env, Java_TabImpl_getUrl(env, weak_java_tab_.get(env)));
return std::move(*gurl);
}
bool TabAndroid::IsUserInteractable() const {
JNIEnv* env = base::android::AttachCurrentThread();
return Java_TabImpl_isUserInteractable(env, weak_java_tab_.get(env));
}
Profile* TabAndroid::GetProfile() const {
if (!web_contents())
return nullptr;
return Profile::FromBrowserContext(web_contents()->GetBrowserContext());
}
sync_sessions::SyncedTabDelegate* TabAndroid::GetSyncedTabDelegate() const {
return synced_tab_delegate_.get();
}
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();
// TODO(crbug.com/836409): TabLoadTracker should not rely on being notified
// directly about tab contents swaps.
resource_coordinator::TabLoadTracker::Get()->SwapTabContents(
old_contents, new_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,
jint jparent_tab_id,
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());
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());
PropagateHideFutureNavigationsToHistoryTabHelper();
PropagateBlockNewNotificationRequestsToWebContents();
SetWindowSessionID(session_window_id_);
ContextMenuHelper::FromWebContents(web_contents())
->SetPopulatorFactory(jcontext_menu_populator_factory);
synced_tab_delegate_->SetWebContents(web_contents(), jparent_tab_id);
// Verify that the WebContents this tab represents matches the expected
// off the record state.
CHECK_EQ(GetProfile()->IsOffTheRecord(), incognito);
if (is_background_tab) {
BackgroundTabManager::GetInstance()->RegisterBackgroundTab(web_contents(),
GetProfile());
}
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::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()->GetMainFrame()->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();
// 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(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::SetAddApi2TransitionToFutureNavigations(JNIEnv* env,
jboolean should_add) {
should_add_api2_transition_to_future_navigations_ = should_add;
}
scoped_refptr<content::DevToolsAgentHost> TabAndroid::GetDevToolsAgentHost() {
return devtools_host_;
}
void TabAndroid::SetHideFutureNavigations(JNIEnv* env, jboolean hide) {
if (hide_future_navigations_ == hide)
return;
hide_future_navigations_ = hide;
PropagateHideFutureNavigationsToHistoryTabHelper();
}
void TabAndroid::SetShouldBlockNewNotificationRequests(JNIEnv* env,
jboolean value) {
if (should_block_new_notification_requests_ == value)
return;
should_block_new_notification_requests_ = value;
PropagateBlockNewNotificationRequestsToWebContents();
}
void TabAndroid::SetDevToolsAgentHost(
scoped_refptr<content::DevToolsAgentHost> host) {
devtools_host_ = std::move(host);
}
void TabAndroid::PropagateHideFutureNavigationsToHistoryTabHelper() {
if (!web_contents())
return;
auto* history_tab_helper = HistoryTabHelper::FromWebContents(web_contents());
if (history_tab_helper)
history_tab_helper->set_hide_all_navigations(hide_future_navigations_);
}
void TabAndroid::PropagateBlockNewNotificationRequestsToWebContents() {
if (!web_contents())
return;
NotificationPermissionContext::SetBlockNewNotificationRequests(
web_contents(), should_block_new_notification_requests_);
}
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) {
std::unique_ptr<GURL> url = url::GURLAndroid::ToNativeGURL(env, jurl);
return HandleNonNavigationAboutURL(*url);
}
static void JNI_TabImpl_Init(JNIEnv* env, const JavaParamRef<jobject>& obj) {
TRACE_EVENT0("native", "TabAndroid::Init");
// This will automatically bind to the Java object and pass ownership there.
new TabAndroid(env, obj);
}