OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 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 "modules/encryptedmedia/HTMLMediaElementEncryptedMedia.h" | 5 #include "modules/encryptedmedia/HTMLMediaElementEncryptedMedia.h" |
6 | 6 |
7 #include "bindings/core/v8/ExceptionState.h" | 7 #include "bindings/core/v8/ExceptionState.h" |
8 #include "bindings/core/v8/ScriptPromise.h" | 8 #include "bindings/core/v8/ScriptPromise.h" |
9 #include "bindings/core/v8/ScriptPromiseResolver.h" | 9 #include "bindings/core/v8/ScriptPromiseResolver.h" |
10 #include "bindings/core/v8/ScriptState.h" | 10 #include "bindings/core/v8/ScriptState.h" |
(...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
111 return handler->promise(); | 111 return handler->promise(); |
112 } | 112 } |
113 | 113 |
114 SetMediaKeysHandler::SetMediaKeysHandler(ScriptState* scriptState, HTMLMediaElem
ent& element, MediaKeys* mediaKeys) | 114 SetMediaKeysHandler::SetMediaKeysHandler(ScriptState* scriptState, HTMLMediaElem
ent& element, MediaKeys* mediaKeys) |
115 : ScriptPromiseResolver(scriptState) | 115 : ScriptPromiseResolver(scriptState) |
116 , m_element(element) | 116 , m_element(element) |
117 , m_newMediaKeys(mediaKeys) | 117 , m_newMediaKeys(mediaKeys) |
118 , m_madeReservation(false) | 118 , m_madeReservation(false) |
119 , m_timer(this, &SetMediaKeysHandler::timerFired) | 119 , m_timer(this, &SetMediaKeysHandler::timerFired) |
120 { | 120 { |
121 DVLOG(EME_LOG_LEVEL) << __FUNCTION__; | 121 DVLOG(EME_LOG_LEVEL) << __func__; |
122 | 122 |
123 // 5. Run the following steps in parallel. | 123 // 5. Run the following steps in parallel. |
124 m_timer.startOneShot(0, BLINK_FROM_HERE); | 124 m_timer.startOneShot(0, BLINK_FROM_HERE); |
125 } | 125 } |
126 | 126 |
127 SetMediaKeysHandler::~SetMediaKeysHandler() | 127 SetMediaKeysHandler::~SetMediaKeysHandler() |
128 { | 128 { |
129 } | 129 } |
130 | 130 |
131 void SetMediaKeysHandler::timerFired(Timer<SetMediaKeysHandler>*) | 131 void SetMediaKeysHandler::timerFired(Timer<SetMediaKeysHandler>*) |
132 { | 132 { |
133 clearExistingMediaKeys(); | 133 clearExistingMediaKeys(); |
134 } | 134 } |
135 | 135 |
136 void SetMediaKeysHandler::clearExistingMediaKeys() | 136 void SetMediaKeysHandler::clearExistingMediaKeys() |
137 { | 137 { |
138 DVLOG(EME_LOG_LEVEL) << __FUNCTION__; | 138 DVLOG(EME_LOG_LEVEL) << __func__; |
139 HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia
::from(*m_element); | 139 HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia
::from(*m_element); |
140 | 140 |
141 // 5.1 If mediaKeys is not null, the CDM instance represented by | 141 // 5.1 If mediaKeys is not null, the CDM instance represented by |
142 // mediaKeys is already in use by another media element, and | 142 // mediaKeys is already in use by another media element, and |
143 // the user agent is unable to use it with this element, let | 143 // the user agent is unable to use it with this element, let |
144 // this object's attaching media keys value be false and | 144 // this object's attaching media keys value be false and |
145 // reject promise with a QuotaExceededError. | 145 // reject promise with a QuotaExceededError. |
146 if (m_newMediaKeys) { | 146 if (m_newMediaKeys) { |
147 if (!m_newMediaKeys->reserveForMediaElement(m_element.get())) { | 147 if (!m_newMediaKeys->reserveForMediaElement(m_element.get())) { |
148 thisElement.m_isAttachingMediaKeys = false; | 148 thisElement.m_isAttachingMediaKeys = false; |
(...skipping 28 matching lines...) Expand all Loading... |
177 return; | 177 return; |
178 } | 178 } |
179 } | 179 } |
180 | 180 |
181 // MediaKeys not currently set or no player connected, so continue on. | 181 // MediaKeys not currently set or no player connected, so continue on. |
182 setNewMediaKeys(); | 182 setNewMediaKeys(); |
183 } | 183 } |
184 | 184 |
185 void SetMediaKeysHandler::setNewMediaKeys() | 185 void SetMediaKeysHandler::setNewMediaKeys() |
186 { | 186 { |
187 DVLOG(EME_LOG_LEVEL) << __FUNCTION__; | 187 DVLOG(EME_LOG_LEVEL) << __func__; |
188 | 188 |
189 // 5.3 If mediaKeys is not null, run the following steps: | 189 // 5.3 If mediaKeys is not null, run the following steps: |
190 if (m_newMediaKeys) { | 190 if (m_newMediaKeys) { |
191 // 5.3.1 Associate the CDM instance represented by mediaKeys with the | 191 // 5.3.1 Associate the CDM instance represented by mediaKeys with the |
192 // media element for decrypting media data. | 192 // media element for decrypting media data. |
193 // 5.3.2 If the preceding step failed, run the following steps: | 193 // 5.3.2 If the preceding step failed, run the following steps: |
194 // (done in setFailed()). | 194 // (done in setFailed()). |
195 // 5.3.3 Queue a task to run the Attempt to Resume Playback If Necessary | 195 // 5.3.3 Queue a task to run the Attempt to Resume Playback If Necessary |
196 // algorithm on the media element. | 196 // algorithm on the media element. |
197 // (Handled in Chromium). | 197 // (Handled in Chromium). |
198 if (m_element->webMediaPlayer()) { | 198 if (m_element->webMediaPlayer()) { |
199 std::unique_ptr<SuccessCallback> successCallback = WTF::bind(&SetMed
iaKeysHandler::finish, wrapPersistent(this)); | 199 std::unique_ptr<SuccessCallback> successCallback = WTF::bind(&SetMed
iaKeysHandler::finish, wrapPersistent(this)); |
200 std::unique_ptr<FailureCallback> failureCallback = WTF::bind(&SetMed
iaKeysHandler::setFailed, wrapPersistent(this)); | 200 std::unique_ptr<FailureCallback> failureCallback = WTF::bind(&SetMed
iaKeysHandler::setFailed, wrapPersistent(this)); |
201 ContentDecryptionModuleResult* result = new SetContentDecryptionModu
leResult(std::move(successCallback), std::move(failureCallback)); | 201 ContentDecryptionModuleResult* result = new SetContentDecryptionModu
leResult(std::move(successCallback), std::move(failureCallback)); |
202 m_element->webMediaPlayer()->setContentDecryptionModule(m_newMediaKe
ys->contentDecryptionModule(), result->result()); | 202 m_element->webMediaPlayer()->setContentDecryptionModule(m_newMediaKe
ys->contentDecryptionModule(), result->result()); |
203 | 203 |
204 // Don't do anything more until |result| is resolved (or rejected). | 204 // Don't do anything more until |result| is resolved (or rejected). |
205 return; | 205 return; |
206 } | 206 } |
207 } | 207 } |
208 | 208 |
209 // MediaKeys doesn't need to be set on the player, so continue on. | 209 // MediaKeys doesn't need to be set on the player, so continue on. |
210 finish(); | 210 finish(); |
211 } | 211 } |
212 | 212 |
213 void SetMediaKeysHandler::finish() | 213 void SetMediaKeysHandler::finish() |
214 { | 214 { |
215 DVLOG(EME_LOG_LEVEL) << __FUNCTION__; | 215 DVLOG(EME_LOG_LEVEL) << __func__; |
216 HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia
::from(*m_element); | 216 HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia
::from(*m_element); |
217 | 217 |
218 // 5.4 Set the mediaKeys attribute to mediaKeys. | 218 // 5.4 Set the mediaKeys attribute to mediaKeys. |
219 if (thisElement.m_mediaKeys) | 219 if (thisElement.m_mediaKeys) |
220 thisElement.m_mediaKeys->clearMediaElement(); | 220 thisElement.m_mediaKeys->clearMediaElement(); |
221 thisElement.m_mediaKeys = m_newMediaKeys; | 221 thisElement.m_mediaKeys = m_newMediaKeys; |
222 if (m_madeReservation) | 222 if (m_madeReservation) |
223 m_newMediaKeys->acceptReservation(); | 223 m_newMediaKeys->acceptReservation(); |
224 | 224 |
225 // 5.5 Let this object's attaching media keys value be false. | 225 // 5.5 Let this object's attaching media keys value be false. |
(...skipping 11 matching lines...) Expand all Loading... |
237 | 237 |
238 // Make sure attaching media keys value is false. | 238 // Make sure attaching media keys value is false. |
239 DCHECK(!HTMLMediaElementEncryptedMedia::from(*m_element).m_isAttachingMediaK
eys); | 239 DCHECK(!HTMLMediaElementEncryptedMedia::from(*m_element).m_isAttachingMediaK
eys); |
240 | 240 |
241 // Reject promise with an appropriate error. | 241 // Reject promise with an appropriate error. |
242 reject(DOMException::create(code, errorMessage)); | 242 reject(DOMException::create(code, errorMessage)); |
243 } | 243 } |
244 | 244 |
245 void SetMediaKeysHandler::clearFailed(ExceptionCode code, const String& errorMes
sage) | 245 void SetMediaKeysHandler::clearFailed(ExceptionCode code, const String& errorMes
sage) |
246 { | 246 { |
247 DVLOG(EME_LOG_LEVEL) << __FUNCTION__ << "(" << code << ", " << errorMessage
<< ")"; | 247 DVLOG(EME_LOG_LEVEL) << __func__ << "(" << code << ", " << errorMessage << "
)"; |
248 HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia
::from(*m_element); | 248 HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia
::from(*m_element); |
249 | 249 |
250 // 5.2.4 If the preceding step failed, let this object's attaching media | 250 // 5.2.4 If the preceding step failed, let this object's attaching media |
251 // keys value be false and reject promise with an appropriate | 251 // keys value be false and reject promise with an appropriate |
252 // error name. | 252 // error name. |
253 thisElement.m_isAttachingMediaKeys = false; | 253 thisElement.m_isAttachingMediaKeys = false; |
254 fail(code, errorMessage); | 254 fail(code, errorMessage); |
255 } | 255 } |
256 | 256 |
257 void SetMediaKeysHandler::setFailed(ExceptionCode code, const String& errorMessa
ge) | 257 void SetMediaKeysHandler::setFailed(ExceptionCode code, const String& errorMessa
ge) |
258 { | 258 { |
259 DVLOG(EME_LOG_LEVEL) << __FUNCTION__ << "(" << code << ", " << errorMessage
<< ")"; | 259 DVLOG(EME_LOG_LEVEL) << __func__ << "(" << code << ", " << errorMessage << "
)"; |
260 HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia
::from(*m_element); | 260 HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia
::from(*m_element); |
261 | 261 |
262 // 5.3.2 If the preceding step failed (in setContentDecryptionModule() | 262 // 5.3.2 If the preceding step failed (in setContentDecryptionModule() |
263 // called from setNewMediaKeys()), run the following steps: | 263 // called from setNewMediaKeys()), run the following steps: |
264 // 5.3.2.1 Set the mediaKeys attribute to null. | 264 // 5.3.2.1 Set the mediaKeys attribute to null. |
265 thisElement.m_mediaKeys.clear(); | 265 thisElement.m_mediaKeys.clear(); |
266 | 266 |
267 // 5.3.2.2 Let this object's attaching media keys value be false. | 267 // 5.3.2.2 Let this object's attaching media keys value be false. |
268 thisElement.m_isAttachingMediaKeys = false; | 268 thisElement.m_isAttachingMediaKeys = false; |
269 | 269 |
(...skipping 11 matching lines...) Expand all Loading... |
281 | 281 |
282 HTMLMediaElementEncryptedMedia::HTMLMediaElementEncryptedMedia(HTMLMediaElement&
element) | 282 HTMLMediaElementEncryptedMedia::HTMLMediaElementEncryptedMedia(HTMLMediaElement&
element) |
283 : m_mediaElement(&element) | 283 : m_mediaElement(&element) |
284 , m_isWaitingForKey(false) | 284 , m_isWaitingForKey(false) |
285 , m_isAttachingMediaKeys(false) | 285 , m_isAttachingMediaKeys(false) |
286 { | 286 { |
287 } | 287 } |
288 | 288 |
289 HTMLMediaElementEncryptedMedia::~HTMLMediaElementEncryptedMedia() | 289 HTMLMediaElementEncryptedMedia::~HTMLMediaElementEncryptedMedia() |
290 { | 290 { |
291 DVLOG(EME_LOG_LEVEL) << __FUNCTION__; | 291 DVLOG(EME_LOG_LEVEL) << __func__; |
292 } | 292 } |
293 | 293 |
294 const char* HTMLMediaElementEncryptedMedia::supplementName() | 294 const char* HTMLMediaElementEncryptedMedia::supplementName() |
295 { | 295 { |
296 return "HTMLMediaElementEncryptedMedia"; | 296 return "HTMLMediaElementEncryptedMedia"; |
297 } | 297 } |
298 | 298 |
299 HTMLMediaElementEncryptedMedia& HTMLMediaElementEncryptedMedia::from(HTMLMediaEl
ement& element) | 299 HTMLMediaElementEncryptedMedia& HTMLMediaElementEncryptedMedia::from(HTMLMediaEl
ement& element) |
300 { | 300 { |
301 HTMLMediaElementEncryptedMedia* supplement = static_cast<HTMLMediaElementEnc
ryptedMedia*>(Supplement<HTMLMediaElement>::from(element, supplementName())); | 301 HTMLMediaElementEncryptedMedia* supplement = static_cast<HTMLMediaElementEnc
ryptedMedia*>(Supplement<HTMLMediaElement>::from(element, supplementName())); |
302 if (!supplement) { | 302 if (!supplement) { |
303 supplement = new HTMLMediaElementEncryptedMedia(element); | 303 supplement = new HTMLMediaElementEncryptedMedia(element); |
304 provideTo(element, supplementName(), supplement); | 304 provideTo(element, supplementName(), supplement); |
305 } | 305 } |
306 return *supplement; | 306 return *supplement; |
307 } | 307 } |
308 | 308 |
309 MediaKeys* HTMLMediaElementEncryptedMedia::mediaKeys(HTMLMediaElement& element) | 309 MediaKeys* HTMLMediaElementEncryptedMedia::mediaKeys(HTMLMediaElement& element) |
310 { | 310 { |
311 HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia
::from(element); | 311 HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia
::from(element); |
312 return thisElement.m_mediaKeys.get(); | 312 return thisElement.m_mediaKeys.get(); |
313 } | 313 } |
314 | 314 |
315 ScriptPromise HTMLMediaElementEncryptedMedia::setMediaKeys(ScriptState* scriptSt
ate, HTMLMediaElement& element, MediaKeys* mediaKeys) | 315 ScriptPromise HTMLMediaElementEncryptedMedia::setMediaKeys(ScriptState* scriptSt
ate, HTMLMediaElement& element, MediaKeys* mediaKeys) |
316 { | 316 { |
317 HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia
::from(element); | 317 HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia
::from(element); |
318 DVLOG(EME_LOG_LEVEL) << __FUNCTION__ << " current(" << thisElement.m_mediaKe
ys.get() << "), new(" << mediaKeys << ")"; | 318 DVLOG(EME_LOG_LEVEL) << __func__ << " current(" << thisElement.m_mediaKeys.g
et() << "), new(" << mediaKeys << ")"; |
319 | 319 |
320 // From http://w3c.github.io/encrypted-media/#setMediaKeys | 320 // From http://w3c.github.io/encrypted-media/#setMediaKeys |
321 | 321 |
322 // 1. If mediaKeys and the mediaKeys attribute are the same object, | 322 // 1. If mediaKeys and the mediaKeys attribute are the same object, |
323 // return a resolved promise. | 323 // return a resolved promise. |
324 if (thisElement.m_mediaKeys == mediaKeys) | 324 if (thisElement.m_mediaKeys == mediaKeys) |
325 return ScriptPromise::castUndefined(scriptState); | 325 return ScriptPromise::castUndefined(scriptState); |
326 | 326 |
327 // 2. If this object's attaching media keys value is true, return a | 327 // 2. If this object's attaching media keys value is true, return a |
328 // promise rejected with an InvalidStateError. | 328 // promise rejected with an InvalidStateError. |
(...skipping 16 matching lines...) Expand all Loading... |
345 initializer.setInitDataType(EncryptedMediaUtils::convertFromInitDataType(ini
tDataType)); | 345 initializer.setInitDataType(EncryptedMediaUtils::convertFromInitDataType(ini
tDataType)); |
346 initializer.setInitData(DOMArrayBuffer::create(initData, initDataLength)); | 346 initializer.setInitData(DOMArrayBuffer::create(initData, initDataLength)); |
347 initializer.setBubbles(false); | 347 initializer.setBubbles(false); |
348 initializer.setCancelable(false); | 348 initializer.setCancelable(false); |
349 | 349 |
350 return MediaEncryptedEvent::create(EventTypeNames::encrypted, initializer); | 350 return MediaEncryptedEvent::create(EventTypeNames::encrypted, initializer); |
351 } | 351 } |
352 | 352 |
353 void HTMLMediaElementEncryptedMedia::encrypted(WebEncryptedMediaInitDataType ini
tDataType, const unsigned char* initData, unsigned initDataLength) | 353 void HTMLMediaElementEncryptedMedia::encrypted(WebEncryptedMediaInitDataType ini
tDataType, const unsigned char* initData, unsigned initDataLength) |
354 { | 354 { |
355 DVLOG(EME_LOG_LEVEL) << __FUNCTION__; | 355 DVLOG(EME_LOG_LEVEL) << __func__; |
356 | 356 |
357 Event* event; | 357 Event* event; |
358 if (m_mediaElement->isMediaDataCORSSameOrigin(m_mediaElement->getExecutionCo
ntext()->getSecurityOrigin())) { | 358 if (m_mediaElement->isMediaDataCORSSameOrigin(m_mediaElement->getExecutionCo
ntext()->getSecurityOrigin())) { |
359 event = createEncryptedEvent(initDataType, initData, initDataLength); | 359 event = createEncryptedEvent(initDataType, initData, initDataLength); |
360 } else { | 360 } else { |
361 // Current page is not allowed to see content from the media file, | 361 // Current page is not allowed to see content from the media file, |
362 // so don't return the initData. However, they still get an event. | 362 // so don't return the initData. However, they still get an event. |
363 event = createEncryptedEvent(WebEncryptedMediaInitDataType::Unknown, nul
lptr, 0); | 363 event = createEncryptedEvent(WebEncryptedMediaInitDataType::Unknown, nul
lptr, 0); |
364 } | 364 } |
365 | 365 |
366 event->setTarget(m_mediaElement); | 366 event->setTarget(m_mediaElement); |
367 m_mediaElement->scheduleEvent(event); | 367 m_mediaElement->scheduleEvent(event); |
368 } | 368 } |
369 | 369 |
370 void HTMLMediaElementEncryptedMedia::didBlockPlaybackWaitingForKey() | 370 void HTMLMediaElementEncryptedMedia::didBlockPlaybackWaitingForKey() |
371 { | 371 { |
372 DVLOG(EME_LOG_LEVEL) << __FUNCTION__; | 372 DVLOG(EME_LOG_LEVEL) << __func__; |
373 | 373 |
374 // From https://w3c.github.io/encrypted-media/#queue-waitingforkey: | 374 // From https://w3c.github.io/encrypted-media/#queue-waitingforkey: |
375 // It should only be called when the HTMLMediaElement object is potentially | 375 // It should only be called when the HTMLMediaElement object is potentially |
376 // playing and its readyState is equal to HAVE_FUTURE_DATA or greater. | 376 // playing and its readyState is equal to HAVE_FUTURE_DATA or greater. |
377 // FIXME: Is this really required? | 377 // FIXME: Is this really required? |
378 | 378 |
379 // 1. Let the media element be the specified HTMLMediaElement object. | 379 // 1. Let the media element be the specified HTMLMediaElement object. |
380 // 2. If the media element's waiting for key value is false, queue a task | 380 // 2. If the media element's waiting for key value is false, queue a task |
381 // to fire a simple event named waitingforkey at the media element. | 381 // to fire a simple event named waitingforkey at the media element. |
382 if (!m_isWaitingForKey) { | 382 if (!m_isWaitingForKey) { |
383 Event* event = Event::create(EventTypeNames::waitingforkey); | 383 Event* event = Event::create(EventTypeNames::waitingforkey); |
384 event->setTarget(m_mediaElement); | 384 event->setTarget(m_mediaElement); |
385 m_mediaElement->scheduleEvent(event); | 385 m_mediaElement->scheduleEvent(event); |
386 } | 386 } |
387 | 387 |
388 // 3. Set the media element's waiting for key value to true. | 388 // 3. Set the media element's waiting for key value to true. |
389 m_isWaitingForKey = true; | 389 m_isWaitingForKey = true; |
390 | 390 |
391 // 4. Suspend playback. | 391 // 4. Suspend playback. |
392 // (Already done on the Chromium side by the decryptors.) | 392 // (Already done on the Chromium side by the decryptors.) |
393 } | 393 } |
394 | 394 |
395 void HTMLMediaElementEncryptedMedia::didResumePlaybackBlockedForKey() | 395 void HTMLMediaElementEncryptedMedia::didResumePlaybackBlockedForKey() |
396 { | 396 { |
397 DVLOG(EME_LOG_LEVEL) << __FUNCTION__; | 397 DVLOG(EME_LOG_LEVEL) << __func__; |
398 | 398 |
399 // Logic is on the Chromium side to attempt to resume playback when a new | 399 // Logic is on the Chromium side to attempt to resume playback when a new |
400 // key is available. However, |m_isWaitingForKey| needs to be cleared so | 400 // key is available. However, |m_isWaitingForKey| needs to be cleared so |
401 // that a later waitingForKey() call can generate the event. | 401 // that a later waitingForKey() call can generate the event. |
402 m_isWaitingForKey = false; | 402 m_isWaitingForKey = false; |
403 } | 403 } |
404 | 404 |
405 WebContentDecryptionModule* HTMLMediaElementEncryptedMedia::contentDecryptionMod
ule() | 405 WebContentDecryptionModule* HTMLMediaElementEncryptedMedia::contentDecryptionMod
ule() |
406 { | 406 { |
407 return m_mediaKeys ? m_mediaKeys->contentDecryptionModule() : 0; | 407 return m_mediaKeys ? m_mediaKeys->contentDecryptionModule() : 0; |
408 } | 408 } |
409 | 409 |
410 DEFINE_TRACE(HTMLMediaElementEncryptedMedia) | 410 DEFINE_TRACE(HTMLMediaElementEncryptedMedia) |
411 { | 411 { |
412 visitor->trace(m_mediaElement); | 412 visitor->trace(m_mediaElement); |
413 visitor->trace(m_mediaKeys); | 413 visitor->trace(m_mediaKeys); |
414 Supplement<HTMLMediaElement>::trace(visitor); | 414 Supplement<HTMLMediaElement>::trace(visitor); |
415 } | 415 } |
416 | 416 |
417 } // namespace blink | 417 } // namespace blink |
OLD | NEW |