summaryrefslogtreecommitdiffstats
path: root/chrome/browser/thumbnails/render_widget_snapshot_taker.cc
blob: da2606c13b369e5dc90c2a96bbd78eaa9848b732 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
// Copyright (c) 2012 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 "chrome/browser/thumbnails/render_widget_snapshot_taker.h"

#include "base/bind.h"
#include "base/memory/scoped_ptr.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "ui/base/layout.h"
#include "ui/gfx/size.h"
#include "ui/gfx/size_conversions.h"
#include "ui/surface/transport_dib.h"

using content::RenderWidgetHost;

struct RenderWidgetSnapshotTaker::AsyncRequestInfo {
  SnapshotReadyCallback callback;
  scoped_ptr<TransportDIB> thumbnail_dib;
  RenderWidgetHost* renderer;  // Not owned.
};

RenderWidgetSnapshotTaker::RenderWidgetSnapshotTaker() {
  // The BrowserProcessImpl creates this non-lazily. If you add nontrivial
  // stuff here, be sure to convert it to being lazily created.
  //
  // We don't register for notifications here since BrowserProcessImpl creates
  // us before the NotificationService is.
}

RenderWidgetSnapshotTaker::~RenderWidgetSnapshotTaker() {
}

void RenderWidgetSnapshotTaker::AskForSnapshot(
    RenderWidgetHost* renderer,
    const SnapshotReadyCallback& callback,
    gfx::Size page_size,
    gfx::Size desired_size) {
  // We are going to render the thumbnail asynchronously now, so keep
  // this callback for later lookup when the rendering is done.
  static int sequence_num = 0;
  sequence_num++;
  float scale_factor = ui::GetScaleFactorScale(ui::GetScaleFactorForNativeView(
      renderer->GetView()->GetNativeView()));
  gfx::Size desired_size_in_pixel = gfx::ToFlooredSize(
      gfx::ScaleSize(desired_size, scale_factor));
  scoped_ptr<TransportDIB> thumbnail_dib(TransportDIB::Create(
      desired_size_in_pixel.GetArea() * 4, sequence_num));

#if defined(OS_LINUX)
  // TODO: IPC a handle to the renderer like Windows.
  // http://code.google.com/p/chromium/issues/detail?id=89777
  NOTIMPLEMENTED();
  return;
#else

#if defined(OS_WIN)
  // Duplicate the handle to the DIB here because the renderer process does not
  // have permission. The duplicated handle is owned by the renderer process,
  // which is responsible for closing it.
  TransportDIB::Handle renderer_dib_handle;
  DuplicateHandle(GetCurrentProcess(), thumbnail_dib->handle(),
                  renderer->GetProcess()->GetHandle(), &renderer_dib_handle,
                  STANDARD_RIGHTS_REQUIRED | FILE_MAP_READ | FILE_MAP_WRITE,
                  FALSE, 0);
  if (!renderer_dib_handle) {
    LOG(WARNING) << "Could not duplicate dib handle for renderer";
    return;
  }
#else
  TransportDIB::Handle renderer_dib_handle = thumbnail_dib->handle();
#endif

  linked_ptr<AsyncRequestInfo> request_info(new AsyncRequestInfo);
  request_info->callback = callback;
  request_info->thumbnail_dib.reset(thumbnail_dib.release());
  request_info->renderer = renderer;
  SnapshotCallbackMap::value_type new_value(sequence_num, request_info);
  std::pair<SnapshotCallbackMap::iterator, bool> result =
      callback_map_.insert(new_value);
  if (!result.second) {
    NOTREACHED() << "Callback already registered?";
    return;
  }

  MonitorRenderer(renderer, true);
  renderer->PaintAtSize(
      renderer_dib_handle, sequence_num, page_size, desired_size);

#endif  // defined(USE_X11)
}

void RenderWidgetSnapshotTaker::CancelSnapshot(
    content::RenderWidgetHost* renderer) {
  SnapshotCallbackMap::iterator iterator = callback_map_.begin();
  while (iterator != callback_map_.end()) {
    if (iterator->second->renderer == renderer) {
      SnapshotCallbackMap::iterator nuked = iterator;
      ++iterator;
      callback_map_.erase(nuked);
      MonitorRenderer(renderer, false);
      continue;
    }
    ++iterator;
  }
}

void RenderWidgetSnapshotTaker::WidgetDidReceivePaintAtSizeAck(
    RenderWidgetHost* widget,
    int sequence_num,
    const gfx::Size& size) {
  // Lookup the callback, run it, and erase it.
  SnapshotCallbackMap::iterator item = callback_map_.find(sequence_num);
  if (item != callback_map_.end()) {
    DCHECK_EQ(widget, item->second->renderer);
    TransportDIB* dib = item->second->thumbnail_dib.get();
    DCHECK(dib);
    if (!dib || !dib->Map()) {
      return;
    }

    // Create an SkBitmap from the DIB.
    SkBitmap non_owned_bitmap;
    SkBitmap result;

    // Fill out the non_owned_bitmap with the right config.  Note that
    // this code assumes that the transport dib is a 32-bit ARGB
    // image.
    non_owned_bitmap.setConfig(SkBitmap::kARGB_8888_Config,
                               size.width(), size.height());
    if (dib->size() < non_owned_bitmap.getSafeSize())
      return;
    non_owned_bitmap.setPixels(dib->memory());

    // Now alloc/copy the memory so we own it and can pass it around,
    // and the memory won't go away when the DIB goes away.
    // TODO: Figure out a way to avoid this copy?
    non_owned_bitmap.copyTo(&result, SkBitmap::kARGB_8888_Config);

    item->second->callback.Run(result);

    // We're done with the callback, and with the DIB, so delete both.
    callback_map_.erase(item);
    MonitorRenderer(widget, false);
  }
}

void RenderWidgetSnapshotTaker::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  switch (type) {
    case content::NOTIFICATION_RENDER_WIDGET_HOST_DID_RECEIVE_PAINT_AT_SIZE_ACK: {
      std::pair<int, gfx::Size>* size_ack_details =
          content::Details<std::pair<int, gfx::Size> >(details).ptr();
      WidgetDidReceivePaintAtSizeAck(
          content::Source<RenderWidgetHost>(source).ptr(),
          size_ack_details->first,
          size_ack_details->second);
      break;
    }

    default:
      NOTREACHED() << "Unexpected notification type: " << type;
  }
}

void RenderWidgetSnapshotTaker::MonitorRenderer(RenderWidgetHost* renderer,
                                                bool monitor) {
  content::Source<RenderWidgetHost> renderer_source =
      content::Source<RenderWidgetHost>(renderer);
  if (monitor) {
    int new_count = ++host_monitor_counts_[renderer];
    if (new_count == 1) {
      registrar_.Add(
          this,
          content::NOTIFICATION_RENDER_WIDGET_HOST_DID_RECEIVE_PAINT_AT_SIZE_ACK,
          renderer_source);
    }
  } else {
    int new_count = --host_monitor_counts_[renderer];
    if (new_count == 0) {
      host_monitor_counts_.erase(renderer);
      registrar_.Remove(
          this,
          content::NOTIFICATION_RENDER_WIDGET_HOST_DID_RECEIVE_PAINT_AT_SIZE_ACK,
          renderer_source);
    }
  }
}