bug 713874 - Black thumbnails are produced by GeckoSoftwareLayerClient.getBitmap() r=mfinkle
☠☠ backed out by 5104f9ed6254 ☠ ☠
authorBrad Lassey <blassey@mozilla.com>
Fri, 13 Jan 2012 13:10:13 -0500
changeset 85732 ba3335f34100a1b9367c50d9d683991f45b0f1ec
parent 85731 cfd6e9dbc3795ea4f122b80ba4f54ea509edeace
child 85733 5104f9ed6254122ef05f236c83060e3420cae367
push id805
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 18:17:35 +0000
treeherdermozilla-aurora@6fb3bf232436 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmfinkle
bugs713874
milestone12.0a1
bug 713874 - Black thumbnails are produced by GeckoSoftwareLayerClient.getBitmap() r=mfinkle
mobile/android/base/GeckoApp.java
mobile/android/base/gfx/GeckoSoftwareLayerClient.java
mobile/android/chrome/content/browser.js
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -588,35 +588,44 @@ abstract public class GeckoApp
 
                 ViewportMetrics viewportMetrics = mSoftwareLayerClient.getGeckoViewportMetrics();
                 if (viewportMetrics != null)
                     mLastViewport = viewportMetrics.toJSON();
 
                 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(tab))
+            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();
 
         if (faviconLoadId == Favicons.NOT_LOADING)
             return;
 
         // Cancel pending favicon load task
@@ -893,16 +902,20 @@ abstract public class GeckoApp
                 Tab tab = handleAddTab(message);
                 Boolean selected = message.getBoolean("selected");
                 if (selected)
                     handleSelectTab(tab.getId());
             } else if (event.equals("Tab:Closed")) {
                 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);
                 handleSelectTab(tabId);
             } else if (event.equals("Doorhanger:Add")) {
                 handleDoorHanger(message);
             } else if (event.equals("Doorhanger:Remove")) {
                 handleDoorHangerRemove(message);
@@ -1529,16 +1542,17 @@ abstract public class GeckoApp
         GeckoAppShell.registerGeckoEventListener("Content:LocationChange", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("Content:SecurityChange", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("Content:StateChange", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("Content:LoadError", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("onCameraCapture", GeckoApp.mAppContext);
         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);
         GeckoAppShell.registerGeckoEventListener("Menu:Remove", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("Gecko:Ready", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("Toast:Show", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("ToggleChrome:Hide", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("ToggleChrome:Show", GeckoApp.mAppContext);
@@ -1765,16 +1779,17 @@ abstract public class GeckoApp
         GeckoAppShell.unregisterGeckoEventListener("Content:LocationChange", GeckoApp.mAppContext);
         GeckoAppShell.unregisterGeckoEventListener("Content:SecurityChange", GeckoApp.mAppContext);
         GeckoAppShell.unregisterGeckoEventListener("Content:StateChange", GeckoApp.mAppContext);
         GeckoAppShell.unregisterGeckoEventListener("Content:LoadError", GeckoApp.mAppContext);
         GeckoAppShell.unregisterGeckoEventListener("onCameraCapture", GeckoApp.mAppContext);
         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);
         GeckoAppShell.unregisterGeckoEventListener("Gecko:Ready", GeckoApp.mAppContext);
         GeckoAppShell.unregisterGeckoEventListener("Toast:Show", GeckoApp.mAppContext);
         GeckoAppShell.unregisterGeckoEventListener("ToggleChrome:Hide", GeckoApp.mAppContext);
         GeckoAppShell.unregisterGeckoEventListener("ToggleChrome:Show", GeckoApp.mAppContext);
         GeckoAppShell.unregisterGeckoEventListener("FormAssist:AutoComplete", GeckoApp.mAppContext);
--- a/mobile/android/base/gfx/GeckoSoftwareLayerClient.java
+++ b/mobile/android/base/gfx/GeckoSoftwareLayerClient.java
@@ -113,16 +113,23 @@ public class GeckoSoftwareLayerClient ex
             public IntSize getSize() { return mBufferSize; }
             @Override
             public int getFormat() { return mFormat; }
         };
 
         mTileLayer = new MultiTileLayer(mCairoImage, TILE_SIZE);
     }
 
+    public int getWidth() {
+        return mBufferSize.width;
+    }
+
+    public int getHeight() {
+        return mBufferSize.height;
+    }
 
     protected void finalize() throws Throwable {
         try {
             if (mBuffer != null)
                 GeckoAppShell.freeDirectBuffer(mBuffer);
             mBuffer = null;
         } finally {
             super.finalize();
@@ -269,23 +276,29 @@ public class GeckoSoftwareLayerClient ex
     public Bitmap getBitmap() {
         // Begin a tile transaction, otherwise the buffer can be destroyed while
         // we're reading from it.
         beginTransaction(mTileLayer);
         try {
             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) {
                 Log.w(LOGTAG, "Unable to create bitmap", oom);
                 return null;
             }
         } finally {
             endTransaction(mTileLayer);
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -203,16 +203,17 @@ var BrowserApp = {
     ViewportHandler.init();
 
     getBridge().setDrawMetadataProvider(MetadataProvider);
 
     Services.obs.addObserver(this, "Tab:Add", false);
     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);
     Services.obs.addObserver(this, "Session:Stop", false);
     Services.obs.addObserver(this, "SaveAs:PDF", false);
     Services.obs.addObserver(this, "Browser:Quit", false);
     Services.obs.addObserver(this, "Preferences:Get", false);
     Services.obs.addObserver(this, "Preferences:Set", false);
@@ -465,16 +466,24 @@ var BrowserApp = {
     let evt = document.createEvent("UIEvents");
     evt.initUIEvent("TabClose", true, false, window, null);
     aTab.browser.dispatchEvent(evt);
 
     aTab.destroy();
     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;
       aTab.active = true;
       let message = {
         gecko: {
           type: "Tab:Selected",
           tabID: aTab.id
@@ -818,16 +827,18 @@ var BrowserApp = {
       if (aTopic == "Tab:Add")
         this.addTab(url, params);
       else
         this.loadURI(url, browser, params);
     } else if (aTopic == "Tab:Select") {
       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") {
       this.saveAsPDF(browser);
     } else if (aTopic == "Preferences:Get") {
       this.getPreferences(aData);
     } else if (aTopic == "Preferences:Set") {
       this.setPreferences(aData);
@@ -1462,16 +1473,36 @@ Tab.prototype = {
       this._viewport.zoom = aViewport.zoom;
       transformChanged = true;
     }
 
     if (transformChanged)
       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);
     let y = this._viewport.offsetY + Math.round(-this.viewportExcess.y * this._viewport.zoom);
 
     let transform =
       "translate(" + x + "px, " +
                      y + "px)";