Backed out changeset d8a07729a34c (bug 1286663)
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 21 Jul 2016 08:04:52 +0200
changeset 331089 6713d9c84fff0b7e007fa1ec6c1b48c8dbe89f4a
parent 331088 e9e32ccc8cfbcc802f41f67c80938a50b7ce1a65
child 331090 c774aed886fd764f7bd4a0011f1da25be7d2d3a6
push id9858
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 14:37:10 +0000
treeherdermozilla-aurora@203106ef6cb6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1286663
milestone50.0a1
backs outd8a07729a34c30f2a5138ef82a0615d4ea838a91
Backed out changeset d8a07729a34c (bug 1286663)
mobile/android/base/java/org/mozilla/gecko/GeckoEvent.java
widget/android/AndroidBridge.cpp
widget/android/AndroidBridge.h
widget/android/AndroidJavaWrappers.cpp
widget/android/AndroidJavaWrappers.h
widget/android/nsAppShell.cpp
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoEvent.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoEvent.java
@@ -63,16 +63,17 @@ public class GeckoEvent {
     // Make sure to keep these values in sync with the enum in
     // AndroidGeckoEvent in widget/android/AndroidJavaWrappers.h
     @JNITarget
     public enum NativeGeckoEvent {
         NATIVE_POKE(0),
         MOTION_EVENT(2),
         NOOP(15),
         VIEWPORT(20),
+        THUMBNAIL(25),
         NATIVE_GESTURE_EVENT(31),
         CALL_OBSERVER(33),
         REMOVE_OBSERVER(34),
         LOW_MEMORY(35),
         TELEMETRY_HISTOGRAM_ADD(37),
         TELEMETRY_UI_SESSION_START(42),
         TELEMETRY_UI_SESSION_STOP(43),
         TELEMETRY_UI_EVENT(44),
@@ -334,16 +335,25 @@ public class GeckoEvent {
           .append(", \"y\" : ").append(metrics.viewportRectTop)
           .append(", \"zoom\" : ").append(metrics.zoomFactor)
           .append(", \"displayPort\" :").append(displayPort.toJSON())
           .append('}');
         event.mCharactersExtra = sb.toString();
         return event;
     }
 
+    public static GeckoEvent createThumbnailEvent(int tabId, int bufw, int bufh, ByteBuffer buffer) {
+        GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.THUMBNAIL);
+        event.mPoints = new Point[1];
+        event.mPoints[0] = new Point(bufw, bufh);
+        event.mMetaState = tabId;
+        event.mBuffer = buffer;
+        return event;
+    }
+
     public static GeckoEvent createZoomedViewEvent(int tabId, int x, int y, int bufw, int bufh, float scaleFactor, ByteBuffer buffer) {
         GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.ZOOMEDVIEW);
         event.mPoints = new Point[2];
         event.mPoints[0] = new Point(x, y);
         event.mPoints[1] = new Point(bufw, bufh);
         event.mX = (double) scaleFactor;
         event.mMetaState = tabId;
         event.mBuffer = buffer;
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -308,16 +308,78 @@ void AutoGlobalWrappedJavaObject::Dispos
     GetEnvForThread()->DeleteGlobalRef(wrapped_obj);
     wrapped_obj = nullptr;
 }
 
 AutoGlobalWrappedJavaObject::~AutoGlobalWrappedJavaObject() {
     Dispose();
 }
 
