summaryrefslogtreecommitdiffstats
path: root/tdehtml/ecma/kjs_proxy.cpp
blob: 204fb46e2cfa3f3e27651ef4177b6e4cd683ba3e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
/*
 *  This file is part of the KDE libraries
 *  Copyright (C) 1999-2001 Harri Porten (porten@kde.org)
 *  Copyright (C) 2001,2003 Peter Kelly (pmk@post.com)
 *  Copyright (C) 2001-2003 David Faure (faure@kde.org)
 *
 *  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 <config.h>

#if defined(HAVE_VALGRIND_MEMCHECK_H) && !defined(NDEBUG)

#include <valgrind/memcheck.h>
#define VALGRIND_SUPPORT

#endif


#include "kjs_proxy.h"

#include "kjs_window.h"
#include "kjs_events.h"
#include "kjs_debugwin.h"
#include "xml/dom_nodeimpl.h"
#include "tdehtmlpart_p.h"
#include <tdehtml_part.h>
#include <tdeprotocolmanager.h>
#include <kdebug.h>
#include <tdemessagebox.h>
#include <tdelocale.h>
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>
#include <assert.h>
#include <kjs/function.h>

using namespace KJS;

extern "C" {
  KJSProxy *kjs_html_init(tdehtml::ChildFrame *childframe);
}

namespace KJS {

class KJSProxyImpl : public KJSProxy {
public:
  KJSProxyImpl(tdehtml::ChildFrame *frame);
  virtual ~KJSProxyImpl();
  virtual TQVariant evaluate(TQString filename, int baseLine, const TQString &, const DOM::Node &n,
			    Completion *completion = 0);
  virtual void clear();
  virtual DOM::EventListener *createHTMLEventHandler(TQString sourceUrl, TQString name, TQString code, DOM::NodeImpl *node);
  virtual void finishedWithEvent(const DOM::Event &event);
  virtual KJS::Interpreter *interpreter();

  virtual void setDebugEnabled(bool enabled);
  virtual void showDebugWindow(bool show=true);
  virtual bool paused() const;
  virtual void dataReceived();

  void initScript();
  void applyUserAgent();

private:
  KJS::ScriptInterpreter* m_script;
  bool m_debugEnabled;
#ifndef NDEBUG
  static int s_count;
#endif
};

} // namespace KJS

#ifndef NDEBUG
int KJSProxyImpl::s_count = 0;
#endif

KJSProxyImpl::KJSProxyImpl(tdehtml::ChildFrame *frame)
{
  m_script = 0;
  m_frame = frame;
  m_debugEnabled = false;
#ifndef NDEBUG
  s_count++;
#endif
}

KJSProxyImpl::~KJSProxyImpl()
{
  if ( m_script ) {
    //kdDebug() << "KJSProxyImpl::~KJSProxyImpl clearing global object " << m_script->globalObject().imp() << endl;
    // This allows to delete the global-object properties, like all the protos
    static_cast<ObjectImp*>(m_script->globalObject().imp())->deleteAllProperties( m_script->globalExec() );
    //kdDebug() << "KJSProxyImpl::~KJSProxyImpl garbage collecting" << endl;
    while (KJS::Interpreter::collect())
        ;
    //kdDebug() << "KJSProxyImpl::~KJSProxyImpl deleting interpreter " << m_script << endl;
    delete m_script;
    //kdDebug() << "KJSProxyImpl::~KJSProxyImpl garbage collecting again" << endl;
    // Garbage collect - as many times as necessary
    // (we could delete an object which was holding another object, so
    // the deref() will happen too late for deleting the impl of the 2nd object).
    while (KJS::Interpreter::collect())
        ;
  }

#ifndef NDEBUG
  s_count--;
  // If it was the last interpreter, we should have nothing left
#ifdef KJS_DEBUG_MEM
  if ( s_count == 0 )
    Interpreter::finalCheck();
#endif
#endif
}

TQVariant KJSProxyImpl::evaluate(TQString filename, int baseLine,
                                const TQString&str, const DOM::Node &n, Completion *completion) {
  // evaluate code. Returns the JS return value or an invalid QVariant
  // if there was none, an error occurred or the type couldn't be converted.

  initScript();
  // inlineCode is true for <a href="javascript:doSomething()">
  // and false for <script>doSomething()</script>. Check if it has the
  // expected value in all cases.
  // See smart window.open policy for where this is used.
  bool inlineCode = filename.isNull();
  //kdDebug(6070) << "KJSProxyImpl::evaluate inlineCode=" << inlineCode << endl;

#ifdef KJS_DEBUGGER
  if (inlineCode)
    filename = "(unknown file)";
  if (KJSDebugWin::debugWindow()) {
    KJSDebugWin::debugWindow()->attach(m_script);
    KJSDebugWin::debugWindow()->setNextSourceInfo(filename,baseLine);
  //    KJSDebugWin::debugWindow()->setMode(KJSDebugWin::Step);
  }
#else
  Q_UNUSED(baseLine);
#endif

  m_script->setInlineCode(inlineCode);
  Window* window = Window::retrieveWindow( m_frame->m_part );
  KJS::Value thisNode = n.isNull() ? Window::retrieve( m_frame->m_part ) : getDOMNode(m_script->globalExec(),n);

  UString code( str );

  KJSCPUGuard guard;
  guard.start();
  Completion comp = m_script->evaluate(code, thisNode);
  guard.stop();

  bool success = ( comp.complType() == Normal ) || ( comp.complType() == ReturnValue );

  if (completion)
    *completion = comp;

#ifdef KJS_DEBUGGER
    //    KJSDebugWin::debugWindow()->setCode(TQString::null);
#endif

  window->afterScriptExecution();

  // let's try to convert the return value
  if (success && comp.value().isValid())
    return ValueToVariant( m_script->globalExec(), comp.value());
  else
  {
    if ( comp.complType() == Throw )
    {
        UString msg = comp.value().toString(m_script->globalExec());
        kdDebug(6070) << "WARNING: Script threw exception: " << msg.qstring() << endl;
    }
    return TQVariant();
  }
}

// Implementation of the debug() function
class TestFunctionImp : public ObjectImp {
public:
  TestFunctionImp() : ObjectImp() {}
  virtual bool implementsCall() const { return true; }
  virtual Value call(ExecState *exec, Object &thisObj, const List &args);
};

Value TestFunctionImp::call(ExecState *exec, Object &/*thisObj*/, const List &args)
{
  fprintf(stderr,"--> %s\n",args[0].toString(exec).ascii());
  return Undefined();
}

