Commit 6b53051c authored by John Abd-El-Malek's avatar John Abd-El-Malek Committed by Commit Bot

Report information to WebLayer embedders when loading errors occurred.

Change-Id: I50993bca97f68dd35226aa43037a38302dc61263
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1902270
Commit-Queue: John Abd-El-Malek <jam@chromium.org>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Cr-Commit-Position: refs/heads/master@{#714066}
parent ec3a1bd5
......@@ -96,6 +96,7 @@ android_library("interfaces_java") {
"org/chromium/weblayer_private/interfaces/APICallException.java",
"org/chromium/weblayer_private/interfaces/BrowserFragmentArgs.java",
"org/chromium/weblayer_private/interfaces/BrowsingDataType.java",
"org/chromium/weblayer_private/interfaces/LoadError.java",
"org/chromium/weblayer_private/interfaces/NavigationState.java",
"org/chromium/weblayer_private/interfaces/NewTabType.java",
"org/chromium/weblayer_private/interfaces/ObjectWrapper.java",
......
......@@ -13,6 +13,7 @@ import org.chromium.weblayer_private.interfaces.APICallException;
import org.chromium.weblayer_private.interfaces.IClientNavigation;
import org.chromium.weblayer_private.interfaces.INavigation;
import org.chromium.weblayer_private.interfaces.INavigationControllerClient;
import org.chromium.weblayer_private.interfaces.LoadError;
import org.chromium.weblayer_private.interfaces.NavigationState;
import java.util.Arrays;
......@@ -81,12 +82,45 @@ public final class NavigationImpl extends INavigation.Stub {
return NavigationImplJni.get().isSameDocument(mNativeNavigationImpl, NavigationImpl.this);
}
@Override
public boolean isErrorPage() {
throwIfNativeDestroyed();
return NavigationImplJni.get().isErrorPage(mNativeNavigationImpl, NavigationImpl.this);
}
@Override
public int getLoadError() {
throwIfNativeDestroyed();
return implLoadErrorToLoadError(
NavigationImplJni.get().getLoadError(mNativeNavigationImpl, NavigationImpl.this));
}
private void throwIfNativeDestroyed() {
if (mNativeNavigationImpl == 0) {
throw new IllegalStateException("Using Navigation after native destroyed");
}
}
@LoadError
private static int implLoadErrorToLoadError(@ImplLoadError int loadError) {
switch (loadError) {
case ImplLoadError.NO_ERROR:
return LoadError.NO_ERROR;
case ImplLoadError.HTTP_CLIENT_ERROR:
return LoadError.HTTP_CLIENT_ERROR;
case ImplLoadError.HTTP_SERVER_ERROR:
return LoadError.HTTP_SERVER_ERROR;
case ImplLoadError.SSL_ERROR:
return LoadError.SSL_ERROR;
case ImplLoadError.CONNECTIVITY_ERROR:
return LoadError.CONNECTIVITY_ERROR;
case ImplLoadError.OTHER_ERROR:
return LoadError.OTHER_ERROR;
default:
throw new IllegalArgumentException("Unexpected load error " + loadError);
}
}
@CalledByNative
private void onNativeDestroyed() {
mNativeNavigationImpl = 0;
......@@ -100,5 +134,7 @@ public final class NavigationImpl extends INavigation.Stub {
String getUri(long nativeNavigationImpl, NavigationImpl caller);
String[] getRedirectChain(long nativeNavigationImpl, NavigationImpl caller);
boolean isSameDocument(long nativeNavigationImpl, NavigationImpl caller);
boolean isErrorPage(long nativeNavigationImpl, NavigationImpl caller);
int getLoadError(long nativeNavigationImpl, NavigationImpl caller);
}
}
......@@ -15,4 +15,8 @@ interface INavigation {
List<String> getRedirectChain() = 2;
boolean isSameDocument() = 3;
boolean isErrorPage() = 4;
int getLoadError() = 5;
}
// Copyright 2019 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.weblayer_private.interfaces;
import androidx.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@IntDef({LoadError.NO_ERROR, LoadError.HTTP_CLIENT_ERROR, LoadError.HTTP_SERVER_ERROR,
LoadError.SSL_ERROR, LoadError.CONNECTIVITY_ERROR, LoadError.OTHER_ERROR})
@Retention(RetentionPolicy.SOURCE)
public @interface LoadError {
int NO_ERROR = 0;
int HTTP_CLIENT_ERROR = 1;
int HTTP_SERVER_ERROR = 2;
int SSL_ERROR = 3;
int CONNECTIVITY_ERROR = 4;
int OTHER_ERROR = 5;
}
......@@ -9,4 +9,4 @@ package org.chromium.weblayer_private.interfaces;
*
* Whenever any AIDL file is changed, sVersionNumber must be incremented.
* */
public final class WebLayerVersion { public static final int sVersionNumber = 11; }
public final class WebLayerVersion { public static final int sVersionNumber = 12; }
// Copyright 2019 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.
#include "weblayer/test/weblayer_browser_test.h"
#include "base/files/file_path.h"
#include "content/public/test/url_loader_interceptor.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "weblayer/public/navigation.h"
#include "weblayer/public/navigation_controller.h"
#include "weblayer/public/navigation_observer.h"
#include "weblayer/public/tab.h"
#include "weblayer/shell/browser/shell.h"
#include "weblayer/test/interstitial_utils.h"
#include "weblayer/test/weblayer_browser_test_utils.h"
namespace weblayer {
namespace {
class OneShotNavigationObserver : public NavigationObserver {
public:
explicit OneShotNavigationObserver(Shell* shell) : tab_(shell->tab()) {
tab_->GetNavigationController()->AddObserver(this);
}
~OneShotNavigationObserver() override {
tab_->GetNavigationController()->RemoveObserver(this);
}
void WaitForNavigation() { run_loop_.Run(); }
bool completed() { return completed_; }
bool is_error_page() { return is_error_page_; }
Navigation::LoadError load_error() { return load_error_; }
private:
// NavigationObserver implementation:
void NavigationCompleted(Navigation* navigation) override {
completed_ = true;
Finish(navigation);
}
void NavigationFailed(Navigation* navigation) override { Finish(navigation); }
void Finish(Navigation* navigation) {
is_error_page_ = navigation->IsErrorPage();
load_error_ = navigation->GetLoadError();
run_loop_.Quit();
}
base::RunLoop run_loop_;
Tab* tab_;
bool completed_ = false;
bool is_error_page_ = false;
Navigation::LoadError load_error_ = Navigation::kNoError;
};
} // namespace
using NavigationBrowserTest = WebLayerBrowserTest;
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, NoError) {
EXPECT_TRUE(embedded_test_server()->Start());
OneShotNavigationObserver observer(shell());
shell()->tab()->GetNavigationController()->Navigate(
embedded_test_server()->GetURL("/simple_page.html"));
observer.WaitForNavigation();
EXPECT_TRUE(observer.completed());
EXPECT_FALSE(observer.is_error_page());
EXPECT_EQ(observer.load_error(), Navigation::kNoError);
}
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, HttpClientError) {
EXPECT_TRUE(embedded_test_server()->Start());
OneShotNavigationObserver observer(shell());
shell()->tab()->GetNavigationController()->Navigate(
embedded_test_server()->GetURL("/non_existent.html"));
observer.WaitForNavigation();
EXPECT_TRUE(observer.completed());
EXPECT_FALSE(observer.is_error_page());
EXPECT_EQ(observer.load_error(), Navigation::kHttpClientError);
}
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, HttpServerError) {
EXPECT_TRUE(embedded_test_server()->Start());
OneShotNavigationObserver observer(shell());
shell()->tab()->GetNavigationController()->Navigate(
embedded_test_server()->GetURL("/echo?status=500"));
observer.WaitForNavigation();
EXPECT_TRUE(observer.completed());
EXPECT_FALSE(observer.is_error_page());
EXPECT_EQ(observer.load_error(), Navigation::kHttpServerError);
}
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, SSLError) {
net::EmbeddedTestServer https_server_mismatched(
net::EmbeddedTestServer::TYPE_HTTPS);
https_server_mismatched.SetSSLConfig(
net::EmbeddedTestServer::CERT_MISMATCHED_NAME);
https_server_mismatched.AddDefaultHandlers(
base::FilePath(FILE_PATH_LITERAL("weblayer/test/data")));
ASSERT_TRUE(https_server_mismatched.Start());
OneShotNavigationObserver observer(shell());
shell()->tab()->GetNavigationController()->Navigate(
https_server_mismatched.GetURL("/simple_page.html"));
observer.WaitForNavigation();
EXPECT_FALSE(observer.completed());
EXPECT_TRUE(observer.is_error_page());
EXPECT_EQ(observer.load_error(), Navigation::kSSLError);
}
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, HttpConnectivityError) {
GURL url("http://doesntexist.com/foo");
auto interceptor = content::URLLoaderInterceptor::SetupRequestFailForURL(
url, net::ERR_NAME_NOT_RESOLVED);
OneShotNavigationObserver observer(shell());
shell()->tab()->GetNavigationController()->Navigate(url);
observer.WaitForNavigation();
EXPECT_FALSE(observer.completed());
EXPECT_TRUE(observer.is_error_page());
EXPECT_EQ(observer.load_error(), Navigation::kConnectivityError);
}
} // namespace weblayer
......@@ -5,6 +5,7 @@
#include "weblayer/browser/navigation_impl.h"
#include "content/public/browser/navigation_handle.h"
#include "net/base/net_errors.h"
#if defined(OS_ANDROID)
#include "base/android/jni_array.h"
......@@ -73,4 +74,30 @@ bool NavigationImpl::IsSameDocument() {
return navigation_handle_->IsSameDocument();
}
bool NavigationImpl::IsErrorPage() {
return navigation_handle_->IsErrorPage();
}
Navigation::LoadError NavigationImpl::GetLoadError() {
auto error_code = navigation_handle_->GetNetErrorCode();
if (auto* response_headers = navigation_handle_->GetResponseHeaders()) {
auto response_code = response_headers->response_code();
if (response_code >= 400 && response_code < 500)
return kHttpClientError;
if (response_code >= 500 && response_code < 600)
return kHttpServerError;
}
if (error_code == net::OK)
return kNoError;
if (net::IsCertificateError(error_code))
return kSSLError;
if (error_code <= -100 && error_code > -200)
return kConnectivityError;
return kOtherError;
}
} // namespace weblayer
......@@ -41,6 +41,14 @@ class NavigationImpl : public Navigation {
const base::android::JavaParamRef<jobject>& obj) {
return IsSameDocument();
}
bool IsErrorPage(JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj) {
return IsErrorPage();
}
int GetLoadError(JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj) {
return static_cast<int>(GetLoadError());
}
base::android::ScopedJavaGlobalRef<jobject> java_navigation() {
return java_navigation_;
......@@ -53,6 +61,8 @@ class NavigationImpl : public Navigation {
const std::vector<GURL>& GetRedirectChain() override;
NavigationState GetState() override;
bool IsSameDocument() override;
bool IsErrorPage() override;
LoadError GetLoadError() override;
content::NavigationHandle* navigation_handle_;
......
......@@ -30,6 +30,7 @@ android_library("java") {
"org/chromium/weblayer/FullscreenCallback.java",
"org/chromium/weblayer/ListenableFuture.java",
"org/chromium/weblayer/ListenableResult.java",
"org/chromium/weblayer/LoadError.java",
"org/chromium/weblayer/NavigationCallback.java",
"org/chromium/weblayer/NavigationController.java",
"org/chromium/weblayer/Navigation.java",
......
// Copyright 2019 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.weblayer;
import androidx.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@IntDef({LoadError.NO_ERROR, LoadError.HTTP_CLIENT_ERROR, LoadError.HTTP_SERVER_ERROR,
LoadError.SSL_ERROR, LoadError.CONNECTIVITY_ERROR, LoadError.OTHER_ERROR})
@Retention(RetentionPolicy.SOURCE)
public @interface LoadError {
/**
* Navigation completed successfully.
*/
int NO_ERROR = org.chromium.weblayer_private.interfaces.LoadError.NO_ERROR;
/**
* Server responded with 4xx status code.
*/
int HTTP_CLIENT_ERROR = org.chromium.weblayer_private.interfaces.LoadError.HTTP_CLIENT_ERROR;
/**
* Server responded with 5xx status code.
*/
int HTTP_SERVER_ERROR = org.chromium.weblayer_private.interfaces.LoadError.HTTP_SERVER_ERROR;
/**
* Certificate error.
*/
int SSL_ERROR = org.chromium.weblayer_private.interfaces.LoadError.SSL_ERROR;
/**
* Problem connecting to server.
*/
int CONNECTIVITY_ERROR = org.chromium.weblayer_private.interfaces.LoadError.CONNECTIVITY_ERROR;
/**
* An error not listed above occurred.
*/
int OTHER_ERROR = org.chromium.weblayer_private.interfaces.LoadError.OTHER_ERROR;
}
......@@ -74,10 +74,37 @@ public final class Navigation extends IClientNavigation.Stub {
* - same page history navigation
*/
public boolean isSameDocument() {
ThreadCheck.ensureOnUiThread();
try {
return mNavigationImpl.isSameDocument();
} catch (RemoteException e) {
throw new APICallException(e);
}
}
/**
* Whether the navigation resulted in an error page (e.g. interstitial). Note that if an error
* page reloads, this will return true even though GetNetErrorCode will be kNoError.
*/
public boolean isErrorPage() {
ThreadCheck.ensureOnUiThread();
try {
return mNavigationImpl.isErrorPage();
} catch (RemoteException e) {
throw new APICallException(e);
}
}
/**
* Return information about the error, if any, that was encountered while loading the page.
*/
@LoadError
public int getLoadError() {
ThreadCheck.ensureOnUiThread();
try {
return mNavigationImpl.getLoadError();
} catch (RemoteException e) {
throw new APICallException(e);
}
}
}
......@@ -43,6 +43,26 @@ class Navigation {
// * pushState/replaceState
// * same page history navigation
virtual bool IsSameDocument() = 0;
// Whether the navigation resulted in an error page (e.g. interstitial). Note
// that if an error page reloads, this will return true even though
// GetNetErrorCode will be kNoError.
virtual bool IsErrorPage() = 0;
// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.weblayer_private
// GENERATED_JAVA_CLASS_NAME_OVERRIDE: ImplLoadError
enum LoadError {
kNoError = 0, // Navigation completed successfully.
kHttpClientError = 1, // Server responded with 4xx status code.
kHttpServerError = 2, // Server responded with 5xx status code.
kSSLError = 3, // Certificate error.
kConnectivityError = 4, // Problem connecting to server.
kOtherError = 5, // An error not listed above occurred.
};
// Return information about the error, if any, that was encountered while
// loading the page.
virtual LoadError GetLoadError() = 0;
};
} // namespace weblayer
......
......@@ -22,6 +22,7 @@ import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.content_public.browser.test.util.Criteria;
import org.chromium.content_public.browser.test.util.CriteriaHelper;
import org.chromium.weblayer.LoadError;
import org.chromium.weblayer.Navigation;
import org.chromium.weblayer.NavigationCallback;
import org.chromium.weblayer.NavigationController;
......@@ -52,11 +53,13 @@ public class NavigationTest {
private Uri mUri;
private boolean mIsSameDocument;
private List<Uri> mRedirectChain;
private @LoadError int mLoadError;
public void notifyCalled(Navigation navigation) {
mUri = navigation.getUri();
mIsSameDocument = navigation.isSameDocument();
mRedirectChain = navigation.getRedirectChain();
mLoadError = navigation.getLoadError();
notifyCalled();
}
......@@ -77,6 +80,13 @@ public class NavigationTest {
waitForCallback(currentCallCount);
assertEquals(mRedirectChain, redirectChain);
}
public void assertCalledWith(int currentCallCount, String uri, @LoadError int loadError)
throws TimeoutException {
waitForCallback(currentCallCount);
assertEquals(mUri.toString(), uri);
assertEquals(mLoadError, loadError);
}
}
public static class NavigationCallbackValueRecorder {
......@@ -341,6 +351,22 @@ public class NavigationTest {
});
}
@Test
@SmallTest
public void testLoadError() throws Exception {
String url = mActivityTestRule.getTestDataURL("non_existent.html");
InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
setNavigationCallback(activity);
int curCompletedCount = mCallback.onCompletedCallback.getCallCount();
mActivityTestRule.navigateAndWait(url);
mCallback.onCompletedCallback.assertCalledWith(
curCompletedCount, url, LoadError.HTTP_CLIENT_ERROR);
}
private void setNavigationCallback(InstrumentationActivity activity) {
runOnUiThreadBlocking(
()
......
......@@ -31,6 +31,9 @@ namespace {
GURL GetStartupURL() {
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(switches::kNoInitialNavigation))
return GURL();
const base::CommandLine::StringVector& args = command_line->GetArgs();
#if defined(OS_ANDROID)
......
......@@ -6,6 +6,9 @@
namespace switches {
// Stops new Shell objects from navigating to a default url.
const char kNoInitialNavigation[] = "no-initial-navigation";
// Makes WebLayer Shell use the given path for its data directory.
const char kWebLayerShellDataPath[] = "data-path";
......
......@@ -7,6 +7,7 @@
namespace switches {
extern const char kNoInitialNavigation[];
extern const char kWebLayerShellDataPath[];
} // namespace switches
......
......@@ -87,6 +87,7 @@ test("weblayer_browsertests") {
]
sources = [
"../browser/navigation_browsertest.cc",
"../browser/ssl_browsertest.cc",
"../browser/webui/webui_browsertest.cc",
"browsertests_main.cc",
......
......@@ -7,6 +7,7 @@
#include "base/base_paths.h"
#include "base/command_line.h"
#include "weblayer/shell/browser/shell.h"
#include "weblayer/shell/common/shell_switches.h"
namespace weblayer {
......@@ -18,6 +19,7 @@ WebLayerBrowserTest::~WebLayerBrowserTest() = default;
void WebLayerBrowserTest::SetUp() {
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
command_line->AppendSwitch(switches::kNoInitialNavigation);
SetUpCommandLine(command_line);
content::BrowserTestBase::SetUp();
}
......
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