3cb77d0ff639117f1d248f2be0cc4d202b937e75
[WebKit-https.git] / Source / WebCore / inspector / InspectorAgent.cpp
1 /*
2  * Copyright (C) 2007, 2008, 2009, 2010 Apple Inc. All rights reserved.
3  * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com>
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
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  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15  *     its contributors may be used to endorse or promote products derived
16  *     from this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 #include "config.h"
31 #include "InspectorAgent.h"
32
33 #if ENABLE(INSPECTOR)
34
35 #include "CachedResource.h"
36 #include "CachedResourceLoader.h"
37 #include "Chrome.h"
38 #include "Cookie.h"
39 #include "CookieJar.h"
40 #include "DOMWindow.h"
41 #include "DOMWrapperWorld.h"
42 #include "Document.h"
43 #include "DocumentLoader.h"
44 #include "Element.h"
45 #include "FloatConversion.h"
46 #include "FloatQuad.h"
47 #include "FloatRect.h"
48 #include "Frame.h"
49 #include "FrameLoadRequest.h"
50 #include "FrameLoader.h"
51 #include "FrameTree.h"
52 #include "FrameView.h"
53 #include "GraphicsContext.h"
54 #include "HTMLFrameOwnerElement.h"
55 #include "HTTPHeaderMap.h"
56 #include "HitTestResult.h"
57 #include "InjectedScript.h"
58 #include "InjectedScriptHost.h"
59 #include "InspectorBrowserDebuggerAgent.h"
60 #include "InspectorCSSAgent.h"
61 #include "InspectorClient.h"
62 #include "InspectorConsoleAgent.h"
63 #include "InspectorController.h"
64 #include "InspectorDOMAgent.h"
65 #include "InspectorDebuggerAgent.h"
66 #include "InspectorFrontend.h"
67 #include "InspectorFrontendClient.h"
68 #include "InspectorInstrumentation.h"
69 #include "InspectorProfilerAgent.h"
70 #include "InspectorResourceAgent.h"
71 #include "InspectorRuntimeAgent.h"
72 #include "InspectorState.h"
73 #include "InspectorTimelineAgent.h"
74 #include "InspectorValues.h"
75 #include "InspectorWorkerResource.h"
76 #include "InstrumentingAgents.h"
77 #include "IntRect.h"
78 #include "Page.h"
79 #include "ProgressTracker.h"
80 #include "Range.h"
81 #include "RenderInline.h"
82 #include "ResourceRequest.h"
83 #include "ResourceResponse.h"
84 #include "ScriptArguments.h"
85 #include "ScriptCallStack.h"
86 #include "ScriptFunctionCall.h"
87 #include "ScriptObject.h"
88 #include "ScriptProfile.h"
89 #include "ScriptProfiler.h"
90 #include "ScriptSourceCode.h"
91 #include "ScriptState.h"
92 #include "SecurityOrigin.h"
93 #include "Settings.h"
94 #include "SharedBuffer.h"
95 #include "TextEncoding.h"
96 #include "TextIterator.h"
97 #include "TextRun.h"
98 #include "UserGestureIndicator.h"
99 #include "WindowFeatures.h"
100 #include <wtf/CurrentTime.h>
101 #include <wtf/ListHashSet.h>
102 #include <wtf/RefCounted.h>
103 #include <wtf/StdLibExtras.h>
104 #include <wtf/UnusedParam.h>
105 #include <wtf/text/StringConcatenate.h>
106
107 #if ENABLE(DATABASE)
108 #include "InspectorDatabaseAgent.h"
109 #endif
110
111 #if ENABLE(DOM_STORAGE)
112 #include "InspectorDOMStorageAgent.h"
113 #endif
114
115 #if ENABLE(OFFLINE_WEB_APPLICATIONS)
116 #include "InspectorApplicationCacheAgent.h"
117 #endif
118
119 using namespace std;
120
121 namespace WebCore {
122
123 namespace InspectorAgentState {
124 static const char searchingForNode[] = "searchingForNode";
125 static const char timelineProfilerEnabled[] = "timelineProfilerEnabled";
126 static const char userInitiatedProfiling[] = "userInitiatedProfiling";
127 static const char debuggerEnabled[] = "debuggerEnabled";
128 static const char profilerEnabled[] = "profilerEnabled";
129 }
130
131 static const char scriptsPanelName[] = "scripts";
132 static const char consolePanelName[] = "console";
133 static const char profilesPanelName[] = "profiles";
134
135 InspectorAgent::InspectorAgent(Page* page, InspectorClient* client)
136     : m_inspectedPage(page)
137     , m_client(client)
138     , m_frontend(0)
139     , m_instrumentingAgents(new InstrumentingAgents())
140     , m_injectedScriptHost(InjectedScriptHost::create(this))
141     , m_domAgent(InspectorDOMAgent::create(m_instrumentingAgents.get(), m_injectedScriptHost.get()))
142     , m_cssAgent(new InspectorCSSAgent(m_domAgent.get()))
143 #if ENABLE(DATABASE)
144     , m_databaseAgent(InspectorDatabaseAgent::create(m_instrumentingAgents.get()))
145 #endif
146 #if ENABLE(DOM_STORAGE)
147     , m_domStorageAgent(InspectorDOMStorageAgent::create(m_instrumentingAgents.get()))
148 #endif
149     , m_state(new InspectorState(client))
150     , m_timelineAgent(InspectorTimelineAgent::create(m_instrumentingAgents.get(), m_state.get()))
151     , m_consoleAgent(new InspectorConsoleAgent(m_instrumentingAgents.get(), this, m_state.get(), m_injectedScriptHost.get(), m_domAgent.get()))
152 #if ENABLE(JAVASCRIPT_DEBUGGER)
153     , m_profilerAgent(InspectorProfilerAgent::create(this))
154 #endif
155 {
156     ASSERT_ARG(page, page);
157     ASSERT_ARG(client, client);
158     InspectorInstrumentation::bindInspectorAgent(m_inspectedPage, this);
159 }
160
161 InspectorAgent::~InspectorAgent()
162 {
163     // These should have been cleared in inspectedPageDestroyed().
164     ASSERT(!m_client);
165     ASSERT(!m_inspectedPage);
166     ASSERT(!m_highlightedNode);
167 }
168
169 void InspectorAgent::inspectedPageDestroyed()
170 {
171     if (m_frontend)
172         m_frontend->disconnectFromBackend();
173
174     hideHighlight();
175
176 #if ENABLE(JAVASCRIPT_DEBUGGER)
177     m_debuggerAgent.clear();
178     m_browserDebuggerAgent.clear();
179 #endif
180
181     ASSERT(m_inspectedPage);
182     InspectorInstrumentation::unbindInspectorAgent(m_inspectedPage);
183     m_inspectedPage = 0;
184
185     releaseFrontendLifetimeAgents();
186     m_injectedScriptHost->disconnectController();
187
188     m_client->inspectorDestroyed();
189     m_client = 0;
190 }
191
192 bool InspectorAgent::searchingForNodeInPage() const
193 {
194     return m_state->getBoolean(InspectorAgentState::searchingForNode);
195 }
196
197 void InspectorAgent::restoreInspectorStateFromCookie(const String& inspectorStateCookie)
198 {
199     m_state->loadFromCookie(inspectorStateCookie);
200
201     m_frontend->frontendReused();
202     m_frontend->inspectedURLChanged(inspectedURL().string());
203     pushDataCollectedOffline();
204
205     m_resourceAgent = InspectorResourceAgent::restore(m_inspectedPage, m_state.get(), m_frontend);
206     m_timelineAgent->restore(m_state.get(), m_frontend);
207
208 #if ENABLE(JAVASCRIPT_DEBUGGER)
209     restoreDebugger(false);
210     restoreProfiler(ProfilerRestoreResetAgent);
211     if (m_state->getBoolean(InspectorAgentState::userInitiatedProfiling))
212         startUserInitiatedProfiling();
213 #endif
214 }
215
216 void InspectorAgent::inspect(Node* node)
217 {
218     if (node->nodeType() != Node::ELEMENT_NODE && node->nodeType() != Node::DOCUMENT_NODE)
219         node = node->parentNode();
220     m_nodeToFocus = node;
221
222     if (!m_frontend)
223         return;
224
225     focusNode();
226 }
227
228 void InspectorAgent::focusNode()
229 {
230     if (!enabled())
231         return;
232
233     ASSERT(m_frontend);
234     ASSERT(m_nodeToFocus);
235
236     m_domAgent->inspect(m_nodeToFocus.get());
237     m_nodeToFocus = 0;
238 }
239
240 void InspectorAgent::highlight(Node* node)
241 {
242     if (!enabled())
243         return;
244     ASSERT_ARG(node, node);
245     m_highlightedNode = node;
246     m_client->highlight(node);
247 }
248
249 void InspectorAgent::highlightDOMNode(long nodeId)
250 {
251     Node* node = 0;
252     if (m_domAgent && (node = m_domAgent->nodeForId(nodeId)))
253         highlight(node);
254 }
255
256 void InspectorAgent::highlightFrame(unsigned long frameId)
257 {
258     Frame* mainFrame = m_inspectedPage->mainFrame();
259     for (Frame* frame = mainFrame; frame; frame = frame->tree()->traverseNext(mainFrame)) {
260         if (reinterpret_cast<uintptr_t>(frame) == frameId && frame->ownerElement()) {
261             highlight(frame->ownerElement());
262             return;
263         }
264     }
265 }
266
267 void InspectorAgent::hideHighlight()
268 {
269     if (!enabled())
270         return;
271     m_highlightedNode = 0;
272     m_client->hideHighlight();
273 }
274
275 void InspectorAgent::mouseDidMoveOverElement(const HitTestResult& result, unsigned)
276 {
277     if (!enabled() || !searchingForNodeInPage())
278         return;
279
280     Node* node = result.innerNode();
281     while (node && node->nodeType() == Node::TEXT_NODE)
282         node = node->parentNode();
283     if (node)
284         highlight(node);
285 }
286
287 bool InspectorAgent::handleMousePress()
288 {
289     if (!enabled() || !searchingForNodeInPage())
290         return false;
291
292     if (m_highlightedNode) {
293         RefPtr<Node> node = m_highlightedNode;
294         setSearchingForNode(false);
295         inspect(node.get());
296     }
297     return true;
298 }
299
300 void InspectorAgent::didClearWindowObjectInWorld(Frame* frame, DOMWrapperWorld* world)
301 {
302     if (world != mainThreadNormalWorld())
303         return;
304
305     if (enabled()) {
306         if (m_frontend && frame == m_inspectedPage->mainFrame())
307             m_injectedScriptHost->discardInjectedScripts();
308
309         if (m_scriptsToEvaluateOnLoad.size()) {
310             ScriptState* scriptState = mainWorldScriptState(frame);
311             for (Vector<String>::iterator it = m_scriptsToEvaluateOnLoad.begin();
312                   it != m_scriptsToEvaluateOnLoad.end(); ++it) {
313                 m_injectedScriptHost->injectScript(*it, scriptState);
314             }
315         }
316     }
317
318     if (!m_inspectorExtensionAPI.isEmpty())
319         m_injectedScriptHost->injectScript(m_inspectorExtensionAPI, mainWorldScriptState(frame));
320 }
321
322 void InspectorAgent::setSearchingForNode(bool enabled)
323 {
324     if (searchingForNodeInPage() == enabled)
325         return;
326     m_state->setBoolean(InspectorAgentState::searchingForNode, enabled);
327     if (!enabled)
328         hideHighlight();
329 }
330
331 void InspectorAgent::setSearchingForNode(bool enabled, bool* newState)
332 {
333     *newState = enabled;
334     setSearchingForNode(enabled);
335 }
336
337 void InspectorAgent::setFrontend(InspectorFrontend* inspectorFrontend)
338 {
339     // We can reconnect to existing front-end -> unmute state.
340     m_state->unmute();
341
342     m_frontend = inspectorFrontend;
343     createFrontendLifetimeAgents();
344
345     m_domAgent->setFrontend(m_frontend);
346     m_consoleAgent->setFrontend(m_frontend);
347     m_timelineAgent->setFrontend(m_frontend);
348 #if ENABLE(DATABASE)
349     m_databaseAgent->setFrontend(m_frontend);
350 #endif
351 #if ENABLE(DOM_STORAGE)
352     m_domStorageAgent->setFrontend(m_frontend);
353 #endif
354     // Initialize Web Inspector title.
355     m_frontend->inspectedURLChanged(inspectedURL().string());
356 }
357
358 void InspectorAgent::disconnectFrontend()
359 {
360     if (!m_frontend)
361         return;
362
363     // Destroying agents would change the state, but we don't want that.
364     // Pre-disconnect state will be used to restore inspector agents.
365     m_state->mute();
366
367     m_frontend = 0;
368
369 #if ENABLE(JAVASCRIPT_DEBUGGER)
370     // If the window is being closed with the debugger enabled,
371     // remember this state to re-enable debugger on the next window
372     // opening.
373     disableDebugger();
374 #endif
375     setSearchingForNode(false);
376
377     hideHighlight();
378
379 #if ENABLE(JAVASCRIPT_DEBUGGER)
380     m_profilerAgent->setFrontend(0);
381     m_profilerAgent->stopUserInitiatedProfiling(true);
382 #endif
383
384     m_consoleAgent->clearFrontend();
385     m_domAgent->clearFrontend();
386     m_timelineAgent->clearFrontend();
387 #if ENABLE(DATABASE)
388     m_databaseAgent->clearFrontend();
389 #endif
390 #if ENABLE(DOM_STORAGE)
391     m_domStorageAgent->clearFrontend();
392 #endif
393
394     releaseFrontendLifetimeAgents();
395     m_userAgentOverride = "";
396 }
397
398 InspectorResourceAgent* InspectorAgent::resourceAgent()
399 {
400     if (!m_resourceAgent && m_frontend)
401         m_resourceAgent = InspectorResourceAgent::create(m_inspectedPage, m_state.get(), m_frontend);
402     return m_resourceAgent.get();
403 }
404
405 void InspectorAgent::createFrontendLifetimeAgents()
406 {
407     m_runtimeAgent = InspectorRuntimeAgent::create(m_injectedScriptHost.get());
408
409 #if ENABLE(OFFLINE_WEB_APPLICATIONS)
410     m_applicationCacheAgent = new InspectorApplicationCacheAgent(m_inspectedPage->mainFrame()->loader()->documentLoader(), m_frontend);
411 #endif
412 }
413
414 void InspectorAgent::releaseFrontendLifetimeAgents()
415 {
416     m_resourceAgent.clear();
417     m_runtimeAgent.clear();
418 #if ENABLE(OFFLINE_WEB_APPLICATIONS)
419     m_applicationCacheAgent.clear();
420 #endif
421 }
422
423 void InspectorAgent::populateScriptObjects()
424 {
425     ASSERT(m_frontend);
426     if (!m_frontend)
427         return;
428
429 #if ENABLE(JAVASCRIPT_DEBUGGER)
430     if (m_profilerAgent->enabled())
431         m_frontend->profilerWasEnabled();
432 #endif
433
434     pushDataCollectedOffline();
435
436     if (m_nodeToFocus)
437         focusNode();
438
439     if (!m_showPanelAfterVisible.isEmpty()) {
440         m_frontend->showPanel(m_showPanelAfterVisible);
441         m_showPanelAfterVisible = "";
442     }
443
444     restoreDebugger(true);
445     restoreProfiler(ProfilerRestoreNoAction);
446
447     // Dispatch pending frontend commands
448     for (Vector<pair<long, String> >::iterator it = m_pendingEvaluateTestCommands.begin(); it != m_pendingEvaluateTestCommands.end(); ++it)
449         m_frontend->evaluateForTestInFrontend((*it).first, (*it).second);
450     m_pendingEvaluateTestCommands.clear();
451 }
452
453 void InspectorAgent::pushDataCollectedOffline()
454 {
455     m_domAgent->setDocument(m_inspectedPage->mainFrame()->document());
456
457 #if ENABLE(JAVASCRIPT_DEBUGGER) && ENABLE(WORKERS)
458     WorkersMap::iterator workersEnd = m_workers.end();
459     for (WorkersMap::iterator it = m_workers.begin(); it != workersEnd; ++it) {
460         InspectorWorkerResource* worker = it->second.get();
461         m_frontend->didCreateWorker(worker->id(), worker->url(), worker->isSharedWorker());
462     }
463 #endif
464 }
465
466 void InspectorAgent::restoreDebugger(bool eraseStickyBreakpoints)
467 {
468     ASSERT(m_frontend);
469 #if ENABLE(JAVASCRIPT_DEBUGGER)
470     if (m_state->getBoolean(InspectorAgentState::debuggerEnabled))
471         enableDebugger(eraseStickyBreakpoints);
472 #endif
473 }
474
475 void InspectorAgent::restoreProfiler(ProfilerRestoreAction action)
476 {
477     ASSERT(m_frontend);
478 #if ENABLE(JAVASCRIPT_DEBUGGER)
479     m_profilerAgent->setFrontend(m_frontend);
480     if (m_state->getBoolean(InspectorAgentState::profilerEnabled))
481         enableProfiler();
482     if (action == ProfilerRestoreResetAgent)
483         m_profilerAgent->resetFrontendProfiles();
484 #endif
485 }
486
487 void InspectorAgent::didCommitLoad(DocumentLoader* loader)
488 {
489     if (!enabled())
490         return;
491
492     if (m_resourceAgent)
493         m_resourceAgent->didCommitLoad(loader);
494
495     ASSERT(m_inspectedPage);
496
497     if (loader->frame() == m_inspectedPage->mainFrame()) {
498         if (m_frontend)
499             m_frontend->inspectedURLChanged(loader->url().string());
500
501         m_injectedScriptHost->discardInjectedScripts();
502         m_consoleAgent->reset();
503
504         if (InspectorTimelineAgent* timelineAgent = m_instrumentingAgents->inspectorTimelineAgent())
505             timelineAgent->didCommitLoad();
506
507 #if ENABLE(OFFLINE_WEB_APPLICATIONS)
508         if (m_applicationCacheAgent)
509             m_applicationCacheAgent->didCommitLoad(loader);
510 #endif
511
512 #if ENABLE(JAVASCRIPT_DEBUGGER)
513         if (m_debuggerAgent) {
514             KURL url = inspectedURLWithoutFragment();
515             m_debuggerAgent->inspectedURLChanged(url);
516             if (m_browserDebuggerAgent)
517                 m_browserDebuggerAgent->inspectedURLChanged(url);
518         }
519 #endif
520
521 #if ENABLE(JAVASCRIPT_DEBUGGER) && USE(JSC)
522         m_profilerAgent->stopUserInitiatedProfiling(true);
523         m_profilerAgent->resetState();
524 #endif
525
526         if (m_frontend) {
527             m_frontend->reset();
528             m_domAgent->reset();
529             m_cssAgent->reset();
530         }
531 #if ENABLE(WORKERS)
532         m_workers.clear();
533 #endif
534 #if ENABLE(DATABASE)
535         m_databaseAgent->clearResources();
536 #endif
537 #if ENABLE(DOM_STORAGE)
538         m_domStorageAgent->clearResources();
539 #endif
540         if (InspectorDOMAgent* domAgent = m_instrumentingAgents->inspectorDOMAgent())
541             domAgent->setDocument(m_inspectedPage->mainFrame()->document());
542     }
543 }
544
545 void InspectorAgent::domContentLoadedEventFired(DocumentLoader* loader, const KURL& url)
546 {
547     if (!enabled() || !isMainResourceLoader(loader, url))
548         return;
549
550     if (InspectorDOMAgent* domAgent = m_instrumentingAgents->inspectorDOMAgent())
551         domAgent->mainFrameDOMContentLoaded();
552     if (InspectorTimelineAgent* timelineAgent = m_instrumentingAgents->inspectorTimelineAgent())
553         timelineAgent->didMarkDOMContentEvent();
554     if (m_frontend)
555         m_frontend->domContentEventFired(currentTime());
556 }
557
558 void InspectorAgent::loadEventFired(DocumentLoader* loader, const KURL& url)
559 {
560     if (!enabled())
561         return;
562
563     if (InspectorDOMAgent* domAgent = m_instrumentingAgents->inspectorDOMAgent())
564         domAgent->loadEventFired(loader->frame()->document());
565
566     if (!isMainResourceLoader(loader, url))
567         return;
568
569     if (InspectorTimelineAgent* timelineAgent = m_instrumentingAgents->inspectorTimelineAgent())
570         timelineAgent->didMarkLoadEvent();
571     if (m_frontend)
572         m_frontend->loadEventFired(currentTime());
573 }
574
575 bool InspectorAgent::isMainResourceLoader(DocumentLoader* loader, const KURL& requestUrl)
576 {
577     return loader->frame() == m_inspectedPage->mainFrame() && requestUrl == loader->requestURL();
578 }
579
580 void InspectorAgent::setUserAgentOverride(const String& userAgent)
581 {
582     m_userAgentOverride = userAgent;
583 }
584
585 void InspectorAgent::applyUserAgentOverride(String* userAgent) const
586 {
587     if (!m_userAgentOverride.isEmpty())
588         *userAgent = m_userAgentOverride;
589 }
590
591 #if ENABLE(WORKERS)
592 class PostWorkerNotificationToFrontendTask : public ScriptExecutionContext::Task {
593 public:
594     static PassOwnPtr<PostWorkerNotificationToFrontendTask> create(PassRefPtr<InspectorWorkerResource> worker, InspectorAgent::WorkerAction action)
595     {
596         return new PostWorkerNotificationToFrontendTask(worker, action);
597     }
598
599 private:
600     PostWorkerNotificationToFrontendTask(PassRefPtr<InspectorWorkerResource> worker, InspectorAgent::WorkerAction action)
601         : m_worker(worker)
602         , m_action(action)
603     {
604     }
605
606     virtual void performTask(ScriptExecutionContext* scriptContext)
607     {
608         if (scriptContext->isDocument()) {
609             if (InspectorAgent* inspectorAgent = static_cast<Document*>(scriptContext)->page()->inspectorController()->m_inspectorAgent.get())
610                 inspectorAgent->postWorkerNotificationToFrontend(*m_worker, m_action);
611         }
612     }
613
614 private:
615     RefPtr<InspectorWorkerResource> m_worker;
616     InspectorAgent::WorkerAction m_action;
617 };
618
619 void InspectorAgent::postWorkerNotificationToFrontend(const InspectorWorkerResource& worker, InspectorAgent::WorkerAction action)
620 {
621     if (!m_frontend)
622         return;
623 #if ENABLE(JAVASCRIPT_DEBUGGER)
624     switch (action) {
625     case InspectorAgent::WorkerCreated:
626         m_frontend->didCreateWorker(worker.id(), worker.url(), worker.isSharedWorker());
627         break;
628     case InspectorAgent::WorkerDestroyed:
629         m_frontend->didDestroyWorker(worker.id());
630         break;
631     }
632 #endif
633 }
634
635 void InspectorAgent::didCreateWorker(intptr_t id, const String& url, bool isSharedWorker)
636 {
637     if (!enabled())
638         return;
639
640     RefPtr<InspectorWorkerResource> workerResource(InspectorWorkerResource::create(id, url, isSharedWorker));
641     m_workers.set(id, workerResource);
642     if (m_inspectedPage && m_frontend)
643         m_inspectedPage->mainFrame()->document()->postTask(PostWorkerNotificationToFrontendTask::create(workerResource, InspectorAgent::WorkerCreated));
644 }
645
646 void InspectorAgent::didDestroyWorker(intptr_t id)
647 {
648     if (!enabled())
649         return;
650
651     WorkersMap::iterator workerResource = m_workers.find(id);
652     if (workerResource == m_workers.end())
653         return;
654     if (m_inspectedPage && m_frontend)
655         m_inspectedPage->mainFrame()->document()->postTask(PostWorkerNotificationToFrontendTask::create(workerResource->second, InspectorAgent::WorkerDestroyed));
656     m_workers.remove(workerResource);
657 }
658 #endif // ENABLE(WORKERS)
659
660 void InspectorAgent::getCookies(RefPtr<InspectorArray>* cookies, WTF::String* cookiesString)
661 {
662     // If we can get raw cookies.
663     ListHashSet<Cookie> rawCookiesList;
664
665     // If we can't get raw cookies - fall back to String representation
666     String stringCookiesList;
667
668     // Return value to getRawCookies should be the same for every call because
669     // the return value is platform/network backend specific, and the call will
670     // always return the same true/false value.
671     bool rawCookiesImplemented = false;
672
673     for (Frame* frame = m_inspectedPage->mainFrame(); frame; frame = frame->tree()->traverseNext(m_inspectedPage->mainFrame())) {
674         Document* document = frame->document();
675         const CachedResourceLoader::DocumentResourceMap& allResources = document->cachedResourceLoader()->allCachedResources();
676         CachedResourceLoader::DocumentResourceMap::const_iterator end = allResources.end();
677         for (CachedResourceLoader::DocumentResourceMap::const_iterator it = allResources.begin(); it != end; ++it) {
678             Vector<Cookie> docCookiesList;
679             rawCookiesImplemented = getRawCookies(document, KURL(ParsedURLString, it->second->url()), docCookiesList);
680
681             if (!rawCookiesImplemented) {
682                 // FIXME: We need duplication checking for the String representation of cookies.
683                 ExceptionCode ec = 0;
684                 stringCookiesList += document->cookie(ec);
685                 // Exceptions are thrown by cookie() in sandboxed frames. That won't happen here
686                 // because "document" is the document of the main frame of the page.
687                 ASSERT(!ec);
688             } else {
689                 int cookiesSize = docCookiesList.size();
690                 for (int i = 0; i < cookiesSize; i++) {
691                     if (!rawCookiesList.contains(docCookiesList[i]))
692                         rawCookiesList.add(docCookiesList[i]);
693                 }
694             }
695         }
696     }
697
698     if (rawCookiesImplemented)
699         *cookies = buildArrayForCookies(rawCookiesList);
700     else
701         *cookiesString = stringCookiesList;
702 }
703
704 PassRefPtr<InspectorArray> InspectorAgent::buildArrayForCookies(ListHashSet<Cookie>& cookiesList)
705 {
706     RefPtr<InspectorArray> cookies = InspectorArray::create();
707
708     ListHashSet<Cookie>::iterator end = cookiesList.end();
709     ListHashSet<Cookie>::iterator it = cookiesList.begin();
710     for (int i = 0; it != end; ++it, i++)
711         cookies->pushObject(buildObjectForCookie(*it));
712
713     return cookies;
714 }
715
716 PassRefPtr<InspectorObject> InspectorAgent::buildObjectForCookie(const Cookie& cookie)
717 {
718     RefPtr<InspectorObject> value = InspectorObject::create();
719     value->setString("name", cookie.name);
720     value->setString("value", cookie.value);
721     value->setString("domain", cookie.domain);
722     value->setString("path", cookie.path);
723     value->setNumber("expires", cookie.expires);
724     value->setNumber("size", (cookie.name.length() + cookie.value.length()));
725     value->setBoolean("httpOnly", cookie.httpOnly);
726     value->setBoolean("secure", cookie.secure);
727     value->setBoolean("session", cookie.session);
728     return value;
729 }
730
731 void InspectorAgent::deleteCookie(const String& cookieName, const String& domain)
732 {
733     for (Frame* frame = m_inspectedPage->mainFrame(); frame; frame = frame->tree()->traverseNext(m_inspectedPage->mainFrame())) {
734         Document* document = frame->document();
735         if (document->url().host() != domain)
736             continue;
737         const CachedResourceLoader::DocumentResourceMap& allResources = document->cachedResourceLoader()->allCachedResources();
738         CachedResourceLoader::DocumentResourceMap::const_iterator end = allResources.end();
739         for (CachedResourceLoader::DocumentResourceMap::const_iterator it = allResources.begin(); it != end; ++it)
740             WebCore::deleteCookie(document, KURL(ParsedURLString, it->second->url()), cookieName);
741     }
742 }
743
744 #if ENABLE(WEB_SOCKETS)
745 void InspectorAgent::didCreateWebSocket(unsigned long identifier, const KURL& requestURL, const KURL& documentURL)
746 {
747     if (!enabled())
748         return;
749     ASSERT(m_inspectedPage);
750
751     if (m_resourceAgent)
752         m_resourceAgent->didCreateWebSocket(identifier, requestURL);
753     UNUSED_PARAM(documentURL);
754 }
755
756 void InspectorAgent::willSendWebSocketHandshakeRequest(unsigned long identifier, const WebSocketHandshakeRequest& request)
757 {
758     if (m_resourceAgent)
759         m_resourceAgent->willSendWebSocketHandshakeRequest(identifier, request);
760 }
761
762 void InspectorAgent::didReceiveWebSocketHandshakeResponse(unsigned long identifier, const WebSocketHandshakeResponse& response)
763 {
764     if (m_resourceAgent)
765         m_resourceAgent->didReceiveWebSocketHandshakeResponse(identifier, response);
766 }
767
768 void InspectorAgent::didCloseWebSocket(unsigned long identifier)
769 {
770     if (m_resourceAgent)
771         m_resourceAgent->didCloseWebSocket(identifier);
772 }
773 #endif // ENABLE(WEB_SOCKETS)
774
775 #if ENABLE(JAVASCRIPT_DEBUGGER)
776 bool InspectorAgent::isRecordingUserInitiatedProfile() const
777 {
778     return m_profilerAgent->isRecordingUserInitiatedProfile();
779 }
780
781 void InspectorAgent::startUserInitiatedProfiling()
782 {
783     if (!enabled())
784         return;
785     m_profilerAgent->startUserInitiatedProfiling();
786     m_state->setBoolean(InspectorAgentState::userInitiatedProfiling, true);
787 }
788
789 void InspectorAgent::stopUserInitiatedProfiling()
790 {
791     m_profilerAgent->stopUserInitiatedProfiling();
792     m_state->setBoolean(InspectorAgentState::userInitiatedProfiling, false);
793     showPanel(profilesPanelName);
794 }
795
796 bool InspectorAgent::profilerEnabled() const
797 {
798     return enabled() && m_profilerAgent->enabled();
799 }
800
801 void InspectorAgent::enableProfiler()
802 {
803     if (profilerEnabled())
804         return;
805     m_state->setBoolean(InspectorAgentState::profilerEnabled, true);
806     m_profilerAgent->enable(false);
807 }
808
809 void InspectorAgent::disableProfiler()
810 {
811     m_state->setBoolean(InspectorAgentState::profilerEnabled, false);
812     m_profilerAgent->disable();
813 }
814 #endif
815
816 #if ENABLE(JAVASCRIPT_DEBUGGER)
817 void InspectorAgent::startUserInitiatedDebugging()
818 {
819     if (debuggerEnabled())
820         return;
821
822     showPanel(scriptsPanelName);
823     if (!m_frontend) {
824         // We are called after show(), set the debuggerEnabled flag so that it was enabled
825         // upon frontend opening.
826         m_state->setBoolean(InspectorAgentState::debuggerEnabled, true);
827     } else
828         enableDebugger(true);
829 }
830
831 void InspectorAgent::enableDebugger(bool eraseStickyBreakpoints)
832 {
833     if (debuggerEnabled())
834         return;
835     m_state->setBoolean(InspectorAgentState::debuggerEnabled, true);
836     ASSERT(m_inspectedPage);
837
838     m_debuggerAgent = InspectorDebuggerAgent::create(this, m_frontend, eraseStickyBreakpoints);
839     m_browserDebuggerAgent = InspectorBrowserDebuggerAgent::create(this, eraseStickyBreakpoints);
840
841     m_frontend->debuggerWasEnabled();
842 }
843
844 void InspectorAgent::disableDebugger()
845 {
846     if (!enabled())
847         return;
848     ASSERT(m_inspectedPage);
849     m_debuggerAgent.clear();
850     m_browserDebuggerAgent.clear();
851
852     if (m_frontend) {
853         m_frontend->debuggerWasDisabled();
854         m_state->setBoolean(InspectorAgentState::debuggerEnabled, false);
855     }
856 }
857
858 void InspectorAgent::resume()
859 {
860     if (m_debuggerAgent)
861         m_debuggerAgent->resume();
862 }
863 #endif
864
865 void InspectorAgent::evaluateForTestInFrontend(long callId, const String& script)
866 {
867     if (m_frontend)
868         m_frontend->evaluateForTestInFrontend(callId, script);
869     else
870         m_pendingEvaluateTestCommands.append(pair<long, String>(callId, script));
871 }
872
873 void InspectorAgent::didEvaluateForTestInFrontend(long callId, const String& jsonResult)
874 {
875     ScriptState* scriptState = scriptStateFromPage(debuggerWorld(), m_inspectedPage);
876     ScriptObject window;
877     ScriptGlobalObject::get(scriptState, "window", window);
878     ScriptFunctionCall function(window, "didEvaluateForTestInFrontend");
879     function.appendArgument(callId);
880     function.appendArgument(jsonResult);
881     function.call();
882 }
883
884 static Path quadToPath(const FloatQuad& quad)
885 {
886     Path quadPath;
887     quadPath.moveTo(quad.p1());
888     quadPath.addLineTo(quad.p2());
889     quadPath.addLineTo(quad.p3());
890     quadPath.addLineTo(quad.p4());
891     quadPath.closeSubpath();
892     return quadPath;
893 }
894
895 static void drawOutlinedQuad(GraphicsContext& context, const FloatQuad& quad, const Color& fillColor)
896 {
897     static const int outlineThickness = 2;
898     static const Color outlineColor(62, 86, 180, 228);
899
900     Path quadPath = quadToPath(quad);
901
902     // Clip out the quad, then draw with a 2px stroke to get a pixel
903     // of outline (because inflating a quad is hard)
904     {
905         context.save();
906         context.clipOut(quadPath);
907
908         context.setStrokeThickness(outlineThickness);
909         context.setStrokeColor(outlineColor, ColorSpaceDeviceRGB);
910         context.strokePath(quadPath);
911
912         context.restore();
913     }
914
915     // Now do the fill
916     context.setFillColor(fillColor, ColorSpaceDeviceRGB);
917     context.fillPath(quadPath);
918 }
919
920 static void drawOutlinedQuadWithClip(GraphicsContext& context, const FloatQuad& quad, const FloatQuad& clipQuad, const Color& fillColor)
921 {
922     context.save();
923     Path clipQuadPath = quadToPath(clipQuad);
924     context.clipOut(clipQuadPath);
925     drawOutlinedQuad(context, quad, fillColor);
926     context.restore();
927 }
928
929 static void drawHighlightForBox(GraphicsContext& context, const FloatQuad& contentQuad, const FloatQuad& paddingQuad, const FloatQuad& borderQuad, const FloatQuad& marginQuad)
930 {
931     static const Color contentBoxColor(125, 173, 217, 128);
932     static const Color paddingBoxColor(125, 173, 217, 160);
933     static const Color borderBoxColor(125, 173, 217, 192);
934     static const Color marginBoxColor(125, 173, 217, 228);
935
936     if (marginQuad != borderQuad)
937         drawOutlinedQuadWithClip(context, marginQuad, borderQuad, marginBoxColor);
938     if (borderQuad != paddingQuad)
939         drawOutlinedQuadWithClip(context, borderQuad, paddingQuad, borderBoxColor);
940     if (paddingQuad != contentQuad)
941         drawOutlinedQuadWithClip(context, paddingQuad, contentQuad, paddingBoxColor);
942
943     drawOutlinedQuad(context, contentQuad, contentBoxColor);
944 }
945
946 static void drawHighlightForLineBoxesOrSVGRenderer(GraphicsContext& context, const Vector<FloatQuad>& lineBoxQuads)
947 {
948     static const Color lineBoxColor(125, 173, 217, 128);
949
950     for (size_t i = 0; i < lineBoxQuads.size(); ++i)
951         drawOutlinedQuad(context, lineBoxQuads[i], lineBoxColor);
952 }
953
954 static inline void convertFromFrameToMainFrame(Frame* frame, IntRect& rect)
955 {
956     rect = frame->page()->mainFrame()->view()->windowToContents(frame->view()->contentsToWindow(rect));
957 }
958
959 static inline IntSize frameToMainFrameOffset(Frame* frame)
960 {
961     IntPoint mainFramePoint = frame->page()->mainFrame()->view()->windowToContents(frame->view()->contentsToWindow(IntPoint()));
962     return mainFramePoint - IntPoint();
963 }
964
965 void InspectorAgent::drawNodeHighlight(GraphicsContext& context) const
966 {
967     if (!m_highlightedNode)
968         return;
969
970     RenderObject* renderer = m_highlightedNode->renderer();
971     Frame* containingFrame = m_highlightedNode->document()->frame();
972     if (!renderer || !containingFrame)
973         return;
974
975     IntSize mainFrameOffset = frameToMainFrameOffset(containingFrame);
976     IntRect boundingBox = renderer->absoluteBoundingBoxRect(true);
977     boundingBox.move(mainFrameOffset);
978
979     IntRect titleReferenceBox = boundingBox;
980
981     ASSERT(m_inspectedPage);
982
983     FrameView* view = m_inspectedPage->mainFrame()->view();
984     FloatRect overlayRect = view->visibleContentRect();
985     if (!overlayRect.contains(boundingBox) && !boundingBox.contains(enclosingIntRect(overlayRect)))
986         overlayRect = view->visibleContentRect();
987     context.translate(-overlayRect.x(), -overlayRect.y());
988
989     // RenderSVGRoot should be highlighted through the isBox() code path, all other SVG elements should just dump their absoluteQuads().
990 #if ENABLE(SVG)
991     bool isSVGRenderer = renderer->node() && renderer->node()->isSVGElement() && !renderer->isSVGRoot();
992 #else
993     bool isSVGRenderer = false;
994 #endif
995
996     if (renderer->isBox() && !isSVGRenderer) {
997         RenderBox* renderBox = toRenderBox(renderer);
998
999         IntRect contentBox = renderBox->contentBoxRect();
1000
1001         IntRect paddingBox(contentBox.x() - renderBox->paddingLeft(), contentBox.y() - renderBox->paddingTop(),
1002                            contentBox.width() + renderBox->paddingLeft() + renderBox->paddingRight(), contentBox.height() + renderBox->paddingTop() + renderBox->paddingBottom());
1003         IntRect borderBox(paddingBox.x() - renderBox->borderLeft(), paddingBox.y() - renderBox->borderTop(),
1004                           paddingBox.width() + renderBox->borderLeft() + renderBox->borderRight(), paddingBox.height() + renderBox->borderTop() + renderBox->borderBottom());
1005         IntRect marginBox(borderBox.x() - renderBox->marginLeft(), borderBox.y() - renderBox->marginTop(),
1006                           borderBox.width() + renderBox->marginLeft() + renderBox->marginRight(), borderBox.height() + renderBox->marginTop() + renderBox->marginBottom());
1007
1008
1009         FloatQuad absContentQuad = renderBox->localToAbsoluteQuad(FloatRect(contentBox));
1010         FloatQuad absPaddingQuad = renderBox->localToAbsoluteQuad(FloatRect(paddingBox));
1011         FloatQuad absBorderQuad = renderBox->localToAbsoluteQuad(FloatRect(borderBox));
1012         FloatQuad absMarginQuad = renderBox->localToAbsoluteQuad(FloatRect(marginBox));
1013
1014         absContentQuad.move(mainFrameOffset);
1015         absPaddingQuad.move(mainFrameOffset);
1016         absBorderQuad.move(mainFrameOffset);
1017         absMarginQuad.move(mainFrameOffset);
1018
1019         titleReferenceBox = absMarginQuad.enclosingBoundingBox();
1020
1021         drawHighlightForBox(context, absContentQuad, absPaddingQuad, absBorderQuad, absMarginQuad);
1022     } else if (renderer->isRenderInline() || isSVGRenderer) {
1023         // FIXME: We should show margins/padding/border for inlines.
1024         Vector<FloatQuad> lineBoxQuads;
1025         renderer->absoluteQuads(lineBoxQuads);
1026         for (unsigned i = 0; i < lineBoxQuads.size(); ++i)
1027             lineBoxQuads[i] += mainFrameOffset;
1028
1029         drawHighlightForLineBoxesOrSVGRenderer(context, lineBoxQuads);
1030     }
1031
1032     // Draw node title if necessary.
1033
1034     if (!m_highlightedNode->isElementNode())
1035         return;
1036
1037     WebCore::Settings* settings = containingFrame->settings();
1038     drawElementTitle(context, titleReferenceBox, overlayRect, settings);
1039 }
1040
1041 void InspectorAgent::drawElementTitle(GraphicsContext& context, const IntRect& boundingBox, const FloatRect& overlayRect, WebCore::Settings* settings) const
1042 {
1043     static const int rectInflatePx = 4;
1044     static const int fontHeightPx = 12;
1045     static const int borderWidthPx = 1;
1046     static const Color tooltipBackgroundColor(255, 255, 194, 255);
1047     static const Color tooltipBorderColor(Color::black);
1048     static const Color tooltipFontColor(Color::black);
1049
1050     Element* element = static_cast<Element*>(m_highlightedNode.get());
1051     bool isXHTML = element->document()->isXHTMLDocument();
1052     String nodeTitle = isXHTML ? element->nodeName() : element->nodeName().lower();
1053     const AtomicString& idValue = element->getIdAttribute();
1054     if (!idValue.isNull() && !idValue.isEmpty()) {
1055         nodeTitle += "#";
1056         nodeTitle += idValue;
1057     }
1058     if (element->hasClass() && element->isStyledElement()) {
1059         const SpaceSplitString& classNamesString = static_cast<StyledElement*>(element)->classNames();
1060         size_t classNameCount = classNamesString.size();
1061         if (classNameCount) {
1062             HashSet<AtomicString> usedClassNames;
1063             for (size_t i = 0; i < classNameCount; ++i) {
1064                 const AtomicString& className = classNamesString[i];
1065                 if (usedClassNames.contains(className))
1066                     continue;
1067                 usedClassNames.add(className);
1068                 nodeTitle += ".";
1069                 nodeTitle += className;
1070             }
1071         }
1072     }
1073
1074     Element* highlightedElement = m_highlightedNode->isElementNode() ? static_cast<Element*>(m_highlightedNode.get()) : 0;
1075     nodeTitle += " [";
1076     nodeTitle += String::number(highlightedElement ? highlightedElement->offsetWidth() : boundingBox.width());
1077     nodeTitle.append(static_cast<UChar>(0x00D7)); // &times;
1078     nodeTitle += String::number(highlightedElement ? highlightedElement->offsetHeight() : boundingBox.height());
1079     nodeTitle += "]";
1080
1081     FontDescription desc;
1082     FontFamily family;
1083     family.setFamily(settings->fixedFontFamily());
1084     desc.setFamily(family);
1085     desc.setComputedSize(fontHeightPx);
1086     Font font = Font(desc, 0, 0);
1087     font.update(0);
1088
1089     TextRun nodeTitleRun(nodeTitle);
1090     IntPoint titleBasePoint = IntPoint(boundingBox.x(), boundingBox.maxY() - 1);
1091     titleBasePoint.move(rectInflatePx, rectInflatePx);
1092     IntRect titleRect = enclosingIntRect(font.selectionRectForText(nodeTitleRun, titleBasePoint, fontHeightPx));
1093     titleRect.inflate(rectInflatePx);
1094
1095     // The initial offsets needed to compensate for a 1px-thick border stroke (which is not a part of the rectangle).
1096     int dx = -borderWidthPx;
1097     int dy = borderWidthPx;
1098
1099     // If the tip sticks beyond the right of overlayRect, right-align the tip with the said boundary.
1100     if (titleRect.maxX() > overlayRect.maxX())
1101         dx = overlayRect.maxX() - titleRect.maxX();
1102
1103     // If the tip sticks beyond the left of overlayRect, left-align the tip with the said boundary.
1104     if (titleRect.x() + dx < overlayRect.x())
1105         dx = overlayRect.x() - titleRect.x() - borderWidthPx;
1106
1107     // If the tip sticks beyond the bottom of overlayRect, show the tip at top of bounding box.
1108     if (titleRect.maxY() > overlayRect.maxY()) {
1109         dy = boundingBox.y() - titleRect.maxY() - borderWidthPx;
1110         // If the tip still sticks beyond the bottom of overlayRect, bottom-align the tip with the said boundary.
1111         if (titleRect.maxY() + dy > overlayRect.maxY())
1112             dy = overlayRect.maxY() - titleRect.maxY();
1113     }
1114
1115     // If the tip sticks beyond the top of overlayRect, show the tip at top of overlayRect.
1116     if (titleRect.y() + dy < overlayRect.y())
1117         dy = overlayRect.y() - titleRect.y() + borderWidthPx;
1118
1119     titleRect.move(dx, dy);
1120     context.setStrokeColor(tooltipBorderColor, ColorSpaceDeviceRGB);
1121     context.setStrokeThickness(borderWidthPx);
1122     context.setFillColor(tooltipBackgroundColor, ColorSpaceDeviceRGB);
1123     context.drawRect(titleRect);
1124     context.setFillColor(tooltipFontColor, ColorSpaceDeviceRGB);
1125     context.drawText(font, nodeTitleRun, IntPoint(titleRect.x() + rectInflatePx, titleRect.y() + font.fontMetrics().height()));
1126 }
1127
1128 void InspectorAgent::openInInspectedWindow(const String& url)
1129 {
1130     Frame* mainFrame = m_inspectedPage->mainFrame();
1131
1132     FrameLoadRequest request(mainFrame->document()->securityOrigin(), ResourceRequest(), "_blank");
1133
1134     bool created;
1135     WindowFeatures windowFeatures;
1136     Frame* newFrame = WebCore::createWindow(mainFrame, mainFrame, request, windowFeatures, created);
1137     if (!newFrame)
1138         return;
1139
1140     UserGestureIndicator indicator(DefinitelyProcessingUserGesture);
1141     newFrame->loader()->setOpener(mainFrame);
1142     newFrame->page()->setOpenedByDOM();
1143     newFrame->loader()->changeLocation(mainFrame->document()->securityOrigin(), newFrame->loader()->completeURL(url), "", false, false);
1144 }
1145
1146 void InspectorAgent::addScriptToEvaluateOnLoad(const String& source)
1147 {
1148     m_scriptsToEvaluateOnLoad.append(source);
1149 }
1150
1151 void InspectorAgent::removeAllScriptsToEvaluateOnLoad()
1152 {
1153     m_scriptsToEvaluateOnLoad.clear();
1154 }
1155
1156 void InspectorAgent::setInspectorExtensionAPI(const String& source)
1157 {
1158     m_inspectorExtensionAPI = source;
1159 }
1160
1161 KURL InspectorAgent::inspectedURL() const
1162 {
1163     return m_inspectedPage->mainFrame()->document()->url();
1164 }
1165
1166 KURL InspectorAgent::inspectedURLWithoutFragment() const
1167 {
1168     KURL url = inspectedURL();
1169     url.removeFragmentIdentifier();
1170     return url;
1171 }
1172
1173 void InspectorAgent::reloadPage(bool ignoreCache)
1174 {
1175     m_inspectedPage->mainFrame()->loader()->reload(ignoreCache);
1176 }
1177
1178 bool InspectorAgent::enabled() const
1179 {
1180     if (!m_inspectedPage)
1181         return false;
1182     return m_inspectedPage->settings()->developerExtrasEnabled();
1183 }
1184
1185 void InspectorAgent::showConsole()
1186 {
1187     showPanel(consolePanelName);
1188 }
1189
1190 void InspectorAgent::showPanel(const String& panel)
1191 {
1192     if (!m_frontend) {
1193         m_showPanelAfterVisible = panel;
1194         return;
1195     }
1196     m_frontend->showPanel(panel);
1197 }
1198
1199 } // namespace WebCore
1200
1201 #endif // ENABLE(INSPECTOR)