1f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)// Copyright 2014 The Chromium Authors. All rights reserved.
22a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
32a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// found in the LICENSE file.
42a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
52a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include <vector>
603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "cc/blink/web_layer_impl_fixed_bounds.h"
72a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "cc/layers/picture_image_layer.h"
8a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)#include "cc/test/fake_layer_tree_host.h"
92a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "cc/test/geometry_test_utils.h"
102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "cc/trees/layer_tree_host_common.h"
112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "testing/gtest/include/gtest/gtest.h"
12868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "third_party/WebKit/public/platform/WebFloatPoint.h"
13868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "third_party/WebKit/public/platform/WebSize.h"
142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "third_party/skia/include/utils/SkMatrix44.h"
152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "ui/gfx/point3_f.h"
162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
17f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)using blink::WebFloatPoint;
18f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)using blink::WebSize;
192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)namespace cc_blink {
212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)namespace {
222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)TEST(WebLayerImplFixedBoundsTest, IdentityBounds) {
242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  scoped_ptr<WebLayerImplFixedBounds> layer(new WebLayerImplFixedBounds());
252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  layer->SetFixedBounds(gfx::Size(100, 100));
262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  layer->setBounds(WebSize(100, 100));
272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  EXPECT_EQ(WebSize(100, 100), layer->bounds());
282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  EXPECT_EQ(gfx::Size(100, 100), layer->layer()->bounds());
292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  EXPECT_EQ(gfx::Transform(), layer->layer()->transform());
302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)gfx::Point3F TransformPoint(const gfx::Transform& transform,
332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                            const gfx::Point3F& point) {
342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  gfx::Point3F result = point;
35d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)  transform.TransformPoint(&result);
362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return result;
372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)void CheckBoundsScaleSimple(WebLayerImplFixedBounds* layer,
402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                            const WebSize& bounds,
412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                            const gfx::Size& fixed_bounds) {
422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  layer->setBounds(bounds);
432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  layer->SetFixedBounds(fixed_bounds);
442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  EXPECT_EQ(bounds, layer->bounds());
462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  EXPECT_EQ(fixed_bounds, layer->layer()->bounds());
472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  EXPECT_TRUE(layer->transform().isIdentity());
482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // An arbitrary point to check the scale and transforms.
502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  gfx::Point3F original_point(10, 20, 1);
512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  gfx::Point3F scaled_point(
522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      original_point.x() * bounds.width / fixed_bounds.width(),
532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      original_point.y() * bounds.height / fixed_bounds.height(),
542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      original_point.z());
555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  // Test if the bounds scale is correctly applied in transform.
56f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  EXPECT_POINT3F_EQ(
57f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      scaled_point,
58f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      TransformPoint(layer->layer()->transform(), original_point));
592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)TEST(WebLayerImplFixedBoundsTest, BoundsScaleSimple) {
622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  scoped_ptr<WebLayerImplFixedBounds> layer(new WebLayerImplFixedBounds());
632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  CheckBoundsScaleSimple(layer.get(), WebSize(100, 200), gfx::Size(150, 250));
642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // Change fixed_bounds.
652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  CheckBoundsScaleSimple(layer.get(), WebSize(100, 200), gfx::Size(75, 100));
662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // Change bounds.
672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  CheckBoundsScaleSimple(layer.get(), WebSize(300, 100), gfx::Size(75, 100));
682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)void ExpectEqualLayerRectsInTarget(cc::Layer* layer1, cc::Layer* layer2) {
712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  gfx::RectF layer1_rect_in_target(layer1->content_bounds());
722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  layer1->draw_transform().TransformRect(&layer1_rect_in_target);
732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  gfx::RectF layer2_rect_in_target(layer2->content_bounds());
752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  layer2->draw_transform().TransformRect(&layer2_rect_in_target);
762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  EXPECT_FLOAT_RECT_EQ(layer1_rect_in_target, layer2_rect_in_target);
782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void CompareFixedBoundsLayerAndNormalLayer(const WebFloatPoint& anchor_point,
815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                           const gfx::Transform& transform) {
822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  const gfx::Size kDeviceViewportSize(800, 600);
832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  const float kDeviceScaleFactor = 2.f;
842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  const float kPageScaleFactor = 1.5f;
852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  WebSize bounds(150, 200);
872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  WebFloatPoint position(20, 30);
882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  gfx::Size fixed_bounds(160, 70);
892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  scoped_ptr<WebLayerImplFixedBounds> root_layer(new WebLayerImplFixedBounds());
912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
922a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  WebLayerImplFixedBounds* fixed_bounds_layer =
932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      new WebLayerImplFixedBounds(cc::PictureImageLayer::Create());
942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  fixed_bounds_layer->setBounds(bounds);
952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  fixed_bounds_layer->SetFixedBounds(fixed_bounds);
962a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  fixed_bounds_layer->setTransform(transform.matrix());
972a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  fixed_bounds_layer->setPosition(position);
982a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  root_layer->addChild(fixed_bounds_layer);
992a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  WebLayerImpl* normal_layer(new WebLayerImpl(cc::PictureImageLayer::Create()));
1012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1022a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  normal_layer->setBounds(bounds);
1032a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  normal_layer->setTransform(transform.matrix());
1042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  normal_layer->setPosition(position);
1052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  root_layer->addChild(normal_layer);
1062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1071320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  cc::FakeLayerTreeHostClient client(cc::FakeLayerTreeHostClient::DIRECT_3D);
1081320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  scoped_ptr<cc::FakeLayerTreeHost> host =
1091320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      cc::FakeLayerTreeHost::Create(&client);
110a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  host->SetRootLayer(root_layer->layer());
111a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)
112ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  {
113ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    cc::RenderSurfaceLayerList render_surface_layer_list;
114558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch    cc::LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
115558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch        root_layer->layer(), kDeviceViewportSize, &render_surface_layer_list);
116558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch    inputs.device_scale_factor = kDeviceScaleFactor;
117558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch    inputs.page_scale_factor = kPageScaleFactor;
118558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch    inputs.page_scale_application_layer = root_layer->layer(),
119558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch    cc::LayerTreeHostCommon::CalculateDrawProperties(&inputs);
120558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch
121ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    ExpectEqualLayerRectsInTarget(normal_layer->layer(),
122ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch                                  fixed_bounds_layer->layer());
123ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  }
1242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // Change of fixed bounds should not affect the target geometries.
126f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  fixed_bounds_layer->SetFixedBounds(
127f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      gfx::Size(fixed_bounds.width() / 2, fixed_bounds.height() * 2));
128ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
129ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  {
130ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    cc::RenderSurfaceLayerList render_surface_layer_list;
131558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch    cc::LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
132558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch        root_layer->layer(), kDeviceViewportSize, &render_surface_layer_list);
133558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch    inputs.device_scale_factor = kDeviceScaleFactor;
134558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch    inputs.page_scale_factor = kPageScaleFactor;
135558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch    inputs.page_scale_application_layer = root_layer->layer(),
136558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch    cc::LayerTreeHostCommon::CalculateDrawProperties(&inputs);
137558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch
138ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    ExpectEqualLayerRectsInTarget(normal_layer->layer(),
139ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch                                  fixed_bounds_layer->layer());
140ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  }
1412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
1422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
143f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)// TODO(perkj): CompareToWebLayerImplSimple disabled on LSAN due to crbug/386080
144f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)#if defined(LEAK_SANITIZER)
145f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)#define MAYBE_CompareToWebLayerImplSimple DISABLED_CompareToWebLayerImplSimple
146f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)#else
147f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)#define MAYBE_CompareToWebLayerImplSimple CompareToWebLayerImplSimple
148f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)#endif
1492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// A black box test that ensures WebLayerImplFixedBounds won't change target
1502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// geometries. Simple case: identity transforms and zero anchor point.
151f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)TEST(WebLayerImplFixedBoundsTest, MAYBE_CompareToWebLayerImplSimple) {
152f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  CompareFixedBoundsLayerAndNormalLayer(WebFloatPoint(0, 0), gfx::Transform());
1532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
1542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
155f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)// TODO(perkj): CompareToWebLayerImplComplex disabled on LSAN due to
156f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)// crbug/386080
157f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)#if defined(LEAK_SANITIZER)
158f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)#define MAYBE_CompareToWebLayerImplComplex DISABLED_CompareToWebLayerImplComplex
159f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)#else
160f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)#define MAYBE_CompareToWebLayerImplComplex CompareToWebLayerImplComplex
161f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)#endif
1622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// A black box test that ensures WebLayerImplFixedBounds won't change target
1632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// geometries. Complex case: complex transforms and non-zero anchor point.
164f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)TEST(WebLayerImplFixedBoundsTest, MAYBE_CompareToWebLayerImplComplex) {
1652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  gfx::Transform transform;
1662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // These are arbitrary values that should not affect the results.
1672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  transform.Translate3d(50, 60, 70);
1682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  transform.Scale3d(2, 3, 4);
1692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  transform.RotateAbout(gfx::Vector3dF(33, 44, 55), 99);
1702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  CompareFixedBoundsLayerAndNormalLayer(WebFloatPoint(0, 0), transform);
1722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // With non-zero anchor point, WebLayerImplFixedBounds will fall back to
1742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // WebLayerImpl.
1755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  CompareFixedBoundsLayerAndNormalLayer(WebFloatPoint(0.4f, 0.6f), transform);
1762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
1772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}  // namespace
17903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)}  // namespace cc_blink
180