1/*
2 * Copyright (C) 2008 Gustavo Noronha Silva
3 * Copyright (C) 2010 Collabora Ltd.
4 *
5 *  This library is free software; you can redistribute it and/or
6 *  modify it under the terms of the GNU Lesser General Public
7 *  License as published by the Free Software Foundation; either
8 *  version 2 of the License, or (at your option) any later version.
9 *
10 *  This library is distributed in the hope that it will be useful,
11 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 *  Lesser General Public License for more details.
14 *
15 *  You should have received a copy of the GNU Lesser General Public
16 *  License along with this library; if not, write to the Free Software
17 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18 */
19
20#include "config.h"
21#include "InspectorClientGtk.h"
22
23#include "Frame.h"
24#include "InspectorController.h"
25#include "NotImplemented.h"
26#include "Page.h"
27#include "PlatformString.h"
28#include "webkitversion.h"
29#include "webkitwebinspector.h"
30#include "webkitwebinspectorprivate.h"
31#include "webkitwebview.h"
32#include "webkitwebviewprivate.h"
33#include <wtf/text/CString.h>
34
35using namespace WebCore;
36
37namespace WebKit {
38
39static void notifyWebViewDestroyed(WebKitWebView* webView, InspectorFrontendClient* inspectorFrontendClient)
40{
41    inspectorFrontendClient->destroyInspectorWindow(true);
42}
43
44namespace {
45
46class InspectorFrontendSettingsGtk : public InspectorFrontendClientLocal::Settings {
47private:
48    virtual ~InspectorFrontendSettingsGtk() { }
49#ifdef HAVE_GSETTINGS
50    static bool shouldIgnoreSetting(const String& key)
51    {
52        // GSettings considers trying to fetch or set a setting that is
53        // not backed by a schema as programmer error, and aborts the
54        // program's execution. We check here to avoid having an unhandled
55        // setting as a fatal error.
56        LOG_VERBOSE(NotYetImplemented, "Unknown key ignored: %s", key.ascii().data());
57        return true;
58    }
59
60    virtual String getProperty(const String& name)
61    {
62        if (shouldIgnoreSetting(name))
63            return String();
64
65        GSettings* settings = inspectorGSettings();
66        if (!settings)
67            return String();
68
69        GRefPtr<GVariant> variant = adoptGRef(g_settings_get_value(settings, name.utf8().data()));
70        return String(g_variant_get_string(variant.get(), 0));
71    }
72
73    virtual void setProperty(const String& name, const String& value)
74    {
75        // Avoid setting unknown keys to avoid aborting the execution.
76        if (shouldIgnoreSetting(name))
77            return;
78
79        GSettings* settings = inspectorGSettings();
80        if (!settings)
81            return;
82
83        GRefPtr<GVariant> variant = adoptGRef(g_variant_new_string(value.utf8().data()));
84        g_settings_set_value(settings, name.utf8().data(), variant.get());
85    }
86#else
87    virtual String getProperty(const String&)
88    {
89        notImplemented();
90        return String();
91    }
92
93    virtual void setProperty(const String&, const String&)
94    {
95        notImplemented();
96    }
97#endif // HAVE_GSETTINGS
98};
99
100} // namespace
101
102InspectorClient::InspectorClient(WebKitWebView* webView)
103    : m_inspectedWebView(webView)
104    , m_frontendPage(0)
105    , m_frontendClient(0)
106{}
107
108InspectorClient::~InspectorClient()
109{
110    if (m_frontendClient) {
111        m_frontendClient->disconnectInspectorClient();
112        m_frontendClient = 0;
113    }
114}
115
116void InspectorClient::inspectorDestroyed()
117{
118    delete this;
119}
120
121void InspectorClient::openInspectorFrontend(InspectorController* controller)
122{
123    // This g_object_get will ref the inspector. We're not doing an
124    // unref if this method succeeds because the inspector object must
125    // be alive even after the inspected WebView is destroyed - the
126    // close-window and destroy signals still need to be
127    // emitted.
128    WebKitWebInspector* webInspector = 0;
129    g_object_get(m_inspectedWebView, "web-inspector", &webInspector, NULL);
130    ASSERT(webInspector);
131
132    WebKitWebView* inspectorWebView = 0;
133    g_signal_emit_by_name(webInspector, "inspect-web-view", m_inspectedWebView, &inspectorWebView);
134
135    if (!inspectorWebView) {
136        g_object_unref(webInspector);
137        return;
138    }
139
140    webkit_web_inspector_set_web_view(webInspector, inspectorWebView);
141
142    GOwnPtr<gchar> inspectorPath(g_build_filename(inspectorFilesPath(), "inspector.html", NULL));
143    GOwnPtr<gchar> inspectorURI(g_filename_to_uri(inspectorPath.get(), 0, 0));
144    webkit_web_view_load_uri(inspectorWebView, inspectorURI.get());
145
146    gtk_widget_show(GTK_WIDGET(inspectorWebView));
147
148    m_frontendPage = core(inspectorWebView);
149    m_frontendClient = new InspectorFrontendClient(m_inspectedWebView, inspectorWebView, webInspector, m_frontendPage, this);
150    m_frontendPage->inspectorController()->setInspectorFrontendClient(m_frontendClient);
151
152    // The inspector must be in it's own PageGroup to avoid deadlock while debugging.
153    m_frontendPage->setGroupName("");
154}
155
156void InspectorClient::releaseFrontendPage()
157{
158    m_frontendPage = 0;
159}
160
161void InspectorClient::highlight(Node*)
162{
163    hideHighlight();
164}
165
166void InspectorClient::hideHighlight()
167{
168    // FIXME: we should be able to only invalidate the actual rects of
169    // the new and old nodes. We need to track the nodes, and take the
170    // actual highlight size into account when calculating the damage
171    // rect.
172    gtk_widget_queue_draw(GTK_WIDGET(m_inspectedWebView));
173}
174
175bool InspectorClient::sendMessageToFrontend(const String& message)
176{
177    return doDispatchMessageOnFrontendPage(m_frontendPage, message);
178}
179
180const char* InspectorClient::inspectorFilesPath()
181{
182    if (m_inspectorFilesPath)
183        m_inspectorFilesPath.get();
184
185    const char* environmentPath = getenv("WEBKIT_INSPECTOR_PATH");
186    if (environmentPath && g_file_test(environmentPath, G_FILE_TEST_IS_DIR))
187        m_inspectorFilesPath.set(g_strdup(environmentPath));
188    else
189        m_inspectorFilesPath.set(g_build_filename(DATA_DIR, "webkitgtk-"WEBKITGTK_API_VERSION_STRING, "webinspector", NULL));
190
191    return m_inspectorFilesPath.get();
192}
193
194InspectorFrontendClient::InspectorFrontendClient(WebKitWebView* inspectedWebView, WebKitWebView* inspectorWebView, WebKitWebInspector* webInspector, Page* inspectorPage, InspectorClient* inspectorClient)
195    : InspectorFrontendClientLocal(core(inspectedWebView)->inspectorController(), inspectorPage, new InspectorFrontendSettingsGtk())
196    , m_inspectorWebView(inspectorWebView)
197    , m_inspectedWebView(inspectedWebView)
198    , m_webInspector(webInspector)
199    , m_inspectorClient(inspectorClient)
200{
201    g_signal_connect(m_inspectorWebView, "destroy",
202                     G_CALLBACK(notifyWebViewDestroyed), (gpointer)this);
203}
204
205InspectorFrontendClient::~InspectorFrontendClient()
206{
207    if (m_inspectorClient) {
208        m_inspectorClient->disconnectFrontendClient();
209        m_inspectorClient = 0;
210    }
211    ASSERT(!m_webInspector);
212}
213
214void InspectorFrontendClient::destroyInspectorWindow(bool notifyInspectorController)
215{
216    if (!m_webInspector)
217        return;
218    WebKitWebInspector* webInspector = m_webInspector;
219    m_webInspector = 0;
220
221    g_signal_handlers_disconnect_by_func(m_inspectorWebView, (gpointer)notifyWebViewDestroyed, (gpointer)this);
222    m_inspectorWebView = 0;
223
224    if (notifyInspectorController)
225        core(m_inspectedWebView)->inspectorController()->disconnectFrontend();
226
227    if (m_inspectorClient)
228        m_inspectorClient->releaseFrontendPage();
229
230    gboolean handled = FALSE;
231    g_signal_emit_by_name(webInspector, "close-window", &handled);
232    ASSERT(handled);
233
234    // Please do not use member variables here because InspectorFrontendClient object pointed by 'this'
235    // has been implicitly deleted by "close-window" function.
236
237    /* we should now dispose our own reference */
238    g_object_unref(webInspector);
239}
240
241String InspectorFrontendClient::localizedStringsURL()
242{
243    GOwnPtr<gchar> stringsPath(g_build_filename(m_inspectorClient->inspectorFilesPath(), "localizedStrings.js", NULL));
244    GOwnPtr<gchar> stringsURI(g_filename_to_uri(stringsPath.get(), 0, 0));
245
246    // FIXME: support l10n of localizedStrings.js
247    return String::fromUTF8(stringsURI.get());
248}
249
250String InspectorFrontendClient::hiddenPanels()
251{
252    notImplemented();
253    return String();
254}
255
256void InspectorFrontendClient::bringToFront()
257{
258    if (!m_inspectorWebView)
259        return;
260
261    gboolean handled = FALSE;
262    g_signal_emit_by_name(m_webInspector, "show-window", &handled);
263}
264
265void InspectorFrontendClient::closeWindow()
266{
267    destroyInspectorWindow(true);
268}
269
270void InspectorFrontendClient::disconnectFromBackend()
271{
272    destroyInspectorWindow(false);
273}
274
275void InspectorFrontendClient::attachWindow()
276{
277    if (!m_inspectorWebView)
278        return;
279
280    gboolean handled = FALSE;
281    g_signal_emit_by_name(m_webInspector, "attach-window", &handled);
282}
283
284void InspectorFrontendClient::detachWindow()
285{
286    if (!m_inspectorWebView)
287        return;
288
289    gboolean handled = FALSE;
290    g_signal_emit_by_name(m_webInspector, "detach-window", &handled);
291}
292
293void InspectorFrontendClient::setAttachedWindowHeight(unsigned height)
294{
295    notImplemented();
296}
297
298void InspectorFrontendClient::inspectedURLChanged(const String& newURL)
299{
300    if (!m_inspectorWebView)
301        return;
302
303    webkit_web_inspector_set_inspected_uri(m_webInspector, newURL.utf8().data());
304}
305
306}
307
308