| // 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/functional/bind.h" | 
 | #include "base/memory/ptr_util.h" | 
 | #include "base/memory/raw_ptr.h" | 
 | #include "base/metrics/histogram_macros.h" | 
 | #include "base/metrics/user_metrics.h" | 
 | #include "base/trace_event/trace_event.h" | 
 | #include "cc/slim/layer.h" | 
 | #include "chrome/android/chrome_jni_headers/TabImpl_jni.h" | 
 | #include "chrome/android/chrome_jni_headers/TabUtils_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/notifications/notification_permission_context.h" | 
 | #include "chrome/browser/profiles/profile.h" | 
 | #include "chrome/browser/profiles/profile_android.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/jni_headers/CriticalPersistedTabData_jni.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_.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<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::slim::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::slim::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)); | 
 | } | 
 |  | 
 | 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_CriticalPersistedTabData_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(); | 
 |   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(); | 
 | } | 
 |  | 
 | bool TabAndroid::IsIncognito() const { | 
 |   JNIEnv* env = base::android::AttachCurrentThread(); | 
 |   const bool is_incognito = | 
 |       Java_TabImpl_isIncognito(env, weak_java_tab_.get(env)); | 
 |   if (Profile* profile = GetProfile()) { | 
 |     CHECK_EQ(is_incognito, profile->IsOffTheRecord()); | 
 |   } | 
 |   return is_incognito; | 
 | } | 
 |  | 
 | 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)); | 
 | } | 
 |  | 
 | bool TabAndroid::isHardwareKeyboardAvailable(TabAndroid* tab_android) { | 
 |   JNIEnv* env = base::android::AttachCurrentThread(); | 
 |   return Java_TabUtils_isHardwareKeyboardAvailable( | 
 |       env, tab_android->GetJavaObject()); | 
 | } | 
 |  | 
 | 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()); | 
 |  | 
 |   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(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()->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(); | 
 |  | 
 |   // 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::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/1368291): 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); | 
 | } | 
 |  | 
 | 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); | 
 | } |