+// Decides if we should store thumbnails for a given docshell based on the presence
+// of a Cache-Control: no-store header and the "browser.cache.disk_cache_ssl" pref.
+static bool ShouldStoreThumbnail(nsIDocShell* docshell) {
+    if (!docshell) {
+        return false;
+    }
+
+    nsresult rv;
+    nsCOMPtr<nsIChannel> channel;
+
+    docshell->GetCurrentDocumentChannel(getter_AddRefs(channel));
+    if (!channel) {
+        return false;
+    }
+
+    nsCOMPtr<nsIHttpChannel> httpChannel;
+    rv = channel->QueryInterface(NS_GET_IID(nsIHttpChannel), getter_AddRefs(httpChannel));
+    if (!NS_SUCCEEDED(rv)) {
+        return false;
+    }
+
+    // Don't store thumbnails for sites that didn't load
+    uint32_t responseStatus;
+    rv = httpChannel->GetResponseStatus(&responseStatus);
+    if (!NS_SUCCEEDED(rv) || floor((double) (responseStatus / 100)) != 2) {
+        return false;
+    }
+
+    // Cache-Control: no-store.
+    bool isNoStoreResponse = false;
+    httpChannel->IsNoStoreResponse(&isNoStoreResponse);
+    if (isNoStoreResponse) {
+        return false;
+    }
+
+    // Deny storage if we're viewing a HTTPS page with a
+    // 'Cache-Control' header having a value that is not 'public'.
+    nsCOMPtr<nsIURI> uri;
+    rv = channel->GetURI(getter_AddRefs(uri));
+    if (!NS_SUCCEEDED(rv)) {
+        return false;
+    }
+
+    // Don't capture HTTPS pages unless the user enabled it
+    // or the page has a Cache-Control:public header.
+    bool isHttps = false;
+    uri->SchemeIs("https", &isHttps);
+    if (isHttps && !Preferences::GetBool("browser.cache.disk_cache_ssl", false)) {
+        nsAutoCString cacheControl;
+        rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Cache-Control"), cacheControl);
+        if (!NS_SUCCEEDED(rv)) {
+            return false;
+        }
+
+        if (!cacheControl.IsEmpty() && !cacheControl.LowerCaseEqualsLiteral("public")) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
 static void
 getHandlersFromStringArray(JNIEnv *aJNIEnv, jobjectArray jArr, jsize aLen,
                            nsIMutableArray *aHandlersArray,
                            nsIHandlerApp **aDefaultApp,
                            const nsAString& aAction = EmptyString(),
                            const nsACString& aMimeType = EmptyCString())
 {
     nsString empty = EmptyString();
@@ -1777,16 +1839,120 @@ AndroidBridge::CaptureZoomedView(mozIDOM
     }
 
     LayerView::updateZoomedView(buffer);
 
     NS_ENSURE_SUCCESS(rv, rv);
     return NS_OK;
 }
 
+nsresult AndroidBridge::CaptureThumbnail(mozIDOMWindowProxy *window, int32_t bufW, int32_t bufH, int32_t tabId, ByteBuffer::Param buffer, bool &shouldStore)
+{
+    nsresult rv;
+    float scale = 1.0;
+
+    if (!buffer)
+        return NS_ERROR_FAILURE;
+
+    // take a screenshot, as wide as possible, proportional to the destination size
+    nsCOMPtr<nsIDOMWindowUtils> utils = do_GetInterface(window);
+    if (!utils)
+        return NS_ERROR_FAILURE;
+
+    nsCOMPtr<nsIDOMClientRect> rect;
+    rv = utils->GetRootBounds(getter_AddRefs(rect));
+    NS_ENSURE_SUCCESS(rv, rv);
+    if (!rect)
+        return NS_ERROR_FAILURE;
+
+    float left, top, width, height;
+    rect->GetLeft(&left);
+    rect->GetTop(&top);
+    rect->GetWidth(&width);
+    rect->GetHeight(&height);
+
+    if (width == 0 || height == 0)
+        return NS_ERROR_FAILURE;
+
+    int32_t srcX = left;
+    int32_t srcY = top;
+    int32_t srcW;
+    int32_t srcH;
+
+    float aspectRatio = ((float) bufW) / bufH;
+    if (width / aspectRatio < height) {
+        srcW = width;
+        srcH = width / aspectRatio;
+    } else {
+        srcW = height * aspectRatio;
+        srcH = height;
+    }
+
+    JNIEnv* const env = jni::GetGeckoThreadEnv();
+
+    AutoLocalJNIFrame jniFrame(env, 0);
+
+    MOZ_ASSERT(window);
+    nsCOMPtr<nsPIDOMWindowOuter> win = nsPIDOMWindowOuter::From(window);
+    RefPtr<nsPresContext> presContext;
+
+    nsIDocShell* docshell = win->GetDocShell();
+
+    // Decide if callers should store this thumbnail for later use.
+    shouldStore = ShouldStoreThumbnail(docshell);
+
+    if (docshell) {
+        docshell->GetPresContext(getter_AddRefs(presContext));
+    }
+
+    if (!presContext)
+        return NS_ERROR_FAILURE;
+    nscolor bgColor = NS_RGB(255, 255, 255);
+    nsCOMPtr<nsIPresShell> presShell = presContext->PresShell();
+    uint32_t renderDocFlags = (nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING |
+                               nsIPresShell::RENDER_DOCUMENT_RELATIVE);
+    nsRect r(nsPresContext::CSSPixelsToAppUnits(srcX / scale),
+             nsPresContext::CSSPixelsToAppUnits(srcY / scale),
+             nsPresContext::CSSPixelsToAppUnits(srcW / scale),
+             nsPresContext::CSSPixelsToAppUnits(srcH / scale));
+
+    bool is24bit = (GetScreenDepth() == 24);
+    uint32_t stride = bufW * (is24bit ? 4 : 2);
+
+    uint8_t* data = static_cast<uint8_t*>(buffer->Address());
+    if (!data)
+        return NS_ERROR_FAILURE;
+
+    MOZ_ASSERT(gfxPlatform::GetPlatform()->SupportsAzureContentForType(BackendType::CAIRO),
+               "Need BackendType::CAIRO support");
+    RefPtr<DrawTarget> dt =
+        Factory::CreateDrawTargetForData(BackendType::CAIRO,
+                                         data,
+                                         IntSize(bufW, bufH),
+                                         stride,
+                                         is24bit ? SurfaceFormat::B8G8R8X8 :
+                                                   SurfaceFormat::R5G6B5_UINT16);
+    if (!dt || !dt->IsValid()) {
+        ALOG_BRIDGE("Error creating DrawTarget");
+        return NS_ERROR_FAILURE;
+    }
+    RefPtr<gfxContext> context = gfxContext::CreateOrNull(dt);
+    MOZ_ASSERT(context); // checked the draw target above
+
+    context->SetMatrix(
+      context->CurrentMatrix().Scale(scale * bufW / srcW,
+                                     scale * bufH / srcH));
+    rv = presShell->RenderDocument(r, renderDocFlags, bgColor, context);
+    if (is24bit) {
+        gfxUtils::ConvertBGRAtoRGBA(data, stride * bufH);
+    }
+    NS_ENSURE_SUCCESS(rv, rv);
+    return NS_OK;
+}
+
 void
 AndroidBridge::GetDisplayPort(bool aPageSizeUpdate, bool aIsBrowserContentDisplayed, int32_t tabId, nsIAndroidViewport* metrics, nsIAndroidDisplayport** displayPort)
 {
 
     ALOG_BRIDGE("Enter: %s", __PRETTY_FUNCTION__);
     if (!mLayerClient) {
         ALOG_BRIDGE("Exceptional Exit: %s", __PRETTY_FUNCTION__);
         return;
--- a/widget/android/AndroidBridge.h
+++ b/widget/android/AndroidBridge.h
@@ -143,16 +143,17 @@ public:
         return sBridge;
     }
 
     /* These are all implemented in Java */
     bool GetThreadNameJavaProfiling(uint32_t aThreadId, nsCString & aResult);
     bool GetFrameNameJavaProfiling(uint32_t aThreadId, uint32_t aSampleId, uint32_t aFrameId, nsCString & aResult);
 
     nsresult CaptureZoomedView(mozIDOMWindowProxy *window, nsIntRect zoomedViewRect, jni::ByteBuffer::Param buffer, float zoomFactor);
+    nsresult CaptureThumbnail(mozIDOMWindowProxy *window, int32_t bufW, int32_t bufH, int32_t tabId, jni::ByteBuffer::Param buffer, bool &shouldStore);
     void GetDisplayPort(bool aPageSizeUpdate, bool aIsBrowserContentDisplayed, int32_t tabId, nsIAndroidViewport* metrics, nsIAndroidDisplayport** displayPort);
     void ContentDocumentChanged();
     bool IsContentDocumentDisplayed();
 
     bool ProgressiveUpdateCallback(bool aHasPendingNewThebesContent, const LayerRect& aDisplayPort, float aDisplayResolution, bool aDrawingCritical,
                                    mozilla::ParentLayerPoint& aScrollOffset, mozilla::CSSToParentLayerScale& aZoom);
 
     void SetLayerClient(java::GeckoLayerClient::Param jobj);
--- a/widget/android/AndroidJavaWrappers.cpp
+++ b/widget/android/AndroidJavaWrappers.cpp
@@ -305,16 +305,23 @@ AndroidGeckoEvent::Init(JNIEnv *jenv, jo
             break;
 
         case VIEWPORT: {
             ReadCharactersField(jenv);
             ReadCharactersExtraField(jenv);
             break;
         }
 
+        case THUMBNAIL: {
+            mMetaState = jenv->GetIntField(jobj, jMetaStateField);
+            ReadPointArray(mPoints, jenv, jPoints, 1);
+            mByteBuffer = new RefCountedJavaObject(jenv, jenv->GetObjectField(jobj, jByteBufferField));
+            break;
+        }
+
         case ZOOMEDVIEW: {
             mX = jenv->GetDoubleField(jobj, jXField);
             mMetaState = jenv->GetIntField(jobj, jMetaStateField);
             ReadPointArray(mPoints, jenv, jPoints, 2);
             mByteBuffer = new RefCountedJavaObject(jenv, jenv->GetObjectField(jobj, jByteBufferField));
             break;
         }
 
--- a/widget/android/AndroidJavaWrappers.h
+++ b/widget/android/AndroidJavaWrappers.h
@@ -586,16 +586,17 @@ protected:
 
 public:
     enum {
         NATIVE_POKE = 0,
         MOTION_EVENT = 2,
         NOOP = 15,
         APZ_INPUT_EVENT = 17, // used internally in AndroidJNI/nsAppShell/nsWindow
         VIEWPORT = 20,
+        THUMBNAIL = 25,
         NATIVE_GESTURE_EVENT = 31,
         CALL_OBSERVER = 33,
         REMOVE_OBSERVER = 34,
         LOW_MEMORY = 35,
         TELEMETRY_HISTOGRAM_ADD = 37,
         ADD_OBSERVER = 38,
         TELEMETRY_UI_SESSION_START = 42,
         TELEMETRY_UI_SESSION_STOP = 43,
--- a/widget/android/nsAppShell.cpp
+++ b/widget/android/nsAppShell.cpp
@@ -76,16 +76,52 @@ typedef mozilla::dom::GamepadPlatformSer
 nsIGeolocationUpdate *gLocationCallback = nullptr;
 nsAutoPtr<mozilla::AndroidGeckoEvent> gLastSizeChange;
 
 nsAppShell* nsAppShell::sAppShell;
 StaticAutoPtr<Mutex> nsAppShell::sAppShellLock;
 
 NS_IMPL_ISUPPORTS_INHERITED(nsAppShell, nsBaseAppShell, nsIObserver)
 
+class ThumbnailRunnable : public Runnable {
+public:
+    ThumbnailRunnable(nsIAndroidBrowserApp* aBrowserApp, int aTabId,
+                       const nsTArray<nsIntPoint>& aPoints, RefCountedJavaObject* aBuffer):
+        mBrowserApp(aBrowserApp), mPoints(aPoints), mTabId(aTabId), mBuffer(aBuffer) {}
+
+    virtual nsresult Run() {
+        const auto& buffer = jni::ByteBuffer::Ref::From(mBuffer->GetObject());
+        nsCOMPtr<mozIDOMWindowProxy> domWindow;
+        nsCOMPtr<nsIBrowserTab> tab;
+        mBrowserApp->GetBrowserTab(mTabId, getter_AddRefs(tab));
+        if (!tab) {
+            widget::ThumbnailHelper::SendThumbnail(buffer, mTabId, false, false);
+            return NS_ERROR_FAILURE;
+        }
+
+        tab->GetWindow(getter_AddRefs(domWindow));
+        if (!domWindow) {
+            widget::ThumbnailHelper::SendThumbnail(buffer, mTabId, false, false);
+            return NS_ERROR_FAILURE;
+        }
+
+        NS_ASSERTION(mPoints.Length() == 1, "Thumbnail event does not have enough coordinates");
+
+        bool shouldStore = true;
+        nsresult rv = AndroidBridge::Bridge()->CaptureThumbnail(domWindow, mPoints[0].x, mPoints[0].y, mTabId, buffer, shouldStore);
+        widget::ThumbnailHelper::SendThumbnail(buffer, mTabId, NS_SUCCEEDED(rv), shouldStore);
+        return rv;
+    }
+private:
+    nsCOMPtr<nsIAndroidBrowserApp> mBrowserApp;
+    nsTArray<nsIntPoint> mPoints;
+    int mTabId;
+    RefPtr<RefCountedJavaObject> mBuffer;
+};
+
 class WakeLockListener final : public nsIDOMMozWakeLockListener {
 private:
   ~WakeLockListener() {}
 
 public:
   NS_DECL_ISUPPORTS;
 
   nsresult Callback(const nsAString& topic, const nsAString& state) override {
@@ -656,16 +692,28 @@ nsAppShell::LegacyGeckoEvent::Run()
 
     EVLOG("nsAppShell: event %p %d", (void*)curEvent.get(), curEvent->Type());
 
     switch (curEvent->Type()) {
     case AndroidGeckoEvent::NATIVE_POKE:
         nsAppShell::Get()->NativeEventCallback();
         break;
 
+    case AndroidGeckoEvent::THUMBNAIL: {
+        if (!nsAppShell::Get()->mBrowserApp)
+            break;
+
+        int32_t tabId = curEvent->MetaState();
+        const nsTArray<nsIntPoint>& points = curEvent->Points();
+        RefCountedJavaObject* buffer = curEvent->ByteBuffer();
+        RefPtr<ThumbnailRunnable> sr = new ThumbnailRunnable(nsAppShell::Get()->mBrowserApp, tabId, points, buffer);
+        MessageLoop::current()->PostIdleTask(NewRunnableMethod(sr.get(), &ThumbnailRunnable::Run));
+        break;
+    }
+
     case AndroidGeckoEvent::ZOOMEDVIEW: {
         if (!nsAppShell::Get()->mBrowserApp)
             break;
         int32_t tabId = curEvent->MetaState();
         const nsTArray<nsIntPoint>& points = curEvent->Points();
         float scaleFactor = (float) curEvent->X();
         RefPtr<RefCountedJavaObject> javaBuffer = curEvent->ByteBuffer();
         const auto& mBuffer = jni::ByteBuffer::Ref::From(javaBuffer->GetObject());