56b3d2f60a1bd21357cc3aacc7c25568247c9ad8
[WebKit-https.git] / Source / WebCore / platform / graphics / mac / MediaPlayerPrivateQTKit.mm
1 /*
2  * Copyright (C) 2007, 2008, 2009, 2010, 2011 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. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #import "config.h"
27
28 #if ENABLE(VIDEO)
29
30 #import "MediaPlayerPrivateQTKit.h"
31
32 #import "BlockExceptions.h"
33 #import "DocumentLoader.h"
34 #import "GraphicsContext.h"
35 #import "URL.h"
36 #import "Logging.h"
37 #import "MIMETypeRegistry.h"
38 #import "MediaTimeQTKit.h"
39 #import "PlatformLayer.h"
40 #import "PlatformTimeRanges.h"
41 #import "SecurityOrigin.h"
42 #import "SoftLinking.h"
43 #import "WebCoreSystemInterface.h"
44 #import <QTKit/QTKit.h>
45 #import <objc/runtime.h>
46
47 SOFT_LINK_FRAMEWORK(QTKit)
48
49 SOFT_LINK(QTKit, QTMakeTime, QTTime, (long long timeValue, long timeScale), (timeValue, timeScale))
50
51 SOFT_LINK_CLASS(QTKit, QTMovie)
52 SOFT_LINK_CLASS(QTKit, QTMovieLayer)
53
54 SOFT_LINK_POINTER(QTKit, QTTrackMediaTypeAttribute, NSString *)
55 SOFT_LINK_POINTER(QTKit, QTMediaTypeAttribute, NSString *)
56 SOFT_LINK_POINTER(QTKit, QTMediaTypeBase, NSString *)
57 SOFT_LINK_POINTER(QTKit, QTMediaTypeMPEG, NSString *)
58 SOFT_LINK_POINTER(QTKit, QTMediaTypeSound, NSString *)
59 SOFT_LINK_POINTER(QTKit, QTMediaTypeText, NSString *)
60 SOFT_LINK_POINTER(QTKit, QTMediaTypeVideo, NSString *)
61 SOFT_LINK_POINTER(QTKit, QTMovieAskUnresolvedDataRefsAttribute, NSString *)
62 SOFT_LINK_POINTER(QTKit, QTMovieLoopsAttribute, NSString *)
63 SOFT_LINK_POINTER(QTKit, QTMovieDataAttribute, NSString *)
64 SOFT_LINK_POINTER(QTKit, QTMovieDataSizeAttribute, NSString *)
65 SOFT_LINK_POINTER(QTKit, QTMovieDidEndNotification, NSString *)
66 SOFT_LINK_POINTER(QTKit, QTMovieHasVideoAttribute, NSString *)
67 SOFT_LINK_POINTER(QTKit, QTMovieHasAudioAttribute, NSString *)
68 SOFT_LINK_POINTER(QTKit, QTMovieIsActiveAttribute, NSString *)
69 SOFT_LINK_POINTER(QTKit, QTMovieLoadStateAttribute, NSString *)
70 SOFT_LINK_POINTER(QTKit, QTMovieLoadStateErrorAttribute, NSString *)
71 SOFT_LINK_POINTER(QTKit, QTMovieLoadStateDidChangeNotification, NSString *)
72 SOFT_LINK_POINTER(QTKit, QTMovieNaturalSizeAttribute, NSString *)
73 SOFT_LINK_POINTER(QTKit, QTMovieCurrentSizeAttribute, NSString *)
74 SOFT_LINK_POINTER(QTKit, QTMoviePreventExternalURLLinksAttribute, NSString *)
75 SOFT_LINK_POINTER(QTKit, QTMovieRateChangesPreservePitchAttribute, NSString *)
76 SOFT_LINK_POINTER(QTKit, QTMovieRateDidChangeNotification, NSString *)
77 SOFT_LINK_POINTER(QTKit, QTMovieSizeDidChangeNotification, NSString *)
78 SOFT_LINK_POINTER(QTKit, QTMovieTimeDidChangeNotification, NSString *)
79 SOFT_LINK_POINTER(QTKit, QTMovieTimeScaleAttribute, NSString *)
80 SOFT_LINK_POINTER(QTKit, QTMovieURLAttribute, NSString *)
81 SOFT_LINK_POINTER(QTKit, QTMovieVolumeDidChangeNotification, NSString *)
82 SOFT_LINK_POINTER(QTKit, QTSecurityPolicyNoCrossSiteAttribute, NSString *)
83 SOFT_LINK_POINTER(QTKit, QTVideoRendererWebKitOnlyNewImageAvailableNotification, NSString *)
84 SOFT_LINK_POINTER(QTKit, QTMovieApertureModeClean, NSString *)
85 SOFT_LINK_POINTER(QTKit, QTMovieApertureModeAttribute, NSString *)
86
87 SOFT_LINK_POINTER_OPTIONAL(QTKit, QTSecurityPolicyNoLocalToRemoteSiteAttribute, NSString *)
88 SOFT_LINK_POINTER_OPTIONAL(QTKit, QTSecurityPolicyNoRemoteToLocalSiteAttribute, NSString *)
89
90 @interface QTMovie(WebKitExtras)
91 - (QTTime)maxTimeLoaded;
92 - (NSArray *)availableRanges;
93 - (NSArray *)loadedRanges;
94 @end
95
96 #define QTMovie getQTMovieClass()
97 #define QTMovieLayer getQTMovieLayerClass()
98
99 #define QTTrackMediaTypeAttribute getQTTrackMediaTypeAttribute()
100 #define QTMediaTypeAttribute getQTMediaTypeAttribute()
101 #define QTMediaTypeBase getQTMediaTypeBase()
102 #define QTMediaTypeMPEG getQTMediaTypeMPEG()
103 #define QTMediaTypeSound getQTMediaTypeSound()
104 #define QTMediaTypeText getQTMediaTypeText()
105 #define QTMediaTypeVideo getQTMediaTypeVideo()
106 #define QTMovieAskUnresolvedDataRefsAttribute getQTMovieAskUnresolvedDataRefsAttribute()
107 #define QTMovieLoopsAttribute getQTMovieLoopsAttribute()
108 #define QTMovieDataAttribute getQTMovieDataAttribute()
109 #define QTMovieDataSizeAttribute getQTMovieDataSizeAttribute()
110 #define QTMovieDidEndNotification getQTMovieDidEndNotification()
111 #define QTMovieHasVideoAttribute getQTMovieHasVideoAttribute()
112 #define QTMovieHasAudioAttribute getQTMovieHasAudioAttribute()
113 #define QTMovieIsActiveAttribute getQTMovieIsActiveAttribute()
114 #define QTMovieLoadStateAttribute getQTMovieLoadStateAttribute()
115 #define QTMovieLoadStateErrorAttribute getQTMovieLoadStateErrorAttribute()
116 #define QTMovieLoadStateDidChangeNotification getQTMovieLoadStateDidChangeNotification()
117 #define QTMovieNaturalSizeAttribute getQTMovieNaturalSizeAttribute()
118 #define QTMovieCurrentSizeAttribute getQTMovieCurrentSizeAttribute()
119 #define QTMoviePreventExternalURLLinksAttribute getQTMoviePreventExternalURLLinksAttribute()
120 #define QTMovieRateChangesPreservePitchAttribute getQTMovieRateChangesPreservePitchAttribute()
121 #define QTMovieRateDidChangeNotification getQTMovieRateDidChangeNotification()
122 #define QTMovieSizeDidChangeNotification getQTMovieSizeDidChangeNotification()
123 #define QTMovieTimeDidChangeNotification getQTMovieTimeDidChangeNotification()
124 #define QTMovieTimeScaleAttribute getQTMovieTimeScaleAttribute()
125 #define QTMovieURLAttribute getQTMovieURLAttribute()
126 #define QTMovieVolumeDidChangeNotification getQTMovieVolumeDidChangeNotification()
127 #define QTSecurityPolicyNoCrossSiteAttribute getQTSecurityPolicyNoCrossSiteAttribute()
128 #define QTSecurityPolicyNoLocalToRemoteSiteAttribute getQTSecurityPolicyNoLocalToRemoteSiteAttribute()
129 #define QTSecurityPolicyNoRemoteToLocalSiteAttribute getQTSecurityPolicyNoRemoteToLocalSiteAttribute()
130 #define QTVideoRendererWebKitOnlyNewImageAvailableNotification getQTVideoRendererWebKitOnlyNewImageAvailableNotification()
131 #define QTMovieApertureModeClean getQTMovieApertureModeClean()
132 #define QTMovieApertureModeAttribute getQTMovieApertureModeAttribute()
133
134 // Older versions of the QTKit header don't have these constants.
135 #if !defined QTKIT_VERSION_MAX_ALLOWED || QTKIT_VERSION_MAX_ALLOWED <= QTKIT_VERSION_7_0
136 enum {
137     QTMovieLoadStateError = -1L,
138     QTMovieLoadStateLoaded  = 2000L,
139     QTMovieLoadStatePlayable = 10000L,
140     QTMovieLoadStatePlaythroughOK = 20000L,
141     QTMovieLoadStateComplete = 100000L
142 };
143 #endif
144
145 using namespace WebCore;
146
147 @interface WebCoreMovieObserver : NSObject
148 {
149     MediaPlayerPrivateQTKit* m_callback;
150     BOOL m_delayCallbacks;
151 }
152 -(id)initWithCallback:(MediaPlayerPrivateQTKit*)callback;
153 -(void)disconnect;
154 -(void)repaint;
155 -(void)setDelayCallbacks:(BOOL)shouldDelay;
156 -(void)loadStateChanged:(NSNotification *)notification;
157 -(void)rateChanged:(NSNotification *)notification;
158 -(void)sizeChanged:(NSNotification *)notification;
159 -(void)timeChanged:(NSNotification *)notification;
160 -(void)didEnd:(NSNotification *)notification;
161 -(void)layerHostChanged:(NSNotification *)notification;
162 @end
163
164 @protocol WebKitVideoRenderingDetails
165 -(void)setMovie:(id)movie;
166 -(void)drawInRect:(NSRect)rect;
167 @end
168
169 namespace WebCore {
170
171 PassOwnPtr<MediaPlayerPrivateInterface> MediaPlayerPrivateQTKit::create(MediaPlayer* player)
172
173     return adoptPtr(new MediaPlayerPrivateQTKit(player));
174 }
175
176 void MediaPlayerPrivateQTKit::registerMediaEngine(MediaEngineRegistrar registrar)
177 {
178     if (isAvailable())
179         registrar(create, getSupportedTypes, supportsType, getSitesInMediaCache, clearMediaCache, clearMediaCacheForSite, 0);
180 }
181
182 MediaPlayerPrivateQTKit::MediaPlayerPrivateQTKit(MediaPlayer* player)
183     : m_player(player)
184     , m_objcObserver(adoptNS([[WebCoreMovieObserver alloc] initWithCallback:this]))
185     , m_seekTo(MediaTime::invalidTime())
186     , m_seekTimer(this, &MediaPlayerPrivateQTKit::seekTimerFired)
187     , m_networkState(MediaPlayer::Empty)
188     , m_readyState(MediaPlayer::HaveNothing)
189     , m_rect()
190     , m_scaleFactor(1, 1)
191     , m_enabledTrackCount(0)
192     , m_totalTrackCount(0)
193     , m_reportedDuration(MediaTime::invalidTime())
194     , m_cachedDuration(MediaTime::invalidTime())
195     , m_timeToRestore(MediaTime::invalidTime())
196     , m_preload(MediaPlayer::Auto)
197     , m_startedPlaying(false)
198     , m_isStreaming(false)
199     , m_visible(false)
200     , m_hasUnsupportedTracks(false)
201     , m_videoFrameHasDrawn(false)
202     , m_isAllowedToRender(false)
203     , m_privateBrowsing(false)
204 {
205 }
206
207 MediaPlayerPrivateQTKit::~MediaPlayerPrivateQTKit()
208 {
209     LOG(Media, "MediaPlayerPrivateQTKit::~MediaPlayerPrivateQTKit(%p)", this);
210     tearDownVideoRendering();
211
212     [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()];
213     [m_objcObserver.get() disconnect];
214 }
215
216 NSMutableDictionary *MediaPlayerPrivateQTKit::commonMovieAttributes() 
217 {
218     NSMutableDictionary *movieAttributes = [NSMutableDictionary dictionaryWithObjectsAndKeys:
219             [NSNumber numberWithBool:m_player->preservesPitch()], QTMovieRateChangesPreservePitchAttribute,
220             [NSNumber numberWithBool:YES], QTMoviePreventExternalURLLinksAttribute,
221             [NSNumber numberWithBool:NO], QTMovieAskUnresolvedDataRefsAttribute,
222             [NSNumber numberWithBool:NO], QTMovieLoopsAttribute,
223             [NSNumber numberWithBool:!m_privateBrowsing], @"QTMovieAllowPersistentCacheAttribute",
224             QTMovieApertureModeClean, QTMovieApertureModeAttribute,
225             nil];
226
227     // Check to see if QTSecurityPolicyNoRemoteToLocalSiteAttribute is defined, which was added in QTKit 7.6.3.
228     // If not, just set NoCrossSite = YES, which does the same thing as NoRemoteToLocal = YES and 
229     // NoLocalToRemote = YES in QTKit < 7.6.3.
230     if (QTSecurityPolicyNoRemoteToLocalSiteAttribute) {
231         [movieAttributes setValue:[NSNumber numberWithBool:NO] forKey:QTSecurityPolicyNoCrossSiteAttribute];
232         [movieAttributes setValue:[NSNumber numberWithBool:YES] forKey:QTSecurityPolicyNoRemoteToLocalSiteAttribute];
233         [movieAttributes setValue:[NSNumber numberWithBool:YES] forKey:QTSecurityPolicyNoLocalToRemoteSiteAttribute];
234     } else
235         [movieAttributes setValue:[NSNumber numberWithBool:YES] forKey:QTSecurityPolicyNoCrossSiteAttribute];
236
237     if (m_preload < MediaPlayer::Auto)
238         [movieAttributes setValue:[NSNumber numberWithBool:YES] forKey:@"QTMovieLimitReadAheadAttribute"];
239
240     return movieAttributes;
241 }
242
243 void MediaPlayerPrivateQTKit::createQTMovie(const String& url)
244 {
245     URL kURL(ParsedURLString, url);
246     NSURL *cocoaURL = kURL;
247     NSMutableDictionary *movieAttributes = commonMovieAttributes();    
248     [movieAttributes setValue:cocoaURL forKey:QTMovieURLAttribute];
249
250     CFDictionaryRef proxySettings = CFNetworkCopySystemProxySettings();
251     CFArrayRef proxiesForURL = CFNetworkCopyProxiesForURL((CFURLRef)cocoaURL, proxySettings);
252     BOOL willUseProxy = YES;
253     
254     if (!proxiesForURL || !CFArrayGetCount(proxiesForURL))
255         willUseProxy = NO;
256     
257     if (CFArrayGetCount(proxiesForURL) == 1) {
258         CFDictionaryRef proxy = (CFDictionaryRef)CFArrayGetValueAtIndex(proxiesForURL, 0);
259         ASSERT(CFGetTypeID(proxy) == CFDictionaryGetTypeID());
260         
261         CFStringRef proxyType = (CFStringRef)CFDictionaryGetValue(proxy, kCFProxyTypeKey);
262         ASSERT(CFGetTypeID(proxyType) == CFStringGetTypeID());
263         
264         if (CFStringCompare(proxyType, kCFProxyTypeNone, 0) == kCFCompareEqualTo)
265             willUseProxy = NO;
266     }
267
268     if (!willUseProxy && !kURL.protocolIsData()) {
269         // Only pass the QTMovieOpenForPlaybackAttribute flag if there are no proxy servers, due
270         // to rdar://problem/7531776, or if not loading a data:// url due to rdar://problem/8103801.
271         [movieAttributes setObject:[NSNumber numberWithBool:YES] forKey:@"QTMovieOpenForPlaybackAttribute"];
272     }
273     
274     if (proxiesForURL)
275         CFRelease(proxiesForURL);
276     if (proxySettings)
277         CFRelease(proxySettings);
278
279     createQTMovie(cocoaURL, movieAttributes);
280 }
281
282 static void disableComponentsOnce()
283 {
284     static bool sComponentsDisabled = false;
285     if (sComponentsDisabled)
286         return;
287     sComponentsDisabled = true;
288
289     // eat/PDF and grip/PDF components must be disabled twice since they are registered twice
290     // with different flags.  However, there is currently a bug in 64-bit QTKit (<rdar://problem/8378237>)
291     // which causes subsequent disable component requests of exactly the same type to be ignored if
292     // QTKitServer has not yet started.  As a result, we must pass in exactly the flags we want to
293     // disable per component.  As a failsafe, if in the future these flags change, we will disable the
294     // PDF components for a third time with a wildcard flags field:
295     uint32_t componentsToDisable[11][5] = {
296         {'eat ', 'TEXT', 'text', 0, 0},
297         {'eat ', 'TXT ', 'text', 0, 0},    
298         {'eat ', 'utxt', 'text', 0, 0},  
299         {'eat ', 'TEXT', 'tx3g', 0, 0},  
300         {'eat ', 'PDF ', 'vide', 0x44802, 0},
301         {'eat ', 'PDF ', 'vide', 0x45802, 0},
302         {'eat ', 'PDF ', 'vide', 0, 0},  
303         {'grip', 'PDF ', 'appl', 0x844a00, 0},
304         {'grip', 'PDF ', 'appl', 0x845a00, 0},
305         {'grip', 'PDF ', 'appl', 0, 0},  
306         {'imdc', 'pdf ', 'appl', 0, 0},  
307     };
308
309     for (size_t i = 0; i < WTF_ARRAY_LENGTH(componentsToDisable); ++i) 
310         wkQTMovieDisableComponent(componentsToDisable[i]);
311 }
312
313 void MediaPlayerPrivateQTKit::createQTMovie(NSURL *url, NSDictionary *movieAttributes)
314 {
315     LOG(Media, "MediaPlayerPrivateQTKit::createQTMovie(%p) ", this);
316     disableComponentsOnce();
317
318     [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()];
319     
320     bool recreating = false;
321     if (m_qtMovie) {
322         recreating = true;
323         destroyQTVideoRenderer();
324         m_qtMovie = 0;
325     }
326     
327     // Disable rtsp streams for now, <rdar://problem/5693967>
328     if (protocolIs([url scheme], "rtsp"))
329         return;
330     
331     NSError *error = nil;
332     m_qtMovie = adoptNS([[QTMovie alloc] initWithAttributes:movieAttributes error:&error]);
333     
334     if (!m_qtMovie)
335         return;
336     
337     [m_qtMovie.get() setVolume:m_player->volume()];
338
339     if (recreating && hasVideo())
340         createQTVideoRenderer(QTVideoRendererModeListensForNewImages);
341     
342     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
343                                              selector:@selector(loadStateChanged:) 
344                                                  name:QTMovieLoadStateDidChangeNotification 
345                                                object:m_qtMovie.get()];
346
347     // In updateState(), we track when maxTimeLoaded() == duration().
348     // In newer version of QuickTime, a notification is emitted when maxTimeLoaded changes.
349     // In older version of QuickTime, QTMovieLoadStateDidChangeNotification be fired.
350     if (NSString *maxTimeLoadedChangeNotification = wkQTMovieMaxTimeLoadedChangeNotification()) {
351         [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
352                                                  selector:@selector(loadedRangesChanged:)
353                                                      name:maxTimeLoadedChangeNotification
354                                                    object:m_qtMovie.get()];        
355     }
356
357     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
358                                              selector:@selector(rateChanged:) 
359                                                  name:QTMovieRateDidChangeNotification 
360                                                object:m_qtMovie.get()];
361     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
362                                              selector:@selector(sizeChanged:) 
363                                                  name:QTMovieSizeDidChangeNotification 
364                                                object:m_qtMovie.get()];
365     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
366                                              selector:@selector(timeChanged:) 
367                                                  name:QTMovieTimeDidChangeNotification 
368                                                object:m_qtMovie.get()];
369     [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
370                                              selector:@selector(didEnd:) 
371                                                  name:QTMovieDidEndNotification 
372                                                object:m_qtMovie.get()];
373 }
374
375 static Class QTVideoRendererClass()
376 {
377      static Class QTVideoRendererWebKitOnlyClass = NSClassFromString(@"QTVideoRendererWebKitOnly");
378      return QTVideoRendererWebKitOnlyClass;
379 }
380
381 void MediaPlayerPrivateQTKit::createQTVideoRenderer(QTVideoRendererMode rendererMode)
382 {
383     LOG(Media, "MediaPlayerPrivateQTKit::createQTVideoRenderer(%p)", this);
384     destroyQTVideoRenderer();
385
386     m_qtVideoRenderer = adoptNS([[QTVideoRendererClass() alloc] init]);
387     if (!m_qtVideoRenderer)
388         return;
389     
390     // associate our movie with our instance of QTVideoRendererWebKitOnly
391     [(id<WebKitVideoRenderingDetails>)m_qtVideoRenderer.get() setMovie:m_qtMovie.get()];
392
393     if (rendererMode == QTVideoRendererModeListensForNewImages) {
394         // listen to QTVideoRendererWebKitOnly's QTVideoRendererWebKitOnlyNewImageDidBecomeAvailableNotification
395         [[NSNotificationCenter defaultCenter] addObserver:m_objcObserver.get()
396                                                  selector:@selector(newImageAvailable:)
397                                                      name:QTVideoRendererWebKitOnlyNewImageAvailableNotification
398                                                    object:m_qtVideoRenderer.get()];
399     }
400 }
401
402 void MediaPlayerPrivateQTKit::destroyQTVideoRenderer()
403 {
404     LOG(Media, "MediaPlayerPrivateQTKit::destroyQTVideoRenderer(%p)", this);
405     if (!m_qtVideoRenderer)
406         return;
407
408     // stop observing the renderer's notifications before we toss it
409     [[NSNotificationCenter defaultCenter] removeObserver:m_objcObserver.get()
410                                                     name:QTVideoRendererWebKitOnlyNewImageAvailableNotification
411                                                   object:m_qtVideoRenderer.get()];
412
413     // disassociate our movie from our instance of QTVideoRendererWebKitOnly
414     [(id<WebKitVideoRenderingDetails>)m_qtVideoRenderer.get() setMovie:nil];    
415
416     m_qtVideoRenderer = nil;
417 }
418
419 void MediaPlayerPrivateQTKit::createQTMovieLayer()
420 {
421     LOG(Media, "MediaPlayerPrivateQTKit::createQTMovieLayer(%p)", this);
422     if (!m_qtMovie)
423         return;
424
425     ASSERT(supportsAcceleratedRendering());
426     
427     if (!m_qtVideoLayer) {
428         m_qtVideoLayer = adoptNS([[QTMovieLayer alloc] init]);
429         if (!m_qtVideoLayer)
430             return;
431
432         [m_qtVideoLayer.get() setMovie:m_qtMovie.get()];
433 #ifndef NDEBUG
434         [(CALayer *)m_qtVideoLayer.get() setName:@"Video layer"];
435 #endif
436         // The layer will get hooked up via RenderLayerBacking::updateConfiguration().
437     }
438 }
439
440 void MediaPlayerPrivateQTKit::destroyQTMovieLayer()
441 {
442     LOG(Media, "MediaPlayerPrivateQTKit::destroyQTMovieLayer(%p)", this);
443     if (!m_qtVideoLayer)
444         return;
445
446     // disassociate our movie from our instance of QTMovieLayer
447     [m_qtVideoLayer.get() setMovie:nil];    
448     m_qtVideoLayer = nil;
449 }
450
451 MediaPlayerPrivateQTKit::MediaRenderingMode MediaPlayerPrivateQTKit::currentRenderingMode() const
452 {
453     if (m_qtVideoLayer)
454         return MediaRenderingMovieLayer;
455
456     if (m_qtVideoRenderer)
457         return MediaRenderingSoftwareRenderer;
458     
459     return MediaRenderingNone;
460 }
461
462 MediaPlayerPrivateQTKit::MediaRenderingMode MediaPlayerPrivateQTKit::preferredRenderingMode() const
463 {
464     if (!m_qtMovie)
465         return MediaRenderingNone;
466
467     if (supportsAcceleratedRendering() && m_player->client().mediaPlayerRenderingCanBeAccelerated(m_player))
468         return MediaRenderingMovieLayer;
469
470     if (!QTVideoRendererClass())
471         return MediaRenderingNone;
472     
473     return MediaRenderingSoftwareRenderer;
474 }
475
476 void MediaPlayerPrivateQTKit::setUpVideoRendering()
477 {
478     LOG(Media, "MediaPlayerPrivateQTKit::setUpVideoRendering(%p)", this);
479     if (!isReadyForVideoSetup())
480         return;
481
482     MediaRenderingMode currentMode = currentRenderingMode();
483     MediaRenderingMode preferredMode = preferredRenderingMode();
484     if (currentMode == preferredMode && currentMode != MediaRenderingNone)
485         return;
486
487     if (currentMode != MediaRenderingNone)  
488         tearDownVideoRendering();
489
490     switch (preferredMode) {
491     case MediaRenderingNone:
492     case MediaRenderingSoftwareRenderer:
493         createQTVideoRenderer(QTVideoRendererModeListensForNewImages);
494         break;
495     case MediaRenderingMovieLayer:
496         createQTMovieLayer();
497         break;
498     }
499
500     // If using a movie layer, inform the client so the compositing tree is updated.
501     if (currentMode == MediaRenderingMovieLayer || preferredMode == MediaRenderingMovieLayer)
502         m_player->client().mediaPlayerRenderingModeChanged(m_player);
503 }
504
505 void MediaPlayerPrivateQTKit::tearDownVideoRendering()
506 {
507     LOG(Media, "MediaPlayerPrivateQTKit::tearDownVideoRendering(%p)", this);
508     if (m_qtVideoRenderer)
509         destroyQTVideoRenderer();
510     if (m_qtVideoLayer)
511         destroyQTMovieLayer();
512 }
513
514 bool MediaPlayerPrivateQTKit::hasSetUpVideoRendering() const
515 {
516     return m_qtVideoLayer
517         || m_qtVideoRenderer;
518 }
519
520 void MediaPlayerPrivateQTKit::resumeLoad()
521 {
522     if (!m_movieURL.isNull())
523         loadInternal(m_movieURL);
524 }
525
526 void MediaPlayerPrivateQTKit::load(const String& url)
527 {
528     LOG(Media, "MediaPlayerPrivateQTKit::load(%p)", this);
529     m_movieURL = url;
530
531     // If the element is not supposed to load any data return immediately.
532     if (m_preload == MediaPlayer::None)
533         return;
534
535     loadInternal(url);
536 }
537
538 void MediaPlayerPrivateQTKit::loadInternal(const String& url)
539 {
540     if (m_networkState != MediaPlayer::Loading) {
541         m_networkState = MediaPlayer::Loading;
542         m_player->networkStateChanged();
543     }
544     if (m_readyState != MediaPlayer::HaveNothing) {
545         m_readyState = MediaPlayer::HaveNothing;
546         m_player->readyStateChanged();
547     }
548     cancelSeek();
549     m_videoFrameHasDrawn = false;
550     
551     [m_objcObserver.get() setDelayCallbacks:YES];
552
553     createQTMovie(url);
554
555     [m_objcObserver.get() loadStateChanged:nil];
556     [m_objcObserver.get() setDelayCallbacks:NO];
557 }
558
559 #if ENABLE(MEDIA_SOURCE)
560 void MediaPlayerPrivateQTKit::load(const String&, MediaSourcePrivateClient*)
561 {
562     m_networkState = MediaPlayer::FormatError;
563     m_player->networkStateChanged();
564 }
565 #endif
566
567
568 void MediaPlayerPrivateQTKit::prepareToPlay()
569 {
570     LOG(Media, "MediaPlayerPrivateQTKit::prepareToPlay(%p)", this);
571     setPreload(MediaPlayer::Auto);
572 }
573
574 PlatformMedia MediaPlayerPrivateQTKit::platformMedia() const
575 {
576     PlatformMedia pm;
577     pm.type = PlatformMedia::QTMovieType;
578     pm.media.qtMovie = m_qtMovie.get();
579     return pm;
580 }
581
582 PlatformLayer* MediaPlayerPrivateQTKit::platformLayer() const
583 {
584     return m_qtVideoLayer.get();
585 }
586
587 void MediaPlayerPrivateQTKit::play()
588 {
589     LOG(Media, "MediaPlayerPrivateQTKit::play(%p)", this);
590     if (!metaDataAvailable())
591         return;
592     m_startedPlaying = true;
593     [m_objcObserver.get() setDelayCallbacks:YES];
594     [m_qtMovie.get() setRate:m_player->rate()];
595     [m_objcObserver.get() setDelayCallbacks:NO];
596 }
597
598 void MediaPlayerPrivateQTKit::pause()
599 {
600     LOG(Media, "MediaPlayerPrivateQTKit::pause(%p)", this);
601     if (!metaDataAvailable())
602         return;
603     m_startedPlaying = false;
604     [m_objcObserver.get() setDelayCallbacks:YES];
605     [m_qtMovie.get() stop];
606     [m_objcObserver.get() setDelayCallbacks:NO];
607 }
608
609 MediaTime MediaPlayerPrivateQTKit::durationMediaTime() const
610 {
611     if (!metaDataAvailable())
612         return MediaTime::zeroTime();
613
614     if (m_cachedDuration.isValid())
615         return m_cachedDuration;
616
617     QTTime time = [m_qtMovie.get() duration];
618     if (time.flags == kQTTimeIsIndefinite)
619         return MediaTime::positiveInfiniteTime();
620     return toMediaTime(time);
621 }
622
623 MediaTime MediaPlayerPrivateQTKit::currentMediaTime() const
624 {
625     if (!metaDataAvailable())
626         return MediaTime::zeroTime();
627     QTTime time = [m_qtMovie.get() currentTime];
628     return toMediaTime(time);
629 }
630
631 void MediaPlayerPrivateQTKit::seek(const MediaTime& inTime)
632 {
633     MediaTime time = inTime;
634     LOG(Media, "MediaPlayerPrivateQTKit::seek(%p) - time %s", this, toString(time).utf8().data());
635     // Nothing to do if we are already in the middle of a seek to the same time.
636     if (time == m_seekTo)
637         return;
638
639     cancelSeek();
640     
641     if (!metaDataAvailable())
642         return;
643     
644     if (time > durationMediaTime())
645         time = durationMediaTime();
646
647     m_seekTo = time;
648     if (maxMediaTimeSeekable() >= m_seekTo)
649         doSeek();
650     else 
651         m_seekTimer.start(0, 0.5f);
652 }
653
654 void MediaPlayerPrivateQTKit::doSeek() 
655 {
656     QTTime qttime = toQTTime(m_seekTo);
657     // setCurrentTime generates several event callbacks, update afterwards
658     [m_objcObserver.get() setDelayCallbacks:YES];
659     float oldRate = [m_qtMovie.get() rate];
660
661     if (oldRate)
662         [m_qtMovie.get() setRate:0];
663     [m_qtMovie.get() setCurrentTime:qttime];
664
665     // restore playback only if not at end, otherwise QTMovie will loop
666     MediaTime timeAfterSeek = currentMediaTime();
667     if (oldRate && timeAfterSeek < durationMediaTime())
668         [m_qtMovie.get() setRate:oldRate];
669
670     cancelSeek();
671     [m_objcObserver.get() setDelayCallbacks:NO];
672 }
673
674 void MediaPlayerPrivateQTKit::cancelSeek()
675 {
676     LOG(Media, "MediaPlayerPrivateQTKit::cancelSeek(%p)", this);
677     m_seekTo = MediaTime::invalidTime();
678     m_seekTimer.stop();
679 }
680
681 void MediaPlayerPrivateQTKit::seekTimerFired(Timer&)
682 {        
683     if (!metaDataAvailable() || !seeking() || currentMediaTime() == m_seekTo) {
684         cancelSeek();
685         updateStates();
686         m_player->timeChanged(); 
687         return;
688     } 
689
690     if (maxMediaTimeSeekable() >= m_seekTo)
691         doSeek();
692     else {
693         MediaPlayer::NetworkState state = networkState();
694         if (state == MediaPlayer::Empty || state == MediaPlayer::Loaded) {
695             cancelSeek();
696             updateStates();
697             m_player->timeChanged();
698         }
699     }
700 }
701
702 bool MediaPlayerPrivateQTKit::paused() const
703 {
704     if (!metaDataAvailable())
705         return true;
706     return [m_qtMovie.get() rate] == 0;
707 }
708
709 bool MediaPlayerPrivateQTKit::seeking() const
710 {
711     if (!metaDataAvailable())
712         return false;
713     return m_seekTo >= MediaTime::zeroTime();
714 }
715
716 IntSize MediaPlayerPrivateQTKit::naturalSize() const
717 {
718     if (!metaDataAvailable())
719         return IntSize();
720
721     // In spite of the name of this method, return QTMovieNaturalSizeAttribute transformed by the 
722     // initial movie scale because the spec says intrinsic size is:
723     //
724     //    ... the dimensions of the resource in CSS pixels after taking into account the resource's 
725     //    dimensions, aspect ratio, clean aperture, resolution, and so forth, as defined for the 
726     //    format used by the resource
727     
728     FloatSize naturalSize([[m_qtMovie.get() attributeForKey:QTMovieNaturalSizeAttribute] sizeValue]);
729     if (naturalSize.isEmpty() && m_isStreaming) {
730         // HTTP Live Streams will occasionally return {0,0} natural sizes while scrubbing.
731         // Work around this problem (<rdar://problem/9078563>) by returning the last valid 
732         // cached natural size:
733         naturalSize = m_cachedNaturalSize;
734     } else {
735         // Unfortunately, due to another QTKit bug (<rdar://problem/9082071>) we won't get a sizeChanged
736         // event when this happens, so we must cache the last valid naturalSize here:
737         m_cachedNaturalSize = naturalSize;
738     }
739         
740     return IntSize(naturalSize.width() * m_scaleFactor.width(), naturalSize.height() * m_scaleFactor.height());
741 }
742
743 bool MediaPlayerPrivateQTKit::hasVideo() const
744 {
745     if (!metaDataAvailable())
746         return false;
747     return [[m_qtMovie.get() attributeForKey:QTMovieHasVideoAttribute] boolValue];
748 }
749
750 bool MediaPlayerPrivateQTKit::hasAudio() const
751 {
752     if (!m_qtMovie)
753         return false;
754     return [[m_qtMovie.get() attributeForKey:QTMovieHasAudioAttribute] boolValue];
755 }
756
757 bool MediaPlayerPrivateQTKit::supportsFullscreen() const
758 {
759     return true;
760 }
761
762 void MediaPlayerPrivateQTKit::setVolume(float volume)
763 {
764     LOG(Media, "MediaPlayerPrivateQTKit::setVolume(%p) - volume %f", this, volume);
765     if (m_qtMovie)
766         [m_qtMovie.get() setVolume:volume];  
767 }
768
769 bool MediaPlayerPrivateQTKit::hasClosedCaptions() const
770 {
771     if (!metaDataAvailable())
772         return false;
773     return wkQTMovieHasClosedCaptions(m_qtMovie.get());  
774 }
775
776 void MediaPlayerPrivateQTKit::setClosedCaptionsVisible(bool closedCaptionsVisible)
777 {
778     if (metaDataAvailable()) {
779         wkQTMovieSetShowClosedCaptions(m_qtMovie.get(), closedCaptionsVisible);
780
781         if (closedCaptionsVisible && m_qtVideoLayer) {
782             // Captions will be rendered upside down unless we flag the movie as flipped (again). See <rdar://7408440>.
783             [m_qtVideoLayer.get() setGeometryFlipped:YES];
784         }
785     }
786 }
787
788 void MediaPlayerPrivateQTKit::setRate(float rate)
789 {
790     LOG(Media, "MediaPlayerPrivateQTKit::setRate(%p) - rate %f", this, rate);
791     if (m_qtMovie)
792         [m_qtMovie.get() setRate:rate];
793 }
794
795 void MediaPlayerPrivateQTKit::setPreservesPitch(bool preservesPitch)
796 {
797     LOG(Media, "MediaPlayerPrivateQTKit::setPreservesPitch(%p) - preservesPitch %d", this, (int)preservesPitch);
798     if (!m_qtMovie)
799         return;
800
801     // QTMovieRateChangesPreservePitchAttribute cannot be changed dynamically after QTMovie creation.
802     // If the passed in value is different than what already exists, we need to recreate the QTMovie for it to take effect.
803     if ([[m_qtMovie.get() attributeForKey:QTMovieRateChangesPreservePitchAttribute] boolValue] == preservesPitch)
804         return;
805
806     RetainPtr<NSDictionary> movieAttributes = adoptNS([[m_qtMovie.get() movieAttributes] mutableCopy]);
807     ASSERT(movieAttributes);
808     [movieAttributes.get() setValue:[NSNumber numberWithBool:preservesPitch] forKey:QTMovieRateChangesPreservePitchAttribute];
809     m_timeToRestore = currentMediaTime();
810
811     createQTMovie([movieAttributes.get() valueForKey:QTMovieURLAttribute], movieAttributes.get());
812 }
813
814 std::unique_ptr<PlatformTimeRanges> MediaPlayerPrivateQTKit::buffered() const
815 {
816     auto timeRanges = PlatformTimeRanges::create();
817     MediaTime loaded = maxMediaTimeLoaded();
818     if (loaded > MediaTime::zeroTime())
819         timeRanges->add(MediaTime::zeroTime(), loaded);
820     return timeRanges;
821 }
822
823 static MediaTime maxValueForTimeRanges(NSArray *ranges)
824 {
825     if (!ranges)
826         return MediaTime::zeroTime();
827
828     MediaTime max;
829     for (NSValue *value in ranges) {
830         QTTimeRange range = [value QTTimeRangeValue];
831         if (!range.time.timeScale || !range.duration.timeScale)
832             continue;
833
834         MediaTime time = toMediaTime(range.time);
835         MediaTime duration = toMediaTime(range.duration);
836         if (time.isValid() && duration.isValid())
837             max = std::max(max, time + duration);
838     }
839
840     return max;
841 }
842
843 MediaTime MediaPlayerPrivateQTKit::maxMediaTimeSeekable() const
844 {
845     if (!metaDataAvailable())
846         return MediaTime::zeroTime();
847
848     // infinite duration means live stream
849     if (durationMediaTime().isPositiveInfinite())
850         return MediaTime::zeroTime();
851
852     NSArray* seekableRanges = [m_qtMovie availableRanges];
853
854     return maxValueForTimeRanges(seekableRanges);
855 }
856
857 MediaTime MediaPlayerPrivateQTKit::maxMediaTimeLoaded() const
858 {
859     if (!metaDataAvailable())
860         return MediaTime::zeroTime();
861     if ([m_qtMovie respondsToSelector:@selector(loadedRanges)])
862         return maxValueForTimeRanges([m_qtMovie loadedRanges]);
863     return toMediaTime([m_qtMovie maxTimeLoaded]);
864 }
865
866 bool MediaPlayerPrivateQTKit::didLoadingProgress() const
867 {
868     if (!duration() || !totalBytes())
869         return false;
870     MediaTime currentMaxTimeLoaded = maxMediaTimeLoaded();
871     bool didLoadingProgress = currentMaxTimeLoaded != m_maxTimeLoadedAtLastDidLoadingProgress;
872     m_maxTimeLoadedAtLastDidLoadingProgress = currentMaxTimeLoaded;
873     return didLoadingProgress;
874 }
875
876 unsigned long long MediaPlayerPrivateQTKit::totalBytes() const
877 {
878     if (!metaDataAvailable())
879         return 0;
880     return [[m_qtMovie.get() attributeForKey:QTMovieDataSizeAttribute] longLongValue];
881 }
882
883 void MediaPlayerPrivateQTKit::cancelLoad()
884 {
885     LOG(Media, "MediaPlayerPrivateQTKit::cancelLoad(%p)", this);
886     // FIXME: Is there a better way to check for this?
887     if (m_networkState < MediaPlayer::Loading || m_networkState == MediaPlayer::Loaded)
888         return;
889     
890     tearDownVideoRendering();
891     m_qtMovie = nil;
892     
893     updateStates();
894 }
895
896 void MediaPlayerPrivateQTKit::cacheMovieScale()
897 {
898     NSSize initialSize = NSZeroSize;
899     NSSize naturalSize = [[m_qtMovie.get() attributeForKey:QTMovieNaturalSizeAttribute] sizeValue];
900
901     // QTMovieCurrentSizeAttribute is not allowed with instances of QTMovie that have been 
902     // opened with QTMovieOpenForPlaybackAttribute, so ask for the display transform attribute instead.
903     NSAffineTransform *displayTransform = [m_qtMovie.get() attributeForKey:@"QTMoviePreferredTransformAttribute"];
904     if (displayTransform)
905         initialSize = [displayTransform transformSize:naturalSize];
906     else {
907         initialSize.width = naturalSize.width;
908         initialSize.height = naturalSize.height;
909     }
910
911     if (naturalSize.width)
912         m_scaleFactor.setWidth(initialSize.width / naturalSize.width);
913     if (naturalSize.height)
914         m_scaleFactor.setHeight(initialSize.height / naturalSize.height);
915 }
916
917 bool MediaPlayerPrivateQTKit::isReadyForVideoSetup() const
918 {
919     return m_readyState >= MediaPlayer::HaveMetadata && m_player->visible();
920 }
921
922 void MediaPlayerPrivateQTKit::prepareForRendering()
923 {
924     LOG(Media, "MediaPlayerPrivateQTKit::prepareForRendering(%p)", this);
925     if (m_isAllowedToRender)
926         return;
927     m_isAllowedToRender = true;
928
929     if (!hasSetUpVideoRendering())
930         setUpVideoRendering();
931
932     // If using a movie layer, inform the client so the compositing tree is updated. This is crucial if the movie
933     // has a poster, as it will most likely not have a layer and we will now be rendering frames to the movie layer.
934     if (currentRenderingMode() == MediaRenderingMovieLayer || preferredRenderingMode() == MediaRenderingMovieLayer)
935         m_player->client().mediaPlayerRenderingModeChanged(m_player);
936 }
937
938 void MediaPlayerPrivateQTKit::updateStates()
939 {
940     MediaPlayer::NetworkState oldNetworkState = m_networkState;
941     MediaPlayer::ReadyState oldReadyState = m_readyState;
942
943     LOG(Media, "MediaPlayerPrivateQTKit::updateStates(%p) - entering with networkState = %i, readyState = %i", this, static_cast<int>(m_networkState), static_cast<int>(m_readyState));
944
945     
946     long loadState = m_qtMovie ? [[m_qtMovie.get() attributeForKey:QTMovieLoadStateAttribute] longValue] : static_cast<long>(QTMovieLoadStateError);
947
948     if (loadState >= QTMovieLoadStateLoaded && m_readyState < MediaPlayer::HaveMetadata) {
949         disableUnsupportedTracks();
950         if (m_player->inMediaDocument()) {
951             if (!m_enabledTrackCount || m_hasUnsupportedTracks) {
952                 // This has a type of media that we do not handle directly with a <video> 
953                 // element, eg. a rtsp track or QuickTime VR. Tell the MediaPlayerClient
954                 // that we noticed.
955                 sawUnsupportedTracks();
956                 return;
957             }
958         } else if (!m_enabledTrackCount)
959             loadState = QTMovieLoadStateError;
960
961         if (loadState != QTMovieLoadStateError) {
962             wkQTMovieSelectPreferredAlternates(m_qtMovie.get());
963             cacheMovieScale();
964             MediaPlayer::MovieLoadType movieType = movieLoadType();
965             m_isStreaming = movieType == MediaPlayer::StoredStream || movieType == MediaPlayer::LiveStream;
966         }
967     }
968     
969     // If this movie is reloading and we mean to restore the current time/rate, this might be the right time to do it.
970     if (loadState >= QTMovieLoadStateLoaded && oldNetworkState < MediaPlayer::Loaded && m_timeToRestore.isValid()) {
971         QTTime qttime = toQTTime(m_timeToRestore);
972         m_timeToRestore = MediaTime::invalidTime();
973             
974         // Disable event callbacks from setCurrentTime for restoring time in a recreated video
975         [m_objcObserver.get() setDelayCallbacks:YES];
976         [m_qtMovie.get() setCurrentTime:qttime];
977         [m_qtMovie.get() setRate:m_player->rate()];
978         [m_objcObserver.get() setDelayCallbacks:NO];
979     }
980
981     BOOL completelyLoaded = !m_isStreaming && (loadState >= QTMovieLoadStateComplete);
982
983     // Note: QT indicates that we are fully loaded with QTMovieLoadStateComplete.
984     // However newer versions of QT do not, so we check maxTimeLoaded against duration.
985     if (!completelyLoaded && !m_isStreaming && metaDataAvailable())
986         completelyLoaded = maxMediaTimeLoaded() == durationMediaTime();
987
988     if (completelyLoaded) {
989         // "Loaded" is reserved for fully buffered movies, never the case when streaming
990         m_networkState = MediaPlayer::Loaded;
991         m_readyState = MediaPlayer::HaveEnoughData;
992     } else if (loadState >= QTMovieLoadStatePlaythroughOK) {
993         m_readyState = MediaPlayer::HaveEnoughData;
994         m_networkState = MediaPlayer::Loading;
995     } else if (loadState >= QTMovieLoadStatePlayable) {
996         // FIXME: This might not work correctly in streaming case, <rdar://problem/5693967>
997         m_readyState = currentMediaTime() < maxMediaTimeLoaded() ? MediaPlayer::HaveFutureData : MediaPlayer::HaveCurrentData;
998         m_networkState = MediaPlayer::Loading;
999     } else if (loadState >= QTMovieLoadStateLoaded) {
1000         m_readyState = MediaPlayer::HaveMetadata;
1001         m_networkState = MediaPlayer::Loading;
1002     } else if (loadState > QTMovieLoadStateError) {
1003         m_readyState = MediaPlayer::HaveNothing;
1004         m_networkState = MediaPlayer::Loading;
1005     } else {
1006         // Loading or decoding failed.
1007
1008         if (m_player->inMediaDocument()) {
1009             // Something went wrong in the loading of media within a standalone file. 
1010             // This can occur with chained refmovies pointing to streamed media.
1011             sawUnsupportedTracks();
1012             return;
1013         }
1014
1015         MediaTime loaded = maxMediaTimeLoaded();
1016         if (!loaded)
1017             m_readyState = MediaPlayer::HaveNothing;
1018
1019         if (!m_enabledTrackCount)
1020             m_networkState = MediaPlayer::FormatError;
1021         else {
1022             // FIXME: We should differentiate between load/network errors and decode errors <rdar://problem/5605692>
1023             if (loaded > MediaTime::zeroTime())
1024                 m_networkState = MediaPlayer::DecodeError;
1025             else
1026                 m_readyState = MediaPlayer::HaveNothing;
1027         }
1028     }
1029
1030     if (isReadyForVideoSetup() && !hasSetUpVideoRendering())
1031         setUpVideoRendering();
1032
1033     if (seeking())
1034         m_readyState = m_readyState >= MediaPlayer::HaveMetadata ? MediaPlayer::HaveMetadata : MediaPlayer::HaveNothing;
1035
1036     // Streaming movies don't use the network when paused.
1037     if (m_isStreaming && m_readyState >= MediaPlayer::HaveMetadata && m_networkState >= MediaPlayer::Loading && [m_qtMovie.get() rate] == 0)
1038         m_networkState = MediaPlayer::Idle;
1039
1040     if (m_networkState != oldNetworkState)
1041         m_player->networkStateChanged();
1042
1043     if (m_readyState != oldReadyState)
1044         m_player->readyStateChanged();
1045
1046     if (loadState >= QTMovieLoadStateLoaded) {
1047         MediaTime dur = durationMediaTime();
1048         if (dur != m_reportedDuration) {
1049             if (m_reportedDuration.isValid())
1050                 m_player->durationChanged();
1051             m_reportedDuration = dur;
1052         }
1053     }
1054
1055     LOG(Media, "MediaPlayerPrivateQTKit::updateStates(%p) - exiting with networkState = %i, readyState = %i", this, static_cast<int>(m_networkState), static_cast<int>(m_readyState));
1056 }
1057
1058 long MediaPlayerPrivateQTKit::platformErrorCode() const
1059 {
1060     if (!m_qtMovie)
1061         return 0;
1062
1063     NSError* error = (NSError*)[m_qtMovie attributeForKey:QTMovieLoadStateErrorAttribute];
1064     if (!error || ![error isKindOfClass:[NSError class]])
1065         return 0;
1066
1067     return [error code];
1068 }
1069
1070 void MediaPlayerPrivateQTKit::loadStateChanged()
1071 {
1072     LOG(Media, "MediaPlayerPrivateQTKit::loadStateChanged(%p) - loadState = %li", this, [[m_qtMovie.get() attributeForKey:QTMovieLoadStateAttribute] longValue]);
1073
1074     if (!m_hasUnsupportedTracks)
1075         updateStates();
1076 }
1077
1078 void MediaPlayerPrivateQTKit::loadedRangesChanged()
1079 {
1080     LOG(Media, "MediaPlayerPrivateQTKit::loadedRangesChanged(%p) - loadState = %li", this, [[m_qtMovie.get() attributeForKey:QTMovieLoadStateAttribute] longValue]);
1081
1082     if (!m_hasUnsupportedTracks)
1083         updateStates();
1084 }
1085
1086 void MediaPlayerPrivateQTKit::rateChanged()
1087 {
1088     LOG(Media, "MediaPlayerPrivateQTKit::rateChanged(%p) - rate = %li", this, [m_qtMovie.get() rate]);
1089     if (m_hasUnsupportedTracks)
1090         return;
1091
1092     updateStates();
1093     m_player->rateChanged();
1094 }
1095
1096 void MediaPlayerPrivateQTKit::sizeChanged()
1097 {
1098     LOG(Media, "MediaPlayerPrivateQTKit::sizeChanged(%p)", this);
1099     if (!m_hasUnsupportedTracks)
1100         m_player->sizeChanged();
1101 }
1102
1103 void MediaPlayerPrivateQTKit::timeChanged()
1104 {
1105     LOG(Media, "MediaPlayerPrivateQTKit::timeChanged(%p)", this);
1106     if (m_hasUnsupportedTracks)
1107         return;
1108
1109     // It may not be possible to seek to a specific time in a streamed movie. When seeking in a 
1110     // stream QuickTime sets the movie time to closest time possible and posts a timechanged 
1111     // notification. Update m_seekTo so we can detect when the seek completes.
1112     if (!m_seekTo.isValid())
1113         m_seekTo = currentMediaTime();
1114
1115     m_timeToRestore = MediaTime::invalidTime();
1116     updateStates();
1117     m_player->timeChanged();
1118 }
1119
1120 void MediaPlayerPrivateQTKit::didEnd()
1121 {
1122     LOG(Media, "MediaPlayerPrivateQTKit::didEnd(%p)", this);
1123     if (m_hasUnsupportedTracks)
1124         return;
1125
1126     m_startedPlaying = false;
1127
1128     // Hang onto the current time and use it as duration from now on since QuickTime is telling us we
1129     // are at the end. Do this because QuickTime sometimes reports one time for duration and stops
1130     // playback at another time, which causes problems in HTMLMediaElement. QTKit's 'ended' event 
1131     // fires when playing in reverse so don't update duration when at time zero!
1132     MediaTime now = currentMediaTime();
1133     if (now > MediaTime::zeroTime())
1134         m_cachedDuration = now;
1135
1136     updateStates();
1137     m_player->timeChanged();
1138 }
1139
1140 void MediaPlayerPrivateQTKit::layerHostChanged(PlatformLayer* rootLayer)
1141 {
1142     UNUSED_PARAM(rootLayer);
1143 }
1144
1145 void MediaPlayerPrivateQTKit::setSize(const IntSize&) 
1146
1147 }
1148
1149 void MediaPlayerPrivateQTKit::setVisible(bool b)
1150 {
1151     if (m_visible != b) {
1152         m_visible = b;
1153         if (b)
1154             setUpVideoRendering();
1155         else
1156             tearDownVideoRendering();
1157     }
1158 }
1159
1160 bool MediaPlayerPrivateQTKit::hasAvailableVideoFrame() const
1161 {
1162     // When using a QTMovieLayer return true as soon as the movie reaches QTMovieLoadStatePlayable 
1163     // because although we don't *know* when the first frame has decoded, by the time we get and 
1164     // process the notification a frame should have propagated the VisualContext and been set on
1165     // the layer.
1166     if (currentRenderingMode() == MediaRenderingMovieLayer)
1167         return m_readyState >= MediaPlayer::HaveCurrentData;
1168
1169     // When using the software renderer QuickTime signals that a frame is available so we might as well
1170     // wait until we know that a frame has been drawn.
1171     return m_videoFrameHasDrawn;
1172 }
1173
1174 void MediaPlayerPrivateQTKit::repaint()
1175 {
1176     if (m_hasUnsupportedTracks)
1177         return;
1178
1179     m_videoFrameHasDrawn = true;
1180     m_player->repaint();
1181 }
1182
1183 void MediaPlayerPrivateQTKit::paintCurrentFrameInContext(GraphicsContext* context, const IntRect& r)
1184 {
1185     id qtVideoRenderer = m_qtVideoRenderer.get();
1186     if (!qtVideoRenderer && currentRenderingMode() == MediaRenderingMovieLayer) {
1187         // We're being told to render into a context, but we already have the
1188         // MovieLayer going. This probably means we've been called from <canvas>.
1189         // Set up a QTVideoRenderer to use, but one that doesn't register for
1190         // update callbacks. That way, it won't bother us asking to repaint.
1191         createQTVideoRenderer(QTVideoRendererModeDefault);
1192         qtVideoRenderer = m_qtVideoRenderer.get();
1193     }
1194     paint(context, r);
1195 }
1196
1197 void MediaPlayerPrivateQTKit::paint(GraphicsContext* context, const IntRect& r)
1198 {
1199     if (context->paintingDisabled() || m_hasUnsupportedTracks)
1200         return;
1201     id qtVideoRenderer = m_qtVideoRenderer.get();
1202     if (!qtVideoRenderer)
1203         return;
1204
1205     [m_objcObserver.get() setDelayCallbacks:YES];
1206     BEGIN_BLOCK_OBJC_EXCEPTIONS;
1207     NSGraphicsContext* newContext;
1208     FloatSize scaleFactor(1.0f, -1.0f);
1209     IntRect paintRect(IntPoint(0, 0), IntSize(r.width(), r.height()));
1210
1211     GraphicsContextStateSaver stateSaver(*context);
1212     context->translate(r.x(), r.y() + r.height());
1213     context->scale(scaleFactor);
1214     context->setImageInterpolationQuality(InterpolationLow);
1215
1216     newContext = [NSGraphicsContext graphicsContextWithGraphicsPort:context->platformContext() flipped:NO];
1217
1218     [NSGraphicsContext saveGraphicsState];
1219     [NSGraphicsContext setCurrentContext:newContext];
1220     [(id<WebKitVideoRenderingDetails>)qtVideoRenderer drawInRect:paintRect];
1221     [NSGraphicsContext restoreGraphicsState];
1222
1223     END_BLOCK_OBJC_EXCEPTIONS;
1224     [m_objcObserver.get() setDelayCallbacks:NO];
1225 }
1226
1227 static bool shouldRejectMIMEType(const String& type)
1228 {
1229     // QTKit will return non-video MIME types which it claims to support, but which we
1230     // do not support in the <video> element. Disclaim all non video/ or audio/ types.
1231     return !type.startsWith("video/") && !type.startsWith("audio/");
1232 }
1233
1234 static void addFileTypesToCache(NSArray * fileTypes, HashSet<String> &cache)
1235 {
1236     int count = [fileTypes count];
1237     for (int n = 0; n < count; n++) {
1238         CFStringRef ext = reinterpret_cast<CFStringRef>([fileTypes objectAtIndex:n]);
1239         RetainPtr<CFStringRef> uti = adoptCF(UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext, NULL));
1240         if (!uti)
1241             continue;
1242         RetainPtr<CFStringRef> mime = adoptCF(UTTypeCopyPreferredTagWithClass(uti.get(), kUTTagClassMIMEType));
1243         if (shouldRejectMIMEType(mime.get()))
1244             continue;
1245         if (mime)
1246             cache.add(mime.get());
1247
1248         // -movieFileTypes: returns both file extensions and OSTypes. The later are surrounded by single
1249         // quotes, eg. 'MooV', so don't bother looking at those.
1250         if (CFStringGetCharacterAtIndex(ext, 0) != '\'') {
1251             // UTI is missing many media related MIME types supported by QTKit (see rdar://6434168), and not all
1252             // web servers use the MIME type UTI returns for an extension (see rdar://7875393), so even if UTI 
1253             // has a type for this extension add any types in hard coded table in the MIME type regsitry.
1254             Vector<String> typesForExtension = MIMETypeRegistry::getMediaMIMETypesForExtension(ext);
1255             unsigned count = typesForExtension.size();
1256             for (unsigned ndx = 0; ndx < count; ++ndx) {
1257                 String type = typesForExtension[ndx].lower();
1258
1259                 if (shouldRejectMIMEType(type))
1260                     continue;
1261
1262                 if (!cache.contains(type))
1263                     cache.add(type);
1264             }
1265         }
1266     }    
1267 }
1268
1269 static HashSet<String> mimeCommonTypesCache()
1270 {
1271     DEPRECATED_DEFINE_STATIC_LOCAL(HashSet<String>, cache, ());
1272     static bool typeListInitialized = false;
1273
1274     if (!typeListInitialized) {
1275         typeListInitialized = true;
1276         NSArray* fileTypes = [QTMovie movieFileTypes:QTIncludeCommonTypes];
1277         addFileTypesToCache(fileTypes, cache);
1278     }
1279     
1280     return cache;
1281
1282
1283 static HashSet<String> mimeModernTypesCache()
1284 {
1285     DEPRECATED_DEFINE_STATIC_LOCAL(HashSet<String>, cache, ());
1286     static bool typeListInitialized = false;
1287     
1288     if (!typeListInitialized) {
1289         typeListInitialized = true;
1290         NSArray* fileTypes = [QTMovie movieFileTypes:(QTMovieFileTypeOptions)wkQTIncludeOnlyModernMediaFileTypes()];
1291         addFileTypesToCache(fileTypes, cache);
1292     }
1293     
1294     return cache;
1295 }
1296
1297 static void concatenateHashSets(HashSet<String>& destination, const HashSet<String>& source)
1298 {
1299     HashSet<String>::const_iterator it = source.begin();
1300     HashSet<String>::const_iterator end = source.end();
1301     for (; it != end; ++it)
1302         destination.add(*it);
1303 }
1304
1305 void MediaPlayerPrivateQTKit::getSupportedTypes(HashSet<String>& supportedTypes)
1306 {
1307     concatenateHashSets(supportedTypes, mimeModernTypesCache());
1308
1309     // Note: this method starts QTKitServer if it isn't already running when in 64-bit because it has to return the list 
1310     // of every MIME type supported by QTKit.
1311     concatenateHashSets(supportedTypes, mimeCommonTypesCache());
1312 }
1313
1314 MediaPlayer::SupportsType MediaPlayerPrivateQTKit::supportsType(const MediaEngineSupportParameters& parameters)
1315 {
1316 #if ENABLE(ENCRYPTED_MEDIA)
1317     // QTKit does not support any encrytped media, so return IsNotSupported if the keySystem is non-NULL:
1318     if (!parameters.keySystem.isNull() && !parameters.keySystem.isEmpty())
1319         return MediaPlayer::IsNotSupported;
1320 #endif
1321
1322 #if ENABLE(MEDIA_SOURCE)
1323     if (parameters.isMediaSource)
1324         return MediaPlayer::IsNotSupported;
1325 #endif
1326
1327     // Only return "IsSupported" if there is no codecs parameter for now as there is no way to ask QT if it supports an
1328     // extended MIME type yet.
1329
1330     // Due to <rdar://problem/10777059>, avoid calling the mime types cache functions if at
1331     // all possible:
1332     if (shouldRejectMIMEType(parameters.type))
1333         return MediaPlayer::IsNotSupported;
1334
1335     // We check the "modern" type cache first, as it doesn't require QTKitServer to start.
1336     if (mimeModernTypesCache().contains(parameters.type) || mimeCommonTypesCache().contains(parameters.type))
1337         return parameters.codecs.isEmpty() ? MediaPlayer::MayBeSupported : MediaPlayer::IsSupported;
1338
1339     return MediaPlayer::IsNotSupported;
1340 }
1341
1342 bool MediaPlayerPrivateQTKit::isAvailable()
1343 {
1344     // On 10.5 and higher, QuickTime will always be new enough for <video> and <audio> support, so we just check that the framework can be loaded.
1345     return QTKitLibrary();
1346 }
1347
1348 void MediaPlayerPrivateQTKit::getSitesInMediaCache(Vector<String>& sites) 
1349 {
1350     NSArray *mediaSites = wkQTGetSitesInMediaDownloadCache();
1351     for (NSString *site in mediaSites)
1352         sites.append(site);
1353 }
1354
1355 void MediaPlayerPrivateQTKit::clearMediaCache()
1356 {
1357     LOG(Media, "MediaPlayerPrivateQTKit::clearMediaCache()");
1358     wkQTClearMediaDownloadCache();
1359 }
1360
1361 void MediaPlayerPrivateQTKit::clearMediaCacheForSite(const String& site)
1362 {
1363     LOG(Media, "MediaPlayerPrivateQTKit::clearMediaCacheForSite()");
1364     wkQTClearMediaDownloadCacheForSite(site);
1365 }
1366
1367 void MediaPlayerPrivateQTKit::disableUnsupportedTracks()
1368 {
1369     LOG(Media, "MediaPlayerPrivateQTKit::disableUnsupportedTracks(%p)", this);
1370
1371     if (!m_qtMovie) {
1372         m_enabledTrackCount = 0;
1373         m_totalTrackCount = 0;
1374         return;
1375     }
1376     
1377     static HashSet<String>* allowedTrackTypes = 0;
1378     if (!allowedTrackTypes) {
1379         allowedTrackTypes = new HashSet<String>;
1380         allowedTrackTypes->add(QTMediaTypeVideo);
1381         allowedTrackTypes->add(QTMediaTypeSound);
1382         allowedTrackTypes->add(QTMediaTypeText);
1383         allowedTrackTypes->add(QTMediaTypeBase);
1384         allowedTrackTypes->add(QTMediaTypeMPEG);
1385         allowedTrackTypes->add("clcp"); // Closed caption
1386         allowedTrackTypes->add("sbtl"); // Subtitle
1387         allowedTrackTypes->add("odsm"); // MPEG-4 object descriptor stream
1388         allowedTrackTypes->add("sdsm"); // MPEG-4 scene description stream
1389         allowedTrackTypes->add("tmcd"); // timecode
1390         allowedTrackTypes->add("tc64"); // timcode-64
1391         allowedTrackTypes->add("tmet"); // timed metadata
1392     }
1393     
1394     NSArray *tracks = [m_qtMovie.get() tracks];
1395     
1396     m_totalTrackCount = [tracks count];
1397     m_enabledTrackCount = m_totalTrackCount;
1398     for (unsigned trackIndex = 0; trackIndex < m_totalTrackCount; trackIndex++) {
1399         // Grab the track at the current index. If there isn't one there, then
1400         // we can move onto the next one.
1401         QTTrack *track = [tracks objectAtIndex:trackIndex];
1402         if (!track)
1403             continue;
1404         
1405         // Check to see if the track is disabled already, we should move along.
1406         // We don't need to re-disable it.
1407         if (![track isEnabled]) {
1408             --m_enabledTrackCount;
1409             continue;
1410         }
1411         
1412         // Get the track's media type.
1413         NSString *mediaType = [track attributeForKey:QTTrackMediaTypeAttribute];
1414         if (!mediaType)
1415             continue;
1416
1417         // Test whether the media type is in our white list.
1418         if (!allowedTrackTypes->contains(mediaType)) {
1419             // If this track type is not allowed, then we need to disable it.
1420             [track setEnabled:NO];
1421             --m_enabledTrackCount;
1422             m_hasUnsupportedTracks = true;
1423         }
1424
1425         // Disable chapter tracks. These are most likely to lead to trouble, as
1426         // they will be composited under the video tracks, forcing QT to do extra
1427         // work.
1428         QTTrack *chapterTrack = [track performSelector:@selector(chapterlist)];
1429         if (!chapterTrack)
1430             continue;
1431         
1432         // Try to grab the media for the track.
1433         QTMedia *chapterMedia = [chapterTrack media];
1434         if (!chapterMedia)
1435             continue;
1436         
1437         // Grab the media type for this track.
1438         id chapterMediaType = [chapterMedia attributeForKey:QTMediaTypeAttribute];
1439         if (!chapterMediaType)
1440             continue;
1441         
1442         // Check to see if the track is a video track. We don't care about
1443         // other non-video tracks.
1444         if (![chapterMediaType isEqual:QTMediaTypeVideo])
1445             continue;
1446         
1447         // Check to see if the track is already disabled. If it is, we
1448         // should move along.
1449         if (![chapterTrack isEnabled])
1450             continue;
1451         
1452         // Disable the evil, evil track.
1453         [chapterTrack setEnabled:NO];
1454         --m_enabledTrackCount;
1455         m_hasUnsupportedTracks = true;
1456     }
1457 }
1458
1459 void MediaPlayerPrivateQTKit::sawUnsupportedTracks()
1460 {
1461     m_hasUnsupportedTracks = true;
1462     m_player->client().mediaPlayerSawUnsupportedTracks(m_player);
1463 }
1464
1465 bool MediaPlayerPrivateQTKit::supportsAcceleratedRendering() const
1466 {
1467     return isReadyForVideoSetup() && getQTMovieLayerClass() != Nil;
1468 }
1469
1470 void MediaPlayerPrivateQTKit::acceleratedRenderingStateChanged()
1471 {
1472     // Set up or change the rendering path if necessary.
1473     setUpVideoRendering();
1474 }
1475
1476 bool MediaPlayerPrivateQTKit::hasSingleSecurityOrigin() const
1477 {
1478     if (!m_qtMovie)
1479         return false;
1480
1481     RefPtr<SecurityOrigin> resolvedOrigin = SecurityOrigin::create(URL(wkQTMovieResolvedURL(m_qtMovie.get())));
1482     RefPtr<SecurityOrigin> requestedOrigin = SecurityOrigin::createFromString(m_movieURL);
1483     return resolvedOrigin->isSameSchemeHostPort(requestedOrigin.get());
1484 }
1485
1486 MediaPlayer::MovieLoadType MediaPlayerPrivateQTKit::movieLoadType() const
1487 {
1488     if (!m_qtMovie)
1489         return MediaPlayer::Unknown;
1490
1491     MediaPlayer::MovieLoadType movieType = (MediaPlayer::MovieLoadType)wkQTMovieGetType(m_qtMovie.get());
1492
1493     // Can't include WebKitSystemInterface from WebCore so we can't get the enum returned
1494     // by wkQTMovieGetType, but at least verify that the value is in the valid range.
1495     ASSERT(movieType >= MediaPlayer::Unknown && movieType <= MediaPlayer::LiveStream);
1496
1497     return movieType;
1498 }
1499
1500 void MediaPlayerPrivateQTKit::setPreload(MediaPlayer::Preload preload)
1501 {
1502     m_preload = preload;
1503     if (m_preload == MediaPlayer::None)
1504         return;
1505
1506     if (!m_qtMovie)
1507         resumeLoad();
1508     else if (m_preload == MediaPlayer::Auto)
1509         [m_qtMovie.get() setAttribute:[NSNumber numberWithBool:NO] forKey:@"QTMovieLimitReadAheadAttribute"];
1510 }
1511
1512 void MediaPlayerPrivateQTKit::setPrivateBrowsingMode(bool privateBrowsing)
1513 {
1514     m_privateBrowsing = privateBrowsing;
1515     if (!m_qtMovie)
1516         return;
1517     [m_qtMovie.get() setAttribute:[NSNumber numberWithBool:!privateBrowsing] forKey:@"QTMovieAllowPersistentCacheAttribute"];
1518 }
1519
1520 bool MediaPlayerPrivateQTKit::canSaveMediaData() const
1521 {
1522     URL url;
1523
1524     if (durationMediaTime().isPositiveInfinite())
1525         return false;
1526
1527     if (m_qtMovie)
1528         url = URL(wkQTMovieResolvedURL(m_qtMovie.get()));
1529     else
1530         url = URL(ParsedURLString, m_movieURL);
1531
1532     if (url.isLocalFile())
1533         return true;
1534
1535     if (url.protocolIsInHTTPFamily())
1536         return true;
1537     
1538     return false;
1539 }
1540
1541 } // namespace WebCore
1542
1543 @implementation WebCoreMovieObserver
1544
1545 - (id)initWithCallback:(MediaPlayerPrivateQTKit*)callback
1546 {
1547     m_callback = callback;
1548     return [super init];
1549 }
1550
1551 - (void)disconnect
1552 {
1553     [NSObject cancelPreviousPerformRequestsWithTarget:self];
1554     m_callback = 0;
1555 }
1556
1557 -(void)repaint
1558 {
1559     if (m_delayCallbacks)
1560         [self performSelector:_cmd withObject:nil afterDelay:0.];
1561     else if (m_callback)
1562         m_callback->repaint();
1563 }
1564
1565 - (void)loadStateChanged:(NSNotification *)unusedNotification
1566 {
1567     UNUSED_PARAM(unusedNotification);
1568     if (m_delayCallbacks)
1569         [self performSelector:_cmd withObject:nil afterDelay:0];
1570     else
1571         m_callback->loadStateChanged();
1572 }
1573
1574 - (void)loadedRangesChanged:(NSNotification *)unusedNotification
1575 {
1576     UNUSED_PARAM(unusedNotification);
1577     if (m_delayCallbacks)
1578         [self performSelector:_cmd withObject:nil afterDelay:0];
1579     else
1580         m_callback->loadedRangesChanged();
1581 }
1582
1583 - (void)rateChanged:(NSNotification *)unusedNotification
1584 {
1585     UNUSED_PARAM(unusedNotification);
1586     if (m_delayCallbacks)
1587         [self performSelector:_cmd withObject:nil afterDelay:0];
1588     else
1589         m_callback->rateChanged();
1590 }
1591
1592 - (void)sizeChanged:(NSNotification *)unusedNotification
1593 {
1594     UNUSED_PARAM(unusedNotification);
1595     if (m_delayCallbacks)
1596         [self performSelector:_cmd withObject:nil afterDelay:0];
1597     else
1598         m_callback->sizeChanged();
1599 }
1600
1601 - (void)timeChanged:(NSNotification *)unusedNotification
1602 {
1603     UNUSED_PARAM(unusedNotification);
1604     if (m_delayCallbacks)
1605         [self performSelector:_cmd withObject:nil afterDelay:0];
1606     else
1607         m_callback->timeChanged();
1608 }
1609
1610 - (void)didEnd:(NSNotification *)unusedNotification
1611 {
1612     UNUSED_PARAM(unusedNotification);
1613     if (m_delayCallbacks)
1614         [self performSelector:_cmd withObject:nil afterDelay:0];
1615     else
1616         m_callback->didEnd();
1617 }
1618
1619 - (void)newImageAvailable:(NSNotification *)unusedNotification
1620 {
1621     UNUSED_PARAM(unusedNotification);
1622     [self repaint];
1623 }
1624
1625 - (void)layerHostChanged:(NSNotification *)notification
1626 {
1627     CALayer* rootLayer = static_cast<CALayer*>([notification object]);
1628     m_callback->layerHostChanged(rootLayer);
1629 }
1630
1631 - (void)setDelayCallbacks:(BOOL)shouldDelay
1632 {
1633     m_delayCallbacks = shouldDelay;
1634 }
1635
1636 @end
1637
1638 #endif