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
22namespace athena {
23
24// Encodes an A8 SkBitmap to grayscale PNG in a worker thread.
25class 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
63ContentProxy::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
73ContentProxy::~ContentProxy() {
74  // If we still have a connection to the original Activity, we make it visible
75  // again.
76  ShowOriginalContent();
77}
78
79void ContentProxy::ContentWillUnload() {
80  content_loaded_ = false;
81}
82
83gfx::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
94void ContentProxy::EvictContent() {
95  raw_image_ = gfx::ImageSkia();
96  png_data_->Release();
97}
98
99void 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
109void 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
124void 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
140void 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
160void 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
181void 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