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