1 // Copyright 2018 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.chrome.browser.tab; 6 7 import android.graphics.Color; 8 9 import androidx.annotation.Nullable; 10 import androidx.annotation.VisibleForTesting; 11 12 import org.chromium.base.UserData; 13 import org.chromium.chrome.browser.previews.Previews; 14 import org.chromium.components.browser_ui.styles.ChromeColors; 15 import org.chromium.components.security_state.ConnectionSecurityLevel; 16 import org.chromium.components.security_state.SecurityStateModel; 17 import org.chromium.content_public.browser.NavigationHandle; 18 import org.chromium.content_public.browser.RenderWidgetHostView; 19 import org.chromium.content_public.browser.WebContents; 20 import org.chromium.net.NetError; 21 import org.chromium.ui.base.WindowAndroid; 22 import org.chromium.ui.util.ColorUtils; 23 24 /** 25 * Manages theme color used for {@link Tab}. Destroyed together with the tab. 26 */ 27 public class TabThemeColorHelper extends EmptyTabObserver implements UserData { 28 private static final Class<TabThemeColorHelper> USER_DATA_KEY = TabThemeColorHelper.class; 29 private final TabImpl mTab; 30 31 private int mDefaultColor; 32 private int mColor; 33 34 /** 35 * The default background color used for {@link #mTab} if the associate web content doesn't 36 * specify a background color. 37 */ 38 private int mDefaultBackgroundColor; 39 40 /** Whether or not the default color is used. */ 41 private boolean mIsDefaultColorUsed; 42 43 /** 44 * Whether or not the color provided by the web page is used. False if the web page does not 45 * provide a custom theme color. 46 */ 47 private boolean mIsUsingColorFromTabContents; 48 createForTab(Tab tab)49 public static void createForTab(Tab tab) { 50 assert get(tab) == null; 51 tab.getUserDataHost().setUserData(USER_DATA_KEY, new TabThemeColorHelper(tab)); 52 } 53 54 @Nullable get(Tab tab)55 public static TabThemeColorHelper get(Tab tab) { 56 return tab.getUserDataHost().getUserData(USER_DATA_KEY); 57 } 58 59 /** Convenience method that returns theme color of {@link Tab}. */ getColor(Tab tab)60 public static int getColor(Tab tab) { 61 return get(tab).getColor(); 62 } 63 64 /** Convenience method that returns default theme color of {@link Tab}. */ getDefaultColor(Tab tab)65 public static int getDefaultColor(Tab tab) { 66 return get(tab).getDefaultColor(); 67 } 68 69 /** @return Whether default theme color is used for the specified {@link Tab}. */ isDefaultColorUsed(Tab tab)70 public static boolean isDefaultColorUsed(Tab tab) { 71 return get(tab).mIsDefaultColorUsed; 72 } 73 74 /** @return Whether the color provided by the web page is used for the specified {@link Tab}. */ isUsingColorFromTabContents(Tab tab)75 public static boolean isUsingColorFromTabContents(Tab tab) { 76 return get(tab).mIsUsingColorFromTabContents; 77 } 78 79 /** @return Whether background color of the specified {@link Tab}. */ getBackgroundColor(Tab tab)80 public static int getBackgroundColor(Tab tab) { 81 return get(tab).getBackgroundColor(); 82 } 83 TabThemeColorHelper(Tab tab)84 private TabThemeColorHelper(Tab tab) { 85 mTab = (TabImpl) tab; 86 mDefaultColor = calculateDefaultColor(); 87 mIsDefaultColorUsed = true; 88 mIsUsingColorFromTabContents = false; 89 mColor = mDefaultColor; 90 updateDefaultBackgroundColor(); 91 tab.addObserver(this); 92 93 updateThemeColor(false); 94 } 95 updateDefaultColor()96 private void updateDefaultColor() { 97 mDefaultColor = calculateDefaultColor(); 98 updateIfNeeded(false); 99 } 100 calculateDefaultColor()101 private int calculateDefaultColor() { 102 return ChromeColors.getDefaultThemeColor( 103 mTab.getContext().getResources(), mTab.isIncognito()); 104 } 105 updateDefaultBackgroundColor()106 private void updateDefaultBackgroundColor() { 107 mDefaultBackgroundColor = 108 ChromeColors.getPrimaryBackgroundColor(mTab.getContext().getResources(), false); 109 } 110 111 /** 112 * Updates the theme color based on if the page is native, the theme color changed, etc. 113 * @param didWebContentsThemeColorChange If the theme color of the web contents is known to have 114 * changed. 115 */ updateThemeColor(boolean didWebContentsThemeColorChange)116 private void updateThemeColor(boolean didWebContentsThemeColorChange) { 117 // Start by assuming the current theme color is the one that should be used. This will 118 // either be transparent, the last theme color, or the color restored from TabState. 119 int themeColor = mColor; 120 121 // Only use the web contents for the theme color if it is known to have changed. This 122 // corresponds to the didChangeThemeColor in WebContentsObserver. 123 if (mTab.getWebContents() != null && didWebContentsThemeColorChange) { 124 themeColor = mTab.getWebContents().getThemeColor(); 125 mIsUsingColorFromTabContents = themeColor != TabState.UNSPECIFIED_THEME_COLOR 126 && ColorUtils.isValidThemeColor(themeColor); 127 if (mIsUsingColorFromTabContents) { 128 mIsDefaultColorUsed = false; 129 } 130 } 131 132 boolean isThemingAllowed = checkThemingAllowed(); 133 if (!isThemingAllowed) { 134 mIsUsingColorFromTabContents = false; 135 } 136 if (!mIsUsingColorFromTabContents) { 137 themeColor = mDefaultColor; 138 mIsDefaultColorUsed = true; 139 if (mTab.getActivity() != null && isThemingAllowed) { 140 int customThemeColor = mTab.getActivity().getActivityThemeColor(); 141 if (customThemeColor != TabState.UNSPECIFIED_THEME_COLOR) { 142 themeColor = customThemeColor; 143 mIsDefaultColorUsed = false; 144 } 145 } 146 } 147 148 // Ensure there is no alpha component to the theme color as that is not supported in the 149 // dependent UI. 150 mColor = themeColor | 0xFF000000; 151 } 152 153 /** 154 * Returns whether theming the activity is allowed (either by the web contents or by the 155 * activity). 156 */ checkThemingAllowed()157 private boolean checkThemingAllowed() { 158 // Do not apply the theme color if there are any security issues on the page. 159 final int securityLevel = 160 SecurityStateModel.getSecurityLevelForWebContents(mTab.getWebContents()); 161 return securityLevel != ConnectionSecurityLevel.DANGEROUS 162 && securityLevel != ConnectionSecurityLevel.SECURE_WITH_POLICY_INSTALLED_CERT 163 && (mTab.getActivity() == null || !mTab.getActivity().isTablet()) 164 && (mTab.getActivity() == null 165 || !mTab.getActivity().getNightModeStateProvider().isInNightMode()) 166 && !mTab.isNativePage() && !mTab.isIncognito() && !Previews.isPreview(mTab); 167 } 168 169 /** 170 * Determines if the theme color has changed and notifies the listeners if it has. 171 * @param didWebContentsThemeColorChange If the theme color of the web contents is known to have 172 * changed. 173 */ updateIfNeeded(boolean didWebContentsThemeColorChange)174 public void updateIfNeeded(boolean didWebContentsThemeColorChange) { 175 int oldThemeColor = mColor; 176 updateThemeColor(didWebContentsThemeColorChange); 177 if (oldThemeColor == mColor) return; 178 mTab.notifyThemeColorChanged(mColor); 179 } 180 181 /** 182 * @return The default theme color for this tab. 183 */ 184 @VisibleForTesting getDefaultColor()185 public int getDefaultColor() { 186 return mDefaultColor; 187 } 188 189 /** 190 * @return The current theme color based on the value passed from the web contents and the 191 * security state. 192 */ getColor()193 public int getColor() { 194 return mColor; 195 } 196 197 /** 198 * Returns the background color of the associate web content of {@link #mTab}, or the default 199 * background color if the web content background color is not specified (i.e. transparent). 200 * See native WebContentsAndroid#GetBackgroundColor. 201 * @return The background color of {@link #mTab}. 202 */ getBackgroundColor()203 public int getBackgroundColor() { 204 if (mTab.isNativePage()) return mTab.getNativePage().getBackgroundColor(); 205 206 WebContents tabWebContents = mTab.getWebContents(); 207 RenderWidgetHostView rwhv = 208 tabWebContents == null ? null : tabWebContents.getRenderWidgetHostView(); 209 final int backgroundColor = rwhv != null ? rwhv.getBackgroundColor() : Color.TRANSPARENT; 210 return backgroundColor == Color.TRANSPARENT ? mDefaultBackgroundColor : backgroundColor; 211 } 212 213 // TabObserver 214 215 @Override onInitialized( Tab tab, String appId, @Nullable Boolean hasThemeColor, int themeColor)216 public void onInitialized( 217 Tab tab, String appId, @Nullable Boolean hasThemeColor, int themeColor) { 218 if (hasThemeColor == null) return; 219 220 // Update from TabState. 221 mIsUsingColorFromTabContents = hasThemeColor; 222 mIsDefaultColorUsed = !mIsUsingColorFromTabContents; 223 mColor = mIsDefaultColorUsed ? getDefaultColor() : themeColor; 224 updateIfNeeded(false); 225 } 226 227 @Override onSSLStateUpdated(Tab tab)228 public void onSSLStateUpdated(Tab tab) { 229 updateIfNeeded(false); 230 } 231 232 @Override onUrlUpdated(Tab tab)233 public void onUrlUpdated(Tab tab) { 234 updateIfNeeded(false); 235 } 236 237 @Override onDidFailLoad(Tab tab, boolean isMainFrame, int errorCode, String failingUrl)238 public void onDidFailLoad(Tab tab, boolean isMainFrame, int errorCode, String failingUrl) { 239 updateIfNeeded(true); 240 } 241 242 @Override onDidFinishNavigation(Tab tab, NavigationHandle navigation)243 public void onDidFinishNavigation(Tab tab, NavigationHandle navigation) { 244 if (navigation.errorCode() != NetError.OK) updateIfNeeded(true); 245 } 246 247 @Override onActivityAttachmentChanged(Tab tab, @Nullable WindowAndroid window)248 public void onActivityAttachmentChanged(Tab tab, @Nullable WindowAndroid window) { 249 updateDefaultColor(); 250 updateDefaultBackgroundColor(); 251 updateIfNeeded(/* didWebContentsThemeColorChange= */ true); 252 } 253 254 @Override onDestroyed(Tab tab)255 public void onDestroyed(Tab tab) { 256 tab.removeObserver(this); 257 } 258 } 259