Bug 1277467 - Add OfflineTabStatusDelegate for displaying tab-is-offline notifiations when appropriate r=sebastian

- Use Content:PageShow event to inform Tabs that they were actively loaded from cache
- Move offline notification logic away from browser.js and into a delegate, which displays notifications when
tab in question is user-visible

MozReview-Commit-ID: 2qCACHyWOlp

--HG--
extra : rebase_source : 457a42ebba5cfc32ab7dabce0a8a11d6511b9c08
This commit is contained in:
Grigory Kruglov 2016-06-24 16:56:04 -07:00
Родитель c09864a3eb
Коммит 26648ad110
11 изменённых файлов: 143 добавлений и 28 удалений

Просмотреть файл

@ -24,6 +24,7 @@ import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.db.SuggestedSites;
import org.mozilla.gecko.delegates.BrowserAppDelegate;
import org.mozilla.gecko.delegates.OfflineTabStatusDelegate;
import org.mozilla.gecko.delegates.ScreenshotDelegate;
import org.mozilla.gecko.distribution.Distribution;
import org.mozilla.gecko.distribution.DistributionStoreCallback;
@ -312,7 +313,8 @@ public class BrowserApp extends GeckoApp
(BrowserAppDelegate) new ReaderViewBookmarkPromotion(),
(BrowserAppDelegate) new ContentNotificationsDelegate(),
(BrowserAppDelegate) new PostUpdateHandler(),
new TelemetryCorePingDelegate()
new TelemetryCorePingDelegate(),
new OfflineTabStatusDelegate()
));
@NonNull

Просмотреть файл

