1/*
2 * Copyright (C) 2011 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1.  Redistributions of source code must retain the above copyright
8 *     notice, this list of conditions and the following disclaimer.
9 * 2.  Redistributions in binary form must reproduce the above copyright
10 *     notice, this list of conditions and the following disclaimer in the
11 *     documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
17 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 */
24
25
26#include "config.h"
27#include "core/inspector/InspectorConsoleAgent.h"
28
29#include "bindings/core/v8/ScriptCallStackFactory.h"
30#include "bindings/core/v8/ScriptController.h"
31#include "bindings/core/v8/ScriptProfiler.h"
32#include "core/frame/LocalFrame.h"
33#include "core/frame/UseCounter.h"
34#include "core/inspector/ConsoleMessage.h"
35#include "core/inspector/ConsoleMessageStorage.h"
36#include "core/inspector/IdentifiersFactory.h"
37#include "core/inspector/InjectedScript.h"
38#include "core/inspector/InjectedScriptHost.h"
39#include "core/inspector/InjectedScriptManager.h"
40#include "core/inspector/InspectorState.h"
41#include "core/inspector/InspectorTimelineAgent.h"
42#include "core/inspector/InstrumentingAgents.h"
43#include "core/inspector/ScriptArguments.h"
44#include "core/inspector/ScriptAsyncCallStack.h"
45#include "core/inspector/ScriptCallFrame.h"
46#include "core/inspector/ScriptCallStack.h"
47#include "core/loader/DocumentLoader.h"
48#include "core/page/Page.h"
49#include "core/xml/XMLHttpRequest.h"
50#include "platform/network/ResourceError.h"
51#include "platform/network/ResourceResponse.h"
52#include "wtf/CurrentTime.h"
53#include "wtf/OwnPtr.h"
54#include "wtf/PassOwnPtr.h"
55#include "wtf/text/StringBuilder.h"
56#include "wtf/text/WTFString.h"
57
58namespace blink {
59
60namespace ConsoleAgentState {
61static const char monitoringXHR[] = "monitoringXHR";
62static const char consoleMessagesEnabled[] = "consoleMessagesEnabled";
63static const char tracingBasedTimeline[] = "tracingBasedTimeline";
64}
65
66int InspectorConsoleAgent::s_enabledAgentCount = 0;
67
68InspectorConsoleAgent::InspectorConsoleAgent(InspectorTimelineAgent* timelineAgent, InjectedScriptManager* injectedScriptManager)
69    : InspectorBaseAgent<InspectorConsoleAgent>("Console")
70    , m_timelineAgent(timelineAgent)
71    , m_injectedScriptManager(injectedScriptManager)
72    , m_frontend(0)
73    , m_enabled(false)
74{
75}
76
77InspectorConsoleAgent::~InspectorConsoleAgent()
78{
79#if !ENABLE(OILPAN)
80    m_instrumentingAgents->setInspectorConsoleAgent(0);
81#endif
82}
83
84void InspectorConsoleAgent::trace(Visitor* visitor)
85{
86    visitor->trace(m_timelineAgent);
87    visitor->trace(m_injectedScriptManager);
88    InspectorBaseAgent::trace(visitor);
89}
90
91void InspectorConsoleAgent::init()
92{
93    m_instrumentingAgents->setInspectorConsoleAgent(this);
94}
95
96void InspectorConsoleAgent::enable(ErrorString*)
97{
98    if (m_enabled)
99        return;
100    m_enabled = true;
101    if (!s_enabledAgentCount)
102        ScriptController::setCaptureCallStackForUncaughtExceptions(true);
103    ++s_enabledAgentCount;
104
105    m_state->setBoolean(ConsoleAgentState::consoleMessagesEnabled, true);
106
107    ConsoleMessageStorage* storage = messageStorage();
108    if (storage->expiredCount()) {
109        RefPtrWillBeRawPtr<ConsoleMessage> expiredMessage = ConsoleMessage::create(OtherMessageSource, WarningMessageLevel, String::format("%d console messages are not shown.", storage->expiredCount()));
110        expiredMessage->setTimestamp(0);
111        sendConsoleMessageToFrontend(expiredMessage.get(), false);
112    }
113
114    size_t messageCount = storage->size();
115    for (size_t i = 0; i < messageCount; ++i)
116        sendConsoleMessageToFrontend(storage->at(i), false);
117}
118
119void InspectorConsoleAgent::disable(ErrorString*)
120{
121    if (!m_enabled)
122        return;
123    m_enabled = false;
124    if (!(--s_enabledAgentCount))
125        ScriptController::setCaptureCallStackForUncaughtExceptions(false);
126    m_state->setBoolean(ConsoleAgentState::consoleMessagesEnabled, false);
127    m_state->setBoolean(ConsoleAgentState::tracingBasedTimeline, false);
128}
129
130void InspectorConsoleAgent::clearMessages(ErrorString*)
131{
132    messageStorage()->clear();
133}
134
135void InspectorConsoleAgent::restore()
136{
137    if (m_state->getBoolean(ConsoleAgentState::consoleMessagesEnabled)) {
138        m_frontend->messagesCleared();
139        ErrorString error;
140        enable(&error);
141    }
142}
143
144void InspectorConsoleAgent::setFrontend(InspectorFrontend* frontend)
145{
146    m_frontend = frontend->console();
147}
148
149void InspectorConsoleAgent::clearFrontend()
150{
151    m_frontend = 0;
152    String errorString;
153    disable(&errorString);
154}
155
156void InspectorConsoleAgent::addMessageToConsole(ConsoleMessage* consoleMessage)
157{
158    if (m_frontend && m_enabled)
159        sendConsoleMessageToFrontend(consoleMessage, true);
160}
161
162void InspectorConsoleAgent::consoleMessagesCleared()
163{
164    m_injectedScriptManager->releaseObjectGroup("console");
165    if (m_frontend && m_enabled)
166        m_frontend->messagesCleared();
167}
168
169void InspectorConsoleAgent::setTracingBasedTimeline(ErrorString*, bool enabled)
170{
171    m_state->setBoolean(ConsoleAgentState::tracingBasedTimeline, enabled);
172}
173
174void InspectorConsoleAgent::consoleTimeline(ExecutionContext* context, const String& title, ScriptState* scriptState)
175{
176    UseCounter::count(context, UseCounter::DevToolsConsoleTimeline);
177    if (!m_state->getBoolean(ConsoleAgentState::tracingBasedTimeline))
178        m_timelineAgent->consoleTimeline(context, title, scriptState);
179}
180
181void InspectorConsoleAgent::consoleTimelineEnd(ExecutionContext* context, const String& title, ScriptState* scriptState)
182{
183    if (!m_state->getBoolean(ConsoleAgentState::tracingBasedTimeline))
184        m_timelineAgent->consoleTimelineEnd(context, title, scriptState);
185}
186
187void InspectorConsoleAgent::frameWindowDiscarded(LocalDOMWindow* window)
188{
189    m_injectedScriptManager->discardInjectedScriptsFor(window);
190}
191
192void InspectorConsoleAgent::didFinishXHRLoading(XMLHttpRequest*, ThreadableLoaderClient*, unsigned long requestIdentifier, ScriptString, const AtomicString& method, const String& url, const String& sendURL, unsigned sendLineNumber)
193{
194    if (m_frontend && m_state->getBoolean(ConsoleAgentState::monitoringXHR)) {
195        String message = "XHR finished loading: " + method + " \"" + url + "\".";
196        RefPtrWillBeRawPtr<ConsoleMessage> consoleMessage = ConsoleMessage::create(NetworkMessageSource, DebugMessageLevel, message, sendURL, sendLineNumber);
197        consoleMessage->setRequestIdentifier(requestIdentifier);
198        messageStorage()->reportMessage(consoleMessage.release());
199    }
200}
201
202void InspectorConsoleAgent::didFailLoading(unsigned long requestIdentifier, const ResourceError& error)
203{
204    if (error.isCancellation()) // Report failures only.
205        return;
206    StringBuilder message;
207    message.appendLiteral("Failed to load resource");
208    if (!error.localizedDescription().isEmpty()) {
209        message.appendLiteral(": ");
210        message.append(error.localizedDescription());
211    }
212    RefPtrWillBeRawPtr<ConsoleMessage> consoleMessage = ConsoleMessage::create(NetworkMessageSource, ErrorMessageLevel, message.toString(), error.failingURL());
213    consoleMessage->setRequestIdentifier(requestIdentifier);
214    messageStorage()->reportMessage(consoleMessage.release());
215}
216
217void InspectorConsoleAgent::setMonitoringXHREnabled(ErrorString*, bool enabled)
218{
219    m_state->setBoolean(ConsoleAgentState::monitoringXHR, enabled);
220}
221
222static TypeBuilder::Console::ConsoleMessage::Source::Enum messageSourceValue(MessageSource source)
223{
224    switch (source) {
225    case XMLMessageSource: return TypeBuilder::Console::ConsoleMessage::Source::Xml;
226    case JSMessageSource: return TypeBuilder::Console::ConsoleMessage::Source::Javascript;
227    case NetworkMessageSource: return TypeBuilder::Console::ConsoleMessage::Source::Network;
228    case ConsoleAPIMessageSource: return TypeBuilder::Console::ConsoleMessage::Source::Console_api;
229    case StorageMessageSource: return TypeBuilder::Console::ConsoleMessage::Source::Storage;
230    case AppCacheMessageSource: return TypeBuilder::Console::ConsoleMessage::Source::Appcache;
231    case RenderingMessageSource: return TypeBuilder::Console::ConsoleMessage::Source::Rendering;
232    case CSSMessageSource: return TypeBuilder::Console::ConsoleMessage::Source::Css;
233    case SecurityMessageSource: return TypeBuilder::Console::ConsoleMessage::Source::Security;
234    case OtherMessageSource: return TypeBuilder::Console::ConsoleMessage::Source::Other;
235    case DeprecationMessageSource: return TypeBuilder::Console::ConsoleMessage::Source::Deprecation;
236    }
237    return TypeBuilder::Console::ConsoleMessage::Source::Other;
238}
239
240
241static TypeBuilder::Console::ConsoleMessage::Type::Enum messageTypeValue(MessageType type)
242{
243    switch (type) {
244    case LogMessageType: return TypeBuilder::Console::ConsoleMessage::Type::Log;
245    case ClearMessageType: return TypeBuilder::Console::ConsoleMessage::Type::Clear;
246    case DirMessageType: return TypeBuilder::Console::ConsoleMessage::Type::Dir;
247    case DirXMLMessageType: return TypeBuilder::Console::ConsoleMessage::Type::Dirxml;
248    case TableMessageType: return TypeBuilder::Console::ConsoleMessage::Type::Table;
249    case TraceMessageType: return TypeBuilder::Console::ConsoleMessage::Type::Trace;
250    case StartGroupMessageType: return TypeBuilder::Console::ConsoleMessage::Type::StartGroup;
251    case StartGroupCollapsedMessageType: return TypeBuilder::Console::ConsoleMessage::Type::StartGroupCollapsed;
252    case EndGroupMessageType: return TypeBuilder::Console::ConsoleMessage::Type::EndGroup;
253    case AssertMessageType: return TypeBuilder::Console::ConsoleMessage::Type::Assert;
254    case TimeEndMessageType: return TypeBuilder::Console::ConsoleMessage::Type::Log;
255    case CountMessageType: return TypeBuilder::Console::ConsoleMessage::Type::Log;
256    }
257    return TypeBuilder::Console::ConsoleMessage::Type::Log;
258}
259
260static TypeBuilder::Console::ConsoleMessage::Level::Enum messageLevelValue(MessageLevel level)
261{
262    switch (level) {
263    case DebugMessageLevel: return TypeBuilder::Console::ConsoleMessage::Level::Debug;
264    case LogMessageLevel: return TypeBuilder::Console::ConsoleMessage::Level::Log;
265    case WarningMessageLevel: return TypeBuilder::Console::ConsoleMessage::Level::Warning;
266    case ErrorMessageLevel: return TypeBuilder::Console::ConsoleMessage::Level::Error;
267    case InfoMessageLevel: return TypeBuilder::Console::ConsoleMessage::Level::Info;
268    }
269    return TypeBuilder::Console::ConsoleMessage::Level::Log;
270}
271
272void InspectorConsoleAgent::sendConsoleMessageToFrontend(ConsoleMessage* consoleMessage, bool generatePreview)
273{
274    if (consoleMessage->workerGlobalScopeProxy())
275        return;
276
277    RefPtr<TypeBuilder::Console::ConsoleMessage> jsonObj = TypeBuilder::Console::ConsoleMessage::create()
278        .setSource(messageSourceValue(consoleMessage->source()))
279        .setLevel(messageLevelValue(consoleMessage->level()))
280        .setText(consoleMessage->message())
281        .setTimestamp(consoleMessage->timestamp());
282    // FIXME: only send out type for ConsoleAPI source messages.
283    jsonObj->setType(messageTypeValue(consoleMessage->type()));
284    jsonObj->setLine(static_cast<int>(consoleMessage->lineNumber()));
285    jsonObj->setColumn(static_cast<int>(consoleMessage->columnNumber()));
286    if (consoleMessage->scriptId())
287        jsonObj->setScriptId(String::number(consoleMessage->scriptId()));
288    jsonObj->setUrl(consoleMessage->url());
289    ScriptState* scriptState = consoleMessage->scriptState();
290    if (scriptState)
291        jsonObj->setExecutionContextId(m_injectedScriptManager->injectedScriptIdFor(scriptState));
292    if (consoleMessage->source() == NetworkMessageSource && consoleMessage->requestIdentifier())
293        jsonObj->setNetworkRequestId(IdentifiersFactory::requestId(consoleMessage->requestIdentifier()));
294    RefPtrWillBeRawPtr<ScriptArguments> arguments = consoleMessage->scriptArguments();
295    if (arguments && arguments->argumentCount()) {
296        InjectedScript injectedScript = m_injectedScriptManager->injectedScriptFor(arguments->scriptState());
297        if (!injectedScript.isEmpty()) {
298            RefPtr<TypeBuilder::Array<TypeBuilder::Runtime::RemoteObject> > jsonArgs = TypeBuilder::Array<TypeBuilder::Runtime::RemoteObject>::create();
299            if (consoleMessage->type() == TableMessageType && generatePreview && arguments->argumentCount()) {
300                ScriptValue table = arguments->argumentAt(0);
301                ScriptValue columns = arguments->argumentCount() > 1 ? arguments->argumentAt(1) : ScriptValue();
302                RefPtr<TypeBuilder::Runtime::RemoteObject> inspectorValue = injectedScript.wrapTable(table, columns);
303                if (!inspectorValue) {
304                    ASSERT_NOT_REACHED();
305                    return;
306                }
307                jsonArgs->addItem(inspectorValue);
308            } else {
309                for (unsigned i = 0; i < arguments->argumentCount(); ++i) {
310                    RefPtr<TypeBuilder::Runtime::RemoteObject> inspectorValue = injectedScript.wrapObject(arguments->argumentAt(i), "console", generatePreview);
311                    if (!inspectorValue) {
312                        ASSERT_NOT_REACHED();
313                        return;
314                    }
315                    jsonArgs->addItem(inspectorValue);
316                }
317            }
318            jsonObj->setParameters(jsonArgs);
319        }
320    }
321    if (consoleMessage->callStack()) {
322        jsonObj->setStackTrace(consoleMessage->callStack()->buildInspectorArray());
323        RefPtrWillBeRawPtr<ScriptAsyncCallStack> asyncCallStack = consoleMessage->callStack()->asyncCallStack();
324        if (asyncCallStack)
325            jsonObj->setAsyncStackTrace(asyncCallStack->buildInspectorObject());
326    }
327    m_frontend->messageAdded(jsonObj);
328    m_frontend->flush();
329}
330
331class InspectableHeapObject FINAL : public InjectedScriptHost::InspectableObject {
332public:
333    explicit InspectableHeapObject(int heapObjectId) : m_heapObjectId(heapObjectId) { }
334    virtual ScriptValue get(ScriptState*) OVERRIDE
335    {
336        return ScriptProfiler::objectByHeapObjectId(m_heapObjectId);
337    }
338private:
339    int m_heapObjectId;
340};
341
342void InspectorConsoleAgent::addInspectedHeapObject(ErrorString*, int inspectedHeapObjectId)
343{
344    m_injectedScriptManager->injectedScriptHost()->addInspectedObject(adoptPtr(new InspectableHeapObject(inspectedHeapObjectId)));
345}
346
347} // namespace blink
348