// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// 
import './composebox.js';
// 
// 
// 
// TODO(crbug.com/511383725): Support onboarding tooltip on Android.
import './lens_search_tooltip.js';
import './onboarding_tooltip.js';
import './banner_promo.js';
import '//resources/cr_components/composebox/contextual_entrypoint_and_menu.js';
import '//resources/cr_elements/cr_button/cr_button.js';
import { HelpBubbleMixinLit } from 'chrome://resources/cr_components/help_bubble/help_bubble_mixin_lit.js';
// 
import './error_dialog.js';
import './error_page.js';
import './ghost_loader.js';
import './top_toolbar.js';
import { isFullWebView } from './web_view_type.js';
import { assert } from 'chrome://resources/js/assert.js';
import { EventTracker } from 'chrome://resources/js/event_tracker.js';
import { loadTimeData } from 'chrome://resources/js/load_time_data.js';
import { CrLitElement } from 'chrome://resources/lit/v3_0/lit.rollup.js';
import { getCss } from './app.css.js';
import { getHtml } from './app.html.js';
import { BrowserProxyImpl } from './contextual_tasks_browser_proxy.js';
import { PostMessageHandler } from './post_message_handler.js';
import { getNonOccludedClipPath } from './utils/clip_path.js';
import { recordAction } from './utils.js';
// 
import { WindowManager } from './window_manager.js';
// The url query parameter keys for the viewport size.
const VIEWPORT_HEIGHT_KEY = 'bih';
const VIEWPORT_WIDTH_KEY = 'biw';
const CHROME_TASK_PARAM_KEY = 'chrome_task_id';
const DEBUG_PARAM_KEY = 'deb';
const CHROME_HOST_PARAM_KEY = 'chrome_host';
const AIOH_URL_IDENTIFIER = 'aioh';
// The extra padding to add to the occluders to ensure that the composebox is
// fully visible. This helps to account for inconsistencies between the bounding
// boxes of the element, and what is actually rendered (for example, box shadows
// on the elements might not be included in the bounding box).
const OCCLUDER_EXTRA_PADDING_PX = 15;
// LINT.IfChange(ComposeboxBorderRadius)
const COMPOSEBOX_BORDER_RADIUS_PX = 24;
// Copy the params from the current aim URL to the webui URL.
// Keeping the params in sync ensures the page reloads or
// restores correctly.
// Update the task ID in the URL.
// Update navigation entry with replaceState/pushState.
function updateTaskDetailsInUrl(taskId, aimUrl, replaceNavigationEntry) {
    const url = new URL(window.location.href);
    const host = url.searchParams.get(CHROME_HOST_PARAM_KEY);
    // Clear the existing params
    url.search = '';
    // Preserve host if present in current URL.
    if (host) {
        url.searchParams.set(CHROME_HOST_PARAM_KEY, host);
    }
    // Add all the params from the aim URL, except host.
    if (aimUrl) {
        try {
            const aimUrlObj = new URL(aimUrl);
            aimUrlObj.searchParams.forEach((value, key) => {
                if (key !== CHROME_HOST_PARAM_KEY) {
                    url.searchParams.set(key, value);
                }
            });
            url.hash = aimUrlObj.hash;
        }
        catch (e) {
            console.error('Failed to parse AI thread URL:', aimUrl, e);
        }
    }
    // Add the task ID to the params.
    if (taskId) {
        url.searchParams.set(CHROME_TASK_PARAM_KEY, taskId.value);
    }
    // Allow back navigation if the task ID changes. Other changes to the URL
    // represent state changes for the current task.
    if (replaceNavigationEntry) {
        window.history.replaceState({}, '', url.href);
    }
    else {
        window.history.pushState({}, '', url.href);
    }
}
// Returns whether the value of the "deb" param contains "nocobrowse1" which
// should cause the user to be removed from the cobrowse ui.
function hasExitCobrowseParam(url) {
    const debParam = url.searchParams.get(DEBUG_PARAM_KEY) || '';
    return debParam.indexOf('nocobrowse1') > -1;
}
// 
// 
const ContextualTasksAppElementBase = HelpBubbleMixinLit(CrLitElement);
// 
export class ContextualTasksAppElement extends ContextualTasksAppElementBase {
    static get is() {
        return 'contextual-tasks-app';
    }
    static get styles() {
        return getCss();
    }
    render() {
        return getHtml.bind(this)();
    }
    static get properties() {
        return {
            isShownInTab_: {
                type: Boolean,
                reflect: true,
            },
            threadTitle_: { type: String },
            darkMode_: {
                type: Boolean,
                reflect: true,
            },
            isErrorPageVisible_: {
                type: Boolean,
                reflect: true,
            },
            isInBasicMode_: { type: Boolean, reflect: true },
            isInputHidden_: { type: Boolean, reflect: true },
            // Means no queries have been submitted in current AIM thread.
            isZeroState_: {
                type: Boolean,
                reflect: true,
            },
            inNlm_: {
                type: Boolean,
                reflect: true,
            },
            // Whether top level navigation was aborted. One example where this can
            // occur is if a user is offline.
            isLoadError_: {
                type: Boolean,
                reflect: true,
            },
            isAiPage_: { type: Boolean, reflect: true },
            isUserSignedIn_: { type: Boolean },
            isLensOverlayShowing_: { type: Boolean },
            isOverlayOpenForAimVisualSearch_: { type: Boolean },
            isGhostLoaderVisible_: { type: Boolean, reflect: true },
            isErrorDialogVisible_: { type: Boolean },
            enableNativeZeroStateSuggestions_: {
                type: Boolean,
                reflect: true,
            },
            enableBasicMode_: {
                type: Boolean,
                reflect: true,
            },
            enableBasicModeZOrder_: {
                type: Boolean,
                reflect: true,
            },
            useStratusDarkModeColors_: {
                type: Boolean,
                reflect: true,
            },
            isInputLocked_: {
                type: Boolean,
            },
            isLoadingZeroStateFromResults_: {
                type: Boolean,
                reflect: true,
            },
            forcedComposeboxBounds_: { type: Object },
            userName_: { type: String },
            friendlyZeroStateTitleBeforeName_: { type: String },
            friendlyZeroStateTitleAfterName_: { type: String },
            friendlyZeroStateTitle: { type: String },
            friendlyZeroStateSubtitle: { type: String },
            occluders_: { type: Array },
            showOnboardingTooltip_: {
                type: Boolean,
                value: loadTimeData.getBoolean('showOnboardingTooltip'),
            },
            showLensSearchTooltip_: {
                type: Boolean,
                value: loadTimeData.getBoolean('askGCoBrowseEnabled'),
            },
            lensSearchTooltipShowing_: { type: Boolean },
            energyEffectEnabled_: {
                type: Boolean,
                reflect: true,
            },
            showSmartTabSharingTryItIph_: { type: Boolean },
            showSmartTabSharingDefaultOnIph_: { type: Boolean },
            composeboxHovered_: {
                type: Boolean,
                reflect: true,
            },
            isAimEligible_: {
                type: Boolean,
                reflect: true,
            },
            isDomContentLoaded_: {
                type: Boolean,
                reflect: true,
            },
            isInitialFrameLoad_: {
                type: Boolean,
                reflect: true,
            },
            onboardingTooltipShowing_: { type: Boolean },
        };
    }
    #energyEffectEnabled__accessor_storage = loadTimeData.getBoolean('energyEffectEnabled');
    get energyEffectEnabled_() { return this.#energyEffectEnabled__accessor_storage; }
    set energyEffectEnabled_(value) { this.#energyEffectEnabled__accessor_storage = value; }
    #showOnboardingTooltip__accessor_storage = loadTimeData.getBoolean('showOnboardingTooltip');
    get showOnboardingTooltip_() { return this.#showOnboardingTooltip__accessor_storage; }
    set showOnboardingTooltip_(value) { this.#showOnboardingTooltip__accessor_storage = value; }
    #showLensSearchTooltip__accessor_storage = loadTimeData.getBoolean('askGCoBrowseEnabled');
    get showLensSearchTooltip_() { return this.#showLensSearchTooltip__accessor_storage; }
    set showLensSearchTooltip_(value) { this.#showLensSearchTooltip__accessor_storage = value; }
    #lensSearchTooltipShowing__accessor_storage = false;
    get lensSearchTooltipShowing_() { return this.#lensSearchTooltipShowing__accessor_storage; }
    set lensSearchTooltipShowing_(value) { this.#lensSearchTooltipShowing__accessor_storage = value; }
    #showSmartTabSharingTryItIph__accessor_storage = false;
    get showSmartTabSharingTryItIph_() { return this.#showSmartTabSharingTryItIph__accessor_storage; }
    set showSmartTabSharingTryItIph_(value) { this.#showSmartTabSharingTryItIph__accessor_storage = value; }
    #showSmartTabSharingDefaultOnIph__accessor_storage = false;
    get showSmartTabSharingDefaultOnIph_() { return this.#showSmartTabSharingDefaultOnIph__accessor_storage; }
    set showSmartTabSharingDefaultOnIph_(value) { this.#showSmartTabSharingDefaultOnIph__accessor_storage = value; }
    #onboardingTooltipShowing__accessor_storage = false;
    get onboardingTooltipShowing_() { return this.#onboardingTooltipShowing__accessor_storage; }
    set onboardingTooltipShowing_(value) { this.#onboardingTooltipShowing__accessor_storage = value; }
    #userName__accessor_storage = loadTimeData.getString('friendlyZeroStateGaiaName');
    get userName_() { return this.#userName__accessor_storage; }
    set userName_(value) { this.#userName__accessor_storage = value; }
    #friendlyZeroStateTitleBeforeName__accessor_storage = loadTimeData.getString('friendlyZeroStateTitleBeforeName');
    get friendlyZeroStateTitleBeforeName_() { return this.#friendlyZeroStateTitleBeforeName__accessor_storage; }
    set friendlyZeroStateTitleBeforeName_(value) { this.#friendlyZeroStateTitleBeforeName__accessor_storage = value; }
    #friendlyZeroStateTitleAfterName__accessor_storage = loadTimeData.getString('friendlyZeroStateTitleAfterName');
    get friendlyZeroStateTitleAfterName_() { return this.#friendlyZeroStateTitleAfterName__accessor_storage; }
    set friendlyZeroStateTitleAfterName_(value) { this.#friendlyZeroStateTitleAfterName__accessor_storage = value; }
    browserProxy_ = BrowserProxyImpl.getInstance();
    #enableBasicMode__accessor_storage = loadTimeData.getBoolean('enableBasicMode');
    // Whether basic mode is enabled. If disabled, isInBasicMode_,
    // isNavigatingFromAiPage_, and pendingBasicMode_ will not be updated.
    get enableBasicMode_() { return this.#enableBasicMode__accessor_storage; }
    set enableBasicMode_(value) { this.#enableBasicMode__accessor_storage = value; }
    #enableBasicModeZOrder__accessor_storage = loadTimeData.getBoolean('enableBasicModeZOrder');
    get enableBasicModeZOrder_() { return this.#enableBasicModeZOrder__accessor_storage; }
    set enableBasicModeZOrder_(value) { this.#enableBasicModeZOrder__accessor_storage = value; }
    nlmUrlParam_ = loadTimeData.getString('nlmUrlParam');
    enableCustomNlmUi_ = loadTimeData.getBoolean('enableCustomNlmUi');
    #isLoadError__accessor_storage = !window.navigator.onLine;
    // Whether top-level navigation failed. Initialized based on online status
    // though top-level navigation could fail for numerous reasons.
    get isLoadError_() { return this.#isLoadError__accessor_storage; }
    set isLoadError_(value) { this.#isLoadError__accessor_storage = value; }
    #isAiPage__accessor_storage = loadTimeData.getBoolean('isAiPage');
    get isAiPage_() { return this.#isAiPage__accessor_storage; }
    set isAiPage_(value) { this.#isAiPage__accessor_storage = value; }
    #isUserSignedIn__accessor_storage = loadTimeData.getBoolean('isSignedIn');
    get isUserSignedIn_() { return this.#isUserSignedIn__accessor_storage; }
    set isUserSignedIn_(value) { this.#isUserSignedIn__accessor_storage = value; }
    #isAimEligible__accessor_storage = loadTimeData.getBoolean('isAimEligible');
    get isAimEligible_() { return this.#isAimEligible__accessor_storage; }
    set isAimEligible_(value) { this.#isAimEligible__accessor_storage = value; }
    #isLensOverlayShowing__accessor_storage = false;
    get isLensOverlayShowing_() { return this.#isLensOverlayShowing__accessor_storage; }
    set isLensOverlayShowing_(value) { this.#isLensOverlayShowing__accessor_storage = value; }
    #isOverlayOpenForAimVisualSearch__accessor_storage = false;
    get isOverlayOpenForAimVisualSearch_() { return this.#isOverlayOpenForAimVisualSearch__accessor_storage; }
    set isOverlayOpenForAimVisualSearch_(value) { this.#isOverlayOpenForAimVisualSearch__accessor_storage = value; }
    #isShownInTab__accessor_storage = loadTimeData.getBoolean('isShownInTab');
    // Indicates if in tab mode. Most start in a tab.
    get isShownInTab_() { return this.#isShownInTab__accessor_storage; }
    set isShownInTab_(value) { this.#isShownInTab__accessor_storage = value; }
    #darkMode__accessor_storage = loadTimeData.getBoolean('darkMode');
    get darkMode_() { return this.#darkMode__accessor_storage; }
    set darkMode_(value) { this.#darkMode__accessor_storage = value; }
    #isErrorDialogVisible__accessor_storage = false;
    get isErrorDialogVisible_() { return this.#isErrorDialogVisible__accessor_storage; }
    set isErrorDialogVisible_(value) { this.#isErrorDialogVisible__accessor_storage = value; }
    pendingUrl_ = '';
    #threadTitle__accessor_storage = '';
    get threadTitle_() { return this.#threadTitle__accessor_storage; }
    set threadTitle_(value) { this.#threadTitle__accessor_storage = value; }
    #isInBasicMode__accessor_storage = false;
    get isInBasicMode_() { return this.#isInBasicMode__accessor_storage; }
    set isInBasicMode_(value) { this.#isInBasicMode__accessor_storage = value; }
    #isInputHidden__accessor_storage = false;
    get isInputHidden_() { return this.#isInputHidden__accessor_storage; }
    set isInputHidden_(value) { this.#isInputHidden__accessor_storage = value; }
    #isErrorPageVisible__accessor_storage = false;
    get isErrorPageVisible_() { return this.#isErrorPageVisible__accessor_storage; }
    set isErrorPageVisible_(value) { this.#isErrorPageVisible__accessor_storage = value; }
    #isZeroState__accessor_storage = loadTimeData.getBoolean('isZeroState');
    // Whether no queries have been submitted in the current AIM thread. This
    // can be undefined on initial load to prevent the composebox from flashing
    // briefly before the zero state is rendered.
    get isZeroState_() { return this.#isZeroState__accessor_storage; }
    set isZeroState_(value) { this.#isZeroState__accessor_storage = value; }
    #enableNativeZeroStateSuggestions__accessor_storage = loadTimeData.getBoolean('enableNativeZeroStateSuggestions');
    get enableNativeZeroStateSuggestions_() { return this.#enableNativeZeroStateSuggestions__accessor_storage; }
    set enableNativeZeroStateSuggestions_(value) { this.#enableNativeZeroStateSuggestions__accessor_storage = value; }
    #inNlm__accessor_storage = false;
    get inNlm_() { return this.#inNlm__accessor_storage; }
    set inNlm_(value) { this.#inNlm__accessor_storage = value; }
    #isGhostLoaderVisible__accessor_storage = loadTimeData.getBoolean('isGhostLoaderVisible');
    get isGhostLoaderVisible_() { return this.#isGhostLoaderVisible__accessor_storage; }
    set isGhostLoaderVisible_(value) { this.#isGhostLoaderVisible__accessor_storage = value; }
    #useStratusDarkModeColors__accessor_storage = loadTimeData.getBoolean('useStratusDarkModeColors');
    get useStratusDarkModeColors_() { return this.#useStratusDarkModeColors__accessor_storage; }
    set useStratusDarkModeColors_(value) { this.#useStratusDarkModeColors__accessor_storage = value; }
    #isInputLocked__accessor_storage = false;
    get isInputLocked_() { return this.#isInputLocked__accessor_storage; }
    set isInputLocked_(value) { this.#isInputLocked__accessor_storage = value; }
    #isLoadingZeroStateFromResults__accessor_storage = false;
    get isLoadingZeroStateFromResults_() { return this.#isLoadingZeroStateFromResults__accessor_storage; }
    set isLoadingZeroStateFromResults_(value) { this.#isLoadingZeroStateFromResults__accessor_storage = value; }
    #forcedComposeboxBounds__accessor_storage = null;
    // The bounds of the composebox that are forced by the embedded page. These
    // bounds are relative to the <webview> and not the viewport.
    get forcedComposeboxBounds_() { return this.#forcedComposeboxBounds__accessor_storage; }
    set forcedComposeboxBounds_(value) { this.#forcedComposeboxBounds__accessor_storage = value; }
    #occluders__accessor_storage = null;
    // A list of occluders that are currently visible to the user. An occluder is
    // any element that is currently visible to the user that may be intersecting
    // and rendering over the composebox. This is a dynamic list send from the
    // embedded page, which allows the client to keep track and know which parts
    // of the composebox are not visible to the user, and therefore not clickable.
    get occluders_() { return this.#occluders__accessor_storage; }
    set occluders_(value) { this.#occluders__accessor_storage = value; }
    #composeboxHovered__accessor_storage = false;
    get composeboxHovered_() { return this.#composeboxHovered__accessor_storage; }
    set composeboxHovered_(value) { this.#composeboxHovered__accessor_storage = value; }
    #friendlyZeroStateSubtitle_accessor_storage = loadTimeData.getString('friendlyZeroStateSubtitle');
    get friendlyZeroStateSubtitle() { return this.#friendlyZeroStateSubtitle_accessor_storage; }
    set friendlyZeroStateSubtitle(value) { this.#friendlyZeroStateSubtitle_accessor_storage = value; }
    #friendlyZeroStateTitle_accessor_storage = loadTimeData.getString('friendlyZeroStateTitle');
    get friendlyZeroStateTitle() { return this.#friendlyZeroStateTitle_accessor_storage; }
    set friendlyZeroStateTitle(value) { this.#friendlyZeroStateTitle_accessor_storage = value; }
    #isDomContentLoaded__accessor_storage = false;
    get isDomContentLoaded_() { return this.#isDomContentLoaded__accessor_storage; }
    set isDomContentLoaded_(value) { this.#isDomContentLoaded__accessor_storage = value; }
    #isInitialFrameLoad__accessor_storage = true;
    // Tracks whether the frame is loading for the very first time to prevent
    // double animations.
    get isInitialFrameLoad_() { return this.#isInitialFrameLoad__accessor_storage; }
    set isInitialFrameLoad_(value) { this.#isInitialFrameLoad__accessor_storage = value; }
    // Tracks whether the frame is currently loading. Needed to avoid race
    // condition while awaiting isAiPage.
    isFrameLoading = false;
    isSubmittingFromComposebox_ = false;
    listenerIds_ = [];
    eventTracker_ = new EventTracker();
    commonSearchParams_ = null;
    postMessageHandler_ = null;
    signInDomains_ = loadTimeData.getString('contextualTasksSignInDomains').split(',');
    host_ = null;
    // Whether the composebox jump fix is enabled. This fix hides the composebox
    // until the server gives the embedded page gives the initial bounds for the
    // composebox.
    enableComposeboxJumpFix_ = loadTimeData.getBoolean('enableComposeboxJumpFix');
    enableGhostLoader_ = loadTimeData.getBoolean('enableGhostLoader');
    // A callback to allow tests to wait until the popstate handler in this class
    // has finished running.
    popStateFinishedCallbackForTesting_ = null;
    // A callback to allow tests to wait until the loadstart handler in this class
    // has finished running.
    onLoadStartFinishedCallbackForTesting_ = null;
    forceBasicModeIfOpeningThreadHistory_ = loadTimeData.getBoolean('forceBasicModeIfOpeningThreadHistory');
    // This is needed to keep navigations between non-AIM pages from triggering
    // the input hide/restore callbacks.
    isNavigatingFromAiPage_ = false;
    // Tracks the basic mode state before a navigation occurs. This is used to
    // restore the basic mode state after the navigation, to ensure that if
    // already in basic mode, the user is returned to basic mode.
    pendingBasicMode_ = null;
    // Tracks the last thread frame load start event. Is null if the thread frame
    // is not currently loading. This is used to track if the thread frame
    // actually loaded or if the load was aborted. Without this, we can't
    // distinguish between the two cases, since load start will always be called
    // even if the load is aborted and the frame therefore never changes.
    lastThreadFrameLoadStartEvent_ = null;
    contextManagementInComposeboxEnabled_ = loadTimeData.getBoolean('contextManagementInComposeboxEnabled');
    constructorStartTime_;
    isFirstLoadCommit_ = true;
    constructor() {
        super();
        this.constructorStartTime_ = performance.now();
    }
    updateThemeFromUrl(url) {
        const csParam = url.searchParams.get('cs');
        if (csParam === '0') {
            this.darkMode_ = false;
        }
        else if (csParam === '1') {
            this.darkMode_ = true;
        }
        this.updateBackgroundColor_();
        this.updateCommonSearchParams();
    }
    get composebox_() {
        // 
        return this.$.composebox || null;
        // 
        // 
    }
    async connectedCallback() {
        super.connectedCallback();
        this.updateBackgroundColor_();
        // 
        this.shadowRoot.addEventListener('context-menu-closed', () => {
            this.hideHelpBubble('ContextualTasksUI::kSmartTabSharingMenuItemElementId');
        });
        // 
        // Record the WebUI URL in case one of the events below fires and changes
        // it.
        const webUiUrlOnLoad = new URL(window.location.href);
        this.host_ = webUiUrlOnLoad.searchParams.get(CHROME_HOST_PARAM_KEY);
        if (!this.host_ && loadTimeData.valueExists('chrome_host')) {
            this.host_ = loadTimeData.getString('chrome_host');
        }
        // Relying on C++ to provide the correct host via getUrlForTask
        const callbackRouter = this.browserProxy_.callbackRouter;
        this.listenerIds_ = [
            callbackRouter.onSidePanelStateChanged.addListener(() => this.updateSidePanelState()),
            callbackRouter.turnOnSmartTabSharing.addListener(() => {
                // 
                const menu = this.getContextualActionMenu();
                if (menu) {
                    menu.setSmartTabSharingToggle(true);
                }
                // 
            }),
            callbackRouter.setThreadTitle.addListener(title => {
                this.threadTitle_ = title;
                document.title = title || loadTimeData.getString('title');
            }),
            callbackRouter.onAiPageStatusChanged.addListener(isAiPage => {
                this.isAiPage_ = isAiPage;
            }),
            callbackRouter.postMessageToWebview.addListener(this.postMessageToWebview.bind(this)),
            callbackRouter.onHandshakeComplete.addListener(this.onHandshakeComplete.bind(this)),
            // TODO(crbug.com/474359572): Rename this to be more descriptive of what
            // it actually does.
            callbackRouter.hideInput.addListener(() => {
                this.isInputHidden_ = true;
            }),
            callbackRouter.restoreInput.addListener(() => {
                this.isInputHidden_ = false;
            }),
            callbackRouter.enterBasicMode.addListener(() => {
                if (!this.enableBasicMode_) {
                    return;
                }
                if (this.isNavigatingFromAiPage_) {
                    this.pendingBasicMode_ = true;
                    return;
                }
                this.isInBasicMode_ = true;
            }),
            callbackRouter.exitBasicMode.addListener(() => {
                if (!this.enableBasicMode_) {
                    return;
                }
                if (this.isNavigatingFromAiPage_) {
                    this.pendingBasicMode_ = false;
                    return;
                }
                this.isInBasicMode_ = false;
            }),
            callbackRouter.injectInput.addListener(async (input) => {
                await this.composebox_?.injectInput(input);
            }),
            callbackRouter.removeInjectedInput.addListener(fileToken => {
                this.composebox_?.deleteFile(fileToken);
            }),
            callbackRouter.setTaskDetails.addListener(updateTaskDetailsInUrl),
            callbackRouter.onZeroStateChange.addListener(isZeroState => {
                const wasZeroState = this.isZeroState_;
                this.isZeroState_ = isZeroState;
                // Navigation goes from a non-zero state page to a zero state page.
                // In this case, we need to trigger the submit cleanup if the submission
                // is coming from the webview since webview does not trigger a cleanup
                // on its own.
                if (this.contextManagementInComposeboxEnabled_ && wasZeroState &&
                    !isZeroState) {
                    if (!this.isSubmittingFromComposebox_) {
                        this.composebox_?.getComposebox().submitCleanup();
                    }
                    this.isSubmittingFromComposebox_ = false;
                }
                // If we just changed to zero state, that means
                // it is a new thread or new AIM page. Otherwise,
                // we are not in zero state anymore, or not in an AIM URL. In
                // both thread/AIM cases for zero state, we clear input.
                if (isZeroState) {
                    this.forceComposeboxFocus();
                    // Reset the forced composebox bounds since the zero state position
                    if (!this.shouldSetForceComposeboxBounds_()) {
                        this.forcedComposeboxBounds_ = null;
                    }
                }
            }),
            callbackRouter.setInNlm.addListener((inNlm) => {
                this.inNlm_ = inNlm;
            }),
            callbackRouter.onLensOverlayStateChanged.addListener((isOverlayShowing, isOverlayOpenForAimVisualSearch) => {
                this.isLensOverlayShowing_ = isOverlayShowing;
                this.isOverlayOpenForAimVisualSearch_ =
                    isOverlayOpenForAimVisualSearch;
            }),
            callbackRouter.showErrorPage.addListener(() => {
                this.isErrorPageVisible_ = true;
            }),
            callbackRouter.hideErrorPage.addListener(() => {
                this.isErrorPageVisible_ = false;
                this.maybeLoadPendingUrl_();
            }),
            callbackRouter.showOauthErrorDialog.addListener(() => {
                this.isErrorDialogVisible_ = true;
            }),
            callbackRouter.updateComposeboxPosition.addListener(this.onUpdateComposeboxPosition_.bind(this)),
            callbackRouter.lockInput.addListener(() => {
                this.isInputLocked_ = true;
            }),
            callbackRouter.unlockInput.addListener(() => {
                this.isInputLocked_ = false;
            }),
            callbackRouter.showSmartTabSharingTryItIph.addListener(() => {
                this.showSmartTabSharingTryItIph_ = true;
            }),
            callbackRouter.showSmartTabSharingDefaultOnIph.addListener(() => {
                this.showSmartTabSharingDefaultOnIph_ = true;
            }),
        ];
        // Track the tooltip visibility events fired from the composebox.
        this.eventTracker_.add(window, 'update-tooltip-visibility', () => this.updateTooltipVisibility_());
        this.eventTracker_.add(window, 'popstate', async () => {
            // The back button may pop state that was pushed by a task change. If that
            // is the case, fetch the URL for the task ID and load that in the frame.
            const taskUuid = new URLSearchParams(location.search).get(CHROME_TASK_PARAM_KEY);
            if (taskUuid) {
                const { url } = await this.browserProxy_.handler.getUrlForTask({ value: taskUuid });
                // Do nothing if the app element is no longer attached to the page. This
                // can occur in tests where awaiting the call above will delay the rest
                // of this handler and affect other tests in the suite.
                if (!this.isConnected) {
                    return;
                }
                if (this.isShownInTab_) {
                    recordAction('ContextualTasks.HistoryNavigation.UserAction.NavigatedInFullTab');
                }
                this.browserProxy_.handler.setTaskId({ value: taskUuid });
                this.$.threadFrame.src = url;
                // Allow tests to wait for this callback to complete.
                if (this.popStateFinishedCallbackForTesting_) {
                    this.popStateFinishedCallbackForTesting_();
                }
            }
        });
        this.updateSidePanelState();
        // Fetch the initial common search params.
        this.updateCommonSearchParams();
        // Listeners for ghost loader. Registered via eventTracker_ so that
        // removeThreadFrameListenersForTesting() can detach them.
        this.eventTracker_.add(this.$.threadFrame, 'loadstart', this.onThreadFrameLoadStart.bind(this));
        this.eventTracker_.add(this.$.threadFrame, 'loadredirect', this.onThreadFrameLoadRedirect.bind(this));
        this.eventTracker_.add(this.$.threadFrame, 'loadabort', this.onThreadFrameLoadAbort.bind(this));
        this.eventTracker_.add(this.$.threadFrame, 'loadcommit', this.onThreadFrameLoadCommit.bind(this));
        this.eventTracker_.add(this.$.threadFrame, 'contentload', this.onThreadFrameContentLoad.bind(this));
        this.eventTracker_.add(window, 'message', (event) => {
            if (event.data === 'domContentLoaded') {
                this.isDomContentLoaded_ = true;
                // Play the zero state animations, unhide the composebox/header,
                // and focus the composebox.
                if (this.isZeroState_ && this.isShownInTab_) {
                    this.playZeroStateAnimations_();
                    this.forceComposeboxFocus();
                }
            }
        });
        // Setup the webview request overrides before loading the first URL.
        this.setupWebviewRequestOverrides();
        // 
        // Handle newwindow events with mock webviews.
        if (loadTimeData.getBoolean('windowTrackingEnabled')) {
            new WindowManager(this.$.threadFrame);
        }
        // 
        // Check if the URL that loaded this page has a task attached to it. If it
        // does, we'll use the tasks URL to load the embedded page.
        const taskUuid = webUiUrlOnLoad.searchParams.get(CHROME_TASK_PARAM_KEY);
        let threadUrl = '';
        if (taskUuid) {
            const { url } = await this.browserProxy_.handler.getUrlForTask({ value: taskUuid });
            this.browserProxy_.handler.setTaskId({ value: taskUuid });
            const aiPageParams = new URLSearchParams(new URL(url).search);
            this.browserProxy_.handler.setThreadTitle(aiPageParams.get('q') || '');
            threadUrl = url;
        }
        else {
            const { url } = await this.browserProxy_.handler.getThreadUrl();
            threadUrl = url;
        }
        const threadUrlAsUrl = new URL(threadUrl);
        this.updateThemeFromUrl(threadUrlAsUrl);
        // If the thread URL has parameters to open history, set basic mode.
        if (this.enableBasicMode_ && this.hasThreadHistoryParams(threadUrlAsUrl) &&
            this.forceBasicModeIfOpeningThreadHistory_) {
            this.isInBasicMode_ = true;
        }
        // If the "open_history" param has any value, open the history panel.
        if (webUiUrlOnLoad.searchParams.has('open_history')) {
            threadUrlAsUrl.searchParams.set('atvm', '1');
            // Remove the param so subsequent loads don't show history again.
            const url = new URL(window.location.href);
            url.searchParams.delete('open_history');
            window.history.replaceState({}, '', url.href);
        }
        if (taskUuid) {
            const { isPendingErrorPage } = await this.browserProxy_.handler.isPendingErrorPage({ value: taskUuid });
            this.isErrorPageVisible_ = isPendingErrorPage;
        }
        // Allow URLs with the debug param set to exit the webui. This param is
        // the same one used to prevent aim urls from entering the webui, so when
        // set, it will be attached to the thread URL which will keep the user out
        // of this UI.
        if (hasExitCobrowseParam(webUiUrlOnLoad)) {
            window.location.href = threadUrlAsUrl.href;
            return;
        }
        // Check if the initial render should be zero state.
        const { isZeroState } = await this.browserProxy_.handler.isZeroState(threadUrlAsUrl.href);
        const { isAiPage } = await this.browserProxy_.handler.isAiPage(threadUrlAsUrl.href);
        this.isZeroState_ = isZeroState;
        this.isAiPage_ = isAiPage;
        this.inNlm_ = this.checkInNlm_(threadUrlAsUrl);
        // Add this fallback: If the DOM already loaded while we were awaiting, play
        // it now!
        if (this.isZeroState_ && this.isDomContentLoaded_ && this.isShownInTab_) {
            this.playZeroStateAnimations_();
            this.forceComposeboxFocus();
        }
        else if (this.isZeroState_ && !this.isShownInTab_) {
            this.playZeroStateAnimations_();
        }
        // The thread URL is considered pending (not loaded immediately in the
        // webview) until oauth tokens are received from the WebUI controller. This
        // prevents situations where the user is technically signed out of the
        // embedded frame and unable to save or access existing data.
        this.pendingUrl_ = threadUrlAsUrl.href;
        this.maybeLoadPendingUrl_();
    }
    disconnectedCallback() {
        super.disconnectedCallback();
        this.listenerIds_.forEach(id => this.browserProxy_.callbackRouter.removeListener(id));
        this.removeWebviewRequestOverrides();
        this.eventTracker_.removeAll();
    }
    firstUpdated() {
        this.postMessageHandler_ =
            new PostMessageHandler(this.$.threadFrame, this.browserProxy_);
        this.updateTooltipVisibility_();
        const composebox = this.composebox_;
        if (!composebox) {
            return;
        }
        this.postMessageHandler_.setInputPlateBoundsUpdateCallback(this.onInputPlateBoundsUpdate_.bind(this));
        this.postMessageHandler_.setInputStateUpdateCallback((toolMode, modelMode, data) => {
            this.composebox_?.onInputStateUpdate(toolMode, modelMode, data);
        });
        this.eventTracker_.add(composebox, 'context-menu-opened', () => this.onComposeboxContextMenuOpened_());
        this.eventTracker_.add(composebox, 'composebox-submit', () => {
            this.isSubmittingFromComposebox_ = true;
        });
        this.eventTracker_.add(composebox, 'mouseenter', () => {
            this.composeboxHovered_ = true;
        });
        this.eventTracker_.add(composebox, 'mouseleave', () => {
            this.composeboxHovered_ = false;
        });
        this.eventTracker_.add(composebox, 'composebox-height-update', (e) => {
            // TODO(crbug.com/483737358): Sending an object instead of a proto
            // is a temporary solution to unblock the prototype. Remove this
            // method once the proto is implemented on the webview side.
            assert(this.postMessageHandler_);
            this.postMessageHandler_.sendObjectMessage({
                type: 'composebox-height-update',
                height: e.detail.height,
            });
            // Update the height of the forced composebox bounds if it is set.
            if (this.forcedComposeboxBounds_) {
                this.forcedComposeboxBounds_ = {
                    ...this.forcedComposeboxBounds_,
                    height: e.detail.height,
                    top: this.forcedComposeboxBounds_.bottom - e.detail.height,
                };
            }
            else {
                this.requestUpdate();
            }
        });
        // 
    }
    updated(changedProperties) {
        super.updated(changedProperties);
        const changedPrivateProperties = changedProperties;
        // Fetch the common search params before setting up the request overrides.
        // TODO(crbug.com/463729504): Add checking to see if dark mode changed.
        if (changedPrivateProperties.has('isShownInTab_')) {
            this.updateCommonSearchParams();
        }
    }
    // 
    getContextualActionMenu() {
        const wrapper = this.shadowRoot?.querySelector('contextual-tasks-composebox');
        if (!wrapper) {
            return null;
        }
        const innerComposebox = wrapper.shadowRoot?.querySelector('#composebox');
        if (!innerComposebox) {
            return null;
        }
        const entrypoint = innerComposebox.shadowRoot
            ?.querySelector('#contextEntrypoint');
        if (!entrypoint) {
            return null;
        }
        return entrypoint.shadowRoot?.querySelector('#menu') ||
            null;
    }
    // 
    updateTooltipVisibility_() {
        // Tooltip not supported on Android. Therefore, make calls to this method
        // a no-op.
        // 
        const onboardingTooltip = this.shadowRoot?.querySelector('#onboardingTooltip') || null;
        const lensSearchTooltip = this.shadowRoot?.querySelector('#lensSearchTooltip') || null;
        const composeboxContainer = this.composebox_;
        if (!composeboxContainer) {
            return;
        }
        const crComposebox = this.composebox_.getComposebox();
        if (!crComposebox) {
            return;
        }
        if (onboardingTooltip) {
            onboardingTooltip.updateTooltipVisibility(composeboxContainer, crComposebox);
            this.onboardingTooltipShowing_ = onboardingTooltip.shouldShow;
        }
        if (lensSearchTooltip) {
            lensSearchTooltip.updateTooltipVisibility(composeboxContainer, crComposebox);
            this.lensSearchTooltipShowing_ = lensSearchTooltip.shouldShow;
        }
        // 
    }
    onOnboardingTooltipDismissed_() {
        this.onboardingTooltipShowing_ = false;
    }
    onLensSearchTooltipDismissed_() {
        this.lensSearchTooltipShowing_ = false;
    }
    playZeroStateAnimations_() {
        this.clearZeroStateAnimations_();
        this.classList.add('play-zero-state');
        this.composebox_?.classList.add('play-zero-state');
        this.composebox_?.startExpandAnimation();
    }
    clearZeroStateAnimations_() {
        this.classList.remove('play-zero-state');
        this.composebox_?.classList.remove('play-zero-state');
    }
    onComposeboxContextMenuOpened_() {
        // 
        setTimeout(() => {
            const menu = this.getContextualActionMenu();
            // Since a separate IPH "Try It" promo may turn the STS feature on, make
            // sure it is not already on before promoting with the help bubble.
            if (menu && !menu.smartTabSharingActive) {
                const menuItem = menu.shadowRoot?.querySelector('#shareTabsTrigger');
                if (menuItem) {
                    const rect = menuItem.getBoundingClientRect();
                    const floatingAnchor = this.shadowRoot.querySelector('#iphMenuSmartTabSharingAnchor');
                    if (floatingAnchor) {
                        floatingAnchor.style.left = `${rect.left}px`;
                        floatingAnchor.style.top = `${rect.top}px`;
                        floatingAnchor.style.width = `${rect.width}px`;
                        floatingAnchor.style.height = `${rect.height}px`;
                        // This is necessary to give the floating anchor time for layout
                        // when the chrome://user-education-internals Launch button has
                        // been pressed. Without the wait, it pops too soon out of place.
                        setTimeout(() => {
                            this.registerHelpBubble('ContextualTasksUI::kSmartTabSharingMenuItemElementId', '#iphMenuSmartTabSharingAnchor', {
                                fixed: true,
                                containerElement: menu.getDialog(),
                            });
                            this.browserProxy_.handler.onContextMenuOpened();
                        }, 100);
                    }
                }
            }
        }, 200);
        // 
    }
    setStyleVariable(variable, value) {
        this.composebox_?.style.setProperty(variable, value);
    }
    onThreadFrameLoadStart(e) {
        const ev = e;
        // If is from inner iframe and not from main webview URL:
        if (!ev.isTopLevel) {
            return;
        }
        // If a thread URL is loaded with the debug param to exit coborowse,
        // navigate the tab to that URL.
        if (hasExitCobrowseParam(new URL(ev.url))) {
            window.location.href = ev.url;
            return;
        }
        this.isLoadError_ = !window.navigator.onLine;
        // Stash the last thread frame load start event. This will be used once the
        // navigation is determined to have aborted or not.
        this.lastThreadFrameLoadStartEvent_ = ev;
    }
    onThreadFrameLoadRedirect(e) {
        const ev = e;
        // If is from inner iframe and not from main webview URL:
        if (!ev.isTopLevel) {
            return;
        }
        this.maybeOnThreadFrameTopLevelNavigation(ev.oldUrl);
    }
    onThreadFrameLoadCommit(e) {
        const ev = e;
        // If is from inner iframe and not from main webview URL:
        if (!ev.isTopLevel) {
            return;
        }
        if (this.isFirstLoadCommit_) {
            this.isFirstLoadCommit_ = false;
            const latencyMs = Math.round(performance.now() - this.constructorStartTime_);
            chrome.metricsPrivate.recordMediumTime('ContextualTasks.OAuth.StartToCommitLatency', latencyMs);
        }
        this.updateBasicModeAfterNavigation();
        this.maybeOnThreadFrameTopLevelNavigation(ev.url);
        // 
    }
    onThreadFrameContentLoad() {
        this.isFrameLoading = false;
        this.isLoadingZeroStateFromResults_ = false;
        this.setIsGhostLoaderVisible(false);
        this.updateBasicModeAfterNavigation();
    }
    async onThreadFrameLoadAbort(ev) {
        const e = ev;
        // It is possible for a redirect to abort a load before committing. To
        // prevent ghost loader flickers in this case, only hide the ghost loader if
        // the frame was previously set to loading.
        if (this.isFrameLoading) {
            this.setIsGhostLoaderVisible(false);
        }
        this.isFrameLoading = false;
        this.isLoadingZeroStateFromResults_ = false;
        // The navigation aborted, so reset the last thread frame load start event,
        // since the frame is no longer loading. Without this, every
        // onThreadFrameLoadStart event will be treated as a navigation even when
        // the navigation has aborted.
        this.lastThreadFrameLoadStartEvent_ = null;
        // Navigations delegated to external applications fire a load abort event.
        // Check if the embedded page is showing an error document.
        if (e.isTopLevel) {
            const { isErrorDocument } = await this.browserProxy_.handler.isEmbeddedPageErrorDocument();
            if (isErrorDocument) {
                // TODO(crbug.com/489713572): Potentially query autocomplete when the
                // error is resolved and the page reloads
                this.isLoadError_ = true;
            }
        }
        this.updateBasicModeAfterNavigation();
    }
    maybeOnThreadFrameTopLevelNavigation(navigationUrl) {
        // Since the navigation has redirected/committed instead of aborted, threat
        // this as a top level navigation. Reset the last thread frame load start
        // event to prevent calling onThreadFrameTopLevelNavigation again.
        if (this.lastThreadFrameLoadStartEvent_ &&
            this.lastThreadFrameLoadStartEvent_.url === navigationUrl) {
            const event = this.lastThreadFrameLoadStartEvent_;
            this.lastThreadFrameLoadStartEvent_ = null;
            const url = new URL(navigationUrl);
            this.updateThemeFromUrl(url);
            this.onThreadFrameTopLevelNavigation(event);
        }
    }
    async onThreadFrameTopLevelNavigation(e) {
        const ev = e;
        // Reset the composebox bounds and the occluders since the embedded page is
        // reloading.
        this.forcedComposeboxBounds_ = null;
        this.occluders_ = null;
        this.isInputHidden_ = false;
        this.isDomContentLoaded_ = false;
        // Set frame loading to true initially to avoid race conditions.
        this.isFrameLoading = true;
        const wasAiPage = this.isAiPage_;
        const wasZeroState = this.isZeroState_;
        const { isAiPage } = await this.browserProxy_.handler.isAiPage(ev.url);
        const { isZeroState } = await this.browserProxy_.handler.isZeroState(ev.url);
        // If the frame is no longer loading after waiting for isAiPage,
        // then exit early to prevent racind.
        if (!this.isFrameLoading) {
            if (this.onLoadStartFinishedCallbackForTesting_) {
                this.onLoadStartFinishedCallbackForTesting_();
            }
            return;
        }
        if (isAiPage && isZeroState) {
            this.isZeroState_ = true;
            if (!this.isInitialFrameLoad_) {
                // Tab instances synchronize animations with the web document lifecycle
                // (waiting for DOM load), while side panel instances trigger them
                // immediately to match the native UI transition.
                if (this.isShownInTab_) {
                    if (this.isDomContentLoaded_) {
                        this.playZeroStateAnimations_();
                    }
                }
                else {
                    this.playZeroStateAnimations_();
                }
            }
        }
        if (!wasZeroState && isZeroState) {
            this.isLoadingZeroStateFromResults_ = true;
        }
        if (!isAiPage) {
            // If this is not an AI page, show the ghost loader.
            // Update the isAiPage_ property so the ghost loader doesn't jump when
            // the property is updated later.
            this.isAiPage_ = isAiPage;
            this.setIsGhostLoaderVisible(true);
        }
        else if (this.enableBasicMode_ && wasAiPage) {
            // Since this is a navigation from one AI page to another,
            // enter basic mode to avoid flickering between navigations.
            this.isNavigatingFromAiPage_ = true;
            // In the case where this basic mode is currently set to false,
            // set the pending basic mode to false right away to avoid flickering when
            // loading the first AIM page (like from a contextual query that directly
            // opens the side panel). Without this call, the first loaded AIM page
            // will load with basic mode enabled and will wait for the AIM page to
            // load and then complete the handshake in order to re-disable basic mode.
            if (!this.isInBasicMode_) {
                this.pendingBasicMode_ = false;
            }
            this.isInBasicMode_ = true;
        }
        this.isInitialFrameLoad_ = false;
        if (this.onLoadStartFinishedCallbackForTesting_) {
            this.onLoadStartFinishedCallbackForTesting_();
        }
    }
    /* Adjust composebox based on server notifications. Negatives are used if
     * server wants to change marginTop, marginRight.
     */
    onUpdateComposeboxPosition_(position) {
        if (position.maxWidth !== null) {
            this.setStyleVariable('--max-composebox-width', `${position.maxWidth}px`);
        }
        if (position.maxHeight !== null) {
            // Set contextual task's composebox max-height.
            this.setStyleVariable('--max-composebox-height', `${position.maxHeight}px`);
            // Set cr-component's composebox max-height.
            this.setStyleVariable('--cr-composebox-max-height', `${position.maxHeight}px`);
        }
        if (position.marginBottom !== null) {
            this.setStyleVariable('--composebox-margin-bottom', `${position.marginBottom}px`);
        }
        if (position.marginLeft !== null) {
            this.setStyleVariable('--composebox-margin-left', `${position.marginLeft}px`);
        }
    }
    onInputPlateBoundsUpdate_(inputRect, occluders) {
        if (inputRect !== undefined) {
            const composebox = this.composebox_;
            const currentHeight = composebox.offsetHeight;
            const currentUrl = this.$.threadFrame.src;
            if (currentUrl.includes(AIOH_URL_IDENTIFIER) &&
                this.forcedComposeboxBounds_ === null && !this.isZeroState_) {
                this.playComposeboxAiohFadeInAnimation_();
            }
            if (currentHeight !== inputRect.height) {
                // If the height that the client reports for the composebox is different
                // from the height that the server is reporting, update the server.
                assert(this.postMessageHandler_);
                this.postMessageHandler_.sendObjectMessage({
                    type: 'composebox-height-update',
                    height: currentHeight,
                });
            }
            // Since the height is controlled client side and the composebox grows
            // updwards, set the top of the rect to match the current height to avoid
            // miscalculations in the clip path.
            const wasHidden = this.isComposeboxHidden_();
            if (this.shouldSetForceComposeboxBounds_()) {
                this.forcedComposeboxBounds_ = {
                    ...inputRect,
                    height: currentHeight,
                    top: inputRect.bottom - currentHeight,
                };
                // Only focus the input if the composebox transitions from hidden to
                // visible. This prevents focus-stealing on subsequent bounds updates
                // (e.g., when the composebox changes height).
                if (wasHidden && !this.isComposeboxHidden_()) {
                    this.updateComplete.then(() => {
                        this.composebox_?.tryFocus();
                    });
                }
            }
            else {
                this.forcedComposeboxBounds_ = null;
            }
        }
        if (occluders !== undefined) {
            this.occluders_ = occluders;
        }
    }
    playComposeboxAiohFadeInAnimation_() {
        const composebox = this.composebox_;
        if (!composebox) {
            return;
        }
        composebox.animate([
            { opacity: 0 },
            { opacity: 1 },
        ], {
            duration: 300,
            delay: 100,
            easing: 'cubic-bezier(0, 0, 0, 1)',
            fill: 'both',
        });
    }
    isComposeboxHidden_() {
        if (!this.isAimEligible_) {
            return true;
        }
        // Stay hidden until the first isZeroState_ value is determined to prevent
        // the composebox from flickering in.
        if (this.isZeroState_ === undefined) {
            return true;
        }
        if (this.isInputHidden_) {
            return true;
        }
        // If using the basic mode without z-ordering, if in basic mode, hide the
        // composebox.
        if (this.enableBasicMode_ && this.isInBasicMode_ &&
            !this.enableBasicModeZOrder_) {
            return true;
        }
        // If in NLM mode, only show the composebox if the forcedComposeboxBounds
        // are set. We expect NLM mode to send us bounds.
        if (this.inNlm_ && !this.forcedComposeboxBounds_) {
            return true;
        }
        // If on an AI page and not the zero state, only show the composebox if the
        // forcedcomposeboxBounds are set. No-op if the feature flag is not enabled.
        if (this.enableComposeboxJumpFix_ && this.isAiPage_ && !this.isZeroState_ &&
            !this.forcedComposeboxBounds_) {
            return true;
        }
        // In all other cases, show the composebox.
        return false;
    }
    // Helper to focus the composebox, even if it is transitioning from hidden.
    forceComposeboxFocus() {
        this.composebox_?.clearInputAndFocus();
        this.updateComplete.then(() => {
            this.composebox_?.tryFocus();
        });
    }
    isComposeboxHeaderWrapperHidden_() {
        if (this.isComposeboxHidden_()) {
            return true;
        }
        return (this.enableBasicMode_ && this.isInBasicMode_ &&
            !this.enableBasicModeZOrder_) ||
            this.inNlm_;
    }
    checkInNlm_(url) {
        if (!this.enableCustomNlmUi_) {
            return false;
        }
        return url.searchParams.has(this.nlmUrlParam_);
    }
    shouldSetForceComposeboxBounds_() {
        return !this.isZeroState_ || this.inNlm_;
    }
    getComposeboxBoundsStyles() {
        if ((this.isZeroState_ && !this.inNlm_) || !this.forcedComposeboxBounds_) {
            return '';
        }
        // Since this.forcedComposeboxBounds_ is relative to the <webview>, and
        // the composebox is relative to the viewport, adjust the bounds to be
        // relative to the viewport.
        const frameRect = this.$.threadFrame.getBoundingClientRect();
        const relativeRect = {
            top: frameRect.top + this.forcedComposeboxBounds_.top,
            left: frameRect.left + this.forcedComposeboxBounds_.left,
            width: this.forcedComposeboxBounds_.width,
            height: this.forcedComposeboxBounds_.height,
            right: frameRect.left + this.forcedComposeboxBounds_.right,
            bottom: frameRect.top + this.forcedComposeboxBounds_.bottom,
        };
        // Do not set height, since the expanding of the composebox is dynamic.
        // Set the bottom of the rect instead of the top to allow the composebox to
        // expand upwards.
        const style = [
            `--composebox-margin-bottom: 0;`, // Need to remove margin on the child
            // container.
            `position: ${this.inNlm_ ? 'fixed' : 'absolute'};`,
            `bottom: ${window.innerHeight - relativeRect.bottom}px;`,
            `left: ${relativeRect.left}px;`,
            `width: ${relativeRect.width}px;`,
            `margin: 0;`,
            `max-width: none;`,
            `min-width: 0;`,
        ];
        return style.join(' ');
    }
    getBannerPromoBoundsStyles_() {
        if ((this.isZeroState_ && !this.inNlm_) || !this.forcedComposeboxBounds_) {
            return '';
        }
        const frameRect = this.$.threadFrame.getBoundingClientRect();
        const relativeRectTop = frameRect.top + this.forcedComposeboxBounds_.top;
        const relativeRectLeft = frameRect.left + this.forcedComposeboxBounds_.left;
        const width = this.forcedComposeboxBounds_.width;
        const bottomGap = 8;
        return [
            `position: ${this.inNlm_ ? 'fixed' : 'absolute'};`,
            `bottom: ${window.innerHeight - relativeRectTop + bottomGap}px;`,
            `left: ${relativeRectLeft}px;`,
            `width: ${width}px;`,
            `margin: 0;`,
            `max-width: none;`,
            `min-width: 0;`,
        ].join(' ');
    }
    getThreadFrameStyles() {
        if (this.occluders_ == null) {
            return '';
        }
        const composebox = this.composebox_;
        if (!composebox) {
            return '';
        }
        // If the forced composebox bounds are set, use those since its cheaper
        // than calling getBoundingClientRect();
        const composeboxBounds = this.forcedComposeboxBounds_ ??
            this.getComposeboxBoundsRelativeToThreadFrame_();
        const frameRect = this.$.threadFrame.getBoundingClientRect();
        // If occluders are present, set the clip path and a z-index that ensures
        // the thread frame is above the occluders.
        const roundedClipPathEnabled = loadTimeData.getBoolean('roundedClipPathEnabled');
        const borderRadius = roundedClipPathEnabled ? COMPOSEBOX_BORDER_RADIUS_PX : 0;
        const result = getNonOccludedClipPath(composeboxBounds, this.occluders_, OCCLUDER_EXTRA_PADDING_PX, frameRect.width, frameRect.height, borderRadius) +
            'z-index: 100;';
        return result;
    }
    getComposeboxBoundsRelativeToThreadFrame_() {
        const composebox = this.composebox_;
        if (!composebox) {
            return null;
        }
        const frameRect = this.$.threadFrame.getBoundingClientRect();
        const composeboxRect = composebox.getBoundingClientRect();
        return {
            top: composeboxRect.top - frameRect.top,
            left: composeboxRect.left - frameRect.left,
            width: composeboxRect.width,
            height: composeboxRect.height,
            right: composeboxRect.right - frameRect.left,
            bottom: composeboxRect.bottom - frameRect.top,
        };
    }
    async onNewThreadClick_() {
        recordAction('ContextualTasks.WebUI.UserAction.OpenNewThread');
        const { url } = await this.browserProxy_.handler.getThreadUrl();
        const newThreadUrl = new URL(url);
        const currentUrl = new URL(this.$.threadFrame.src);
        const source = currentUrl.searchParams.get('source');
        if (source) {
            newThreadUrl.searchParams.set('source', source);
        }
        const aep = currentUrl.searchParams.get('aep');
        if (aep) {
            newThreadUrl.searchParams.set('aep', aep);
        }
        this.$.threadFrame.src = newThreadUrl.href;
        const composebox = this.composebox_;
        if (composebox) {
            composebox.startExpandAnimation();
            composebox.clearInputAndFocus();
        }
    }
    get isLoadErrorForTesting() {
        return this.isLoadError_;
    }
    getEnableNativeZeroStateSuggestionsForTesting() {
        return this.enableNativeZeroStateSuggestions_;
    }
    setEnableNativeZeroStateSuggestionsForTesting(enable) {
        this.enableNativeZeroStateSuggestions_ = enable;
    }
    setIsInBasicModeForTesting(isInBasicMode) {
        this.isInBasicMode_ = isInBasicMode;
    }
    getForcedComposeboxBoundsForTesting() {
        return this.forcedComposeboxBounds_;
    }
    postMessageToWebview(message) {
        assert(this.postMessageHandler_);
        this.postMessageHandler_.sendMessage(new Uint8Array(message));
    }
    maybeLoadPendingUrl_() {
        // If all the data needed to make the initial request is available, load
        // the pending URL.
        if (this.pendingUrl_ && this.commonSearchParams_ &&
            !this.isErrorPageVisible_) {
            this.$.threadFrame.src = this.pendingUrl_;
            this.pendingUrl_ = '';
        }
    }
    onHandshakeComplete() {
        assert(this.postMessageHandler_);
        this.postMessageHandler_.completeHandshake();
    }
    async updateSidePanelState() {
        const { isInTab } = await this.browserProxy_.handler.isShownInTab();
        this.isShownInTab_ = isInTab;
    }
    async updateCommonSearchParams() {
        // TODO(crbug.com/463729504): Add support for dark mode.
        const { params } = await this.browserProxy_.handler.getCommonSearchParams(
        /*isDarkMode=*/ this.darkMode_, 
        /*isSidePanel=*/ !this.isShownInTab_);
        this.updateBackgroundColor_();
        this.commonSearchParams_ = params;
        this.maybeLoadPendingUrl_();
    }
    setupWebviewRequestOverrides() {
        if (isFullWebView(this.$.threadFrame)) {
            this.$.threadFrame.request.onBeforeRequest.addListener(this.onBeforeRequest, {
                types: ['main_frame'],
                urls: ['<all_urls>'],
            }, ['blocking']);
            // Inject a script to notify the embedder when the DOM has loaded so the
            // app knows when to show the header and composebox.
            this.$.threadFrame.addContentScripts([{
                    name: 'contextualTasksDomContentLoaded',
                    matches: ['<all_urls>'],
                    js: {
                        code: `
            (() => {
              let messageSent = false;
              let embedderSource = null;

              const send = () => {
                if (messageSent || !embedderSource) return;
                if (document.readyState === 'loading') return;
                embedderSource.postMessage('domContentLoaded', '*');
                messageSent = true;
              };

              document.addEventListener('DOMContentLoaded', send);

              window.addEventListener('message', (e) => {
                if (messageSent || !e.source || e.source === window) return;
                embedderSource = e.source;
                send();
              });
            })();
          `,
                    },
                    run_at: 'document_start',
                }]);
        }
        // Allow downloading files. This is necessary since aim can generate
        // images for download.
        this.$.threadFrame.addEventListener('permissionrequest', (e) => {
            const permissionEvent = e;
            if (permissionEvent.permission === 'download') {
                permissionEvent.request.allow();
            }
            else if (permissionEvent.permission === 'geolocation') {
                permissionEvent.request.allow();
            }
        });
        const userAgentSuffix = loadTimeData.getString('userAgentSuffix');
        if (userAgentSuffix) {
            const userAgent = this.$.threadFrame.getUserAgent();
            if (!userAgent.endsWith(userAgentSuffix)) {
                this.$.threadFrame.setUserAgentOverride(`${userAgent} ${userAgentSuffix}`);
            }
        }
    }
    removeWebviewRequestOverrides() {
        if (isFullWebView(this.$.threadFrame)) {
            this.$.threadFrame.request.onBeforeRequest.removeListener(this.onBeforeRequest);
        }
    }
    addCommonSearchParams(url) {
        if (!this.commonSearchParams_) {
            return url;
        }
        for (const [key, value] of Object.entries(this.commonSearchParams_)) {
            // If the url already has a key, skip it to avoid overriding it. `cs` is an
            // exception since it will cause UI mismatch between native and embedded
            // page.
            if (key !== 'cs' && url.searchParams.has(key)) {
                continue;
            }
            if (value === '') {
                url.searchParams.delete(key);
            }
            else {
                url.searchParams.set(key, value);
            }
        }
        // The viewport width and height are also common search params but they
        // are not retrieved from the browser as the viewport is determined by the
        // contextual tasks webview.
        const resultsBoundingRect = this.$.threadFrame.getBoundingClientRect();
        if (resultsBoundingRect.width > 0) {
            url.searchParams.set(VIEWPORT_WIDTH_KEY, resultsBoundingRect.width.toString());
        }
        if (resultsBoundingRect.height > 0) {
            url.searchParams.set(VIEWPORT_HEIGHT_KEY, resultsBoundingRect.height.toString());
        }
        return url;
    }
    onBeforeRequest = (details) => {
        const url = new URL(details.url);
        const newUrl = this.addCommonSearchParams(url);
        const isSigninDomain = !!this.signInDomains_.find((domain) => domain === url.host);
        if (this.host_ && !isSigninDomain) {
            newUrl.host = this.host_;
        }
        if (newUrl.href !== details.url) {
            return { redirectUrl: newUrl.href };
        }
        return {};
    };
    getThreadUrlForTesting() {
        return this.$.threadFrame.src;
    }
    onErrorDialogClose_() {
        this.isErrorDialogVisible_ = false;
    }
    updateTooltipVisibilityForTesting() {
        this.updateTooltipVisibility_();
    }
    // Onboarding tooltip is not supported on Android.
    // 
    get numberOfTimesTooltipShownForTesting() {
        return this.$.onboardingTooltip?.numberOfTimesTooltipShownForTesting ?? 0;
    }
    set numberOfTimesTooltipShownForTesting(n) {
        if (this.$.onboardingTooltip) {
            this.$.onboardingTooltip.numberOfTimesTooltipShownForTesting = n;
        }
    }
    set userDismissedTooltipForTesting(dismissed) {
        if (this.$.onboardingTooltip) {
            this.$.onboardingTooltip.userDismissedTooltipForTesting = dismissed;
        }
    }
    get tooltipResizeObserverForTesting() {
        return this.$.onboardingTooltip?.tooltipResizeObserverForTesting ?? null;
    }
    // 
    updateBasicModeAfterNavigation() {
        if (!this.enableBasicMode_ || !this.isNavigatingFromAiPage_) {
            return;
        }
        // If basic mode was changed while loading the
        // thread frame, utilize that new value instead.
        if (this.pendingBasicMode_ !== null) {
            this.isInBasicMode_ = this.pendingBasicMode_;
        }
        this.isNavigatingFromAiPage_ = false;
        this.pendingBasicMode_ = null;
    }
    setIsGhostLoaderVisible(isVisible) {
        if (this.enableGhostLoader_) {
            this.isGhostLoaderVisible_ = isVisible;
        }
    }
    hasThreadHistoryParams(url) {
        return url.searchParams.get('atvm') === '1' ||
            url.searchParams.get('atvm') === '3';
    }
    setPopStateFinishedCallbackForTesting(callback) {
        this.popStateFinishedCallbackForTesting_ = callback;
    }
    setOnLoadStartFinishedCallbackForTesting(callback) {
        this.onLoadStartFinishedCallbackForTesting_ = callback;
    }
    setMockPostMessageHandlerForTesting(mockPostMessageHandler) {
        this.postMessageHandler_ = mockPostMessageHandler;
    }
    // Since this is a derivative state, this is one of the only protected
    // or private members that should have testing setter method.
    // The rest should be changed through testProxy routerRemote.
    setIsNavigatingFromAiPageForTesting(isNavigatingForTesting) {
        this.isNavigatingFromAiPage_ = isNavigatingForTesting;
    }
    // Detaches the threadFrame load listeners so real <webview> events cannot
    // run the handlers while a test drives them manually.
    removeThreadFrameListenersForTesting() {
        this.eventTracker_.remove(this.$.threadFrame, 'loadstart');
        this.eventTracker_.remove(this.$.threadFrame, 'loadredirect');
        this.eventTracker_.remove(this.$.threadFrame, 'loadabort');
        this.eventTracker_.remove(this.$.threadFrame, 'loadcommit');
        this.eventTracker_.remove(this.$.threadFrame, 'contentload');
    }
    isNavigatingForTesting() {
        return this.isNavigatingFromAiPage_;
    }
    onBeforeRequestForTesting(details) {
        return this.onBeforeRequest(details);
    }
    getIsFrameLoadingForTesting() {
        return this.isFrameLoading;
    }
    onThreadFrameLoadStartForTesting(event) {
        this.onThreadFrameLoadStart(event);
    }
    onThreadFrameLoadCommitForTesting(event) {
        this.onThreadFrameLoadCommit(event);
    }
    onThreadFrameContentLoadForTesting() {
        this.onThreadFrameContentLoad();
    }
    async onThreadFrameLoadAbortForTesting(event) {
        await this.onThreadFrameLoadAbort(event);
    }
    setIsZeroStateForTesting(isZeroState) {
        this.isZeroState_ = isZeroState;
    }
    setIsInitialFrameLoadForTesting(isInitialFrameLoad) {
        this.isInitialFrameLoad_ = isInitialFrameLoad;
    }
    setInNlmForTesting(inNlm) {
        this.inNlm_ = inNlm;
    }
    setForcedComposeboxBoundsForTesting(bounds) {
        this.forcedComposeboxBounds_ = bounds;
    }
    onInputPlateBoundsUpdateForTesting(inputRect, occluders) {
        this.onInputPlateBoundsUpdate_(inputRect, occluders);
    }
    getOccludersForTesting() {
        return this.occluders_;
    }
    getPendingBasicModeForTesting() {
        return this.pendingBasicMode_;
    }
    updateBackgroundColor_() {
        if (this.darkMode_) {
            document.body.style.backgroundColor = this.useStratusDarkModeColors_ ?
                'rgba(34, 36, 43, 1)' :
                'rgba(16, 18, 23, 1)';
        }
        else {
            document.body.style.backgroundColor = 'rgba(255, 255, 255, 1)';
        }
    }
    onStsTryItDismiss_() {
        this.hideStsTryItPromo_();
        this.browserProxy_.handler.notifySmartTabSharingTryItIphResult(false);
    }
    onStsTryItAccept_() {
        this.hideStsTryItPromo_();
        this.browserProxy_.handler.notifySmartTabSharingTryItIphResult(true);
        // 
        const menu = this.getContextualActionMenu();
        if (menu) {
            menu.setSmartTabSharingToggle(true);
        }
        // 
    }
    hideStsTryItPromo_() {
        this.showSmartTabSharingTryItIph_ = false;
    }
    onStsDefaultOnDismiss_() {
        this.hideStsDefaultOnPromo_();
        this.browserProxy_.handler.notifySmartTabSharingDefaultOnIphResult(false);
    }
    onStsDefaultOnAccept_() {
        this.hideStsDefaultOnPromo_();
        this.browserProxy_.handler.notifySmartTabSharingDefaultOnIphResult(true);
        // 
        const menu = this.getContextualActionMenu();
        if (menu) {
            menu.setSmartTabSharingToggle(true);
        }
        // 
    }
    hideStsDefaultOnPromo_() {
        this.showSmartTabSharingDefaultOnIph_ = false;
    }
}
customElements.define(ContextualTasksAppElement.is, ContextualTasksAppElement);
