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