void KJSProxyImpl::clear() {
  // clear resources allocated by the interpreter, and make it ready to be used by another page
  // We have to keep it, so that the Window object for the part remains the same.
  // (we used to delete and re-create it, previously)
  if (m_script) {
#ifdef KJS_DEBUGGER
    // ###
    KJSDebugWin *debugWin = KJSDebugWin::debugWindow();
    if (debugWin) {
      if (debugWin->getExecState() &&
          debugWin->getExecState()->interpreter() == m_script)
        debugWin->slotStop();
      debugWin->clearInterpreter(m_script);
    }
#endif
    m_script->clear();

    Window *win = static_cast<Window *>(m_script->globalObject().imp());
    if (win) {
      win->clear( m_script->globalExec() );
      // re-add "debug", clear() removed it
      m_script->globalObject().put(m_script->globalExec(),
                                   "debug", Value(new TestFunctionImp()), Internal);
      if ( win->part() )
        applyUserAgent();
    }

    // Really delete everything that can be, so that the DOM nodes get deref'ed
    //kdDebug() << k_funcinfo << "all done -> collecting" << endl;
    while (KJS::Interpreter::collect())
        ;
  }
}

DOM::EventListener *KJSProxyImpl::createHTMLEventHandler(TQString sourceUrl, TQString name, TQString code, DOM::NodeImpl *node)
{
  initScript();

#ifdef KJS_DEBUGGER
  if (KJSDebugWin::debugWindow()) {
    KJSDebugWin::debugWindow()->attach(m_script);
    KJSDebugWin::debugWindow()->setNextSourceInfo(sourceUrl,m_handlerLineno);
  }
#else
  Q_UNUSED(sourceUrl);
#endif

  return KJS::Window::retrieveWindow(m_frame->m_part)->getJSLazyEventListener(code,name,node);
}

void KJSProxyImpl::finishedWithEvent(const DOM::Event &event)
{
  // This is called when the DOM implementation has finished with a particular event. This
  // is the case in sitations where an event has been created just for temporary usage,
  // e.g. an image load or mouse move. Once the event has been dispatched, it is forgotten
  // by the DOM implementation and so does not need to be cached still by the interpreter
  ScriptInterpreter::forgetDOMObject(event.handle());
}

KJS::Interpreter *KJSProxyImpl::interpreter()
{
  if (!m_script)
    initScript();
  return m_script;
}

void KJSProxyImpl::setDebugEnabled(bool enabled)
{
#ifdef KJS_DEBUGGER
  m_debugEnabled = enabled;
  //if (m_script)
  //    m_script->setDebuggingEnabled(enabled);
  // NOTE: this is consistent across all KJSProxyImpl instances, as we only
  // ever have 1 debug window
  if (!enabled && KJSDebugWin::debugWindow()) {
    KJSDebugWin::destroyInstance();
  }
  else if (enabled && !KJSDebugWin::debugWindow()) {
    KJSDebugWin::createInstance();
    initScript();
    KJSDebugWin::debugWindow()->attach(m_script);
  }
#else
  Q_UNUSED(enabled);
#endif
}

