File indexing completed on 2024-12-01 09:50:26

0001 /*
0002  *  This file is part of the KDE libraries
0003  *  Copyright (C) 2006 Matt Broadstone (mbroadst@gmail.com)
0004  *  Copyright (C) 2007 Maks Orlovich <maksim@kde.org>
0005  *  Copyright (C) 2000-2001 Harri Porten (porten@kde.org)
0006  *  Copyright (C) 2001,2003 Peter Kelly (pmk@post.com)
0007  *
0008  *  This library is free software; you can redistribute it and/or
0009  *  modify it under the terms of the GNU Library General Public
0010  *  License as published by the Free Software Foundation; either
0011  *  version 2 of the License, or (at your option) any later version.
0012  *
0013  *  This library is distributed in the hope that it will be useful,
0014  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
0015  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0016  *  Library General Public License for more details.
0017  *
0018  *  You should have received a copy of the GNU Library General Public
0019  *  License along with this library; if not, write to the Free Software
0020  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
0021  */
0022 
0023 #include "debugwindow.h"
0024 
0025 #include <QSharedData>
0026 #include "khtml_debug.h"
0027 #include <QtAlgorithms>
0028 
0029 #include <ktoolbar.h>
0030 #include <klocalizedstring.h>
0031 #include "khtml_debug.h"
0032 #include <kactioncollection.h>
0033 #include <ktoggleaction.h>
0034 #include <kconfig.h>
0035 #include <kstringhandler.h>
0036 #include <kxmlguifactory.h>
0037 
0038 #include <ktexteditor/configinterface.h>
0039 #include <ktexteditor/sessionconfiginterface.h>
0040 #include <ktexteditor/modificationinterface.h>
0041 #include <ktexteditor/editorchooser.h>
0042 #include <ktexteditor/cursor.h>
0043 
0044 #include "kjs_proxy.h"
0045 #include "kjs_dom.h"
0046 #include "kjs_binding.h"
0047 #include "khtmlview.h"
0048 #include "khtml_settings.h"
0049 #include "khtml_factory.h"
0050 #include <kjs/ustring.h>
0051 #include <kjs/object.h>
0052 #include <kjs/function.h>
0053 #include <kjs/context.h>
0054 #include <ecma/kjs_window.h>
0055 
0056 #include <QMenu>
0057 #include <QMenuBar>
0058 #include <QVBoxLayout>
0059 #include <QSplitter>
0060 #include <QStatusBar>
0061 #include <QTabWidget>
0062 #include <QToolButton>
0063 
0064 #include "breakpointsdock.h"
0065 #include "consoledock.h"
0066 #include "localvariabledock.h"
0067 #include "watchesdock.h"
0068 #include "callstackdock.h"
0069 #include "scriptsdock.h"
0070 
0071 #include "value2string.h"
0072 #include "errordlg.h"
0073 
0074 using namespace KJS;
0075 using namespace KJSDebugger;
0076 
0077 DebugWindow *DebugWindow::s_debugger = 0;
0078 
0079 DebugWindow *DebugWindow::window()
0080 {
0081     if (!s_debugger) {
0082         s_debugger = new DebugWindow();
0083     }
0084 
0085     return s_debugger;
0086 }
0087 
0088 // ----------------------------------------------
0089 
0090 DebugWindow::DebugWindow(QWidget *parent)
0091     : KXmlGuiWindow(parent, Qt::Window),
0092       KComponentData("kjs_debugger")
0093 {
0094     setAttribute(Qt::WA_DeleteOnClose, false);
0095     setObjectName(QLatin1String("DebugWindow"));
0096     setCaption(i18n("JavaScript Debugger"));
0097 
0098 //     m_watches = new WatchesDock;
0099     m_localVariables = new LocalVariablesDock;
0100     m_scripts = new ScriptsDock;
0101     m_callStack = new CallStackDock;
0102     //m_breakpoints = new BreakpointsDock;
0103     m_console = new ConsoleDock;
0104     connect(m_console, SIGNAL(requestEval(QString)),
0105             this,      SLOT(doEval(QString)));
0106 
0107     addDockWidget(Qt::LeftDockWidgetArea, m_scripts);
0108     addDockWidget(Qt::LeftDockWidgetArea, m_localVariables);
0109     addDockWidget(Qt::LeftDockWidgetArea, m_callStack);
0110     //addDockWidget(Qt::LeftDockWidgetArea, m_breakpoints);
0111 //     addDockWidget(Qt::LeftDockWidgetArea, m_watches);
0112 
0113     QSplitter *splitter = new QSplitter(Qt::Vertical);
0114     createTabWidget();
0115     splitter->addWidget(m_tabWidget);
0116     splitter->addWidget(m_console);
0117     splitter->setStretchFactor(0, 10);
0118     splitter->setStretchFactor(1, 1);
0119 
0120     setCentralWidget(splitter);
0121     resize(800, 500);
0122 
0123     syncFromConfig(); // need to do it before creating actions to know their state
0124     createActions();
0125     createMenus();
0126     createToolBars();
0127     createStatusBar();
0128     m_tabWidget->hide();
0129 
0130     connect(m_scripts, SIGNAL(displayScript(KJSDebugger::DebugDocument*)),
0131             this, SLOT(displayScript(KJSDebugger::DebugDocument*)));
0132     connect(m_callStack, SIGNAL(displayScript(KJSDebugger::DebugDocument*,int)),
0133             this, SLOT(displayScript(KJSDebugger::DebugDocument*,int)));
0134     connect(m_callStack, SIGNAL(displayScript(KJSDebugger::DebugDocument*,int)),
0135             this, SLOT(updateVarView()));
0136 
0137     m_breakAtNext = false;
0138     m_modalLevel  = 0;
0139     m_runningSessionCtx = 0;
0140 }
0141 
0142 void DebugWindow::syncFromConfig()
0143 {
0144     KConfigGroup config(KSharedConfig::openConfig(), "Javascript Debugger");
0145     m_reindentSources = config.readEntry<bool>("ReindentSources", true);
0146     m_catchExceptions = config.readEntry<bool>("CatchExceptions", true);
0147     // m_catchExceptions = khtmlpart->settings()->isJavaScriptErrorReportingEnabled();
0148     // m_reindentSources =
0149 }
0150 
0151 void DebugWindow::syncToConfig()
0152 {
0153     KConfigGroup config(KSharedConfig::openConfig(), "Javascript Debugger");
0154     config.writeEntry("ReindentSources", m_reindentSources);
0155     config.writeEntry("CatchExceptions", m_catchExceptions);
0156 }
0157 
0158 bool DebugWindow::shouldReindentSources() const
0159 {
0160     return m_reindentSources;
0161 }
0162 
0163 void DebugWindow::settingsChanged()
0164 {
0165     m_catchExceptions = m_catchExceptionsAction->isChecked();
0166     m_reindentSources = m_reindentAction->isChecked();
0167     syncToConfig();
0168 }
0169 
0170 void DebugWindow::createActions()
0171 {
0172     // Flow control actions
0173     m_stopAct = new KToggleAction(QIcon::fromTheme(":/images/stop.png"), i18n("&Break at Next Statement"), this);
0174     m_stopAct->setIconText(i18n("Break at Next"));
0175     actionCollection()->addAction("stop", m_stopAct);
0176     m_stopAct->setEnabled(true);
0177     connect(m_stopAct, SIGNAL(triggered(bool)), this, SLOT(stopAtNext()));
0178 
0179     m_continueAct = new QAction(QIcon::fromTheme(":/images/continue.png"), i18n("Continue"), this);
0180     actionCollection()->addAction("continue", m_continueAct);
0181     actionCollection()->setDefaultShortcut(m_continueAct, Qt::Key_F9);
0182     m_continueAct->setEnabled(false);
0183     connect(m_continueAct, SIGNAL(triggered(bool)), this, SLOT(continueExecution()));
0184 
0185     m_stepOverAct = new QAction(QIcon::fromTheme(":/images/step-over.png"), i18n("Step Over"), this);
0186     actionCollection()->addAction("stepOver", m_stepOverAct);
0187     actionCollection()->setDefaultShortcut(m_stepOverAct, Qt::Key_F10);
0188     m_stepOverAct->setEnabled(false);
0189     connect(m_stepOverAct, SIGNAL(triggered(bool)), this, SLOT(stepOver()));
0190 
0191     m_stepIntoAct = new QAction(QIcon::fromTheme(":/images/step-into.png"), i18n("Step Into"), this);
0192     actionCollection()->addAction("stepInto", m_stepIntoAct);
0193     actionCollection()->setDefaultShortcut(m_stepIntoAct, Qt::Key_F11);
0194     m_stepIntoAct->setEnabled(false);
0195 
0196     connect(m_stepIntoAct, SIGNAL(triggered(bool)), this, SLOT(stepInto()));
0197 
0198     m_stepOutAct = new QAction(QIcon::fromTheme(":/images/step-out.png"), i18n("Step Out"), this);
0199     actionCollection()->addAction("stepOut", m_stepOutAct);
0200     actionCollection()->setDefaultShortcut(m_stepOutAct, Qt::Key_F12);
0201     m_stepOutAct->setEnabled(false);
0202     connect(m_stepOutAct, SIGNAL(triggered(bool)), this, SLOT(stepOut()));
0203 
0204     m_reindentAction = new KToggleAction(i18n("Reindent Sources"), this);
0205     actionCollection()->addAction("reindent", m_reindentAction);
0206     m_reindentAction->setChecked(m_reindentSources);
0207     connect(m_reindentAction, SIGNAL(toggled(bool)), this, SLOT(settingsChanged()));
0208 
0209     m_catchExceptionsAction = new KToggleAction(i18n("Report Exceptions"), this);
0210     actionCollection()->addAction("except", m_catchExceptionsAction);
0211     m_catchExceptionsAction->setChecked(m_catchExceptions);
0212     connect(m_catchExceptionsAction, SIGNAL(toggled(bool)), this, SLOT(settingsChanged()));
0213 }
0214 
0215 void DebugWindow::createMenus()
0216 {
0217     QMenu *debugMenu = new QMenu(i18n("&Debug"), menuBar());
0218     debugMenu->addAction(m_stopAct);
0219     debugMenu->addAction(m_continueAct);
0220     debugMenu->addAction(m_stepOverAct);
0221     debugMenu->addAction(m_stepIntoAct);
0222     debugMenu->addAction(m_stepOutAct);
0223     menuBar()->addMenu(debugMenu);
0224 
0225     QMenu *settingsMenu = new QMenu(i18n("&Settings"), menuBar());
0226     settingsMenu->addAction(m_catchExceptionsAction);
0227     settingsMenu->addAction(m_reindentAction);
0228     menuBar()->addMenu(settingsMenu);
0229 }
0230 
0231 void DebugWindow::createToolBars()
0232 {
0233     toolBar()->addAction(m_stopAct);
0234     toolBar()->addSeparator();
0235     toolBar()->addAction(m_continueAct);
0236     toolBar()->addAction(m_stepOverAct);
0237     toolBar()->addAction(m_stepIntoAct);
0238     toolBar()->addAction(m_stepOutAct);
0239 }
0240 
0241 void DebugWindow::createTabWidget()
0242 {
0243     m_tabWidget = new QTabWidget;
0244 
0245     QToolButton *closeTabButton = new QToolButton(m_tabWidget);
0246     m_tabWidget->setCornerWidget(closeTabButton, Qt::TopRightCorner);
0247     closeTabButton->setCursor(Qt::ArrowCursor);
0248     closeTabButton->setAutoRaise(true);
0249     closeTabButton->setIcon(QIcon::fromTheme("tab-close"));
0250     connect(closeTabButton, SIGNAL(clicked()), this, SLOT(closeTab()));
0251     closeTabButton->setToolTip(i18n("Close source"));
0252     closeTabButton->setEnabled(true);
0253 }
0254 
0255 void DebugWindow::createStatusBar()
0256 {
0257     statusBar()->showMessage(i18n("Ready"));
0258 }
0259 
0260 void DebugWindow::updateStoppedMark(RunMode mode)
0261 {
0262     if (!ctx()) {
0263         return;
0264     }
0265 
0266     DebugDocument::Ptr doc = ctx()->activeDocument();
0267     assert(!doc.isNull());
0268     KTextEditor::MarkInterface *imark = qobject_cast<KTextEditor::MarkInterface *>(doc->viewerDocument());
0269 
0270     if (mode == Running) {
0271         // No longer stopped there.
0272         if (imark)
0273             imark->removeMark(ctx()->activeLine() - doc->baseLine(),
0274                               KTextEditor::MarkInterface::Execution);
0275     } else {
0276         displayScript(doc.get(), ctx()->activeLine());
0277         if (imark)
0278             imark->addMark(ctx()->activeLine() - doc->baseLine(),
0279                            KTextEditor::MarkInterface::Execution);
0280     }
0281 }
0282 
0283 void DebugWindow::setUIMode(RunMode mode)
0284 {
0285     // update editor stuff. We want to do it first, since the callstack
0286     // may try to restore our position in some cases
0287     updateStoppedMark(mode);
0288 
0289     if (mode == Running) {
0290         // Toggle buttons..
0291         m_continueAct->setEnabled(false);
0292         m_stepIntoAct->setEnabled(false);
0293         m_stepOutAct->setEnabled(false);
0294         m_stepOverAct->setEnabled(false);
0295         m_runningSessionCtx = ctx();
0296     } else {
0297         // Show local variables and the bt
0298         m_localVariables->updateDisplay(ctx()->execContexts.top());
0299         m_callStack->displayStack(ctx());
0300 
0301         // Toggle buttons..
0302         m_continueAct->setEnabled(true);
0303         m_stepIntoAct->setEnabled(true);
0304         m_stepOutAct->setEnabled(true);
0305         m_stepOverAct->setEnabled(true);
0306         m_runningSessionCtx = 0;
0307     }
0308 }
0309 
0310 // -------------------------------------------------------------
0311 
0312 bool DebugWindow::isBlocked()
0313 {
0314     DebugWindow *self = window();
0315     if (!self) {
0316         return false;
0317     }
0318     return self->inSession() || self->m_modalLevel;
0319 }
0320 
0321 void DebugWindow::resetTimeoutsIfNeeded()
0322 {
0323     if (!isBlocked()) {
0324         KJS::Interpreter *intp = KJS::Interpreter::firstInterpreter();
0325         do {
0326             intp->restartTimeoutCheck();
0327             intp = intp->nextInterpreter();
0328         } while (intp != KJS::Interpreter::firstInterpreter());
0329     }
0330 }
0331 
0332 void DebugWindow::forceStopAtNext()
0333 {
0334     DebugWindow *self = window();
0335     self->m_breakAtNext = true;
0336 }
0337 
0338 void DebugWindow::stopAtNext()
0339 {
0340     m_breakAtNext = m_stopAct->isChecked();
0341 }
0342 
0343 bool DebugWindow::shouldContinue(InterpreterContext *ic)
0344 {
0345     return !ic || ic->mode != Abort;
0346 }
0347 
0348 void DebugWindow::leaveDebugSession()
0349 {
0350     // Update UI for running mode, unless we expect things to be quick;
0351     // in which case we'll only update if we have to, when running stops
0352     if (ctx()->mode != Step) {
0353         setUIMode(Running);
0354     } else { // In the other case, we still want to remove the old running marker, however
0355         updateStoppedMark(Running);
0356     }
0357 
0358     m_activeSessionCtxs.pop();
0359     resetTimeoutsIfNeeded();
0360     exitLoop();
0361 }
0362 
0363 void DebugWindow::continueExecution()
0364 {
0365     if (!ctx()) {
0366         return;    //In case we're in the middle of a step.. Hardly ideal, but..
0367     }
0368     leaveDebugSession();
0369 }
0370 
0371 void DebugWindow::stepInto()
0372 {
0373     if (!ctx()) {
0374         return;    //In case we're in the middle of a step.. Hardly ideal, but..
0375     }
0376     ctx()->mode = Step;
0377     leaveDebugSession();
0378 }
0379 
0380 void DebugWindow::stepOut()
0381 {
0382     if (!ctx()) {
0383         return;    //In case we're in the middle of a step.. Hardly ideal, but..
0384     }
0385     ctx()->mode        = StepOut;
0386     ctx()->depthAtSkip = ctx()->execContexts.size();
0387     leaveDebugSession();
0388 }
0389 
0390 void DebugWindow::stepOver()
0391 {
0392     if (!ctx()) {
0393         return;    //In case we're in the middle of a step.. Hardly ideal, but..
0394     }
0395     ctx()->mode        = StepOver;
0396     ctx()->depthAtSkip = ctx()->execContexts.size();
0397     leaveDebugSession();
0398 }
0399 
0400 DebugWindow::~DebugWindow()
0401 {
0402     assert(m_docsForIntrp.isEmpty());
0403     assert(m_docForSid.isEmpty());
0404     assert(m_activeSessionCtxs.isEmpty());
0405     s_debugger = 0;
0406 }
0407 
0408 void DebugWindow::closeEvent(QCloseEvent *event)
0409 {
0410     if (inSession()) {
0411         event->setAccepted(false);
0412     } else {
0413         KXmlGuiWindow::closeEvent(event);
0414     }
0415 }
0416 
0417 // -------------------------------------------------------------
0418 
0419 void DebugWindow::attach(Interpreter *interp)
0420 {
0421     // ::attach can be called many times, so handle that
0422     if (!m_contexts[interp]) {
0423         m_contexts[interp] = new InterpreterContext;
0424     }
0425     KJS::Debugger::attach(interp);
0426 }
0427 
0428 void DebugWindow::cleanupDocument(DebugDocument::Ptr doc)
0429 {
0430     m_docForSid.remove(doc->sid());
0431     m_scripts->documentDestroyed(doc.get());
0432 }
0433 
0434 static void fatalAssert(bool shouldBeTrue, const char *error)
0435 {
0436     if (!shouldBeTrue) {
0437         qFatal(error);
0438     }
0439 }
0440 
0441 void DebugWindow::detach(KJS::Interpreter *interp)
0442 {
0443     assert(interp); //detach(0) should never get here, since only ~Debugger calls it
0444 
0445     // Make sure no weird recursions can still happen!
0446     InterpreterContext *ctx = m_contexts[interp];
0447     assert(!m_activeSessionCtxs.contains(ctx));
0448 
0449     // Go through, and kill all the fragments from here.
0450     QList<DebugDocument::Ptr> docs = m_docsForIntrp[interp];
0451 
0452     foreach (DebugDocument::Ptr doc, docs) {
0453         cleanupDocument(doc);
0454     }
0455 
0456     m_docsForIntrp.remove(interp);
0457 
0458     delete m_contexts.take(interp);
0459     resetTimeoutsIfNeeded();
0460 
0461     KJS::Debugger::detach(interp);
0462 }
0463 
0464 void DebugWindow::clearInterpreter(KJS::Interpreter *interp)
0465 {
0466     // We may get a clear when we weren't even attached, if the
0467     // interpreter gets created but nothing gets run in it.
0468     // Be careful not to insert a bogus null into contexts map then
0469     InterpreterContext *ctx = m_contexts.value(interp);
0470     if (!ctx) {
0471         return;
0472     }
0473 
0474     fatalAssert(!m_activeSessionCtxs.contains(ctx), "Interpreter clear on active session");
0475 
0476     // Cleanup all documents; but we keep the open windows open so
0477     // they can be reused.
0478     QMutableListIterator<DebugDocument::Ptr> i(m_docsForIntrp[interp]);
0479     while (i.hasNext()) {
0480         DebugDocument::Ptr doc = i.next();
0481         if (m_openDocuments.contains(doc.get())) {
0482             doc->markReload();
0483         } else {
0484             i.remove();
0485         }
0486 
0487         cleanupDocument(doc);
0488     }
0489 }
0490 
0491 bool DebugWindow::sourceParsed(ExecState *exec, int sourceId, const UString &jsSourceURL,
0492                                const UString &source, int startingLineNumber, int errorLine, const UString &/* errorMsg */)
0493 {
0494     Q_UNUSED(exec);
0495 
0496     // qCDebug(KHTML_LOG) << "sourceId: " << sourceId
0497             << "sourceURL: " << jsSourceURL.qstring()
0498             << "startingLineNumber: " << startingLineNumber
0499             << "errorLine: " << errorLine;
0500 
0501     QString sourceURL = jsSourceURL.qstring();
0502     // Tell it about this portion..
0503     QString qsource =  source.qstring();
0504 
0505     DebugDocument::Ptr document;
0506 
0507     // See if there is an open document window we can reuse...
0508     foreach (DebugDocument::Ptr cand, m_openDocuments) {
0509         if (cand->isMarkedReload() && cand->url() == sourceURL && cand->baseLine() == startingLineNumber) {
0510             document = cand;
0511         }
0512     }
0513 
0514     // If we don't have a document, make a new one.
0515     if (!document) {
0516         // If there is no URL, try to figure one out from the caller ---
0517         // useful for function constructor and eval.
0518         QString uiURL = sourceURL;
0519 
0520         if (uiURL.isEmpty()) {
0521             // Scan through all contexts, and see which one matches
0522             foreach (InterpreterContext *ic, m_contexts) {
0523                 if (!ic->execContexts.isEmpty() && ic->execContexts.top() == exec) {
0524                     uiURL = ic->callStack.top().doc->url();
0525                     break;
0526                 }
0527             }
0528 
0529         }
0530 
0531         document = new DebugDocument(exec->dynamicInterpreter(), uiURL,
0532                                      sourceId, startingLineNumber, qsource);
0533 
0534         connect(document.get(), SIGNAL(documentDestroyed(KJSDebugger::DebugDocument*)),
0535                 this, SLOT(documentDestroyed(KJSDebugger::DebugDocument*)));
0536     } else {
0537         // Otherwise, update.
0538         document->reloaded(sourceId, qsource);
0539     }
0540 
0541     m_docsForIntrp[exec->dynamicInterpreter()].append(document);
0542 
0543     // Show it in the script list view
0544     m_scripts->addDocument(document.get());
0545 
0546     // Memorize the document..
0547     m_docForSid[sourceId] = document;
0548 
0549     if (qsource.contains("function")) { // Approximate knowledge of whether code has functions. Ewwww...
0550         document->setHasFunctions();
0551     }
0552 
0553     return shouldContinue(m_contexts[exec->dynamicInterpreter()]);
0554 }
0555 
0556 bool DebugWindow::exception(ExecState *exec, int sourceId, int lineNo, JSValue *exceptionObj)
0557 {
0558     InterpreterContext *ic = m_contexts[exec->dynamicInterpreter()];
0559 
0560     // Don't report it if error reporting is not on
0561     KParts::ReadOnlyPart *part = static_cast<ScriptInterpreter *>(exec->dynamicInterpreter())->part();
0562     KHTMLPart *khtmlpart = qobject_cast<KHTMLPart *>(part);
0563 
0564     if ((khtmlpart && !khtmlpart->settings()->isJavaScriptErrorReportingEnabled()) || !m_catchExceptions) {
0565         return shouldContinue(ic);
0566     }
0567 
0568     QString exceptionMsg = exceptionToString(exec, exceptionObj);
0569 
0570     // Look up fragment info from sourceId
0571     DebugDocument::Ptr doc = m_docForSid[sourceId];
0572 
0573     // Figure out filename.
0574     QString url = "????";
0575     if (exec->context()->codeType() == EvalCode) {
0576         url = "eval";
0577     }
0578     if (!doc->url().isEmpty()) {
0579         url = doc->url();
0580     }
0581 
0582     QString msg = i18n("An error occurred while attempting to run a script on this page.\n\n%1 line %2:\n%3",
0583                        KStringHandler::rsqueeze(url, 80), lineNo, exceptionMsg);
0584 
0585     KJSErrorDialog dlg(this /*dlgParent*/, msg, true);
0586     TimerPauser pause(exec); // don't let any timers fire while we're doing this!
0587     ++m_modalLevel;
0588     dlg.exec();
0589     --m_modalLevel;
0590     resetTimeoutsIfNeeded();
0591 
0592     if (dlg.dontShowAgain()) {
0593         m_catchExceptions = false;
0594         m_catchExceptionsAction->setChecked(false);
0595     }
0596 
0597     if (dlg.debugSelected()) {
0598         // We generally want to stop at the current line, to see what's going on... There is one exception, though:
0599         // in case we've got a parse error, we can't actually stop, but we want to still display stuff.
0600         if (ic->hasActiveDocument()) {
0601             enterDebugSession(exec, doc.get(), lineNo);
0602         } else {
0603             displayScript(doc.get(), lineNo);
0604         }
0605     }
0606 
0607     return shouldContinue(ic);
0608 }
0609 
0610 bool DebugWindow::atStatement(ExecState *exec, int sourceId, int firstLine, int lastLine)
0611 {
0612     InterpreterContext *ctx = m_contexts[exec->dynamicInterpreter()];
0613     ctx->updateCall(firstLine);
0614     return checkSourceLocation(exec, sourceId, firstLine, lastLine);
0615 }
0616 
0617 bool DebugWindow::checkSourceLocation(KJS::ExecState *exec, int sourceId, int firstLine, int lastLine)
0618 {
0619     Q_UNUSED(lastLine);
0620 
0621     InterpreterContext *candidateCtx = m_contexts[exec->dynamicInterpreter()];
0622 
0623     if (!shouldContinue(candidateCtx)) {
0624         return false;
0625     }
0626 
0627     bool enterDebugMode = false;
0628 
0629     // We stop when breakAtNext is set regardless of the context.
0630     if (m_breakAtNext) {
0631         enterDebugMode = true;
0632     }
0633 
0634     if (candidateCtx->mode == Step) {
0635         enterDebugMode = true;
0636     } else if (candidateCtx->mode == StepOver) {
0637         if (candidateCtx->execContexts.size() <= candidateCtx->depthAtSkip) {
0638             enterDebugMode = true;
0639         }
0640     }
0641 
0642     DebugDocument::Ptr document = m_docForSid[sourceId];
0643     assert(!document.isNull());
0644 
0645     // Now check for breakpoints if needed
0646     if (document->hasBreakpoint(firstLine)) {
0647         enterDebugMode = true;
0648     }
0649 
0650     // Block the UI, and enable all the debugging buttons, etc.
0651     if (enterDebugMode) {
0652         enterDebugSession(exec, document.get(), firstLine);
0653     }
0654 
0655     // re-checking the abort mode here, in case it got change when recursing
0656     return shouldContinue(candidateCtx);
0657 }
0658 
0659 bool DebugWindow::enterContext(ExecState *exec, int sourceId, int lineno, JSObject *function, const List &args)
0660 {
0661     Q_UNUSED(args);
0662     InterpreterContext *ctx = m_contexts[exec->dynamicInterpreter()];
0663 
0664     // First update call stack.
0665     DebugDocument::Ptr document = m_docForSid[sourceId];
0666     QString stackEntry = document->name();
0667     if (function && function->inherits(&InternalFunctionImp::info)) {
0668         KJS::InternalFunctionImp *func = static_cast<InternalFunctionImp *>(function);
0669         QString functionName = func->functionName().qstring();
0670         if (!functionName.isEmpty()) {
0671             stackEntry = functionName;
0672         }
0673     }
0674 
0675     if (exec->context()->codeType() == EvalCode) {
0676         stackEntry = "eval";
0677     }
0678 
0679     ctx->addCall(document, stackEntry, lineno);
0680     ctx->execContexts.push(exec);
0681 
0682     return shouldContinue(ctx);
0683 }
0684 
0685 bool DebugWindow::exitContext(ExecState *exec, int sourceId, int lineno, JSObject *function)
0686 {
0687     Q_UNUSED(lineno);
0688     Q_UNUSED(function);
0689     InterpreterContext *ic  = m_contexts[exec->dynamicInterpreter()];
0690 
0691     if (m_localVariables->currentlyDisplaying() == exec) {
0692         // Clear local variable and stack display when exiting a function
0693         // it corresponds to
0694         m_localVariables->updateDisplay(0);
0695         m_callStack->clearDisplay();
0696     }
0697 
0698     ic->removeCall();
0699     assert(ic->execContexts.top() == exec);
0700     ic->execContexts.pop();
0701 
0702     // See if we should stop on the next instruction.
0703     // Note that we should not test StepOver here, as
0704     // we may have a return event at same level
0705     // in case of a f(g(), h()) type setup
0706     // Note that in the above case a StepOut from
0707     // g() would step into h() from below, which is reasonable
0708     if (ic->mode == StepOut) {
0709         if (ic->execContexts.size() < ic->depthAtSkip) {
0710             ic->mode = Step;
0711         }
0712     }
0713 
0714     // There is a special case here: we may have clicked step, and
0715     // ran out of code, and now UI is in stopped mode (since we avoid
0716     // switching Stopped->Running->Stopped on plain single-step)
0717     // This happens when:
0718     // 1) No session is active
0719     // 2) The context steck for this session is empty
0720     // 3) This session is thought to be waiting for a step.
0721     if (m_activeSessionCtxs.isEmpty() &&
0722             ic->execContexts.isEmpty() && ic->mode == Step) {
0723         setUIMode(Running);
0724     }
0725 
0726     // On the other hand, UI may be in running mode, but we've just
0727     // ran out of all the code for this interpreter. In this case,
0728     // reactive the previous session in stopped mode.
0729     if (!m_activeSessionCtxs.isEmpty() && m_runningSessionCtx == ic) {
0730         if (ic->execContexts.isEmpty()) {
0731             setUIMode(Stopped);
0732         } else {
0733             fatalAssert(exec->context()->callingContext(), "Apparent event re-entry");
0734         }
0735         // Sanity check: the modality protection should disallow us to exit
0736         // from a context called by KHTML unless it's at very top level
0737         // (e.g. no other execs on top)
0738     }
0739 
0740     // Also, if we exit from an eval context, we probably want to
0741     // clear the corresponding document, unless it's open.
0742     // We can not do it safely if there are any functions declared,
0743     // however, since they can escape.
0744     if (exec->context()->codeType() == EvalCode) {
0745         DebugDocument::Ptr doc = m_docForSid[sourceId];
0746         if (!m_openDocuments.contains(doc.get()) && !doc->hasFunctions()) {
0747             cleanupDocument(doc);
0748             m_docsForIntrp[exec->dynamicInterpreter()].removeAll(doc);
0749         }
0750     }
0751 
0752     return shouldContinue(ic);
0753 }
0754 
0755 // End KJS::Debugger overloads
0756 
0757 void DebugWindow::doEval(const QString &qcode)
0758 {
0759     // Work out which execution state to use. If we're currently in a debugging session,
0760     // use the context of the presently selected frame, if any --- otherwise, use the global execution
0761     // state from the interpreter corresponding to the currently displayed source file.
0762     ExecState *exec;
0763     JSObject  *thisObj;
0764 
0765     if (inSession()) {
0766         exec = m_callStack->selectedFrameContext();
0767         if (!exec) {
0768             exec = m_activeSessionCtxs.top()->execContexts.top();
0769         }
0770         thisObj = exec->context()->thisValue();
0771     } else {
0772         int idx = m_tabWidget->currentIndex();
0773         if (idx < 0) {
0774             m_console->reportResult(qcode,
0775                                     i18n("Do not know where to evaluate the expression. Please pause a script or open a source file."));
0776             return;
0777         }
0778         DebugDocument *document = m_openDocuments[idx];
0779         exec    = document->interpreter()->globalExec();
0780         thisObj = document->interpreter()->globalObject();
0781     }
0782 
0783     JSValue   *oldException = 0;
0784 
0785     UString code(qcode);
0786 
0787     Interpreter *interp = exec->dynamicInterpreter();
0788 
0789     // If there was a previous exception, clear it for now and save it.
0790     if (exec->hadException()) {
0791         oldException = exec->exception();
0792         exec->clearException();
0793     }
0794 
0795     JSObject *obj = interp->globalObject()->get(exec, "eval")->getObject();
0796     List args;
0797     args.append(jsString(code));
0798 
0799     // ### we want the CPU guard here.. But only for this stuff,
0800     // not others things. oooh boy. punt for now
0801 
0802     JSValue *retVal = obj->call(exec, thisObj, args);
0803 
0804     // Print the return value or exception message to the console
0805     QString msg;
0806     if (exec->hadException()) {
0807         JSValue *exc = exec->exception();
0808         exec->clearException();
0809         msg = i18n("Evaluation threw an exception %1", exceptionToString(exec, exc));
0810     } else {
0811         msg = valueToString(retVal);
0812     }
0813 
0814     // Restore old exception if need be, and always clear ours
0815     exec->clearException();
0816     if (oldException) {
0817         exec->setException(oldException);
0818     }
0819 
0820     m_console->reportResult(qcode, msg);
0821 
0822     // Make sure to re-activate the line we were stopped in,
0823     // in case a nested session was active
0824     if (inSession()) {
0825         setUIMode(Stopped);
0826     }
0827 }
0828 
0829 void DebugWindow::updateVarView()
0830 {
0831     m_localVariables->updateDisplay(m_callStack->selectedFrameContext());
0832 }
0833 
0834 void DebugWindow::displayScript(DebugDocument *document)
0835 {
0836     displayScript(document, -1);
0837 }
0838 
0839 void DebugWindow::displayScript(DebugDocument *document, int line)
0840 {
0841     if (!isVisible()) {
0842         show();
0843     }
0844 
0845     if (m_tabWidget->isHidden()) {
0846         m_tabWidget->show();
0847     }
0848 
0849     KTextEditor::View *view = document->viewerView();
0850 
0851     if (!m_openDocuments.contains(document)) {
0852         m_openDocuments.append(document);
0853         m_tabWidget->addTab(view, document->name());
0854     }
0855 
0856     // Focus the tab
0857     int idx = m_openDocuments.indexOf(document);
0858     m_tabWidget->setCurrentIndex(idx);
0859 
0860     // Go to line..
0861     if (line != -1) {
0862         view->setCursorPosition(KTextEditor::Cursor(line - document->baseLine(), 0));
0863     }
0864 }
0865 
0866 void DebugWindow::documentDestroyed(KJSDebugger::DebugDocument *doc)
0867 {
0868     //### this is likely to be very ugly UI-wise
0869     // Close this document..
0870     int idx = m_openDocuments.indexOf(doc);
0871     if (idx == -1) {
0872         return;
0873     }
0874 
0875     m_tabWidget->removeTab(idx);
0876     m_openDocuments.removeAt(idx);
0877     if (m_openDocuments.isEmpty()) {
0878         m_tabWidget->hide();
0879     }
0880 }
0881 
0882 void DebugWindow::closeTab()
0883 {
0884     int idx = m_tabWidget->currentIndex();
0885     m_tabWidget->removeTab(idx);
0886     m_openDocuments.removeAt(idx);
0887     if (m_openDocuments.isEmpty()) {
0888         m_tabWidget->hide();
0889     }
0890 }
0891 
0892 void DebugWindow::markSet(KTextEditor::Document *document, KTextEditor::Mark mark,
0893                           KTextEditor::MarkInterface::MarkChangeAction action)
0894 {
0895     if (mark.type != KTextEditor::MarkInterface::BreakpointActive) {
0896         return;
0897     }
0898 
0899     // ### ugleeee -- get our docu from viewer docu's parent, to avoid book keeping
0900     DebugDocument *debugDocument = qobject_cast<DebugDocument *>(document->parent());
0901     assert(debugDocument);
0902 
0903     int lineNumber = mark.line + debugDocument->baseLine();
0904     switch (action) {
0905     case KTextEditor::MarkInterface::MarkAdded:
0906         // qCDebug(KHTML_LOG) << lineNumber;
0907         debugDocument->setBreakpoint(lineNumber);
0908         break;
0909     case KTextEditor::MarkInterface::MarkRemoved:
0910         debugDocument->removeBreakpoint(lineNumber);
0911         break;
0912     }
0913 
0914     // qCDebug(KHTML_LOG) << "breakpoint set for: " << endl
0915             << "document: " << document->documentName() << endl
0916             << "line: " << lineNumber;
0917 }
0918 
0919 void DebugWindow::enterDebugSession(KJS::ExecState *exec, DebugDocument *document, int line)
0920 {
0921     Q_UNUSED(document);
0922     Q_UNUSED(line);
0923 
0924     // This "enters" a new debugging session, i.e. enables usage of the debugging window
0925     // It re-enters the qt event loop here, allowing execution of other parts of the
0926     // program to continue while the script is stopped. We have to be a bit careful here,
0927     // i.e. make sure the user can't quit the app, and disable other event handlers which
0928     // could interfere with the debugging session.
0929 
0930     if (!isVisible()) {
0931         show();
0932     }
0933 
0934     m_activeSessionCtxs.push(m_contexts[exec->dynamicInterpreter()]);
0935     ctx()->mode   = Normal;
0936     m_breakAtNext = false;
0937     m_stopAct->setChecked(false);
0938 
0939     setUIMode(Stopped);
0940     enterLoop();
0941 }
0942 
0943 //// Event handling - ripped from old kjsdebugger
0944 
0945 bool DebugWindow::eventFilter(QObject *object, QEvent *event)
0946 {
0947     switch (event->type()) {
0948     case QEvent::MouseButtonPress:
0949     case QEvent::MouseButtonRelease:
0950     case QEvent::MouseButtonDblClick:
0951     case QEvent::MouseMove:
0952     case QEvent::KeyPress:
0953     case QEvent::KeyRelease:
0954     case QEvent::Destroy:
0955     case QEvent::Close:
0956     case QEvent::Quit:
0957     case QEvent::Shortcut:
0958     case QEvent::ShortcutOverride: {
0959         while (object->parent()) {
0960             object = object->parent();
0961         }
0962         if (object == this) {
0963             return QWidget::eventFilter(object, event);
0964         } else {
0965             if (event->type() == QEvent::Close) {
0966                 event->setAccepted(false);
0967             }
0968             return true;
0969         }
0970     }
0971     break;
0972     default:
0973         return QWidget::eventFilter(object, event);
0974     }
0975 
0976 }
0977 
0978 void DebugWindow::enterLoop()
0979 {
0980     QEventLoop eventLoop;
0981     m_activeEventLoops.push(&eventLoop);
0982 
0983     if (m_activeSessionCtxs.size() == 1) {
0984         enterModality();
0985     }
0986 
0987 //    eventLoop.exec(QEventLoop::X11ExcludeTimers | QEventLoop::ExcludeSocketNotifiers);
0988     eventLoop.exec();
0989     m_activeEventLoops.pop();
0990 }
0991 
0992 void DebugWindow::exitLoop()
0993 {
0994     if (m_activeSessionCtxs.isEmpty()) {
0995         leaveModality();
0996     }
0997     m_activeEventLoops.top()->quit();
0998 }
0999 
1000 void DebugWindow::enterModality()
1001 {
1002     QWidgetList widgets = QApplication::allWidgets();
1003     foreach (QWidget *widget, widgets) {
1004         widget->installEventFilter(this);
1005     }
1006 }
1007 
1008 void DebugWindow::leaveModality()
1009 {
1010     QWidgetList widgets = QApplication::allWidgets();
1011     foreach (QWidget *widget, widgets) {
1012         widget->removeEventFilter(this);
1013     }
1014 }
1015 
1016 #include "moc_debugwindow.cpp"