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 are
6 * met:
7 *
8 *     * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *     * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 *     * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#include "config.h"
32#include "bindings/core/v8/PageScriptDebugServer.h"
33
34#include "bindings/core/v8/DOMWrapperWorld.h"
35#include "bindings/core/v8/ScriptController.h"
36#include "bindings/core/v8/ScriptSourceCode.h"
37#include "bindings/core/v8/V8Binding.h"
38#include "bindings/core/v8/V8ScriptRunner.h"
39#include "bindings/core/v8/V8Window.h"
40#include "bindings/core/v8/WindowProxy.h"
41#include "core/frame/FrameConsole.h"
42#include "core/frame/FrameHost.h"
43#include "core/frame/LocalFrame.h"
44#include "core/frame/UseCounter.h"
45#include "core/inspector/InspectorInstrumentation.h"
46#include "core/inspector/InspectorTraceEvents.h"
47#include "core/inspector/ScriptDebugListener.h"
48#include "core/page/Page.h"
49#include "wtf/OwnPtr.h"
50#include "wtf/PassOwnPtr.h"
51#include "wtf/StdLibExtras.h"
52#include "wtf/TemporaryChange.h"
53#include "wtf/text/StringBuilder.h"
54
55namespace blink {
56
57static LocalFrame* retrieveFrameWithGlobalObjectCheck(v8::Handle<v8::Context> context)
58{
59    if (context.IsEmpty())
60        return 0;
61
62    // FIXME: This is a temporary hack for crbug.com/345014.
63    // Currently it's possible that V8 can trigger Debugger::ProcessDebugEvent for a context
64    // that is being initialized (i.e., inside Context::New() of the context).
65    // We should fix the V8 side so that it won't trigger the event for a half-baked context
66    // because there is no way in the embedder side to check if the context is half-baked or not.
67    if (isMainThread() && DOMWrapperWorld::windowIsBeingInitialized())
68        return 0;
69
70    v8::Handle<v8::Value> global = V8Window::findInstanceInPrototypeChain(context->Global(), context->GetIsolate());
71    if (global.IsEmpty())
72        return 0;
73
74    return toFrameIfNotDetached(context);
75}
76
77void PageScriptDebugServer::setPreprocessorSource(const String& preprocessorSource)
78{
79    if (preprocessorSource.isEmpty())
80        m_preprocessorSourceCode.clear();
81    else
82        m_preprocessorSourceCode = adoptPtr(new ScriptSourceCode(preprocessorSource));
83    m_scriptPreprocessor.clear();
84}
85
86PageScriptDebugServer& PageScriptDebugServer::shared()
87{
88    DEFINE_STATIC_LOCAL(PageScriptDebugServer, server, ());
89    return server;
90}
91
92v8::Isolate* PageScriptDebugServer::s_mainThreadIsolate = 0;
93
94void PageScriptDebugServer::setMainThreadIsolate(v8::Isolate* isolate)
95{
96    s_mainThreadIsolate = isolate;
97}
98
99PageScriptDebugServer::PageScriptDebugServer()
100    : ScriptDebugServer(s_mainThreadIsolate)
101    , m_pausedPage(0)
102{
103}
104
105PageScriptDebugServer::~PageScriptDebugServer()
106{
107}
108
109void PageScriptDebugServer::addListener(ScriptDebugListener* listener, Page* page)
110{
111    ScriptController& scriptController = page->deprecatedLocalMainFrame()->script();
112    if (!scriptController.canExecuteScripts(NotAboutToExecuteScript))
113        return;
114
115    v8::HandleScope scope(m_isolate);
116
117    if (!m_listenersMap.size()) {
118        v8::Debug::SetDebugEventListener(&PageScriptDebugServer::v8DebugEventCallback, v8::External::New(m_isolate, this));
119        ensureDebuggerScriptCompiled();
120    }
121
122    v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext();
123    v8::Context::Scope contextScope(debuggerContext);
124
125    v8::Local<v8::Object> debuggerScript = m_debuggerScript.newLocal(m_isolate);
126    ASSERT(!debuggerScript->IsUndefined());
127    m_listenersMap.set(page, listener);
128
129    WindowProxy* windowProxy = scriptController.existingWindowProxy(DOMWrapperWorld::mainWorld());
130    if (!windowProxy || !windowProxy->isContextInitialized())
131        return;
132    v8::Local<v8::Context> context = windowProxy->context();
133    v8::Handle<v8::Function> getScriptsFunction = v8::Local<v8::Function>::Cast(debuggerScript->Get(v8AtomicString(m_isolate, "getScripts")));
134    v8::Handle<v8::Value> argv[] = { context->GetEmbedderData(0) };
135    v8::Handle<v8::Value> value = V8ScriptRunner::callInternalFunction(getScriptsFunction, debuggerScript, WTF_ARRAY_LENGTH(argv), argv, m_isolate);
136    if (value.IsEmpty())
137        return;
138    ASSERT(!value->IsUndefined() && value->IsArray());
139    v8::Handle<v8::Array> scriptsArray = v8::Handle<v8::Array>::Cast(value);
140    for (unsigned i = 0; i < scriptsArray->Length(); ++i)
141        dispatchDidParseSource(listener, v8::Handle<v8::Object>::Cast(scriptsArray->Get(v8::Integer::New(m_isolate, i))), CompileSuccess);
142}
143
144void PageScriptDebugServer::removeListener(ScriptDebugListener* listener, Page* page)
145{
146    if (!m_listenersMap.contains(page))
147        return;
148
149    if (m_pausedPage == page)
150        continueProgram();
151
152    m_listenersMap.remove(page);
153
154    if (m_listenersMap.isEmpty()) {
155        discardDebuggerScript();
156        v8::Debug::SetDebugEventListener(0);
157        // FIXME: Remove all breakpoints set by the agent.
158    }
159}
160
161void PageScriptDebugServer::interruptAndRun(PassOwnPtr<Task> task)
162{
163    ScriptDebugServer::interruptAndRun(task, s_mainThreadIsolate);
164}
165
166void PageScriptDebugServer::setClientMessageLoop(PassOwnPtr<ClientMessageLoop> clientMessageLoop)
167{
168    m_clientMessageLoop = clientMessageLoop;
169}
170
171void PageScriptDebugServer::compileScript(ScriptState* scriptState, const String& expression, const String& sourceURL, String* scriptId, String* exceptionDetailsText, int* lineNumber, int* columnNumber, RefPtrWillBeRawPtr<ScriptCallStack>* stackTrace)
172{
173    ExecutionContext* executionContext = scriptState->executionContext();
174    RefPtrWillBeRawPtr<LocalFrame> protect(toDocument(executionContext)->frame());
175    ScriptDebugServer::compileScript(scriptState, expression, sourceURL, scriptId, exceptionDetailsText, lineNumber, columnNumber, stackTrace);
176    if (!scriptId->isNull())
177        m_compiledScriptURLs.set(*scriptId, sourceURL);
178}
179
180void PageScriptDebugServer::clearCompiledScripts()
181{
182    ScriptDebugServer::clearCompiledScripts();
183    m_compiledScriptURLs.clear();
184}
185
186void PageScriptDebugServer::runScript(ScriptState* scriptState, const String& scriptId, ScriptValue* result, bool* wasThrown, String* exceptionDetailsText, int* lineNumber, int* columnNumber, RefPtrWillBeRawPtr<ScriptCallStack>* stackTrace)
187{
188    String sourceURL = m_compiledScriptURLs.take(scriptId);
189
190    ExecutionContext* executionContext = scriptState->executionContext();
191    LocalFrame* frame = toDocument(executionContext)->frame();
192    TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "EvaluateScript", "data", InspectorEvaluateScriptEvent::data(frame, sourceURL, TextPosition::minimumPosition().m_line.oneBasedInt()));
193    TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline.stack"), "CallStack", "stack", InspectorCallStackEvent::currentCallStack());
194    // FIXME(361045): remove InspectorInstrumentation calls once DevTools Timeline migrates to tracing.
195    InspectorInstrumentationCookie cookie;
196    if (frame)
197        cookie = InspectorInstrumentation::willEvaluateScript(frame, sourceURL, TextPosition::minimumPosition().m_line.oneBasedInt());
198
199    RefPtrWillBeRawPtr<LocalFrame> protect(frame);
200    ScriptDebugServer::runScript(scriptState, scriptId, result, wasThrown, exceptionDetailsText, lineNumber, columnNumber, stackTrace);
201
202    if (frame)
203        InspectorInstrumentation::didEvaluateScript(cookie);
204    TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "UpdateCounters", "data", InspectorUpdateCountersEvent::data());
205}
206
207ScriptDebugListener* PageScriptDebugServer::getDebugListenerForContext(v8::Handle<v8::Context> context)
208{
209    v8::HandleScope scope(m_isolate);
210    LocalFrame* frame = retrieveFrameWithGlobalObjectCheck(context);
211    if (!frame)
212        return 0;
213    return m_listenersMap.get(frame->page());
214}
215
216void PageScriptDebugServer::runMessageLoopOnPause(v8::Handle<v8::Context> context)
217{
218    v8::HandleScope scope(m_isolate);
219    LocalFrame* frame = retrieveFrameWithGlobalObjectCheck(context);
220    m_pausedPage = frame->page();
221
222    // Wait for continue or step command.
223    m_clientMessageLoop->run(m_pausedPage);
224
225    // The listener may have been removed in the nested loop.
226    if (ScriptDebugListener* listener = m_listenersMap.get(m_pausedPage))
227        listener->didContinue();
228
229    m_pausedPage = 0;
230}
231
232void PageScriptDebugServer::quitMessageLoopOnPause()
233{
234    m_clientMessageLoop->quitNow();
235}
236
237void PageScriptDebugServer::preprocessBeforeCompile(const v8::Debug::EventDetails& eventDetails)
238{
239    v8::Handle<v8::Context> eventContext = eventDetails.GetEventContext();
240    LocalFrame* frame = retrieveFrameWithGlobalObjectCheck(eventContext);
241    if (!frame)
242        return;
243
244    if (!canPreprocess(frame))
245        return;
246
247    v8::Handle<v8::Object> eventData = eventDetails.GetEventData();
248    v8::Local<v8::Context> debugContext = v8::Debug::GetDebugContext();
249    v8::Context::Scope contextScope(debugContext);
250    v8::TryCatch tryCatch;
251    // <script> tag source and attribute value source are preprocessed before we enter V8.
252    // Avoid preprocessing any internal scripts by processing only eval source in this V8 event handler.
253    v8::Handle<v8::Value> argvEventData[] = { eventData };
254    v8::Handle<v8::Value> v8Value = callDebuggerMethod("isEvalCompilation", WTF_ARRAY_LENGTH(argvEventData), argvEventData);
255    if (v8Value.IsEmpty() || !v8Value->ToBoolean()->Value())
256        return;
257
258    // The name and source are in the JS event data.
259    String scriptName = toCoreStringWithUndefinedOrNullCheck(callDebuggerMethod("getScriptName", WTF_ARRAY_LENGTH(argvEventData), argvEventData));
260    String script = toCoreStringWithUndefinedOrNullCheck(callDebuggerMethod("getScriptSource", WTF_ARRAY_LENGTH(argvEventData), argvEventData));
261
262    String preprocessedSource  = m_scriptPreprocessor->preprocessSourceCode(script, scriptName);
263
264    v8::Handle<v8::Value> argvPreprocessedScript[] = { eventData, v8String(debugContext->GetIsolate(), preprocessedSource) };
265    callDebuggerMethod("setScriptSource", WTF_ARRAY_LENGTH(argvPreprocessedScript), argvPreprocessedScript);
266}
267
268static bool isCreatingPreprocessor = false;
269
270bool PageScriptDebugServer::canPreprocess(LocalFrame* frame)
271{
272    ASSERT(frame);
273
274    if (!m_preprocessorSourceCode || !frame->page() || isCreatingPreprocessor)
275        return false;
276
277    // We delay the creation of the preprocessor until just before the first JS from the
278    // Web page to ensure that the debugger's console initialization code has completed.
279    if (!m_scriptPreprocessor) {
280        TemporaryChange<bool> isPreprocessing(isCreatingPreprocessor, true);
281        m_scriptPreprocessor = adoptPtr(new ScriptPreprocessor(*m_preprocessorSourceCode.get(), frame));
282    }
283
284    if (m_scriptPreprocessor->isValid())
285        return true;
286
287    m_scriptPreprocessor.clear();
288    // Don't retry the compile if we fail one time.
289    m_preprocessorSourceCode.clear();
290    return false;
291}
292
293// Source to Source processing iff debugger enabled and it has loaded a preprocessor.
294PassOwnPtr<ScriptSourceCode> PageScriptDebugServer::preprocess(LocalFrame* frame, const ScriptSourceCode& sourceCode)
295{
296    if (!canPreprocess(frame))
297        return PassOwnPtr<ScriptSourceCode>();
298
299    String preprocessedSource = m_scriptPreprocessor->preprocessSourceCode(sourceCode.source(), sourceCode.url());
300    return adoptPtr(new ScriptSourceCode(preprocessedSource, sourceCode.url()));
301}
302
303String PageScriptDebugServer::preprocessEventListener(LocalFrame* frame, const String& source, const String& url, const String& functionName)
304{
305    if (!canPreprocess(frame))
306        return source;
307
308    return m_scriptPreprocessor->preprocessSourceCode(source, url, functionName);
309}
310
311void PageScriptDebugServer::clearPreprocessor()
312{
313    m_scriptPreprocessor.clear();
314}
315
316void PageScriptDebugServer::muteWarningsAndDeprecations()
317{
318    FrameConsole::mute();
319    UseCounter::muteForInspector();
320}
321
322void PageScriptDebugServer::unmuteWarningsAndDeprecations()
323{
324    FrameConsole::unmute();
325    UseCounter::unmuteForInspector();
326}
327
328} // namespace blink
329