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

Side by Side Diff: components/exo/surface.cc

Issue 2041193006: exo: Only create new SurfaceLayer if layer properties change. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 6 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
« no previous file with comments | « components/exo/surface.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "components/exo/surface.h" 5 #include "components/exo/surface.h"
6 6
7 #include <utility> 7 #include <utility>
8 8
9 #include "base/callback_helpers.h" 9 #include "base/callback_helpers.h"
10 #include "base/logging.h" 10 #include "base/logging.h"
(...skipping 171 matching lines...) Expand 10 before | Expand all | Expand 10 after
182 182
183 void SurfaceFactoryOwner::SetBeginFrameSource( 183 void SurfaceFactoryOwner::SetBeginFrameSource(
184 cc::BeginFrameSource* begin_frame_source) {} 184 cc::BeginFrameSource* begin_frame_source) {}
185 185
186 //////////////////////////////////////////////////////////////////////////////// 186 ////////////////////////////////////////////////////////////////////////////////
187 // Surface, public: 187 // Surface, public:
188 188
189 Surface::Surface() 189 Surface::Surface()
190 : window_(new aura::Window(new CustomWindowDelegate(this))), 190 : window_(new aura::Window(new CustomWindowDelegate(this))),
191 has_pending_contents_(false), 191 has_pending_contents_(false),
192 pending_input_region_(SkIRect::MakeLargest()),
193 pending_buffer_scale_(1.0f),
194 pending_only_visible_on_secure_output_(false),
195 only_visible_on_secure_output_(false),
196 pending_blend_mode_(SkXfermode::kSrcOver_Mode),
197 pending_alpha_(1.0f),
198 alpha_(1.0f),
199 input_region_(SkIRect::MakeLargest()),
200 needs_commit_surface_hierarchy_(false), 192 needs_commit_surface_hierarchy_(false),
201 update_contents_after_successful_compositing_(false), 193 update_contents_after_successful_compositing_(false),
202 compositor_(nullptr), 194 compositor_(nullptr),
203 delegate_(nullptr) { 195 delegate_(nullptr) {
204 window_->SetType(ui::wm::WINDOW_TYPE_CONTROL); 196 window_->SetType(ui::wm::WINDOW_TYPE_CONTROL);
205 window_->SetName("ExoSurface"); 197 window_->SetName("ExoSurface");
206 window_->SetProperty(kSurfaceKey, this); 198 window_->SetProperty(kSurfaceKey, this);
207 window_->Init(ui::LAYER_SOLID_COLOR); 199 window_->Init(ui::LAYER_SOLID_COLOR);
208 window_->set_layer_owner_delegate(this); 200 window_->set_layer_owner_delegate(this);
209 window_->SetEventTargeter(base::WrapUnique(new CustomWindowTargeter)); 201 window_->SetEventTargeter(base::WrapUnique(new CustomWindowTargeter));
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
276 void Surface::RequestFrameCallback(const FrameCallback& callback) { 268 void Surface::RequestFrameCallback(const FrameCallback& callback) {
277 TRACE_EVENT0("exo", "Surface::RequestFrameCallback"); 269 TRACE_EVENT0("exo", "Surface::RequestFrameCallback");
278 270
279 pending_frame_callbacks_.push_back(callback); 271 pending_frame_callbacks_.push_back(callback);
280 } 272 }
281 273
282 void Surface::SetOpaqueRegion(const SkRegion& region) { 274 void Surface::SetOpaqueRegion(const SkRegion& region) {
283 TRACE_EVENT1("exo", "Surface::SetOpaqueRegion", "region", 275 TRACE_EVENT1("exo", "Surface::SetOpaqueRegion", "region",
284 gfx::SkIRectToRect(region.getBounds()).ToString()); 276 gfx::SkIRectToRect(region.getBounds()).ToString());
285 277
286 pending_opaque_region_ = region; 278 pending_state_.opaque_region = region;
287 } 279 }
288 280
289 void Surface::SetInputRegion(const SkRegion& region) { 281 void Surface::SetInputRegion(const SkRegion& region) {
290 TRACE_EVENT1("exo", "Surface::SetInputRegion", "region", 282 TRACE_EVENT1("exo", "Surface::SetInputRegion", "region",
291 gfx::SkIRectToRect(region.getBounds()).ToString()); 283 gfx::SkIRectToRect(region.getBounds()).ToString());
292 284
293 pending_input_region_ = region; 285 pending_state_.input_region = region;
294 } 286 }
295 287
296 void Surface::SetBufferScale(float scale) { 288 void Surface::SetBufferScale(float scale) {
297 TRACE_EVENT1("exo", "Surface::SetBufferScale", "scale", scale); 289 TRACE_EVENT1("exo", "Surface::SetBufferScale", "scale", scale);
298 290
299 pending_buffer_scale_ = scale; 291 pending_state_.buffer_scale = scale;
300 } 292 }
301 293
302 void Surface::AddSubSurface(Surface* sub_surface) { 294 void Surface::AddSubSurface(Surface* sub_surface) {
303 TRACE_EVENT1("exo", "Surface::AddSubSurface", "sub_surface", 295 TRACE_EVENT1("exo", "Surface::AddSubSurface", "sub_surface",
304 sub_surface->AsTracedValue()); 296 sub_surface->AsTracedValue());
305 297
306 DCHECK(!sub_surface->window()->parent()); 298 DCHECK(!sub_surface->window()->parent());
307 DCHECK(!sub_surface->window()->IsVisible()); 299 DCHECK(!sub_surface->window()->IsVisible());
308 window_->AddChild(sub_surface->window()); 300 window_->AddChild(sub_surface->window());
309 301
310 DCHECK(!ListContainsEntry(pending_sub_surfaces_, sub_surface)); 302 DCHECK(!ListContainsEntry(pending_sub_surfaces_, sub_surface));
311 pending_sub_surfaces_.push_back(std::make_pair(sub_surface, gfx::Point())); 303 pending_sub_surfaces_.push_back(std::make_pair(sub_surface, gfx::Point()));
304 has_pending_layer_changes_ = true;
312 } 305 }
313 306
314 void Surface::RemoveSubSurface(Surface* sub_surface) { 307 void Surface::RemoveSubSurface(Surface* sub_surface) {
315 TRACE_EVENT1("exo", "Surface::AddSubSurface", "sub_surface", 308 TRACE_EVENT1("exo", "Surface::AddSubSurface", "sub_surface",
316 sub_surface->AsTracedValue()); 309 sub_surface->AsTracedValue());
317 310
318 window_->RemoveChild(sub_surface->window()); 311 window_->RemoveChild(sub_surface->window());
319 if (sub_surface->window()->IsVisible()) 312 if (sub_surface->window()->IsVisible())
320 sub_surface->window()->Hide(); 313 sub_surface->window()->Hide();
321 314
322 DCHECK(ListContainsEntry(pending_sub_surfaces_, sub_surface)); 315 DCHECK(ListContainsEntry(pending_sub_surfaces_, sub_surface));
323 pending_sub_surfaces_.erase( 316 pending_sub_surfaces_.erase(
324 FindListEntry(pending_sub_surfaces_, sub_surface)); 317 FindListEntry(pending_sub_surfaces_, sub_surface));
318 has_pending_layer_changes_ = true;
325 } 319 }
326 320
327 void Surface::SetSubSurfacePosition(Surface* sub_surface, 321 void Surface::SetSubSurfacePosition(Surface* sub_surface,
328 const gfx::Point& position) { 322 const gfx::Point& position) {
329 TRACE_EVENT2("exo", "Surface::SetSubSurfacePosition", "sub_surface", 323 TRACE_EVENT2("exo", "Surface::SetSubSurfacePosition", "sub_surface",
330 sub_surface->AsTracedValue(), "position", position.ToString()); 324 sub_surface->AsTracedValue(), "position", position.ToString());
331 325
332 auto it = FindListEntry(pending_sub_surfaces_, sub_surface); 326 auto it = FindListEntry(pending_sub_surfaces_, sub_surface);
333 DCHECK(it != pending_sub_surfaces_.end()); 327 DCHECK(it != pending_sub_surfaces_.end());
328 if (it->second == position)
329 return;
334 it->second = position; 330 it->second = position;
331 has_pending_layer_changes_ = true;
335 } 332 }
336 333
337 void Surface::PlaceSubSurfaceAbove(Surface* sub_surface, Surface* reference) { 334 void Surface::PlaceSubSurfaceAbove(Surface* sub_surface, Surface* reference) {
338 TRACE_EVENT2("exo", "Surface::PlaceSubSurfaceAbove", "sub_surface", 335 TRACE_EVENT2("exo", "Surface::PlaceSubSurfaceAbove", "sub_surface",
339 sub_surface->AsTracedValue(), "reference", 336 sub_surface->AsTracedValue(), "reference",
340 reference->AsTracedValue()); 337 reference->AsTracedValue());
341 338
342 if (sub_surface == reference) { 339 if (sub_surface == reference) {
343 DLOG(WARNING) << "Client tried to place sub-surface above itself"; 340 DLOG(WARNING) << "Client tried to place sub-surface above itself";
344 return; 341 return;
345 } 342 }
346 343
347 auto position_it = pending_sub_surfaces_.begin(); 344 auto position_it = pending_sub_surfaces_.begin();
348 if (reference != this) { 345 if (reference != this) {
349 position_it = FindListEntry(pending_sub_surfaces_, reference); 346 position_it = FindListEntry(pending_sub_surfaces_, reference);
350 if (position_it == pending_sub_surfaces_.end()) { 347 if (position_it == pending_sub_surfaces_.end()) {
351 DLOG(WARNING) << "Client tried to place sub-surface above a reference " 348 DLOG(WARNING) << "Client tried to place sub-surface above a reference "
352 "surface that is neither a parent nor a sibling"; 349 "surface that is neither a parent nor a sibling";
353 return; 350 return;
354 } 351 }
355 352
356 // Advance iterator to have |position_it| point to the sibling surface 353 // Advance iterator to have |position_it| point to the sibling surface
357 // above |reference|. 354 // above |reference|.
358 ++position_it; 355 ++position_it;
359 } 356 }
360 357
361 DCHECK(ListContainsEntry(pending_sub_surfaces_, sub_surface)); 358 DCHECK(ListContainsEntry(pending_sub_surfaces_, sub_surface));
362 pending_sub_surfaces_.splice( 359 auto it = FindListEntry(pending_sub_surfaces_, sub_surface);
363 position_it, pending_sub_surfaces_, 360 if (it == position_it)
364 FindListEntry(pending_sub_surfaces_, sub_surface)); 361 return;
362 pending_sub_surfaces_.splice(position_it, pending_sub_surfaces_, it);
363 has_pending_layer_changes_ = true;
365 } 364 }
366 365
367 void Surface::PlaceSubSurfaceBelow(Surface* sub_surface, Surface* sibling) { 366 void Surface::PlaceSubSurfaceBelow(Surface* sub_surface, Surface* sibling) {
368 TRACE_EVENT2("exo", "Surface::PlaceSubSurfaceBelow", "sub_surface", 367 TRACE_EVENT2("exo", "Surface::PlaceSubSurfaceBelow", "sub_surface",
369 sub_surface->AsTracedValue(), "sibling", 368 sub_surface->AsTracedValue(), "sibling",
370 sibling->AsTracedValue()); 369 sibling->AsTracedValue());
371 370
372 if (sub_surface == sibling) { 371 if (sub_surface == sibling) {
373 DLOG(WARNING) << "Client tried to place sub-surface below itself"; 372 DLOG(WARNING) << "Client tried to place sub-surface below itself";
374 return; 373 return;
375 } 374 }
376 375
377 auto sibling_it = FindListEntry(pending_sub_surfaces_, sibling); 376 auto sibling_it = FindListEntry(pending_sub_surfaces_, sibling);
378 if (sibling_it == pending_sub_surfaces_.end()) { 377 if (sibling_it == pending_sub_surfaces_.end()) {
379 DLOG(WARNING) << "Client tried to place sub-surface below a surface that " 378 DLOG(WARNING) << "Client tried to place sub-surface below a surface that "
380 "is not a sibling"; 379 "is not a sibling";
381 return; 380 return;
382 } 381 }
383 382
384 DCHECK(ListContainsEntry(pending_sub_surfaces_, sub_surface)); 383 DCHECK(ListContainsEntry(pending_sub_surfaces_, sub_surface));
385 pending_sub_surfaces_.splice( 384 auto it = FindListEntry(pending_sub_surfaces_, sub_surface);
386 sibling_it, pending_sub_surfaces_, 385 if (it == sibling_it)
387 FindListEntry(pending_sub_surfaces_, sub_surface)); 386 return;
387 pending_sub_surfaces_.splice(sibling_it, pending_sub_surfaces_, it);
388 has_pending_layer_changes_ = true;
388 } 389 }
389 390
390 void Surface::SetViewport(const gfx::Size& viewport) { 391 void Surface::SetViewport(const gfx::Size& viewport) {
391 TRACE_EVENT1("exo", "Surface::SetViewport", "viewport", viewport.ToString()); 392 TRACE_EVENT1("exo", "Surface::SetViewport", "viewport", viewport.ToString());
392 393
393 pending_viewport_ = viewport; 394 pending_state_.viewport = viewport;
394 } 395 }
395 396
396 void Surface::SetCrop(const gfx::RectF& crop) { 397 void Surface::SetCrop(const gfx::RectF& crop) {
397 TRACE_EVENT1("exo", "Surface::SetCrop", "crop", crop.ToString()); 398 TRACE_EVENT1("exo", "Surface::SetCrop", "crop", crop.ToString());
398 399
399 pending_crop_ = crop; 400 pending_state_.crop = crop;
400 } 401 }
401 402
402 void Surface::SetOnlyVisibleOnSecureOutput(bool only_visible_on_secure_output) { 403 void Surface::SetOnlyVisibleOnSecureOutput(bool only_visible_on_secure_output) {
403 TRACE_EVENT1("exo", "Surface::SetOnlyVisibleOnSecureOutput", 404 TRACE_EVENT1("exo", "Surface::SetOnlyVisibleOnSecureOutput",
404 "only_visible_on_secure_output", only_visible_on_secure_output); 405 "only_visible_on_secure_output", only_visible_on_secure_output);
405 406
406 pending_only_visible_on_secure_output_ = only_visible_on_secure_output; 407 pending_state_.only_visible_on_secure_output = only_visible_on_secure_output;
407 } 408 }
408 409
409 void Surface::SetBlendMode(SkXfermode::Mode blend_mode) { 410 void Surface::SetBlendMode(SkXfermode::Mode blend_mode) {
410 TRACE_EVENT1("exo", "Surface::SetBlendMode", "blend_mode", blend_mode); 411 TRACE_EVENT1("exo", "Surface::SetBlendMode", "blend_mode", blend_mode);
411 412
412 pending_blend_mode_ = blend_mode; 413 pending_state_.blend_mode = blend_mode;
413 } 414 }
414 415
415 void Surface::SetAlpha(float alpha) { 416 void Surface::SetAlpha(float alpha) {
416 TRACE_EVENT1("exo", "Surface::SetAlpha", "alpha", alpha); 417 TRACE_EVENT1("exo", "Surface::SetAlpha", "alpha", alpha);
417 418
418 pending_alpha_ = alpha; 419 pending_state_.alpha = alpha;
419 } 420 }
420 421
421 void Surface::Commit() { 422 void Surface::Commit() {
422 TRACE_EVENT0("exo", "Surface::Commit"); 423 TRACE_EVENT0("exo", "Surface::Commit");
423 424
424 needs_commit_surface_hierarchy_ = true; 425 needs_commit_surface_hierarchy_ = true;
425 426
426 if (delegate_) 427 if (state_ != pending_state_)
428 has_pending_layer_changes_ = true;
429
430 if (has_pending_contents_) {
431 if (pending_buffer_ &&
432 (current_resource_.size != pending_buffer_->GetSize())) {
433 has_pending_layer_changes_ = true;
434 } else if (!pending_buffer_ && !current_resource_.size.IsEmpty()) {
435 has_pending_layer_changes_ = true;
436 }
437 }
438
439 if (delegate_) {
427 delegate_->OnSurfaceCommit(); 440 delegate_->OnSurfaceCommit();
428 else 441 } else {
442 CheckIfSurfaceHierarchyNeedsCommitToNewSurfaces();
429 CommitSurfaceHierarchy(); 443 CommitSurfaceHierarchy();
444 }
430 } 445 }
431 446
432 void Surface::CommitSurfaceHierarchy() { 447 void Surface::CommitSurfaceHierarchy() {
433 DCHECK(needs_commit_surface_hierarchy_); 448 DCHECK(needs_commit_surface_hierarchy_);
434 needs_commit_surface_hierarchy_ = false; 449 needs_commit_surface_hierarchy_ = false;
450 has_pending_layer_changes_ = false;
435 451
436 // TODO(dcastagna): Make secure_output_only a layer property instead of a 452 state_ = pending_state_;
437 // texture mailbox flag so this can be changed without have to provide 453 pending_state_.only_visible_on_secure_output = false;
438 // new contents.
439 only_visible_on_secure_output_ = pending_only_visible_on_secure_output_;
440 pending_only_visible_on_secure_output_ = false;
441
442 // Update current alpha.
443 alpha_ = pending_alpha_;
444
445 // Update current crop rectangle.
446 crop_ = pending_crop_;
447 454
448 if (factory_owner_) { 455 if (factory_owner_) {
449 CommitSurfaceContents(); 456 CommitSurfaceContents();
450 } else { 457 } else {
451 CommitTextureContents(); 458 CommitTextureContents();
452 } 459 }
453 460
454 // Update current input region.
455 input_region_ = pending_input_region_;
456
457 // Synchronize window hierarchy. This will position and update the stacking 461 // Synchronize window hierarchy. This will position and update the stacking
458 // order of all sub-surfaces after committing all pending state of sub-surface 462 // order of all sub-surfaces after committing all pending state of sub-surface
459 // descendants. 463 // descendants.
460 aura::Window* stacking_target = nullptr; 464 aura::Window* stacking_target = nullptr;
461 for (auto& sub_surface_entry : pending_sub_surfaces_) { 465 for (auto& sub_surface_entry : pending_sub_surfaces_) {
462 Surface* sub_surface = sub_surface_entry.first; 466 Surface* sub_surface = sub_surface_entry.first;
463 467
464 // Synchronsouly commit all pending state of the sub-surface and its 468 // Synchronsouly commit all pending state of the sub-surface and its
465 // decendents. 469 // decendents.
466 if (sub_surface->needs_commit_surface_hierarchy()) 470 if (sub_surface->needs_commit_surface_hierarchy())
(...skipping 16 matching lines...) Expand all
483 sub_surface->window()->SetBounds(gfx::Rect( 487 sub_surface->window()->SetBounds(gfx::Rect(
484 sub_surface_entry.second, sub_surface->window()->layer()->size())); 488 sub_surface_entry.second, sub_surface->window()->layer()->size()));
485 } 489 }
486 } 490 }
487 491
488 bool Surface::IsSynchronized() const { 492 bool Surface::IsSynchronized() const {
489 return delegate_ ? delegate_->IsSurfaceSynchronized() : false; 493 return delegate_ ? delegate_->IsSurfaceSynchronized() : false;
490 } 494 }
491 495
492 gfx::Rect Surface::GetHitTestBounds() const { 496 gfx::Rect Surface::GetHitTestBounds() const {
493 SkIRect bounds = input_region_.getBounds(); 497 SkIRect bounds = state_.input_region.getBounds();
494 if (!bounds.intersect( 498 if (!bounds.intersect(
495 gfx::RectToSkIRect(gfx::Rect(window_->layer()->size())))) 499 gfx::RectToSkIRect(gfx::Rect(window_->layer()->size()))))
496 return gfx::Rect(); 500 return gfx::Rect();
497 return gfx::SkIRectToRect(bounds); 501 return gfx::SkIRectToRect(bounds);
498 } 502 }
499 503
500 bool Surface::HitTestRect(const gfx::Rect& rect) const { 504 bool Surface::HitTestRect(const gfx::Rect& rect) const {
501 if (HasHitTestMask()) 505 if (HasHitTestMask())
502 return input_region_.intersects(gfx::RectToSkIRect(rect)); 506 return state_.input_region.intersects(gfx::RectToSkIRect(rect));
503 507
504 return rect.Intersects(gfx::Rect(window_->layer()->size())); 508 return rect.Intersects(gfx::Rect(window_->layer()->size()));
505 } 509 }
506 510
507 bool Surface::HasHitTestMask() const { 511 bool Surface::HasHitTestMask() const {
508 return !input_region_.contains( 512 return !state_.input_region.contains(
509 gfx::RectToSkIRect(gfx::Rect(window_->layer()->size()))); 513 gfx::RectToSkIRect(gfx::Rect(window_->layer()->size())));
510 } 514 }
511 515
512 void Surface::GetHitTestMask(gfx::Path* mask) const { 516 void Surface::GetHitTestMask(gfx::Path* mask) const {
513 input_region_.getBoundaryPath(mask); 517 state_.input_region.getBoundaryPath(mask);
514 } 518 }
515 519
516 void Surface::RegisterCursorProvider(Pointer* provider) { 520 void Surface::RegisterCursorProvider(Pointer* provider) {
517 cursor_providers_.insert(provider); 521 cursor_providers_.insert(provider);
518 } 522 }
519 523
520 void Surface::UnregisterCursorProvider(Pointer* provider) { 524 void Surface::UnregisterCursorProvider(Pointer* provider) {
521 cursor_providers_.erase(provider); 525 cursor_providers_.erase(provider);
522 } 526 }
523 527
(...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after
633 compositor_ = nullptr; 637 compositor_ = nullptr;
634 } 638 }
635 639
636 void Surface::WillDraw(cc::SurfaceId id) { 640 void Surface::WillDraw(cc::SurfaceId id) {
637 while (!active_frame_callbacks_.empty()) { 641 while (!active_frame_callbacks_.empty()) {
638 active_frame_callbacks_.front().Run(base::TimeTicks::Now()); 642 active_frame_callbacks_.front().Run(base::TimeTicks::Now());
639 active_frame_callbacks_.pop_front(); 643 active_frame_callbacks_.pop_front();
640 } 644 }
641 } 645 }
642 646
647 void Surface::CheckIfSurfaceHierarchyNeedsCommitToNewSurfaces() {
648 if (HasLayerHierarchyChanged())
649 SetSurfaceHierarchyNeedsCommitToNewSurfaces();
650 }
651
652 Surface::State::State() : input_region(SkIRect::MakeLargest()) {}
653
654 Surface::State::~State() = default;
655
656 bool Surface::State::operator==(const State& other) {
657 return (other.crop == crop && alpha == other.alpha &&
658 other.blend_mode == blend_mode && other.viewport == viewport &&
659 other.opaque_region == opaque_region &&
660 other.buffer_scale == buffer_scale &&
661 other.input_region == input_region);
662 }
663
664 bool Surface::HasLayerHierarchyChanged() const {
665 if (needs_commit_surface_hierarchy_ && has_pending_layer_changes_)
666 return true;
667
668 for (const auto& sub_surface_entry : pending_sub_surfaces_) {
669 if (sub_surface_entry.first->HasLayerHierarchyChanged())
670 return true;
671 }
672 return false;
673 }
674
675 void Surface::SetSurfaceHierarchyNeedsCommitToNewSurfaces() {
676 needs_commit_to_new_surface_ = true;
677 for (auto& sub_surface_entry : pending_sub_surfaces_) {
678 sub_surface_entry.first->SetSurfaceHierarchyNeedsCommitToNewSurfaces();
679 }
680 }
681
643 void Surface::CommitTextureContents() { 682 void Surface::CommitTextureContents() {
644 // We update contents if Attach() has been called since last commit. 683 // We update contents if Attach() has been called since last commit.
645 if (has_pending_contents_) { 684 if (has_pending_contents_) {
646 has_pending_contents_ = false; 685 has_pending_contents_ = false;
647 686
648 current_buffer_ = pending_buffer_; 687 current_buffer_ = pending_buffer_;
649 pending_buffer_.reset(); 688 pending_buffer_.reset();
650 689
651 cc::TextureMailbox texture_mailbox; 690 cc::TextureMailbox texture_mailbox;
652 std::unique_ptr<cc::SingleReleaseCallback> texture_mailbox_release_callback; 691 std::unique_ptr<cc::SingleReleaseCallback> texture_mailbox_release_callback;
653 if (current_buffer_) { 692 if (current_buffer_) {
654 texture_mailbox_release_callback = current_buffer_->ProduceTextureMailbox( 693 texture_mailbox_release_callback = current_buffer_->ProduceTextureMailbox(
655 &texture_mailbox, only_visible_on_secure_output_, 694 &texture_mailbox, state_.only_visible_on_secure_output,
656 true /* client_usage */); 695 true /* client_usage */);
657 } 696 }
658 697
659 // Update layer with the new contents. 698 // Update layer with the new contents.
660 if (texture_mailbox_release_callback) { 699 if (texture_mailbox_release_callback) {
661 texture_size_in_dip_ = gfx::ScaleToFlooredSize( 700 texture_size_in_dip_ = gfx::ScaleToFlooredSize(
662 texture_mailbox.size_in_pixels(), 1.0f / pending_buffer_scale_); 701 texture_mailbox.size_in_pixels(), 1.0f / state_.buffer_scale);
663 window_->layer()->SetTextureMailbox( 702 window_->layer()->SetTextureMailbox(
664 texture_mailbox, std::move(texture_mailbox_release_callback), 703 texture_mailbox, std::move(texture_mailbox_release_callback),
665 texture_size_in_dip_); 704 texture_size_in_dip_);
666 window_->layer()->SetTextureFlipped(false); 705 window_->layer()->SetTextureFlipped(false);
667 } else { 706 } else {
668 // Show solid color content if no buffer is attached or we failed 707 // Show solid color content if no buffer is attached or we failed
669 // to produce a texture mailbox for the currently attached buffer. 708 // to produce a texture mailbox for the currently attached buffer.
670 window_->layer()->SetShowSolidColorContent(); 709 window_->layer()->SetShowSolidColorContent();
671 window_->layer()->SetColor(SK_ColorBLACK); 710 window_->layer()->SetColor(SK_ColorBLACK);
672 } 711 }
673 712
674 // Schedule redraw of the damage region. 713 // Schedule redraw of the damage region.
675 for (SkRegion::Iterator it(pending_damage_); !it.done(); it.next()) 714 for (SkRegion::Iterator it(pending_damage_); !it.done(); it.next())
676 window_->layer()->SchedulePaint(gfx::SkIRectToRect(it.rect())); 715 window_->layer()->SchedulePaint(gfx::SkIRectToRect(it.rect()));
677 716
678 // Reset damage. 717 // Reset damage.
679 pending_damage_.setEmpty(); 718 pending_damage_.setEmpty();
680 } 719 }
681 720
682 if (window_->layer()->has_external_content()) { 721 if (window_->layer()->has_external_content()) {
683 // Determine the new surface size. 722 // Determine the new surface size.
684 // - Texture size in DIP defines the size if nothing else is set. 723 // - Texture size in DIP defines the size if nothing else is set.
685 // - If a viewport is set then that defines the size, otherwise 724 // - If a viewport is set then that defines the size, otherwise
686 // the crop rectangle defines the size if set. 725 // the crop rectangle defines the size if set.
687 gfx::Size contents_size = texture_size_in_dip_; 726 gfx::Size contents_size = texture_size_in_dip_;
688 if (!pending_viewport_.IsEmpty()) { 727 if (!state_.viewport.IsEmpty()) {
689 contents_size = pending_viewport_; 728 contents_size = state_.viewport;
690 } else if (!crop_.IsEmpty()) { 729 } else if (!state_.crop.IsEmpty()) {
691 DLOG_IF(WARNING, !gfx::IsExpressibleAsInt(crop_.width()) || 730 DLOG_IF(WARNING, !gfx::IsExpressibleAsInt(state_.crop.width()) ||
692 !gfx::IsExpressibleAsInt(crop_.height())) 731 !gfx::IsExpressibleAsInt(state_.crop.height()))
693 << "Crop rectangle size (" << crop_.size().ToString() 732 << "Crop rectangle size (" << state_.crop.size().ToString()
694 << ") most be expressible using integers when viewport is not set"; 733 << ") most be expressible using integers when viewport is not set";
695 contents_size = gfx::ToCeiledSize(crop_.size()); 734 contents_size = gfx::ToCeiledSize(state_.crop.size());
696 } 735 }
697 window_->layer()->SetTextureCrop(crop_); 736 window_->layer()->SetTextureCrop(state_.crop);
698 window_->layer()->SetTextureScale( 737 window_->layer()->SetTextureScale(
699 static_cast<float>(texture_size_in_dip_.width()) / 738 static_cast<float>(texture_size_in_dip_.width()) /
700 contents_size.width(), 739 contents_size.width(),
701 static_cast<float>(texture_size_in_dip_.height()) / 740 static_cast<float>(texture_size_in_dip_.height()) /
702 contents_size.height()); 741 contents_size.height());
703 window_->layer()->SetTextureAlpha(alpha_); 742 window_->layer()->SetTextureAlpha(state_.alpha);
704 window_->layer()->SetBounds( 743 window_->layer()->SetBounds(
705 gfx::Rect(window_->layer()->bounds().origin(), contents_size)); 744 gfx::Rect(window_->layer()->bounds().origin(), contents_size));
706 } 745 }
707 746
708 // Move pending frame callbacks to the end of |frame_callbacks_|. 747 // Move pending frame callbacks to the end of |frame_callbacks_|.
709 frame_callbacks_.splice(frame_callbacks_.end(), pending_frame_callbacks_); 748 frame_callbacks_.splice(frame_callbacks_.end(), pending_frame_callbacks_);
710 749
711 // Update alpha compositing properties. 750 // Update alpha compositing properties.
712 // TODO(reveman): Use a more reliable way to force blending off than setting 751 // TODO(reveman): Use a more reliable way to force blending off than setting
713 // fills-bounds-opaquely. 752 // fills-bounds-opaquely.
714 window_->layer()->SetFillsBoundsOpaquely( 753 window_->layer()->SetFillsBoundsOpaquely(
715 pending_blend_mode_ == SkXfermode::kSrc_Mode || 754 state_.blend_mode == SkXfermode::kSrc_Mode ||
716 pending_opaque_region_.contains( 755 state_.opaque_region.contains(
717 gfx::RectToSkIRect(gfx::Rect(window_->layer()->size())))); 756 gfx::RectToSkIRect(gfx::Rect(window_->layer()->size()))));
718 } 757 }
719 758
720 void Surface::CommitSurfaceContents() { 759 void Surface::CommitSurfaceContents() {
721 // We update contents if Attach() has been called since last commit. 760 // We update contents if Attach() has been called since last commit.
722 if (has_pending_contents_) { 761 if (has_pending_contents_) {
723 has_pending_contents_ = false; 762 has_pending_contents_ = false;
724 current_buffer_ = pending_buffer_; 763 current_buffer_ = pending_buffer_;
725 pending_buffer_.reset(); 764 pending_buffer_.reset();
726 765
727 if (current_buffer_) { 766 if (current_buffer_) {
728 std::unique_ptr<cc::SingleReleaseCallback> 767 std::unique_ptr<cc::SingleReleaseCallback>
729 texture_mailbox_release_callback; 768 texture_mailbox_release_callback;
730 769
731 cc::TextureMailbox texture_mailbox; 770 cc::TextureMailbox texture_mailbox;
732 texture_mailbox_release_callback = current_buffer_->ProduceTextureMailbox( 771 texture_mailbox_release_callback = current_buffer_->ProduceTextureMailbox(
733 &texture_mailbox, only_visible_on_secure_output_, 772 &texture_mailbox, state_.only_visible_on_secure_output,
734 true /* client_usage */); 773 true /* client_usage */);
735 cc::TransferableResource resource; 774 cc::TransferableResource resource;
736 resource.id = next_resource_id_++; 775 resource.id = next_resource_id_++;
737 resource.format = cc::RGBA_8888; 776 resource.format = cc::RGBA_8888;
738 resource.filter = 777 resource.filter =
739 texture_mailbox.nearest_neighbor() ? GL_NEAREST : GL_LINEAR; 778 texture_mailbox.nearest_neighbor() ? GL_NEAREST : GL_LINEAR;
740 resource.size = texture_mailbox.size_in_pixels(); 779 resource.size = texture_mailbox.size_in_pixels();
741 resource.mailbox_holder = gpu::MailboxHolder(texture_mailbox.mailbox(), 780 resource.mailbox_holder = gpu::MailboxHolder(texture_mailbox.mailbox(),
742 texture_mailbox.sync_token(), 781 texture_mailbox.sync_token(),
743 texture_mailbox.target()); 782 texture_mailbox.target());
744 resource.is_overlay_candidate = texture_mailbox.is_overlay_candidate(); 783 resource.is_overlay_candidate = texture_mailbox.is_overlay_candidate();
745 784
746 factory_owner_->release_callbacks_[resource.id] = std::make_pair( 785 factory_owner_->release_callbacks_[resource.id] = std::make_pair(
747 factory_owner_, std::move(texture_mailbox_release_callback)); 786 factory_owner_, std::move(texture_mailbox_release_callback));
748 current_resource_ = resource; 787 current_resource_ = resource;
749 } else { 788 } else {
750 current_resource_.id = 0; 789 current_resource_.id = 0;
751 current_resource_.size = gfx::Size(); 790 current_resource_.size = gfx::Size();
752 } 791 }
753 } 792 }
754 793
755 cc::SurfaceId old_surface_id = surface_id_; 794 cc::SurfaceId old_surface_id = surface_id_;
756 surface_id_ = factory_owner_->id_allocator_->GenerateId(); 795 if (needs_commit_to_new_surface_ || surface_id_.is_null()) {
757 factory_owner_->surface_factory_->Create(surface_id_); 796 needs_commit_to_new_surface_ = false;
797 surface_id_ = factory_owner_->id_allocator_->GenerateId();
798 factory_owner_->surface_factory_->Create(surface_id_);
799 }
758 800
759 gfx::Size buffer_size = current_resource_.size; 801 gfx::Size buffer_size = current_resource_.size;
760 gfx::SizeF scaled_buffer_size( 802 gfx::SizeF scaled_buffer_size(
761 gfx::ScaleSize(gfx::SizeF(buffer_size), 1.0f / pending_buffer_scale_)); 803 gfx::ScaleSize(gfx::SizeF(buffer_size), 1.0f / state_.buffer_scale));
762 804
763 gfx::Size layer_size; // Size of the output layer, in DIP. 805 gfx::Size layer_size; // Size of the output layer, in DIP.
764 if (!pending_viewport_.IsEmpty()) { 806 if (!state_.viewport.IsEmpty()) {
765 layer_size = pending_viewport_; 807 layer_size = state_.viewport;
766 } else if (!crop_.IsEmpty()) { 808 } else if (!state_.crop.IsEmpty()) {
767 DLOG_IF(WARNING, !gfx::IsExpressibleAsInt(crop_.width()) || 809 DLOG_IF(WARNING, !gfx::IsExpressibleAsInt(state_.crop.width()) ||
768 !gfx::IsExpressibleAsInt(crop_.height())) 810 !gfx::IsExpressibleAsInt(state_.crop.height()))
769 << "Crop rectangle size (" << crop_.size().ToString() 811 << "Crop rectangle size (" << state_.crop.size().ToString()
770 << ") most be expressible using integers when viewport is not set"; 812 << ") most be expressible using integers when viewport is not set";
771 layer_size = gfx::ToCeiledSize(crop_.size()); 813 layer_size = gfx::ToCeiledSize(state_.crop.size());
772 } else { 814 } else {
773 layer_size = gfx::ToCeiledSize(scaled_buffer_size); 815 layer_size = gfx::ToCeiledSize(scaled_buffer_size);
774 } 816 }
775 817
776 // TODO(jbauman): Figure out how this interacts with the pixel size of 818 // TODO(jbauman): Figure out how this interacts with the pixel size of
777 // CopyOutputRequests on the layer. 819 // CopyOutputRequests on the layer.
778 float contents_surface_to_layer_scale = 1.0; 820 float contents_surface_to_layer_scale = 1.0;
779 gfx::Size contents_surface_size = layer_size; 821 gfx::Size contents_surface_size = layer_size;
780 822
781 gfx::PointF uv_top_left(0.f, 0.f); 823 gfx::PointF uv_top_left(0.f, 0.f);
782 gfx::PointF uv_bottom_right(1.f, 1.f); 824 gfx::PointF uv_bottom_right(1.f, 1.f);
783 if (!crop_.IsEmpty()) { 825 if (!state_.crop.IsEmpty()) {
784 uv_top_left = crop_.origin(); 826 uv_top_left = state_.crop.origin();
785 827
786 uv_top_left.Scale(1.f / scaled_buffer_size.width(), 828 uv_top_left.Scale(1.f / scaled_buffer_size.width(),
787 1.f / scaled_buffer_size.height()); 829 1.f / scaled_buffer_size.height());
788 uv_bottom_right = crop_.bottom_right(); 830 uv_bottom_right = state_.crop.bottom_right();
789 uv_bottom_right.Scale(1.f / scaled_buffer_size.width(), 831 uv_bottom_right.Scale(1.f / scaled_buffer_size.width(),
790 1.f / scaled_buffer_size.height()); 832 1.f / scaled_buffer_size.height());
791 } 833 }
792 834
793 // pending_damage_ is in Surface coordinates. 835 // pending_damage_ is in Surface coordinates.
794 gfx::Rect damage_rect = gfx::SkIRectToRect(pending_damage_.getBounds()); 836 gfx::Rect damage_rect = gfx::SkIRectToRect(pending_damage_.getBounds());
795 837
796 std::unique_ptr<cc::RenderPass> render_pass = cc::RenderPass::Create(); 838 std::unique_ptr<cc::RenderPass> render_pass = cc::RenderPass::Create();
797 render_pass->SetAll(cc::RenderPassId(1, 1), gfx::Rect(contents_surface_size), 839 render_pass->SetAll(cc::RenderPassId(1, 1), gfx::Rect(contents_surface_size),
798 damage_rect, gfx::Transform(), true); 840 damage_rect, gfx::Transform(), true);
799 841
800 gfx::Rect quad_rect = gfx::Rect(contents_surface_size); 842 gfx::Rect quad_rect = gfx::Rect(contents_surface_size);
801 cc::SharedQuadState* quad_state = 843 cc::SharedQuadState* quad_state =
802 render_pass->CreateAndAppendSharedQuadState(); 844 render_pass->CreateAndAppendSharedQuadState();
803 quad_state->quad_layer_bounds = contents_surface_size; 845 quad_state->quad_layer_bounds = contents_surface_size;
804 quad_state->visible_quad_layer_rect = quad_rect; 846 quad_state->visible_quad_layer_rect = quad_rect;
805 quad_state->opacity = alpha_; 847 quad_state->opacity = state_.alpha;
806 848
807 bool frame_is_opaque = false; 849 bool frame_is_opaque = false;
808 850
809 std::unique_ptr<cc::DelegatedFrameData> delegated_frame( 851 std::unique_ptr<cc::DelegatedFrameData> delegated_frame(
810 new cc::DelegatedFrameData); 852 new cc::DelegatedFrameData);
811 if (current_resource_.id) { 853 if (current_resource_.id) {
812 cc::TextureDrawQuad* texture_quad = 854 cc::TextureDrawQuad* texture_quad =
813 render_pass->CreateAndAppendDrawQuad<cc::TextureDrawQuad>(); 855 render_pass->CreateAndAppendDrawQuad<cc::TextureDrawQuad>();
814 float vertex_opacity[4] = {1.0, 1.0, 1.0, 1.0}; 856 float vertex_opacity[4] = {1.0, 1.0, 1.0, 1.0};
815 gfx::Rect opaque_rect; 857 gfx::Rect opaque_rect;
816 frame_is_opaque = 858 frame_is_opaque =
817 pending_blend_mode_ == SkXfermode::kSrc_Mode || 859 state_.blend_mode == SkXfermode::kSrc_Mode ||
818 pending_opaque_region_.contains(gfx::RectToSkIRect(quad_rect)); 860 state_.opaque_region.contains(gfx::RectToSkIRect(quad_rect));
819 if (frame_is_opaque) { 861 if (frame_is_opaque) {
820 opaque_rect = quad_rect; 862 opaque_rect = quad_rect;
821 } else if (pending_opaque_region_.isRect()) { 863 } else if (state_.opaque_region.isRect()) {
822 opaque_rect = gfx::SkIRectToRect(pending_opaque_region_.getBounds()); 864 opaque_rect = gfx::SkIRectToRect(state_.opaque_region.getBounds());
823 } 865 }
824 866
825 texture_quad->SetNew(quad_state, quad_rect, opaque_rect, quad_rect, 867 texture_quad->SetNew(quad_state, quad_rect, opaque_rect, quad_rect,
826 current_resource_.id, true, uv_top_left, 868 current_resource_.id, true, uv_top_left,
827 uv_bottom_right, SK_ColorTRANSPARENT, vertex_opacity, 869 uv_bottom_right, SK_ColorTRANSPARENT, vertex_opacity,
828 false, false, only_visible_on_secure_output_); 870 false, false, state_.only_visible_on_secure_output);
829 delegated_frame->resource_list.push_back(current_resource_); 871 delegated_frame->resource_list.push_back(current_resource_);
830 } else { 872 } else {
831 cc::SolidColorDrawQuad* solid_quad = 873 cc::SolidColorDrawQuad* solid_quad =
832 render_pass->CreateAndAppendDrawQuad<cc::SolidColorDrawQuad>(); 874 render_pass->CreateAndAppendDrawQuad<cc::SolidColorDrawQuad>();
833 solid_quad->SetNew(quad_state, quad_rect, quad_rect, SK_ColorBLACK, false); 875 solid_quad->SetNew(quad_state, quad_rect, quad_rect, SK_ColorBLACK, false);
834 frame_is_opaque = true; 876 frame_is_opaque = true;
835 } 877 }
836 878
837 delegated_frame->render_pass_list.push_back(std::move(render_pass)); 879 delegated_frame->render_pass_list.push_back(std::move(render_pass));
838 std::unique_ptr<cc::CompositorFrame> frame(new cc::CompositorFrame); 880 std::unique_ptr<cc::CompositorFrame> frame(new cc::CompositorFrame);
839 frame->delegated_frame_data = std::move(delegated_frame); 881 frame->delegated_frame_data = std::move(delegated_frame);
840 882
841 factory_owner_->surface_factory_->SubmitCompositorFrame( 883 factory_owner_->surface_factory_->SubmitCompositorFrame(
842 surface_id_, std::move(frame), cc::SurfaceFactory::DrawCallback()); 884 surface_id_, std::move(frame), cc::SurfaceFactory::DrawCallback());
843 885
844 if (!old_surface_id.is_null()) { 886 if (!old_surface_id.is_null() && old_surface_id != surface_id_) {
845 factory_owner_->surface_factory_->SetPreviousFrameSurface(surface_id_, 887 factory_owner_->surface_factory_->SetPreviousFrameSurface(surface_id_,
846 old_surface_id); 888 old_surface_id);
847 factory_owner_->surface_factory_->Destroy(old_surface_id); 889 factory_owner_->surface_factory_->Destroy(old_surface_id);
848 } 890 }
849 891
850 window_->layer()->SetShowSurface( 892 if (old_surface_id != surface_id_) {
851 surface_id_, 893 window_->layer()->SetShowSurface(
852 base::Bind(&SatisfyCallback, base::Unretained(surface_manager_)), 894 surface_id_,
853 base::Bind(&RequireCallback, base::Unretained(surface_manager_)), 895 base::Bind(&SatisfyCallback, base::Unretained(surface_manager_)),
854 contents_surface_size, contents_surface_to_layer_scale, layer_size); 896 base::Bind(&RequireCallback, base::Unretained(surface_manager_)),
855 window_->layer()->SetBounds( 897 contents_surface_size, contents_surface_to_layer_scale, layer_size);
856 gfx::Rect(window_->layer()->bounds().origin(), layer_size)); 898 window_->layer()->SetBounds(
857 window_->layer()->SetFillsBoundsOpaquely(alpha_ == 1.0f && frame_is_opaque); 899 gfx::Rect(window_->layer()->bounds().origin(), layer_size));
900 window_->layer()->SetFillsBoundsOpaquely(state_.alpha == 1.0f &&
901 frame_is_opaque);
902 }
858 903
859 // Reset damage. 904 // Reset damage.
860 pending_damage_.setEmpty(); 905 pending_damage_.setEmpty();
861 906
862 DCHECK(!current_resource_.id || 907 DCHECK(!current_resource_.id ||
863 factory_owner_->release_callbacks_.count(current_resource_.id)); 908 factory_owner_->release_callbacks_.count(current_resource_.id));
864 909
865 // Move pending frame callbacks to the end of active_frame_callbacks_ 910 // Move pending frame callbacks to the end of active_frame_callbacks_
866 active_frame_callbacks_.splice(active_frame_callbacks_.end(), 911 active_frame_callbacks_.splice(active_frame_callbacks_.end(),
867 pending_frame_callbacks_); 912 pending_frame_callbacks_);
868 } 913 }
869 914
870 void Surface::SetTextureLayerContents(ui::Layer* layer) { 915 void Surface::SetTextureLayerContents(ui::Layer* layer) {
871 DCHECK(current_buffer_); 916 DCHECK(current_buffer_);
872 917
873 cc::TextureMailbox texture_mailbox; 918 cc::TextureMailbox texture_mailbox;
874 std::unique_ptr<cc::SingleReleaseCallback> texture_mailbox_release_callback = 919 std::unique_ptr<cc::SingleReleaseCallback> texture_mailbox_release_callback =
875 current_buffer_->ProduceTextureMailbox(&texture_mailbox, 920 current_buffer_->ProduceTextureMailbox(
876 only_visible_on_secure_output_, 921 &texture_mailbox, state_.only_visible_on_secure_output,
877 false /* client_usage */); 922 false /* client_usage */);
878 if (!texture_mailbox_release_callback) 923 if (!texture_mailbox_release_callback)
879 return; 924 return;
880 925
881 layer->SetTextureMailbox(texture_mailbox, 926 layer->SetTextureMailbox(texture_mailbox,
882 std::move(texture_mailbox_release_callback), 927 std::move(texture_mailbox_release_callback),
883 texture_size_in_dip_); 928 texture_size_in_dip_);
884 layer->SetTextureFlipped(false); 929 layer->SetTextureFlipped(false);
885 layer->SetTextureCrop(crop_); 930 layer->SetTextureCrop(state_.crop);
886 layer->SetTextureScale(static_cast<float>(texture_size_in_dip_.width()) / 931 layer->SetTextureScale(static_cast<float>(texture_size_in_dip_.width()) /
887 layer->bounds().width(), 932 layer->bounds().width(),
888 static_cast<float>(texture_size_in_dip_.height()) / 933 static_cast<float>(texture_size_in_dip_.height()) /
889 layer->bounds().height()); 934 layer->bounds().height());
890 layer->SetTextureAlpha(alpha_); 935 layer->SetTextureAlpha(state_.alpha);
891 } 936 }
892 937
893 void Surface::SetSurfaceLayerContents(ui::Layer* layer) { 938 void Surface::SetSurfaceLayerContents(ui::Layer* layer) {
894 if (surface_id_.is_null()) 939 if (surface_id_.is_null())
895 return; 940 return;
896 941
897 gfx::Size layer_size = layer->bounds().size(); 942 gfx::Size layer_size = layer->bounds().size();
898 float contents_surface_to_layer_scale = 1.0f; 943 float contents_surface_to_layer_scale = 1.0f;
899 944
900 layer->SetShowSurface( 945 layer->SetShowSurface(
901 surface_id_, 946 surface_id_,
902 base::Bind(&SatisfyCallback, base::Unretained(surface_manager_)), 947 base::Bind(&SatisfyCallback, base::Unretained(surface_manager_)),
903 base::Bind(&RequireCallback, base::Unretained(surface_manager_)), 948 base::Bind(&RequireCallback, base::Unretained(surface_manager_)),
904 layer_size, contents_surface_to_layer_scale, layer_size); 949 layer_size, contents_surface_to_layer_scale, layer_size);
905 } 950 }
906 951
907 bool Surface::use_surface_layer_ = false; 952 bool Surface::use_surface_layer_ = false;
908 953
909 } // namespace exo 954 } // namespace exo
OLDNEW
« no previous file with comments | « components/exo/surface.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698