/* -*- 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 "FennecJNINatives.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 java::ThumbnailHelper::Natives , public java::ZoomedView::Natives { ThumbnailHelper() = delete; static already_AddRefed GetWindowForTab(int32_t aTabId) { nsAppShell* const appShell = nsAppShell::Get(); if (!appShell) { return nullptr; } nsCOMPtr browserApp = appShell->GetBrowserApp(); if (!browserApp) { return nullptr; } nsCOMPtr window; nsCOMPtr tab; if (NS_FAILED(browserApp->GetBrowserTab(aTabId, getter_AddRefs(tab))) || !tab || NS_FAILED(tab->GetWindow(getter_AddRefs(window))) || !window) { return nullptr; } return window.forget(); } // 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 channel; if (NS_FAILED(docShell->GetCurrentDocumentChannel( getter_AddRefs(channel))) || !channel) { return false; } nsCOMPtr 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 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 GetThumbnailAndDocShell(mozIDOMWindowProxy* aWindow, jni::ByteBuffer::Param aData, int32_t aThumbWidth, int32_t aThumbHeight, const CSSRect& aPageRect, float aZoomFactor) { nsCOMPtr win = nsPIDOMWindowOuter::From(aWindow); nsCOMPtr docShell = win->GetDocShell(); RefPtr presContext; if (!docShell || NS_FAILED(docShell->GetPresContext( getter_AddRefs(presContext))) || !presContext) { return nullptr; } uint8_t* const data = static_cast(aData->Address()); if (!data) { return nullptr; } const bool is24bit = !AndroidBridge::Bridge() || AndroidBridge::Bridge()->GetScreenDepth() == 24; const uint32_t stride = aThumbWidth * (is24bit ? 4 : 2); RefPtr dt = gfxPlatform::GetPlatform()->CreateDrawTargetForData( data, IntSize(aThumbWidth, aThumbHeight), stride, is24bit ? SurfaceFormat::B8G8R8A8 : SurfaceFormat::R5G6B5_UINT16); if (!dt || !dt->IsValid()) { return nullptr; } nsCOMPtr presShell = presContext->PresShell(); RefPtr context = gfxContext::CreateOrNull(dt); MOZ_ASSERT(context); // checked the draw target above context->SetMatrix(context->CurrentMatrix().Scale( aZoomFactor * float(aThumbWidth) / aPageRect.width, aZoomFactor * float(aThumbHeight) / aPageRect.height)); const nsRect drawRect( nsPresContext::CSSPixelsToAppUnits(aPageRect.x), nsPresContext::CSSPixelsToAppUnits(aPageRect.y), nsPresContext::CSSPixelsToAppUnits(aPageRect.width), nsPresContext::CSSPixelsToAppUnits(aPageRect.height)); 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: static void Init() { java::ThumbnailHelper::Natives::Init(); java::ZoomedView::Natives::Init(); } template static void OnNativeCall(Functor&& aCall) { class IdleEvent : public nsAppShell::LambdaEvent { using Base = nsAppShell::LambdaEvent; public: IdleEvent(Functor&& aCall) : Base(Forward(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(Forward(aCall))); } static void RequestThumbnail(jni::ByteBuffer::Param aData, jni::Object::Param aTab, int32_t aTabId, int32_t aWidth, int32_t aHeight) { nsCOMPtr window = GetWindowForTab(aTabId); if (!window || !aData) { java::ThumbnailHelper::NotifyThumbnail( aData, aTab, /* success */ false, /* store */ false); return; } // take a screenshot, as wide as possible, proportional to the destination size nsCOMPtr utils = do_GetInterface(window); nsCOMPtr 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) { java::ThumbnailHelper::NotifyThumbnail( aData, aTab, /* success */ false, /* store */ false); return; } const float aspectRatio = float(aWidth) / float(aHeight); if (pageWidth / aspectRatio < pageHeight) { pageHeight = pageWidth / aspectRatio; } else { pageWidth = pageHeight * aspectRatio; } nsCOMPtr docShell = GetThumbnailAndDocShell( window, aData, aWidth, aHeight, CSSRect(pageLeft, pageTop, pageWidth, pageHeight), /* aZoomFactor */ 1.0f); const bool success = !!docShell; const bool store = success ? ShouldStoreThumbnail(docShell) : false; java::ThumbnailHelper::NotifyThumbnail(aData, aTab, success, store); } static void RequestZoomedViewData(jni::ByteBuffer::Param aData, int32_t aTabId, int32_t aX, int32_t aY, int32_t aWidth, int32_t aHeight, float aScale) { nsCOMPtr window = GetWindowForTab(aTabId); if (!window || !aData) { return; } nsCOMPtr win = nsPIDOMWindowOuter::From(window); nsCOMPtr docShell = win->GetDocShell(); RefPtr presContext; if (!docShell || NS_FAILED(docShell->GetPresContext( getter_AddRefs(presContext))) || !presContext) { return; } nsCOMPtr presShell = presContext->PresShell(); LayoutDeviceRect rect = LayoutDeviceRect(aX, aY, aWidth, aHeight); const float resolution = presShell->GetCumulativeResolution(); rect.Scale(1.0f / LayoutDeviceToLayerScale(resolution).scale); docShell = GetThumbnailAndDocShell( window, aData, aWidth, aHeight, CSSRect::FromAppUnits( rect.ToAppUnits(rect, presContext->AppUnitsPerDevPixel())), aScale); if (docShell) { java::LayerView::UpdateZoomedView(aData); } } }; } // namespace mozilla #endif // ThumbnailHelper_h