Revision control

Copy as Markdown

/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.focus.webview;
import android.content.res.Resources;
import androidx.collection.ArrayMap;
import androidx.core.util.Pair;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import org.mozilla.focus.R;
import org.mozilla.focus.utils.HtmlLoader;
import java.util.HashMap;
import java.util.Map;
public class ErrorPage {
private static final HashMap<Integer, Pair<Integer, Integer>> errorDescriptionMap;
static {
errorDescriptionMap = new HashMap<>();
// Chromium's mapping (internal error code, to Android WebView error code) is described at:
errorDescriptionMap.put(WebViewClient.ERROR_UNKNOWN,
new Pair<>(R.string.error_connectionfailure_title, R.string.error_connectionfailure_message));
// This is probably the most commonly shown error. If there's no network, we inevitably
// show this.
errorDescriptionMap.put(WebViewClient.ERROR_HOST_LOOKUP,
new Pair<>(R.string.error_hostLookup_title, R.string.error_hostLookup_message));
// WebViewClient.ERROR_UNSUPPORTED_AUTH_SCHEME
// TODO: we don't actually handle this in firefox - does this happen in real life?
// WebViewClient.ERROR_AUTHENTICATION
// TODO: there's no point in implementing this until we actually support http auth (#159)
errorDescriptionMap.put(WebViewClient.ERROR_CONNECT,
new Pair<>(R.string.error_connect_title, R.string.error_connect_message));
// It's unclear what this actually means - it's not well documented. Based on looking at
// ErrorCodeConversionHelper this could happen if networking is disabled during load, in which
// case the generic error is good enough:
errorDescriptionMap.put(WebViewClient.ERROR_IO,
new Pair<>(R.string.error_connectionfailure_title, R.string.error_connectionfailure_message));
errorDescriptionMap.put(WebViewClient.ERROR_TIMEOUT,
new Pair<>(R.string.error_timeout_title, R.string.error_timeout_message));
errorDescriptionMap.put(WebViewClient.ERROR_REDIRECT_LOOP,
new Pair<>(R.string.error_redirectLoop_title, R.string.error_redirectLoop_message));
// We already try to handle external URLs if possible (i.e. we offer to open the corresponding
// app, if available for a given scheme). If we end up here that means no app exists.
// We could consider showing an "open google play" link here, but ultimately it's hard
// to know whether that's the right step, especially if there are no good apps for actually
// handling such a protocol there - moreover there doesn't seem to be a good way to search
// google play for apps supporting a given scheme.
errorDescriptionMap.put(WebViewClient.ERROR_UNSUPPORTED_SCHEME,
new Pair<>(R.string.error_unsupportedprotocol_title, R.string.error_unsupportedprotocol_message));
errorDescriptionMap.put(WebViewClient.ERROR_FAILED_SSL_HANDSHAKE,
new Pair<>(R.string.error_sslhandshake_title, R.string.error_sslhandshake_message));
errorDescriptionMap.put(WebViewClient.ERROR_BAD_URL,
new Pair<>(R.string.error_malformedURI_title, R.string.error_malformedURI_message));
// WebView returns ERROR_UNKNOWN when we try to access a file:/// on Android (with the error string
// containing access denied), so I'm not too sure why these codes exist:
// sure why these error codes exit
// WebViewClient.ERROR_FILE;
// WebViewClient.ERROR_FILE_NOT_FOUND;
// Seems to be an indication of OOM, insufficient resources, or too many queued DNS queries
errorDescriptionMap.put(WebViewClient.ERROR_TOO_MANY_REQUESTS,
new Pair<>(R.string.error_generic_title, R.string.error_generic_message));
}
public static boolean supportsErrorCode(final int errorCode) {
return (errorDescriptionMap.get(errorCode) != null);
}
public static void loadErrorPage(final WebView webView, final String desiredURL, final int errorCode) {
final Pair<Integer, Integer> errorResourceIDs = errorDescriptionMap.get(errorCode);
if (errorResourceIDs == null) {
throw new IllegalArgumentException("Cannot load error description for unsupported errorcode=" + errorCode);
}
// This is quite hacky: ideally we'd just load the css file directly using a '<link rel="stylesheet"'.
// However WebView thinks it's still loading the original page, which can be an https:// page.
// If mixed content blocking is enabled (which is probably what we want in Focus), then webkit
// will block file:///android_res/ links from being loaded - which blocks our css from being loaded.
// We could hack around that by enabling mixed content when loading an error page (and reenabling it
// once that's loaded), but doing that correctly and reliably isn't particularly simple. Loading
// the css data and stuffing it into our html is much simpler, especially since we're already doing
// string substitutions.
// As an added bonus: file:/// URIs are broken if the app-ID != app package, see:
// references when running debug builds, and probably klar too) - which means this wouldn't
// be possible even if we hacked around the mixed content issues.
final String cssString = HtmlLoader.loadResourceFile(webView.getContext(), R.raw.errorpage_style, null);
final Map<String, String> substitutionMap = new ArrayMap<>();
final Resources resources = webView.getContext().getResources();
substitutionMap.put("%page-title%", resources.getString(R.string.errorpage_title));
substitutionMap.put("%button%", resources.getString(R.string.errorpage_refresh));
substitutionMap.put("%messageShort%", resources.getString(errorResourceIDs.first));
substitutionMap.put("%messageLong%", resources.getString(errorResourceIDs.second, desiredURL));
substitutionMap.put("%css%", cssString);
final String errorPage = HtmlLoader.loadResourceFile(webView.getContext(), R.raw.errorpage, substitutionMap);
// We could load the raw html file directly into the webview using a file:///android_res/
// URI - however we'd then need to do some JS hacking to do our String substitutions. Moreover
// we'd have to deal with the mixed-content issues detailed above in that case.
webView.loadDataWithBaseURL(desiredURL, errorPage, "text/html", "UTF8", desiredURL);
}
}