// Copyright 2015 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.tab;
import android.os.SystemClock;
import android.support.annotation.IntDef;
import android.view.View;
import org.chromium.base.ActivityState;
import org.chromium.base.ApplicationState;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.Log;
import org.chromium.base.ObserverList.RewindableIterator;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.chrome.browser.ChromeApplication;
import org.chromium.chrome.browser.fullscreen.FullscreenManager;
import org.chromium.chrome.browser.media.MediaCaptureNotificationService;
import org.chromium.chrome.browser.metrics.UmaSessionStats;
import org.chromium.chrome.browser.metrics.UmaUtils;
import org.chromium.chrome.browser.policy.PolicyAuditor;
import org.chromium.chrome.browser.policy.PolicyAuditor.AuditEvent;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.browser.WebContentsObserver;
import java.util.concurrent.TimeUnit;
/**
* WebContentsObserver used by Tab.
*/
public class TabWebContentsObserver extends WebContentsObserver {
// URL didFailLoad error code. Should match the value in net_error_list.h.
public static final int BLOCKED_BY_ADMINISTRATOR = -22;
/** Used for logging. */
private static final String TAG = "TabWebContentsObs";
// TabRendererCrashStatus defined in tools/metrics/histograms/histograms.xml.
private static final int TAB_RENDERER_CRASH_STATUS_SHOWN_IN_FOREGROUND_APP = 0;
private static final int TAB_RENDERER_CRASH_STATUS_HIDDEN_IN_FOREGROUND_APP = 1;
private static final int TAB_RENDERER_CRASH_STATUS_HIDDEN_IN_BACKGROUND_APP = 2;
private static final int TAB_RENDERER_CRASH_STATUS_MAX = 3;
// TabRendererExitStatus defined in tools/metrics/histograms/histograms.xml.
// Designed to replace TabRendererCrashStatus if numbers line up.
@IntDef({TAB_RENDERER_EXIT_STATUS_OOM_PROTECTED_IN_RUNNING_APP,
TAB_RENDERER_EXIT_STATUS_OOM_PROTECTED_IN_PAUSED_APP,
TAB_RENDERER_EXIT_STATUS_OOM_PROTECTED_IN_BACKGROUND_APP,
TAB_RENDERER_EXIT_STATUS_NOT_PROTECTED_IN_RUNNING_APP,
TAB_RENDERER_EXIT_STATUS_NOT_PROTECTED_IN_PAUSED_APP,
TAB_RENDERER_EXIT_STATUS_NOT_PROTECTED_IN_BACKGROUND_APP,
TAB_RENDERER_EXIT_STATUS_MAX})
private @interface TabRendererExitStatus {}
private static final int TAB_RENDERER_EXIT_STATUS_OOM_PROTECTED_IN_RUNNING_APP = 0;
private static final int TAB_RENDERER_EXIT_STATUS_OOM_PROTECTED_IN_PAUSED_APP = 1;
private static final int TAB_RENDERER_EXIT_STATUS_OOM_PROTECTED_IN_BACKGROUND_APP = 2;
private static final int TAB_RENDERER_EXIT_STATUS_NOT_PROTECTED_IN_RUNNING_APP = 3;
private static final int TAB_RENDERER_EXIT_STATUS_NOT_PROTECTED_IN_PAUSED_APP = 4;
private static final int TAB_RENDERER_EXIT_STATUS_NOT_PROTECTED_IN_BACKGROUND_APP = 5;
private static final int TAB_RENDERER_EXIT_STATUS_MAX = 6;
private final Tab mTab;
public TabWebContentsObserver(WebContents webContents, Tab tab) {
super(webContents);
mTab = tab;
}
@Override
public void renderProcessGone(boolean processWasOomProtected) {
Log.i(TAG, "renderProcessGone() for tab id: " + mTab.getId()
+ ", oom protected: " + Boolean.toString(processWasOomProtected)
+ ", already needs reload: " + Boolean.toString(mTab.needsReload()));
// Do nothing for subsequent calls that happen while the tab remains crashed. This
// can occur when the tab is in the background and it shares the renderer with other
// tabs. After the renderer crashes, the WebContents of its tabs are still around
// and they still share the RenderProcessHost. When one of the tabs reloads spawning
// a new renderer for the shared RenderProcessHost and the new renderer crashes
// again, all tabs sharing this renderer will be notified about the crash (including
// potential background tabs that did not reload yet).
if (mTab.needsReload() || mTab.isShowingSadTab()) return;
// This will replace TabRendererCrashStatus if numbers line up.
int appState = ApplicationStatus.getStateForApplication();
boolean applicationRunning = (appState == ApplicationState.HAS_RUNNING_ACTIVITIES);
boolean applicationPaused = (appState == ApplicationState.HAS_PAUSED_ACTIVITIES);
@TabRendererExitStatus int rendererExitStatus = TAB_RENDERER_EXIT_STATUS_MAX;
if (processWasOomProtected) {
if (applicationRunning) {
rendererExitStatus = TAB_RENDERER_EXIT_STATUS_OOM_PROTECTED_IN_RUNNING_APP;
} else if (applicationPaused) {
rendererExitStatus = TAB_RENDERER_EXIT_STATUS_OOM_PROTECTED_IN_PAUSED_APP;
} else {
rendererExitStatus = TAB_RENDERER_EXIT_STATUS_OOM_PROTECTED_IN_BACKGROUND_APP;
}
} else {
if (applicationRunning) {
rendererExitStatus = TAB_RENDERER_EXIT_STATUS_NOT_PROTECTED_IN_RUNNING_APP;
} else if (applicationPaused) {
rendererExitStatus = TAB_RENDERER_EXIT_STATUS_NOT_PROTECTED_IN_PAUSED_APP;
} else {
rendererExitStatus = TAB_RENDERER_EXIT_STATUS_NOT_PROTECTED_IN_BACKGROUND_APP;
}
}
RecordHistogram.recordEnumeratedHistogram(
"Tab.RendererExitStatus", rendererExitStatus, TAB_RENDERER_EXIT_STATUS_MAX);
int activityState = ApplicationStatus.getStateForActivity(
mTab.getWindowAndroid().getActivity().get());
int rendererCrashStatus = TAB_RENDERER_CRASH_STATUS_MAX;
if (!processWasOomProtected
|| activityState == ActivityState.PAUSED
|| activityState == ActivityState.STOPPED
|| activityState == ActivityState.DESTROYED) {
// The tab crashed in background or was killed by the OS out-of-memory killer.
//setNeedsReload(true);
mTab.setNeedsReload(true);
if (applicationRunning) {
rendererCrashStatus = TAB_RENDERER_CRASH_STATUS_HIDDEN_IN_FOREGROUND_APP;
} else {
rendererCrashStatus = TAB_RENDERER_CRASH_STATUS_HIDDEN_IN_BACKGROUND_APP;
}
} else {
rendererCrashStatus = TAB_RENDERER_CRASH_STATUS_SHOWN_IN_FOREGROUND_APP;
mTab.showSadTab();
// This is necessary to correlate histogram data with stability counts.
UmaSessionStats.logRendererCrash();
}
RecordHistogram.recordEnumeratedHistogram(
"Tab.RendererCrashStatus", rendererCrashStatus, TAB_RENDERER_CRASH_STATUS_MAX);
mTab.handleTabCrash();
boolean sadTabShown = mTab.isShowingSadTab();
RewindableIterator<TabObserver> observers = mTab.getTabObservers();
while (observers.hasNext()) {
observers.next().onCrash(mTab, sadTabShown);
}
}
@Override
public void navigationEntryCommitted() {
if (mTab.getNativePage() != null) {
mTab.pushNativePageStateToNavigationEntry();
}
}
@Override
public void didFinishNavigation(
boolean isMainFrame, boolean isErrorPage, boolean hasCommitted) {
if (isMainFrame && hasCommitted) mTab.setIsShowingErrorPage(isErrorPage);
}
@Override
public void didFinishLoad(long frameId, String validatedUrl, boolean isMainFrame) {
if (isMainFrame) mTab.didFinishPageLoad();
PolicyAuditor auditor =
((ChromeApplication) mTab.getApplicationContext()).getPolicyAuditor();
auditor.notifyAuditEvent(
mTab.getApplicationContext(), AuditEvent.OPEN_URL_SUCCESS, validatedUrl, "");
}
@Override
public void didFailLoad(boolean isProvisionalLoad, boolean isMainFrame, int errorCode,
String description, String failingUrl, boolean wasIgnoredByHandler) {
mTab.updateThemeColorIfNeeded(true);
RewindableIterator<TabObserver> observers = mTab.getTabObservers();
while (observers.hasNext()) {
observers.next().onDidFailLoad(mTab, isProvisionalLoad, isMainFrame, errorCode,
description, failingUrl);
}
if (isMainFrame) mTab.didFailPageLoad(errorCode);
PolicyAuditor auditor =
((ChromeApplication) mTab.getApplicationContext()).getPolicyAuditor();
auditor.notifyAuditEvent(mTab.getApplicationContext(), AuditEvent.OPEN_URL_FAILURE,
failingUrl, description);
if (errorCode == BLOCKED_BY_ADMINISTRATOR) {
auditor.notifyAuditEvent(
mTab.getApplicationContext(), AuditEvent.OPEN_URL_BLOCKED, failingUrl, "");
}
}
@Override
public void didStartProvisionalLoadForFrame(long frameId, long parentFrameId,
boolean isMainFrame, String validatedUrl, boolean isErrorPage,
boolean isIframeSrcdoc) {
if (isMainFrame) mTab.didStartPageLoad(validatedUrl, isErrorPage);
RewindableIterator<TabObserver> observers = mTab.getTabObservers();
while (observers.hasNext()) {
observers.next().onDidStartProvisionalLoadForFrame(mTab, frameId, parentFrameId,
isMainFrame, validatedUrl, isErrorPage, isIframeSrcdoc);
}
}
@Override
public void didCommitProvisionalLoadForFrame(long frameId, boolean isMainFrame, String url,
int transitionType) {
if (isMainFrame && UmaUtils.isRunningApplicationStart()) {
// Currently it takes about 2000ms to commit a navigation if the measurement
// begins very early in the browser start. How many buckets (b) are needed to
// explore the _typical_ values with granularity 100ms and a maximum duration
// of 1 minute?
// s^{n+1} / s^{n} = 2100 / 2000
// s = 1.05
// s^b = 60000
// b = ln(60000) / ln(1.05) ~= 225
RecordHistogram.recordCustomTimesHistogram("Startup.FirstCommitNavigationTime2",
SystemClock.uptimeMillis() - UmaUtils.getForegroundStartTime(),
1, 60000 /* 1 minute */, TimeUnit.MILLISECONDS, 225);
UmaUtils.setRunningApplicationStart(false);
}
if (isMainFrame) {
mTab.setIsTabStateDirty(true);
mTab.updateTitle();
}
RewindableIterator<TabObserver> observers = mTab.getTabObservers();
while (observers.hasNext()) {
observers.next().onDidCommitProvisionalLoadForFrame(
mTab, frameId, isMainFrame, url, transitionType);
}
observers.rewind();
while (observers.hasNext()) {
observers.next().onUrlUpdated(mTab);
}
if (!isMainFrame) return;
mTab.handleDidCommitProvisonalLoadForFrame(url, transitionType);
}
@Override
public void didNavigateMainFrame(String url, String baseUrl,
boolean isNavigationToDifferentPage, boolean isFragmentNavigation, int statusCode) {
FullscreenManager fullscreenManager = mTab.getFullscreenManager();
if (isNavigationToDifferentPage && fullscreenManager != null) {
fullscreenManager.setPersistentFullscreenMode(false);
}
RewindableIterator<TabObserver> observers = mTab.getTabObservers();
while (observers.hasNext()) {
observers.next().onDidNavigateMainFrame(
mTab, url, baseUrl, isNavigationToDifferentPage,
isFragmentNavigation, statusCode);
}
mTab.stopSwipeRefreshHandler();
}
@Override
public void didFirstVisuallyNonEmptyPaint() {
RewindableIterator<TabObserver> observers = mTab.getTabObservers();
while (observers.hasNext()) {
observers.next().didFirstVisuallyNonEmptyPaint(mTab);
}
}
@Override
public void didChangeThemeColor(int color) {
mTab.updateThemeColorIfNeeded(true);
}
@Override
public void didAttachInterstitialPage() {
mTab.getInfoBarContainer().setVisibility(View.INVISIBLE);
mTab.showRenderedPage();
mTab.updateThemeColorIfNeeded(false);
RewindableIterator<TabObserver> observers = mTab.getTabObservers();
while (observers.hasNext()) {
observers.next().onDidAttachInterstitialPage(mTab);
}
mTab.notifyLoadProgress(mTab.getProgress());
mTab.updateFullscreenEnabledState();
PolicyAuditor auditor =
((ChromeApplication) mTab.getApplicationContext()).getPolicyAuditor();
auditor.notifyCertificateFailure(
PolicyAuditor.nativeGetCertificateFailure(mTab.getWebContents()),
mTab.getApplicationContext());
}
@Override
public void didDetachInterstitialPage() {
mTab.getInfoBarContainer().setVisibility(View.VISIBLE);
mTab.updateThemeColorIfNeeded(false);
RewindableIterator<TabObserver> observers = mTab.getTabObservers();
while (observers.hasNext()) {
observers.next().onDidDetachInterstitialPage(mTab);
}
mTab.notifyLoadProgress(mTab.getProgress());
mTab.updateFullscreenEnabledState();
if (!mTab.maybeShowNativePage(mTab.getUrl(), false)) {
mTab.showRenderedPage();
}
}
@Override
public void didStartNavigationToPendingEntry(String url) {
RewindableIterator<TabObserver> observers = mTab.getTabObservers();
while (observers.hasNext()) {
observers.next().onDidStartNavigationToPendingEntry(mTab, url);
}
}
@Override
public void destroy() {
MediaCaptureNotificationService.updateMediaNotificationForTab(
mTab.getApplicationContext(), mTab.getId(), 0, mTab.getUrl());
super.destroy();
}
}