Bug 1286663 - Add request thumbnail native method to ThumbnailHelper; r=snorp
authorJim Chen <nchen@mozilla.com>
Thu, 21 Jul 2016 13:49:04 -0400
changeset 331271 74bee84b4e04b86dece328cd8dedd51f05e250b1
parent 331270 a9946d087a152d5bface0783a73c5717cfaa2075
child 331272 ac00f838d3dc23aec0e72afb17b75785f4af5eb2
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)
reviewerssnorp
bugs1286663
milestone50.0a1
Bug 1286663 - Add request thumbnail native method to ThumbnailHelper; r=snorp Add and use a request thumbnail native method call in ThumbnailHelper, instead of using the THUMBNAIL event in GeckoEvent.
mobile/android/base/java/org/mozilla/gecko/ThumbnailHelper.java
widget/android/AndroidBridge.cpp
widget/android/ThumbnailHelper.h
widget/android/nsAppShell.cpp
widget/android/nsAppShell.h
--- a/mobile/android/base/java/org/mozilla/gecko/ThumbnailHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/ThumbnailHelper.java
@@ -149,20 +149,22 @@ public final class ThumbnailHelper {
             // getAndProcessThumbnailFor which will hopefully be when we have more free memory.
             synchronized (mPendingThumbnails) {
                 mPendingThumbnails.clear();
             }
             return;
         }
 
         Log.d(LOGTAG, "Sending thumbnail event: " + mWidth + ", " + mHeight);
-        GeckoEvent e = GeckoEvent.createThumbnailEvent(tab.getId(), mWidth, mHeight, mBuffer);
-        GeckoAppShell.sendEventToGecko(e);
+        requestThumbnail(mBuffer, tab.getId(), mWidth, mHeight);
     }
 
