2011-04-12 Alejandro G. Castro <alex@igalia.com>
[WebKit-https.git] / Source / WebKit2 / UIProcess / gtk / WebView.cpp
1 /*
2  * Copyright (C) 2010 Apple Inc. All rights reserved.
3  * Portions Copyright (c) 2010 Motorola Mobility, Inc.  All rights reserved.
4  * Copyright (C) 2011 Igalia S.L.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
16  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
19  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
25  * THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #include "config.h"
29 #include "WebView.h"
30
31 #include "ChunkedUpdateDrawingAreaProxy.h"
32 #include "NativeWebKeyboardEvent.h"
33 #include "NotImplemented.h"
34 #include "WebContext.h"
35 #include "WebContextMenuProxy.h"
36 #include "WebEventFactory.h"
37 #include "WebViewWidget.h"
38 #include "WebPageProxy.h"
39 #include <wtf/text/WTFString.h>
40
41 typedef HashMap<int, const char*> IntConstCharHashMap;
42
43 using namespace WebCore;
44
45 namespace WebKit {
46
47 void WebView::handleFocusInEvent(GtkWidget* widget)
48 {
49     if (!(m_isPageActive)) {
50         m_isPageActive = true;
51         m_page->viewStateDidChange(WebPageProxy::ViewWindowIsActive);
52     }
53
54     m_page->viewStateDidChange(WebPageProxy::ViewIsFocused);
55 }
56
57 void WebView::handleFocusOutEvent(GtkWidget* widget)
58 {
59     m_isPageActive = false;
60     m_page->viewStateDidChange(WebPageProxy::ViewWindowIsActive);
61 }
62
63
64 static void backspaceCallback(GtkWidget* widget, WebView* client)
65 {
66     g_signal_stop_emission_by_name(widget, "backspace");
67     client->addPendingEditorCommand("DeleteBackward");
68 }
69
70 static void selectAllCallback(GtkWidget* widget, gboolean select, WebView* client)
71 {
72     g_signal_stop_emission_by_name(widget, "select-all");
73     client->addPendingEditorCommand(select ? "SelectAll" : "Unselect");
74 }
75
76 static void cutClipboardCallback(GtkWidget* widget, WebView* client)
77 {
78     g_signal_stop_emission_by_name(widget, "cut-clipboard");
79     client->addPendingEditorCommand("Cut");
80 }
81
82 static void copyClipboardCallback(GtkWidget* widget, WebView* client)
83 {
84     g_signal_stop_emission_by_name(widget, "copy-clipboard");
85     client->addPendingEditorCommand("Copy");
86 }
87
88 static void pasteClipboardCallback(GtkWidget* widget, WebView* client)
89 {
90     g_signal_stop_emission_by_name(widget, "paste-clipboard");
91     client->addPendingEditorCommand("Paste");
92 }
93
94 static void toggleOverwriteCallback(GtkWidget* widget, EditorClient*)
95 {
96     // We don't support toggling the overwrite mode, but the default callback expects
97     // the GtkTextView to have a layout, so we handle this signal just to stop it.
98     g_signal_stop_emission_by_name(widget, "toggle-overwrite");
99 }
100
101 // GTK+ will still send these signals to the web view. So we can safely stop signal
102 // emission without breaking accessibility.
103 static void popupMenuCallback(GtkWidget* widget, EditorClient*)
104 {
105     g_signal_stop_emission_by_name(widget, "popup-menu");
106 }
107
108 static void showHelpCallback(GtkWidget* widget, EditorClient*)
109 {
110     g_signal_stop_emission_by_name(widget, "show-help");
111 }
112
113 static const char* const gtkDeleteCommands[][2] = {
114     { "DeleteBackward",               "DeleteForward"                        }, // Characters
115     { "DeleteWordBackward",           "DeleteWordForward"                    }, // Word ends
116     { "DeleteWordBackward",           "DeleteWordForward"                    }, // Words
117     { "DeleteToBeginningOfLine",      "DeleteToEndOfLine"                    }, // Lines
118     { "DeleteToBeginningOfLine",      "DeleteToEndOfLine"                    }, // Line ends
119     { "DeleteToBeginningOfParagraph", "DeleteToEndOfParagraph"               }, // Paragraph ends
120     { "DeleteToBeginningOfParagraph", "DeleteToEndOfParagraph"               }, // Paragraphs
121     { 0,                              0                                      } // Whitespace (M-\ in Emacs)
122 };
123
124 static void deleteFromCursorCallback(GtkWidget* widget, GtkDeleteType deleteType, gint count, WebView* client)
125 {
126     g_signal_stop_emission_by_name(widget, "delete-from-cursor");
127     int direction = count > 0 ? 1 : 0;
128
129     // Ensuring that deleteType <= G_N_ELEMENTS here results in a compiler warning
130     // that the condition is always true.
131
132     if (deleteType == GTK_DELETE_WORDS) {
133         if (!direction) {
134             client->addPendingEditorCommand("MoveWordForward");
135             client->addPendingEditorCommand("MoveWordBackward");
136         } else {
137             client->addPendingEditorCommand("MoveWordBackward");
138             client->addPendingEditorCommand("MoveWordForward");
139         }
140     } else if (deleteType == GTK_DELETE_DISPLAY_LINES) {
141         if (!direction)
142             client->addPendingEditorCommand("MoveToBeginningOfLine");
143         else
144             client->addPendingEditorCommand("MoveToEndOfLine");
145     } else if (deleteType == GTK_DELETE_PARAGRAPHS) {
146         if (!direction)
147             client->addPendingEditorCommand("MoveToBeginningOfParagraph");
148         else
149             client->addPendingEditorCommand("MoveToEndOfParagraph");
150     }
151
152     const char* rawCommand = gtkDeleteCommands[deleteType][direction];
153     if (!rawCommand)
154       return;
155
156     for (int i = 0; i < abs(count); i++)
157         client->addPendingEditorCommand(rawCommand);
158 }
159
160 static const char* const gtkMoveCommands[][4] = {
161     { "MoveBackward",                                   "MoveForward",
162       "MoveBackwardAndModifySelection",                 "MoveForwardAndModifySelection"             }, // Forward/backward grapheme
163     { "MoveLeft",                                       "MoveRight",
164       "MoveBackwardAndModifySelection",                 "MoveForwardAndModifySelection"             }, // Left/right grapheme
165     { "MoveWordBackward",                               "MoveWordForward",
166       "MoveWordBackwardAndModifySelection",             "MoveWordForwardAndModifySelection"         }, // Forward/backward word
167     { "MoveUp",                                         "MoveDown",
168       "MoveUpAndModifySelection",                       "MoveDownAndModifySelection"                }, // Up/down line
169     { "MoveToBeginningOfLine",                          "MoveToEndOfLine",
170       "MoveToBeginningOfLineAndModifySelection",        "MoveToEndOfLineAndModifySelection"         }, // Up/down line ends
171     { "MoveParagraphForward",                           "MoveParagraphBackward",
172       "MoveParagraphForwardAndModifySelection",         "MoveParagraphBackwardAndModifySelection"   }, // Up/down paragraphs
173     { "MoveToBeginningOfParagraph",                     "MoveToEndOfParagraph",
174       "MoveToBeginningOfParagraphAndModifySelection",   "MoveToEndOfParagraphAndModifySelection"    }, // Up/down paragraph ends.
175     { "MovePageUp",                                     "MovePageDown",
176       "MovePageUpAndModifySelection",                   "MovePageDownAndModifySelection"            }, // Up/down page
177     { "MoveToBeginningOfDocument",                      "MoveToEndOfDocument",
178       "MoveToBeginningOfDocumentAndModifySelection",    "MoveToEndOfDocumentAndModifySelection"     }, // Begin/end of buffer
179     { 0,                                                0,
180       0,                                                0                                           } // Horizontal page movement
181 };
182
183 static void moveCursorCallback(GtkWidget* widget, GtkMovementStep step, gint count, gboolean extendSelection, WebView* client)
184 {
185     g_signal_stop_emission_by_name(widget, "move-cursor");
186     int direction = count > 0 ? 1 : 0;
187     if (extendSelection)
188         direction += 2;
189
190     if (static_cast<unsigned>(step) >= G_N_ELEMENTS(gtkMoveCommands))
191         return;
192
193     const char* rawCommand = gtkMoveCommands[step][direction];
194     if (!rawCommand)
195         return;
196
197     for (int i = 0; i < abs(count); i++)
198         client->addPendingEditorCommand(rawCommand);
199 }
200
201 static const unsigned CtrlKey = 1 << 0;
202 static const unsigned AltKey = 1 << 1;
203 static const unsigned ShiftKey = 1 << 2;
204
205 struct KeyDownEntry {
206     unsigned virtualKey;
207     unsigned modifiers;
208     const char* name;
209 };
210
211 struct KeyPressEntry {
212     unsigned charCode;
213     unsigned modifiers;
214     const char* name;
215 };
216
217 static const KeyDownEntry keyDownEntries[] = {
218     { 'B',       CtrlKey,            "ToggleBold"                                  },
219     { 'I',       CtrlKey,            "ToggleItalic"                                },
220     { VK_ESCAPE, 0,                  "Cancel"                                      },
221     { VK_OEM_PERIOD, CtrlKey,        "Cancel"                                      },
222     { VK_TAB,    0,                  "InsertTab"                                   },
223     { VK_TAB,    ShiftKey,           "InsertBacktab"                               },
224     { VK_RETURN, 0,                  "InsertNewline"                               },
225     { VK_RETURN, CtrlKey,            "InsertNewline"                               },
226     { VK_RETURN, AltKey,             "InsertNewline"                               },
227     { VK_RETURN, AltKey | ShiftKey,  "InsertNewline"                               },
228 };
229
230 static const KeyPressEntry keyPressEntries[] = {
231     { '\t',   0,                  "InsertTab"                                   },
232     { '\t',   ShiftKey,           "InsertBacktab"                               },
233     { '\r',   0,                  "InsertNewline"                               },
234     { '\r',   CtrlKey,            "InsertNewline"                               },
235     { '\r',   AltKey,             "InsertNewline"                               },
236     { '\r',   AltKey | ShiftKey,  "InsertNewline"                               },
237 };
238
239 WebView::WebView(WebContext* context, WebPageGroup* pageGroup)
240     : m_isPageActive(true)
241     , m_nativeWidget(gtk_text_view_new())
242 {
243     m_page = context->createWebPage(this, pageGroup);
244
245     m_viewWidget = static_cast<GtkWidget*>(g_object_new(WEB_VIEW_TYPE_WIDGET, NULL));
246     ASSERT(m_viewWidget);
247
248     m_page->initializeWebPage();
249
250     WebViewWidget* webViewWidget = WEB_VIEW_WIDGET(m_viewWidget);
251     webViewWidgetSetWebViewInstance(webViewWidget, this);
252
253     g_signal_connect(m_nativeWidget.get(), "backspace", G_CALLBACK(backspaceCallback), this);
254     g_signal_connect(m_nativeWidget.get(), "cut-clipboard", G_CALLBACK(cutClipboardCallback), this);
255     g_signal_connect(m_nativeWidget.get(), "copy-clipboard", G_CALLBACK(copyClipboardCallback), this);
256     g_signal_connect(m_nativeWidget.get(), "paste-clipboard", G_CALLBACK(pasteClipboardCallback), this);
257     g_signal_connect(m_nativeWidget.get(), "select-all", G_CALLBACK(selectAllCallback), this);
258     g_signal_connect(m_nativeWidget.get(), "move-cursor", G_CALLBACK(moveCursorCallback), this);
259     g_signal_connect(m_nativeWidget.get(), "delete-from-cursor", G_CALLBACK(deleteFromCursorCallback), this);
260     g_signal_connect(m_nativeWidget.get(), "toggle-overwrite", G_CALLBACK(toggleOverwriteCallback), this);
261     g_signal_connect(m_nativeWidget.get(), "popup-menu", G_CALLBACK(popupMenuCallback), this);
262     g_signal_connect(m_nativeWidget.get(), "show-help", G_CALLBACK(showHelpCallback), this);
263 }
264
265 WebView::~WebView()
266 {
267 }
268
269 GdkWindow* WebView::getWebViewWindow()
270 {
271     return gtk_widget_get_window(m_viewWidget);
272 }
273
274 void WebView::paint(GtkWidget* widget, GdkRectangle rect, cairo_t* cr)
275 {
276     m_page->drawingArea()->paint(IntRect(rect), cr);
277 }
278
279 void WebView::setSize(GtkWidget*, IntSize windowSize)
280 {
281     m_page->drawingArea()->setSize(windowSize, IntSize());
282 }
283
284 void WebView::handleKeyboardEvent(GdkEventKey* event)
285 {
286     m_page->handleKeyboardEvent(NativeWebKeyboardEvent(reinterpret_cast<GdkEvent*>(event)));
287 }
288
289 void WebView::handleMouseEvent(GdkEvent* event, int currentClickCount)
290 {
291     m_page->handleMouseEvent(WebEventFactory::createWebMouseEvent(event, currentClickCount));
292 }
293
294 void WebView::handleWheelEvent(GdkEventScroll* event)
295 {
296     m_page->handleWheelEvent(WebEventFactory::createWebWheelEvent(event));
297 }
298
299 void WebView::getEditorCommandsForKeyEvent(const NativeWebKeyboardEvent& event, Vector<WTF::String>& commandList)
300 {
301     m_pendingEditorCommands.clear();
302
303 #ifdef GTK_API_VERSION_2
304     gtk_bindings_activate_event(GTK_OBJECT(m_nativeWidget.get()), const_cast<GdkEventKey*>(&event.nativeEvent()->key));
305 #else
306     gtk_bindings_activate_event(G_OBJECT(m_nativeWidget.get()), const_cast<GdkEventKey*>(&event.nativeEvent()->key));
307 #endif
308
309     if (m_pendingEditorCommands.isEmpty()) {
310         commandList.append(m_pendingEditorCommands);
311         return;
312     }
313
314     DEFINE_STATIC_LOCAL(IntConstCharHashMap, keyDownCommandsMap, ());
315     DEFINE_STATIC_LOCAL(IntConstCharHashMap, keyPressCommandsMap, ());
316
317     if (keyDownCommandsMap.isEmpty()) {
318         for (unsigned i = 0; i < G_N_ELEMENTS(keyDownEntries); i++)
319             keyDownCommandsMap.set(keyDownEntries[i].modifiers << 16 | keyDownEntries[i].virtualKey, keyDownEntries[i].name);
320
321         for (unsigned i = 0; i < G_N_ELEMENTS(keyPressEntries); i++)
322             keyPressCommandsMap.set(keyPressEntries[i].modifiers << 16 | keyPressEntries[i].charCode, keyPressEntries[i].name);
323     }
324
325     unsigned modifiers = 0;
326     if (event.shiftKey())
327         modifiers |= ShiftKey;
328     if (event.altKey())
329         modifiers |= AltKey;
330     if (event.controlKey())
331         modifiers |= CtrlKey;
332
333     // For keypress events, we want charCode(), but keyCode() does that.
334     int mapKey = modifiers << 16 | event.nativeVirtualKeyCode();
335     if (mapKey) {
336         HashMap<int, const char*>* commandMap = event.type() == WebEvent::KeyDown ?
337             &keyDownCommandsMap : &keyPressCommandsMap;
338         if (const char* commandString = commandMap->get(mapKey))
339             m_pendingEditorCommands.append(commandString);
340     }
341
342     commandList.append(m_pendingEditorCommands);
343 }
344
345 bool WebView::isActive()
346 {
347     return m_isPageActive;
348 }
349
350 void WebView::close()
351 {
352     m_page->close();
353 }
354
355 // PageClient's pure virtual functions
356 PassOwnPtr<DrawingAreaProxy> WebView::createDrawingAreaProxy()
357 {
358     return ChunkedUpdateDrawingAreaProxy::create(this, m_page.get());
359 }
360
361 void WebView::setViewNeedsDisplay(const WebCore::IntRect&)
362 {
363     notImplemented();
364 }
365
366 void WebView::displayView()
367 {
368     notImplemented();
369 }
370
371 void WebView::scrollView(const WebCore::IntRect& scrollRect, const WebCore::IntSize& scrollOffset)
372 {
373     notImplemented();
374 }
375
376 WebCore::IntSize WebView::viewSize()
377 {
378     GtkAllocation allocation;
379     gtk_widget_get_allocation(m_viewWidget, &allocation);
380     return IntSize(allocation.width, allocation.height);
381 }
382
383 bool WebView::isViewWindowActive()
384 {
385     notImplemented();
386     return true;
387 }
388
389 bool WebView::isViewFocused()
390 {
391     notImplemented();
392     return true;
393 }
394
395 bool WebView::isViewVisible()
396 {
397     notImplemented();
398     return true;
399 }
400
401 bool WebView::isViewInWindow()
402 {
403     notImplemented();
404     return true;
405 }
406
407 void WebView::WebView::processDidCrash()
408 {
409     notImplemented();
410 }
411
412 void WebView::didRelaunchProcess()
413 {
414     notImplemented();
415 }
416
417 void WebView::takeFocus(bool)
418 {
419     notImplemented();
420 }
421
422 void WebView::toolTipChanged(const String&, const String&)
423 {
424     notImplemented();
425 }
426
427 void WebView::setCursor(const Cursor& cursor)
428 {
429     // [GTK] Widget::setCursor() gets called frequently
430     // http://bugs.webkit.org/show_bug.cgi?id=16388
431     // Setting the cursor may be an expensive operation in some backends,
432     // so don't re-set the cursor if it's already set to the target value.
433     GdkWindow* window = gtk_widget_get_window(m_viewWidget);
434     GdkCursor* currentCursor = gdk_window_get_cursor(window);
435     GdkCursor* newCursor = cursor.platformCursor().get();
436     if (currentCursor != newCursor)
437         gdk_window_set_cursor(window, newCursor);
438 }
439
440 void WebView::setViewportArguments(const WebCore::ViewportArguments&)
441 {
442     notImplemented();
443 }
444
445 void WebView::registerEditCommand(PassRefPtr<WebEditCommandProxy>, WebPageProxy::UndoOrRedo)
446 {
447     notImplemented();
448 }
449
450 void WebView::clearAllEditCommands()
451 {
452     notImplemented();
453 }
454
455 bool WebView::canUndoRedo(WebPageProxy::UndoOrRedo)
456 {
457     notImplemented();
458     return false;
459 }
460
461 void WebView::executeUndoRedo(WebPageProxy::UndoOrRedo)
462 {
463     notImplemented();
464 }
465
466 FloatRect WebView::convertToDeviceSpace(const FloatRect& viewRect)
467 {
468     notImplemented();
469     return viewRect;
470 }
471
472 FloatRect WebView::convertToUserSpace(const FloatRect& viewRect)
473 {
474     notImplemented();
475     return viewRect;
476 }
477
478 IntRect WebView::windowToScreen(const IntRect& rect)
479 {
480     notImplemented();
481     return IntRect();
482 }
483
484 void WebView::doneWithKeyEvent(const NativeWebKeyboardEvent&, bool wasEventHandled)
485 {
486     notImplemented();
487 }
488
489 void WebView::didNotHandleKeyEvent(const NativeWebKeyboardEvent& event)
490 {
491     notImplemented();
492 }
493
494 PassRefPtr<WebPopupMenuProxy> WebView::createPopupMenuProxy(WebPageProxy*)
495 {
496     notImplemented();
497     return 0;
498 }
499
500 PassRefPtr<WebContextMenuProxy> WebView::createContextMenuProxy(WebPageProxy*)
501 {
502     notImplemented();
503     return 0;
504 }
505
506 void WebView::setFindIndicator(PassRefPtr<FindIndicator>, bool fadeOut)
507 {
508     notImplemented();
509 }
510
511 #if USE(ACCELERATED_COMPOSITING)
512 void WebView::pageDidEnterAcceleratedCompositing()
513 {
514     notImplemented();
515 }
516
517 void WebView::pageDidLeaveAcceleratedCompositing()
518 {
519     notImplemented();
520 }
521 #endif // USE(ACCELERATED_COMPOSITING)
522
523 void WebView::didCommitLoadForMainFrame(bool useCustomRepresentation)
524 {
525 }
526
527 void WebView::didFinishLoadingDataForCustomRepresentation(const String& suggestedFilename, const CoreIPC::DataReference&)
528 {
529 }
530
531 double WebView::customRepresentationZoomFactor()
532 {
533     notImplemented();
534     return 0;
535 }
536
537 void WebView::setCustomRepresentationZoomFactor(double)
538 {
539     notImplemented();
540 }
541
542 void WebView::pageClosed()
543 {
544     notImplemented();
545 }
546
547 void WebView::didChangeScrollbarsForMainFrame() const
548 {
549 }
550
551 void WebView::flashBackingStoreUpdates(const Vector<IntRect>&)
552 {
553     notImplemented();
554 }
555
556 void WebView::findStringInCustomRepresentation(const String&, FindOptions, unsigned)
557 {
558     notImplemented();
559 }
560
561 void WebView::countStringMatchesInCustomRepresentation(const String&, FindOptions, unsigned)
562 {
563     notImplemented();
564 }
565
566 } // namespace WebKit