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

Side by Side Diff: third_party/WebKit/Source/platform/graphics/compositing/PaintArtifactCompositor.cpp

Issue 2161523002: Isolation of subtrees of stacking contexts and out-of-flow positioned objects (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@CachePaintProperties
Patch Set: - Created 4 years, 5 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
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 "platform/graphics/compositing/PaintArtifactCompositor.h" 5 #include "platform/graphics/compositing/PaintArtifactCompositor.h"
6 6
7 #include "cc/layers/content_layer_client.h" 7 #include "cc/layers/content_layer_client.h"
8 #include "cc/layers/layer.h" 8 #include "cc/layers/layer.h"
9 #include "cc/layers/picture_layer.h" 9 #include "cc/layers/picture_layer.h"
10 #include "cc/playback/display_item_list.h" 10 #include "cc/playback/display_item_list.h"
(...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after
115 return list; 115 return list;
116 } 116 }
117 117
118 static gfx::Transform transformToTransformSpace(const TransformPaintPropertyNode * currentSpace, const TransformPaintPropertyNode* targetSpace) 118 static gfx::Transform transformToTransformSpace(const TransformPaintPropertyNode * currentSpace, const TransformPaintPropertyNode* targetSpace)
119 { 119 {
120 TransformationMatrix matrix; 120 TransformationMatrix matrix;
121 while (currentSpace != targetSpace) { 121 while (currentSpace != targetSpace) {
122 TransformationMatrix localMatrix = currentSpace->matrix(); 122 TransformationMatrix localMatrix = currentSpace->matrix();
123 localMatrix.applyTransformOrigin(currentSpace->origin()); 123 localMatrix.applyTransformOrigin(currentSpace->origin());
124 matrix = localMatrix * matrix; 124 matrix = localMatrix * matrix;
125 currentSpace = currentSpace->parent(); 125 currentSpace = currentSpace->nonIsolationParent();
126 } 126 }
127 return gfx::Transform(TransformationMatrix::toSkMatrix44(matrix)); 127 return gfx::Transform(TransformationMatrix::toSkMatrix44(matrix));
128 } 128 }
129 129
130 const TransformPaintPropertyNode* localTransformSpace(const ClipPaintPropertyNod e* clip) 130 const TransformPaintPropertyNode* localTransformSpace(const ClipPaintPropertyNod e* clip)
131 { 131 {
132 return clip ? clip->localTransformSpace() : nullptr; 132 if (!clip)
133 return nullptr;
134 DCHECK(!clip->isIsolationNode());
135 if (const TransformPaintPropertyNode* transform = clip->localTransformSpace( ))
136 return transform->nonIsolationNode();
137 return nullptr;
133 } 138 }
134 139
135 scoped_refptr<cc::Layer> createClipLayer(const ClipPaintPropertyNode* node) 140 scoped_refptr<cc::Layer> createClipLayer(const ClipPaintPropertyNode* node)
136 { 141 {
137 // TODO(jbroman): Handle rounded-rect clips. 142 // TODO(jbroman): Handle rounded-rect clips.
138 // TODO(jbroman): Handle clips of non-integer size. 143 // TODO(jbroman): Handle clips of non-integer size.
139 gfx::RectF clipRect = node->clipRect().rect(); 144 gfx::RectF clipRect = node->clipRect().rect();
140 145
141 // TODO(jbroman): This, and the similar logic in 146 // TODO(jbroman): This, and the similar logic in
142 // PaintArtifactCompositor::update, will need to be updated to account for 147 // PaintArtifactCompositor::update, will need to be updated to account for
143 // other kinds of intermediate layers, such as those that apply effects. 148 // other kinds of intermediate layers, such as those that apply effects.
144 // TODO(jbroman): This assumes that the transform space of this node's 149 // TODO(jbroman): This assumes that the transform space of this node's
145 // parent is an ancestor of this node's transform space. That's not 150 // parent is an ancestor of this node's transform space. That's not
146 // necessarily true, and this should be fixed. crbug.com/597156 151 // necessarily true, and this should be fixed. crbug.com/597156
147 gfx::Transform transform = transformToTransformSpace(localTransformSpace(nod e), localTransformSpace(node->parent())); 152 const ClipPaintPropertyNode* parentClip = node->nonIsolationParent();
153 gfx::Transform transform = transformToTransformSpace(localTransformSpace(nod e), localTransformSpace(parentClip));
148 gfx::Vector2dF offset = clipRect.OffsetFromOrigin(); 154 gfx::Vector2dF offset = clipRect.OffsetFromOrigin();
149 transform.Translate(offset.x(), offset.y()); 155 transform.Translate(offset.x(), offset.y());
150 if (node->parent()) { 156 if (parentClip) {
151 FloatPoint offsetDueToParentClipOffset = node->parent()->clipRect().rect ().location(); 157 FloatPoint offsetDueToParentClipOffset = parentClip->clipRect().rect().l ocation();
152 gfx::Transform undoClipOffset; 158 gfx::Transform undoClipOffset;
153 undoClipOffset.Translate(-offsetDueToParentClipOffset.x(), -offsetDueToP arentClipOffset.y()); 159 undoClipOffset.Translate(-offsetDueToParentClipOffset.x(), -offsetDueToP arentClipOffset.y());
154 transform.ConcatTransform(undoClipOffset); 160 transform.ConcatTransform(undoClipOffset);
155 } 161 }
156 162
157 scoped_refptr<cc::Layer> layer = cc::Layer::Create(); 163 scoped_refptr<cc::Layer> layer = cc::Layer::Create();
158 layer->SetIsDrawable(false); 164 layer->SetIsDrawable(false);
159 layer->SetMasksToBounds(true); 165 layer->SetMasksToBounds(true);
160 layer->SetPosition(gfx::PointF()); 166 layer->SetPosition(gfx::PointF());
161 layer->SetBounds(gfx::ToRoundedSize(clipRect.size())); 167 layer->SetBounds(gfx::ToRoundedSize(clipRect.size()));
(...skipping 23 matching lines...) Expand all
185 // Otherwise, we need to build new clip layers. 191 // Otherwise, we need to build new clip layers.
186 // We do this from the bottom up. 192 // We do this from the bottom up.
187 size_t numExistingClipLayers = m_clipLayers.size(); 193 size_t numExistingClipLayers = m_clipLayers.size();
188 scoped_refptr<cc::Layer> childLayer; 194 scoped_refptr<cc::Layer> childLayer;
189 do { 195 do {
190 scoped_refptr<cc::Layer> clipLayer = createClipLayer(clip); 196 scoped_refptr<cc::Layer> clipLayer = createClipLayer(clip);
191 m_clipLayers.append(NodeLayerPair(clip, clipLayer.get())); 197 m_clipLayers.append(NodeLayerPair(clip, clipLayer.get()));
192 if (childLayer) 198 if (childLayer)
193 clipLayer->AddChild(std::move(childLayer)); 199 clipLayer->AddChild(std::move(childLayer));
194 childLayer = std::move(clipLayer); 200 childLayer = std::move(clipLayer);
195 clip = clip->parent(); 201 clip = clip->nonIsolationParent();
196 } while (ancestor != clip); 202 } while (ancestor != clip);
197 ancestorClipLayer->AddChild(std::move(childLayer)); 203 ancestorClipLayer->AddChild(std::move(childLayer));
198 204
199 // Rearrange the new clip layers to be in top-down order, like they 205 // Rearrange the new clip layers to be in top-down order, like they
200 // should be. 206 // should be.
201 std::reverse(m_clipLayers.begin() + numExistingClipLayers, m_clipLayers. end()); 207 std::reverse(m_clipLayers.begin() + numExistingClipLayers, m_clipLayers. end());
202 208
203 // Return the last (bottom-most) clip layer. 209 // Return the last (bottom-most) clip layer.
204 return m_clipLayers.last().second; 210 return m_clipLayers.last().second;
205 } 211 }
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after
281 return; 287 return;
282 } 288 }
283 289
284 // TODO(jbroman): This should be incremental. 290 // TODO(jbroman): This should be incremental.
285 m_rootLayer->RemoveAllChildren(); 291 m_rootLayer->RemoveAllChildren();
286 m_contentLayerClients.clear(); 292 m_contentLayerClients.clear();
287 293
288 m_contentLayerClients.reserveCapacity(paintArtifact.paintChunks().size()); 294 m_contentLayerClients.reserveCapacity(paintArtifact.paintChunks().size());
289 ClipLayerManager clipLayerManager(m_rootLayer.get()); 295 ClipLayerManager clipLayerManager(m_rootLayer.get());
290 for (const PaintChunk& paintChunk : paintArtifact.paintChunks()) { 296 for (const PaintChunk& paintChunk : paintArtifact.paintChunks()) {
291 cc::Layer* parent = clipLayerManager.switchToNewClipLayer(paintChunk.pro perties.clip.get()); 297 const ClipPaintPropertyNode* clip = paintChunk.properties.clip->nonIsola tionNode();
298 cc::Layer* parent = clipLayerManager.switchToNewClipLayer(clip);
292 299
293 gfx::Vector2dF layerOffset; 300 gfx::Vector2dF layerOffset;
294 scoped_refptr<cc::Layer> layer = layerForPaintChunk(paintArtifact, paint Chunk, layerOffset); 301 scoped_refptr<cc::Layer> layer = layerForPaintChunk(paintArtifact, paint Chunk, layerOffset);
295 // TODO(jbroman): Same as above. This assumes the transform space of the current clip is 302 // TODO(jbroman): Same as above. This assumes the transform space of the current clip is
296 // an ancestor of the chunk. It is not necessarily true. crbug.com/59715 6 303 // an ancestor of the chunk. It is not necessarily true. crbug.com/59715 6
297 gfx::Transform transform = transformToTransformSpace(paintChunk.properti es.transform.get(), localTransformSpace(paintChunk.properties.clip.get())); 304 gfx::Transform transform = transformToTransformSpace(paintChunk.properti es.transform->nonIsolationNode(), localTransformSpace(clip));
298 transform.Translate(layerOffset.x(), layerOffset.y()); 305 transform.Translate(layerOffset.x(), layerOffset.y());
299 // If a clip was applied, its origin needs to be cancelled out in 306 // If a clip was applied, its origin needs to be cancelled out in
300 // this transform. 307 // this transform.
301 if (const auto* clip = paintChunk.properties.clip.get()) { 308 if (clip) {
302 FloatPoint offsetDueToClipOffset = clip->clipRect().rect().location( ); 309 FloatPoint offsetDueToClipOffset = clip->clipRect().rect().location( );
303 gfx::Transform undoClipOffset; 310 gfx::Transform undoClipOffset;
304 undoClipOffset.Translate(-offsetDueToClipOffset.x(), -offsetDueToCli pOffset.y()); 311 undoClipOffset.Translate(-offsetDueToClipOffset.x(), -offsetDueToCli pOffset.y());
305 transform.ConcatTransform(undoClipOffset); 312 transform.ConcatTransform(undoClipOffset);
306 } 313 }
307 layer->SetTransform(transform); 314 layer->SetTransform(transform);
308 layer->SetDoubleSided(!paintChunk.properties.backfaceHidden); 315 layer->SetDoubleSided(!paintChunk.properties.backfaceHidden);
309 layer->SetNeedsDisplay(); 316 layer->SetNeedsDisplay();
310 parent->AddChild(layer); 317 parent->AddChild(layer);
311 318
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after
393 int PropertyTreeManager::compositorIdForTransformNode(const TransformPaintProper tyNode* transformNode) 400 int PropertyTreeManager::compositorIdForTransformNode(const TransformPaintProper tyNode* transformNode)
394 { 401 {
395 if (!transformNode) 402 if (!transformNode)
396 return kSecondaryRootNodeId; 403 return kSecondaryRootNodeId;
397 404
398 auto it = m_transformNodeMap.find(transformNode); 405 auto it = m_transformNodeMap.find(transformNode);
399 if (it != m_transformNodeMap.end()) 406 if (it != m_transformNodeMap.end())
400 return it->value; 407 return it->value;
401 408
402 scoped_refptr<cc::Layer> dummyLayer = cc::Layer::Create(); 409 scoped_refptr<cc::Layer> dummyLayer = cc::Layer::Create();
403 int parentId = compositorIdForTransformNode(transformNode->parent()); 410 int parentId = compositorIdForTransformNode(transformNode->nonIsolationParen t());
404 int id = transformTree().Insert(cc::TransformNode(), parentId); 411 int id = transformTree().Insert(cc::TransformNode(), parentId);
405 412
406 cc::TransformNode& compositorNode = *transformTree().Node(id); 413 cc::TransformNode& compositorNode = *transformTree().Node(id);
407 transformTree().SetTargetId(id, kRealRootNodeId); 414 transformTree().SetTargetId(id, kRealRootNodeId);
408 transformTree().SetContentTargetId(id, kRealRootNodeId); 415 transformTree().SetContentTargetId(id, kRealRootNodeId);
409 compositorNode.source_node_id = parentId; 416 compositorNode.source_node_id = parentId;
410 417
411 FloatPoint3D origin = transformNode->origin(); 418 FloatPoint3D origin = transformNode->origin();
412 compositorNode.pre_local.matrix().setTranslate( 419 compositorNode.pre_local.matrix().setTranslate(
413 -origin.x(), -origin.y(), -origin.z()); 420 -origin.x(), -origin.y(), -origin.z());
(...skipping 18 matching lines...) Expand all
432 int PropertyTreeManager::compositorIdForClipNode(const ClipPaintPropertyNode* cl ipNode) 439 int PropertyTreeManager::compositorIdForClipNode(const ClipPaintPropertyNode* cl ipNode)
433 { 440 {
434 if (!clipNode) 441 if (!clipNode)
435 return kSecondaryRootNodeId; 442 return kSecondaryRootNodeId;
436 443
437 auto it = m_clipNodeMap.find(clipNode); 444 auto it = m_clipNodeMap.find(clipNode);
438 if (it != m_clipNodeMap.end()) 445 if (it != m_clipNodeMap.end())
439 return it->value; 446 return it->value;
440 447
441 scoped_refptr<cc::Layer> dummyLayer = cc::Layer::Create(); 448 scoped_refptr<cc::Layer> dummyLayer = cc::Layer::Create();
442 int parentId = compositorIdForClipNode(clipNode->parent()); 449 int parentId = compositorIdForClipNode(clipNode->nonIsolationParent());
443 int id = clipTree().Insert(cc::ClipNode(), parentId); 450 int id = clipTree().Insert(cc::ClipNode(), parentId);
444 451
445 cc::ClipNode& compositorNode = *clipTree().Node(id); 452 cc::ClipNode& compositorNode = *clipTree().Node(id);
446 compositorNode.owner_id = dummyLayer->id(); 453 compositorNode.owner_id = dummyLayer->id();
447 454
448 // TODO(jbroman): Don't discard rounded corners. 455 // TODO(jbroman): Don't discard rounded corners.
449 compositorNode.clip = clipNode->clipRect().rect(); 456 compositorNode.clip = clipNode->clipRect().rect();
450 compositorNode.transform_id = compositorIdForTransformNode(clipNode->localTr ansformSpace()); 457 compositorNode.transform_id = compositorIdForTransformNode(localTransformSpa ce(clipNode));
451 compositorNode.target_transform_id = kRealRootNodeId; 458 compositorNode.target_transform_id = kRealRootNodeId;
452 compositorNode.applies_local_clip = true; 459 compositorNode.applies_local_clip = true;
453 compositorNode.layers_are_clipped = true; 460 compositorNode.layers_are_clipped = true;
454 compositorNode.layers_are_clipped_when_surfaces_disabled = true; 461 compositorNode.layers_are_clipped_when_surfaces_disabled = true;
455 462
456 m_rootLayer->AddChild(dummyLayer); 463 m_rootLayer->AddChild(dummyLayer);
457 dummyLayer->SetTransformTreeIndex(compositorNode.transform_id); 464 dummyLayer->SetTransformTreeIndex(compositorNode.transform_id);
458 dummyLayer->SetClipTreeIndex(id); 465 dummyLayer->SetClipTreeIndex(id);
459 dummyLayer->SetEffectTreeIndex(kSecondaryRootNodeId); 466 dummyLayer->SetEffectTreeIndex(kSecondaryRootNodeId);
460 dummyLayer->SetScrollTreeIndex(kRealRootNodeId); 467 dummyLayer->SetScrollTreeIndex(kRealRootNodeId);
(...skipping 26 matching lines...) Expand all
487 } 494 }
488 while (depthB > depthA) { 495 while (depthB > depthA) {
489 nodeB = nodeB->parent(); 496 nodeB = nodeB->parent();
490 depthB--; 497 depthB--;
491 } 498 }
492 DCHECK_EQ(depthA, depthB); 499 DCHECK_EQ(depthA, depthB);
493 while (nodeA != nodeB) { 500 while (nodeA != nodeB) {
494 nodeA = nodeA->parent(); 501 nodeA = nodeA->parent();
495 nodeB = nodeB->parent(); 502 nodeB = nodeB->parent();
496 } 503 }
497 return nodeA; 504 return nodeA ? nodeA->nonIsolationNode() : nullptr;
498 } 505 }
499 506
500 int PropertyTreeManager::switchToEffectNode(const EffectPaintPropertyNode& nextE ffect) 507 int PropertyTreeManager::switchToEffectNode(const EffectPaintPropertyNode& nextE ffect)
501 { 508 {
502 const EffectPaintPropertyNode* ancestor = lowestCommonAncestor(currentEffect Node(), &nextEffect); 509 const EffectPaintPropertyNode* ancestor = lowestCommonAncestor(currentEffect Node(), &nextEffect);
503 while (currentEffectNode() != ancestor) 510 while (currentEffectNode() != ancestor)
504 m_effectStack.removeLast(); 511 m_effectStack.removeLast();
505 512
506 #if DCHECK_IS_ON() 513 #if DCHECK_IS_ON()
507 DCHECK(m_isFirstEffectEver || currentEffectNode()) << "Malformed effect tree . Nodes in the same property tree should have common root."; 514 DCHECK(m_isFirstEffectEver || currentEffectNode()) << "Malformed effect tree . Nodes in the same property tree should have common root.";
508 m_isFirstEffectEver = false; 515 m_isFirstEffectEver = false;
509 #endif 516 #endif
510 buildEffectNodesRecursively(&nextEffect); 517 buildEffectNodesRecursively(&nextEffect);
511 518
512 return compositorIdForCurrentEffectNode(); 519 return compositorIdForCurrentEffectNode();
513 } 520 }
514 521
515 void PropertyTreeManager::buildEffectNodesRecursively(const EffectPaintPropertyN ode* nextEffect) 522 void PropertyTreeManager::buildEffectNodesRecursively(const EffectPaintPropertyN ode* nextEffect)
516 { 523 {
517 if (nextEffect == currentEffectNode()) 524 if (nextEffect == currentEffectNode())
518 return; 525 return;
519 DCHECK(nextEffect); 526 DCHECK(nextEffect);
520 527
521 buildEffectNodesRecursively(nextEffect->parent()); 528 buildEffectNodesRecursively(nextEffect->nonIsolationParent());
522 DCHECK_EQ(nextEffect->parent(), currentEffectNode()); 529 DCHECK_EQ(nextEffect->nonIsolationParent(), currentEffectNode());
523 530
524 #if DCHECK_IS_ON() 531 #if DCHECK_IS_ON()
525 DCHECK(!m_effectNodesConverted.contains(nextEffect)) << "Malformed paint art ifact. Paint chunks under the same effect should be contiguous."; 532 DCHECK(!m_effectNodesConverted.contains(nextEffect)) << "Malformed paint art ifact. Paint chunks under the same effect should be contiguous.";
526 m_effectNodesConverted.add(nextEffect); 533 m_effectNodesConverted.add(nextEffect);
527 #endif 534 #endif
528 535
529 // We currently create dummy layers to host effect nodes and corresponding r ender surface. 536 // We currently create dummy layers to host effect nodes and corresponding r ender surface.
530 // This should be removed once cc implements better support for freestanding property trees. 537 // This should be removed once cc implements better support for freestanding property trees.
531 scoped_refptr<cc::Layer> dummyLayer = cc::Layer::Create(); 538 scoped_refptr<cc::Layer> dummyLayer = cc::Layer::Create();
532 m_rootLayer->AddChild(dummyLayer); 539 m_rootLayer->AddChild(dummyLayer);
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
565 m_rootLayer->SetEffectTreeIndex(kSecondaryRootNodeId); 572 m_rootLayer->SetEffectTreeIndex(kSecondaryRootNodeId);
566 m_rootLayer->SetScrollTreeIndex(kRealRootNodeId); 573 m_rootLayer->SetScrollTreeIndex(kRealRootNodeId);
567 574
568 PropertyTreeManager propertyTreeManager(*host->property_trees(), m_rootLayer .get()); 575 PropertyTreeManager propertyTreeManager(*host->property_trees(), m_rootLayer .get());
569 m_contentLayerClients.clear(); 576 m_contentLayerClients.clear();
570 m_contentLayerClients.reserveCapacity(paintArtifact.paintChunks().size()); 577 m_contentLayerClients.reserveCapacity(paintArtifact.paintChunks().size());
571 for (const PaintChunk& paintChunk : paintArtifact.paintChunks()) { 578 for (const PaintChunk& paintChunk : paintArtifact.paintChunks()) {
572 gfx::Vector2dF layerOffset; 579 gfx::Vector2dF layerOffset;
573 scoped_refptr<cc::Layer> layer = layerForPaintChunk(paintArtifact, paint Chunk, layerOffset); 580 scoped_refptr<cc::Layer> layer = layerForPaintChunk(paintArtifact, paint Chunk, layerOffset);
574 581
575 int transformId = propertyTreeManager.compositorIdForTransformNode(paint Chunk.properties.transform.get()); 582 int transformId = propertyTreeManager.compositorIdForTransformNode(paint Chunk.properties.transform->nonIsolationNode());
576 int clipId = propertyTreeManager.compositorIdForClipNode(paintChunk.prop erties.clip.get()); 583 int clipId = propertyTreeManager.compositorIdForClipNode(paintChunk.prop erties.clip->nonIsolationNode());
577 int effectId = propertyTreeManager.switchToEffectNode(*paintChunk.proper ties.effect.get()); 584 int effectId = propertyTreeManager.switchToEffectNode(*paintChunk.proper ties.effect->nonIsolationNode());
578 585
579 layer->set_offset_to_transform_parent(layerOffset); 586 layer->set_offset_to_transform_parent(layerOffset);
580 587
581 m_rootLayer->AddChild(layer); 588 m_rootLayer->AddChild(layer);
582 layer->set_property_tree_sequence_number(kPropertyTreeSequenceNumber); 589 layer->set_property_tree_sequence_number(kPropertyTreeSequenceNumber);
583 layer->SetTransformTreeIndex(transformId); 590 layer->SetTransformTreeIndex(transformId);
584 layer->SetClipTreeIndex(clipId); 591 layer->SetClipTreeIndex(clipId);
585 layer->SetEffectTreeIndex(effectId); 592 layer->SetEffectTreeIndex(effectId);
586 layer->SetScrollTreeIndex(kRealRootNodeId); 593 layer->SetScrollTreeIndex(kRealRootNodeId);
587 594
588 if (m_extraDataForTestingEnabled) 595 if (m_extraDataForTestingEnabled)
589 m_extraDataForTesting->contentLayers.append(layer); 596 m_extraDataForTesting->contentLayers.append(layer);
590 } 597 }
591 598
592 // Mark the property trees as having been rebuilt. 599 // Mark the property trees as having been rebuilt.
593 host->property_trees()->sequence_number = kPropertyTreeSequenceNumber; 600 host->property_trees()->sequence_number = kPropertyTreeSequenceNumber;
594 host->property_trees()->needs_rebuild = false; 601 host->property_trees()->needs_rebuild = false;
595 } 602 }
596 603
597 } // namespace blink 604 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698