1 /*
2 * This file is part of the KDE libraries
3 * Copyright (C) 1999-2003 Harri Porten (porten@kde.org)
4 * Copyright (C) 2001-2003 David Faure (faure@kde.org)
5 * Copyright (C) 2003 Apple Computer, Inc.
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 #include "kjs_binding.h"
23
24 #include <config-khtml.h>
25 #if HAVE_VALGRIND_MEMCHECK_H
26
27 #include <valgrind/memcheck.h>
28 #define VALGRIND_SUPPORT
29
30 #endif
31
32 #include "kjs_dom.h"
33 #include "kjs_range.h"
34
35 #include <dom/css_stylesheet.h>
36 #include <dom/dom_exception.h>
37 #include <dom/dom2_range.h>
38 #include <dom/dom3_xpath.h>
39 #include <xml/dom2_eventsimpl.h>
40 #include <khtmlpart_p.h>
41
42 #include "khtml_debug.h"
43 #include <kparts/browserextension.h>
44 #include <kmessagebox.h>
45 #include <QTextDocument> // Qt::escape
46
47 #ifdef KJS_DEBUGGER
48 #include "debugger/debugwindow.h"
49 #endif
50
51 #include <QList>
52
53 #include <assert.h>
54
55 using namespace KJSDebugger;
56
57 namespace KJS
58 {
59
toString(ExecState *) const60 UString DOMObject::toString(ExecState *) const
61 {
62 return "[object " + className() + "]";
63 }
64
65 HashMap<void *, DOMObject *> *ScriptInterpreter::s_allDomObjects;
66
67 typedef QList<ScriptInterpreter *> InterpreterList;
68 static InterpreterList *interpreterList;
69
ScriptInterpreter(JSGlobalObject * global,khtml::ChildFrame * frame)70 ScriptInterpreter::ScriptInterpreter(JSGlobalObject *global, khtml::ChildFrame *frame)
71 : Interpreter(global), m_frame(frame),
72 m_evt(nullptr), m_inlineCode(false), m_timerCallback(false)
73 {
74 #ifdef KJS_VERBOSE
75 qCDebug(KHTML_LOG) << "ScriptInterpreter::ScriptInterpreter " << this << " for part=" << m_frame;
76 #endif
77 if (!interpreterList) {
78 interpreterList = new InterpreterList;
79 }
80 interpreterList->append(this);
81 }
82
~ScriptInterpreter()83 ScriptInterpreter::~ScriptInterpreter()
84 {
85 #ifdef KJS_VERBOSE
86 qCDebug(KHTML_LOG) << "ScriptInterpreter::~ScriptInterpreter " << this << " for part=" << m_frame;
87 #endif
88 assert(interpreterList && interpreterList->contains(this));
89 interpreterList->removeAll(this);
90 if (interpreterList->isEmpty()) {
91 delete interpreterList;
92 interpreterList = nullptr;
93 }
94 }
95
forgetDOMObject(void * objectHandle)96 void ScriptInterpreter::forgetDOMObject(void *objectHandle)
97 {
98 if (!interpreterList) {
99 return;
100 }
101
102 for (int i = 0; i < interpreterList->size(); ++i) {
103 interpreterList->at(i)->m_domObjects.remove(objectHandle);
104 }
105 allDomObjects()->remove(objectHandle);
106 }
107
mark(bool isMain)108 void ScriptInterpreter::mark(bool isMain)
109 {
110 Interpreter::mark(isMain);
111 #ifdef KJS_VERBOSE
112 qCDebug(KHTML_LOG) << "ScriptInterpreter::mark " << this << " marking " << m_domObjects.size() << " DOM objects";
113 #endif
114 HashMap<void *, DOMObject *>::iterator it = m_domObjects.begin();
115 while (it != m_domObjects.end()) {
116 DOMObject *obj = it->second;
117 if (obj->shouldMark()) {
118 obj->mark();
119 }
120 ++it;
121 }
122 }
123
part() const124 KParts::ReadOnlyPart *ScriptInterpreter::part() const
125 {
126 return m_frame->m_part.data();
127 }
128
isWindowOpenAllowed() const129 bool ScriptInterpreter::isWindowOpenAllowed() const
130 {
131 if (m_evt) {
132 int id = m_evt->handle()->id();
133 bool eventOk = ( // mouse events
134 id == DOM::EventImpl::CLICK_EVENT ||
135 id == DOM::EventImpl::MOUSEUP_EVENT || id == DOM::EventImpl::MOUSEDOWN_EVENT ||
136 id == DOM::EventImpl::KHTML_ECMA_CLICK_EVENT || id == DOM::EventImpl::KHTML_ECMA_DBLCLICK_EVENT ||
137 // keyboard events
138 id == DOM::EventImpl::KEYDOWN_EVENT || id == DOM::EventImpl::KEYPRESS_EVENT ||
139 id == DOM::EventImpl::KEYUP_EVENT ||
140 // other accepted events
141 id == DOM::EventImpl::SELECT_EVENT || id == DOM::EventImpl::CHANGE_EVENT ||
142 id == DOM::EventImpl::SUBMIT_EVENT);
143 // qCDebug(KHTML_LOG) << "Window.open, smart policy: id=" << id << " eventOk=" << eventOk;
144 if (eventOk) {
145 return true;
146 }
147 } else { // no event
148 if (m_inlineCode && !m_timerCallback) {
149 // This is the <a href="javascript:window.open('...')> case -> we let it through
150 return true;
151 // qCDebug(KHTML_LOG) << "Window.open, smart policy, no event, inline code -> ok";
152 } else { // This is the <script>window.open(...)</script> case or a timer callback -> block it
153 // qCDebug(KHTML_LOG) << "Window.open, smart policy, no event, <script> tag -> refused";
154 }
155 }
156 return false;
157 }
158
159 bool ScriptInterpreter::s_disableCPUGuard = false;
160
startCPUGuard()161 void ScriptInterpreter::startCPUGuard()
162 {
163 if (s_disableCPUGuard) {
164 return;
165 }
166
167 unsigned time = 5000;
168 #ifdef VALGRIND_SUPPORT
169 if (RUNNING_ON_VALGRIND) {
170 time *= 50;
171 }
172 #endif
173
174 setTimeoutTime(time);
175 startTimeoutCheck();
176 }
177
stopCPUGuard()178 void ScriptInterpreter::stopCPUGuard()
179 {
180 if (s_disableCPUGuard) {
181 return;
182 }
183 stopTimeoutCheck();
184 }
185
shouldInterruptScript() const186 bool ScriptInterpreter::shouldInterruptScript() const
187 {
188 #ifdef KJS_DEBUGGER
189 if (DebugWindow::isBlocked()) {
190 return false;
191 }
192 #endif
193
194 // qCDebug(KHTML_LOG) << "alarmhandler";
195 return KMessageBox::warningYesNo(nullptr, i18n("A script on this page is causing KHTML to freeze. If it continues to run, other applications may become less responsive.\nDo you want to stop the script?"), i18n("JavaScript"), KGuiItem(i18n("&Stop Script")), KStandardGuiItem::cont(), "kjscupguard_alarmhandler") == KMessageBox::Yes;
196 }
197
UString(const QString & d)198 UString::UString(const QString &d)
199 {
200 if (d.length() > UString::maxUChars()) {
201 m_rep = &Rep::null;
202 return;
203 }
204
205 unsigned int len = d.length();
206 UChar *dat = static_cast<UChar *>(fastMalloc(sizeof(UChar) * len));
207 memcpy(dat, d.unicode(), len * sizeof(UChar));
208 m_rep = UString::Rep::create(dat, len);
209 }
210
UString(const DOM::DOMString & d)211 UString::UString(const DOM::DOMString &d)
212 {
213 if (d.isNull()) {
214 // we do a conversion here as null DOMStrings shouldn't cross
215 // the boundary to kjs. They should either be empty strings
216 // or explicitly converted to KJS::Null via getString().
217 m_rep = &Rep::empty;
218 return;
219 }
220 if (d.length() > UString::maxUChars()) {
221 m_rep = &Rep::null;
222 return;
223 }
224
225 unsigned int len = d.length();
226 UChar *dat = static_cast<UChar *>(fastMalloc(sizeof(UChar) * len));
227 memcpy(dat, d.unicode(), len * sizeof(UChar));
228 m_rep = UString::Rep::create(dat, len);
229 }
230
domString() const231 DOM::DOMString UString::domString() const
232 {
233 return DOM::DOMString((QChar *) data(), size());
234 }
235
qstring() const236 QString UString::qstring() const
237 {
238 return QString((QChar *) data(), size());
239 }
240
domString() const241 DOM::DOMString Identifier::domString() const
242 {
243 return DOM::DOMString((QChar *) data(), size());
244 }
245
qstring() const246 QString Identifier::qstring() const
247 {
248 return QString((QChar *) data(), size());
249 }
250
valueGetterAdapter(ExecState * exec,JSObject *,const Identifier &,const PropertySlot & slot)251 JSValue *valueGetterAdapter(ExecState *exec, JSObject *, const Identifier &, const PropertySlot &slot)
252 {
253 Q_UNUSED(exec);
254 return static_cast<JSValue *>(slot.customValue());
255 }
256
toNode(JSValue * val)257 DOM::NodeImpl *toNode(JSValue *val)
258 {
259 JSObject *obj = val->getObject();
260 if (!obj || !obj->inherits(&DOMNode::info)) {
261 return nullptr;
262 }
263
264 const DOMNode *dobj = static_cast<const DOMNode *>(obj);
265 return dobj->impl();
266 }
267
getStringOrNull(DOM::DOMString s)268 JSValue *getStringOrNull(DOM::DOMString s)
269 {
270 if (s.isNull()) {
271 return jsNull();
272 } else {
273 return jsString(s);
274 }
275 }
276
valueToStringWithNullCheck(ExecState * exec,JSValue * val)277 DOM::DOMString valueToStringWithNullCheck(ExecState *exec, JSValue *val)
278 {
279 if (val->isNull()) {
280 return DOM::DOMString();
281 }
282 return val->toString(exec).domString();
283 }
284
ValueToVariant(ExecState * exec,JSValue * val)285 QVariant ValueToVariant(ExecState *exec, JSValue *val)
286 {
287 QVariant res;
288 switch (val->type()) {
289 case BooleanType:
290 res = QVariant(val->toBoolean(exec));
291 break;
292 case NumberType:
293 res = QVariant(val->toNumber(exec));
294 break;
295 case StringType:
296 res = QVariant(val->toString(exec).qstring());
297 break;
298 default:
299 // everything else will be 'invalid'
300 break;
301 }
302 return res;
303 }
304
setDOMException(ExecState * exec,int internalCode)305 void setDOMException(ExecState *exec, int internalCode)
306 {
307 if (internalCode == 0 || exec->hadException()) {
308 return;
309 }
310
311 const char *type = nullptr;
312
313 DOMString name;
314 DOMString exceptionString;
315 JSObject *errorObject = nullptr;
316 int code = -1; // this will get the public exception code,
317 // as opposed to the internal one
318
319 // ### we should probably introduce classes for things other than range + core
320 if (DOM::RangeException::isRangeExceptionCode(internalCode)) {
321 type = "DOM Range";
322 code = internalCode - DOM::RangeException::_EXCEPTION_OFFSET;
323 name = DOM::RangeException::codeAsString(code);
324 errorObject = new RangeException(exec);
325 } else if (DOM::CSSException::isCSSExceptionCode(internalCode)) {
326 type = "CSS";
327 code = internalCode - DOM::CSSException::_EXCEPTION_OFFSET;
328 name = DOM::CSSException::codeAsString(code);
329 } else if (DOM::EventException::isEventExceptionCode(internalCode)) {
330 type = "DOM Events";
331 code = internalCode - DOM::EventException::_EXCEPTION_OFFSET;
332 name = DOM::EventException::codeAsString(code);
333 } else if (DOM::XPathException::isXPathExceptionCode(internalCode)) {
334 type = "XPath";
335 code = internalCode - DOM::XPathException::_EXCEPTION_OFFSET;
336 name = DOM::XPathException::codeAsString(code);
337 } else {
338 // Generic DOM.
339 type = "DOM";
340 code = internalCode;
341 name = DOM::DOMException::codeAsString(code);
342 errorObject = new JSDOMException(exec);
343 }
344
345 if (!errorObject) {
346 // 100 characters is a big enough buffer, because there are:
347 // 13 characters in the message
348 // 10 characters in the longest type, "DOM Events"
349 // 27 characters in the longest name, "NO_MODIFICATION_ALLOWED_ERR"
350 // 20 or so digits in the longest integer's ASCII form (even if int is 64-bit)
351 // 1 byte for a null character
352 // That adds up to about 70 bytes.
353 char buffer[100];
354
355 if (!name.isEmpty()) {
356 qsnprintf(buffer, 99, "%s: %s Exception %d", name.string().toLatin1().data(), type, code);
357 } else {
358 qsnprintf(buffer, 99, "%s Exception %d", type, code);
359 }
360 errorObject = throwError(exec, GeneralError, buffer);
361 } else {
362 exec->setException(errorObject);
363 }
364
365 errorObject->put(exec, exec->propertyNames().name, jsString(UString(type) + " Exception"));
366 errorObject->put(exec, exec->propertyNames().message, jsString(name));
367 errorObject->put(exec, "code", jsNumber(code));
368 }
369
valueToString(KJS::JSValue * value)370 QString valueToString(KJS::JSValue *value)
371 {
372 switch (value->type()) {
373 case KJS::NumberType: {
374 double v = 0.0;
375 value->getNumber(v);
376 return QString::number(v);
377 }
378 case KJS::BooleanType:
379 return value->getBoolean() ? "true" : "false";
380 case KJS::StringType: {
381 KJS::UString s;
382 value->getString(s);
383 return '"' + s.qstring() + '"';
384 }
385 case KJS::UndefinedType:
386 return "undefined";
387 case KJS::NullType:
388 return "null";
389 case KJS::ObjectType:
390 return "[object " + static_cast<KJS::JSObject *>(value)->className().qstring() + "]";
391 case KJS::GetterSetterType:
392 case KJS::UnspecifiedType:
393 default:
394 return QString();
395 }
396 }
397
exceptionToString(ExecState * exec,JSValue * exceptionObj)398 QString exceptionToString(ExecState *exec, JSValue *exceptionObj)
399 {
400 QString exceptionMsg = valueToString(exceptionObj);
401
402 // Since we purposefully bypass toString, we need to figure out
403 // string serialization ourselves.
404 //### might be easier to export class info for ErrorInstance ---
405
406 JSObject *valueObj = exceptionObj->getObject();
407 JSValue *protoObj = valueObj ? valueObj->prototype() : nullptr;
408
409 bool exception = false;
410 bool syntaxError = false;
411 if (protoObj == exec->lexicalInterpreter()->builtinSyntaxErrorPrototype()) {
412 exception = true;
413 syntaxError = true;
414 }
415
416 if (protoObj == exec->lexicalInterpreter()->builtinErrorPrototype() ||
417 protoObj == exec->lexicalInterpreter()->builtinEvalErrorPrototype() ||
418 protoObj == exec->lexicalInterpreter()->builtinReferenceErrorPrototype() ||
419 protoObj == exec->lexicalInterpreter()->builtinRangeErrorPrototype() ||
420 protoObj == exec->lexicalInterpreter()->builtinTypeErrorPrototype() ||
421 protoObj == exec->lexicalInterpreter()->builtinURIErrorPrototype()) {
422 exception = true;
423 }
424
425 if (!exception) {
426 return exceptionMsg;
427 }
428
429 // Clear exceptions temporarily so we can get/call a few things.
430 // We memorize the old exception first, of course. Note that
431 // This is not always the same as exceptionObj since we may be
432 // asked to translate a non-active exception
433 JSValue *oldExceptionObj = exec->exception();
434 exec->clearException();
435
436 // We want to serialize the syntax errors ourselves, to provide the line number.
437 // The URL is in "sourceURL" and the line is in "line"
438 // ### TODO: Perhaps we want to use 'sourceId' in case of eval contexts.
439 if (syntaxError) {
440 JSValue *lineValue = valueObj->get(exec, "line");
441 JSValue *urlValue = valueObj->get(exec, "sourceURL");
442
443 int line = lineValue->toNumber(exec);
444 QString url = urlValue->toString(exec).qstring();
445 exceptionMsg = i18n("Parse error at %1 line %2",
446 url.toHtmlEscaped(), line + 1);
447 } else {
448 // ### it's still not 100% safe to call toString here, even on
449 // native exception objects, since someone might have changed the toString property
450 // of the exception prototype, but I'll punt on this case for now.
451 exceptionMsg = exceptionObj->toString(exec).qstring();
452 }
453 exec->setException(oldExceptionObj);
454 return exceptionMsg;
455 }
456
457 } //namespace KJS
458