1/*
2 * Copyright (C) 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. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#import "config.h"
27#import "WKPrintingView.h"
28
29#import "Logging.h"
30#import "PrintInfo.h"
31#import "WebData.h"
32#import "WebPageProxy.h"
33
34using namespace WebKit;
35using namespace WebCore;
36
37NSString * const WebKitOriginalTopPrintingMarginKey = @"WebKitOriginalTopMargin";
38NSString * const WebKitOriginalBottomPrintingMarginKey = @"WebKitOriginalBottomMargin";
39
40NSString * const NSPrintInfoDidChangeNotification = @"NSPrintInfoDidChange";
41
42static BOOL isForcingPreviewUpdate;
43
44@implementation WKPrintingView
45
46- (id)initWithFrameProxy:(WebKit::WebFrameProxy*)frame view:(NSView *)wkView
47{
48    self = [super init]; // No frame rect to pass to NSView.
49    if (!self)
50        return nil;
51
52    _webFrame = frame;
53    _wkView = wkView;
54
55    return self;
56}
57
58- (BOOL)isFlipped
59{
60    return YES;
61}
62
63- (void)_setAutodisplay:(BOOL)newState
64{
65    if (!newState && [[_wkView.get() window] isAutodisplay])
66        [_wkView.get() displayIfNeeded];
67
68    [[_wkView.get() window] setAutodisplay:newState];
69
70    // For some reason, painting doesn't happen for a long time without this call, <rdar://problem/8975229>.
71    if (newState)
72        [_wkView.get() displayIfNeeded];
73}
74
75
76- (void)_suspendAutodisplay
77{
78    // A drawRect: call on WKView causes a switch to screen mode, which is slow due to relayout, and we want to avoid that.
79    // Disabling autodisplay will prevent random updates from causing this, but resizing the window will still work.
80    if (_autodisplayResumeTimer) {
81        [_autodisplayResumeTimer invalidate];
82        _autodisplayResumeTimer = nil;
83    } else
84        [self _setAutodisplay:NO];
85}
86
87- (void)_delayedResumeAutodisplayTimerFired
88{
89    ASSERT(isMainThread());
90
91    _autodisplayResumeTimer = nil;
92    [self _setAutodisplay:YES];
93}
94
95- (void)_delayedResumeAutodisplay
96{
97    // AppKit calls endDocument/beginDocument when print option change. We don't want to switch between print and screen mode just for that,
98    // and enabling autodisplay may result in switching into screen mode. So, autodisplay is only resumed on next run loop iteration.
99    if (!_autodisplayResumeTimer) {
100        _autodisplayResumeTimer = [NSTimer timerWithTimeInterval:0 target:self selector:@selector(_delayedResumeAutodisplayTimerFired) userInfo:nil repeats:NO];
101        // The timer must be scheduled on main thread, because printing thread may finish before it fires.
102        [[NSRunLoop mainRunLoop] addTimer:_autodisplayResumeTimer forMode:NSDefaultRunLoopMode];
103    }
104}
105
106- (void)_adjustPrintingMarginsForHeaderAndFooter
107{
108    NSPrintInfo *info = [_printOperation printInfo];
109    NSMutableDictionary *infoDictionary = [info dictionary];
110
111    // We need to modify the top and bottom margins in the NSPrintInfo to account for the space needed by the
112    // header and footer. Because this method can be called more than once on the same NSPrintInfo (see 5038087),
113    // we stash away the unmodified top and bottom margins the first time this method is called, and we read from
114    // those stashed-away values on subsequent calls.
115    double originalTopMargin;
116    double originalBottomMargin;
117    NSNumber *originalTopMarginNumber = [infoDictionary objectForKey:WebKitOriginalTopPrintingMarginKey];
118    if (!originalTopMarginNumber) {
119        ASSERT(![infoDictionary objectForKey:WebKitOriginalBottomPrintingMarginKey]);
120        originalTopMargin = [info topMargin];
121        originalBottomMargin = [info bottomMargin];
122        [infoDictionary setObject:[NSNumber numberWithDouble:originalTopMargin] forKey:WebKitOriginalTopPrintingMarginKey];
123        [infoDictionary setObject:[NSNumber numberWithDouble:originalBottomMargin] forKey:WebKitOriginalBottomPrintingMarginKey];
124    } else {
125        ASSERT([originalTopMarginNumber isKindOfClass:[NSNumber class]]);
126        ASSERT([[infoDictionary objectForKey:WebKitOriginalBottomPrintingMarginKey] isKindOfClass:[NSNumber class]]);
127        originalTopMargin = [originalTopMarginNumber doubleValue];
128        originalBottomMargin = [[infoDictionary objectForKey:WebKitOriginalBottomPrintingMarginKey] doubleValue];
129    }
130
131    CGFloat scale = [info scalingFactor];
132    [info setTopMargin:originalTopMargin + _webFrame->page()->headerHeight(_webFrame.get()) * scale];
133    [info setBottomMargin:originalBottomMargin + _webFrame->page()->footerHeight(_webFrame.get()) * scale];
134}
135
136- (BOOL)_isPrintingPreview
137{
138    // <rdar://problem/8901041> Please add an API returning whether the current print operation is for preview.
139    // Assuming that if NSPrintOperation is allowed to spawn a thread for printing, it will. Print preview doesn't spawn a thread.
140    return !_isPrintingFromSecondaryThread;
141}
142
143- (void)_updatePreview
144{
145    // <rdar://problem/8900923> Please add an API to force print preview update.
146    ASSERT(!isForcingPreviewUpdate);
147    isForcingPreviewUpdate = YES;
148    [[NSNotificationCenter defaultCenter] postNotificationName:NSPrintInfoDidChangeNotification object:nil];
149    isForcingPreviewUpdate = NO;
150}
151
152- (BOOL)_hasPageRects
153{
154    // WebCore always prints at least one page.
155    return !_printingPageRects.isEmpty();
156}
157
158- (NSUInteger)_firstPrintedPageNumber
159{
160    // Need to directly access the dictionary because -[NSPrintOperation pageRange] verifies pagination, potentially causing recursion.
161    return [[[[_printOperation printInfo] dictionary] objectForKey:NSPrintFirstPage] unsignedIntegerValue];
162}
163
164- (NSUInteger)_lastPrintedPageNumber
165{
166    ASSERT([self _hasPageRects]);
167
168    // Need to directly access the dictionary because -[NSPrintOperation pageRange] verifies pagination, potentially causing recursion.
169    NSUInteger firstPage = [[[[_printOperation printInfo] dictionary] objectForKey:NSPrintFirstPage] unsignedIntegerValue];
170    NSUInteger lastPage = [[[[_printOperation printInfo] dictionary] objectForKey:NSPrintLastPage] unsignedIntegerValue];
171    if (lastPage - firstPage >= _printingPageRects.size())
172        return _printingPageRects.size();
173    return lastPage;
174}
175
176- (uint64_t)_expectedPreviewCallbackForRect:(const IntRect&)rect
177{
178    for (HashMap<uint64_t, WebCore::IntRect>::iterator iter = _expectedPreviewCallbacks.begin(); iter != _expectedPreviewCallbacks.end(); ++iter) {
179        if (iter->second  == rect)
180            return iter->first;
181    }
182    return 0;
183}
184
185struct IPCCallbackContext {
186    RetainPtr<WKPrintingView> view;
187    uint64_t callbackID;
188};
189
190static void pageDidDrawToPDF(WKDataRef dataRef, WKErrorRef, void* untypedContext)
191{
192    ASSERT(isMainThread());
193
194    OwnPtr<IPCCallbackContext> context = adoptPtr(static_cast<IPCCallbackContext*>(untypedContext));
195    WKPrintingView *view = context->view.get();
196    WebData* data = toImpl(dataRef);
197
198    if (context->callbackID == view->_expectedPrintCallback) {
199        ASSERT(![view _isPrintingPreview]);
200        ASSERT(view->_printedPagesData.isEmpty());
201        ASSERT(!view->_printedPagesPDFDocument);
202        if (data)
203            view->_printedPagesData.append(data->bytes(), data->size());
204        view->_expectedPrintCallback = 0;
205        view->_printingCallbackCondition.signal();
206    } else {
207        // If the user has already changed print setup, then this response is obsolete. And this callback is not in response to the latest request,
208        // then the user has already moved to another page - we'll cache the response, but won't draw it.
209        HashMap<uint64_t, WebCore::IntRect>::iterator iter = view->_expectedPreviewCallbacks.find(context->callbackID);
210        if (iter != view->_expectedPreviewCallbacks.end()) {
211            ASSERT([view _isPrintingPreview]);
212
213            if (data) {
214                pair<HashMap<WebCore::IntRect, Vector<uint8_t> >::iterator, bool> entry = view->_pagePreviews.add(iter->second, Vector<uint8_t>());
215                entry.first->second.append(data->bytes(), data->size());
216            }
217            view->_expectedPreviewCallbacks.remove(context->callbackID);
218            bool receivedResponseToLatestRequest = view->_latestExpectedPreviewCallback == context->callbackID;
219            if (receivedResponseToLatestRequest) {
220                view->_latestExpectedPreviewCallback = 0;
221                [view _updatePreview];
222            }
223        }
224    }
225}
226
227- (void)_preparePDFDataForPrintingOnSecondaryThread
228{
229    ASSERT(isMainThread());
230
231    if (!_webFrame->page()) {
232        _printingCallbackCondition.signal();
233        return;
234    }
235
236    MutexLocker lock(_printingCallbackMutex);
237
238    ASSERT([self _hasPageRects]);
239    ASSERT(_printedPagesData.isEmpty());
240    ASSERT(!_printedPagesPDFDocument);
241    ASSERT(!_expectedPrintCallback);
242
243    NSUInteger firstPage = [self _firstPrintedPageNumber];
244    NSUInteger lastPage = [self _lastPrintedPageNumber];
245
246    ASSERT(firstPage > 0);
247    ASSERT(firstPage <= lastPage);
248    LOG(View, "WKPrintingView requesting PDF data for pages %u...%u", firstPage, lastPage);
249
250    // Return to printing mode if we're already back to screen (e.g. due to window resizing).
251    _webFrame->page()->beginPrinting(_webFrame.get(), PrintInfo([_printOperation printInfo]));
252
253    IPCCallbackContext* context = new IPCCallbackContext;
254    RefPtr<DataCallback> callback = DataCallback::create(context, pageDidDrawToPDF);
255    _expectedPrintCallback = callback->callbackID();
256
257    context->view = self;
258    context->callbackID = callback->callbackID();
259
260    _webFrame->page()->drawPagesToPDF(_webFrame.get(), firstPage - 1, lastPage - firstPage + 1, callback.get());
261}
262
263static void pageDidComputePageRects(const Vector<WebCore::IntRect>& pageRects, double totalScaleFactorForPrinting, WKErrorRef, void* untypedContext)
264{
265    ASSERT(isMainThread());
266
267    OwnPtr<IPCCallbackContext> context = adoptPtr(static_cast<IPCCallbackContext*>(untypedContext));
268    WKPrintingView *view = context->view.get();
269
270    // If the user has already changed print setup, then this response is obsolete.
271    if (context->callbackID == view->_expectedComputedPagesCallback) {
272        ASSERT(isMainThread());
273        ASSERT(view->_expectedPreviewCallbacks.isEmpty());
274        ASSERT(!view->_latestExpectedPreviewCallback);
275        ASSERT(!view->_expectedPrintCallback);
276        ASSERT(view->_pagePreviews.isEmpty());
277        view->_expectedComputedPagesCallback = 0;
278
279        view->_printingPageRects = pageRects;
280        view->_totalScaleFactorForPrinting = totalScaleFactorForPrinting;
281
282        // Sanitize a response coming from the Web process.
283        if (view->_printingPageRects.isEmpty())
284            view->_printingPageRects.append(IntRect(0, 0, 1, 1));
285        if (view->_totalScaleFactorForPrinting <= 0)
286            view->_totalScaleFactorForPrinting = 1;
287
288        const IntRect& lastPrintingPageRect = view->_printingPageRects[view->_printingPageRects.size() - 1];
289        NSRect newFrameSize = NSMakeRect(0, 0,
290            ceil(lastPrintingPageRect.maxX() * view->_totalScaleFactorForPrinting),
291            ceil(lastPrintingPageRect.maxY() * view->_totalScaleFactorForPrinting));
292        LOG(View, "WKPrintingView setting frame size to x:%g y:%g width:%g height:%g", newFrameSize.origin.x, newFrameSize.origin.y, newFrameSize.size.width, newFrameSize.size.height);
293        [view setFrame:newFrameSize];
294
295        if ([view _isPrintingPreview]) {
296            // Show page count, and ask for an actual image to replace placeholder.
297            [view _updatePreview];
298        } else {
299            // When printing, request everything we'll need beforehand.
300            [view _preparePDFDataForPrintingOnSecondaryThread];
301        }
302    }
303}
304
305- (BOOL)_askPageToComputePageRects
306{
307    ASSERT(isMainThread());
308
309    if (!_webFrame->page())
310        return NO;
311
312    ASSERT(!_expectedComputedPagesCallback);
313
314    IPCCallbackContext* context = new IPCCallbackContext;
315    RefPtr<ComputedPagesCallback> callback = ComputedPagesCallback::create(context, pageDidComputePageRects);
316    _expectedComputedPagesCallback = callback->callbackID();
317    context->view = self;
318    context->callbackID = _expectedComputedPagesCallback;
319
320    _webFrame->page()->computePagesForPrinting(_webFrame.get(), PrintInfo([_printOperation printInfo]), callback.release());
321    return YES;
322}
323
324static void prepareDataForPrintingOnSecondaryThread(void* untypedContext)
325{
326    ASSERT(isMainThread());
327
328    WKPrintingView *view = static_cast<WKPrintingView *>(untypedContext);
329    MutexLocker lock(view->_printingCallbackMutex);
330
331    // We may have received page rects while a message to call this function traveled from secondary thread to main one.
332    if ([view _hasPageRects]) {
333        [view _preparePDFDataForPrintingOnSecondaryThread];
334        return;
335    }
336
337    // A request for pages has already been made, just wait for it to finish.
338    if (view->_expectedComputedPagesCallback)
339        return;
340
341    [view _askPageToComputePageRects];
342}
343
344- (BOOL)knowsPageRange:(NSRangePointer)range
345{
346    LOG(View, "-[WKPrintingView %p knowsPageRange:], %s, %s", self, [self _hasPageRects] ? "print data is available" : "print data is not available yet", isMainThread() ? "on main thread" : "on secondary thread");
347    ASSERT(_printOperation == [NSPrintOperation currentOperation]);
348
349    // Assuming that once we switch to printing from a secondary thread, we don't go back.
350    ASSERT(!_isPrintingFromSecondaryThread || !isMainThread());
351    if (!isMainThread())
352        _isPrintingFromSecondaryThread = YES;
353
354    if (!_webFrame->page()) {
355        *range = NSMakeRange(1, NSIntegerMax);
356        return YES;
357    }
358
359    [self _suspendAutodisplay];
360
361    [self _adjustPrintingMarginsForHeaderAndFooter];
362
363    if ([self _hasPageRects])
364        *range = NSMakeRange(1, _printingPageRects.size());
365    else if (!isMainThread()) {
366        ASSERT(![self _isPrintingPreview]);
367        MutexLocker lock(_printingCallbackMutex);
368        callOnMainThread(prepareDataForPrintingOnSecondaryThread, self);
369        _printingCallbackCondition.wait(_printingCallbackMutex);
370        *range = NSMakeRange(1, _printingPageRects.size());
371    } else {
372        ASSERT([self _isPrintingPreview]);
373
374        // If a request for pages hasn't already been made, make it now.
375        if (!_expectedComputedPagesCallback)
376            [self _askPageToComputePageRects];
377
378        *range = NSMakeRange(1, NSIntegerMax);
379    }
380    return YES;
381}
382
383- (unsigned)_pageForRect:(NSRect)rect
384{
385    // Assuming that rect exactly matches one of the pages.
386    for (size_t i = 0; i < _printingPageRects.size(); ++i) {
387        IntRect currentRect(_printingPageRects[i]);
388        currentRect.scale(_totalScaleFactorForPrinting);
389        if (rect.origin.y == currentRect.y() && rect.origin.x == currentRect.x())
390            return i + 1;
391    }
392    ASSERT_NOT_REACHED();
393    return 0; // Invalid page number.
394}
395
396- (void)_drawPDFDocument:(CGPDFDocumentRef)pdfDocument page:(unsigned)page atPoint:(NSPoint)point
397{
398    if (!pdfDocument) {
399        LOG_ERROR("Couldn't create a PDF document with data passed for preview");
400        return;
401    }
402
403    CGPDFPageRef pdfPage = CGPDFDocumentGetPage(pdfDocument, page);
404    if (!pdfPage) {
405        LOG_ERROR("Preview data doesn't have page %d", page);
406        return;
407    }
408
409    NSGraphicsContext *nsGraphicsContext = [NSGraphicsContext currentContext];
410    CGContextRef context = static_cast<CGContextRef>([nsGraphicsContext graphicsPort]);
411
412    CGContextSaveGState(context);
413    CGContextTranslateCTM(context, point.x, point.y);
414    CGContextScaleCTM(context, _totalScaleFactorForPrinting, -_totalScaleFactorForPrinting);
415    CGContextTranslateCTM(context, 0, -CGPDFPageGetBoxRect(pdfPage, kCGPDFMediaBox).size.height);
416    CGContextDrawPDFPage(context, pdfPage);
417    CGContextRestoreGState(context);
418}
419
420- (void)_drawPreview:(NSRect)nsRect
421{
422    ASSERT(isMainThread());
423
424    IntRect rect(nsRect);
425    rect.scale(1 / _totalScaleFactorForPrinting);
426    HashMap<WebCore::IntRect, Vector<uint8_t> >::iterator pagePreviewIterator = _pagePreviews.find(rect);
427    if (pagePreviewIterator == _pagePreviews.end())  {
428        // It's too early to ask for page preview if we don't even know page size and scale.
429        if ([self _hasPageRects]) {
430            if (uint64_t existingCallback = [self _expectedPreviewCallbackForRect:rect]) {
431                // We've already asked for a preview of this page, and are waiting for response.
432                // There is no need to ask again.
433                _latestExpectedPreviewCallback = existingCallback;
434            } else {
435                // Preview isn't available yet, request it asynchronously.
436                if (!_webFrame->page())
437                    return;
438
439                // Return to printing mode if we're already back to screen (e.g. due to window resizing).
440                _webFrame->page()->beginPrinting(_webFrame.get(), PrintInfo([_printOperation printInfo]));
441
442                IPCCallbackContext* context = new IPCCallbackContext;
443                RefPtr<DataCallback> callback = DataCallback::create(context, pageDidDrawToPDF);
444                _latestExpectedPreviewCallback = callback->callbackID();
445                _expectedPreviewCallbacks.add(_latestExpectedPreviewCallback, rect);
446
447                context->view = self;
448                context->callbackID = callback->callbackID();
449
450                _webFrame->page()->drawRectToPDF(_webFrame.get(), rect, callback.get());
451                return;
452            }
453        }
454
455        // FIXME: Draw a placeholder
456        return;
457    }
458
459    const Vector<uint8_t>& pdfData = pagePreviewIterator->second;
460    RetainPtr<CGDataProviderRef> pdfDataProvider(AdoptCF, CGDataProviderCreateWithData(0, pdfData.data(), pdfData.size(), 0));
461    RetainPtr<CGPDFDocumentRef> pdfDocument(AdoptCF, CGPDFDocumentCreateWithProvider(pdfDataProvider.get()));
462
463    [self _drawPDFDocument:pdfDocument.get() page:1 atPoint:NSMakePoint(nsRect.origin.x, nsRect.origin.y)];
464}
465
466- (void)drawRect:(NSRect)nsRect
467{
468    LOG(View, "WKPrintingView %p printing rect x:%g, y:%g, width:%g, height:%g%s", self, nsRect.origin.x, nsRect.origin.y, nsRect.size.width, nsRect.size.height, [self _isPrintingPreview] ? " for preview" : "");
469
470    ASSERT(_printOperation == [NSPrintOperation currentOperation]);
471
472    if (!_webFrame->page())
473        return;
474
475    if ([self _isPrintingPreview]) {
476        [self _drawPreview:nsRect];
477        return;
478    }
479
480    ASSERT(!isMainThread());
481    ASSERT(!_printedPagesData.isEmpty()); // Prepared by knowsPageRange:
482
483    if (!_printedPagesPDFDocument) {
484        RetainPtr<CGDataProviderRef> pdfDataProvider(AdoptCF, CGDataProviderCreateWithData(0, _printedPagesData.data(), _printedPagesData.size(), 0));
485        _printedPagesPDFDocument.adoptCF(CGPDFDocumentCreateWithProvider(pdfDataProvider.get()));
486    }
487
488    unsigned printedPageNumber = [self _pageForRect:nsRect] - [self _firstPrintedPageNumber] + 1;
489    [self _drawPDFDocument:_printedPagesPDFDocument.get() page:printedPageNumber atPoint:NSMakePoint(nsRect.origin.x, nsRect.origin.y)];
490}
491
492- (void)_drawPageBorderWithSizeOnMainThread:(NSSize)borderSize
493{
494    ASSERT(isMainThread());
495
496    // When printing from a secondary thread, the main thread doesn't have graphics context and printing operation set up.
497    NSGraphicsContext *currentContext = [NSGraphicsContext currentContext];
498    [NSGraphicsContext setCurrentContext:[_printOperation context]];
499
500    ASSERT(![NSPrintOperation currentOperation]);
501    [NSPrintOperation setCurrentOperation:_printOperation];
502
503    [self drawPageBorderWithSize:borderSize];
504
505    [NSPrintOperation setCurrentOperation:nil];
506    [NSGraphicsContext setCurrentContext:currentContext];
507}
508
509- (void)drawPageBorderWithSize:(NSSize)borderSize
510{
511    ASSERT(NSEqualSizes(borderSize, [[_printOperation printInfo] paperSize]));
512    ASSERT(_printOperation == [NSPrintOperation currentOperation]);
513
514    if (!isMainThread()) {
515        // Don't call the client from a secondary thread.
516        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[WKPrintingView instanceMethodSignatureForSelector:@selector(_drawPageBorderWithSizeOnMainThread:)]];
517        [invocation setSelector:@selector(_drawPageBorderWithSizeOnMainThread:)];
518        [invocation setArgument:&borderSize atIndex:2];
519        [invocation performSelectorOnMainThread:@selector(invokeWithTarget:) withObject:self waitUntilDone:YES];
520        return;
521    }
522
523    if (!_webFrame->page())
524        return;
525
526    // The header and footer rect height scales with the page, but the width is always
527    // all the way across the printed page (inset by printing margins).
528    NSPrintInfo *printInfo = [_printOperation printInfo];
529    CGFloat scale = [printInfo scalingFactor];
530    NSSize paperSize = [printInfo paperSize];
531    CGFloat headerFooterLeft = [printInfo leftMargin] / scale;
532    CGFloat headerFooterWidth = (paperSize.width - ([printInfo leftMargin] + [printInfo rightMargin])) / scale;
533    NSRect footerRect = NSMakeRect(headerFooterLeft, [printInfo bottomMargin] / scale - _webFrame->page()->footerHeight(_webFrame.get()), headerFooterWidth, _webFrame->page()->footerHeight(_webFrame.get()));
534    NSRect headerRect = NSMakeRect(headerFooterLeft, (paperSize.height - [printInfo topMargin]) / scale, headerFooterWidth, _webFrame->page()->headerHeight(_webFrame.get()));
535
536    NSGraphicsContext *currentContext = [NSGraphicsContext currentContext];
537    [currentContext saveGraphicsState];
538    NSRectClip(headerRect);
539    _webFrame->page()->drawHeader(_webFrame.get(), headerRect);
540    [currentContext restoreGraphicsState];
541
542    [currentContext saveGraphicsState];
543    NSRectClip(footerRect);
544    _webFrame->page()->drawFooter(_webFrame.get(), footerRect);
545    [currentContext restoreGraphicsState];
546}
547
548- (NSRect)rectForPage:(NSInteger)page
549{
550    ASSERT(_printOperation == [NSPrintOperation currentOperation]);
551    if (![self _hasPageRects]) {
552        LOG(View, "-[WKPrintingView %p rectForPage:%d] - data is not yet available", self, (int)page);
553        if (!_webFrame->page()) {
554            // We may have not told AppKit how many pages there are, so it will try to print until a null rect is returned.
555            return NSMakeRect(0, 0, 0, 0);
556        }
557        // We must be still calculating the page range.
558        ASSERT(_expectedComputedPagesCallback);
559        return NSMakeRect(0, 0, 1, 1);
560    }
561
562    // If Web process crashes while computing page rects, we never tell AppKit how many pages there are.
563    // Returning a null rect prevents selecting non-existent pages in preview dialog.
564    if (static_cast<unsigned>(page) > _printingPageRects.size()) {
565        ASSERT(!_webFrame->page());
566        return NSMakeRect(0, 0, 0, 0);
567    }
568
569    IntRect rect = _printingPageRects[page - 1];
570    rect.scale(_totalScaleFactorForPrinting);
571    LOG(View, "-[WKPrintingView %p rectForPage:%d] -> x %d, y %d, width %d, height %d", self, (int)page, rect.x(), rect.y(), rect.width(), rect.height());
572    return rect;
573}
574
575// Temporary workaround for <rdar://problem/8944535>. Force correct printout positioning.
576- (NSPoint)locationOfPrintRect:(NSRect)aRect
577{
578    ASSERT(_printOperation == [NSPrintOperation currentOperation]);
579    return NSMakePoint([[_printOperation printInfo] leftMargin], [[_printOperation printInfo] bottomMargin]);
580}
581
582- (void)beginDocument
583{
584    ASSERT(_printOperation == [NSPrintOperation currentOperation]);
585
586    // Forcing preview update gets us here, but page setup hasn't actually changed.
587    if (isForcingPreviewUpdate)
588        return;
589
590    LOG(View, "-[WKPrintingView %p beginDocument]", self);
591
592    [super beginDocument];
593
594    [self _suspendAutodisplay];
595}
596
597- (void)endDocument
598{
599    ASSERT(_printOperation == [NSPrintOperation currentOperation]);
600
601    // Forcing preview update gets us here, but page setup hasn't actually changed.
602    if (isForcingPreviewUpdate)
603        return;
604
605    LOG(View, "-[WKPrintingView %p endDocument] - clearing cached data", self);
606
607    // Both existing data and pending responses are now obsolete.
608    _printingPageRects.clear();
609    _totalScaleFactorForPrinting = 1;
610    _pagePreviews.clear();
611    _printedPagesData.clear();
612    _printedPagesPDFDocument = nullptr;
613    _expectedComputedPagesCallback = 0;
614    _expectedPreviewCallbacks.clear();
615    _latestExpectedPreviewCallback = 0;
616    _expectedPrintCallback = 0;
617
618    [self _delayedResumeAutodisplay];
619
620    [super endDocument];
621}
622@end
623