107a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch// Copyright 2014 The Chromium Authors. All rights reserved. 207a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch// Use of this source code is governed by a BSD-style license that can be 307a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch// found in the LICENSE file. 407a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch 507a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch#include "config.h" 65d92fedcae5e801a8b224de090094f2d9df0b54aTorne (Richard Coles)#include "core/rendering/compositing/CompositingInputsUpdater.h" 707a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch 8f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles)#include "core/rendering/RenderBlock.h" 907a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch#include "core/rendering/RenderLayer.h" 1007a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch#include "core/rendering/compositing/CompositedLayerMapping.h" 11197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch#include "core/rendering/compositing/RenderLayerCompositor.h" 12197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch#include "platform/TraceEvent.h" 1307a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch 14c1847b1379d12d0e05df27436bf19a9b1bf12deaTorne (Richard Coles)namespace blink { 1507a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch 165d92fedcae5e801a8b224de090094f2d9df0b54aTorne (Richard Coles)CompositingInputsUpdater::CompositingInputsUpdater(RenderLayer* rootRenderLayer) 1707a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch : m_geometryMap(UseTransforms) 1807a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch , m_rootRenderLayer(rootRenderLayer) 1907a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch{ 2007a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch} 2107a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch 225d92fedcae5e801a8b224de090094f2d9df0b54aTorne (Richard Coles)CompositingInputsUpdater::~CompositingInputsUpdater() 2307a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch{ 2407a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch} 2507a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch 26197021e6b966cfb06891637935ef33fff06433d1Ben Murdochvoid CompositingInputsUpdater::update() 27197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch{ 28e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles) TRACE_EVENT0("blink", "CompositingInputsUpdater::update"); 29197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch updateRecursive(m_rootRenderLayer, DoNotForceUpdate, AncestorInfo()); 30197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch} 31197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch 32197021e6b966cfb06891637935ef33fff06433d1Ben Murdochstatic const RenderLayer* findParentLayerOnClippingContainerChain(const RenderLayer* layer) 33197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch{ 34197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch RenderObject* current = layer->renderer(); 35197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch while (current) { 36197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch if (current->style()->position() == FixedPosition) { 37197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch for (current = current->parent(); current && !current->canContainFixedPositionObjects(); current = current->parent()) { 387242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci // All types of clips apply to fixed-position descendants of other fixed-position elements. 397242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci // Note: it's unclear whether this is what the spec says. Firefox does not clip, but Chrome does. 407242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci if (current->style()->position() == FixedPosition && current->hasClipOrOverflowClip()) { 417242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci ASSERT(current->hasLayer()); 427242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci return static_cast<const RenderLayerModelObject*>(current)->layer(); 437242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci } 447242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci 45197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch // CSS clip applies to fixed position elements even for ancestors that are not what the 46197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch // fixed element is positioned with respect to. 47197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch if (current->hasClip()) { 48197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch ASSERT(current->hasLayer()); 49197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch return static_cast<const RenderLayerModelObject*>(current)->layer(); 50197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch } 51197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch } 52197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch } else { 53197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch current = current->containingBlock(); 54197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch } 55197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch 56197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch if (current->hasLayer()) 57197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch return static_cast<const RenderLayerModelObject*>(current)->layer(); 58197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch // Having clip or overflow clip forces the RenderObject to become a layer. 59197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch ASSERT(!current->hasClipOrOverflowClip()); 60197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch } 61197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch ASSERT_NOT_REACHED(); 62197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch return 0; 63197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch} 64197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch 65197021e6b966cfb06891637935ef33fff06433d1Ben Murdochstatic const RenderLayer* findParentLayerOnContainingBlockChain(const RenderObject* object) 66197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch{ 67197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch for (const RenderObject* current = object; current; current = current->containingBlock()) { 68197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch if (current->hasLayer()) 69197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch return static_cast<const RenderLayerModelObject*>(current)->layer(); 70197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch } 71197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch ASSERT_NOT_REACHED(); 72197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch return 0; 73197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch} 74197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch 75197021e6b966cfb06891637935ef33fff06433d1Ben Murdochstatic bool hasClippedStackingAncestor(const RenderLayer* layer, const RenderLayer* clippingLayer) 76197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch{ 77197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch if (layer == clippingLayer) 78197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch return false; 79197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch const RenderObject* clippingRenderer = clippingLayer->renderer(); 80197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch for (const RenderLayer* current = layer->compositingContainer(); current && current != clippingLayer; current = current->compositingContainer()) { 81c1847b1379d12d0e05df27436bf19a9b1bf12deaTorne (Richard Coles) if (current->renderer()->hasClipOrOverflowClip() && !clippingRenderer->isDescendantOf(current->renderer())) 82c1847b1379d12d0e05df27436bf19a9b1bf12deaTorne (Richard Coles) return true; 83c1847b1379d12d0e05df27436bf19a9b1bf12deaTorne (Richard Coles) 84197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch if (const RenderObject* container = current->clippingContainer()) { 85197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch if (clippingRenderer != container && !clippingRenderer->isDescendantOf(container)) 86197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch return true; 87197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch } 88197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch } 89197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch return false; 90197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch} 91197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch 92197021e6b966cfb06891637935ef33fff06433d1Ben Murdochvoid CompositingInputsUpdater::updateRecursive(RenderLayer* layer, UpdateType updateType, AncestorInfo info) 9307a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch{ 945d92fedcae5e801a8b224de090094f2d9df0b54aTorne (Richard Coles) if (!layer->childNeedsCompositingInputsUpdate() && updateType != ForceUpdate) 9507a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch return; 9607a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch 9707a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch m_geometryMap.pushMappingsToAncestor(layer, layer->parent()); 9807a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch 9907a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch if (layer->hasCompositedLayerMapping()) 100f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles) info.enclosingCompositedLayer = layer; 10107a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch 1025d92fedcae5e801a8b224de090094f2d9df0b54aTorne (Richard Coles) if (layer->needsCompositingInputsUpdate()) { 103f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles) if (info.enclosingCompositedLayer) 10476c265b59aa821ccbf8c75ab2bb0d036e97d2956Torne (Richard Coles) info.enclosingCompositedLayer->compositedLayerMapping()->setNeedsGraphicsLayerUpdate(GraphicsLayerUpdateSubtree); 10507a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch updateType = ForceUpdate; 10607a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch } 10707a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch 10807a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch if (updateType == ForceUpdate) { 109197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch RenderLayer::AncestorDependentCompositingInputs properties; 11007a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch 11107a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch if (!layer->isRootLayer()) { 112a9984bf9ddc3cf73fdae3f29134a2bab379e7029Ben Murdoch properties.clippedAbsoluteBoundingBox = enclosingIntRect(m_geometryMap.absoluteRect(layer->boundingBoxForCompositingOverlapTest())); 11307a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch // FIXME: Setting the absBounds to 1x1 instead of 0x0 makes very little sense, 11407a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch // but removing this code will make JSGameBench sad. 11507a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch // See https://codereview.chromium.org/13912020/ 11607a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch if (properties.clippedAbsoluteBoundingBox.isEmpty()) 11707a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch properties.clippedAbsoluteBoundingBox.setSize(IntSize(1, 1)); 11807a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch 11907a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch IntRect clipRect = pixelSnappedIntRect(layer->clipper().backgroundClipRect(ClipRectsContext(m_rootRenderLayer, AbsoluteClipRects)).rect()); 12007a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch properties.clippedAbsoluteBoundingBox.intersect(clipRect); 1216f543c786fc42989f552b4daa774ca5ff32fa697Ben Murdoch 1226f543c786fc42989f552b4daa774ca5ff32fa697Ben Murdoch const RenderLayer* parent = layer->parent(); 123197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch properties.opacityAncestor = parent->isTransparent() ? parent : parent->opacityAncestor(); 1247242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci properties.transformAncestor = parent->hasTransform() ? parent : parent->transformAncestor(); 125197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch properties.filterAncestor = parent->hasFilter() ? parent : parent->filterAncestor(); 126197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch 127197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch if (info.hasAncestorWithClipOrOverflowClip) { 128197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch const RenderLayer* parentLayerOnClippingContainerChain = findParentLayerOnClippingContainerChain(layer); 129197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch const bool parentHasClipOrOverflowClip = parentLayerOnClippingContainerChain->renderer()->hasClipOrOverflowClip(); 130197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch properties.clippingContainer = parentHasClipOrOverflowClip ? parentLayerOnClippingContainerChain->renderer() : parentLayerOnClippingContainerChain->clippingContainer(); 131197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch } 132197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch 133197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch if (info.lastScrollingAncestor) { 134197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch const RenderObject* containingBlock = layer->renderer()->containingBlock(); 135197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch const RenderLayer* parentLayerOnContainingBlockChain = findParentLayerOnContainingBlockChain(containingBlock); 136197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch 137197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch properties.ancestorScrollingLayer = parentLayerOnContainingBlockChain->ancestorScrollingLayer(); 138197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch if (parentLayerOnContainingBlockChain->scrollsOverflow()) 139197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch properties.ancestorScrollingLayer = parentLayerOnContainingBlockChain; 140197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch 141197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch if (layer->renderer()->isOutOfFlowPositioned() && !layer->subtreeIsInvisible()) { 142197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch const RenderObject* lastScroller = info.lastScrollingAncestor->renderer(); 143197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch const RenderLayer* clippingLayer = properties.clippingContainer ? properties.clippingContainer->enclosingLayer() : layer->compositor()->rootRenderLayer(); 144197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch properties.isUnclippedDescendant = lastScroller != containingBlock && lastScroller->isDescendantOf(containingBlock); 145197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch if (hasClippedStackingAncestor(layer, clippingLayer)) 146197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch properties.clipParent = clippingLayer; 147197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch } 148197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch 149197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch if (!layer->stackingNode()->isNormalFlowOnly() 150197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch && properties.ancestorScrollingLayer 151197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch && !info.ancestorStackingContext->renderer()->isDescendantOf(properties.ancestorScrollingLayer->renderer())) 152197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch properties.scrollParent = properties.ancestorScrollingLayer; 153f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles) } 15407a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch } 15507a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch 156197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch properties.hasAncestorWithClipPath = info.hasAncestorWithClipPath; 157197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch layer->updateAncestorDependentCompositingInputs(properties); 15807a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch } 15907a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch 160197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch if (layer->stackingNode()->isStackingContext()) 161197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch info.ancestorStackingContext = layer; 162197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch 163f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles) if (layer->scrollsOverflow()) 164197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch info.lastScrollingAncestor = layer; 165f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles) 166197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch if (layer->renderer()->hasClipOrOverflowClip()) 167197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch info.hasAncestorWithClipOrOverflowClip = true; 168197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch 169197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch if (layer->renderer()->hasClipPath()) 170197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch info.hasAncestorWithClipPath = true; 171197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch 172197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch RenderLayer::DescendantDependentCompositingInputs descendantProperties; 173197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch for (RenderLayer* child = layer->firstChild(); child; child = child->nextSibling()) { 174197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch updateRecursive(child, updateType, info); 175e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles) 176197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch descendantProperties.hasDescendantWithClipPath |= child->hasDescendantWithClipPath() || child->renderer()->hasClipPath(); 177e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles) descendantProperties.hasDescendantWithBlendMode |= child->hasDescendantWithBlendMode() || child->renderer()->hasBlendMode(); 178197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch } 17907a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch 180197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch layer->updateDescendantDependentCompositingInputs(descendantProperties); 181197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch layer->didUpdateCompositingInputs(); 182e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles) 183e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles) m_geometryMap.popMappingsToAncestor(layer->parent()); 18407a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch} 18507a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch 186197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch#if ENABLE(ASSERT) 18707a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch 1885d92fedcae5e801a8b224de090094f2d9df0b54aTorne (Richard Coles)void CompositingInputsUpdater::assertNeedsCompositingInputsUpdateBitsCleared(RenderLayer* layer) 18907a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch{ 1905d92fedcae5e801a8b224de090094f2d9df0b54aTorne (Richard Coles) ASSERT(!layer->childNeedsCompositingInputsUpdate()); 1915d92fedcae5e801a8b224de090094f2d9df0b54aTorne (Richard Coles) ASSERT(!layer->needsCompositingInputsUpdate()); 19207a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch 19307a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch for (RenderLayer* child = layer->firstChild(); child; child = child->nextSibling()) 1945d92fedcae5e801a8b224de090094f2d9df0b54aTorne (Richard Coles) assertNeedsCompositingInputsUpdateBitsCleared(child); 19507a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch} 19607a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch 19707a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch#endif 19807a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch 199c1847b1379d12d0e05df27436bf19a9b1bf12deaTorne (Richard Coles)} // namespace blink 200