diff --git a/mobile/android/focus-android/app/src/main/res/values/strings.xml b/mobile/android/focus-android/app/src/main/res/values/strings.xml
index c212ae6a172ff..16124c196a071 100644
--- a/mobile/android/focus-android/app/src/main/res/values/strings.xml
+++ b/mobile/android/focus-android/app/src/main/res/values/strings.xml
@@ -105,6 +105,7 @@
To be clear, sites and service you use can still know where you\'ve been.Your Rights
+
This link will open in %2$s. Are you sure you want to exit %1$s?
@@ -112,4 +113,62 @@
None of the apps on your device are able to open this link. Do you want to exit %1$s and open %2$s to find a suitable app?Exit Private Browsing?
+
+
+ Problem loading page
+ Try Again
+
+ Unable to connect
+
+
+
The site could be temporarily unavailable or too busy. Try again in a few moments.
+
If you are unable to load any pages, check your mobile device’s data or Wi-Fi connection.
+
+ ]]>
+
+
+ @string/error_connectionfailure_title
+ @string/error_connectionfailure_message
+
+ The connection timed out
+ @string/error_connectionfailure_message
+
+ Server not found
+
+
Check the address for typing errors such as
+ ww.example.com instead of
+ www.example.com
+
If you are unable to load any pages, check your device’s data or Wi-Fi connection.
+
+ ]]>
+
+
+ The address isn’t valid
+
+
Web addresses are usually written like http://www.example.com/
+
Make sure that you’re using forward slashes (i.e. /).
+
+ ]]>
+
+ The page isn’t redirecting properly
+
This problem can sometimes be caused by disabling or refusing to accept cookies.
]]>
+
+ The address wasn’t understood
+
You might need to install other software to open this address.
]]>
+
+ Secure Connection Failed
+
+
This could be a problem with the server’s configuration, or it could be
+someone trying to impersonate the server.
+
If you have connected to this server successfully in the past, the error may
+be temporary, and you can try again later.
+
+ ]]>
+
+ Oops.
+ We can’t load this page for some reason.
diff --git a/mobile/android/focus-android/app/src/webkit/java/org/mozilla/focus/webkit/ErrorPage.java b/mobile/android/focus-android/app/src/webkit/java/org/mozilla/focus/webkit/ErrorPage.java
new file mode 100644
index 0000000000000..045b820fc56ba
--- /dev/null
+++ b/mobile/android/focus-android/app/src/webkit/java/org/mozilla/focus/webkit/ErrorPage.java
@@ -0,0 +1,177 @@
+/* -*- 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.webkit;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RawRes;
+import android.support.v4.util.ArrayMap;
+import android.support.v4.util.Pair;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+
+import org.mozilla.focus.R;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.HashMap;
+import java.util.Map;
+
+public class ErrorPage {
+
+ private static final HashMap> errorDescriptionMap;
+
+ static {
+ errorDescriptionMap = new HashMap<>();
+
+ // Chromium's mapping (internal error code, to Android WebView error code) is described at:
+ // https://chromium.googlesource.com/chromium/src.git/+/master/android_webview/java/src/org/chromium/android_webview/ErrorCodeConversionHelper.java
+
+ 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));
+
+ // We don't support file:/// URLs, so we shouldn't have to handle errors opening files either
+// 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);
+ }
+
+ /**
+ * Load a given resource file into a String.
+ *
+ * @param substitutionTable A table of substitions, e.g. %shortMessage% -> "Error loading page..."
+ * @return The file content, with all substitutions having being made.
+ */
+ private static String loadResourceFile(@NonNull final Context context,
+ @NonNull final @RawRes int resourceID,
+ @Nullable final Map substitutionTable) {
+
+ BufferedReader fileReader = null;
+
+ try {
+ final InputStream fileStream = context.getResources().openRawResource(resourceID);
+ fileReader = new BufferedReader(new InputStreamReader(fileStream));
+
+ final StringBuilder outputBuffer = new StringBuilder();
+
+ String line;
+ while ((line = fileReader.readLine()) != null) {
+ if (substitutionTable != null) {
+ for (final Map.Entry entry : substitutionTable.entrySet()) {
+ line = line.replace(entry.getKey(), entry.getValue());
+ }
+ }
+
+ outputBuffer.append(line);
+ }
+
+ return outputBuffer.toString();
+ } catch (final IOException e) {
+ throw new IllegalStateException("Unable to load error page data");
+ } finally {
+ try {
+ if (fileReader != null) {
+ fileReader.close();
+ }
+ } catch (IOException e) {
+ // There's pretty much nothing we can do here. It doesn't seem right to crash
+ // just because we couldn't close a file?
+ }
+ }
+ }
+
+ public static void loadErrorPage(final WebView webView, final String desiredURL, final int errorCode) {
+ final Pair 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 ' 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 = 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);
+ }
+}
diff --git a/mobile/android/focus-android/app/src/webkit/java/org/mozilla/focus/webkit/TrackingProtectionWebViewClient.java b/mobile/android/focus-android/app/src/webkit/java/org/mozilla/focus/webkit/TrackingProtectionWebViewClient.java
index c0b3f62079df9..3976f67b742f4 100644
--- a/mobile/android/focus-android/app/src/webkit/java/org/mozilla/focus/webkit/TrackingProtectionWebViewClient.java
+++ b/mobile/android/focus-android/app/src/webkit/java/org/mozilla/focus/webkit/TrackingProtectionWebViewClient.java
@@ -6,8 +6,11 @@
import android.content.Context;
import android.graphics.Bitmap;
+import android.net.http.SslError;
import android.os.AsyncTask;
import android.support.annotation.WorkerThread;
+import android.webkit.HttpAuthHandler;
+import android.webkit.SslErrorHandler;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
@@ -104,4 +107,23 @@ public boolean shouldOverrideUrlLoading(WebView view, String url) {
return super.shouldOverrideUrlLoading(view, url);
}
+ @Override
+ public void onReceivedError(final WebView webView, int errorCode,
+ final String description, String failingUrl) {
+
+ // This is a hack: onReceivedError(WebView, WebResourceRequest, WebResourceError) is API 23+ only,
+ // - the WebResourceRequest would let us know if the error affects the main frame or not. As a workaround
+ // we just check whether the failing URL is the current URL, which is enough to detect an error
+ // in the main frame.
+
+ // The API 23+ version also return a *slightly* more usable description, via WebResourceError.getError();
+ // e.g.. "There was a network error.", whereas this version provides things like "net::ERR_NAME_NOT_RESOLVED"
+ if (failingUrl.equals(currentPageURL) &&
+ ErrorPage.supportsErrorCode(errorCode)) {
+ ErrorPage.loadErrorPage(webView, currentPageURL, errorCode);
+ return;
+ }
+
+ super.onReceivedError(webView, errorCode, description, failingUrl);
+ }
}