1 /*
2  *  This file is part of the KDE libraries
3  *  Copyright (C) 2006 Matt Broadstone (mbroadst@gmail.com)
4  *  Copyright (C) 2007 Maks Orlovich <maksim@kde.org>
5  *  Copyright (C) 2000-2001 Harri Porten (porten@kde.org)
6  *  Copyright (C) 2001,2003 Peter Kelly (pmk@post.com)
7  *
8  *  This library is free software; you can redistribute it and/or
9  *  modify it under the terms of the GNU Library General Public
10  *  License as published by the Free Software Foundation; either
11  *  version 2 of the License, or (at your option) any later version.
12  *
13  *  This library is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  *  Library General Public License for more details.
17  *
18  *  You should have received a copy of the GNU Library General Public
19  *  License along with this library; if not, write to the Free Software
20  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21  */
22 
23 #include "debugwindow.h"
24 
25 #include <QSharedData>
26 #include "khtml_debug.h"
27 #include <QtAlgorithms>
28 
29 #include <ktoolbar.h>
30 #include <klocalizedstring.h>
31 #include "khtml_debug.h"
32 #include <kactioncollection.h>
33 #include <ktoggleaction.h>
34 #include <kconfig.h>
35 #include <kstringhandler.h>
36 #include <kxmlguifactory.h>
37 
38 #include <ktexteditor/configinterface.h>
39 #include <ktexteditor/sessionconfiginterface.h>
40 #include <ktexteditor/modificationinterface.h>
41 #include <ktexteditor/editorchooser.h>
42 #include <ktexteditor/cursor.h>
43 
44 #include "kjs_proxy.h"
45 #include "kjs_dom.h"
46 #include "kjs_binding.h"
47 #include "khtmlview.h"
48 #include "khtml_settings.h"
49 #include "khtml_factory.h"
50 #include <kjs/ustring.h>
51 #include <kjs/object.h>
52 #include <kjs/function.h>
53 #include <kjs/context.h>
54 #include <ecma/kjs_window.h>
55 
56 #include <QMenu>
57 #include <QMenuBar>
58 #include <QVBoxLayout>
59 #include <QSplitter>
60 #include <QStatusBar>
61 #include <QTabWidget>
62 #include <QToolButton>
63 
64 #include "breakpointsdock.h"
65 #include "consoledock.h"
66 #include "localvariabledock.h"
67 #include "watchesdock.h"
68 #include "callstackdock.h"
69 #include "scriptsdock.h"
70 
71 #include "value2string.h"
72 #include "errordlg.h"
73 
74 using namespace KJS;
75 using namespace KJSDebugger;
76 
77 DebugWindow *DebugWindow::s_debugger = 0;
78 
window()79 DebugWindow *DebugWindow::window()
80 {
81     if (!s_debugger) {
82         s_debugger = new DebugWindow();
83     }
84 
85     return s_debugger;
86 }
87 
88 // ----------------------------------------------
89 
DebugWindow(QWidget * parent)90 DebugWindow::DebugWindow(QWidget *parent)
91     : KXmlGuiWindow(parent, Qt::Window),
92       KComponentData("kjs_debugger")
93 {
94     setAttribute(Qt::WA_DeleteOnClose, false);
95     setObjectName(QLatin1String("DebugWindow"));
96     setCaption(i18n("JavaScript Debugger"));
97 
98 //     m_watches = new WatchesDock;
99     m_localVariables = new LocalVariablesDock;
100     m_scripts = new ScriptsDock;
101     m_callStack = new CallStackDock;
102     //m_breakpoints = new BreakpointsDock;
103     m_console = new ConsoleDock;
104     connect(m_console, SIGNAL(requestEval(QString)),
105             this,      SLOT(doEval(QString)));
106 
107     addDockWidget(Qt::LeftDockWidgetArea, m_scripts);
108     addDockWidget(Qt::LeftDockWidgetArea, m_localVariables);
109     addDockWidget(Qt::LeftDockWidgetArea, m_callStack);
110     //addDockWidget(Qt::LeftDockWidgetArea, m_breakpoints);
111 //     addDockWidget(Qt::LeftDockWidgetArea, m_watches);
112 
113     QSplitter *splitter = new QSplitter(Qt::Vertical);
114     createTabWidget();
115     splitter->addWidget(m_tabWidget);
116     splitter->addWidget(m_console);
117     splitter->setStretchFactor(0, 10);
118     splitter->setStretchFactor(1, 1);
119 
120     setCentralWidget(splitter);
121     resize(800, 500);
122 
123     syncFromConfig(); // need to do it before creating actions to know their state
124     createActions();
125     createMenus();
126     createToolBars();
127     createStatusBar();
128     m_tabWidget->hide();
129 
130     connect(m_scripts, SIGNAL(displayScript(KJSDebugger::DebugDocument*)),
131             this, SLOT(displayScript(KJSDebugger::DebugDocument*)));
132     connect(m_callStack, SIGNAL(displayScript(KJSDebugger::DebugDocument*,int)),
133             this, SLOT(displayScript(KJSDebugger::DebugDocument*,int)));
134     connect(m_callStack, SIGNAL(displayScript(KJSDebugger::DebugDocument*,int)),
135             this, SLOT(updateVarView()));
136 
137     m_breakAtNext = false;
138     m_modalLevel  = 0;
139     m_runningSessionCtx = 0;
140 }
141 
syncFromConfig()142 void DebugWindow::syncFromConfig()
143 {
144     KConfigGroup config(KSharedConfig::openConfig(), "Javascript Debugger");
145     m_reindentSources = config.readEntry<bool>("ReindentSources", true);
146     m_catchExceptions = config.readEntry<bool>("CatchExceptions", true);
147     // m_catchExceptions = khtmlpart->settings()->isJavaScriptErrorReportingEnabled();
148     // m_reindentSources =
149 }
150 
syncToConfig()151 void DebugWindow::syncToConfig()
152 {
153     KConfigGroup config(KSharedConfig::openConfig(), "Javascript Debugger");
154     config.writeEntry("ReindentSources", m_reindentSources);
155     config.writeEntry("CatchExceptions", m_catchExceptions);
156 }
157 
shouldReindentSources() const158 bool DebugWindow::shouldReindentSources() const
159 {
160     return m_reindentSources;
161 }
162 
settingsChanged()163 void DebugWindow::settingsChanged()
164 {
165     m_catchExceptions = m_catchExceptionsAction->isChecked();
166     m_reindentSources = m_reindentAction->isChecked();
167     syncToConfig();
168 }
169 
createActions()170 void DebugWindow::createActions()
171 {
172     // Flow control actions
173     m_stopAct = new KToggleAction(QIcon::fromTheme(":/images/stop.png"), i18n("&Break at Next Statement"), this);
174     m_stopAct->setIconText(i18n("Break at Next"));
175     actionCollection()->addAction("stop", m_stopAct);
176     m_stopAct->setEnabled(true);
177     connect(m_stopAct, SIGNAL(triggered(bool)), this, SLOT(stopAtNext()));
178 
179     m_continueAct = new QAction(QIcon::fromTheme(":/images/continue.png"), i18n("Continue"), this);
180     actionCollection()->addAction("continue", m_continueAct);
181     actionCollection()->setDefaultShortcut(m_continueAct, Qt::Key_F9);
182     m_continueAct->setEnabled(false);
183     connect(m_continueAct, SIGNAL(triggered(bool)), this, SLOT(continueExecution()));
184 
185     m_stepOverAct = new QAction(QIcon::fromTheme(":/images/step-over.png"), i18n("Step Over"), this);
186     actionCollection()->addAction("stepOver", m_stepOverAct);
187     actionCollection()->setDefaultShortcut(m_stepOverAct, Qt::Key_F10);
188     m_stepOverAct->setEnabled(false);
189     connect(m_stepOverAct, SIGNAL(triggered(bool)), this, SLOT(stepOver()));
190 
191     m_stepIntoAct = new QAction(QIcon::fromTheme(":/images/step-into.png"), i18n("Step Into"), this);
192     actionCollection()->addAction("stepInto", m_stepIntoAct);
193     actionCollection()->setDefaultShortcut(m_stepIntoAct, Qt::Key_F11);
194     m_stepIntoAct->setEnabled(false);
195 
196     connect(m_stepIntoAct, SIGNAL(triggered(bool)), this, SLOT(stepInto()));
197 
198     m_stepOutAct = new QAction(QIcon::fromTheme(":/images/step-out.png"), i18n("Step Out"), this);
199     actionCollection()->addAction("stepOut", m_stepOutAct);
200     actionCollection()->setDefaultShortcut(m_stepOutAct, Qt::Key_F12);
201     m_stepOutAct->setEnabled(false);
202     connect(m_stepOutAct, SIGNAL(triggered(bool)), this, SLOT(stepOut()));
203 
204     m_reindentAction = new KToggleAction(i18n("Reindent Sources"), this);
205     actionCollection()->addAction("reindent", m_reindentAction);
206     m_reindentAction->setChecked(m_reindentSources);
207     connect(m_reindentAction, SIGNAL(toggled(bool)), this, SLOT(settingsChanged()));
208 
209     m_catchExceptionsAction = new KToggleAction(i18n("Report Exceptions"), this);
210     actionCollection()->addAction("except", m_catchExceptionsAction);
211     m_catchExceptionsAction->setChecked(m_catchExceptions);
212     connect(m_catchExceptionsAction, SIGNAL(toggled(bool)), this, SLOT(settingsChanged()));
213 }
214 
createMenus()215 void DebugWindow::createMenus()
216 {
217     QMenu *debugMenu = new QMenu(i18n("&Debug"), menuBar());
218     debugMenu->addAction(m_stopAct);
219     debugMenu->addAction(m_continueAct);
220     debugMenu->addAction(m_stepOverAct);
221     debugMenu->addAction(m_stepIntoAct);
222     debugMenu->addAction(m_stepOutAct);
223     menuBar()->addMenu(debugMenu);
224 
225     QMenu *settingsMenu = new QMenu(i18n("&Settings"), menuBar());
226     settingsMenu->addAction(m_catchExceptionsAction);
227     settingsMenu->addAction(m_reindentAction);
228     menuBar()->addMenu(settingsMenu);
229 }
230 
createToolBars()231 void DebugWindow::createToolBars()
232 {
233     toolBar()->addAction(m_stopAct);
234     toolBar()->addSeparator();
235     toolBar()->addAction(m_continueAct);
236     toolBar()->addAction(m_stepOverAct);
237     toolBar()->addAction(m_stepIntoAct);
238     toolBar()->addAction(m_stepOutAct);
239 }
240 
createTabWidget()241 void DebugWindow::createTabWidget()
242 {
243     m_tabWidget = new QTabWidget;
244 
245     QToolButton *closeTabButton = new QToolButton(m_tabWidget);
246     m_tabWidget->setCornerWidget(closeTabButton, Qt::TopRightCorner);
247     closeTabButton->setCursor(Qt::ArrowCursor);
248     closeTabButton->setAutoRaise(true);
249     closeTabButton->setIcon(QIcon::fromTheme("tab-close"));
250     connect(closeTabButton, SIGNAL(clicked()), this, SLOT(closeTab()));
251     closeTabButton->setToolTip(i18n("Close source"));
252     closeTabButton->setEnabled(true);
253 }
254 
createStatusBar()255 void DebugWindow::createStatusBar()
256 {
257     statusBar()->showMessage(i18n("Ready"));
258 }
259 
updateStoppedMark(RunMode mode)260 void DebugWindow::updateStoppedMark(RunMode mode)
261 {
262     if (!ctx()) {
263         return;
264     }
265 
266     DebugDocument::Ptr doc = ctx()->activeDocument();
267     assert(!doc.isNull());
268     KTextEditor::MarkInterface *imark = qobject_cast<KTextEditor::MarkInterface *>(doc->viewerDocument());
269 
270     if (mode == Running) {
271         // No longer stopped there.
272         if (imark)
273             imark->removeMark(ctx()->activeLine() - doc->baseLine(),
274                               KTextEditor::MarkInterface::Execution);
275     } else {
276         displayScript(doc.get(), ctx()->activeLine());
277         if (imark)
278             imark->addMark(ctx()->activeLine() - doc->baseLine(),
279                            KTextEditor::MarkInterface::Execution);
280     }
281 }
282 
setUIMode(RunMode mode)283 void DebugWindow::setUIMode(RunMode mode)
284 {
285     // update editor stuff. We want to do it first, since the callstack
286     // may try to restore our position in some cases
287     updateStoppedMark(mode);
288 
289     if (mode == Running) {
290         // Toggle buttons..
291         m_continueAct->setEnabled(false);
292         m_stepIntoAct->setEnabled(false);
293         m_stepOutAct->setEnabled(false);
294         m_stepOverAct->setEnabled(false);
295         m_runningSessionCtx = ctx();
296     } else {
297         // Show local variables and the bt
298         m_localVariables->updateDisplay(ctx()->execContexts.top());
299         m_callStack->displayStack(ctx());
300 
301         // Toggle buttons..
302         m_continueAct->setEnabled(true);
303         m_stepIntoAct->setEnabled(true);
304         m_stepOutAct->setEnabled(true);
305         m_stepOverAct->setEnabled(true);
306         m_runningSessionCtx = 0;
307     }
308 }
309 
310 // -------------------------------------------------------------
311 
isBlocked()312 bool DebugWindow::isBlocked()
313 {
314     DebugWindow *self = window();
315     if (!self) {
316         return false;
317     }
318     return self->inSession() || self->m_modalLevel;
319 }
320 
resetTimeoutsIfNeeded()321 void DebugWindow::resetTimeoutsIfNeeded()
322 {
323     if (!isBlocked()) {
324         KJS::Interpreter *intp = KJS::Interpreter::firstInterpreter();
325         do {
326             intp->restartTimeoutCheck();
327             intp = intp->nextInterpreter();
328         } while (intp != KJS::Interpreter::firstInterpreter());
329     }
330 }
331 
forceStopAtNext()332 void DebugWindow::forceStopAtNext()
333 {
334     DebugWindow *self = window();
335     self->m_breakAtNext = true;
336 }
337 
stopAtNext()338 void DebugWindow::stopAtNext()
339 {
340     m_breakAtNext = m_stopAct->isChecked();
341 }
342 
shouldContinue(InterpreterContext * ic)343 bool DebugWindow::shouldContinue(InterpreterContext *ic)
344 {
345     return !ic || ic->mode != Abort;
346 }
347 
leaveDebugSession()348 void DebugWindow::leaveDebugSession()
349 {
350     // Update UI for running mode, unless we expect things to be quick;
351     // in which case we'll only update if we have to, when running stops
352     if (ctx()->mode != Step) {
353         setUIMode(Running);
354     } else { // In the other case, we still want to remove the old running marker, however
355         updateStoppedMark(Running);
356     }
357 
358     m_activeSessionCtxs.pop();
359     resetTimeoutsIfNeeded();
360     exitLoop();
361 }
362 
continueExecution()363 void DebugWindow::continueExecution()
364 {
365     if (!ctx()) {
366         return;    //In case we're in the middle of a step.. Hardly ideal, but..
367     }
368     leaveDebugSession();
369 }
370 
stepInto()371 void DebugWindow::stepInto()
372 {
373     if (!ctx()) {
374         return;    //In case we're in the middle of a step.. Hardly ideal, but..
375     }
376     ctx()->mode = Step;
377     leaveDebugSession();
378 }
379 
stepOut()380 void DebugWindow::stepOut()
381 {
382     if (!ctx()) {
383         return;    //In case we're in the middle of a step.. Hardly ideal, but..
384     }
385     ctx()->mode        = StepOut;
386     ctx()->depthAtSkip = ctx()->execContexts.size();
387     leaveDebugSession();
388 }
389 
stepOver()390 void DebugWindow::stepOver()
391 {
392     if (!ctx()) {
393         return;    //In case we're in the middle of a step.. Hardly ideal, but..
394     }
395     ctx()->mode        = StepOver;
396     ctx()->depthAtSkip = ctx()->execContexts.size();
397     leaveDebugSession();
398 }
399 
~DebugWindow()400 DebugWindow::~DebugWindow()
401 {
402     assert(m_docsForIntrp.isEmpty());
403     assert(m_docForSid.isEmpty());
404     assert(m_activeSessionCtxs.isEmpty());
405     s_debugger = 0;
406 }
407 
closeEvent(QCloseEvent * event)408 void DebugWindow::closeEvent(QCloseEvent *event)
409 {
410     if (inSession()) {
411         event->setAccepted(false);
412     } else {
413         KXmlGuiWindow::closeEvent(event);
414     }
415 }
416 
417 // -------------------------------------------------------------
418 
attach(Interpreter * interp)419 void DebugWindow::attach(Interpreter *interp)
420 {
421     // ::attach can be called many times, so handle that
422     if (!m_contexts[interp]) {
423         m_contexts[interp] = new InterpreterContext;
424     }
425     KJS::Debugger::attach(interp);
426 }
427 
cleanupDocument(DebugDocument::Ptr doc)428 void DebugWindow::cleanupDocument(DebugDocument::Ptr doc)
429 {
430     m_docForSid.remove(doc->sid());
431     m_scripts->documentDestroyed(doc.get());
432 }
433 
fatalAssert(bool shouldBeTrue,const char * error)434 static void fatalAssert(bool shouldBeTrue, const char *error)
435 {
436     if (!shouldBeTrue) {
437         qFatal(error);
438     }
439 }
440 
detach(KJS::Interpreter * interp)441 void DebugWindow::detach(KJS::Interpreter *interp)
442 {
443     assert(interp); //detach(0) should never get here, since only ~Debugger calls it
444 
445     // Make sure no weird recursions can still happen!
446     InterpreterContext *ctx = m_contexts[interp];
447     assert(!m_activeSessionCtxs.contains(ctx));
448 
449     // Go through, and kill all the fragments from here.
450     QList<DebugDocument::Ptr> docs = m_docsForIntrp[interp];
451 
452     foreach (DebugDocument::Ptr doc, docs) {
453         cleanupDocument(doc);
454     }
455 
456     m_docsForIntrp.remove(interp);
457 
458     delete m_contexts.take(interp);
459     resetTimeoutsIfNeeded();
460 
461     KJS::Debugger::detach(interp);
462 }
463 
clearInterpreter(KJS::Interpreter * interp)464 void DebugWindow::clearInterpreter(KJS::Interpreter *interp)
465 {
466     // We may get a clear when we weren't even attached, if the
467     // interpreter gets created but nothing gets run in it.
468     // Be careful not to insert a bogus null into contexts map then
469     InterpreterContext *ctx = m_contexts.value(interp);
470     if (!ctx) {
471         return;
472     }
473 
474     fatalAssert(!m_activeSessionCtxs.contains(ctx), "Interpreter clear on active session");
475 
476     // Cleanup all documents; but we keep the open windows open so
477     // they can be reused.
478     QMutableListIterator<DebugDocument::Ptr> i(m_docsForIntrp[interp]);
479     while (i.hasNext()) {
480         DebugDocument::Ptr doc = i.next();
481         if (m_openDocuments.contains(doc.get())) {
482             doc->markReload();
483         } else {
484             i.remove();
485         }
486 
487         cleanupDocument(doc);
488     }
489 }
490 
sourceParsed(ExecState * exec,int sourceId,const UString & jsSourceURL,const UString & source,int startingLineNumber,int errorLine,const UString &)491 bool DebugWindow::sourceParsed(ExecState *exec, int sourceId, const UString &jsSourceURL,
492                                const UString &source, int startingLineNumber, int errorLine, const UString &/* errorMsg */)
493 {
494     Q_UNUSED(exec);
495 
496     // qCDebug(KHTML_LOG) << "sourceId: " << sourceId
497             << "sourceURL: " << jsSourceURL.qstring()
498             << "startingLineNumber: " << startingLineNumber
499             << "errorLine: " << errorLine;
500 
501     QString sourceURL = jsSourceURL.qstring();
502     // Tell it about this portion..
503     QString qsource =  source.qstring();
504 
505     DebugDocument::Ptr document;
506 
507     // See if there is an open document window we can reuse...
508     foreach (DebugDocument::Ptr cand, m_openDocuments) {
509         if (cand->isMarkedReload() && cand->url() == sourceURL && cand->baseLine() == startingLineNumber) {
510             document = cand;
511         }
512     }
513 
514     // If we don't have a document, make a new one.
515     if (!document) {
516         // If there is no URL, try to figure one out from the caller ---
517         // useful for function constructor and eval.
518         QString uiURL = sourceURL;
519 
520         if (uiURL.isEmpty()) {
521             // Scan through all contexts, and see which one matches
522             foreach (InterpreterContext *ic, m_contexts) {
523                 if (!ic->execContexts.isEmpty() && ic->execContexts.top() == exec) {
524                     uiURL = ic->callStack.top().doc->url();
525                     break;
526                 }
527             }
528 
529         }
530 
531         document = new DebugDocument(exec->dynamicInterpreter(), uiURL,
532                                      sourceId, startingLineNumber, qsource);
533 
534         connect(document.get(), SIGNAL(documentDestroyed(KJSDebugger::DebugDocument*)),
535                 this, SLOT(documentDestroyed(KJSDebugger::DebugDocument*)));
536     } else {
537         // Otherwise, update.
538         document->reloaded(sourceId, qsource);
539     }
540 
541     m_docsForIntrp[exec->dynamicInterpreter()].append(document);
542 
543     // Show it in the script list view
544     m_scripts->addDocument(document.get());
545 
546     // Memorize the document..
547     m_docForSid[sourceId] = document;
548 
549     if (qsource.contains("function")) { // Approximate knowledge of whether code has functions. Ewwww...
550         document->setHasFunctions();
551     }
552 
553     return shouldContinue(m_contexts[exec->dynamicInterpreter()]);
554 }
555 
exception(ExecState * exec,int sourceId,int lineNo,JSValue * exceptionObj)556 bool DebugWindow::exception(ExecState *exec, int sourceId, int lineNo, JSValue *exceptionObj)
557 {
558     InterpreterContext *ic = m_contexts[exec->dynamicInterpreter()];
559 
560     // Don't report it if error reporting is not on
561     KParts::ReadOnlyPart *part = static_cast<ScriptInterpreter *>(exec->dynamicInterpreter())->part();
562     KHTMLPart *khtmlpart = qobject_cast<KHTMLPart *>(part);
563 
564     if ((khtmlpart && !khtmlpart->settings()->isJavaScriptErrorReportingEnabled()) || !m_catchExceptions) {
565         return shouldContinue(ic);
566     }
567 
568     QString exceptionMsg = exceptionToString(exec, exceptionObj);
569 
570     // Look up fragment info from sourceId
571     DebugDocument::Ptr doc = m_docForSid[sourceId];
572 
573     // Figure out filename.
574     QString url = "????";
575     if (exec->context()->codeType() == EvalCode) {
576         url = "eval";
577     }
578     if (!doc->url().isEmpty()) {
579         url = doc->url();
580     }
581 
582     QString msg = i18n("An error occurred while attempting to run a script on this page.\n\n%1 line %2:\n%3",
583                        KStringHandler::rsqueeze(url, 80), lineNo, exceptionMsg);
584 
585     KJSErrorDialog dlg(this /*dlgParent*/, msg, true);
586     TimerPauser pause(exec); // don't let any timers fire while we're doing this!
587     ++m_modalLevel;
588     dlg.exec();
589     --m_modalLevel;
590     resetTimeoutsIfNeeded();
591 
592     if (dlg.dontShowAgain()) {
593         m_catchExceptions = false;
594         m_catchExceptionsAction->setChecked(false);
595     }
596 
597     if (dlg.debugSelected()) {
598         // We generally want to stop at the current line, to see what's going on... There is one exception, though:
599         // in case we've got a parse error, we can't actually stop, but we want to still display stuff.
600         if (ic->hasActiveDocument()) {
601             enterDebugSession(exec, doc.get(), lineNo);
602         } else {
603             displayScript(doc.get(), lineNo);
604         }
605     }
606 
607     return shouldContinue(ic);
608 }
609 
atStatement(ExecState * exec,int sourceId,int firstLine,int lastLine)610 bool DebugWindow::atStatement(ExecState *exec, int sourceId, int firstLine, int lastLine)
611 {
612     InterpreterContext *ctx = m_contexts[exec->dynamicInterpreter()];
613     ctx->updateCall(firstLine);
614     return checkSourceLocation(exec, sourceId, firstLine, lastLine);
615 }
616 
checkSourceLocation(KJS::ExecState * exec,int sourceId,int firstLine,int lastLine)617 bool DebugWindow::checkSourceLocation(KJS::ExecState *exec, int sourceId, int firstLine, int lastLine)
618 {
619     Q_UNUSED(lastLine);
620 
621     InterpreterContext *candidateCtx = m_contexts[exec->dynamicInterpreter()];
622 
623     if (!shouldContinue(candidateCtx)) {
624         return false;
625     }
626 
627     bool enterDebugMode = false;
628 
629     // We stop when breakAtNext is set regardless of the context.
630     if (m_breakAtNext) {
631         enterDebugMode = true;
632     }
633 
634     if (candidateCtx->mode == Step) {
635         enterDebugMode = true;
636     } else if (candidateCtx->mode == StepOver) {
637         if (candidateCtx->execContexts.size() <= candidateCtx->depthAtSkip) {
638             enterDebugMode = true;
639         }
640     }
641 
642     DebugDocument::Ptr document = m_docForSid[sourceId];
643     assert(!document.isNull());
644 
645     // Now check for breakpoints if needed
646     if (document->hasBreakpoint(firstLine)) {
647         enterDebugMode = true;
648     }
649 
650     // Block the UI, and enable all the debugging buttons, etc.
651     if (enterDebugMode) {
652         enterDebugSession(exec, document.get(), firstLine);
653     }
654 
655     // re-checking the abort mode here, in case it got change when recursing
656     return shouldContinue(candidateCtx);
657 }
658 
enterContext(ExecState * exec,int sourceId,int lineno,JSObject * function,const List & args)659 bool DebugWindow::enterContext(ExecState *exec, int sourceId, int lineno, JSObject *function, const List &args)
660 {
661     Q_UNUSED(args);
662     InterpreterContext *ctx = m_contexts[exec->dynamicInterpreter()];
663 
664     // First update call stack.
665     DebugDocument::Ptr document = m_docForSid[sourceId];
666     QString stackEntry = document->name();
667     if (function && function->inherits(&InternalFunctionImp::info)) {
668         KJS::InternalFunctionImp *func = static_cast<InternalFunctionImp *>(function);
669         QString functionName = func->functionName().qstring();
670         if (!functionName.isEmpty()) {
671             stackEntry = functionName;
672         }
673     }
674 
675     if (exec->context()->codeType() == EvalCode) {
676         stackEntry = "eval";
677     }
678 
679     ctx->addCall(document, stackEntry, lineno);
680     ctx->execContexts.push(exec);
681 
682     return shouldContinue(ctx);
683 }
684 
exitContext(ExecState * exec,int sourceId,int lineno,JSObject * function)685 bool DebugWindow::exitContext(ExecState *exec, int sourceId, int lineno, JSObject *function)
686 {
687     Q_UNUSED(lineno);
688     Q_UNUSED(function);
689     InterpreterContext *ic  = m_contexts[exec->dynamicInterpreter()];
690 
691     if (m_localVariables->currentlyDisplaying() == exec) {
692         // Clear local variable and stack display when exiting a function
693         // it corresponds to
694         m_localVariables->updateDisplay(0);
695         m_callStack->clearDisplay();
696     }
697 
698     ic->removeCall();
699     assert(ic->execContexts.top() == exec);
700     ic->execContexts.pop();
701 
702     // See if we should stop on the next instruction.
703     // Note that we should not test StepOver here, as
704     // we may have a return event at same level
705     // in case of a f(g(), h()) type setup
706     // Note that in the above case a StepOut from
707     // g() would step into h() from below, which is reasonable
708     if (ic->mode == StepOut) {
709         if (ic->execContexts.size() < ic->depthAtSkip) {
710             ic->mode = Step;
711         }
712     }
713 
714     // There is a special case here: we may have clicked step, and
715     // ran out of code, and now UI is in stopped mode (since we avoid
716     // switching Stopped->Running->Stopped on plain single-step)
717     // This happens when:
718     // 1) No session is active
719     // 2) The context steck for this session is empty
720     // 3) This session is thought to be waiting for a step.
721     if (m_activeSessionCtxs.isEmpty() &&
722             ic->execContexts.isEmpty() && ic->mode == Step) {
723         setUIMode(Running);
724     }
725 
726     // On the other hand, UI may be in running mode, but we've just
727     // ran out of all the code for this interpreter. In this case,
728     // reactive the previous session in stopped mode.
729     if (!m_activeSessionCtxs.isEmpty() && m_runningSessionCtx == ic) {
730         if (ic->execContexts.isEmpty()) {
731             setUIMode(Stopped);
732         } else {
733             fatalAssert(exec->context()->callingContext(), "Apparent event re-entry");
734         }
735         // Sanity check: the modality protection should disallow us to exit
736         // from a context called by KHTML unless it's at very top level
737         // (e.g. no other execs on top)
738     }
739 
740     // Also, if we exit from an eval context, we probably want to
741     // clear the corresponding document, unless it's open.
742     // We can not do it safely if there are any functions declared,
743     // however, since they can escape.
744     if (exec->context()->codeType() == EvalCode) {
745         DebugDocument::Ptr doc = m_docForSid[sourceId];
746         if (!m_openDocuments.contains(doc.get()) && !doc->hasFunctions()) {
747             cleanupDocument(doc);
748             m_docsForIntrp[exec->dynamicInterpreter()].removeAll(doc);
749         }
750     }
751 
752     return shouldContinue(ic);
753 }
754 
755 // End KJS::Debugger overloads
756 
doEval(const QString & qcode)757 void DebugWindow::doEval(const QString &qcode)
758 {
759     // Work out which execution state to use. If we're currently in a debugging session,
760     // use the context of the presently selected frame, if any --- otherwise, use the global execution
761     // state from the interpreter corresponding to the currently displayed source file.
762     ExecState *exec;
763     JSObject  *thisObj;
764 
765     if (inSession()) {
766         exec = m_callStack->selectedFrameContext();
767         if (!exec) {
768             exec = m_activeSessionCtxs.top()->execContexts.top();
769         }
770         thisObj = exec->context()->thisValue();
771     } else {
772         int idx = m_tabWidget->currentIndex();
773         if (idx < 0) {
774             m_console->reportResult(qcode,
775                                     i18n("Do not know where to evaluate the expression. Please pause a script or open a source file."));
776             return;
777         }
778         DebugDocument *document = m_openDocuments[idx];
779         exec    = document->interpreter()->globalExec();
780         thisObj = document->interpreter()->globalObject();
781     }
782 
783     JSValue   *oldException = 0;
784 
785     UString code(qcode);
786 
787     Interpreter *interp = exec->dynamicInterpreter();
788 
789     // If there was a previous exception, clear it for now and save it.
790     if (exec->hadException()) {
791         oldException = exec->exception();
792         exec->clearException();
793     }
794 
795     JSObject *obj = interp->globalObject()->get(exec, "eval")->getObject();
796     List args;
797     args.append(jsString(code));
798 
799     // ### we want the CPU guard here.. But only for this stuff,
800     // not others things. oooh boy. punt for now
801 
802     JSValue *retVal = obj->call(exec, thisObj, args);
803 
804     // Print the return value or exception message to the console
805     QString msg;
806     if (exec->hadException()) {
807         JSValue *exc = exec->exception();
808         exec->clearException();
809         msg = i18n("Evaluation threw an exception %1", exceptionToString(exec, exc));
810     } else {
811         msg = valueToString(retVal);
812     }
813 
814     // Restore old exception if need be, and always clear ours
815     exec->clearException();
816     if (oldException) {
817         exec->setException(oldException);
818     }
819 
820     m_console->reportResult(qcode, msg);
821 
822     // Make sure to re-activate the line we were stopped in,
823     // in case a nested session was active
824     if (inSession()) {
825         setUIMode(Stopped);
826     }
827 }
828 
updateVarView()829 void DebugWindow::updateVarView()
830 {
831     m_localVariables->updateDisplay(m_callStack->selectedFrameContext());
832 }
833 
displayScript(DebugDocument * document)834 void DebugWindow::displayScript(DebugDocument *document)
835 {
836     displayScript(document, -1);
837 }
838 
displayScript(DebugDocument * document,int line)839 void DebugWindow::displayScript(DebugDocument *document, int line)
840 {
841     if (!isVisible()) {
842         show();
843     }
844 
845     if (m_tabWidget->isHidden()) {
846         m_tabWidget->show();
847     }
848 
849     KTextEditor::View *view = document->viewerView();
850 
851     if (!m_openDocuments.contains(document)) {
852         m_openDocuments.append(document);
853         m_tabWidget->addTab(view, document->name());
854     }
855 
856     // Focus the tab
857     int idx = m_openDocuments.indexOf(document);
858     m_tabWidget->setCurrentIndex(idx);
859 
860     // Go to line..
861     if (line != -1) {
862         view->setCursorPosition(KTextEditor::Cursor(line - document->baseLine(), 0));
863     }
864 }
865 
documentDestroyed(KJSDebugger::DebugDocument * doc)866 void DebugWindow::documentDestroyed(KJSDebugger::DebugDocument *doc)
867 {
868     //### this is likely to be very ugly UI-wise
869     // Close this document..
870     int idx = m_openDocuments.indexOf(doc);
871     if (idx == -1) {
872         return;
873     }
874 
875     m_tabWidget->removeTab(idx);
876     m_openDocuments.removeAt(idx);
877     if (m_openDocuments.isEmpty()) {
878         m_tabWidget->hide();
879     }
880 }
881 
closeTab()882 void DebugWindow::closeTab()
883 {
884     int idx = m_tabWidget->currentIndex();
885     m_tabWidget->removeTab(idx);
886     m_openDocuments.removeAt(idx);
887     if (m_openDocuments.isEmpty()) {
888         m_tabWidget->hide();
889     }
890 }
891 
markSet(KTextEditor::Document * document,KTextEditor::Mark mark,KTextEditor::MarkInterface::MarkChangeAction action)892 void DebugWindow::markSet(KTextEditor::Document *document, KTextEditor::Mark mark,
893                           KTextEditor::MarkInterface::MarkChangeAction action)
894 {
895     if (mark.type != KTextEditor::MarkInterface::BreakpointActive) {
896         return;
897     }
898 
899     // ### ugleeee -- get our docu from viewer docu's parent, to avoid book keeping
900     DebugDocument *debugDocument = qobject_cast<DebugDocument *>(document->parent());
901     assert(debugDocument);
902 
903     int lineNumber = mark.line + debugDocument->baseLine();
904     switch (action) {
905     case KTextEditor::MarkInterface::MarkAdded:
906         // qCDebug(KHTML_LOG) << lineNumber;
907         debugDocument->setBreakpoint(lineNumber);
908         break;
909     case KTextEditor::MarkInterface::MarkRemoved:
910         debugDocument->removeBreakpoint(lineNumber);
911         break;
912     }
913 
914     // qCDebug(KHTML_LOG) << "breakpoint set for: " << endl
915             << "document: " << document->documentName() << endl
916             << "line: " << lineNumber;
917 }
918 
enterDebugSession(KJS::ExecState * exec,DebugDocument * document,int line)919 void DebugWindow::enterDebugSession(KJS::ExecState *exec, DebugDocument *document, int line)
920 {
921     Q_UNUSED(document);
922     Q_UNUSED(line);
923 
924     // This "enters" a new debugging session, i.e. enables usage of the debugging window
925     // It re-enters the qt event loop here, allowing execution of other parts of the
926     // program to continue while the script is stopped. We have to be a bit careful here,
927     // i.e. make sure the user can't quit the app, and disable other event handlers which
928     // could interfere with the debugging session.
929 
930     if (!isVisible()) {
931         show();
932     }
933 
934     m_activeSessionCtxs.push(m_contexts[exec->dynamicInterpreter()]);
935     ctx()->mode   = Normal;
936     m_breakAtNext = false;
937     m_stopAct->setChecked(false);
938 
939     setUIMode(Stopped);
940     enterLoop();
941 }
942 
943 //// Event handling - ripped from old kjsdebugger
944 
eventFilter(QObject * object,QEvent * event)945 bool DebugWindow::eventFilter(QObject *object, QEvent *event)
946 {
947     switch (event->type()) {
948     case QEvent::MouseButtonPress:
949     case QEvent::MouseButtonRelease:
950     case QEvent::MouseButtonDblClick:
951     case QEvent::MouseMove:
952     case QEvent::KeyPress:
953     case QEvent::KeyRelease:
954     case QEvent::Destroy:
955     case QEvent::Close:
956     case QEvent::Quit:
957     case QEvent::Shortcut:
958     case QEvent::ShortcutOverride: {
959         while (object->parent()) {
960             object = object->parent();
961         }
962         if (object == this) {
963             return QWidget::eventFilter(object, event);
964         } else {
965             if (event->type() == QEvent::Close) {
966                 event->setAccepted(false);
967             }
968             return true;
969         }
970     }
971     break;
972     default:
973         return QWidget::eventFilter(object, event);
974     }
975 
976 }
977 
enterLoop()978 void DebugWindow::enterLoop()
979 {
980     QEventLoop eventLoop;
981     m_activeEventLoops.push(&eventLoop);
982 
983     if (m_activeSessionCtxs.size() == 1) {
984         enterModality();
985     }
986 
987 //    eventLoop.exec(QEventLoop::X11ExcludeTimers | QEventLoop::ExcludeSocketNotifiers);
988     eventLoop.exec();
989     m_activeEventLoops.pop();
990 }
991 
exitLoop()992 void DebugWindow::exitLoop()
993 {
994     if (m_activeSessionCtxs.isEmpty()) {
995         leaveModality();
996     }
997     m_activeEventLoops.top()->quit();
998 }
999 
enterModality()1000 void DebugWindow::enterModality()
1001 {
1002     QWidgetList widgets = QApplication::allWidgets();
1003     foreach (QWidget *widget, widgets) {
1004         widget->installEventFilter(this);
1005     }
1006 }
1007 
leaveModality()1008 void DebugWindow::leaveModality()
1009 {
1010     QWidgetList widgets = QApplication::allWidgets();
1011     foreach (QWidget *widget, widgets) {
1012         widget->removeEventFilter(this);
1013     }
1014 }
1015 
1016