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