#include "config.h"
#include "qwebelement.h"
#include "CSSComputedStyleDeclaration.h"
#include "CSSMutableStyleDeclaration.h"
#include "CSSParser.h"
#include "CSSRuleList.h"
#include "CSSRule.h"
#include "CSSStyleRule.h"
#include "CString.h"
#include "Document.h"
#include "DocumentFragment.h"
#include "FrameView.h"
#include "HTMLElement.h"
#include "JSGlobalObject.h"
#include "JSHTMLElement.h"
#include "JSObject.h"
#include "NodeList.h"
#include "PropertyNameArray.h"
#include "ScriptFunctionCall.h"
#include "StaticNodeList.h"
#include "qt_runtime.h"
#include "qwebframe.h"
#include "qwebframe_p.h"
#include "runtime_root.h"
#include <wtf/Vector.h>
using namespace WebCore;
class QWebElementPrivate
{
public:
};
QWebElement::QWebElement()
: d(0)
, m_element(0)
{
}
QWebElement::QWebElement(WebCore::Element* domElement)
: d(0)
, m_element(domElement)
{
if (m_element)
m_element->ref();
}
QWebElement::QWebElement(WebCore::Node* node)
: d(0)
, m_element(0)
{
if (node && node->isHTMLElement()) {
m_element = static_cast<HTMLElement*>(node);
m_element->ref();
}
}
QWebElement::QWebElement(const QWebElement &other)
: d(0)
, m_element(other.m_element)
{
if (m_element)
m_element->ref();
}
QWebElement &QWebElement::operator=(const QWebElement &other)
{
if (this != &other) {
Element *otherElement = other.m_element;
if (otherElement)
otherElement->ref();
if (m_element)
m_element->deref();
m_element = otherElement;
}
return *this;
}
QWebElement::~QWebElement()
{
delete d;
if (m_element)
m_element->deref();
}
bool QWebElement::operator==(const QWebElement& o) const
{
return m_element == o.m_element;
}
bool QWebElement::operator!=(const QWebElement& o) const
{
return m_element != o.m_element;
}
bool QWebElement::isNull() const
{
return !m_element;
}
QList<QWebElement> QWebElement::findAll(const QString &selectorQuery) const
{
QList<QWebElement> elements;
if (!m_element)
return elements;
ExceptionCode exception = 0; RefPtr<NodeList> nodes = m_element->querySelectorAll(selectorQuery, exception);
if (!nodes)
return elements;
for (int i = 0; i < nodes->length(); ++i) {
WebCore::Node* n = nodes->item(i);
elements.append(QWebElement(static_cast<Element*>(n)));
}
return elements;
}
QWebElement QWebElement::findFirst(const QString &selectorQuery) const
{
if (!m_element)
return QWebElement();
ExceptionCode exception = 0; return QWebElement(m_element->querySelector(selectorQuery, exception).get());
}
void QWebElement::setPlainText(const QString &text)
{
if (!m_element || !m_element->isHTMLElement())
return;
ExceptionCode exception = 0;
static_cast<HTMLElement*>(m_element)->setInnerText(text, exception);
}
QString QWebElement::toPlainText() const
{
if (!m_element || !m_element->isHTMLElement())
return QString();
return static_cast<HTMLElement*>(m_element)->innerText();
}
void QWebElement::setOuterXml(const QString &markup)
{
if (!m_element || !m_element->isHTMLElement())
return;
ExceptionCode exception = 0;
static_cast<HTMLElement*>(m_element)->setOuterHTML(markup, exception);
}
QString QWebElement::toOuterXml() const
{
if (!m_element || !m_element->isHTMLElement())
return QString();
return static_cast<HTMLElement*>(m_element)->outerHTML();
}
void QWebElement::setInnerXml(const QString &markup)
{
if (!m_element || !m_element->isHTMLElement())
return;
ExceptionCode exception = 0;
static_cast<HTMLElement*>(m_element)->setInnerHTML(markup, exception);
}
QString QWebElement::toInnerXml() const
{
if (!m_element || !m_element->isHTMLElement())
return QString();
return static_cast<HTMLElement*>(m_element)->innerHTML();
}
void QWebElement::setAttribute(const QString &name, const QString &value)
{
if (!m_element)
return;
ExceptionCode exception = 0;
m_element->setAttribute(name, value, exception);
}
void QWebElement::setAttributeNS(const QString &namespaceUri, const QString &name, const QString &value)
{
if (!m_element)
return;
WebCore::ExceptionCode exception = 0;
m_element->setAttributeNS(namespaceUri, name, value, exception);
}
QString QWebElement::attribute(const QString &name, const QString &defaultValue) const
{
if (!m_element)
return QString();
if (m_element->hasAttribute(name))
return m_element->getAttribute(name);
else
return defaultValue;
}
QString QWebElement::attributeNS(const QString &namespaceUri, const QString &name, const QString &defaultValue) const
{
if (!m_element)
return QString();
if (m_element->hasAttributeNS(namespaceUri, name))
return m_element->getAttributeNS(namespaceUri, name);
else
return defaultValue;
}
bool QWebElement::hasAttribute(const QString &name) const
{
if (!m_element)
return false;
return m_element->hasAttribute(name);
}
bool QWebElement::hasAttributeNS(const QString &namespaceUri, const QString &name) const
{
if (!m_element)
return false;
return m_element->hasAttributeNS(namespaceUri, name);
}
void QWebElement::removeAttribute(const QString &name)
{
if (!m_element)
return;
ExceptionCode exception = 0;
m_element->removeAttribute(name, exception);
}
void QWebElement::removeAttributeNS(const QString &namespaceUri, const QString &name)
{
if (!m_element)
return;
WebCore::ExceptionCode exception = 0;
m_element->removeAttributeNS(namespaceUri, name, exception);
}
bool QWebElement::hasAttributes() const
{
if (!m_element)
return false;
return m_element->hasAttributes();
}
QRect QWebElement::geometry() const
{
if (!m_element)
return QRect();
return m_element->getRect();
}
QString QWebElement::tagName() const
{
if (!m_element)
return QString();
return m_element->tagName();
}
QString QWebElement::prefix() const
{
if (!m_element)
return QString();
return m_element->prefix();
}
QString QWebElement::localName() const
{
if (!m_element)
return QString();
return m_element->localName();
}
QString QWebElement::namespaceUri() const
{
if (!m_element)
return QString();
return m_element->namespaceURI();
}
QWebElement QWebElement::parent() const
{
if (m_element)
return QWebElement(m_element->parentElement());
return QWebElement();
}
QWebElement QWebElement::firstChild() const
{
if (!m_element)
return QWebElement();
for (Node* child = m_element->firstChild(); child; child = child->nextSibling()) {
if (!child->isElementNode())
continue;
Element* e = static_cast<Element*>(child);
return QWebElement(e);
}
return QWebElement();
}
QWebElement QWebElement::lastChild() const
{
if (!m_element)
return QWebElement();
for (Node* child = m_element->lastChild(); child; child = child->previousSibling()) {
if (!child->isElementNode())
continue;
Element* e = static_cast<Element*>(child);
return QWebElement(e);
}
return QWebElement();
}
QWebElement QWebElement::nextSibling() const
{
if (!m_element)
return QWebElement();
for (Node* sib = m_element->nextSibling(); sib; sib = sib->nextSibling()) {
if (!sib->isElementNode())
continue;
Element* e = static_cast<Element*>(sib);
return QWebElement(e);
}
return QWebElement();
}
QWebElement QWebElement::previousSibling() const
{
if (!m_element)
return QWebElement();
for (Node* sib = m_element->previousSibling(); sib; sib = sib->previousSibling()) {
if (!sib->isElementNode())
continue;
Element* e = static_cast<Element*>(sib);
return QWebElement(e);
}
return QWebElement();
}
QWebElement QWebElement::document() const
{
if (!m_element)
return QWebElement();
Document* document = m_element->document();
if (!document)
return QWebElement();
return QWebElement(document->documentElement());
}
QWebFrame *QWebElement::webFrame() const
{
if (!m_element)
return 0;
Document* document = m_element->document();
if (!document)
return 0;
Frame* frame = document->frame();
if (!frame)
return 0;
return QWebFramePrivate::kit(frame);
}
static bool setupScriptContext(WebCore::Element* element, JSC::JSValue& thisValue, ScriptState*& state, ScriptController*& scriptController)
{
if (!element)
return false;
Document* document = element->document();
if (!document)
return false;
Frame* frame = document->frame();
if (!frame)
return false;
scriptController = frame->script();
if (!scriptController)
return false;
state = scriptController->globalObject()->globalExec();
if (!state)
return false;
thisValue = toJS(state, element);
if (!thisValue)
return false;
return true;
}
static bool setupScriptObject(WebCore::Element* element, ScriptObject& object, ScriptState*& state, ScriptController*& scriptController)
{
if (!element)
return false;
Document* document = element->document();
if (!document)
return false;
Frame* frame = document->frame();
if (!frame)
return false;
scriptController = frame->script();
state = scriptController->globalObject()->globalExec();
JSC::JSValue thisValue = toJS(state, element);
if (!thisValue)
return false;
JSC::JSObject* thisObject = thisValue.toObject(state);
if (!thisObject)
return false;
object = ScriptObject(thisObject);
return true;
}
QVariant QWebElement::evaluateScript(const QString& scriptSource)
{
if (scriptSource.isEmpty())
return QVariant();
ScriptState* state = 0;
JSC::JSValue thisValue;
ScriptController* scriptController = 0;
if (!setupScriptContext(m_element, thisValue, state, scriptController))
return QVariant();
JSC::ScopeChain& scopeChain = state->dynamicGlobalObject()->globalScopeChain();
JSC::UString script((const UChar*)scriptSource.data(), scriptSource.length());
JSC::Completion completion = JSC::evaluate(state, scopeChain, JSC::makeSource(script), thisValue);
if ((completion.complType() != JSC::ReturnValue) && (completion.complType() != JSC::Normal))
return QVariant();
JSC::JSValue result = completion.value();
if (!result)
return QVariant();
int distance = 0;
return JSC::Bindings::convertValueToQVariant(state, result, QMetaType::Void, &distance);
}
QVariant QWebElement::callFunction(const QString &name, const QVariantList &arguments)
{
ScriptState* state = 0;
ScriptObject thisObject;
ScriptController* scriptController = 0;
if (!setupScriptObject(m_element, thisObject, state, scriptController))
return QVariant();
ScriptFunctionCall functionCall(state, thisObject, name);
for (QVariantList::ConstIterator it = arguments.constBegin(), end = arguments.constEnd();
it != end; ++it)
functionCall.appendArgument(JSC::Bindings::convertQVariantToValue(state, scriptController->bindingRootObject(), *it));
bool hadException = false;
ScriptValue result = functionCall.call(hadException);
if (hadException)
return QVariant();
int distance = 0;
return JSC::Bindings::convertValueToQVariant(state, result.jsValue(), QMetaType::Void, &distance);
}
QStringList QWebElement::functions() const
{
ScriptState* state = 0;
ScriptObject thisObject;
ScriptController* scriptController = 0;
if (!setupScriptObject(m_element, thisObject, state, scriptController))
return QStringList();
JSC::JSObject* object = thisObject.jsObject();
if (!object)
return QStringList();
QStringList names;
JSC::PropertyNameArray properties(state);
object->getPropertyNames(state, properties);
for (JSC::PropertyNameArray::const_iterator it = properties.begin();
it != properties.end(); ++it) {
JSC::JSValue property = object->get(state, *it);
if (!property)
continue;
JSC::JSObject* function = property.toObject(state);
if (!function)
continue;
JSC::CallData callData;
JSC::CallType callType = function->getCallData(callData);
if (callType == JSC::CallTypeNone)
continue;
JSC::UString ustring = (*it).ustring();
names << QString::fromUtf16((const ushort*)ustring.rep()->data(),ustring.size());
}
if (state->hadException())
state->clearException();
return names;
}
QVariant QWebElement::scriptableProperty(const QString &name) const
{
ScriptState* state = 0;
ScriptObject thisObject;
ScriptController *scriptController = 0;
if (!setupScriptObject(m_element, thisObject, state, scriptController))
return QVariant();
String wcName(name);
JSC::JSValue property = thisObject.jsObject()->get(state, JSC::Identifier(state, wcName));
if (state->hadException())
state->clearException();
int distance = 0;
return JSC::Bindings::convertValueToQVariant(state, property, QMetaType::Void, &distance);
}
void QWebElement::setScriptableProperty(const QString &name, const QVariant &value)
{
ScriptState* state = 0;
ScriptObject thisObject;
ScriptController* scriptController = 0;
if (!setupScriptObject(m_element, thisObject, state, scriptController))
return;
JSC::JSValue jsValue = JSC::Bindings::convertQVariantToValue(state, scriptController->bindingRootObject(), value);
if (!jsValue)
return;
String wcName(name);
JSC::PutPropertySlot slot;
thisObject.jsObject()->put(state, JSC::Identifier(state, wcName), jsValue, slot);
if (state->hadException())
state->clearException();
}
QStringList QWebElement::scriptableProperties() const
{
if (!m_element)
return QStringList();
Document* document = m_element->document();
if (!document)
return QStringList();
Frame* frame = document->frame();
if (!frame)
return QStringList();
ScriptController* script = frame->script();
JSC::ExecState* exec = script->globalObject()->globalExec();
JSC::JSValue thisValue = toJS(exec, m_element);
if (!thisValue)
return QStringList();
JSC::JSObject* object = thisValue.toObject(exec);
if (!object)
return QStringList();
QStringList names;
JSC::PropertyNameArray properties(exec);
object->getPropertyNames(exec, properties);
for (JSC::PropertyNameArray::const_iterator it = properties.begin();
it != properties.end(); ++it) {
JSC::JSValue property = object->get(exec, *it);
if (!property)
continue;
JSC::JSObject* function = property.toObject(exec);
if (!function)
continue;
JSC::CallData callData;
JSC::CallType callType = function->getCallData(callData);
if (callType != JSC::CallTypeNone)
continue;
JSC::UString ustring = (*it).ustring();
names << QString::fromUtf16((const ushort*)ustring.rep()->data(),ustring.size());
}
if (exec->hadException())
exec->clearException();
return names;
}
QString QWebElement::styleProperty(const QString &name, ResolveRule rule) const
{
if (!m_element || !m_element->isStyledElement())
return QString();
int propID = cssPropertyID(name);
if (!propID)
return QString();
CSSStyleDeclaration* style = static_cast<StyledElement*>(m_element)->style();
if (rule == IgnoreCascadingStyles)
return style->getPropertyValue(propID);
if (rule == RespectCascadingStyles) {
if (style->getPropertyPriority(propID))
return style->getPropertyValue(propID);
DOMWindow* domWindow = m_element->document()->frame()->domWindow();
if (RefPtr<CSSRuleList> rules = domWindow->getMatchedCSSRules(m_element, "")) {
for (int i = rules->length(); i > 0; --i) {
CSSStyleRule* rule = static_cast<CSSStyleRule*>(rules->item(i - 1));
if (rule->style()->getPropertyPriority(propID))
return rule->style()->getPropertyValue(propID);
if (style->getPropertyValue(propID).isEmpty())
style = rule->style();
}
}
return style->getPropertyValue(propID);
}
return QString();
}
void QWebElement::setStyleProperty(const QString &name, const QString &value, StylePriority priority)
{
if (!m_element || !m_element->isStyledElement())
return;
int propID = cssPropertyID(name);
CSSStyleDeclaration* style = static_cast<StyledElement*>(m_element)->style();
if (!propID || !style)
return;
ExceptionCode exception = 0;
const QRegExp hasImportantTest(QLatin1String("!\\s*important"));
int index = value.indexOf(hasImportantTest);
QString newValue = (index != -1) ? value.left(index - 1) : value;
switch (priority) {
case NormalStylePriority:
style->setProperty(name, newValue, "", exception);
break;
case DeclaredStylePriority:
style->setProperty(name, newValue, (index != -1) ? "important" : "", exception);
break;
case ImportantStylePriority:
style->setProperty(name, newValue, "important", exception);
break;
default:
break;
}
}
QString QWebElement::computedStyleProperty(const QString &name) const
{
if (!m_element || !m_element->isStyledElement())
return QString();
int propID = cssPropertyID(name);
RefPtr<CSSComputedStyleDeclaration> style = computedStyle(m_element);
if (!propID || !style)
return QString();
return style->getPropertyValue(propID);
}
QStringList QWebElement::classes() const
{
if (!hasAttribute(QLatin1String("class")))
return QStringList();
QStringList classes = attribute(QLatin1String("class")).simplified().split(QLatin1Char(' '), QString::SkipEmptyParts);
#if QT_VERSION >= 0x040500
classes.removeDuplicates();
#else
int n = classes.size();
int j = 0;
QSet<QString> seen;
seen.reserve(n);
for (int i = 0; i < n; ++i) {
const QString& s = classes.at(i);
if (seen.contains(s))
continue;
seen.insert(s);
if (j != i)
classes[j] = s;
++j;
}
if (n != j)
classes.erase(classes.begin() + j, classes.end());
#endif
return classes;
}
bool QWebElement::hasClass(const QString &name) const
{
QStringList list = classes();
return list.contains(name);
}
void QWebElement::addClass(const QString &name)
{
QStringList list = classes();
if (!list.contains(name)) {
list.append(name);
QString value = list.join(QLatin1String(" "));
setAttribute(QLatin1String("class"), value);
}
}
void QWebElement::removeClass(const QString &name)
{
QStringList list = classes();
if (list.contains(name)) {
list.removeAll(name);
QString value = list.join(QLatin1String(" "));
setAttribute(QLatin1String("class"), value);
}
}
void QWebElement::toggleClass(const QString &name)
{
QStringList list = classes();
if (list.contains(name))
list.removeAll(name);
else
list.append(name);
QString value = list.join(QLatin1String(" "));
setAttribute(QLatin1String("class"), value);
}
void QWebElement::appendInside(const QWebElement &element)
{
if (!m_element || element.isNull())
return;
ExceptionCode exception = 0;
m_element->appendChild(element.m_element, exception);
}
void QWebElement::appendInside(const QString &markup)
{
if (!m_element)
return;
if (!m_element->isHTMLElement())
return;
HTMLElement* htmlElement = static_cast<HTMLElement*>(m_element);
RefPtr<DocumentFragment> fragment = htmlElement->createContextualFragment(markup);
ExceptionCode exception = 0;
m_element->appendChild(fragment, exception);
}
void QWebElement::prependInside(const QWebElement &element)
{
if (!m_element || element.isNull())
return;
ExceptionCode exception = 0;
if (m_element->hasChildNodes())
m_element->insertBefore(element.m_element, m_element->firstChild(), exception);
else
m_element->appendChild(element.m_element, exception);
}
void QWebElement::prependInside(const QString &markup)
{
if (!m_element)
return;
if (!m_element->isHTMLElement())
return;
HTMLElement* htmlElement = static_cast<HTMLElement*>(m_element);
RefPtr<DocumentFragment> fragment = htmlElement->createContextualFragment(markup);
ExceptionCode exception = 0;
if (m_element->hasChildNodes())
m_element->insertBefore(fragment, m_element->firstChild(), exception);
else
m_element->appendChild(fragment, exception);
}
void QWebElement::prependOutside(const QWebElement &element)
{
if (!m_element || element.isNull())
return;
if (!m_element->parent())
return;
ExceptionCode exception = 0;
m_element->parent()->insertBefore(element.m_element, m_element, exception);
}
void QWebElement::prependOutside(const QString &markup)
{
if (!m_element)
return;
if (!m_element->parent())
return;
if (!m_element->isHTMLElement())
return;
HTMLElement* htmlElement = static_cast<HTMLElement*>(m_element);
RefPtr<DocumentFragment> fragment = htmlElement->createContextualFragment(markup);
ExceptionCode exception = 0;
m_element->parent()->insertBefore(fragment, m_element, exception);
}
void QWebElement::appendOutside(const QWebElement &element)
{
if (!m_element || element.isNull())
return;
if (!m_element->parent())
return;
ExceptionCode exception = 0;
if (!m_element->nextSibling())
m_element->parent()->appendChild(element.m_element, exception);
else
m_element->parent()->insertBefore(element.m_element, m_element->nextSibling(), exception);
}
void QWebElement::appendOutside(const QString &markup)
{
if (!m_element)
return;
if (!m_element->parent())
return;
if (!m_element->isHTMLElement())
return;
HTMLElement* htmlElement = static_cast<HTMLElement*>(m_element);
RefPtr<DocumentFragment> fragment = htmlElement->createContextualFragment(markup);
ExceptionCode exception = 0;
if (!m_element->nextSibling())
m_element->parent()->appendChild(fragment, exception);
else
m_element->parent()->insertBefore(fragment, m_element->nextSibling(), exception);
}
QWebElement QWebElement::clone() const
{
if (!m_element)
return QWebElement();
return QWebElement(m_element->cloneElementWithChildren().get());
}
QWebElement &QWebElement::takeFromDocument()
{
if (!m_element)
return *this;
ExceptionCode exception = 0;
m_element->remove(exception);
return *this;
}
void QWebElement::removeFromDocument()
{
if (!m_element)
return;
ExceptionCode exception = 0;
m_element->remove(exception);
m_element->deref();
m_element = 0;
}
void QWebElement::removeChildren()
{
if (!m_element)
return;
m_element->removeAllChildren();
}
static RefPtr<Node> findInsertionPoint(PassRefPtr<Node> root)
{
RefPtr<Node> node = root;
while (node->hasChildNodes() && node->firstChild()->isElementNode())
node = node->firstChild();
if (node->isHTMLElement()) {
HTMLElement* element = static_cast<HTMLElement*>(node.get());
if (element->endTagRequirement() == TagStatusForbidden)
node = node->parentElement();
}
return node;
}
void QWebElement::encloseContentsWith(const QWebElement &element)
{
if (!m_element || element.isNull())
return;
RefPtr<Node> insertionPoint = findInsertionPoint(element.m_element);
if (!insertionPoint)
return;
ExceptionCode exception = 0;
for (RefPtr<Node> child = m_element->firstChild(); child;) {
RefPtr<Node> next = child->nextSibling();
insertionPoint->appendChild(child, exception);
child = next;
}
if (m_element->hasChildNodes())
m_element->insertBefore(element.m_element, m_element->firstChild(), exception);
else
m_element->appendChild(element.m_element, exception);
}
void QWebElement::encloseContentsWith(const QString &markup)
{
if (!m_element)
return;
if (!m_element->parent())
return;
if (!m_element->isHTMLElement())
return;
HTMLElement* htmlElement = static_cast<HTMLElement*>(m_element);
RefPtr<DocumentFragment> fragment = htmlElement->createContextualFragment(markup);
if (!fragment || !fragment->firstChild())
return;
RefPtr<Node> insertionPoint = findInsertionPoint(fragment->firstChild());
if (!insertionPoint)
return;
ExceptionCode exception = 0;
for (RefPtr<Node> child = m_element->firstChild(); child;) {
RefPtr<Node> next = child->nextSibling();
insertionPoint->appendChild(child, exception);
child = next;
}
if (m_element->hasChildNodes())
m_element->insertBefore(fragment, m_element->firstChild(), exception);
else
m_element->appendChild(fragment, exception);
}
void QWebElement::encloseWith(const QWebElement &element)
{
if (!m_element || element.isNull())
return;
RefPtr<Node> insertionPoint = findInsertionPoint(element.m_element);
if (!insertionPoint)
return;
Node* parentNode = m_element->parent();
Node* siblingNode = m_element->nextSibling();
ExceptionCode exception = 0;
insertionPoint->appendChild(m_element, exception);
if (!siblingNode)
parentNode->appendChild(element.m_element, exception);
else
parentNode->insertBefore(element.m_element, siblingNode, exception);
}
void QWebElement::encloseWith(const QString &markup)
{
if (!m_element)
return;
if (!m_element->parent())
return;
if (!m_element->isHTMLElement())
return;
HTMLElement* htmlElement = static_cast<HTMLElement*>(m_element);
RefPtr<DocumentFragment> fragment = htmlElement->createContextualFragment(markup);
if (!fragment || !fragment->firstChild())
return;
RefPtr<Node> insertionPoint = findInsertionPoint(fragment->firstChild());
if (!insertionPoint)
return;
Node* parentNode = m_element->parent();
Node* siblingNode = m_element->nextSibling();
ExceptionCode exception = 0;
insertionPoint->appendChild(m_element, exception);
if (!siblingNode)
parentNode->appendChild(fragment, exception);
else
parentNode->insertBefore(fragment, siblingNode, exception);
}
void QWebElement::replace(const QWebElement &element)
{
if (!m_element || element.isNull())
return;
appendOutside(element);
takeFromDocument();
}
void QWebElement::replace(const QString &markup)
{
if (!m_element)
return;
appendOutside(markup);
takeFromDocument();
}