1/* 2 * Copyright (C) 2010, 2011, 2012 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 "WebPage.h" 28 29#import "AttributedString.h" 30#import "DataReference.h" 31#import "EditorState.h" 32#import "PDFKitImports.h" 33#import "PageBanner.h" 34#import "PluginView.h" 35#import "PrintInfo.h" 36#import "WKAccessibilityWebPageObject.h" 37#import "WebCoreArgumentCoders.h" 38#import "WebEvent.h" 39#import "WebEventConversion.h" 40#import "WebFrame.h" 41#import "WebImage.h" 42#import "WebInspector.h" 43#import "WebPageProxyMessages.h" 44#import "WebPreferencesStore.h" 45#import "WebProcess.h" 46#import <PDFKit/PDFKit.h> 47#import <QuartzCore/QuartzCore.h> 48#import <WebCore/AXObjectCache.h> 49#import <WebCore/EventHandler.h> 50#import <WebCore/FocusController.h> 51#import <WebCore/Frame.h> 52#import <WebCore/FrameLoader.h> 53#import <WebCore/FrameView.h> 54#import <WebCore/HTMLConverter.h> 55#import <WebCore/HitTestResult.h> 56#import <WebCore/KeyboardEvent.h> 57#import <WebCore/MIMETypeRegistry.h> 58#import <WebCore/NetworkingContext.h> 59#import <WebCore/Page.h> 60#import <WebCore/PlatformKeyboardEvent.h> 61#import <WebCore/PluginDocument.h> 62#import <WebCore/RenderObject.h> 63#import <WebCore/RenderStyle.h> 64#import <WebCore/ResourceHandle.h> 65#import <WebCore/ScrollView.h> 66#import <WebCore/StyleInheritedData.h> 67#import <WebCore/TextIterator.h> 68#import <WebCore/VisibleUnits.h> 69#import <WebCore/WindowsKeyboardCodes.h> 70#import <WebKitSystemInterface.h> 71 72using namespace WebCore; 73 74namespace WebKit { 75 76static PassRefPtr<Range> convertToRange(Frame*, NSRange); 77 78void WebPage::platformInitialize() 79{ 80#if USE(CFNETWORK) 81 m_page->addSchedulePair(SchedulePair::create([[NSRunLoop currentRunLoop] getCFRunLoop], kCFRunLoopCommonModes)); 82#else 83 m_page->addSchedulePair(SchedulePair::create([NSRunLoop currentRunLoop], kCFRunLoopCommonModes)); 84#endif 85 86 WKAccessibilityWebPageObject* mockAccessibilityElement = [[[WKAccessibilityWebPageObject alloc] init] autorelease]; 87 88 // Get the pid for the starting process. 89 pid_t pid = WebProcess::shared().presenterApplicationPid(); 90 WKAXInitializeElementWithPresenterPid(mockAccessibilityElement, pid); 91 [mockAccessibilityElement setWebPage:this]; 92 93 // send data back over 94 NSData* remoteToken = (NSData *)WKAXRemoteTokenForElement(mockAccessibilityElement); 95 CoreIPC::DataReference dataToken = CoreIPC::DataReference(reinterpret_cast<const uint8_t*>([remoteToken bytes]), [remoteToken length]); 96 send(Messages::WebPageProxy::RegisterWebProcessAccessibilityToken(dataToken)); 97 m_mockAccessibilityElement = mockAccessibilityElement; 98} 99 100NSObject *WebPage::accessibilityObjectForMainFramePlugin() 101{ 102 Frame* frame = m_page->mainFrame(); 103 if (!frame) 104 return 0; 105 106 if (PluginView* pluginView = pluginViewForFrame(frame)) 107 return pluginView->accessibilityObject(); 108 109 return 0; 110} 111 112void WebPage::platformPreferencesDidChange(const WebPreferencesStore& store) 113{ 114 if (WebInspector* inspector = this->inspector()) 115 inspector->setInspectorUsesWebKitUserInterface(store.getBoolValueForKey(WebPreferencesKey::inspectorUsesWebKitUserInterfaceKey())); 116 117 BOOL omitPDFSupport = [[NSUserDefaults standardUserDefaults] boolForKey:@"WebKitOmitPDFSupport"]; 118 if (!shouldUsePDFPlugin() && !omitPDFSupport) { 119 // If we don't have PDFPlugin, we will use a PDF view in the UI process for PDF and PostScript MIME types. 120 HashSet<String> mimeTypes = MIMETypeRegistry::getPDFAndPostScriptMIMETypes(); 121 for (HashSet<String>::iterator it = mimeTypes.begin(); it != mimeTypes.end(); ++it) 122 m_mimeTypesWithCustomRepresentations.add(*it); 123 } 124} 125 126bool WebPage::shouldUsePDFPlugin() const 127{ 128 return pdfPluginEnabled() && classFromPDFKit(@"PDFLayerController"); 129} 130 131typedef HashMap<String, String> SelectorNameMap; 132 133// Map selectors into Editor command names. 134// This is not needed for any selectors that have the same name as the Editor command. 135static const SelectorNameMap* createSelectorExceptionMap() 136{ 137 SelectorNameMap* map = new HashMap<String, String>; 138 139 map->add("insertNewlineIgnoringFieldEditor:", "InsertNewline"); 140 map->add("insertParagraphSeparator:", "InsertNewline"); 141 map->add("insertTabIgnoringFieldEditor:", "InsertTab"); 142 map->add("pageDown:", "MovePageDown"); 143 map->add("pageDownAndModifySelection:", "MovePageDownAndModifySelection"); 144 map->add("pageUp:", "MovePageUp"); 145 map->add("pageUpAndModifySelection:", "MovePageUpAndModifySelection"); 146 147 return map; 148} 149 150static String commandNameForSelectorName(const String& selectorName) 151{ 152 // Check the exception map first. 153 static const SelectorNameMap* exceptionMap = createSelectorExceptionMap(); 154 SelectorNameMap::const_iterator it = exceptionMap->find(selectorName); 155 if (it != exceptionMap->end()) 156 return it->value; 157 158 // Remove the trailing colon. 159 // No need to capitalize the command name since Editor command names are not case sensitive. 160 size_t selectorNameLength = selectorName.length(); 161 if (selectorNameLength < 2 || selectorName[selectorNameLength - 1] != ':') 162 return String(); 163 return selectorName.left(selectorNameLength - 1); 164} 165 166static Frame* frameForEvent(KeyboardEvent* event) 167{ 168 Node* node = event->target()->toNode(); 169 ASSERT(node); 170 Frame* frame = node->document()->frame(); 171 ASSERT(frame); 172 return frame; 173} 174 175bool WebPage::executeKeypressCommandsInternal(const Vector<WebCore::KeypressCommand>& commands, KeyboardEvent* event) 176{ 177 Frame* frame = frameForEvent(event); 178 ASSERT(frame->page() == corePage()); 179 180 bool eventWasHandled = false; 181 for (size_t i = 0; i < commands.size(); ++i) { 182 if (commands[i].commandName == "insertText:") { 183 ASSERT(!frame->editor().hasComposition()); 184 185 if (!frame->editor().canEdit()) 186 continue; 187 188 // An insertText: might be handled by other responders in the chain if we don't handle it. 189 // One example is space bar that results in scrolling down the page. 190 eventWasHandled |= frame->editor().insertText(commands[i].text, event); 191 } else { 192 Editor::Command command = frame->editor().command(commandNameForSelectorName(commands[i].commandName)); 193 if (command.isSupported()) { 194 bool commandExecutedByEditor = command.execute(event); 195 eventWasHandled |= commandExecutedByEditor; 196 if (!commandExecutedByEditor) { 197 bool performedNonEditingBehavior = event->keyEvent()->type() == PlatformEvent::RawKeyDown && performNonEditingBehaviorForSelector(commands[i].commandName, event); 198 eventWasHandled |= performedNonEditingBehavior; 199 } 200 } else { 201 bool commandWasHandledByUIProcess = false; 202 WebProcess::shared().parentProcessConnection()->sendSync(Messages::WebPageProxy::ExecuteSavedCommandBySelector(commands[i].commandName), 203 Messages::WebPageProxy::ExecuteSavedCommandBySelector::Reply(commandWasHandledByUIProcess), m_pageID); 204 eventWasHandled |= commandWasHandledByUIProcess; 205 } 206 } 207 } 208 return eventWasHandled; 209} 210 211bool WebPage::handleEditingKeyboardEvent(KeyboardEvent* event, bool saveCommands) 212{ 213 ASSERT(!saveCommands || event->keypressCommands().isEmpty()); // Save commands once for each event. 214 215 Frame* frame = frameForEvent(event); 216 217 const PlatformKeyboardEvent* platformEvent = event->keyEvent(); 218 if (!platformEvent) 219 return false; 220 Vector<KeypressCommand>& commands = event->keypressCommands(); 221 222 if ([platformEvent->macEvent() type] == NSFlagsChanged) 223 return false; 224 225 bool eventWasHandled = false; 226 227 if (saveCommands) { 228 KeyboardEvent* oldEvent = m_keyboardEventBeingInterpreted; 229 m_keyboardEventBeingInterpreted = event; 230 bool sendResult = WebProcess::shared().parentProcessConnection()->sendSync(Messages::WebPageProxy::InterpretQueuedKeyEvent(editorState()), 231 Messages::WebPageProxy::InterpretQueuedKeyEvent::Reply(eventWasHandled, commands), m_pageID); 232 m_keyboardEventBeingInterpreted = oldEvent; 233 if (!sendResult) 234 return false; 235 236 // An input method may make several actions per keypress. For example, pressing Return with Korean IM both confirms it and sends a newline. 237 // IM-like actions are handled immediately (so the return value from UI process is true), but there are saved commands that 238 // should be handled like normal text input after DOM event dispatch. 239 if (!event->keypressCommands().isEmpty()) 240 return false; 241 } else { 242 // Are there commands that could just cause text insertion if executed via Editor? 243 // WebKit doesn't have enough information about mode to decide how they should be treated, so we leave it upon WebCore 244 // to either handle them immediately (e.g. Tab that changes focus) or let a keypress event be generated 245 // (e.g. Tab that inserts a Tab character, or Enter). 246 bool haveTextInsertionCommands = false; 247 for (size_t i = 0; i < commands.size(); ++i) { 248 if (frame->editor().command(commandNameForSelectorName(commands[i].commandName)).isTextInsertion()) 249 haveTextInsertionCommands = true; 250 } 251 // If there are no text insertion commands, default keydown handler is the right time to execute the commands. 252 // Keypress (Char event) handler is the latest opportunity to execute. 253 if (!haveTextInsertionCommands || platformEvent->type() == PlatformEvent::Char) { 254 eventWasHandled = executeKeypressCommandsInternal(event->keypressCommands(), event); 255 event->keypressCommands().clear(); 256 } 257 } 258 return eventWasHandled; 259} 260 261void WebPage::sendComplexTextInputToPlugin(uint64_t pluginComplexTextInputIdentifier, const String& textInput) 262{ 263 for (HashSet<PluginView*>::const_iterator it = m_pluginViews.begin(), end = m_pluginViews.end(); it != end; ++it) { 264 if ((*it)->sendComplexTextInput(pluginComplexTextInputIdentifier, textInput)) 265 break; 266 } 267} 268 269void WebPage::setComposition(const String& text, Vector<CompositionUnderline> underlines, uint64_t selectionStart, uint64_t selectionEnd, uint64_t replacementRangeStart, uint64_t replacementRangeEnd, EditorState& newState) 270{ 271 Frame* frame = m_page->focusController()->focusedOrMainFrame(); 272 273 if (frame->selection()->isContentEditable()) { 274 RefPtr<Range> replacementRange; 275 if (replacementRangeStart != NSNotFound) { 276 replacementRange = convertToRange(frame, NSMakeRange(replacementRangeStart, replacementRangeEnd - replacementRangeStart)); 277 frame->selection()->setSelection(VisibleSelection(replacementRange.get(), SEL_DEFAULT_AFFINITY)); 278 } 279 280 frame->editor().setComposition(text, underlines, selectionStart, selectionEnd); 281 } 282 283 newState = editorState(); 284} 285 286void WebPage::confirmComposition(EditorState& newState) 287{ 288 Frame* frame = m_page->focusController()->focusedOrMainFrame(); 289 290 frame->editor().confirmComposition(); 291 292 newState = editorState(); 293} 294 295void WebPage::cancelComposition(EditorState& newState) 296{ 297 Frame* frame = m_page->focusController()->focusedOrMainFrame(); 298 299 frame->editor().cancelComposition(); 300 301 newState = editorState(); 302} 303 304void WebPage::insertText(const String& text, uint64_t replacementRangeStart, uint64_t replacementRangeEnd, bool& handled, EditorState& newState) 305{ 306 Frame* frame = m_page->focusController()->focusedOrMainFrame(); 307 308 if (replacementRangeStart != NSNotFound) { 309 RefPtr<Range> replacementRange = convertToRange(frame, NSMakeRange(replacementRangeStart, replacementRangeEnd - replacementRangeStart)); 310 if (replacementRange) 311 frame->selection()->setSelection(VisibleSelection(replacementRange.get(), SEL_DEFAULT_AFFINITY)); 312 } 313 314 if (!frame->editor().hasComposition()) { 315 // An insertText: might be handled by other responders in the chain if we don't handle it. 316 // One example is space bar that results in scrolling down the page. 317 handled = frame->editor().insertText(text, m_keyboardEventBeingInterpreted); 318 } else { 319 handled = true; 320 frame->editor().confirmComposition(text); 321 } 322 323 newState = editorState(); 324} 325 326void WebPage::insertDictatedText(const String& text, uint64_t replacementRangeStart, uint64_t replacementRangeEnd, const Vector<WebCore::DictationAlternative>& dictationAlternativeLocations, bool& handled, EditorState& newState) 327{ 328 Frame* frame = m_page->focusController()->focusedOrMainFrame(); 329 330 if (replacementRangeStart != NSNotFound) { 331 RefPtr<Range> replacementRange = convertToRange(frame, NSMakeRange(replacementRangeStart, replacementRangeEnd - replacementRangeStart)); 332 if (replacementRange) 333 frame->selection()->setSelection(VisibleSelection(replacementRange.get(), SEL_DEFAULT_AFFINITY)); 334 } 335 336 ASSERT(!frame->editor().hasComposition()); 337 handled = frame->editor().insertDictatedText(text, dictationAlternativeLocations, m_keyboardEventBeingInterpreted); 338 newState = editorState(); 339} 340 341void WebPage::getMarkedRange(uint64_t& location, uint64_t& length) 342{ 343 location = NSNotFound; 344 length = 0; 345 Frame* frame = m_page->focusController()->focusedOrMainFrame(); 346 if (!frame) 347 return; 348 349 RefPtr<Range> range = frame->editor().compositionRange(); 350 size_t locationSize; 351 size_t lengthSize; 352 if (range && TextIterator::getLocationAndLengthFromRange(frame->selection()->rootEditableElementOrDocumentElement(), range.get(), locationSize, lengthSize)) { 353 location = static_cast<uint64_t>(locationSize); 354 length = static_cast<uint64_t>(lengthSize); 355 } 356} 357 358void WebPage::getSelectedRange(uint64_t& location, uint64_t& length) 359{ 360 location = NSNotFound; 361 length = 0; 362 Frame* frame = m_page->focusController()->focusedOrMainFrame(); 363 if (!frame) 364 return; 365 366 size_t locationSize; 367 size_t lengthSize; 368 RefPtr<Range> range = frame->selection()->toNormalizedRange(); 369 if (range && TextIterator::getLocationAndLengthFromRange(frame->selection()->rootEditableElementOrDocumentElement(), range.get(), locationSize, lengthSize)) { 370 location = static_cast<uint64_t>(locationSize); 371 length = static_cast<uint64_t>(lengthSize); 372 } 373} 374 375void WebPage::getAttributedSubstringFromRange(uint64_t location, uint64_t length, AttributedString& result) 376{ 377 NSRange nsRange = NSMakeRange(location, length - location); 378 379 Frame* frame = m_page->focusController()->focusedOrMainFrame(); 380 if (!frame) 381 return; 382 383 if (frame->selection()->isNone() || !frame->selection()->isContentEditable() || frame->selection()->isInPasswordField()) 384 return; 385 386 RefPtr<Range> range = convertToRange(frame, nsRange); 387 if (!range) 388 return; 389 390 result.string = [WebHTMLConverter editingAttributedStringFromRange:range.get()]; 391 NSAttributedString* attributedString = result.string.get(); 392 393 // [WebHTMLConverter editingAttributedStringFromRange:] insists on inserting a trailing 394 // whitespace at the end of the string which breaks the ATOK input method. <rdar://problem/5400551> 395 // To work around this we truncate the resultant string to the correct length. 396 if ([attributedString length] > nsRange.length) { 397 ASSERT([attributedString length] == nsRange.length + 1); 398 ASSERT([[attributedString string] characterAtIndex:nsRange.length] == '\n' || [[attributedString string] characterAtIndex:nsRange.length] == ' '); 399 result.string = [attributedString attributedSubstringFromRange:NSMakeRange(0, nsRange.length)]; 400 } 401} 402 403void WebPage::characterIndexForPoint(IntPoint point, uint64_t& index) 404{ 405 index = NSNotFound; 406 Frame* frame = m_page->mainFrame(); 407 if (!frame) 408 return; 409 410 HitTestResult result = frame->eventHandler()->hitTestResultAtPoint(point); 411 frame = result.innerNonSharedNode() ? result.innerNodeFrame() : m_page->focusController()->focusedOrMainFrame(); 412 413 RefPtr<Range> range = frame->rangeForPoint(result.roundedPointInInnerNodeFrame()); 414 if (!range) 415 return; 416 417 size_t location; 418 size_t length; 419 if (TextIterator::getLocationAndLengthFromRange(frame->selection()->rootEditableElementOrDocumentElement(), range.get(), location, length)) 420 index = static_cast<uint64_t>(location); 421} 422 423PassRefPtr<Range> convertToRange(Frame* frame, NSRange nsrange) 424{ 425 if (nsrange.location > INT_MAX) 426 return 0; 427 if (nsrange.length > INT_MAX || nsrange.location + nsrange.length > INT_MAX) 428 nsrange.length = INT_MAX - nsrange.location; 429 430 // our critical assumption is that we are only called by input methods that 431 // concentrate on a given area containing the selection 432 // We have to do this because of text fields and textareas. The DOM for those is not 433 // directly in the document DOM, so serialization is problematic. Our solution is 434 // to use the root editable element of the selection start as the positional base. 435 // That fits with AppKit's idea of an input context. 436 return TextIterator::rangeFromLocationAndLength(frame->selection()->rootEditableElementOrDocumentElement(), nsrange.location, nsrange.length); 437} 438 439void WebPage::firstRectForCharacterRange(uint64_t location, uint64_t length, WebCore::IntRect& resultRect) 440{ 441 Frame* frame = m_page->focusController()->focusedOrMainFrame(); 442 resultRect.setLocation(IntPoint(0, 0)); 443 resultRect.setSize(IntSize(0, 0)); 444 445 RefPtr<Range> range = convertToRange(frame, NSMakeRange(location, length)); 446 if (!range) 447 return; 448 449 ASSERT(range->startContainer()); 450 ASSERT(range->endContainer()); 451 452 IntRect rect = frame->editor().firstRectForRange(range.get()); 453 resultRect = frame->view()->contentsToWindow(rect); 454} 455 456void WebPage::executeKeypressCommands(const Vector<WebCore::KeypressCommand>& commands, bool& handled, EditorState& newState) 457{ 458 handled = executeKeypressCommandsInternal(commands, m_keyboardEventBeingInterpreted); 459 newState = editorState(); 460} 461 462static bool isPositionInRange(const VisiblePosition& position, Range* range) 463{ 464 RefPtr<Range> positionRange = makeRange(position, position); 465 466 ExceptionCode ec = 0; 467 range->compareBoundaryPoints(Range::START_TO_START, positionRange.get(), ec); 468 if (ec) 469 return false; 470 471 if (!range->isPointInRange(positionRange->startContainer(), positionRange->startOffset(), ec)) 472 return false; 473 if (ec) 474 return false; 475 476 return true; 477} 478 479static bool shouldUseSelection(const VisiblePosition& position, const VisibleSelection& selection) 480{ 481 if (!selection.isRange()) 482 return false; 483 484 RefPtr<Range> selectedRange = selection.toNormalizedRange(); 485 if (!selectedRange) 486 return false; 487 488 return isPositionInRange(position, selectedRange.get()); 489} 490 491void WebPage::performDictionaryLookupAtLocation(const FloatPoint& floatPoint) 492{ 493 Frame* frame = m_page->mainFrame(); 494 if (!frame) 495 return; 496 497 if (PluginView* pluginView = pluginViewForFrame(frame)) { 498 if (pluginView->performDictionaryLookupAtLocation(floatPoint)) 499 return; 500 } 501 502 // Find the frame the point is over. 503 IntPoint point = roundedIntPoint(floatPoint); 504 HitTestResult result = frame->eventHandler()->hitTestResultAtPoint(frame->view()->windowToContents(point)); 505 frame = result.innerNonSharedNode() ? result.innerNonSharedNode()->document()->frame() : m_page->focusController()->focusedOrMainFrame(); 506 507 IntPoint translatedPoint = frame->view()->windowToContents(point); 508 509 // Don't do anything if there is no character at the point. 510 if (!frame->rangeForPoint(translatedPoint)) 511 return; 512 513 VisiblePosition position = frame->visiblePositionForPoint(translatedPoint); 514 VisibleSelection selection = m_page->focusController()->focusedOrMainFrame()->selection()->selection(); 515 if (shouldUseSelection(position, selection)) { 516 performDictionaryLookupForSelection(frame, selection); 517 return; 518 } 519 520 NSDictionary *options = nil; 521 522 // As context, we are going to use four lines of text before and after the point. (Dictionary can sometimes look up things that are four lines long) 523 const int numberOfLinesOfContext = 4; 524 VisiblePosition contextStart = position; 525 VisiblePosition contextEnd = position; 526 for (int i = 0; i < numberOfLinesOfContext; i++) { 527 VisiblePosition n = previousLinePosition(contextStart, contextStart.lineDirectionPointForBlockDirectionNavigation()); 528 if (n.isNull() || n == contextStart) 529 break; 530 contextStart = n; 531 } 532 for (int i = 0; i < numberOfLinesOfContext; i++) { 533 VisiblePosition n = nextLinePosition(contextEnd, contextEnd.lineDirectionPointForBlockDirectionNavigation()); 534 if (n.isNull() || n == contextEnd) 535 break; 536 contextEnd = n; 537 } 538 539 VisiblePosition lineStart = startOfLine(contextStart); 540 if (!lineStart.isNull()) 541 contextStart = lineStart; 542 543 VisiblePosition lineEnd = endOfLine(contextEnd); 544 if (!lineEnd.isNull()) 545 contextEnd = lineEnd; 546 547 NSRange rangeToPass = NSMakeRange(TextIterator::rangeLength(makeRange(contextStart, position).get()), 0); 548 549 RefPtr<Range> fullCharacterRange = makeRange(contextStart, contextEnd); 550 String fullPlainTextString = plainText(fullCharacterRange.get()); 551 552 NSRange extractedRange = WKExtractWordDefinitionTokenRangeFromContextualString(fullPlainTextString, rangeToPass, &options); 553 554 // This function sometimes returns {NSNotFound, 0} if it was unable to determine a good string. 555 if (extractedRange.location == NSNotFound) 556 return; 557 558 RefPtr<Range> finalRange = TextIterator::subrange(fullCharacterRange.get(), extractedRange.location, extractedRange.length); 559 if (!finalRange) 560 return; 561 562 performDictionaryLookupForRange(frame, finalRange.get(), options); 563} 564 565void WebPage::performDictionaryLookupForSelection(Frame* frame, const VisibleSelection& selection) 566{ 567 RefPtr<Range> selectedRange = selection.toNormalizedRange(); 568 if (!selectedRange) 569 return; 570 571 NSDictionary *options = nil; 572 573 VisiblePosition selectionStart = selection.visibleStart(); 574 VisiblePosition selectionEnd = selection.visibleEnd(); 575 576 // As context, we are going to use the surrounding paragraphs of text. 577 VisiblePosition paragraphStart = startOfParagraph(selectionStart); 578 VisiblePosition paragraphEnd = endOfParagraph(selectionEnd); 579 580 int lengthToSelectionStart = TextIterator::rangeLength(makeRange(paragraphStart, selectionStart).get()); 581 int lengthToSelectionEnd = TextIterator::rangeLength(makeRange(paragraphStart, selectionEnd).get()); 582 NSRange rangeToPass = NSMakeRange(lengthToSelectionStart, lengthToSelectionEnd - lengthToSelectionStart); 583 584 String fullPlainTextString = plainText(makeRange(paragraphStart, paragraphEnd).get()); 585 586 // Since we already have the range we want, we just need to grab the returned options. 587 WKExtractWordDefinitionTokenRangeFromContextualString(fullPlainTextString, rangeToPass, &options); 588 589 performDictionaryLookupForRange(frame, selectedRange.get(), options); 590} 591 592void WebPage::performDictionaryLookupForRange(Frame* frame, Range* range, NSDictionary *options) 593{ 594 if (range->text().stripWhiteSpace().isEmpty()) 595 return; 596 597 RenderObject* renderer = range->startContainer()->renderer(); 598 RenderStyle* style = renderer->style(); 599 600 Vector<FloatQuad> quads; 601 range->textQuads(quads); 602 if (quads.isEmpty()) 603 return; 604 605 IntRect rangeRect = frame->view()->contentsToWindow(quads[0].enclosingBoundingBox()); 606 607 DictionaryPopupInfo dictionaryPopupInfo; 608 dictionaryPopupInfo.origin = FloatPoint(rangeRect.x(), rangeRect.y() + (style->fontMetrics().ascent() * pageScaleFactor())); 609 dictionaryPopupInfo.options = (CFDictionaryRef)options; 610 611 NSAttributedString *nsAttributedString = [WebHTMLConverter editingAttributedStringFromRange:range]; 612 613 RetainPtr<NSMutableAttributedString> scaledNSAttributedString = adoptNS([[NSMutableAttributedString alloc] initWithString:[nsAttributedString string]]); 614 615 NSFontManager *fontManager = [NSFontManager sharedFontManager]; 616 617 [nsAttributedString enumerateAttributesInRange:NSMakeRange(0, [nsAttributedString length]) options:0 usingBlock:^(NSDictionary *attributes, NSRange range, BOOL *stop) { 618 RetainPtr<NSMutableDictionary> scaledAttributes = adoptNS([attributes mutableCopy]); 619 620 NSFont *font = [scaledAttributes objectForKey:NSFontAttributeName]; 621 if (font) { 622 font = [fontManager convertFont:font toSize:[font pointSize] * pageScaleFactor()]; 623 [scaledAttributes setObject:font forKey:NSFontAttributeName]; 624 } 625 626 [scaledNSAttributedString.get() addAttributes:scaledAttributes.get() range:range]; 627 }]; 628 629 AttributedString attributedString; 630 attributedString.string = scaledNSAttributedString; 631 632 send(Messages::WebPageProxy::DidPerformDictionaryLookup(attributedString, dictionaryPopupInfo)); 633} 634 635bool WebPage::performNonEditingBehaviorForSelector(const String& selector, KeyboardEvent* event) 636{ 637 // First give accessibility a chance to handle the event. 638 Frame* frame = frameForEvent(event); 639 frame->eventHandler()->handleKeyboardSelectionMovementForAccessibility(event); 640 if (event->defaultHandled()) 641 return true; 642 643 // FIXME: All these selectors have corresponding Editor commands, but the commands only work in editable content. 644 // Should such non-editing behaviors be implemented in Editor or EventHandler::defaultArrowEventHandler() perhaps? 645 646 bool didPerformAction = false; 647 648 if (selector == "moveUp:") 649 didPerformAction = scroll(m_page.get(), ScrollUp, ScrollByLine); 650 else if (selector == "moveToBeginningOfParagraph:") 651 didPerformAction = scroll(m_page.get(), ScrollUp, ScrollByPage); 652 else if (selector == "moveToBeginningOfDocument:") { 653 didPerformAction = scroll(m_page.get(), ScrollUp, ScrollByDocument); 654 didPerformAction |= scroll(m_page.get(), ScrollLeft, ScrollByDocument); 655 } else if (selector == "moveDown:") 656 didPerformAction = scroll(m_page.get(), ScrollDown, ScrollByLine); 657 else if (selector == "moveToEndOfParagraph:") 658 didPerformAction = scroll(m_page.get(), ScrollDown, ScrollByPage); 659 else if (selector == "moveToEndOfDocument:") { 660 didPerformAction = scroll(m_page.get(), ScrollDown, ScrollByDocument); 661 didPerformAction |= scroll(m_page.get(), ScrollLeft, ScrollByDocument); 662 } else if (selector == "moveLeft:") 663 didPerformAction = scroll(m_page.get(), ScrollLeft, ScrollByLine); 664 else if (selector == "moveWordLeft:") 665 didPerformAction = scroll(m_page.get(), ScrollLeft, ScrollByPage); 666 else if (selector == "moveToLeftEndOfLine:") 667 didPerformAction = m_page->goBack(); 668 else if (selector == "moveRight:") 669 didPerformAction = scroll(m_page.get(), ScrollRight, ScrollByLine); 670 else if (selector == "moveWordRight:") 671 didPerformAction = scroll(m_page.get(), ScrollRight, ScrollByPage); 672 else if (selector == "moveToRightEndOfLine:") 673 didPerformAction = m_page->goForward(); 674 675 return didPerformAction; 676} 677 678bool WebPage::performDefaultBehaviorForKeyEvent(const WebKeyboardEvent&) 679{ 680 return false; 681} 682 683void WebPage::registerUIProcessAccessibilityTokens(const CoreIPC::DataReference& elementToken, const CoreIPC::DataReference& windowToken) 684{ 685 NSData* elementTokenData = [NSData dataWithBytes:elementToken.data() length:elementToken.size()]; 686 NSData* windowTokenData = [NSData dataWithBytes:windowToken.data() length:windowToken.size()]; 687 id remoteElement = WKAXRemoteElementForToken(elementTokenData); 688 id remoteWindow = WKAXRemoteElementForToken(windowTokenData); 689 WKAXSetWindowForRemoteElement(remoteWindow, remoteElement); 690 691 [accessibilityRemoteObject() setRemoteParent:remoteElement]; 692} 693 694void WebPage::readSelectionFromPasteboard(const String& pasteboardName, bool& result) 695{ 696 Frame* frame = m_page->focusController()->focusedOrMainFrame(); 697 if (!frame || frame->selection()->isNone()) { 698 result = false; 699 return; 700 } 701 frame->editor().readSelectionFromPasteboard(pasteboardName); 702 result = true; 703} 704 705void WebPage::getStringSelectionForPasteboard(String& stringValue) 706{ 707 Frame* frame = m_page->focusController()->focusedOrMainFrame(); 708 709 if (!frame) 710 return; 711 712 if (PluginView* pluginView = focusedPluginViewForFrame(frame)) { 713 String selection = pluginView->getSelectionString(); 714 if (!selection.isNull()) { 715 stringValue = selection; 716 return; 717 } 718 } 719 720 if (frame->selection()->isNone()) 721 return; 722 723 stringValue = frame->editor().stringSelectionForPasteboard(); 724} 725 726void WebPage::getDataSelectionForPasteboard(const String pasteboardType, SharedMemory::Handle& handle, uint64_t& size) 727{ 728 Frame* frame = m_page->focusController()->focusedOrMainFrame(); 729 if (!frame || frame->selection()->isNone()) 730 return; 731 732 RefPtr<SharedBuffer> buffer = frame->editor().dataSelectionForPasteboard(pasteboardType); 733 if (!buffer) { 734 size = 0; 735 return; 736 } 737 size = buffer->size(); 738 RefPtr<SharedMemory> sharedMemoryBuffer = SharedMemory::create(size); 739 memcpy(sharedMemoryBuffer->data(), buffer->data(), size); 740 sharedMemoryBuffer->createHandle(handle, SharedMemory::ReadOnly); 741} 742 743WKAccessibilityWebPageObject* WebPage::accessibilityRemoteObject() 744{ 745 return m_mockAccessibilityElement.get(); 746} 747 748bool WebPage::platformHasLocalDataForURL(const WebCore::KURL& url) 749{ 750 NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:url]; 751 [request setValue:(NSString*)userAgent() forHTTPHeaderField:@"User-Agent"]; 752 NSCachedURLResponse *cachedResponse; 753 if (CFURLStorageSessionRef storageSession = corePage()->mainFrame()->loader()->networkingContext()->storageSession().platformSession()) 754 cachedResponse = WKCachedResponseForRequest(storageSession, request); 755 else 756 cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request]; 757 [request release]; 758 759 return cachedResponse; 760} 761 762static NSCachedURLResponse *cachedResponseForURL(WebPage* webPage, const KURL& url) 763{ 764 RetainPtr<NSMutableURLRequest> request = adoptNS([[NSMutableURLRequest alloc] initWithURL:url]); 765 [request.get() setValue:(NSString *)webPage->userAgent() forHTTPHeaderField:@"User-Agent"]; 766 767 if (CFURLStorageSessionRef storageSession = webPage->corePage()->mainFrame()->loader()->networkingContext()->storageSession().platformSession()) 768 return WKCachedResponseForRequest(storageSession, request.get()); 769 770 return [[NSURLCache sharedURLCache] cachedResponseForRequest:request.get()]; 771} 772 773String WebPage::cachedSuggestedFilenameForURL(const KURL& url) 774{ 775 return [[cachedResponseForURL(this, url) response] suggestedFilename]; 776} 777 778String WebPage::cachedResponseMIMETypeForURL(const KURL& url) 779{ 780 return [[cachedResponseForURL(this, url) response] MIMEType]; 781} 782 783PassRefPtr<SharedBuffer> WebPage::cachedResponseDataForURL(const KURL& url) 784{ 785 return SharedBuffer::wrapNSData([cachedResponseForURL(this, url) data]); 786} 787 788bool WebPage::platformCanHandleRequest(const WebCore::ResourceRequest& request) 789{ 790 if ([NSURLConnection canHandleRequest:request.nsURLRequest(DoNotUpdateHTTPBody)]) 791 return true; 792 793 // FIXME: Return true if this scheme is any one WebKit2 knows how to handle. 794 return request.url().protocolIs("applewebdata"); 795} 796 797void WebPage::shouldDelayWindowOrderingEvent(const WebKit::WebMouseEvent& event, bool& result) 798{ 799 result = false; 800 Frame* frame = m_page->focusController()->focusedOrMainFrame(); 801 if (!frame) 802 return; 803 804#if ENABLE(DRAG_SUPPORT) 805 HitTestResult hitResult = frame->eventHandler()->hitTestResultAtPoint(frame->view()->windowToContents(event.position()), HitTestRequest::ReadOnly | HitTestRequest::Active); 806 if (hitResult.isSelected()) 807 result = frame->eventHandler()->eventMayStartDrag(platform(event)); 808#endif 809} 810 811void WebPage::acceptsFirstMouse(int eventNumber, const WebKit::WebMouseEvent& event, bool& result) 812{ 813 result = false; 814 Frame* frame = m_page->focusController()->focusedOrMainFrame(); 815 if (!frame) 816 return; 817 818 HitTestResult hitResult = frame->eventHandler()->hitTestResultAtPoint(frame->view()->windowToContents(event.position()), HitTestRequest::ReadOnly | HitTestRequest::Active); 819 frame->eventHandler()->setActivationEventNumber(eventNumber); 820#if ENABLE(DRAG_SUPPORT) 821 if (hitResult.isSelected()) 822 result = frame->eventHandler()->eventMayStartDrag(platform(event)); 823 else 824#endif 825 result = !!hitResult.scrollbar(); 826} 827 828void WebPage::setLayerHostingMode(LayerHostingMode layerHostingMode) 829{ 830 m_layerHostingMode = layerHostingMode; 831 832 for (HashSet<PluginView*>::const_iterator it = m_pluginViews.begin(), end = m_pluginViews.end(); it != end; ++it) 833 (*it)->setLayerHostingMode(layerHostingMode); 834} 835 836void WebPage::setTopOverhangImage(PassRefPtr<WebImage> image) 837{ 838 FrameView* frameView = m_mainFrame->coreFrame()->view(); 839 if (!frameView) 840 return; 841 842 GraphicsLayer* layer = frameView->setWantsLayerForTopOverHangArea(image.get()); 843 if (!layer) 844 return; 845 846 layer->setSize(image->size()); 847 layer->setPosition(FloatPoint(0, -image->size().height())); 848 849 RetainPtr<CGImageRef> cgImage = image->bitmap()->makeCGImageCopy(); 850 layer->platformLayer().contents = (id)cgImage.get(); 851} 852 853void WebPage::setBottomOverhangImage(PassRefPtr<WebImage> image) 854{ 855 FrameView* frameView = m_mainFrame->coreFrame()->view(); 856 if (!frameView) 857 return; 858 859 GraphicsLayer* layer = frameView->setWantsLayerForBottomOverHangArea(image.get()); 860 if (!layer) 861 return; 862 863 layer->setSize(image->size()); 864 865 RetainPtr<CGImageRef> cgImage = image->bitmap()->makeCGImageCopy(); 866 layer->platformLayer().contents = (id)cgImage.get(); 867} 868 869void WebPage::updateHeaderAndFooterLayersForDeviceScaleChange(float scaleFactor) 870{ 871 if (m_headerBanner) 872 m_headerBanner->didChangeDeviceScaleFactor(scaleFactor); 873 if (m_footerBanner) 874 m_footerBanner->didChangeDeviceScaleFactor(scaleFactor); 875} 876 877void WebPage::computePagesForPrintingPDFDocument(uint64_t frameID, const PrintInfo& printInfo, Vector<IntRect>& resultPageRects) 878{ 879 ASSERT(resultPageRects.isEmpty()); 880 WebFrame* frame = WebProcess::shared().webFrame(frameID); 881 Frame* coreFrame = frame ? frame->coreFrame() : 0; 882 RetainPtr<PDFDocument> pdfDocument = coreFrame ? pdfDocumentForPrintingFrame(coreFrame) : 0; 883 if ([pdfDocument.get() allowsPrinting]) { 884 NSUInteger pageCount = [pdfDocument.get() pageCount]; 885 IntRect pageRect(0, 0, ceilf(printInfo.availablePaperWidth), ceilf(printInfo.availablePaperHeight)); 886 for (NSUInteger i = 1; i <= pageCount; ++i) { 887 resultPageRects.append(pageRect); 888 pageRect.move(0, pageRect.height()); 889 } 890 } 891} 892 893static inline CGFloat roundCGFloat(CGFloat f) 894{ 895 if (sizeof(CGFloat) == sizeof(float)) 896 return roundf(static_cast<float>(f)); 897 return static_cast<CGFloat>(round(f)); 898} 899 900static void drawPDFPage(PDFDocument *pdfDocument, CFIndex pageIndex, CGContextRef context, CGFloat pageSetupScaleFactor, CGSize paperSize) 901{ 902 CGContextSaveGState(context); 903 904 CGContextScaleCTM(context, pageSetupScaleFactor, pageSetupScaleFactor); 905 906 PDFPage *pdfPage = [pdfDocument pageAtIndex:pageIndex]; 907 NSRect cropBox = [pdfPage boundsForBox:kPDFDisplayBoxCropBox]; 908 if (NSIsEmptyRect(cropBox)) 909 cropBox = [pdfPage boundsForBox:kPDFDisplayBoxMediaBox]; 910 else 911 cropBox = NSIntersectionRect(cropBox, [pdfPage boundsForBox:kPDFDisplayBoxMediaBox]); 912 913 // Always auto-rotate PDF content regardless of the paper orientation. 914 NSInteger rotation = [pdfPage rotation]; 915 if (rotation == 90 || rotation == 270) 916 std::swap(cropBox.size.width, cropBox.size.height); 917 918 bool shouldRotate = (paperSize.width < paperSize.height) != (cropBox.size.width < cropBox.size.height); 919 if (shouldRotate) 920 std::swap(cropBox.size.width, cropBox.size.height); 921 922 // Center. 923 CGFloat widthDifference = paperSize.width / pageSetupScaleFactor - cropBox.size.width; 924 CGFloat heightDifference = paperSize.height / pageSetupScaleFactor - cropBox.size.height; 925 if (widthDifference || heightDifference) 926 CGContextTranslateCTM(context, roundCGFloat(widthDifference / 2), roundCGFloat(heightDifference / 2)); 927 928 if (shouldRotate) { 929 CGContextRotateCTM(context, static_cast<CGFloat>(piOverTwoDouble)); 930 CGContextTranslateCTM(context, 0, -cropBox.size.width); 931 } 932 933 [NSGraphicsContext saveGraphicsState]; 934 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:context flipped:NO]]; 935 [pdfPage drawWithBox:kPDFDisplayBoxCropBox]; 936 [NSGraphicsContext restoreGraphicsState]; 937 938 CGAffineTransform transform = CGContextGetCTM(context); 939 940 for (PDFAnnotation *annotation in [pdfPage annotations]) { 941 if (![annotation isKindOfClass:pdfAnnotationLinkClass()]) 942 continue; 943 944 PDFAnnotationLink *linkAnnotation = (PDFAnnotationLink *)annotation; 945 NSURL *url = [linkAnnotation URL]; 946 if (!url) 947 continue; 948 949 CGRect urlRect = NSRectToCGRect([linkAnnotation bounds]); 950 CGRect transformedRect = CGRectApplyAffineTransform(urlRect, transform); 951 CGPDFContextSetURLForRect(context, (CFURLRef)url, transformedRect); 952 } 953 954 CGContextRestoreGState(context); 955} 956 957void WebPage::drawPDFDocument(CGContextRef context, PDFDocument *pdfDocument, const PrintInfo& printInfo, const WebCore::IntRect& rect) 958{ 959 NSUInteger pageCount = [pdfDocument pageCount]; 960 IntSize paperSize(ceilf(printInfo.availablePaperWidth), ceilf(printInfo.availablePaperHeight)); 961 IntRect pageRect(IntPoint(), paperSize); 962 for (NSUInteger i = 0; i < pageCount; ++i) { 963 if (pageRect.intersects(rect)) { 964 CGContextSaveGState(context); 965 966 CGContextTranslateCTM(context, pageRect.x() - rect.x(), pageRect.y() - rect.y()); 967 drawPDFPage(pdfDocument, i, context, printInfo.pageSetupScaleFactor, paperSize); 968 969 CGContextRestoreGState(context); 970 } 971 pageRect.move(0, pageRect.height()); 972 } 973} 974 975void WebPage::drawPagesToPDFFromPDFDocument(CGContextRef context, PDFDocument *pdfDocument, const PrintInfo& printInfo, uint32_t first, uint32_t count) 976{ 977 NSUInteger pageCount = [pdfDocument pageCount]; 978 for (uint32_t page = first; page < first + count; ++page) { 979 if (page >= pageCount) 980 break; 981 982 RetainPtr<CFDictionaryRef> pageInfo = adoptCF(CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); 983 984 CGPDFContextBeginPage(context, pageInfo.get()); 985 drawPDFPage(pdfDocument, page, context, printInfo.pageSetupScaleFactor, CGSizeMake(printInfo.availablePaperWidth, printInfo.availablePaperHeight)); 986 CGPDFContextEndPage(context); 987 } 988} 989 990void WebPage::didUpdateInWindowStateTimerFired() 991{ 992 [CATransaction flush]; 993 send(Messages::WebPageProxy::DidUpdateInWindowState()); 994} 995 996} // namespace WebKit 997