Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 /* | 1 /* |
| 2 * Copyright (C) 2012 Google Inc. All rights reserved. | 2 * Copyright (C) 2012 Google Inc. All rights reserved. |
| 3 * | 3 * |
| 4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
| 5 * modification, are permitted provided that the following conditions | 5 * modification, are permitted provided that the following conditions |
| 6 * are met: | 6 * are met: |
| 7 * | 7 * |
| 8 * 1. Redistributions of source code must retain the above copyright | 8 * 1. Redistributions of source code must retain the above copyright |
| 9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
| 10 * 2. Redistributions in binary form must reproduce the above copyright | 10 * 2. Redistributions in binary form must reproduce the above copyright |
| (...skipping 121 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 132 TRACE_EVENT_INSTANT0("test_gpu", "Canvas2DLayerBridgeCreation", TRACE_EVENT_ SCOPE_GLOBAL); | 132 TRACE_EVENT_INSTANT0("test_gpu", "Canvas2DLayerBridgeCreation", TRACE_EVENT_ SCOPE_GLOBAL); |
| 133 startRecording(); | 133 startRecording(); |
| 134 #ifndef NDEBUG | 134 #ifndef NDEBUG |
| 135 canvas2DLayerBridgeInstanceCounter().increment(); | 135 canvas2DLayerBridgeInstanceCounter().increment(); |
| 136 #endif | 136 #endif |
| 137 } | 137 } |
| 138 | 138 |
| 139 Canvas2DLayerBridge::~Canvas2DLayerBridge() | 139 Canvas2DLayerBridge::~Canvas2DLayerBridge() |
| 140 { | 140 { |
| 141 ASSERT(m_destructionInProgress); | 141 ASSERT(m_destructionInProgress); |
| 142 #if USE_IOSURFACE_FOR_2D_CANVAS | |
| 143 clearCHROMIUMImageCache(); | |
| 144 #endif // USE_IOSURFACE_FOR_2D_CANVAS | |
| 145 | |
| 142 m_layer.clear(); | 146 m_layer.clear(); |
| 143 ASSERT(m_mailboxes.size() == 0); | 147 ASSERT(m_mailboxes.size() == 0); |
| 144 #ifndef NDEBUG | 148 #ifndef NDEBUG |
| 145 canvas2DLayerBridgeInstanceCounter().decrement(); | 149 canvas2DLayerBridgeInstanceCounter().decrement(); |
| 146 #endif | 150 #endif |
| 147 } | 151 } |
| 148 | 152 |
| 149 void Canvas2DLayerBridge::startRecording() | 153 void Canvas2DLayerBridge::startRecording() |
| 150 { | 154 { |
| 151 ASSERT(m_isDeferralEnabled); | 155 ASSERT(m_isDeferralEnabled); |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 194 // of the canvas would result in the canvas being accelerated. Presentation is assumed to be | 198 // of the canvas would result in the canvas being accelerated. Presentation is assumed to be |
| 195 // a 'PreferAcceleration' operation. | 199 // a 'PreferAcceleration' operation. |
| 196 return shouldAccelerate(PreferAcceleration); | 200 return shouldAccelerate(PreferAcceleration); |
| 197 } | 201 } |
| 198 | 202 |
| 199 GLenum Canvas2DLayerBridge::getGLFilter() | 203 GLenum Canvas2DLayerBridge::getGLFilter() |
| 200 { | 204 { |
| 201 return m_filterQuality == kNone_SkFilterQuality ? GL_NEAREST : GL_LINEAR; | 205 return m_filterQuality == kNone_SkFilterQuality ? GL_NEAREST : GL_LINEAR; |
| 202 } | 206 } |
| 203 | 207 |
| 204 Canvas2DLayerBridge::MailboxInfo& Canvas2DLayerBridge::createMailboxInfo() | 208 #if USE_IOSURFACE_FOR_2D_CANVAS |
| 209 bool Canvas2DLayerBridge::prepareIOSurfaceMailboxFromImage(SkImage* image, WebEx ternalTextureMailbox* outMailbox) | |
| 210 { | |
| 211 // Need to flush skia's internal queue because texture is about to be access ed directly | |
| 212 GrContext* grContext = m_contextProvider->grContext(); | |
| 213 grContext->flush(); | |
| 214 | |
| 215 ImageInfo imageInfo = createIOSurfaceBackedTexture(); | |
| 216 if (imageInfo.empty()) | |
| 217 return false; | |
| 218 | |
| 219 GLuint imageTexture = skia::GrBackendObjectToGrGLTextureInfo(image->getTextu reHandle(true))->fID; | |
| 220 context()->copySubTextureCHROMIUM(imageTexture, imageInfo.m_textureId, 0, 0, 0, 0, m_size.width(), m_size.height(), GL_FALSE, GL_FALSE, GL_FALSE); | |
| 221 | |
| 222 MailboxInfo& info = m_mailboxes.first(); | |
| 223 info.m_mailbox.textureTarget = GC3D_TEXTURE_RECTANGLE_ARB; | |
| 224 context()->genMailboxCHROMIUM(info.m_mailbox.name); | |
| 225 context()->produceTextureDirectCHROMIUM(imageInfo.m_textureId, info.m_mailbo x.textureTarget, info.m_mailbox.name); | |
| 226 info.m_mailbox.allowOverlay = true; | |
| 227 | |
| 228 const WGC3Duint64 fenceSync = context()->insertFenceSyncCHROMIUM(); | |
| 229 context()->flush(); | |
| 230 info.m_mailbox.validSyncToken = context()->genSyncTokenCHROMIUM(fenceSync, i nfo.m_mailbox.syncToken); | |
| 231 | |
| 232 info.m_imageInfo = imageInfo; | |
| 233 *outMailbox = info.m_mailbox; | |
| 234 | |
| 235 context()->bindTexture(GC3D_TEXTURE_RECTANGLE_ARB, 0); | |
| 236 | |
| 237 // Because we are changing the texture binding without going through skia, | |
| 238 // we must dirty the context. | |
| 239 grContext->resetContext(kTextureBinding_GrGLBackendState); | |
| 240 | |
| 241 return true; | |
| 242 } | |
| 243 | |
| 244 Canvas2DLayerBridge::ImageInfo Canvas2DLayerBridge::createIOSurfaceBackedTexture () | |
| 245 { | |
| 246 if (!m_imageInfoCache.isEmpty()) { | |
| 247 Canvas2DLayerBridge::ImageInfo info = m_imageInfoCache.last(); | |
| 248 m_imageInfoCache.removeLast(); | |
| 249 return info; | |
| 250 } | |
| 251 | |
| 252 WebGraphicsContext3D* webContext = context(); | |
| 253 GLuint imageId = webContext->createGpuMemoryBufferImageCHROMIUM(m_size.width (), m_size.height(), GL_BGRA_EXT, GC3D_SCANOUT_CHROMIUM); | |
| 254 if (!imageId) | |
| 255 return Canvas2DLayerBridge::ImageInfo(); | |
| 256 | |
| 257 GLuint textureId= webContext->createTexture(); | |
| 258 if (!textureId) { | |
| 259 webContext->destroyImageCHROMIUM(imageId); | |
| 260 return Canvas2DLayerBridge::ImageInfo(); | |
| 261 } | |
| 262 | |
| 263 GLenum target = GC3D_TEXTURE_RECTANGLE_ARB; | |
| 264 webContext->bindTexture(target, textureId); | |
| 265 webContext->texParameteri(target, GL_TEXTURE_MAG_FILTER, getGLFilter()); | |
| 266 webContext->texParameteri(target, GL_TEXTURE_MIN_FILTER, getGLFilter()); | |
| 267 webContext->texParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | |
| 268 webContext->texParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | |
| 269 webContext->bindTexImage2DCHROMIUM(target, imageId); | |
| 270 | |
| 271 return Canvas2DLayerBridge::ImageInfo(imageId, textureId); | |
| 272 } | |
| 273 | |
| 274 void Canvas2DLayerBridge::deleteCHROMIUMImage(ImageInfo info) | |
| 275 { | |
| 276 WebGraphicsContext3D* webContext = context(); | |
| 277 if (webContext->isContextLost()) | |
| 278 return; | |
| 279 | |
| 280 GLenum target = GC3D_TEXTURE_RECTANGLE_ARB; | |
| 281 webContext->bindTexture(target, info.m_textureId); | |
| 282 webContext->releaseTexImage2DCHROMIUM(target, info.m_imageId); | |
| 283 webContext->destroyImageCHROMIUM(info.m_imageId); | |
| 284 webContext->deleteTexture(info.m_textureId); | |
| 285 webContext->bindTexture(target, 0); | |
| 286 | |
| 287 resetSkiaTextureBinding(); | |
| 288 } | |
| 289 | |
| 290 void Canvas2DLayerBridge::clearCHROMIUMImageCache() | |
|
Ken Russell (switch to Gerrit)
2016/03/10 01:58:04
This must be called when the canvas is resized, co
erikchen
2016/03/10 02:03:37
I am under the impression that resizing happens in
Ken Russell (switch to Gerrit)
2016/03/10 02:59:41
OK, yes, it looks like HTMLCanvasElement::m_imageB
| |
| 291 { | |
| 292 for (const auto& it : m_imageInfoCache) { | |
| 293 deleteCHROMIUMImage(it); | |
| 294 } | |
| 295 m_imageInfoCache.clear(); | |
| 296 } | |
| 297 #endif // USE_IOSURFACE_FOR_2D_CANVAS | |
| 298 | |
| 299 void Canvas2DLayerBridge::createMailboxInfo() | |
| 205 { | 300 { |
| 206 MailboxInfo tmp; | 301 MailboxInfo tmp; |
| 207 tmp.m_parentLayerBridge = this; | 302 tmp.m_parentLayerBridge = this; |
| 208 m_mailboxes.prepend(tmp); | 303 m_mailboxes.prepend(tmp); |
| 209 MailboxInfo& mailboxInfo = m_mailboxes.first(); | |
| 210 return mailboxInfo; | |
| 211 } | 304 } |
| 212 | 305 |
| 213 bool Canvas2DLayerBridge::prepareMailboxFromImage(PassRefPtr<SkImage> image, Web ExternalTextureMailbox* outMailbox) | 306 bool Canvas2DLayerBridge::prepareMailboxFromImage(PassRefPtr<SkImage> image, Web ExternalTextureMailbox* outMailbox) |
| 214 { | 307 { |
| 215 MailboxInfo& mailboxInfo = createMailboxInfo(); | 308 createMailboxInfo(); |
| 309 MailboxInfo& mailboxInfo = m_mailboxes.first(); | |
| 216 mailboxInfo.m_mailbox.nearestNeighbor = getGLFilter() == GL_NEAREST; | 310 mailboxInfo.m_mailbox.nearestNeighbor = getGLFilter() == GL_NEAREST; |
| 217 mailboxInfo.m_image = image; | |
| 218 | 311 |
| 219 GrContext* grContext = m_contextProvider->grContext(); | 312 GrContext* grContext = m_contextProvider->grContext(); |
| 220 if (!grContext) | 313 if (!grContext) { |
| 314 mailboxInfo.m_image = image; | |
| 221 return true; // for testing: skip gl stuff when using a mock graphics co ntext. | 315 return true; // for testing: skip gl stuff when using a mock graphics co ntext. |
| 316 } | |
| 317 | |
| 318 #if USE_IOSURFACE_FOR_2D_CANVAS | |
| 319 if (RuntimeEnabledFeatures::canvas2dImageChromiumEnabled()) { | |
| 320 if (prepareIOSurfaceMailboxFromImage(image.get(), outMailbox)) | |
| 321 return true; | |
| 322 // Note: if IOSurface backed texture creation failed we fall back to the | |
| 323 // non-IOSurface path. | |
| 324 } | |
| 325 #endif // USE_IOSURFACE_FOR_2D_CANVAS | |
| 326 | |
| 327 mailboxInfo.m_image = image; | |
| 222 | 328 |
| 223 if (RuntimeEnabledFeatures::forceDisable2dCanvasCopyOnWriteEnabled()) | 329 if (RuntimeEnabledFeatures::forceDisable2dCanvasCopyOnWriteEnabled()) |
| 224 m_surface->notifyContentWillChange(SkSurface::kRetain_ContentChangeMode) ; | 330 m_surface->notifyContentWillChange(SkSurface::kRetain_ContentChangeMode) ; |
| 225 | 331 |
| 226 // Need to flush skia's internal queue because texture is about to be access ed directly | 332 // Need to flush skia's internal queue because texture is about to be access ed directly |
| 227 grContext->flush(); | 333 grContext->flush(); |
| 228 | 334 |
| 229 // Because of texture sharing with the compositor, we must invalidate | 335 // Because of texture sharing with the compositor, we must invalidate |
| 230 // the state cached in skia so that the deferred copy on write | 336 // the state cached in skia so that the deferred copy on write |
| 231 // in SkSurface_Gpu does not make any false assumptions. | 337 // in SkSurface_Gpu does not make any false assumptions. |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 264 } | 370 } |
| 265 webContext->bindTexture(GL_TEXTURE_2D, 0); | 371 webContext->bindTexture(GL_TEXTURE_2D, 0); |
| 266 // Because we are changing the texture binding without going through skia, | 372 // Because we are changing the texture binding without going through skia, |
| 267 // we must dirty the context. | 373 // we must dirty the context. |
| 268 grContext->resetContext(kTextureBinding_GrGLBackendState); | 374 grContext->resetContext(kTextureBinding_GrGLBackendState); |
| 269 | 375 |
| 270 *outMailbox = mailboxInfo.m_mailbox; | 376 *outMailbox = mailboxInfo.m_mailbox; |
| 271 return true; | 377 return true; |
| 272 } | 378 } |
| 273 | 379 |
| 380 void Canvas2DLayerBridge::resetSkiaTextureBinding() | |
| 381 { | |
| 382 GrContext* grContext = m_contextProvider->grContext(); | |
| 383 if (grContext) | |
| 384 grContext->resetContext(kTextureBinding_GrGLBackendState); | |
| 385 } | |
| 386 | |
| 274 static void hibernateWrapper(WeakPtr<Canvas2DLayerBridge> bridge, double /*idleD eadline*/) | 387 static void hibernateWrapper(WeakPtr<Canvas2DLayerBridge> bridge, double /*idleD eadline*/) |
| 275 { | 388 { |
| 276 if (bridge) { | 389 if (bridge) { |
| 277 bridge->hibernate(); | 390 bridge->hibernate(); |
| 278 } else { | 391 } else { |
| 279 Canvas2DLayerBridge::Logger localLogger; | 392 Canvas2DLayerBridge::Logger localLogger; |
| 280 localLogger.reportHibernationEvent(Canvas2DLayerBridge::HibernationAbort edDueToDestructionWhileHibernatePending); | 393 localLogger.reportHibernationEvent(Canvas2DLayerBridge::HibernationAbort edDueToDestructionWhileHibernatePending); |
| 281 } | 394 } |
| 282 } | 395 } |
| 283 | 396 |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 327 // case because flushRecordingOnly should only fail it it fails to allocate | 440 // case because flushRecordingOnly should only fail it it fails to allocate |
| 328 // a surface, and we have an early exit at the top of this function for when | 441 // a surface, and we have an early exit at the top of this function for when |
| 329 // 'this' does not already have a surface. | 442 // 'this' does not already have a surface. |
| 330 ASSERT(!m_haveRecordedDrawCommands); | 443 ASSERT(!m_haveRecordedDrawCommands); |
| 331 SkPaint copyPaint; | 444 SkPaint copyPaint; |
| 332 copyPaint.setXfermodeMode(SkXfermode::kSrc_Mode); | 445 copyPaint.setXfermodeMode(SkXfermode::kSrc_Mode); |
| 333 m_surface->draw(tempHibernationSurface->getCanvas(), 0, 0, ©Paint); // G PU readback | 446 m_surface->draw(tempHibernationSurface->getCanvas(), 0, 0, ©Paint); // G PU readback |
| 334 m_hibernationImage = adoptRef(tempHibernationSurface->newImageSnapshot()); | 447 m_hibernationImage = adoptRef(tempHibernationSurface->newImageSnapshot()); |
| 335 m_surface.clear(); // destroy the GPU-backed buffer | 448 m_surface.clear(); // destroy the GPU-backed buffer |
| 336 m_layer->clearTexture(); | 449 m_layer->clearTexture(); |
| 450 #if USE_IOSURFACE_FOR_2D_CANVAS | |
| 451 clearCHROMIUMImageCache(); | |
| 452 #endif // USE_IOSURFACE_FOR_2D_CANVAS | |
| 337 m_logger->didStartHibernating(); | 453 m_logger->didStartHibernating(); |
| 338 | |
| 339 } | 454 } |
| 340 | 455 |
| 341 void Canvas2DLayerBridge::reportSurfaceCreationFailure() | 456 void Canvas2DLayerBridge::reportSurfaceCreationFailure() |
| 342 { | 457 { |
| 343 if (!m_surfaceCreationFailedAtLeastOnce) { | 458 if (!m_surfaceCreationFailedAtLeastOnce) { |
| 344 // Only count the failure once per instance so that the histogram may | 459 // Only count the failure once per instance so that the histogram may |
| 345 // reflect the proportion of Canvas2DLayerBridge instances with surface | 460 // reflect the proportion of Canvas2DLayerBridge instances with surface |
| 346 // allocation failures. | 461 // allocation failures. |
| 347 CanvasMetrics::countCanvasContextUsage(CanvasMetrics::GPUAccelerated2DCa nvasSurfaceCreationFailed); | 462 CanvasMetrics::countCanvasContextUsage(CanvasMetrics::GPUAccelerated2DCa nvasSurfaceCreationFailed); |
| 348 m_surfaceCreationFailedAtLeastOnce = true; | 463 m_surfaceCreationFailedAtLeastOnce = true; |
| (...skipping 367 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 716 --releasedMailboxInfo; | 831 --releasedMailboxInfo; |
| 717 if (nameEquals(releasedMailboxInfo->m_mailbox, mailbox)) { | 832 if (nameEquals(releasedMailboxInfo->m_mailbox, mailbox)) { |
| 718 break; | 833 break; |
| 719 } | 834 } |
| 720 ASSERT(releasedMailboxInfo != firstMailbox); | 835 ASSERT(releasedMailboxInfo != firstMailbox); |
| 721 } | 836 } |
| 722 | 837 |
| 723 if (!contextLost) { | 838 if (!contextLost) { |
| 724 // Invalidate texture state in case the compositor altered it since the copy-on-write. | 839 // Invalidate texture state in case the compositor altered it since the copy-on-write. |
| 725 if (releasedMailboxInfo->m_image) { | 840 if (releasedMailboxInfo->m_image) { |
| 841 #if USE_IOSURFACE_FOR_2D_CANVAS | |
| 842 ASSERT(releasedMailboxInfo->m_imageInfo.empty()); | |
| 843 #endif // USE_IOSURFACE_FOR_2D_CANVAS | |
| 726 if (mailbox.validSyncToken) { | 844 if (mailbox.validSyncToken) { |
| 727 context()->waitSyncTokenCHROMIUM(mailbox.syncToken); | 845 context()->waitSyncTokenCHROMIUM(mailbox.syncToken); |
| 728 } | 846 } |
| 729 GrTexture* texture = releasedMailboxInfo->m_image->getTexture(); | 847 GrTexture* texture = releasedMailboxInfo->m_image->getTexture(); |
| 730 if (texture) { | 848 if (texture) { |
| 731 if (lostResource) { | 849 if (lostResource) { |
| 732 texture->abandon(); | 850 texture->abandon(); |
| 733 } else { | 851 } else { |
| 734 texture->textureParamsModified(); | 852 texture->textureParamsModified(); |
| 735 } | 853 } |
| 736 } | 854 } |
| 737 } | 855 } |
| 856 | |
| 857 #if USE_IOSURFACE_FOR_2D_CANVAS | |
| 858 if (!releasedMailboxInfo->m_imageInfo.empty() && !lostResource) { | |
| 859 m_imageInfoCache.append(releasedMailboxInfo->m_imageInfo); | |
| 860 } | |
| 861 #endif // USE_IOSURFACE_FOR_2D_CANVAS | |
| 738 } | 862 } |
| 739 | 863 |
| 740 RefPtr<Canvas2DLayerBridge> selfRef; | 864 RefPtr<Canvas2DLayerBridge> selfRef; |
| 741 if (m_destructionInProgress) { | 865 if (m_destructionInProgress) { |
| 742 // To avoid memory use after free, take a scoped self-reference | 866 // To avoid memory use after free, take a scoped self-reference |
| 743 // to postpone destruction until the end of this function. | 867 // to postpone destruction until the end of this function. |
| 744 selfRef = this; | 868 selfRef = this; |
| 745 } | 869 } |
| 746 | 870 |
| 747 // The destruction of 'releasedMailboxInfo' will: | 871 // The destruction of 'releasedMailboxInfo' will: |
| (...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 835 // parameters. | 959 // parameters. |
| 836 getOrCreateSurface()->notifyContentWillChange(SkSurface::kRetain_ContentChan geMode); | 960 getOrCreateSurface()->notifyContentWillChange(SkSurface::kRetain_ContentChan geMode); |
| 837 return adoptRef(m_surface->newImageSnapshot()); | 961 return adoptRef(m_surface->newImageSnapshot()); |
| 838 } | 962 } |
| 839 | 963 |
| 840 void Canvas2DLayerBridge::willOverwriteCanvas() | 964 void Canvas2DLayerBridge::willOverwriteCanvas() |
| 841 { | 965 { |
| 842 skipQueuedDrawCommands(); | 966 skipQueuedDrawCommands(); |
| 843 } | 967 } |
| 844 | 968 |
| 969 #if USE_IOSURFACE_FOR_2D_CANVAS | |
| 970 Canvas2DLayerBridge::ImageInfo::ImageInfo(GLuint imageId, GLuint textureId) : m_ imageId(imageId), m_textureId(textureId) | |
| 971 { | |
| 972 ASSERT(imageId); | |
| 973 ASSERT(textureId); | |
| 974 } | |
| 975 | |
| 976 bool Canvas2DLayerBridge::ImageInfo::empty() | |
| 977 { | |
| 978 return m_imageId == 0; | |
| 979 } | |
| 980 #endif // USE_IOSURFACE_FOR_2D_CANVAS | |
| 981 | |
| 845 Canvas2DLayerBridge::MailboxInfo::MailboxInfo(const MailboxInfo& other) | 982 Canvas2DLayerBridge::MailboxInfo::MailboxInfo(const MailboxInfo& other) |
| 846 { | 983 { |
| 847 memcpy(&m_mailbox, &other.m_mailbox, sizeof(m_mailbox)); | 984 memcpy(&m_mailbox, &other.m_mailbox, sizeof(m_mailbox)); |
| 848 m_image = other.m_image; | 985 m_image = other.m_image; |
| 849 m_parentLayerBridge = other.m_parentLayerBridge; | 986 m_parentLayerBridge = other.m_parentLayerBridge; |
| 987 #if USE_IOSURFACE_FOR_2D_CANVAS | |
| 988 m_imageInfo = other.m_imageInfo; | |
| 989 #endif // USE_IOSURFACE_FOR_2D_CANVAS | |
| 850 } | 990 } |
| 851 | 991 |
| 852 void Canvas2DLayerBridge::Logger::reportHibernationEvent(HibernationEvent event) | 992 void Canvas2DLayerBridge::Logger::reportHibernationEvent(HibernationEvent event) |
| 853 { | 993 { |
| 854 DEFINE_STATIC_LOCAL(EnumerationHistogram, hibernationHistogram, ("Canvas.Hib ernationEvents", HibernationEventCount)); | 994 DEFINE_STATIC_LOCAL(EnumerationHistogram, hibernationHistogram, ("Canvas.Hib ernationEvents", HibernationEventCount)); |
| 855 hibernationHistogram.count(event); | 995 hibernationHistogram.count(event); |
| 856 } | 996 } |
| 857 | 997 |
| 858 } // namespace blink | 998 } // namespace blink |
| OLD | NEW |