void KJSProxyImpl::showDebugWindow(bool /*show*/)
{
#ifdef KJS_DEBUGGER
  if (KJSDebugWin::debugWindow())
    KJSDebugWin::debugWindow()->show();
#else
  //Q_UNUSED(show);
#endif
}

bool KJSProxyImpl::paused() const
{
#ifdef KJS_DEBUGGER
  if (KJSDebugWin::debugWindow())
    return KJSDebugWin::debugWindow()->inSession();
#endif
  return false;
}

void KJSProxyImpl::dataReceived()
{
#ifdef KJS_DEBUGGER
  if (KJSDebugWin::debugWindow() && m_frame->m_part)
    KJSDebugWin::debugWindow()->sourceChanged(m_script,m_frame->m_part->url().url());
#endif
}

void KJSProxyImpl::initScript()
{
  if (m_script)
    return;

  // Build the global object - which is a Window instance
  Object globalObject( new Window(m_frame) );

  // Create a KJS interpreter for this part
  m_script = new KJS::ScriptInterpreter(globalObject, m_frame);
  static_cast<ObjectImp*>(globalObject.imp())->setPrototype(m_script->builtinObjectPrototype());

#ifdef KJS_DEBUGGER
  //m_script->setDebuggingEnabled(m_debugEnabled);
#endif
  //m_script->enableDebug();
  globalObject.put(m_script->globalExec(),
		   "debug", Value(new TestFunctionImp()), Internal);
  applyUserAgent();
}

void KJSProxyImpl::applyUserAgent()
{
  assert( m_script );
  TQString host = m_frame->m_part->url().isLocalFile() ? "localhost" : m_frame->m_part->url().host();
  TQString userAgent = KProtocolManager::userAgentForHost(host);
  if (userAgent.find(TQString::fromLatin1("Microsoft")) >= 0 ||
      userAgent.find(TQString::fromLatin1("MSIE")) >= 0)
  {
    m_script->setCompatMode(Interpreter::IECompat);
#ifdef KJS_VERBOSE
    kdDebug() << "Setting IE compat mode" << endl;
#endif
  }
  else
    // If we find "Mozilla" but not "(compatible, ...)" we are a real Netscape
    if (userAgent.find(TQString::fromLatin1("Mozilla")) >= 0 &&
        userAgent.find(TQString::fromLatin1("compatible")) == -1 &&
        userAgent.find(TQString::fromLatin1("KHTML")) == -1)
    {
      m_script->setCompatMode(Interpreter::NetscapeCompat);
#ifdef KJS_VERBOSE
      kdDebug() << "Setting NS compat mode" << endl;
#endif
    }
}

// Helper method, so that all classes which need jScript() don't need to be added
// as friend to TDEHTMLPart
KJSProxy * KJSProxy::proxy( TDEHTMLPart *part )
{
    return part->jScript();
}

// initialize HTML module
KJSProxy *kjs_html_init(tdehtml::ChildFrame *childframe)
{
  return new KJSProxyImpl(childframe);
}

void KJSCPUGuard::start(unsigned int ms, unsigned int i_ms)
{
#ifdef VALGRIND_SUPPORT
    if (RUNNING_ON_VALGRIND) {
        ms   *= 50;
        i_ms *= 50;
    }
#endif

  oldAlarmHandler = signal(SIGVTALRM, alarmHandler);
  itimerval tv = {
      { static_cast<time_t>( i_ms / 1000 ), static_cast<suseconds_t>( (i_ms % 1000) * 1000 ) },
      { static_cast<time_t>( ms / 1000 ), static_cast<suseconds_t>( (ms % 1000) * 1000 ) }
  };
  setitimer(ITIMER_VIRTUAL, &tv, &oldtv);
}

void KJSCPUGuard::stop()
{
  setitimer(ITIMER_VIRTUAL, &oldtv, 0L);
  signal(SIGVTALRM, oldAlarmHandler);
}

bool KJSCPUGuard::confirmTerminate() {
  kdDebug(6070) << "alarmhandler" << endl;
  return KMessageBox::warningYesNo(0L, i18n("A script on this page is causing TDEHTML to freeze. If it continues to run, other applications may become less responsive.\nDo you want to abort the script?"), i18n("JavaScript"), i18n("&Abort"), KStdGuiItem::cont(), "kjscupguard_alarmhandler") == KMessageBox::Yes;
}

void KJSCPUGuard::alarmHandler(int) {
    ExecState::requestTerminate();
    ExecState::confirmTerminate = KJSCPUGuard::confirmTerminate;
}