Bug 1414084 - Part 13 - Cache PageActions. r=Grisha

Since converting a PageAction message into an actual PageAction object also en-
tails parsing the image data URL into a drawable, we leave that task to the
PageActionLayout.

This means that the PageAction cache needs to operate slightly differently than
the MenuItem cache. First, we store all PageAction BundleEvent messages that
arrive while no PageActionLayout is ready and then forward them en masse when
one becomes available. Secondly, if the PageActionLayout is going away again,
we then also take a list of already parsed PageAction objects for safekeeping.

MozReview-Commit-ID: AcPPONXqe46

--HG--
extra : rebase_source : 696df760f28f9d126858920b544585e4c86219ff
This commit is contained in:
Jan Henning 2018-02-26 21:50:50 +01:00
parent a6932b97b2
commit f47eb731da
2 changed files with 151 additions and 53 deletions

View File

@ -6,6 +6,8 @@
package org.mozilla.gecko;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.Log;
@ -18,8 +20,12 @@ import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.GeckoBundle;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static org.mozilla.gecko.toolbar.PageActionLayout.PageAction;
import static org.mozilla.gecko.toolbar.PageActionLayout.PageActionLayoutDelegate;
/**
* For certain UI items added by add-ons or other JS/Gecko code, Gecko notifies us whenever an item
* is added, changed or removed. Since we must not miss any of these notifications and need to re-
@ -60,6 +66,14 @@ public class AddonUICache implements BundleEventListener {
private int mAddonMenuNextID = ADDON_MENU_OFFSET;
private Menu mMenu;
// A collection of PageAction messages that are still pending transformation into
// a full PageAction - most importantly transformation of the image data URI
// into a Drawable.
private final Map<String, GeckoBundle> mPendingPageActionQueue = new LinkedHashMap<>();
// A collection of PageActions ready for immediate usage.
private List<PageAction> mResolvedPageActionCache;
private PageActionLayoutDelegate mPageActionDelegate;
private boolean mInitialized;
public static AddonUICache getInstance() {
@ -77,6 +91,8 @@ public class AddonUICache implements BundleEventListener {
"Menu:Add",
"Menu:Update",
"Menu:Remove",
"PageActions:Add",
"PageActions:Remove",
null);
mInitialized = true;
@ -87,6 +103,9 @@ public class AddonUICache implements BundleEventListener {
mAddonMenuItemsCache.clear();
mAddonMenuNextID = ADDON_MENU_OFFSET;
mMenu = null;
mPendingPageActionQueue.clear();
mResolvedPageActionCache = null;
mPageActionDelegate = null;
}
@Override
@ -125,9 +144,59 @@ public class AddonUICache implements BundleEventListener {
updateAddonMenuItem(message.getString("uuid"),
message.getBundle("options"));
break;
case "PageActions:Add":
if (mPageActionDelegate != null) {
mPageActionDelegate.addPageAction(message);
} else {
mPendingPageActionQueue.put(message.getString("id"), message);
}
break;
case "PageActions:Remove":
if (mPageActionDelegate != null) {
mPageActionDelegate.removePageAction(message);
} else {
mPendingPageActionQueue.remove(message.getString("id"));
}
break;
}
}
/**
* If a list of {@link PageAction PageActions} has previously been provided in
* {@link AddonUICache#removePageActionLayoutDelegate}, it will be transferred back to the
* {@link PageActionLayoutDelegate}.
* In addition, any <code>GeckoBundles</code> containing <code>PageAction</code> messages that
* arrived while no delegate was available will now be transmitted.
* <p>
* Following this, any <code>PageAction</code> messages that arrive will be forwarded
* immediately to the provided delegate.
*/
public void setPageActionLayoutDelegate(final @NonNull PageActionLayoutDelegate newDelegate) {
newDelegate.setCachedPageActions(mResolvedPageActionCache);
mResolvedPageActionCache = null;
for (GeckoBundle pageActionMessage : mPendingPageActionQueue.values()) {
newDelegate.addPageAction(pageActionMessage);
}
mPendingPageActionQueue.clear();
mPageActionDelegate = newDelegate;
}
/**
* Clears the current PageActionDelegate and optionally takes a list of PageActions
* that will be stored, e.g. if the class that provided the delegate is going away.
*
* In addition, all PageAction EventDispatcher messages that arrive while no delegate is
* available will be stored for later retrieval.
*/
public void removePageActionLayoutDelegate(final @Nullable List<PageAction> pageActionsToCache) {
mPageActionDelegate = null;
mResolvedPageActionCache = pageActionsToCache;
}
/**
* Starts handling add-on menu items for the given {@link Menu} and also adds any
* menu items that have already been cached.
@ -278,4 +347,5 @@ public class AddonUICache implements BundleEventListener {
return null;
}
}

View File

@ -5,6 +5,7 @@
package org.mozilla.gecko.toolbar;
import org.mozilla.gecko.AddonUICache;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.R;
@ -14,8 +15,6 @@ import org.mozilla.gecko.preferences.GeckoPreferences;
import org.mozilla.gecko.pwa.PwaUtils;
import org.mozilla.gecko.util.DrawableUtil;
import org.mozilla.gecko.util.ResourceDrawableUtils;
import org.mozilla.gecko.util.BundleEventListener;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.GeckoBundle;
import org.mozilla.gecko.util.ShortcutUtils;
import org.mozilla.gecko.util.ThreadUtils;
@ -45,17 +44,21 @@ import java.util.ArrayList;
import static org.mozilla.gecko.toolbar.PageActionLayout.PageAction.UUID_PAGE_ACTION_PWA;
public class PageActionLayout extends ThemedLinearLayout implements BundleEventListener,
View.OnClickListener,
View.OnLongClickListener {
public class PageActionLayout extends ThemedLinearLayout
implements View.OnClickListener, View.OnLongClickListener {
private static final String MENU_BUTTON_KEY = "MENU_BUTTON_KEY";
private static final int DEFAULT_PAGE_ACTIONS_SHOWN = 2;
public static final String PREF_PWA_ONBOARDING = GeckoPreferences.NON_PREF_PREFIX + "pref_pwa_onboarding";
public interface PageActionLayoutDelegate {
void addPageAction(GeckoBundle message);
void removePageAction(GeckoBundle message);
void setCachedPageActions(List<PageAction> cachedPageActions);
}
private final Context mContext;
private final LinearLayout mLayout;
private final List<PageAction> mPageActionList;
private List<PageAction> mPageActionList;
private GeckoPopupMenu mPageActionsMenu;
@ -67,26 +70,51 @@ public class PageActionLayout extends ThemedLinearLayout implements BundleEventL
super(context, attrs);
mContext = context;
mLayout = this;
mPageActionList = new ArrayList<PageAction>();
setNumberShown(DEFAULT_PAGE_ACTIONS_SHOWN);
refreshPageActionIcons();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
EventDispatcher.getInstance().registerUiThreadListener(this,
"PageActions:Add",
"PageActions:Remove");
// Calling this will cause the AddonUICache to synchronously call addPageAction for all
// PageAction messages it received while no PageActionLayout was available.
// Processing those messages entails converting a data: URI into a drawable, which can take
// a few ms per message and therefore in theory cause some jank.
// In practice however, PageAction messages are commonly only generated when tabs (from the
// BrowserApp UI) are actually loading, so the AddonUICache's message queueing mechanism
// only becomes relevant if the BrowserApp UI has then been backgrounded *and* subsequently
// destroyed by the OS while some tabs are still loading. Merely starting up Gecko through a
// GeckoView-based activity on the other hand is not enough to queue up PageAction messages.
// Therefore, this case should happen rarely enough that we can tolerate it without having
// to think about moving the image processing into some asynchronous code path.
AddonUICache.getInstance().setPageActionLayoutDelegate(new PageActionLayoutDelegate() {
@Override
public void addPageAction(final GeckoBundle message) {
onAddPageAction(message);
}
@Override
public void removePageAction(final GeckoBundle message) {
onRemovePageAction(message);
}
@Override
public void setCachedPageActions(final List<PageAction> cachedPageActions) {
if (cachedPageActions != null) {
mPageActionList = cachedPageActions;
} else {
mPageActionList = new ArrayList<>();
}
setNumberShown(DEFAULT_PAGE_ACTIONS_SHOWN);
refreshPageActionIcons();
}
});
}
@Override
protected void onDetachedFromWindow() {
EventDispatcher.getInstance().unregisterUiThreadListener(this,
"PageActions:Add",
"PageActions:Remove");
AddonUICache.getInstance().removePageActionLayoutDelegate(mPageActionList);
super.onDetachedFromWindow();
}
@ -114,52 +142,52 @@ public class PageActionLayout extends ThemedLinearLayout implements BundleEventL
}
}
@Override // BundleEventListener
public void handleMessage(final String event, final GeckoBundle message,
final EventCallback callback) {
private void onAddPageAction(final GeckoBundle message) {
ThreadUtils.assertOnUiThread();
hidePreviousConfirmPrompt();
if ("PageActions:Add".equals(event)) {
final String id = message.getString("id");
final String id = message.getString("id");
boolean alreadyAdded = isPwaAdded(id);
if (alreadyAdded) {
return;
boolean alreadyAdded = isPwaAdded(id);
if (alreadyAdded) {
return;
}
maybeShowPwaOnboarding(id);
final String title = message.getString("title");
final String imageURL = message.getString("icon");
final boolean important = message.getBoolean("important");
final boolean useTint = message.getBoolean("useTint");
addPageAction(id, title, imageURL, useTint, new OnPageActionClickListeners() {
@Override
public void onClick(final String id) {
if (UUID_PAGE_ACTION_PWA.equals(id)) {
mPwaConfirm = PwaConfirm.show(getContext());
return;
}
final GeckoBundle data = new GeckoBundle(1);
data.putString("id", id);
EventDispatcher.getInstance().dispatch("PageActions:Clicked", data);
}
maybeShowPwaOnboarding(id);
@Override
public boolean onLongClick(String id) {
final GeckoBundle data = new GeckoBundle(1);
data.putString("id", id);
EventDispatcher.getInstance().dispatch("PageActions:LongClicked", data);
return true;
}
}, important);
}
final String title = message.getString("title");
final String imageURL = message.getString("icon");
final boolean important = message.getBoolean("important");
final boolean useTint = message.getBoolean("useTint");
private void onRemovePageAction(final GeckoBundle message) {
ThreadUtils.assertOnUiThread();
addPageAction(id, title, imageURL, useTint, new OnPageActionClickListeners() {
@Override
public void onClick(final String id) {
if (UUID_PAGE_ACTION_PWA.equals(id)) {
mPwaConfirm = PwaConfirm.show(getContext());
return;
}
final GeckoBundle data = new GeckoBundle(1);
data.putString("id", id);
EventDispatcher.getInstance().dispatch("PageActions:Clicked", data);
}
@Override
public boolean onLongClick(String id) {
final GeckoBundle data = new GeckoBundle(1);
data.putString("id", id);
EventDispatcher.getInstance().dispatch("PageActions:LongClicked", data);
return true;
}
}, important);
} else if ("PageActions:Remove".equals(event)) {
removePageAction(message.getString("id"));
}
hidePreviousConfirmPrompt();
removePageAction(message.getString("id"));
}
private void maybeShowPwaOnboarding(String id) {