Reviewed by Richard.
[WebKit-https.git] / WebCore / khtml / ecma / kjs_proxy.cpp
1 // -*- c-basic-offset: 2 -*-
2 /*
3  *  This file is part of the KDE libraries
4  *  Copyright (C) 1999-2001 Harri Porten (porten@kde.org)
5  *  Copyright (C) 2001 Peter Kelly (pmk@post.com)
6  *
7  *  This library is free software; you can redistribute it and/or
8  *  modify it under the terms of the GNU Lesser General Public
9  *  License as published by the Free Software Foundation; either
10  *  version 2 of the License, or (at your option) any later version.
11  *
12  *  This library is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  *  Lesser General Public License for more details.
16  *
17  *  You should have received a copy of the GNU Lesser General Public
18  *  License along with this library; if not, write to the Free Software
19  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  */
21
22 #include "kjs_proxy.h"
23
24 #include "kjs_window.h"
25 #include "kjs_events.h"
26 #include <khtml_part.h>
27 #include <kprotocolmanager.h>
28 #include <kdebug.h>
29 #include <kjs/collector.h>
30
31 using namespace KJS;
32
33 extern "C" {
34   KJSProxy *kjs_html_init(KHTMLPart *khtmlpart);
35 }
36
37 class KJSProxyImpl : public KJSProxy {
38 public:
39   KJSProxyImpl(KHTMLPart *part);
40   virtual ~KJSProxyImpl();
41   virtual QVariant evaluate(QString filename, int baseLine, const QString&str, const DOM::Node &n);
42   virtual void clear();
43   virtual DOM::EventListener *createHTMLEventHandler(QString sourceUrl, QString code, DOM::NodeImpl *node);
44   virtual void finishedWithEvent(const DOM::Event &event);
45   virtual KJS::ScriptInterpreter *interpreter();
46
47   virtual void setDebugEnabled(bool enabled);
48   virtual bool paused() const;
49   virtual void setSourceFile(QString url, QString code);
50   virtual void appendSourceFile(QString url, QString code);
51
52   void initScript();
53
54 private:
55   KJS::ScriptInterpreter* m_script;
56   bool m_debugEnabled;
57 #ifndef NDEBUG
58   static int s_count;
59 #endif
60 };
61
62 #ifndef NDEBUG
63 int KJSProxyImpl::s_count = 0;
64 #endif
65
66 KJSProxyImpl::KJSProxyImpl(KHTMLPart *part)
67 {
68   m_script = 0;
69   m_part = part;
70   m_debugEnabled = false;
71 #ifndef NDEBUG
72   s_count++;
73 #endif
74 }
75
76 KJSProxyImpl::~KJSProxyImpl()
77 {
78   //kdDebug() << "KJSProxyImpl::~KJSProxyImpl deleting interpreter " << m_script << endl;
79   delete m_script;
80 #ifndef NDEBUG
81   s_count--;
82   // If it was the last interpreter, we should have nothing left
83 #ifdef KJS_DEBUG_MEM
84   if ( s_count == 0 )
85     Interpreter::finalCheck();
86 #endif
87 #endif
88 }
89
90 QVariant KJSProxyImpl::evaluate(QString filename, int baseLine,
91                                 const QString&str, const DOM::Node &n) {
92   // evaluate code. Returns the JS return value or an invalid QVariant
93   // if there was none, an error occured or the type couldn't be converted.
94
95   initScript();
96   // inlineCode is true for <a href="javascript:doSomething()">
97   // and false for <script>doSomething()</script>. Check if it has the
98   // expected value in all cases.
99   // See smart window.open policy for where this is used.
100   bool inlineCode = filename.isNull();
101   //kdDebug(6070) << "KJSProxyImpl::evaluate inlineCode=" << inlineCode << endl;
102
103 #ifdef KJS_DEBUGGER
104   // ###    KJSDebugWin::instance()->attach(m_script);
105   if (inlineCode)
106     filename = "(unknown file)";
107   if (KJSDebugWin::instance())
108     KJSDebugWin::instance()->setNextSourceInfo(filename,baseLine);
109   //    KJSDebugWin::instance()->setMode(KJS::Debugger::Step);
110 #else
111   Q_UNUSED(baseLine);
112 #endif
113
114   m_script->setInlineCode(inlineCode);
115   KJS::Value thisNode = n.isNull() ? Window::retrieve( m_part ) : getDOMNode(m_script->globalExec(),n);
116
117   KJS::Interpreter::lock();
118   UString code( str );
119   KJS::Interpreter::unlock();
120
121   Completion comp = m_script->evaluate(filename, baseLine, code, thisNode);
122   bool success = ( comp.complType() == Normal ) || ( comp.complType() == ReturnValue );  
123 #ifdef KJS_DEBUGGER
124     //    KJSDebugWin::instance()->setCode(QString::null);
125 #endif
126
127   // let's try to convert the return value
128   if (success && !comp.value().isNull())
129     return ValueToVariant( m_script->globalExec(), comp.value());
130   else
131   {
132     if ( comp.complType() == Throw )
133     {
134         KJS::Interpreter::lock();
135         UString errorMessage = comp.value().toString(m_script->globalExec());
136         int lineNumber =  comp.value().toObject(m_script->globalExec()).get(m_script->globalExec(), "line").toInt32(m_script->globalExec());
137         UString sourceURL = comp.value().toObject(m_script->globalExec()).get(m_script->globalExec(), "sourceURL").toString(m_script->globalExec());
138         KJS::Interpreter::unlock();
139
140 #if APPLE_CHANGES
141         KWQ(m_part)->addMessageToConsole(errorMessage.qstring(), lineNumber, sourceURL.qstring());
142 #else
143         kdWarning(6070) << "Script threw exception: " << errorMessage.qstring() << endl;
144 #endif
145     }
146     return QVariant();
147   }
148 }
149
150 void KJSProxyImpl::clear() {
151   // clear resources allocated by the interpreter, and make it ready to be used by another page
152   // We have to keep it, so that the Window object for the part remains the same.
153   // (we used to delete and re-create it, previously)
154   if (m_script) {
155 #ifdef KJS_DEBUGGER
156     KJSDebugWin *debugWin = KJSDebugWin::instance();
157     if (debugWin && debugWin->currentScript() == m_script) {
158         debugWin->setMode(KJSDebugWin::Stop);
159 //        debugWin->leaveSession();
160     }
161 #endif
162     Window *win = Window::retrieveWindow(m_part);
163     if (win)
164         win->clear( m_script->globalExec() );
165   }
166 }
167
168 DOM::EventListener *KJSProxyImpl::createHTMLEventHandler(QString sourceUrl, QString code, DOM::NodeImpl *node)
169 {
170 #ifdef KJS_DEBUGGER
171   if (KJSDebugWin::instance())
172     KJSDebugWin::instance()->setNextSourceInfo(sourceUrl,m_handlerLineno);
173 #else
174   Q_UNUSED(sourceUrl);
175 #endif
176
177   initScript();
178   return KJS::Window::retrieveWindow(m_part)->getJSLazyEventListener(code,node,m_handlerLineno);
179 }
180
181 void KJSProxyImpl::finishedWithEvent(const DOM::Event &event)
182 {
183   // This is called when the DOM implementation has finished with a particular event. This
184   // is the case in sitations where an event has been created just for temporary usage,
185   // e.g. an image load or mouse move. Once the event has been dispatched, it is forgotten
186   // by the DOM implementation and so does not need to be cached still by the interpreter
187   m_script->forgetDOMObject(event.handle());
188 }
189
190 KJS::ScriptInterpreter *KJSProxyImpl::interpreter()
191 {
192   if (!m_script)
193     initScript();
194   m_part->keepAlive();
195   return m_script;
196 }
197
198 void KJSProxyImpl::setDebugEnabled(bool enabled)
199 {
200 #ifdef KJS_DEBUGGER
201   m_debugEnabled = enabled;
202   if (m_script)
203       m_script->setDebuggingEnabled(enabled);
204   // NOTE: this is consistent across all KJSProxyImpl instances, as we only
205   // ever have 1 debug window
206   if (!enabled && KJSDebugWin::instance()) {
207     KJSDebugWin::destroyInstance();
208   }
209   else if (enabled && !KJSDebugWin::instance()) {
210     KJSDebugWin::createInstance();
211     initScript();
212     KJSDebugWin::instance()->attach(m_script);
213   }
214 #else
215   Q_UNUSED(enabled);
216 #endif
217 }
218
219 bool KJSProxyImpl::paused() const
220 {
221 #ifdef KJS_DEBUGGER
222   if (KJSDebugWin::instance())
223     return KJSDebugWin::instance()->inSession();
224 #endif
225   return false;
226 }
227
228 void KJSProxyImpl::setSourceFile(QString url, QString code)
229 {
230 #ifdef KJS_DEBUGGER
231   if (KJSDebugWin::instance())
232     KJSDebugWin::instance()->setSourceFile(url,code);
233 #else
234   Q_UNUSED(url);
235   Q_UNUSED(code);
236 #endif
237
238 }
239
240 void KJSProxyImpl::appendSourceFile(QString url, QString code)
241 {
242 #ifdef KJS_DEBUGGER
243   if (KJSDebugWin::instance())
244     KJSDebugWin::instance()->appendSourceFile(url,code);
245 #else
246   Q_UNUSED(url);
247   Q_UNUSED(code);
248 #endif
249 }
250
251 // Implementation of the debug() function
252 class TestFunctionImp : public ObjectImp {
253 public:
254   TestFunctionImp() : ObjectImp() {}
255   virtual bool implementsCall() const { return true; }
256   virtual Value call(ExecState *exec, Object &thisObj, const List &args);
257 };
258
259 Value TestFunctionImp::call(ExecState *exec, Object &/*thisObj*/, const List &args)
260 {
261   fprintf(stderr,"--> %s\n",args[0].toString(exec).ascii());
262   return Undefined();
263 }
264
265 void KJSProxyImpl::initScript()
266 {
267   if (m_script)
268     return;
269
270   // Build the global object - which is a Window instance
271   KJS::Interpreter::lock();
272   Object globalObject( new Window(m_part) );
273   KJS::Interpreter::unlock();
274
275   // Create a KJS interpreter for this part
276   m_script = new KJS::ScriptInterpreter(globalObject, m_part);
277
278 #ifdef KJS_DEBUGGER
279   m_script->setDebuggingEnabled(m_debugEnabled);
280 #endif
281   //m_script->enableDebug();
282   KJS::Interpreter::lock();
283   globalObject.put(m_script->globalExec(),
284                    "debug", Value(new TestFunctionImp()), Internal);
285   KJS::Interpreter::unlock();
286
287 #if APPLE_CHANGES
288   QString userAgent = KWQ(m_part)->userAgent();
289 #else
290   QString userAgent = KProtocolManager::userAgentForHost(m_part->url().host());
291 #endif
292   if (userAgent.find(QString::fromLatin1("Microsoft")) >= 0 ||
293       userAgent.find(QString::fromLatin1("MSIE")) >= 0)
294     m_script->setCompatMode(Interpreter::IECompat);
295   else
296     // If we find "Mozilla" but not "(compatible, ...)" we are a real Netscape
297     if (userAgent.find(QString::fromLatin1("Mozilla")) >= 0 &&
298         userAgent.find(QString::fromLatin1("compatible")) == -1)
299       m_script->setCompatMode(Interpreter::NetscapeCompat);
300 }
301
302 // Helper method, so that all classes which need jScript() don't need to be added
303 // as friend to KHTMLPart
304 KJSProxy * KJSProxy::proxy( KHTMLPart *part )
305 {
306     return part->jScript();
307 }
308
309 // initialize HTML module
310 KJSProxy *kjs_html_init(KHTMLPart *khtmlpart)
311 {
312   return new KJSProxyImpl(khtmlpart);
313 }