mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 817067 - Introduce a ThumbnailHelper to deal with thumbnailing of pages. r=cpeterson, gcp
This commit is contained in:
parent
04efb729ae
commit
7e8cc24ac5
@ -742,8 +742,8 @@ public class AboutHomeContent extends ScrollView
|
||||
// Just using getWidth() will use incorrect values during onMeasure when rotating the device
|
||||
// Instead we pass in the measuredWidth, which is correct
|
||||
int w = getColumnWidth(measuredWidth);
|
||||
Tabs.setThumbnailWidth(w);
|
||||
heightMeasureSpec = MeasureSpec.makeMeasureSpec((int)(w*Tabs.getThumbnailAspectRatio()*numRows) + getPaddingTop() + getPaddingBottom(),
|
||||
ThumbnailHelper.getInstance().setThumbnailWidth(w);
|
||||
heightMeasureSpec = MeasureSpec.makeMeasureSpec((int)(w*ThumbnailHelper.THUMBNAIL_ASPECT_RATIO*numRows) + getPaddingTop() + getPaddingBottom(),
|
||||
MeasureSpec.EXACTLY);
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
@ -771,7 +771,7 @@ public class AboutHomeContent extends ScrollView
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
super.bindView(view, context, cursor);
|
||||
view.setLayoutParams(new AbsListView.LayoutParams(mTopSitesGrid.getColumnWidth(),
|
||||
Math.round(mTopSitesGrid.getColumnWidth()*Tabs.getThumbnailAspectRatio())));
|
||||
Math.round(mTopSitesGrid.getColumnWidth()*ThumbnailHelper.THUMBNAIL_ASPECT_RATIO)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,6 @@ import android.content.pm.ServiceInfo;
|
||||
import android.content.pm.Signature;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
@ -102,7 +101,6 @@ import java.io.OutputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
@ -705,54 +703,6 @@ abstract public class GeckoApp
|
||||
outState.putString(SAVED_STATE_PRIVATE_SESSION, mPrivateBrowsingSession);
|
||||
}
|
||||
|
||||
void getAndProcessThumbnailForTab(final Tab tab) {
|
||||
if ("about:home".equals(tab.getURL())) {
|
||||
tab.updateThumbnail(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (tab.getState() == Tab.STATE_DELAYED) {
|
||||
if (tab.getURL() != null) {
|
||||
byte[] thumbnail = BrowserDB.getThumbnailForUrl(getContentResolver(), tab.getURL());
|
||||
if (thumbnail != null)
|
||||
processThumbnail(tab, null, thumbnail);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int dw = Tabs.getThumbnailWidth();
|
||||
int dh = Tabs.getThumbnailHeight();
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createScreenshotEvent(tab.getId(), 0, 0, 0, 0, 0, 0, dw, dh, dw, dh, ScreenshotHandler.SCREENSHOT_THUMBNAIL, tab.getThumbnailBuffer()));
|
||||
}
|
||||
|
||||
void handleThumbnailData(Tab tab, ByteBuffer data) {
|
||||
if (shouldUpdateThumbnail(tab)) {
|
||||
Bitmap b = tab.getThumbnailBitmap();
|
||||
data.position(0);
|
||||
b.copyPixelsFromBuffer(data);
|
||||
processThumbnail(tab, b, null);
|
||||
}
|
||||
}
|
||||
|
||||
void processThumbnail(Tab thumbnailTab, Bitmap bitmap, byte[] compressed) {
|
||||
try {
|
||||
if (bitmap == null) {
|
||||
if (compressed == null) {
|
||||
Log.w(LOGTAG, "processThumbnail: one of bitmap or compressed must be non-null!");
|
||||
return;
|
||||
}
|
||||
bitmap = BitmapFactory.decodeByteArray(compressed, 0, compressed.length);
|
||||
}
|
||||
thumbnailTab.updateThumbnail(bitmap);
|
||||
} catch (OutOfMemoryError ome) {
|
||||
Log.w(LOGTAG, "decoding byte array ran out of memory", ome);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldUpdateThumbnail(Tab tab) {
|
||||
return (Tabs.getInstance().isSelectedTab(tab) || areTabsShown());
|
||||
}
|
||||
|
||||
public void hideFormAssistPopup() {
|
||||
if (mFormAssistPopup != null)
|
||||
mFormAssistPopup.hide();
|
||||
@ -1224,7 +1174,7 @@ abstract public class GeckoApp
|
||||
if (!TextUtils.equals(oldURL, tab.getURL()))
|
||||
return;
|
||||
|
||||
getAndProcessThumbnailForTab(tab);
|
||||
ThumbnailHelper.getInstance().getAndProcessThumbnailFor(tab);
|
||||
if (Tabs.getInstance().isSelectedTab(tab)) {
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createStartPaintListentingEvent(tab.getId()));
|
||||
ScreenshotHandler.screenshotWholePage(tab);
|
||||
|
@ -137,6 +137,7 @@ FENNEC_JAVA_FILES = \
|
||||
Telemetry.java \
|
||||
TextSelection.java \
|
||||
TextSelectionHandle.java \
|
||||
ThumbnailHelper.java \
|
||||
WebAppAllocator.java \
|
||||
ZoomConstraints.java \
|
||||
gfx/BitmapUtils.java \
|
||||
|
@ -367,7 +367,7 @@ public final class ScreenshotHandler implements Runnable {
|
||||
{
|
||||
Tab tab = Tabs.getInstance().getTab(tabId);
|
||||
if (tab != null) {
|
||||
GeckoApp.mAppContext.handleThumbnailData(tab, data);
|
||||
ThumbnailHelper.getInstance().handleThumbnailData(tab, data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ package org.mozilla.gecko;
|
||||
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.gfx.Layer;
|
||||
import org.mozilla.gecko.mozglue.DirectBufferAllocator;
|
||||
import org.mozilla.gecko.util.GeckoAsyncTask;
|
||||
|
||||
import org.json.JSONException;
|
||||
@ -25,7 +24,6 @@ import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
@ -64,7 +62,6 @@ public class Tab {
|
||||
private ContentObserver mContentObserver;
|
||||
private int mCheckerboardColor = Color.WHITE;
|
||||
private int mState;
|
||||
private ByteBuffer mThumbnailBuffer;
|
||||
private Bitmap mThumbnailBitmap;
|
||||
private boolean mDesktopMode;
|
||||
private boolean mEnteringReaderMode;
|
||||
@ -155,33 +152,25 @@ public class Tab {
|
||||
return mThumbnail;
|
||||
}
|
||||
|
||||
synchronized public ByteBuffer getThumbnailBuffer() {
|
||||
int capacity = Tabs.getThumbnailWidth() * Tabs.getThumbnailHeight() * 2 /* 16 bpp */;
|
||||
if (mThumbnailBuffer != null && mThumbnailBuffer.capacity() == capacity)
|
||||
return mThumbnailBuffer;
|
||||
freeBuffer();
|
||||
mThumbnailBitmap = null;
|
||||
mThumbnailBuffer = DirectBufferAllocator.allocate(capacity);
|
||||
return mThumbnailBuffer;
|
||||
}
|
||||
|
||||
synchronized public Bitmap getThumbnailBitmap() {
|
||||
// Bug 787318 - Honeycomb has a bug with bitmap caching, we can't
|
||||
// reuse the bitmap there.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB
|
||||
|| Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB_MR2) {
|
||||
if (mThumbnailBitmap != null)
|
||||
return mThumbnailBitmap;
|
||||
} else {
|
||||
if (mThumbnailBitmap != null)
|
||||
public Bitmap getThumbnailBitmap(int width, int height) {
|
||||
if (mThumbnailBitmap != null) {
|
||||
// Bug 787318 - Honeycomb has a bug with bitmap caching, we can't
|
||||
// reuse the bitmap there.
|
||||
boolean honeycomb = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB
|
||||
&& Build.VERSION.SDK_INT <= Build.VERSION_CODES.HONEYCOMB_MR2);
|
||||
boolean sizeChange = mThumbnailBitmap.getWidth() != width
|
||||
|| mThumbnailBitmap.getHeight() != height;
|
||||
if (honeycomb || sizeChange) {
|
||||
mThumbnailBitmap.recycle();
|
||||
mThumbnailBitmap = null;
|
||||
}
|
||||
}
|
||||
return mThumbnailBitmap = Bitmap.createBitmap(Tabs.getThumbnailWidth(), Tabs.getThumbnailHeight(), Bitmap.Config.RGB_565);
|
||||
}
|
||||
|
||||
synchronized void freeBuffer() {
|
||||
DirectBufferAllocator.free(mThumbnailBuffer);
|
||||
mThumbnailBuffer = null;
|
||||
if (mThumbnailBitmap == null) {
|
||||
mThumbnailBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
|
||||
}
|
||||
|
||||
return mThumbnailBitmap;
|
||||
}
|
||||
|
||||
public void updateThumbnail(final Bitmap b) {
|
||||
|
@ -51,8 +51,6 @@ public class Tabs implements GeckoEventListener {
|
||||
|
||||
private GeckoApp mActivity;
|
||||
|
||||
static private int sThumbnailWidth = -1;
|
||||
|
||||
private Tabs() {
|
||||
registerEventListener("SessionHistory:New");
|
||||
registerEventListener("SessionHistory:Back");
|
||||
@ -69,24 +67,6 @@ public class Tabs implements GeckoEventListener {
|
||||
registerEventListener("Reader:Share");
|
||||
}
|
||||
|
||||
static public void setThumbnailWidth(int val) {
|
||||
// Round this to the next highest power of two
|
||||
sThumbnailWidth = (int)(Math.pow( 2, Math.ceil(Math.log(val)/Math.log(2) )));
|
||||
}
|
||||
|
||||
static public int getThumbnailWidth() {
|
||||
if (sThumbnailWidth < 0) {
|
||||
sThumbnailWidth = (int) (GeckoApp.mAppContext.getResources().getDimension(R.dimen.tab_thumbnail_width));
|
||||
}
|
||||
return sThumbnailWidth & ~0x1;
|
||||
}
|
||||
|
||||
static public int getThumbnailHeight() {
|
||||
return Math.round(getThumbnailWidth() * getThumbnailAspectRatio()) & ~0x1;
|
||||
}
|
||||
|
||||
static public float getThumbnailAspectRatio() { return 0.714f; }
|
||||
|
||||
public void attachToActivity(GeckoApp activity) {
|
||||
mActivity = activity;
|
||||
}
|
||||
@ -114,7 +94,6 @@ public class Tabs implements GeckoEventListener {
|
||||
Tab tab = getTab(id);
|
||||
mOrder.remove(tab);
|
||||
mTabs.remove(id);
|
||||
tab.freeBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
@ -330,12 +309,13 @@ public class Tabs implements GeckoEventListener {
|
||||
}
|
||||
|
||||
public void refreshThumbnails() {
|
||||
final ThumbnailHelper helper = ThumbnailHelper.getInstance();
|
||||
Iterator<Tab> iterator = mTabs.values().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
final Tab tab = iterator.next();
|
||||
GeckoAppShell.getHandler().post(new Runnable() {
|
||||
public void run() {
|
||||
mActivity.getAndProcessThumbnailForTab(tab);
|
||||
helper.getAndProcessThumbnailFor(tab);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
199
mobile/android/base/ThumbnailHelper.java
Normal file
199
mobile/android/base/ThumbnailHelper.java
Normal file
@ -0,0 +1,199 @@
|
||||
/* -*- 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.gecko;
|
||||
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.gfx.IntSize;
|
||||
import org.mozilla.gecko.mozglue.DirectBufferAllocator;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.util.Log;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.LinkedList;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Helper class to generate thumbnails for tabs.
|
||||
* Internally, a queue of pending thumbnails is maintained in mPendingThumbnails.
|
||||
* The head of the queue is the thumbnail that is currently being processed; upon
|
||||
* completion of the current thumbnail the next one is automatically processed.
|
||||
* Changes to the thumbnail width are stashed in mPendingWidth and the change is
|
||||
* applied between thumbnail processing. This allows a single thumbnail buffer to
|
||||
* be used for all thumbnails.
|
||||
*/
|
||||
final class ThumbnailHelper {
|
||||
private static final String LOGTAG = "GeckoThumbnailHelper";
|
||||
|
||||
public static final float THUMBNAIL_ASPECT_RATIO = 0.714f; // this is a 5:7 ratio (as per UX decision)
|
||||
|
||||
// static singleton stuff
|
||||
|
||||
private static ThumbnailHelper sInstance;
|
||||
|
||||
public static synchronized ThumbnailHelper getInstance() {
|
||||
if (sInstance == null) {
|
||||
sInstance = new ThumbnailHelper();
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
// instance stuff
|
||||
|
||||
private final LinkedList<Tab> mPendingThumbnails; // synchronized access only
|
||||
private AtomicInteger mPendingWidth;
|
||||
private int mWidth;
|
||||
private int mHeight;
|
||||
private ByteBuffer mBuffer;
|
||||
|
||||
private ThumbnailHelper() {
|
||||
mPendingThumbnails = new LinkedList<Tab>();
|
||||
mPendingWidth = new AtomicInteger((int)GeckoApp.mAppContext.getResources().getDimension(R.dimen.tab_thumbnail_width));
|
||||
mWidth = -1;
|
||||
mHeight = -1;
|
||||
}
|
||||
|
||||
public void getAndProcessThumbnailFor(Tab tab) {
|
||||
if ("about:home".equals(tab.getURL())) {
|
||||
tab.updateThumbnail(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (tab.getState() == Tab.STATE_DELAYED) {
|
||||
String url = tab.getURL();
|
||||
if (url != null) {
|
||||
byte[] thumbnail = BrowserDB.getThumbnailForUrl(GeckoApp.mAppContext.getContentResolver(), url);
|
||||
if (thumbnail != null) {
|
||||
setTabThumbnail(tab, null, thumbnail);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (mPendingThumbnails) {
|
||||
if (mPendingThumbnails.lastIndexOf(tab) > 0) {
|
||||
// This tab is already in the queue, so don't add it again.
|
||||
// Note that if this tab is only at the *head* of the queue,
|
||||
// (i.e. mPendingThumbnails.lastIndexOf(tab) == 0) then we do
|
||||
// add it again because it may have already been thumbnailed
|
||||
// and now we need to do it again.
|
||||
return;
|
||||
}
|
||||
|
||||
mPendingThumbnails.add(tab);
|
||||
if (mPendingThumbnails.size() > 1) {
|
||||
// Some thumbnail was already being processed, so wait
|
||||
// for that to be done.
|
||||
return;
|
||||
}
|
||||
}
|
||||
requestThumbnailFor(tab);
|
||||
}
|
||||
|
||||
public void setThumbnailWidth(int width) {
|
||||
mPendingWidth.set(IntSize.nextPowerOfTwo(width));
|
||||
}
|
||||
|
||||
private void updateThumbnailSize() {
|
||||
// Apply any pending width updates
|
||||
mWidth = mPendingWidth.get();
|
||||
|
||||
mWidth &= ~0x1; // Ensure the width is always an even number (bug 776906)
|
||||
mHeight = Math.round(mWidth * THUMBNAIL_ASPECT_RATIO);
|
||||
|
||||
int capacity = mWidth * mHeight * 2; // Multiply by 2 for 16bpp
|
||||
if (mBuffer == null || mBuffer.capacity() != capacity) {
|
||||
if (mBuffer != null) {
|
||||
mBuffer = DirectBufferAllocator.free(mBuffer);
|
||||
}
|
||||
try {
|
||||
mBuffer = DirectBufferAllocator.allocate(capacity);
|
||||
} catch (OutOfMemoryError oom) {
|
||||
Log.w(LOGTAG, "Unable to allocate thumbnail buffer of capacity " + capacity);
|
||||
// At this point mBuffer will be pointing to null, so we are in a sane state.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void requestThumbnailFor(Tab tab) {
|
||||
updateThumbnailSize();
|
||||
|
||||
if (mBuffer == null) {
|
||||
// Buffer allocation may have failed. In this case we can't send the
|
||||
// event requesting the screenshot which means we won't get back a response
|
||||
// and so our queue will grow unboundedly. Handle this scenario by clearing
|
||||
// the queue (no point trying more thumbnailing right now since we're likely
|
||||
// low on memory). We will try again normally on the next call to
|
||||
// getAndProcessThumbnailFor which will hopefully be when we have more free memory.
|
||||
synchronized (mPendingThumbnails) {
|
||||
mPendingThumbnails.clear();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
GeckoEvent e = GeckoEvent.createScreenshotEvent(
|
||||
tab.getId(),
|
||||
0, 0, 0, 0, // sx, sy, sw, sh
|
||||
0, 0, mWidth, mHeight, // dx, dy, dw, dh
|
||||
mWidth, mHeight, // bw, bh
|
||||
ScreenshotHandler.SCREENSHOT_THUMBNAIL,
|
||||
mBuffer);
|
||||
GeckoAppShell.sendEventToGecko(e);
|
||||
}
|
||||
|
||||
/* This method is invoked by Gecko once the thumbnail data is ready. */
|
||||
void handleThumbnailData(Tab tab, ByteBuffer data) {
|
||||
if (data != mBuffer) {
|
||||
// This should never happen, but log it and recover gracefully
|
||||
Log.e(LOGTAG, "handleThumbnailData called with an unexpected ByteBuffer!");
|
||||
}
|
||||
|
||||
if (shouldUpdateThumbnail(tab)) {
|
||||
processThumbnailData(tab, data);
|
||||
}
|
||||
Tab nextTab = null;
|
||||
synchronized (mPendingThumbnails) {
|
||||
if (tab != mPendingThumbnails.peek()) {
|
||||
Log.e(LOGTAG, "handleThumbnailData called with unexpected tab's data!");
|
||||
// This should never happen, but recover gracefully by processing the
|
||||
// unexpected tab that we found in the queue
|
||||
} else {
|
||||
mPendingThumbnails.remove();
|
||||
}
|
||||
nextTab = mPendingThumbnails.peek();
|
||||
}
|
||||
if (nextTab != null) {
|
||||
requestThumbnailFor(nextTab);
|
||||
}
|
||||
}
|
||||
|
||||
private void processThumbnailData(Tab tab, ByteBuffer data) {
|
||||
Bitmap b = tab.getThumbnailBitmap(mWidth, mHeight);
|
||||
data.position(0);
|
||||
b.copyPixelsFromBuffer(data);
|
||||
setTabThumbnail(tab, b, null);
|
||||
}
|
||||
|
||||
private void setTabThumbnail(Tab tab, Bitmap bitmap, byte[] compressed) {
|
||||
try {
|
||||
if (bitmap == null) {
|
||||
if (compressed == null) {
|
||||
Log.w(LOGTAG, "setTabThumbnail: one of bitmap or compressed must be non-null!");
|
||||
return;
|
||||
}
|
||||
bitmap = BitmapFactory.decodeByteArray(compressed, 0, compressed.length);
|
||||
}
|
||||
tab.updateThumbnail(bitmap);
|
||||
} catch (OutOfMemoryError ome) {
|
||||
Log.w(LOGTAG, "setTabThumbnail: decoding byte array of length " + compressed.length + " ran out of memory");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldUpdateThumbnail(Tab tab) {
|
||||
return (Tabs.getInstance().isSelectedTab(tab) || GeckoApp.mAppContext.areTabsShown());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user