1 /* 2 * Copyright (C) 2008 Gustavo Noronha Silva 3 * Copyright (C) 2008, 2009 Holger Hans Peter Freyther 4 * Copyright (C) 2009 Collabora Ltd. 5 * 6 * This library is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU Library General Public 8 * License as published by the Free Software Foundation; either 9 * version 2 of the License, or (at your option) any later version. 10 * 11 * This library is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 * Library General Public License for more details. 15 * 16 * You should have received a copy of the GNU Library General Public License 17 * along with this library; see the file COPYING.LIB. If not, write to 18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 * Boston, MA 02110-1301, USA. 20 */ 21 22 #include "config.h" 23 #include "webkitwebinspector.h" 24 25 #include "DumpRenderTreeSupportGtk.h" 26 #include "FocusController.h" 27 #include "Frame.h" 28 #include "HitTestRequest.h" 29 #include "HitTestResult.h" 30 #include "InspectorClientGtk.h" 31 #include "InspectorController.h" 32 #include "InspectorInstrumentation.h" 33 #include "IntPoint.h" 34 #include "Page.h" 35 #include "RenderLayer.h" 36 #include "RenderView.h" 37 #include "webkit/WebKitDOMNodePrivate.h" 38 #include "webkitglobalsprivate.h" 39 #include "webkitmarshal.h" 40 #include "webkitwebinspectorprivate.h" 41 #include <glib/gi18n-lib.h> 42 43 /** 44 * SECTION:webkitwebinspector 45 * @short_description: Access to the WebKit Inspector 46 * 47 * The WebKit Inspector is a graphical tool to inspect and change 48 * the content of a #WebKitWebView. It also includes an interactive 49 * JavaScriptDebugger. Using this class one can get a GtkWidget which 50 * can be embedded into an application to show the inspector. 51 * 52 * The inspector is available when the #WebKitWebSettings of the 53 * #WebKitWebView has set the #WebKitWebSettings:enable-developer-extras 54 * to true otherwise no inspector is available. 55 * 56 * <informalexample><programlisting> 57 * /<!-- -->* Enable the developer extras *<!-- -->/ 58 * WebKitWebSettings *setting = webkit_web_view_get_settings (WEBKIT_WEB_VIEW(my_webview)); 59 * g_object_set (G_OBJECT(settings), "enable-developer-extras", TRUE, NULL); 60 * 61 * /<!-- -->* load some data or reload to be able to inspect the page*<!-- -->/ 62 * webkit_web_view_open (WEBKIT_WEB_VIEW(my_webview), "http://www.gnome.org"); 63 * 64 * /<!-- -->* Embed the inspector somewhere *<!-- -->/ 65 * WebKitWebInspector *inspector = webkit_web_view_get_inspector (WEBKIT_WEB_VIEW(my_webview)); 66 * g_signal_connect (G_OBJECT (inspector), "inspect-web-view", G_CALLBACK(create_gtk_window_around_it), NULL); 67 * g_signal_connect (G_OBJECT (inspector), "show-window", G_CALLBACK(show_inpector_window), NULL)); 68 * g_signal_connect (G_OBJECT (inspector), "notify::inspected-uri", G_CALLBACK(inspected_uri_changed_do_stuff), NULL); 69 * </programlisting></informalexample> 70 */ 71 72 using namespace WebKit; 73 using namespace WebCore; 74 75 enum { 76 INSPECT_WEB_VIEW, 77 SHOW_WINDOW, 78 ATTACH_WINDOW, 79 DETACH_WINDOW, 80 CLOSE_WINDOW, 81 FINISHED, 82 LAST_SIGNAL 83 }; 84 85 static guint webkit_web_inspector_signals[LAST_SIGNAL] = { 0, }; 86 87 enum { 88 PROP_0, 89 90 PROP_WEB_VIEW, 91 PROP_INSPECTED_URI, 92 PROP_JAVASCRIPT_PROFILING_ENABLED, 93 PROP_TIMELINE_PROFILING_ENABLED 94 }; 95 96 G_DEFINE_TYPE(WebKitWebInspector, webkit_web_inspector, G_TYPE_OBJECT) 97 98 struct _WebKitWebInspectorPrivate { 99 WebCore::Page* page; 100 WebKitWebView* inspector_view; 101 gchar* inspected_uri; 102 }; 103 104 static void webkit_web_inspector_finalize(GObject* object); 105 106 static void webkit_web_inspector_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec); 107 108 static void webkit_web_inspector_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec); 109 110 static gboolean webkit_inspect_web_view_request_handled(GSignalInvocationHint* ihint, GValue* returnAccu, const GValue* handlerReturn, gpointer dummy) 111 { 112 gboolean continueEmission = TRUE; 113 gpointer newWebView = g_value_get_object(handlerReturn); 114 g_value_set_object(returnAccu, newWebView); 115 116 if (newWebView) 117 continueEmission = FALSE; 118 119 return continueEmission; 120 } 121 122 static void webkit_web_inspector_class_init(WebKitWebInspectorClass* klass) 123 { 124 GObjectClass* gobject_class = G_OBJECT_CLASS(klass); 125 gobject_class->finalize = webkit_web_inspector_finalize; 126 gobject_class->set_property = webkit_web_inspector_set_property; 127 gobject_class->get_property = webkit_web_inspector_get_property; 128 129 /** 130 * WebKitWebInspector::inspect-web-view: 131 * @web_inspector: the object on which the signal is emitted 132 * @web_view: the #WebKitWebView which will be inspected 133 * 134 * Emitted when the user activates the 'inspect' context menu item 135 * to inspect a web view. The application which is interested in 136 * the inspector should create a window, or otherwise add the 137 * #WebKitWebView it creates to an existing window. 138 * 139 * You don't need to handle the reference count of the 140 * #WebKitWebView instance you create; the widget to which you add 141 * it will do that. 142 * 143 * Return value: (transfer none): a newly allocated #WebKitWebView or %NULL 144 * 145 * Since: 1.0.3 146 */ 147 webkit_web_inspector_signals[INSPECT_WEB_VIEW] = g_signal_new("inspect-web-view", 148 G_TYPE_FROM_CLASS(klass), 149 (GSignalFlags)G_SIGNAL_RUN_LAST, 150 0, 151 webkit_inspect_web_view_request_handled, 152 NULL, 153 webkit_marshal_OBJECT__OBJECT, 154 WEBKIT_TYPE_WEB_VIEW , 1, 155 WEBKIT_TYPE_WEB_VIEW); 156 157 /** 158 * WebKitWebInspector::show-window: 159 * @web_inspector: the object on which the signal is emitted 160 * @return: %TRUE if the signal has been handled 161 * 162 * Emitted when the inspector window should be displayed. Notice 163 * that the window must have been created already by handling 164 * #WebKitWebInspector::inspect-web-view. 165 * 166 * Since: 1.0.3 167 */ 168 webkit_web_inspector_signals[SHOW_WINDOW] = g_signal_new("show-window", 169 G_TYPE_FROM_CLASS(klass), 170 (GSignalFlags)G_SIGNAL_RUN_LAST, 171 0, 172 g_signal_accumulator_true_handled, 173 NULL, 174 webkit_marshal_BOOLEAN__VOID, 175 G_TYPE_BOOLEAN , 0); 176 177 /** 178 * WebKitWebInspector::attach-window: 179 * @web_inspector: the object on which the signal is emitted 180 * @return: %TRUE if the signal has been handled 181 * 182 * Emitted when the inspector should appear at the same window as 183 * the #WebKitWebView being inspected. 184 * 185 * Since: 1.0.3 186 */ 187 webkit_web_inspector_signals[ATTACH_WINDOW] = g_signal_new("attach-window", 188 G_TYPE_FROM_CLASS(klass), 189 (GSignalFlags)G_SIGNAL_RUN_LAST, 190 0, 191 g_signal_accumulator_true_handled, 192 NULL, 193 webkit_marshal_BOOLEAN__VOID, 194 G_TYPE_BOOLEAN , 0); 195 196 /** 197 * WebKitWebInspector::detach-window: 198 * @web_inspector: the object on which the signal is emitted 199 * @return: %TRUE if the signal has been handled 200 * 201 * Emitted when the inspector should appear in a separate window. 202 * 203 * Since: 1.0.3 204 */ 205 webkit_web_inspector_signals[DETACH_WINDOW] = g_signal_new("detach-window", 206 G_TYPE_FROM_CLASS(klass), 207 (GSignalFlags)G_SIGNAL_RUN_LAST, 208 0, 209 g_signal_accumulator_true_handled, 210 NULL, 211 webkit_marshal_BOOLEAN__VOID, 212 G_TYPE_BOOLEAN , 0); 213 214 /** 215 * WebKitWebInspector::close-window: 216 * @web_inspector: the object on which the signal is emitted 217 * @return: %TRUE if the signal has been handled 218 * 219 * Emitted when the inspector window should be closed. You can 220 * destroy the window or hide it so that it can be displayed again 221 * by handling #WebKitWebInspector::show-window later on. 222 * 223 * Notice that the inspected #WebKitWebView may no longer exist 224 * when this signal is emitted. 225 * 226 * Notice, too, that if you decide to destroy the window, 227 * #WebKitWebInspector::inspect-web-view will be emmited again, when the user 228 * inspects an element. 229 * 230 * Since: 1.0.3 231 */ 232 webkit_web_inspector_signals[CLOSE_WINDOW] = g_signal_new("close-window", 233 G_TYPE_FROM_CLASS(klass), 234 (GSignalFlags)G_SIGNAL_RUN_LAST, 235 0, 236 g_signal_accumulator_true_handled, 237 NULL, 238 webkit_marshal_BOOLEAN__VOID, 239 G_TYPE_BOOLEAN , 0); 240 241 /** 242 * WebKitWebInspector::finished: 243 * @web_inspector: the object on which the signal is emitted 244 * 245 * Emitted when the inspection is done. You should release your 246 * references on the inspector at this time. The inspected 247 * #WebKitWebView may no longer exist when this signal is emitted. 248 * 249 * Since: 1.0.3 250 */ 251 webkit_web_inspector_signals[FINISHED] = g_signal_new("finished", 252 G_TYPE_FROM_CLASS(klass), 253 (GSignalFlags)G_SIGNAL_RUN_LAST, 254 0, 255 NULL, 256 NULL, 257 g_cclosure_marshal_VOID__VOID, 258 G_TYPE_NONE , 0); 259 260 /* 261 * properties 262 */ 263 264 /** 265 * WebKitWebInspector:web-view: 266 * 267 * The Web View that renders the Web Inspector itself. 268 * 269 * Since: 1.0.3 270 */ 271 g_object_class_install_property(gobject_class, PROP_WEB_VIEW, 272 g_param_spec_object("web-view", 273 _("Web View"), 274 _("The Web View that renders the Web Inspector itself"), 275 WEBKIT_TYPE_WEB_VIEW, 276 WEBKIT_PARAM_READABLE)); 277 278 /** 279 * WebKitWebInspector:inspected-uri: 280 * 281 * The URI that is currently being inspected. 282 * 283 * Since: 1.0.3 284 */ 285 g_object_class_install_property(gobject_class, PROP_INSPECTED_URI, 286 g_param_spec_string("inspected-uri", 287 _("Inspected URI"), 288 _("The URI that is currently being inspected"), 289 NULL, 290 WEBKIT_PARAM_READABLE)); 291 292 /** 293 * WebKitWebInspector:javascript-profiling-enabled 294 * 295 * This is enabling JavaScript profiling in the Inspector. This means 296 * that Console.profiles will return the profiles. 297 * 298 * Since: 1.1.1 299 */ 300 g_object_class_install_property(gobject_class, 301 PROP_JAVASCRIPT_PROFILING_ENABLED, 302 g_param_spec_boolean( 303 "javascript-profiling-enabled", 304 _("Enable JavaScript profiling"), 305 _("Profile the executed JavaScript."), 306 FALSE, 307 WEBKIT_PARAM_READWRITE)); 308 309 /** 310 * WebKitWebInspector:timeline-profiling-enabled 311 * 312 * This is enabling Timeline profiling in the Inspector. 313 * 314 * Since: 1.1.17 315 */ 316 g_object_class_install_property(gobject_class, 317 PROP_TIMELINE_PROFILING_ENABLED, 318 g_param_spec_boolean( 319 "timeline-profiling-enabled", 320 _("Enable Timeline profiling"), 321 _("Profile the WebCore instrumentation."), 322 FALSE, 323 WEBKIT_PARAM_READWRITE)); 324 325 g_type_class_add_private(klass, sizeof(WebKitWebInspectorPrivate)); 326 } 327 328 static void webkit_web_inspector_init(WebKitWebInspector* web_inspector) 329 { 330 web_inspector->priv = G_TYPE_INSTANCE_GET_PRIVATE(web_inspector, WEBKIT_TYPE_WEB_INSPECTOR, WebKitWebInspectorPrivate); 331 } 332 333 static void webkit_web_inspector_finalize(GObject* object) 334 { 335 WebKitWebInspector* web_inspector = WEBKIT_WEB_INSPECTOR(object); 336 WebKitWebInspectorPrivate* priv = web_inspector->priv; 337 338 if (priv->inspector_view) 339 g_object_unref(priv->inspector_view); 340 341 if (priv->inspected_uri) 342 g_free(priv->inspected_uri); 343 344 G_OBJECT_CLASS(webkit_web_inspector_parent_class)->finalize(object); 345 } 346 347 static void webkit_web_inspector_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec) 348 { 349 WebKitWebInspector* web_inspector = WEBKIT_WEB_INSPECTOR(object); 350 WebKitWebInspectorPrivate* priv = web_inspector->priv; 351 352 switch(prop_id) { 353 case PROP_JAVASCRIPT_PROFILING_ENABLED: { 354 #if ENABLE(JAVASCRIPT_DEBUGGER) 355 bool enabled = g_value_get_boolean(value); 356 WebCore::InspectorController* controller = priv->page->inspectorController(); 357 if (enabled) 358 controller->enableProfiler(); 359 else 360 controller->disableProfiler(); 361 #else 362 g_message("PROP_JAVASCRIPT_PROFILING_ENABLED is not work because of the javascript debugger is disabled\n"); 363 #endif 364 break; 365 } 366 case PROP_TIMELINE_PROFILING_ENABLED: { 367 bool enabled = g_value_get_boolean(value); 368 WebCore::InspectorController* controller = priv->page->inspectorController(); 369 if (enabled) 370 controller->startTimelineProfiler(); 371 else 372 controller->stopTimelineProfiler(); 373 break; 374 } 375 default: 376 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); 377 break; 378 } 379 } 380 381 static void webkit_web_inspector_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec) 382 { 383 WebKitWebInspector* web_inspector = WEBKIT_WEB_INSPECTOR(object); 384 WebKitWebInspectorPrivate* priv = web_inspector->priv; 385 386 switch (prop_id) { 387 case PROP_WEB_VIEW: 388 g_value_set_object(value, priv->inspector_view); 389 break; 390 case PROP_INSPECTED_URI: 391 g_value_set_string(value, priv->inspected_uri); 392 break; 393 case PROP_JAVASCRIPT_PROFILING_ENABLED: 394 #if ENABLE(JAVASCRIPT_DEBUGGER) 395 g_value_set_boolean(value, priv->page->inspectorController()->profilerEnabled()); 396 #else 397 g_message("PROP_JAVASCRIPT_PROFILING_ENABLED is not work because of the javascript debugger is disabled\n"); 398 #endif 399 break; 400 case PROP_TIMELINE_PROFILING_ENABLED: 401 g_value_set_boolean(value, priv->page->inspectorController()->timelineProfilerEnabled()); 402 break; 403 default: 404 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); 405 break; 406 } 407 } 408 409 // internal use only 410 void webkit_web_inspector_set_web_view(WebKitWebInspector *web_inspector, WebKitWebView *web_view) 411 { 412 g_return_if_fail(WEBKIT_IS_WEB_INSPECTOR(web_inspector)); 413 g_return_if_fail(WEBKIT_IS_WEB_VIEW(web_view)); 414 415 WebKitWebInspectorPrivate* priv = web_inspector->priv; 416 417 if (priv->inspector_view) 418 g_object_unref(priv->inspector_view); 419 420 g_object_ref(web_view); 421 priv->inspector_view = web_view; 422 } 423 424 /** 425 * webkit_web_inspector_get_web_view: 426 * 427 * Obtains the #WebKitWebView that is used to render the 428 * inspector. The #WebKitWebView instance is created by the 429 * application, by handling the #WebKitWebInspector::inspect-web-view signal. This means 430 * that this method may return %NULL if the user hasn't inspected 431 * anything. 432 * 433 * Returns: (transfer none): the #WebKitWebView instance that is used 434 * to render the inspector or %NULL if it is not yet created. 435 * 436 * Since: 1.0.3 437 **/ 438 WebKitWebView* webkit_web_inspector_get_web_view(WebKitWebInspector *web_inspector) 439 { 440 WebKitWebInspectorPrivate* priv = web_inspector->priv; 441 442 return priv->inspector_view; 443 } 444 445 // internal use only 446 void webkit_web_inspector_set_inspected_uri(WebKitWebInspector* web_inspector, const gchar* inspected_uri) 447 { 448 g_return_if_fail(WEBKIT_IS_WEB_INSPECTOR(web_inspector)); 449 450 WebKitWebInspectorPrivate* priv = web_inspector->priv; 451 452 g_free(priv->inspected_uri); 453 priv->inspected_uri = g_strdup(inspected_uri); 454 } 455 456 /** 457 * webkit_web_inspector_get_inspected_uri: 458 * 459 * Obtains the URI that is currently being inspected. 460 * 461 * Returns: a pointer to the URI as an internally allocated string; it 462 * should not be freed, modified or stored. 463 * 464 * Since: 1.0.3 465 **/ 466 const gchar* webkit_web_inspector_get_inspected_uri(WebKitWebInspector *web_inspector) 467 { 468 WebKitWebInspectorPrivate* priv = web_inspector->priv; 469 470 return priv->inspected_uri; 471 } 472 473 void 474 webkit_web_inspector_set_inspector_client(WebKitWebInspector* web_inspector, WebCore::Page* page) 475 { 476 WebKitWebInspectorPrivate* priv = web_inspector->priv; 477 478 priv->page = page; 479 } 480 481 /** 482 * webkit_web_inspector_show: 483 * @webInspector: the #WebKitWebInspector that will be shown 484 * 485 * Causes the Web Inspector to be shown. 486 * 487 * Since: 1.1.17 488 */ 489 void webkit_web_inspector_show(WebKitWebInspector* webInspector) 490 { 491 g_return_if_fail(WEBKIT_IS_WEB_INSPECTOR(webInspector)); 492 493 WebKitWebInspectorPrivate* priv = webInspector->priv; 494 495 Frame* frame = priv->page->focusController()->focusedOrMainFrame(); 496 FrameView* view = frame->view(); 497 498 if (!view) 499 return; 500 501 priv->page->inspectorController()->show(); 502 } 503 504 /** 505 * webkit_web_inspector_inspect_node: 506 * @web_inspector: the #WebKitWebInspector that will do the inspection 507 * @node: the #WebKitDOMNode to inspect 508 * 509 * Causes the Web Inspector to inspect the given node. 510 * 511 * Since: 1.3.7 512 */ 513 void webkit_web_inspector_inspect_node(WebKitWebInspector* webInspector, WebKitDOMNode* node) 514 { 515 g_return_if_fail(WEBKIT_IS_WEB_INSPECTOR(webInspector)); 516 g_return_if_fail(WEBKIT_DOM_IS_NODE(node)); 517 518 webInspector->priv->page->inspectorController()->inspect(core(node)); 519 } 520 521 /** 522 * webkit_web_inspector_inspect_coordinates: 523 * @web_inspector: the #WebKitWebInspector that will do the inspection 524 * @x: the X coordinate of the node to be inspected 525 * @y: the Y coordinate of the node to be inspected 526 * 527 * Causes the Web Inspector to inspect the node that is located at the 528 * given coordinates of the widget. The coordinates should be relative 529 * to the #WebKitWebView widget, not to the scrollable content, and 530 * may be obtained from a #GdkEvent directly. 531 * 532 * This means @x, and @y being zero doesn't guarantee you will hit the 533 * left-most top corner of the content, since the contents may have 534 * been scrolled. 535 * 536 * Since: 1.1.17 537 */ 538 void webkit_web_inspector_inspect_coordinates(WebKitWebInspector* webInspector, gdouble x, gdouble y) 539 { 540 g_return_if_fail(WEBKIT_IS_WEB_INSPECTOR(webInspector)); 541 g_return_if_fail(x >= 0 && y >= 0); 542 543 WebKitWebInspectorPrivate* priv = webInspector->priv; 544 545 Frame* frame = priv->page->focusController()->focusedOrMainFrame(); 546 FrameView* view = frame->view(); 547 548 if (!view) 549 return; 550 551 HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active); 552 IntPoint documentPoint = view->windowToContents(IntPoint(static_cast<int>(x), static_cast<int>(y))); 553 HitTestResult result(documentPoint); 554 555 frame->contentRenderer()->layer()->hitTest(request, result); 556 priv->page->inspectorController()->inspect(result.innerNonSharedNode()); 557 } 558 559 /** 560 * webkit_web_inspector_close: 561 * @webInspector: the #WebKitWebInspector that will be closed 562 * 563 * Causes the Web Inspector to be closed. 564 * 565 * Since: 1.1.17 566 */ 567 void webkit_web_inspector_close(WebKitWebInspector* webInspector) 568 { 569 g_return_if_fail(WEBKIT_IS_WEB_INSPECTOR(webInspector)); 570 571 WebKitWebInspectorPrivate* priv = webInspector->priv; 572 priv->page->inspectorController()->close(); 573 } 574 575 void webkit_web_inspector_execute_script(WebKitWebInspector* webInspector, long callId, const gchar* script) 576 { 577 g_return_if_fail(WEBKIT_IS_WEB_INSPECTOR(webInspector)); 578 g_return_if_fail(script); 579 580 WebKitWebInspectorPrivate* priv = webInspector->priv; 581 priv->page->inspectorController()->evaluateForTestInFrontend(callId, script); 582 } 583 584 #ifdef HAVE_GSETTINGS 585 static bool isSchemaAvailable(const char* schemaID) 586 { 587 const char* const* availableSchemas = g_settings_list_schemas(); 588 char* const* iter = const_cast<char* const*>(availableSchemas); 589 590 while (*iter) { 591 if (g_str_equal(schemaID, *iter)) 592 return true; 593 iter++; 594 } 595 596 return false; 597 } 598 599 GSettings* inspectorGSettings() 600 { 601 static GSettings* settings = 0; 602 if (settings) 603 return settings; 604 605 // Unfortunately GSettings will abort the process execution if the schema is not 606 // installed, which is the case for when running tests, or even the introspection dump 607 // at build time, so check if we have the schema before trying to initialize it. 608 const gchar* schemaID = "org.webkitgtk-"WEBKITGTK_API_VERSION_STRING".inspector"; 609 if (!isSchemaAvailable(schemaID)) { 610 611 // This warning is very common on the build bots, which hides valid warnings. 612 // Skip printing it if we are running inside DumpRenderTree. 613 if (!DumpRenderTreeSupportGtk::dumpRenderTreeModeEnabled()) 614 g_warning("GSettings schema not found - settings will not be used or saved."); 615 return 0; 616 } 617 618 settings = g_settings_new(schemaID); 619 return settings; 620 } 621 #endif 622