Commit 864dcb75 authored by Patrick Noland's avatar Patrick Noland Committed by Commit Bot

[ToolbarMVC] Make LoadProgress into a discrete MVC Component

This moves logic from ToolbarManager into a Coordinator, Mediator, and
ViewBinder that are hooked up to the existing progress view, and adds
unit tests for the new mediator.
This change shouldn't affect visible behavior.

Bug: 865801
Change-Id: I24e121aec7be79535b67d8c5b647327c0e8ba99c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2001264Reviewed-by: default avatarYusuf Ozuysal <yusufo@chromium.org>
Reviewed-by: default avatarMatthew Jones <mdjones@chromium.org>
Commit-Queue: Patrick Noland <pnoland@chromium.org>
Cr-Commit-Position: refs/heads/master@{#745075}
parent fba9a5b4
...@@ -1695,6 +1695,11 @@ chrome_java_sources = [ ...@@ -1695,6 +1695,11 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarMediator.java", "java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarMediator.java",
"java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarModel.java", "java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarModel.java",
"java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarViewBinder.java", "java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarViewBinder.java",
"java/src/org/chromium/chrome/browser/toolbar/load_progress/LoadProgressCoordinator.java",
"java/src/org/chromium/chrome/browser/toolbar/load_progress/LoadProgressMediator.java",
"java/src/org/chromium/chrome/browser/toolbar/load_progress/LoadProgressProperties.java",
"java/src/org/chromium/chrome/browser/toolbar/load_progress/LoadProgressSimulator.java",
"java/src/org/chromium/chrome/browser/toolbar/load_progress/LoadProgressViewBinder.java",
"java/src/org/chromium/chrome/browser/toolbar/top/ActionModeController.java", "java/src/org/chromium/chrome/browser/toolbar/top/ActionModeController.java",
"java/src/org/chromium/chrome/browser/toolbar/top/CustomTabToolbar.java", "java/src/org/chromium/chrome/browser/toolbar/top/CustomTabToolbar.java",
"java/src/org/chromium/chrome/browser/toolbar/top/CustomTabToolbarAnimationDelegate.java", "java/src/org/chromium/chrome/browser/toolbar/top/CustomTabToolbarAnimationDelegate.java",
......
...@@ -206,6 +206,7 @@ chrome_junit_test_java_sources = [ ...@@ -206,6 +206,7 @@ chrome_junit_test_java_sources = [
"junit/src/org/chromium/chrome/browser/tabstate/TabStateUnitTest.java", "junit/src/org/chromium/chrome/browser/tabstate/TabStateUnitTest.java",
"junit/src/org/chromium/chrome/browser/tasks/EngagementTimeUtilTest.java", "junit/src/org/chromium/chrome/browser/tasks/EngagementTimeUtilTest.java",
"junit/src/org/chromium/chrome/browser/tasks/JourneyManagerTest.java", "junit/src/org/chromium/chrome/browser/tasks/JourneyManagerTest.java",
"junit/src/org/chromium/chrome/browser/toolbar/load_progress/LoadProgressMediatorTest.java",
"junit/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediatorUnitTest.java", "junit/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediatorUnitTest.java",
"junit/src/org/chromium/chrome/browser/usage_stats/EventTrackerTest.java", "junit/src/org/chromium/chrome/browser/usage_stats/EventTrackerTest.java",
"junit/src/org/chromium/chrome/browser/usage_stats/PageViewObserverTest.java", "junit/src/org/chromium/chrome/browser/usage_stats/PageViewObserverTest.java",
......
// Copyright 2020 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.load_progress;
import org.chromium.chrome.browser.ActivityTabProvider;
import org.chromium.chrome.browser.toolbar.ToolbarProgressBar;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
/**
* Coordinator for the load progress bar. Owns all progress bar sub-components.
*/
public class LoadProgressCoordinator {
private final PropertyModel mModel;
private final LoadProgressMediator mMediator;
private final ToolbarProgressBar mProgressBarView;
private final LoadProgressViewBinder mLoadProgressViewBinder;
private final PropertyModelChangeProcessor<PropertyModel, ToolbarProgressBar, PropertyKey>
mPropertyModelChangeProcessor;
public LoadProgressCoordinator(
ActivityTabProvider activityTabProvider, ToolbarProgressBar progressBarView) {
mProgressBarView = progressBarView;
mModel = new PropertyModel(LoadProgressProperties.ALL_KEYS);
mMediator = new LoadProgressMediator(activityTabProvider, mModel);
mLoadProgressViewBinder = new LoadProgressViewBinder();
mPropertyModelChangeProcessor = PropertyModelChangeProcessor.create(
mModel, mProgressBarView, mLoadProgressViewBinder::bind);
}
}
// Copyright 2020 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.load_progress;
import org.chromium.base.MathUtils;
import org.chromium.chrome.browser.ActivityTabProvider;
import org.chromium.chrome.browser.native_page.NativePageFactory;
import org.chromium.chrome.browser.ntp.NewTabPage;
import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.toolbar.load_progress.LoadProgressProperties.CompletionState;
import org.chromium.content_public.browser.NavigationHandle;
import org.chromium.ui.modelutil.PropertyModel;
/**
* Mediator for the load progress bar. Listens for changes to the loading state of the current tab
* and adjusts its property model accordingly.
*/
public class LoadProgressMediator {
static final float MINIMUM_LOAD_PROGRESS = 0.05f;
private final PropertyModel mModel;
private final EmptyTabObserver mTabObserver;
private final LoadProgressSimulator mLoadProgressSimulator;
public LoadProgressMediator(ActivityTabProvider activityTabProvider, PropertyModel model) {
mModel = model;
mLoadProgressSimulator = new LoadProgressSimulator(model);
mTabObserver = new ActivityTabProvider.ActivityTabTabObserver(activityTabProvider) {
@Override
public void onDidStartNavigation(Tab tab, NavigationHandle navigation) {
if (!navigation.isInMainFrame()) {
return;
}
if (NativePageFactory.isNativePageUrl(navigation.getUrl(), tab.isIncognito())) {
finishLoadProgress(false);
return;
}
mLoadProgressSimulator.cancel();
startLoadProgress();
updateLoadProgress(tab.getProgress());
}
@Override
public void onLoadStopped(Tab tab, boolean toDifferentDocument) {
if (!toDifferentDocument) return;
// If we made some progress, fast-forward to complete, otherwise just dismiss any
// MINIMUM_LOAD_PROGRESS that had been set.
if (tab.getProgress() > MINIMUM_LOAD_PROGRESS && tab.getProgress() < 1) {
updateLoadProgress(1.0f);
}
finishLoadProgress(true);
}
@Override
public void onLoadProgressChanged(Tab tab, float progress) {
if (NewTabPage.isNTPUrl(tab.getUrlString())
|| NativePageFactory.isNativePageUrl(
tab.getUrlString(), tab.isIncognito())) {
return;
}
updateLoadProgress(progress);
}
@Override
public void onWebContentsSwapped(Tab tab, boolean didStartLoad, boolean didFinishLoad) {
// If loading both started and finished before we swapped in the WebContents, we
// won't get any load progress signals. Otherwise, we should receive at least one
// real signal so we don't need to simulate them.
if (didStartLoad && didFinishLoad) {
mLoadProgressSimulator.start();
}
}
@Override
public void onCrash(Tab tab) {
finishLoadProgress(false);
}
@Override
protected void onObservingDifferentTab(Tab tab) {
onNewTabObserved(tab);
}
};
onNewTabObserved(activityTabProvider.get());
}
private void onNewTabObserved(Tab tab) {
if (tab == null) return;
if (tab.isLoading()) {
if (NativePageFactory.isNativePageUrl(tab.getUrlString(), tab.isIncognito())) {
finishLoadProgress(false);
} else {
startLoadProgress();
updateLoadProgress(tab.getProgress());
}
} else {
finishLoadProgress(false);
}
}
private void startLoadProgress() {
mModel.set(LoadProgressProperties.COMPLETION_STATE, CompletionState.UNFINISHED);
}
private void updateLoadProgress(float progress) {
progress = Math.max(progress, MINIMUM_LOAD_PROGRESS);
mModel.set(LoadProgressProperties.PROGRESS, progress);
if (MathUtils.areFloatsEqual(progress, 1)) finishLoadProgress(true);
}
private void finishLoadProgress(boolean animateCompletion) {
mLoadProgressSimulator.cancel();
@CompletionState
int completionState = animateCompletion ? CompletionState.FINISHED_DO_ANIMATE
: CompletionState.FINISHED_DONT_ANIMATE;
mModel.set(LoadProgressProperties.COMPLETION_STATE, completionState);
}
}
// Copyright 2020 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.load_progress;
import androidx.annotation.IntDef;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** List of load progress bar properties. */
class LoadProgressProperties {
@IntDef({
CompletionState.UNFINISHED,
CompletionState.FINISHED_DO_ANIMATE,
CompletionState.FINISHED_DONT_ANIMATE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface CompletionState {
int UNFINISHED = 0;
int FINISHED_DO_ANIMATE = 1;
int FINISHED_DONT_ANIMATE = 2;
}
public static final PropertyModel.WritableIntPropertyKey COMPLETION_STATE =
new PropertyModel.WritableIntPropertyKey();
public static final PropertyModel.WritableFloatPropertyKey PROGRESS =
new PropertyModel.WritableFloatPropertyKey();
static final PropertyKey[] ALL_KEYS = {COMPLETION_STATE, PROGRESS};
}
\ No newline at end of file
// Copyright 2020 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.load_progress;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.MathUtils;
import org.chromium.ui.modelutil.PropertyModel;
/**
* Simulator for load progress changes when the page being rendered doesn't actually send load
* progress signals, e.g. when swapping in a pre-rendered page. Uses a message loop to send update
* messages to itself to update the simulated progress value on a regular interval.
*/
class LoadProgressSimulator {
private static final int MSG_ID_UPDATE_PROGRESS = 1;
private static final int PROGRESS_INCREMENT_DELAY_MS = 10;
@VisibleForTesting
static final float PROGRESS_INCREMENT = 0.1f;
private final PropertyModel mModel;
private final Handler mHandler;
private float mProgress;
public LoadProgressSimulator(PropertyModel model) {
mModel = model;
mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
assert msg.what == MSG_ID_UPDATE_PROGRESS;
mProgress = Math.min(1, mProgress += PROGRESS_INCREMENT);
mModel.set(LoadProgressProperties.PROGRESS, mProgress);
if (MathUtils.areFloatsEqual(mProgress, 1.0f)) {
mModel.set(LoadProgressProperties.COMPLETION_STATE,
LoadProgressProperties.CompletionState.FINISHED_DO_ANIMATE);
return;
}
sendEmptyMessageDelayed(MSG_ID_UPDATE_PROGRESS, PROGRESS_INCREMENT_DELAY_MS);
}
};
}
/**
* Start simulating load progress from a baseline of 0.
*/
public void start() {
mProgress = 0.0f;
mModel.set(LoadProgressProperties.COMPLETION_STATE,
LoadProgressProperties.CompletionState.UNFINISHED);
mModel.set(LoadProgressProperties.PROGRESS, mProgress);
mHandler.sendEmptyMessage(MSG_ID_UPDATE_PROGRESS);
}
/**
* Cancels simulating load progress.
*/
public void cancel() {
mModel.set(LoadProgressProperties.COMPLETION_STATE,
LoadProgressProperties.CompletionState.FINISHED_DONT_ANIMATE);
mHandler.removeMessages(MSG_ID_UPDATE_PROGRESS);
}
}
\ No newline at end of file
// Copyright 2020 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.load_progress;
import org.chromium.chrome.browser.toolbar.ToolbarProgressBar;
import org.chromium.chrome.browser.toolbar.load_progress.LoadProgressProperties.CompletionState;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
/**
* View binder for the load progress bar. Adjusts view properties on ToolbarProgressBar in response
* to changes in the associated property model.
*/
public class LoadProgressViewBinder {
public void bind(PropertyModel model, ToolbarProgressBar view, PropertyKey propertyKey) {
if (propertyKey == LoadProgressProperties.COMPLETION_STATE) {
@CompletionState
int completionState = model.get(LoadProgressProperties.COMPLETION_STATE);
boolean done = !(completionState == CompletionState.UNFINISHED);
if (done) {
view.finish(completionState == CompletionState.FINISHED_DO_ANIMATE);
} else {
view.start();
}
} else if (propertyKey == LoadProgressProperties.PROGRESS) {
float progress = model.get(LoadProgressProperties.PROGRESS);
view.setProgress(progress);
}
}
}
mdjones@chromium.org
pnoland@chromium.org
# COMPONENT: UI>Browser>Toolbar
# OS: Android
// Copyright 2020 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.load_progress;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.os.Looper;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.Shadows;
import org.robolectric.annotation.Config;
import org.chromium.base.Callback;
import org.chromium.base.MathUtils;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.ActivityTabProvider;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabImpl;
import org.chromium.chrome.browser.tab.TabObserver;
import org.chromium.chrome.browser.toolbar.load_progress.LoadProgressProperties.CompletionState;
import org.chromium.content_public.browser.NavigationHandle;
import org.chromium.ui.modelutil.PropertyModel;
/** Unit tests for LoadProgressMediator. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class LoadProgressMediatorTest {
private static final String URL_1 = "http://starting.url";
private static final String NATIVE_PAGE_URL = "chrome-native://newtab";
@Mock
public ActivityTabProvider mActivityTabProvider;
@Mock
private TabImpl mTab;
@Mock
private TabImpl mTab2;
@Captor
public ArgumentCaptor<TabObserver> mTabObserverCaptor;
@Captor
public ArgumentCaptor<Callback<Tab>> mActivityTabObserverCaptor;
private PropertyModel mModel;
private LoadProgressMediator mMediator;
private TabObserver mTabObserver;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mActivityTabProvider.get()).thenReturn(mTab);
mModel = new PropertyModel(LoadProgressProperties.ALL_KEYS);
mMediator = new LoadProgressMediator(mActivityTabProvider, mModel);
verify(mActivityTabProvider).addObserver(mActivityTabObserverCaptor.capture());
verify(mTab).addObserver(mTabObserverCaptor.capture());
mTabObserver = mTabObserverCaptor.getValue();
}
@Test
public void loadRegularPage() {
assertEquals(mModel.get(LoadProgressProperties.COMPLETION_STATE),
CompletionState.FINISHED_DONT_ANIMATE);
NavigationHandle navigation = new NavigationHandle(0, URL_1, true, false, false);
mTabObserver.onDidStartNavigation(mTab, navigation);
assertEquals(
mModel.get(LoadProgressProperties.COMPLETION_STATE), CompletionState.UNFINISHED);
assertEquals(mModel.get(LoadProgressProperties.PROGRESS),
LoadProgressMediator.MINIMUM_LOAD_PROGRESS, MathUtils.EPSILON);
mTabObserver.onLoadProgressChanged(mTab, 0.1f);
assertEquals(mModel.get(LoadProgressProperties.PROGRESS), 0.1f, MathUtils.EPSILON);
mTabObserver.onLoadProgressChanged(mTab, 1.0f);
assertEquals(mModel.get(LoadProgressProperties.PROGRESS), 1.0f, MathUtils.EPSILON);
assertEquals(mModel.get(LoadProgressProperties.COMPLETION_STATE),
CompletionState.FINISHED_DO_ANIMATE);
}
@Test
public void switchToLoadingTab() {
doReturn(true).when(mTab2).isLoading();
doReturn(0.1f).when(mTab2).getProgress();
mActivityTabObserverCaptor.getValue().onResult(mTab2);
verify(mTab2, times(1)).addObserver(any());
assertEquals(
mModel.get(LoadProgressProperties.COMPLETION_STATE), CompletionState.UNFINISHED);
assertEquals(mModel.get(LoadProgressProperties.PROGRESS), 0.1f, MathUtils.EPSILON);
}
@Test
public void switchToLoadedTab() {
NavigationHandle navigation = new NavigationHandle(0, URL_1, true, false, false);
mTabObserver.onDidStartNavigation(mTab, navigation);
assertEquals(
mModel.get(LoadProgressProperties.COMPLETION_STATE), CompletionState.UNFINISHED);
assertEquals(mModel.get(LoadProgressProperties.PROGRESS),
LoadProgressMediator.MINIMUM_LOAD_PROGRESS, MathUtils.EPSILON);
mActivityTabObserverCaptor.getValue().onResult(mTab2);
verify(mTab2, times(1)).addObserver(any());
assertEquals(mModel.get(LoadProgressProperties.COMPLETION_STATE),
CompletionState.FINISHED_DONT_ANIMATE);
}
@Test
public void loadNativePage() {
doReturn(0.1f).when(mTab).getProgress();
NavigationHandle navigation = new NavigationHandle(0, URL_1, true, false, false);
mTabObserver.onDidStartNavigation(mTab, navigation);
assertEquals(
mModel.get(LoadProgressProperties.COMPLETION_STATE), CompletionState.UNFINISHED);
assertEquals(mModel.get(LoadProgressProperties.PROGRESS), 0.1f, MathUtils.EPSILON);
navigation = new NavigationHandle(0, NATIVE_PAGE_URL, true, false, false);
mTabObserver.onDidStartNavigation(mTab, navigation);
assertEquals(mModel.get(LoadProgressProperties.COMPLETION_STATE),
CompletionState.FINISHED_DONT_ANIMATE);
}
@Test
public void switchToTabWithNativePage() {
NavigationHandle navigation = new NavigationHandle(0, URL_1, true, false, false);
mTabObserver.onDidStartNavigation(mTab, navigation);
assertEquals(
mModel.get(LoadProgressProperties.COMPLETION_STATE), CompletionState.UNFINISHED);
assertEquals(mModel.get(LoadProgressProperties.PROGRESS),
LoadProgressMediator.MINIMUM_LOAD_PROGRESS, MathUtils.EPSILON);
when(mTab2.getUrlString()).thenReturn(NATIVE_PAGE_URL);
mActivityTabObserverCaptor.getValue().onResult(mTab2);
verify(mTab2, times(1)).addObserver(any());
assertEquals(mModel.get(LoadProgressProperties.COMPLETION_STATE),
CompletionState.FINISHED_DONT_ANIMATE);
assertEquals(mModel.get(LoadProgressProperties.PROGRESS),
LoadProgressMediator.MINIMUM_LOAD_PROGRESS, MathUtils.EPSILON);
}
@Test
public void pageCrashes() {
NavigationHandle navigation = new NavigationHandle(0, URL_1, true, false, false);
mTabObserver.onDidStartNavigation(mTab, navigation);
assertEquals(
mModel.get(LoadProgressProperties.COMPLETION_STATE), CompletionState.UNFINISHED);
assertEquals(mModel.get(LoadProgressProperties.PROGRESS),
LoadProgressMediator.MINIMUM_LOAD_PROGRESS, MathUtils.EPSILON);
mTabObserver.onCrash(mTab);
assertEquals(mModel.get(LoadProgressProperties.COMPLETION_STATE),
CompletionState.FINISHED_DONT_ANIMATE);
assertEquals(mModel.get(LoadProgressProperties.PROGRESS),
LoadProgressMediator.MINIMUM_LOAD_PROGRESS, MathUtils.EPSILON);
}
@Test
public void testSwapWebContents() {
assertEquals(mModel.get(LoadProgressProperties.COMPLETION_STATE),
CompletionState.FINISHED_DONT_ANIMATE);
mTabObserver.onWebContentsSwapped(mTab, true, true);
assertEquals(
mModel.get(LoadProgressProperties.COMPLETION_STATE), CompletionState.UNFINISHED);
assertEquals(mModel.get(LoadProgressProperties.PROGRESS),
LoadProgressSimulator.PROGRESS_INCREMENT, MathUtils.EPSILON);
float expectedProgress = LoadProgressSimulator.PROGRESS_INCREMENT * 2;
while (expectedProgress < 1.0f + LoadProgressSimulator.PROGRESS_INCREMENT) {
Shadows.shadowOf(Looper.getMainLooper()).runToEndOfTasks();
assertEquals(mModel.get(LoadProgressProperties.PROGRESS), expectedProgress,
MathUtils.EPSILON);
expectedProgress += LoadProgressSimulator.PROGRESS_INCREMENT;
}
assertEquals(mModel.get(LoadProgressProperties.COMPLETION_STATE),
CompletionState.FINISHED_DO_ANIMATE);
}
}
file://chrome/android/java/src/org/chromium/chrome/browser/toolbar/load_progress/OWNERS
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment