KHtml

debugwindow.cpp
1 /*
2  * This file is part of the KDE libraries
3  * Copyright (C) 2006 Matt Broadstone ([email protected])
4  * Copyright (C) 2007 Maks Orlovich <[email protected]>
5  * Copyright (C) 2000-2001 Harri Porten ([email protected])
6  * Copyright (C) 2001,2003 Peter Kelly ([email protected])
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 
79 DebugWindow *DebugWindow::window()
80 {
81  if (!s_debugger) {
82  s_debugger = new DebugWindow();
83  }
84 
85  return s_debugger;
86 }
87 
88 // ----------------------------------------------
89 
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 
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 
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 
158 bool DebugWindow::shouldReindentSources() const
159 {
160  return m_reindentSources;
161 }
162 
163 void DebugWindow::settingsChanged()
164 {
165  m_catchExceptions = m_catchExceptionsAction->isChecked();
166  m_reindentSources = m_reindentAction->isChecked();
167  syncToConfig();
168 }
169 
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 
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 
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 
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 
255 void DebugWindow::createStatusBar()
256 {
257  statusBar()->showMessage(i18n("Ready"));
258 }
259 
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 
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 
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 
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 
332 void DebugWindow::forceStopAtNext()
333 {
334  DebugWindow *self = window();
335  self->m_breakAtNext = true;
336 }
337 
338 void DebugWindow::stopAtNext()
339 {
340  m_breakAtNext = m_stopAct->isChecked();
341 }
342 
343 bool DebugWindow::shouldContinue(InterpreterContext *ic)
344 {
345  return !ic || ic->mode != Abort;
346 }
347 
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 
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 
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 
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 
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 
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 
408 void DebugWindow::closeEvent(QCloseEvent *event)
409 {
410  if (inSession()) {
411  event->setAccepted(false);
412  } else {
414  }
415 }
416 
417 // -------------------------------------------------------------
418 
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 
428 void DebugWindow::cleanupDocument(DebugDocument::Ptr doc)
429 {
430  m_docForSid.remove(doc->sid());
431  m_scripts->documentDestroyed(doc.get());
432 }
433 
434 static void fatalAssert(bool shouldBeTrue, const char *error)
435 {
436  if (!shouldBeTrue) {
437  qFatal(error);
438  }
439 }
440 
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 
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 
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 
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 
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 
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 
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 
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 
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 
829 void DebugWindow::updateVarView()
830 {
831  m_localVariables->updateDisplay(m_callStack->selectedFrameContext());
832 }
833 
834 void DebugWindow::displayScript(DebugDocument *document)
835 {
836  displayScript(document, -1);
837 }
838 
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 
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 
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 
892 void DebugWindow::markSet(KTextEditor::Document *document, KTextEditor::Mark mark,
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) {
906  // qCDebug(KHTML_LOG) << lineNumber;
907  debugDocument->setBreakpoint(lineNumber);
908  break;
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 
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 
944 
945 bool DebugWindow::eventFilter(QObject *object, QEvent *event)
946 {
947  switch (event->type()) {
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:
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 
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 
992 void DebugWindow::exitLoop()
993 {
994  if (m_activeSessionCtxs.isEmpty()) {
995  leaveModality();
996  }
997  m_activeEventLoops.top()->quit();
998 }
999 
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 
virtual void removeMark(int line, uint markType)=0
MouseButtonPress
QString & append(QChar ch)
QEvent::Type type() const const
virtual bool setCursorPosition(Cursor position)=0
void setCursor(const QCursor &)
DebuggerWindow.
Definition: debugwindow.h:73
Interpreter * dynamicInterpreter() const
KJOBWIDGETS_EXPORT QWidget * window(KJob *job)
KCOREADDONS_EXPORT QString rsqueeze(const QString &str, int maxlen=40)
KJS_EXTERNAL_EXPORT QString qstring() const
void setStretchFactor(int index, int stretch)
void append(JSValue *val)
LeftDockWidgetArea
This class is khtml&#39;s main class.
Definition: khtml_part.h:208
TopRightCorner
QTextStream & endl(QTextStream &stream)
KToggleAction * pause(const QObject *recvr, const char *slot, QObject *parent)
void setIcon(const QIcon &icon)
QAction * addAction(const QString &text)
KSharedConfigPtr config()
void addWidget(QWidget *widget)
JSObject * thisValue() const
void setException(JSValue *e)
We inherit from Interpreter, to save a pointer to the HTML part that the interpreter runs for...
Definition: kjs_binding.h:96
void setEnabled(bool)
void installEventFilter(QObject *filterObj)
int exec(QEventLoop::ProcessEventsFlags flags)
void closeEvent(QCloseEvent *) override
virtual void attach(Interpreter *interp)
bool isEmpty() const const
JSValue * exception() const
ArrowCursor
virtual bool eventFilter(QObject *watched, QEvent *event)
void setAutoRaise(bool enable)
bool hadException() const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
virtual QString documentName() const =0
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
WA_DeleteOnClose
QString i18n(const char *text, const TYPE &arg...)
void clearException()
Helper for pausing/resuming timers.
Definition: kjs_window.h:354
QIcon fromTheme(const QString &name)
Vertical
void setToolTip(const QString &)
virtual void detach(Interpreter *interp)
QObject * parent() const const
QWidgetList allWidgets()
Window
Definition: kjs_window.h:393
const KHTMLSettings * settings() const
void removeEventFilter(QObject *obj)
JSGlobalObject * globalObject() const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Tue Jan 19 2021 22:48:23 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.