1/*
2 * Copyright (C) 2012 Igalia S.L.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2,1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20#include "config.h"
21#include "WebKitWebInspector.h"
22
23#include "WebInspectorProxy.h"
24#include "WebInspectorProxyClient.h"
25#include "WebKitWebInspectorPrivate.h"
26#include <glib/gi18n-lib.h>
27#include <wtf/glib/GRefPtr.h>
28#include <wtf/glib/WTFGType.h>
29#include <wtf/text/CString.h>
30
31using namespace WebKit;
32
33/**
34 * SECTION: WebKitWebInspector
35 * @Short_description: Access to the WebKit inspector
36 * @Title: WebKitWebInspector
37 *
38 * The WebKit Inspector is a graphical tool to inspect and change the
39 * content of a #WebKitWebView. It also includes an interactive
40 * JavaScript debugger. Using this class one can get a #GtkWidget
41 * which can be embedded into an application to show the inspector.
42 *
43 * The inspector is available when the #WebKitSettings of the
44 * #WebKitWebView has set the #WebKitSettings:enable-developer-extras
45 * to true, otherwise no inspector is available.
46 *
47 * <informalexample><programlisting>
48 * /<!-- -->* Enable the developer extras *<!-- -->/
49 * WebKitSettings *setting = webkit_web_view_get_settings (WEBKIT_WEB_VIEW(my_webview));
50 * g_object_set (G_OBJECT(settings), "enable-developer-extras", TRUE, NULL);
51 *
52 * /<!-- -->* Load some data or reload to be able to inspect the page*<!-- -->/
53 * webkit_web_view_load_uri (WEBKIT_WEB_VIEW(my_webview), "http://www.gnome.org");
54 *
55 * /<!-- -->* Show the inspector *<!-- -->/
56 * WebKitWebInspector *inspector = webkit_web_view_get_inspector (WEBKIT_WEB_VIEW(my_webview));
57 * webkit_web_inspector_show (WEBKIT_WEB_INSPECTOR(inspector));
58 * </programlisting></informalexample>
59 *
60 */
61
62enum {
63 OPEN_WINDOW,
64 BRING_TO_FRONT,
65 CLOSED,
66 ATTACH,
67 DETACH,
68
69 LAST_SIGNAL
70};
71
72enum {
73 PROP_0,
74
75 PROP_INSPECTED_URI,
76 PROP_ATTACHED_HEIGHT,
77 PROP_CAN_ATTACH
78};
79
80struct _WebKitWebInspectorPrivate {
81 ~_WebKitWebInspectorPrivate()
82 {
83 webInspector->setClient(nullptr);
84 }
85
86 RefPtr<WebInspectorProxy> webInspector;
87 CString inspectedURI;
88 unsigned attachedHeight;
89 bool canAttach;
90};
91
92WEBKIT_DEFINE_TYPE(WebKitWebInspector, webkit_web_inspector, G_TYPE_OBJECT)
93
94static guint signals[LAST_SIGNAL] = { 0, };
95
96static void webkitWebInspectorGetProperty(GObject* object, guint propId, GValue* value, GParamSpec* paramSpec)
97{
98 WebKitWebInspector* inspector = WEBKIT_WEB_INSPECTOR(object);
99
100 switch (propId) {
101 case PROP_INSPECTED_URI:
102 g_value_set_string(value, webkit_web_inspector_get_inspected_uri(inspector));
103 break;
104 case PROP_ATTACHED_HEIGHT:
105 g_value_set_uint(value, webkit_web_inspector_get_attached_height(inspector));
106 break;
107 case PROP_CAN_ATTACH:
108 g_value_set_boolean(value, webkit_web_inspector_get_can_attach(inspector));
109 break;
110 default:
111 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, paramSpec);
112 }
113}
114
115static void webkit_web_inspector_class_init(WebKitWebInspectorClass* findClass)
116{
117 GObjectClass* gObjectClass = G_OBJECT_CLASS(findClass);
118 gObjectClass->get_property = webkitWebInspectorGetProperty;
119
120 /**
121 * WebKitWebInspector:inspected-uri:
122 *
123 * The URI that is currently being inspected.
124 */
125 g_object_class_install_property(gObjectClass,
126 PROP_INSPECTED_URI,
127 g_param_spec_string("inspected-uri",
128 _("Inspected URI"),
129 _("The URI that is currently being inspected"),
130 0,
131 WEBKIT_PARAM_READABLE));
132 /**
133 * WebKitWebInspector:attached-height:
134 *
135 * The height that the inspector view should have when it is attached.
136 */
137 g_object_class_install_property(gObjectClass,
138 PROP_ATTACHED_HEIGHT,
139 g_param_spec_uint("attached-height",
140 _("Attached Height"),
141 _("The height that the inspector view should have when it is attached"),
142 0, G_MAXUINT, 0,
143 WEBKIT_PARAM_READABLE));
144
145 /**
146 * WebKitWebInspector:can-attach:
147 *
148 * Whether the @inspector can be attached to the same window that contains
149 * the inspected view.
150 *
151 * Since: 2.8
152 */
153 g_object_class_install_property(
154 gObjectClass,
155 PROP_CAN_ATTACH,
156 g_param_spec_boolean(
157 "can-attach",
158 _("Can Attach"),
159 _("Whether the inspector can be attached to the same window that contains the inspected view"),
160 FALSE,
161 WEBKIT_PARAM_READABLE));
162
163 /**
164 * WebKitWebInspector::open-window:
165 * @inspector: the #WebKitWebInspector on which the signal is emitted
166 *
167 * Emitted when the inspector is requested to open in a separate window.
168 * If this signal is not handled, a #GtkWindow with the inspector will be
169 * created and shown, so you only need to handle this signal if you want
170 * to use your own window.
171 * This signal is emitted after #WebKitWebInspector::detach to show
172 * the inspector in a separate window after being detached.
173 *
174 * To prevent the inspector from being shown you can connect to this
175 * signal and simply return %TRUE
176 *
177 * Returns: %TRUE to stop other handlers from being invoked for the event.
178 * %FALSE to propagate the event further.
179 */
180 signals[OPEN_WINDOW] = g_signal_new(
181 "open-window",
182 G_TYPE_FROM_CLASS(gObjectClass),
183 G_SIGNAL_RUN_LAST,
184 0,
185 g_signal_accumulator_true_handled, nullptr,
186 g_cclosure_marshal_generic,
187 G_TYPE_BOOLEAN, 0);
188
189 /**
190 * WebKitWebInspector::bring-to-front:
191 * @inspector: the #WebKitWebInspector on which the signal is emitted
192 *
193 * Emitted when the inspector should be shown.
194 *
195 * If the inspector is not attached the inspector window should be shown
196 * on top of any other windows.
197 * If the inspector is attached the inspector view should be made visible.
198 * For example, if the inspector view is attached using a tab in a browser
199 * window, the browser window should be raised and the tab containing the
200 * inspector view should be the active one.
201 * In both cases, if this signal is not handled, the default implementation
202 * calls gtk_window_present() on the current toplevel #GtkWindow of the
203 * inspector view.
204 *
205 * Returns: %TRUE to stop other handlers from being invoked for the event.
206 * %FALSE to propagate the event further.
207 */
208 signals[BRING_TO_FRONT] = g_signal_new(
209 "bring-to-front",
210 G_TYPE_FROM_CLASS(gObjectClass),
211 G_SIGNAL_RUN_LAST,
212 0,
213 g_signal_accumulator_true_handled, nullptr,
214 g_cclosure_marshal_generic,
215 G_TYPE_BOOLEAN, 0);
216
217 /**
218 * WebKitWebInspector::closed:
219 * @inspector: the #WebKitWebInspector on which the signal is emitted
220 *
221 * Emitted when the inspector page is closed. If you are using your own
222 * inspector window, you should connect to this signal and destroy your
223 * window.
224 */
225 signals[CLOSED] =
226 g_signal_new("closed",
227 G_TYPE_FROM_CLASS(gObjectClass),
228 G_SIGNAL_RUN_LAST,
229 0, 0, 0,
230 g_cclosure_marshal_VOID__VOID,
231 G_TYPE_NONE, 0);
232
233 /**
234 * WebKitWebInspector::attach:
235 * @inspector: the #WebKitWebInspector on which the signal is emitted
236 *
237 * Emitted when the inspector is requested to be attached to the window
238 * where the inspected web view is.
239 * If this signal is not handled the inspector view will be automatically
240 * attached to the inspected view, so you only need to handle this signal
241 * if you want to attach the inspector view yourself (for example, to add
242 * the inspector view to a browser tab).
243 *
244 * To prevent the inspector view from being attached you can connect to this
245 * signal and simply return %TRUE.
246 *
247 * Returns: %TRUE to stop other handlers from being invoked for the event.
248 * %FALSE to propagate the event further.
249 */
250 signals[ATTACH] = g_signal_new(
251 "attach",
252 G_TYPE_FROM_CLASS(gObjectClass),
253 G_SIGNAL_RUN_LAST,
254 0,
255 g_signal_accumulator_true_handled, nullptr,
256 g_cclosure_marshal_generic,
257 G_TYPE_BOOLEAN, 0);
258
259 /**
260 * WebKitWebInspector::detach:
261 * @inspector: the #WebKitWebInspector on which the signal is emitted
262 *
263 * Emitted when the inspector is requested to be detached from the window
264 * it is currently attached to. The inspector is detached when the inspector page
265 * is about to be closed, and this signal is emitted right before
266 * #WebKitWebInspector::closed, or when the user clicks on the detach button
267 * in the inspector view to show the inspector in a separate window. In this case
268 * the signal #WebKitWebInspector::open-window is emitted after this one.
269 *
270 * To prevent the inspector view from being detached you can connect to this
271 * signal and simply return %TRUE.
272 *
273 * Returns: %TRUE to stop other handlers from being invoked for the event.
274 * %FALSE to propagate the event further.
275 */
276 signals[DETACH] = g_signal_new(
277 "detach",
278 G_TYPE_FROM_CLASS(gObjectClass),
279 G_SIGNAL_RUN_LAST,
280 0,
281 g_signal_accumulator_true_handled, nullptr,
282 g_cclosure_marshal_generic,
283 G_TYPE_BOOLEAN, 0);
284}
285
286class WebKitInspectorClient final : public WebInspectorProxyClient {
287public:
288 explicit WebKitInspectorClient(WebKitWebInspector* inspector)
289 : m_inspector(inspector)
290 {
291 }
292
293private:
294 bool openWindow(WebInspectorProxy&) override
295 {
296 gboolean returnValue;
297 g_signal_emit(m_inspector, signals[OPEN_WINDOW], 0, &returnValue);
298 return returnValue;
299 }
300
301 void didClose(WebInspectorProxy&) override
302 {
303 g_signal_emit(m_inspector, signals[CLOSED], 0);
304 }
305
306 bool bringToFront(WebInspectorProxy&) override
307 {
308 gboolean returnValue;
309 g_signal_emit(m_inspector, signals[BRING_TO_FRONT], 0, &returnValue);
310 return returnValue;
311 }
312
313 void inspectedURLChanged(WebInspectorProxy&, const String& url) override
314 {
315 CString uri = url.utf8();
316 if (uri == m_inspector->priv->inspectedURI)
317 return;
318 m_inspector->priv->inspectedURI = uri;
319 g_object_notify(G_OBJECT(m_inspector), "inspected-uri");
320 }
321
322 bool attach(WebInspectorProxy&) override
323 {
324 gboolean returnValue;
325 g_signal_emit(m_inspector, signals[ATTACH], 0, &returnValue);
326 return returnValue;
327 }
328
329 bool detach(WebInspectorProxy&) override
330 {
331 gboolean returnValue;
332 g_signal_emit(m_inspector, signals[DETACH], 0, &returnValue);
333 return returnValue;
334 }
335
336 void didChangeAttachedHeight(WebInspectorProxy&, unsigned height) override
337 {
338 if (m_inspector->priv->attachedHeight == height)
339 return;
340 m_inspector->priv->attachedHeight = height;
341 g_object_notify(G_OBJECT(m_inspector), "attached-height");
342 }
343
344 void didChangeAttachedWidth(WebInspectorProxy&, unsigned width) override
345 {
346 }
347
348 void didChangeAttachAvailability(WebInspectorProxy&, bool available) override
349 {
350 if (m_inspector->priv->canAttach == available)
351 return;
352 m_inspector->priv->canAttach = available;
353 g_object_notify(G_OBJECT(m_inspector), "can-attach");
354 }
355
356 WebKitWebInspector* m_inspector;
357};
358
359WebKitWebInspector* webkitWebInspectorCreate(WebInspectorProxy* webInspector)
360{
361 WebKitWebInspector* inspector = WEBKIT_WEB_INSPECTOR(g_object_new(WEBKIT_TYPE_WEB_INSPECTOR, NULL));
362 inspector->priv->webInspector = webInspector;
363 webInspector->setClient(std::make_unique<WebKitInspectorClient>(inspector));
364 return inspector;
365}
366
367/**
368 * webkit_web_inspector_get_web_view:
369 * @inspector: a #WebKitWebInspector
370 *
371 * Get the #WebKitWebViewBase used to display the inspector.
372 * This might be %NULL if the inspector hasn't been loaded yet,
373 * or it has been closed.
374 *
375 * Returns: (transfer none): the #WebKitWebViewBase used to display the inspector or %NULL
376 */
377WebKitWebViewBase* webkit_web_inspector_get_web_view(WebKitWebInspector* inspector)
378{
379 g_return_val_if_fail(WEBKIT_IS_WEB_INSPECTOR(inspector), 0);
380
381 return WEBKIT_WEB_VIEW_BASE(inspector->priv->webInspector->inspectorView());
382}
383
384/**
385 * webkit_web_inspector_get_inspected_uri:
386 * @inspector: a #WebKitWebInspector
387 *
388 * Get the URI that is currently being inspected. This can be %NULL if
389 * nothing has been loaded yet in the inspected view, if the inspector
390 * has been closed or when inspected view was loaded from a HTML string
391 * instead of a URI.
392 *
393 * Returns: the URI that is currently being inspected or %NULL
394 */
395const char* webkit_web_inspector_get_inspected_uri(WebKitWebInspector* inspector)
396{
397 g_return_val_if_fail(WEBKIT_IS_WEB_INSPECTOR(inspector), 0);
398
399 return inspector->priv->inspectedURI.data();
400}
401
402/**
403 * webkit_web_inspector_get_can_attach:
404 * @inspector: a #WebKitWebInspector
405 *
406 * Whether the @inspector can be attached to the same window that contains
407 * the inspected view.
408 *
409 * Returns: %TRUE if there is enough room for the inspector view inside the
410 * window that contains the inspected view, or %FALSE otherwise.
411 *
412 * Since: 2.8
413 */
414gboolean webkit_web_inspector_get_can_attach(WebKitWebInspector* inspector)
415{
416 g_return_val_if_fail(WEBKIT_IS_WEB_INSPECTOR(inspector), FALSE);
417
418 return inspector->priv->canAttach;
419}
420
421/**
422 * webkit_web_inspector_is_attached:
423 * @inspector: a #WebKitWebInspector
424 *
425 * Whether the @inspector view is currently attached to the same window that contains
426 * the inspected view.
427 *
428 * Returns: %TRUE if @inspector is currently attached or %FALSE otherwise
429 */
430gboolean webkit_web_inspector_is_attached(WebKitWebInspector* inspector)
431{
432 g_return_val_if_fail(WEBKIT_IS_WEB_INSPECTOR(inspector), FALSE);
433
434 return inspector->priv->webInspector->isAttached();
435}
436
437/**
438 * webkit_web_inspector_attach:
439 * @inspector: a #WebKitWebInspector
440 *
441 * Request @inspector to be attached. The signal #WebKitWebInspector::attach
442 * will be emitted. If the inspector is already attached it does nothing.
443 */
444void webkit_web_inspector_attach(WebKitWebInspector* inspector)
445{
446 g_return_if_fail(WEBKIT_IS_WEB_INSPECTOR(inspector));
447
448 if (inspector->priv->webInspector->isAttached())
449 return;
450 inspector->priv->webInspector->attach();
451}
452
453/**
454 * webkit_web_inspector_detach:
455 * @inspector: a #WebKitWebInspector
456 *
457 * Request @inspector to be detached. The signal #WebKitWebInspector::detach
458 * will be emitted. If the inspector is already detached it does nothing.
459 */
460void webkit_web_inspector_detach(WebKitWebInspector* inspector)
461{
462 g_return_if_fail(WEBKIT_IS_WEB_INSPECTOR(inspector));
463
464 if (!inspector->priv->webInspector->isAttached())
465 return;
466 inspector->priv->webInspector->detach();
467}
468
469/**
470 * webkit_web_inspector_show:
471 * @inspector: a #WebKitWebInspector
472 *
473 * Request @inspector to be shown.
474 */
475void webkit_web_inspector_show(WebKitWebInspector* inspector)
476{
477 g_return_if_fail(WEBKIT_IS_WEB_INSPECTOR(inspector));
478
479 inspector->priv->webInspector->show();
480}
481
482/**
483 * webkit_web_inspector_close:
484 * @inspector: a #WebKitWebInspector
485 *
486 * Request @inspector to be closed.
487 */
488void webkit_web_inspector_close(WebKitWebInspector* inspector)
489{
490 g_return_if_fail(WEBKIT_IS_WEB_INSPECTOR(inspector));
491
492 inspector->priv->webInspector->close();
493}
494
495/**
496 * webkit_web_inspector_get_attached_height:
497 * @inspector: a #WebKitWebInspector
498 *
499 * Get the height that the inspector view should have when
500 * it's attached. If the inspector view is not attached this
501 * returns 0.
502 *
503 * Returns: the height of the inspector view when attached
504 */
505guint webkit_web_inspector_get_attached_height(WebKitWebInspector* inspector)
506{
507 g_return_val_if_fail(WEBKIT_IS_WEB_INSPECTOR(inspector), 0);
508
509 if (!inspector->priv->webInspector->isAttached())
510 return 0;
511 return inspector->priv->attachedHeight;
512}
513