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 "PageClientImpl.h"
30 
31 #include "ChunkedUpdateDrawingAreaProxy.h"
32 #include "NativeWebKeyboardEvent.h"
33 #include "NativeWebMouseEvent.h"
34 #include "NotImplemented.h"
35 #include "WebContext.h"
36 #include "WebContextMenuProxy.h"
37 #include "WebEventFactory.h"
38 #include "WebKitWebViewBasePrivate.h"
39 #include "WebPageProxy.h"
40 #include <wtf/text/WTFString.h>
41 
42 typedef HashMap<int, const char*> IntConstCharHashMap;
43 
44 using namespace WebCore;
45 
46 namespace WebKit {
47 
backspaceCallback(GtkWidget * widget,PageClientImpl * client)48 static void backspaceCallback(GtkWidget* widget, PageClientImpl* client)
49 {
50     g_signal_stop_emission_by_name(widget, "backspace");
51     client->addPendingEditorCommand("DeleteBackward");
52 }
53 
selectAllCallback(GtkWidget * widget,gboolean select,PageClientImpl * client)54 static void selectAllCallback(GtkWidget* widget, gboolean select, PageClientImpl* client)
55 {
56     g_signal_stop_emission_by_name(widget, "select-all");
57     client->addPendingEditorCommand(select ? "SelectAll" : "Unselect");
58 }
59 
cutClipboardCallback(GtkWidget * widget,PageClientImpl * client)60 static void cutClipboardCallback(GtkWidget* widget, PageClientImpl* client)
61 {
62     g_signal_stop_emission_by_name(widget, "cut-clipboard");
63     client->addPendingEditorCommand("Cut");
64 }
65 
copyClipboardCallback(GtkWidget * widget,PageClientImpl * client)66 static void copyClipboardCallback(GtkWidget* widget, PageClientImpl* client)
67 {
68     g_signal_stop_emission_by_name(widget, "copy-clipboard");
69     client->addPendingEditorCommand("Copy");
70 }
71 
pasteClipboardCallback(GtkWidget * widget,PageClientImpl * client)72 static void pasteClipboardCallback(GtkWidget* widget, PageClientImpl* client)
73 {
74     g_signal_stop_emission_by_name(widget, "paste-clipboard");
75     client->addPendingEditorCommand("Paste");
76 }
77 
toggleOverwriteCallback(GtkWidget * widget,EditorClient *)78 static void toggleOverwriteCallback(GtkWidget* widget, EditorClient*)
79 {
80     // We don't support toggling the overwrite mode, but the default callback expects
81     // the GtkTextView to have a layout, so we handle this signal just to stop it.
82     g_signal_stop_emission_by_name(widget, "toggle-overwrite");
83 }
84 
85 // GTK+ will still send these signals to the web view. So we can safely stop signal
86 // emission without breaking accessibility.
popupMenuCallback(GtkWidget * widget,EditorClient *)87 static void popupMenuCallback(GtkWidget* widget, EditorClient*)
88 {
89     g_signal_stop_emission_by_name(widget, "popup-menu");
90 }
91 
showHelpCallback(GtkWidget * widget,EditorClient *)92 static void showHelpCallback(GtkWidget* widget, EditorClient*)
93 {
94     g_signal_stop_emission_by_name(widget, "show-help");
95 }
96 
97 static const char* const gtkDeleteCommands[][2] = {
98     { "DeleteBackward",               "DeleteForward"                        }, // Characters
99     { "DeleteWordBackward",           "DeleteWordForward"                    }, // Word ends
100     { "DeleteWordBackward",           "DeleteWordForward"                    }, // Words
101     { "DeleteToBeginningOfLine",      "DeleteToEndOfLine"                    }, // Lines
102     { "DeleteToBeginningOfLine",      "DeleteToEndOfLine"                    }, // Line ends
103     { "DeleteToBeginningOfParagraph", "DeleteToEndOfParagraph"               }, // Paragraph ends
104     { "DeleteToBeginningOfParagraph", "DeleteToEndOfParagraph"               }, // Paragraphs
105     { 0,                              0                                      } // Whitespace (M-\ in Emacs)
106 };
107 
deleteFromCursorCallback(GtkWidget * widget,GtkDeleteType deleteType,gint count,PageClientImpl * client)108 static void deleteFromCursorCallback(GtkWidget* widget, GtkDeleteType deleteType, gint count, PageClientImpl* client)
109 {
110     g_signal_stop_emission_by_name(widget, "delete-from-cursor");
111     int direction = count > 0 ? 1 : 0;
112 
113     // Ensuring that deleteType <= G_N_ELEMENTS here results in a compiler warning
114     // that the condition is always true.
115 
116     if (deleteType == GTK_DELETE_WORDS) {
117         if (!direction) {
118             client->addPendingEditorCommand("MoveWordForward");
119             client->addPendingEditorCommand("MoveWordBackward");
120         } else {
121             client->addPendingEditorCommand("MoveWordBackward");
122             client->addPendingEditorCommand("MoveWordForward");
123         }
124     } else if (deleteType == GTK_DELETE_DISPLAY_LINES) {
125         if (!direction)
126             client->addPendingEditorCommand("MoveToBeginningOfLine");
127         else
128             client->addPendingEditorCommand("MoveToEndOfLine");
129     } else if (deleteType == GTK_DELETE_PARAGRAPHS) {
130         if (!direction)
131             client->addPendingEditorCommand("MoveToBeginningOfParagraph");
132         else
133             client->addPendingEditorCommand("MoveToEndOfParagraph");
134     }
135 
136     const char* rawCommand = gtkDeleteCommands[deleteType][direction];
137     if (!rawCommand)
138       return;
139 
140     for (int i = 0; i < abs(count); i++)
141         client->addPendingEditorCommand(rawCommand);
142 }
143 
144 static const char* const gtkMoveCommands[][4] = {
145     { "MoveBackward",                                   "MoveForward",
146       "MoveBackwardAndModifySelection",                 "MoveForwardAndModifySelection"             }, // Forward/backward grapheme
147     { "MoveLeft",                                       "MoveRight",
148       "MoveBackwardAndModifySelection",                 "MoveForwardAndModifySelection"             }, // Left/right grapheme
149     { "MoveWordBackward",                               "MoveWordForward",
150       "MoveWordBackwardAndModifySelection",             "MoveWordForwardAndModifySelection"         }, // Forward/backward word
151     { "MoveUp",                                         "MoveDown",
152       "MoveUpAndModifySelection",                       "MoveDownAndModifySelection"                }, // Up/down line
153     { "MoveToBeginningOfLine",                          "MoveToEndOfLine",
154       "MoveToBeginningOfLineAndModifySelection",        "MoveToEndOfLineAndModifySelection"         }, // Up/down line ends
155     { "MoveParagraphForward",                           "MoveParagraphBackward",
156       "MoveParagraphForwardAndModifySelection",         "MoveParagraphBackwardAndModifySelection"   }, // Up/down paragraphs
157     { "MoveToBeginningOfParagraph",                     "MoveToEndOfParagraph",
158       "MoveToBeginningOfParagraphAndModifySelection",   "MoveToEndOfParagraphAndModifySelection"    }, // Up/down paragraph ends.
159     { "MovePageUp",                                     "MovePageDown",
160       "MovePageUpAndModifySelection",                   "MovePageDownAndModifySelection"            }, // Up/down page
161     { "MoveToBeginningOfDocument",                      "MoveToEndOfDocument",
162       "MoveToBeginningOfDocumentAndModifySelection",    "MoveToEndOfDocumentAndModifySelection"     }, // Begin/end of buffer
163     { 0,                                                0,
164       0,                                                0                                           } // Horizontal page movement
165 };
166 
moveCursorCallback(GtkWidget * widget,GtkMovementStep step,gint count,gboolean extendSelection,PageClientImpl * client)167 static void moveCursorCallback(GtkWidget* widget, GtkMovementStep step, gint count, gboolean extendSelection, PageClientImpl* client)
168 {
169     g_signal_stop_emission_by_name(widget, "move-cursor");
170     int direction = count > 0 ? 1 : 0;
171     if (extendSelection)
172         direction += 2;
173 
174     if (static_cast<unsigned>(step) >= G_N_ELEMENTS(gtkMoveCommands))
175         return;
176 
177     const char* rawCommand = gtkMoveCommands[step][direction];
178     if (!rawCommand)
179         return;
180 
181     for (int i = 0; i < abs(count); i++)
182         client->addPendingEditorCommand(rawCommand);
183 }
184 
185 static const unsigned CtrlKey = 1 << 0;
186 static const unsigned AltKey = 1 << 1;
187 static const unsigned ShiftKey = 1 << 2;
188 
189 struct KeyDownEntry {
190     unsigned virtualKey;
191     unsigned modifiers;
192     const char* name;
193 };
194 
195 struct KeyPressEntry {
196     unsigned charCode;
197     unsigned modifiers;
198     const char* name;
199 };
200 
201 static const KeyDownEntry keyDownEntries[] = {
202     { 'B',       CtrlKey,            "ToggleBold"                                  },
203     { 'I',       CtrlKey,            "ToggleItalic"                                },
204     { VK_ESCAPE, 0,                  "Cancel"                                      },
205     { VK_OEM_PERIOD, CtrlKey,        "Cancel"                                      },
206     { VK_TAB,    0,                  "InsertTab"                                   },
207     { VK_TAB,    ShiftKey,           "InsertBacktab"                               },
208     { VK_RETURN, 0,                  "InsertNewline"                               },
209     { VK_RETURN, CtrlKey,            "InsertNewline"                               },
210     { VK_RETURN, AltKey,             "InsertNewline"                               },
211     { VK_RETURN, AltKey | ShiftKey,  "InsertNewline"                               },
212 };
213 
214 static const KeyPressEntry keyPressEntries[] = {
215     { '\t',   0,                  "InsertTab"                                   },
216     { '\t',   ShiftKey,           "InsertBacktab"                               },
217     { '\r',   0,                  "InsertNewline"                               },
218     { '\r',   CtrlKey,            "InsertNewline"                               },
219     { '\r',   AltKey,             "InsertNewline"                               },
220     { '\r',   AltKey | ShiftKey,  "InsertNewline"                               },
221 };
222 
PageClientImpl(GtkWidget * viewWidget)223 PageClientImpl::PageClientImpl(GtkWidget* viewWidget)
224     : m_viewWidget(viewWidget)
225     , m_nativeWidget(gtk_text_view_new())
226 {
227     g_signal_connect(m_nativeWidget.get(), "backspace", G_CALLBACK(backspaceCallback), this);
228     g_signal_connect(m_nativeWidget.get(), "cut-clipboard", G_CALLBACK(cutClipboardCallback), this);
229     g_signal_connect(m_nativeWidget.get(), "copy-clipboard", G_CALLBACK(copyClipboardCallback), this);
230     g_signal_connect(m_nativeWidget.get(), "paste-clipboard", G_CALLBACK(pasteClipboardCallback), this);
231     g_signal_connect(m_nativeWidget.get(), "select-all", G_CALLBACK(selectAllCallback), this);
232     g_signal_connect(m_nativeWidget.get(), "move-cursor", G_CALLBACK(moveCursorCallback), this);
233     g_signal_connect(m_nativeWidget.get(), "delete-from-cursor", G_CALLBACK(deleteFromCursorCallback), this);
234     g_signal_connect(m_nativeWidget.get(), "toggle-overwrite", G_CALLBACK(toggleOverwriteCallback), this);
235     g_signal_connect(m_nativeWidget.get(), "popup-menu", G_CALLBACK(popupMenuCallback), this);
236     g_signal_connect(m_nativeWidget.get(), "show-help", G_CALLBACK(showHelpCallback), this);
237 }
238 
~PageClientImpl()239 PageClientImpl::~PageClientImpl()
240 {
241 }
242 
getEditorCommandsForKeyEvent(const NativeWebKeyboardEvent & event,Vector<WTF::String> & commandList)243 void PageClientImpl::getEditorCommandsForKeyEvent(const NativeWebKeyboardEvent& event, Vector<WTF::String>& commandList)
244 {
245     m_pendingEditorCommands.clear();
246 
247 #ifdef GTK_API_VERSION_2
248     gtk_bindings_activate_event(GTK_OBJECT(m_nativeWidget.get()), const_cast<GdkEventKey*>(&event.nativeEvent()->key));
249 #else
250     gtk_bindings_activate_event(G_OBJECT(m_nativeWidget.get()), const_cast<GdkEventKey*>(&event.nativeEvent()->key));
251 #endif
252 
253     if (m_pendingEditorCommands.isEmpty()) {
254         commandList.append(m_pendingEditorCommands);
255         return;
256     }
257 
258     DEFINE_STATIC_LOCAL(IntConstCharHashMap, keyDownCommandsMap, ());
259     DEFINE_STATIC_LOCAL(IntConstCharHashMap, keyPressCommandsMap, ());
260 
261     if (keyDownCommandsMap.isEmpty()) {
262         for (unsigned i = 0; i < G_N_ELEMENTS(keyDownEntries); i++)
263             keyDownCommandsMap.set(keyDownEntries[i].modifiers << 16 | keyDownEntries[i].virtualKey, keyDownEntries[i].name);
264 
265         for (unsigned i = 0; i < G_N_ELEMENTS(keyPressEntries); i++)
266             keyPressCommandsMap.set(keyPressEntries[i].modifiers << 16 | keyPressEntries[i].charCode, keyPressEntries[i].name);
267     }
268 
269     unsigned modifiers = 0;
270     if (event.shiftKey())
271         modifiers |= ShiftKey;
272     if (event.altKey())
273         modifiers |= AltKey;
274     if (event.controlKey())
275         modifiers |= CtrlKey;
276 
277     // For keypress events, we want charCode(), but keyCode() does that.
278     int mapKey = modifiers << 16 | event.nativeVirtualKeyCode();
279     if (mapKey) {
280         HashMap<int, const char*>* commandMap = event.type() == WebEvent::KeyDown ?
281             &keyDownCommandsMap : &keyPressCommandsMap;
282         if (const char* commandString = commandMap->get(mapKey))
283             m_pendingEditorCommands.append(commandString);
284     }
285 
286     commandList.append(m_pendingEditorCommands);
287 }
288 
289 // PageClient's pure virtual functions
createDrawingAreaProxy()290 PassOwnPtr<DrawingAreaProxy> PageClientImpl::createDrawingAreaProxy()
291 {
292     WebKitWebViewBase* view = WEBKIT_WEB_VIEW_BASE(m_viewWidget);
293     return ChunkedUpdateDrawingAreaProxy::create(view, webkitWebViewBaseGetPage(view));
294 }
295 
setViewNeedsDisplay(const WebCore::IntRect &)296 void PageClientImpl::setViewNeedsDisplay(const WebCore::IntRect&)
297 {
298     notImplemented();
299 }
300 
displayView()301 void PageClientImpl::displayView()
302 {
303     notImplemented();
304 }
305 
scrollView(const WebCore::IntRect & scrollRect,const WebCore::IntSize & scrollOffset)306 void PageClientImpl::scrollView(const WebCore::IntRect& scrollRect, const WebCore::IntSize& scrollOffset)
307 {
308     notImplemented();
309 }
310 
viewSize()311 WebCore::IntSize PageClientImpl::viewSize()
312 {
313     GtkAllocation allocation;
314     gtk_widget_get_allocation(m_viewWidget, &allocation);
315     return IntSize(allocation.width, allocation.height);
316 }
317 
isViewWindowActive()318 bool PageClientImpl::isViewWindowActive()
319 {
320     notImplemented();
321     return true;
322 }
323 
isViewFocused()324 bool PageClientImpl::isViewFocused()
325 {
326     notImplemented();
327     return true;
328 }
329 
isViewVisible()330 bool PageClientImpl::isViewVisible()
331 {
332     notImplemented();
333     return true;
334 }
335 
isViewInWindow()336 bool PageClientImpl::isViewInWindow()
337 {
338     notImplemented();
339     return true;
340 }
341 
processDidCrash()342 void PageClientImpl::PageClientImpl::processDidCrash()
343 {
344     notImplemented();
345 }
346 
didRelaunchProcess()347 void PageClientImpl::didRelaunchProcess()
348 {
349     notImplemented();
350 }
351 
takeFocus(bool)352 void PageClientImpl::takeFocus(bool)
353 {
354     notImplemented();
355 }
356 
toolTipChanged(const String &,const String &)357 void PageClientImpl::toolTipChanged(const String&, const String&)
358 {
359     notImplemented();
360 }
361 
setCursor(const Cursor & cursor)362 void PageClientImpl::setCursor(const Cursor& cursor)
363 {
364     // [GTK] Widget::setCursor() gets called frequently
365     // http://bugs.webkit.org/show_bug.cgi?id=16388
366     // Setting the cursor may be an expensive operation in some backends,
367     // so don't re-set the cursor if it's already set to the target value.
368     GdkWindow* window = gtk_widget_get_window(m_viewWidget);
369     GdkCursor* currentCursor = gdk_window_get_cursor(window);
370     GdkCursor* newCursor = cursor.platformCursor().get();
371     if (currentCursor != newCursor)
372         gdk_window_set_cursor(window, newCursor);
373 }
374 
setViewportArguments(const WebCore::ViewportArguments &)375 void PageClientImpl::setViewportArguments(const WebCore::ViewportArguments&)
376 {
377     notImplemented();
378 }
379 
registerEditCommand(PassRefPtr<WebEditCommandProxy>,WebPageProxy::UndoOrRedo)380 void PageClientImpl::registerEditCommand(PassRefPtr<WebEditCommandProxy>, WebPageProxy::UndoOrRedo)
381 {
382     notImplemented();
383 }
384 
clearAllEditCommands()385 void PageClientImpl::clearAllEditCommands()
386 {
387     notImplemented();
388 }
389 
canUndoRedo(WebPageProxy::UndoOrRedo)390 bool PageClientImpl::canUndoRedo(WebPageProxy::UndoOrRedo)
391 {
392     notImplemented();
393     return false;
394 }
395 
executeUndoRedo(WebPageProxy::UndoOrRedo)396 void PageClientImpl::executeUndoRedo(WebPageProxy::UndoOrRedo)
397 {
398     notImplemented();
399 }
400 
convertToDeviceSpace(const FloatRect & viewRect)401 FloatRect PageClientImpl::convertToDeviceSpace(const FloatRect& viewRect)
402 {
403     notImplemented();
404     return viewRect;
405 }
406 
convertToUserSpace(const FloatRect & viewRect)407 FloatRect PageClientImpl::convertToUserSpace(const FloatRect& viewRect)
408 {
409     notImplemented();
410     return viewRect;
411 }
412 
windowToScreen(const IntRect & rect)413 IntRect PageClientImpl::windowToScreen(const IntRect& rect)
414 {
415     notImplemented();
416     return IntRect();
417 }
418 
doneWithKeyEvent(const NativeWebKeyboardEvent &,bool wasEventHandled)419 void PageClientImpl::doneWithKeyEvent(const NativeWebKeyboardEvent&, bool wasEventHandled)
420 {
421     notImplemented();
422 }
423 
didNotHandleKeyEvent(const NativeWebKeyboardEvent & event)424 void PageClientImpl::didNotHandleKeyEvent(const NativeWebKeyboardEvent& event)
425 {
426     notImplemented();
427 }
428 
createPopupMenuProxy(WebPageProxy *)429 PassRefPtr<WebPopupMenuProxy> PageClientImpl::createPopupMenuProxy(WebPageProxy*)
430 {
431     notImplemented();
432     return 0;
433 }
434 
createContextMenuProxy(WebPageProxy *)435 PassRefPtr<WebContextMenuProxy> PageClientImpl::createContextMenuProxy(WebPageProxy*)
436 {
437     notImplemented();
438     return 0;
439 }
440 
setFindIndicator(PassRefPtr<FindIndicator>,bool fadeOut)441 void PageClientImpl::setFindIndicator(PassRefPtr<FindIndicator>, bool fadeOut)
442 {
443     notImplemented();
444 }
445 
446 #if USE(ACCELERATED_COMPOSITING)
pageDidEnterAcceleratedCompositing()447 void PageClientImpl::pageDidEnterAcceleratedCompositing()
448 {
449     notImplemented();
450 }
451 
pageDidLeaveAcceleratedCompositing()452 void PageClientImpl::pageDidLeaveAcceleratedCompositing()
453 {
454     notImplemented();
455 }
456 #endif // USE(ACCELERATED_COMPOSITING)
457 
didCommitLoadForMainFrame(bool useCustomRepresentation)458 void PageClientImpl::didCommitLoadForMainFrame(bool useCustomRepresentation)
459 {
460 }
461 
didFinishLoadingDataForCustomRepresentation(const String & suggestedFilename,const CoreIPC::DataReference &)462 void PageClientImpl::didFinishLoadingDataForCustomRepresentation(const String& suggestedFilename, const CoreIPC::DataReference&)
463 {
464 }
465 
customRepresentationZoomFactor()466 double PageClientImpl::customRepresentationZoomFactor()
467 {
468     notImplemented();
469     return 0;
470 }
471 
setCustomRepresentationZoomFactor(double)472 void PageClientImpl::setCustomRepresentationZoomFactor(double)
473 {
474     notImplemented();
475 }
476 
pageClosed()477 void PageClientImpl::pageClosed()
478 {
479     notImplemented();
480 }
481 
didChangeScrollbarsForMainFrame() const482 void PageClientImpl::didChangeScrollbarsForMainFrame() const
483 {
484 }
485 
flashBackingStoreUpdates(const Vector<IntRect> &)486 void PageClientImpl::flashBackingStoreUpdates(const Vector<IntRect>&)
487 {
488     notImplemented();
489 }
490 
findStringInCustomRepresentation(const String &,FindOptions,unsigned)491 void PageClientImpl::findStringInCustomRepresentation(const String&, FindOptions, unsigned)
492 {
493     notImplemented();
494 }
495 
countStringMatchesInCustomRepresentation(const String &,FindOptions,unsigned)496 void PageClientImpl::countStringMatchesInCustomRepresentation(const String&, FindOptions, unsigned)
497 {
498     notImplemented();
499 }
500 
501 } // namespace WebKit
502