// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "athena/content/content_proxy.h" #include "athena/activity/public/activity.h" #include "athena/activity/public/activity_view_model.h" #include "base/bind.h" #include "base/threading/worker_pool.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/render_widget_host_view.h" #include "content/public/browser/web_contents.h" #include "ui/aura/window.h" #include "ui/gfx/codec/png_codec.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/image/image.h" #include "ui/gfx/image/image_png_rep.h" #include "ui/views/controls/webview/webview.h" #include "ui/views/widget/widget.h" namespace athena { // Encodes an A8 SkBitmap to grayscale PNG in a worker thread. class ProxyImageData : public base::RefCountedThreadSafe<ProxyImageData> { public: ProxyImageData() { } void EncodeImage(const SkBitmap& bitmap, base::Closure callback) { if (!base::WorkerPool::PostTaskAndReply(FROM_HERE, base::Bind(&ProxyImageData::EncodeOnWorker, this, bitmap), callback, true)) { // When coming here, the resulting image will be empty. DCHECK(false) << "Cannot start bitmap encode task."; callback.Run(); } } scoped_refptr<base::RefCountedBytes> data() const { return data_; } private: friend class base::RefCountedThreadSafe<ProxyImageData>; virtual ~ProxyImageData() { } void EncodeOnWorker(const SkBitmap& bitmap) { DCHECK_EQ(bitmap.colorType(), kAlpha_8_SkColorType); // Encode the A8 bitmap to grayscale PNG treating alpha as color intensity. std::vector<unsigned char> data; if (gfx::PNGCodec::EncodeA8SkBitmap(bitmap, &data)) data_ = new base::RefCountedBytes(data); } scoped_refptr<base::RefCountedBytes> data_; DISALLOW_COPY_AND_ASSIGN(ProxyImageData); }; ContentProxy::ContentProxy(views::WebView* web_view, Activity* activity) : web_view_(web_view), content_visible_(true), content_loaded_(true), content_creation_called_(false), proxy_content_to_image_factory_(this) { // Note: The content will be hidden once the image got created. CreateProxyContent(); } ContentProxy::~ContentProxy() { // If we still have a connection to the original Activity, we make it visible // again. ShowOriginalContent(); } void ContentProxy::ContentWillUnload() { content_loaded_ = false; } gfx::ImageSkia ContentProxy::GetContentImage() { // While we compress to PNG, we use the original read back. if (!raw_image_.isNull() || !png_data_.get()) return raw_image_; // Otherwise we convert the PNG. std::vector<gfx::ImagePNGRep> image_reps; image_reps.push_back(gfx::ImagePNGRep(png_data_, 0.0f)); return *(gfx::Image(image_reps).ToImageSkia()); } void ContentProxy::EvictContent() { raw_image_ = gfx::ImageSkia(); png_data_->Release(); } void ContentProxy::OnPreContentDestroyed() { // Since we are breaking now the connection to the old content, we make the // content visible again before we continue. // Note: Since the owning window is invisible, it does not matter that we // make the web content visible if the window gets destroyed shortly after. ShowOriginalContent(); web_view_ = NULL; } void ContentProxy::ShowOriginalContent() { if (web_view_ && !content_visible_) { // Show the original |web_view_| again. web_view_->SetFastResize(false); // If the content is loaded, we ask it to relayout itself since the // dimensions might have changed. If not, we will reload new content and no // layout is required for the old content. if (content_loaded_) web_view_->Layout(); web_view_->GetWebContents()->GetNativeView()->Show(); web_view_->SetVisible(true); content_visible_ = true; } } void ContentProxy::HideOriginalContent() { if (web_view_ && content_visible_) { // Hide the |web_view_|. // TODO(skuhne): We might consider removing the view from the window while // it's hidden - it should work the same way as show/hide and does not have // any window re-ordering effect. Furthermore we want possibly to suppress // any resizing of content (not only fast resize) here to avoid jank on // rotation. web_view_->GetWebContents()->GetNativeView()->Hide(); web_view_->SetVisible(false); // Don't allow the content to get resized with window size changes. web_view_->SetFastResize(true); content_visible_ = false; } } void ContentProxy::CreateProxyContent() { DCHECK(!content_creation_called_); content_creation_called_ = true; // Unit tests might not have a |web_view_|. if (!web_view_) return; content::RenderViewHost* host = web_view_->GetWebContents()->GetRenderViewHost(); DCHECK(host && host->GetView()); gfx::Size source = host->GetView()->GetViewBounds().size(); gfx::Size target = gfx::Size(source.width() / 2, source.height() / 2); host->CopyFromBackingStore( gfx::Rect(), target, base::Bind(&ContentProxy::OnContentImageRead, proxy_content_to_image_factory_.GetWeakPtr()), kAlpha_8_SkColorType); } void ContentProxy::OnContentImageRead(bool success, const SkBitmap& bitmap) { // Now we can hide the content. Note that after hiding we are freeing memory // and if something goes wrong we will end up with an empty page. HideOriginalContent(); if (!success || bitmap.empty() || bitmap.isNull()) return; // While we are encoding the image, we keep the current image as reference // to have something for the overview mode to grab. Once we have the encoded // PNG, we will get rid of this. raw_image_ = gfx::ImageSkia::CreateFrom1xBitmap(bitmap); scoped_refptr<ProxyImageData> png_image = new ProxyImageData(); png_image->EncodeImage( bitmap, base::Bind(&ContentProxy::OnContentImageEncodeComplete, proxy_content_to_image_factory_.GetWeakPtr(), png_image)); } void ContentProxy::OnContentImageEncodeComplete( scoped_refptr<ProxyImageData> image) { png_data_ = image->data(); // From now on we decode the image as needed to save memory. raw_image_ = gfx::ImageSkia(); } } // namespace athena