Suggested by Alexey.
[WebKit-https.git] / WebCore / khtml / ecma / kjs_binding.cpp
1 /*
2  *  This file is part of the KDE libraries
3  *  Copyright (C) 1999-2001 Harri Porten (porten@kde.org)
4  *  Copyright (C) 2004 Apple Computer, Inc.
5  *
6  *  This library is free software; you can redistribute it and/or
7  *  modify it under the terms of the GNU Lesser General Public
8  *  License as published by the Free Software Foundation; either
9  *  version 2 of the License, or (at your option) any later version.
10  *
11  *  This library is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  *  Lesser General Public License for more details.
15  *
16  *  You should have received a copy of the GNU Lesser General Public
17  *  License along with this library; if not, write to the Free Software
18  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20
21 // gcc 3.x can't handle including the HashMap pointer specialization in this file
22 #ifndef __GLIBCXX__ // less than gcc 3.4
23 #define HASH_MAP_PTR_SPEC_WORKAROUND 1
24 #endif
25
26 #include "config.h"
27 #include "kjs_binding.h"
28
29 #include "kjs_dom.h"
30 #include "kjs_window.h"
31 #include <kjs/internal.h> // for InterpreterImp
32 #include <kjs/collector.h>
33 #include <kxmlcore/HashMap.h>
34
35 #include "dom/dom_exception.h"
36 #include "dom/dom2_events.h"
37 #include "dom/dom2_range.h"
38 #include "xml/dom2_eventsimpl.h"
39 #include "xml/EventNames.h"
40 #include "dom/css_stylesheet.h"
41 #include "Frame.h"
42
43 #include <kdebug.h>
44
45 using namespace DOM::EventNames;
46
47 using DOM::AtomicString;
48 using DOM::CSSException;
49 using DOM::DOMString;
50 using DOM::DocumentImpl;
51 using DOM::EventException;
52 using DOM::NodeImpl;
53 using DOM::RangeException;
54
55 namespace KJS {
56
57 UString DOMObject::toString(ExecState *) const
58 {
59   return "[object " + className() + "]";
60 }
61
62 typedef HashMap<void*, DOMObject*> DOMObjectMap;
63 typedef HashMap<NodeImpl*, DOMNode*> NodeMap;
64 typedef HashMap<DocumentImpl*, NodeMap*> NodePerDocMap;
65
66 static DOMObjectMap *domObjects()
67
68   static DOMObjectMap* staticDomObjects = new DOMObjectMap();
69   return staticDomObjects;
70 }
71
72 static NodePerDocMap *domNodesPerDocument()
73 {
74   static NodePerDocMap *staticDOMNodesPerDocument = new NodePerDocMap();
75   return staticDOMNodesPerDocument;
76 }
77
78
79 ScriptInterpreter::ScriptInterpreter( JSObject *global, Frame *frame )
80   : Interpreter( global ), m_frame(frame),
81     m_evt( 0L ), m_inlineCode(false), m_timerCallback(false)
82 {
83 #ifdef KJS_VERBOSE
84   kdDebug(6070) << "ScriptInterpreter::ScriptInterpreter " << this << " for frame=" << m_frame << endl;
85 #endif
86 }
87
88 ScriptInterpreter::~ScriptInterpreter()
89 {
90 #ifdef KJS_VERBOSE
91   kdDebug(6070) << "ScriptInterpreter::~ScriptInterpreter " << this << " for frame=" << m_frame << endl;
92 #endif
93 }
94
95 DOMObject* ScriptInterpreter::getDOMObject(void* objectHandle) 
96 {
97     return domObjects()->get(objectHandle);
98 }
99
100 void ScriptInterpreter::putDOMObject(void* objectHandle, DOMObject* obj) 
101 {
102     domObjects()->set(objectHandle, obj);
103 }
104
105 void ScriptInterpreter::forgetDOMObject(void* objectHandle)
106 {
107     domObjects()->remove(objectHandle);
108 }
109
110 DOMNode *ScriptInterpreter::getDOMNodeForDocument(DOM::DocumentImpl *document, DOM::NodeImpl *node)
111 {
112     if (!document)
113         return static_cast<DOMNode *>(domObjects()->get(node));
114     NodeMap *documentDict = domNodesPerDocument()->get(document);
115     if (documentDict)
116         return documentDict->get(node);
117     return NULL;
118 }
119
120 void ScriptInterpreter::forgetDOMNodeForDocument(DOM::DocumentImpl *document, NodeImpl *node)
121 {
122     if (!document) {
123         domObjects()->remove(node);
124         return;
125     }
126     NodeMap *documentDict = domNodesPerDocument()->get(document);
127     if (documentDict)
128         documentDict->remove(node);
129 }
130
131 void ScriptInterpreter::putDOMNodeForDocument(DOM::DocumentImpl *document, NodeImpl *nodeHandle, DOMNode *nodeWrapper)
132 {
133     if (!document) {
134         domObjects()->set(nodeHandle, nodeWrapper);
135         return;
136     }
137     NodeMap *documentDict = domNodesPerDocument()->get(document);
138     if (!documentDict) {
139         documentDict = new NodeMap();
140         domNodesPerDocument()->set(document, documentDict);
141     }
142     documentDict->set(nodeHandle, nodeWrapper);
143 }
144
145 void ScriptInterpreter::forgetAllDOMNodesForDocument(DOM::DocumentImpl *document)
146 {
147     assert(document);
148     NodePerDocMap::iterator it = domNodesPerDocument()->find(document);
149     if (it != domNodesPerDocument()->end()) {
150         delete it->second;
151         domNodesPerDocument()->remove(it);
152     }
153 }
154
155 void ScriptInterpreter::mark()
156 {
157   NodePerDocMap::iterator dictEnd = domNodesPerDocument()->end();
158   for (NodePerDocMap::iterator dictIt = domNodesPerDocument()->begin();
159        dictIt != dictEnd;
160        ++dictIt) {
161     
162       NodeMap *nodeDict = dictIt->second;
163       NodeMap::iterator nodeEnd = nodeDict->end();
164       for (NodeMap::iterator nodeIt = nodeDict->begin();
165            nodeIt != nodeEnd;
166            ++nodeIt) {
167
168         DOMNode *node = nodeIt->second;
169         // don't mark wrappers for nodes that are no longer in the
170         // document - they should not be saved if the node is not
171         // otherwise reachable from JS.
172         if (node->impl()->inDocument() && !node->marked())
173             node->mark();
174       }
175   }
176 }
177
178 ExecState *ScriptInterpreter::globalExec()
179 {
180     // we need to make sure that any script execution happening in this
181     // frame does not destroy it
182     m_frame->keepAlive();
183     return Interpreter::globalExec();
184 }
185
186 void ScriptInterpreter::updateDOMNodeDocument(DOM::NodeImpl *node, DOM::DocumentImpl *oldDoc, DOM::DocumentImpl *newDoc)
187 {
188   DOMNode *cachedObject = getDOMNodeForDocument(oldDoc, node);
189   if (cachedObject) {
190     putDOMNodeForDocument(newDoc, node, cachedObject);
191     forgetDOMNodeForDocument(oldDoc, node);
192   }
193 }
194
195 bool ScriptInterpreter::wasRunByUserGesture() const
196 {
197   if ( m_evt )
198   {
199     const AtomicString &type = m_evt->type();
200     bool eventOk = ( // mouse events
201       type == clickEvent || type == mousedownEvent ||
202       type == mouseupEvent || type == khtmlDblclickEvent ||
203       // keyboard events
204       type == keydownEvent || type == keypressEvent ||
205       type == keyupEvent ||
206       // other accepted events
207       type == selectEvent || type == changeEvent ||
208       type == focusEvent || type == blurEvent ||
209       type == submitEvent );
210     if (eventOk)
211       return true;
212   } else // no event
213   {
214     if ( m_inlineCode  && !m_timerCallback )
215     {
216       // This is the <a href="javascript:window.open('...')> case -> we let it through
217       return true;
218       kdDebug(6070) << "Window.open, smart policy, no event, inline code -> ok" << endl;
219     }
220     else // This is the <script>window.open(...)</script> case or a timer callback -> block it
221       kdDebug(6070) << "Window.open, smart policy, no event, <script> tag -> refused" << endl;
222   }
223   return false;
224 }
225
226
227 bool ScriptInterpreter::isGlobalObject(JSValue *v)
228 {
229     return v->isObject(&Window::info);
230 }
231
232 bool ScriptInterpreter::isSafeScript (const Interpreter *_target)
233 {
234     const KJS::ScriptInterpreter *target = static_cast<const ScriptInterpreter *>(_target);
235
236     return KJS::Window::isSafeScript (this, target);
237 }
238
239 Interpreter *ScriptInterpreter::interpreterForGlobalObject (const JSValue *imp)
240 {
241     const KJS::Window *win = static_cast<const KJS::Window *>(imp);
242     return win->interpreter();
243 }
244
245 void *ScriptInterpreter::createLanguageInstanceForValue (ExecState *exec, int language, JSObject *value, const Bindings::RootObject *origin, const Bindings::RootObject *current)
246 {
247     void *result = 0;
248     
249 #if __APPLE__
250     // FIXME: Need to implement bindings support.
251     if (language == Bindings::Instance::ObjectiveCLanguage)
252         result = createObjcInstanceForValue (exec, value, origin, current);
253     
254     if (!result)
255         result = Interpreter::createLanguageInstanceForValue (exec, language, value, origin, current);
256 #endif
257     return result;
258 }
259
260
261 //////
262
263 UString::UString(const QString &d)
264 {
265   // reinterpret_cast is ugly but in this case safe, since QChar and UChar have the same
266   // memory layout
267   m_rep = UString::Rep::createCopying(reinterpret_cast<const UChar *>(d.unicode()), d.length());
268 }
269
270 UString::UString(const DOMString &d)
271 {
272   if (d.isNull()) {
273     m_rep = &Rep::null;
274     return;
275   }
276   // reinterpret_cast is ugly but in this case safe, since QChar and UChar have the same
277   // memory layout
278   m_rep = UString::Rep::createCopying(reinterpret_cast<const UChar *>(d.unicode()), d.length());
279 }
280
281 UString::UString(const AtomicString &d)
282 {
283   if (d.isNull()) {
284     m_rep = &Rep::null;
285     return;
286   }
287   // reinterpret_cast is ugly but in this case safe, since QChar and UChar have the same
288   // memory layout
289   m_rep = UString::Rep::createCopying(reinterpret_cast<const UChar *>(d.domString().unicode()), d.domString().length());
290 }
291
292 DOMString UString::domString() const
293 {
294   if (isNull())
295     return DOMString();
296   if (isEmpty())
297     return DOMString("");
298   return DOMString((QChar*) data(), size());
299 }
300
301 QString UString::qstring() const
302 {
303   if (isNull())
304     return QString();
305   if (isEmpty())
306     return QString("");
307   return QString((QChar*) data(), size());
308 }
309
310 QConstString UString::qconststring() const
311 {
312   return QConstString((QChar*) data(), size());
313 }
314
315 DOMString Identifier::domString() const
316 {
317   if (isNull())
318     return DOMString();
319   if (isEmpty())
320     return DOMString("");
321   return DOMString((QChar*) data(), size());
322 }
323
324 QString Identifier::qstring() const
325 {
326   if (isNull())
327     return QString();
328   if (isEmpty())
329     return QString("");
330   return QString((QChar*) data(), size());
331 }
332
333 JSValue *jsStringOrNull(const DOMString &s)
334 {
335     if (s.isNull())
336         return jsNull();
337     return jsString(s);
338 }
339
340 JSValue *jsStringOrUndefined(const DOMString &s)
341 {
342     if (s.isNull())
343         return jsUndefined();
344     return jsString(s);
345 }
346
347 DOMString valueToStringWithNullCheck(ExecState *exec, JSValue *val)
348 {
349     if (val->isNull())
350         return DOMString();
351     
352     return val->toString(exec).domString();
353 }
354
355 QVariant ValueToVariant(ExecState* exec, JSValue *val) {
356   QVariant res;
357   switch (val->type()) {
358   case BooleanType:
359     res = QVariant(val->toBoolean(exec), 0);
360     break;
361   case NumberType:
362     res = QVariant(val->toNumber(exec));
363     break;
364   case StringType:
365     res = QVariant(val->toString(exec).qstring());
366     break;
367   default:
368     // everything else will be 'invalid'
369     break;
370   }
371   return res;
372 }
373
374 static const char * const exceptionNames[] = {
375     0,
376     "INDEX_SIZE_ERR",
377     "DOMSTRING_SIZE_ERR",
378     "HIERARCHY_REQUEST_ERR",
379     "WRONG_DOCUMENT_ERR",
380     "INVALID_CHARACTER_ERR",
381     "NO_DATA_ALLOWED_ERR",
382     "NO_MODIFICATION_ALLOWED_ERR",
383     "NOT_FOUND_ERR",
384     "NOT_SUPPORTED_ERR",
385     "INUSE_ATTRIBUTE_ERR",
386     "INVALID_STATE_ERR",
387     "SYNTAX_ERR",
388     "INVALID_MODIFICATION_ERR",
389     "NAMESPACE_ERR",
390     "INVALID_ACCESS_ERR",
391 };
392
393 static const char * const rangeExceptionNames[] = {
394     0, "BAD_BOUNDARYPOINTS_ERR", "INVALID_NODE_TYPE_ERR"
395 };
396
397 static const char * const cssExceptionNames[] = {
398     "SYNTAX_ERR", "INVALID_MODIFICATION_ERR"
399 };
400
401 static const char * const eventExceptionNames[] = {
402     "UNSPECIFIED_EVENT_TYPE_ERR"
403 };
404
405 void setDOMException(ExecState *exec, int DOMExceptionCode)
406 {
407   if (DOMExceptionCode == 0 || exec->hadException())
408     return;
409
410   const char* type = "DOM";
411   int code = DOMExceptionCode;
412
413   const char * const * nameTable;
414   int nameTableSize;
415   if (code >= RangeException::_EXCEPTION_OFFSET && code <= RangeException::_EXCEPTION_MAX) {
416     type = "DOM Range";
417     code -= RangeException::_EXCEPTION_OFFSET;
418     nameTable = rangeExceptionNames;
419     nameTableSize = sizeof(rangeExceptionNames) / sizeof(rangeExceptionNames[0]);
420   } else if (code >= CSSException::_EXCEPTION_OFFSET && code <= CSSException::_EXCEPTION_MAX) {
421     type = "CSS";
422     code -= CSSException::_EXCEPTION_OFFSET;
423     nameTable = cssExceptionNames;
424     nameTableSize = sizeof(cssExceptionNames) / sizeof(cssExceptionNames[0]);
425   } else if (code >= EventException::_EXCEPTION_OFFSET && code <= EventException::_EXCEPTION_MAX) {
426     type = "DOM Events";
427     code -= EventException::_EXCEPTION_OFFSET;
428     nameTable = eventExceptionNames;
429     nameTableSize = sizeof(eventExceptionNames) / sizeof(eventExceptionNames[0]);
430   } else {
431     nameTable = exceptionNames;
432     nameTableSize = sizeof(exceptionNames) / sizeof(exceptionNames[0]);
433   }
434
435   const char* name = code < nameTableSize ? nameTable[code] : 0;
436
437   // 100 characters is a big enough buffer, because there are:
438   //   13 characters in the message
439   //   10 characters in the longest type, "DOM Events"
440   //   27 characters in the longest name, "NO_MODIFICATION_ALLOWED_ERR"
441   //   20 or so digits in the longest integer's ASCII form (even if int is 64-bit)
442   //   1 byte for a null character
443   // That adds up to about 70 bytes.
444   char buffer[100];
445
446   if (name)
447     sprintf(buffer, "%s: %s Exception %d", name, type, code);
448   else
449     sprintf(buffer, "%s Exception %d", type, code);
450
451   JSObject *errorObject = throwError(exec, GeneralError, buffer);
452   errorObject->put(exec, "code", jsNumber(code));
453 }
454
455 }