blob: 2d53db6c548532a36c159fa2920f7897232871c7 [file] [log] [blame]
// Copyright 2014 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.toolbar;
import static org.chromium.chrome.browser.incognito.IncognitoUtils.getNonPrimaryOTRProfileFromWindowAndroid;
import android.content.Context;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import androidx.annotation.ColorRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.ObserverList;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.dom_distiller.DomDistillerTabUtils;
import org.chromium.chrome.browser.layouts.LayoutStateProvider;
import org.chromium.chrome.browser.layouts.LayoutType;
import org.chromium.chrome.browser.offlinepages.OfflinePageUtils;
import org.chromium.chrome.browser.omnibox.ChromeAutocompleteSchemeClassifier;
import org.chromium.chrome.browser.omnibox.LocationBarDataProvider;
import org.chromium.chrome.browser.omnibox.SearchEngineLogoUtils;
import org.chromium.chrome.browser.omnibox.UrlBarData;
import org.chromium.chrome.browser.paint_preview.TabbedPaintPreview;
import org.chromium.chrome.browser.previews.Previews;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TrustedCdn;
import org.chromium.chrome.browser.ui.native_page.NativePage;
import org.chromium.components.browser_ui.styles.ChromeColors;
import org.chromium.components.dom_distiller.core.DomDistillerUrlUtils;
import org.chromium.components.embedder_support.util.UrlConstants;
import org.chromium.components.embedder_support.util.UrlUtilities;
import org.chromium.components.metrics.OmniboxEventProtos.OmniboxEventProto.PageClassification;
import org.chromium.components.omnibox.OmniboxUrlEmphasizer;
import org.chromium.components.omnibox.SecurityStatusIcon;
import org.chromium.components.security_state.ConnectionSecurityLevel;
import org.chromium.components.security_state.SecurityStateModel;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.util.ColorUtils;
import org.chromium.url.URI;
import java.net.URISyntaxException;
/**
* Provides a way of accessing toolbar data and state.
*/
public class LocationBarModel implements ToolbarDataProvider, LocationBarDataProvider {
private final Context mContext;
private final NewTabPageDelegate mNtpDelegate;
private Tab mTab;
private int mPrimaryColor;
private LayoutStateProvider mLayoutStateProvider;
private boolean mIsIncognito;
private boolean mIsUsingBrandColor;
private boolean mShouldShowOmniboxInOverviewMode;
private long mNativeLocationBarModelAndroid;
private ObserverList<LocationBarDataProvider.Observer> mLocationBarDataObservers =
new ObserverList<>();
/**
* Default constructor for this class.
* @param context The Context used for styling the toolbar visuals.
*/
public LocationBarModel(Context context, NewTabPageDelegate newTabPageDelegate) {
mContext = context;
mNtpDelegate = newTabPageDelegate;
mPrimaryColor = ChromeColors.getDefaultThemeColor(context.getResources(), false);
}
/**
* Handle any initialization that must occur after native has been initialized.
*/
public void initializeWithNative() {
mNativeLocationBarModelAndroid = LocationBarModelJni.get().init(LocationBarModel.this);
}
/**
* Destroys the native LocationBarModel.
*/
public void destroy() {
if (mNativeLocationBarModelAndroid == 0) return;
LocationBarModelJni.get().destroy(mNativeLocationBarModelAndroid, LocationBarModel.this);
mNativeLocationBarModelAndroid = 0;
}
/**
* @return The currently active WebContents being used by the Toolbar.
*/
@CalledByNative
private WebContents getActiveWebContents() {
if (!hasTab()) return null;
return mTab.getWebContents();
}
/**
* Sets the tab that contains the information to be displayed in the toolbar.
*
* @param tab The tab associated currently with the toolbar.
* @param isIncognito Whether the incognito model is currently selected, which must match the
* passed in tab if non-null.
*/
public void setTab(Tab tab, boolean isIncognito) {
assert tab == null || tab.isIncognito() == isIncognito;
mTab = tab;
if (mIsIncognito != isIncognito) {
mIsIncognito = isIncognito;
notifyIncognitoStateChanged();
}
updateUsingBrandColor();
notifyTitleChanged();
notifyUrlChanged();
}
@Override
public Tab getTab() {
return hasTab() ? mTab : null;
}
@Override
public boolean hasTab() {
// TODO(dtrainor, tedchoc): Remove the isInitialized() check when we no longer wait for
// TAB_CLOSED events to remove this tab. Otherwise there is a chance we use this tab after
// {@link ChromeTab#destroy()} is called.
return mTab != null && mTab.isInitialized();
}
@Override
public void addObserver(LocationBarDataProvider.Observer observer) {
mLocationBarDataObservers.addObserver(observer);
}
@Override
public void removeObserver(LocationBarDataProvider.Observer observer) {
mLocationBarDataObservers.removeObserver(observer);
}
@Override
public String getCurrentUrl() {
// Provide NTP url instead of most recent tab url for searches in overview mode (when Start
// Surface is enabled). .
if (isInOverviewAndShowingOmnibox()) {
return UrlConstants.NTP_URL;
}
// TODO(yusufo) : Consider using this for all calls from getTab() for accessing url.
if (!hasTab() || !getTab().isInitialized()) return "";
// Tab.getUrl() returns empty string if it does not have a URL.
return getTab().getUrlString().trim();
}
public void notifyUrlChanged() {
for (LocationBarDataProvider.Observer observer : mLocationBarDataObservers) {
observer.onUrlChanged();
}
}
@Override
public NewTabPageDelegate getNewTabPageDelegate() {
return mNtpDelegate;
}
void notifyNtpStartedLoading() {
for (Observer observer : mLocationBarDataObservers) {
observer.onNtpStartedLoading();
}
}
@Override
public UrlBarData getUrlBarData() {
if (!hasTab()) return UrlBarData.EMPTY;
String url = getCurrentUrl();
if (NativePage.isNativePageUrl(url, isIncognito()) || UrlUtilities.isNTPUrl(url)) {
return UrlBarData.EMPTY;
}
String formattedUrl = getFormattedFullUrl();
if (mTab.isFrozen()) return buildUrlBarData(url, formattedUrl);
if (DomDistillerUrlUtils.isDistilledPage(url)) {
String originalUrl = DomDistillerUrlUtils.getOriginalUrlFromDistillerUrl(url);
if (originalUrl != null) {
return buildUrlBarData(
DomDistillerTabUtils.getFormattedUrlFromOriginalDistillerUrl(originalUrl));
}
return buildUrlBarData(url, formattedUrl);
}
// Strip the scheme from committed preview pages only.
if (isPreview()) {
return buildUrlBarData(url, UrlUtilities.stripScheme(url));
}
if (isOfflinePage()) {
String originalUrl = mTab.getOriginalUrl();
formattedUrl = UrlUtilities.stripScheme(
DomDistillerTabUtils.getFormattedUrlFromOriginalDistillerUrl(originalUrl));
// Clear the editing text for untrusted offline pages.
if (!OfflinePageUtils.isShowingTrustedOfflinePage(mTab.getWebContents())) {
return buildUrlBarData(url, formattedUrl, "");
}
return buildUrlBarData(url, formattedUrl);
}
String urlForDisplay = getUrlForDisplay();
if (!urlForDisplay.equals(formattedUrl)) {
return buildUrlBarData(url, urlForDisplay, formattedUrl);
}
return buildUrlBarData(url, formattedUrl);
}
private UrlBarData buildUrlBarData(String url) {
return buildUrlBarData(url, url, url);
}
private UrlBarData buildUrlBarData(String url, String displayText) {
return buildUrlBarData(url, displayText, displayText);
}
private UrlBarData buildUrlBarData(String url, String displayText, String editingText) {
SpannableStringBuilder spannableDisplayText = new SpannableStringBuilder(displayText);
if (mNativeLocationBarModelAndroid != 0 && spannableDisplayText.length() > 0
&& shouldEmphasizeUrl()) {
boolean isInternalPage = false;
try {
isInternalPage = UrlUtilities.isInternalScheme(new URI(url));
} catch (URISyntaxException e) {
// Ignore as this only is for applying color
}
ChromeAutocompleteSchemeClassifier chromeAutocompleteSchemeClassifier =
new ChromeAutocompleteSchemeClassifier(getProfile());
OmniboxUrlEmphasizer.emphasizeUrl(spannableDisplayText, mContext.getResources(),
chromeAutocompleteSchemeClassifier, getSecurityLevel(), isInternalPage,
!ColorUtils.shouldUseLightForegroundOnBackground(getPrimaryColor()),
shouldEmphasizeHttpsScheme());
chromeAutocompleteSchemeClassifier.destroy();
}
return UrlBarData.forUrlAndText(url, spannableDisplayText, editingText);
}
/**
* @return True if the displayed URL should be emphasized, false if the displayed text
* already has formatting for emphasis applied.
*/
private boolean shouldEmphasizeUrl() {
// If the toolbar shows the publisher URL, it applies its own formatting for emphasis.
if (mTab == null) return true;
return TrustedCdn.getPublisherUrl(mTab) == null;
}
/**
* @return Whether the light security theme should be used.
*/
@VisibleForTesting
public boolean shouldEmphasizeHttpsScheme() {
return !isUsingBrandColor() && !isIncognito();
}
@Override
public String getTitle() {
if (!hasTab()) return "";
String title = getTab().getTitle();
return TextUtils.isEmpty(title) ? title : title.trim();
}
public void notifyTitleChanged() {
for (LocationBarDataProvider.Observer observer : mLocationBarDataObservers) {
observer.onTitleChanged();
}
}
@Override
public boolean isIncognito() {
return mIsIncognito;
}
private void notifyIncognitoStateChanged() {
for (LocationBarDataProvider.Observer observer : mLocationBarDataObservers) {
observer.onIncognitoStateChanged();
}
}
/**
* @return Whether the location bar is showing in overview mode. If the location bar should not
* currently be showing in overview mode, returns false.
*/
@Override
public boolean isInOverviewAndShowingOmnibox() {
if (!mShouldShowOmniboxInOverviewMode) return false;
return mLayoutStateProvider != null
&& mLayoutStateProvider.isLayoutVisible(LayoutType.TAB_SWITCHER);
}
/**
* @return Whether the location bar should show when in overview mode.
*/
@Override
public boolean shouldShowLocationBarInOverviewMode() {
return mShouldShowOmniboxInOverviewMode;
}
@Override
public Profile getProfile() {
Profile lastUsedRegularProfile = Profile.getLastUsedRegularProfile();
if (mIsIncognito) {
WindowAndroid windowAndroid = (mTab != null) ? mTab.getWindowAndroid() : null;
// If the mTab belongs to a CustomTabActivity then we return the non-primary OTR profile
// which is associated with it. For all other cases we return the primary OTR profile.
Profile nonPrimaryOTRProfile = getNonPrimaryOTRProfileFromWindowAndroid(windowAndroid);
if (nonPrimaryOTRProfile != null) return nonPrimaryOTRProfile;
// When in overview mode with no open tabs, there has not been created an
// OTR profile yet. #getOffTheRecordProfile will create a profile if none
// exists.
assert lastUsedRegularProfile.hasPrimaryOTRProfile() || isInOverviewAndShowingOmnibox();
// Return the primary OTR profile.
return lastUsedRegularProfile.getPrimaryOTRProfile();
}
return lastUsedRegularProfile;
}
public void setLayoutStateProvider(LayoutStateProvider layoutStateProvider) {
mLayoutStateProvider = layoutStateProvider;
}
public void setShouldShowOmniboxInOverviewMode(boolean shouldShowOmniboxInOverviewMode) {
mShouldShowOmniboxInOverviewMode = shouldShowOmniboxInOverviewMode;
}
/**
* Sets the primary color and changes the state for isUsingBrandColor.
* @param color The primary color for the current tab.
*/
public void setPrimaryColor(int color) {
mPrimaryColor = color;
updateUsingBrandColor();
}
private void updateUsingBrandColor() {
mIsUsingBrandColor = !isIncognito()
&& mPrimaryColor
!= ChromeColors.getDefaultThemeColor(mContext.getResources(), isIncognito())
&& hasTab() && !mTab.isNativePage();
}
@Override
public int getPrimaryColor() {
return isInOverviewAndShowingOmnibox()
? ChromeColors.getDefaultThemeColor(mContext.getResources(), isIncognito())
: mPrimaryColor;
}
@Override
public boolean isUsingBrandColor() {
// If the overview is visible, force use of primary color, which is also overridden when the
// overview is visible.
return isInOverviewAndShowingOmnibox() || mIsUsingBrandColor;
}
@Override
public boolean isOfflinePage() {
return hasTab() && OfflinePageUtils.isOfflinePage(mTab);
}
@Override
public boolean isPreview() {
return hasTab() && Previews.isPreview(mTab);
}
@Override
public boolean isPaintPreview() {
return hasTab() && TabbedPaintPreview.get(mTab).isShowing();
}
@Override
public int getSecurityLevel() {
Tab tab = getTab();
return getSecurityLevel(tab, isOfflinePage(), TrustedCdn.getPublisherUrl(tab));
}
@Override
public int getPageClassification(boolean isFocusedFromFakebox) {
if (mNativeLocationBarModelAndroid == 0) return PageClassification.INVALID_SPEC_VALUE;
// Provide NTP as page class in overview mode (when Start Surface is enabled). No call
// to the backend necessary or possible, since there is no tab or navigation entry.
if (isInOverviewAndShowingOmnibox()) return PageClassification.NTP_VALUE;
return LocationBarModelJni.get().getPageClassification(
mNativeLocationBarModelAndroid, LocationBarModel.this, isFocusedFromFakebox);
}
@Override
public int getSecurityIconResource(boolean isTablet) {
return getSecurityIconResource(
getSecurityLevel(), !isTablet, isOfflinePage(), isPreview(), isPaintPreview());
}
@Override
@StringRes
public int getSecurityIconContentDescriptionResourceId() {
return SecurityStatusIcon.getSecurityIconContentDescriptionResourceId(getSecurityLevel());
}
@VisibleForTesting
@ConnectionSecurityLevel
int getSecurityLevel(Tab tab, boolean isOfflinePage, @Nullable String publisherUrl) {
if (tab == null || isOfflinePage) {
return ConnectionSecurityLevel.NONE;
}
if (publisherUrl != null) {
assert getSecurityLevelFromStateModel(tab.getWebContents())
!= ConnectionSecurityLevel.DANGEROUS;
return (URI.create(publisherUrl).getScheme().equals(UrlConstants.HTTPS_SCHEME))
? ConnectionSecurityLevel.SECURE
: ConnectionSecurityLevel.WARNING;
}
return getSecurityLevelFromStateModel(tab.getWebContents());
}
@VisibleForTesting
@ConnectionSecurityLevel
int getSecurityLevelFromStateModel(WebContents webContents) {
int securityLevel = SecurityStateModel.getSecurityLevelForWebContents(webContents);
return securityLevel;
}
@VisibleForTesting
@DrawableRes
int getSecurityIconResource(int securityLevel, boolean isSmallDevice, boolean isOfflinePage,
boolean isPreview, boolean isPaintPreview) {
// Paint Preview appears on top of WebContents and shows a visual representation of the page
// that has been previously stored locally.
if (isPaintPreview) return R.drawable.omnibox_info;
// Checking for a preview first because one possible preview type is showing an offline page
// on a slow connection. In this case, the previews UI takes precedence.
if (isPreview) {
return R.drawable.preview_pin_round;
} else if (isOfflinePage) {
return R.drawable.ic_offline_pin_24dp;
}
// Return early if native initialization hasn't been done yet.
if ((securityLevel == ConnectionSecurityLevel.NONE
|| securityLevel == ConnectionSecurityLevel.WARNING)
&& mNativeLocationBarModelAndroid == 0) {
return R.drawable.omnibox_info;
}
boolean skipIconForNeutralState =
!SearchEngineLogoUtils.shouldShowSearchEngineLogo(isIncognito())
|| mNtpDelegate.isCurrentlyVisible();
return SecurityStatusIcon.getSecurityIconResource(securityLevel,
SecurityStateModel.shouldShowDangerTriangleForWarningLevel(), isSmallDevice,
skipIconForNeutralState);
}
@Override
public @ColorRes int getSecurityIconColorStateList() {
int securityLevel = getSecurityLevel();
int color = getPrimaryColor();
boolean needLightIcon = ColorUtils.shouldUseLightForegroundOnBackground(color);
if (isIncognito() || needLightIcon) {
// For a dark theme color, use light icons.
return ToolbarColors.getThemedToolbarIconTintRes(true);
}
if (isPreview()) {
// There will never be a preview in incognito. Always use the darker color rather than
// incorporating with the block above.
return R.color.locationbar_status_preview_color;
}
return ToolbarColors.getThemedToolbarIconTintRes(false);
}
/** @return The formatted URL suitable for editing. */
public String getFormattedFullUrl() {
if (mNativeLocationBarModelAndroid == 0) return "";
return LocationBarModelJni.get().getFormattedFullURL(
mNativeLocationBarModelAndroid, LocationBarModel.this);
}
/** @return The formatted URL suitable for display only. */
public String getUrlForDisplay() {
if (mNativeLocationBarModelAndroid == 0) return "";
return LocationBarModelJni.get().getURLForDisplay(
mNativeLocationBarModelAndroid, LocationBarModel.this);
}
@NativeMethods
interface Natives {
long init(LocationBarModel caller);
void destroy(long nativeLocationBarModelAndroid, LocationBarModel caller);
String getFormattedFullURL(long nativeLocationBarModelAndroid, LocationBarModel caller);
String getURLForDisplay(long nativeLocationBarModelAndroid, LocationBarModel caller);
int getPageClassification(long nativeLocationBarModelAndroid, LocationBarModel caller,
boolean isFocusedFromFakebox);
}
}