| // Copyright 2019 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. |
| |
| package org.chromium.chrome.browser.app.tab_activity_glue; |
| |
| import android.app.Activity; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.provider.Browser; |
| import android.text.TextUtils; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| |
| import org.chromium.base.ContextUtils; |
| import org.chromium.base.IntentUtils; |
| import org.chromium.base.UserData; |
| import org.chromium.base.annotations.NativeMethods; |
| import org.chromium.chrome.browser.IntentHandler; |
| import org.chromium.chrome.browser.app.tabmodel.AsyncTabParamsManagerSingleton; |
| import org.chromium.chrome.browser.compositor.CompositorViewHolder; |
| import org.chromium.chrome.browser.document.ChromeLauncherActivity; |
| import org.chromium.chrome.browser.flags.ChromeFeatureList; |
| import org.chromium.chrome.browser.tab.Tab; |
| import org.chromium.chrome.browser.tab.TabDelegateFactory; |
| import org.chromium.chrome.browser.tab.TabStateAttributes; |
| import org.chromium.chrome.browser.tabmodel.TabModelSelector; |
| import org.chromium.chrome.browser.tabmodel.TabReparentingParams; |
| import org.chromium.content_public.browser.WebContents; |
| import org.chromium.ui.base.WindowAndroid; |
| |
| /** |
| * Takes care of reparenting a Tab object from one Activity to another. |
| */ |
| public class ReparentingTask implements UserData { |
| /** Provides data to {@link ReparentingTask} facilitate reparenting tabs. */ |
| public interface Delegate { |
| /** |
| * Gets a {@link CompositorViewHolder} which is passed on to {@link ReparentingTask}, used |
| * in the reparenting process. |
| */ |
| CompositorViewHolder getCompositorViewHolder(); |
| |
| /** |
| * Gets a {@link WindowAndroid} which is passed on to {@link ReparentingTask}, used in the |
| * reparenting process. |
| */ |
| WindowAndroid getWindowAndroid(); |
| |
| /** |
| * Gets a {@link TabDelegateFactory} which is passed on to {@link ReparentingTask}, used in |
| * the reparenting process. |
| */ |
| TabDelegateFactory getTabDelegateFactory(); |
| } |
| |
| private static final Class<ReparentingTask> USER_DATA_KEY = ReparentingTask.class; |
| |
| private final Tab mTab; |
| |
| /** |
| * @param tab {@link Tab} object. |
| * @return {@link ReparentingTask} object for a given {@link Tab}. Creates one |
| * if not present. |
| */ |
| public static ReparentingTask from(Tab tab) { |
| ReparentingTask reparentingTask = get(tab); |
| if (reparentingTask == null) { |
| reparentingTask = |
| tab.getUserDataHost().setUserData(USER_DATA_KEY, new ReparentingTask(tab)); |
| } |
| return reparentingTask; |
| } |
| |
| @Nullable |
| public static ReparentingTask get(Tab tab) { |
| return tab.getUserDataHost().getUserData(USER_DATA_KEY); |
| } |
| |
| private ReparentingTask(Tab tab) { |
| mTab = tab; |
| } |
| |
| /** |
| * Begins the tab reparenting process. Detaches the tab from its current activity and fires |
| * an Intent to reparent the tab into its new host activity. |
| * |
| * @param context {@link Context} object used to start a new activity. |
| * @param intent An optional intent with the desired component, flags, or extras to use when |
| * launching the new host activity. This intent's URI and action will be |
| * overridden. This may be null if no intent customization is needed. |
| * @param startActivityOptions Options to pass to {@link Activity#startActivity(Intent, Bundle)} |
| * @param finalizeCallback A callback that will be called after the tab is attached to the new |
| * host activity in {@link #attachAndFinishReparenting}. |
| */ |
| public void begin(Context context, Intent intent, Bundle startActivityOptions, |
| Runnable finalizeCallback) { |
| setupIntent(context, intent, finalizeCallback); |
| context.startActivity(intent, startActivityOptions); |
| } |
| |
| /** |
| * Sets up the given intent to be used for reparenting a tab. |
| * @param context {@link Context} object used to start a new activity. |
| * @param intent An optional intent with the desired component, flags, or extras to use when |
| * launching the new host activity. This intent's URI and action will be |
| * overridden. This may be null if no intent customization is needed. |
| * @param finalizeCallback A callback that will be called after the tab is attached to the new |
| * host activity in {@link #attachAndFinishReparenting}. |
| */ |
| public void setupIntent(Context context, Intent intent, Runnable finalizeCallback) { |
| if (intent == null) intent = new Intent(); |
| if (intent.getComponent() == null) { |
| intent.setClass(ContextUtils.getApplicationContext(), ChromeLauncherActivity.class); |
| } |
| intent.setAction(Intent.ACTION_VIEW); |
| if (TextUtils.isEmpty(intent.getDataString())) { |
| intent.setData(Uri.parse(mTab.getUrl().getSpec())); |
| } |
| if (mTab.isIncognito()) { |
| intent.putExtra(Browser.EXTRA_APPLICATION_ID, |
| ContextUtils.getApplicationContext().getPackageName()); |
| intent.putExtra(IntentHandler.EXTRA_OPEN_NEW_INCOGNITO_TAB, true); |
| } |
| IntentUtils.addTrustedIntentExtras(intent); |
| |
| if (ChromeFeatureList.isEnabled(ChromeFeatureList.TAB_REPARENTING)) { |
| // Add the tab to AsyncTabParamsManager before removing it from the current model to |
| // ensure the global count of tabs is correct. See https://crbug.com/611806. |
| IntentHandler.setTabId(intent, mTab.getId()); |
| AsyncTabParamsManagerSingleton.getInstance().add( |
| mTab.getId(), new TabReparentingParams(mTab, finalizeCallback)); |
| |
| detach(); |
| } |
| } |
| |
| /** |
| * Detaches a tab from its current activity if any. |
| * |
| * In details, this function: |
| * - Removes the tab from its current {@link TabModelSelector}, effectively severing |
| * the {@link Activity} to {@link Tab} link. |
| */ |
| public void detach() { |
| // TODO(yusufo): We can't call tab.updateWindowAndroid that sets |mWindowAndroid| to null |
| // because many code paths (including navigation) expect the tab to always be associated |
| // with an activity, and will crash. crbug.com/657007 |
| WebContents webContents = mTab.getWebContents(); |
| if (webContents != null) webContents.setTopLevelNativeWindow(null); |
| |
| // TabModelSelector of this Tab, if present, gets notified to remove the tab from |
| // the TabModel it belonged to. |
| mTab.updateAttachment(null, null); |
| } |
| |
| /** |
| * Finishes the tab reparenting process. Attaches this tab to a new activity, and updates |
| * the tab and related objects to reference it. This updates many delegates inside the tab |
| * and {@link WebContents} both on java and native sides. |
| * |
| * @param delegate A delegate that provides dependencies. |
| * @param finalizeCallback A Callback to be called after the Tab has been reparented. |
| */ |
| public void finish(@NonNull Delegate delegate, @Nullable Runnable finalizeCallback) { |
| delegate.getCompositorViewHolder().prepareForTabReparenting(); |
| attach(delegate.getWindowAndroid(), delegate.getTabDelegateFactory()); |
| if (!mTab.isDestroyed()) { |
| TabStateAttributes.from(mTab).setIsTabStateDirty(true); |
| } |
| if (finalizeCallback != null) finalizeCallback.run(); |
| } |
| |
| /** |
| * Attaches the tab to the new activity and updates the tab and related objects to reference the |
| * new activity. This updates many delegates inside the tab and {@link WebContents} both on |
| * java and native sides. |
| * |
| * @param window A new {@link WindowAndroid} to attach the tab to. |
| * @param tabDelegateFactory The new delegate factory this tab should be using. |
| */ |
| private void attach(WindowAndroid window, TabDelegateFactory tabDelegateFactory) { |
| // Assert that the tab is currently in detached state. |
| assert mTab.getWebContents() == null |
| || mTab.getWebContents().getTopLevelNativeWindow() == null; |
| mTab.updateAttachment(window, tabDelegateFactory); |
| ReparentingTaskJni.get().attachTab(mTab.getWebContents()); |
| } |
| |
| @NativeMethods |
| interface Natives { |
| void attachTab(WebContents webContents); |
| } |
| } |