Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "modules/fetch/FormDataBytesConsumer.h" | |
| 6 | |
| 7 #include "core/dom/DOMArrayBuffer.h" | |
| 8 #include "core/dom/DOMTypedArray.h" | |
| 9 #include "core/dom/Document.h" | |
| 10 #include "core/html/FormData.h" | |
| 11 #include "core/testing/DummyPageHolder.h" | |
| 12 #include "modules/fetch/BytesConsumerTestUtil.h" | |
| 13 #include "platform/blob/BlobData.h" | |
| 14 #include "platform/network/EncodedFormData.h" | |
| 15 #include "testing/gmock/include/gmock/gmock.h" | |
| 16 #include "testing/gtest/include/gtest/gtest.h" | |
| 17 #include "wtf/RefPtr.h" | |
| 18 #include "wtf/Vector.h" | |
| 19 #include "wtf/text/WTFString.h" | |
| 20 | |
| 21 namespace blink { | |
| 22 namespace { | |
| 23 | |
| 24 using Result = BytesConsumer::Result; | |
| 25 using ::testing::_; | |
| 26 using ::testing::DoAll; | |
| 27 using ::testing::InSequence; | |
| 28 using ::testing::Return; | |
| 29 using Checkpoint = ::testing::StrictMock<::testing::MockFunction<void(int)>>; | |
| 30 | |
| 31 String toString(const Vector<char>& v) | |
| 32 { | |
| 33 return String(v.data(), v.size()); | |
| 34 } | |
| 35 | |
| 36 PassRefPtr<EncodedFormData> complexFormData() | |
| 37 { | |
| 38 RefPtr<EncodedFormData> data = EncodedFormData::create(); | |
| 39 | |
| 40 data->appendData("foo", 3); | |
| 41 data->appendFileRange("/foo/bar/baz", 3, 4, 5); | |
| 42 data->appendFileSystemURLRange(KURL(KURL(), "file:///foo/bar/baz"), 6, 7, 8) ; | |
| 43 std::unique_ptr<BlobData> blobData = BlobData::create(); | |
| 44 blobData->appendText("hello", false); | |
| 45 auto size = blobData->length(); | |
| 46 RefPtr<BlobDataHandle> blobDataHandle = BlobDataHandle::create(std::move(blo bData), size); | |
| 47 data->appendBlob(blobDataHandle->uuid(), blobDataHandle); | |
| 48 Vector<char> boundary; | |
| 49 boundary.append("\0", 1); | |
| 50 data->setBoundary(boundary); | |
| 51 return data.release(); | |
| 52 } | |
| 53 | |
| 54 class NoopClient final : public GarbageCollectedFinalized<NoopClient>, public By tesConsumer::Client { | |
| 55 USING_GARBAGE_COLLECTED_MIXIN(NoopClient); | |
| 56 public: | |
| 57 void onStateChange() override {} | |
| 58 }; | |
| 59 | |
| 60 class MockBytesConsumer : public BytesConsumer { | |
| 61 public: | |
| 62 static MockBytesConsumer* create() { return new ::testing::StrictMock<MockBy tesConsumer>(); } | |
| 63 | |
| 64 MOCK_METHOD2(beginRead, Result(const char**, size_t*)); | |
| 65 MOCK_METHOD1(endRead, Result(size_t)); | |
| 66 MOCK_METHOD1(setClient, void(Client*)); | |
| 67 MOCK_METHOD0(clearClient, void()); | |
| 68 MOCK_METHOD0(cancel, void()); | |
| 69 MOCK_CONST_METHOD0(getPublicState, PublicState()); | |
| 70 MOCK_CONST_METHOD0(getError, Error()); | |
| 71 | |
| 72 String debugName() const override { return "MockBytesConsumer"; } | |
| 73 | |
| 74 protected: | |
| 75 MockBytesConsumer() = default; | |
| 76 }; | |
| 77 | |
| 78 class FormDataBytesConsumerTest : public ::testing::Test { | |
| 79 public: | |
| 80 FormDataBytesConsumerTest() : m_page(DummyPageHolder::create()) {} | |
| 81 | |
| 82 protected: | |
| 83 Document* getDocument() { return &m_page->document(); } | |
| 84 | |
| 85 std::unique_ptr<DummyPageHolder> m_page; | |
| 86 }; | |
| 87 | |
| 88 TEST_F(FormDataBytesConsumerTest, ReadFromString) | |
| 89 { | |
| 90 auto result = (new BytesConsumerTestUtil::Reader(new FormDataBytesConsumer(" hello, world")))->run(); | |
| 91 EXPECT_EQ(Result::Done, result.first); | |
| 92 EXPECT_EQ("hello, world", toString(result.second)); | |
| 93 } | |
| 94 | |
| 95 TEST_F(FormDataBytesConsumerTest, TwoPhaseReadFromString) | |
| 96 { | |
| 97 auto result = (new BytesConsumerTestUtil::TwoPhaseReader(new FormDataBytesCo nsumer("hello, world")))->run(); | |
| 98 EXPECT_EQ(Result::Done, result.first); | |
| 99 EXPECT_EQ("hello, world", toString(result.second)); | |
| 100 } | |
| 101 | |
| 102 TEST_F(FormDataBytesConsumerTest, ReadFromStringNonLatin) | |
| 103 { | |
| 104 constexpr UChar cs[] = {0x3042, 0}; | |
| 105 auto result = (new BytesConsumerTestUtil::Reader(new FormDataBytesConsumer(S tring(cs))))->run(); | |
| 106 EXPECT_EQ(Result::Done, result.first); | |
| 107 EXPECT_EQ("\xe3\x81\x82", toString(result.second)); | |
| 108 } | |
| 109 | |
| 110 TEST_F(FormDataBytesConsumerTest, ReadFromArrayBuffer) | |
| 111 { | |
| 112 constexpr unsigned char data[] = { 0x21, 0xfe, 0x00, 0x00, 0xff, 0xa3, 0x42, 0x30, 0x42, 0x99, 0x88 }; | |
| 113 DOMArrayBuffer* buffer = DOMArrayBuffer::create(data, WTF_ARRAY_LENGTH(data) ); | |
| 114 auto result = (new BytesConsumerTestUtil::Reader(new FormDataBytesConsumer(b uffer)))->run(); | |
| 115 Vector<char> expected; | |
| 116 expected.append(data, WTF_ARRAY_LENGTH(data)); | |
| 117 | |
| 118 EXPECT_EQ(Result::Done, result.first); | |
| 119 EXPECT_EQ(expected, result.second); | |
| 120 } | |
| 121 | |
| 122 TEST_F(FormDataBytesConsumerTest, ReadFromArrayBufferView) | |
| 123 { | |
| 124 constexpr unsigned char data[] = { 0x21, 0xfe, 0x00, 0x00, 0xff, 0xa3, 0x42, 0x30, 0x42, 0x99, 0x88 }; | |
| 125 constexpr size_t offset = 1, size = 4; | |
| 126 DOMArrayBuffer* buffer = DOMArrayBuffer::create(data, WTF_ARRAY_LENGTH(data) ); | |
| 127 auto result = (new BytesConsumerTestUtil::Reader(new FormDataBytesConsumer(D OMUint8Array::create(buffer, offset, size))))->run(); | |
| 128 Vector<char> expected; | |
| 129 expected.append(data + offset, size); | |
| 130 | |
| 131 EXPECT_EQ(Result::Done, result.first); | |
| 132 EXPECT_EQ(expected, result.second); | |
| 133 } | |
| 134 | |
| 135 TEST_F(FormDataBytesConsumerTest, ReadFromSimpleFormData) | |
| 136 { | |
| 137 RefPtr<EncodedFormData> data = EncodedFormData::create(); | |
| 138 data->appendData("foo", 3); | |
| 139 data->appendData("hoge", 4); | |
| 140 | |
| 141 auto result = (new BytesConsumerTestUtil::Reader(new FormDataBytesConsumer(g etDocument(), data)))->run(); | |
| 142 EXPECT_EQ(Result::Done, result.first); | |
| 143 EXPECT_EQ("foohoge", toString(result.second)); | |
| 144 } | |
| 145 | |
| 146 TEST_F(FormDataBytesConsumerTest, ReadFromComplexFormData) | |
| 147 { | |
| 148 RefPtr<EncodedFormData> data = complexFormData(); | |
| 149 MockBytesConsumer* underlying = MockBytesConsumer::create(); | |
| 150 BytesConsumer* consumer = FormDataBytesConsumer::createForTesting(getDocumen t(), data, underlying); | |
| 151 Checkpoint checkpoint; | |
| 152 | |
| 153 char c; | |
| 154 size_t read = 0; | |
| 155 | |
| 156 InSequence s; | |
| 157 EXPECT_CALL(checkpoint, Call(1)); | |
| 158 EXPECT_CALL(*underlying, beginRead(_, _)).WillOnce(DoAll(::testing::SetArgPo intee<1>(0), Return(Result::Ok))); | |
| 159 EXPECT_CALL(*underlying, endRead(0)).WillOnce(Return(Result::Ok)); | |
| 160 EXPECT_CALL(checkpoint, Call(2)); | |
| 161 | |
| 162 checkpoint.Call(1); | |
| 163 EXPECT_EQ(Result::Ok, consumer->read(&c, 1, &read)); | |
| 164 checkpoint.Call(2); | |
| 165 } | |
| 166 | |
| 167 TEST_F(FormDataBytesConsumerTest, TwoPhaseReadFromComplexFormData) | |
| 168 { | |
| 169 RefPtr<EncodedFormData> data = complexFormData(); | |
| 170 MockBytesConsumer* underlying = MockBytesConsumer::create(); | |
| 171 BytesConsumer* consumer = FormDataBytesConsumer::createForTesting(getDocumen t(), data, underlying); | |
| 172 Checkpoint checkpoint; | |
| 173 | |
| 174 const char* buffer = nullptr; | |
| 175 size_t available = 0; | |
| 176 | |
| 177 InSequence s; | |
| 178 EXPECT_CALL(checkpoint, Call(1)); | |
| 179 EXPECT_CALL(*underlying, beginRead(&buffer, &available)).WillOnce(Return(Res ult::Ok)); | |
| 180 EXPECT_CALL(checkpoint, Call(2)); | |
| 181 EXPECT_CALL(*underlying, endRead(0)).WillOnce(Return(Result::Ok)); | |
| 182 EXPECT_CALL(checkpoint, Call(3)); | |
| 183 | |
| 184 checkpoint.Call(1); | |
| 185 ASSERT_EQ(Result::Ok, consumer->beginRead(&buffer, &available)); | |
| 186 checkpoint.Call(2); | |
| 187 EXPECT_EQ(Result::Ok, consumer->endRead(0)); | |
| 188 checkpoint.Call(3); | |
| 189 } | |
| 190 | |
| 191 TEST_F(FormDataBytesConsumerTest, DrainAsBlobDataHandleFromString) | |
| 192 { | |
| 193 BytesConsumer* consumer = new FormDataBytesConsumer("hello, world"); | |
| 194 RefPtr<BlobDataHandle> blobDataHandle = consumer->drainAsBlobDataHandle(); | |
| 195 ASSERT_TRUE(blobDataHandle); | |
| 196 | |
| 197 EXPECT_EQ(String(), blobDataHandle->type()); | |
| 198 EXPECT_EQ(12u, blobDataHandle->size()); | |
| 199 EXPECT_FALSE(consumer->drainAsFormData()); | |
| 200 char c; | |
| 201 size_t readSize; | |
| 202 EXPECT_EQ(Result::Done, consumer->read(&c, 1, &readSize)); | |
| 203 EXPECT_EQ(BytesConsumer::PublicState::Closed, consumer->getPublicState()); | |
| 204 } | |
| 205 | |
| 206 TEST_F(FormDataBytesConsumerTest, DrainAsBlobDataHandleFromArrayBuffer) | |
| 207 { | |
| 208 BytesConsumer* consumer = new FormDataBytesConsumer(DOMArrayBuffer::create(" foo", 3)); | |
| 209 RefPtr<BlobDataHandle> blobDataHandle = consumer->drainAsBlobDataHandle(); | |
| 210 ASSERT_TRUE(blobDataHandle); | |
| 211 | |
| 212 EXPECT_EQ(String(), blobDataHandle->type()); | |
| 213 EXPECT_EQ(3u, blobDataHandle->size()); | |
| 214 EXPECT_FALSE(consumer->drainAsFormData()); | |
| 215 char c; | |
| 216 size_t readSize; | |
| 217 EXPECT_EQ(Result::Done, consumer->read(&c, 1, &readSize)); | |
| 218 EXPECT_EQ(BytesConsumer::PublicState::Closed, consumer->getPublicState()); | |
| 219 } | |
| 220 | |
| 221 TEST_F(FormDataBytesConsumerTest, DrainAsBlobDataHandleFromSimpleFormData) | |
| 222 { | |
| 223 FormData* data = FormData::create(UTF8Encoding()); | |
| 224 data->append("name1", "value1"); | |
| 225 data->append("name2", "value2"); | |
| 226 RefPtr<EncodedFormData> inputFormData = data->encodeMultiPartFormData(); | |
| 227 | |
| 228 BytesConsumer* consumer = new FormDataBytesConsumer(getDocument(), inputForm Data); | |
| 229 RefPtr<BlobDataHandle> blobDataHandle = consumer->drainAsBlobDataHandle(); | |
| 230 ASSERT_TRUE(blobDataHandle); | |
| 231 | |
| 232 EXPECT_EQ(String(), blobDataHandle->type()); | |
| 233 EXPECT_EQ(inputFormData->flattenToString().utf8().length(), blobDataHandle-> size()); | |
| 234 EXPECT_FALSE(consumer->drainAsFormData()); | |
| 235 char c; | |
| 236 size_t readSize; | |
| 237 EXPECT_EQ(Result::Done, consumer->read(&c, 1, &readSize)); | |
| 238 EXPECT_EQ(BytesConsumer::PublicState::Closed, consumer->getPublicState()); | |
| 239 } | |
| 240 | |
| 241 TEST_F(FormDataBytesConsumerTest, DrainAsBlobDataHandleFromComplexFormData) | |
| 242 { | |
| 243 RefPtr<EncodedFormData> inputFormData = complexFormData(); | |
| 244 | |
| 245 BytesConsumer* consumer = new FormDataBytesConsumer(getDocument(), inputForm Data); | |
| 246 RefPtr<BlobDataHandle> blobDataHandle = consumer->drainAsBlobDataHandle(); | |
| 247 ASSERT_TRUE(blobDataHandle); | |
| 248 | |
| 249 EXPECT_FALSE(consumer->drainAsFormData()); | |
| 250 char c; | |
| 251 size_t readSize; | |
| 252 EXPECT_EQ(Result::Done, consumer->read(&c, 1, &readSize)); | |
| 253 EXPECT_EQ(BytesConsumer::PublicState::Closed, consumer->getPublicState()); | |
| 254 } | |
| 255 | |
| 256 TEST_F(FormDataBytesConsumerTest, DrainAsFormDataFromString) | |
| 257 { | |
| 258 BytesConsumer* consumer = new FormDataBytesConsumer("hello, world"); | |
| 259 RefPtr<EncodedFormData> formData = consumer->drainAsFormData(); | |
| 260 ASSERT_TRUE(formData); | |
| 261 EXPECT_EQ("hello, world", formData->flattenToString()); | |
| 262 | |
| 263 EXPECT_FALSE(consumer->drainAsBlobDataHandle()); | |
| 264 const char* buffer = nullptr; | |
| 265 size_t size; | |
| 266 EXPECT_EQ(Result::Done, consumer->read(nullptr, 0, &size)); | |
| 267 EXPECT_EQ(Result::Done, consumer->beginRead(&buffer, &size)); | |
| 268 EXPECT_EQ(BytesConsumer::PublicState::Closed, consumer->getPublicState()); | |
| 269 } | |
| 270 | |
| 271 TEST_F(FormDataBytesConsumerTest, DrainAsFormDataFromArrayBuffer) | |
| 272 { | |
| 273 BytesConsumer* consumer = new FormDataBytesConsumer(DOMArrayBuffer::create(" foo", 3)); | |
| 274 RefPtr<EncodedFormData> formData = consumer->drainAsFormData(); | |
| 275 ASSERT_TRUE(formData); | |
| 276 EXPECT_TRUE(formData->isSafeToSendToAnotherThread()); | |
| 277 EXPECT_EQ("foo", formData->flattenToString()); | |
| 278 | |
| 279 EXPECT_FALSE(consumer->drainAsBlobDataHandle()); | |
| 280 const char* buffer = nullptr; | |
| 281 size_t size; | |
| 282 EXPECT_EQ(Result::Done, consumer->read(nullptr, 0, &size)); | |
| 283 EXPECT_EQ(Result::Done, consumer->beginRead(&buffer, &size)); | |
| 284 EXPECT_EQ(BytesConsumer::PublicState::Closed, consumer->getPublicState()); | |
| 285 } | |
| 286 | |
| 287 TEST_F(FormDataBytesConsumerTest, DrainAsFormDataFromSimpleFormData) | |
| 288 { | |
| 289 FormData* data = FormData::create(UTF8Encoding()); | |
| 290 data->append("name1", "value1"); | |
| 291 data->append("name2", "value2"); | |
| 292 RefPtr<EncodedFormData> inputFormData = data->encodeMultiPartFormData(); | |
| 293 | |
| 294 BytesConsumer* consumer = new FormDataBytesConsumer(getDocument(), inputForm Data); | |
| 295 EXPECT_EQ(inputFormData, consumer->drainAsFormData()); | |
| 296 EXPECT_FALSE(consumer->drainAsBlobDataHandle()); | |
| 297 const char* buffer = nullptr; | |
| 298 size_t size; | |
| 299 EXPECT_EQ(Result::Done, consumer->read(nullptr, 0, &size)); | |
| 300 EXPECT_EQ(Result::Done, consumer->beginRead(&buffer, &size)); | |
| 301 EXPECT_EQ(BytesConsumer::PublicState::Closed, consumer->getPublicState()); | |
| 302 } | |
| 303 | |
| 304 TEST_F(FormDataBytesConsumerTest, DrainAsFormDataFromComplexFormData) | |
| 305 { | |
| 306 RefPtr<EncodedFormData> inputFormData = complexFormData(); | |
| 307 | |
| 308 BytesConsumer* consumer = new FormDataBytesConsumer(getDocument(), inputForm Data); | |
| 309 EXPECT_EQ(inputFormData, consumer->drainAsFormData()); | |
| 310 EXPECT_FALSE(consumer->drainAsBlobDataHandle()); | |
| 311 const char* buffer = nullptr; | |
| 312 size_t size; | |
| 313 EXPECT_EQ(Result::Done, consumer->read(nullptr, 0, &size)); | |
| 314 EXPECT_EQ(Result::Done, consumer->beginRead(&buffer, &size)); | |
| 315 EXPECT_EQ(BytesConsumer::PublicState::Closed, consumer->getPublicState()); | |
| 316 } | |
| 317 | |
| 318 TEST_F(FormDataBytesConsumerTest, ReadAffectsDraining) | |
| 319 { | |
| 320 char c; | |
| 321 size_t readSize = 0; | |
| 322 BytesConsumer* consumer = new FormDataBytesConsumer("hello, world"); | |
| 323 EXPECT_EQ(Result::Ok, consumer->read(&c, 0, &readSize)); | |
| 324 EXPECT_EQ(0u, readSize); | |
| 325 EXPECT_FALSE(consumer->drainAsFormData()); | |
| 326 EXPECT_FALSE(consumer->drainAsBlobDataHandle()); | |
| 327 EXPECT_EQ(BytesConsumer::PublicState::ReadableOrWaiting, consumer->getPublic State()); | |
| 328 } | |
| 329 | |
| 330 TEST_F(FormDataBytesConsumerTest, BeginReadAffectsDraining) | |
| 331 { | |
| 332 const char* buffer = nullptr; | |
| 333 size_t available = 0; | |
| 334 BytesConsumer* consumer = new FormDataBytesConsumer("hello, world"); | |
| 335 ASSERT_EQ(Result::Ok, consumer->beginRead(&buffer, &available)); | |
| 336 EXPECT_EQ("hello, world", String(buffer, available)); | |
| 337 | |
| 338 ASSERT_EQ(Result::Ok, consumer->endRead(0)); | |
| 339 EXPECT_FALSE(consumer->drainAsFormData()); | |
| 340 EXPECT_FALSE(consumer->drainAsBlobDataHandle()); | |
| 341 EXPECT_EQ(BytesConsumer::PublicState::ReadableOrWaiting, consumer->getPublic State()); | |
| 342 } | |
| 343 | |
| 344 TEST_F(FormDataBytesConsumerTest, BeginReadAffectsDrainingWithComplexFormData) | |
| 345 { | |
| 346 MockBytesConsumer* underlying = MockBytesConsumer::create(); | |
| 347 BytesConsumer* consumer = FormDataBytesConsumer::createForTesting(getDocumen t(), complexFormData(), underlying); | |
| 348 | |
| 349 const char* buffer = nullptr; | |
| 350 size_t available = 0; | |
| 351 Checkpoint checkpoint; | |
| 352 | |
| 353 InSequence s; | |
| 354 EXPECT_CALL(checkpoint, Call(1)); | |
| 355 EXPECT_CALL(*underlying, beginRead(&buffer, &available)).WillOnce(Return(Res ult::Ok)); | |
| 356 EXPECT_CALL(*underlying, endRead(0)).WillOnce(Return(Result::Ok)); | |
| 357 EXPECT_CALL(checkpoint, Call(2)); | |
| 358 // drainAsFormData / drainAsBlobDataHandle should not be called here. | |
| 359 EXPECT_CALL(checkpoint, Call(3)); | |
| 360 | |
| 361 checkpoint.Call(1); | |
| 362 ASSERT_EQ(Result::Ok, consumer->beginRead(&buffer, &available)); | |
| 363 ASSERT_EQ(Result::Ok, consumer->endRead(0)); | |
| 364 checkpoint.Call(2); | |
| 365 EXPECT_FALSE(consumer->drainAsFormData()); | |
| 366 EXPECT_FALSE(consumer->drainAsBlobDataHandle()); | |
| 367 checkpoint.Call(3); | |
|
hiroshige
2016/09/16 07:24:34
Could you test getPublicState() here?
yhirano
2016/09/21 03:16:16
In this case consumer->getPublicState() is simply
hiroshige
2016/09/21 07:42:19
I think at least we should test getPublicState() d
yhirano
2016/09/21 08:02:12
Done.
| |
| 368 } | |
| 369 | |
| 370 TEST_F(FormDataBytesConsumerTest, SetClientWithComplexFormData) | |
| 371 { | |
| 372 RefPtr<EncodedFormData> inputFormData = complexFormData(); | |
| 373 | |
| 374 MockBytesConsumer* underlying = MockBytesConsumer::create(); | |
| 375 BytesConsumer* consumer = FormDataBytesConsumer::createForTesting(getDocumen t(), inputFormData, underlying); | |
| 376 Checkpoint checkpoint; | |
| 377 | |
| 378 InSequence s; | |
| 379 EXPECT_CALL(checkpoint, Call(1)); | |
| 380 EXPECT_CALL(*underlying, setClient(_)); | |
| 381 EXPECT_CALL(checkpoint, Call(2)); | |
| 382 EXPECT_CALL(*underlying, clearClient()); | |
| 383 EXPECT_CALL(checkpoint, Call(3)); | |
| 384 | |
| 385 checkpoint.Call(1); | |
| 386 consumer->setClient(new NoopClient()); | |
| 387 checkpoint.Call(2); | |
| 388 consumer->clearClient(); | |
| 389 checkpoint.Call(3); | |
| 390 } | |
| 391 | |
| 392 TEST_F(FormDataBytesConsumerTest, CancelWithComplexFormData) | |
| 393 { | |
| 394 RefPtr<EncodedFormData> inputFormData = complexFormData(); | |
| 395 | |
| 396 MockBytesConsumer* underlying = MockBytesConsumer::create(); | |
| 397 BytesConsumer* consumer = FormDataBytesConsumer::createForTesting(getDocumen t(), inputFormData, underlying); | |
| 398 Checkpoint checkpoint; | |
| 399 | |
| 400 InSequence s; | |
| 401 EXPECT_CALL(checkpoint, Call(1)); | |
| 402 EXPECT_CALL(*underlying, cancel()); | |
| 403 EXPECT_CALL(checkpoint, Call(2)); | |
| 404 | |
| 405 checkpoint.Call(1); | |
| 406 consumer->cancel(); | |
| 407 checkpoint.Call(2); | |
| 408 } | |
| 409 | |
| 410 } // namespace | |
| 411 } // namespace blink | |
| OLD | NEW |