Repository navigation
Expand file tree
/
Copy pathFVWebViewIcon.m
More file actions
715 lines (585 loc) · 28.7 KB
/
Copy pathFVWebViewIcon.m
File metadata and controls
715 lines (585 loc) · 28.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
//
// FVWebViewIcon.m
// FileView
//
// Created by Adam Maxwell on 12/30/07.
/*
This software is Copyright (c) 2007-2013
Adam Maxwell. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
- Neither the name of Adam Maxwell nor the names of any
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "FVWebViewIcon.h"
#import "FVFinderIcon.h"
#import "FVMIMEIcon.h"
@interface WebView (FVExtensions)
- (BOOL)fv_isLoading;
@end
@implementation WebView (FVExtensions)
- (BOOL)fv_isLoading;
{
// available in 10.4.11 and later
if ([self respondsToSelector:@selector(isLoading)])
return [self isLoading];
// Modified from http://trac.webkit.org/browser/trunk/WebKit/mac/WebView/WebView.mm implementation of -[WebView _isLoading].
WebFrame *mainFrame = [self mainFrame];
return [[mainFrame dataSource] isLoading] || [[mainFrame provisionalDataSource] isLoading];
}
@end
@implementation FVWebViewIcon
// limit number of loading views to keep memory usage down; size is tunable
static int8_t _maxWebViews = 0;
static int8_t _numberOfWebViews = 0;
static NSString * const FVWebIconWebViewAvailableNotificationName = @"FVWebIconWebViewAvailableNotificationName";
// avoid doing network operations for subsequent render attempts of known MIME icons
static NSMutableDictionary *_mimeIcons = nil;
static NSLock *_mimeIconLock = nil;
#define IDLE 0
#define LOADING 1
#define LOADED 2
+ (void)initialize
{
FVINITIALIZE(FVWebViewIcon);
NSNumber *maxViews = [[NSUserDefaults standardUserDefaults] objectForKey:@"FVWebIconMaximumNumberOfWebViews"];
// default value of 5, with valid range (0, 50)
if (nil == maxViews) {
_maxWebViews = 5;
}
else if ([maxViews intValue] > 50) {
FVLog(@"Limiting number of webviews to 50 (FVWebIconMaximumNumberOfWebViews = %@)", maxViews);
_maxWebViews = 50;
}
else if ([maxViews intValue] >= 0) {
_maxWebViews = [maxViews intValue];
}
else {
// negative values
_maxWebViews = 0;
}
if (nil == _mimeIcons) {
_mimeIcons = [NSMutableDictionary new];
_mimeIconLock = [NSLock new];
}
}
// size of the view frame; large enough to fit a reasonably sized page
+ (NSSize)_webViewSize { return NSMakeSize(1000, 900); }
// return nil if _maxWebViews is exceeded
+ (WebView *)_newWebView
{
FVAPIAssert1(pthread_main_np() != 0, @"*** threading violation *** %s requires main thread", __func__);
FVAPIParameterAssert(_maxWebViews > 0);
WebView *view = nil;
if (_numberOfWebViews < _maxWebViews) {
NSSize size = [self _webViewSize];
view = [[WebView alloc] initWithFrame:NSMakeRect(0, 0, size.width, size.height)];
_numberOfWebViews++;
NSString *prefIdentifier = [NSString stringWithFormat:@"%@.%@", [[NSBundle bundleForClass:self] bundleIdentifier], self];
[view setPreferencesIdentifier:prefIdentifier];
WebPreferences *prefs = [view preferences];
[prefs setPlugInsEnabled:NO];
[prefs setJavaEnabled:NO];
[prefs setJavaScriptCanOpenWindowsAutomatically:NO];
[prefs setJavaScriptEnabled:NO];
[prefs setAllowsAnimatedImages:NO];
/*
WebCacheModelDocumentViewer is the most memory-efficient setting; remote resources are still cached to disk,
supposedly, but in practice this doesn't seem to happen (or else they're pruned too early). Using
WebCacheModelDocumentBrowser gives much better performance, and memory usage is the same or less, particularly
if you have multiple pages loading the same resources (e.g., many ScienceDirect thumbnails).
*/
if ([prefs respondsToSelector:@selector(setCacheModel:)])
[prefs setCacheModel:WebCacheModelDocumentBrowser];
}
return view;
}
+ (BOOL)_isSupportedScheme:(NSString *)scheme
{
NSString *lcString = [scheme lowercaseString];
return [lcString isEqualToString:@"http"] || [lcString isEqualToString:@"https"] || [lcString isEqualToString:@"ftp"];
}
- (id)initWithURL:(NSURL *)aURL;
{
NSParameterAssert(nil != [aURL scheme]);
// if webviews are disabled, return a finder icon to avoid problems with looping notifications
// if this is not an http or file URL, return a finder icon instead
if (0 == _maxWebViews || (NO == [[self class] _isSupportedScheme:[aURL scheme]] && NO == [aURL isFileURL])) {
NSZone *zone = [self zone];
[super dealloc];
self = (id)[[FVFinderIcon allocWithZone:zone] initWithURL:aURL];
}
else if ((self = [super init])) {
_httpURL = [aURL copyWithZone:[self zone]];
_fullImage = NULL;
_thumbnail = NULL;
_viewImage = NULL;
_fallbackIcon = nil;
_webView = nil;
_redirectedFrames = [NSCountedSet new];
// we can predict these sizes ahead of time since we have a fixed aspect ratio
_thumbnailSize = [[self class] _webViewSize];
FVIconLimitThumbnailSize(&_thumbnailSize);
_fullImageSize = _thumbnailSize;
FVIconLimitFullImageSize(&_fullImageSize);
_desiredSize = NSZeroSize;
_cacheKey = [FVCGImageCache newKeyForURL:_httpURL];
_condLock = [[NSConditionLock allocWithZone:[self zone]] initWithCondition:IDLE];
_cancelledLoad = false;
}
return self;
}
- (void)_releaseWebView
{
FVAPIAssert1(pthread_main_np() != 0, @"*** threading violation *** %s requires main thread", __func__);
// in case we get -releaseResources or -dealloc while waiting for another webview
[[NSNotificationCenter defaultCenter] removeObserver:self name:FVWebIconWebViewAvailableNotificationName object:[self class]];
if (nil != _webView) {
[_webView setPolicyDelegate:nil];
[_webView setFrameLoadDelegate:nil];
[_webView setResourceLoadDelegate:nil];
[_webView stopLoading:nil];
FVAPIAssert([_webView downloadDelegate] == nil, @"downloadDelegate non-nil");
FVAPIAssert([_webView UIDelegate] == nil, @"UIDelegate non-nil");
// autorelease instead of release to workaround rdar://problem/9923439 (WebKit: crash when loading cancelled due to policy delegate callout)
[_webView autorelease];
// may have frames if the load was cancelled?
[_redirectedFrames removeAllObjects];
_numberOfWebViews--;
_webView = nil;
// notify observers that _numberOfWebViews has been decremented
[[NSNotificationCenter defaultCenter] postNotificationName:FVWebIconWebViewAvailableNotificationName object:[self class]];
}
}
- (void)dealloc
{
// typically on the main thread here, but not guaranteed; can never be called during a load anyway
if (pthread_main_np() != 0) {
[self performSelectorOnMainThread:@selector(_releaseWebView) withObject:nil waitUntilDone:YES modes:[NSArray arrayWithObject:(id)kCFRunLoopCommonModes]];
}
else {
// make sure to deregister for notification
[self _releaseWebView];
}
[_redirectedFrames release];
[_condLock release];
CGImageRelease(_viewImage);
CGImageRelease(_fullImage);
CGImageRelease(_thumbnail);
[_httpURL release];
[_fallbackIcon release];
[_cacheKey release];
[super dealloc];
}
- (BOOL)canReleaseResources;
{
return ([_condLock condition] == LOADING || NULL != _fullImage || NULL != _thumbnail || [_fallbackIcon canReleaseResources]);
}
- (void)releaseResources
{
// allow current waiters on LOADING to exit
if ([_condLock tryLockWhenCondition:LOADING]) {
/*
Cancel any pending loads (only occur inside LOADING condition)
If webview is non-nil, we need to cancel the load.
If webview is nil, we need to unregister for the notification.
*/
[self performSelectorOnMainThread:@selector(_releaseWebView) withObject:nil waitUntilDone:YES modes:[NSArray arrayWithObject:(id)kCFRunLoopCommonModes]];
// should never happen, but make sure we can't cache garbage...
if (_viewImage) {
FVLog(@"%s found a non-NULL _viewImage, and is disposing of it", __func__);
CFRelease(_viewImage);
_viewImage = NULL;
}
// need to avoid creating the fallback icon in this case, so we know to retry later
_cancelledLoad = true;
[_condLock unlockWithCondition:LOADED];
}
// could possibly fail to take the lock during a callout to _pageDidFinishLoading, and in that case we should just wait
// block until IDLE is set, so current waiters don't get hosed by resetting the condition to IDLE
[_condLock lockWhenCondition:IDLE];
CGImageRelease(_fullImage);
_fullImage = NULL;
CGImageRelease(_thumbnail);
_thumbnail = NULL;
// currently a noop
[_fallbackIcon releaseResources];
// reset condition so -renderOffscreen will complete if it's called again
[_condLock unlockWithCondition:IDLE];
}
- (void)recache;
{
[FVCGImageCache invalidateCachesForKey:_cacheKey];
[self releaseResources];
// this is a sentinel value for needsRenderForSize:
[self lock];
_cancelledLoad = false;
[_fallbackIcon release];
_fallbackIcon = nil;
[self unlock];
}
- (BOOL)tryLock { return [_condLock tryLock]; }
- (void)lock { [_condLock lock]; }
- (void)unlock { [_condLock unlock]; }
// size of the _fullImage
- (NSSize)size { return _fullImageSize; }
// size of the _thumbnail (1/5 of webview size)
- (NSSize)_thumbnailSize { return _thumbnailSize; }
- (BOOL)needsRenderForSize:(NSSize)size
{
BOOL needsRender = NO;
if ([_condLock tryLockWhenCondition:IDLE]) {
/*
1) if we know the web view failed, the only thing we can use is the fallback icon
2) if we're drawing a large icon and the web view hasn't failed (yet), we depend on _fullImage (which may be in disk cache)
3) if we're drawing a small icon and the web view hasn't failed (yet), we depend on _thumbnail (so webview needs to load)
*/
_desiredSize = size;
if (nil != _fallbackIcon)
needsRender = [_fallbackIcon needsRenderForSize:size];
else if (FVShouldDrawFullImageWithThumbnailSize(size, [self _thumbnailSize]))
needsRender = (NULL == _fullImage);
else
needsRender = (NULL == _thumbnail);
[self unlock];
}
return needsRender;
}
- (void)_handleWebView:(WebView *)sender loadError:(NSError *)error forFrame:(WebFrame *)frame;
{
FVAPIAssert1(pthread_main_np() != 0, @"*** threading violation *** %s requires main thread", __func__);
FVAPIParameterAssert([sender isEqual:_webView]);
/*
If a frame fails to load and the webview isn't loading anything else, bail out.
??? Not sure anymore why I chose that condition; maybe in case of partial failures?
In addition, if a fallback icon has been created in decidePolicyForMIMEType:,
we want to cancel loading here as a signal to draw that. Otherwise, we may continue
loading other frames, then draw the webview.
*/
if (NO == [_webView fv_isLoading] || nil != _fallbackIcon) {
[self _releaseWebView];
// condition should always be LOADING, but -releaseResources may have the lock
if ([_condLock tryLockWhenCondition:LOADING]) {
// return to -renderOffscreen to handle the failure
[_condLock unlockWithCondition:LOADED];
}
}
}
- (void)webView:(WebView *)sender didFailProvisionalLoadWithError:(NSError *)error forFrame:(WebFrame *)frame;
{
[self _handleWebView:sender loadError:error forFrame:frame];
}
- (void)webView:(WebView *)sender didFailLoadWithError:(NSError *)error forFrame:(WebFrame *)frame;
{
[self _handleWebView:sender loadError:error forFrame:frame];
}
- (void)_pageDidFinishLoading
{
FVAPIAssert1(pthread_main_np() != 0, @"*** threading violation *** %s requires main thread", __func__);
FVAPIAssert1([_webView fv_isLoading] == NO, @"%s called while webview was loading", __func__);
// release resources called after page finished loading; it calls main thread to cancel webview and we deadlock
if ([_condLock tryLockWhenCondition:LOADING] == NO)
return;
// display the main frame's view directly to avoid showing the scrollers
WebFrameView *view = [[_webView mainFrame] frameView];
[view setAllowsScrolling:NO];
// actual size of the view
NSSize size = [[self class] _webViewSize];
FVBitmapContext *bitmapContext = [FVBitmapContext bitmapContextWithSize:size];
CGContextRef context = [bitmapContext graphicsPort];
CGContextClearRect(context, CGRectMake(0, 0, size.width, size.height));
NSGraphicsContext *nsContext = [_webView isFlipped] ? [bitmapContext flippedGraphicsContext] : [bitmapContext graphicsContext];
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext:nsContext];
[nsContext saveGraphicsState];
[[NSColor whiteColor] setFill];
NSRect rect = NSMakeRect(0, 0, size.width, size.height);
[[NSBezierPath fv_bezierPathWithRoundRect:rect xRadius:5 yRadius:5] fill];
[nsContext restoreGraphicsState];
[_webView setFrame:NSInsetRect(rect, 10, 10)];
CGContextSaveGState(context);
CGContextTranslateCTM(context, 10, 10);
/*
Force document layout. This was added as a workaround for a bug introduced with Safari 4.0.3 on 10.5.8,
where setAllowsScrolling:NO caused a white page to be drawn. Interestingly, it also fixes some prior
display problems with sciencedirect.com thumbnails, so it's likely that setAllowsScrolling: has never
worked correctly. Note that [[view documentView] setNeedsLayout:] is not sufficient, so I conclude that
displayRectIgnoringOpacity:inContext: doesn't go through drawRect: (impossible), or the WebDocumentView
protocol is not correctly implemented.
*/
[[view documentView] layout];
[_webView displayRectIgnoringOpacity:[_webView bounds] inContext:nsContext];
CGContextRestoreGState(context);
[NSGraphicsContext restoreGraphicsState];
// temporary CGImage from the full webview
CGImageRelease(_viewImage);
_viewImage = CGBitmapContextCreateImage(context);
// clear out the webview, since we won't need it again
[self _releaseWebView];
[_condLock unlockWithCondition:LOADED];
// return to -renderOffscreen for scaling and caching
}
/*
Notes on workarounds for rdar://problem/7025679
Redirect and -isLoading don't work as I expected. As an example, this page
http://dx.doi.org/10.1175/1520-0426(2003)20%3C730:AACEAF%3E2.0.CO;2
does a server side redirect to
http://journals.allenpress.com/jrnlserv/?request=get-abstract&doi=10.1175/1520-0426(2003)20%3C730:AACEAF%3E2.0.CO;2
which then does a client side redirect to
http://ams.allenpress.com/perlserv/?request=get-abstract&doi=10.1175%2F1520-0426(2003)20%3C730:AACEAF%3E2.0.CO%3B2
-[WebView isLoading] returns NO after the journals.allenpress.com URL has loaded, but that's before the ams.allenpress.com
URL has started loading. Hence, we track client side redirected frames between the redirect and their provisional load,
and use that in conjunction with -[WebView isLoading]. This is essentially Apple's suggested workaround, and they say
WebKit works as designed here so 7025679 was marked "Behaves Correctly."
Related: rdar://problem/7046376 (add API or callback for complete loading)
rdar://problem/7046354 (improve documentation for willPerformClientRedirectToURL:)
*/
- (void)webView:(WebView *)sender willPerformClientRedirectToURL:(NSURL *)URL delay:(NSTimeInterval)seconds fireDate:(NSDate *)date forFrame:(WebFrame *)frame
{
// client redirect is delayed, and -[WebView isLoading] returns NO until the provisional load starts
[_redirectedFrames addObject:frame];
}
- (void)webView:(WebView *)sender didCancelClientRedirectForFrame:(WebFrame *)frame;
{
[_redirectedFrames removeObject:frame];
}
- (void)webView:(WebView *)sender didStartProvisionalLoadForFrame:(WebFrame *)frame;
{
// once a provisional load has started, -[WebView isLoading] will return correct status
[_redirectedFrames removeObject:frame];
}
- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
{
FVAPIAssert1(pthread_main_np() != 0, @"*** threading violation *** %s requires main thread", __func__);
FVAPIParameterAssert([sender isEqual:_webView]);
// wait until all frames are loaded; perform after a delay because of redirect problems
if (NO == [_webView fv_isLoading] && [_redirectedFrames count] == 0)
[self _pageDidFinishLoading];
}
- (void)webView:(WebView *)sender decidePolicyForMIMEType:(NSString *)type request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id < WebPolicyDecisionListener >)listener
{
FVAPIAssert1(pthread_main_np() != 0, @"*** threading violation *** %s requires main thread", __func__);
FVAPIParameterAssert([sender isEqual:_webView]);
/*
!!! Better to just load text/html and ignore everything else? The point of implementing this method is to ignore PDF.
It doesn't show up in the thumbnail and it's slow to load, so there's no point in loading it. Plugins are disabled,
so stuff like Flash should be ignored anyway, and WebKit doesn't try to display PostScript AFAIK.
Documentation says the default implementation checks "If request is not a directory", which is...odd.
See http://trac.webkit.org/projects/webkit/browser/trunk/WebKit/mac/DefaultDelegates/WebDefaultPolicyDelegate.m
*/
CFStringRef theUTI = type == nil ? NULL : UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (CFStringRef)type, NULL);
// FVWebViewIcon handles .webarchive files, so implement the standard file: URL handler
if ([[request URL] isFileURL]) {
BOOL isDirectory = NO;
[[NSFileManager defaultManager] fileExistsAtPath:[[request URL] path] isDirectory:&isDirectory];
if (isDirectory) {
[listener ignore];
} else if ([[sender class] canShowMIMEType:type]) {
[listener use];
} else {
[listener ignore];
}
}
else if (NULL != theUTI && FALSE == UTTypeConformsTo(theUTI, kUTTypeText)) {
// condition should always be LOADING, but -releaseResources may have the lock
if ([_condLock tryLockWhenCondition:LOADING]) {
[_fallbackIcon release];
_fallbackIcon = [[FVMIMEIcon allocWithZone:[self zone]] initWithMIMEType:type];
// shouldn't yet be in the table
if (_fallbackIcon) {
[_mimeIconLock lock];
[_mimeIcons setObject:_fallbackIcon forKey:_cacheKey];
[_mimeIconLock unlock];
}
[self unlock];
}
// this triggers webView:didFailProvisionalLoadWithError:forFrame:
[listener ignore];
}
else if ([[sender class] canShowMIMEType:type]) {
[listener use];
}
else {
[listener ignore];
}
if (theUTI) CFRelease(theUTI);
}
// should only be relevant for clicking links, but implement it anyway
- (void)webView:(WebView *)sender decidePolicyForNewWindowAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request newFrameName:(NSString *)frameName decisionListener:(id < WebPolicyDecisionListener >)listener
{
FVAPIAssert1(pthread_main_np() != 0, @"*** threading violation *** %s requires main thread", __func__);
FVAPIParameterAssert([sender isEqual:_webView]);
[listener ignore];
}
- (void)webView:(WebView *)sender resource:(id)identifier didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge fromDataSource:(WebDataSource *)dataSource
{
FVAPIAssert1(pthread_main_np() != 0, @"*** threading violation *** %s requires main thread", __func__);
FVAPIParameterAssert([sender isEqual:_webView]);
/*
Causes rendering of a 401 (unauthorized) page here. Callout to stopLoading:
or _handleWebView:loadError:forFrame: will cause a crash in the URL loader.
*/
[[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
}
- (void)renderOffscreenOnMainThread
{
FVAPIAssert1(pthread_main_np() != 0, @"*** threading violation *** %s requires main thread", __func__);
// !!! Was seeing occasional assertion failures here with the new operation queue setup; it's likely to be a race with releaseResources:. Should be eliminated with the new condition lock scheme.
NSAssert(nil == _webView, @"*** Render error *** renderOffscreenOnMainThread called when _webView already exists");
if (nil == _webView)
_webView = [[self class] _newWebView];
if (nil == _webView) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleWebViewAvailableNotification:) name:FVWebIconWebViewAvailableNotificationName object:[self class]];
}
else {
// See also http://lists.apple.com/archives/quicklook-dev/2007/Nov/msg00047.html
// Note: changed from blocking to non-blocking; we now just keep state and rely on the delegate methods.
[_webView setFrameLoadDelegate:self];
[_webView setPolicyDelegate:self];
[_webView setResourceLoadDelegate:self];
NSURLRequest *request = [NSURLRequest requestWithURL:_httpURL cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:60.0];
[[_webView mainFrame] loadRequest:request];
}
}
- (void)_handleWebViewAvailableNotification:(NSNotification *)aNotification
{
FVAPIAssert1(pthread_main_np() != 0, @"*** threading violation *** %s requires main thread", __func__);
[[NSNotificationCenter defaultCenter] removeObserver:self name:FVWebIconWebViewAvailableNotificationName object:[self class]];
[self renderOffscreenOnMainThread];
}
- (NSString *)debugDescription
{
NSAssert1([self tryLock], @"*** threading violation *** failed to lock before %@", NSStringFromSelector(_cmd));
return [NSString stringWithFormat:@"%@: { \n\tURL = %@\n\tWebView = %@\n\tFull image = %@\n\tThumbnail = %@\n }", [self description], _httpURL, _webView, _fullImage, _thumbnail];
}
- (void)renderOffscreen
{
[[self class] _startRenderingForKey:_cacheKey];
[_condLock lockWhenCondition:IDLE];
/*
!!! Early return after a quick check here, since these don't result in a cached image.
In the best case, URL loading would cache the results, and this check is unnecessary.
However, it's lightweight, and definitely avoids network traffic and main thread callout,
when the same program has multiple views or otherwise ends up creating the same icons
repeatedly.
*/
[_mimeIconLock lock];
FVIcon *mimeIcon = [_mimeIcons objectForKey:_cacheKey];
if (mimeIcon) {
_fallbackIcon = [mimeIcon retain];
[_mimeIconLock unlock];
[_condLock unlockWithCondition:IDLE];
[[self class] _stopRenderingForKey:_cacheKey];
return;
}
// unlock on either branch of the previous conditional
[_mimeIconLock unlock];
if ([NSThread instancesRespondToSelector:@selector(setName:)] && pthread_main_np() == 0)
[[NSThread currentThread] setName:[_httpURL absoluteString]];
// check the disk cache first
// note that _fullImage may be non-NULL if we were added to the FVOperationQueue multiple times before renderOffscreen was called
if (NULL == _fullImage && FVShouldDrawFullImageWithThumbnailSize(_desiredSize, [self _thumbnailSize]))
_fullImage = [FVCGImageCache newImageForKey:_cacheKey];
// always load for the fast drawing path
if (NULL == _thumbnail)
_thumbnail = [FVCGImageCache newThumbnailForKey:_cacheKey];
// unlock before calling performSelectorOnMainThread:... since it could cause a callout that tries to acquire the lock (one of the delegate methods)
if (NULL == _thumbnail && NULL == _fullImage) {
// make sure needsRenderForSize: knows that we're actively rendering, so renderOffscreen doesn't get called again
[_condLock unlockWithCondition:LOADING];
[self performSelectorOnMainThread:@selector(renderOffscreenOnMainThread) withObject:nil waitUntilDone:NO modes:[NSArray arrayWithObject:(id)kCFRunLoopCommonModes]];
[_condLock lockWhenCondition:LOADED];
CGImageRef fullImage = NULL, thumbnail = NULL;
/*
Possible states:
1) non-NULL _viewImage: successfull load
2) NULL view image
a) cancelled load
b) failed to load
*/
if (NULL != _viewImage) {
CGImageRelease(_fullImage);
_fullImage = FVCreateResampledFullImage(_viewImage);
// resample to a thumbnail size that will draw quickly
CGImageRelease(_thumbnail);
_thumbnail = FVCreateResampledThumbnail(_viewImage);
// get rid of the original image
CGImageRelease(_viewImage);
_viewImage = NULL;
// keep local references for caching, so we can unlock and notify observers for drawing (can't draw if we're holding the lock)
fullImage = CGImageRetain(_fullImage);
thumbnail = CGImageRetain(_thumbnail);
}
else if (nil == _fallbackIcon && false == _cancelledLoad) {
_fallbackIcon = [[FVFinderIcon allocWithZone:[self zone]] initWithURL:_httpURL];
}
// don't allocate anything for a load that was cancelled (releaseResources called during load)
// unlock before caching images so drawing can take place
[_condLock unlockWithCondition:IDLE];
if (fullImage) [FVCGImageCache cacheImage:fullImage forKey:_cacheKey];
if (thumbnail) [FVCGImageCache cacheThumbnail:thumbnail forKey:_cacheKey];
CGImageRelease(fullImage);
CGImageRelease(thumbnail);
}
else {
[_condLock unlockWithCondition:IDLE];
}
[[self class] _stopRenderingForKey:_cacheKey];
}
- (void)fastDrawInRect:(NSRect)dstRect ofContext:(CGContextRef)context;
{
if ([_condLock tryLockWhenCondition:IDLE]) {
if (_thumbnail) {
CGContextDrawImage(context, [self _drawingRectWithRect:dstRect], _thumbnail);
[self unlock];
}
else {
[self unlock];
// let drawInRect: handle the rect conversion
[self drawInRect:dstRect ofContext:context];
}
}
else {
[self _drawPlaceholderInRect:dstRect ofContext:context];
}
}
- (void)drawInRect:(NSRect)dstRect ofContext:(CGContextRef)context
{
if ([_condLock tryLockWhenCondition:IDLE]) {
CGRect drawRect = [self _drawingRectWithRect:dstRect];
BOOL shouldDrawFullImage = FVShouldDrawFullImageWithThumbnailSize(dstRect.size, [self _thumbnailSize]);
// if we have _fullImage, we're guaranteed to have _thumbnail
if (_fullImage && shouldDrawFullImage)
CGContextDrawImage(context, drawRect, _fullImage);
else if (_thumbnail)
CGContextDrawImage(context, drawRect, _thumbnail);
else if (nil == _fallbackIcon)
[self _drawPlaceholderInRect:dstRect ofContext:context];
else
[_fallbackIcon drawInRect:dstRect ofContext:context];
[self unlock];
}
else {
[self _drawPlaceholderInRect:dstRect ofContext:context];
}
}
@end