Home | History | Annotate | Download | only in content
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "athena/content/content_proxy.h"
      6 
      7 #include "athena/activity/public/activity.h"
      8 #include "athena/activity/public/activity_view_model.h"
      9 #include "base/bind.h"
     10 #include "base/threading/worker_pool.h"
     11 #include "content/public/browser/render_view_host.h"
     12 #include "content/public/browser/render_widget_host_view.h"
     13 #include "content/public/browser/web_contents.h"
     14 #include "ui/aura/window.h"
     15 #include "ui/gfx/codec/png_codec.h"
     16 #include "ui/gfx/geometry/rect.h"
     17 #include "ui/gfx/image/image.h"
     18 #include "ui/gfx/image/image_png_rep.h"
     19 #include "ui/views/controls/webview/webview.h"
     20 #include "ui/views/widget/widget.h"
     21 
     22 namespace athena {
     23 
     24 // Encodes an A8 SkBitmap to grayscale PNG in a worker thread.
     25 class ProxyImageData : public base::RefCountedThreadSafe<ProxyImageData> {
     26  public:
     27   ProxyImageData() {
     28   }
     29 
     30   void EncodeImage(const SkBitmap& bitmap, base::Closure callback) {
     31     if (!base::WorkerPool::PostTaskAndReply(FROM_HERE,
     32             base::Bind(&ProxyImageData::EncodeOnWorker,
     33                        this,
     34                        bitmap),
     35             callback,
     36             true)) {
     37       // When coming here, the resulting image will be empty.
     38       DCHECK(false) << "Cannot start bitmap encode task.";
     39       callback.Run();
     40     }
     41   }
     42 
     43   scoped_refptr<base::RefCountedBytes> data() const { return data_; }
     44 
     45  private:
     46   friend class base::RefCountedThreadSafe<ProxyImageData>;
     47   virtual ~ProxyImageData() {
     48   }
     49 
     50   void EncodeOnWorker(const SkBitmap& bitmap) {
     51     DCHECK_EQ(bitmap.colorType(), kAlpha_8_SkColorType);
     52     // Encode the A8 bitmap to grayscale PNG treating alpha as color intensity.
     53     std::vector<unsigned char> data;
     54     if (gfx::PNGCodec::EncodeA8SkBitmap(bitmap, &data))
     55       data_ = new base::RefCountedBytes(data);
     56   }
     57 
     58   scoped_refptr<base::RefCountedBytes> data_;
     59 
     60   DISALLOW_COPY_AND_ASSIGN(ProxyImageData);
     61 };
     62 
     63 ContentProxy::ContentProxy(views::WebView* web_view, Activity* activity)
     64     : web_view_(web_view),
     65       content_visible_(true),
     66       content_loaded_(true),
     67       content_creation_called_(false),
     68       proxy_content_to_image_factory_(this) {
     69   // Note: The content will be hidden once the image got created.
     70   CreateProxyContent();
     71 }
     72 
     73 ContentProxy::~ContentProxy() {
     74   // If we still have a connection to the original Activity, we make it visible
     75   // again.
     76   ShowOriginalContent();
     77 }
     78 
     79 void ContentProxy::ContentWillUnload() {
     80   content_loaded_ = false;
     81 }
     82 
     83 gfx::ImageSkia ContentProxy::GetContentImage() {
     84   // While we compress to PNG, we use the original read back.
     85   if (!raw_image_.isNull() || !png_data_.get())
     86     return raw_image_;
     87 
     88   // Otherwise we convert the PNG.
     89   std::vector<gfx::ImagePNGRep> image_reps;
     90   image_reps.push_back(gfx::ImagePNGRep(png_data_, 0.0f));
     91   return *(gfx::Image(image_reps).ToImageSkia());
     92 }
     93 
     94 void ContentProxy::EvictContent() {
     95   raw_image_ = gfx::ImageSkia();
     96   png_data_->Release();
     97 }
     98 
     99 void ContentProxy::OnPreContentDestroyed() {
    100   // Since we are breaking now the connection to the old content, we make the
    101   // content visible again before we continue.
    102   // Note: Since the owning window is invisible, it does not matter that we
    103   // make the web content visible if the window gets destroyed shortly after.
    104   ShowOriginalContent();
    105 
    106   web_view_ = NULL;
    107 }
    108 
    109 void ContentProxy::ShowOriginalContent() {
    110   if (web_view_ && !content_visible_) {
    111     // Show the original |web_view_| again.
    112     web_view_->SetFastResize(false);
    113     // If the content is loaded, we ask it to relayout itself since the
    114     // dimensions might have changed. If not, we will reload new content and no
    115     // layout is required for the old content.
    116     if (content_loaded_)
    117       web_view_->Layout();
    118     web_view_->GetWebContents()->GetNativeView()->Show();
    119     web_view_->SetVisible(true);
    120     content_visible_ = true;
    121   }
    122 }
    123 
    124 void ContentProxy::HideOriginalContent() {
    125   if (web_view_ && content_visible_) {
    126     // Hide the |web_view_|.
    127     // TODO(skuhne): We might consider removing the view from the window while
    128     // it's hidden - it should work the same way as show/hide and does not have
    129     // any window re-ordering effect. Furthermore we want possibly to suppress
    130     // any resizing of content (not only fast resize) here to avoid jank on
    131     // rotation.
    132     web_view_->GetWebContents()->GetNativeView()->Hide();
    133     web_view_->SetVisible(false);
    134     // Don't allow the content to get resized with window size changes.
    135     web_view_->SetFastResize(true);
    136     content_visible_ = false;
    137   }
    138 }
    139 
    140 void ContentProxy::CreateProxyContent() {
    141   DCHECK(!content_creation_called_);
    142   content_creation_called_ = true;
    143   // Unit tests might not have a |web_view_|.
    144   if (!web_view_)
    145     return;
    146 
    147   content::RenderViewHost* host =
    148       web_view_->GetWebContents()->GetRenderViewHost();
    149   DCHECK(host && host->GetView());
    150   gfx::Size source = host->GetView()->GetViewBounds().size();
    151   gfx::Size target = gfx::Size(source.width() / 2, source.height() / 2);
    152   host->CopyFromBackingStore(
    153       gfx::Rect(),
    154       target,
    155       base::Bind(&ContentProxy::OnContentImageRead,
    156                  proxy_content_to_image_factory_.GetWeakPtr()),
    157       kAlpha_8_SkColorType);
    158 }
    159 
    160 void ContentProxy::OnContentImageRead(bool success, const SkBitmap& bitmap) {
    161   // Now we can hide the content. Note that after hiding we are freeing memory
    162   // and if something goes wrong we will end up with an empty page.
    163   HideOriginalContent();
    164 
    165   if (!success || bitmap.empty() || bitmap.isNull())
    166     return;
    167 
    168   // While we are encoding the image, we keep the current image as reference
    169   // to have something for the overview mode to grab. Once we have the encoded
    170   // PNG, we will get rid of this.
    171   raw_image_ = gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
    172 
    173   scoped_refptr<ProxyImageData> png_image = new ProxyImageData();
    174   png_image->EncodeImage(
    175       bitmap,
    176       base::Bind(&ContentProxy::OnContentImageEncodeComplete,
    177                  proxy_content_to_image_factory_.GetWeakPtr(),
    178                  png_image));
    179 }
    180 
    181 void ContentProxy::OnContentImageEncodeComplete(
    182     scoped_refptr<ProxyImageData> image) {
    183   png_data_ = image->data();
    184 
    185   // From now on we decode the image as needed to save memory.
    186   raw_image_ = gfx::ImageSkia();
    187 }
    188 
    189 }  // namespace athena
    190