+    @WrapForJNI
+    private static native void requestThumbnail(ByteBuffer data, int tabId, int width, int height);
+
     /* This method is invoked by JNI once the thumbnail data is ready. */
     @WrapForJNI(stubName = "SendThumbnail")
     public static void notifyThumbnail(ByteBuffer data, int tabId, boolean success, boolean shouldStore) {
         Tab tab = Tabs.getInstance().getTab(tabId);
         ThumbnailHelper helper = ThumbnailHelper.getInstance();
         if (success && tab != null) {
             helper.handleThumbnailData(tab, data, shouldStore ? CachePolicy.STORE : CachePolicy.NO_STORE);
         }
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -1701,17 +1701,17 @@ AndroidBridge::PumpMessageLoop()
 
     return GeckoThread::PumpMessageLoop(msg);
 }
 
 NS_IMETHODIMP nsAndroidBridge::GetBrowserApp(nsIAndroidBrowserApp * *aBrowserApp)
 {
     nsAppShell* const appShell = nsAppShell::Get();
     if (appShell)
-        appShell->GetBrowserApp(aBrowserApp);
+        NS_IF_ADDREF(*aBrowserApp = appShell->GetBrowserApp());
     return NS_OK;
 }
 
 NS_IMETHODIMP nsAndroidBridge::SetBrowserApp(nsIAndroidBrowserApp *aBrowserApp)
 {
     nsAppShell* const appShell = nsAppShell::Get();
     if (appShell)
         appShell->SetBrowserApp(aBrowserApp);
new file mode 100644
--- /dev/null
+++ b/widget/android/ThumbnailHelper.h
@@ -0,0 +1,253 @@
+/* -*- Mode: c++; 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/. */
+
+#ifndef ThumbnailHelper_h
+#define ThumbnailHelper_h
+
+#include "AndroidBridge.h"
+#include "GeneratedJNINatives.h"
+#include "gfxPlatform.h"
+#include "mozIDOMWindow.h"
+#include "nsAppShell.h"
+#include "nsCOMPtr.h"
+#include "nsIChannel.h"
+#include "nsIDOMWindowUtils.h"
+#include "nsIDOMClientRect.h"
+#include "nsIDocShell.h"
+#include "nsIHttpChannel.h"
+#include "nsIPresShell.h"
+#include "nsIURI.h"
+#include "nsPIDOMWindow.h"
+#include "nsPresContext.h"
+
+#include "mozilla/Preferences.h"
+
+namespace mozilla {
+
+class ThumbnailHelper final
+    : public widget::ThumbnailHelper::Natives<ThumbnailHelper>
+    , public jni::UsesNativeCallProxy
+{
+    ThumbnailHelper() = delete;
+
+    // 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)
+    {
+        nsCOMPtr<nsIChannel> channel;
+        if (NS_FAILED(docShell->GetCurrentDocumentChannel(
+                getter_AddRefs(channel))) || !channel) {
+            return false;
+        }
+
+        nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
+        if (!httpChannel) {
+            // Allow storing non-HTTP thumbnails.
+            return true;
+        }
+
+        // Don't store thumbnails for sites that didn't load or have
+        // Cache-Control: no-store.
+        uint32_t responseStatus = 0;
+        bool isNoStoreResponse = false;
+
+        if (NS_FAILED(httpChannel->GetResponseStatus(&responseStatus)) ||
+                (responseStatus / 100) != 2 ||
+                NS_FAILED(httpChannel->IsNoStoreResponse(&isNoStoreResponse)) ||
+                isNoStoreResponse) {
+            return false;
+        }
+
+        // Deny storage if we're viewing a HTTPS page with a 'Cache-Control'
+        // header having a value that is not 'public', unless enabled by user.
+        nsCOMPtr<nsIURI> uri;
+        bool isHttps = false;
+
+        if (NS_FAILED(channel->GetURI(getter_AddRefs(uri))) ||
+                !uri ||
+                NS_FAILED(uri->SchemeIs("https", &isHttps))) {
+            return false;
+        }
+
+        if (!isHttps ||
+                Preferences::GetBool("browser.cache.disk_cache_ssl", false)) {
+            // Allow storing non-HTTPS thumbnails, and HTTPS ones if enabled by
+            // user.
+            return true;
+        }
+
+        nsAutoCString cacheControl;
+        if (NS_FAILED(httpChannel->GetResponseHeader(
+                NS_LITERAL_CSTRING("Cache-Control"), cacheControl))) {
+            return false;
+        }
+
+        if (cacheControl.IsEmpty() ||
+                cacheControl.LowerCaseEqualsLiteral("public")) {
+            // Allow no cache-control, or public cache-control.
+            return true;
+        }
+        return false;
+    }
+
+    // Return a non-null nsIDocShell to indicate success.
+    static already_AddRefed<nsIDocShell>
+    GetThumbnailAndDocShell(mozIDOMWindowProxy* window,
+                            jni::ByteBuffer::Param aData,
+                            int32_t aThumbWidth, int32_t aThumbHeight)
+    {
+        // take a screenshot, as wide as possible, proportional to the destination size
+        nsCOMPtr<nsIDOMWindowUtils> utils = do_GetInterface(window);
+        nsCOMPtr<nsIDOMClientRect> rect;
+        float pageLeft = 0.0f, pageTop = 0.0f, pageWidth = 0.0f, pageHeight = 0.0f;
+
+        if (!utils ||
+                NS_FAILED(utils->GetRootBounds(getter_AddRefs(rect))) ||
+                !rect ||
+                NS_FAILED(rect->GetLeft(&pageLeft)) ||
+                NS_FAILED(rect->GetTop(&pageTop)) ||
+                NS_FAILED(rect->GetWidth(&pageWidth)) ||
+                NS_FAILED(rect->GetHeight(&pageHeight)) ||
+                int32_t(pageWidth) == 0 || int32_t(pageHeight) == 0) {
+            return nullptr;
+        }
+
+        const float aspectRatio = float(aThumbWidth) / float(aThumbHeight);
+        if (pageWidth / aspectRatio < pageHeight) {
+            pageHeight = pageWidth / aspectRatio;
+        } else {
+            pageWidth = pageHeight * aspectRatio;
+        }
+
+        nsCOMPtr<nsPIDOMWindowOuter> win = nsPIDOMWindowOuter::From(window);
+        nsCOMPtr<nsIDocShell> docShell = win->GetDocShell();
+        RefPtr<nsPresContext> presContext;
+
+        if (!docShell || NS_FAILED(docShell->GetPresContext(
+                getter_AddRefs(presContext))) || !presContext) {
+            return nullptr;
+        }
+
+        MOZ_ASSERT(gfxPlatform::GetPlatform()->
+                           SupportsAzureContentForType(BackendType::CAIRO),
+                   "Need BackendType::CAIRO support");
+
+        uint8_t* const data = static_cast<uint8_t*>(aData->Address());
+        if (!data) {
+            return nullptr;
+        }
+
+        const bool is24bit = !AndroidBridge::Bridge() ||
+                AndroidBridge::Bridge()->GetScreenDepth() == 24;
+        const uint32_t stride = aThumbWidth * (is24bit ? 4 : 2);
+
+        RefPtr<DrawTarget> dt = gfx::Factory::CreateDrawTargetForData(
+                BackendType::CAIRO,
+                data,
+                IntSize(aThumbWidth, aThumbHeight),
+                stride,
+                is24bit ? SurfaceFormat::B8G8R8X8
+                        : SurfaceFormat::R5G6B5_UINT16);
+
+        if (!dt || !dt->IsValid()) {
+            return nullptr;
+        }
+
+        nsCOMPtr<nsIPresShell> presShell = presContext->PresShell();
+        RefPtr<gfxContext> context = gfxContext::CreateOrNull(dt);
+        MOZ_ASSERT(context); // checked the draw target above
+
+        const float scale = 1.0f;
+
+        context->SetMatrix(context->CurrentMatrix().Scale(
+                scale * float(aThumbWidth) / pageWidth,
+                scale * float(aThumbHeight) / pageHeight));
+
+        const nsRect drawRect(
+                nsPresContext::CSSPixelsToAppUnits(pageLeft / scale),
+                nsPresContext::CSSPixelsToAppUnits(pageTop / scale),
+                nsPresContext::CSSPixelsToAppUnits(pageWidth / scale),
+                nsPresContext::CSSPixelsToAppUnits(pageHeight / scale));
+        const uint32_t renderDocFlags =
+                nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING |
+                nsIPresShell::RENDER_DOCUMENT_RELATIVE;
+        const nscolor bgColor = NS_RGB(255, 255, 255);
+
+        if (NS_FAILED(presShell->RenderDocument(
+                drawRect, renderDocFlags, bgColor, context))) {
+            return nullptr;
+        }
+
+        if (is24bit) {
+            gfxUtils::ConvertBGRAtoRGBA(data, stride * aThumbHeight);
+        }
+
+        return docShell.forget();
+    }
+
+public:
+    template<class Functor>
+    static void OnNativeCall(Functor&& aCall)
+    {
+        class IdleEvent : public nsAppShell::LambdaEvent<Functor>
+        {
+            using Base = nsAppShell::LambdaEvent<Functor>;
+
+        public:
+            IdleEvent(Functor&& aCall)
+                : Base(Forward<Functor>(aCall))
+            {}
+
+            void Run() override
+            {
+                MessageLoop::current()->PostIdleTask(
+                        NS_NewRunnableFunction(Move(Base::lambda)));
+            }
+        };
+
+        // Invoke RequestThumbnail on the main thread when the thread is idle.
+        nsAppShell::PostEvent(MakeUnique<IdleEvent>(Forward<Functor>(aCall)));
+    }
+
+    static void
+    RequestThumbnail(jni::ByteBuffer::Param aData, int32_t aTabId,
+                     int32_t aWidth, int32_t aHeight)
+    {
+        nsAppShell* const appShell = nsAppShell::Get();
+        if (!appShell || !aData) {
+            return;
+        }
+
+        nsCOMPtr<nsIAndroidBrowserApp> browserApp = appShell->GetBrowserApp();
+        if (!browserApp) {
+            return;
+        }
+
+        nsCOMPtr<mozIDOMWindowProxy> window;
+        nsCOMPtr<nsIBrowserTab> tab;
+
+        if (NS_FAILED(browserApp->GetBrowserTab(aTabId, getter_AddRefs(tab))) ||
+                !tab ||
+                NS_FAILED(tab->GetWindow(getter_AddRefs(window))) ||
+                !window) {
+            widget::ThumbnailHelper::SendThumbnail(
+                    aData, aTabId, /* success */ false, /* store */ false);
+            return;
+        }
+
+        nsCOMPtr<nsIDocShell> docShell = GetThumbnailAndDocShell(
+                window, aData, aWidth, aHeight);
+        const bool success = !!docShell;
+        const bool store = success ? ShouldStoreThumbnail(docShell) : false;
+
+        widget::ThumbnailHelper::SendThumbnail(aData, aTabId, success, store);
+    }
+};
+
+} // namespace mozilla
+
+#endif // ThumbnailHelper_h
--- a/widget/android/nsAppShell.cpp
+++ b/widget/android/nsAppShell.cpp
@@ -57,16 +57,17 @@
 #ifdef MOZ_LOGGING
 #include "mozilla/Logging.h"
 #endif
 
 #include "ANRReporter.h"
 #include "GeckoNetworkManager.h"
 #include "GeckoScreenOrientation.h"
 #include "PrefsHelper.h"
+#include "ThumbnailHelper.h"
 
 #ifdef DEBUG_ANDROID_EVENTS
 #define EVLOG(args...)  ALOG(args)
 #else
 #define EVLOG(args...) do { } while (0)
 #endif
 
 using namespace mozilla;
@@ -383,16 +384,17 @@ nsAppShell::nsAppShell()
         // Initialize JNI and Set the corresponding state in GeckoThread.
         AndroidBridge::ConstructBridge();
         GeckoAppShellSupport::Init();
         GeckoThreadSupport::Init();
         mozilla::ANRReporter::Init();
         mozilla::GeckoNetworkManager::Init();
         mozilla::GeckoScreenOrientation::Init();
         mozilla::PrefsHelper::Init();
+        mozilla::ThumbnailHelper::Init();
         nsWindow::InitNatives();
 
         widget::GeckoThread::SetState(widget::GeckoThread::State::JNI_READY());
     }
 
     sPowerManagerService = do_GetService(POWERMANAGERSERVICE_CONTRACTID);
 
     if (sPowerManagerService) {
--- a/widget/android/nsAppShell.h
+++ b/widget/android/nsAppShell.h
@@ -140,18 +140,18 @@ public:
                                     mozilla::UniquePtr<Event>&&) = nullptr);
 
     static already_AddRefed<nsIURI> ResolveURI(const nsCString& aUriStr);
 
     void SetBrowserApp(nsIAndroidBrowserApp* aBrowserApp) {
         mBrowserApp = aBrowserApp;
     }
 
-    void GetBrowserApp(nsIAndroidBrowserApp* *aBrowserApp) {
-        *aBrowserApp = mBrowserApp;
+    nsIAndroidBrowserApp* GetBrowserApp() {
+        return mBrowserApp;
     }
 
 protected:
     static nsAppShell* sAppShell;
     static mozilla::StaticAutoPtr<mozilla::Mutex> sAppShellLock;
 
     virtual ~nsAppShell();