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"