diff --git a/mobile/android/base/GeckoApp.java b/mobile/android/base/GeckoApp.java index 61eef9e29233..57c53a6ecbee 100644 --- a/mobile/android/base/GeckoApp.java +++ b/mobile/android/base/GeckoApp.java @@ -593,25 +593,34 @@ abstract public class GeckoApp mLastUri = lastHistoryEntry.mUri; mLastTitle = lastHistoryEntry.mTitle; Bitmap bitmap = mSoftwareLayerClient.getBitmap(); + if (bitmap != null) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.PNG, 0, bos); - mLastScreen = bos.toByteArray(); - - // Make a thumbnail for the given tab, if it's still selected - // NOTE: bitmap is recycled in updateThumbnail - if (tab == mThumbnailTab) { - if (mThumbnailTab.getURL().equals("about:home")) - mThumbnailTab.updateThumbnail(null); - else - mThumbnailTab.updateThumbnail(bitmap); - } + processThumbnail(tab, bitmap, bos.toByteArray()); } else { mLastScreen = null; + GeckoAppShell.sendEventToGecko( + new GeckoEvent("Tab:Screenshot", + "{\"width\": \"" + mSoftwareLayerClient.getWidth() + "\", " + + "\"height\": \"" + mSoftwareLayerClient.getHeight() + "\", " + + "\"tabID\": \"" + tab.getId() + "\" }")); } } } } + + void processThumbnail(Tab thumbnailTab, Bitmap bitmap, byte[] compressed) { + if (Tabs.getInstance().isSelectedTab(thumbnailTab)) + mLastScreen = compressed; + if (thumbnailTab.getURL().equals("about:home")) { + thumbnailTab.updateThumbnail(null); + return; + } + if (bitmap == null) + bitmap = BitmapFactory.decodeByteArray(compressed, 0, compressed.length); + thumbnailTab.updateThumbnail(bitmap); + } private void maybeCancelFaviconLoad(Tab tab) { long faviconLoadId = tab.getFaviconLoadId(); @@ -898,6 +907,10 @@ abstract public class GeckoApp Log.i(LOGTAG, "Destroyed a tab"); int tabId = message.getInt("tabID"); handleCloseTab(tabId); + } else if (event.equals("Tab:ScreenshotData")) { + int tabId = message.getInt("tabID"); + Tab tab = Tabs.getInstance().getTab(tabId); + processThumbnail(tab, null, Base64.decode(message.getString("data").substring(22), Base64.DEFAULT)); } else if (event.equals("Tab:Selected")) { int tabId = message.getInt("tabID"); Log.i(LOGTAG, "Switched to tab: " + tabId); @@ -1534,6 +1547,7 @@ abstract public class GeckoApp GeckoAppShell.registerGeckoEventListener("Tab:Added", GeckoApp.mAppContext); GeckoAppShell.registerGeckoEventListener("Tab:Closed", GeckoApp.mAppContext); GeckoAppShell.registerGeckoEventListener("Tab:Selected", GeckoApp.mAppContext); + GeckoAppShell.registerGeckoEventListener("Tab:ScreenshotData", GeckoApp.mAppContext); GeckoAppShell.registerGeckoEventListener("Doorhanger:Add", GeckoApp.mAppContext); GeckoAppShell.registerGeckoEventListener("Doorhanger:Remove", GeckoApp.mAppContext); GeckoAppShell.registerGeckoEventListener("Menu:Add", GeckoApp.mAppContext); @@ -1770,6 +1784,7 @@ abstract public class GeckoApp GeckoAppShell.unregisterGeckoEventListener("Tab:Added", GeckoApp.mAppContext); GeckoAppShell.unregisterGeckoEventListener("Tab:Closed", GeckoApp.mAppContext); GeckoAppShell.unregisterGeckoEventListener("Tab:Selected", GeckoApp.mAppContext); + GeckoAppShell.unregisterGeckoEventListener("Tab:ScreenshotData", GeckoApp.mAppContext); GeckoAppShell.unregisterGeckoEventListener("Doorhanger:Add", GeckoApp.mAppContext); GeckoAppShell.unregisterGeckoEventListener("Menu:Add", GeckoApp.mAppContext); GeckoAppShell.unregisterGeckoEventListener("Menu:Remove", GeckoApp.mAppContext); diff --git a/mobile/android/base/gfx/GeckoSoftwareLayerClient.java b/mobile/android/base/gfx/GeckoSoftwareLayerClient.java index a19350bd95c8..5c7d29da20fc 100644 --- a/mobile/android/base/gfx/GeckoSoftwareLayerClient.java +++ b/mobile/android/base/gfx/GeckoSoftwareLayerClient.java @@ -118,6 +118,13 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL mTileLayer = new MultiTileLayer(mCairoImage, TILE_SIZE); } + public int getWidth() { + return mBufferSize.width; + } + + public int getHeight() { + return mBufferSize.height; + } protected void finalize() throws Throwable { try { @@ -274,13 +281,19 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL if (mBuffer == null || mBufferSize.width <= 0 || mBufferSize.height <= 0) return null; try { - Bitmap b = Bitmap.createBitmap(mBufferSize.width, mBufferSize.height, - CairoUtils.cairoFormatTobitmapConfig(mFormat)); + Bitmap b = null; - if (mTileLayer instanceof MultiTileLayer) + if (mTileLayer instanceof MultiTileLayer) { + b = Bitmap.createBitmap(mBufferSize.width, mBufferSize.height, + CairoUtils.cairoFormatTobitmapConfig(mFormat)); copyPixelsFromMultiTileLayer(b); - else + } else if (mTileLayer instanceof SingleTileLayer) { + b = Bitmap.createBitmap(mBufferSize.width, mBufferSize.height, + CairoUtils.cairoFormatTobitmapConfig(mFormat)); b.copyPixelsFromBuffer(mBuffer.asIntBuffer()); + } else { + Log.w(LOGTAG, "getBitmap() called on a layer (" + mTileLayer + ") we don't know how to get a bitmap from"); + } return b; } catch (OutOfMemoryError oom) { diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index 969ccedb06d8..2e299ce9b2b2 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -208,6 +208,7 @@ var BrowserApp = { Services.obs.addObserver(this, "Tab:Load", false); Services.obs.addObserver(this, "Tab:Select", false); Services.obs.addObserver(this, "Tab:Close", false); + Services.obs.addObserver(this, "Tab:Screenshot", false); Services.obs.addObserver(this, "Session:Back", false); Services.obs.addObserver(this, "Session:Forward", false); Services.obs.addObserver(this, "Session:Reload", false); @@ -470,6 +471,14 @@ var BrowserApp = { this._tabs.splice(this._tabs.indexOf(aTab), 1); }, + screenshotTab: function screenshotTab(aData) { + let json = JSON.parse(aData); + let tab = this.getTabForId(parseInt(json.tabID)); + let width = parseInt(json.width); + let height = parseInt(json.height); + tab.screenshot(width, height); + }, + selectTab: function selectTab(aTab) { if (aTab != null) { this.selectedTab = aTab; @@ -823,6 +832,8 @@ var BrowserApp = { this.selectTab(this.getTabForId(parseInt(aData))); } else if (aTopic == "Tab:Close") { this.closeTab(this.getTabForId(parseInt(aData))); + } else if (aTopic == "Tab:Screenshot") { + this.screenshotTab(aData); } else if (aTopic == "Browser:Quit") { this.quit(); } else if (aTopic == "SaveAs:PDF") { @@ -1467,6 +1478,26 @@ Tab.prototype = { this.updateTransform(); }, + screenshot: function(aWidth, aHeight) { + if (!this.browser || !this.browser.contentWindow) + return; + let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); + canvas.setAttribute("width", aWidth); + canvas.setAttribute("height", aHeight); + let ctx = canvas.getContext("2d"); + ctx.drawWindow(this.browser.contentWindow, 0, 0, aWidth, aHeight, "rgb(255, 255, 255)"); + let message = { + gecko: { + type: "Tab:ScreenshotData", + tabID: this.id, + width: aWidth, + height: aHeight, + data: canvas.toDataURL() + } + }; + sendMessageToJava(message); + }, + updateTransform: function() { let hasZoom = (Math.abs(this._viewport.zoom - 1.0) >= 1e-6); let x = this._viewport.offsetX + Math.round(-this.viewportExcess.x * this._viewport.zoom);