// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.content.browser.webcontents;
import org.jni_zero.CalledByNative;
import org.jni_zero.JNINamespace;
import org.jni_zero.NativeMethods;
import org.chromium.base.ObserverList;
import org.chromium.base.ObserverList.RewindableIterator;
import org.chromium.base.TerminationStatus;
import org.chromium.base.ThreadUtils;
import org.chromium.content_public.browser.GlobalRenderFrameHostId;
import org.chromium.content_public.browser.LifecycleState;
import org.chromium.content_public.browser.LoadCommittedDetails;
import org.chromium.content_public.browser.MediaSession;
import org.chromium.content_public.browser.NavigationHandle;
import org.chromium.content_public.browser.WebContentsObserver;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.mojom.VirtualKeyboardMode;
import org.chromium.url.GURL;
import java.util.Iterator;
/**
* Serves as a compound observer proxy for dispatching WebContentsObserver callbacks,
* avoiding redundant JNI-related work when there are multiple Java-based observers.
*/
@JNINamespace("content")
class WebContentsObserverProxy extends WebContentsObserver {
private long mNativeWebContentsObserverProxy;
private final ObserverList<WebContentsObserver> mObservers;
private int mObserverCallsCurrentlyHandling;
/**
* Constructs a new WebContentsObserverProxy for a given WebContents
* instance. A native WebContentsObserver instance will be created, which
* will observe the native counterpart to the provided WebContents.
*
* @param webContents The WebContents instance to observe.
*/
public WebContentsObserverProxy(WebContentsImpl webContents) {
ThreadUtils.assertOnUiThread();
mNativeWebContentsObserverProxy =
WebContentsObserverProxyJni.get().init(WebContentsObserverProxy.this, webContents);
mObservers = new ObserverList<WebContentsObserver>();
mObserverCallsCurrentlyHandling = 0;
}
/**
* Add an observer to the list of proxied observers.
* @param observer The WebContentsObserver instance to add.
*/
void addObserver(WebContentsObserver observer) {
assert mNativeWebContentsObserverProxy != 0;
mObservers.addObserver(observer);
}
/**
* Remove an observer from the list of proxied observers.
* @param observer The WebContentsObserver instance to remove.
*/
void removeObserver(WebContentsObserver observer) {
mObservers.removeObserver(observer);
}
/**
* @return Whether there are any active, proxied observers.
*/
boolean hasObservers() {
return !mObservers.isEmpty();
}
public boolean isHandlingObserverCall() {
return mObserverCallsCurrentlyHandling > 0;
}
private void handleObserverCall() {
mObserverCallsCurrentlyHandling++;
}
private void finishObserverCall() {
mObserverCallsCurrentlyHandling--;
}
@CalledByNative
public void renderFrameCreated(int renderProcessId, int renderFrameId) {
renderFrameCreated(new GlobalRenderFrameHostId(renderProcessId, renderFrameId));
}
@Override
public void renderFrameCreated(GlobalRenderFrameHostId id) {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator.next().renderFrameCreated(id);
}
finishObserverCall();
}
@CalledByNative
public void renderFrameDeleted(int renderProcessId, int renderFrameId) {
renderFrameDeleted(new GlobalRenderFrameHostId(renderProcessId, renderFrameId));
}
@Override
public void renderFrameDeleted(GlobalRenderFrameHostId id) {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator.next().renderFrameDeleted(id);
}
finishObserverCall();
}
@Override
@CalledByNative
public void primaryMainFrameRenderProcessGone(@TerminationStatus int terminationStatus) {
// Don't call handleObserverCall() and finishObserverCall() to explicitly allow a
// WebContents to be destroyed while handling an this observer call. See
// https://chromium-review.googlesource.com/c/chromium/src/+/2343269 for details
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator.next().primaryMainFrameRenderProcessGone(terminationStatus);
}
}
@Override
@CalledByNative
public void didStartNavigationInPrimaryMainFrame(NavigationHandle navigation) {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator.next().didStartNavigationInPrimaryMainFrame(navigation);
}
finishObserverCall();
}
@Override
@CalledByNative
public void didRedirectNavigation(NavigationHandle navigation) {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator.next().didRedirectNavigation(navigation);
}
finishObserverCall();
}
@Override
@CalledByNative
public void didFinishNavigationInPrimaryMainFrame(NavigationHandle navigation) {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator.next().didFinishNavigationInPrimaryMainFrame(navigation);
}
finishObserverCall();
}
@Override
@CalledByNative
public void didStartLoading(GURL url) {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator.next().didStartLoading(url);
}
finishObserverCall();
}
@Override
@CalledByNative
public void didStopLoading(GURL url, boolean isKnownValid) {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator.next().didStopLoading(url, isKnownValid);
}
finishObserverCall();
}
@Override
@CalledByNative
public void loadProgressChanged(float progress) {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator.next().loadProgressChanged(progress);
}
finishObserverCall();
}
@Override
@CalledByNative
public void didChangeVisibleSecurityState() {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator.next().didChangeVisibleSecurityState();
}
finishObserverCall();
}
@Override
@CalledByNative
public void didFailLoad(
boolean isInPrimaryMainFrame,
int errorCode,
GURL failingUrl,
@LifecycleState int frameLifecycleState) {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator
.next()
.didFailLoad(isInPrimaryMainFrame, errorCode, failingUrl, frameLifecycleState);
}
finishObserverCall();
}
@Override
@CalledByNative
public void didFirstVisuallyNonEmptyPaint() {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator.next().didFirstVisuallyNonEmptyPaint();
}
finishObserverCall();
}
@Override
@CalledByNative
public void wasShown() {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator.next().wasShown();
}
finishObserverCall();
}
@Override
@CalledByNative
public void wasHidden() {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator.next().wasHidden();
}
finishObserverCall();
}
@Override
@CalledByNative
public void titleWasSet(String title) {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator.next().titleWasSet(title);
}
finishObserverCall();
}
@Override
@CalledByNative
public void primaryMainDocumentElementAvailable() {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator.next().primaryMainDocumentElementAvailable();
}
finishObserverCall();
}
@CalledByNative
private void didFinishLoadInPrimaryMainFrame(
int renderProcessId,
int renderFrameId,
GURL url,
boolean isKnownValid,
@LifecycleState int frameLifecycleState) {
didFinishLoadInPrimaryMainFrame(
new GlobalRenderFrameHostId(renderProcessId, renderFrameId),
url,
isKnownValid,
frameLifecycleState);
}
@Override
public void didFinishLoadInPrimaryMainFrame(
GlobalRenderFrameHostId rfhId,
GURL url,
boolean isKnownValid,
@LifecycleState int rfhLifecycleState) {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator
.next()
.didFinishLoadInPrimaryMainFrame(rfhId, url, isKnownValid, rfhLifecycleState);
}
finishObserverCall();
}
@CalledByNative
private void documentLoadedInPrimaryMainFrame(
int renderProcessId, int renderFrameId, @LifecycleState int rfhLifecycleState) {
documentLoadedInPrimaryMainFrame(
new GlobalRenderFrameHostId(renderProcessId, renderFrameId), rfhLifecycleState);
}
@Override
public void documentLoadedInPrimaryMainFrame(
GlobalRenderFrameHostId rfhId, @LifecycleState int rfhLifecycleState) {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator.next().documentLoadedInPrimaryMainFrame(rfhId, rfhLifecycleState);
}
finishObserverCall();
}
@Override
@CalledByNative
public void navigationEntryCommitted(LoadCommittedDetails details) {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator.next().navigationEntryCommitted(details);
}
finishObserverCall();
}
@Override
@CalledByNative
public void navigationEntriesDeleted() {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator.next().navigationEntriesDeleted();
}
finishObserverCall();
}
@Override
@CalledByNative
public void navigationEntriesChanged() {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator.next().navigationEntriesChanged();
}
finishObserverCall();
}
@Override
@CalledByNative
public void frameReceivedUserActivation() {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator.next().frameReceivedUserActivation();
}
finishObserverCall();
}
@Override
@CalledByNative
public void didChangeThemeColor() {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator.next().didChangeThemeColor();
}
finishObserverCall();
}
@Override
@CalledByNative
public void onBackgroundColorChanged() {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
WebContentsObserver obs = observersIterator.next();
obs.onBackgroundColorChanged();
}
finishObserverCall();
}
@Override
@CalledByNative
public void mediaStartedPlaying() {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator.next().mediaStartedPlaying();
}
finishObserverCall();
}
@Override
@CalledByNative
public void mediaStoppedPlaying() {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator.next().mediaStoppedPlaying();
}
finishObserverCall();
}
@Override
@CalledByNative
public void hasEffectivelyFullscreenVideoChange(boolean isFullscreen) {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator.next().hasEffectivelyFullscreenVideoChange(isFullscreen);
}
finishObserverCall();
}
@Override
@CalledByNative
public void didToggleFullscreenModeForTab(boolean enteredFullscreen, boolean willCauseResize) {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator
.next()
.didToggleFullscreenModeForTab(enteredFullscreen, willCauseResize);
}
finishObserverCall();
}
@Override
@CalledByNative
public void viewportFitChanged(@WebContentsObserver.ViewportFitType int value) {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator.next().viewportFitChanged(value);
}
finishObserverCall();
}
@Override
@CalledByNative
public void virtualKeyboardModeChanged(@VirtualKeyboardMode.EnumType int mode) {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator.next().virtualKeyboardModeChanged(mode);
}
finishObserverCall();
}
@Override
@CalledByNative
public void onWebContentsFocused() {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator.next().onWebContentsFocused();
}
finishObserverCall();
}
@Override
@CalledByNative
public void onWebContentsLostFocus() {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator.next().onWebContentsLostFocus();
}
finishObserverCall();
}
@Override
public void onTopLevelNativeWindowChanged(WindowAndroid windowAndroid) {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator.next().onTopLevelNativeWindowChanged(windowAndroid);
}
finishObserverCall();
}
@Override
@CalledByNative
public void mediaSessionCreated(MediaSession mediaSession) {
handleObserverCall();
Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
for (; observersIterator.hasNext(); ) {
observersIterator.next().mediaSessionCreated(mediaSession);
}
finishObserverCall();
}
@Override
@CalledByNative
public void destroy() {
// Super destruction semantics (removing the observer from the
// Java-based WebContents) are quite different, so we explicitly avoid
// calling it here.
ThreadUtils.assertOnUiThread();
RewindableIterator<WebContentsObserver> observersIterator = mObservers.rewindableIterator();
for (; observersIterator.hasNext(); ) {
observersIterator.next().destroy();
}
// All observer destroy() implementations should result in their removal
// from the proxy.
String remainingObservers = "These observers were not removed: ";
if (!mObservers.isEmpty()) {
for (observersIterator.rewind(); observersIterator.hasNext(); ) {
remainingObservers += observersIterator.next().getClass().getName() + " ";
}
}
assert mObservers.isEmpty() : remainingObservers;
mObservers.clear();
if (mNativeWebContentsObserverProxy != 0) {
WebContentsObserverProxyJni.get()
.destroy(mNativeWebContentsObserverProxy, WebContentsObserverProxy.this);
mNativeWebContentsObserverProxy = 0;
}
}
@NativeMethods
interface Natives {
long init(WebContentsObserverProxy caller, WebContentsImpl webContents);
void destroy(long nativeWebContentsObserverProxy, WebContentsObserverProxy caller);
}
}