You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1140 lines
33 KiB
1140 lines
33 KiB
/*
|
|
* This file is part of the KDE libraries
|
|
* Copyright (C) 2000-2001 Harri Porten (porten@kde.org)
|
|
* Copyright (C) 2001,2003 Peter Kelly (pmk@post.com)
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "kjs_debugwin.h"
|
|
#include "kjs_proxy.h"
|
|
|
|
#ifdef KJS_DEBUGGER
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <tqlayout.h>
|
|
#include <tqpushbutton.h>
|
|
#include <tqtextedit.h>
|
|
#include <tqlistbox.h>
|
|
#include <tqmultilineedit.h>
|
|
#include <tqapplication.h>
|
|
#include <tqsplitter.h>
|
|
#include <tqcombobox.h>
|
|
#include <tqbitmap.h>
|
|
#include <tqwidgetlist.h>
|
|
#include <tqlabel.h>
|
|
#include <tqdatastream.h>
|
|
#include <tqcstring.h>
|
|
#include <tqpainter.h>
|
|
#include <tqscrollbar.h>
|
|
|
|
#include <klocale.h>
|
|
#include <kdebug.h>
|
|
#include <kiconloader.h>
|
|
#include <kglobal.h>
|
|
#include <kmessagebox.h>
|
|
#include <kguiitem.h>
|
|
#include <kpopupmenu.h>
|
|
#include <kmenubar.h>
|
|
#include <kaction.h>
|
|
#include <kactioncollection.h>
|
|
#include <kglobalsettings.h>
|
|
#include <kshortcut.h>
|
|
#include <kconfig.h>
|
|
#include <kconfigbase.h>
|
|
#include <kapplication.h>
|
|
#include <dcop/dcopclient.h>
|
|
#include <kstringhandler.h>
|
|
|
|
#include "kjs_dom.h"
|
|
#include "kjs_binding.h"
|
|
#include "khtml_part.h"
|
|
#include "khtmlview.h"
|
|
#include "khtml_pagecache.h"
|
|
#include "khtml_settings.h"
|
|
#include "khtml_factory.h"
|
|
#include "misc/decoder.h"
|
|
#include <kjs/ustring.h>
|
|
#include <kjs/object.h>
|
|
#include <kjs/function.h>
|
|
#include <kjs/interpreter.h>
|
|
|
|
using namespace KJS;
|
|
using namespace khtml;
|
|
|
|
SourceDisplay::SourceDisplay(KJSDebugWin *debugWin, TQWidget *parent, const char *name)
|
|
: TQScrollView(parent,name), m_currentLine(-1), m_sourceFile(0), m_debugWin(debugWin),
|
|
m_font(KGlobalSettings::fixedFont())
|
|
{
|
|
verticalScrollBar()->setLineStep(TQFontMetrics(m_font).height());
|
|
viewport()->setBackgroundMode(TQt::NoBackground);
|
|
m_breakpointIcon = KGlobal::iconLoader()->loadIcon("stop",KIcon::Small);
|
|
}
|
|
|
|
SourceDisplay::~SourceDisplay()
|
|
{
|
|
if (m_sourceFile) {
|
|
m_sourceFile->deref();
|
|
m_sourceFile = 0L;
|
|
}
|
|
}
|
|
|
|
void SourceDisplay::setSource(SourceFile *sourceFile)
|
|
{
|
|
if ( sourceFile )
|
|
sourceFile->ref();
|
|
if (m_sourceFile)
|
|
m_sourceFile->deref();
|
|
m_sourceFile = sourceFile;
|
|
if ( m_sourceFile )
|
|
m_sourceFile->ref();
|
|
|
|
if (!m_sourceFile || !m_debugWin->isVisible()) {
|
|
return;
|
|
}
|
|
|
|
TQString code = sourceFile->getCode();
|
|
const TQChar *chars = code.tqunicode();
|
|
uint len = code.length();
|
|
TQChar newLine('\n');
|
|
TQChar cr('\r');
|
|
TQChar tab('\t');
|
|
TQString tabstr(" ");
|
|
TQString line;
|
|
m_lines.clear();
|
|
int width = 0;
|
|
TQFontMetrics metrics(m_font);
|
|
|
|
for (uint pos = 0; pos < len; pos++) {
|
|
TQChar c = chars[pos];
|
|
if (c == cr) {
|
|
if (pos < len-1 && chars[pos+1] == newLine)
|
|
continue;
|
|
else
|
|
c = newLine;
|
|
}
|
|
if (c == newLine) {
|
|
m_lines.append(line);
|
|
int lineWidth = metrics.width(line);
|
|
if (lineWidth > width)
|
|
width = lineWidth;
|
|
line = "";
|
|
}
|
|
else if (c == tab) {
|
|
line += tabstr;
|
|
}
|
|
else {
|
|
line += c;
|
|
}
|
|
}
|
|
if (line.length()) {
|
|
m_lines.append(line);
|
|
int lineWidth = metrics.width(line);
|
|
if (lineWidth > width)
|
|
width = lineWidth;
|
|
}
|
|
|
|
int linenoDisplayWidth = metrics.width("888888");
|
|
resizeContents(linenoDisplayWidth+4+width,metrics.height()*m_lines.count());
|
|
update();
|
|
sourceFile->deref();
|
|
}
|
|
|
|
void SourceDisplay::setCurrentLine(int lineno, bool doCenter)
|
|
{
|
|
m_currentLine = lineno;
|
|
|
|
if (doCenter && m_currentLine >= 0) {
|
|
TQFontMetrics metrics(m_font);
|
|
int height = metrics.height();
|
|
center(0,height*m_currentLine+height/2);
|
|
}
|
|
|
|
updateContents();
|
|
}
|
|
|
|
void SourceDisplay::contentsMousePressEvent(TQMouseEvent *e)
|
|
{
|
|
TQScrollView::mouseDoubleClickEvent(e);
|
|
TQFontMetrics metrics(m_font);
|
|
int lineno = e->y()/metrics.height();
|
|
emit lineDoubleClicked(lineno+1); // line numbers start from 1
|
|
}
|
|
|
|
void SourceDisplay::showEvent(TQShowEvent *)
|
|
{
|
|
setSource(m_sourceFile);
|
|
}
|
|
|
|
void SourceDisplay::drawContents(TQPainter *p, int clipx, int clipy, int clipw, int cliph)
|
|
{
|
|
if (!m_sourceFile) {
|
|
p->fillRect(clipx,clipy,clipw,cliph,tqpalette().active().base());
|
|
return;
|
|
}
|
|
|
|
TQFontMetrics metrics(m_font);
|
|
int height = metrics.height();
|
|
|
|
int bottom = clipy + cliph;
|
|
int right = clipx + clipw;
|
|
|
|
int firstLine = clipy/height-1;
|
|
if (firstLine < 0)
|
|
firstLine = 0;
|
|
int lastLine = bottom/height+2;
|
|
if (lastLine > (int)m_lines.count())
|
|
lastLine = m_lines.count();
|
|
|
|
p->setFont(m_font);
|
|
|
|
int linenoWidth = metrics.width("888888");
|
|
|
|
for (int lineno = firstLine; lineno <= lastLine; lineno++) {
|
|
TQString linenoStr = TQString().sprintf("%d",lineno+1);
|
|
|
|
|
|
p->fillRect(0,height*lineno,linenoWidth,height,tqpalette().active().mid());
|
|
|
|
p->setPen(tqpalette().active().text());
|
|
p->drawText(0,height*lineno,linenoWidth,height,Qt::AlignRight,linenoStr);
|
|
|
|
TQColor bgColor;
|
|
TQColor textColor;
|
|
|
|
if (lineno == m_currentLine) {
|
|
bgColor = tqpalette().active().highlight();
|
|
textColor = tqpalette().active().highlightedText();
|
|
}
|
|
else if (m_debugWin->haveBreakpoint(m_sourceFile,lineno+1,lineno+1)) {
|
|
bgColor = tqpalette().active().text();
|
|
textColor = tqpalette().active().base();
|
|
p->drawPixmap(2,height*lineno+height/2-m_breakpointIcon.height()/2,m_breakpointIcon);
|
|
}
|
|
else {
|
|
bgColor = tqpalette().active().base();
|
|
textColor = tqpalette().active().text();
|
|
}
|
|
|
|
p->fillRect(linenoWidth,height*lineno,right-linenoWidth,height,bgColor);
|
|
p->setPen(textColor);
|
|
p->drawText(linenoWidth+4,height*lineno,contentsWidth()-linenoWidth-4,height,
|
|
Qt::AlignLeft,m_lines[lineno]);
|
|
}
|
|
|
|
int remainingTop = height*(lastLine+1);
|
|
p->fillRect(0,remainingTop,linenoWidth,bottom-remainingTop,tqpalette().active().mid());
|
|
|
|
p->fillRect(linenoWidth,remainingTop,
|
|
right-linenoWidth,bottom-remainingTop,tqpalette().active().base());
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
KJSDebugWin * KJSDebugWin::kjs_html_debugger = 0;
|
|
|
|
TQString SourceFile::getCode()
|
|
{
|
|
if (interpreter) {
|
|
KHTMLPart *part = ::tqqt_cast<KHTMLPart*>(static_cast<ScriptInterpreter*>(interpreter)->part());
|
|
if (part && url == part->url().url() && KHTMLPageCache::self()->isValid(part->cacheId())) {
|
|
Decoder *decoder = part->createDecoder();
|
|
TQByteArray data;
|
|
TQDataStream stream(data,IO_WriteOnly);
|
|
KHTMLPageCache::self()->saveData(part->cacheId(),&stream);
|
|
TQString str;
|
|
if (data.size() == 0)
|
|
str = "";
|
|
else
|
|
str = decoder->decode(data.data(),data.size()) + decoder->flush();
|
|
delete decoder;
|
|
return str;
|
|
}
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
SourceFragment::SourceFragment(int sid, int bl, int el, SourceFile *sf)
|
|
{
|
|
sourceId = sid;
|
|
baseLine = bl;
|
|
errorLine = el;
|
|
sourceFile = sf;
|
|
sourceFile->ref();
|
|
}
|
|
|
|
SourceFragment::~SourceFragment()
|
|
{
|
|
sourceFile->deref();
|
|
sourceFile = 0L;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
KJSErrorDialog::KJSErrorDialog(TQWidget *parent, const TQString& errorMessage, bool showDebug)
|
|
: KDialogBase(parent,0,true,i18n("JavaScript Error"),
|
|
showDebug ? KDialogBase::Ok|KDialogBase::User1 : KDialogBase::Ok,
|
|
KDialogBase::Ok,false,KGuiItem("&Debug","gear"))
|
|
{
|
|
TQWidget *page = new TQWidget(this);
|
|
setMainWidget(page);
|
|
|
|
TQLabel *iconLabel = new TQLabel("",page);
|
|
iconLabel->setPixmap(KGlobal::iconLoader()->loadIcon("messagebox_critical",
|
|
KIcon::NoGroup,KIcon::SizeMedium,
|
|
KIcon::DefaultState,0,true));
|
|
|
|
TQWidget *contents = new TQWidget(page);
|
|
TQLabel *label = new TQLabel(errorMessage,contents);
|
|
m_dontShowAgainCb = new TQCheckBox(i18n("&Do not show this message again"),contents);
|
|
|
|
TQVBoxLayout *vl = new TQVBoxLayout(contents,0,spacingHint());
|
|
vl->addWidget(label);
|
|
vl->addWidget(m_dontShowAgainCb);
|
|
|
|
TQHBoxLayout *topLayout = new TQHBoxLayout(page,0,spacingHint());
|
|
topLayout->addWidget(iconLabel);
|
|
topLayout->addWidget(contents);
|
|
topLayout->addStretch(10);
|
|
|
|
m_debugSelected = false;
|
|
}
|
|
|
|
KJSErrorDialog::~KJSErrorDialog()
|
|
{
|
|
}
|
|
|
|
void KJSErrorDialog::slotUser1()
|
|
{
|
|
m_debugSelected = true;
|
|
close();
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
EvalMultiLineEdit::EvalMultiLineEdit(TQWidget *parent)
|
|
: TQMultiLineEdit(parent) {
|
|
}
|
|
|
|
void EvalMultiLineEdit::keyPressEvent(TQKeyEvent * e)
|
|
{
|
|
if (e->key() == Qt::Key_Return) {
|
|
if (hasSelectedText()) {
|
|
m_code = selectedText();
|
|
} else {
|
|
int para, index;
|
|
getCursorPosition(¶, &index);
|
|
m_code = text(para);
|
|
}
|
|
end();
|
|
}
|
|
TQMultiLineEdit::keyPressEvent(e);
|
|
}
|
|
//-------------------------------------------------------------------------
|
|
KJSDebugWin::KJSDebugWin(TQWidget *parent, const char *name)
|
|
: KMainWindow(parent, name, (WFlags)WType_TopLevel), KInstance("kjs_debugger")
|
|
{
|
|
m_breakpoints = 0;
|
|
m_breakpointCount = 0;
|
|
|
|
m_curSourceFile = 0;
|
|
m_mode = Continue;
|
|
m_nextSourceUrl = "";
|
|
m_nextSourceBaseLine = 1;
|
|
m_execs = 0;
|
|
m_execsCount = 0;
|
|
m_execsAlloc = 0;
|
|
m_steppingDepth = 0;
|
|
|
|
m_stopIcon = KGlobal::iconLoader()->loadIcon("stop",KIcon::Small);
|
|
m_emptyIcon = TQPixmap(m_stopIcon.width(),m_stopIcon.height());
|
|
TQBitmap emptyMask(m_stopIcon.width(),m_stopIcon.height(),true);
|
|
m_emptyIcon.setMask(emptyMask);
|
|
|
|
setCaption(i18n("JavaScript Debugger"));
|
|
|
|
TQWidget *mainWidget = new TQWidget(this);
|
|
setCentralWidget(mainWidget);
|
|
|
|
TQVBoxLayout *vl = new TQVBoxLayout(mainWidget,5);
|
|
|
|
// frame list & code
|
|
TQSplitter *hsplitter = new TQSplitter(Qt::Vertical,mainWidget);
|
|
TQSplitter *vsplitter = new TQSplitter(hsplitter);
|
|
TQFont font(KGlobalSettings::fixedFont());
|
|
|
|
TQWidget *contextContainer = new TQWidget(vsplitter);
|
|
|
|
TQLabel *contextLabel = new TQLabel(i18n("Call stack"),contextContainer);
|
|
TQWidget *contextListContainer = new TQWidget(contextContainer);
|
|
m_contextList = new TQListBox(contextListContainer);
|
|
m_contextList->setMinimumSize(100,200);
|
|
connect(m_contextList,TQT_SIGNAL(highlighted(int)),this,TQT_SLOT(slotShowFrame(int)));
|
|
|
|
TQHBoxLayout *clistLayout = new TQHBoxLayout(contextListContainer);
|
|
clistLayout->addWidget(m_contextList);
|
|
clistLayout->addSpacing(KDialog::spacingHint());
|
|
|
|
TQVBoxLayout *contextLayout = new TQVBoxLayout(contextContainer);
|
|
contextLayout->addWidget(contextLabel);
|
|
contextLayout->addSpacing(KDialog::spacingHint());
|
|
contextLayout->addWidget(contextListContainer);
|
|
|
|
// source selection & display
|
|
TQWidget *sourceSelDisplay = new TQWidget(vsplitter);
|
|
TQVBoxLayout *ssdvl = new TQVBoxLayout(sourceSelDisplay);
|
|
|
|
m_sourceSel = new TQComboBox(toolBar());
|
|
connect(m_sourceSel,TQT_SIGNAL(activated(int)),this,TQT_SLOT(slotSourceSelected(int)));
|
|
|
|
m_sourceDisplay = new SourceDisplay(this,sourceSelDisplay);
|
|
ssdvl->addWidget(m_sourceDisplay);
|
|
connect(m_sourceDisplay,TQT_SIGNAL(lineDoubleClicked(int)),TQT_SLOT(slotToggleBreakpoint(int)));
|
|
|
|
TQValueList<int> vsplitSizes;
|
|
vsplitSizes.insert(vsplitSizes.end(),120);
|
|
vsplitSizes.insert(vsplitSizes.end(),480);
|
|
vsplitter->setSizes(vsplitSizes);
|
|
|
|
// evaluate
|
|
|
|
TQWidget *evalContainer = new TQWidget(hsplitter);
|
|
|
|
TQLabel *evalLabel = new TQLabel(i18n("JavaScript console"),evalContainer);
|
|
m_evalEdit = new EvalMultiLineEdit(evalContainer);
|
|
m_evalEdit->setWordWrap(TQMultiLineEdit::NoWrap);
|
|
m_evalEdit->setFont(font);
|
|
connect(m_evalEdit,TQT_SIGNAL(returnPressed()),TQT_SLOT(slotEval()));
|
|
m_evalDepth = 0;
|
|
|
|
TQVBoxLayout *evalLayout = new TQVBoxLayout(evalContainer);
|
|
evalLayout->addSpacing(KDialog::spacingHint());
|
|
evalLayout->addWidget(evalLabel);
|
|
evalLayout->addSpacing(KDialog::spacingHint());
|
|
evalLayout->addWidget(m_evalEdit);
|
|
|
|
TQValueList<int> hsplitSizes;
|
|
hsplitSizes.insert(hsplitSizes.end(),400);
|
|
hsplitSizes.insert(hsplitSizes.end(),200);
|
|
hsplitter->setSizes(hsplitSizes);
|
|
|
|
vl->addWidget(hsplitter);
|
|
|
|
// actions
|
|
KPopupMenu *debugMenu = new KPopupMenu(this);
|
|
menuBar()->insertItem("&Debug",debugMenu);
|
|
|
|
m_actionCollection = new KActionCollection(this);
|
|
m_actionCollection->setInstance(this);
|
|
|
|
// Venkman use F12, KDevelop F10
|
|
KShortcut scNext = KShortcut(KKeySequence(KKey(Qt::Key_F12)));
|
|
scNext.append(KKeySequence(KKey(Qt::Key_F10)));
|
|
m_nextAction = new KAction(i18n("Next breakpoint","&Next"),"dbgnext",scNext,TQT_TQOBJECT(this),TQT_SLOT(slotNext()),
|
|
m_actionCollection,"next");
|
|
m_stepAction = new KAction(i18n("&Step"),"dbgstep",KShortcut(Qt::Key_F11),TQT_TQOBJECT(this),TQT_SLOT(slotStep()),
|
|
m_actionCollection,"step");
|
|
// Venkman use F5, Kdevelop F9
|
|
KShortcut scCont = KShortcut(KKeySequence(KKey(Qt::Key_F5)));
|
|
scCont.append(KKeySequence(KKey(Qt::Key_F9)));
|
|
m_continueAction = new KAction(i18n("&Continue"),"dbgrun",scCont,TQT_TQOBJECT(this),TQT_SLOT(slotContinue()),
|
|
m_actionCollection,"cont");
|
|
m_stopAction = new KAction(i18n("St&op"),"stop",KShortcut(Qt::Key_F4),TQT_TQOBJECT(this),TQT_SLOT(slotStop()),
|
|
m_actionCollection,"stop");
|
|
m_breakAction = new KAction(i18n("&Break at Next Statement"),"dbgrunto",KShortcut(Qt::Key_F8),TQT_TQOBJECT(this),TQT_SLOT(slotBreakNext()),
|
|
m_actionCollection,"breaknext");
|
|
|
|
|
|
m_nextAction->setToolTip(i18n("Next breakpoint","Next"));
|
|
m_stepAction->setToolTip(i18n("Step"));
|
|
m_continueAction->setToolTip(i18n("Continue"));
|
|
m_stopAction->setToolTip(i18n("Stop"));
|
|
m_breakAction->setToolTip("Break at next Statement");
|
|
|
|
m_nextAction->setEnabled(false);
|
|
m_stepAction->setEnabled(false);
|
|
m_continueAction->setEnabled(false);
|
|
m_stopAction->setEnabled(false);
|
|
m_breakAction->setEnabled(true);
|
|
|
|
m_nextAction->plug(debugMenu);
|
|
m_stepAction->plug(debugMenu);
|
|
m_continueAction->plug(debugMenu);
|
|
// m_stopAction->plug(debugMenu); ### disabled until DebuggerImp::stop() works reliably
|
|
m_breakAction->plug(debugMenu);
|
|
|
|
m_nextAction->plug(toolBar());
|
|
m_stepAction->plug(toolBar());
|
|
m_continueAction->plug(toolBar());
|
|
// m_stopAction->plug(toolBar()); ###
|
|
m_breakAction->plug(toolBar());
|
|
|
|
toolBar()->insertWidget(1,300,m_sourceSel);
|
|
toolBar()->setItemAutoSized(1);
|
|
|
|
updateContextList();
|
|
setMinimumSize(300,200);
|
|
resize(600,450);
|
|
|
|
}
|
|
|
|
KJSDebugWin::~KJSDebugWin()
|
|
{
|
|
free(m_breakpoints);
|
|
free(m_execs);
|
|
}
|
|
|
|
KJSDebugWin *KJSDebugWin::createInstance()
|
|
{
|
|
assert(!kjs_html_debugger);
|
|
kjs_html_debugger = new KJSDebugWin();
|
|
return kjs_html_debugger;
|
|
}
|
|
|
|
void KJSDebugWin::destroyInstance()
|
|
{
|
|
assert(kjs_html_debugger);
|
|
kjs_html_debugger->hide();
|
|
delete kjs_html_debugger;
|
|
}
|
|
|
|
void KJSDebugWin::slotNext()
|
|
{
|
|
m_mode = Next;
|
|
leaveSession();
|
|
}
|
|
|
|
void KJSDebugWin::slotStep()
|
|
{
|
|
m_mode = Step;
|
|
leaveSession();
|
|
}
|
|
|
|
void KJSDebugWin::slotContinue()
|
|
{
|
|
m_mode = Continue;
|
|
leaveSession();
|
|
}
|
|
|
|
void KJSDebugWin::slotStop()
|
|
{
|
|
m_mode = Stop;
|
|
while (!m_execStates.isEmpty())
|
|
leaveSession();
|
|
}
|
|
|
|
void KJSDebugWin::slotBreakNext()
|
|
{
|
|
m_mode = Step;
|
|
}
|
|
|
|
void KJSDebugWin::slotToggleBreakpoint(int lineno)
|
|
{
|
|
if (m_sourceSel->currentItem() < 0)
|
|
return;
|
|
|
|
SourceFile *sourceFile = m_sourceSelFiles.at(m_sourceSel->currentItem());
|
|
|
|
// Find the source fragment containing the selected line (if any)
|
|
int sourceId = -1;
|
|
int highestBaseLine = -1;
|
|
TQMap<int,SourceFragment*>::Iterator it;
|
|
|
|
for (it = m_sourceFragments.begin(); it != m_sourceFragments.end(); ++it) {
|
|
SourceFragment *sourceFragment = it.data();
|
|
if (sourceFragment &&
|
|
sourceFragment->sourceFile == sourceFile &&
|
|
sourceFragment->baseLine <= lineno &&
|
|
sourceFragment->baseLine > highestBaseLine) {
|
|
|
|
sourceId = sourceFragment->sourceId;
|
|
highestBaseLine = sourceFragment->baseLine;
|
|
}
|
|
}
|
|
|
|
if (sourceId < 0)
|
|
return;
|
|
|
|
// Update the source code display with the appropriate icon
|
|
int fragmentLineno = lineno-highestBaseLine+1;
|
|
if (!setBreakpoint(sourceId,fragmentLineno)) // was already set
|
|
deleteBreakpoint(sourceId,fragmentLineno);
|
|
|
|
m_sourceDisplay->updateContents();
|
|
}
|
|
|
|
void KJSDebugWin::slotShowFrame(int frameno)
|
|
{
|
|
if (frameno < 0 || frameno >= m_execsCount)
|
|
return;
|
|
|
|
Context ctx = m_execs[frameno]->context();
|
|
setSourceLine(ctx.sourceId(),ctx.curStmtFirstLine());
|
|
}
|
|
|
|
void KJSDebugWin::slotSourceSelected(int sourceSelIndex)
|
|
{
|
|
// A source file has been selected from the drop-down list - display the file
|
|
if (sourceSelIndex < 0 || sourceSelIndex >= (int)m_sourceSel->count())
|
|
return;
|
|
SourceFile *sourceFile = m_sourceSelFiles.at(sourceSelIndex);
|
|
displaySourceFile(sourceFile,true);
|
|
|
|
// If the currently selected context is in the current source file, then hilight
|
|
// the line it's on.
|
|
if (m_contextList->currentItem() >= 0) {
|
|
Context ctx = m_execs[m_contextList->currentItem()]->context();
|
|
if (m_sourceFragments[ctx.sourceId()]->sourceFile == m_sourceSelFiles.at(sourceSelIndex))
|
|
setSourceLine(ctx.sourceId(),ctx.curStmtFirstLine());
|
|
}
|
|
}
|
|
|
|
void KJSDebugWin::slotEval()
|
|
{
|
|
// Work out which execution state to use. If we're currently in a debugging session,
|
|
// use the current context - otherwise, use the global execution state from the interpreter
|
|
// corresponding to the currently displayed source file.
|
|
ExecState *exec;
|
|
Object thisobj;
|
|
if (m_execStates.isEmpty()) {
|
|
if (m_sourceSel->currentItem() < 0)
|
|
return;
|
|
SourceFile *sourceFile = m_sourceSelFiles.at(m_sourceSel->currentItem());
|
|
if (!sourceFile->interpreter)
|
|
return;
|
|
exec = sourceFile->interpreter->globalExec();
|
|
thisobj = exec->interpreter()->globalObject();
|
|
}
|
|
else {
|
|
exec = m_execStates.top();
|
|
thisobj = exec->context().thisValue();
|
|
}
|
|
|
|
// Evaluate the js code from m_evalEdit
|
|
UString code(m_evalEdit->code());
|
|
TQString msg;
|
|
|
|
KJSCPUGuard guard;
|
|
guard.start();
|
|
|
|
Interpreter *interp = exec->interpreter();
|
|
|
|
Object obj = Object::dynamicCast(interp->globalObject().get(exec, "eval"));
|
|
List args;
|
|
args.append(String(code));
|
|
|
|
m_evalDepth++;
|
|
Value retval = obj.call(exec, thisobj, args);
|
|
m_evalDepth--;
|
|
guard.stop();
|
|
|
|
// Print the return value or exception message to the console
|
|
if (exec->hadException()) {
|
|
Value exc = exec->exception();
|
|
exec->clearException();
|
|
msg = "Exception: " + exc.toString(interp->globalExec()).qstring();
|
|
}
|
|
else {
|
|
msg = retval.toString(interp->globalExec()).qstring();
|
|
}
|
|
|
|
m_evalEdit->insert(msg+"\n");
|
|
updateContextList();
|
|
}
|
|
|
|
void KJSDebugWin::closeEvent(TQCloseEvent *e)
|
|
{
|
|
while (!m_execStates.isEmpty()) // ### not sure if this will work
|
|
leaveSession();
|
|
return TQWidget::closeEvent(e);
|
|
}
|
|
|
|
bool KJSDebugWin::eventFilter(TQObject *o, TQEvent *e)
|
|
{
|
|
switch (e->type()) {
|
|
case TQEvent::MouseButtonPress:
|
|
case TQEvent::MouseButtonRelease:
|
|
case TQEvent::MouseButtonDblClick:
|
|
case TQEvent::MouseMove:
|
|
case TQEvent::KeyPress:
|
|
case TQEvent::KeyRelease:
|
|
case TQEvent::Destroy:
|
|
case TQEvent::Close:
|
|
case TQEvent::Quit:
|
|
while (o->parent())
|
|
o = TQT_TQOBJECT(o->parent());
|
|
if (TQT_BASE_OBJECT(o) == TQT_BASE_OBJECT(this))
|
|
return TQWidget::eventFilter(o,e);
|
|
else
|
|
return true;
|
|
break;
|
|
default:
|
|
return TQWidget::eventFilter(o,e);
|
|
}
|
|
}
|
|
|
|
void KJSDebugWin::disableOtherWindows()
|
|
{
|
|
TQWidgetList *widgets = TQApplication::tqallWidgets();
|
|
TQWidgetListIt it(*widgets);
|
|
for (; it.current(); ++it)
|
|
it.current()->installEventFilter(this);
|
|
}
|
|
|
|
void KJSDebugWin::enableOtherWindows()
|
|
{
|
|
TQWidgetList *widgets = TQApplication::tqallWidgets();
|
|
TQWidgetListIt it(*widgets);
|
|
for (; it.current(); ++it)
|
|
it.current()->removeEventFilter(this);
|
|
}
|
|
|
|
bool KJSDebugWin::sourceParsed(KJS::ExecState *exec, int sourceId,
|
|
const KJS::UString &source, int errorLine)
|
|
{
|
|
// Work out which source file this fragment is in
|
|
SourceFile *sourceFile = 0;
|
|
if (!m_nextSourceUrl.isEmpty())
|
|
sourceFile = getSourceFile(exec->interpreter(),m_nextSourceUrl);
|
|
|
|
int index;
|
|
if (!sourceFile) {
|
|
index = m_sourceSel->count();
|
|
if (!m_nextSourceUrl.isEmpty()) {
|
|
|
|
TQString code = source.qstring();
|
|
KParts::ReadOnlyPart *part = static_cast<ScriptInterpreter*>(exec->interpreter())->part();
|
|
if (m_nextSourceUrl == part->url().url()) {
|
|
// Only store the code here if it's not from the part's html page... in that
|
|
// case we can get it from KHTMLPageCache
|
|
code = TQString::null;
|
|
}
|
|
|
|
sourceFile = new SourceFile(m_nextSourceUrl,code,exec->interpreter());
|
|
setSourceFile(exec->interpreter(),m_nextSourceUrl,sourceFile);
|
|
m_sourceSelFiles.append(sourceFile);
|
|
m_sourceSel->insertItem(m_nextSourceUrl);
|
|
}
|
|
else {
|
|
// Sourced passed from somewhere else (possibly an eval call)... we don't know the url,
|
|
// but we still know the interpreter
|
|
sourceFile = new SourceFile("(unknown)",source.qstring(),exec->interpreter());
|
|
m_sourceSelFiles.append(sourceFile);
|
|
m_sourceSel->insertItem(TQString::number(index) += "-???");
|
|
}
|
|
}
|
|
else {
|
|
// Ensure that each source file to be displayed is associated with
|
|
// an appropriate interpreter
|
|
if (!sourceFile->interpreter)
|
|
sourceFile->interpreter = exec->interpreter();
|
|
for (index = 0; index < m_sourceSel->count(); index++) {
|
|
if (m_sourceSelFiles.at(index) == sourceFile)
|
|
break;
|
|
}
|
|
assert(index < m_sourceSel->count());
|
|
}
|
|
|
|
SourceFragment *sf = new SourceFragment(sourceId,m_nextSourceBaseLine,errorLine,sourceFile);
|
|
m_sourceFragments[sourceId] = sf;
|
|
|
|
if (m_sourceSel->currentItem() < 0)
|
|
m_sourceSel->setCurrentItem(index);
|
|
|
|
if (m_sourceSel->currentItem() == index) {
|
|
displaySourceFile(sourceFile,true);
|
|
}
|
|
|
|
m_nextSourceBaseLine = 1;
|
|
m_nextSourceUrl = "";
|
|
|
|
return (m_mode != Stop);
|
|
}
|
|
|
|
bool KJSDebugWin::sourceUnused(KJS::ExecState *exec, int sourceId)
|
|
{
|
|
// Verify that there aren't any contexts on the stack using the given sourceId
|
|
// This should never be the case because this function is only called when
|
|
// the interpreter has deleted all Node objects for the source.
|
|
for (int e = 0; e < m_execsCount; e++)
|
|
assert(m_execs[e]->context().sourceId() != sourceId);
|
|
|
|
// Now remove the fragment (and the SourceFile, if it was the last fragment in that file)
|
|
SourceFragment *fragment = m_sourceFragments[sourceId];
|
|
if (fragment) {
|
|
m_sourceFragments.erase(sourceId);
|
|
|
|
SourceFile *sourceFile = fragment->sourceFile;
|
|
if (sourceFile->hasOneRef()) {
|
|
for (int i = 0; i < m_sourceSel->count(); i++) {
|
|
if (m_sourceSelFiles.at(i) == sourceFile) {
|
|
m_sourceSel->removeItem(i);
|
|
m_sourceSelFiles.remove(i);
|
|
break;
|
|
}
|
|
}
|
|
removeSourceFile(exec->interpreter(),sourceFile->url);
|
|
}
|
|
delete fragment;
|
|
}
|
|
|
|
return (m_mode != Stop);
|
|
}
|
|
|
|
bool KJSDebugWin::exception(ExecState *exec, const Value &value, bool inTryCatch)
|
|
{
|
|
assert(value.isValid());
|
|
|
|
// Ignore exceptions that will be caught by the script
|
|
if (inTryCatch)
|
|
return true;
|
|
|
|
KParts::ReadOnlyPart *part = static_cast<ScriptInterpreter*>(exec->interpreter())->part();
|
|
KHTMLPart *khtmlpart = ::tqqt_cast<KHTMLPart*>(part);
|
|
if (khtmlpart && !khtmlpart->settings()->isJavaScriptErrorReportingEnabled())
|
|
return true;
|
|
|
|
TQWidget *dlgParent = (m_evalDepth == 0) ? (TQWidget*)part->widget() : (TQWidget*)this;
|
|
|
|
TQString exceptionMsg = value.toString(exec).qstring();
|
|
|
|
// Syntax errors are a special case. For these we want to display the url & lineno,
|
|
// which isn't included in the exception messeage. So we work it out from the values
|
|
// passed to sourceParsed()
|
|
Object valueObj = Object::dynamicCast(value);
|
|
Object syntaxError = exec->interpreter()->builtinSyntaxError();
|
|
if (valueObj.isValid() && valueObj.get(exec,"constructor").imp() == syntaxError.imp()) {
|
|
Value sidValue = valueObj.get(exec,"sid");
|
|
if (sidValue.isA(NumberType)) { // sid is not set for Function() constructor
|
|
int sourceId = (int)sidValue.toNumber(exec);
|
|
assert(m_sourceFragments[sourceId]);
|
|
exceptionMsg = i18n("Parse error at %1 line %2")
|
|
.arg(m_sourceFragments[sourceId]->sourceFile->url)
|
|
.arg(m_sourceFragments[sourceId]->baseLine+m_sourceFragments[sourceId]->errorLine-1);
|
|
}
|
|
}
|
|
|
|
bool dontShowAgain = false;
|
|
if (m_execsCount == 0) {
|
|
// An exception occurred and we're not currently executing any code... this can
|
|
// happen in some cases e.g. a parse error, or native code accessing funcitons like
|
|
// Object::put()
|
|
TQString msg = i18n("An error occurred while attempting to run a script on this page.\n\n%1")
|
|
.arg(exceptionMsg);
|
|
KJSErrorDialog dlg(dlgParent,msg,false);
|
|
dlg.exec();
|
|
dontShowAgain = dlg.dontShowAgain();
|
|
}
|
|
else {
|
|
Context ctx = m_execs[m_execsCount-1]->context();
|
|
SourceFragment *sourceFragment = m_sourceFragments[ctx.sourceId()];
|
|
TQString msg = i18n("An error occurred while attempting to run a script on this page.\n\n%1 line %2:\n%3")
|
|
.arg(KStringHandler::rsqueeze( sourceFragment->sourceFile->url,80),
|
|
TQString::number( sourceFragment->baseLine+ctx.curStmtFirstLine()-1),
|
|
exceptionMsg);
|
|
|
|
KJSErrorDialog dlg(dlgParent,msg,true);
|
|
dlg.exec();
|
|
dontShowAgain = dlg.dontShowAgain();
|
|
|
|
if (dlg.debugSelected()) {
|
|
m_mode = Next;
|
|
m_steppingDepth = m_execsCount-1;
|
|
enterSession(exec);
|
|
}
|
|
}
|
|
|
|
if (dontShowAgain) {
|
|
KConfig *config = kapp->config();
|
|
KConfigGroupSaver saver(config,TQString::tqfromLatin1("Java/JavaScript Settings"));
|
|
config->writeEntry("ReportJavaScriptErrors",TQVariant(false,0));
|
|
config->sync();
|
|
TQByteArray data;
|
|
kapp->dcopClient()->send( "konqueror*", "KonquerorIface", "reparseConfiguration()", data );
|
|
}
|
|
|
|
return (m_mode != Stop);
|
|
}
|
|
|
|
bool KJSDebugWin::atStatement(KJS::ExecState *exec)
|
|
{
|
|
assert(m_execsCount > 0);
|
|
assert(m_execs[m_execsCount-1] == exec);
|
|
checkBreak(exec);
|
|
return (m_mode != Stop);
|
|
}
|
|
|
|
bool KJSDebugWin::enterContext(ExecState *exec)
|
|
{
|
|
if (m_execsCount >= m_execsAlloc) {
|
|
m_execsAlloc += 10;
|
|
m_execs = (ExecState**)realloc(m_execs,m_execsAlloc*sizeof(ExecState*));
|
|
}
|
|
m_execs[m_execsCount++] = exec;
|
|
|
|
if (m_mode == Step)
|
|
m_steppingDepth = m_execsCount-1;
|
|
|
|
checkBreak(exec);
|
|
return (m_mode != Stop);
|
|
}
|
|
|
|
bool KJSDebugWin::exitContext(ExecState *exec, const Completion &/*completion*/)
|
|
{
|
|
assert(m_execsCount > 0);
|
|
assert(m_execs[m_execsCount-1] == exec);
|
|
|
|
checkBreak(exec);
|
|
|
|
m_execsCount--;
|
|
if (m_steppingDepth > m_execsCount-1)
|
|
m_steppingDepth = m_execsCount-1;
|
|
if (m_execsCount == 0)
|
|
updateContextList();
|
|
|
|
return (m_mode != Stop);
|
|
}
|
|
|
|
void KJSDebugWin::displaySourceFile(SourceFile *sourceFile, bool forceRefresh)
|
|
{
|
|
if (m_curSourceFile == sourceFile && !forceRefresh)
|
|
return;
|
|
sourceFile->ref();
|
|
m_sourceDisplay->setSource(sourceFile);
|
|
if (m_curSourceFile)
|
|
m_curSourceFile->deref();
|
|
m_curSourceFile = sourceFile;
|
|
}
|
|
|
|
void KJSDebugWin::setSourceLine(int sourceId, int lineno)
|
|
{
|
|
SourceFragment *source = m_sourceFragments[sourceId];
|
|
if (!source)
|
|
return;
|
|
|
|
SourceFile *sourceFile = source->sourceFile;
|
|
if (m_curSourceFile != source->sourceFile) {
|
|
for (int i = 0; i < m_sourceSel->count(); i++)
|
|
if (m_sourceSelFiles.at(i) == sourceFile)
|
|
m_sourceSel->setCurrentItem(i);
|
|
displaySourceFile(sourceFile,false);
|
|
}
|
|
m_sourceDisplay->setCurrentLine(source->baseLine+lineno-2);
|
|
}
|
|
|
|
void KJSDebugWin::setNextSourceInfo(TQString url, int baseLine)
|
|
{
|
|
m_nextSourceUrl = url;
|
|
m_nextSourceBaseLine = baseLine;
|
|
}
|
|
|
|
void KJSDebugWin::sourceChanged(Interpreter *interpreter, TQString url)
|
|
{
|
|
SourceFile *sourceFile = getSourceFile(interpreter,url);
|
|
if (sourceFile && m_curSourceFile == sourceFile)
|
|
displaySourceFile(sourceFile,true);
|
|
}
|
|
|
|
void KJSDebugWin::clearInterpreter(Interpreter *interpreter)
|
|
{
|
|
TQMap<int,SourceFragment*>::Iterator it;
|
|
|
|
for (it = m_sourceFragments.begin(); it != m_sourceFragments.end(); ++it)
|
|
if (it.data() && it.data()->sourceFile->interpreter == interpreter)
|
|
it.data()->sourceFile->interpreter = 0;
|
|
}
|
|
|
|
SourceFile *KJSDebugWin::getSourceFile(Interpreter *interpreter, TQString url)
|
|
{
|
|
TQString key = TQString("%1|%2").arg((long)interpreter).arg(url);
|
|
return m_sourceFiles[key];
|
|
}
|
|
|
|
void KJSDebugWin::setSourceFile(Interpreter *interpreter, TQString url, SourceFile *sourceFile)
|
|
{
|
|
TQString key = TQString("%1|%2").arg((long)interpreter).arg(url);
|
|
sourceFile->ref();
|
|
if (SourceFile* oldFile = m_sourceFiles[key])
|
|
oldFile->deref();
|
|
m_sourceFiles[key] = sourceFile;
|
|
}
|
|
|
|
void KJSDebugWin::removeSourceFile(Interpreter *interpreter, TQString url)
|
|
{
|
|
TQString key = TQString("%1|%2").arg((long)interpreter).arg(url);
|
|
if (SourceFile* oldFile = m_sourceFiles[key])
|
|
oldFile->deref();
|
|
m_sourceFiles.remove(key);
|
|
}
|
|
|
|
void KJSDebugWin::checkBreak(ExecState *exec)
|
|
{
|
|
if (m_breakpointCount > 0) {
|
|
Context ctx = m_execs[m_execsCount-1]->context();
|
|
if (haveBreakpoint(ctx.sourceId(),ctx.curStmtFirstLine(),ctx.curStmtLastLine())) {
|
|
m_mode = Next;
|
|
m_steppingDepth = m_execsCount-1;
|
|
}
|
|
}
|
|
|
|
if ((m_mode == Step || m_mode == Next) && m_steppingDepth == m_execsCount-1)
|
|
enterSession(exec);
|
|
}
|
|
|
|
void KJSDebugWin::enterSession(ExecState *exec)
|
|
{
|
|
// This "enters" a new debugging session, i.e. enables usage of the debugging window
|
|
// It re-enters the qt event loop here, allowing execution of other parts of the
|
|
// program to continue while the script is stopped. We have to be a bit careful here,
|
|
// i.e. make sure the user can't quit the app, and disable other event handlers which
|
|
// could interfere with the debugging session.
|
|
if (!isVisible())
|
|
show();
|
|
|
|
m_mode = Continue;
|
|
|
|
if (m_execStates.isEmpty()) {
|
|
disableOtherWindows();
|
|
m_nextAction->setEnabled(true);
|
|
m_stepAction->setEnabled(true);
|
|
m_continueAction->setEnabled(true);
|
|
m_stopAction->setEnabled(true);
|
|
m_breakAction->setEnabled(false);
|
|
}
|
|
m_execStates.push(exec);
|
|
|
|
updateContextList();
|
|
|
|
tqApp->enter_loop(); // won't return until leaveSession() is called
|
|
}
|
|
|
|
void KJSDebugWin::leaveSession()
|
|
{
|
|
// Disables debugging for this window and returns to execute the rest of the script
|
|
// (or aborts execution, if the user pressed stop). When this returns, the program
|
|
// will exit the qt event loop, i.e. return to whatever processing was being done
|
|
// before the debugger was stopped.
|
|
assert(!m_execStates.isEmpty());
|
|
|
|
m_execStates.pop();
|
|
|
|
if (m_execStates.isEmpty()) {
|
|
m_nextAction->setEnabled(false);
|
|
m_stepAction->setEnabled(false);
|
|
m_continueAction->setEnabled(false);
|
|
m_stopAction->setEnabled(false);
|
|
m_breakAction->setEnabled(true);
|
|
m_sourceDisplay->setCurrentLine(-1);
|
|
enableOtherWindows();
|
|
}
|
|
|
|
tqApp->exit_loop();
|
|
}
|
|
|
|
void KJSDebugWin::updateContextList()
|
|
{
|
|
disconnect(m_contextList,TQT_SIGNAL(highlighted(int)),this,TQT_SLOT(slotShowFrame(int)));
|
|
|
|
m_contextList->clear();
|
|
for (int i = 0; i < m_execsCount; i++)
|
|
m_contextList->insertItem(contextStr(m_execs[i]->context()));
|
|
|
|
if (m_execsCount > 0) {
|
|
m_contextList->setSelected(m_execsCount-1, true);
|
|
Context ctx = m_execs[m_execsCount-1]->context();
|
|
setSourceLine(ctx.sourceId(),ctx.curStmtFirstLine());
|
|
}
|
|
|
|
connect(m_contextList,TQT_SIGNAL(highlighted(int)),this,TQT_SLOT(slotShowFrame(int)));
|
|
}
|
|
|
|
TQString KJSDebugWin::contextStr(const Context &ctx)
|
|
{
|
|
TQString str = "";
|
|
SourceFragment *sourceFragment = m_sourceFragments[ctx.sourceId()];
|
|
TQString url = sourceFragment->sourceFile->url;
|
|
int fileLineno = sourceFragment->baseLine+ctx.curStmtFirstLine()-1;
|
|
|
|
switch (ctx.codeType()) {
|
|
case GlobalCode:
|
|
str = TQString("Global code at %1:%2").arg(url).arg(fileLineno);
|
|
break;
|
|
case EvalCode:
|
|
str = TQString("Eval code at %1:%2").arg(url).arg(fileLineno);
|
|
break;
|
|
case FunctionCode:
|
|
if (!ctx.functionName().isNull())
|
|
str = TQString("%1() at %2:%3").arg(ctx.functionName().qstring()).arg(url).arg(fileLineno);
|
|
else
|
|
str = TQString("Anonymous function at %1:%2").arg(url).arg(fileLineno);
|
|
break;
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
bool KJSDebugWin::setBreakpoint(int sourceId, int lineno)
|
|
{
|
|
if (haveBreakpoint(sourceId,lineno,lineno))
|
|
return false;
|
|
|
|
m_breakpointCount++;
|
|
m_breakpoints = static_cast<Breakpoint*>(realloc(m_breakpoints,
|
|
m_breakpointCount*sizeof(Breakpoint)));
|
|
m_breakpoints[m_breakpointCount-1].sourceId = sourceId;
|
|
m_breakpoints[m_breakpointCount-1].lineno = lineno;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool KJSDebugWin::deleteBreakpoint(int sourceId, int lineno)
|
|
{
|
|
for (int i = 0; i < m_breakpointCount; i++) {
|
|
if (m_breakpoints[i].sourceId == sourceId && m_breakpoints[i].lineno == lineno) {
|
|
|
|
memmove(m_breakpoints+i,m_breakpoints+i+1,(m_breakpointCount-i-1)*sizeof(Breakpoint));
|
|
m_breakpointCount--;
|
|
m_breakpoints = static_cast<Breakpoint*>(realloc(m_breakpoints,
|
|
m_breakpointCount*sizeof(Breakpoint)));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KJSDebugWin::haveBreakpoint(SourceFile *sourceFile, int line0, int line1)
|
|
{
|
|
for (int i = 0; i < m_breakpointCount; i++) {
|
|
int sourceId = m_breakpoints[i].sourceId;
|
|
int lineno = m_breakpoints[i].lineno;
|
|
if (m_sourceFragments.tqcontains(sourceId) &&
|
|
m_sourceFragments[sourceId]->sourceFile == sourceFile) {
|
|
int absLineno = m_sourceFragments[sourceId]->baseLine+lineno-1;
|
|
if (absLineno >= line0 && absLineno <= line1)
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#include "kjs_debugwin.moc"
|
|
|
|
#endif // KJS_DEBUGGER
|