2 * Copyright (C) 2010 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
29 #import "AccessibilityWebPageObject.h"
30 #import "DataReference.h"
31 #import "PluginView.h"
32 #import "WebCoreArgumentCoders.h"
35 #import "WebPageProxyMessages.h"
36 #import "WebProcess.h"
37 #import <WebCore/AXObjectCache.h>
38 #import <WebCore/FocusController.h>
39 #import <WebCore/Frame.h>
40 #import <WebCore/FrameView.h>
41 #import <WebCore/HitTestResult.h>
42 #import <WebCore/KeyboardEvent.h>
43 #import <WebCore/Page.h>
44 #import <WebCore/PlatformKeyboardEvent.h>
45 #import <WebCore/ResourceHandle.h>
46 #import <WebCore/ScrollView.h>
47 #import <WebCore/TextIterator.h>
48 #import <WebCore/WindowsKeyboardCodes.h>
49 #import <WebKitSystemInterface.h>
51 using namespace WebCore;
55 void WebPage::platformInitialize()
57 m_page->addSchedulePair(SchedulePair::create([NSRunLoop currentRunLoop], kCFRunLoopCommonModes));
59 #if !defined(BUILDING_ON_SNOW_LEOPARD)
60 AccessibilityWebPageObject* mockAccessibilityElement = [[[AccessibilityWebPageObject alloc] init] autorelease];
62 // Get the pid for the starting process.
63 pid_t pid = WebProcess::shared().presenterApplicationPid();
64 WKAXInitializeElementWithPresenterPid(mockAccessibilityElement, pid);
65 [mockAccessibilityElement setWebPage:this];
67 // send data back over
68 NSData* remoteToken = (NSData *)WKAXRemoteTokenForElement(mockAccessibilityElement);
69 CoreIPC::DataReference dataToken = CoreIPC::DataReference(reinterpret_cast<const uint8_t*>([remoteToken bytes]), [remoteToken length]);
70 send(Messages::WebPageProxy::RegisterWebProcessAccessibilityToken(dataToken));
71 m_mockAccessibilityElement = mockAccessibilityElement;
75 void WebPage::platformPreferencesDidChange(const WebPreferencesStore&)
79 // FIXME: need to add support for input methods
81 bool WebPage::interceptEditingKeyboardEvent(KeyboardEvent* evt, bool shouldSaveCommand)
83 Node* node = evt->target()->toNode();
85 Frame* frame = node->document()->frame();
88 const PlatformKeyboardEvent* keyEvent = evt->keyEvent();
91 const Vector<KeypressCommand>& commands = evt->keypressCommands();
92 bool hasKeypressCommand = !commands.isEmpty();
94 bool eventWasHandled = false;
96 if (shouldSaveCommand || !hasKeypressCommand) {
97 Vector<KeypressCommand> commandsList;
98 Vector<CompositionUnderline> underlines;
101 if (!WebProcess::shared().connection()->sendSync(Messages::WebPageProxy::InterpretKeyEvent(keyEvent->type()),
102 Messages::WebPageProxy::InterpretKeyEvent::Reply(commandsList, start, end, underlines),
103 m_pageID, CoreIPC::Connection::NoTimeout))
105 if (commandsList.isEmpty())
106 return eventWasHandled;
108 if (commandsList[0].commandName == "setMarkedText") {
109 frame->editor()->setComposition(commandsList[0].text, underlines, start, end);
110 eventWasHandled = true;
111 } else if (commandsList[0].commandName == "insertText" && frame->editor()->hasComposition()) {
112 frame->editor()->confirmComposition(commandsList[0].text);
113 eventWasHandled = true;
114 } else if (commandsList[0].commandName == "unmarkText") {
115 frame->editor()->confirmComposition();
116 eventWasHandled = true;
118 for (size_t i = 0; i < commandsList.size(); i++)
119 evt->keypressCommands().append(commandsList[i]);
122 size_t size = commands.size();
123 // Are there commands that would just cause text insertion if executed via Editor?
124 // WebKit doesn't have enough information about mode to decide how they should be treated, so we leave it upon WebCore
125 // to either handle them immediately (e.g. Tab that changes focus) or let a keypress event be generated
126 // (e.g. Tab that inserts a Tab character, or Enter).
127 bool haveTextInsertionCommands = false;
128 for (size_t i = 0; i < size; ++i) {
129 if (frame->editor()->command(commands[i].commandName).isTextInsertion())
130 haveTextInsertionCommands = true;
132 if (!haveTextInsertionCommands || keyEvent->type() == PlatformKeyboardEvent::Char) {
133 for (size_t i = 0; i < size; ++i) {
134 if (commands[i].commandName == "insertText") {
135 // Don't insert null or control characters as they can result in unexpected behaviour
136 if (evt->charCode() < ' ')
138 eventWasHandled = frame->editor()->insertText(commands[i].text, evt);
140 if (frame->editor()->command(commands[i].commandName).isSupported())
141 eventWasHandled = frame->editor()->command(commands[i].commandName).execute(evt);
145 return eventWasHandled;
148 void WebPage::sendComplexTextInputToPlugin(uint64_t pluginComplexTextInputIdentifier, const String& textInput)
150 for (HashSet<PluginView*>::const_iterator it = m_pluginViews.begin(), end = m_pluginViews.end(); it != end; ++it) {
151 if ((*it)->sendComplexTextInput(pluginComplexTextInputIdentifier, textInput))
156 void WebPage::getMarkedRange(uint64_t& location, uint64_t& length)
158 location = NSNotFound;
160 Frame* frame = m_page->focusController()->focusedOrMainFrame();
164 getLocationAndLengthFromRange(frame->editor()->compositionRange().get(), location, length);
167 static PassRefPtr<Range> characterRangeAtPoint(Frame* frame, const IntPoint& point)
169 VisiblePosition position = frame->visiblePositionForPoint(point);
170 if (position.isNull())
173 VisiblePosition previous = position.previous();
174 if (previous.isNotNull()) {
175 RefPtr<Range> previousCharacterRange = makeRange(previous, position);
176 IntRect rect = frame->editor()->firstRectForRange(previousCharacterRange.get());
177 if (rect.contains(point))
178 return previousCharacterRange.release();
181 VisiblePosition next = position.next();
182 if (next.isNotNull()) {
183 RefPtr<Range> nextCharacterRange = makeRange(position, next);
184 IntRect rect = frame->editor()->firstRectForRange(nextCharacterRange.get());
185 if (rect.contains(point))
186 return nextCharacterRange.release();
192 void WebPage::characterIndexForPoint(IntPoint point, uint64_t& index)
195 Frame* frame = m_page->mainFrame();
199 HitTestResult result = frame->eventHandler()->hitTestResultAtPoint(point, false);
200 frame = result.innerNonSharedNode() ? result.innerNonSharedNode()->document()->frame() : m_page->focusController()->focusedOrMainFrame();
202 RefPtr<Range> range = characterRangeAtPoint(frame, result.point());
207 getLocationAndLengthFromRange(range.get(), index, length);
210 static PassRefPtr<Range> convertToRange(Frame* frame, NSRange nsrange)
212 if (nsrange.location > INT_MAX)
214 if (nsrange.length > INT_MAX || nsrange.location + nsrange.length > INT_MAX)
215 nsrange.length = INT_MAX - nsrange.location;
217 // our critical assumption is that we are only called by input methods that
218 // concentrate on a given area containing the selection
219 // We have to do this because of text fields and textareas. The DOM for those is not
220 // directly in the document DOM, so serialization is problematic. Our solution is
221 // to use the root editable element of the selection start as the positional base.
222 // That fits with AppKit's idea of an input context.
223 Element* selectionRoot = frame->selection()->rootEditableElement();
224 Element* scope = selectionRoot ? selectionRoot : frame->document()->documentElement();
225 return TextIterator::rangeFromLocationAndLength(scope, nsrange.location, nsrange.length);
228 void WebPage::firstRectForCharacterRange(uint64_t location, uint64_t length, WebCore::IntRect& resultRect)
230 Frame* frame = m_page->focusController()->focusedOrMainFrame();
231 resultRect.setLocation(IntPoint(0, 0));
232 resultRect.setSize(IntSize(0, 0));
234 RefPtr<Range> range = convertToRange(frame, NSMakeRange(location, length));
238 ASSERT(range->startContainer());
239 ASSERT(range->endContainer());
241 IntRect rect = frame->editor()->firstRectForRange(range.get());
242 resultRect = frame->view()->contentsToWindow(rect);
245 void WebPage::performDictionaryLookupAtLocation(const FloatPoint& floatPoint)
247 Frame* frame = m_page->mainFrame();
251 // Find the frame the point is over.
252 IntPoint point = roundedIntPoint(floatPoint);
254 HitTestResult result = frame->eventHandler()->hitTestResultAtPoint(point, false);
255 frame = result.innerNonSharedNode() ? result.innerNonSharedNode()->document()->frame() : m_page->focusController()->focusedOrMainFrame();
257 // Figure out if there are any characters under the point.
258 RefPtr<Range> characterRange = characterRangeAtPoint(frame, frame->view()->windowToContents(point));
262 // Grab the currently selected text.
263 RefPtr<Range> selectedRange = m_page->focusController()->focusedOrMainFrame()->selection()->selection().toNormalizedRange();
265 // Use the selected text if the point was anywhere in it. Assertain this by seeing if either character range
266 // the mouse is over is contained by the selection range.
267 if (characterRange && selectedRange) {
268 ExceptionCode ec = 0;
269 selectedRange->compareBoundaryPoints(Range::START_TO_START, characterRange.get(), ec);
271 if (selectedRange->isPointInRange(characterRange->startContainer(), characterRange->startOffset(), ec)) {
273 characterRange = selectedRange;
281 // Ensure we have whole words.
282 VisibleSelection selection(characterRange.get());
283 selection.expandUsingGranularity(WordGranularity);
285 RefPtr<Range> finalRange = selection.toNormalizedRange();
289 performDictionaryLookupForRange(DictionaryPopupInfo::HotKey, frame, finalRange.get());
292 void WebPage::performDictionaryLookupForRange(DictionaryPopupInfo::Type type, Frame* frame, Range* range)
294 String rangeText = range->text();
295 if (rangeText.stripWhiteSpace().isEmpty())
298 RenderObject* renderer = range->startContainer()->renderer();
299 RenderStyle* style = renderer->style();
300 NSFont *font = style->font().primaryFont()->getNSFont();
304 CFDictionaryRef fontDescriptorAttributes = (CFDictionaryRef)[[font fontDescriptor] fontAttributes];
305 if (!fontDescriptorAttributes)
308 Vector<FloatQuad> quads;
309 range->textQuads(quads);
313 IntRect rangeRect = frame->view()->contentsToWindow(quads[0].enclosingBoundingBox());
315 DictionaryPopupInfo dictionaryPopupInfo;
316 dictionaryPopupInfo.type = type;
317 dictionaryPopupInfo.origin = FloatPoint(rangeRect.x(), rangeRect.y());
318 dictionaryPopupInfo.fontInfo.fontAttributeDictionary = fontDescriptorAttributes;
320 send(Messages::WebPageProxy::DidPerformDictionaryLookup(rangeText, dictionaryPopupInfo));
323 static inline void scroll(Page* page, ScrollDirection direction, ScrollGranularity granularity)
325 page->focusController()->focusedOrMainFrame()->eventHandler()->scrollRecursively(direction, granularity);
328 static inline void logicalScroll(Page* page, ScrollLogicalDirection direction, ScrollGranularity granularity)
330 page->focusController()->focusedOrMainFrame()->eventHandler()->logicalScrollRecursively(direction, granularity);
333 bool WebPage::performDefaultBehaviorForKeyEvent(const WebKeyboardEvent& keyboardEvent)
335 if (keyboardEvent.type() != WebEvent::KeyDown)
338 // FIXME: This should be in WebCore.
340 switch (keyboardEvent.windowsVirtualKeyCode()) {
342 if (keyboardEvent.shiftKey())
348 if (keyboardEvent.shiftKey())
349 logicalScroll(m_page.get(), ScrollBlockDirectionBackward, ScrollByPage);
351 logicalScroll(m_page.get(), ScrollBlockDirectionForward, ScrollByPage);
354 logicalScroll(m_page.get(), ScrollBlockDirectionBackward, ScrollByPage);
357 logicalScroll(m_page.get(), ScrollBlockDirectionForward, ScrollByPage);
360 logicalScroll(m_page.get(), ScrollBlockDirectionBackward, ScrollByDocument);
363 logicalScroll(m_page.get(), ScrollBlockDirectionForward, ScrollByDocument);
366 if (keyboardEvent.shiftKey())
368 if (keyboardEvent.metaKey()) {
369 scroll(m_page.get(), ScrollUp, ScrollByDocument);
370 scroll(m_page.get(), ScrollLeft, ScrollByDocument);
371 } else if (keyboardEvent.altKey() || keyboardEvent.controlKey())
372 scroll(m_page.get(), ScrollUp, ScrollByPage);
374 scroll(m_page.get(), ScrollUp, ScrollByLine);
377 if (keyboardEvent.shiftKey())
379 if (keyboardEvent.metaKey()) {
380 scroll(m_page.get(), ScrollDown, ScrollByDocument);
381 scroll(m_page.get(), ScrollLeft, ScrollByDocument);
382 } else if (keyboardEvent.altKey() || keyboardEvent.controlKey())
383 scroll(m_page.get(), ScrollDown, ScrollByPage);
385 scroll(m_page.get(), ScrollDown, ScrollByLine);
388 if (keyboardEvent.shiftKey())
390 if (keyboardEvent.metaKey())
393 if (keyboardEvent.altKey() | keyboardEvent.controlKey())
394 scroll(m_page.get(), ScrollLeft, ScrollByPage);
396 scroll(m_page.get(), ScrollLeft, ScrollByLine);
400 if (keyboardEvent.shiftKey())
402 if (keyboardEvent.metaKey())
405 if (keyboardEvent.altKey() || keyboardEvent.controlKey())
406 scroll(m_page.get(), ScrollRight, ScrollByPage);
408 scroll(m_page.get(), ScrollRight, ScrollByLine);
418 void WebPage::registerUIProcessAccessibilityTokens(const CoreIPC::DataReference& elementToken, const CoreIPC::DataReference& windowToken)
420 #if !defined(BUILDING_ON_SNOW_LEOPARD)
421 NSData* elementTokenData = [NSData dataWithBytes:elementToken.data() length:elementToken.size()];
422 NSData* windowTokenData = [NSData dataWithBytes:windowToken.data() length:windowToken.size()];
423 id remoteElement = WKAXRemoteElementForToken(elementTokenData);
424 id remoteWindow = WKAXRemoteElementForToken(windowTokenData);
425 WKAXSetWindowForRemoteElement(remoteWindow, remoteElement);
427 [accessibilityRemoteObject() setRemoteParent:remoteElement];
431 void WebPage::writeSelectionToPasteboard(const String& pasteboardName, const Vector<String>& pasteboardTypes, bool& result)
433 Frame* frame = m_page->focusController()->focusedOrMainFrame();
434 frame->editor()->writeSelectionToPasteboard(pasteboardName, pasteboardTypes);
438 AccessibilityWebPageObject* WebPage::accessibilityRemoteObject()
440 return m_mockAccessibilityElement.get();
443 bool WebPage::platformHasLocalDataForURL(const WebCore::KURL& url)
445 NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:url];
446 [request setValue:(NSString*)userAgent() forHTTPHeaderField:@"User-Agent"];
447 NSCachedURLResponse *cachedResponse;
448 #if USE(CFURLSTORAGESESSIONS)
449 if (CFURLStorageSessionRef storageSession = ResourceHandle::privateBrowsingStorageSession())
450 cachedResponse = WKCachedResponseForRequest(storageSession, request);
453 cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
456 return cachedResponse;
459 String WebPage::cachedResponseMIMETypeForURL(const WebCore::KURL& url)
461 NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:url];
462 [request setValue:(NSString*)userAgent() forHTTPHeaderField:@"User-Agent"];
463 NSCachedURLResponse *cachedResponse;
464 #if USE(CFURLSTORAGESESSIONS)
465 if (CFURLStorageSessionRef storageSession = ResourceHandle::privateBrowsingStorageSession())
466 cachedResponse = WKCachedResponseForRequest(storageSession, request);
469 cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
472 return [[cachedResponse response] MIMEType];
475 bool WebPage::canHandleRequest(const WebCore::ResourceRequest& request)
477 if ([NSURLConnection canHandleRequest:request.nsURLRequest()])
480 // FIXME: Return true if this scheme is any one WebKit2 knows how to handle.
481 return request.url().protocolIs("applewebdata");
484 } // namespace WebKit