Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(280)

Side by Side Diff: android_webview/browser/view_renderer_impl.cc

Issue 12041009: [Android WebView] Migrate the rendering code to a separate set of classes. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 7 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2013 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 "android_webview/browser/view_renderer_impl.h"
6
7 #include <android/bitmap.h>
8 #include <sys/system_properties.h>
9
10 #include "android_webview/common/renderer_picture_map.h"
11 #include "android_webview/public/browser/draw_gl.h"
12 #include "android_webview/public/browser/draw_sw.h"
13 #include "base/android/jni_android.h"
14 #include "base/debug/trace_event.h"
15 #include "base/logging.h"
16 #include "cc/layer.h"
17 #include "content/public/browser/android/content_view_core.h"
18 #include "content/public/browser/render_process_host.h"
19 #include "content/public/browser/web_contents.h"
20 #include "third_party/skia/include/core/SkBitmap.h"
21 #include "third_party/skia/include/core/SkCanvas.h"
22 #include "third_party/skia/include/core/SkDevice.h"
23 #include "ui/gfx/size.h"
24 #include "ui/gfx/transform.h"
25 #include "ui/gl/gl_bindings.h"
26
27 // TODO(leandrogracia): remove when crbug.com/164140 is closed.
28 // Borrowed from gl2ext.h. Cannot be included due to conflicts with
29 // gl_bindings.h and the EGL library methods (eglGetCurrentContext).
30 #ifndef GL_TEXTURE_EXTERNAL_OES
31 #define GL_TEXTURE_EXTERNAL_OES 0x8D65
32 #endif
33
34 #ifndef GL_TEXTURE_BINDING_EXTERNAL_OES
35 #define GL_TEXTURE_BINDING_EXTERNAL_OES 0x8D67
36 #endif
37
38 using base::android::AttachCurrentThread;
39 using base::android::ScopedJavaLocalRef;
40 using content::Compositor;
41 using content::ContentViewCore;
42
43 namespace {
44
45 typedef base::Callback<bool(SkCanvas*)> RenderMethod;
46
47 static bool RasterizeIntoBitmap(JNIEnv* env,
48 jobject jbitmap,
mkosiba (inactive) 2013/01/22 02:13:34 can we use a ScopedLocalJavaRef instead?
Leandro Graciá Gil 2013/01/22 08:18:46 Actually, I think we could use a const JavaRef<job
49 int scroll_x,
50 int scroll_y,
51 const RenderMethod& renderer) {
52 DCHECK(jbitmap);
53
54 AndroidBitmapInfo bitmap_info;
55 if (AndroidBitmap_getInfo(env, jbitmap, &bitmap_info) < 0) {
56 LOG(WARNING) << "Error getting java bitmap info.";
57 return false;
58 }
59
60 void* pixels = NULL;
61 if (AndroidBitmap_lockPixels(env, jbitmap, &pixels) < 0) {
62 LOG(WARNING) << "Error locking java bitmap pixels.";
63 return false;
64 }
65
66 bool succeeded = false;
67 {
68 SkBitmap bitmap;
69 bitmap.setConfig(SkBitmap::kARGB_8888_Config,
70 bitmap_info.width,
71 bitmap_info.height,
72 bitmap_info.stride);
73 bitmap.setPixels(pixels);
74
75 SkDevice device(bitmap);
76 SkCanvas canvas(&device);
77 canvas.translate(-scroll_x, -scroll_y);
78 succeeded = renderer.Run(&canvas);
79 }
80
81 if (AndroidBitmap_unlockPixels(env, jbitmap) < 0) {
82 LOG(WARNING) << "Error unlocking java bitmap pixels.";
mkosiba (inactive) 2013/01/22 02:13:34 LOG(ERROR)? same for the previous methods?
Leandro Graciá Gil 2013/01/22 08:18:46 Done.
83 return false;
84 }
85
86 return succeeded;
87 }
88
89 } // namespace
90
91 namespace android_webview {
92
93 // static
94 ViewRendererImpl* ViewRendererImpl::Create(ViewRenderer::Client* client,
95 JavaHelper* java_helper) {
96 return new ViewRendererImpl(client, java_helper);
97 }
98
99 ViewRendererImpl::ViewRendererImpl(ViewRenderer::Client* client,
100 JavaHelper* java_helper)
101 : ViewRenderer(client, java_helper),
102 ALLOW_THIS_IN_INITIALIZER_LIST(compositor_(Compositor::Create(this))),
103 view_clip_layer_(cc::Layer::create()),
104 transform_layer_(cc::Layer::create()),
105 scissor_clip_layer_(cc::Layer::create()),
106 view_visible_(false),
107 compositor_visible_(false),
108 is_composite_pending_(false),
109 on_new_picture_mode_(kOnNewPictureDisabled),
110 last_frame_context_(NULL),
111 web_contents_(NULL) {
112 DCHECK(java_helper);
113
114 // Define the view hierarchy.
115 transform_layer_->addChild(view_clip_layer_);
116 scissor_clip_layer_->addChild(transform_layer_);
117 compositor_->SetRootLayer(scissor_clip_layer_);
118
119 RendererPictureMap::CreateInstance();
120 }
121
122 ViewRendererImpl::~ViewRendererImpl() {
123 }
124
125 void ViewRendererImpl::SetContents(ContentViewCore* content_view_core) {
benm (inactive) 2013/01/21 20:34:49 Could this param be WebContents rather than CVC? T
Leandro Graciá Gil 2013/01/22 08:18:46 We could go the other way around: WebContents -> C
126 web_contents_ = content_view_core->GetWebContents();
127 if (!view_renderer_host_)
128 view_renderer_host_.reset(new ViewRendererHost(web_contents_, this));
129 else
130 view_renderer_host_->Observe(web_contents_);
mkosiba (inactive) 2013/01/22 02:13:34 as I think joth pointed out. The naming seems off.
Leandro Graciá Gil 2013/01/22 08:18:46 Naming fixed (I hope).
131
132 view_clip_layer_->removeAllChildren();
133 view_clip_layer_->addChild(content_view_core->GetLayer());
134 Invalidate();
135 }
136
137 void ViewRendererImpl::DrawGL(AwDrawGLInfo* draw_info) {
138 TRACE_EVENT0("ViewRendererImpl", "ViewRendererImpl::DrawGL");
139
140 if (!scissor_clip_layer_ || draw_info->mode == AwDrawGLInfo::kModeProcess)
141 return;
142
143 DCHECK_EQ(draw_info->mode, AwDrawGLInfo::kModeDraw);
144
145 SetCompositorVisibility(view_visible_);
146 if (!compositor_visible_)
147 return;
148
149 // TODO(leandrogracia): remove when crbug.com/164140 is closed.
benm (inactive) 2013/01/21 20:34:49 What is this section for?
Leandro Graciá Gil 2013/01/22 08:18:46 This is our temporary manual GL state restoration.
150 // ---------------------------------------------------------------------------
151 GLint texture_external_oes_binding;
152 glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texture_external_oes_binding);
153
154 GLint vertex_array_buffer_binding;
155 glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &vertex_array_buffer_binding);
156
157 GLint index_array_buffer_binding;
158 glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &index_array_buffer_binding);
159
160 GLint pack_alignment;
161 glGetIntegerv(GL_PACK_ALIGNMENT, &pack_alignment);
162
163 GLint unpack_alignment;
164 glGetIntegerv(GL_UNPACK_ALIGNMENT, &unpack_alignment);
165
166 struct {
167 GLint enabled;
168 GLint size;
169 GLint type;
170 GLint normalized;
171 GLint stride;
172 GLvoid* pointer;
173 } vertex_attrib[3];
174
175 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(vertex_attrib); ++i) {
176 glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_ENABLED,
177 &vertex_attrib[i].enabled);
178 glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_SIZE,
179 &vertex_attrib[i].size);
180 glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_TYPE,
181 &vertex_attrib[i].type);
182 glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED,
183 &vertex_attrib[i].normalized);
184 glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_STRIDE,
185 &vertex_attrib[i].stride);
186 glGetVertexAttribPointerv(i, GL_VERTEX_ATTRIB_ARRAY_POINTER,
187 &vertex_attrib[i].pointer);
188 }
189
190 GLboolean depth_test;
191 glGetBooleanv(GL_DEPTH_TEST, &depth_test);
192
193 GLboolean cull_face;
194 glGetBooleanv(GL_CULL_FACE, &cull_face);
195
196 GLboolean color_mask[4];
197 glGetBooleanv(GL_COLOR_WRITEMASK, color_mask);
198
199 GLboolean blend_enabled;
200 glGetBooleanv(GL_BLEND, &blend_enabled);
201
202 GLint blend_src_rgb;
203 glGetIntegerv(GL_BLEND_SRC_RGB, &blend_src_rgb);
204
205 GLint blend_src_alpha;
206 glGetIntegerv(GL_BLEND_SRC_ALPHA, &blend_src_alpha);
207
208 GLint blend_dest_rgb;
209 glGetIntegerv(GL_BLEND_DST_RGB, &blend_dest_rgb);
210
211 GLint blend_dest_alpha;
212 glGetIntegerv(GL_BLEND_DST_ALPHA, &blend_dest_alpha);
213
214 GLint active_texture;
215 glGetIntegerv(GL_ACTIVE_TEXTURE, &active_texture);
216
217 GLint viewport[4];
218 glGetIntegerv(GL_VIEWPORT, viewport);
219
220 GLboolean scissor_test;
221 glGetBooleanv(GL_SCISSOR_TEST, &scissor_test);
222
223 GLint scissor_box[4];
224 glGetIntegerv(GL_SCISSOR_BOX, scissor_box);
225
226 GLint current_program;
227 glGetIntegerv(GL_CURRENT_PROGRAM, &current_program);
228 // ---------------------------------------------------------------------------
229
230 // We need to watch if the current Android context has changed and enforce
231 // a clean-up in the compositor.
232 EGLContext current_context = eglGetCurrentContext();
233 if (!current_context) {
234 LOG(WARNING) << "No current context attached. Skipping composite.";
235 return;
236 }
237
238 if (last_frame_context_ != current_context) {
239 if (last_frame_context_)
240 ResetCompositor();
241 last_frame_context_ = current_context;
242 }
243
244 compositor_->SetWindowBounds(gfx::Size(draw_info->width, draw_info->height));
245
246 if (draw_info->is_layer) {
247 // When rendering into a separate layer no view clipping, transform,
248 // scissoring or background transparency need to be handled.
249 // The Android framework will composite us afterwards.
250 compositor_->SetHasTransparentBackground(false);
251 view_clip_layer_->setMasksToBounds(false);
252 transform_layer_->setTransform(gfx::Transform());
253 scissor_clip_layer_->setMasksToBounds(false);
254 scissor_clip_layer_->setPosition(gfx::PointF());
255 scissor_clip_layer_->setBounds(gfx::Size());
256 scissor_clip_layer_->setSublayerTransform(gfx::Transform());
257
258 } else {
259 compositor_->SetHasTransparentBackground(true);
260
261 gfx::Rect clip_rect(draw_info->clip_left, draw_info->clip_top,
262 draw_info->clip_right - draw_info->clip_left,
263 draw_info->clip_bottom - draw_info->clip_top);
264
265 scissor_clip_layer_->setPosition(clip_rect.origin());
266 scissor_clip_layer_->setBounds(clip_rect.size());
267 scissor_clip_layer_->setMasksToBounds(true);
268
269 // The compositor clipping architecture enforces us to have the clip layer
270 // as an ancestor of the area we want to clip, but this makes the transform
271 // become relative to the clip area rather than the full surface. The clip
272 // position offset needs to be undone before applying the transform.
273 gfx::Transform undo_clip_position;
274 undo_clip_position.Translate(-clip_rect.x(), -clip_rect.y());
275 scissor_clip_layer_->setSublayerTransform(undo_clip_position);
276
277 gfx::Transform transform;
278 transform.matrix().setColMajorf(draw_info->transform);
279
280 // The scrolling values of the Android Framework affect the transformation
281 // matrix. This needs to be undone to let the compositor handle scrolling.
282 transform.Translate(hw_rendering_scroll_.x(), hw_rendering_scroll_.y());
283 transform_layer_->setTransform(transform);
284
285 view_clip_layer_->setMasksToBounds(true);
286 }
287
288 compositor_->Composite();
289 is_composite_pending_ = false;
290
291 // TODO(leandrogracia): remove when crbug.com/164140 is closed.
292 // ---------------------------------------------------------------------------
293 char no_gl_restore_prop[PROP_VALUE_MAX];
294 __system_property_get("webview.chromium_no_gl_restore", no_gl_restore_prop);
295 if (!strcmp(no_gl_restore_prop, "true")) {
296 LOG(WARNING) << "Android GL functor not restoring the previous GL state.";
297 } else {
298 glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture_external_oes_binding);
299 glBindBuffer(GL_ARRAY_BUFFER, vertex_array_buffer_binding);
300 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_array_buffer_binding);
301
302 glPixelStorei(GL_PACK_ALIGNMENT, pack_alignment);
303 glPixelStorei(GL_UNPACK_ALIGNMENT, unpack_alignment);
304
305 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(vertex_attrib); ++i) {
306 glVertexAttribPointer(i, vertex_attrib[i].size,
307 vertex_attrib[i].type, vertex_attrib[i].normalized,
308 vertex_attrib[i].stride, vertex_attrib[i].pointer);
309
310 if (vertex_attrib[i].enabled)
311 glEnableVertexAttribArray(i);
312 else
313 glDisableVertexAttribArray(i);
314 }
315
316 if (depth_test)
317 glEnable(GL_DEPTH_TEST);
318 else
319 glDisable(GL_DEPTH_TEST);
320
321 if (cull_face)
322 glEnable(GL_CULL_FACE);
323 else
324 glDisable(GL_CULL_FACE);
325
326 glColorMask(color_mask[0], color_mask[1], color_mask[2],
327 color_mask[3]);
328
329 if (blend_enabled)
330 glEnable(GL_BLEND);
331 else
332 glDisable(GL_BLEND);
333
334 glBlendFuncSeparate(blend_src_rgb, blend_dest_rgb,
335 blend_src_alpha, blend_dest_alpha);
336
337 glActiveTexture(active_texture);
338
339 glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
340
341 if (scissor_test)
342 glEnable(GL_SCISSOR_TEST);
343 else
344 glDisable(GL_SCISSOR_TEST);
345
346 glScissor(scissor_box[0], scissor_box[1], scissor_box[2],
347 scissor_box[3]);
348
349 glUseProgram(current_program);
350 }
351 // ---------------------------------------------------------------------------
352 }
353
354 void ViewRendererImpl::SetScrollForHWFrame(int x, int y) {
355 hw_rendering_scroll_ = gfx::Point(x, y);
356 }
357
358 bool ViewRendererImpl::DrawSW(jobject java_canvas, const gfx::Rect& clip) {
359 TRACE_EVENT0("ViewRenderer", "ViewRendererImpl::DrawSW");
360
361 AwPixelInfo* pixels;
362 JNIEnv* env = AttachCurrentThread();
363
364 // Render into an auxiliary bitmap if pixel info is not available.
365 if (!SWDrawFunctions() ||
366 (pixels = SWDrawFunctions()->access_pixels(env, java_canvas)) == NULL) {
367 ScopedJavaLocalRef<jobject> jbitmap(java_helper()->CreateBitmap(
368 env, clip.width(), clip.height()));
369 if (!jbitmap.obj())
370 return false;
371
372 if (!RasterizeIntoBitmap(env, jbitmap.obj(), clip.x(), clip.y(),
373 base::Bind(&ViewRendererImpl::RenderSW, base::Unretained(this))))
374 return false;
375
376 java_helper()->DrawBitmapIntoCanvas(env, jbitmap.obj(), java_canvas);
377 return true;
378 }
379
380 // Draw in a SkCanvas built over the pixel information.
381 bool succeeded = false;
382 {
383 SkBitmap bitmap;
384 bitmap.setConfig(static_cast<SkBitmap::Config>(pixels->config),
385 pixels->width,
386 pixels->height,
387 pixels->row_bytes);
388 bitmap.setPixels(pixels->pixels);
389 SkDevice device(bitmap);
390 SkCanvas canvas(&device);
391 SkMatrix matrix;
392 for (int i = 0; i < 9; i++)
393 matrix.set(i, pixels->matrix[i]);
394 canvas.setMatrix(matrix);
395
396 SkRegion clip;
397 if (pixels->clip_region_size) {
398 size_t bytes_read = clip.readFromMemory(pixels->clip_region);
399 DCHECK_EQ(pixels->clip_region_size, bytes_read);
400 canvas.setClipRegion(clip);
401 } else {
402 clip.setRect(SkIRect::MakeWH(pixels->width, pixels->height));
403 }
404
405 succeeded = RenderSW(&canvas);
406 }
407
408 SWDrawFunctions()->release_pixels(pixels);
409 return succeeded;
410 }
411
412 ScopedJavaLocalRef<jobject> ViewRendererImpl::CapturePicture() {
413 skia::RefPtr<SkPicture> picture = GetLastCapturedPicture();
414 if (!picture || !SWDrawFunctions())
415 return ScopedJavaLocalRef<jobject>();
416
417 JNIEnv* env = AttachCurrentThread();
418 if (IsSkiaVersionCompatible()) {
419 return ScopedJavaLocalRef<jobject>(env,
420 SWDrawFunctions()->create_picture(env, picture->clone()));
421 }
422
423 // If Skia versions are not compatible, workaround it by rasterizing the
424 // picture into a bitmap and drawing it into a new Java picture.
425 ScopedJavaLocalRef<jobject> jbitmap(java_helper()->CreateBitmap(
426 env, picture->width(), picture->height()));
427 if (!jbitmap.obj())
428 return ScopedJavaLocalRef<jobject>();
429
430 if (!RasterizeIntoBitmap(env, jbitmap.obj(), 0, 0,
431 base::Bind(&ViewRendererImpl::RenderPicture, base::Unretained(this))))
432 return ScopedJavaLocalRef<jobject>();
433
434 return java_helper()->RecordRasterizedBitmap(env, jbitmap.obj());
joth 2013/01/21 22:54:24 there's a subtle snag here. As you're using the Re
Leandro Graciá Gil 2013/01/22 08:18:46 Good point. I'll be removing the soft reference.
435 }
436
437 void ViewRendererImpl::EnableOnNewPicture(OnNewPictureMode mode) {
438 on_new_picture_mode_ = mode;
439
440 // TODO(leandrogracia): uncomment when sw rendering uses Ubercompositor.
441 // Until then we need the callback enabled for SW mode invalidation.
442 // If onNewPicture is triggered only on invalidation do not capture
443 // pictures on every new frame.
444 //DCHECK(view_renderer_host_);
445 //view_renderer_host_->EnableCapturePictureCallback(
446 // on_new_picture_mode_ == kOnNewPictureEnabled);
447 }
448
449 void ViewRendererImpl::OnVisibilityChanged(bool view_visible,
450 bool window_visible) {
451 view_visible_ = window_visible && view_visible;
452 Invalidate();
453 }
454
455 void ViewRendererImpl::OnSizeChanged(int width, int height) {
456 view_clip_layer_->setBounds(gfx::Size(width, height));
457 }
458
459 void ViewRendererImpl::OnAttachedToWindow(int width, int height) {
460 view_clip_layer_->setBounds(gfx::Size(width, height));
461 }
462
463 void ViewRendererImpl::OnDetachedFromWindow() {
464 view_visible_ = false;
465 SetCompositorVisibility(false);
466 }
467
468 void ViewRendererImpl::ScheduleComposite() {
469 TRACE_EVENT0("ViewRendererImpl", "ViewRendererImpl::ScheduleComposite");
470
471 if (is_composite_pending_)
472 return;
473
474 is_composite_pending_ = true;
475 Invalidate();
476 }
477
478 void ViewRendererImpl::OnSwapBuffersCompleted() {
benm (inactive) 2013/01/21 20:34:49 is this a TODO or intentionally a no-op?
Leandro Graciá Gil 2013/01/22 08:18:46 Intentionally a no-op, but I just noticed that thi
479 }
480
481 skia::RefPtr<SkPicture> ViewRendererImpl::GetLastCapturedPicture() {
482 // Use the latest available picture if the listener callback is enabled.
483 skia::RefPtr<SkPicture> picture;
484 if (on_new_picture_mode_ == kOnNewPictureEnabled)
485 picture = RendererPictureMap::GetInstance()->GetRendererPicture(
486 web_contents_->GetRoutingID());
487
488 // If not available or not in listener mode get it synchronously.
489 if (!picture) {
490 DCHECK(view_renderer_host_);
491 view_renderer_host_->CapturePictureSync();
492 picture = RendererPictureMap::GetInstance()->GetRendererPicture(
493 web_contents_->GetRoutingID());
494 }
495
496 return picture;
497 }
498
499 void ViewRendererImpl::OnPictureUpdated(int process_id, int render_view_id) {
500 CHECK_EQ(web_contents_->GetRenderProcessHost()->GetID(), process_id);
501 if (render_view_id != web_contents_->GetRoutingID())
502 return;
503
504 // TODO(leandrogracia): this can be made unconditional once software rendering
505 // uses Ubercompositor. Until then this path is required for SW invalidations.
506 if (on_new_picture_mode_ == kOnNewPictureEnabled)
507 client()->OnNewPicture(CapturePicture());
508
509 // TODO(leandrogracia): delete when sw rendering uses Ubercompositor.
510 // Invalidation should be provided by the compositor only.
511 Invalidate();
512 }
513
514 void ViewRendererImpl::SetCompositorVisibility(bool visible) {
515 if (compositor_visible_ != visible) {
516 compositor_visible_ = visible;
517 compositor_->SetVisible(compositor_visible_);
518 }
519 }
520
521 void ViewRendererImpl::ResetCompositor() {
522 compositor_.reset(content::Compositor::Create(this));
523 compositor_->SetRootLayer(scissor_clip_layer_);
524 }
525
526 void ViewRendererImpl::Invalidate() {
benm (inactive) 2013/01/21 20:34:49 Should this function be a TODO: remove when using
Leandro Graciá Gil 2013/01/22 08:18:46 Nope. This is the method that triggers invalidate
527 if (view_visible_)
528 client()->Invalidate();
529
530 // When not in invalidation-only mode onNewPicture will be triggered
531 // from the OnPictureUpdated callback.
532 if (on_new_picture_mode_ == kOnNewPictureInvalidationOnly)
533 client()->OnNewPicture(ScopedJavaLocalRef<jobject>());
534 }
535
536 bool ViewRendererImpl::RenderSW(SkCanvas* canvas) {
537 // TODO(leandrogracia): once Ubercompositor is ready and we support software
538 // rendering mode, we should avoid this as much as we can, ideally always.
539 // This includes finding a proper replacement for onDraw calls in hardware
540 // mode with software canvases. http://crbug.com/170086.
541 return RenderPicture(canvas);
542
543 //compositor_->Composite();
benm (inactive) 2013/01/21 20:34:49 intended to be commented? When should it be uncomm
Leandro Graciá Gil 2013/01/22 08:18:46 Same as above, once we use the compositor for SW r
544 //is_composite_pending_ = false;
545 //return true;
546 }
547
548 bool ViewRendererImpl::RenderPicture(SkCanvas* canvas) {
549 skia::RefPtr<SkPicture> picture = GetLastCapturedPicture();
550 if (!picture)
551 return false;
552
553 picture->draw(canvas);
554 return true;
555 }
556
557 } // namespace android_webview
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698