Commit 822effcf authored by Christian O. Andersson's avatar Christian O. Andersson Committed by Commit Bot

Enable suppression of error page from the Java side


Design doc: https://docs.google.com/document/d/188BbgvTdhKf8bXikCL-JMduEclJOd9GxhKu79hmSVdo/edit

We wish to provide a means of optionally suppressing the display of the
webview error page so that applications that wish to display their own
error content may do so without presenting a poor user experience.

Test: run_webview_instrumentation_test_apk -f AwSettingsTest#testShouldSuppressErrorPage
Change-Id: I15c42acd7d562e7d37bb4f84d92267db49f0a55a
Reviewed-on: https://chromium-review.googlesource.com/c/1185001
Auto-Submit: Christian O. Andersson <cricke@chromium.org>
Reviewed-by: default avatarBo <boliu@chromium.org>
Reviewed-by: default avatarRobert Sesek <rsesek@chromium.org>
Reviewed-by: default avatarChangwan Ryu <changwan@chromium.org>
Reviewed-by: default avatarNate Fischer <ntfschr@chromium.org>
Commit-Queue: Christian O. Andersson <cricke@chromium.org>
Cr-Commit-Position: refs/heads/master@{#626102}
parent e7df4948
......@@ -142,6 +142,7 @@ void AwSettings::UpdateEverythingLocked(JNIEnv* env,
UpdateFormDataPreferencesLocked(env, obj);
UpdateRendererPreferencesLocked(env, obj);
UpdateOffscreenPreRasterLocked(env, obj);
UpdateShouldSuppressErrorStateLocked(env, obj);
}
void AwSettings::UpdateUserAgentLocked(JNIEnv* env,
......@@ -197,6 +198,17 @@ void AwSettings::UpdateInitialPageScaleLocked(
}
}
void AwSettings::UpdateShouldSuppressErrorStateLocked(
JNIEnv* env,
const JavaParamRef<jobject>& obj) {
AwRenderViewHostExt* rvhe = GetAwRenderViewHostExt();
if (!rvhe)
return;
bool suppress = Java_AwSettings_getShouldSuppressErrorPageLocked(env, obj);
rvhe->SetShouldSuppressErrorPage(suppress);
}
void AwSettings::UpdateFormDataPreferencesLocked(
JNIEnv* env,
const JavaParamRef<jobject>& obj) {
......
......@@ -44,6 +44,9 @@ class AwSettings : public content::WebContentsObserver {
void UpdateInitialPageScaleLocked(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj);
void UpdateShouldSuppressErrorStateLocked(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj);
void UpdateUserAgentLocked(JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj);
void UpdateWebkitPreferencesLocked(
......
......@@ -21,8 +21,8 @@
namespace android_webview {
AwRenderViewHostExt::AwRenderViewHostExt(
AwRenderViewHostExtClient* client, content::WebContents* contents)
AwRenderViewHostExt::AwRenderViewHostExt(AwRenderViewHostExtClient* client,
content::WebContents* contents)
: content::WebContentsObserver(contents),
client_(client),
background_color_(SK_ColorWHITE),
......@@ -117,6 +117,17 @@ void AwRenderViewHostExt::SetBackgroundColor(SkColor c) {
}
}
void AwRenderViewHostExt::SetShouldSuppressErrorPage(bool suppress) {
// We need to store state on the browser-side, as state might need to be
// synchronized again later (see AwRenderViewHostExt::RenderFrameCreated)
if (should_suppress_error_page_ == suppress)
return;
should_suppress_error_page_ = suppress;
web_contents()->SendToAllFrames(new AwViewMsg_ShouldSuppressErrorPage(
MSG_ROUTING_NONE, should_suppress_error_page_));
}
void AwRenderViewHostExt::SetJsOnlineProperty(bool network_up) {
web_contents()->GetRenderViewHost()->Send(
new AwViewMsg_SetJsOnlineProperty(network_up));
......@@ -153,6 +164,14 @@ void AwRenderViewHostExt::RenderFrameCreated(
frame_host->Send(new AwViewMsg_SetBackgroundColor(
frame_host->GetRoutingID(), background_color_));
}
// Synchronizing error page suppression state down to the renderer cannot be
// done when RenderViewHostChanged is fired (similar to how other settings do
// it) because for cross-origin navigations in multi-process mode, the
// navigation will already have started then. Also, newly created subframes
// need to inherit the state.
frame_host->Send(new AwViewMsg_ShouldSuppressErrorPage(
frame_host->GetRoutingID(), should_suppress_error_page_));
}
void AwRenderViewHostExt::DidFinishNavigation(
......
......@@ -75,6 +75,7 @@ class AwRenderViewHostExt : public content::WebContentsObserver {
// the meta viewport tag.
void SetInitialPageScale(double page_scale_factor);
void SetBackgroundColor(SkColor c);
void SetShouldSuppressErrorPage(bool suppress);
void SetJsOnlineProperty(bool network_up);
void SmoothScroll(int target_x, int target_y, long duration_ms);
......@@ -122,6 +123,9 @@ class AwRenderViewHostExt : public content::WebContentsObserver {
service_manager::BinderRegistry registry_;
// Some WebView users might want to show their own error pages / logic.
bool should_suppress_error_page_ = false;
SEQUENCE_CHECKER(sequence_checker_);
DISALLOW_COPY_AND_ASSIGN(AwRenderViewHostExt);
......
......@@ -87,6 +87,11 @@ IPC_MESSAGE_ROUTED3(AwViewMsg_SmoothScroll,
int /* target_y */,
int /* duration_ms */)
// Sent to inform renderers whether the internal error page should be shown or
// not.
IPC_MESSAGE_ROUTED1(AwViewMsg_ShouldSuppressErrorPage,
bool /* should_suppress_error_page */)
//-----------------------------------------------------------------------------
// RenderView messages
// These are messages sent from the renderer to the browser process.
......
......@@ -114,6 +114,7 @@ public class AwSettings {
private int mForceDarkMode = FORCE_DARK_AUTO;
private boolean mCSSHexAlphaColorEnabled;
private boolean mScrollTopLeftInteropEnabled;
private boolean mShouldSuppressErrorPage;
private boolean mOffscreenPreRaster;
private int mDisabledMenuItems = WebSettings.MENU_ITEM_NONE;
......@@ -1257,6 +1258,35 @@ public class AwSettings {
}
}
@CalledByNative
private boolean getShouldSuppressErrorPageLocked() {
assert Thread.holdsLock(mAwSettingsLock);
return mShouldSuppressErrorPage;
}
public boolean getShouldSuppressErrorPage() {
synchronized (mAwSettingsLock) {
return getShouldSuppressErrorPageLocked();
}
}
public void setShouldSuppressErrorPage(boolean suppressed) {
synchronized (mAwSettingsLock) {
if (mShouldSuppressErrorPage == suppressed) return;
mShouldSuppressErrorPage = suppressed;
updateShouldSuppressErrorStateLocked();
}
}
private void updateShouldSuppressErrorStateLocked() {
mEventHandler.runOnUiThreadBlockingAndLocked(() -> {
assert Thread.holdsLock(mAwSettingsLock);
assert mNativeAwSettings != 0;
nativeUpdateShouldSuppressErrorStateLocked(mNativeAwSettings);
});
}
@CalledByNative
private boolean getSupportLegacyQuirksLocked() {
assert Thread.holdsLock(mAwSettingsLock);
......@@ -1814,4 +1844,6 @@ public class AwSettings {
private native void nativeUpdateRendererPreferencesLocked(long nativeAwSettings);
private native void nativeUpdateOffscreenPreRasterLocked(long nativeAwSettings);
private native void nativeUpdateShouldSuppressErrorStateLocked(long nativeAwSettings);
}
......@@ -47,6 +47,7 @@ import org.chromium.base.test.util.RetryOnFailure;
import org.chromium.base.test.util.TestFileUtil;
import org.chromium.base.test.util.UrlUtils;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.browser.WebContentsObserver;
import org.chromium.content_public.browser.test.util.DOMUtils;
import org.chromium.content_public.browser.test.util.HistoryUtils;
import org.chromium.content_public.common.ContentSwitches;
......@@ -1463,6 +1464,91 @@ public class AwSettingsTest {
}
}
class AwSettingsShouldSuppressErrorPageTestHelper extends AwSettingsTestHelper<Boolean> {
private static final String BAD_SCHEME_URL = "htt://nonsense";
private static final String PREV_TITLE = "cuencpobgjhfdmdovhmfdkjf";
private static final int MAX_TIME_LOADING_ERROR_PAGE = 1000;
private final AwContents mAwContents;
AwSettingsShouldSuppressErrorPageTestHelper(AwTestContainerView containerView,
TestAwContentsClient contentViewClient) throws Throwable {
super(containerView, contentViewClient, true);
mAwContents = containerView.getAwContents();
}
@Override
protected Boolean getAlteredValue() {
return ENABLED;
}
@Override
protected Boolean getInitialValue() {
return DISABLED;
}
@Override
protected Boolean getCurrentValue() {
return mAwSettings.getShouldSuppressErrorPage();
}
@Override
protected void setCurrentValue(Boolean value) {
mAwSettings.setShouldSuppressErrorPage(value);
}
@Override
protected void doEnsureSettingHasValue(Boolean value) throws Throwable {
// Load a known state
loadDataSync(getData());
final WebContents webContents = mAwContents.getWebContents();
final CallbackHelper onTitleUpdatedHelper = new CallbackHelper();
final WebContentsObserver observer =
ThreadUtils.runOnUiThreadBlocking(() -> new WebContentsObserver(webContents) {
@Override
public void titleWasSet(String title) {
onTitleUpdatedHelper.notifyCalled();
}
});
int callCount = onTitleUpdatedHelper.getCallCount();
loadUrlSync(BAD_SCHEME_URL);
// Verify the state in settings reflect what we expect
AwSettings settings = mActivityTestRule.getAwSettingsOnUiThread(mAwContents);
Assert.assertEquals(value, settings.getShouldSuppressErrorPage());
// Verify the error page is shown / suppressed
if (value == DISABLED) {
// Showing an error page should change the page title.
onTitleUpdatedHelper.waitForCallback(
"Showing an error page should change the page title, "
+ "but no change happened",
callCount);
Assert.assertNotEquals("Showing an error page should change the page title, "
+ "but no change happened",
PREV_TITLE, getTitleOnUiThread());
} else {
// Suppressing the error page should mean nothing changes (no callbacks). However,
// verifying that the error page actually never loads isn't straight-forward,
// as it happens asynchronously.
// In fact, there doesn't seem to be any direct, non-flaky way of detecting this.
Thread.sleep(MAX_TIME_LOADING_ERROR_PAGE);
Assert.assertEquals(
"Suppressing an error page should leave the page title unchanged, "
+ "but a change still happened",
PREV_TITLE, getTitleOnUiThread());
}
ThreadUtils.runOnUiThreadBlocking(() -> webContents.removeObserver(observer));
}
private String getData() {
return "<html><head><title>" + PREV_TITLE
+ "</title></head><body>Page Text</body></html>";
}
}
public static int calcDisplayWidthDp(Context context) {
return ThreadUtils.runOnUiThreadBlockingNoException(() -> {
DisplayAndroid displayAndroid = DisplayAndroid.getNonMultiDisplay(context);
......@@ -1636,6 +1722,17 @@ public class AwSettingsTest {
Assert.assertEquals(Build.ID, patternMatcher.group(7));
}
@Test
@MediumTest
@Feature({"AndroidWebView", "Preferences"})
public void testShouldSuppressErrorPage() throws Throwable {
ViewPair views = createViews();
runPerViewSettingsTest(new AwSettingsShouldSuppressErrorPageTestHelper(
views.getContainer0(), views.getClient0()),
new AwSettingsShouldSuppressErrorPageTestHelper(
views.getContainer1(), views.getClient1()));
}
@Test
@SmallTest
@Feature({"AndroidWebView", "Preferences"})
......
......@@ -191,6 +191,19 @@ bool AwContentRendererClient::HasErrorPage(int http_status_code) {
return http_status_code >= 400;
}
bool AwContentRendererClient::ShouldSuppressErrorPage(
content::RenderFrame* render_frame,
const GURL& url) {
DCHECK(render_frame != nullptr);
AwRenderFrameExt* render_frame_ext =
AwRenderFrameExt::FromRenderFrame(render_frame);
if (render_frame_ext == nullptr)
return false;
return render_frame_ext->GetShouldSuppressErrorPage();
}
void AwContentRendererClient::PrepareErrorPage(
content::RenderFrame* render_frame,
const blink::WebURLError& error,
......
......@@ -37,6 +37,8 @@ class AwContentRendererClient : public content::ContentRendererClient,
void RenderFrameCreated(content::RenderFrame* render_frame) override;
void RenderViewCreated(content::RenderView* render_view) override;
bool HasErrorPage(int http_status_code) override;
bool ShouldSuppressErrorPage(content::RenderFrame* render_frame,
const GURL& url) override;
void PrepareErrorPage(content::RenderFrame* render_frame,
const blink::WebURLError& error,
const std::string& http_method,
......
......@@ -4,10 +4,12 @@
#include "android_webview/renderer/aw_render_frame_ext.h"
#include <map>
#include <memory>
#include "android_webview/common/aw_hit_test_data.h"
#include "android_webview/common/render_view_messages.h"
#include "base/lazy_instance.h"
#include "base/strings/utf_string_conversions.h"
#include "components/autofill/content/renderer/autofill_agent.h"
#include "components/autofill/content/renderer/password_autofill_agent.h"
......@@ -141,6 +143,11 @@ void PopulateHitTestData(const GURL& absolute_link_url,
} // namespace
// Registry for RenderFrame => AwRenderFrameExt lookups
typedef std::map<content::RenderFrame*, AwRenderFrameExt*> FrameExtMap;
base::LazyInstance<FrameExtMap>::Leaky render_frame_ext_map =
LAZY_INSTANCE_INITIALIZER;
AwRenderFrameExt::AwRenderFrameExt(content::RenderFrame* render_frame)
: content::RenderFrameObserver(render_frame) {
// TODO(sgurun) do not create a password autofill agent (change
......@@ -149,9 +156,37 @@ AwRenderFrameExt::AwRenderFrameExt(content::RenderFrame* render_frame)
new autofill::PasswordAutofillAgent(render_frame, &registry_);
new autofill::AutofillAgent(render_frame, password_autofill_agent, nullptr,
&registry_);
// Add myself to the RenderFrame => AwRenderFrameExt register.
render_frame_ext_map.Get().emplace(render_frame, this);
}
AwRenderFrameExt::~AwRenderFrameExt() {
// Remove myself from the RenderFrame => AwRenderFrameExt register. Ideally,
// we'd just use render_frame() and erase by key. However, by this time the
// render_frame has already been cleared so we have to iterate over all
// render_frames in the map and wipe the one(s) that point to this
// AwRenderFrameExt
auto& map = render_frame_ext_map.Get();
auto it = map.begin();
while (it != map.end()) {
if (it->second == this) {
it = map.erase(it);
} else {
++it;
}
}
}
AwRenderFrameExt* AwRenderFrameExt::FromRenderFrame(
content::RenderFrame* render_frame) {
DCHECK(render_frame != nullptr);
auto iter = render_frame_ext_map.Get().find(render_frame);
DCHECK(render_frame_ext_map.Get().end() != iter)
<< "Should always exist a render_frame_ext for a render_frame";
AwRenderFrameExt* render_frame_ext = iter->second;
return render_frame_ext;
}
bool AwRenderFrameExt::OnAssociatedInterfaceRequestForFrame(
......@@ -195,6 +230,8 @@ bool AwRenderFrameExt::OnMessageReceived(const IPC::Message& message) {
OnResetScrollAndScaleState)
IPC_MESSAGE_HANDLER(AwViewMsg_SetInitialPageScale, OnSetInitialPageScale)
IPC_MESSAGE_HANDLER(AwViewMsg_SetBackgroundColor, OnSetBackgroundColor)
IPC_MESSAGE_HANDLER(AwViewMsg_ShouldSuppressErrorPage,
OnSetShouldSuppressErrorPage)
IPC_MESSAGE_HANDLER(AwViewMsg_SmoothScroll, OnSmoothScroll)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
......@@ -310,6 +347,14 @@ void AwRenderFrameExt::OnSmoothScroll(int target_x,
webview->SmoothScroll(target_x, target_y, static_cast<long>(duration_ms));
}
void AwRenderFrameExt::OnSetShouldSuppressErrorPage(bool suppress) {
this->should_suppress_error_page_ = suppress;
}
bool AwRenderFrameExt::GetShouldSuppressErrorPage() {
return this->should_suppress_error_page_;
}
blink::WebView* AwRenderFrameExt::GetWebView() {
if (!render_frame() || !render_frame()->GetRenderView() ||
!render_frame()->GetRenderView()->GetWebView())
......
......@@ -27,7 +27,11 @@ namespace android_webview {
// WebKit directly to implement (and that aren't needed in the chrome app).
class AwRenderFrameExt : public content::RenderFrameObserver {
public:
AwRenderFrameExt(content::RenderFrame* render_frame);
explicit AwRenderFrameExt(content::RenderFrame* render_frame);
static AwRenderFrameExt* FromRenderFrame(content::RenderFrame* render_frame);
bool GetShouldSuppressErrorPage();
private:
~AwRenderFrameExt() override;
......@@ -57,6 +61,8 @@ class AwRenderFrameExt : public content::RenderFrameObserver {
void OnSmoothScroll(int target_x, int target_y, int duration_ms);
void OnSetShouldSuppressErrorPage(bool suppress);
blink::WebView* GetWebView();
blink::WebFrameWidget* GetWebFrameWidget();
......@@ -64,6 +70,9 @@ class AwRenderFrameExt : public content::RenderFrameObserver {
blink::AssociatedInterfaceRegistry registry_;
// Some WebView users might want to show their own error pages / logic
bool should_suppress_error_page_ = false;
DISALLOW_COPY_AND_ASSIGN(AwRenderFrameExt);
};
......
......@@ -12,7 +12,9 @@
namespace android_webview {
AwRenderViewExt::AwRenderViewExt(content::RenderView* render_view)
: content::RenderViewObserver(render_view) {}
: content::RenderViewObserver(render_view) {
DCHECK(render_view != nullptr);
}
AwRenderViewExt::~AwRenderViewExt() {}
......
......@@ -11,6 +11,10 @@
namespace android_webview {
// NOTE: We should not add more things to RenderView and related classes.
// RenderView is deprecated in content, since it is not compatible
// with site isolation/out of process iframes.
// Render process side of AwRenderViewHostExt, this provides cross-process
// implementation of miscellaneous WebView functions that we need to poke
// WebKit directly to implement (and that aren't needed in the chrome app).
......
......@@ -20,4 +20,7 @@ public interface WebSettingsBoundaryInterface {
void setDisabledActionModeMenuItems(int menuItems);
int getDisabledActionModeMenuItems();
void setShouldSuppressErrorPage(boolean suppressed);
boolean getShouldSuppressErrorPage();
}
......@@ -132,6 +132,10 @@ public class Features {
// ProxyController.clearProxyOverride
public static final String PROXY_OVERRIDE = "PROXY_OVERRIDE:3";
// WebSettingsCompat.setShouldSuppressErrorPage
// WebSettingsCompat.getShouldSuppressErrorPage
public static final String SUPPRESS_ERROR_PAGE = "SUPPRESS_ERROR_PAGE";
// WebViewCompat.getWebViewRenderer
public static final String GET_WEB_VIEW_RENDERER = "GET_WEB_VIEW_RENDERER";
......
......@@ -46,4 +46,14 @@ class SupportLibWebSettingsAdapter implements WebSettingsBoundaryInterface {
public int getDisabledActionModeMenuItems() {
return mAwSettings.getDisabledActionModeMenuItems();
}
@Override
public boolean getShouldSuppressErrorPage() {
return mAwSettings.getShouldSuppressErrorPage();
}
@Override
public void setShouldSuppressErrorPage(boolean suppressed) {
mAwSettings.setShouldSuppressErrorPage(suppressed);
}
}
......@@ -65,6 +65,7 @@ class SupportLibWebViewChromiumFactory implements WebViewProviderFactoryBoundary
Features.GET_WEB_VIEW_CLIENT,
Features.GET_WEB_CHROME_CLIENT,
Features.PROXY_OVERRIDE + Features.DEV_SUFFIX,
Features.SUPPRESS_ERROR_PAGE + Features.DEV_SUFFIX,
Features.GET_WEB_VIEW_RENDERER,
Features.WEB_VIEW_RENDERER_TERMINATE,
Features.TRACING_CONTROLLER_BASIC_USAGE,
......
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