kjs_debugwin.cpp

00001 /*
00002  *  This file is part of the KDE libraries
00003  *  Copyright (C) 2000-2001 Harri Porten (porten@kde.org)
00004  *  Copyright (C) 2001,2003 Peter Kelly (pmk@post.com)
00005  *
00006  *  This library is free software; you can redistribute it and/or
00007  *  modify it under the terms of the GNU Library General Public
00008  *  License as published by the Free Software Foundation; either
00009  *  version 2 of the License, or (at your option) any later version.
00010  *
00011  *  This library is distributed in the hope that it will be useful,
00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014  *  Library General Public License for more details.
00015  *
00016  *  You should have received a copy of the GNU Library General Public
00017  *  License along with this library; if not, write to the Free Software
00018  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
00019  */
00020 
00021 #include "kjs_debugwin.h"
00022 #include "kjs_proxy.h"
00023 
00024 #ifdef KJS_DEBUGGER
00025 
00026 #include <assert.h>
00027 #include <stdlib.h>
00028 #include <qlayout.h>
00029 #include <qpushbutton.h>
00030 #include <qtextedit.h>
00031 #include <qlistbox.h>
00032 #include <qmultilineedit.h>
00033 #include <qapplication.h>
00034 #include <qsplitter.h>
00035 #include <qcombobox.h>
00036 #include <qbitmap.h>
00037 #include <qwidgetlist.h>
00038 #include <qlabel.h>
00039 #include <qdatastream.h>
00040 #include <qcstring.h>
00041 #include <qpainter.h>
00042 #include <qscrollbar.h>
00043 
00044 #include <klocale.h>
00045 #include <kdebug.h>
00046 #include <kiconloader.h>
00047 #include <kglobal.h>
00048 #include <kmessagebox.h>
00049 #include <kguiitem.h>
00050 #include <kpopupmenu.h>
00051 #include <kmenubar.h>
00052 #include <kaction.h>
00053 #include <kactioncollection.h>
00054 #include <kglobalsettings.h>
00055 #include <kshortcut.h>
00056 #include <kconfig.h>
00057 #include <kconfigbase.h>
00058 #include <kapplication.h>
00059 #include <dcop/dcopclient.h>
00060 #include <kstringhandler.h> 
00061 
00062 #include "kjs_dom.h"
00063 #include "kjs_binding.h"
00064 #include "khtml_part.h"
00065 #include "khtmlview.h"
00066 #include "khtml_pagecache.h"
00067 #include "khtml_settings.h"
00068 #include "khtml_factory.h"
00069 #include "misc/decoder.h"
00070 #include <kjs/ustring.h>
00071 #include <kjs/object.h>
00072 #include <kjs/function.h>
00073 #include <kjs/interpreter.h>
00074 
00075 using namespace KJS;
00076 using namespace khtml;
00077 
00078 SourceDisplay::SourceDisplay(KJSDebugWin *debugWin, QWidget *parent, const char *name)
00079   : QScrollView(parent,name), m_currentLine(-1), m_sourceFile(0), m_debugWin(debugWin),
00080     m_font(KGlobalSettings::fixedFont())
00081 {
00082   verticalScrollBar()->setLineStep(QFontMetrics(m_font).height());
00083   viewport()->setBackgroundMode(Qt::NoBackground);
00084   m_breakpointIcon = KGlobal::iconLoader()->loadIcon("stop",KIcon::Small);
00085 }
00086 
00087 SourceDisplay::~SourceDisplay()
00088 {
00089   if (m_sourceFile) {
00090     m_sourceFile->deref();
00091     m_sourceFile = 0L;
00092   }
00093 }
00094 
00095 void SourceDisplay::setSource(SourceFile *sourceFile)
00096 {
00097   if ( sourceFile )
00098       sourceFile->ref();
00099   if (m_sourceFile)
00100       m_sourceFile->deref();
00101   m_sourceFile = sourceFile;
00102   if ( m_sourceFile )
00103       m_sourceFile->ref();
00104 
00105   if (!m_sourceFile || !m_debugWin->isVisible()) {
00106     return;
00107   }
00108 
00109   QString code = sourceFile->getCode();
00110   const QChar *chars = code.unicode();
00111   uint len = code.length();
00112   QChar newLine('\n');
00113   QChar cr('\r');
00114   QChar tab('\t');
00115   QString tabstr("        ");
00116   QString line;
00117   m_lines.clear();
00118   int width = 0;
00119   QFontMetrics metrics(m_font);
00120 
00121   for (uint pos = 0; pos < len; pos++) {
00122     QChar c = chars[pos];
00123     if (c == cr) {
00124       if (pos < len-1 && chars[pos+1] == newLine)
00125     continue;
00126       else
00127     c = newLine;
00128     }
00129     if (c == newLine) {
00130       m_lines.append(line);
00131       int lineWidth = metrics.width(line);
00132       if (lineWidth > width)
00133     width = lineWidth;
00134       line = "";
00135     }
00136     else if (c == tab) {
00137       line += tabstr;
00138     }
00139     else {
00140       line += c;
00141     }
00142   }
00143   if (line.length()) {
00144     m_lines.append(line);
00145     int lineWidth = metrics.width(line);
00146     if (lineWidth > width)
00147       width = lineWidth;
00148   }
00149 
00150   int linenoDisplayWidth = metrics.width("888888");
00151   resizeContents(linenoDisplayWidth+4+width,metrics.height()*m_lines.count());
00152   update();
00153   sourceFile->deref();
00154 }
00155 
00156 void SourceDisplay::setCurrentLine(int lineno, bool doCenter)
00157 {
00158   m_currentLine = lineno;
00159 
00160   if (doCenter && m_currentLine >= 0) {
00161     QFontMetrics metrics(m_font);
00162     int height = metrics.height();
00163     center(0,height*m_currentLine+height/2);
00164   }
00165 
00166   updateContents();
00167 }
00168 
00169 void SourceDisplay::contentsMousePressEvent(QMouseEvent *e)
00170 {
00171   QScrollView::mouseDoubleClickEvent(e);
00172   QFontMetrics metrics(m_font);
00173   int lineno = e->y()/metrics.height();
00174   emit lineDoubleClicked(lineno+1); // line numbers start from 1
00175 }
00176 
00177 void SourceDisplay::showEvent(QShowEvent *)
00178 {
00179     setSource(m_sourceFile);
00180 }
00181 
00182 void SourceDisplay::drawContents(QPainter *p, int clipx, int clipy, int clipw, int cliph)
00183 {
00184   if (!m_sourceFile) {
00185     p->fillRect(clipx,clipy,clipw,cliph,palette().active().base());
00186     return;
00187   }
00188 
00189   QFontMetrics metrics(m_font);
00190   int height = metrics.height();
00191 
00192   int bottom = clipy + cliph;
00193   int right = clipx + clipw;
00194 
00195   int firstLine = clipy/height-1;
00196   if (firstLine < 0)
00197     firstLine = 0;
00198   int lastLine = bottom/height+2;
00199   if (lastLine > (int)m_lines.count())
00200     lastLine = m_lines.count();
00201 
00202   p->setFont(m_font);
00203 
00204   int linenoWidth = metrics.width("888888");
00205 
00206   for (int lineno = firstLine; lineno <= lastLine; lineno++) {
00207     QString linenoStr = QString().sprintf("%d",lineno+1);
00208 
00209 
00210     p->fillRect(0,height*lineno,linenoWidth,height,palette().active().mid());
00211 
00212     p->setPen(palette().active().text());
00213     p->drawText(0,height*lineno,linenoWidth,height,Qt::AlignRight,linenoStr);
00214 
00215     QColor bgColor;
00216     QColor textColor;
00217 
00218     if (lineno == m_currentLine) {
00219       bgColor = palette().active().highlight();
00220       textColor = palette().active().highlightedText();
00221     }
00222     else if (m_debugWin->haveBreakpoint(m_sourceFile,lineno+1,lineno+1)) {
00223       bgColor = palette().active().text();
00224       textColor = palette().active().base();
00225       p->drawPixmap(2,height*lineno+height/2-m_breakpointIcon.height()/2,m_breakpointIcon);
00226     }
00227     else {
00228       bgColor = palette().active().base();
00229       textColor = palette().active().text();
00230     }
00231 
00232     p->fillRect(linenoWidth,height*lineno,right-linenoWidth,height,bgColor);
00233     p->setPen(textColor);
00234     p->drawText(linenoWidth+4,height*lineno,contentsWidth()-linenoWidth-4,height,
00235         Qt::AlignLeft,m_lines[lineno]);
00236   }
00237 
00238   int remainingTop = height*(lastLine+1);
00239   p->fillRect(0,remainingTop,linenoWidth,bottom-remainingTop,palette().active().mid());
00240 
00241   p->fillRect(linenoWidth,remainingTop,
00242           right-linenoWidth,bottom-remainingTop,palette().active().base());
00243 }
00244 
00245 //-------------------------------------------------------------------------
00246 
00247 KJSDebugWin * KJSDebugWin::kjs_html_debugger = 0;
00248 
00249 QString SourceFile::getCode()
00250 {
00251   if (interpreter) {
00252     KHTMLPart *part = ::qt_cast<KHTMLPart*>(static_cast<ScriptInterpreter*>(interpreter)->part());
00253     if (part && url == part->url().url() && KHTMLPageCache::self()->isValid(part->cacheId())) {
00254       Decoder *decoder = part->createDecoder();
00255       QByteArray data;
00256       QDataStream stream(data,IO_WriteOnly);
00257       KHTMLPageCache::self()->saveData(part->cacheId(),&stream);
00258       QString str;
00259       if (data.size() == 0)
00260     str = "";
00261       else
00262     str = decoder->decode(data.data(),data.size()) + decoder->flush();
00263       delete decoder;
00264       return str;
00265     }
00266   }
00267 
00268   return code;
00269 }
00270 
00271 //-------------------------------------------------------------------------
00272 
00273 SourceFragment::SourceFragment(int sid, int bl, int el, SourceFile *sf)
00274 {
00275   sourceId = sid;
00276   baseLine = bl;
00277   errorLine = el;
00278   sourceFile = sf;
00279   sourceFile->ref();
00280 }
00281 
00282 SourceFragment::~SourceFragment()
00283 {
00284   sourceFile->deref();
00285   sourceFile = 0L;
00286 }
00287 
00288 //-------------------------------------------------------------------------
00289 
00290 KJSErrorDialog::KJSErrorDialog(QWidget *parent, const QString& errorMessage, bool showDebug)
00291   : KDialogBase(parent,0,true,i18n("JavaScript Error"),
00292         showDebug ? KDialogBase::Ok|KDialogBase::User1 : KDialogBase::Ok,
00293         KDialogBase::Ok,false,KGuiItem("&Debug","gear"))
00294 {
00295   QWidget *page = new QWidget(this);
00296   setMainWidget(page);
00297 
00298   QLabel *iconLabel = new QLabel("",page);
00299   iconLabel->setPixmap(KGlobal::iconLoader()->loadIcon("messagebox_critical",
00300                                KIcon::NoGroup,KIcon::SizeMedium,
00301                                KIcon::DefaultState,0,true));
00302 
00303   QWidget *contents = new QWidget(page);
00304   QLabel *label = new QLabel(errorMessage,contents);
00305   m_dontShowAgainCb = new QCheckBox(i18n("&Do not show this message again"),contents);
00306 
00307   QVBoxLayout *vl = new QVBoxLayout(contents,0,spacingHint());
00308   vl->addWidget(label);
00309   vl->addWidget(m_dontShowAgainCb);
00310 
00311   QHBoxLayout *topLayout = new QHBoxLayout(page,0,spacingHint());
00312   topLayout->addWidget(iconLabel);
00313   topLayout->addWidget(contents);
00314   topLayout->addStretch(10);
00315 
00316   m_debugSelected = false;
00317 }
00318 
00319 KJSErrorDialog::~KJSErrorDialog()
00320 {
00321 }
00322 
00323 void KJSErrorDialog::slotUser1()
00324 {
00325   m_debugSelected = true;
00326   close();
00327 }
00328 
00329 //-------------------------------------------------------------------------
00330 EvalMultiLineEdit::EvalMultiLineEdit(QWidget *parent)
00331     : QMultiLineEdit(parent) {
00332 }
00333 
00334 void EvalMultiLineEdit::keyPressEvent(QKeyEvent * e)
00335 {
00336     if (e->key() == Qt::Key_Return) {
00337         if (hasSelectedText()) {
00338             m_code = selectedText();
00339         } else {
00340             int para, index;
00341             getCursorPosition(&para, &index);
00342             m_code = text(para);
00343         }
00344         end();
00345     }
00346     QMultiLineEdit::keyPressEvent(e);
00347 }
00348 //-------------------------------------------------------------------------
00349 KJSDebugWin::KJSDebugWin(QWidget *parent, const char *name)
00350   : KMainWindow(parent, name, WType_TopLevel), KInstance("kjs_debugger")
00351 {
00352   m_breakpoints = 0;
00353   m_breakpointCount = 0;
00354 
00355   m_curSourceFile = 0;
00356   m_mode = Continue;
00357   m_nextSourceUrl = "";
00358   m_nextSourceBaseLine = 1;
00359   m_execs = 0;
00360   m_execsCount = 0;
00361   m_execsAlloc = 0;
00362   m_steppingDepth = 0;
00363 
00364   m_stopIcon = KGlobal::iconLoader()->loadIcon("stop",KIcon::Small);
00365   m_emptyIcon = QPixmap(m_stopIcon.width(),m_stopIcon.height());
00366   QBitmap emptyMask(m_stopIcon.width(),m_stopIcon.height(),true);
00367   m_emptyIcon.setMask(emptyMask);
00368 
00369   setCaption(i18n("JavaScript Debugger"));
00370 
00371   QWidget *mainWidget = new QWidget(this);
00372   setCentralWidget(mainWidget);
00373 
00374   QVBoxLayout *vl = new QVBoxLayout(mainWidget,5);
00375 
00376   // frame list & code
00377   QSplitter *hsplitter = new QSplitter(Qt::Vertical,mainWidget);
00378   QSplitter *vsplitter = new QSplitter(hsplitter);
00379   QFont font(KGlobalSettings::fixedFont());
00380 
00381   QWidget *contextContainer = new QWidget(vsplitter);
00382 
00383   QLabel *contextLabel = new QLabel(i18n("Call stack"),contextContainer);
00384   QWidget *contextListContainer = new QWidget(contextContainer);
00385   m_contextList = new QListBox(contextListContainer);
00386   m_contextList->setMinimumSize(100,200);
00387   connect(m_contextList,SIGNAL(highlighted(int)),this,SLOT(slotShowFrame(int)));
00388 
00389   QHBoxLayout *clistLayout = new QHBoxLayout(contextListContainer);
00390   clistLayout->addWidget(m_contextList);
00391   clistLayout->addSpacing(KDialog::spacingHint());
00392 
00393   QVBoxLayout *contextLayout = new QVBoxLayout(contextContainer);
00394   contextLayout->addWidget(contextLabel);
00395   contextLayout->addSpacing(KDialog::spacingHint());
00396   contextLayout->addWidget(contextListContainer);
00397 
00398   // source selection & display
00399   QWidget *sourceSelDisplay = new QWidget(vsplitter);
00400   QVBoxLayout *ssdvl = new QVBoxLayout(sourceSelDisplay);
00401 
00402   m_sourceSel = new QComboBox(toolBar());
00403   connect(m_sourceSel,SIGNAL(activated(int)),this,SLOT(slotSourceSelected(int)));
00404 
00405   m_sourceDisplay = new SourceDisplay(this,sourceSelDisplay);
00406   ssdvl->addWidget(m_sourceDisplay);
00407   connect(m_sourceDisplay,SIGNAL(lineDoubleClicked(int)),SLOT(slotToggleBreakpoint(int)));
00408 
00409   QValueList<int> vsplitSizes;
00410   vsplitSizes.insert(vsplitSizes.end(),120);
00411   vsplitSizes.insert(vsplitSizes.end(),480);
00412   vsplitter->setSizes(vsplitSizes);
00413 
00414   // evaluate
00415 
00416   QWidget *evalContainer = new QWidget(hsplitter);
00417 
00418   QLabel *evalLabel = new QLabel(i18n("JavaScript console"),evalContainer);
00419   m_evalEdit = new EvalMultiLineEdit(evalContainer);
00420   m_evalEdit->setWordWrap(QMultiLineEdit::NoWrap);
00421   m_evalEdit->setFont(font);
00422   connect(m_evalEdit,SIGNAL(returnPressed()),SLOT(slotEval()));
00423   m_evalDepth = 0;
00424 
00425   QVBoxLayout *evalLayout = new QVBoxLayout(evalContainer);
00426   evalLayout->addSpacing(KDialog::spacingHint());
00427   evalLayout->addWidget(evalLabel);
00428   evalLayout->addSpacing(KDialog::spacingHint());
00429   evalLayout->addWidget(m_evalEdit);
00430 
00431   QValueList<int> hsplitSizes;
00432   hsplitSizes.insert(hsplitSizes.end(),400);
00433   hsplitSizes.insert(hsplitSizes.end(),200);
00434   hsplitter->setSizes(hsplitSizes);
00435 
00436   vl->addWidget(hsplitter);
00437 
00438   // actions
00439   KPopupMenu *debugMenu = new KPopupMenu(this);
00440   menuBar()->insertItem("&Debug",debugMenu);
00441 
00442   m_actionCollection = new KActionCollection(this);
00443   m_actionCollection->setInstance(this);
00444 
00445   // Venkman use F12, KDevelop F10
00446   KShortcut scNext = KShortcut(KKeySequence(KKey(Qt::Key_F12)));
00447   scNext.append(KKeySequence(KKey(Qt::Key_F10)));
00448   m_nextAction       = new KAction(i18n("Next breakpoint","&Next"),"dbgnext",scNext,this,SLOT(slotNext()),
00449                    m_actionCollection,"next");
00450   m_stepAction       = new KAction(i18n("&Step"),"dbgstep",KShortcut(Qt::Key_F11),this,SLOT(slotStep()),
00451                    m_actionCollection,"step");
00452   // Venkman use F5, Kdevelop F9
00453   KShortcut scCont = KShortcut(KKeySequence(KKey(Qt::Key_F5)));
00454   scCont.append(KKeySequence(KKey(Qt::Key_F9)));
00455   m_continueAction   = new KAction(i18n("&Continue"),"dbgrun",scCont,this,SLOT(slotContinue()),
00456                    m_actionCollection,"cont");
00457   m_stopAction       = new KAction(i18n("St&op"),"stop",KShortcut(Qt::Key_F4),this,SLOT(slotStop()),
00458                    m_actionCollection,"stop");
00459   m_breakAction      = new KAction(i18n("&Break at Next Statement"),"dbgrunto",KShortcut(Qt::Key_F8),this,SLOT(slotBreakNext()),
00460                    m_actionCollection,"breaknext");
00461 
00462 
00463   m_nextAction->setToolTip(i18n("Next breakpoint","Next"));
00464   m_stepAction->setToolTip(i18n("Step"));
00465   m_continueAction->setToolTip(i18n("Continue"));
00466   m_stopAction->setToolTip(i18n("Stop"));
00467   m_breakAction->setToolTip("Break at next Statement");
00468 
00469   m_nextAction->setEnabled(false);
00470   m_stepAction->setEnabled(false);
00471   m_continueAction->setEnabled(false);
00472   m_stopAction->setEnabled(false);
00473   m_breakAction->setEnabled(true);
00474 
00475   m_nextAction->plug(debugMenu);
00476   m_stepAction->plug(debugMenu);
00477   m_continueAction->plug(debugMenu);
00478 //   m_stopAction->plug(debugMenu); ### disabled until DebuggerImp::stop() works reliably
00479   m_breakAction->plug(debugMenu);
00480 
00481   m_nextAction->plug(toolBar());
00482   m_stepAction->plug(toolBar());
00483   m_continueAction->plug(toolBar());
00484 //   m_stopAction->plug(toolBar()); ###
00485   m_breakAction->plug(toolBar());
00486 
00487   toolBar()->insertWidget(1,300,m_sourceSel);
00488   toolBar()->setItemAutoSized(1);
00489 
00490   updateContextList();
00491   setMinimumSize(300,200);
00492   resize(600,450);
00493 
00494 }
00495 
00496 KJSDebugWin::~KJSDebugWin()
00497 {
00498   free(m_breakpoints);
00499   free(m_execs);
00500 }
00501 
00502 KJSDebugWin *KJSDebugWin::createInstance()
00503 {
00504   assert(!kjs_html_debugger);
00505   kjs_html_debugger = new KJSDebugWin();
00506   return kjs_html_debugger;
00507 }
00508 
00509 void KJSDebugWin::destroyInstance()
00510 {
00511   assert(kjs_html_debugger);
00512   kjs_html_debugger->hide();
00513   delete kjs_html_debugger;
00514 }
00515 
00516 void KJSDebugWin::slotNext()
00517 {
00518   m_mode = Next;
00519   leaveSession();
00520 }
00521 
00522 void KJSDebugWin::slotStep()
00523 {
00524   m_mode = Step;
00525   leaveSession();
00526 }
00527 
00528 void KJSDebugWin::slotContinue()
00529 {
00530   m_mode = Continue;
00531   leaveSession();
00532 }
00533 
00534 void KJSDebugWin::slotStop()
00535 {
00536   m_mode = Stop;
00537   while (!m_execStates.isEmpty())
00538     leaveSession();
00539 }
00540 
00541 void KJSDebugWin::slotBreakNext()
00542 {
00543   m_mode = Step;
00544 }
00545 
00546 void KJSDebugWin::slotToggleBreakpoint(int lineno)
00547 {
00548   if (m_sourceSel->currentItem() < 0)
00549     return;
00550 
00551   SourceFile *sourceFile = m_sourceSelFiles.at(m_sourceSel->currentItem());
00552 
00553   // Find the source fragment containing the selected line (if any)
00554   int sourceId = -1;
00555   int highestBaseLine = -1;
00556   QMap<int,SourceFragment*>::Iterator it;
00557 
00558   for (it = m_sourceFragments.begin(); it != m_sourceFragments.end(); ++it) {
00559     SourceFragment *sourceFragment = it.data();
00560     if (sourceFragment &&
00561     sourceFragment->sourceFile == sourceFile &&
00562     sourceFragment->baseLine <= lineno &&
00563     sourceFragment->baseLine > highestBaseLine) {
00564 
00565     sourceId = sourceFragment->sourceId;
00566     highestBaseLine = sourceFragment->baseLine;
00567     }
00568   }
00569 
00570   if (sourceId < 0)
00571     return;
00572 
00573   // Update the source code display with the appropriate icon
00574   int fragmentLineno = lineno-highestBaseLine+1;
00575   if (!setBreakpoint(sourceId,fragmentLineno)) // was already set
00576     deleteBreakpoint(sourceId,fragmentLineno);
00577 
00578   m_sourceDisplay->updateContents();
00579 }
00580 
00581 void KJSDebugWin::slotShowFrame(int frameno)
00582 {
00583   if (frameno < 0 || frameno >= m_execsCount)
00584     return;
00585 
00586   Context ctx = m_execs[frameno]->context();
00587   setSourceLine(ctx.sourceId(),ctx.curStmtFirstLine());
00588 }
00589 
00590 void KJSDebugWin::slotSourceSelected(int sourceSelIndex)
00591 {
00592   // A source file has been selected from the drop-down list - display the file
00593   if (sourceSelIndex < 0 || sourceSelIndex >= (int)m_sourceSel->count())
00594     return;
00595   SourceFile *sourceFile = m_sourceSelFiles.at(sourceSelIndex);
00596   displaySourceFile(sourceFile,true);
00597 
00598   // If the currently selected context is in the current source file, then hilight
00599   // the line it's on.
00600   if (m_contextList->currentItem() >= 0) {
00601     Context ctx = m_execs[m_contextList->currentItem()]->context();
00602     if (m_sourceFragments[ctx.sourceId()]->sourceFile == m_sourceSelFiles.at(sourceSelIndex))
00603       setSourceLine(ctx.sourceId(),ctx.curStmtFirstLine());
00604   }
00605 }
00606 
00607 void KJSDebugWin::slotEval()
00608 {
00609   // Work out which execution state to use. If we're currently in a debugging session,
00610   // use the current context - otherwise, use the global execution state from the interpreter
00611   // corresponding to the currently displayed source file.
00612   ExecState *exec;
00613   Object thisobj;
00614   if (m_execStates.isEmpty()) {
00615     if (m_sourceSel->currentItem() < 0)
00616       return;
00617     SourceFile *sourceFile = m_sourceSelFiles.at(m_sourceSel->currentItem());
00618     if (!sourceFile->interpreter)
00619       return;
00620     exec = sourceFile->interpreter->globalExec();
00621     thisobj = exec->interpreter()->globalObject();
00622   }
00623   else {
00624     exec = m_execStates.top();
00625     thisobj = exec->context().thisValue();
00626   }
00627 
00628   // Evaluate the js code from m_evalEdit
00629   UString code(m_evalEdit->code());
00630   QString msg;
00631 
00632   KJSCPUGuard guard;
00633   guard.start();
00634 
00635   Interpreter *interp = exec->interpreter();
00636 
00637   Object obj = Object::dynamicCast(interp->globalObject().get(exec, "eval"));
00638   List args;
00639   args.append(String(code));
00640 
00641   m_evalDepth++;
00642   Value retval = obj.call(exec, thisobj, args);
00643   m_evalDepth--;
00644   guard.stop();
00645 
00646   // Print the return value or exception message to the console
00647   if (exec->hadException()) {
00648     Value exc = exec->exception();
00649     exec->clearException();
00650     msg = "Exception: " + exc.toString(interp->globalExec()).qstring();
00651   }
00652   else {
00653     msg = retval.toString(interp->globalExec()).qstring();
00654   }
00655 
00656   m_evalEdit->insert(msg+"\n");
00657   updateContextList();
00658 }
00659 
00660 void KJSDebugWin::closeEvent(QCloseEvent *e)
00661 {
00662   while (!m_execStates.isEmpty()) // ### not sure if this will work
00663     leaveSession();
00664   return QWidget::closeEvent(e);
00665 }
00666 
00667 bool KJSDebugWin::eventFilter(QObject *o, QEvent *e)
00668 {
00669   switch (e->type()) {
00670   case QEvent::MouseButtonPress:
00671   case QEvent::MouseButtonRelease:
00672   case QEvent::MouseButtonDblClick:
00673   case QEvent::MouseMove:
00674   case QEvent::KeyPress:
00675   case QEvent::KeyRelease:
00676   case QEvent::Destroy:
00677   case QEvent::Close:
00678   case QEvent::Quit:
00679     while (o->parent())
00680       o = o->parent();
00681     if (o == this)
00682       return QWidget::eventFilter(o,e);
00683     else
00684       return true;
00685     break;
00686   default:
00687     return QWidget::eventFilter(o,e);
00688   }
00689 }
00690 
00691 void KJSDebugWin::disableOtherWindows()
00692 {
00693   QWidgetList *widgets = QApplication::allWidgets();
00694   QWidgetListIt it(*widgets);
00695   for (; it.current(); ++it)
00696     it.current()->installEventFilter(this);
00697 }
00698 
00699 void KJSDebugWin::enableOtherWindows()
00700 {
00701   QWidgetList *widgets = QApplication::allWidgets();
00702   QWidgetListIt it(*widgets);
00703   for (; it.current(); ++it)
00704     it.current()->removeEventFilter(this);
00705 }
00706 
00707 bool KJSDebugWin::sourceParsed(KJS::ExecState *exec, int sourceId,
00708                                const KJS::UString &source, int errorLine)
00709 {
00710   // Work out which source file this fragment is in
00711   SourceFile *sourceFile = 0;
00712   if (!m_nextSourceUrl.isEmpty())
00713     sourceFile = getSourceFile(exec->interpreter(),m_nextSourceUrl);
00714 
00715   int index;
00716   if (!sourceFile) {
00717     index = m_sourceSel->count();
00718     if (!m_nextSourceUrl.isEmpty()) {
00719 
00720       QString code = source.qstring();
00721       KParts::ReadOnlyPart *part = static_cast<ScriptInterpreter*>(exec->interpreter())->part();
00722       if (m_nextSourceUrl == part->url().url()) {
00723     // Only store the code here if it's not from the part's html page... in that
00724     // case we can get it from KHTMLPageCache
00725     code = QString::null;
00726       }
00727 
00728       sourceFile = new SourceFile(m_nextSourceUrl,code,exec->interpreter());
00729       setSourceFile(exec->interpreter(),m_nextSourceUrl,sourceFile);
00730       m_sourceSelFiles.append(sourceFile);
00731       m_sourceSel->insertItem(m_nextSourceUrl);
00732     }
00733     else {
00734       // Sourced passed from somewhere else (possibly an eval call)... we don't know the url,
00735       // but we still know the interpreter
00736       sourceFile = new SourceFile("(unknown)",source.qstring(),exec->interpreter());
00737       m_sourceSelFiles.append(sourceFile);
00738       m_sourceSel->insertItem(QString::number(index) += "-???");
00739     }
00740   }
00741   else {
00742     // Ensure that each source file to be displayed is associated with
00743     // an appropriate interpreter
00744     if (!sourceFile->interpreter)
00745       sourceFile->interpreter = exec->interpreter();
00746     for (index = 0; index < m_sourceSel->count(); index++) {
00747       if (m_sourceSelFiles.at(index) == sourceFile)
00748     break;
00749     }
00750     assert(index < m_sourceSel->count());
00751   }
00752 
00753   SourceFragment *sf = new SourceFragment(sourceId,m_nextSourceBaseLine,errorLine,sourceFile);
00754   m_sourceFragments[sourceId] = sf;
00755 
00756   if (m_sourceSel->currentItem() < 0)
00757     m_sourceSel->setCurrentItem(index);
00758 
00759   if (m_sourceSel->currentItem() == index) {
00760     displaySourceFile(sourceFile,true);
00761   }
00762 
00763   m_nextSourceBaseLine = 1;
00764   m_nextSourceUrl = "";
00765 
00766   return (m_mode != Stop);
00767 }
00768 
00769 bool KJSDebugWin::sourceUnused(KJS::ExecState *exec, int sourceId)
00770 {
00771   // Verify that there aren't any contexts on the stack using the given sourceId
00772   // This should never be the case because this function is only called when
00773   // the interpreter has deleted all Node objects for the source.
00774   for (int e = 0; e < m_execsCount; e++)
00775     assert(m_execs[e]->context().sourceId() != sourceId);
00776 
00777   // Now remove the fragment (and the SourceFile, if it was the last fragment in that file)
00778   SourceFragment *fragment = m_sourceFragments[sourceId];
00779   if (fragment) {
00780     m_sourceFragments.erase(sourceId);
00781 
00782     SourceFile *sourceFile = fragment->sourceFile;
00783     if (sourceFile->hasOneRef()) {
00784       for (int i = 0; i < m_sourceSel->count(); i++) {
00785     if (m_sourceSelFiles.at(i) == sourceFile) {
00786       m_sourceSel->removeItem(i);
00787       m_sourceSelFiles.remove(i);
00788       break;
00789     }
00790       }
00791       removeSourceFile(exec->interpreter(),sourceFile->url);
00792     }
00793     delete fragment;
00794   }
00795 
00796   return (m_mode != Stop);
00797 }
00798 
00799 bool KJSDebugWin::exception(ExecState *exec, const Value &value, bool inTryCatch)
00800 {
00801   assert(value.isValid());
00802 
00803   // Ignore exceptions that will be caught by the script
00804   if (inTryCatch)
00805     return true;
00806 
00807   KParts::ReadOnlyPart *part = static_cast<ScriptInterpreter*>(exec->interpreter())->part();
00808   KHTMLPart *khtmlpart = ::qt_cast<KHTMLPart*>(part);
00809   if (khtmlpart && !khtmlpart->settings()->isJavaScriptErrorReportingEnabled())
00810     return true;
00811 
00812   QWidget *dlgParent = (m_evalDepth == 0) ? (QWidget*)part->widget() : (QWidget*)this;
00813 
00814   QString exceptionMsg = value.toString(exec).qstring();
00815 
00816   // Syntax errors are a special case. For these we want to display the url & lineno,
00817   // which isn't included in the exception messeage. So we work it out from the values
00818   // passed to sourceParsed()
00819   Object valueObj = Object::dynamicCast(value);
00820   Object syntaxError = exec->interpreter()->builtinSyntaxError();
00821   if (valueObj.isValid() && valueObj.get(exec,"constructor").imp() == syntaxError.imp()) {
00822     Value sidValue = valueObj.get(exec,"sid");
00823     if (sidValue.isA(NumberType)) { // sid is not set for Function() constructor
00824       int sourceId = (int)sidValue.toNumber(exec);
00825       assert(m_sourceFragments[sourceId]);
00826       exceptionMsg = i18n("Parse error at %1 line %2")
00827              .arg(m_sourceFragments[sourceId]->sourceFile->url)
00828              .arg(m_sourceFragments[sourceId]->baseLine+m_sourceFragments[sourceId]->errorLine-1);
00829     }
00830   }
00831 
00832   bool dontShowAgain = false;
00833   if (m_execsCount == 0) {
00834     // An exception occurred and we're not currently executing any code... this can
00835     // happen in some cases e.g. a parse error, or native code accessing funcitons like
00836     // Object::put()
00837     QString msg = i18n("An error occurred while attempting to run a script on this page.\n\n%1")
00838           .arg(exceptionMsg);
00839     KJSErrorDialog dlg(dlgParent,msg,false);
00840     dlg.exec();
00841     dontShowAgain = dlg.dontShowAgain();
00842   }
00843   else {
00844     Context ctx = m_execs[m_execsCount-1]->context();
00845     SourceFragment *sourceFragment = m_sourceFragments[ctx.sourceId()];
00846     QString msg = i18n("An error occurred while attempting to run a script on this page.\n\n%1 line %2:\n%3")
00847           .arg(KStringHandler::rsqueeze( sourceFragment->sourceFile->url,80),
00848           QString::number( sourceFragment->baseLine+ctx.curStmtFirstLine()-1),
00849           exceptionMsg);
00850 
00851     KJSErrorDialog dlg(dlgParent,msg,true);
00852     dlg.exec();
00853     dontShowAgain = dlg.dontShowAgain();
00854 
00855     if (dlg.debugSelected()) {
00856       m_mode = Next;
00857       m_steppingDepth = m_execsCount-1;
00858       enterSession(exec);
00859     }
00860   }
00861 
00862   if (dontShowAgain) {
00863     KConfig *config = kapp->config();
00864     KConfigGroupSaver saver(config,QString::fromLatin1("Java/JavaScript Settings"));
00865     config->writeEntry("ReportJavaScriptErrors",QVariant(false,0));
00866     config->sync();
00867     QByteArray data;
00868     kapp->dcopClient()->send( "konqueror*", "KonquerorIface", "reparseConfiguration()", data );
00869   }
00870 
00871   return (m_mode != Stop);
00872 }
00873 
00874 bool KJSDebugWin::atStatement(KJS::ExecState *exec)
00875 {
00876   assert(m_execsCount > 0);
00877   assert(m_execs[m_execsCount-1] == exec);
00878   checkBreak(exec);
00879   return (m_mode != Stop);
00880 }
00881 
00882 bool KJSDebugWin::enterContext(ExecState *exec)
00883 {
00884   if (m_execsCount >= m_execsAlloc) {
00885     m_execsAlloc += 10;
00886     m_execs = (ExecState**)realloc(m_execs,m_execsAlloc*sizeof(ExecState*));
00887   }
00888   m_execs[m_execsCount++] = exec;
00889 
00890   if (m_mode == Step)
00891     m_steppingDepth = m_execsCount-1;
00892 
00893   checkBreak(exec);
00894   return (m_mode != Stop);
00895 }
00896 
00897 bool KJSDebugWin::exitContext(ExecState *exec, const Completion &/*completion*/)
00898 {
00899   assert(m_execsCount > 0);
00900   assert(m_execs[m_execsCount-1] == exec);
00901 
00902   checkBreak(exec);
00903 
00904   m_execsCount--;
00905   if (m_steppingDepth > m_execsCount-1)
00906     m_steppingDepth = m_execsCount-1;
00907   if (m_execsCount == 0)
00908     updateContextList();
00909 
00910   return (m_mode != Stop);
00911 }
00912 
00913 void KJSDebugWin::displaySourceFile(SourceFile *sourceFile, bool forceRefresh)
00914 {
00915   if (m_curSourceFile == sourceFile && !forceRefresh)
00916     return;
00917   sourceFile->ref();
00918   m_sourceDisplay->setSource(sourceFile);
00919   if (m_curSourceFile)
00920      m_curSourceFile->deref();
00921   m_curSourceFile = sourceFile;
00922 }
00923 
00924 void KJSDebugWin::setSourceLine(int sourceId, int lineno)
00925 {
00926   SourceFragment *source = m_sourceFragments[sourceId];
00927   if (!source)
00928     return;
00929 
00930   SourceFile *sourceFile = source->sourceFile;
00931   if (m_curSourceFile != source->sourceFile) {
00932       for (int i = 0; i < m_sourceSel->count(); i++)
00933     if (m_sourceSelFiles.at(i) == sourceFile)
00934       m_sourceSel->setCurrentItem(i);
00935       displaySourceFile(sourceFile,false);
00936   }
00937   m_sourceDisplay->setCurrentLine(source->baseLine+lineno-2);
00938 }
00939 
00940 void KJSDebugWin::setNextSourceInfo(QString url, int baseLine)
00941 {
00942   m_nextSourceUrl = url;
00943   m_nextSourceBaseLine = baseLine;
00944 }
00945 
00946 void KJSDebugWin::sourceChanged(Interpreter *interpreter, QString url)
00947 {
00948   SourceFile *sourceFile = getSourceFile(interpreter,url);
00949   if (sourceFile && m_curSourceFile == sourceFile)
00950     displaySourceFile(sourceFile,true);
00951 }
00952 
00953 void KJSDebugWin::clearInterpreter(Interpreter *interpreter)
00954 {
00955   QMap<int,SourceFragment*>::Iterator it;
00956 
00957   for (it = m_sourceFragments.begin(); it != m_sourceFragments.end(); ++it)
00958     if (it.data() && it.data()->sourceFile->interpreter == interpreter)
00959       it.data()->sourceFile->interpreter = 0;
00960 }
00961 
00962 SourceFile *KJSDebugWin::getSourceFile(Interpreter *interpreter, QString url)
00963 {
00964   QString key = QString("%1|%2").arg((long)interpreter).arg(url);
00965   return m_sourceFiles[key];
00966 }
00967 
00968 void KJSDebugWin::setSourceFile(Interpreter *interpreter, QString url, SourceFile *sourceFile)
00969 {
00970   QString key = QString("%1|%2").arg((long)interpreter).arg(url);
00971   sourceFile->ref();
00972   if (SourceFile* oldFile = m_sourceFiles[key])
00973     oldFile->deref();
00974   m_sourceFiles[key] = sourceFile;
00975 }
00976 
00977 void KJSDebugWin::removeSourceFile(Interpreter *interpreter, QString url)
00978 {
00979   QString key = QString("%1|%2").arg((long)interpreter).arg(url);
00980   if (SourceFile* oldFile = m_sourceFiles[key])
00981     oldFile->deref();
00982   m_sourceFiles.remove(key);
00983 }
00984 
00985 void KJSDebugWin::checkBreak(ExecState *exec)
00986 {
00987   if (m_breakpointCount > 0) {
00988     Context ctx = m_execs[m_execsCount-1]->context();
00989     if (haveBreakpoint(ctx.sourceId(),ctx.curStmtFirstLine(),ctx.curStmtLastLine())) {
00990       m_mode = Next;
00991       m_steppingDepth = m_execsCount-1;
00992     }
00993   }
00994 
00995   if ((m_mode == Step || m_mode == Next) && m_steppingDepth == m_execsCount-1)
00996     enterSession(exec);
00997 }
00998 
00999 void KJSDebugWin::enterSession(ExecState *exec)
01000 {
01001   // This "enters" a new debugging session, i.e. enables usage of the debugging window
01002   // It re-enters the qt event loop here, allowing execution of other parts of the
01003   // program to continue while the script is stopped. We have to be a bit careful here,
01004   // i.e. make sure the user can't quit the app, and disable other event handlers which
01005   // could interfere with the debugging session.
01006   if (!isVisible())
01007     show();
01008 
01009   m_mode = Continue;
01010 
01011   if (m_execStates.isEmpty()) {
01012     disableOtherWindows();
01013     m_nextAction->setEnabled(true);
01014     m_stepAction->setEnabled(true);
01015     m_continueAction->setEnabled(true);
01016     m_stopAction->setEnabled(true);
01017     m_breakAction->setEnabled(false);
01018   }
01019   m_execStates.push(exec);
01020 
01021   updateContextList();
01022 
01023   qApp->enter_loop(); // won't return until leaveSession() is called
01024 }
01025 
01026 void KJSDebugWin::leaveSession()
01027 {
01028   // Disables debugging for this window and returns to execute the rest of the script
01029   // (or aborts execution, if the user pressed stop). When this returns, the program
01030   // will exit the qt event loop, i.e. return to whatever processing was being done
01031   // before the debugger was stopped.
01032   assert(!m_execStates.isEmpty());
01033 
01034   m_execStates.pop();
01035 
01036   if (m_execStates.isEmpty()) {
01037     m_nextAction->setEnabled(false);
01038     m_stepAction->setEnabled(false);
01039     m_continueAction->setEnabled(false);
01040     m_stopAction->setEnabled(false);
01041     m_breakAction->setEnabled(true);
01042     m_sourceDisplay->setCurrentLine(-1);
01043     enableOtherWindows();
01044   }
01045 
01046   qApp->exit_loop();
01047 }
01048 
01049 void KJSDebugWin::updateContextList()
01050 {
01051   disconnect(m_contextList,SIGNAL(highlighted(int)),this,SLOT(slotShowFrame(int)));
01052 
01053   m_contextList->clear();
01054   for (int i = 0; i < m_execsCount; i++)
01055     m_contextList->insertItem(contextStr(m_execs[i]->context()));
01056 
01057   if (m_execsCount > 0) {
01058     m_contextList->setSelected(m_execsCount-1, true);
01059     Context ctx = m_execs[m_execsCount-1]->context();
01060     setSourceLine(ctx.sourceId(),ctx.curStmtFirstLine());
01061   }
01062 
01063   connect(m_contextList,SIGNAL(highlighted(int)),this,SLOT(slotShowFrame(int)));
01064 }
01065 
01066 QString KJSDebugWin::contextStr(const Context &ctx)
01067 {
01068   QString str = "";
01069   SourceFragment *sourceFragment = m_sourceFragments[ctx.sourceId()];
01070   QString url = sourceFragment->sourceFile->url;
01071   int fileLineno = sourceFragment->baseLine+ctx.curStmtFirstLine()-1;
01072 
01073   switch (ctx.codeType()) {
01074   case GlobalCode:
01075     str = QString("Global code at %1:%2").arg(url).arg(fileLineno);
01076     break;
01077   case EvalCode:
01078     str = QString("Eval code at %1:%2").arg(url).arg(fileLineno);
01079     break;
01080   case FunctionCode:
01081     if (!ctx.functionName().isNull())
01082       str = QString("%1() at %2:%3").arg(ctx.functionName().qstring()).arg(url).arg(fileLineno);
01083     else
01084       str = QString("Anonymous function at %1:%2").arg(url).arg(fileLineno);
01085     break;
01086   }
01087 
01088   return str;
01089 }
01090 
01091 bool KJSDebugWin::setBreakpoint(int sourceId, int lineno)
01092 {
01093   if (haveBreakpoint(sourceId,lineno,lineno))
01094     return false;
01095 
01096   m_breakpointCount++;
01097   m_breakpoints = static_cast<Breakpoint*>(realloc(m_breakpoints,
01098                            m_breakpointCount*sizeof(Breakpoint)));
01099   m_breakpoints[m_breakpointCount-1].sourceId = sourceId;
01100   m_breakpoints[m_breakpointCount-1].lineno = lineno;
01101 
01102   return true;
01103 }
01104 
01105 bool KJSDebugWin::deleteBreakpoint(int sourceId, int lineno)
01106 {
01107   for (int i = 0; i < m_breakpointCount; i++) {
01108     if (m_breakpoints[i].sourceId == sourceId && m_breakpoints[i].lineno == lineno) {
01109 
01110       memmove(m_breakpoints+i,m_breakpoints+i+1,(m_breakpointCount-i-1)*sizeof(Breakpoint));
01111       m_breakpointCount--;
01112       m_breakpoints = static_cast<Breakpoint*>(realloc(m_breakpoints,
01113                                m_breakpointCount*sizeof(Breakpoint)));
01114       return true;
01115     }
01116   }
01117 
01118   return false;
01119 }
01120 
01121 bool KJSDebugWin::haveBreakpoint(SourceFile *sourceFile, int line0, int line1)
01122 {
01123   for (int i = 0; i < m_breakpointCount; i++) {
01124     int sourceId = m_breakpoints[i].sourceId;
01125     int lineno = m_breakpoints[i].lineno;
01126     if (m_sourceFragments.contains(sourceId) &&
01127         m_sourceFragments[sourceId]->sourceFile == sourceFile) {
01128       int absLineno = m_sourceFragments[sourceId]->baseLine+lineno-1;
01129       if (absLineno >= line0 && absLineno <= line1)
01130     return true;
01131     }
01132   }
01133 
01134   return false;
01135 }
01136 
01137 #include "kjs_debugwin.moc"
01138 
01139 #endif // KJS_DEBUGGER
KDE Home | KDE Accessibility Home | Description of Access Keys