| // 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/android/token_android.h" |
| #include "base/check_is_test.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/notimplemented.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_features.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/renderer_preferences_util.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 "components/tabs/public/supports_handles.h" |
| #include "components/tabs/public/tab_collection.h" |
| #include "components/tabs/public/tab_group_tab_collection.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 "third_party/blink/public/common/renderer_preferences/renderer_preferences.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::ConvertJavaStringToUTF8; |
| 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 |
| |
| namespace tabs { |
| |
| // static |
| TabInterface* TabInterface::GetFromContents( |
| content::WebContents* web_contents) { |
| auto* tab_android = TabAndroid::FromWebContents(web_contents); |
| CHECK(tab_android); |
| return tab_android; |
| } |
| |
| const TabInterface* TabInterface::GetFromContents( |
| const content::WebContents* web_contents) { |
| const auto* tab_android = TabAndroid::FromWebContents(web_contents); |
| CHECK(tab_android); |
| return tab_android; |
| } |
| |
| // static |
| TabInterface* TabInterface::MaybeGetFromContents( |
| content::WebContents* web_contents) { |
| return TabAndroid::FromWebContents(web_contents); |
| } |
| |
| } // namespace tabs |
| |
| // static |
| TabAndroid* TabAndroid::FromWebContents( |
| const content::WebContents* web_contents) { |
| return TabAndroidHelper::FromWebContents(web_contents); |
| } |
| |
| // static |
| TabAndroid* TabAndroid::FromTabHandle(tabs::TabHandle handle) { |
| return static_cast<TabAndroid*>(handle.Get()); |
| } |
| |
| // static |
| TabAndroid* TabAndroid::GetNativeTab(JNIEnv* env, const JavaRef<jobject>& obj) { |
| return reinterpret_cast<TabAndroid*>(Java_TabImpl_getNativePtr(env, obj)); |
| } |
| |
| // static |
| 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; |
| } |
| |
| // static |
| 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(); |
| const jni_zero::JavaRef<jobject>& obj = weak_java_tab_.get(env); |
| if (obj) { |
| Java_TabImpl_clearNativePtr(env, obj); |
| } |
| } |
| |
| // static |
| std::unique_ptr<TabAndroid> TabAndroid::CreateForTesting( |
| Profile* profile, |
| int tab_id, |
| std::unique_ptr<content::WebContents> web_contents) { |
| std::unique_ptr<TabAndroid> tab(new TabAndroid(profile, tab_id)); |
| tab->web_contents_ = std::move(web_contents); |
| return tab; |
| } |
| |
| 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); |
| } |
| |
| base::android::ScopedJavaLocalRef<jobject> TabAndroid::GetJavaObject() const { |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| return weak_java_tab_.get(env); |
| } |
| |
| scoped_refptr<cc::slim::Layer> TabAndroid::GetContentLayer() const { |
| return content_layer_; |
| } |
| |
| 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(); |
| return Java_TabImpl_getTitle(env, weak_java_tab_.get(env)); |
| } |
| |
| 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); |
| } |
| |
| int TabAndroid::GetTabLaunchTypeAtCreation() const { |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| return Java_TabImpl_getTabLaunchTypeAtCreation(env, weak_java_tab_.get(env)); |
| } |
| |
| int TabAndroid::GetParentId() const { |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| return Java_TabImpl_getParentId(env, weak_java_tab_.get(env)); |
| } |
| |
| 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() const { |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| return Java_TabImpl_isCustomTab(env, weak_java_tab_.get(env)); |
| } |
| |
| bool TabAndroid::IsHidden() const { |
| 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()); |
| |
| renderer_preferences_util::UpdateFromSystemSettings( |
| web_contents_->GetMutableRendererPrefs(), |
| Profile::FromBrowserContext(web_contents_->GetBrowserContext())); |
| 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()); |
| tab_features_ = |
| std::make_unique<tabs::TabFeatures>(web_contents_.get(), profile()); |
| // 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())); |
| |
| const SessionID session_id = |
| sessions::SessionTabHelper::IdForTab(web_contents_.get()); |
| CHECK(session_id.is_valid()); |
| SetSessionId(session_id.id()); |
| 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. |
| 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, |
| bool clear_delegate) { |
| DCHECK(contents); |
| |
| if (contents->GetNativeView()) { |
| contents->GetNativeView()->GetLayer()->RemoveFromParent(); |
| } |
| |
| if (clear_delegate) { |
| contents->SetDelegate(nullptr); |
| } |
| } |
| } // namespace |
| |
| void TabAndroid::DestroyWebContents(JNIEnv* env) { |
| WillRemoveWebContentsFromTab(web_contents(), /*clear_delegate=*/false); |
| |
| // 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); |
| } |
| |
| tab_features_.reset(); |
| web_contents_.reset(); |
| |
| synced_tab_delegate_->ResetWebContents(); |
| } |
| |
| void TabAndroid::ReleaseWebContents(JNIEnv* env) { |
| WillRemoveWebContentsFromTab(web_contents(), /*clear_delegate=*/true); |
| |
| // Ownership of |released_contents| is assumed by the code that initiated the |
| // release. |
| tab_features_.reset(); |
| 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); |
| ClearSessionId(); |
| |
| synced_tab_delegate_->ResetWebContents(); |
| } |
| |
| bool TabAndroid::IsPhysicalBackingSizeEmpty( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& jweb_contents) { |
| content::WebContents* web_contents = |
| content::WebContents::FromJavaWebContents(jweb_contents); |
| auto size = web_contents->GetNativeView()->GetPhysicalBackingSize(); |
| return size.IsEmpty(); |
| } |
| |
| 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, |
| std::string& url, |
| std::u16string& title) { |
| DCHECK(web_contents()); |
| |
| 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() const { |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| return Java_TabImpl_isTrustedWebActivity(env, weak_java_tab_.get(env)); |
| } |
| |
| base::WeakPtr<TabAndroid> TabAndroid::GetTabAndroidWeakPtr() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| base::WeakPtr<tabs::TabInterface> TabAndroid::GetWeakPtr() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| content::WebContents* TabAndroid::GetContents() const { |
| return web_contents_.get(); |
| } |
| |
| void TabAndroid::Close() { |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| Java_TabImpl_closeTabFromNative(env, weak_java_tab_.get(env)); |
| } |
| |
| base::CallbackListSubscription TabAndroid::RegisterWillDiscardContents( |
| WillDiscardContentsCallback callback) { |
| // Tab discarding is currently an OS level operation and we don't necessarily |
| // get signal when this occurs. |
| NOTIMPLEMENTED(); |
| return base::CallbackListSubscription(); |
| } |
| |
| bool TabAndroid::IsActivated() const { |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| return Java_TabImpl_isActivated(env, weak_java_tab_.get(env)); |
| } |
| |
| // TODO(crbug.com/409366905): Finish TabInterface implementation. |
| base::CallbackListSubscription TabAndroid::RegisterDidActivate( |
| DidActivateCallback callback) { |
| NOTIMPLEMENTED(); |
| return base::CallbackListSubscription(); |
| } |
| |
| // TODO(crbug.com/409366905): Finish TabInterface implementation. |
| base::CallbackListSubscription TabAndroid::RegisterWillDeactivate( |
| WillDeactivateCallback callback) { |
| NOTIMPLEMENTED(); |
| return base::CallbackListSubscription(); |
| } |
| |
| bool TabAndroid::IsVisible() const { |
| return !IsHidden(); |
| } |
| |
| bool TabAndroid::IsSelected() const { |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| return Java_TabImpl_isMultiSelected(env, weak_java_tab_.get(env)); |
| } |
| |
| // TODO(crbug.com/409366905): Finish TabInterface implementation. |
| base::CallbackListSubscription TabAndroid::RegisterDidBecomeVisible( |
| DidBecomeVisibleCallback callback) { |
| NOTIMPLEMENTED(); |
| return base::CallbackListSubscription(); |
| } |
| |
| // TODO(crbug.com/409366905): Finish TabInterface implementation. |
| base::CallbackListSubscription TabAndroid::RegisterWillBecomeHidden( |
| WillBecomeHiddenCallback callback) { |
| NOTIMPLEMENTED(); |
| return base::CallbackListSubscription(); |
| } |
| |
| // TODO(crbug.com/409366905): Finish TabInterface implementation. |
| base::CallbackListSubscription TabAndroid::RegisterWillDetach( |
| WillDetach callback) { |
| NOTIMPLEMENTED(); |
| return base::CallbackListSubscription(); |
| } |
| |
| // TODO(crbug.com/409366905): Finish TabInterface implementation. |
| base::CallbackListSubscription TabAndroid::RegisterDidInsert( |
| DidInsertCallback callback) { |
| NOTIMPLEMENTED(); |
| return base::CallbackListSubscription(); |
| } |
| |
| // TODO(crbug.com/409366905): Finish TabInterface implementation. |
| base::CallbackListSubscription TabAndroid::RegisterPinnedStateChanged( |
| PinnedStateChangedCallback callback) { |
| NOTIMPLEMENTED(); |
| return base::CallbackListSubscription(); |
| } |
| |
| // TODO(crbug.com/409366905): Finish TabInterface implementation. |
| base::CallbackListSubscription TabAndroid::RegisterGroupChanged( |
| GroupChangedCallback callback) { |
| NOTIMPLEMENTED(); |
| return base::CallbackListSubscription(); |
| } |
| |
| // For now tab scoped modals should continue to be handled by the window-scoped |
| // ModalDialogManager class in Java. |
| // TODO(crbug.com/422208977): Investigate adding a capability to trigger tab |
| // scoped modals directly to tab. |
| |
| bool TabAndroid::CanShowModalUI() const { |
| NOTIMPLEMENTED(); |
| return false; |
| } |
| |
| std::unique_ptr<tabs::ScopedTabModalUI> TabAndroid::ShowModalUI() { |
| NOTIMPLEMENTED(); |
| return nullptr; |
| } |
| |
| base::CallbackListSubscription TabAndroid::RegisterModalUIChanged( |
| TabInterfaceCallback callback) { |
| NOTIMPLEMENTED(); |
| return base::CallbackListSubscription(); |
| } |
| |
| bool TabAndroid::IsInNormalWindow() const { |
| return true; |
| } |
| |
| tabs::TabFeatures* TabAndroid::GetTabFeatures() { |
| return tab_features_.get(); |
| } |
| |
| const tabs::TabFeatures* TabAndroid::GetTabFeatures() const { |
| return tab_features_.get(); |
| } |
| |
| bool TabAndroid::IsPinned() const { |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| return Java_TabImpl_getIsPinned(env, weak_java_tab_.get(env)); |
| } |
| |
| // Split tabs is currently desktop only. |
| bool TabAndroid::IsSplit() const { |
| NOTIMPLEMENTED(); |
| return false; |
| } |
| |
| std::optional<tab_groups::TabGroupId> TabAndroid::GetGroup() const { |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| std::optional<base::Token> token = |
| Java_TabImpl_getTabGroupId(env, weak_java_tab_.get(env)); |
| return tab_groups::TabGroupId::FromOptionalToken(token); |
| } |
| |
| // Split tabs is currently desktop only. |
| std::optional<split_tabs::SplitTabId> TabAndroid::GetSplit() const { |
| NOTIMPLEMENTED(); |
| return std::nullopt; |
| } |
| |
| tabs::TabCollection* TabAndroid::GetParentCollection( |
| base::PassKey<tabs::TabCollection>) const { |
| return parent_collection_; |
| } |
| |
| const tabs::TabCollection* TabAndroid::GetParentCollection() const { |
| return parent_collection_; |
| } |
| |
| void TabAndroid::OnReparented(tabs::TabCollection* parent, |
| base::PassKey<tabs::TabCollection> pass_key) { |
| parent_collection_ = parent; |
| OnAncestorChanged(pass_key); |
| } |
| |
| void TabAndroid::OnAncestorChanged(base::PassKey<tabs::TabCollection>) { |
| // TODO(crbug.com/409366905): Possibly add a detached state. |
| if (parent_collection_) { |
| UpdateProperties(); |
| } |
| } |
| |
| ui::UnownedUserDataHost& TabAndroid::GetUnownedUserDataHost() { |
| return unowned_user_data_host_; |
| } |
| |
| const ui::UnownedUserDataHost& TabAndroid::GetUnownedUserDataHost() const { |
| return unowned_user_data_host_; |
| } |
| |
| TabAndroid::TabAndroid(Profile* profile, int tab_id) |
| : 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()) { |
| CHECK_IS_TEST(); |
| } |
| |
| void TabAndroid::UpdateProperties() { |
| bool pinned = false; |
| std::optional<tab_groups::TabGroupId> tab_group_id = std::nullopt; |
| |
| tabs::TabCollection* ancestor = parent_collection_; |
| while (ancestor) { |
| switch (ancestor->type()) { |
| case tabs::TabCollection::Type::PINNED: |
| pinned = true; |
| break; |
| case tabs::TabCollection::Type::GROUP: |
| tab_group_id = static_cast<tabs::TabGroupTabCollection*>(ancestor) |
| ->GetTabGroupId(); |
| break; |
| case tabs::TabCollection::Type::SPLIT: |
| // Intentional fallthrough. Split tabs is currently desktop only. |
| case tabs::TabCollection::Type::TABSTRIP: |
| case tabs::TabCollection::Type::UNPINNED: |
| break; |
| } |
| ancestor = ancestor->GetParentCollection(); |
| } |
| |
| SetIsPinned(pinned); |
| SetTabGroupId(tab_group_id); |
| } |
| |
| void TabAndroid::SetIsPinned(bool is_pinned) { |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| Java_TabImpl_setIsPinned(env, weak_java_tab_.get(env), is_pinned); |
| } |
| |
| void TabAndroid::SetTabGroupId( |
| std::optional<tab_groups::TabGroupId> tab_group_id) { |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| ScopedJavaLocalRef<jobject> java_token; |
| if (tab_group_id) { |
| java_token = |
| base::android::TokenAndroid::Create(env, tab_group_id->token()); |
| } |
| Java_TabImpl_setTabGroupId(env, weak_java_tab_.get(env), java_token); |
| } |
| |
| 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); |
| // TODO(crbug.com/418187845): Set browser context to support URL block policy. |
| return HandleNonNavigationAboutURL(url, /*context=*/nullptr); |
| } |
| |
| 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); |
| } |