Delay WebProcess launch until a load is triggered in a Web view
[WebKit-https.git] / Source / WebKit / UIProcess / RemoteLayerTree / RemoteLayerTreeDrawingAreaProxy.mm
1 /*
2  * Copyright (C) 2012-2018 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "RemoteLayerTreeDrawingAreaProxy.h"
28
29 #import "DrawingAreaMessages.h"
30 #import "Logging.h"
31 #import "RemoteLayerTreeDrawingAreaProxyMessages.h"
32 #import "RemoteScrollingCoordinatorProxy.h"
33 #import "RemoteScrollingCoordinatorTransaction.h"
34 #import "WebPageProxy.h"
35 #import "WebProcessProxy.h"
36 #import <QuartzCore/QuartzCore.h>
37 #import <WebCore/GraphicsContextCG.h>
38 #import <WebCore/IOSurfacePool.h>
39 #import <WebCore/WebActionDisablingCALayerDelegate.h>
40 #import <wtf/MachSendRight.h>
41 #import <wtf/SystemTracing.h>
42
43 // FIXME: Mac will need something similar; we should figure out how to share this with DisplayRefreshMonitor without
44 // breaking WebKit1 behavior or WebKit2-WebKit1 coexistence.
45 #if PLATFORM(IOS_FAMILY)
46 @interface WKOneShotDisplayLinkHandler : NSObject {
47     WebKit::RemoteLayerTreeDrawingAreaProxy* _drawingAreaProxy;
48     CADisplayLink *_displayLink;
49 }
50
51 - (id)initWithDrawingAreaProxy:(WebKit::RemoteLayerTreeDrawingAreaProxy*)drawingAreaProxy;
52 - (void)displayLinkFired:(CADisplayLink *)sender;
53 - (void)invalidate;
54 - (void)schedule;
55
56 @end
57
58 @implementation WKOneShotDisplayLinkHandler
59
60 - (id)initWithDrawingAreaProxy:(WebKit::RemoteLayerTreeDrawingAreaProxy*)drawingAreaProxy
61 {
62     if (self = [super init]) {
63         _drawingAreaProxy = drawingAreaProxy;
64         // Note that CADisplayLink retains its target (self), so a call to -invalidate is needed on teardown.
65         _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkFired:)];
66         [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
67         _displayLink.paused = YES;
68         _displayLink.preferredFramesPerSecond = 60;
69     }
70     return self;
71 }
72
73 - (void)dealloc
74 {
75     ASSERT(!_displayLink);
76     [super dealloc];
77 }
78
79 - (void)displayLinkFired:(CADisplayLink *)sender
80 {
81     ASSERT(isUIThread());
82     _drawingAreaProxy->didRefreshDisplay();
83 }
84
85 - (void)invalidate
86 {
87     [_displayLink invalidate];
88     _displayLink = nullptr;
89 }
90
91 - (void)schedule
92 {
93     _displayLink.paused = NO;
94 }
95
96 - (void)pause
97 {
98     _displayLink.paused = YES;
99 }
100
101 @end
102 #endif
103
104 namespace WebKit {
105 using namespace IPC;
106 using namespace WebCore;
107
108 RemoteLayerTreeDrawingAreaProxy::RemoteLayerTreeDrawingAreaProxy(WebPageProxy& webPageProxy, WebProcessProxy& process)
109     : DrawingAreaProxy(DrawingAreaTypeRemoteLayerTree, webPageProxy, process)
110     , m_remoteLayerTreeHost(std::make_unique<RemoteLayerTreeHost>(*this))
111 {
112 #if HAVE(IOSURFACE)
113     // We don't want to pool surfaces in the UI process.
114     // FIXME: We should do this somewhere else.
115     IOSurfacePool::sharedPool().setPoolSize(0);
116 #endif
117
118     process.addMessageReceiver(Messages::RemoteLayerTreeDrawingAreaProxy::messageReceiverName(), m_identifier.toUInt64(), *this);
119
120     if (m_webPageProxy.preferences().tiledScrollingIndicatorVisible())
121         initializeDebugIndicator();
122 }
123
124 RemoteLayerTreeDrawingAreaProxy::~RemoteLayerTreeDrawingAreaProxy()
125 {
126     m_callbacks.invalidate(CallbackBase::Error::OwnerWasInvalidated);
127     process().removeMessageReceiver(Messages::RemoteLayerTreeDrawingAreaProxy::messageReceiverName(), m_identifier.toUInt64());
128
129 #if PLATFORM(IOS_FAMILY)
130     [m_displayLinkHandler invalidate];
131 #endif
132 }
133
134
135 std::unique_ptr<RemoteLayerTreeHost> RemoteLayerTreeDrawingAreaProxy::detachRemoteLayerTreeHost()
136 {
137     m_remoteLayerTreeHost->detachFromDrawingArea();
138     return WTFMove(m_remoteLayerTreeHost);
139 }
140
141
142 #if PLATFORM(IOS_FAMILY)
143 WKOneShotDisplayLinkHandler *RemoteLayerTreeDrawingAreaProxy::displayLinkHandler()
144 {
145     if (!m_displayLinkHandler)
146         m_displayLinkHandler = adoptNS([[WKOneShotDisplayLinkHandler alloc] initWithDrawingAreaProxy:this]);
147     return m_displayLinkHandler.get();
148 }
149 #endif
150
151 void RemoteLayerTreeDrawingAreaProxy::sizeDidChange()
152 {
153     if (!m_webPageProxy.hasRunningProcess())
154         return;
155
156     if (m_isWaitingForDidUpdateGeometry)
157         return;
158
159     sendUpdateGeometry();
160 }
161
162 void RemoteLayerTreeDrawingAreaProxy::deviceScaleFactorDidChange()
163 {
164     send(Messages::DrawingArea::SetDeviceScaleFactor(m_webPageProxy.deviceScaleFactor()));
165 }
166
167 void RemoteLayerTreeDrawingAreaProxy::didUpdateGeometry()
168 {
169     ASSERT(m_isWaitingForDidUpdateGeometry);
170
171     m_isWaitingForDidUpdateGeometry = false;
172
173     // If the WKView was resized while we were waiting for a DidUpdateGeometry reply from the web process,
174     // we need to resend the new size here.
175     if (m_lastSentSize != m_size)
176         sendUpdateGeometry();
177 }
178
179 void RemoteLayerTreeDrawingAreaProxy::sendUpdateGeometry()
180 {
181     m_lastSentSize = m_size;
182     send(Messages::DrawingArea::UpdateGeometry(m_size, false /* flushSynchronously */, MachSendRight()));
183     m_isWaitingForDidUpdateGeometry = true;
184 }
185
186 void RemoteLayerTreeDrawingAreaProxy::willCommitLayerTree(uint64_t transactionID)
187 {
188     m_pendingLayerTreeTransactionID = transactionID;
189 }
190
191 void RemoteLayerTreeDrawingAreaProxy::commitLayerTree(const RemoteLayerTreeTransaction& layerTreeTransaction, const RemoteScrollingCoordinatorTransaction& scrollingTreeTransaction)
192 {
193     TraceScope tracingScope(CommitLayerTreeStart, CommitLayerTreeEnd);
194
195     LOG(RemoteLayerTree, "%s", layerTreeTransaction.description().data());
196     LOG(RemoteLayerTree, "%s", scrollingTreeTransaction.description().data());
197
198     ASSERT(layerTreeTransaction.transactionID() == m_lastVisibleTransactionID + 1);
199     m_transactionIDForPendingCACommit = layerTreeTransaction.transactionID();
200     m_activityStateChangeID = layerTreeTransaction.activityStateChangeID();
201
202     if (layerTreeTransaction.hasEditorState())
203         m_webPageProxy.editorStateChanged(layerTreeTransaction.editorState());
204
205     if (m_remoteLayerTreeHost->updateLayerTree(layerTreeTransaction)) {
206         if (layerTreeTransaction.transactionID() >= m_transactionIDForUnhidingContent)
207             m_webPageProxy.setRemoteLayerTreeRootNode(m_remoteLayerTreeHost->rootNode());
208         else
209             m_remoteLayerTreeHost->detachRootLayer();
210     }
211
212 #if ENABLE(ASYNC_SCROLLING)
213     RemoteScrollingCoordinatorProxy::RequestedScrollInfo requestedScrollInfo;
214     m_webPageProxy.scrollingCoordinatorProxy()->commitScrollingTreeState(scrollingTreeTransaction, requestedScrollInfo);
215 #endif
216
217     m_webPageProxy.didCommitLayerTree(layerTreeTransaction);
218
219 #if ENABLE(ASYNC_SCROLLING)
220     if (m_webPageProxy.scrollingCoordinatorProxy()->hasFixedOrSticky()) {
221 #if PLATFORM(IOS_FAMILY)
222         // If we got a new layer for a fixed or sticky node, its position from the WebProcess is probably stale. We need to re-run the "viewport" changed logic to udpate it with our UI-side state.
223         FloatRect layoutViewport = m_webPageProxy.computeCustomFixedPositionRect(m_webPageProxy.unobscuredContentRect(), m_webPageProxy.unobscuredContentRectRespectingInputViewBounds(), m_webPageProxy.customFixedPositionRect(), m_webPageProxy.displayedContentScale(), FrameView::LayoutViewportConstraint::Unconstrained);
224         m_webPageProxy.scrollingCoordinatorProxy()->viewportChangedViaDelegatedScrolling(m_webPageProxy.unobscuredContentRect().location(), layoutViewport, m_webPageProxy.displayedContentScale());
225 #else
226         m_webPageProxy.scrollingCoordinatorProxy()->applyScrollingTreeLayerPositions();
227 #endif
228     }
229
230     // Handle requested scroll position updates from the scrolling tree transaction after didCommitLayerTree()
231     // has updated the view size based on the content size.
232     if (requestedScrollInfo.requestsScrollPositionUpdate)
233         m_webPageProxy.requestScroll(requestedScrollInfo.requestedScrollPosition, layerTreeTransaction.scrollOrigin(), requestedScrollInfo.requestIsProgrammaticScroll);
234 #endif // ENABLE(ASYNC_SCROLLING)
235
236     if (m_debugIndicatorLayerTreeHost) {
237         float scale = indicatorScale(layerTreeTransaction.contentsSize());
238         bool rootLayerChanged = m_debugIndicatorLayerTreeHost->updateLayerTree(layerTreeTransaction, scale);
239         IntPoint scrollPosition;
240 #if PLATFORM(MAC)
241         scrollPosition = layerTreeTransaction.scrollPosition();
242 #endif
243         updateDebugIndicator(layerTreeTransaction.contentsSize(), rootLayerChanged, scale, scrollPosition);
244         m_debugIndicatorLayerTreeHost->rootLayer().name = @"Indicator host root";
245     }
246
247     m_webPageProxy.layerTreeCommitComplete();
248
249 #if PLATFORM(IOS_FAMILY)
250     if (std::exchange(m_didUpdateMessageState, NeedsDidUpdate) == MissedCommit)
251         didRefreshDisplay();
252     [displayLinkHandler() schedule];
253 #else
254     m_didUpdateMessageState = NeedsDidUpdate;
255     didRefreshDisplay();
256 #endif
257
258     if (auto milestones = layerTreeTransaction.newlyReachedLayoutMilestones())
259         m_webPageProxy.didReachLayoutMilestone(milestones);
260
261     for (auto& callbackID : layerTreeTransaction.callbackIDs()) {
262         if (auto callback = m_callbacks.take<VoidCallback>(callbackID))
263             callback->performCallback();
264     }
265 }
266
267 void RemoteLayerTreeDrawingAreaProxy::acceleratedAnimationDidStart(uint64_t layerID, const String& key, MonotonicTime startTime)
268 {
269     send(Messages::DrawingArea::AcceleratedAnimationDidStart(layerID, key, startTime));
270 }
271
272 void RemoteLayerTreeDrawingAreaProxy::acceleratedAnimationDidEnd(uint64_t layerID, const String& key)
273 {
274     send(Messages::DrawingArea::AcceleratedAnimationDidEnd(layerID, key));
275 }
276
277 static const float indicatorInset = 10;
278
279 #if PLATFORM(MAC)
280 void RemoteLayerTreeDrawingAreaProxy::setViewExposedRect(Optional<WebCore::FloatRect> viewExposedRect)
281 {
282     DrawingAreaProxy::setViewExposedRect(viewExposedRect);
283     updateDebugIndicatorPosition();
284 }
285 #endif
286
287 FloatPoint RemoteLayerTreeDrawingAreaProxy::indicatorLocation() const
288 {
289     if (m_webPageProxy.delegatesScrolling()) {
290 #if PLATFORM(IOS_FAMILY)
291         FloatPoint tiledMapLocation = m_webPageProxy.unobscuredContentRect().location().expandedTo(FloatPoint());
292         tiledMapLocation = tiledMapLocation.expandedTo(m_webPageProxy.exposedContentRect().location());
293
294         float absoluteInset = indicatorInset / m_webPageProxy.displayedContentScale();
295         tiledMapLocation += FloatSize(absoluteInset, absoluteInset);
296 #else
297         FloatPoint tiledMapLocation;
298         if (viewExposedRect())
299             tiledMapLocation = viewExposedRect().value().location();
300
301         tiledMapLocation += FloatSize(indicatorInset, indicatorInset);
302         float scale = 1 / m_webPageProxy.pageScaleFactor();
303         tiledMapLocation.scale(scale);
304 #endif
305         return tiledMapLocation;
306     }
307     
308     return FloatPoint(indicatorInset, indicatorInset + m_webPageProxy.topContentInset());
309 }
310
311 void RemoteLayerTreeDrawingAreaProxy::updateDebugIndicatorPosition()
312 {
313     if (!m_tileMapHostLayer)
314         return;
315
316     [m_tileMapHostLayer setPosition:indicatorLocation()];
317 }
318
319 float RemoteLayerTreeDrawingAreaProxy::indicatorScale(IntSize contentsSize) const
320 {
321     // Pick a good scale.
322     IntSize viewSize = m_webPageProxy.viewSize();
323
324     float scale = 1;
325     if (!contentsSize.isEmpty()) {
326         float widthScale = std::min<float>((viewSize.width() - 2 * indicatorInset) / contentsSize.width(), 0.05);
327         scale = std::min(widthScale, static_cast<float>(viewSize.height() - 2 * indicatorInset) / contentsSize.height());
328     }
329     
330     return scale;
331 }
332
333 void RemoteLayerTreeDrawingAreaProxy::updateDebugIndicator()
334 {
335     // FIXME: we should also update live information during scale.
336     updateDebugIndicatorPosition();
337 }
338
339 void RemoteLayerTreeDrawingAreaProxy::updateDebugIndicator(IntSize contentsSize, bool rootLayerChanged, float scale, const IntPoint& scrollPosition)
340 {
341     // Make sure we're the last sublayer.
342     CALayer *rootLayer = m_remoteLayerTreeHost->rootLayer();
343     [m_tileMapHostLayer removeFromSuperlayer];
344     [rootLayer addSublayer:m_tileMapHostLayer.get()];
345
346     // Pick a good scale.
347     IntSize viewSize = m_webPageProxy.viewSize();
348
349     [m_tileMapHostLayer setBounds:FloatRect(FloatPoint(), contentsSize)];
350     [m_tileMapHostLayer setPosition:indicatorLocation()];
351     [m_tileMapHostLayer setTransform:CATransform3DMakeScale(scale, scale, 1)];
352
353     if (rootLayerChanged) {
354         [m_tileMapHostLayer setSublayers:@[]];
355         [m_tileMapHostLayer addSublayer:m_debugIndicatorLayerTreeHost->rootLayer()];
356         [m_tileMapHostLayer addSublayer:m_exposedRectIndicatorLayer.get()];
357     }
358     
359     const float indicatorBorderWidth = 1;
360     float counterScaledBorder = indicatorBorderWidth / scale;
361
362     [m_exposedRectIndicatorLayer setBorderWidth:counterScaledBorder];
363
364     if (m_webPageProxy.delegatesScrolling()) {
365         FloatRect scaledExposedRect;
366 #if PLATFORM(IOS_FAMILY)
367         scaledExposedRect = m_webPageProxy.exposedContentRect();
368 #else
369         if (viewExposedRect())
370             scaledExposedRect = viewExposedRect().value();
371         float scale = 1 / m_webPageProxy.pageScaleFactor();
372         scaledExposedRect.scale(scale);
373 #endif
374         [m_exposedRectIndicatorLayer setPosition:scaledExposedRect.location()];
375         [m_exposedRectIndicatorLayer setBounds:FloatRect(FloatPoint(), scaledExposedRect.size())];
376     } else {
377         [m_exposedRectIndicatorLayer setPosition:scrollPosition];
378         [m_exposedRectIndicatorLayer setBounds:FloatRect(FloatPoint(), viewSize)];
379     }
380 }
381
382 void RemoteLayerTreeDrawingAreaProxy::initializeDebugIndicator()
383 {
384     m_debugIndicatorLayerTreeHost = std::make_unique<RemoteLayerTreeHost>(*this);
385     m_debugIndicatorLayerTreeHost->setIsDebugLayerTreeHost(true);
386
387     m_tileMapHostLayer = adoptNS([[CALayer alloc] init]);
388     [m_tileMapHostLayer setName:@"Tile map host"];
389     [m_tileMapHostLayer setDelegate:[WebActionDisablingCALayerDelegate shared]];
390     [m_tileMapHostLayer setAnchorPoint:CGPointZero];
391     [m_tileMapHostLayer setOpacity:0.8];
392     [m_tileMapHostLayer setMasksToBounds:YES];
393     [m_tileMapHostLayer setBorderWidth:2];
394
395     CGColorSpaceRef colorSpace = sRGBColorSpaceRef();
396     {
397         const CGFloat components[] = { 1, 1, 1, 0.6 };
398         RetainPtr<CGColorRef> color = adoptCF(CGColorCreate(colorSpace, components));
399         [m_tileMapHostLayer setBackgroundColor:color.get()];
400
401         const CGFloat borderComponents[] = { 0, 0, 0, 1 };
402         RetainPtr<CGColorRef> borderColor = adoptCF(CGColorCreate(colorSpace, borderComponents));
403         [m_tileMapHostLayer setBorderColor:borderColor.get()];
404     }
405     
406     m_exposedRectIndicatorLayer = adoptNS([[CALayer alloc] init]);
407     [m_exposedRectIndicatorLayer setDelegate:[WebActionDisablingCALayerDelegate shared]];
408     [m_exposedRectIndicatorLayer setAnchorPoint:CGPointZero];
409
410     {
411         const CGFloat components[] = { 0, 1, 0, 1 };
412         RetainPtr<CGColorRef> color = adoptCF(CGColorCreate(colorSpace, components));
413         [m_exposedRectIndicatorLayer setBorderColor:color.get()];
414     }
415 }
416
417 void RemoteLayerTreeDrawingAreaProxy::didRefreshDisplay()
418 {
419     if (!m_webPageProxy.hasRunningProcess())
420         return;
421
422     if (m_didUpdateMessageState != NeedsDidUpdate) {
423         m_didUpdateMessageState = MissedCommit;
424 #if PLATFORM(IOS_FAMILY)
425         [displayLinkHandler() pause];
426 #endif
427         return;
428     }
429     
430     m_didUpdateMessageState = DoesNotNeedDidUpdate;
431
432     // Waiting for CA to commit is insufficient, because the render server can still be
433     // using our backing store. We can improve this by waiting for the render server to commit
434     // if we find API to do so, but for now we will make extra buffers if need be.
435     send(Messages::DrawingArea::DidUpdate());
436
437     m_lastVisibleTransactionID = m_transactionIDForPendingCACommit;
438
439     m_webPageProxy.didUpdateActivityState();
440 }
441
442 void RemoteLayerTreeDrawingAreaProxy::waitForDidUpdateActivityState(ActivityStateChangeID activityStateChangeID)
443 {
444     ASSERT(activityStateChangeID != ActivityStateChangeAsynchronous);
445
446     // We must send the didUpdate message before blocking on the next commit, otherwise
447     // we can be guaranteed that the next commit won't come until after the waitForAndDispatchImmediately times out.
448     if (m_didUpdateMessageState != DoesNotNeedDidUpdate)
449         didRefreshDisplay();
450
451     static Seconds activityStateUpdateTimeout = [] {
452         if (id value = [[NSUserDefaults standardUserDefaults] objectForKey:@"WebKitOverrideActivityStateUpdateTimeout"])
453             return Seconds([value doubleValue]);
454
455 #if PLATFORM(IOS_FAMILY)
456         return Seconds::fromMilliseconds(500);
457 #else
458         return Seconds::fromMilliseconds(250);
459 #endif
460     }();
461
462     auto startTime = MonotonicTime::now();
463     while (process().connection()->waitForAndDispatchImmediately<Messages::RemoteLayerTreeDrawingAreaProxy::CommitLayerTree>(m_identifier.toUInt64(), activityStateUpdateTimeout - (MonotonicTime::now() - startTime), IPC::WaitForOption::InterruptWaitingIfSyncMessageArrives)) {
464         if (activityStateChangeID == ActivityStateChangeAsynchronous || activityStateChangeID <= m_activityStateChangeID)
465             return;
466     }
467 }
468
469 void RemoteLayerTreeDrawingAreaProxy::dispatchAfterEnsuringDrawing(WTF::Function<void (CallbackBase::Error)>&& callbackFunction)
470 {
471     if (!m_webPageProxy.hasRunningProcess()) {
472         callbackFunction(CallbackBase::Error::OwnerWasInvalidated);
473         return;
474     }
475
476     send(Messages::DrawingArea::AddTransactionCallbackID(m_callbacks.put(WTFMove(callbackFunction), process().throttler().backgroundActivityToken())));
477 }
478
479 void RemoteLayerTreeDrawingAreaProxy::hideContentUntilPendingUpdate()
480 {
481     m_transactionIDForUnhidingContent = nextLayerTreeTransactionID();
482     m_remoteLayerTreeHost->detachRootLayer();
483 }
484
485 void RemoteLayerTreeDrawingAreaProxy::hideContentUntilAnyUpdate()
486 {
487     m_remoteLayerTreeHost->detachRootLayer();
488 }
489
490 void RemoteLayerTreeDrawingAreaProxy::prepareForAppSuspension()
491 {
492     m_remoteLayerTreeHost->mapAllIOSurfaceBackingStore();
493 }
494
495 bool RemoteLayerTreeDrawingAreaProxy::hasVisibleContent() const
496 {
497     return m_remoteLayerTreeHost->rootLayer();
498 }
499
500 bool RemoteLayerTreeDrawingAreaProxy::isAlwaysOnLoggingAllowed() const
501 {
502     return m_webPageProxy.isAlwaysOnLoggingAllowed();
503 }
504
505 CALayer *RemoteLayerTreeDrawingAreaProxy::layerWithIDForTesting(uint64_t layerID) const
506 {
507     return m_remoteLayerTreeHost->layerWithIDForTesting(layerID);
508 }
509
510 } // namespace WebKit