1/*
2 * Copyright (C) 2007, 2008, 2009, 2010 Apple Inc. All rights reserved.
3 * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com>
4 * Copyright (C) 2011 Google Inc. All rights reserved.
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 *
10 * 1.  Redistributions of source code must retain the above copyright
11 *     notice, this list of conditions and the following disclaimer.
12 * 2.  Redistributions in binary form must reproduce the above copyright
13 *     notice, this list of conditions and the following disclaimer in the
14 *     documentation and/or other materials provided with the distribution.
15 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
16 *     its contributors may be used to endorse or promote products derived
17 *     from this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#include "config.h"
32#include "InspectorAgent.h"
33
34#if ENABLE(INSPECTOR)
35
36#include "Document.h"
37#include "DocumentLoader.h"
38#include "Frame.h"
39#include "GraphicsContext.h"
40#include "InjectedScriptHost.h"
41#include "InjectedScriptManager.h"
42#include "InspectorBrowserDebuggerAgent.h"
43#include "InspectorCSSAgent.h"
44#include "InspectorClient.h"
45#include "InspectorConsoleAgent.h"
46#include "InspectorController.h"
47#include "InspectorDOMAgent.h"
48#include "InspectorFrontend.h"
49#include "InspectorInstrumentation.h"
50#include "InspectorPageAgent.h"
51#include "InspectorProfilerAgent.h"
52#include "InspectorResourceAgent.h"
53#include "InspectorRuntimeAgent.h"
54#include "InspectorState.h"
55#include "InspectorTimelineAgent.h"
56#include "InspectorValues.h"
57#include "InspectorWorkerResource.h"
58#include "InstrumentingAgents.h"
59#include "Page.h"
60#include "PageDebuggerAgent.h"
61#include "ResourceRequest.h"
62#include "ScriptFunctionCall.h"
63#include "ScriptObject.h"
64#include "ScriptState.h"
65#include "Settings.h"
66
67#if ENABLE(DATABASE)
68#include "InspectorDatabaseAgent.h"
69#endif
70
71#if ENABLE(DOM_STORAGE)
72#include "InspectorDOMStorageAgent.h"
73#endif
74
75#if ENABLE(OFFLINE_WEB_APPLICATIONS)
76#include "InspectorApplicationCacheAgent.h"
77#endif
78
79using namespace std;
80
81namespace WebCore {
82
83namespace InspectorAgentState {
84static const char timelineProfilerEnabled[] = "timelineProfilerEnabled";
85static const char debuggerEnabled[] = "debuggerEnabled";
86}
87
88static const char scriptsPanelName[] = "scripts";
89static const char consolePanelName[] = "console";
90static const char profilesPanelName[] = "profiles";
91
92namespace {
93
94class PageRuntimeAgent : public InspectorRuntimeAgent {
95public:
96    PageRuntimeAgent(InjectedScriptManager* injectedScriptManager, Page* page)
97        : InspectorRuntimeAgent(injectedScriptManager)
98        , m_inspectedPage(page) { }
99    virtual ~PageRuntimeAgent() { }
100
101private:
102    virtual ScriptState* getDefaultInspectedState() { return mainWorldScriptState(m_inspectedPage->mainFrame()); }
103    Page* m_inspectedPage;
104};
105
106}
107
108InspectorAgent::InspectorAgent(Page* page, InspectorClient* client, InjectedScriptManager* injectedScriptManager)
109    : m_inspectedPage(page)
110    , m_client(client)
111    , m_frontend(0)
112    , m_instrumentingAgents(new InstrumentingAgents())
113    , m_injectedScriptManager(injectedScriptManager)
114    , m_state(new InspectorState(client))
115    , m_pageAgent(InspectorPageAgent::create(m_instrumentingAgents.get(), page, injectedScriptManager))
116    , m_domAgent(InspectorDOMAgent::create(m_instrumentingAgents.get(), page, m_client, m_state.get(), injectedScriptManager))
117    , m_cssAgent(new InspectorCSSAgent(m_instrumentingAgents.get(), m_domAgent.get()))
118#if ENABLE(DATABASE)
119    , m_databaseAgent(InspectorDatabaseAgent::create(m_instrumentingAgents.get(), m_state.get()))
120#endif
121#if ENABLE(DOM_STORAGE)
122    , m_domStorageAgent(InspectorDOMStorageAgent::create(m_instrumentingAgents.get()))
123#endif
124    , m_timelineAgent(InspectorTimelineAgent::create(m_instrumentingAgents.get(), m_state.get()))
125#if ENABLE(OFFLINE_WEB_APPLICATIONS)
126    , m_applicationCacheAgent(new InspectorApplicationCacheAgent(m_instrumentingAgents.get(), page))
127#endif
128    , m_resourceAgent(InspectorResourceAgent::create(m_instrumentingAgents.get(), page, m_state.get()))
129    , m_runtimeAgent(adoptPtr(new PageRuntimeAgent(m_injectedScriptManager, page)))
130    , m_consoleAgent(new InspectorConsoleAgent(m_instrumentingAgents.get(), this, m_state.get(), injectedScriptManager, m_domAgent.get()))
131#if ENABLE(JAVASCRIPT_DEBUGGER)
132    , m_debuggerAgent(PageDebuggerAgent::create(m_instrumentingAgents.get(), m_state.get(), page, injectedScriptManager))
133    , m_browserDebuggerAgent(InspectorBrowserDebuggerAgent::create(m_instrumentingAgents.get(), m_state.get(), m_domAgent.get(), m_debuggerAgent.get(), this))
134    , m_profilerAgent(InspectorProfilerAgent::create(m_instrumentingAgents.get(), m_consoleAgent.get(), page, m_state.get()))
135#endif
136    , m_canIssueEvaluateForTestInFrontend(false)
137{
138    ASSERT_ARG(page, page);
139    ASSERT_ARG(client, client);
140    InspectorInstrumentation::bindInspectorAgent(m_inspectedPage, this);
141    m_instrumentingAgents->setInspectorAgent(this);
142
143    m_injectedScriptManager->injectedScriptHost()->init(this
144        , m_consoleAgent.get()
145#if ENABLE(DATABASE)
146        , m_databaseAgent.get()
147#endif
148#if ENABLE(DOM_STORAGE)
149        , m_domStorageAgent.get()
150#endif
151#if ENABLE(JAVASCRIPT_DEBUGGER)
152        , m_debuggerAgent.get()
153#endif
154    );
155}
156
157InspectorAgent::~InspectorAgent()
158{
159    m_instrumentingAgents->setInspectorAgent(0);
160
161    // These should have been cleared in inspectedPageDestroyed().
162    ASSERT(!m_client);
163    ASSERT(!m_inspectedPage);
164}
165
166void InspectorAgent::inspectedPageDestroyed()
167{
168    if (m_frontend) {
169        m_frontend->inspector()->disconnectFromBackend();
170        disconnectFrontend();
171    }
172
173#if ENABLE(JAVASCRIPT_DEBUGGER)
174    m_browserDebuggerAgent.clear();
175    m_debuggerAgent.clear();
176#endif
177
178    ASSERT(m_inspectedPage);
179    InspectorInstrumentation::unbindInspectorAgent(m_inspectedPage);
180    m_inspectedPage = 0;
181
182    m_injectedScriptManager->disconnect();
183
184    m_client->inspectorDestroyed();
185    m_client = 0;
186}
187
188void InspectorAgent::restoreInspectorStateFromCookie(const String& inspectorStateCookie)
189{
190    m_state->loadFromCookie(inspectorStateCookie);
191
192    m_frontend->inspector()->frontendReused();
193    m_pageAgent->restore();
194
195    m_domAgent->restore();
196    m_resourceAgent->restore();
197    m_timelineAgent->restore();
198
199#if ENABLE(DATABASE)
200    m_databaseAgent->restore();
201#endif
202
203#if ENABLE(JAVASCRIPT_DEBUGGER)
204    m_debuggerAgent->restore();
205    m_profilerAgent->restore();
206#endif
207}
208
209void InspectorAgent::didClearWindowObjectInWorld(Frame* frame, DOMWrapperWorld* world)
210{
211    if (world != mainThreadNormalWorld())
212        return;
213
214    if (!m_inspectorExtensionAPI.isEmpty())
215        m_injectedScriptManager->injectScript(m_inspectorExtensionAPI, mainWorldScriptState(frame));
216}
217
218void InspectorAgent::setFrontend(InspectorFrontend* inspectorFrontend)
219{
220    // We can reconnect to existing front-end -> unmute state.
221    m_state->unmute();
222
223    m_frontend = inspectorFrontend;
224
225#if ENABLE(OFFLINE_WEB_APPLICATIONS)
226    m_applicationCacheAgent->setFrontend(m_frontend);
227#endif
228    m_pageAgent->setFrontend(m_frontend);
229    m_domAgent->setFrontend(m_frontend);
230    m_consoleAgent->setFrontend(m_frontend);
231    m_timelineAgent->setFrontend(m_frontend);
232    m_resourceAgent->setFrontend(m_frontend);
233#if ENABLE(JAVASCRIPT_DEBUGGER)
234    m_debuggerAgent->setFrontend(m_frontend);
235    m_profilerAgent->setFrontend(m_frontend);
236#endif
237#if ENABLE(DATABASE)
238    m_databaseAgent->setFrontend(m_frontend);
239#endif
240#if ENABLE(DOM_STORAGE)
241    m_domStorageAgent->setFrontend(m_frontend);
242#endif
243
244    if (!m_showPanelAfterVisible.isEmpty()) {
245        m_frontend->inspector()->showPanel(m_showPanelAfterVisible);
246        m_showPanelAfterVisible = String();
247    }
248#if ENABLE(JAVASCRIPT_DEBUGGER) && ENABLE(WORKERS)
249    WorkersMap::iterator workersEnd = m_workers.end();
250    for (WorkersMap::iterator it = m_workers.begin(); it != workersEnd; ++it) {
251        InspectorWorkerResource* worker = it->second.get();
252        m_frontend->inspector()->didCreateWorker(worker->id(), worker->url(), worker->isSharedWorker());
253    }
254#endif
255    // Dispatch pending frontend commands
256    issueEvaluateForTestCommands();
257}
258
259void InspectorAgent::disconnectFrontend()
260{
261    if (!m_frontend)
262        return;
263
264    m_canIssueEvaluateForTestInFrontend = false;
265    m_pendingEvaluateTestCommands.clear();
266
267    // Destroying agents would change the state, but we don't want that.
268    // Pre-disconnect state will be used to restore inspector agents.
269    m_state->mute();
270
271    m_frontend = 0;
272
273#if ENABLE(JAVASCRIPT_DEBUGGER)
274    m_debuggerAgent->clearFrontend();
275    m_browserDebuggerAgent->clearFrontend();
276    m_profilerAgent->clearFrontend();
277#endif
278
279#if ENABLE(OFFLINE_WEB_APPLICATIONS)
280    m_applicationCacheAgent->clearFrontend();
281#endif
282
283    m_consoleAgent->clearFrontend();
284    m_domAgent->clearFrontend();
285    m_timelineAgent->clearFrontend();
286    m_resourceAgent->clearFrontend();
287#if ENABLE(DATABASE)
288    m_databaseAgent->clearFrontend();
289#endif
290#if ENABLE(DOM_STORAGE)
291    m_domStorageAgent->clearFrontend();
292#endif
293    m_pageAgent->clearFrontend();
294}
295
296void InspectorAgent::didCommitLoad()
297{
298    if (m_frontend)
299        m_frontend->inspector()->reset();
300
301    m_injectedScriptManager->discardInjectedScripts();
302#if ENABLE(WORKERS)
303    m_workers.clear();
304#endif
305}
306
307void InspectorAgent::domContentLoadedEventFired()
308{
309    m_injectedScriptManager->injectedScriptHost()->clearInspectedNodes();
310}
311
312bool InspectorAgent::isMainResourceLoader(DocumentLoader* loader, const KURL& requestUrl)
313{
314    return loader->frame() == m_inspectedPage->mainFrame() && requestUrl == loader->requestURL();
315}
316
317#if ENABLE(WORKERS)
318class PostWorkerNotificationToFrontendTask : public ScriptExecutionContext::Task {
319public:
320    static PassOwnPtr<PostWorkerNotificationToFrontendTask> create(PassRefPtr<InspectorWorkerResource> worker, InspectorAgent::WorkerAction action)
321    {
322        return new PostWorkerNotificationToFrontendTask(worker, action);
323    }
324
325private:
326    PostWorkerNotificationToFrontendTask(PassRefPtr<InspectorWorkerResource> worker, InspectorAgent::WorkerAction action)
327        : m_worker(worker)
328        , m_action(action)
329    {
330    }
331
332    virtual void performTask(ScriptExecutionContext* scriptContext)
333    {
334        if (scriptContext->isDocument()) {
335            if (InspectorAgent* inspectorAgent = static_cast<Document*>(scriptContext)->page()->inspectorController()->m_inspectorAgent.get())
336                inspectorAgent->postWorkerNotificationToFrontend(*m_worker, m_action);
337        }
338    }
339
340private:
341    RefPtr<InspectorWorkerResource> m_worker;
342    InspectorAgent::WorkerAction m_action;
343};
344
345void InspectorAgent::postWorkerNotificationToFrontend(const InspectorWorkerResource& worker, InspectorAgent::WorkerAction action)
346{
347    if (!m_frontend)
348        return;
349#if ENABLE(JAVASCRIPT_DEBUGGER)
350    switch (action) {
351    case InspectorAgent::WorkerCreated:
352        m_frontend->inspector()->didCreateWorker(worker.id(), worker.url(), worker.isSharedWorker());
353        break;
354    case InspectorAgent::WorkerDestroyed:
355        m_frontend->inspector()->didDestroyWorker(worker.id());
356        break;
357    }
358#endif
359}
360
361void InspectorAgent::didCreateWorker(intptr_t id, const String& url, bool isSharedWorker)
362{
363    if (!enabled())
364        return;
365
366    RefPtr<InspectorWorkerResource> workerResource(InspectorWorkerResource::create(id, url, isSharedWorker));
367    m_workers.set(id, workerResource);
368    if (m_inspectedPage && m_frontend)
369        m_inspectedPage->mainFrame()->document()->postTask(PostWorkerNotificationToFrontendTask::create(workerResource, InspectorAgent::WorkerCreated));
370}
371
372void InspectorAgent::didDestroyWorker(intptr_t id)
373{
374    if (!enabled())
375        return;
376
377    WorkersMap::iterator workerResource = m_workers.find(id);
378    if (workerResource == m_workers.end())
379        return;
380    if (m_inspectedPage && m_frontend)
381        m_inspectedPage->mainFrame()->document()->postTask(PostWorkerNotificationToFrontendTask::create(workerResource->second, InspectorAgent::WorkerDestroyed));
382    m_workers.remove(workerResource);
383}
384#endif // ENABLE(WORKERS)
385
386#if ENABLE(JAVASCRIPT_DEBUGGER)
387void InspectorAgent::showProfilesPanel()
388{
389    showPanel(profilesPanelName);
390}
391#endif
392
393void InspectorAgent::evaluateForTestInFrontend(long callId, const String& script)
394{
395    m_pendingEvaluateTestCommands.append(pair<long, String>(callId, script));
396    if (m_canIssueEvaluateForTestInFrontend)
397        issueEvaluateForTestCommands();
398}
399
400void InspectorAgent::setInspectorExtensionAPI(const String& source)
401{
402    m_inspectorExtensionAPI = source;
403}
404
405KURL InspectorAgent::inspectedURL() const
406{
407    return m_inspectedPage->mainFrame()->document()->url();
408}
409
410KURL InspectorAgent::inspectedURLWithoutFragment() const
411{
412    KURL url = inspectedURL();
413    url.removeFragmentIdentifier();
414    return url;
415}
416
417bool InspectorAgent::enabled() const
418{
419    if (!m_inspectedPage)
420        return false;
421    return m_inspectedPage->settings()->developerExtrasEnabled();
422}
423
424void InspectorAgent::showConsole()
425{
426    showPanel(consolePanelName);
427}
428
429void InspectorAgent::showPanel(const String& panel)
430{
431    if (!m_frontend) {
432        m_showPanelAfterVisible = panel;
433        return;
434    }
435    m_frontend->inspector()->showPanel(panel);
436}
437
438void InspectorAgent::issueEvaluateForTestCommands()
439{
440    if (m_frontend) {
441        Vector<pair<long, String> > copy = m_pendingEvaluateTestCommands;
442        m_pendingEvaluateTestCommands.clear();
443        for (Vector<pair<long, String> >::iterator it = copy.begin(); m_frontend && it != copy.end(); ++it)
444            m_frontend->inspector()->evaluateForTestInFrontend((*it).first, (*it).second);
445        m_canIssueEvaluateForTestInFrontend = true;
446    }
447}
448
449} // namespace WebCore
450
451#endif // ENABLE(INSPECTOR)
452