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