1 /*
2 * This file is part of the KDE libraries
3 * Copyright (C) 1999-2001 Harri Porten (porten@kde.org)
4 * Copyright (C) 2003 Apple Computer, Inc.
5 * Copyright (C) 2007, 2008 Maksim Orlovich (maksim@kde.org)
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library 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 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22 #ifndef _KJS_BINDING_H_
23 #define _KJS_BINDING_H_
24
25 #include <khtml_export.h>
26 #include <kjs/interpreter.h>
27 #include <kjs/global.h>
28 #include <wtf/HashMap.h>
29
30 #include <dom/dom_node.h>
31 #include <QVariant>
32 #include <QHash>
33 #include <QString>
34 #include <kjs/lookup.h>
35 #include <kjs/function.h>
36 #include <kjs/JSVariableObject.h>
37 #include <kjs/object_object.h>
38 #include <misc/shared.h>
39
40 #include <stdlib.h> // for abort
41
42 #define KJS_CHECK_THIS( ClassName, theObj ) \
43 if (!theObj || !theObj->inherits(&ClassName::info)) { \
44 KJS::UString errMsg = "Attempt at calling a function that expects a "; \
45 errMsg += ClassName::info.className; \
46 errMsg += " on a "; \
47 errMsg += theObj->className(); \
48 KJS::JSObject *err = KJS::Error::create(exec, KJS::TypeError, errMsg.ascii()); \
49 exec->setException(err); \
50 return err; \
51 }
52
53 namespace KParts
54 {
55 class ReadOnlyPart;
56 class LiveConnectExtension;
57 }
58
59 namespace khtml
60 {
61 class ChildFrame;
62 }
63
64 namespace KJS
65 {
66
67 // For the ecma debugger we provide our own conversion rather than the
68 // use the native one, since using the toString
69 // method on object can invoke code
70 QString valueToString(KJS::JSValue *value);
71
72 // Serializes an exception for human consumption.
73 QString exceptionToString(ExecState *exec, JSValue *exception);
74
75 /**
76 * Base class for all objects in this binding. Doesn't manage exceptions any more
77 */
78 class DOMObject : public JSObject
79 {
80 protected:
DOMObject()81 DOMObject() : JSObject() {}
DOMObject(JSObject * proto)82 DOMObject(JSObject *proto) : JSObject(proto) {}
83 public:
shouldMark()84 bool shouldMark() const
85 {
86 return !_prop.isEmpty();
87 }
88 UString toString(ExecState *exec) const override;
89 };
90
91 /**
92 * We inherit from Interpreter, to save a pointer to the HTML part
93 * that the interpreter runs for.
94 * The interpreter also stores the DOM object - >KJS::DOMObject cache.
95 */
96 class ScriptInterpreter : public Interpreter
97 {
98 public:
99 ScriptInterpreter(JSGlobalObject *global, khtml::ChildFrame *frame);
100 virtual ~ScriptInterpreter();
101
102 // We need to keep track of wrappers in 2 ways:
103 // - we want the same wrapper for the same node (see #145775)
104 // - we want to drop all the references from this interpreter on clear, so
105 // wrappers don't stick around. Hence we have a global set and a per-interpreter one.
106
107 // Reuses an existing wrapper, perhaps also updating the current map
108 // to refer to it as well.
getDOMObject(void * objectHandle)109 DOMObject *getDOMObject(void *objectHandle)
110 {
111 DOMObject *existing = allDomObjects()->get(objectHandle);
112 if (existing) {
113 m_domObjects.set(objectHandle, existing);
114 }
115 return existing;
116 }
117
putDOMObject(void * objectHandle,DOMObject * obj)118 void putDOMObject(void *objectHandle, DOMObject *obj)
119 {
120 allDomObjects()->set(objectHandle, obj);
121 m_domObjects.set(objectHandle, obj);
122 }
123
124 static void forgetDOMObject(void *objectHandle);
125
clear()126 void clear()
127 {
128 m_domObjects.clear(); // Global set will be cleared at GC time.
129 }
130
131 /**
132 * Mark objects in the DOMObject cache.
133 */
134 void mark(bool isMain) override;
135 KParts::ReadOnlyPart *part() const;
136
rtti()137 int rtti() override
138 {
139 return 1;
140 }
141
142 /**
143 * Set the event that is triggering the execution of a script, if any
144 */
setCurrentEvent(DOM::Event * evt)145 void setCurrentEvent(DOM::Event *evt)
146 {
147 m_evt = evt;
148 }
setInlineCode(bool inlineCode)149 void setInlineCode(bool inlineCode)
150 {
151 m_inlineCode = inlineCode;
152 }
setProcessingTimerCallback(bool timerCallback)153 void setProcessingTimerCallback(bool timerCallback)
154 {
155 m_timerCallback = timerCallback;
156 }
157 /**
158 * "Smart" window.open policy
159 */
160 bool isWindowOpenAllowed() const;
161
162 /**
163 * CPU guard API. This should be used instead of Interpreter
164 * methods as it manages the timeouts, including VG support
165 */
166 bool shouldInterruptScript() const override;
167 void startCPUGuard();
168 void stopCPUGuard();
169
turnOffCPUGuard()170 static void turnOffCPUGuard()
171 {
172 s_disableCPUGuard = true;
173 }
174 private:
175 khtml::ChildFrame *m_frame;
176 HashMap<void *, DOMObject *> m_domObjects;
177 static HashMap<void *, DOMObject *> *s_allDomObjects;
allDomObjects()178 static HashMap<void *, DOMObject *> *allDomObjects()
179 {
180 if (!s_allDomObjects) {
181 s_allDomObjects = new HashMap<void *, DOMObject *>();
182 }
183 return s_allDomObjects;
184 }
185
186 DOM::Event *m_evt;
187 bool m_inlineCode;
188 bool m_timerCallback;
189 static bool s_disableCPUGuard;
190 };
191
192 /** Some templates to help make wrappers. example:
193 class Foo: public DOMWrapperObject<DOM::FooImpl> {
194 }
195
196 ...
197
198 getWrapper<Foo>(exec, someImpl);
199 */
200 template<typename Wrapper>
getWrapper(ExecState * exec,typename Wrapper::wrappedType * g)201 JSValue *getWrapper(ExecState *exec, typename Wrapper::wrappedType *g)
202 {
203 DOMObject *ret = nullptr;
204 if (!g) {
205 return jsNull();
206 }
207
208 ScriptInterpreter *interp = static_cast<ScriptInterpreter *>(exec->dynamicInterpreter());
209 if ((ret = interp->getDOMObject(g))) {
210 return ret;
211 }
212
213 ret = new Wrapper(exec, g);
214 interp->putDOMObject(g, ret);
215 return ret;
216 }
217
218 template<typename Wrapped>
219 class DOMWrapperObject : public DOMObject
220 {
221 public:
222 typedef Wrapped wrappedType;
223 typedef DOMWrapperObject<Wrapped> WrapperBase;
224
DOMWrapperObject(JSObject * proto,Wrapped * wrapee)225 DOMWrapperObject(JSObject *proto, Wrapped *wrapee):
226 DOMObject(proto), m_impl(wrapee)
227 {}
228
~DOMWrapperObject()229 virtual ~DOMWrapperObject()
230 {
231 ScriptInterpreter::forgetDOMObject(m_impl.get());
232 }
233
toBoolean(ExecState *)234 bool toBoolean(ExecState *) const override
235 {
236 return true;
237 }
238
impl()239 Wrapped *impl()
240 {
241 return m_impl.get();
242 }
impl()243 const Wrapped *impl() const
244 {
245 return m_impl.get();
246 }
247 private:
248 SharedPtr<Wrapped> m_impl;
249 };
250
251 /**
252 A little helper for setting stuff up given an entry
253 */
254 template<class FuncImp, class ThisImp>
getSlotFromEntry(const HashEntry * entry,ThisImp * thisObj,PropertySlot & slot)255 inline void getSlotFromEntry(const HashEntry *entry, ThisImp *thisObj, PropertySlot &slot)
256 {
257 if (entry->attr & Function) {
258 slot.setStaticEntry(thisObj, entry, staticFunctionGetter<FuncImp>);
259 } else {
260 slot.setStaticEntry(thisObj, entry, staticValueGetter<ThisImp>);
261 }
262 }
263
264 /**
265 Like getStaticPropertySlot but doesn't check the parent. Handy when there
266 are both functions and values
267 */
268 template <class FuncImp, class ThisImp>
getStaticOwnPropertySlot(const HashTable * table,ThisImp * thisObj,const Identifier & propertyName,PropertySlot & slot)269 inline bool getStaticOwnPropertySlot(const HashTable *table,
270 ThisImp *thisObj, const Identifier &propertyName, PropertySlot &slot)
271 {
272 const HashEntry *entry = Lookup::findEntry(table, propertyName);
273
274 if (!entry) { // not found, forward to parent
275 return false;
276 }
277
278 if (entry->attr & Function) {
279 slot.setStaticEntry(thisObj, entry, staticFunctionGetter<FuncImp>);
280 } else {
281 slot.setStaticEntry(thisObj, entry, staticValueGetter<ThisImp>);
282 }
283
284 return true;
285 }
286
287 /**
288 Handler for local table-looked up things.
289 */
290 template<class ThisImp>
getStaticOwnValueSlot(const HashTable * table,ThisImp * thisObj,const Identifier & propertyName,PropertySlot & slot)291 inline bool getStaticOwnValueSlot(const HashTable *table,
292 ThisImp *thisObj, const Identifier &propertyName, PropertySlot &slot)
293 {
294 const HashEntry *entry = Lookup::findEntry(table, propertyName);
295 if (!entry) {
296 return false;
297 }
298
299 assert(!(entry->attr & Function));
300 slot.setStaticEntry(thisObj, entry, staticValueGetter<ThisImp>);
301 return true;
302 }
303
304 /* Helper for the below*/
305 template<class JSTypeImp>
indexGetterAdapter(ExecState * exec,JSObject *,unsigned,const PropertySlot & slot)306 JSValue *indexGetterAdapter(ExecState *exec, JSObject *, unsigned, const PropertySlot &slot)
307 {
308 JSTypeImp *thisObj = static_cast<JSTypeImp *>(slot.slotBase());
309 return thisObj->indexGetter(exec, slot.index());
310 }
311
312 /**
313 Handler for index properties. Will call "length" method on the listObj
314 to determine whether it's in range, and arrange to have indexGetter called
315 */
316 template<class ThisImp, class BaseObj>
getIndexSlot(ThisImp * thisObj,const BaseObj & listObj,const Identifier & propertyName,PropertySlot & slot)317 inline bool getIndexSlot(ThisImp *thisObj, const BaseObj &listObj,
318 const Identifier &propertyName, PropertySlot &slot)
319 {
320 bool ok;
321 unsigned u = propertyName.toArrayIndex(&ok);
322 if (ok && u < listObj.length()) {
323 slot.setCustomIndex(thisObj, u, indexGetterAdapter<ThisImp>);
324 return true;
325 }
326 return false;
327 }
328
329 /**
330 Version that takes an external bound
331 */
332 template<class ThisImp>
getIndexSlot(ThisImp * thisObj,unsigned lengthLimit,const Identifier & propertyName,PropertySlot & slot)333 inline bool getIndexSlot(ThisImp *thisObj, unsigned lengthLimit,
334 const Identifier &propertyName, PropertySlot &slot)
335 {
336 bool ok;
337 unsigned u = propertyName.toArrayIndex(&ok);
338 if (ok && u < lengthLimit) {
339 slot.setCustomIndex(thisObj, u, indexGetterAdapter<ThisImp>);
340 return true;
341 }
342 return false;
343 }
344
345 template<class ThisImp>
getIndexSlot(ThisImp * thisObj,int lengthLimit,const Identifier & propertyName,PropertySlot & slot)346 inline bool getIndexSlot(ThisImp *thisObj, int lengthLimit,
347 const Identifier &propertyName, PropertySlot &slot)
348 {
349 return getIndexSlot(thisObj, (unsigned)lengthLimit, propertyName, slot);
350 }
351
352 /**
353 Version w/o the bounds check
354 */
355 template<class ThisImp>
getIndexSlot(ThisImp * thisObj,const Identifier & propertyName,PropertySlot & slot)356 inline bool getIndexSlot(ThisImp *thisObj, const Identifier &propertyName, PropertySlot &slot)
357 {
358 bool ok;
359 unsigned u = propertyName.toArrayIndex(&ok);
360 if (ok) {
361 slot.setCustomIndex(thisObj, u, indexGetterAdapter<ThisImp>);
362 return true;
363 }
364 return false;
365 }
366
367 /* Helper for below */
368 JSValue *valueGetterAdapter(ExecState *exec, JSObject *, const Identifier &, const PropertySlot &slot);
369
370 /**
371 This sets up the slot to return a particular JSValue*; unlike
372 setValueSlot, it does not require there to be a location to point at
373 */
getImmediateValueSlot(JSObject * thisObj,JSValue * value,PropertySlot & slot)374 inline bool getImmediateValueSlot(JSObject *thisObj, JSValue *value, PropertySlot &slot)
375 {
376 slot.setCustomValue(thisObj, value, valueGetterAdapter);
377 return true;
378 }
379
380 /**
381 * Retrieve from cache, or create, a KJS object around a DOM object
382 */
383 template<class DOMObj, class KJSDOMObj>
cacheDOMObject(ExecState * exec,DOMObj * domObj)384 inline JSValue *cacheDOMObject(ExecState *exec, DOMObj *domObj)
385 {
386 DOMObject *ret;
387 if (!domObj) {
388 return jsNull();
389 }
390 ScriptInterpreter *interp = static_cast<ScriptInterpreter *>(exec->dynamicInterpreter());
391 if ((ret = interp->getDOMObject(domObj))) {
392 return ret;
393 } else {
394 ret = new KJSDOMObj(exec, domObj);
395 interp->putDOMObject(domObj, ret);
396 return ret;
397 }
398 }
399
400 /**
401 * Convert an object to a Node. Returns 0 if not possible.
402 */
403 DOM::NodeImpl *toNode(JSValue *);
404 /**
405 * Get a String object, or Null() if s is null
406 */
407 JSValue *getStringOrNull(DOM::DOMString s);
408
409 /**
410 * Null string if the value is null
411 */
412 DOM::DOMString valueToStringWithNullCheck(ExecState *exec, JSValue *v);
413
414 /**
415 * Convert a KJS value into a QVariant
416 */
417 QVariant ValueToVariant(ExecState *exec, JSValue *val);
418
419 // Convert a DOM implementation exception code into a JavaScript exception in the execution state.
420 void setDOMException(ExecState *exec, int DOMExceptionCode);
421
422 // Helper class to call setDOMException on exit without adding lots of separate calls to that function.
423 class DOMExceptionTranslator
424 {
425 public:
DOMExceptionTranslator(ExecState * exec)426 explicit DOMExceptionTranslator(ExecState *exec) : m_exec(exec), m_code(0) { }
~DOMExceptionTranslator()427 ~DOMExceptionTranslator()
428 {
429 setDOMException(m_exec, m_code);
430 }
431 operator int &()
432 {
433 return m_code;
434 }
435 operator int *()
436 {
437 return &m_code;
438 }
439
triggered()440 bool triggered()
441 {
442 return m_code;
443 }
444 private:
445 ExecState *m_exec;
446 int m_code;
447 };
448
449 // convenience function
jsString(const QString & s)450 inline JSCell *jsString(const QString &s)
451 {
452 return jsString(UString(s));
453 }
454
455 // This is used to create pseudo-constructor objects, like Mozillaish
456 // Element, HTMLDocument, etc., which do not act like real constructors,
457 // but do have the prototype property pointing to prototype of "instances"
458 #define DEFINE_PSEUDO_CONSTRUCTOR(ClassName) \
459 class ClassName : public DOMObject { \
460 public: \
461 ClassName(ExecState *); \
462 virtual const ClassInfo* classInfo() const { return &info; } \
463 static const ClassInfo info; \
464 static JSObject* self(ExecState *exec); \
465 virtual bool implementsHasInstance() const; \
466 };
467
468 #define IMPLEMENT_PSEUDO_CONSTRUCTOR_IMP(Class,ClassName,ProtoClass,ParentProto) \
469 const ClassInfo Class::info = { ClassName, nullptr, nullptr, nullptr }; \
470 Class::Class(ExecState* exec): DOMObject(ParentProto) {\
471 /* Since ProtoClass ctor might need us, make sure we're registered */ \
472 exec->lexicalInterpreter()->globalObject()->put(exec, "[[" ClassName ".constructor]]", this, KJS::Internal | KJS::DontEnum); \
473 JSObject* proto = ProtoClass::self(exec); \
474 putDirect(exec->propertyNames().prototype, proto, DontDelete|ReadOnly); \
475 }\
476 JSObject* Class::self(ExecState *exec) { \
477 return cacheGlobalObject<Class>(exec, "[[" ClassName ".constructor]]"); \
478 } \
479 bool Class::implementsHasInstance() const { \
480 return true; \
481 }
482
483 #define IMPLEMENT_PSEUDO_CONSTRUCTOR(Class,ClassName,ProtoClass) \
484 IMPLEMENT_PSEUDO_CONSTRUCTOR_IMP(Class,ClassName,ProtoClass,exec->lexicalInterpreter()->builtinObjectPrototype())
485
486 #define IMPLEMENT_PSEUDO_CONSTRUCTOR_WITH_PARENT(Class,ClassName,ProtoClass,ParentProtoClass) \
487 IMPLEMENT_PSEUDO_CONSTRUCTOR_IMP(Class,ClassName,ProtoClass,ParentProtoClass::self(exec))
488
489 // This declares a constant table, which merely maps everything in its
490 // table to its token value. Can be used as a prototype
491 #define DEFINE_CONSTANT_TABLE(Class) \
492 class Class : public DOMObject { \
493 public: \
494 Class(ExecState *exec): DOMObject(exec->lexicalInterpreter()->builtinObjectPrototype()) {} \
495 \
496 using KJS::JSObject::getOwnPropertySlot;\
497 virtual bool getOwnPropertySlot(ExecState *exec, const Identifier& propertyName, PropertySlot& slot);\
498 JSValue* getValueProperty(ExecState *exec, int token) const; \
499 virtual const ClassInfo* classInfo() const { return &info; } \
500 static const ClassInfo info; \
501 static JSObject* self(ExecState *exec);\
502 static Identifier* s_name; \
503 static Identifier* name(); \
504 };
505
506 // Emits an implementation of a constant table
507 #define IMPLEMENT_CONSTANT_TABLE(Class,ClassName) \
508 bool Class::getOwnPropertySlot(ExecState *exec, const Identifier& propertyName, PropertySlot& slot) \
509 { \
510 return getStaticValueSlot<Class, DOMObject>(exec, &Class##Table, this, propertyName, slot);\
511 }\
512 JSValue* Class::getValueProperty(ExecState * /*exec*/, int token) const { \
513 /* We use the token as the value to return directly*/ \
514 return jsNumber((unsigned int)token); \
515 } \
516 JSObject* Class::self(ExecState *exec) { \
517 return cacheGlobalObject<Class>(exec, *name()); \
518 } \
519 Identifier* Class::s_name = nullptr; \
520 Identifier* Class::name() { \
521 if (!s_name) s_name = new Identifier("[[" ClassName ".constant_table]]"); \
522 return s_name; \
523 } \
524 const ClassInfo Class::info = { ClassName, nullptr, &Class##Table, nullptr };
525
526 // cacheGlobalObject<> is not in namespace KJS - need to use ::cacheGlobalObject<>
527 #define KJS_EMPTY_PROTOTYPE_IMP(ClassName, ClassProto, ProtoCode) \
528 class ClassProto : public JSObject { \
529 friend JSObject* KJS_CACHEGLOBALOBJECT_NS cacheGlobalObject<ClassProto>(ExecState *exec, const Identifier &propertyName); \
530 public: \
531 static JSObject* self(ExecState *exec) { \
532 return KJS_CACHEGLOBALOBJECT_NS cacheGlobalObject<ClassProto>(exec, *name()); \
533 } \
534 virtual const ClassInfo *classInfo() const { return &info; } \
535 static const ClassInfo info; \
536 protected: \
537 ClassProto( ExecState *exec ) \
538 : JSObject( ProtoCode ) {} \
539 \
540 static Identifier* s_name; \
541 static Identifier* name() { \
542 if (!s_name) s_name = new Identifier("[[" ClassName ".prototype]]"); \
543 return s_name; \
544 }\
545 }; \
546 Identifier* ClassProto::s_name = nullptr; \
547 const ClassInfo ClassProto::info = { ClassName, nullptr, nullptr, nullptr };
548
549 #define KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE(ClassName, ClassProto, ClassProtoProto) \
550 KJS_EMPTY_PROTOTYPE_IMP(ClassName, ClassProto, ClassProtoProto::self(exec))
551
552 } // namespace
553
554 #endif
555