File indexing completed on 2024-04-28 11:37:52

0001 /*
0002  *  This file is part of the KDE libraries
0003  *  Copyright (C) 1999-2003 Harri Porten (porten@kde.org)
0004  *  Copyright (C) 2001-2003 David Faure (faure@kde.org)
0005  *  Copyright (C) 2003 Apple Computer, Inc.
0006  *
0007  *  This library is free software; you can redistribute it and/or
0008  *  modify it under the terms of the GNU Library General Public
0009  *  License as published by the Free Software Foundation; either
0010  *  version 2 of the License, or (at your option) any later version.
0011  *
0012  *  This library is distributed in the hope that it will be useful,
0013  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
0014  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0015  *  Library General Public License for more details.
0016  *
0017  *  You should have received a copy of the GNU Library General Public
0018  *  License along with this library; if not, write to the Free Software
0019  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
0020  */
0021 
0022 #include "kjs_binding.h"
0023 
0024 #include <config-khtml.h>
0025 #if HAVE_VALGRIND_MEMCHECK_H
0026 
0027 #include <valgrind/memcheck.h>
0028 #define VALGRIND_SUPPORT
0029 
0030 #endif
0031 
0032 #include "kjs_dom.h"
0033 #include "kjs_range.h"
0034 
0035 #include <dom/css_stylesheet.h>
0036 #include <dom/dom_exception.h>
0037 #include <dom/dom2_range.h>
0038 #include <dom/dom3_xpath.h>
0039 #include <xml/dom2_eventsimpl.h>
0040 #include <khtmlpart_p.h>
0041 
0042 #include "khtml_debug.h"
0043 #include <kparts/browserextension.h>
0044 #include <kmessagebox.h>
0045 #include <QTextDocument> // Qt::escape
0046 
0047 #ifdef KJS_DEBUGGER
0048 #include "debugger/debugwindow.h"
0049 #endif
0050 
0051 #include <QList>
0052 
0053 #include <assert.h>
0054 
0055 using namespace KJSDebugger;
0056 
0057 namespace KJS
0058 {
0059 
0060 UString DOMObject::toString(ExecState *) const
0061 {
0062     return "[object " + className() + "]";
0063 }
0064 
0065 HashMap<void *, DOMObject *> *ScriptInterpreter::s_allDomObjects;
0066 
0067 typedef QList<ScriptInterpreter *> InterpreterList;
0068 static InterpreterList *interpreterList;
0069 
0070 ScriptInterpreter::ScriptInterpreter(JSGlobalObject *global, khtml::ChildFrame *frame)
0071     : Interpreter(global), m_frame(frame),
0072       m_evt(nullptr), m_inlineCode(false), m_timerCallback(false)
0073 {
0074 #ifdef KJS_VERBOSE
0075     qCDebug(KHTML_LOG) << "ScriptInterpreter::ScriptInterpreter " << this << " for part=" << m_frame;
0076 #endif
0077     if (!interpreterList) {
0078         interpreterList = new InterpreterList;
0079     }
0080     interpreterList->append(this);
0081 }
0082 
0083 ScriptInterpreter::~ScriptInterpreter()
0084 {
0085 #ifdef KJS_VERBOSE
0086     qCDebug(KHTML_LOG) << "ScriptInterpreter::~ScriptInterpreter " << this << " for part=" << m_frame;
0087 #endif
0088     assert(interpreterList && interpreterList->contains(this));
0089     interpreterList->removeAll(this);
0090     if (interpreterList->isEmpty()) {
0091         delete interpreterList;
0092         interpreterList = nullptr;
0093     }
0094 }
0095 
0096 void ScriptInterpreter::forgetDOMObject(void *objectHandle)
0097 {
0098     if (!interpreterList) {
0099         return;
0100     }
0101 
0102     for (int i = 0; i < interpreterList->size(); ++i) {
0103         interpreterList->at(i)->m_domObjects.remove(objectHandle);
0104     }
0105     allDomObjects()->remove(objectHandle);
0106 }
0107 
0108 void ScriptInterpreter::mark(bool isMain)
0109 {
0110     Interpreter::mark(isMain);
0111 #ifdef KJS_VERBOSE
0112     qCDebug(KHTML_LOG) << "ScriptInterpreter::mark " << this << " marking " << m_domObjects.size() << " DOM objects";
0113 #endif
0114     HashMap<void *, DOMObject *>::iterator it = m_domObjects.begin();
0115     while (it != m_domObjects.end()) {
0116         DOMObject *obj = it->second;
0117         if (obj->shouldMark()) {
0118             obj->mark();
0119         }
0120         ++it;
0121     }
0122 }
0123 
0124 KParts::ReadOnlyPart *ScriptInterpreter::part() const
0125 {
0126     return m_frame->m_part.data();
0127 }
0128 
0129 bool ScriptInterpreter::isWindowOpenAllowed() const
0130 {
0131     if (m_evt) {
0132         int id = m_evt->handle()->id();
0133         bool eventOk = ( // mouse events
0134                            id == DOM::EventImpl::CLICK_EVENT ||
0135                            id == DOM::EventImpl::MOUSEUP_EVENT || id == DOM::EventImpl::MOUSEDOWN_EVENT ||
0136                            id == DOM::EventImpl::KHTML_ECMA_CLICK_EVENT || id == DOM::EventImpl::KHTML_ECMA_DBLCLICK_EVENT ||
0137                            // keyboard events
0138                            id == DOM::EventImpl::KEYDOWN_EVENT || id == DOM::EventImpl::KEYPRESS_EVENT ||
0139                            id == DOM::EventImpl::KEYUP_EVENT ||
0140                            // other accepted events
0141                            id == DOM::EventImpl::SELECT_EVENT || id == DOM::EventImpl::CHANGE_EVENT ||
0142                            id == DOM::EventImpl::SUBMIT_EVENT);
0143         // qCDebug(KHTML_LOG) << "Window.open, smart policy: id=" << id << " eventOk=" << eventOk;
0144         if (eventOk) {
0145             return true;
0146         }
0147     } else { // no event
0148         if (m_inlineCode && !m_timerCallback) {
0149             // This is the <a href="javascript:window.open('...')> case -> we let it through
0150             return true;
0151             // qCDebug(KHTML_LOG) << "Window.open, smart policy, no event, inline code -> ok";
0152         } else { // This is the <script>window.open(...)</script> case or a timer callback -> block it
0153             // qCDebug(KHTML_LOG) << "Window.open, smart policy, no event, <script> tag -> refused";
0154         }
0155     }
0156     return false;
0157 }
0158 
0159 bool ScriptInterpreter::s_disableCPUGuard = false;
0160 
0161 void ScriptInterpreter::startCPUGuard()
0162 {
0163     if (s_disableCPUGuard) {
0164         return;
0165     }
0166 
0167     unsigned time = 5000;
0168 #ifdef VALGRIND_SUPPORT
0169     if (RUNNING_ON_VALGRIND) {
0170         time *= 50;
0171     }
0172 #endif
0173 
0174     setTimeoutTime(time);
0175     startTimeoutCheck();
0176 }
0177 
0178 void ScriptInterpreter::stopCPUGuard()
0179 {
0180     if (s_disableCPUGuard) {
0181         return;
0182     }
0183     stopTimeoutCheck();
0184 }
0185 
0186 bool ScriptInterpreter::shouldInterruptScript() const
0187 {
0188 #ifdef KJS_DEBUGGER
0189     if (DebugWindow::isBlocked()) {
0190         return false;
0191     }
0192 #endif
0193 
0194     // qCDebug(KHTML_LOG) << "alarmhandler";
0195     return KMessageBox::warningTwoActions(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::PrimaryAction;
0196 }
0197 
0198 UString::UString(const QString &d)
0199 {
0200     if (d.length() > UString::maxUChars()) {
0201         m_rep = &Rep::null;
0202         return;
0203     }
0204 
0205     unsigned int len = d.length();
0206     UChar *dat = static_cast<UChar *>(fastMalloc(sizeof(UChar) * len));
0207     memcpy(dat, d.unicode(), len * sizeof(UChar));
0208     m_rep = UString::Rep::create(dat, len);
0209 }
0210 
0211 UString::UString(const DOM::DOMString &d)
0212 {
0213     if (d.isNull()) {
0214         // we do a conversion here as null DOMStrings shouldn't cross
0215         // the boundary to kjs. They should either be empty strings
0216         // or explicitly converted to KJS::Null via getString().
0217         m_rep = &Rep::empty;
0218         return;
0219     }
0220     if (d.length() > UString::maxUChars()) {
0221         m_rep = &Rep::null;
0222         return;
0223     }
0224 
0225     unsigned int len = d.length();
0226     UChar *dat = static_cast<UChar *>(fastMalloc(sizeof(UChar) * len));
0227     memcpy(dat, d.unicode(), len * sizeof(UChar));
0228     m_rep = UString::Rep::create(dat, len);
0229 }
0230 
0231 DOM::DOMString UString::domString() const
0232 {
0233     return DOM::DOMString((QChar *) data(), size());
0234 }
0235 
0236 QString UString::qstring() const
0237 {
0238     return QString((QChar *) data(), size());
0239 }
0240 
0241 DOM::DOMString Identifier::domString() const
0242 {
0243     return DOM::DOMString((QChar *) data(), size());
0244 }
0245 
0246 QString Identifier::qstring() const
0247 {
0248     return QString((QChar *) data(), size());
0249 }
0250 
0251 JSValue *valueGetterAdapter(ExecState *exec, JSObject *, const Identifier &, const PropertySlot &slot)
0252 {
0253     Q_UNUSED(exec);
0254     return static_cast<JSValue *>(slot.customValue());
0255 }
0256 
0257 DOM::NodeImpl *toNode(JSValue *val)
0258 {
0259     JSObject *obj = val->getObject();
0260     if (!obj || !obj->inherits(&DOMNode::info)) {
0261         return nullptr;
0262     }
0263 
0264     const DOMNode *dobj = static_cast<const DOMNode *>(obj);
0265     return dobj->impl();
0266 }
0267 
0268 JSValue *getStringOrNull(DOM::DOMString s)
0269 {
0270     if (s.isNull()) {
0271         return jsNull();
0272     } else {
0273         return jsString(s);
0274     }
0275 }
0276 
0277 DOM::DOMString valueToStringWithNullCheck(ExecState *exec, JSValue *val)
0278 {
0279     if (val->isNull()) {
0280         return DOM::DOMString();
0281     }
0282     return val->toString(exec).domString();
0283 }
0284 
0285 QVariant ValueToVariant(ExecState *exec, JSValue *val)
0286 {
0287     QVariant res;
0288     switch (val->type()) {
0289     case BooleanType:
0290         res = QVariant(val->toBoolean(exec));
0291         break;
0292     case NumberType:
0293         res = QVariant(val->toNumber(exec));
0294         break;
0295     case StringType:
0296         res = QVariant(val->toString(exec).qstring());
0297         break;
0298     default:
0299         // everything else will be 'invalid'
0300         break;
0301     }
0302     return res;
0303 }
0304 
0305 void setDOMException(ExecState *exec, int internalCode)
0306 {
0307     if (internalCode == 0 || exec->hadException()) {
0308         return;
0309     }
0310 
0311     const char *type = nullptr;
0312 
0313     DOMString name;
0314     DOMString exceptionString;
0315     JSObject *errorObject = nullptr;
0316     int code = -1; // this will get the public exception code,
0317     // as opposed to the internal one
0318 
0319     // ### we should probably introduce classes for things other than range + core
0320     if (DOM::RangeException::isRangeExceptionCode(internalCode)) {
0321         type = "DOM Range";
0322         code = internalCode - DOM::RangeException::_EXCEPTION_OFFSET;
0323         name = DOM::RangeException::codeAsString(code);
0324         errorObject = new RangeException(exec);
0325     } else if (DOM::CSSException::isCSSExceptionCode(internalCode)) {
0326         type = "CSS";
0327         code = internalCode - DOM::CSSException::_EXCEPTION_OFFSET;
0328         name = DOM::CSSException::codeAsString(code);
0329     } else if (DOM::EventException::isEventExceptionCode(internalCode)) {
0330         type = "DOM Events";
0331         code = internalCode - DOM::EventException::_EXCEPTION_OFFSET;
0332         name = DOM::EventException::codeAsString(code);
0333     } else if (DOM::XPathException::isXPathExceptionCode(internalCode)) {
0334         type = "XPath";
0335         code = internalCode - DOM::XPathException::_EXCEPTION_OFFSET;
0336         name = DOM::XPathException::codeAsString(code);
0337     } else {
0338         // Generic DOM.
0339         type = "DOM";
0340         code = internalCode;
0341         name = DOM::DOMException::codeAsString(code);
0342         errorObject = new JSDOMException(exec);
0343     }
0344 
0345     if (!errorObject) {
0346         // 100 characters is a big enough buffer, because there are:
0347         //   13 characters in the message
0348         //   10 characters in the longest type, "DOM Events"
0349         //   27 characters in the longest name, "NO_MODIFICATION_ALLOWED_ERR"
0350         //   20 or so digits in the longest integer's ASCII form (even if int is 64-bit)
0351         //   1 byte for a null character
0352         // That adds up to about 70 bytes.
0353         char buffer[100];
0354 
0355         if (!name.isEmpty()) {
0356             qsnprintf(buffer, 99, "%s: %s Exception %d", name.string().toLatin1().data(), type, code);
0357         } else {
0358             qsnprintf(buffer, 99, "%s Exception %d", type, code);
0359         }
0360         errorObject = throwError(exec, GeneralError, buffer);
0361     } else {
0362         exec->setException(errorObject);
0363     }
0364 
0365     errorObject->put(exec, exec->propertyNames().name, jsString(UString(type) + " Exception"));
0366     errorObject->put(exec, exec->propertyNames().message, jsString(name));
0367     errorObject->put(exec, "code", jsNumber(code));
0368 }
0369 
0370 QString valueToString(KJS::JSValue *value)
0371 {
0372     switch (value->type()) {
0373     case KJS::NumberType: {
0374         double v = 0.0;
0375         value->getNumber(v);
0376         return QString::number(v);
0377     }
0378     case KJS::BooleanType:
0379         return value->getBoolean() ? "true" : "false";
0380     case KJS::StringType: {
0381         KJS::UString s;
0382         value->getString(s);
0383         return '"' + s.qstring() + '"';
0384     }
0385     case KJS::UndefinedType:
0386         return "undefined";
0387     case KJS::NullType:
0388         return "null";
0389     case KJS::ObjectType:
0390         return "[object " + static_cast<KJS::JSObject *>(value)->className().qstring() + "]";
0391     case KJS::GetterSetterType:
0392     case KJS::UnspecifiedType:
0393     default:
0394         return QString();
0395     }
0396 }
0397 
0398 QString exceptionToString(ExecState *exec, JSValue *exceptionObj)
0399 {
0400     QString exceptionMsg = valueToString(exceptionObj);
0401 
0402     // Since we purposefully bypass toString, we need to figure out
0403     // string serialization ourselves.
0404     //### might be easier to export class info for ErrorInstance ---
0405 
0406     JSObject *valueObj = exceptionObj->getObject();
0407     JSValue  *protoObj = valueObj ? valueObj->prototype() : nullptr;
0408 
0409     bool exception   = false;
0410     bool syntaxError = false;
0411     if (protoObj == exec->lexicalInterpreter()->builtinSyntaxErrorPrototype()) {
0412         exception   = true;
0413         syntaxError = true;
0414     }
0415 
0416     if (protoObj == exec->lexicalInterpreter()->builtinErrorPrototype()          ||
0417             protoObj == exec->lexicalInterpreter()->builtinEvalErrorPrototype()      ||
0418             protoObj == exec->lexicalInterpreter()->builtinReferenceErrorPrototype() ||
0419             protoObj == exec->lexicalInterpreter()->builtinRangeErrorPrototype()     ||
0420             protoObj == exec->lexicalInterpreter()->builtinTypeErrorPrototype()      ||
0421             protoObj == exec->lexicalInterpreter()->builtinURIErrorPrototype()) {
0422         exception = true;
0423     }
0424 
0425     if (!exception) {
0426         return exceptionMsg;
0427     }
0428 
0429     // Clear exceptions temporarily so we can get/call a few things.
0430     // We memorize the old exception first, of course. Note that
0431     // This is not always the same as exceptionObj since we may be
0432     //  asked to translate a non-active exception
0433     JSValue *oldExceptionObj = exec->exception();
0434     exec->clearException();
0435 
0436     // We want to serialize the syntax errors ourselves, to provide the line number.
0437     // The URL is in "sourceURL" and the line is in "line"
0438     // ### TODO: Perhaps we want to use 'sourceId' in case of eval contexts.
0439     if (syntaxError) {
0440         JSValue *lineValue = valueObj->get(exec, "line");
0441         JSValue *urlValue  = valueObj->get(exec, "sourceURL");
0442 
0443         int      line = lineValue->toNumber(exec);
0444         QString  url  = urlValue->toString(exec).qstring();
0445         exceptionMsg = i18n("Parse error at %1 line %2",
0446                             url.toHtmlEscaped(), line + 1);
0447     } else {
0448         // ### it's still not 100% safe to call toString here, even on
0449         // native exception objects, since someone might have changed the toString property
0450         // of the exception prototype, but I'll punt on this case for now.
0451         exceptionMsg = exceptionObj->toString(exec).qstring();
0452     }
0453     exec->setException(oldExceptionObj);
0454     return exceptionMsg;
0455 }
0456 
0457 } //namespace KJS