@ -104,6 +104,9 @@ public class Tab {
private boolean mIsEditing;
private final TabEditingState mEditingState = new TabEditingState();
// Will be true when tab is loaded from cache while device was offline.
private boolean mLoadedFromCache;
public static final int STATE_DELAYED = 0;
public static final int STATE_LOADING = 1;
public static final int STATE_SUCCESS = 2;
@ -301,6 +304,10 @@ public class Tab {
return mHasOpenSearch;
}
public boolean hasLoadedFromCache() {
return mLoadedFromCache;
}
public SiteIdentity getSiteIdentity() {
return mSiteIdentity;
}
@ -536,6 +543,10 @@ public class Tab {
mHasOpenSearch = hasOpenSearch;
}
public void setLoadedFromCache(boolean loadedFromCache) {
mLoadedFromCache = loadedFromCache;
}
public void updateIdentityData(JSONObject identityData) {
mSiteIdentity.update(identityData);
}

Просмотреть файл

@ -105,6 +105,7 @@ public class Tabs implements GeckoEventListener {
"Tab:Added",
"Tab:Close",
"Tab:Select",
"Tab:LoadedFromCache",
"Content:LocationChange",
"Content:SecurityChange",
"Content:StateChange",
@ -505,8 +506,9 @@ public class Tabs implements GeckoEventListener {
tab.handleContentLoaded();
notifyListeners(tab, Tabs.TabEvents.LOAD_ERROR);
} else if (event.equals("Content:PageShow")) {
notifyListeners(tab, TabEvents.PAGE_SHOW);
tab.setLoadedFromCache(message.getBoolean("fromCache"));
tab.updateUserRequested(message.getString("userRequested"));
notifyListeners(tab, TabEvents.PAGE_SHOW);
} else if (event.equals("DOMContentLoaded")) {
tab.handleContentLoaded();
String backgroundColor = message.getString("bgColor");

Просмотреть файл

@ -104,6 +104,9 @@ public interface TelemetryContract {
// Stop holding a resource (reader, bookmark, etc) for viewing later.
UNSAVE("unsave.1"),
// When the user performs actions on the in-content network error page.
NETERROR("neterror.1"),
// VALUES BELOW THIS LINE ARE EXCLUSIVE TO TESTING.
_TEST1("_test_event_1.1"),
_TEST2("_test_event_2.1"),

Просмотреть файл

@ -0,0 +1,110 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; 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.gecko.delegates;
import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.CallSuper;
import android.support.design.widget.Snackbar;
import android.support.v4.content.ContextCompat;
import org.mozilla.gecko.AboutPages;
import org.mozilla.gecko.BrowserApp;
import org.mozilla.gecko.R;
import org.mozilla.gecko.SnackbarHelper;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.WeakHashMap;
/**
* Displays "Showing offline version" message when tabs are loaded from cache while offline.
*/
public class OfflineTabStatusDelegate extends TabsTrayVisibilityAwareDelegate implements Tabs.OnTabsChangedListener {
private WeakReference<Activity> activityReference;
private WeakHashMap<Tab, Void> tabsQueuedForOfflineSnackbar = new WeakHashMap<>();
@CallSuper
@Override
public void onCreate(BrowserApp browserApp, Bundle savedInstanceState) {
super.onCreate(browserApp, savedInstanceState);
activityReference = new WeakReference<Activity>(browserApp);
}
@Override
public void onResume(BrowserApp browserApp) {
Tabs.registerOnTabsChangedListener(this);
}
@Override
public void onPause(BrowserApp browserApp) {
Tabs.unregisterOnTabsChangedListener(this);
}
public void onTabChanged(final Tab tab, Tabs.TabEvents event, String data) {
if (tab == null) {
return;
}
// Ignore tabs loaded regularly.
if (!tab.hasLoadedFromCache()) {
return;
}
// Ignore tabs displaying about pages
if (AboutPages.isAboutPage(tab.getURL())) {
return;
}
switch (event) {
// Show offline notification if tab is visible, or queue it for display later.
case PAGE_SHOW:
if (!isTabsTrayVisible() && Tabs.getInstance().isSelectedTab(tab)) {
showLoadedOfflineSnackbar(activityReference.get());
} else {
tabsQueuedForOfflineSnackbar.put(tab, null);
}
break;
// When tab is selected and offline notification was queued, display it if possible.
// SELECTED event might also fire when we're on a TabStrip, so check first.
case SELECTED:
if (isTabsTrayVisible()) {
break;
}
if (tabsQueuedForOfflineSnackbar.containsKey(tab)) {
showLoadedOfflineSnackbar(activityReference.get());
tabsQueuedForOfflineSnackbar.remove(tab);
}
break;
}
}
/**
* Displays the notification snackbar and logs a telemetry event.
*
* @param activity which will be used for displaying the snackbar.
*/
private static void showLoadedOfflineSnackbar(final Activity activity) {
if (activity == null) {
return;
}
Telemetry.sendUIEvent(TelemetryContract.Event.NETERROR, TelemetryContract.Method.TOAST, "usecache");
SnackbarHelper.showSnackbarWithActionAndColors(
activity,
activity.getResources().getString(R.string.tab_offline_version),
Snackbar.LENGTH_INDEFINITE,
null, null, null,
ContextCompat.getColor(activity, R.color.link_blue),
null
);
}
}

Просмотреть файл

@ -9,6 +9,7 @@ import android.app.Activity;
import android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
import android.support.annotation.CallSuper;
import android.util.Log;
import com.keepsafe.switchboard.SwitchBoard;
@ -23,7 +24,7 @@ import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.db.UrlAnnotations;
import org.mozilla.gecko.delegates.ForegroundAwareDelegate;
import org.mozilla.gecko.delegates.TabsTrayVisibilityAwareDelegate;
import org.mozilla.gecko.util.Experiments;
import org.mozilla.gecko.util.ThreadUtils;
@ -34,7 +35,7 @@ import ch.boye.httpclientandroidlib.util.TextUtils;
/**
* Promote "Add to home screen" if user visits website often.
*/
public class AddToHomeScreenPromotion extends ForegroundAwareDelegate implements Tabs.OnTabsChangedListener {
public class AddToHomeScreenPromotion extends TabsTrayVisibilityAwareDelegate implements Tabs.OnTabsChangedListener {
private static class URLHistory {
public final long visits;
public final long lastVisit;
@ -57,6 +58,7 @@ public class AddToHomeScreenPromotion extends ForegroundAwareDelegate implements
private int lastVisitMinimumAgeMs;
private int lastVisitMaximumAgeMs;
@CallSuper
@Override
public void onCreate(BrowserApp browserApp, Bundle savedInstanceState) {
super.onCreate(browserApp, savedInstanceState);
@ -127,7 +129,7 @@ public class AddToHomeScreenPromotion extends ForegroundAwareDelegate implements
return;
}
if (!isInForeground) {
if (isTabsTrayVisible()) {
// We only want to show this prompt if this tab is in the foreground and not on top
// of the tabs tray.
return;

Просмотреть файл

@ -47,6 +47,9 @@
<!ENTITY switch_to_tab "Switch to tab">
<!-- Localization note: Shown in a snackbar when tab is loaded from cache while device was offline. -->
<!ENTITY tab_offline_version "Showing offline version">
<!ENTITY crash_reporter_title "&brandShortName; Crash Reporter">
<!ENTITY crash_message2 "&brandShortName; had a problem and crashed. Your tabs should be listed on the &brandShortName; Start page when you restart.">
<!ENTITY crash_send_report_message3 "Tell &vendorShortName; about this crash so they can fix it">

Просмотреть файл

@ -262,6 +262,7 @@ gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
'delegates/BookmarkStateChangeDelegate.java',
'delegates/BrowserAppDelegate.java',
'delegates/BrowserAppDelegateWithReference.java',
'delegates/OfflineTabStatusDelegate.java',
'delegates/ScreenshotDelegate.java',
'delegates/TabsTrayVisibilityAwareDelegate.java',
'DevToolsAuthHelper.java',

Просмотреть файл

@ -68,6 +68,8 @@
<string name="switch_to_tab">&switch_to_tab;</string>
<string name="tab_offline_version">&tab_offline_version;</string>
<string name="crash_reporter_title">&crash_reporter_title;</string>
<string name="crash_message2">&crash_message2;</string>
<string name="crash_send_report_message3">&crash_send_report_message3;</string>

Просмотреть файл

@ -4297,7 +4297,8 @@ Tab.prototype = {
Messaging.sendRequest({
type: "Content:PageShow",
tabID: this.id,
userRequested: this.userRequested
userRequested: this.userRequested,
fromCache: Tabs.useCache
});
this.isSearch = false;
@ -7377,25 +7378,6 @@ var Tabs = {
// Clear the domain cache whenever a page is loaded into any browser.
this._domains.clear();
// Notify if we are loading a page from cache.
if (this._useCache) {
let targetDoc = aEvent.originalTarget;
let isTopLevel = (targetDoc.defaultView.parent === targetDoc.defaultView);
// Ignore any about: pages, especially about:neterror since it means we failed to find the page in cache.
let targetURI = targetDoc.documentURI;
if (isTopLevel && !targetURI.startsWith("about:")) {
UITelemetry.addEvent("neterror.1", "toast", null, "usecache");
Snackbars.show(
Strings.browser.GetStringFromName("networkOffline.message2"),
Snackbars.LENGTH_INDEFINITE,
{
// link_blue
backgroundColor: "#0096DD"
}
);
}
}
break;
case "TabOpen":
// Use opening a new tab as a trigger to expire the most stale tab.

Просмотреть файл

@ -434,9 +434,6 @@ openInApp.pageAction = Open in App
openInApp.ok = OK
openInApp.cancel = Cancel
#Network Offline
networkOffline.message2 = Showing offline version
#Tab sharing
tabshare.title = "Choose a tab to stream"
#Tabs in context menus