diff --git a/src/css/css_base.cpp b/src/css/css_base.cpp index 9b3ffaf..1675615 100644 --- a/src/css/css_base.cpp +++ b/src/css/css_base.cpp @@ -1,458 +1,458 @@ /* * This file is part of the DOM implementation for KDE. * * Copyright 1999-2003 Lars Knoll (knoll@kde.org) * Copyright 1999 Waldo Bastian (bastian@kde.org) * Copyright 2001 Andreas Schlapbach (schlpbch@iam.unibe.ch) * Copyright 2001-2003 Dirk Mueller (mueller@kde.org) * Copyright 2002 Apple Computer, Inc. * Copyright 2004 Allan Sandfeld Jensen (kde@carewolf.com) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ //#define CSS_DEBUG #include "css_base.h" #include #include #ifdef CSS_DEBUG #include "cssproperties.h" #endif #include "css_stylesheetimpl.h" #include #include "css_valueimpl.h" using namespace DOM; void StyleBaseImpl::checkLoaded() const { if (m_parent) { m_parent->checkLoaded(); } } void StyleBaseImpl::checkPending() const { if (m_parent) { m_parent->checkPending(); } } StyleSheetImpl *StyleBaseImpl::stylesheet() { StyleBaseImpl *b = this; while (b && !b->isStyleSheet()) { b = b->m_parent; } return static_cast(b); } QUrl StyleBaseImpl::baseURL() { // try to find the style sheet. If found look for its url. // If it has none, look for the parentsheet, or the parentNode and // try to find out about their url StyleSheetImpl *sheet = stylesheet(); if (!sheet) { return QUrl(); } if (!sheet->href().isNull()) { return QUrl(sheet->href().string()); } // find parent if (sheet->parent()) { return sheet->parent()->baseURL(); } if (!sheet->ownerNode()) { return QUrl(); } return sheet->ownerNode()->document()->baseURL(); } void StyleBaseImpl::setParsedValue(int propId, const CSSValueImpl *parsedValue, bool important, QList *propList) { QMutableListIterator propIt(*propList); propIt.toBack(); // just remove the top one - not sure what should happen if we have multiple instances of the property CSSProperty *p; while (propIt.hasPrevious()) { p = propIt.previous(); if (p->m_id == propId && p->m_important == important) { delete propIt.value(); propIt.remove(); break; } } CSSProperty *prop = new CSSProperty(); prop->m_id = propId; prop->setValue(const_cast(parsedValue)); prop->m_important = important; propList->append(prop); #ifdef CSS_DEBUG qDebug() << "added property: " << getPropertyName(propId).string() // non implemented yet << ", value: " << parsedValue->cssText().string() << " important: " << prop->m_important; #endif } // ------------------------------------------------------------------------------ StyleListImpl::~StyleListImpl() { StyleBaseImpl *n; if (!m_lstChildren) { return; } QListIterator it(*m_lstChildren); while (it.hasNext()) { n = it.next(); n->setParent(nullptr); if (!n->refCount()) { delete n; } } delete m_lstChildren; } // -------------------------------------------------------------------------------- void CSSSelector::print(void) { // qDebug() << "[Selector: tag = " << QString::number(makeId(tagNamespace.id(), tagLocalName.id()),16) << ", attr = \"" << makeId(attrNamespace.id(), attrLocalName.id()) << "\", match = \"" << match // << "\" value = \"" << value.string().string().toLatin1().constData() << "\" relation = " << (int)relation - // << "]" << endl; + // << "]"; if (tagHistory) { tagHistory->print(); } // qDebug() << " specificity = " << specificity(); } unsigned int CSSSelector::specificity() const { int s = ((tagLocalName.id() == anyLocalName) ? 0 : 1); switch (match) { case Id: s += 0x10000; break; case Exact: case Set: case List: case Class: case Hyphen: case PseudoClass: case PseudoElement: case Contain: case Begin: case End: s += 0x100; case None: break; } if (tagHistory) { s += tagHistory->specificity(); } // make sure it doesn't overflow return s & 0xffffff; } void CSSSelector::extractPseudoType() const { if (match != PseudoClass && match != PseudoElement) { return; } _pseudoType = PseudoOther; bool element = false; bool compat = false; if (!value.isEmpty()) { value = value.string().lower(); switch (value[0].unicode()) { case '-': if (value == "-khtml-replaced") { _pseudoType = PseudoReplaced; } else if (value == "-khtml-marker") { _pseudoType = PseudoMarker; } element = true; break; case 'a': if (value == "active") { _pseudoType = PseudoActive; } else if (value == "after") { _pseudoType = PseudoAfter; element = compat = true; } break; case 'b': if (value == "before") { _pseudoType = PseudoBefore; element = compat = true; } break; case 'c': if (value == "checked") { _pseudoType = PseudoChecked; } else if (value == "contains(") { _pseudoType = PseudoContains; } break; case 'd': if (value == "disabled") { _pseudoType = PseudoDisabled; } if (value == "default") { _pseudoType = PseudoDefault; } break; case 'e': if (value == "empty") { _pseudoType = PseudoEmpty; } else if (value == "enabled") { _pseudoType = PseudoEnabled; } break; case 'f': if (value == "first-child") { _pseudoType = PseudoFirstChild; } else if (value == "first-letter") { _pseudoType = PseudoFirstLetter; element = compat = true; } else if (value == "first-line") { _pseudoType = PseudoFirstLine; element = compat = true; } else if (value == "first-of-type") { _pseudoType = PseudoFirstOfType; } else if (value == "focus") { _pseudoType = PseudoFocus; } break; case 'h': if (value == "hover") { _pseudoType = PseudoHover; } break; case 'i': if (value == "indeterminate") { _pseudoType = PseudoIndeterminate; } break; case 'l': if (value == "link") { _pseudoType = PseudoLink; } else if (value == "lang(") { _pseudoType = PseudoLang; } else if (value == "last-child") { _pseudoType = PseudoLastChild; } else if (value == "last-of-type") { _pseudoType = PseudoLastOfType; } break; case 'n': if (value == "not(") { _pseudoType = PseudoNot; } else if (value == "nth-child(") { _pseudoType = PseudoNthChild; } else if (value == "nth-last-child(") { _pseudoType = PseudoNthLastChild; } else if (value == "nth-of-type(") { _pseudoType = PseudoNthOfType; } else if (value == "nth-last-of-type(") { _pseudoType = PseudoNthLastOfType; } break; case 'o': if (value == "only-child") { _pseudoType = PseudoOnlyChild; } else if (value == "only-of-type") { _pseudoType = PseudoOnlyOfType; } break; case 'r': if (value == "root") { _pseudoType = PseudoRoot; } else if (value == "read-only") { _pseudoType = PseudoReadOnly; } else if (value == "read-write") { _pseudoType = PseudoReadWrite; } break; case 's': if (value == "selection") { _pseudoType = PseudoSelection; element = true; } break; case 't': if (value == "target") { _pseudoType = PseudoTarget; } break; case 'v': if (value == "visited") { _pseudoType = PseudoVisited; } break; } } if (match == PseudoClass && element) if (!compat) { _pseudoType = PseudoOther; } else { match = PseudoElement; } else if (match == PseudoElement && !element) { _pseudoType = PseudoOther; } } bool CSSSelector::operator == (const CSSSelector &other) const { const CSSSelector *sel1 = this; const CSSSelector *sel2 = &other; while (sel1 && sel2) { //assert(sel1->_pseudoType != PseudoNotParsed); //assert(sel2->_pseudoType != PseudoNotParsed); if (sel1->tagLocalName.id() != sel2->tagLocalName.id() || sel1->attrLocalName.id() != sel2->attrLocalName.id() || sel1->tagNamespace.id() != sel2->tagNamespace.id() || sel1->attrNamespace.id() != sel2->attrNamespace.id() || sel1->relation != sel2->relation || sel1->match != sel2->match || sel1->value != sel2->value || sel1->pseudoType() != sel2->pseudoType() || sel1->string_arg != sel2->string_arg) { return false; } sel1 = sel1->tagHistory; sel2 = sel2->tagHistory; } if (sel1 || sel2) { return false; } return true; } DOMString CSSSelector::selectorText() const { // FIXME: Support namespaces when dumping the selector text. This requires preserving // the original namespace prefix used. Ugh. -dwh DOMString str; const CSSSelector *cs = this; quint16 tag = cs->tagLocalName.id(); if (tag == anyLocalName && cs->match == CSSSelector::None) { str = "*"; } else if (tag != anyLocalName) { str = LocalName::fromId(tag).toString(); } const CSSSelector *op = nullptr; while (true) { if (makeId(cs->attrNamespace.id(), cs->attrLocalName.id()) == ATTR_ID && cs->match == CSSSelector::Id) { str += "#"; str += cs->value; } else if (cs->match == CSSSelector::Class) { str += "."; str += cs->value; } else if (cs->match == CSSSelector::PseudoClass) { str += ":"; str += cs->value; if (!cs->string_arg.isEmpty()) { // e.g :nth-child(...) str += cs->string_arg; str += ")"; } else if (cs->simpleSelector && !op) { // :not(...) op = cs; cs = cs->simpleSelector; continue; } } else if (cs->match == CSSSelector::PseudoElement) { str += "::"; str += cs->value; } // optional attribute else if (cs->attrLocalName.id()) { DOMString attrName = LocalName::fromId(cs->attrLocalName.id()).toString(); str += "["; str += attrName; switch (cs->match) { case CSSSelector::Exact: str += "="; break; case CSSSelector::Set: break; case CSSSelector::List: str += "~="; break; case CSSSelector::Hyphen: str += "|="; break; case CSSSelector::Begin: str += "^="; break; case CSSSelector::End: str += "$="; break; case CSSSelector::Contain: str += "*="; break; default: qWarning() << "Unhandled case in CSSStyleRuleImpl::selectorText : match=" << cs->match; } if (cs->match != CSSSelector::Set) { str += "\""; str += cs->value; str += "\""; } str += "]"; } if (op && !cs->tagHistory) { cs = op; op = nullptr; str += ")"; } if ((cs->relation != CSSSelector::SubSelector && !op) || !cs->tagHistory) { break; } cs = cs->tagHistory; } if (cs->tagHistory) { DOMString tagHistoryText = cs->tagHistory->selectorText(); if (cs->relation == DirectAdjacent) { str = tagHistoryText + DOMString(" + ") + str; } else if (cs->relation == IndirectAdjacent) { str = tagHistoryText + DOMString(" ~ ") + str; } else if (cs->relation == Child) { str = tagHistoryText + DOMString(" > ") + str; } else { // Descendant str = tagHistoryText + DOMString(" ") + str; } } return str; } // ---------------------------------------------------------------------------- diff --git a/src/css/css_valueimpl.cpp b/src/css/css_valueimpl.cpp index c964ae8..1ae152e 100644 --- a/src/css/css_valueimpl.cpp +++ b/src/css/css_valueimpl.cpp @@ -1,1869 +1,1869 @@ /** * This file is part of the DOM implementation for KDE. * * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) * (C) 2004-2008 Apple Computer, Inc. * (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com) * (C) 2009 Germain Garand (germain@ebooksfrance.org) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "css_valueimpl.h" #include "css_ruleimpl.h" #include "css_stylesheetimpl.h" #include "css/csshelper.h" #include "cssparser.h" #include "cssproperties.h" #include "cssvalues.h" #include #include #include #include #include #include #include #include #include #include #include // Hack for debugging purposes extern DOM::DOMString getPropertyName(unsigned short id); using khtml::FontDef; using namespace DOM; using namespace WTF; static int propertyID(const DOMString &s) { char buffer[maxCSSPropertyNameLength]; unsigned len = s.length(); if (len > maxCSSPropertyNameLength) { return 0; } for (unsigned i = 0; i != len; ++i) { unsigned short c = s[i].unicode(); if (c == 0 || c >= 0x7F) { return 0; // illegal character } buffer[i] = s[i].toLower().unicode(); } return getPropertyID(buffer, len); } // "ident" from the CSS tokenizer, minus backslash-escape sequences static bool isCSSTokenizerIdentifier(const DOMString &string) { const QChar *p = string.unicode(); const QChar *end = p + string.length(); // -? if (p != end && p[0] == '-') { ++p; } // {nmstart} if (p == end || !(p[0] == '_' || p[0] >= 128 || isASCIIAlpha(p->unicode()))) { return false; } ++p; // {nmchar}* for (; p != end; ++p) { if (!(p[0] == '_' || p[0] == '-' || p[0] >= 128 || isASCIIAlphanumeric(p->unicode()))) { return false; } } return true; } static DOMString quoteString(const DOMString &string) { // FIXME: Also need to transform control characters into \ sequences. QString s = string.string(); s.replace('\\', "\\\\"); s.replace('\'', "\\'"); return QString('\'' + s + '\''); } // Quotes the string if it needs quoting. static DOMString quoteStringIfNeeded(const DOMString &string) { return isCSSTokenizerIdentifier(string) ? string : quoteString(string); } static inline bool isInitialOrInherit(const CSSValueImpl *value) { const unsigned short t = value->cssValueType(); return (t == CSSValue::CSS_INHERIT || t == CSSValue::CSS_INITIAL); } CSSStyleDeclarationImpl::CSSStyleDeclarationImpl(CSSRuleImpl *parent) : StyleBaseImpl(parent) { m_lstValues = nullptr; m_node = nullptr; } CSSStyleDeclarationImpl::CSSStyleDeclarationImpl(CSSRuleImpl *parent, QList *lstValues) : StyleBaseImpl(parent) { m_lstValues = lstValues; m_node = nullptr; } CSSStyleDeclarationImpl &CSSStyleDeclarationImpl::operator= (const CSSStyleDeclarationImpl &o) { if (this == &o) { return *this; } // don't attach it to the same node, just leave the current m_node value if (m_lstValues) { qDeleteAll(*m_lstValues); } delete m_lstValues; m_lstValues = nullptr; if (o.m_lstValues) { m_lstValues = new QList; QListIterator lstValuesIt(*o.m_lstValues); while (lstValuesIt.hasNext()) { m_lstValues->append(new CSSProperty(*lstValuesIt.next())); } } return *this; } CSSStyleDeclarationImpl::~CSSStyleDeclarationImpl() { if (m_lstValues) { qDeleteAll(*m_lstValues); } delete m_lstValues; // we don't use refcounting for m_node, to avoid cyclic references (see ElementImpl) } CSSValueImpl *CSSStyleDeclarationImpl::getPropertyCSSValue(const DOMString &propertyName) const { int propID = propertyID(propertyName); if (!propID) { return nullptr; } return getPropertyCSSValue(propID); } DOMString CSSStyleDeclarationImpl::getPropertyValue(const DOMString &propertyName) const { int propID = propertyID(propertyName); if (!propID) { return DOMString(); } return getPropertyValue(propID); } DOMString CSSStyleDeclarationImpl::getPropertyPriority(const DOMString &propertyName) const { int propID = propertyID(propertyName); if (!propID) { return DOMString(); } return getPropertyPriority(propID) ? "important" : ""; } void CSSStyleDeclarationImpl::setProperty(const DOMString &propertyName, const DOMString &value, const DOMString &priority) { int propID = propertyID(propertyName); if (!propID) { // set exception? return; } bool important = priority.string().indexOf("important", 0, Qt::CaseInsensitive) != -1; setProperty(propID, value, important); } DOMString CSSStyleDeclarationImpl::removeProperty(const DOMString &propertyName) { int propID = propertyID(propertyName); if (!propID) { return DOMString(); } DOMString old; removeProperty(propID, &old); return old; } DOMString CSSStyleDeclarationImpl::getPropertyValue(int propertyID) const { if (!m_lstValues || m_lstValues->isEmpty()) { return DOMString(); } CSSValueImpl *value = getPropertyCSSValue(propertyID); if (value) { return value->cssText(); } // Shorthand and 4-values properties switch (propertyID) { case CSS_PROP_BACKGROUND_POSITION: { // ## Is this correct? The code in cssparser.cpp is confusing const int properties[2] = { CSS_PROP_BACKGROUND_POSITION_X, CSS_PROP_BACKGROUND_POSITION_Y }; return getLayeredShortHandValue(properties, 2); } case CSS_PROP_BACKGROUND: { // 'clip' must come after 'origin' in this array const int properties[9] = { CSS_PROP_BACKGROUND_IMAGE, CSS_PROP_BACKGROUND_REPEAT, CSS_PROP_BACKGROUND_ATTACHMENT, CSS_PROP_BACKGROUND_POSITION_X, CSS_PROP_BACKGROUND_POSITION_Y, CSS_PROP_BACKGROUND_SIZE, CSS_PROP_BACKGROUND_ORIGIN, CSS_PROP_BACKGROUND_CLIP, CSS_PROP_BACKGROUND_COLOR }; return getLayeredShortHandValue(properties, 9); } case CSS_PROP_BORDER: { const int properties[3][4] = {{ CSS_PROP_BORDER_TOP_WIDTH, CSS_PROP_BORDER_RIGHT_WIDTH, CSS_PROP_BORDER_BOTTOM_WIDTH, CSS_PROP_BORDER_LEFT_WIDTH }, { CSS_PROP_BORDER_TOP_STYLE, CSS_PROP_BORDER_RIGHT_STYLE, CSS_PROP_BORDER_BOTTOM_STYLE, CSS_PROP_BORDER_LEFT_STYLE }, { CSS_PROP_BORDER_TOP_COLOR, CSS_PROP_BORDER_RIGHT_COLOR, CSS_PROP_BORDER_LEFT_COLOR, CSS_PROP_BORDER_BOTTOM_COLOR } }; DOMString res; const int nrprops = sizeof(properties) / sizeof(properties[0]); for (int i = 0; i < nrprops; ++i) { DOMString value = getCommonValue(properties[i], 4); if (!value.isNull()) { if (!res.isNull()) { res += " "; } res += value; } } return res; } case CSS_PROP_BORDER_TOP: { const int properties[3] = { CSS_PROP_BORDER_TOP_WIDTH, CSS_PROP_BORDER_TOP_STYLE, CSS_PROP_BORDER_TOP_COLOR }; return getShortHandValue(properties, 3); } case CSS_PROP_BORDER_RIGHT: { const int properties[3] = { CSS_PROP_BORDER_RIGHT_WIDTH, CSS_PROP_BORDER_RIGHT_STYLE, CSS_PROP_BORDER_RIGHT_COLOR }; return getShortHandValue(properties, 3); } case CSS_PROP_BORDER_BOTTOM: { const int properties[3] = { CSS_PROP_BORDER_BOTTOM_WIDTH, CSS_PROP_BORDER_BOTTOM_STYLE, CSS_PROP_BORDER_BOTTOM_COLOR }; return getShortHandValue(properties, 3); } case CSS_PROP_BORDER_LEFT: { const int properties[3] = { CSS_PROP_BORDER_LEFT_WIDTH, CSS_PROP_BORDER_LEFT_STYLE, CSS_PROP_BORDER_LEFT_COLOR }; return getShortHandValue(properties, 3); } case CSS_PROP_OUTLINE: { const int properties[3] = { CSS_PROP_OUTLINE_WIDTH, CSS_PROP_OUTLINE_STYLE, CSS_PROP_OUTLINE_COLOR }; return getShortHandValue(properties, 3); } case CSS_PROP_BORDER_COLOR: { const int properties[4] = { CSS_PROP_BORDER_TOP_COLOR, CSS_PROP_BORDER_RIGHT_COLOR, CSS_PROP_BORDER_BOTTOM_COLOR, CSS_PROP_BORDER_LEFT_COLOR }; return get4Values(properties); } case CSS_PROP_BORDER_WIDTH: { const int properties[4] = { CSS_PROP_BORDER_TOP_WIDTH, CSS_PROP_BORDER_RIGHT_WIDTH, CSS_PROP_BORDER_BOTTOM_WIDTH, CSS_PROP_BORDER_LEFT_WIDTH }; return get4Values(properties); } case CSS_PROP_BORDER_STYLE: { const int properties[4] = { CSS_PROP_BORDER_TOP_STYLE, CSS_PROP_BORDER_RIGHT_STYLE, CSS_PROP_BORDER_BOTTOM_STYLE, CSS_PROP_BORDER_LEFT_STYLE }; return get4Values(properties); } case CSS_PROP_MARGIN: { const int properties[4] = { CSS_PROP_MARGIN_TOP, CSS_PROP_MARGIN_RIGHT, CSS_PROP_MARGIN_BOTTOM, CSS_PROP_MARGIN_LEFT }; return get4Values(properties); } case CSS_PROP_PADDING: { const int properties[4] = { CSS_PROP_PADDING_TOP, CSS_PROP_PADDING_RIGHT, CSS_PROP_PADDING_BOTTOM, CSS_PROP_PADDING_LEFT }; return get4Values(properties); } case CSS_PROP_LIST_STYLE: { const int properties[3] = { CSS_PROP_LIST_STYLE_TYPE, CSS_PROP_LIST_STYLE_POSITION, CSS_PROP_LIST_STYLE_IMAGE }; return getShortHandValue(properties, 3); } } //qDebug() << "property not found:" << propertyID; return DOMString(); } // only returns a non-null value if all properties have the same, non-null value DOMString CSSStyleDeclarationImpl::getCommonValue(const int *properties, int number) const { DOMString res; for (int i = 0; i < number; ++i) { CSSValueImpl *value = getPropertyCSSValue(properties[i]); if (!value) { return DOMString(); } DOMString text = value->cssText(); if (text.isNull()) { return DOMString(); } if (res.isNull()) { res = text; } else if (res != text) { return DOMString(); } } return res; } DOMString CSSStyleDeclarationImpl::get4Values(const int *properties) const { // Assume the properties are in the order top, right, bottom, left. QVector values(4); for (int i = 0; i < 4; ++i) { CSSValueImpl *val = getPropertyCSSValue(properties[i]); // All 4 properties must be specified. if (!val || isInitialOrInherit(val)) { return DOMString(); } else { values[i] = val->cssText(); } } // Reduce shorthand. if (values.at(1) == values.at(3)) { // right/left values.remove(3); if (values.at(0) == values.at(2)) { // top/bottom values.remove(2); if (values.at(0) == values.at(1)) { values.remove(1); } } } DOMString res; const int valuesSize = values.size(); for (int i = 0; i < valuesSize; ++i) { if (!res.isNull()) { res += " "; } res += values.at(i); } return res; } static inline DOMString posXYSize_string_helper(DOMString &bPosX, DOMString &bPosY, DOMString &bSize) { DOMString res, position; if (!bPosX.isEmpty() && !bPosY.isEmpty()) { position = bPosX + DOMString(" ") + bPosY; } else if (bPosX.isEmpty() && !bPosY.isEmpty()) { position = DOMString("0% ") + bPosY; } else if (!bPosX.isEmpty() && bPosY.isEmpty()) { position = bPosX + DOMString(" 0%"); } if (!bSize.isEmpty()) { if (position.isEmpty()) { res = DOMString("0% 0%") + DOMString(" / ") + bSize; } else { res = position + DOMString(" / ") + bSize; } } else { res = position; } return res; } DOMString CSSStyleDeclarationImpl::getLayeredShortHandValue(const int *properties, unsigned number) const { DOMString res; unsigned i; unsigned j; // Begin by collecting the properties into an array. QVector values(number); unsigned numLayers = 0; for (i = 0; i < number; ++i) { values[i] = getPropertyCSSValue(properties[i]); if (values[i]) { if (values[i]->isValueList()) { CSSValueListImpl *valueList = static_cast(values[i]); numLayers = qMax(valueList->length(), (unsigned long)numLayers); } else { numLayers = qMax(1U, numLayers); } } } // Now stitch the properties together. // Implicit initial values are flagged as such and can safely be omitted. for (i = 0; i < numLayers; i++) { DOMString layerRes; DOMString bPosX, bPosY, bSize; for (j = 0; j < number; j++) { CSSValueImpl *value = nullptr; if (values[j]) { if (values[j]->isValueList()) { value = static_cast(values[j])->item(i); } else { value = values[j]; // Color only belongs in the last layer. if (properties[j] == CSS_PROP_BACKGROUND_COLOR) { if (i != numLayers - 1) { value = nullptr; } } else if (i != 0) { // Other singletons only belong in the first layer. value = nullptr; } } } if (value && !value->isImplicitInitialValue()) { // positionX,positionY,size should be handled separately in order // to return a consistent and valid 'background' property string if (properties[j] == CSS_PROP_BACKGROUND_POSITION_X) { bPosX = value->cssText(); } else if (properties[j] == CSS_PROP_BACKGROUND_POSITION_Y) { bPosY = value->cssText(); } else if (properties[j] == CSS_PROP_BACKGROUND_SIZE) { bSize = value->cssText(); } else { if (!layerRes.isNull()) { layerRes += " "; } layerRes += value->cssText(); } } } // now add positionX,positionY,size DOMString posXYSize = posXYSize_string_helper(bPosX, bPosY, bSize); if (!posXYSize.isEmpty()) { if (!layerRes.isNull()) { layerRes += " "; } layerRes += posXYSize; } if (!layerRes.isNull()) { if (!res.isNull()) { res += ", "; } res += layerRes; } } return res; } DOMString CSSStyleDeclarationImpl::getShortHandValue(const int *properties, int number) const { DOMString res; for (int i = 0; i < number; ++i) { CSSValueImpl *value = getPropertyCSSValue(properties[i]); if (value) { // TODO provide default value if !value if (!res.isNull()) { res += " "; } res += value->cssText(); } } return res; } CSSValueImpl *CSSStyleDeclarationImpl::getPropertyCSSValue(int propertyID) const { if (!m_lstValues || m_lstValues->isEmpty()) { return nullptr; } QListIterator lstValuesIt(*m_lstValues); CSSProperty *current; while (lstValuesIt.hasNext()) { current = lstValuesIt.next(); if (current->m_id == propertyID) { return current->value(); } } return nullptr; } bool CSSStyleDeclarationImpl::isPropertyImplicit(int propertyID) const { QListIterator lstValuesIt(*m_lstValues); CSSProperty const *current; while (lstValuesIt.hasNext()) { current = lstValuesIt.next(); if (current->m_id == propertyID) { return current->isImplicit(); } } return false; } // --------------- Shorthands mapping ---------------- // In order top be able to remove a shorthand property, // we need a reverse mapping from the shorthands to their composing properties. // ### Warning: keep in sync when introducing new shorthands. struct PropertyLonghand { PropertyLonghand() : m_properties(nullptr) , m_length(0) { } PropertyLonghand(const int *firstProperty, unsigned numProperties) : m_properties(firstProperty) , m_length(numProperties) { } const int *properties() const { return m_properties; } unsigned length() const { return m_length; } private: const int *m_properties; unsigned m_length; }; static void initShorthandMap(QHash &shorthandMap) { #define SET_SHORTHAND_MAP_ENTRY(map, propID, array) \ map.insert(propID, PropertyLonghand(array, sizeof(array) / sizeof(array[0]))) // Do not change the order of the following four shorthands, and keep them together. static const int borderProperties[4][3] = { { CSS_PROP_BORDER_TOP_COLOR, CSS_PROP_BORDER_TOP_STYLE, CSS_PROP_BORDER_TOP_WIDTH }, { CSS_PROP_BORDER_RIGHT_COLOR, CSS_PROP_BORDER_RIGHT_STYLE, CSS_PROP_BORDER_RIGHT_WIDTH }, { CSS_PROP_BORDER_BOTTOM_COLOR, CSS_PROP_BORDER_BOTTOM_STYLE, CSS_PROP_BORDER_BOTTOM_WIDTH }, { CSS_PROP_BORDER_LEFT_COLOR, CSS_PROP_BORDER_LEFT_STYLE, CSS_PROP_BORDER_LEFT_WIDTH } }; SET_SHORTHAND_MAP_ENTRY(shorthandMap, CSS_PROP_BORDER_TOP, borderProperties[0]); SET_SHORTHAND_MAP_ENTRY(shorthandMap, CSS_PROP_BORDER_RIGHT, borderProperties[1]); SET_SHORTHAND_MAP_ENTRY(shorthandMap, CSS_PROP_BORDER_BOTTOM, borderProperties[2]); SET_SHORTHAND_MAP_ENTRY(shorthandMap, CSS_PROP_BORDER_LEFT, borderProperties[3]); shorthandMap.insert(CSS_PROP_BORDER, PropertyLonghand(borderProperties[0], sizeof(borderProperties) / sizeof(borderProperties[0][0]))); static const int borderColorProperties[] = { CSS_PROP_BORDER_TOP_COLOR, CSS_PROP_BORDER_RIGHT_COLOR, CSS_PROP_BORDER_BOTTOM_COLOR, CSS_PROP_BORDER_LEFT_COLOR }; SET_SHORTHAND_MAP_ENTRY(shorthandMap, CSS_PROP_BORDER_COLOR, borderColorProperties); static const int borderStyleProperties[] = { CSS_PROP_BORDER_TOP_STYLE, CSS_PROP_BORDER_RIGHT_STYLE, CSS_PROP_BORDER_BOTTOM_STYLE, CSS_PROP_BORDER_LEFT_STYLE }; SET_SHORTHAND_MAP_ENTRY(shorthandMap, CSS_PROP_BORDER_STYLE, borderStyleProperties); static const int borderWidthProperties[] = { CSS_PROP_BORDER_TOP_WIDTH, CSS_PROP_BORDER_RIGHT_WIDTH, CSS_PROP_BORDER_BOTTOM_WIDTH, CSS_PROP_BORDER_LEFT_WIDTH }; SET_SHORTHAND_MAP_ENTRY(shorthandMap, CSS_PROP_BORDER_WIDTH, borderWidthProperties); static const int backgroundPositionProperties[] = { CSS_PROP_BACKGROUND_POSITION_X, CSS_PROP_BACKGROUND_POSITION_Y }; SET_SHORTHAND_MAP_ENTRY(shorthandMap, CSS_PROP_BACKGROUND_POSITION, backgroundPositionProperties); static const int borderSpacingProperties[] = { CSS_PROP__KHTML_BORDER_HORIZONTAL_SPACING, CSS_PROP__KHTML_BORDER_VERTICAL_SPACING }; SET_SHORTHAND_MAP_ENTRY(shorthandMap, CSS_PROP_BORDER_SPACING, borderSpacingProperties); static const int listStyleProperties[] = { CSS_PROP_LIST_STYLE_IMAGE, CSS_PROP_LIST_STYLE_POSITION, CSS_PROP_LIST_STYLE_TYPE }; SET_SHORTHAND_MAP_ENTRY(shorthandMap, CSS_PROP_LIST_STYLE, listStyleProperties); static const int marginProperties[] = { CSS_PROP_MARGIN_TOP, CSS_PROP_MARGIN_RIGHT, CSS_PROP_MARGIN_BOTTOM, CSS_PROP_MARGIN_LEFT }; SET_SHORTHAND_MAP_ENTRY(shorthandMap, CSS_PROP_MARGIN, marginProperties); #ifdef APPLE_CHANGES static const int marginCollapseProperties[] = { CSS_PROP__KHTML_MARGIN_TOP_COLLAPSE, CSS_PROP__KHTML_MARGIN_BOTTOM_COLLAPSE }; SET_SHORTHAND_MAP_ENTRY(shorthandMap, CSS_PROP__KHTML_MARGIN_COLLAPSE, marginCollapseProperties); #endif static const int marqueeProperties[] = { CSS_PROP__KHTML_MARQUEE_DIRECTION, CSS_PROP__KHTML_MARQUEE_INCREMENT, CSS_PROP__KHTML_MARQUEE_REPETITION, CSS_PROP__KHTML_MARQUEE_STYLE, CSS_PROP__KHTML_MARQUEE_SPEED }; SET_SHORTHAND_MAP_ENTRY(shorthandMap, CSS_PROP__KHTML_MARQUEE, marqueeProperties); static const int outlineProperties[] = { CSS_PROP_OUTLINE_COLOR, CSS_PROP_OUTLINE_OFFSET, CSS_PROP_OUTLINE_STYLE, CSS_PROP_OUTLINE_WIDTH }; SET_SHORTHAND_MAP_ENTRY(shorthandMap, CSS_PROP_OUTLINE, outlineProperties); static const int paddingProperties[] = { CSS_PROP_PADDING_TOP, CSS_PROP_PADDING_RIGHT, CSS_PROP_PADDING_BOTTOM, CSS_PROP_PADDING_LEFT }; SET_SHORTHAND_MAP_ENTRY(shorthandMap, CSS_PROP_PADDING, paddingProperties); #ifdef APPLE_CHANGES static const int textStrokeProperties[] = { CSS_PROP__KHTML_TEXT_STROKE_COLOR, CSS_PROP__KHTML_TEXT_STROKE_WIDTH }; SET_SHORTHAND_MAP_ENTRY(shorthandMap, CSS_PROP__KHTML_TEXT_STROKE, textStrokeProperties); #endif static const int backgroundProperties[] = { CSS_PROP_BACKGROUND_ATTACHMENT, CSS_PROP_BACKGROUND_COLOR, CSS_PROP_BACKGROUND_IMAGE, CSS_PROP_BACKGROUND_POSITION_X, CSS_PROP_BACKGROUND_POSITION_Y, CSS_PROP_BACKGROUND_REPEAT, CSS_PROP_BACKGROUND_SIZE, CSS_PROP_BACKGROUND_ORIGIN, CSS_PROP_BACKGROUND_CLIP }; SET_SHORTHAND_MAP_ENTRY(shorthandMap, CSS_PROP_BACKGROUND, backgroundProperties); #ifdef APPLE_CHANGES static const int columnsProperties[] = { CSS_PROP__KHTML_COLUMN_WIDTH, CSS_PROP__KHTML_COLUMN_COUNT }; SET_SHORTHAND_MAP_ENTRY(shorthandMap, CSS_PROP__KHTML_COLUMNS, columnsProperties); static const int columnRuleProperties[] = { CSS_PROP__KHTML_COLUMN_RULE_COLOR, CSS_PROP__KHTML_COLUMN_RULE_STYLE, CSS_PROP__KHTML_COLUMN_RULE_WIDTH }; SET_SHORTHAND_MAP_ENTRY(shorthandMap, CSS_PROP__KHTML_COLUMN_RULE, columnRuleProperties); #endif static const int overflowProperties[] = { CSS_PROP_OVERFLOW_X, CSS_PROP_OVERFLOW_Y }; SET_SHORTHAND_MAP_ENTRY(shorthandMap, CSS_PROP_OVERFLOW, overflowProperties); static const int borderRadiusProperties[] = { CSS_PROP_BORDER_TOP_RIGHT_RADIUS, CSS_PROP_BORDER_TOP_LEFT_RADIUS, CSS_PROP_BORDER_BOTTOM_LEFT_RADIUS, CSS_PROP_BORDER_BOTTOM_RIGHT_RADIUS }; SET_SHORTHAND_MAP_ENTRY(shorthandMap, CSS_PROP_BORDER_RADIUS, borderRadiusProperties); static const int markerProperties[] = { CSS_PROP_MARKER_START, CSS_PROP_MARKER_MID, CSS_PROP_MARKER_END }; SET_SHORTHAND_MAP_ENTRY(shorthandMap, CSS_PROP_MARKER, markerProperties); static const int fontProperties[] = { CSS_PROP_FONT_STYLE, CSS_PROP_FONT_VARIANT, CSS_PROP_FONT_WEIGHT, CSS_PROP_FONT_SIZE, CSS_PROP_LINE_HEIGHT, CSS_PROP_FONT_FAMILY }; SET_SHORTHAND_MAP_ENTRY(shorthandMap, CSS_PROP_FONT, fontProperties); #undef SET_SHORTHAND_MAP_ENTRY } // ------------------------------------------- void CSSStyleDeclarationImpl::removeProperty(int propertyID, DOM::DOMString *old) { if (!m_lstValues || m_lstValues->isEmpty()) { return; } bool changed = false; static QHash shorthandMap; if (shorthandMap.isEmpty()) { initShorthandMap(shorthandMap); } PropertyLonghand longhand = shorthandMap.value(propertyID); if (longhand.length()) { changed = removePropertiesInSet(longhand.properties(), longhand.length()); } // FIXME: Return an equivalent shorthand when possible. QMutableListIterator lstValuesIt(*m_lstValues); CSSProperty *current; lstValuesIt.toBack(); while (lstValuesIt.hasPrevious()) { current = lstValuesIt.previous(); if (current->m_id == propertyID) { if (old) { *old = current->value()->cssText(); } delete lstValuesIt.value(); lstValuesIt.remove(); changed = true; break; } } if (changed) { setChanged(); } } bool CSSStyleDeclarationImpl::removePropertiesInSet(const int *set, unsigned length) { bool changed = false; for (unsigned i = 0; i < length; i++) { QMutableListIterator lstValuesIt(*m_lstValues); CSSProperty *current; lstValuesIt.toBack(); while (lstValuesIt.hasPrevious()) { current = lstValuesIt.previous(); if (current->m_id == set[i]) { delete lstValuesIt.value(); lstValuesIt.remove(); changed = true; break; } } } return changed; } void CSSStyleDeclarationImpl::setChanged() { if (m_node) { m_node->setChanged(); return; } // ### quick&dirty hack for KDE 3.0... make this MUCH better! (Dirk) for (StyleBaseImpl *stylesheet = this; stylesheet; stylesheet = stylesheet->parent()) if (stylesheet->isCSSStyleSheet()) { static_cast(stylesheet)->doc()->updateStyleSelector(); break; } } void CSSStyleDeclarationImpl::clear() { if (!m_lstValues) { return; } QMutableListIterator it(*m_lstValues); while (it.hasNext()) { delete it.next(); it.remove(); } } bool CSSStyleDeclarationImpl::getPropertyPriority(int propertyID) const { if (m_lstValues && !m_lstValues->isEmpty()) { QListIterator lstValuesIt(*m_lstValues); CSSProperty *current; while (lstValuesIt.hasNext()) { current = lstValuesIt.next(); if (propertyID == current->m_id) { return current->m_important; } } } return false; } bool CSSStyleDeclarationImpl::setProperty(int id, const DOMString &value, bool important, int &ec) { ec = 0; // Setting the value to an empty string just removes the property in both IE and Gecko. // Setting it to null seems to produce less consistent results, but we treat it just the same. if (value.isEmpty()) { removeProperty(id); return true; } bool success = setProperty(id, value, important); #if 0 if (!success) { // CSS DOM requires raising SYNTAX_ERR here, but this is too dangerous for compatibility, // see . } #endif return success; } bool CSSStyleDeclarationImpl::setProperty(int id, const DOMString &value, bool important) { if (!m_lstValues) { m_lstValues = new QList; } CSSParser parser(strictParsing); bool success = parser.parseValue(this, id, value, important); if (!success) { // qDebug() << "CSSStyleDeclarationImpl::setProperty invalid property: [" << getPropertyName(id).string() - //<< "] value: [" << value.string() << "]"<< endl; + //<< "] value: [" << value.string() << "]"; } else { setChanged(); } return success; } void CSSStyleDeclarationImpl::setProperty(int id, int value, bool important) { if (!m_lstValues) { m_lstValues = new QList; } removeProperty(id); CSSValueImpl *cssValue = new CSSPrimitiveValueImpl(value); setParsedValue(id, cssValue, important, m_lstValues); setChanged(); } void CSSStyleDeclarationImpl::setLengthProperty(int id, const DOM::DOMString &value, bool important, bool _multiLength) { bool parseMode = strictParsing; strictParsing = false; multiLength = _multiLength; setProperty(id, value, important); strictParsing = parseMode; multiLength = false; } void CSSStyleDeclarationImpl::setProperty(const DOMString &propertyString) { if (!m_lstValues) { m_lstValues = new QList; } CSSParser parser(strictParsing); parser.parseDeclaration(this, propertyString); setChanged(); } unsigned long CSSStyleDeclarationImpl::length() const { return m_lstValues ? m_lstValues->count() : 0; } DOMString CSSStyleDeclarationImpl::item(unsigned long index) const { if (m_lstValues && index < (unsigned)m_lstValues->count() && m_lstValues->at(index)) { return getPropertyName(m_lstValues->at(index)->m_id); } return DOMString(); } CSSRuleImpl *CSSStyleDeclarationImpl::parentRule() const { return (m_parent && m_parent->isRule()) ? static_cast(m_parent) : nullptr; } DOM::DOMString CSSStyleDeclarationImpl::cssText() const { if (!m_lstValues || m_lstValues->isEmpty()) { return DOMString(); } DOMString result; const CSSProperty *positionXProp = nullptr; const CSSProperty *positionYProp = nullptr; QListIterator lstValuesIt(*m_lstValues); while (lstValuesIt.hasNext()) { const CSSProperty *cur = lstValuesIt.next(); if (cur->id() == CSS_PROP_BACKGROUND_POSITION_X) { positionXProp = cur; } else if (cur->id() == CSS_PROP_BACKGROUND_POSITION_Y) { positionYProp = cur; } else { result += cur->cssText(); } } // FIXME: This is a not-so-nice way to turn x/y positions into single background-position in output. // It is required because background-position-x/y are non-standard properties and generated output // would not work in Firefox // It would be a better solution if background-position was CSS_PAIR. if (positionXProp && positionYProp && positionXProp->isImportant() == positionYProp->isImportant()) { DOMString positionValue; const int properties[2] = { CSS_PROP_BACKGROUND_POSITION_X, CSS_PROP_BACKGROUND_POSITION_Y }; if (positionXProp->value()->isValueList() || positionYProp->value()->isValueList()) { positionValue = getLayeredShortHandValue(properties, 2); } else { positionValue = positionXProp->value()->cssText() + DOMString(" ") + positionYProp->value()->cssText(); } result += DOMString("background-position: ") + positionValue + DOMString((positionXProp->isImportant() ? " !important" : "")) + DOMString("; "); } else { if (positionXProp) { result += positionXProp->cssText(); } if (positionYProp) { result += positionYProp->cssText(); } } return result; } void CSSStyleDeclarationImpl::setCssText(const DOM::DOMString &text) { if (m_lstValues) { qDeleteAll(*m_lstValues); m_lstValues->clear(); } else { m_lstValues = new QList; } CSSParser parser(strictParsing); parser.parseDeclaration(this, text); setChanged(); } bool CSSStyleDeclarationImpl::parseString(const DOMString &/*string*/, bool) { // qDebug() << "WARNING: CSSStyleDeclarationImpl::parseString, unimplemented, was called"; return false; // ### } // -------------------------------------------------------------------------------------- void CSSInlineStyleDeclarationImpl::setChanged() { if (m_node) { m_node->setNeedsStyleAttributeUpdate(); } CSSStyleDeclarationImpl::setChanged(); } void CSSInlineStyleDeclarationImpl::updateFromAttribute(const DOMString &value) { if (!m_lstValues) { m_lstValues = new QList; } else { clear(); } CSSParser parser(strictParsing); parser.parseDeclaration(this, value); CSSStyleDeclarationImpl::setChanged(); } // -------------------------------------------------------------------------------------- unsigned short CSSInheritedValueImpl::cssValueType() const { return CSSValue::CSS_INHERIT; } DOM::DOMString CSSInheritedValueImpl::cssText() const { return DOMString("inherit"); } unsigned short CSSInitialValueImpl::cssValueType() const { return CSSValue::CSS_INITIAL; } DOM::DOMString CSSInitialValueImpl::cssText() const { return DOMString("initial"); } // ---------------------------------------------------------------------------------------- CSSValueListImpl::~CSSValueListImpl() { for (QListIterator iterator(m_values); iterator.hasNext();) { iterator.next()->deref(); } } unsigned short CSSValueListImpl::cssValueType() const { return CSSValue::CSS_VALUE_LIST; } void CSSValueListImpl::append(CSSValueImpl *val) { m_values.append(val); val->ref(); } DOM::DOMString CSSValueListImpl::cssText() const { DOMString separatorString; if (m_separator == Comma) { separatorString = DOMString(", "); } else { // Space separatorString = DOMString(" "); } DOMString result = ""; for (QListIterator iterator(m_values); iterator.hasNext();) { if (!result.isEmpty()) { result += separatorString; } result += iterator.next()->cssText(); } return result; } // ------------------------------------------------------------------------------------- CSSPrimitiveValueImpl::CSSPrimitiveValueImpl() : CSSValueImpl() { m_type = 0; } CSSPrimitiveValueImpl::CSSPrimitiveValueImpl(int ident) : CSSValueImpl() { m_value.ident = ident; m_type = CSSPrimitiveValue::CSS_IDENT; } CSSPrimitiveValueImpl::CSSPrimitiveValueImpl(double num, CSSPrimitiveValue::UnitTypes type) { m_value.num = num; m_type = type; } CSSPrimitiveValueImpl::CSSPrimitiveValueImpl(const DOMString &str, CSSPrimitiveValue::UnitTypes type) { m_value.string = str.implementation(); if (m_value.string) { m_value.string->ref(); } m_type = type; } CSSPrimitiveValueImpl::CSSPrimitiveValueImpl(CounterImpl *c) { m_value.counter = c; if (m_value.counter) { m_value.counter->ref(); } m_type = CSSPrimitiveValue::CSS_COUNTER; } CSSPrimitiveValueImpl::CSSPrimitiveValueImpl(RectImpl *r) { m_value.rect = r; if (m_value.rect) { m_value.rect->ref(); } m_type = CSSPrimitiveValue::CSS_RECT; } CSSPrimitiveValueImpl::CSSPrimitiveValueImpl(QRgb color) { m_value.rgbcolor = color; m_type = CSSPrimitiveValue::CSS_RGBCOLOR; } CSSPrimitiveValueImpl::CSSPrimitiveValueImpl(PairImpl *p) { m_value.pair = p; if (m_value.pair) { m_value.pair->ref(); } m_type = CSSPrimitiveValue::CSS_PAIR; } CSSPrimitiveValueImpl::~CSSPrimitiveValueImpl() { cleanup(); } void CSSPrimitiveValueImpl::cleanup() { switch (m_type) { case CSSPrimitiveValue::CSS_STRING: case CSSPrimitiveValue::CSS_URI: case CSSPrimitiveValue::CSS_ATTR: if (m_value.string) { m_value.string->deref(); } break; case CSSPrimitiveValue::CSS_COUNTER: m_value.counter->deref(); break; case CSSPrimitiveValue::CSS_RECT: m_value.rect->deref(); break; case CSSPrimitiveValue::CSS_PAIR: m_value.pair->deref(); break; default: break; } m_type = 0; } int CSSPrimitiveValueImpl::computeLength(khtml::RenderStyle *style, khtml::RenderStyle *rootStyle, int logicalDpiY) { return snapValue(computeLengthFloat(style, rootStyle, logicalDpiY)); } double CSSPrimitiveValueImpl::computeLengthFloat(khtml::RenderStyle *style, khtml::RenderStyle *rootStyle, int logicalDpiY) { unsigned short type = primitiveType(); double dpiY = 72.; // fallback if (logicalDpiY) { dpiY = logicalDpiY; } if (!khtml::printpainter && dpiY < 96) { dpiY = 96.; } double factor = 1.; switch (type) { case CSSPrimitiveValue::CSS_EMS: factor = style->font().pixelSize(); break; case CSSPrimitiveValue::CSS_EXS: factor = style->htmlFont().xHeight(); break; case CSSPrimitiveValue::CSS_CHS: { const int zw = style->htmlFont().zeroCharWidth(); if (zw != -1) { factor = zw; } else { // assume 0.5em return ((double)0.5 * style->font().pixelSize()); } break; } case CSSPrimitiveValue::CSS_REMS: factor = rootStyle->font().pixelSize(); break; case CSSPrimitiveValue::CSS_PX: break; case CSSPrimitiveValue::CSS_CM: factor = dpiY / 2.54; //72dpi/(2.54 cm/in) break; case CSSPrimitiveValue::CSS_MM: factor = dpiY / 25.4; break; case CSSPrimitiveValue::CSS_IN: factor = dpiY; break; case CSSPrimitiveValue::CSS_PT: factor = dpiY / 72.; break; case CSSPrimitiveValue::CSS_PC: // 1 pc == 12 pt factor = dpiY * 12. / 72.; break; default: return -1; } return floatValue(type) * factor; } int CSSPrimitiveValueImpl::getDPIResolution() const { unsigned short type = primitiveType(); double factor = 1.; switch (type) { case CSSPrimitiveValue::CSS_DPI: break; case CSSPrimitiveValue::CSS_DPCM: factor = 2.54; break; default: return -1; } return (int)(0.01 + floatValue(type) * factor); } void CSSPrimitiveValueImpl::setFloatValue(unsigned short unitType, double floatValue, int &exceptioncode) { exceptioncode = 0; cleanup(); // ### check if property supports this type if (m_type > CSSPrimitiveValue::CSS_DIMENSION) { exceptioncode = CSSException::SYNTAX_ERR + CSSException::_EXCEPTION_OFFSET; return; } //if(m_type > CSSPrimitiveValue::CSS_DIMENSION) throw DOMException(DOMException::INVALID_ACCESS_ERR); m_value.num = floatValue; m_type = unitType; } void CSSPrimitiveValueImpl::setStringValue(unsigned short stringType, const DOMString &stringValue, int &exceptioncode) { exceptioncode = 0; cleanup(); //if(m_type < CSSPrimitiveValue::CSS_STRING) throw DOMException(DOMException::INVALID_ACCESS_ERR); //if(m_type > CSSPrimitiveValue::CSS_ATTR) throw DOMException(DOMException::INVALID_ACCESS_ERR); if (m_type < CSSPrimitiveValue::CSS_STRING || m_type > CSSPrimitiveValue::CSS_ATTR) { exceptioncode = CSSException::SYNTAX_ERR + CSSException::_EXCEPTION_OFFSET; return; } if (stringType != CSSPrimitiveValue::CSS_IDENT) { m_value.string = stringValue.implementation(); m_value.string->ref(); m_type = stringType; } // ### parse ident } unsigned short CSSPrimitiveValueImpl::cssValueType() const { return CSSValue::CSS_PRIMITIVE_VALUE; } bool CSSPrimitiveValueImpl::parseString(const DOMString &/*string*/, bool) { // ### // qDebug() << "WARNING: CSSPrimitiveValueImpl::parseString, unimplemented, was called"; return false; } int CSSPrimitiveValueImpl::getIdent() { if (m_type != CSSPrimitiveValue::CSS_IDENT) { return 0; } return m_value.ident; } DOM::DOMString CSSPrimitiveValueImpl::cssText() const { // ### return the original value instead of a generated one (e.g. color // name if it was specified) - check what spec says about this DOMString text; switch (m_type) { case CSSPrimitiveValue::CSS_UNKNOWN: // ### break; case CSSPrimitiveValue::CSS_NUMBER: // We want to output integral values w/o a period, but others as-is if (m_value.num == (int)m_value.num) { text = DOMString(QString::number((int)m_value.num)); } else { text = DOMString(QString::number(m_value.num)); } break; case CSSPrimitiveValue::CSS_PERCENTAGE: text = DOMString(QString::number(m_value.num) + "%"); break; case CSSPrimitiveValue::CSS_EMS: text = DOMString(QString::number(m_value.num) + "em"); break; case CSSPrimitiveValue::CSS_EXS: text = DOMString(QString::number(m_value.num) + "ex"); break; case CSSPrimitiveValue::CSS_CHS: text = DOMString(QString::number( m_value.num ) + "ch"); break; case CSSPrimitiveValue::CSS_REMS: text = DOMString(QString::number( m_value.num ) + "rem"); break; case CSSPrimitiveValue::CSS_PX: text = DOMString(QString::number(m_value.num) + "px"); break; case CSSPrimitiveValue::CSS_CM: text = DOMString(QString::number(m_value.num) + "cm"); break; case CSSPrimitiveValue::CSS_MM: text = DOMString(QString::number(m_value.num) + "mm"); break; case CSSPrimitiveValue::CSS_IN: text = DOMString(QString::number(m_value.num) + "in"); break; case CSSPrimitiveValue::CSS_PT: text = DOMString(QString::number(m_value.num) + "pt"); break; case CSSPrimitiveValue::CSS_PC: text = DOMString(QString::number(m_value.num) + "pc"); break; case CSSPrimitiveValue::CSS_DEG: text = DOMString(QString::number(m_value.num) + "deg"); break; case CSSPrimitiveValue::CSS_RAD: text = DOMString(QString::number(m_value.num) + "rad"); break; case CSSPrimitiveValue::CSS_GRAD: text = DOMString(QString::number(m_value.num) + "grad"); break; case CSSPrimitiveValue::CSS_MS: text = DOMString(QString::number(m_value.num) + "ms"); break; case CSSPrimitiveValue::CSS_S: text = DOMString(QString::number(m_value.num) + "s"); break; case CSSPrimitiveValue::CSS_HZ: text = DOMString(QString::number(m_value.num) + "hz"); break; case CSSPrimitiveValue::CSS_KHZ: text = DOMString(QString::number(m_value.num) + "khz"); break; case CSSPrimitiveValue::CSS_DIMENSION: // ### break; case CSSPrimitiveValue::CSS_STRING: text = quoteStringIfNeeded(m_value.string); break; case CSSPrimitiveValue::CSS_URI: text = "url("; text += DOMString(m_value.string); text += ")"; break; case CSSPrimitiveValue::CSS_IDENT: text = getValueName(m_value.ident); break; case CSSPrimitiveValue::CSS_ATTR: text = "attr("; text += DOMString(m_value.string); text += ")"; break; case CSSPrimitiveValue::CSS_COUNTER: text = "counter("; text += m_value.counter->m_identifier; text += ")"; // ### add list-style and separator break; case CSSPrimitiveValue::CSS_RECT: { RectImpl *rectVal = getRectValue(); text = "rect("; text += rectVal->top()->cssText() + DOMString(" "); text += rectVal->right()->cssText() + DOMString(" "); text += rectVal->bottom()->cssText() + DOMString(" "); text += rectVal->left()->cssText() + DOMString(")"); break; } case CSSPrimitiveValue::CSS_RGBCOLOR: if (qAlpha(m_value.rgbcolor) != 0xFF) { if (m_value.rgbcolor == khtml::transparentColor) { text = "transparent"; } else text = QString("rgba(" + QString::number(qRed(m_value.rgbcolor)) + ", " + QString::number(qGreen(m_value.rgbcolor)) + ", " + QString::number(qBlue(m_value.rgbcolor)) + ", " + QString::number(qAlpha(m_value.rgbcolor) / 255.0) + ")"); } else { text = QString("rgb(" + QString::number(qRed(m_value.rgbcolor)) + ", " + QString::number(qGreen(m_value.rgbcolor)) + ", " + QString::number(qBlue(m_value.rgbcolor)) + ")"); } break; case CSSPrimitiveValue::CSS_PAIR: text = m_value.pair->first()->cssText(); text += " "; text += m_value.pair->second()->cssText(); break; default: break; } return text; } // ----------------------------------------------------------------- RectImpl::RectImpl() { m_top = nullptr; m_right = nullptr; m_bottom = nullptr; m_left = nullptr; } RectImpl::~RectImpl() { if (m_top) { m_top->deref(); } if (m_right) { m_right->deref(); } if (m_bottom) { m_bottom->deref(); } if (m_left) { m_left->deref(); } } void RectImpl::setTop(CSSPrimitiveValueImpl *top) { if (top) { top->ref(); } if (m_top) { m_top->deref(); } m_top = top; } void RectImpl::setRight(CSSPrimitiveValueImpl *right) { if (right) { right->ref(); } if (m_right) { m_right->deref(); } m_right = right; } void RectImpl::setBottom(CSSPrimitiveValueImpl *bottom) { if (bottom) { bottom->ref(); } if (m_bottom) { m_bottom->deref(); } m_bottom = bottom; } void RectImpl::setLeft(CSSPrimitiveValueImpl *left) { if (left) { left->ref(); } if (m_left) { m_left->deref(); } m_left = left; } // ----------------------------------------------------------------- PairImpl::~PairImpl() { if (m_first) { m_first->deref(); } if (m_second) { m_second->deref(); } } void PairImpl::setFirst(CSSPrimitiveValueImpl *first) { if (first == m_first) { return; } if (m_first) { m_first->deref(); } m_first = first; if (m_first) { m_first->ref(); } } void PairImpl::setSecond(CSSPrimitiveValueImpl *second) { if (second == m_second) { return; } if (m_second) { m_second->deref(); } m_second = second; if (m_second) { m_second->ref(); } } // ----------------------------------------------------------------- CSSImageValueImpl::CSSImageValueImpl(const DOMString &url, StyleBaseImpl *style) : CSSPrimitiveValueImpl(url, CSSPrimitiveValue::CSS_URI) { m_image = nullptr; const DOMString imgUrl = url.trimSpaces(); if (!imgUrl.isEmpty()) { m_fullImageUrl = style->baseURL().resolved(QUrl(imgUrl.string())).toString(); } else { m_fullImageUrl.clear(); } } CSSImageValueImpl::CSSImageValueImpl() : CSSPrimitiveValueImpl(CSS_VAL_NONE) { m_image = nullptr; m_fullImageUrl.clear(); } CSSImageValueImpl::~CSSImageValueImpl() { if (m_image) { m_image->deref(this); } } khtml::CachedImage *CSSImageValueImpl::requestCssImage(DocumentImpl *doc) { if (!m_image && !m_fullImageUrl.isEmpty()) { m_image = doc->docLoader()->requestImage(m_fullImageUrl); if (m_image) { m_image->ref(this); } } return m_image; } // ------------------------------------------------------------------------ FontFamilyValueImpl::FontFamilyValueImpl(const QString &string) : CSSPrimitiveValueImpl(DOMString(string), CSSPrimitiveValue::CSS_STRING) { static const QRegExp parenReg(" \\(.*\\)$"); // static const QRegExp braceReg(" \\[.*\\]$"); parsedFontName = string; // a language tag is often added in braces at the end. Remove it. parsedFontName.replace(parenReg, QString()); #if 0 // cannot use such early checks against the font database anymore, // as the font subsystem might not contain the requested font yet // (case of downloadable font faces) // remove [Xft] qualifiers parsedFontName.replace(braceReg, QString()); const QString &available = KHTMLSettings::availableFamilies(); parsedFontName = parsedFontName.toLower(); // qDebug() << "searching for face '" << parsedFontName << "'"; int pos = available.indexOf(',' + parsedFontName + ',', 0, Qt::CaseInsensitive); if (pos == -1) { // many pages add extra MSs to make sure it's windows only ;( if (parsedFontName.startsWith("ms ")) { parsedFontName = parsedFontName.mid(3); } if (parsedFontName.endsWith(" ms")) { parsedFontName.truncate(parsedFontName.length() - 3); } pos = available.indexOf(",ms " + parsedFontName + ',', 0, Qt::CaseInsensitive); if (pos == -1) { pos = available.indexOf(',' + parsedFontName + " ms,", 0, Qt::CaseInsensitive); } } if (pos != -1) { ++pos; int p = available.indexOf(',', pos); assert(p != -1); // available is supposed to start and end with , parsedFontName = available.mid(pos, p - pos); // qDebug() << "going for '" << parsedFontName << "'"; } #endif // !APPLE_CHANGES } FontValueImpl::FontValueImpl() : style(nullptr), variant(nullptr), weight(nullptr), size(nullptr), lineHeight(nullptr), family(nullptr) { } FontValueImpl::~FontValueImpl() { delete style; delete variant; delete weight; delete size; delete lineHeight; delete family; } DOMString FontValueImpl::cssText() const { // font variant weight size / line-height family DOMString result(""); if (style) { result += style->cssText(); } if (variant) { if (result.length() > 0) { result += " "; } result += variant->cssText(); } if (weight) { if (result.length() > 0) { result += " "; } result += weight->cssText(); } if (size) { if (result.length() > 0) { result += " "; } result += size->cssText(); } if (lineHeight) { if (!size) { result += " "; } result += "/"; result += lineHeight->cssText(); } if (family) { if (result.length() > 0) { result += " "; } result += family->cssText(); } return result; } QuotesValueImpl::QuotesValueImpl() : levels(0) { } DOMString QuotesValueImpl::cssText() const { return QString("\"" + data.join("\" \"") + "\""); } void QuotesValueImpl::addLevel(const QString &open, const QString &close) { data.append(open); data.append(close); levels++; } QString QuotesValueImpl::openQuote(int level) const { if (levels == 0) { return ""; } level--; // increments are calculated before openQuote is called // qDebug() << "Open quote level:" << level; if (level < 0) { level = 0; } else if (level >= (int) levels) { level = (int)(levels - 1); } return data[level * 2]; } QString QuotesValueImpl::closeQuote(int level) const { if (levels == 0) { return ""; } // qDebug() << "Close quote level:" << level; if (level < 0) { level = 0; } else if (level >= (int) levels) { level = (int)(levels - 1); } return data[level * 2 + 1]; } // Used for text-shadow and box-shadow ShadowValueImpl::ShadowValueImpl(CSSPrimitiveValueImpl *_x, CSSPrimitiveValueImpl *_y, CSSPrimitiveValueImpl *_blur, CSSPrimitiveValueImpl *_color) : x(_x), y(_y), blur(_blur), color(_color) {} ShadowValueImpl::~ShadowValueImpl() { delete x; delete y; delete blur; delete color; } DOMString ShadowValueImpl::cssText() const { DOMString text(""); if (color) { text += color->cssText(); } if (x) { if (text.length() > 0) { text += " "; } text += x->cssText(); } if (y) { if (text.length() > 0) { text += " "; } text += y->cssText(); } if (blur) { if (text.length() > 0) { text += " "; } text += blur->cssText(); } return text; } DOMString CounterActImpl::cssText() const { DOMString text(m_counter); text += DOMString(QString::number(m_value)); return text; } DOMString CSSProperty::cssText() const { return getPropertyName(m_id) + DOMString(": ") + m_value->cssText() + (m_important ? DOMString(" !important") : DOMString()) + DOMString("; "); } // ------------------------------------------------------------------------- #if 0 // ENABLE(SVG_FONTS) bool CSSFontFaceSrcValueImpl::isSVGFontFaceSrc() const { return !strcasecmp(m_format, "svg"); } #endif bool CSSFontFaceSrcValueImpl::isSupportedFormat() const { // Normally we would just check the format, but in order to avoid conflicts with the old WinIE style of font-face, // we will also check to see if the URL ends with .eot. If so, we'll go ahead and assume that we shouldn't load it. if (m_format.isEmpty()) { // Check for .eot. if (m_resource.endsWith(".eot") || m_resource.endsWith(".EOT")) { return false; } return true; } return !strcasecmp(m_format, "truetype") || !strcasecmp(m_format, "opentype") || !strcasecmp(m_format, "woff") #if 0 //ENABLE(SVG_FONTS) || isSVGFontFaceSrc() #endif ; } DOMString CSSFontFaceSrcValueImpl::cssText() const { DOMString result; if (isLocal()) { result += "local("; } else { result += "url("; } result += m_resource; result += ")"; if (!m_format.isEmpty()) { result += " format("; result += m_format; result += ")"; } return result; } diff --git a/src/css/cssparser.cpp b/src/css/cssparser.cpp index d298b54..db3d02d 100644 --- a/src/css/cssparser.cpp +++ b/src/css/cssparser.cpp @@ -1,3353 +1,3352 @@ /* * This file is part of the DOM implementation for KDE. * * Copyright 2003 Lars Knoll (knoll@kde.org) * Copyright 2005 Allan Sandfeld Jensen (kde@carewolf.com) * Copyright (C) 2004, 2005, 2006, 2007 Apple Computer, Inc. * Copyright (C) 2008 Maksim Orlovich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ // #define CSS_DEBUG // #define TOKEN_DEBUG #define YYDEBUG 0 #include "cssparser.h" #include #include #include #include "css_valueimpl.h" #include "css_ruleimpl.h" #include "css_stylesheetimpl.h" #include "css_mediaquery.h" #include "cssproperties.h" #include "cssvalues.h" #include #include using namespace DOM; // used to promote background: left to left center #define BACKGROUND_SKIP_CENTER( num ) \ if ( !pos_ok[ num ] && expected != 1 ) { \ pos_ok[num] = true; \ pos[num] = 0; \ skip_next = false; \ } ValueList::~ValueList() { const int numValues = m_values.size(); for (int i = 0; i < numValues; i++) if (m_values[i].unit == Value::Function) { delete m_values[i].function; } } namespace { class ShorthandScope { public: ShorthandScope(CSSParser *parser, int propId) : m_parser(parser) { if (!(m_parser->m_inParseShorthand++)) { m_parser->m_currentShorthand = propId; } } ~ShorthandScope() { if (!(--m_parser->m_inParseShorthand)) { m_parser->m_currentShorthand = 0; } } private: CSSParser *m_parser; }; } using namespace DOM; #if YYDEBUG > 0 extern int cssyydebug; #endif extern int cssyyparse(void *parser); CSSParser *CSSParser::currentParser = nullptr; CSSParser::CSSParser(bool strictParsing) { #ifdef CSS_DEBUG qDebug() << "CSSParser::CSSParser this=" << this; #endif strict = strictParsing; parsedProperties = (CSSProperty **) malloc(32 * sizeof(CSSProperty *)); numParsedProperties = 0; maxParsedProperties = 32; data = nullptr; valueList = nullptr; rule = nullptr; id = 0; important = false; m_inParseShorthand = 0; m_currentShorthand = 0; m_implicitShorthand = false; yy_start = 1; #if YYDEBUG > 0 cssyydebug = 1; #endif } CSSParser::~CSSParser() { if (numParsedProperties) { clearProperties(); } free(parsedProperties); delete valueList; #ifdef CSS_DEBUG qDebug() << "CSSParser::~CSSParser this=" << this; #endif free(data); } unsigned int CSSParser::defaultNamespace() { if (styleElement && styleElement->isCSSStyleSheet()) { return static_cast(styleElement)->defaultNamespace(); } else { return anyNamespace; } } void CSSParser::runParser() { CSSParser *old = currentParser; currentParser = this; cssyyparse(this); currentParser = old; boundLocalNames.clear(); } void CSSParser::setupParser(const char *prefix, const DOMString &string, const char *suffix) { unsigned preflen = strlen(prefix); unsigned sufflen = strlen(suffix); int length = string.length() + preflen + sufflen + 8; free(data); data = (unsigned short *)malloc(length * sizeof(unsigned short)); for (unsigned i = 0; i < preflen; i++) { data[i] = prefix[i]; } memcpy(data + preflen, string.unicode(), string.length()*sizeof(unsigned short)); unsigned start = preflen + string.length(); unsigned end = start + sufflen; for (unsigned i = start; i < end; i++) { data[i] = suffix[i - start]; } // the flex scanner sometimes give invalid reads for any // smaller padding - try e.g. css/invalid-rules-005.html or see #167318 data[length - 1] = 0; data[length - 2] = 0; data[length - 3] = 0; data[length - 4] = 0; data[length - 5] = 0; data[length - 6] = 0; data[length - 7] = 0; data[length - 8] = 0; yyTok = -1; block_nesting = 0; yy_hold_char = 0; yyleng = 0; yytext = yy_c_buf_p = data; yy_hold_char = *yy_c_buf_p; } void CSSParser::parseSheet(CSSStyleSheetImpl *sheet, const DOMString &string) { styleElement = sheet; styleDocument = nullptr; setupParser("", string, ""); #ifdef CSS_DEBUG qDebug() << ">>>>>>> start parsing style sheet"; #endif runParser(); #ifdef CSS_DEBUG qDebug() << "<<<<<<< done parsing style sheet"; #endif delete rule; rule = nullptr; } CSSRuleImpl *CSSParser::parseRule(DOM::CSSStyleSheetImpl *sheet, const DOM::DOMString &string) { styleElement = sheet; styleDocument = nullptr; setupParser("@-khtml-rule{", string, "} "); runParser(); CSSRuleImpl *result = rule; rule = nullptr; return result; } static void addParsedProperties(DOM::CSSStyleDeclarationImpl *declaration, CSSProperty **parsedProperties, int numProperties) { for (int i = 0; i < numProperties; i++) { // Only add properties that have no !important counterpart present if (!declaration->getPropertyPriority(parsedProperties[i]->id()) || parsedProperties[i]->isImportant()) { declaration->removeProperty(parsedProperties[i]->m_id); declaration->values()->append(parsedProperties[i]); } } } bool CSSParser::parseValue(DOM::CSSStyleDeclarationImpl *declaration, int _id, const DOM::DOMString &string, bool _important) { #ifdef CSS_DEBUG qDebug() << "CSSParser::parseValue: id=" << _id << " important=" << _important - << " value='" << string.string() << "'" << endl; + << " value='" << string.string() << "'"; #endif styleElement = declaration->stylesheet(); styleDocument = nullptr; setupParser("@-khtml-value{", string, "} "); id = _id; important = _important; runParser(); delete rule; rule = nullptr; bool ok = false; if (numParsedProperties) { ok = true; addParsedProperties(declaration, parsedProperties, numParsedProperties); numParsedProperties = 0; } return ok; } bool CSSParser::parseDeclaration(DOM::CSSStyleDeclarationImpl *declaration, const DOM::DOMString &string) { #ifdef CSS_DEBUG qDebug() << "CSSParser::parseDeclaration:" - << " value='" << string.string() << "'" << endl; + << " value='" << string.string() << "'"; #endif styleElement = declaration->stylesheet(); styleDocument = nullptr; setupParser("@-khtml-decls{", string, "} "); runParser(); delete rule; rule = nullptr; bool ok = false; if (numParsedProperties) { ok = true; addParsedProperties(declaration, parsedProperties, numParsedProperties); numParsedProperties = 0; } return ok; } bool CSSParser::parseMediaQuery(DOM::MediaListImpl *queries, const DOM::DOMString &string) { if (string.isEmpty() || string.isNull()) { return true; } mediaQuery = nullptr; // can't use { because tokenizer state switches from mediaquery to initial state when it sees { token. // instead insert one " " (which is S in parser.y) setupParser("@-khtml-mediaquery ", string, "} "); runParser(); bool ok = false; if (mediaQuery) { ok = true; queries->appendMediaQuery(mediaQuery); mediaQuery = nullptr; } return ok; } QList CSSParser::parseSelectorList(DOM::DocumentImpl *doc, const DOM::DOMString &string) { styleElement = nullptr; styleDocument = doc; selectors.clear(); setupParser("@-khtml-selectors{", string, "} "); runParser(); // Make sure to detect problems with pseudos, too bool ok = true; for (int i = 0; i < selectors.size(); ++i) { // we need to check not only us, but also other things we're connected to via // combinators for (DOM::CSSSelector *sel = selectors[i]; sel; sel = sel->tagHistory) { if (sel->match == CSSSelector::PseudoClass || sel->match == CSSSelector::PseudoElement) { if (sel->pseudoType() == CSSSelector::PseudoOther) { ok = false; break; } } } } if (!ok) { qDeleteAll(selectors); selectors.clear(); } return selectors; } void CSSParser::addProperty(int propId, CSSValueImpl *value, bool important) { CSSProperty *prop = new CSSProperty; prop->m_id = propId; prop->setValue(value); prop->m_important = important; prop->m_implicit = m_implicitShorthand; if (numParsedProperties >= maxParsedProperties) { maxParsedProperties += 32; parsedProperties = (CSSProperty **) realloc(parsedProperties, maxParsedProperties * sizeof(CSSProperty *)); } parsedProperties[numParsedProperties++] = prop; } void CSSParser::rollbackParsedProperties(int toNumParsedProperties) { while (numParsedProperties > toNumParsedProperties) { --numParsedProperties; delete parsedProperties[numParsedProperties]; } } CSSStyleDeclarationImpl *CSSParser::createStyleDeclaration(CSSStyleRuleImpl *rule) { QList *propList = new QList; for (int i = 0; i < numParsedProperties; i++) { propList->append(parsedProperties[i]); } numParsedProperties = 0; return new CSSStyleDeclarationImpl(rule, propList); } CSSStyleDeclarationImpl *CSSParser::createFontFaceStyleDeclaration(CSSFontFaceRuleImpl *rule) { QList *propList = new QList; CSSProperty *overriddenSrcProperty = nullptr; for (int i = 0; i < numParsedProperties; i++) { CSSProperty *property = parsedProperties[i]; int id = property->id(); if ((id == CSS_PROP_FONT_WEIGHT || id == CSS_PROP_FONT_STYLE || id == CSS_PROP_FONT_VARIANT) && property->value()->isPrimitiveValue()) { // change those to a list of values containing a single value, so that we may always cast to a list in the CSSFontSelector. CSSValueImpl *value = property->value(); value->ref(); property->setValue(new CSSValueListImpl(CSSValueListImpl::Comma)); static_cast(property->value())->append(value); value->deref(); } else if (id == CSS_PROP_SRC) { overriddenSrcProperty = property; continue; } propList->append(property); } if (overriddenSrcProperty) { propList->append(overriddenSrcProperty); } numParsedProperties = 0; return new CSSStyleDeclarationImpl(rule, propList); } void CSSParser::clearProperties() { for (int i = 0; i < numParsedProperties; i++) { delete parsedProperties[i]; } numParsedProperties = 0; } DOM::DocumentImpl *CSSParser::document() const { if (!styleDocument) { const StyleBaseImpl *root = styleElement; while (root->parent()) { root = root->parent(); } if (root->isCSSStyleSheet()) { styleDocument = static_cast(root)->doc(); } } return styleDocument; } bool CSSParser::validUnit(Value *value, int unitflags, bool strict) { if (unitflags & FNonNeg && value->fValue < 0) { return false; } bool b = false; switch (value->unit) { case CSSPrimitiveValue::CSS_NUMBER: b = (unitflags & FNumber); if (!b && ((unitflags & FLength) && (value->fValue == 0 || !strict))) { value->unit = CSSPrimitiveValue::CSS_PX; b = true; } if (!b && (unitflags & FInteger) && value->isInt) { b = true; } break; case CSSPrimitiveValue::CSS_PERCENTAGE: b = (unitflags & FPercent); break; case Value::Q_EMS: case CSSPrimitiveValue::CSS_EMS: case CSSPrimitiveValue::CSS_EXS: case CSSPrimitiveValue::CSS_CHS: case CSSPrimitiveValue::CSS_REMS: case CSSPrimitiveValue::CSS_PX: case CSSPrimitiveValue::CSS_CM: case CSSPrimitiveValue::CSS_MM: case CSSPrimitiveValue::CSS_IN: case CSSPrimitiveValue::CSS_PT: case CSSPrimitiveValue::CSS_PC: b = (unitflags & FLength); break; case CSSPrimitiveValue::CSS_MS: case CSSPrimitiveValue::CSS_S: b = (unitflags & FTime); break; case CSSPrimitiveValue::CSS_DEG: case CSSPrimitiveValue::CSS_RAD: case CSSPrimitiveValue::CSS_GRAD: case CSSPrimitiveValue::CSS_HZ: case CSSPrimitiveValue::CSS_KHZ: case CSSPrimitiveValue::CSS_DPI: case CSSPrimitiveValue::CSS_DPCM: case CSSPrimitiveValue::CSS_DIMENSION: default: break; } return b; } bool CSSParser::parseValue(int propId, bool important) { if (!valueList) { return false; } Value *value = valueList->current(); if (!value) { return false; } int id = value->id; const int num = inShorthand() ? 1 : valueList->size(); if (id == CSS_VAL_INHERIT) { if (num != 1) { return false; } addProperty(propId, new CSSInheritedValueImpl(), important); return true; } else if (id == CSS_VAL_INITIAL) { if (num != 1) { return false; } addProperty(propId, new CSSInitialValueImpl(false/*implicit initial*/), important); return true; } bool valid_primitive = false; CSSValueImpl *parsedValue = nullptr; switch (propId) { /* The comment to the left defines all valid value of this properties as defined * in CSS 2, Appendix F. Property index */ /* All the CSS properties are not supported by the renderer at the moment. * Note that all the CSS2 Aural properties are only checked, if CSS_AURAL is defined * (see parseAuralValues). As we don't support them at all this seems reasonable. */ case CSS_PROP_SIZE: // {1,2} | auto | portrait | landscape | inherit // case CSS_PROP_PAGE: // | auto // ### CHECK // ### To be done if (id) { valid_primitive = true; } break; case CSS_PROP_UNICODE_BIDI: // normal | embed | bidi-override | inherit if (id == CSS_VAL_NORMAL || id == CSS_VAL_EMBED || id == CSS_VAL_BIDI_OVERRIDE) { valid_primitive = true; } break; case CSS_PROP_POSITION: // static | relative | absolute | fixed | inherit if (id == CSS_VAL_STATIC || id == CSS_VAL_RELATIVE || id == CSS_VAL_ABSOLUTE || id == CSS_VAL_FIXED) { valid_primitive = true; } break; case CSS_PROP_PAGE_BREAK_AFTER: // auto | always | avoid | left | right | inherit case CSS_PROP_PAGE_BREAK_BEFORE: // auto | always | avoid | left | right | inherit if (id == CSS_VAL_AUTO || id == CSS_VAL_ALWAYS || id == CSS_VAL_AVOID || id == CSS_VAL_LEFT || id == CSS_VAL_RIGHT) { valid_primitive = true; } break; case CSS_PROP_PAGE_BREAK_INSIDE: // avoid | auto | inherit if (id == CSS_VAL_AUTO || id == CSS_VAL_AVOID) { valid_primitive = true; } break; case CSS_PROP_EMPTY_CELLS: // show | hide | inherit if (id == CSS_VAL_SHOW || id == CSS_VAL_HIDE) { valid_primitive = true; } break; case CSS_PROP_QUOTES: // [ ]+ | none | inherit if (id == CSS_VAL_NONE) { valid_primitive = true; } else { QuotesValueImpl *quotes = new QuotesValueImpl; bool is_valid = true; QString open, close; Value *val = valueList->current(); while (val) { if (val->unit == CSSPrimitiveValue::CSS_STRING) { open = qString(val->string); } else { is_valid = false; break; } valueList->next(); val = valueList->current(); if (val && val->unit == CSSPrimitiveValue::CSS_STRING) { close = qString(val->string); } else { is_valid = false; break; } quotes->addLevel(open, close); valueList->next(); val = valueList->current(); } if (is_valid) { parsedValue = quotes; } else { delete quotes; } } break; case CSS_PROP_CONTENT: // normal | none | inherit | // [ | | | attr(X) | open-quote | close-quote | no-open-quote | no-close-quote ]+ if (id == CSS_VAL_NORMAL || id == CSS_VAL_NONE) { valid_primitive = true; } else { return parseContent(propId, important); } break; case CSS_PROP_WHITE_SPACE: // normal | pre | nowrap | pre-wrap | pre-line | inherit if (id == CSS_VAL_NORMAL || id == CSS_VAL_PRE || id == CSS_VAL_PRE_WRAP || id == CSS_VAL_PRE_LINE || id == CSS_VAL_NOWRAP) { valid_primitive = true; } break; case CSS_PROP_CLIP: // | auto | inherit if (id == CSS_VAL_AUTO) { valid_primitive = true; } else if (value->unit == Value::Function) { return parseShape(propId, important); } break; /* Start of supported CSS properties with validation. This is needed for parseShortHand to work * correctly and allows optimization in khtml::applyRule(..) */ case CSS_PROP_CAPTION_SIDE: // top | bottom | left | right | inherit // Left and right were deprecated in CSS 2.1 and never supported by KHTML if ( /* id == CSS_VAL_LEFT || id == CSS_VAL_RIGHT || */ id == CSS_VAL_TOP || id == CSS_VAL_BOTTOM) { valid_primitive = true; } break; case CSS_PROP_BORDER_COLLAPSE: // collapse | separate | inherit if (id == CSS_VAL_COLLAPSE || id == CSS_VAL_SEPARATE) { valid_primitive = true; } break; case CSS_PROP_VISIBILITY: // visible | hidden | collapse | inherit if (id == CSS_VAL_VISIBLE || id == CSS_VAL_HIDDEN || id == CSS_VAL_COLLAPSE) { valid_primitive = true; } break; case CSS_PROP_OVERFLOW: // visible | hidden | scroll | auto | marquee | inherit case CSS_PROP_OVERFLOW_X: case CSS_PROP_OVERFLOW_Y: if (id == CSS_VAL_VISIBLE || id == CSS_VAL_HIDDEN || id == CSS_VAL_SCROLL || id == CSS_VAL_AUTO || id == CSS_VAL_MARQUEE) { valid_primitive = true; } break; case CSS_PROP_LIST_STYLE_POSITION: // inside | outside | inherit if (id == CSS_VAL_INSIDE || id == CSS_VAL_OUTSIDE) { valid_primitive = true; } break; case CSS_PROP_LIST_STYLE_TYPE: // disc | circle | square | decimal | decimal-leading-zero | lower-roman | // upper-roman | lower-greek | lower-alpha | lower-latin | upper-alpha | // upper-latin | hebrew | armenian | georgian | cjk-ideographic | hiragana | // katakana | hiragana-iroha | katakana-iroha | none | inherit if ((id >= CSS_VAL_DISC && id <= CSS_VAL__KHTML_CLOSE_QUOTE) || id == CSS_VAL_NONE) { valid_primitive = true; } break; case CSS_PROP_DISPLAY: // inline | block | list-item | run-in | inline-block | -khtml-ruler | table | // inline-table | table-row-group | table-header-group | table-footer-group | table-row | // table-column-group | table-column | table-cell | table-caption | none | inherit if ((id >= CSS_VAL_INLINE && id <= CSS_VAL_TABLE_CAPTION) || id == CSS_VAL_NONE) { valid_primitive = true; } break; case CSS_PROP_DIRECTION: // ltr | rtl | inherit if (id == CSS_VAL_LTR || id == CSS_VAL_RTL) { valid_primitive = true; } break; case CSS_PROP_TEXT_TRANSFORM: // capitalize | uppercase | lowercase | none | inherit if ((id >= CSS_VAL_CAPITALIZE && id <= CSS_VAL_LOWERCASE) || id == CSS_VAL_NONE) { valid_primitive = true; } break; case CSS_PROP_FLOAT: // left | right | none | khtml_left | khtml_right | inherit + center for buggy CSS if (id == CSS_VAL_LEFT || id == CSS_VAL_RIGHT || id == CSS_VAL__KHTML_LEFT || id == CSS_VAL__KHTML_RIGHT || id == CSS_VAL_NONE || id == CSS_VAL_CENTER) { valid_primitive = true; } break; case CSS_PROP_CLEAR: // none | left | right | both | inherit if (id == CSS_VAL_NONE || id == CSS_VAL_LEFT || id == CSS_VAL_RIGHT || id == CSS_VAL_BOTH) { valid_primitive = true; } break; case CSS_PROP_TEXT_ALIGN: // left | right | center | justify | khtml_left | khtml_right | khtml_center | | inherit if ((id >= CSS_VAL__KHTML_AUTO && id <= CSS_VAL__KHTML_CENTER) || value->unit == CSSPrimitiveValue::CSS_STRING) { valid_primitive = true; } break; case CSS_PROP_OUTLINE_STYLE: // | inherit case CSS_PROP_BORDER_TOP_STYLE: //// | inherit case CSS_PROP_BORDER_RIGHT_STYLE: // Defined as: none | hidden | dotted | dashed | case CSS_PROP_BORDER_BOTTOM_STYLE: // solid | double | groove | ridge | inset | outset | -khtml-native case CSS_PROP_BORDER_LEFT_STYLE: //// if (id >= CSS_VAL__KHTML_NATIVE && id <= CSS_VAL_DOUBLE) { valid_primitive = true; } break; case CSS_PROP_FONT_WEIGHT: // normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | inherit id = parseFontWeight(value, false); if (id) { valid_primitive = true; } break; case CSS_PROP_BORDER_TOP_RIGHT_RADIUS: case CSS_PROP_BORDER_BOTTOM_RIGHT_RADIUS: case CSS_PROP_BORDER_BOTTOM_LEFT_RADIUS: case CSS_PROP_BORDER_TOP_LEFT_RADIUS: { // ? if (num < 1 || num > 2) { return false; } if (!validUnit(value, FLength | FPercent | FNonNeg, strict)) { return false; } CSSPrimitiveValueImpl *horiz = new CSSPrimitiveValueImpl(value->fValue, (CSSPrimitiveValue::UnitTypes) value->unit); CSSPrimitiveValueImpl *vert; if (num == 2) { value = valueList->next(); if (!validUnit(value, FLength | FPercent | FNonNeg, strict)) { delete horiz; return false; } vert = new CSSPrimitiveValueImpl(value->fValue, (CSSPrimitiveValue::UnitTypes) value->unit); } else { vert = horiz; } addProperty(propId, new CSSPrimitiveValueImpl(new PairImpl(horiz, vert)), important); return true; } case CSS_PROP_BORDER_RADIUS: return parseBorderRadius(important); case CSS_PROP_BORDER_SPACING: { const int properties[2] = { CSS_PROP__KHTML_BORDER_HORIZONTAL_SPACING, CSS_PROP__KHTML_BORDER_VERTICAL_SPACING }; if (num == 1) { ShorthandScope scope(this, CSS_PROP_BORDER_SPACING); if (!parseValue(properties[0], important)) { return false; } CSSValueImpl *value = parsedProperties[numParsedProperties - 1]->value(); addProperty(properties[1], value, important); return true; } else if (num == 2) { ShorthandScope scope(this, CSS_PROP_BORDER_SPACING); const int oldNumParsedProperties = numParsedProperties; if (!parseValue(properties[0], important)) { return false; } if (!parseValue(properties[1], important)) { rollbackParsedProperties(oldNumParsedProperties); return false; } return true; } return false; } case CSS_PROP__KHTML_BORDER_HORIZONTAL_SPACING: case CSS_PROP__KHTML_BORDER_VERTICAL_SPACING: valid_primitive = validUnit(value, FLength | FNonNeg, strict); break; case CSS_PROP_SCROLLBAR_FACE_COLOR: // IE5.5 case CSS_PROP_SCROLLBAR_SHADOW_COLOR: // IE5.5 case CSS_PROP_SCROLLBAR_HIGHLIGHT_COLOR: // IE5.5 case CSS_PROP_SCROLLBAR_3DLIGHT_COLOR: // IE5.5 case CSS_PROP_SCROLLBAR_DARKSHADOW_COLOR: // IE5.5 case CSS_PROP_SCROLLBAR_TRACK_COLOR: // IE5.5 case CSS_PROP_SCROLLBAR_ARROW_COLOR: // IE5.5 case CSS_PROP_SCROLLBAR_BASE_COLOR: // IE5.5 if (strict) { break; } /* nobreak */ case CSS_PROP_OUTLINE_COLOR: // | invert | inherit // outline has "invert" as additional keyword. if (propId == CSS_PROP_OUTLINE_COLOR && id == CSS_VAL_INVERT) { valid_primitive = true; break; } /* nobreak */ case CSS_PROP_BACKGROUND_COLOR: // | inherit case CSS_PROP_BORDER_TOP_COLOR: // | inherit case CSS_PROP_BORDER_RIGHT_COLOR: // | inherit case CSS_PROP_BORDER_BOTTOM_COLOR: // | inherit case CSS_PROP_BORDER_LEFT_COLOR: // | inherit case CSS_PROP_COLOR: // | inherit if (id == CSS_VAL__KHTML_TEXT || id == CSS_VAL_MENU || (id >= CSS_VAL_AQUA && id <= CSS_VAL_WINDOWTEXT) || id == CSS_VAL_TRANSPARENT || id == CSS_VAL_CURRENTCOLOR || (id >= CSS_VAL_GREY && id < CSS_VAL__KHTML_TEXT && !strict)) { valid_primitive = true; } else { parsedValue = parseColor(); if (parsedValue) { valueList->next(); } } break; case CSS_PROP_CURSOR: // [ auto | default | none | // context-menu | help | pointer | progress | wait | // cell | crosshair | text | vertical-text | // alias | copy | move | no-drop | not-allowed | // e-resize | ne-resize | nw-resize | n-resize | se-resize | sw-resize | s-resize | w-resize | // ew-resize | ns-resize | nesw-resize | nwse-resize | // col-resize | row-resize | all-scroll // ] ] | inherit // MSIE 5 compatibility :/ if (!strict && id == CSS_VAL_HAND) { id = CSS_VAL_POINTER; valid_primitive = true; } else if ((id >= CSS_VAL_AUTO && id <= CSS_VAL_ALL_SCROLL) || id == CSS_VAL_NONE) { valid_primitive = true; } break; case CSS_PROP_BACKGROUND_ATTACHMENT: case CSS_PROP_BACKGROUND_CLIP: case CSS_PROP_BACKGROUND_IMAGE: case CSS_PROP_BACKGROUND_ORIGIN: case CSS_PROP_BACKGROUND_POSITION: case CSS_PROP_BACKGROUND_POSITION_X: case CSS_PROP_BACKGROUND_POSITION_Y: case CSS_PROP_BACKGROUND_SIZE: case CSS_PROP_BACKGROUND_REPEAT: { CSSValueImpl *val1 = nullptr, *val2 = nullptr; int propId1, propId2; if (parseBackgroundProperty(propId, propId1, propId2, val1, val2)) { addProperty(propId1, val1, important); if (val2) { addProperty(propId2, val2, important); } return true; } return false; } case CSS_PROP_LIST_STYLE_IMAGE: // | none | inherit if (id == CSS_VAL_NONE) { parsedValue = new CSSImageValueImpl(); valueList->next(); } else if (value->unit == CSSPrimitiveValue::CSS_URI) { // ### allow string in non strict mode? if (styleElement) { const DOMString uri = domString(value->string); parsedValue = new CSSImageValueImpl(uri, styleElement); valueList->next(); } } break; case CSS_PROP_OUTLINE_WIDTH: // | inherit case CSS_PROP_BORDER_TOP_WIDTH: //// | inherit case CSS_PROP_BORDER_RIGHT_WIDTH: // Which is defined as case CSS_PROP_BORDER_BOTTOM_WIDTH: // thin | medium | thick | case CSS_PROP_BORDER_LEFT_WIDTH: //// if (id == CSS_VAL_THIN || id == CSS_VAL_MEDIUM || id == CSS_VAL_THICK) { valid_primitive = true; } else { valid_primitive = validUnit(value, FLength|FNonNeg, strict); } break; case CSS_PROP_LETTER_SPACING: // normal | | inherit case CSS_PROP_WORD_SPACING: // normal | | inherit if (id == CSS_VAL_NORMAL) { valid_primitive = true; } else { valid_primitive = validUnit(value, FLength, strict); } break; case CSS_PROP_TEXT_INDENT: // | | inherit valid_primitive = (!id && validUnit(value, FLength | FPercent, strict)); break; case CSS_PROP_PADDING_TOP: // | | inherit case CSS_PROP_PADDING_RIGHT: // | inherit case CSS_PROP_PADDING_BOTTOM: // Which is defined as case CSS_PROP_PADDING_LEFT: // | case CSS_PROP__KHTML_PADDING_START: valid_primitive = (!id && validUnit(value, FLength | FPercent | FNonNeg, strict)); break; case CSS_PROP_MAX_HEIGHT: // | | none | inherit case CSS_PROP_MAX_WIDTH: // | | none | inherit if (id == CSS_VAL_NONE) { valid_primitive = true; break; } /* nobreak */ case CSS_PROP_MIN_HEIGHT: // | | inherit case CSS_PROP_MIN_WIDTH: // | | inherit valid_primitive = (!id && validUnit(value, FLength | FPercent | FNonNeg, strict)); break; case CSS_PROP_FONT_SIZE: // | | | | inherit if (id >= CSS_VAL_XX_SMALL && id <= CSS_VAL_LARGER) { valid_primitive = true; } else { valid_primitive = validUnit(value, FLength | FPercent | FNonNeg, strict); } break; case CSS_PROP_FONT_STYLE: // normal | italic | oblique | inherit if (id == CSS_VAL_NORMAL || id == CSS_VAL_ITALIC || id == CSS_VAL_OBLIQUE) { valid_primitive = true; } break; case CSS_PROP_FONT_VARIANT: // normal | small-caps | inherit if (id == CSS_VAL_NORMAL || id == CSS_VAL_SMALL_CAPS) { valid_primitive = true; } break; case CSS_PROP_VERTICAL_ALIGN: // baseline | sub | super | top | text-top | middle | bottom | text-bottom | // | | inherit if (id >= CSS_VAL_BASELINE && id <= CSS_VAL__KHTML_BASELINE_MIDDLE) { valid_primitive = true; } else { valid_primitive = (!id && validUnit(value, FLength | FPercent, strict)); } break; case CSS_PROP_HEIGHT: // | | auto | inherit case CSS_PROP_WIDTH: // | | auto | inherit if (id == CSS_VAL_AUTO) { valid_primitive = true; } else // ### handle multilength case where we allow relative units { valid_primitive = (!id && validUnit(value, FLength | FPercent | FNonNeg, strict)); } break; case CSS_PROP_BOTTOM: // | | auto | inherit case CSS_PROP_LEFT: // | | auto | inherit case CSS_PROP_RIGHT: // | | auto | inherit case CSS_PROP_TOP: // | | auto | inherit case CSS_PROP_MARGIN_TOP: //// | inherit case CSS_PROP_MARGIN_RIGHT: // Which is defined as case CSS_PROP_MARGIN_BOTTOM: // | | auto | inherit case CSS_PROP_MARGIN_LEFT: //// case CSS_PROP__KHTML_MARGIN_START: if (id == CSS_VAL_AUTO) { valid_primitive = true; } else { valid_primitive = (!id && validUnit(value, FLength | FPercent, strict)); } break; case CSS_PROP_Z_INDEX: // auto | | inherit // qDebug("parsing z-index: id=%d, fValue=%f", id, value->fValue ); if (id == CSS_VAL_AUTO) { valid_primitive = true; break; } /* nobreak */ case CSS_PROP_ORPHANS: // | inherit case CSS_PROP_WIDOWS: // | inherit // ### not supported later on valid_primitive = (!id && validUnit(value, FInteger, false)); break; case CSS_PROP_LINE_HEIGHT: // normal | | | | inherit if (id == CSS_VAL_NORMAL) { valid_primitive = true; } else { valid_primitive = (!id && validUnit(value, FNumber | FLength | FPercent | FNonNeg, strict)); } break; case CSS_PROP_COUNTER_INCREMENT: // [ ? ]+ | none | inherit if (id == CSS_VAL_NONE) { valid_primitive = true; } else { return parseCounter(propId, true, important); } break; case CSS_PROP_COUNTER_RESET: // [ ? ]+ | none | inherit if (id == CSS_VAL_NONE) { valid_primitive = true; } else { return parseCounter(propId, false, important); } break; case CSS_PROP_FONT_FAMILY: // [[ | ],]* [ | ] | inherit { parsedValue = parseFontFamily(); break; } case CSS_PROP_TEXT_DECORATION: // none | [ underline || overline || line-through || blink ] | inherit if (id == CSS_VAL_NONE) { valid_primitive = true; } else { CSSValueListImpl *list = new CSSValueListImpl; bool is_valid = true; while (is_valid && value) { switch (value->id) { case CSS_VAL_BLINK: break; case CSS_VAL_UNDERLINE: case CSS_VAL_OVERLINE: case CSS_VAL_LINE_THROUGH: list->append(new CSSPrimitiveValueImpl(value->id)); break; default: is_valid = false; } value = valueList->next(); } //qDebug() << "got " << list->length() << "d decorations"; if (list->length() && is_valid) { parsedValue = list; valueList->next(); } else { delete list; } } break; case CSS_PROP_TABLE_LAYOUT: // auto | fixed | inherit if (id == CSS_VAL_AUTO || id == CSS_VAL_FIXED) { valid_primitive = true; } break; case CSS_PROP_SRC: // Only used within @font-face, so cannot use inherit | initial or be !important. This is a list of urls or local references. return parseFontFaceSrc(); case CSS_PROP_UNICODE_RANGE: // return parseFontFaceUnicodeRange(); break; case CSS_PROP__KHTML_FLOW_MODE: if (id == CSS_VAL__KHTML_NORMAL || id == CSS_VAL__KHTML_AROUND_FLOATS) { valid_primitive = true; } break; /* CSS3 properties */ case CSS_PROP_BOX_SIZING: // border-box | content-box | inherit if (id == CSS_VAL_BORDER_BOX || id == CSS_VAL_CONTENT_BOX) { valid_primitive = true; } break; case CSS_PROP_OUTLINE_OFFSET: valid_primitive = validUnit(value, FLength, strict); break; case CSS_PROP_TEXT_SHADOW: // CSS2 property, dropped in CSS2.1, back in CSS3, so treat as CSS3 if (id == CSS_VAL_NONE) { valid_primitive = true; } else { return parseShadow(propId, important); } break; case CSS_PROP_OPACITY: valid_primitive = validUnit(value, FNumber, strict); break; case CSS_PROP__KHTML_USER_INPUT: // none | enabled | disabled | inherit if (id == CSS_VAL_NONE || id == CSS_VAL_ENABLED || id == CSS_VAL_DISABLED) { valid_primitive = true; } // qDebug() << "CSS_PROP__KHTML_USER_INPUT: " << valid_primitive; break; case CSS_PROP__KHTML_MARQUEE: { const int properties[5] = { CSS_PROP__KHTML_MARQUEE_DIRECTION, CSS_PROP__KHTML_MARQUEE_INCREMENT, CSS_PROP__KHTML_MARQUEE_REPETITION, CSS_PROP__KHTML_MARQUEE_STYLE, CSS_PROP__KHTML_MARQUEE_SPEED }; return parseShortHand(propId, properties, 5, important); } case CSS_PROP__KHTML_MARQUEE_DIRECTION: if (id == CSS_VAL_FORWARDS || id == CSS_VAL_BACKWARDS || id == CSS_VAL_AHEAD || id == CSS_VAL_REVERSE || id == CSS_VAL_LEFT || id == CSS_VAL_RIGHT || id == CSS_VAL_DOWN || id == CSS_VAL_UP || id == CSS_VAL_AUTO) { valid_primitive = true; } break; case CSS_PROP__KHTML_MARQUEE_INCREMENT: if (id == CSS_VAL_SMALL || id == CSS_VAL_LARGE || id == CSS_VAL_MEDIUM) { valid_primitive = true; } else { valid_primitive = validUnit(value, FLength | FPercent, strict); } break; case CSS_PROP__KHTML_MARQUEE_STYLE: if (id == CSS_VAL_NONE || id == CSS_VAL_SLIDE || id == CSS_VAL_SCROLL || id == CSS_VAL_ALTERNATE || id == CSS_VAL_UNFURL) { valid_primitive = true; } break; case CSS_PROP__KHTML_MARQUEE_REPETITION: if (id == CSS_VAL_INFINITE) { valid_primitive = true; } else { valid_primitive = validUnit(value, FInteger | FNonNeg, strict); } break; case CSS_PROP__KHTML_MARQUEE_SPEED: if (id == CSS_VAL_NORMAL || id == CSS_VAL_SLOW || id == CSS_VAL_FAST) { valid_primitive = true; } else { valid_primitive = validUnit(value, FTime | FInteger | FNonNeg, strict); } break; case CSS_PROP_TEXT_OVERFLOW: // clip | ellipsis if (id == CSS_VAL_CLIP || id == CSS_VAL_ELLIPSIS) { valid_primitive = true; } break; // End of CSS3 properties /* shorthand properties */ case CSS_PROP_BACKGROUND: // ['background-color' || 'background-image' ||'background-repeat' || // 'background-attachment' || 'background-position'] | inherit return parseBackgroundShorthand(important); case CSS_PROP_BORDER: // [ 'border-width' || 'border-style' || ] | inherit { const int properties[3] = { CSS_PROP_BORDER_WIDTH, CSS_PROP_BORDER_STYLE, CSS_PROP_BORDER_COLOR }; return parseShortHand(propId, properties, 3, important); } case CSS_PROP_BORDER_TOP: // [ 'border-top-width' || 'border-style' || ] | inherit { const int properties[3] = { CSS_PROP_BORDER_TOP_WIDTH, CSS_PROP_BORDER_TOP_STYLE, CSS_PROP_BORDER_TOP_COLOR }; return parseShortHand(propId, properties, 3, important); } case CSS_PROP_BORDER_RIGHT: // [ 'border-right-width' || 'border-style' || ] | inherit { const int properties[3] = { CSS_PROP_BORDER_RIGHT_WIDTH, CSS_PROP_BORDER_RIGHT_STYLE, CSS_PROP_BORDER_RIGHT_COLOR }; return parseShortHand(propId, properties, 3, important); } case CSS_PROP_BORDER_BOTTOM: // [ 'border-bottom-width' || 'border-style' || ] | inherit { const int properties[3] = { CSS_PROP_BORDER_BOTTOM_WIDTH, CSS_PROP_BORDER_BOTTOM_STYLE, CSS_PROP_BORDER_BOTTOM_COLOR }; return parseShortHand(propId, properties, 3, important); } case CSS_PROP_BORDER_LEFT: // [ 'border-left-width' || 'border-style' || ] | inherit { const int properties[3] = { CSS_PROP_BORDER_LEFT_WIDTH, CSS_PROP_BORDER_LEFT_STYLE, CSS_PROP_BORDER_LEFT_COLOR }; return parseShortHand(propId, properties, 3, important); } case CSS_PROP_OUTLINE: // [ 'outline-color' || 'outline-style' || 'outline-width' ] | inherit { const int properties[3] = { CSS_PROP_OUTLINE_WIDTH, CSS_PROP_OUTLINE_STYLE, CSS_PROP_OUTLINE_COLOR }; return parseShortHand(propId, properties, 3, important); } case CSS_PROP_BORDER_COLOR: // {1,4} | inherit { const int properties[4] = { CSS_PROP_BORDER_TOP_COLOR, CSS_PROP_BORDER_RIGHT_COLOR, CSS_PROP_BORDER_BOTTOM_COLOR, CSS_PROP_BORDER_LEFT_COLOR }; return parse4Values(propId, properties, important); } case CSS_PROP_BORDER_WIDTH: // {1,4} | inherit { const int properties[4] = { CSS_PROP_BORDER_TOP_WIDTH, CSS_PROP_BORDER_RIGHT_WIDTH, CSS_PROP_BORDER_BOTTOM_WIDTH, CSS_PROP_BORDER_LEFT_WIDTH }; return parse4Values(propId, properties, important); } case CSS_PROP_BORDER_STYLE: // {1,4} | inherit { const int properties[4] = { CSS_PROP_BORDER_TOP_STYLE, CSS_PROP_BORDER_RIGHT_STYLE, CSS_PROP_BORDER_BOTTOM_STYLE, CSS_PROP_BORDER_LEFT_STYLE }; return parse4Values(propId, properties, important); } case CSS_PROP_MARGIN: // {1,4} | inherit { const int properties[4] = { CSS_PROP_MARGIN_TOP, CSS_PROP_MARGIN_RIGHT, CSS_PROP_MARGIN_BOTTOM, CSS_PROP_MARGIN_LEFT }; return parse4Values(propId, properties, important); } case CSS_PROP_PADDING: // {1,4} | inherit { const int properties[4] = { CSS_PROP_PADDING_TOP, CSS_PROP_PADDING_RIGHT, CSS_PROP_PADDING_BOTTOM, CSS_PROP_PADDING_LEFT }; return parse4Values(propId, properties, important); } case CSS_PROP_FONT: { return parseFontShorthand(important); } case CSS_PROP_LIST_STYLE: { return parseListStyleShorthand(important); } case CSS_PROP_WORD_WRAP: // normal | break-word if (id == CSS_VAL_NORMAL || id == CSS_VAL_BREAK_WORD) { valid_primitive = true; } break; default: return parseSVGValue(propId, important); // #ifdef CSS_DEBUG // qDebug() << "illegal or CSS2 Aural property: " << val; // #endif //break; } if (valid_primitive) { if (id != 0) { parsedValue = new CSSPrimitiveValueImpl(id); } else if (value->unit == CSSPrimitiveValue::CSS_STRING) parsedValue = new CSSPrimitiveValueImpl(domString(value->string), (CSSPrimitiveValue::UnitTypes) value->unit); else if (value->unit >= CSSPrimitiveValue::CSS_NUMBER && value->unit <= CSSPrimitiveValue::CSS_KHZ) { parsedValue = new CSSPrimitiveValueImpl(value->fValue, (CSSPrimitiveValue::UnitTypes) value->unit); } else if (value->unit >= Value::Q_EMS) { parsedValue = new CSSQuirkPrimitiveValueImpl(value->fValue, CSSPrimitiveValue::CSS_EMS); } valueList->next(); } if (parsedValue) { if (!valueList->current() || inShorthand()) { addProperty(propId, parsedValue, important); return true; } delete parsedValue; } return false; } void CSSParser::addBackgroundValue(CSSValueImpl *&lval, CSSValueImpl *rval) { if (lval) { if (lval->isValueList()) { static_cast(lval)->append(rval); } else { CSSValueImpl *oldVal = lval; CSSValueListImpl *list = new CSSValueListImpl(CSSValueListImpl::Comma); lval = list; list->append(oldVal); list->append(rval); } } else { lval = rval; } } bool CSSParser::parseBackgroundShorthand(bool important) { // Order is important in this array: // 'position' must come before color because a plain old "0" is a legal color in quirks mode // but it's usually the X coordinate of a position. // 'size' must be the next property after 'position' in order to correctly parse '/size'. // 'origin' must come before 'clip' because the first value found belongs to 'origin', // the second (if any) to 'clip'. const int numProperties = 8; const int properties[numProperties] = { CSS_PROP_BACKGROUND_IMAGE, CSS_PROP_BACKGROUND_REPEAT, CSS_PROP_BACKGROUND_ATTACHMENT, CSS_PROP_BACKGROUND_POSITION, CSS_PROP_BACKGROUND_SIZE, CSS_PROP_BACKGROUND_ORIGIN, CSS_PROP_BACKGROUND_CLIP, CSS_PROP_BACKGROUND_COLOR }; ShorthandScope scope(this, CSS_PROP_BACKGROUND); bool parsedProperty[numProperties] = { false }; // compiler will repeat false as necessary CSSValueImpl *values[numProperties] = { nullptr }; // compiler will repeat 0 as necessary CSSValueImpl *positionYValue = nullptr; int parsedOriginIdent = 0; int i; while (valueList->current()) { Value *val = valueList->current(); if (val->unit == Value::Operator && val->iValue == ',') { // We hit the end. Fill in all remaining values with the initial value. valueList->next(); for (i = 0; i < numProperties; ++i) { if (properties[i] == CSS_PROP_BACKGROUND_COLOR && parsedProperty[i]) // Color is not allowed except as the last item in a list. Reject the entire // property. { goto fail; } if (!parsedProperty[i] && properties[i] != CSS_PROP_BACKGROUND_COLOR) { if (properties[i] == CSS_PROP_BACKGROUND_CLIP && parsedOriginIdent != 0) { addBackgroundValue(values[i], new CSSPrimitiveValueImpl(parsedOriginIdent)); } else { addBackgroundValue(values[i], new CSSInitialValueImpl(true/*implicit initial*/)); if (properties[i] == CSS_PROP_BACKGROUND_POSITION) { addBackgroundValue(positionYValue, new CSSInitialValueImpl(true/*implicit initial*/)); } } } parsedProperty[i] = false; } parsedOriginIdent = 0; if (!valueList->current()) { break; } } bool found = false; for (i = 0; !found && i < numProperties; ++i) { if (!parsedProperty[i]) { CSSValueImpl *val1 = nullptr, *val2 = nullptr; int propId1, propId2; if (parseBackgroundProperty(properties[i], propId1, propId2, val1, val2)) { parsedProperty[i] = found = true; addBackgroundValue(values[i], val1); if (properties[i] == CSS_PROP_BACKGROUND_POSITION) { addBackgroundValue(positionYValue, val2); // after 'position' there could be '/size', check for it const Value *v = valueList->current(); if (v && v->unit == Value::Operator && v->iValue == '/') { // next property _must_ be 'size' valueList->next(); ++i; // 'size' is at the next position in properties[] array CSSValueImpl *retVal1 = nullptr, *retVal2 = nullptr; if (parseBackgroundProperty(properties[i], propId1, propId2, retVal1, retVal2)) { parsedProperty[i] = true; addBackgroundValue(values[i], retVal1); } else { goto fail; } } } else if (properties[i] == CSS_PROP_BACKGROUND_ORIGIN) { parsedOriginIdent = static_cast(val1)->getIdent(); } else if (properties[i] == CSS_PROP_BACKGROUND_SIZE) { // we caught an invalid length|percent as background-size goto fail; } } } } // if we didn't find at least one match, this is an // invalid shorthand and we have to ignore it if (!found) { goto fail; } } // end of while loop // Fill in any remaining properties with the initial value. for (i = 0; i < numProperties; ++i) { if (!parsedProperty[i]) { if (properties[i] == CSS_PROP_BACKGROUND_CLIP && parsedOriginIdent != 0) { addBackgroundValue(values[i], new CSSPrimitiveValueImpl(parsedOriginIdent)); } else { addBackgroundValue(values[i], new CSSInitialValueImpl(true/*implicit initial*/)); if (properties[i] == CSS_PROP_BACKGROUND_POSITION) { addBackgroundValue(positionYValue, new CSSInitialValueImpl(true/*implicit initial*/)); } } } } // Now add all of the properties we found. for (i = 0; i < numProperties; i++) { if (properties[i] == CSS_PROP_BACKGROUND_POSITION) { addProperty(CSS_PROP_BACKGROUND_POSITION_X, values[i], important); addProperty(CSS_PROP_BACKGROUND_POSITION_Y, positionYValue, important); } else { addProperty(properties[i], values[i], important); } } return true; fail: for (int k = 0; k < numProperties; k++) { delete values[k]; } delete positionYValue; return false; } static void completeMissingRadii(SharedPtr *array) { if (!array[1]) { array[1] = array[0]; // top-left => top-right } if (!array[2]) { array[2] = array[0]; // top-left => bottom-right } if (!array[3]) { array[3] = array[1]; // top-left => bottom-right } } bool CSSParser::parseBorderRadius(bool important) { const int properties[4] = { CSS_PROP_BORDER_TOP_LEFT_RADIUS, CSS_PROP_BORDER_TOP_RIGHT_RADIUS, CSS_PROP_BORDER_BOTTOM_RIGHT_RADIUS, CSS_PROP_BORDER_BOTTOM_LEFT_RADIUS }; SharedPtr horiz[4], vert[4]; for (int c = 0; c < 4; ++c) { horiz[c] = nullptr; vert [c] = nullptr; } Value *value; // Parse horizontal ones until / or done. for (int c = 0; c < 4; ++c) { value = valueList->current(); if (!value || (value->unit == Value::Operator && value->iValue == '/')) { break; //Saw slash -- exit w/o consuming as we'll use it below. } if (!validUnit(value, FLength | FPercent | FNonNeg, strict)) { return false; } horiz[c] = new CSSPrimitiveValueImpl(value->fValue, (CSSPrimitiveValue::UnitTypes) value->unit); value = valueList->next(); } if (!horiz[0]) { return false; } completeMissingRadii(horiz); // Do we have vertical radii afterwards? if (value && value->unit == Value::Operator && value->iValue == '/') { valueList->next(); for (int c = 0; c < 4; ++c) { // qDebug() << c; value = valueList->current(); if (!value) { break; } if (!validUnit(value, FLength | FPercent | FNonNeg, strict)) { return false; } vert[c] = new CSSPrimitiveValueImpl(value->fValue, (CSSPrimitiveValue::UnitTypes) value->unit); value = valueList->next(); } // If we didn't parse anything, or there is stuff remaining, this is malformed if (!vert[0] || valueList->current()) { return false; } completeMissingRadii(vert); } else { // Nope -- we better be at the end. if (valueList->current()) { return false; } for (int c = 0; c < 4; ++c) { vert[c] = horiz[c]; } } // All OK parsing, add properties for (int c = 0; c < 4; ++c) addProperty(properties[c], new CSSPrimitiveValueImpl( new PairImpl(horiz[c].get(), vert[c].get())), important); return true; } bool CSSParser::parseShortHand(int propId, const int *properties, const int numProperties, bool important) { if (valueList->size() > numProperties) { // discard return false; } ShorthandScope scope(this, propId); // Store current numParsedProperties, we need it in case we should rollback later const int oldNumParsedProperties = numParsedProperties; // Setup an array of booleans to mark which property has been found bool fnd[6]; //Trust me ;) for (int i = 0; i < numProperties; i++) { fnd[i] = false; } bool discard = false; unsigned short numValidProperties = 0; bool foundValid = false; while (valueList->current()) { foundValid = false; for (int propIndex = 0; propIndex < numProperties; ++propIndex) { if (parseValue(properties[propIndex], important)) { foundValid = true; ++numValidProperties; if (fnd[propIndex]) { // found a duplicate discard = true; } else { fnd[propIndex] = true; } break; } } // if we didn't find at least one match, this is an // invalid shorthand and we have to ignore it if (!foundValid) { discard = true; } if (discard) { break; } } if (discard) { // Remove valid properties previously added by parseValue(), if any rollbackParsedProperties(oldNumParsedProperties); return false; } if (numValidProperties == numProperties) { return true; } // Fill in any remaining properties with the initial value. m_implicitShorthand = true; for (int i = 0; i < numProperties; ++i) { if (!fnd[i]) { addProperty(properties[i], new CSSInitialValueImpl(true/*implicit initial*/), important); } } m_implicitShorthand = false; return true; } bool CSSParser::parse4Values(int propId, const int *properties, bool important) { /* From the CSS 2 specs, 8.3 * If there is only one value, it applies to all sides. If there are two values, the top and * bottom margins are set to the first value and the right and left margins are set to the second. * If there are three values, the top is set to the first value, the left and right are set to the * second, and the bottom is set to the third. If there are four values, they apply to the top, * right, bottom, and left, respectively. */ const int num = inShorthand() ? 1 : valueList->size(); //qDebug("parse4Values: num=%d %d", num, valueList->numValues ); ShorthandScope scope(this, propId); const int oldNumParsedProperties = numParsedProperties; // the order is top, right, bottom, left switch (num) { case 1: { if (!parseValue(properties[0], important)) { return false; } CSSValueImpl *value = parsedProperties[numParsedProperties - 1]->value(); m_implicitShorthand = true; addProperty(properties[1], value, important); addProperty(properties[2], value, important); addProperty(properties[3], value, important); m_implicitShorthand = false; break; } case 2: { if (!parseValue(properties[0], important) || !parseValue(properties[1], important)) { rollbackParsedProperties(oldNumParsedProperties); return false; } CSSValueImpl *value = parsedProperties[numParsedProperties - 2]->value(); m_implicitShorthand = true; addProperty(properties[2], value, important); value = parsedProperties[numParsedProperties - 2]->value(); addProperty(properties[3], value, important); m_implicitShorthand = false; break; } case 3: { if (!parseValue(properties[0], important) || !parseValue(properties[1], important) || !parseValue(properties[2], important)) { rollbackParsedProperties(oldNumParsedProperties); return false; } CSSValueImpl *value = parsedProperties[numParsedProperties - 2]->value(); m_implicitShorthand = true; addProperty(properties[3], value, important); m_implicitShorthand = false; break; } case 4: { if (!parseValue(properties[0], important) || !parseValue(properties[1], important) || !parseValue(properties[2], important) || !parseValue(properties[3], important)) { rollbackParsedProperties(oldNumParsedProperties); return false; } break; } default: { return false; } } return true; } // [ | | | attr(X) | open-quote | close-quote | no-open-quote | no-close-quote ]+ | inherit // in CSS 2.1 this got somewhat reduced: // [ | attr(X) | open-quote | close-quote | no-open-quote | no-close-quote ]+ | inherit bool CSSParser::parseContent(int propId, bool important) { QScopedPointer values( new CSSValueListImpl(CSSValueListImpl::Comma)); bool isValid = true; Value *val; CSSValueImpl *parsedValue = nullptr; while ((val = valueList->current())) { parsedValue = nullptr; if (val->unit == CSSPrimitiveValue::CSS_URI) { if (styleElement) { const DOMString uri = domString(val->string); parsedValue = new CSSImageValueImpl(uri, styleElement); #ifdef CSS_DEBUG qDebug() << "content, url=" << uri.string() << " base=" << styleElement->baseURL().url(); #endif } } else if (val->unit == Value::Function) { // attr( X ) | counter( X [,Y] ) | counters( X, Y, [,Z] ) ValueList *args = val->function->args; QString fname = qString(val->function->name).toLower(); if (!args) { return false; } if (fname == "attr(") { if (args->size() != 1) { return false; } Value *a = args->current(); if (a->unit != CSSPrimitiveValue::CSS_IDENT) { isValid = false; break; } if (qString(a->string)[0] == '-') { isValid = false; break; } parsedValue = new CSSPrimitiveValueImpl(domString(a->string), CSSPrimitiveValue::CSS_ATTR); } else if (fname == "counter(") { parsedValue = parseCounterContent(args, false); if (!parsedValue) { return false; } } else if (fname == "counters(") { parsedValue = parseCounterContent(args, true); if (!parsedValue) { return false; } } else { return false; } } else if (val->unit == CSSPrimitiveValue::CSS_IDENT) { // open-quote | close-quote | no-open-quote | no-close-quote if (val->id == CSS_VAL_OPEN_QUOTE || val->id == CSS_VAL_CLOSE_QUOTE || val->id == CSS_VAL_NO_OPEN_QUOTE || val->id == CSS_VAL_NO_CLOSE_QUOTE) { parsedValue = new CSSPrimitiveValueImpl(val->id); } } else if (val->unit == CSSPrimitiveValue::CSS_STRING) { parsedValue = new CSSPrimitiveValueImpl(domString(val->string), CSSPrimitiveValue::CSS_STRING); } if (parsedValue) { values->append(parsedValue); } else { isValid = false; break; } valueList->next(); } if (isValid && values->length()) { addProperty(propId, values.take(), important); valueList->next(); return true; } return false; } CSSValueImpl *CSSParser::parseCounterContent(ValueList *args, bool counters) { const int argsSize = args->size(); if (counters || (argsSize != 1 && argsSize != 3)) if (!counters || (argsSize != 3 && argsSize != 5)) { return nullptr; } CounterImpl *counter = new CounterImpl; Value *i = args->current(); if (i->unit != CSSPrimitiveValue::CSS_IDENT) { goto invalid; } if (qString(i->string)[0] == '-') { goto invalid; } counter->m_identifier = domString(i->string); if (counters) { i = args->next(); if (i->unit != Value::Operator || i->iValue != ',') { goto invalid; } i = args->next(); if (i->unit != CSSPrimitiveValue::CSS_STRING) { goto invalid; } counter->m_separator = domString(i->string); } counter->m_listStyle = CSS_VAL_DECIMAL - CSS_VAL_DISC; i = args->next(); if (i) { if (i->unit != Value::Operator || i->iValue != ',') { goto invalid; } i = args->next(); if (i->unit != CSSPrimitiveValue::CSS_IDENT) { goto invalid; } if (i->id < CSS_VAL_DISC || i->id > CSS_VAL__KHTML_CLOSE_QUOTE) { goto invalid; } counter->m_listStyle = i->id - CSS_VAL_DISC; } return new CSSPrimitiveValueImpl(counter); invalid: delete counter; return nullptr; } CSSValueImpl *CSSParser::parseBackgroundColor() { int id = valueList->current()->id; if (id == CSS_VAL__KHTML_TEXT || id == CSS_VAL_TRANSPARENT || id == CSS_VAL_CURRENTCOLOR || (id >= CSS_VAL_AQUA && id <= CSS_VAL_WINDOWTEXT) || id == CSS_VAL_MENU || (id >= CSS_VAL_GREY && id < CSS_VAL__KHTML_TEXT && !strict)) { return new CSSPrimitiveValueImpl(id); } return parseColor(); } CSSValueImpl *CSSParser::parseBackgroundImage(bool &didParse) { const Value *v = valueList->current(); if (v->id == CSS_VAL_NONE) { didParse = true; return new CSSImageValueImpl(); } else if (v->unit == CSSPrimitiveValue::CSS_URI) { didParse = true; if (styleElement) { const DOMString uri = domString(v->string); return new CSSImageValueImpl(uri, styleElement); } else { return nullptr; } } else { didParse = false; return nullptr; } } CSSValueImpl *CSSParser::parseBackgroundPositionXY(BackgroundPosKind &kindOut) { int id = valueList->current()->id; if (id == CSS_VAL_LEFT || id == CSS_VAL_TOP || id == CSS_VAL_RIGHT || id == CSS_VAL_BOTTOM || id == CSS_VAL_CENTER) { int percent = 0; if (id == CSS_VAL_LEFT || id == CSS_VAL_RIGHT) { kindOut = BgPos_X; if (id == CSS_VAL_RIGHT) { percent = 100; } } else if (id == CSS_VAL_TOP || id == CSS_VAL_BOTTOM) { kindOut = BgPos_Y; if (id == CSS_VAL_BOTTOM) { percent = 100; } } else if (id == CSS_VAL_CENTER) { // Center is ambiguous, so we're not sure which position we've found yet, an x or a y. kindOut = BgPos_Center; percent = 50; } return new CSSPrimitiveValueImpl(percent, CSSPrimitiveValue::CSS_PERCENTAGE); } if (validUnit(valueList->current(), FPercent | FLength, strict)) { kindOut = BgPos_NonKW; return new CSSPrimitiveValueImpl(valueList->current()->fValue, (CSSPrimitiveValue::UnitTypes)valueList->current()->unit); } return nullptr; } void CSSParser::parseBackgroundPosition(CSSValueImpl *&value1, CSSValueImpl *&value2) { value1 = value2 = nullptr; // Parse the first value. We're just making sure that it is one of the valid keywords or a percentage/length. BackgroundPosKind value1pos; value1 = parseBackgroundPositionXY(value1pos); if (!value1) { return; } // Parse the second value, if any. Value *value = valueList->next(); // First check for the comma. If so, we are finished parsing this value or value pair. if (value && value->unit == Value::Operator && value->iValue == ',') { value = nullptr; } bool secondValueSpecifiedAndValid = false; BackgroundPosKind value2pos = BgPos_Center; // true if not specified. if (value) { value2 = parseBackgroundPositionXY(value2pos); if (value2) { secondValueSpecifiedAndValid = true; } else { if (!inShorthand()) { delete value1; value1 = nullptr; return; } } } if (!value2) // Only one value was specified. The other direction is always 50%. // If the one given was not a keyword, it should be viewed as 'x', // and so setting value2 would set y, as desired. // If the one value was a keyword, the swap below would put things in order // if needed. { value2 = new CSSPrimitiveValueImpl(50, CSSPrimitiveValue::CSS_PERCENTAGE); } // Check for various failures bool ok = true; // Two keywords on the same axis. if (value1pos == BgPos_X && value2pos == BgPos_X) { ok = false; } if (value1pos == BgPos_Y && value2pos == BgPos_Y) { ok = false; } // Will we need to swap them? bool swap = (value1pos == BgPos_Y || value2pos == BgPos_X); // If we had a non-KW value and a keyword value that's in the "wrong" position, // this is malformed (#169612) if (swap && (value1pos == BgPos_NonKW || value2pos == BgPos_NonKW)) { ok = false; } if (!ok) { delete value1; delete value2; value1 = nullptr; value2 = nullptr; return; } if (swap) { // Swap our two values. CSSValueImpl *val = value2; value2 = value1; value1 = val; } if (secondValueSpecifiedAndValid) { valueList->next(); } } CSSValueImpl *CSSParser::parseBackgroundSize() { Value *value = valueList->current(); // Parse the first value. CSSPrimitiveValueImpl *parsedValue1; if (value->id == CSS_VAL_COVER || value->id == CSS_VAL_CONTAIN) { valueList->next(); return new CSSPrimitiveValueImpl(value->id); } if (value->id == CSS_VAL_AUTO) { parsedValue1 = new CSSPrimitiveValueImpl(CSS_VAL_AUTO); } else if (validUnit(value, FLength | FPercent | FNonNeg, strict)) { parsedValue1 = new CSSPrimitiveValueImpl(value->fValue, (CSSPrimitiveValue::UnitTypes)value->unit); } else { return nullptr; } // Parse the second value, if any. value = valueList->next(); // First check for the comma. If so, we are finished parsing this value or value pair. if (value && value->unit == Value::Operator && value->iValue == ',') { value = nullptr; } CSSPrimitiveValueImpl *parsedValue2 = nullptr; if (value) { if (value->id == CSS_VAL_AUTO) { parsedValue2 = new CSSPrimitiveValueImpl(CSS_VAL_AUTO); } else if (validUnit(value, FLength | FPercent | FNonNeg, strict)) { parsedValue2 = new CSSPrimitiveValueImpl(value->fValue, (CSSPrimitiveValue::UnitTypes)value->unit); } else if (!inShorthand()) { delete parsedValue1; return nullptr; } } if (parsedValue2) { valueList->next(); } else { // If only one value is given the second is assumed to be ‘auto’ parsedValue2 = new CSSPrimitiveValueImpl(CSS_VAL_AUTO); } PairImpl *pair = new PairImpl(parsedValue1, parsedValue2); return new CSSPrimitiveValueImpl(pair); } bool CSSParser::parseBackgroundProperty(int propId, int &propId1, int &propId2, CSSValueImpl *&retValue1, CSSValueImpl *&retValue2) { #ifdef CSS_DEBUG qDebug() << "parseBackgroundProperty()"; qDebug() << "LOOKING FOR: " << getPropertyName(propId).string(); #endif Value *val; CSSValueListImpl *value = new CSSValueListImpl(CSSValueListImpl::Comma); CSSValueListImpl *value2 = new CSSValueListImpl(CSSValueListImpl::Comma); bool expectComma = false; retValue1 = retValue2 = nullptr; propId1 = propId; propId2 = propId; if (propId == CSS_PROP_BACKGROUND_POSITION) { propId1 = CSS_PROP_BACKGROUND_POSITION_X; propId2 = CSS_PROP_BACKGROUND_POSITION_Y; } while ((val = valueList->current())) { CSSValueImpl *currValue = nullptr, *currValue2 = nullptr; if (expectComma) { if (val->unit != Value::Operator || val->iValue != ',') { goto failed; } valueList->next(); expectComma = false; } else { switch (propId) { case CSS_PROP_BACKGROUND_ATTACHMENT: if (val->id == CSS_VAL_SCROLL || val->id == CSS_VAL_FIXED || val->id == CSS_VAL_LOCAL) { currValue = new CSSPrimitiveValueImpl(val->id); valueList->next(); } break; case CSS_PROP_BACKGROUND_COLOR: currValue = parseBackgroundColor(); if (currValue) { valueList->next(); } break; case CSS_PROP_BACKGROUND_IMAGE: { bool didParse = false; currValue = parseBackgroundImage(didParse); if (didParse) { valueList->next(); } break; } case CSS_PROP_BACKGROUND_CLIP: case CSS_PROP_BACKGROUND_ORIGIN: if (val->id == CSS_VAL_BORDER_BOX || val->id == CSS_VAL_PADDING_BOX || val->id == CSS_VAL_CONTENT_BOX) { currValue = new CSSPrimitiveValueImpl(val->id); valueList->next(); } break; case CSS_PROP_BACKGROUND_POSITION: parseBackgroundPosition(currValue, currValue2); // parseBackgroundPosition advances the valueList pointer break; case CSS_PROP_BACKGROUND_POSITION_X: { BackgroundPosKind pos; currValue = parseBackgroundPositionXY(pos); if (currValue) { if (pos == BgPos_Y) { delete currValue; currValue = nullptr; } else { valueList->next(); } } break; } case CSS_PROP_BACKGROUND_POSITION_Y: { BackgroundPosKind pos; currValue = parseBackgroundPositionXY(pos); if (currValue) { if (pos == BgPos_X) { delete currValue; currValue = nullptr; } else { valueList->next(); } } break; } case CSS_PROP_BACKGROUND_REPEAT: if (val->id >= CSS_VAL_REPEAT && val->id <= CSS_VAL_NO_REPEAT) { currValue = new CSSPrimitiveValueImpl(val->id); valueList->next(); } break; case CSS_PROP_BACKGROUND_SIZE: currValue = parseBackgroundSize(); // parseBackgroundSize advances the valueList pointer break; } if (!currValue) { goto failed; } // When parsing the 'background' shorthand property return the parsed value... if (inShorthand()) { retValue1 = currValue; if (currValue2) { retValue2 = currValue2; } delete value; delete value2; return true; } // ...if not in shorthand, append to the list of value for the property // and expect a comma for the next value (if any) value->append(currValue); if (currValue2) { value2->append(currValue2); } expectComma = true; } } // Now return the value list if (value->length() > 0) { retValue1 = value; if (value2->length() > 0) { retValue2 = value2; } else { delete value2; } return true; } failed: delete value; delete value2; return false; } bool CSSParser::parseShape(int propId, bool important) { Value *value = valueList->current(); ValueList *args = value->function->args; QString fname = qString(value->function->name).toLower(); //qDebug( "parseShape: fname: %d", fname.toLatin1().constData() ); if (fname != "rect(" || !args) { return false; } const int argsSize = args->size(); // rect( t, r, b, l ) || rect( t r b l ) if (argsSize != 4 && argsSize != 7) { return false; } RectImpl *rect = new RectImpl(); bool valid = true; int i = 0; Value *a = args->current(); while (a) { CSSPrimitiveValueImpl *length; if (a->id == CSS_VAL_AUTO) { length = new CSSPrimitiveValueImpl(CSS_VAL_AUTO); } else { valid = validUnit(a, FLength, strict); if (!valid) { break; } length = new CSSPrimitiveValueImpl(a->fValue, (CSSPrimitiveValue::UnitTypes) a->unit); } if (i == 0) { rect->setTop(length); } else if (i == 1) { rect->setRight(length); } else if (i == 2) { rect->setBottom(length); } else { rect->setLeft(length); } a = args->next(); if (a && argsSize == 7) { if (a->unit == Value::Operator && a->iValue == ',') { a = args->next(); } else { valid = false; break; } } i++; } if (valid) { addProperty(propId, new CSSPrimitiveValueImpl(rect), important); valueList->next(); return true; } delete rect; return false; } // [ [ <'font-style'> || <'font-variant'> || <'font-weight'> ]? <'font-size'> [ / <'line-height'> ]? <'font-family'> ] | // caption | icon | menu | message-box | small-caption | status-bar bool CSSParser::parseFontShorthand(bool important) { Value *value = valueList->current(); if (valueList->size() == 1) { // Must be a system font identifier if (value->id >= CSS_VAL_CAPTION && value->id <= CSS_VAL_STATUS_BAR) { addProperty(CSS_PROP_FONT, new CSSPrimitiveValueImpl(value->id), important); return true; } return false; } CSSValueListImpl *family = nullptr; CSSPrimitiveValueImpl *style = nullptr, *variant = nullptr, *weight = nullptr, *size = nullptr, *lineHeight = nullptr; ShorthandScope scope(this, CSS_PROP_FONT); // optional font-style, font-variant and font-weight while (value) { //qWarning() << "got value" << value->id << "/" << //(value->unit == CSSPrimitiveValue::CSS_STRING || value->unit == CSSPrimitiveValue::CSS_IDENT ? qString(value->string) : QString()); const int id = value->id; if (id == CSS_VAL_NORMAL) { // do nothing, it's the initial value for all three } else if (id == CSS_VAL_ITALIC || id == CSS_VAL_OBLIQUE) { if (style) { goto invalid; } style = new CSSPrimitiveValueImpl(id); } else if (id == CSS_VAL_SMALL_CAPS) { if (variant) { goto invalid; } variant = new CSSPrimitiveValueImpl(id); } else if (int weightValueId = parseFontWeight(value, true)) { if (weight) { goto invalid; } weight = new CSSPrimitiveValueImpl(weightValueId); } else { break; } value = valueList->next(); } if (!value) { goto invalid; } // set undefined values to default if (!style) { style = new CSSPrimitiveValueImpl(CSS_VAL_NORMAL); } if (!variant) { variant = new CSSPrimitiveValueImpl(CSS_VAL_NORMAL); } if (!weight) { weight = new CSSPrimitiveValueImpl(CSS_VAL_NORMAL); } //qWarning() << "parsed style, variant, weight:" << style->cssText() << variant->cssText() << weight->cssText(); // now a font-size _must_ come // | | | | inherit if (value->id >= CSS_VAL_XX_SMALL && value->id <= CSS_VAL_LARGER) { size = new CSSPrimitiveValueImpl(value->id); } else if (validUnit(value, FLength | FPercent | FNonNeg, strict)) { size = new CSSPrimitiveValueImpl(value->fValue, (CSSPrimitiveValue::UnitTypes) value->unit); } if (!size) { goto invalid; } //qWarning() << "parsed size:" << size->cssText(); // now /line-height could come, next font-family _must_ come value = valueList->next(); if (!value) { goto invalid; } if (value->unit == Value::Operator && value->iValue == '/') { // line-height value = valueList->next(); if (!value) { goto invalid; } if (value->id == CSS_VAL_NORMAL) { // default value, nothing to do } else if (validUnit(value, FNumber | FLength | FPercent | FNonNeg, strict)) { lineHeight = new CSSPrimitiveValueImpl(value->fValue, (CSSPrimitiveValue::UnitTypes) value->unit); } else { goto invalid; } value = valueList->next(); if (!value) { goto invalid; } } // if undefined set to default if (!lineHeight) { lineHeight = new CSSPrimitiveValueImpl(CSS_VAL_NORMAL); } //qWarning() << "parsed line-height:" << lineHeight->cssText(); // font-family _must_ come now family = parseFontFamily(); if (valueList->current() || !family) { goto invalid; } //qWarning() << "parsed family:" << family->cssText(); addProperty(CSS_PROP_FONT_FAMILY, family, important); addProperty(CSS_PROP_FONT_STYLE, style, important); addProperty(CSS_PROP_FONT_VARIANT, variant, important); addProperty(CSS_PROP_FONT_WEIGHT, weight, important); addProperty(CSS_PROP_FONT_SIZE, size, important); addProperty(CSS_PROP_LINE_HEIGHT, lineHeight, important); return true; invalid: //qWarning() << " -> invalid"; delete family; delete style; delete variant; delete weight; delete size; delete lineHeight; return false; } int CSSParser::parseFontWeight(Value *val, bool strict) { const int valId = val->id; if (valId >= CSS_VAL_NORMAL && valId <= CSS_VAL_900) { // Valid primitive return valId; } if (validUnit(val, FInteger | FNonNeg, strict)) { int weight = static_cast(val->fValue); if ((weight % 100)) { // Invalid return 0; } weight /= 100; if (weight >= 1 && weight <= 9) { return (CSS_VAL_100 + weight - 1); } } return 0; } CSSValueListImpl *CSSParser::parseFontFamily() { // qDebug() << "CSSParser::parseFontFamily current=" << valueList->currentValue; CSSValueListImpl *list = new CSSValueListImpl(CSSValueListImpl::Comma); Value *value = valueList->current(); QString currFace; while (value) { // qDebug() << "got value " << value->id << " / " // << (value->unit == CSSPrimitiveValue::CSS_STRING || -// value->unit == CSSPrimitiveValue::CSS_IDENT ? qString( value->string ) : QString() ) -// << endl; +// value->unit == CSSPrimitiveValue::CSS_IDENT ? qString( value->string ) : QString() ); Value *nextValue = valueList->next(); bool nextValBreaksFont = !nextValue || (nextValue->unit == Value::Operator && nextValue->iValue == ','); bool nextValIsFontName = nextValue && ((nextValue->id >= CSS_VAL_SERIF && nextValue->id <= CSS_VAL_MONOSPACE) || (nextValue->unit == CSSPrimitiveValue::CSS_STRING || nextValue->unit == CSSPrimitiveValue::CSS_IDENT)); if (value->id == CSS_VAL_INHERIT && inShorthand() && currFace.isNull() && nextValBreaksFont) { // fail (#169610) delete list; return nullptr; } if (value->id >= CSS_VAL_SERIF && value->id <= CSS_VAL_MONOSPACE) { if (!currFace.isNull()) { currFace += ' '; currFace += qString(value->string); } else if (nextValBreaksFont || !nextValIsFontName) { if (!currFace.isNull()) { list->append(new FontFamilyValueImpl(currFace)); currFace.clear(); } list->append(new CSSPrimitiveValueImpl(value->id)); } else { currFace = qString(value->string); } } else if (value->unit == CSSPrimitiveValue::CSS_STRING) { // Strings never share in a family name. currFace.clear(); list->append(new FontFamilyValueImpl(qString(value->string))); } else if (value->unit == CSSPrimitiveValue::CSS_IDENT) { if (!currFace.isNull()) { currFace += ' '; currFace += qString(value->string); } else if (nextValBreaksFont || !nextValIsFontName) { if (!currFace.isNull()) { list->append(new FontFamilyValueImpl(currFace)); currFace.clear(); } list->append(new FontFamilyValueImpl(qString(value->string))); } else { currFace = qString(value->string); } } else { //qDebug() << "invalid family part"; break; } if (!nextValue) { break; } if (nextValBreaksFont) { value = valueList->next(); if (!currFace.isNull()) { list->append(new FontFamilyValueImpl(currFace)); } currFace.clear(); } else if (nextValIsFontName) { value = nextValue; } else { break; } } if (!currFace.isNull()) { list->append(new FontFamilyValueImpl(currFace)); } if (!list->length()) { delete list; list = nullptr; } return list; } bool CSSParser::parseFontFaceSrc() { CSSValueListImpl *values = new CSSValueListImpl(CSSValueListImpl::Comma); Value *val; bool expectComma = false; bool allowFormat = false; bool failed = false; CSSFontFaceSrcValueImpl *uriValue = nullptr; while ((val = valueList->current())) { CSSFontFaceSrcValueImpl *parsedValue = nullptr; if (val->unit == CSSPrimitiveValue::CSS_URI && !expectComma && styleElement) { const DOMString uri = domString(val->string).trimSpaces(); parsedValue = new CSSFontFaceSrcValueImpl(DOMString(QUrl(styleElement->baseURL()).resolved(QUrl(uri.string())).toString()), false /*local*/); uriValue = parsedValue; allowFormat = true; expectComma = true; } else if (val->unit == Value::Function) { // There are two allowed functions: local() and format(). // For both we expect a string argument ValueList *args = val->function->args; if (args && args->size() == 1 && (args->current()->unit == CSSPrimitiveValue::CSS_STRING || args->current()->unit == CSSPrimitiveValue::CSS_IDENT)) { if (!strcasecmp(domString(val->function->name), "local(") && !expectComma) { expectComma = true; allowFormat = false; Value *a = args->current(); uriValue = nullptr; parsedValue = new CSSFontFaceSrcValueImpl(domString(a->string), true /*local src*/); } else if (!strcasecmp(domString(val->function->name), "format(") && allowFormat && uriValue) { expectComma = true; allowFormat = false; uriValue->setFormat(domString(args->current()->string)); uriValue = nullptr; valueList->next(); continue; } } } else if (val->unit == Value::Operator && val->iValue == ',' && expectComma) { expectComma = false; allowFormat = false; uriValue = nullptr; valueList->next(); continue; } if (parsedValue) { values->append(parsedValue); } else { failed = true; break; } valueList->next(); } if (values->length() && !failed) { addProperty(CSS_PROP_SRC, values, important); valueList->next(); return true; } else { delete values; } return false; } // [ || || ] bool CSSParser::parseListStyleShorthand(bool important) { if (valueList->size() > 3) { // discard return false; } CSSValueImpl *type = nullptr; CSSValueImpl *position = nullptr; CSSValueImpl *image = nullptr; int numberOfNone = 0; Value *value = valueList->current(); while (value) { const int valId = value->id; if (valId == CSS_VAL_NONE) { // just count ++numberOfNone; } else if (valId >= CSS_VAL_DISC && valId <= CSS_VAL__KHTML_CLOSE_QUOTE) { if (!type) { type = new CSSPrimitiveValueImpl(valId); } else { goto invalid; } } else if (valId == CSS_VAL_INSIDE || valId == CSS_VAL_OUTSIDE) { if (!position) { position = new CSSPrimitiveValueImpl(valId); } else { goto invalid; } } else if (value->unit == CSSPrimitiveValue::CSS_URI) { if (!image) { // ### allow string in non strict mode? if (styleElement) { const DOMString uri = domString(value->string); image = new CSSImageValueImpl(uri, styleElement); } } else { goto invalid; } } else { goto invalid; } value = valueList->next(); } // Set whichever of 'list-style-type' and 'list-style-image' are not otherwise specified, to 'none' switch (numberOfNone) { case 0: { break; } case 1: { if (image && type) { goto invalid; } if (!image) { image = new CSSImageValueImpl(); } if (!type) { type = new CSSPrimitiveValueImpl(CSS_VAL_NONE); } break; } case 2: { if (image || type) { goto invalid; } else { image = new CSSImageValueImpl(); type = new CSSPrimitiveValueImpl(CSS_VAL_NONE); } break; } default: // numberOfNone == 3 goto invalid; } // The shorthand is valid: fill-in any remaining properties with default value if (!type) { type = new CSSPrimitiveValueImpl(CSS_VAL_DISC); } if (!position) { position = new CSSPrimitiveValueImpl(CSS_VAL_OUTSIDE); } if (!image) { image = new CSSImageValueImpl(); } addProperty(CSS_PROP_LIST_STYLE_TYPE, type, important); addProperty(CSS_PROP_LIST_STYLE_POSITION, position, important); addProperty(CSS_PROP_LIST_STYLE_IMAGE, image, important); return true; invalid: delete type; delete position; delete image; return false; } bool CSSParser::parseColorParameters(Value *value, int *colorArray, bool parseAlpha) { ValueList *args = value->function->args; Value *v = args->current(); // Get the first value if (!validUnit(v, FInteger | FPercent, true)) { return false; } bool isPercent = (v->unit == CSSPrimitiveValue::CSS_PERCENTAGE); colorArray[0] = static_cast(v->fValue * (isPercent ? 256.0 / 100.0 : 1.0)); for (int i = 1; i < 3; i++) { v = args->next(); if (v->unit != Value::Operator && v->iValue != ',') { return false; } v = args->next(); if (!validUnit(v, (isPercent ? FPercent : FInteger), true)) { return false; } colorArray[i] = static_cast(v->fValue * (isPercent ? 256.0 / 100.0 : 1.0)); } if (parseAlpha) { v = args->next(); if (v->unit != Value::Operator && v->iValue != ',') { return false; } v = args->next(); if (!validUnit(v, FNumber, true)) { return false; } colorArray[3] = static_cast(qMax(0.0, qMin(1.0, v->fValue)) * 255); //krazy:exclude=qminmax } return true; } // CSS3 specification defines the format of a HSL color as // hsl(, , ) // and with alpha, the format is // hsla(, , , ) // The first value, HUE, is in an angle with a value between 0 and 360 bool CSSParser::parseHSLParameters(Value *value, double *colorArray, bool parseAlpha) { ValueList *args = value->function->args; Value *v = args->current(); // Get the first value if (!validUnit(v, FInteger, true)) { return false; } // normalize the Hue value and change it to be between 0 and 1.0 colorArray[0] = (((static_cast(v->fValue) % 360) + 360) % 360) / 360.0; for (int i = 1; i < 3; i++) { v = args->next(); if (v->unit != Value::Operator && v->iValue != ',') { return false; } v = args->next(); if (!validUnit(v, FPercent, true)) { return false; } colorArray[i] = qMax(0.0, qMin(100.0, v->fValue)) / 100.0; // needs to be value between 0 and 1.0, krazy:exclude=qminmax } if (parseAlpha) { v = args->next(); if (v->unit != Value::Operator && v->iValue != ',') { return false; } v = args->next(); if (!validUnit(v, FNumber, true)) { return false; } colorArray[3] = qMax(0.0, qMin(1.0, v->fValue)); //krazy:exclude=qminmax } return true; } static int hex2int(unsigned short c, bool *error) { if (c >= '0' && c <= '9') { return c - '0'; } else if (c >= 'A' && c <= 'F') { return 10 + c - 'A'; } else if (c >= 'a' && c <= 'f') { return 10 + c - 'a'; } else { *error = true; return -1; } } static bool parseColor(int unit, const QString &name, QRgb &rgb, bool strict) { int len = name.length(); if (!len) { return false; } if (unit == CSSPrimitiveValue::CSS_RGBCOLOR || !strict) { const unsigned short *c = reinterpret_cast(name.unicode()); rgb = 0xff; // fixed alpha if (len == 6) { // RRGGBB bool error = false; for (int i = 0; i < 6; ++i, ++c) { rgb = rgb << 4 | hex2int(*c, &error); } if (!error) { return true; } } else if (len == 3) { // RGB, shortcut for RRGGBB bool error = false; for (int i = 0; i < 3; ++i, ++c) { rgb = rgb << 8 | 0x11 * hex2int(*c, &error); } if (!error) { return true; } } } if (unit == CSSPrimitiveValue::CSS_IDENT) { // try a little harder QColor tc; tc.setNamedColor(name.toLower()); if (tc.isValid()) { rgb = tc.rgba(); return true; } } return false; } CSSPrimitiveValueImpl *CSSParser::parseColor() { return parseColorFromValue(valueList->current()); } CSSPrimitiveValueImpl *CSSParser::parseColorFromValue(Value *value) { QRgb c = khtml::transparentColor; if (!strict && value->unit == CSSPrimitiveValue::CSS_NUMBER && // color: 000000 (quirk) value->fValue >= 0. && value->fValue < 1000000.) { QString str; str.sprintf("%06d", (int)(value->fValue + .5)); if (!::parseColor(CSSPrimitiveValue::CSS_RGBCOLOR, str, c, strict)) { return nullptr; } } else if (value->unit == CSSPrimitiveValue::CSS_RGBCOLOR || // color: #ff0000 value->unit == CSSPrimitiveValue::CSS_IDENT || // color: red || color: ff0000 (quirk) (!strict && value->unit == CSSPrimitiveValue::CSS_DIMENSION)) { // color: 00ffff (quirk) if (!::parseColor(value->unit, qString(value->string), c, strict)) { return nullptr; } } else if (value->unit == Value::Function && value->function->args != nullptr && value->function->args->size() == 5 /* rgb + two commas */ && qString(value->function->name).toLower() == "rgb(") { int colorValues[3]; if (!parseColorParameters(value, colorValues, false)) { return nullptr; } colorValues[0] = qMax(0, qMin(255, colorValues[0])); colorValues[1] = qMax(0, qMin(255, colorValues[1])); colorValues[2] = qMax(0, qMin(255, colorValues[2])); c = qRgb(colorValues[0], colorValues[1], colorValues[2]); } else if (value->unit == Value::Function && value->function->args != nullptr && value->function->args->size() == 7 /* rgba + three commas */ && domString(value->function->name).lower() == "rgba(") { int colorValues[4]; if (!parseColorParameters(value, colorValues, true)) { return nullptr; } colorValues[0] = qMax(0, qMin(255, colorValues[0])); colorValues[1] = qMax(0, qMin(255, colorValues[1])); colorValues[2] = qMax(0, qMin(255, colorValues[2])); c = qRgba(colorValues[0], colorValues[1], colorValues[2], colorValues[3]); } else if (value->unit == Value::Function && value->function->args != nullptr && value->function->args->size() == 5 /* hsl + two commas */ && domString(value->function->name).lower() == "hsl(") { double colorValues[3]; if (!parseHSLParameters(value, colorValues, false)) { return nullptr; } c = khtml::qRgbaFromHsla(colorValues[0], colorValues[1], colorValues[2], 1.0); } else if (value->unit == Value::Function && value->function->args != nullptr && value->function->args->size() == 7 /* hsla + three commas */ && domString(value->function->name).lower() == "hsla(") { double colorValues[4]; if (!parseHSLParameters(value, colorValues, true)) { return nullptr; } c = khtml::qRgbaFromHsla(colorValues[0], colorValues[1], colorValues[2], colorValues[3]); } else { return nullptr; } return new CSSPrimitiveValueImpl(c); } // This class tracks parsing state for shadow values. If it goes out of scope (e.g., due to an early return) // without the allowBreak bit being set, then it will clean up all of the objects and destroy them. struct ShadowParseContext { ShadowParseContext() : values(nullptr), x(nullptr), y(nullptr), blur(nullptr), color(nullptr), allowX(true), allowY(false), allowBlur(false), allowColor(true), allowBreak(true) {} ~ShadowParseContext() { if (!allowBreak) { delete values; delete x; delete y; delete blur; delete color; } } bool allowLength() { return allowX || allowY || allowBlur; } bool failed() { return allowBreak = false; } void commitValue() { // Handle the ,, case gracefully by doing nothing. if (x || y || blur || color) { if (!values) { values = new CSSValueListImpl(CSSValueListImpl::Comma); } // Construct the current shadow value and add it to the list. values->append(new ShadowValueImpl(x, y, blur, color)); } // Now reset for the next shadow value. x = y = blur = color = nullptr; allowX = allowColor = allowBreak = true; allowY = allowBlur = false; } void commitLength(Value *v) { CSSPrimitiveValueImpl *val = new CSSPrimitiveValueImpl(v->fValue, (CSSPrimitiveValue::UnitTypes)v->unit); if (allowX) { x = val; allowX = false; allowY = true; allowColor = false; allowBreak = false; } else if (allowY) { y = val; allowY = false; allowBlur = true; allowColor = true; allowBreak = true; } else if (allowBlur) { blur = val; allowBlur = false; } else { delete val; } } void commitColor(CSSPrimitiveValueImpl *val) { color = val; allowColor = false; if (allowX) { allowBreak = false; } else { allowBlur = false; } } CSSValueListImpl *values; CSSPrimitiveValueImpl *x; CSSPrimitiveValueImpl *y; CSSPrimitiveValueImpl *blur; CSSPrimitiveValueImpl *color; bool allowX; bool allowY; bool allowBlur; bool allowColor; bool allowBreak; }; bool CSSParser::parseShadow(int propId, bool important) { ShadowParseContext context; Value *val; while ((val = valueList->current())) { // Check for a comma break first. if (val->unit == Value::Operator) { if (val->iValue != ',' || !context.allowBreak) // Other operators aren't legal or we aren't done with the current shadow // value. Treat as invalid. { return context.failed(); } // The value is good. Commit it. context.commitValue(); } // Check to see if we're a length. else if (validUnit(val, FLength, true)) { // We required a length and didn't get one. Invalid. if (!context.allowLength()) { return context.failed(); } // A length is allowed here. Construct the value and add it. context.commitLength(val); } else { // The only other type of value that's ok is a color value. CSSPrimitiveValueImpl *parsedColor = nullptr; bool isColor = ((val->id >= CSS_VAL_AQUA && val->id <= CSS_VAL_WINDOWTEXT) || val->id == CSS_VAL_MENU || (val->id >= CSS_VAL_GREY && val->id <= CSS_VAL__KHTML_TEXT && !strict)); if (!context.allowColor) { return context.failed(); } if (isColor) { parsedColor = new CSSPrimitiveValueImpl(val->id); } if (!parsedColor) // It's not built-in. Try to parse it as a color. { parsedColor = parseColorFromValue(val); } if (!parsedColor) { return context.failed(); } context.commitColor(parsedColor); } valueList->next(); } if (context.allowBreak) { context.commitValue(); if (context.values->length()) { addProperty(propId, context.values, important); valueList->next(); return true; } } return context.failed(); } bool CSSParser::parseCounter(int propId, bool increment, bool important) { enum { ID, VAL, COMMA } state = ID; CSSValueListImpl *list = new CSSValueListImpl; DOMString c; Value *val; while (true) { val = valueList->current(); switch (state) { // Commas are not allowed according to the standard, but Opera allows them and being the only // other browser with counter support we need to match their behavior to work with current use case COMMA: state = ID; if (val && val->unit == Value::Operator && val->iValue == ',') { valueList->next(); continue; } // no break case ID: if (val && val->unit == CSSPrimitiveValue::CSS_IDENT) { c = qString(val->string); state = VAL; valueList->next(); continue; } break; case VAL: { short i = 0; if (val && val->unit == CSSPrimitiveValue::CSS_NUMBER) { i = (short)val->fValue; valueList->next(); } else { i = (increment) ? 1 : 0; } CounterActImpl *cv = new CounterActImpl(c, i); list->append(cv); state = COMMA; continue; } } break; } if (list->length() > 0) { addProperty(propId, list, important); return true; } delete list; return false; } static inline int yyerror(const char *str) { // assert( 0 ); #ifdef CSS_DEBUG qDebug() << "CSS parse error " << str; #else Q_UNUSED(str); #endif return 1; } static const double dIntMax = INT_MAX; #define END 0 #include "parser.h" int DOM::CSSParser::lex(void *_yylval) { YYSTYPE *yylval = (YYSTYPE *)_yylval; int token = lex(); int length; unsigned short *t = text(&length); #ifdef TOKEN_DEBUG qDebug("CSSTokenizer: got token %d: '%s'", token, token == END ? "" : QString((QChar *)t, length).toLatin1().constData()); #endif switch (token) { case '{': block_nesting++; break; case '}': if (block_nesting) { block_nesting--; } break; case END: if (block_nesting) { block_nesting--; return '}'; } break; case S: case SGML_CD: case INCLUDES: case DASHMATCH: break; case URI: case STRING: case IDENT: case NTH: case HASH: case HEXCOLOR: case DIMEN: case UNICODERANGE: case NOTFUNCTION: case FUNCTION: yylval->string.string = t; yylval->string.length = length; break; case IMPORT_SYM: case PAGE_SYM: case MEDIA_SYM: case FONT_FACE_SYM: case CHARSET_SYM: case NAMESPACE_SYM: case IMPORTANT_SYM: break; case QEMS: length--; case GRADS: case DPCM: length--; case DEGS: case RADS: case KHERZ: case DPI: case REMS: length--; case MSECS: case HERZ: case EMS: case EXS: case CHS: case PXS: case CMS: case MMS: case INS: case PTS: case PCS: length--; case SECS: case PERCENTAGE: length--; case FLOAT: case INTEGER: yylval->val = qMin(QString((QChar *)t, length).toDouble(), dIntMax); //qDebug("value = %s, converted=%.2f", QString((QChar *)t, length).toLatin1().constData(), yylval->val); break; default: break; } return token; } static inline int toHex(char c) { if ('0' <= c && c <= '9') { return c - '0'; } if ('a' <= c && c <= 'f') { return c - 'a' + 10; } if ('A' <= c && c <= 'F') { return c - 'A' + 10; } return 0; } unsigned short *DOM::CSSParser::text(int *length) { unsigned short *start = yytext; int l = yyleng; switch (yyTok) { case STRING: l--; /* nobreak */ case HASH: case HEXCOLOR: start++; l--; break; case URI: // "url("{w}{string}{w}")" // "url("{w}{url}{w}")" // strip "url(" and ")" start += 4; l -= 5; // strip {w} while (l && (*start == ' ' || *start == '\t' || *start == '\r' || *start == '\n' || *start == '\f')) { start++; l--; } if (*start == '"' || *start == '\'') { start++; l--; } while (l && (start[l - 1] == ' ' || start[l - 1] == '\t' || start[l - 1] == '\r' || start[l - 1] == '\n' || start[l - 1] == '\f')) { l--; } if (l && (start[l - 1] == '\"' || start[l - 1] == '\'')) { l--; } default: break; } // process escapes unsigned short *out = start; unsigned short *escape = nullptr; for (int i = 0; i < l; i++) { unsigned short *current = start + i; if (escape == current - 1) { if ((*current >= '0' && *current <= '9') || (*current >= 'a' && *current <= 'f') || (*current >= 'A' && *current <= 'F')) { continue; } if (yyTok == STRING && (*current == '\n' || *current == '\r' || *current == '\f')) { // ### handle \r\n case if (*current != '\r') { escape = nullptr; } continue; } // in all other cases copy the char to output // ### *out++ = *current; escape = nullptr; continue; } if (escape == current - 2 && yyTok == STRING && *(current - 1) == '\r' && *current == '\n') { escape = nullptr; continue; } if (escape > current - 7 && ((*current >= '0' && *current <= '9') || (*current >= 'a' && *current <= 'f') || (*current >= 'A' && *current <= 'F'))) { continue; } if (escape) { // add escaped char int uc = 0; escape++; while (escape < current) { // qDebug("toHex( %c = %x", (char)*escape, toHex( *escape ) ); uc *= 16; uc += toHex(*escape); escape++; } // qDebug(" converting escape: string='%s', value=0x%x", QString( (QChar *)e, current-e ).toLatin1().constData(), uc ); // can't handle chars outside utf16 if (uc > 0xffff) { uc = 0xfffd; } *(out++) = (unsigned short)uc; escape = nullptr; if (*current == ' ' || *current == '\t' || *current == '\r' || *current == '\n' || *current == '\f') { continue; } } if (!escape && *current == '\\') { escape = current; continue; } *(out++) = *current; } if (escape) { // add escaped char int uc = 0; escape++; while (escape < start + l) { // qDebug("toHex( %c = %x", (char)*escape, toHex( *escape ) ); uc *= 16; uc += toHex(*escape); escape++; } // qDebug(" converting escape: string='%s', value=0x%x", QString( (QChar *)e, current-e ).toLatin1().constData(), uc ); // can't handle chars outside utf16 if (uc > 0xffff) { uc = 0xfffd; } *(out++) = (unsigned short)uc; } *length = out - start; return start; } // When we reach the end of the input we switch over // the lexer to this alternative buffer and keep it stuck here. // (and as it contains nulls, flex will keep on reporting // end of buffer, and we will keep reseting the input // pointer to the beginning of this). static unsigned short postEofBuf[2]; #define YY_DECL int DOM::CSSParser::lex() #define yyconst const typedef int yy_state_type; typedef unsigned int YY_CHAR; // this line makes sure we treat all Unicode chars correctly. #define YY_SC_TO_UI(c) (c > 0xff ? 0xff : c) #define YY_DO_BEFORE_ACTION \ yytext = yy_bp; \ yyleng = (int) (yy_cp - yy_bp); \ yy_hold_char = *yy_cp; \ *yy_cp = 0; \ yy_c_buf_p = yy_cp; #define YY_BREAK break; #define ECHO qDebug( "%s", QString( (QChar *)yytext, yyleng ).toLatin1().constData() ) #define YY_RULE_SETUP #define INITIAL 0 #define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) #define YY_START ((yy_start - 1) / 2) #define yyterminate()\ do { \ if (yy_act == YY_END_OF_BUFFER) { \ yy_c_buf_p = postEofBuf; \ yy_hold_char = 0; /* first char of the postEndOf to 'restore' */ \ } \ yyTok = END; return yyTok; \ } while (0) #define YY_FATAL_ERROR(a) qFatal(a) #define BEGIN yy_start = 1 + 2 * #define COMMENT 1 #include "tokenizer.cpp" diff --git a/src/css/cssstyleselector.cpp b/src/css/cssstyleselector.cpp index 04a6448..f401882 100644 --- a/src/css/cssstyleselector.cpp +++ b/src/css/cssstyleselector.cpp @@ -1,4915 +1,4915 @@ /** * This file is part of the CSS implementation for KDE. * * Copyright 1999-2003 Lars Knoll (knoll@kde.org) * Copyright 2003-2004 Apple Computer, Inc. * Copyright 2004-2010 Allan Sandfeld Jensen (kde@carewolf.com) * Copyright 2004-2008 Germain Garand (germain@ebooksfrance.org) * Copyright 2008 Vyacheslav Tokarev (tsjoker@gmail.com) * (C) 2005, 2006, 2008 Apple Computer, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "css/cssstyleselector.h" #include "rendering/render_style.h" #include "css/css_stylesheetimpl.h" #include "css/css_ruleimpl.h" #include "css/css_valueimpl.h" #include "css/csshelper.h" #include "css/css_webfont.h" #include "rendering/render_object.h" #include "html/html_documentimpl.h" #include "html/html_elementimpl.h" #include "xml/dom_elementimpl.h" #include "xml/dom_restyler.h" #include "dom/css_rule.h" #include "dom/css_value.h" #include "khtml_global.h" #include "khtmlpart_p.h" using namespace khtml; using namespace DOM; #include "css/cssproperties.h" #include "css/cssvalues.h" #include "css/css_mediaquery.h" #include "misc/khtmllayout.h" #include "khtml_settings.h" #include "misc/helper.h" #include "misc/loader.h" #include "rendering/font.h" #include "khtmlview.h" #include "khtml_part.h" #include #include #include #include #include #include #include #include #include #include // keep in sync with html4.css' #define KHTML_STYLE_VERSION 1 #undef PRELATIVE #undef PABSOLUTE // handle value "inherit" on a default inherited property #define HANDLE_INHERIT_ON_INHERITED_PROPERTY(prop, Prop) \ if (isInherit) \ {\ style->set##Prop(parentStyle->prop());\ return;\ } // handle value "inherit" on a default non-inherited property #define HANDLE_INHERIT_ON_NONINHERITED_PROPERTY(prop, Prop) \ if (isInherit) \ {\ style->setInheritedNoninherited(true);\ style->set##Prop(parentStyle->prop());\ return;\ } #define HANDLE_INITIAL(prop, Prop) \ if (isInitial) \ {\ style->set##Prop(RenderStyle::initial##Prop());\ return;\ } #define HANDLE_INITIAL_AND_INHERIT_ON_NONINHERITED_PROPERTY(prop, Prop) \ HANDLE_INITIAL(prop, Prop) \ else \ HANDLE_INHERIT_ON_NONINHERITED_PROPERTY(prop, Prop) #define HANDLE_INITIAL_AND_INHERIT_ON_INHERITED_PROPERTY(prop, Prop) \ HANDLE_INITIAL(prop, Prop) \ else \ HANDLE_INHERIT_ON_INHERITED_PROPERTY(prop, Prop) // all non-inherited properties #define HANDLE_INHERIT_AND_INITIAL_WITH_VALUE(prop, Prop, Value) \ HANDLE_INHERIT_ON_NONINHERITED_PROPERTY(prop, Prop) \ else if (isInitial) \ {\ style->set##Prop(RenderStyle::initial##Value());\ return;\ } #define HANDLE_BACKGROUND_INHERIT_AND_INITIAL(prop, Prop) \ if (isInherit) { \ style->setInheritedNoninherited(true); \ BackgroundLayer* currChild = style->accessBackgroundLayers(); \ BackgroundLayer* prevChild = 0; \ const BackgroundLayer* currParent = parentStyle->backgroundLayers(); \ while (currParent && currParent->is##Prop##Set()) { \ if (!currChild) { \ /* Need to make a new layer.*/ \ currChild = new BackgroundLayer(); \ prevChild->setNext(currChild); \ } \ currChild->set##Prop(currParent->prop()); \ prevChild = currChild; \ currChild = prevChild->next(); \ currParent = currParent->next(); \ } \ \ while (currChild) { \ /* Reset any remaining layers to not have the property set. */ \ currChild->clear##Prop(); \ currChild = currChild->next(); \ } \ } else if (isInitial) { \ BackgroundLayer* currChild = style->accessBackgroundLayers(); \ currChild->set##Prop(RenderStyle::initial##Prop()); \ for (currChild = currChild->next(); currChild; currChild = currChild->next()) \ currChild->clear##Prop(); \ } #define HANDLE_BACKGROUND_VALUE(prop, Prop, value) { \ HANDLE_BACKGROUND_INHERIT_AND_INITIAL(prop, Prop) \ else { \ if (!value->isPrimitiveValue() && !value->isValueList()) \ return; \ BackgroundLayer* currChild = style->accessBackgroundLayers(); \ BackgroundLayer* prevChild = 0; \ if (value->isPrimitiveValue()) { \ map##Prop(currChild, value); \ currChild = currChild->next(); \ } \ else { \ /* Walk each value and put it into a layer, creating new layers as needed. */ \ CSSValueListImpl* valueList = static_cast(value); \ for (unsigned int i = 0; i < valueList->length(); i++) { \ if (!currChild) { \ /* Need to make a new layer to hold this value */ \ currChild = new BackgroundLayer(); \ prevChild->setNext(currChild); \ } \ map##Prop(currChild, valueList->item(i)); \ prevChild = currChild; \ currChild = currChild->next(); \ } \ } \ while (currChild) { \ /* Reset all remaining layers to not have the property set. */ \ currChild->clear##Prop(); \ currChild = currChild->next(); \ } \ } } #define HANDLE_INHERIT_COND(propID, prop, Prop) \ if (id == propID) \ {\ style->set##Prop(parentStyle->prop());\ return;\ } #define HANDLE_INHERIT_COND_WITH_BACKUP(propID, prop, propAlt, Prop) \ if (id == propID) { \ if (parentStyle->prop().isValid()) \ style->set##Prop(parentStyle->prop()); \ else \ style->set##Prop(parentStyle->propAlt()); \ return; \ } #define HANDLE_INITIAL_COND(propID, Prop) \ if (id == propID) \ {\ style->set##Prop(RenderStyle::initial##Prop());\ return;\ } #define HANDLE_INITIAL_COND_WITH_VALUE(propID, Prop, Value) \ if (id == propID) \ {\ style->set##Prop(RenderStyle::initial##Value());\ return;\ } namespace khtml { CSSStyleSelectorList *CSSStyleSelector::s_defaultStyle; CSSStyleSelectorList *CSSStyleSelector::s_defaultQuirksStyle; CSSStyleSelectorList *CSSStyleSelector::s_defaultNonCSSHintsStyle; CSSStyleSelectorList *CSSStyleSelector::s_defaultPrintStyle; CSSStyleSheetImpl *CSSStyleSelector::s_defaultSheet; CSSStyleSheetImpl *CSSStyleSelector::s_defaultNonCSSHintsSheet; RenderStyle *CSSStyleSelector::styleNotYetAvailable; CSSStyleSheetImpl *CSSStyleSelector::s_quirksSheet; enum PseudoState { PseudoUnknown, PseudoNone, PseudoLink, PseudoVisited}; static PseudoState pseudoState; CSSStyleSelector::CSSStyleSelector(DocumentImpl *doc, QString userStyleSheet, StyleSheetListImpl *styleSheets, const QUrl &url, bool _strictParsing) { KHTMLView *view = doc->view(); KHTMLPart *part = doc->part(); m_fontSelector = new CSSFontSelector(doc); init(part ? part->settings() : nullptr, doc); strictParsing = _strictParsing; selectors = nullptr; selectorCache = nullptr; propertiesBuffer = nullptr; nextPropertyIndexes = nullptr; userStyle = nullptr; userSheet = nullptr; logicalDpiY = doc->logicalDpiY(); if (logicalDpiY) { // this may be null, not everyone uses khtmlview (Niko) computeFontSizes(logicalDpiY, part ? part->fontScaleFactor() : 100); } // build a limited default style suitable to evaluation of media queries // containing relative constraints, like "screen and (max-width: 10em)" setupDefaultRootStyle(doc); if (view) { m_medium = new MediaQueryEvaluator(view->mediaType(), view->part(), m_rootDefaultStyle); } else { m_medium = new MediaQueryEvaluator("all", nullptr, m_rootDefaultStyle); } if (!userStyleSheet.isEmpty()) { userSheet = new DOM::CSSStyleSheetImpl(doc); userSheet->parseString(DOMString(userStyleSheet)); userStyle = new CSSStyleSelectorList(); userStyle->append(userSheet, m_medium, this); } // add stylesheets from document authorStyle = nullptr; implicitStyle = nullptr; foreach (StyleSheetImpl *sh, styleSheets->styleSheets) { if (sh->isCSSStyleSheet()) { if (static_cast(sh)->implicit()) { if (!implicitStyle) { implicitStyle = new CSSStyleSelectorList(); } implicitStyle->append(static_cast(sh), m_medium, this); } else if (sh->isCSSStyleSheet() && !sh->disabled()) { if (!authorStyle) { authorStyle = new CSSStyleSelectorList(); } authorStyle->append(static_cast(sh), m_medium, this); } } } buildLists(); //qDebug() << "number of style sheets in document " << authorStyleSheets.count(); //qDebug() << "CSSStyleSelector: author style has " << authorStyle->count() << " elements"; QUrl u = url; u.setQuery(QString()); u.setFragment(QString()); encodedurl.file = u.url(); int pos = encodedurl.file.lastIndexOf('/'); encodedurl.path = encodedurl.file; if (pos > 0) { encodedurl.path.truncate(pos); encodedurl.path += '/'; } u.setPath(QString()); encodedurl.host = u.url(); //qDebug() << "CSSStyleSelector::CSSStyleSelector encoded url " << encodedurl.path; } CSSStyleSelector::CSSStyleSelector(CSSStyleSheetImpl *sheet) { m_fontSelector = new CSSFontSelector(sheet->doc()); init(nullptr, nullptr); KHTMLView *view = sheet->doc()->view(); setupDefaultRootStyle(sheet->doc()); if (view) { m_medium = new MediaQueryEvaluator(view->mediaType(), view->part(), m_rootDefaultStyle); } else { m_medium = new MediaQueryEvaluator("screen", nullptr, m_rootDefaultStyle); } if (sheet->implicit()) { implicitStyle = new CSSStyleSelectorList(); implicitStyle->append(sheet, m_medium, this); } else { authorStyle = new CSSStyleSelectorList(); authorStyle->append(sheet, m_medium, this); } } void CSSStyleSelector::init(const KHTMLSettings *_settings, DocumentImpl *doc) { element = nullptr; settings = _settings; logicalDpiY = 0; if (!s_defaultStyle) { loadDefaultStyle(settings, doc); } defaultStyle = s_defaultStyle; defaultPrintStyle = s_defaultPrintStyle; defaultQuirksStyle = s_defaultQuirksStyle; defaultNonCSSHintsStyle = s_defaultNonCSSHintsStyle; m_rootDefaultStyle = nullptr; m_medium = nullptr; } CSSStyleSelector::~CSSStyleSelector() { clearLists(); delete authorStyle; delete implicitStyle; delete userStyle; delete userSheet; delete m_rootDefaultStyle; delete m_medium; delete m_fontSelector; } void CSSStyleSelector::addSheet(CSSStyleSheetImpl *sheet) { KHTMLView *view = sheet->doc()->view(); setupDefaultRootStyle(sheet->doc()); delete m_medium; m_medium = nullptr; delete authorStyle; authorStyle = nullptr; delete implicitStyle; implicitStyle = nullptr; if (view) { m_medium = new MediaQueryEvaluator(view->mediaType(), view->part(), m_rootDefaultStyle); } else { m_medium = new MediaQueryEvaluator("screen", nullptr, m_rootDefaultStyle); } if (sheet->implicit()) { if (!implicitStyle) { implicitStyle = new CSSStyleSelectorList(); } implicitStyle->append(sheet, m_medium, this); } else { if (!authorStyle) { authorStyle = new CSSStyleSelectorList(); } authorStyle->append(sheet, m_medium, this); } } void CSSStyleSelector::loadDefaultStyle(const KHTMLSettings *s, DocumentImpl *doc) { if (s_defaultStyle) { return; } MediaQueryEvaluator screenEval("screen"); MediaQueryEvaluator printEval("print"); { QFile f(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kf5/khtml/css/html4.css")); f.open(QIODevice::ReadOnly); QByteArray file(f.size() + 1, 0); int readbytes = f.read(file.data(), f.size()); f.close(); if (readbytes >= 0) { file[readbytes] = '\0'; } QString style = QLatin1String(file.data()); QRegExp checkVersion("KHTML_STYLE_VERSION:\\s*(\\d+)"); checkVersion.setMinimal(true); if (checkVersion.indexIn(style) == -1 || checkVersion.cap(1).toInt() != KHTML_STYLE_VERSION) { qFatal("!!!!!!! ERROR !!!!!!! - KHTML default stylesheet version mismatch. Aborting. Check your installation. File used was: %s. Expected STYLE_VERSION %d\n", QFileInfo(f).absoluteFilePath().toLatin1().data(), KHTML_STYLE_VERSION); } if (s) { style += s->settingsToCSS(); } DOMString str(style); s_defaultSheet = new DOM::CSSStyleSheetImpl(doc); s_defaultSheet->parseString(str); // Collect only strict-mode rules. s_defaultStyle = new CSSStyleSelectorList(); s_defaultStyle->append(s_defaultSheet, &screenEval, doc->styleSelector()); s_defaultPrintStyle = new CSSStyleSelectorList(); s_defaultPrintStyle->append(s_defaultSheet, &printEval, doc->styleSelector()); } { QFile f(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kf5/khtml/css/quirks.css")); f.open(QIODevice::ReadOnly); QByteArray file(f.size() + 1, 0); int readbytes = f.read(file.data(), f.size()); f.close(); if (readbytes >= 0) { file[readbytes] = '\0'; } QString style = QLatin1String(file.data()); DOMString str(style); s_quirksSheet = new DOM::CSSStyleSheetImpl(doc); s_quirksSheet->parseString(str); // Collect only quirks-mode rules. s_defaultQuirksStyle = new CSSStyleSelectorList(); s_defaultQuirksStyle->append(s_quirksSheet, &screenEval, doc->styleSelector()); } { QFile f(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kf5/khtml/css/presentational.css")); f.open(QIODevice::ReadOnly); QByteArray file(f.size() + 1, 0); int readbytes = f.read(file.data(), f.size()); f.close(); if (readbytes >= 0) { file[readbytes] = '\0'; } QString style = QLatin1String(file.data()); DOMString str(style); s_defaultNonCSSHintsSheet = new DOM::CSSStyleSheetImpl(doc); s_defaultNonCSSHintsSheet->parseString(str); s_defaultNonCSSHintsStyle = new CSSStyleSelectorList(); s_defaultNonCSSHintsStyle->append(s_defaultNonCSSHintsSheet, &screenEval, doc->styleSelector()); } //qDebug() << "CSSStyleSelector: default style has " << defaultStyle->count() << " elements"; } void CSSStyleSelector::clear() { delete s_defaultStyle; delete s_defaultQuirksStyle; delete s_defaultPrintStyle; delete s_defaultNonCSSHintsStyle; delete s_defaultSheet; delete s_defaultNonCSSHintsSheet; delete styleNotYetAvailable; s_defaultStyle = nullptr; s_defaultQuirksStyle = nullptr; s_defaultPrintStyle = nullptr; s_defaultNonCSSHintsStyle = nullptr; s_defaultSheet = nullptr; s_defaultNonCSSHintsSheet = nullptr; styleNotYetAvailable = nullptr; } void CSSStyleSelector::reparseConfiguration() { // nice leak, but best we can do right now. hopefully it is only rare. s_defaultStyle = nullptr; s_defaultQuirksStyle = nullptr; s_defaultPrintStyle = nullptr; s_defaultNonCSSHintsStyle = nullptr; s_defaultSheet = nullptr; } #define MAXFONTSIZES 8 void CSSStyleSelector::computeFontSizes(int logicalDpiY, int zoomFactor) { computeFontSizesFor(logicalDpiY, zoomFactor, m_fontSizes, false); computeFontSizesFor(logicalDpiY, zoomFactor, m_fixedFontSizes, true); } void CSSStyleSelector::computeFontSizesFor(int logicalDpiY, int zoomFactor, QVector &fontSizes, bool isFixed) { #ifdef APPLE_CHANGES // We don't want to scale the settings by the dpi. const float toPix = 1.0; #else Q_UNUSED(isFixed); const float toPix = qMax(logicalDpiY, 96) / 72.0f; #endif // ######### fix isFixed code again. fontSizes.resize(MAXFONTSIZES); float scale = 1.0; static const float fontFactors[] = {3.0f / 5.0f, 3.0f / 4.0f, 8.0f / 9.0f, 1.0f, 6.0f / 5.0f, 3.0f / 2.0f, 2.0f, 3.0f}; static const float smallFontFactors[] = {3.0f / 4.0f, 5.0f / 6.0f, 8.0f / 9.0f, 1.0f, 6.0f / 5.0f, 3.0f / 2.0f, 2.0f, 3.0f}; float mediumFontSize, factor; if (!khtml::printpainter) { scale *= zoomFactor / 100.0; #ifdef APPLE_CHANGES if (isFixed) { mediumFontSize = settings->mediumFixedFontSize() * toPix; } else #endif mediumFontSize = settings->mediumFontSize() * toPix; m_minFontSize = settings->minFontSize() * toPix; } else { // ### depending on something / configurable ? mediumFontSize = 12; m_minFontSize = 6; } const float *factors = scale * mediumFontSize >= 12.5 ? fontFactors : smallFontFactors; for (int i = 0; i < MAXFONTSIZES; i++) { factor = scale * factors[i]; fontSizes[i] = qMax(qRound(mediumFontSize * factor), m_minFontSize); //qDebug() << "index:" << i << "factor:" << factors[i] << "font pix size:" << fontSizes[i]; } } #undef MAXFONTSIZES RenderStyle *CSSStyleSelector::locateSimilarStyle() { ElementImpl *s = nullptr, *t = nullptr, *c = nullptr; if (!element) { return nullptr; } // Check previous siblings. unsigned count = 0; NodeImpl *n = element; do { for (n = n->previousSibling(); n && !n->isElementNode(); n = n->previousSibling()); if (!n) { break; } ElementImpl *e = static_cast(n); if (++count > 10) { break; } if (!s) { s = e; // sibling match } if (e->id() != element->id()) { continue; } if (!t) { t = e; // tag match } if (element->hasClass()) { if (!e->hasClass()) { continue; } const DOMString &class1 = element->getAttribute(ATTR_CLASS); const DOMString &class2 = e->getAttribute(ATTR_CLASS); if (class1 != class2) { continue; } } if (!c) { c = e; // class match } break; } while (true); // if possible return sibling that matches tag and class if (c && c->renderer() && c->renderer()->style()) { return c->renderer()->style(); } // second best: return sibling that matches tag if (t && t->renderer() && t->renderer()->style()) { return t->renderer()->style(); } // third best: return sibling element if (s && s->renderer() && s->renderer()->style()) { return s->renderer()->style(); } // last attempt: return parent element NodeImpl *p = element->parentNode(); if (p && p->renderer()) { return p->renderer()->style(); } return nullptr; } static inline void bubbleSort(CSSOrderedProperty **b, CSSOrderedProperty **e) { while (b < e) { bool swapped = false; CSSOrderedProperty **y = e + 1; CSSOrderedProperty **x = e; CSSOrderedProperty **swappedPos = nullptr; do { if (!((**(--x)) < (**(--y)))) { swapped = true; swappedPos = x; CSSOrderedProperty *tmp = *y; *y = *x; *x = tmp; } } while (x != b); if (!swapped) { break; } b = swappedPos + 1; } } RenderStyle *CSSStyleSelector::styleForElement(ElementImpl *e, RenderStyle *fallbackParentStyle) { if (!e->document()->haveStylesheetsLoaded() || !e->document()->view()) { if (!styleNotYetAvailable) { styleNotYetAvailable = new RenderStyle(); styleNotYetAvailable->setDisplay(NONE); styleNotYetAvailable->ref(); } return styleNotYetAvailable; } // set some variables we will need pseudoState = PseudoUnknown; element = e; parentNode = e->parentNode(); parentStyle = (parentNode && parentNode->renderer()) ? parentNode->renderer()->style() : fallbackParentStyle; view = element->document()->view(); part = view->part(); settings = part->settings(); logicalDpiY = element->document()->logicalDpiY(); // reset dynamic DOM dependencies e->document()->dynamicDomRestyler().resetDependencies(e); style = new RenderStyle(); if (parentStyle) { style->inheritFrom(parentStyle); } else { parentStyle = style; } const RenderObject *docElementRenderer = e->document()->documentElement()->renderer(); m_rootStyle = docElementRenderer ? docElementRenderer->style() : m_rootDefaultStyle; // try to sort out most style rules as early as possible. quint16 cssTagId = localNamePart(element->id()); int smatch = 0; int schecked = 0; // do aggressive selection of selectors to check // instead of going over whole constructed list, // skip selectors that won't match for sure (e.g. with different id or class) QVarLengthArray selectorsForCheck; // add unknown selectors to always be checked for (unsigned int i = otherSelector; i < selectors_size; i = nextSimilarSelector[i]) { selectorsForCheck.append(i); } // check if we got class attribute on element: add selectors with it to the list if (e->hasClass()) { const ClassNames &classNames = element->classNames(); for (unsigned int i = 0; i < classNames.size(); ++i) { WTF::HashMap::iterator it = classSelector.find((quintptr)classNames[i].impl()); if (it != classSelector.end()) for (unsigned int j = it->second; j < selectors_size; j = nextSimilarSelector[j]) { selectorsForCheck.append(j); } } } // check if we got id attribute on element: add selectors with it to the list DOMStringImpl *idValue = element->getAttributeImplById(ATTR_ID); if (idValue && idValue->length()) { bool caseSensitive = (e->document()->htmlMode() == DocumentImpl::XHtml) || strictParsing; AtomicString elementId = caseSensitive ? idValue : idValue->lower(); WTF::HashMap::iterator it = idSelector.find((quintptr)elementId.impl()); if (it != idSelector.end()) for (unsigned int j = it->second; j < selectors_size; j = nextSimilarSelector[j]) { selectorsForCheck.append(j); } } // add all selectors with given local tag WTF::HashMap::iterator it = tagSelector.find(cssTagId); if (it != tagSelector.end()) for (unsigned int j = it->second; j < selectors_size; j = nextSimilarSelector[j]) { selectorsForCheck.append(j); } // build per-element cache summaries. prepareToMatchElement(element, true); propsToApply.clear(); pseudoProps.clear(); // now go over selectors that we prepared for check // selectors yet in random order, so we store only matched selector indexes to sort after unsigned amountOfMatchedSelectors = 0; for (int k = 0; k < selectorsForCheck.size(); ++k) { unsigned i = selectorsForCheck[k]; quint16 tag = selectors[i]->tagLocalName.id(); if (cssTagId == tag || tag == anyLocalName) { ++schecked; checkSelector(i, e); if (selectorCache[i].state == Applies || selectorCache[i].state == AppliesPseudo) { selectorsForCheck[amountOfMatchedSelectors++] = i; } } else { selectorCache[i].state = Invalid; } } // sort only matched selectors and then collect properties qSort(selectorsForCheck.data(), selectorsForCheck.data() + amountOfMatchedSelectors); for (unsigned k = 0; k < amountOfMatchedSelectors; ++k) { unsigned i = selectorsForCheck[k]; if (selectorCache[i].state == Applies) { ++smatch; for (unsigned p = selectorCache[i].firstPropertyIndex; p < properties_size; p = nextPropertyIndexes[p]) { propsToApply.append(propertiesBuffer + p); } } else if (selectorCache[i].state == AppliesPseudo) { for (unsigned p = selectorCache[i].firstPropertyIndex; p < properties_size; p = nextPropertyIndexes[p]) { pseudoProps.append(propertiesBuffer + p); propertiesBuffer[p].pseudoId = (RenderStyle::PseudoId) selectors[i]->pseudoId; } } } // clear selectorsForCheck, it shouldn't be used after selectorsForCheck.clear(); // Inline style declarations, after all others. // Non-css hints from presentational attributes will also be collected here // receiving the proper priority so has to cascade from before author rules (cf.CSS 2.1-6.4.4). addInlineDeclarations(e); // qDebug( "styleForElement( %s )", e->tagName().string().toLatin1().constData() ); // qDebug( "%d selectors, %d checked, %d match, %d properties ( of %d )", // selectors_size, schecked, smatch, numPropsToApply, properties_size ); if (propsToApply.size()) { bubbleSort(propsToApply.data(), propsToApply.data() + propsToApply.size() - 1); } if (pseudoProps.size()) { bubbleSort(pseudoProps.data(), pseudoProps.data() + pseudoProps.size() - 1); } // we can't apply style rules without a view() and a part. This // tends to happen on delayed destruction of widget Renderobjects if (part) { fontDirty = false; if (propsToApply.size()) { for (unsigned int i = 0; i < propsToApply.size(); ++i) { if (fontDirty && propsToApply[i]->priority >= (1 << 30)) { // we are past the font properties, time to update to the // correct font #ifdef APPLE_CHANGES checkForGenericFamilyChange(style, parentStyle); #endif style->htmlFont().update(logicalDpiY); fontDirty = false; } DOM::CSSProperty *prop = propsToApply[i]->prop; // if (prop->m_id == CSS_PROP__KONQ_USER_INPUT) qDebug() << "El: "<nodeName().string() << " user-input: "<<((CSSPrimitiveValueImpl *)prop->value())->getIdent(); // if (prop->m_id == CSS_PROP_TEXT_TRANSFORM) qDebug() << "El: "<nodeName().string(); applyRule(prop->m_id, prop->value()); } if (fontDirty) { #ifdef APPLE_CHANGES checkForGenericFamilyChange(style, parentStyle); #endif style->htmlFont().update(logicalDpiY); } } // Clean up our style object's display and text decorations (among other fixups). adjustRenderStyle(style, e); if (pseudoProps.size()) { fontDirty = false; //qDebug("%d applying %d pseudo props", e->cssTagId(), pseudoProps->count() ); for (unsigned int i = 0; i < pseudoProps.size(); ++i) { if (fontDirty && pseudoProps[i]->priority >= (1 << 30)) { // we are past the font properties, time to update to the // correct font //We have to do this for all pseudo styles RenderStyle *pseudoStyle = style->pseudoStyle; while (pseudoStyle) { pseudoStyle->htmlFont().update(logicalDpiY); pseudoStyle = pseudoStyle->pseudoStyle; } fontDirty = false; } RenderStyle *pseudoStyle; pseudoStyle = style->getPseudoStyle(pseudoProps[i]->pseudoId); if (!pseudoStyle) { pseudoStyle = style->addPseudoStyle(pseudoProps[i]->pseudoId); if (pseudoStyle) { pseudoStyle->inheritFrom(style); } } RenderStyle *oldStyle = style; RenderStyle *oldParentStyle = parentStyle; parentStyle = style; style = pseudoStyle; if (pseudoStyle) { DOM::CSSProperty *prop = pseudoProps[i]->prop; applyRule(prop->m_id, prop->value()); } style = oldStyle; parentStyle = oldParentStyle; } if (fontDirty) { RenderStyle *pseudoStyle = style->pseudoStyle; while (pseudoStyle) { pseudoStyle->htmlFont().update(logicalDpiY); pseudoStyle = pseudoStyle->pseudoStyle; } } } } // Now adjust all our pseudo-styles. RenderStyle *pseudoStyle = style->pseudoStyle; while (pseudoStyle) { adjustRenderStyle(pseudoStyle, nullptr); pseudoStyle = pseudoStyle->pseudoStyle; } // Try and share or partially share the style with our siblings RenderStyle *commonStyle = locateSimilarStyle(); if (commonStyle) { style->compactWith(commonStyle); } // Now return the style. return style; } void CSSStyleSelector::prepareToMatchElement(DOM::ElementImpl *e, bool withDeps) { rememberDependencies = withDeps; element = e; // build caches for element so it could be used in heuristic for descendant selectors // go up the tree and cache possible tags, classes and ids tagCache.clear(); idCache.clear(); classCache.clear(); ElementImpl *current = element; while (true) { NodeImpl *parent = current->parentNode(); if (!parent || !parent->isElementNode()) { break; } current = static_cast(parent); if (current->hasClass()) { const ClassNames &classNames = current->classNames(); for (unsigned i = 0; i < classNames.size(); ++i) { classCache.add((quintptr)classNames[i].impl()); } } DOMStringImpl *idValue = current->getAttributeImplById(ATTR_ID); if (idValue && idValue->length()) { bool caseSensitive = (current->document()->htmlMode() == DocumentImpl::XHtml) || strictParsing; AtomicString currentId = caseSensitive ? idValue : idValue->lower(); // though currentId is local and could be deleted from AtomicStringImpl cache right away // don't care about that, cause selector values are stable and only they will be checked later idCache.add((quintptr)currentId.impl()); } tagCache.add(localNamePart(current->id())); } } void CSSStyleSelector::adjustRenderStyle(RenderStyle *style, DOM::ElementImpl *e) { // Cache our original display. style->setOriginalDisplay(style->display()); if (style->display() != NONE) { // If we have a that specifies a float property, in quirks mode we just drop the float // property. // Sites also commonly use display:inline/block on s and s. In quirks mode we force // these tags to retain their display types. if (!strictParsing && e) { if (e->id() == ID_TD) { style->setDisplay(TABLE_CELL); style->setFloating(FNONE); } else if (e->id() == ID_TABLE) { style->setDisplay(style->isDisplayInlineType() ? INLINE_TABLE : TABLE); } } // Table headers with a text-align of auto will change the text-align to center. if (e && e->id() == ID_TH && style->textAlign() == TAAUTO) { style->setTextAlign(CENTER); } // Mutate the display to BLOCK or TABLE for certain cases, e.g., if someone attempts to // position or float an inline, compact, or run-in. Cache the original display, since it // may be needed for positioned elements that have to compute their static normal flow // positions. We also force inline-level roots to be block-level. if (style->display() != BLOCK && style->display() != TABLE /*&& style->display() != BOX*/ && (style->position() == PABSOLUTE || style->position() == PFIXED || style->floating() != FNONE || (e && e->document()->documentElement() == e))) { if (style->display() == INLINE_TABLE) { style->setDisplay(TABLE); } // else if (style->display() == INLINE_BOX) // style->setDisplay(BOX); else if (style->display() == LIST_ITEM) { // It is a WinIE bug that floated list items lose their bullets, so we'll emulate the quirk, // but only in quirks mode. if (!strictParsing && style->floating() != FNONE) { style->setDisplay(BLOCK); } } else { style->setDisplay(BLOCK); } } else if (e && e->id() == ID_BUTTON && style->isOriginalDisplayInlineType()) { //
or . formElt = getForm(impl()); if (formElt) { scope.push(static_cast(getDOMNode(exec, formElt))); } else { DOM::NodeImpl *form = element.parentNode(); while (form && form->id() != ID_FORM) { form = form->parentNode(); } if (form) { scope.push(static_cast(getDOMNode(exec, form))); } } // The element is on top, searched first. scope.push(static_cast(getDOMNode(exec, &element))); } JSValue *KJS::HTMLElementFunction::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args) { KJS_CHECK_THIS(HTMLElement, thisObj); DOMExceptionTranslator exception(exec); #ifdef KJS_VERBOSE qDebug() << "KJS::HTMLElementFunction::callAsFunction "; #endif DOM::HTMLElementImpl &element = *static_cast(thisObj)->impl(); switch (element.id()) { case ID_FORM: { DOM::HTMLFormElementImpl &form = static_cast(element); if (id == KJS::HTMLElement::FormSubmit) { KHTMLPart *part = element.document()->part(); KHTMLSettings::KJSWindowOpenPolicy policy = KHTMLSettings::KJSWindowOpenAllow; if (part) { policy = part->settings()->windowOpenPolicy(part->url().host()); } bool block = false; if (policy != KHTMLSettings::KJSWindowOpenAllow) { block = true; // if this is a form without a target, don't block if (form.target().isEmpty()) { block = false; } QString caption; // if there is a frame with the target name, don't block if (part) { if (!part->url().host().isEmpty()) { caption = part->url().host() + " - "; } if (Window::targetIsExistingWindow(part, form.target().string())) { block = false; } } if (block && policy == KHTMLSettings::KJSWindowOpenAsk && part) { if (part) { emit part->browserExtension()->requestFocus(part); } caption += i18n("Confirmation: JavaScript Popup"); if (KMessageBox::questionYesNo(part->view(), form.action().isEmpty() ? i18n("This site is submitting a form which will open up a new browser " "window via JavaScript.\n" "Do you want to allow the form to be submitted?") : i18n("This site is submitting a form which will open

%1

in a new browser window via JavaScript.
" "Do you want to allow the form to be submitted?
", KStringHandler::csqueeze(form.action().string(), 100)), caption, KGuiItem(i18n("Allow")), KGuiItem(i18n("Do Not Allow"))) == KMessageBox::Yes) { block = false; } } else if (block && policy == KHTMLSettings::KJSWindowOpenSmart) { if (static_cast(exec->dynamicInterpreter())->isWindowOpenAllowed()) { // This submission has been triggered by the user block = false; } } } if (!block) { form.submit(); } return jsUndefined(); } else if (id == KJS::HTMLElement::FormReset) { form.reset(); return jsUndefined(); } } break; case ID_BODY: { if (id == KJS::HTMLElement::BodyFocus) { // Just blur everything. Not perfect, but good enough for now element.document()->setFocusNode(nullptr); } } break; case ID_SELECT: { DOM::HTMLSelectElementImpl &select = static_cast(element); if (id == KJS::HTMLElement::SelectAdd) { select.add(toHTMLElement(args[0]), toHTMLElement(args[1]), exception); return jsUndefined(); } else if (id == KJS::HTMLElement::SelectItem) { SharedPtr opts = select.options(); return getDOMNode(exec, opts->item(static_cast(args[0]->toNumber(exec)))); } else if (id == KJS::HTMLElement::SelectRemove) { // Apparently this takes both elements and indices (ebay.fr) DOM::NodeImpl *node = toNode(args[0]); if (node && node->id() == ID_OPTION) { select.removeChild(node, exception); } else { select.remove(int(args[0]->toNumber(exec))); } return jsUndefined(); } } break; case ID_INPUT: { DOM::HTMLInputElementImpl &input = static_cast(element); if (id == KJS::HTMLElement::InputSelect) { input.select(); return jsUndefined(); } else if (id == KJS::HTMLElement::InputClick) { input.click(); return jsUndefined(); } else if (id == KJS::HTMLElement::InputSetSelectionRange) { input.setSelectionRange(args[0]->toNumber(exec), args[1]->toNumber(exec)); return jsUndefined(); } } break; case ID_BUTTON: { DOM::HTMLButtonElementImpl &button = static_cast(element); if (id == KJS::HTMLElement::ButtonClick) { button.click(); return jsUndefined(); } } break; case ID_TEXTAREA: { DOM::HTMLTextAreaElementImpl &textarea = static_cast(element); if (id == KJS::HTMLElement::TextAreaSelect) { textarea.select(); return jsUndefined(); } else if (id == KJS::HTMLElement::TextAreaSetSelectionRange) { textarea.setSelectionRange(args[0]->toNumber(exec), args[1]->toNumber(exec)); return jsUndefined(); } } break; case ID_A: { DOM::HTMLAnchorElementImpl &anchor = static_cast(element); if (id == KJS::HTMLElement::AnchorClick) { anchor.click(); return jsUndefined(); } else if (id == KJS::HTMLElement::AnchorToString) { return jsString(static_cast(thisObj)->toString(exec)); } } break; case ID_TABLE: { DOM::HTMLTableElementImpl &table = static_cast(element); if (id == KJS::HTMLElement::TableCreateTHead) { return getDOMNode(exec, table.createTHead()); } else if (id == KJS::HTMLElement::TableDeleteTHead) { table.deleteTHead(); return jsUndefined(); } else if (id == KJS::HTMLElement::TableCreateTFoot) { return getDOMNode(exec, table.createTFoot()); } else if (id == KJS::HTMLElement::TableDeleteTFoot) { table.deleteTFoot(); return jsUndefined(); } else if (id == KJS::HTMLElement::TableCreateCaption) { return getDOMNode(exec, table.createCaption()); } else if (id == KJS::HTMLElement::TableDeleteCaption) { table.deleteCaption(); return jsUndefined(); } else if (id == KJS::HTMLElement::TableInsertRow) { return getDOMNode(exec, table.insertRow(args[0]->toInteger(exec), exception)); } else if (id == KJS::HTMLElement::TableDeleteRow) { table.deleteRow(args[0]->toInteger(exec), exception); return jsUndefined(); } } break; case ID_THEAD: case ID_TBODY: case ID_TFOOT: { DOM::HTMLTableSectionElementImpl &tableSection = static_cast(element); if (id == KJS::HTMLElement::TableSectionInsertRow) { return getDOMNode(exec, tableSection.insertRow(args[0]->toInteger(exec), exception)); } else if (id == KJS::HTMLElement::TableSectionDeleteRow) { tableSection.deleteRow(args[0]->toInteger(exec), exception); return jsUndefined(); } } break; case ID_TR: { DOM::HTMLTableRowElementImpl &tableRow = static_cast(element); if (id == KJS::HTMLElement::TableRowInsertCell) { return getDOMNode(exec, tableRow.insertCell(args[0]->toInteger(exec), exception)); } else if (id == KJS::HTMLElement::TableRowDeleteCell) { tableRow.deleteCell(args[0]->toInteger(exec), exception); return jsUndefined(); } break; } case ID_MARQUEE: { if (id == KJS::HTMLElement::MarqueeStart && element.renderer() && element.renderer()->layer() && element.renderer()->layer()->marquee()) { element.renderer()->layer()->marquee()->start(); return jsUndefined(); } else if (id == KJS::HTMLElement::MarqueeStop && element.renderer() && element.renderer()->layer() && element.renderer()->layer()->marquee()) { element.renderer()->layer()->marquee()->stop(); return jsUndefined(); } break; } case ID_CANVAS: { DOM::HTMLCanvasElementImpl &canvasEl = static_cast(element); if (id == KJS::HTMLElement::CanvasGetContext) { if (args[0]->toString(exec) == "2d") { return getWrapper(exec, canvasEl.getContext2D()); } return jsNull(); } else if (id == KJS::HTMLElement::CanvasToDataURL) { return jsString(canvasEl.toDataURL(exception)); } break; } case ID_IFRAME: { DOM::HTMLIFrameElementImpl &iFrame = static_cast(element); if (id == KJS::HTMLElement::IFrameGetSVGDocument) { if (!checkNodeSecurity(exec, iFrame.contentDocument()) || !iFrame.contentDocument() || !iFrame.contentDocument()->isSVGDocument()) { return jsUndefined(); } return getDOMNode(exec, iFrame.contentDocument()); } } case ID_OBJECT: { DOM::HTMLObjectElementImpl &object = static_cast(element); if (id == KJS::HTMLElement::ObjectGetSVGDocument) { if (!checkNodeSecurity(exec, object.contentDocument()) || !object.contentDocument() || !object.contentDocument()->isSVGDocument()) { return jsUndefined(); } return getDOMNode(exec, object.contentDocument()); } } } if (id == HTMLElement::ElementScrollIntoView) { bool alignToTop = true; if (args.size() > 0) { alignToTop = args[0]->toBoolean(exec); } element.scrollIntoView(alignToTop); return jsUndefined(); } return jsUndefined(); } void KJS::HTMLElement::put(ExecState *exec, const Identifier &propertyName, JSValue *value, int attr) { #ifdef KJS_VERBOSE DOM::DOMString str = value->type() == NullType ? DOM::DOMString() : value->toString(exec).domString(); #endif DOM::HTMLElementImpl &element = *impl(); #ifdef KJS_VERBOSE qDebug() << "KJS::HTMLElement::tryPut " << propertyName.qstring() << " thisTag=" << element.tagName().string() - << " str=" << str.string() << endl; + << " str=" << str.string(); #endif // // First look at dynamic properties switch (element.id()) { case ID_SELECT: { DOM::HTMLSelectElementImpl &select = static_cast(element); bool ok; /*uint u =*/ propertyName.qstring().toULong(&ok); if (ok) { JSObject *coll = getSelectHTMLCollection(exec, select.options(), &select)->getObject(); if (coll) { coll->put(exec, propertyName, value); } return; } break; } case ID_APPLET: case ID_OBJECT: case ID_EMBED: { KParts::ScriptableExtension *se = getScriptableExtension(element); if (pluginRootPut(exec, se, propertyName, value)) { return; } break; } default: break; } const HashTable *table = classInfo()->propHashTable; // get the right hashtable const HashEntry *entry = table ? Lookup::findEntry(table, propertyName) : nullptr; if (entry) { if (entry->attr & Function) { // function: put as override property JSObject::put(exec, propertyName, value, attr); return; } else if (!(entry->attr & ReadOnly)) { // let lookupPut print the warning if read-only putValueProperty(exec, entry->value, value, attr); return; } } lookupPut(exec, propertyName, value, attr, &HTMLElementTable, this); } bool KJS::HTMLElement::handleBoundWrite(ExecState *exec, int token, JSValue *value) { const BoundPropInfo *prop = boundPropInfo()->value(token); if (!prop) { return false; } if (prop->type & T_ReadOnly) { return false; } DOM::DOMString str = value->type() == NullType ? DOM::DOMString() : value->toString(exec).domString(); assert(prop->elId == NotApplicable || prop->elId == impl()->id()); switch (prop->type) { case T_String: case T_StrOrNl: case T_URL: impl()->setAttribute(prop->attrId, str); return true; case T_Int: impl()->setAttribute(prop->attrId, QString::number(value->toInteger(exec))); return true; case T_Bool: impl()->setAttribute(prop->attrId, value->toBoolean(exec) ? "" : nullptr); return true; case T_Res: //ignored return true; default: assert(0); return false; } } void KJS::HTMLElement::putValueProperty(ExecState *exec, int token, JSValue *value, int) { if (handleBoundWrite(exec, token, value)) { return; } DOMExceptionTranslator exception(exec); DOM::DOMString str = value->type() == NullType ? DOM::DOMString() : value->toString(exec).domString(); DOM::HTMLElementImpl &element = *impl(); #ifdef KJS_VERBOSE qDebug() << "KJS::HTMLElement::putValueProperty " << " thisTag=" << element.tagName().string() - << " token=" << token << endl; + << " token=" << token; #endif switch (element.id()) { case ID_TITLE: { DOM::HTMLTitleElementImpl &title = static_cast(element); switch (token) { case TitleText: { title.setText(str); return; } } } break; case ID_ISINDEX: { DOM::HTMLIsIndexElementImpl &isindex = static_cast(element); switch (token) { // read-only: form case IsIndexPrompt: { isindex.setPrompt(str); return; } } } break; case ID_BODY: { //DOM::HTMLBodyElementImpl& body = static_cast(element); switch (token) { case BodyOnLoad: setWindowListener(exec, DOM::EventImpl::LOAD_EVENT, value); break; case BodyOnError: setWindowListener(exec, DOM::EventImpl::ERROR_EVENT, value); break; case BodyOnBlur: setWindowListener(exec, DOM::EventImpl::BLUR_EVENT, value); break; case BodyOnFocus: setWindowListener(exec, DOM::EventImpl::FOCUS_EVENT, value); break; case BodyOnMessage: setWindowListener(exec, DOM::EventImpl::MESSAGE_EVENT, value); break; case BodyOnHashChange: setWindowListener(exec, DOM::EventImpl::HASHCHANGE_EVENT, value); break; } } case ID_FRAMESET: { switch (token) { case FrameSetOnMessage: setWindowListener(exec, DOM::EventImpl::MESSAGE_EVENT, value); break; } } break; case ID_SELECT: { DOM::HTMLSelectElementImpl &select = static_cast(element); switch (token) { // read-only: type case SelectSelectedIndex: { select.setSelectedIndex(value->toInteger(exec)); return; } case SelectValue: { select.setValue(str.implementation()); return; } case SelectLength: { // read-only according to the NS spec, but webpages need it writeable JSObject *coll = getSelectHTMLCollection(exec, select.options(), &select)->getObject(); if (coll) { coll->put(exec, "length", value); } return; } // read-only: form // read-only: options case SelectName: { select.setName(str); return; } } } break; case ID_OPTION: { DOM::HTMLOptionElementImpl &option = static_cast(element); switch (token) { // read-only: form // read-only: text <--- According to the DOM, but JavaScript and JScript both allow changes. // So, we'll do it here and not add it to our DOM headers. case OptionText: { RefPtr nl(option.childNodes()); const unsigned int length = nl->length(); for (unsigned int i = 0; i < length; ++i) { if (nl->item(i)->nodeType() == DOM::Node::TEXT_NODE) { static_cast(nl->item(i))->setData(str, exception); return; } } // No child text node found, creating one DOM::TextImpl *t = option.document()->createTextNode(str.implementation()); int dummyexception; option.appendChild(t, dummyexception); // #### exec->setException ? return; } // read-only: index case OptionSelected: { option.setSelected(value->toBoolean(exec)); return; } case OptionValue: { option.setValue(str.implementation()); return; } } } break; case ID_INPUT: { DOM::HTMLInputElementImpl &input = static_cast(element); switch (token) { case InputChecked: { input.setChecked(value->toBoolean(exec)); return; } case InputIndeterminate: { input.setIndeterminate(value->toBoolean(exec)); return; } case InputName: { input.setName(str); return; } case InputType: { input.setType(str); return; } case InputValue: { input.setValue(str); return; } case InputSelectionStart: { input.setSelectionStart(value->toInteger(exec)); return; } case InputSelectionEnd: { input.setSelectionEnd(value->toInteger(exec)); return; } case InputPlaceholder: { input.setPlaceholder(str); return; } } } break; case ID_TEXTAREA: { DOM::HTMLTextAreaElementImpl &textarea = static_cast(element); switch (token) { case TextAreaDefaultValue: { textarea.setDefaultValue(str); return; } case TextAreaName: { textarea.setName(str); return; } case TextAreaValue: { textarea.setValue(str); return; } case TextAreaSelectionStart: { textarea.setSelectionStart(value->toInteger(exec)); return; } case TextAreaSelectionEnd: { textarea.setSelectionEnd(value->toInteger(exec)); return; } case TextAreaPlaceholder: { textarea.setPlaceholder(str); return; } } } break; case ID_A: { DOM::HTMLAnchorElementImpl &anchor = static_cast(element); switch (token) { case AnchorSearch: { QString href = getURLArg(ATTR_HREF); QUrl u(href); QString q = str.isEmpty() ? QString() : str.string(); u.setQuery(q); anchor.setAttribute(ATTR_HREF, u.url()); return; } } } break; case ID_IMG: { DOM::HTMLImageElementImpl &image = static_cast(element); switch (token) { case ImageHeight: { image.setHeight(value->toInteger(exec)); return; } case ImageWidth: { image.setWidth(value->toInteger(exec)); return; } } } break; case ID_CANVAS: { DOM::HTMLCanvasElementImpl &canvas = static_cast(element); switch (token) { // ### it may make sense to do something different here, to at least // emulate reflecting properties somewhat. case CanvasWidth: canvas.setAttribute(ATTR_WIDTH, value->toString(exec).domString()); return; case CanvasHeight: canvas.setAttribute(ATTR_HEIGHT, value->toString(exec).domString()); return; } } break; // case ID_FIELDSET: { // DOM::HTMLFieldSetElementImpl& fieldSet = static_cast(element); // // read-only: form // } // break; case ID_SCRIPT: { DOM::HTMLScriptElementImpl &script = static_cast(element); switch (token) { case ScriptText: { script.setText(str); return; } } } break; case ID_TABLE: { DOM::HTMLTableElementImpl &table = static_cast(element); switch (token) { case TableCaption: { table.setCaption(toHTMLTableCaptionElement(value)); // type HTMLTableCaptionElement return; } case TableTHead: { table.setTHead(toHTMLTableSectionElement(value)); // type HTMLTableSectionElement return; } case TableTFoot: { table.setTFoot(toHTMLTableSectionElement(value)); // type HTMLTableSectionElement return; } } } break; case ID_FRAME: { DOM::HTMLFrameElementImpl &frameElement = static_cast(element); switch (token) { // read-only: FrameContentDocument: case FrameLocation: { frameElement.setLocation(str.string()); return; } } } break; } // generic properties switch (token) { case ElementInnerHTML: element.setInnerHTML(str, exception); return; case ElementInnerText: element.setInnerText(str, exception); return; case ElementContentEditable: element.setContentEditable(str); return; case ElementTabIndex: element.setTabIndex(value->toInteger(exec)); return; default: // qDebug() << "WARNING: KJS::HTMLElement::putValueProperty unhandled token " << token << " thisTag=" << element.tagName().string() << " str=" << str.string(); break; } } //Prototype mess for this... KJS_DEFINE_PROTOTYPE(HTMLElementProto) KJS_IMPLEMENT_PROTOTYPE("HTMLElement", HTMLElementProto, HTMLElementFunction, DOMElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLElementPseudoCtor, "HTMLElement", HTMLElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLHtmlElement", HTMLHtmlElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLHtmlElementPseudoCtor, "HTMLHtmlElement", HTMLHtmlElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLHeadElement", HTMLHeadElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLHeadElementPseudoCtor, "HTMLHeadElement", HTMLHeadElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLLinkElement", HTMLLinkElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLLinkElementPseudoCtor, "HTMLLinkElement", HTMLLinkElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLTitleElement", HTMLTitleElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLTitleElementPseudoCtor, "HTMLTitleElement", HTMLTitleElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLMetaElement", HTMLMetaElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLMetaElementPseudoCtor, "HTMLMetaElement", HTMLMetaElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLBaseElement", HTMLBaseElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLBaseElementPseudoCtor, "HTMLBaseElement", HTMLBaseElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLIsIndexElement", HTMLIsIndexElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLIsIndexElementPseudoCtor, "HTMLIsIndexElement", HTMLIsIndexElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLStyleElement", HTMLStyleElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLStyleElementPseudoCtor, "HTMLStyleElement", HTMLStyleElementProto) KJS_DEFINE_PROTOTYPE(HTMLBodyElementProto) KJS_IMPLEMENT_PROTOTYPE("HTMLBodyElement", HTMLBodyElementProto, HTMLElementFunction, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLBodyElementPseudoCtor, "HTMLBodyElement", HTMLBodyElementProto) KJS_DEFINE_PROTOTYPE(HTMLFormElementProto) KJS_IMPLEMENT_PROTOTYPE("HTMLFormElement", HTMLFormElementProto, HTMLElementFunction, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLFormElementPseudoCtor, "HTMLFormElement", HTMLFormElementProto) KJS_DEFINE_PROTOTYPE(HTMLSelectElementProto) KJS_IMPLEMENT_PROTOTYPE("HTMLSelectElement", HTMLSelectElementProto, HTMLElementFunction, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLSelectElementPseudoCtor, "HTMLSelectElement", HTMLSelectElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLOptGroupElement", HTMLOptGroupElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLOptGroupElementPseudoCtor, "HTMLOptGroupElement", HTMLOptGroupElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLOptionElement", HTMLOptionElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLOptionElementPseudoCtor, "HTMLOptionElement", HTMLOptionElementProto) KJS_DEFINE_PROTOTYPE(HTMLInputElementProto) KJS_IMPLEMENT_PROTOTYPE("HTMLInputElement", HTMLInputElementProto, HTMLElementFunction, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLInputElementPseudoCtor, "HTMLInputElement", HTMLInputElementProto) KJS_DEFINE_PROTOTYPE(HTMLTextAreaElementProto) KJS_IMPLEMENT_PROTOTYPE("HTMLTextAreaElement", HTMLTextAreaElementProto, HTMLElementFunction, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLTextAreaElementPseudoCtor, "HTMLTextAreaElement", HTMLTextAreaElementProto) KJS_DEFINE_PROTOTYPE(HTMLButtonElementProto) KJS_IMPLEMENT_PROTOTYPE("HTMLButtonElement", HTMLButtonElementProto, HTMLElementFunction, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLButtonElementPseudoCtor, "HTMLButtonElement", HTMLButtonElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLLabelElement", HTMLLabelElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLLabelElementPseudoCtor, "HTMLLabelElement", HTMLLabelElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLFieldSetElement", HTMLFieldSetElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLFieldSetElementPseudoCtor, "HTMLFieldSetElement", HTMLFieldSetElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLLegendElement", HTMLLegendElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLLegendElementPseudoCtor, "HTMLLegendElement", HTMLLegendElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLUListElement", HTMLUListElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLUListElementPseudoCtor, "HTMLUListElement", HTMLUListElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLOListElement", HTMLOListElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLOListElementPseudoCtor, "HTMLOListElement", HTMLOListElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLDListElement", HTMLDListElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLDListElementPseudoCtor, "HTMLDListElement", HTMLDListElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLDirectoryElement", HTMLDirectoryElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLDirectoryElementPseudoCtor, "HTMLDirectoryElement", HTMLDirectoryElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLMenuElement", HTMLMenuElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLMenuElementPseudoCtor, "HTMLMenuElement", HTMLMenuElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLLIElement", HTMLLIElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLLIElementPseudoCtor, "HTMLLIElement", HTMLLIElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLDivElement", HTMLDivElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLDivElementPseudoCtor, "HTMLDivElement", HTMLDivElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLParagraphElement", HTMLParagraphElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLParagraphElementPseudoCtor, "HTMLParagraphElement", HTMLParagraphElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLHeadingElement", HTMLHeadingElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLHeadingElementPseudoCtor, "HTMLHeadingElement", HTMLHeadingElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLBlockQuoteElement", HTMLBlockQuoteElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLBlockQuoteElementPseudoCtor, "HTMLBlockQuoteElement", HTMLBlockQuoteElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLQuoteElement", HTMLQuoteElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLQuoteElementPseudoCtor, "HTMLQuoteElement", HTMLQuoteElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLPreElement", HTMLPreElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLPreElementPseudoCtor, "HTMLPreElement", HTMLPreElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLBRElement", HTMLBRElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLBRElementPseudoCtor, "HTMLBRElement", HTMLBRElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLBaseFontElement", HTMLBaseFontElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLBaseFontElementPseudoCtor, "HTMLBaseFontElement", HTMLBaseFontElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLFontElement", HTMLFontElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLFontElementPseudoCtor, "HTMLFontElement", HTMLFontElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLHRElement", HTMLHRElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLHRElementPseudoCtor, "HTMLHRElement", HTMLHRElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLModElement", HTMLModElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLModElementPseudoCtor, "HTMLModElement", HTMLModElementProto) KJS_DEFINE_PROTOTYPE(HTMLAnchorElementProto) KJS_IMPLEMENT_PROTOTYPE("HTMLAnchorElement", HTMLAnchorElementProto, HTMLElementFunction, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLAnchorElementPseudoCtor, "HTMLAnchorElement", HTMLAnchorElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLImageElement", HTMLImageElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLImageElementPseudoCtor, "HTMLImageElement", HTMLImageElementProto) KJS_DEFINE_PROTOTYPE(HTMLObjectElementProto) KJS_IMPLEMENT_PROTOTYPE("HTMLObjectElement", HTMLObjectElementProto, HTMLElementFunction, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLObjectElementPseudoCtor, "HTMLObjectElement", HTMLObjectElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLParamElement", HTMLParamElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLParamElementPseudoCtor, "HTMLParamElement", HTMLParamElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLAppletElement", HTMLAppletElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLAppletElementPseudoCtor, "HTMLAppletElement", HTMLAppletElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLMapElement", HTMLMapElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLMapElementPseudoCtor, "HTMLMapElement", HTMLMapElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLAreaElement", HTMLAreaElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLAreaElementPseudoCtor, "HTMLAreaElement", HTMLAreaElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLScriptElement", HTMLScriptElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLScriptElementPseudoCtor, "HTMLScriptElement", HTMLScriptElementProto) KJS_DEFINE_PROTOTYPE(HTMLTableElementProto) KJS_IMPLEMENT_PROTOTYPE("HTMLTableElement", HTMLTableElementProto, HTMLElementFunction, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLTableElementPseudoCtor, "HTMLTableElement", HTMLTableElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLTableCaptionElement", HTMLTableCaptionElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLTableCaptionElementPseudoCtor, "HTMLTableCaptionElement", HTMLTableCaptionElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLTableColElement", HTMLTableColElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLTableColElementPseudoCtor, "HTMLTableColElement", HTMLTableColElementProto) KJS_DEFINE_PROTOTYPE(HTMLTableSectionElementProto) KJS_IMPLEMENT_PROTOTYPE("HTMLTableSectionElement", HTMLTableSectionElementProto, HTMLElementFunction, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLTableSectionElementPseudoCtor, "HTMLTableSectionElement", HTMLTableSectionElementProto) KJS_DEFINE_PROTOTYPE(HTMLTableRowElementProto) KJS_IMPLEMENT_PROTOTYPE("HTMLTableRowElement", HTMLTableRowElementProto, HTMLElementFunction, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLTableRowElementPseudoCtor, "HTMLTableRowElement", HTMLTableRowElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLTableCellElement", HTMLTableCellElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLTableCellElementPseudoCtor, "HTMLTableCellElement", HTMLTableCellElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLFrameSetElement", HTMLFrameSetElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLFrameSetElementPseudoCtor, "HTMLFrameSetElement", HTMLFrameSetElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLLayerElement", HTMLLayerElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLLayerElementPseudoCtor, "HTMLLayerElement", HTMLLayerElementProto) KJS_EMPTY_PROTOTYPE_WITH_PROTOTYPE("HTMLFrameElement", HTMLFrameElementProto, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLFrameElementPseudoCtor, "HTMLFrameElement", HTMLFrameElementProto) KJS_DEFINE_PROTOTYPE(HTMLIFrameElementProto) KJS_IMPLEMENT_PROTOTYPE("HTMLIFrameElement", HTMLIFrameElementProto, HTMLElementFunction, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLIFrameElementPseudoCtor, "HTMLIFrameElement", HTMLIFrameElementProto) KJS_DEFINE_PROTOTYPE(HTMLMarqueeElementProto) KJS_IMPLEMENT_PROTOTYPE("HTMLMarqueeElement", HTMLMarqueeElementProto, HTMLElementFunction, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLMarqueeElementPseudoCtor, "HTMLMarqueeElement", HTMLMarqueeElementProto) KJS_DEFINE_PROTOTYPE(HTMLCanvasElementProto) KJS_IMPLEMENT_PROTOTYPE("HTMLCanvasElement", HTMLCanvasElementProto, HTMLElementFunction, HTMLElementProto) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLCanvasElementPseudoCtor, "HTMLCanvasElement", HTMLCanvasElementProto) static JSObject *prototypeForID(ExecState *exec, DOM::NodeImpl::Id id) { switch (id) { case ID_HTML: return HTMLHtmlElementProto::self(exec); case ID_HEAD: return HTMLHeadElementProto::self(exec); case ID_LINK: return HTMLLinkElementProto::self(exec); case ID_TITLE: return HTMLTitleElementProto::self(exec); case ID_META: return HTMLMetaElementProto::self(exec); case ID_BASE: return HTMLBaseElementProto::self(exec); case ID_ISINDEX: return HTMLIsIndexElementProto::self(exec); case ID_STYLE: return HTMLStyleElementProto::self(exec); case ID_BODY: return HTMLBodyElementProto::self(exec); case ID_FORM: return HTMLFormElementProto::self(exec); case ID_SELECT: return HTMLSelectElementProto::self(exec); case ID_OPTGROUP: return HTMLOptGroupElementProto::self(exec); case ID_OPTION: return HTMLOptionElementProto::self(exec); case ID_INPUT: return HTMLInputElementProto::self(exec); case ID_TEXTAREA: return HTMLTextAreaElementProto::self(exec); case ID_BUTTON: return HTMLButtonElementProto::self(exec); case ID_LABEL: return HTMLLabelElementProto::self(exec); case ID_FIELDSET: return HTMLFieldSetElementProto::self(exec); case ID_LEGEND: return HTMLLegendElementProto::self(exec); case ID_UL: return HTMLUListElementProto::self(exec); case ID_OL: return HTMLOListElementProto::self(exec); case ID_DL: return HTMLDListElementProto::self(exec); case ID_DIR: return HTMLDirectoryElementProto::self(exec); case ID_MENU: return HTMLMenuElementProto::self(exec); case ID_LI: return HTMLLIElementProto::self(exec); case ID_DIV: return HTMLDivElementProto::self(exec); case ID_P: return HTMLParagraphElementProto::self(exec); case ID_H1: case ID_H2: case ID_H3: case ID_H4: case ID_H5: case ID_H6: return HTMLHeadingElementProto::self(exec); case ID_BLOCKQUOTE: return HTMLBlockQuoteElementProto::self(exec); case ID_Q: return HTMLQuoteElementProto::self(exec); case ID_PRE: return HTMLPreElementProto::self(exec); case ID_BR: return HTMLBRElementProto::self(exec); case ID_BASEFONT: return HTMLBaseFontElementProto::self(exec); case ID_FONT: return HTMLFontElementProto::self(exec); case ID_HR: return HTMLHRElementProto::self(exec); case ID_INS: case ID_DEL: return HTMLModElementProto::self(exec); case ID_A: return HTMLAnchorElementProto::self(exec); case ID_IMG: return HTMLImageElementProto::self(exec); case ID_OBJECT: return HTMLObjectElementProto::self(exec); case ID_PARAM: return HTMLParamElementProto::self(exec); case ID_APPLET: return HTMLAppletElementProto::self(exec); case ID_MAP: return HTMLMapElementProto::self(exec); case ID_AREA: return HTMLAreaElementProto::self(exec); case ID_SCRIPT: return HTMLScriptElementProto::self(exec); case ID_TABLE: return HTMLTableElementProto::self(exec); case ID_CAPTION: return HTMLTableCaptionElementProto::self(exec); case ID_COL: case ID_COLGROUP: return HTMLTableColElementProto::self(exec); case ID_THEAD: case ID_TBODY: case ID_TFOOT: return HTMLTableSectionElementProto::self(exec); case ID_TR: return HTMLTableRowElementProto::self(exec); case ID_TD: case ID_TH: return HTMLTableCellElementProto::self(exec); case ID_FRAMESET: return HTMLFrameSetElementProto::self(exec); case ID_LAYER: return HTMLLayerElementProto::self(exec); case ID_FRAME: return HTMLFrameElementProto::self(exec); case ID_IFRAME: return HTMLIFrameElementProto::self(exec); case ID_MARQUEE: return HTMLMarqueeElementProto::self(exec); case ID_CANVAS: return HTMLCanvasElementProto::self(exec); default: return HTMLElementProto::self(exec); } } // ------------------------------------------------------------------------- /* Source for HTMLCollectionProtoTable. @begin HTMLCollectionProtoTable 3 item HTMLCollection::Item DontDelete|Function 1 namedItem HTMLCollection::NamedItem DontDelete|Function 1 tags HTMLCollection::Tags DontDelete|Function 1 @end */ KJS_DEFINE_PROTOTYPE(HTMLCollectionProto) KJS_IMPLEMENT_PROTOFUNC(HTMLCollectionProtoFunc) KJS_IMPLEMENT_PROTOTYPE("HTMLCollection", HTMLCollectionProto, HTMLCollectionProtoFunc, ObjectPrototype) IMPLEMENT_PSEUDO_CONSTRUCTOR(HTMLCollectionPseudoCtor, "HTMLCollection", HTMLCollectionProto) const ClassInfo KJS::HTMLCollection::info = { "HTMLCollection", nullptr, nullptr, nullptr }; KJS::HTMLCollection::HTMLCollection(ExecState *exec, DOM::HTMLCollectionImpl *c) : DOMObject(HTMLCollectionProto::self(exec)), m_impl(c), hidden(false) {} KJS::HTMLCollection::HTMLCollection(JSObject *proto, DOM::HTMLCollectionImpl *c) : DOMObject(proto), m_impl(c), hidden(false) {} KJS::HTMLCollection::~HTMLCollection() { ScriptInterpreter::forgetDOMObject(m_impl.get()); } bool KJS::HTMLCollection::masqueradeAsUndefined() const { return hidden; } bool KJS::HTMLCollection::toBoolean(ExecState *) const { return !hidden; } JSValue *HTMLCollection::indexGetter(ExecState *exec, unsigned index) { return getDOMNode(exec, m_impl->item(index)); } void KJS::HTMLCollection::getOwnPropertyNames(ExecState *exec, PropertyNameArray &propertyNames, PropertyMap::PropertyMode mode) { for (unsigned i = 0; i < m_impl->length(); ++i) { propertyNames.add(Identifier::from(i)); } propertyNames.add(exec->propertyNames().length); JSObject::getOwnPropertyNames(exec, propertyNames, mode); } JSValue *HTMLCollection::lengthGetter(ExecState *, JSObject *, const Identifier &, const PropertySlot &slot) { HTMLCollection *thisObj = static_cast(slot.slotBase()); return jsNumber(thisObj->m_impl->length()); } JSValue *HTMLCollection::nameGetter(ExecState *exec, JSObject *, const Identifier &propertyName, const PropertySlot &slot) { HTMLCollection *thisObj = static_cast(slot.slotBase()); return thisObj->getNamedItems(exec, propertyName); } bool KJS::HTMLCollection::getOwnPropertySlot(ExecState *exec, const Identifier &propertyName, PropertySlot &slot) { #ifdef KJS_VERBOSE qDebug() << "KJS::HTMLCollection::getOwnPropertySlot " << propertyName.ascii(); #endif if (propertyName.isEmpty()) { return false; } if (propertyName == exec->propertyNames().length) { #ifdef KJS_VERBOSE qDebug() << " collection length is " << m_impl->length(); #endif slot.setCustom(this, lengthGetter); return true; } // Look in the prototype (for functions) before assuming it's an item's name JSValue *proto = prototype(); if (proto->isObject() && static_cast(proto)->hasProperty(exec, propertyName)) { return false; } // name or index ? if (getIndexSlot(this, *m_impl, propertyName, slot)) { return true; } if (!getNamedItems(exec, propertyName)->isUndefined()) { slot.setCustom(this, nameGetter); return true; } return DOMObject::getOwnPropertySlot(exec, propertyName, slot); } // HTMLCollections are strange objects, they support both get and call, // so that document.forms.item(0) and document.forms(0) both work. JSValue *KJS::HTMLCollection::callAsFunction(ExecState *exec, JSObject *, const List &args) { // Do not use thisObj here. It can be the HTMLDocument, in the document.forms(i) case. /*if( thisObj.imp() != this ) { // qDebug() << "WARNING: thisObj.imp() != this in HTMLCollection::tryCall"; KJS::printInfo(exec,"KJS::HTMLCollection::tryCall thisObj",thisObj,-1); KJS::printInfo(exec,"KJS::HTMLCollection::tryCall this",Value(this),-1); }*/ // Also, do we need the TypeError test here ? HTMLCollectionImpl &collection = *m_impl; // Also, do we need the TypeError test here ? if (args.size() == 1) { // support for document.all() etc. bool ok; UString s = args[0]->toString(exec); unsigned int u = s.toArrayIndex(&ok); if (ok) { return getDOMNode(exec, collection.item(u)); } // support for document.images('') etc. return getNamedItems(exec, Identifier(s)); } else if (args.size() >= 1) { // the second arg, if set, is the index of the item we want bool ok; UString s = args[0]->toString(exec); unsigned int u = args[1]->toString(exec).toArrayIndex(&ok); if (ok) { DOM::DOMString pstr = s.domString(); DOM::NodeImpl *node = collection.namedItem(pstr); while (node) { if (!u) { return getDOMNode(exec, node); } node = collection.nextNamedItem(pstr); --u; } } } return jsUndefined(); } JSValue *KJS::HTMLCollection::getNamedItems(ExecState *exec, const Identifier &propertyName) const { #ifdef KJS_VERBOSE qDebug() << "KJS::HTMLCollection::getNamedItems " << propertyName.ascii(); #endif DOM::DOMString pstr = propertyName.domString(); const QList matches = m_impl->namedItems(pstr); if (!matches.isEmpty()) { if (matches.size() == 1) { #ifdef KJS_VERBOSE qDebug() << "returning single node"; #endif return getDOMNode(exec, matches[0]); } else { // multiple items, return a collection QList > nodes; foreach (DOM::NodeImpl *node, matches) { nodes.append(node); } #ifdef KJS_VERBOSE qDebug() << "returning list of " << matches.count() << " nodes"; #endif return new DOMNamedNodesCollection(exec, nodes); } } #ifdef KJS_VERBOSE qDebug() << "not found"; #endif return jsUndefined(); } JSValue *KJS::HTMLCollectionProtoFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args) { KJS_CHECK_THIS(KJS::HTMLCollection, thisObj); HTMLCollectionImpl &coll = *static_cast(thisObj)->impl(); switch (id) { case KJS::HTMLCollection::Item: { // support for item() (DOM) UString s = args[0]->toString(exec); bool ok; unsigned int u = s.toArrayIndex(&ok); if (ok) { return getDOMNode(exec, coll.item(u)); } // support for item('') (IE only) qWarning() << "non-standard HTMLCollection.item('" << s.ascii() << "') called, use namedItem instead"; return static_cast(thisObj)->getNamedItems(exec, Identifier(s)); } case KJS::HTMLCollection::Tags: { DOM::DOMString tagName = args[0]->toString(exec).domString(); DOM::NodeListImpl *list; // getElementsByTagName exists in Document and in Element, pick up the right one if (coll.base()->nodeType() == DOM::Node::DOCUMENT_NODE) { DOM::DocumentImpl *doc = static_cast(coll.base()); list = doc->getElementsByTagName(tagName); #ifdef KJS_VERBOSE qDebug() << "KJS::HTMLCollectionProtoFunc::callAsFunction document.tags(" << tagName.string() << ") -> " << list->length() << " items in node list"; #endif } else { DOM::ElementImpl *e = static_cast(coll.base()); list = e->getElementsByTagName(tagName); #ifdef KJS_VERBOSE qDebug() << "KJS::HTMLCollectionProtoFunc::tryCall element.tags(" << tagName.string() << ") -> " << list->length() << " items in node list"; #endif } return getDOMNodeList(exec, list); } case KJS::HTMLCollection::NamedItem: { JSValue *val = static_cast(thisObj)->getNamedItems(exec, Identifier(args[0]->toString(exec))); // Must return null when asking for a named item that isn't in the collection // (DOM2 testsuite, HTMLCollection12 test) if (val->type() == KJS::UndefinedType) { return jsNull(); } else { return val; } } default: return jsUndefined(); } } // ------------------------------------------------------------------------- /* Source for HTMLSelectCollectionProtoTable. @begin HTMLSelectCollectionProtoTable 1 add HTMLSelectCollection::Add DontDelete|Function 2 remove HTMLSelectCollection::Remove DontDelete|Function 1 @end */ KJS_DEFINE_PROTOTYPE(HTMLSelectCollectionProto) KJS_IMPLEMENT_PROTOFUNC(HTMLSelectCollectionProtoFunc) KJS_IMPLEMENT_PROTOTYPE("HTMLOptionsCollection", HTMLSelectCollectionProto, HTMLSelectCollectionProtoFunc, HTMLCollectionProto) const ClassInfo KJS::HTMLSelectCollection::info = { "HTMLOptionsCollection", &HTMLCollection::info, nullptr, nullptr }; KJS::HTMLSelectCollection::HTMLSelectCollection(ExecState *exec, DOM::HTMLCollectionImpl *c, DOM::HTMLSelectElementImpl *e) : HTMLCollection(HTMLSelectCollectionProto::self(exec), c), element(e) { } JSValue *HTMLSelectCollection::selectedIndexGetter(ExecState *, JSObject *, const Identifier &, const PropertySlot &slot) { HTMLSelectCollection *thisObj = static_cast(slot.slotBase()); return jsNumber(thisObj->element->selectedIndex()); } JSValue *HTMLSelectCollection::selectedValueGetter(ExecState *, JSObject *, const Identifier &propertyName, const PropertySlot &slot) { Q_UNUSED(propertyName); HTMLSelectCollection *thisObj = static_cast(slot.slotBase()); return jsString(thisObj->element->value()); } bool KJS::HTMLSelectCollection::getOwnPropertySlot(ExecState *exec, const Identifier &p, PropertySlot &slot) { if (p == "selectedIndex") { slot.setCustom(this, selectedIndexGetter); return true; } else if (p == "value") { slot.setCustom(this, selectedValueGetter); return true; } return HTMLCollection::getOwnPropertySlot(exec, p, slot); } void KJS::HTMLSelectCollection::put(ExecState *exec, const Identifier &propertyName, JSValue *value, int) { DOMExceptionTranslator exception(exec); #ifdef KJS_VERBOSE qDebug() << "KJS::HTMLSelectCollection::put " << propertyName.qstring(); #endif if (propertyName == "selectedIndex") { element->setSelectedIndex(value->toInteger(exec)); return; } // resize ? else if (propertyName == exec->propertyNames().length) { uint32_t newLen; bool converted = value->getUInt32(newLen); if (!converted) { return; } // CVE-2009-2537 (vendors agreed on max 10000 elements) if (newLen > 10000) { setDOMException(exec, DOMException::INDEX_SIZE_ERR); return; } long diff = element->length() - newLen; if (diff < 0) { // add dummy elements do { ElementImpl *option = element->document()->createElement("option", exception); if (exception.triggered()) { return; } element->add(static_cast(option), nullptr, exception); if (exception.triggered()) { return; } } while (++diff); } else // remove elements while (diff-- > 0) { element->remove(newLen + diff); } return; } // an index ? bool ok; unsigned int u = propertyName.qstring().toULong(&ok); if (!ok) { return; } if (value->type() == NullType || value->type() == UndefinedType) { // null and undefined delete. others, too ? element->remove(u); return; } // is v an option element ? DOM::NodeImpl *node = KJS::toNode(value); if (!node || node->id() != ID_OPTION) { return; } DOM::HTMLOptionElementImpl *option = static_cast(node); if (option->document() != element->document()) { option = static_cast(element->ownerDocument()->importNode(option, true, exception)); } if (exception.triggered()) { delete option; return; } long diff = long(u) - element->length(); DOM::HTMLElementImpl *before = nullptr; // out of array bounds ? first insert empty dummies if (diff > 0) { while (diff--) { element->add( static_cast(element->document()->createElement("OPTION")), before, exception); if (exception.triggered()) { delete option; return; } } // replace an existing entry ? } else if (diff < 0) { SharedPtr options = element->options(); before = static_cast(options->item(u + 1)); element->remove(u); } // finally add the new element element->add(option, before, exception); } JSValue *KJS::HTMLSelectCollectionProtoFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args) { KJS_CHECK_THIS(KJS::HTMLSelectCollection, thisObj); DOM::HTMLSelectElementImpl *element = static_cast(thisObj)->toElement(); switch (id) { case KJS::HTMLSelectCollection::Add: { //Non-standard select.options.add. //The first argument is the item, 2nd is offset. //IE and Mozilla are both quite picky here, too... DOM::NodeImpl *node = KJS::toNode(args[0]); if (!node || node->id() != ID_OPTION) { return throwError(exec, GeneralError, "Invalid argument to HTMLOptionsCollection::add"); } DOM::HTMLOptionElementImpl *option = static_cast(node); int pos = 0; //By default append, if not specified or null.. if (args[1]->isUndefined()) { pos = element->length(); } else { pos = (int)args[1]->toNumber(exec); } if (pos < 0) { return throwError(exec, GeneralError, "Invalid index argument to HTMLOptionsCollection::add"); } DOMExceptionTranslator exception(exec); if (pos >= element->length()) { //Append element->add(option, nullptr, exception); } else { //Find what to prepend before.. QVector items = element->listItems(); int dummy; element->insertBefore(option, items.at(pos), dummy); } return jsUndefined(); break; } case KJS::HTMLSelectCollection::Remove: { double index; if (!args[0]->getNumber(index)) { index = 0; } else { if (static_cast(index) >= element->length()) { index = 0; } } element->remove(static_cast(index)); return jsUndefined(); break; } default: break; } return jsUndefined(); } ////////////////////// Option Object //////////////////////// OptionConstructorImp::OptionConstructorImp(ExecState *exec, DOM::DocumentImpl *d) : JSObject(exec->lexicalInterpreter()->builtinObjectPrototype()), doc(d) { // ## isn't there some redundancy between JSObject::_proto and the "prototype" property ? //put(exec,"prototype", ...,DontEnum|DontDelete|ReadOnly); // no. of arguments for constructor // ## is 4 correct ? 0 to 4, it seems to be put(exec, exec->propertyNames().length, jsNumber(4), ReadOnly | DontDelete | DontEnum); } bool OptionConstructorImp::implementsConstruct() const { return true; } JSObject *OptionConstructorImp::construct(ExecState *exec, const List &args) { DOMExceptionTranslator exception(exec); DOM::ElementImpl *el = doc->createElement("OPTION"); DOM::HTMLOptionElementImpl *opt = static_cast(el); int sz = args.size(); SharedPtr t = doc->createTextNode(""); int dummyexception = 0;// #### exec->setException ? opt->appendChild(t.get(), dummyexception); if (sz > 0) { t->setData(args[0]->toString(exec).domString(), exception); // set the text } if (sz > 1) { opt->setValue(args[1]->toString(exec).domString().implementation()); } if (sz > 2) { opt->setDefaultSelected(args[2]->toBoolean(exec)); } if (sz > 3) { opt->setSelected(args[3]->toBoolean(exec)); } return getDOMNode(exec, opt)->getObject(); } ////////////////////// Image Object //////////////////////// //Like in other browsers, we merely make a new HTMLImageElement //not in tree for this. ImageConstructorImp::ImageConstructorImp(ExecState *exec, DOM::DocumentImpl *d) : JSObject(exec->lexicalInterpreter()->builtinObjectPrototype()), doc(d) { } bool ImageConstructorImp::implementsConstruct() const { return true; } JSObject *ImageConstructorImp::construct(ExecState *exec, const List &list) { bool widthSet = false, heightSet = false; int width = 0, height = 0; if (list.size() > 0) { widthSet = true; JSValue *w = list.at(0); width = w->toInt32(exec); } if (list.size() > 1) { heightSet = true; JSValue *h = list.at(1); height = h->toInt32(exec); } HTMLImageElementImpl *image = static_cast(doc->createElement("img")); if (widthSet) { image->setAttribute(ATTR_WIDTH, QString::number(width)); } if (heightSet) { image->setAttribute(ATTR_HEIGHT, QString::number(height)); } return getDOMNode(exec, image)->getObject(); } JSValue *getHTMLCollection(ExecState *exec, DOM::HTMLCollectionImpl *c, bool hide) { assert(!c || c->getType() != HTMLCollectionImpl::SELECT_OPTIONS); JSValue *coll = cacheDOMObject(exec, c); if (hide) { KJS::HTMLCollection *impl = static_cast(coll); impl->hide(); } return coll; } JSValue *getSelectHTMLCollection(ExecState *exec, DOM::HTMLCollectionImpl *c, DOM::HTMLSelectElementImpl *e) { assert(!c || c->getType() == HTMLCollectionImpl::SELECT_OPTIONS); DOMObject *ret; if (!c) { return jsNull(); } ScriptInterpreter *interp = static_cast(exec->dynamicInterpreter()); if ((ret = interp->getDOMObject(c))) { return ret; } else { ret = new HTMLSelectCollection(exec, c, e); interp->putDOMObject(c, ret); return ret; } } } //namespace KJS diff --git a/src/editing/editor.cpp b/src/editing/editor.cpp index 3303fd4..f5b1e23 100644 --- a/src/editing/editor.cpp +++ b/src/editing/editor.cpp @@ -1,617 +1,617 @@ /* This file is part of the KDE project * * Copyright (C) 2004 Leo Savernik * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "editor.h" #include "jsediting.h" #include "htmlediting_impl.h" #include "css/css_renderstyledeclarationimpl.h" #include "css/css_valueimpl.h" #include "xml/dom_selection.h" #include "xml/dom_docimpl.h" #include "xml/dom_elementimpl.h" #include "xml/dom_textimpl.h" #include "xml/dom2_rangeimpl.h" #include "khtml_part.h" #include "khtml_ext.h" #include "khtmlpart_p.h" #include #include #ifndef APPLE_CHANGES # ifdef assert # undef assert # endif # define assert(x) Q_ASSERT(x) #endif #define PREPARE_JSEDITOR_CALL(command, retval) \ JSEditor *js = m_part->xmlDocImpl() ? m_part->xmlDocImpl()->jsEditor() : 0; \ if (!js) return retval; \ const CommandImp *imp = js->commandImp(command) #define DEBUG_COMMANDS using namespace WTF; using namespace DOM; using khtml::RenderStyleDeclarationImpl; using khtml::EditCommandImpl; using khtml::ApplyStyleCommandImpl; using khtml::TypingCommandImpl; using khtml::EditorContext; using khtml::IndentOutdentCommandImpl; // -------------------------------------------------------------------------- namespace DOM { static const int sMaxUndoSteps = 1000; class EditorPrivate { public: void registerUndo(EditCommandImpl *cmd, bool clearRedoStack = true) { if (m_undo.count() >= sMaxUndoSteps) { m_undo.pop_front(); } if (clearRedoStack) { m_redo.clear(); } m_undo.push(cmd); } void registerRedo(EditCommandImpl *cmd) { if (m_redo.count() >= sMaxUndoSteps) { m_redo.pop_front(); } m_redo.push(cmd); } RefPtr m_lastEditCommand; QStack > m_undo; QStack > m_redo; }; } // ========================================================================== Editor::Editor(KHTMLPart *part) : d(new EditorPrivate), m_typingStyle(nullptr), m_part(part) { } Editor::~Editor() { if (m_typingStyle) { m_typingStyle->deref(); } delete d; } bool Editor::execCommand(const DOMString &command, bool userInterface, const DOMString &value) { PREPARE_JSEDITOR_CALL(command, false); return js->execCommand(imp, userInterface, value); } bool Editor::queryCommandEnabled(const DOMString &command) { PREPARE_JSEDITOR_CALL(command, false); return js->queryCommandEnabled(imp); } bool Editor::queryCommandIndeterm(const DOMString &command) { PREPARE_JSEDITOR_CALL(command, false); return js->queryCommandIndeterm(imp); } bool Editor::queryCommandState(const DOMString &command) { PREPARE_JSEDITOR_CALL(command, false); return js->queryCommandState(imp); } bool Editor::queryCommandSupported(const DOMString &command) { PREPARE_JSEDITOR_CALL(command, false); return js->queryCommandSupported(imp); } DOMString Editor::queryCommandValue(const DOMString &command) { PREPARE_JSEDITOR_CALL(command, DOMString()); return js->queryCommandValue(imp); } bool Editor::execCommand(EditorCommand command, bool userInterface, const DOMString &value) { PREPARE_JSEDITOR_CALL(command, false); return js->execCommand(imp, userInterface, value); } bool Editor::queryCommandEnabled(EditorCommand command) { PREPARE_JSEDITOR_CALL(command, false); return js->queryCommandEnabled(imp); } bool Editor::queryCommandIndeterm(EditorCommand command) { PREPARE_JSEDITOR_CALL(command, false); return js->queryCommandIndeterm(imp); } bool Editor::queryCommandState(EditorCommand command) { PREPARE_JSEDITOR_CALL(command, false); return js->queryCommandState(imp); } bool Editor::queryCommandSupported(EditorCommand command) { PREPARE_JSEDITOR_CALL(command, false); return js->queryCommandSupported(imp); } DOMString Editor::queryCommandValue(EditorCommand command) { PREPARE_JSEDITOR_CALL(command, DOMString()); return js->queryCommandValue(imp); } void Editor::copy() { static_cast(m_part->browserExtension())->copy(); } void Editor::cut() { // ### static_cast(m_part->browserExtension())->cut(); } void Editor::paste() { // ### // security? // static_cast(m_part->browserExtension())->paste(); } void Editor::print() { static_cast(m_part->browserExtension())->print(); } bool Editor::canPaste() const { // ### return false; } void Editor::redo() { if (d->m_redo.isEmpty()) { return; } RefPtr e = d->m_redo.pop(); e->reapply(); } void Editor::undo() { if (d->m_undo.isEmpty()) { return; } RefPtr e = d->m_undo.pop(); e->unapply(); } bool Editor::canRedo() const { return !d->m_redo.isEmpty(); } bool Editor::canUndo() const { return !d->m_undo.isEmpty(); } void Editor::applyStyle(CSSStyleDeclarationImpl *style) { switch (m_part->caret().state()) { case Selection::NONE: // do nothing break; case Selection::CARET: // FIXME: This blows away all the other properties of the typing style. setTypingStyle(style); break; case Selection::RANGE: if (m_part->xmlDocImpl() && style) { #ifdef DEBUG_COMMANDS - // qDebug() << "[create ApplyStyleCommand]" << endl; + // qDebug() << "[create ApplyStyleCommand]"; #endif // FIXME (new ApplyStyleCommandImpl(m_part->xmlDocImpl(), style))->apply(); } break; } } static void updateState(CSSStyleDeclarationImpl *desiredStyle, CSSStyleDeclarationImpl *computedStyle, bool &atStart, Editor::TriState &state) { QListIterator it(*desiredStyle->values()); while (it.hasNext()) { int propertyID = it.next()->id(); DOMString desiredProperty = desiredStyle->getPropertyValue(propertyID); DOMString computedProperty = computedStyle->getPropertyValue(propertyID); Editor::TriState propertyState = strcasecmp(desiredProperty, computedProperty) == 0 ? Editor::TrueTriState : Editor::FalseTriState; if (atStart) { state = propertyState; atStart = false; } else if (state != propertyState) { state = Editor::MixedTriState; break; } } } Editor::TriState Editor::selectionHasStyle(CSSStyleDeclarationImpl *style) const { bool atStart = true; TriState state = FalseTriState; EditorContext *ctx = m_part->editorContext(); if (ctx->m_selection.state() != Selection::RANGE) { NodeImpl *nodeToRemove; CSSStyleDeclarationImpl *selectionStyle = selectionComputedStyle(nodeToRemove); if (!selectionStyle) { return FalseTriState; } selectionStyle->ref(); updateState(style, selectionStyle, atStart, state); selectionStyle->deref(); if (nodeToRemove) { int exceptionCode = 0; nodeToRemove->remove(exceptionCode); assert(exceptionCode == 0); } } else { for (NodeImpl *node = ctx->m_selection.start().node(); node; node = node->traverseNextNode()) { if (node->isHTMLElement()) { CSSStyleDeclarationImpl *computedStyle = new RenderStyleDeclarationImpl(node); computedStyle->ref(); updateState(style, computedStyle, atStart, state); computedStyle->deref(); if (state == MixedTriState) { break; } } if (node == ctx->m_selection.end().node()) { break; } } } return state; } bool Editor::selectionStartHasStyle(CSSStyleDeclarationImpl *style) const { NodeImpl *nodeToRemove; CSSStyleDeclarationImpl *selectionStyle = selectionComputedStyle(nodeToRemove); if (!selectionStyle) { return false; } selectionStyle->ref(); bool match = true; QListIterator it(*style->values()); while (it.hasNext()) { int propertyID = it.next()->id(); DOMString desiredProperty = style->getPropertyValue(propertyID); DOMString selectionProperty = selectionStyle->getPropertyValue(propertyID); if (strcasecmp(selectionProperty, desiredProperty) != 0) { match = false; break; } } selectionStyle->deref(); if (nodeToRemove) { int exceptionCode = 0; nodeToRemove->remove(exceptionCode); assert(exceptionCode == 0); } return match; } DOMString Editor::selectionStartStylePropertyValue(int stylePropertyID) const { NodeImpl *nodeToRemove; CSSStyleDeclarationImpl *selectionStyle = selectionComputedStyle(nodeToRemove); if (!selectionStyle) { return DOMString(); } selectionStyle->ref(); DOMString value = selectionStyle->getPropertyValue(stylePropertyID); selectionStyle->deref(); if (nodeToRemove) { int exceptionCode = 0; nodeToRemove->remove(exceptionCode); assert(exceptionCode == 0); } return value; } CSSStyleDeclarationImpl *Editor::selectionComputedStyle(NodeImpl *&nodeToRemove) const { nodeToRemove = nullptr; if (!m_part->xmlDocImpl()) { return nullptr; } EditorContext *ctx = m_part->editorContext(); if (ctx->m_selection.state() == Selection::NONE) { return nullptr; } Range range(ctx->m_selection.toRange()); Position pos(range.startContainer().handle(), range.startOffset()); assert(pos.notEmpty()); ElementImpl *elem = pos.element(); ElementImpl *styleElement = elem; int exceptionCode = 0; if (m_typingStyle) { styleElement = m_part->xmlDocImpl()->createHTMLElement("SPAN"); // assert(exceptionCode == 0); styleElement->setAttribute(ATTR_STYLE, m_typingStyle->cssText().implementation()); // assert(exceptionCode == 0); TextImpl *text = m_part->xmlDocImpl()->createEditingTextNode(""); styleElement->appendChild(text, exceptionCode); assert(exceptionCode == 0); elem->appendChild(styleElement, exceptionCode); assert(exceptionCode == 0); nodeToRemove = styleElement; } return new RenderStyleDeclarationImpl(styleElement); } PassRefPtr Editor::lastEditCommand() const { return d->m_lastEditCommand; } void Editor::appliedEditing(EditCommandImpl *cmd) { #ifdef DEBUG_COMMANDS - // qDebug() << "[Applied editing]" << endl; + // qDebug() << "[Applied editing]"; #endif // make sure we have all the changes in rendering tree applied with relayout if needed before setting caret // in particular that could be required for inline boxes recomputation when inserting text m_part->xmlDocImpl()->updateLayout(); m_part->setCaret(cmd->endingSelection(), false); // Command will be equal to last edit command only in the case of typing if (d->m_lastEditCommand == cmd) { assert(cmd->isTypingCommand()); } else { // Only register a new undo command if the command passed in is // different from the last command d->registerUndo(cmd); d->m_lastEditCommand = cmd; } m_part->editorContext()->m_selection.setNeedsLayout(true); m_part->selectionLayoutChanged(); // ### only emit if caret pos changed m_part->emitCaretPositionChanged(cmd->endingSelection().caretPos()); } void Editor::unappliedEditing(EditCommandImpl *cmd) { // see comment in appliedEditing() m_part->xmlDocImpl()->updateLayout(); m_part->setCaret(cmd->startingSelection()); d->registerRedo(cmd); #ifdef APPLE_CHANGES KWQ(this)->respondToChangedContents(); #else m_part->editorContext()->m_selection.setNeedsLayout(true); m_part->selectionLayoutChanged(); // ### only emit if caret pos changed m_part->emitCaretPositionChanged(cmd->startingSelection().caretPos()); #endif d->m_lastEditCommand = nullptr; } void Editor::reappliedEditing(EditCommandImpl *cmd) { // see comment in appliedEditing() m_part->xmlDocImpl()->updateLayout(); m_part->setCaret(cmd->endingSelection()); d->registerUndo(cmd, false /*clearRedoStack*/); #ifdef APPLE_CHANGES KWQ(this)->respondToChangedContents(); #else m_part->selectionLayoutChanged(); // ### only emit if caret pos changed m_part->emitCaretPositionChanged(cmd->endingSelection().caretPos()); #endif d->m_lastEditCommand = nullptr; } CSSStyleDeclarationImpl *Editor::typingStyle() const { return m_typingStyle; } void Editor::setTypingStyle(CSSStyleDeclarationImpl *style) { CSSStyleDeclarationImpl *old = m_typingStyle; m_typingStyle = style; if (m_typingStyle) { m_typingStyle->ref(); } if (old) { old->deref(); } } void Editor::clearTypingStyle() { setTypingStyle(nullptr); } void Editor::closeTyping() { EditCommandImpl *lastCommand = lastEditCommand().get(); if (lastCommand && lastCommand->isTypingCommand()) { static_cast(lastCommand)->closeTyping(); } } void Editor::indent() { RefPtr command = new IndentOutdentCommandImpl(m_part->xmlDocImpl(), IndentOutdentCommandImpl::Indent); command->apply(); } void Editor::outdent() { RefPtr command = new IndentOutdentCommandImpl(m_part->xmlDocImpl(), IndentOutdentCommandImpl::Outdent); command->apply(); } bool Editor::handleKeyEvent(QKeyEvent *_ke) { bool handled = false; bool ctrl = _ke->modifiers() & Qt::ControlModifier; bool alt = _ke->modifiers() & Qt::AltModifier; //bool shift = _ke->modifiers() & Qt::ShiftModifier; bool meta = _ke->modifiers() & Qt::MetaModifier; if (ctrl || alt || meta) { return false; } switch (_ke->key()) { case Qt::Key_Delete: { Selection selectionToDelete = m_part->caret(); #ifdef DEBUG_COMMANDS - // qDebug() << "========== KEY_DELETE ==========" << endl; + // qDebug() << "========== KEY_DELETE =========="; #endif if (selectionToDelete.state() == Selection::CARET) { Position pos(selectionToDelete.start()); #ifdef DEBUG_COMMANDS - // qDebug() << "pos.inLastEditableInRootEditableElement " << pos.inLastEditableInRootEditableElement() << " pos.offset " << pos.offset() << " pos.max " << pos.node()->caretMaxRenderedOffset() << endl; + // qDebug() << "pos.inLastEditableInRootEditableElement " << pos.inLastEditableInRootEditableElement() << " pos.offset " << pos.offset() << " pos.max " << pos.node()->caretMaxRenderedOffset(); #endif if (pos.nextCharacterPosition() == pos) { // we're at the end of a root editable block...do nothing #ifdef DEBUG_COMMANDS - // qDebug() << "no delete!!!!!!!!!!" << endl; + // qDebug() << "no delete!!!!!!!!!!"; #endif break; } m_part->d->editor_context.m_selection = Selection(pos, pos.nextCharacterPosition()); } // fall through } case Qt::Key_Backspace: TypingCommandImpl::deleteKeyPressed0(m_part->xmlDocImpl()); handled = true; break; case Qt::Key_Return: case Qt::Key_Enter: // if (shift) TypingCommandImpl::insertNewline0(m_part->xmlDocImpl()); // else // TypingCommand::insertParagraph(m_part->xmlDocImpl()); handled = true; break; case Qt::Key_Escape: case Qt::Key_Insert: // FIXME implement me handled = true; break; default: // handle_input: if (m_part->caret().state() != Selection::CARET) { // We didn't get a chance to grab the caret, likely because // a script messed with contentEditable in the middle of events // acquire it now if there isn't a selection // qDebug() << "Editable node w/o caret!"; DOM::NodeImpl *focus = m_part->xmlDocImpl()->focusNode(); if (m_part->caret().state() == Selection::NONE) { if (focus) { m_part->setCaret(Position(focus, focus->caretMinOffset())); } else { break; } } } if (!_ke->text().isEmpty()) { TypingCommandImpl::insertText0(m_part->xmlDocImpl(), _ke->text()); handled = true; } } //if (handled) { // ### check when to emit it // m_part->emitSelectionChanged(); //} return handled; } diff --git a/src/editing/htmlediting_impl.cpp b/src/editing/htmlediting_impl.cpp index 3536f8e..7b56dd9 100644 --- a/src/editing/htmlediting_impl.cpp +++ b/src/editing/htmlediting_impl.cpp @@ -1,3108 +1,3108 @@ /* * Copyright (C) 2004 Apple Computer, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "htmlediting_impl.h" #include "editor.h" #include "css/cssproperties.h" #include "css/css_valueimpl.h" #include "dom/css_value.h" #include "html/html_elementimpl.h" #include "html/html_imageimpl.h" #include "rendering/render_object.h" #include "rendering/render_style.h" #include "rendering/render_text.h" #include "xml/dom_docimpl.h" #include "xml/dom_elementimpl.h" #include "xml/dom_position.h" #include "xml/dom_positioniterator.h" #include "xml/dom_nodeimpl.h" #include "xml/dom_selection.h" #include "xml/dom_stringimpl.h" #include "xml/dom_textimpl.h" #include "xml/dom2_rangeimpl.h" #include "xml/dom2_viewsimpl.h" #include "khtml_part.h" #include "khtmlview.h" #include #include #include using DOM::AttrImpl; using DOM::CSSPrimitiveValue; using DOM::CSSPrimitiveValueImpl; using DOM::CSSProperty; using DOM::CSSStyleDeclarationImpl; using DOM::CSSValueImpl; using DOM::DocumentFragmentImpl; using DOM::DocumentImpl; using DOM::DOMString; using DOM::DOMStringImpl; using DOM::EditingTextImpl; using DOM::PositionIterator; using DOM::ElementImpl; using DOM::HTMLElementImpl; using DOM::HTMLImageElementImpl; using DOM::NamedAttrMapImpl; using DOM::Node; using DOM::NodeImpl; using DOM::NodeListImpl; using DOM::Position; using DOM::Range; using DOM::RangeImpl; using DOM::Selection; using DOM::TextImpl; using DOM::TreeWalkerImpl; using DOM::Editor; #define DEBUG_COMMANDS 0 namespace khtml { static inline bool isNBSP(const QChar &c) { return c == QChar(0xa0); } static inline bool isWS(const QChar &c) { return c.isSpace() && c != QChar(0xa0); } static inline bool isWS(const DOMString &text) { if (text.length() != 1) { return false; } return isWS(text[0]); } static inline bool isWS(const Position &pos) { if (!pos.node()) { return false; } if (!pos.node()->isTextNode()) { return false; } const DOMString &string = static_cast(pos.node())->data(); return isWS(string[pos.offset()]); } static bool shouldPruneNode(NodeImpl *node) { if (!node) { return false; } RenderObject *renderer = node->renderer(); if (!renderer) { return true; } if (node->hasChildNodes()) { return false; } if (node->rootEditableElement() == node) { return false; } if (renderer->isBR() || renderer->isReplaced()) { return false; } if (node->isTextNode()) { TextImpl *text = static_cast(node); if (text->length() == 0) { return true; } return false; } if (!node->isHTMLElement()/* && !node->isXMLElementNode()*/) { return false; } if (node->id() == ID_BODY) { return false; } if (!node->isContentEditable()) { return false; } return true; } static Position leadingWhitespacePosition(const Position &pos) { assert(pos.notEmpty()); Selection selection(pos); Position prev = pos.previousCharacterPosition(); if (prev != pos && prev.node()->inSameContainingBlockFlowElement(pos.node()) && prev.node()->isTextNode()) { DOMString string = static_cast(prev.node())->data(); if (isWS(string[prev.offset()])) { return prev; } } return Position(); } static Position trailingWhitespacePosition(const Position &pos) { assert(pos.notEmpty()); if (pos.node()->isTextNode()) { TextImpl *textNode = static_cast(pos.node()); if (pos.offset() >= (long)textNode->length()) { Position next = pos.nextCharacterPosition(); if (next != pos && next.node()->inSameContainingBlockFlowElement(pos.node()) && next.node()->isTextNode()) { DOMString string = static_cast(next.node())->data(); if (isWS(string[0])) { return next; } } } else { DOMString string = static_cast(pos.node())->data(); if (isWS(string[pos.offset()])) { return pos; } } } return Position(); } static bool textNodesAreJoinable(TextImpl *text1, TextImpl *text2) { assert(text1); assert(text2); return (text1->nextSibling() == text2); } static DOMString &nonBreakingSpaceString() { static DOMString nonBreakingSpaceString = QString(QChar(0xa0)); return nonBreakingSpaceString; } static DOMString &styleSpanClassString() { static DOMString styleSpanClassString = "khtml-style-span"; return styleSpanClassString; } //------------------------------------------------------------------------------------------ // EditCommandImpl EditCommandImpl::EditCommandImpl(DocumentImpl *document) : SharedCommandImpl(), m_document(document), m_state(NotApplied), m_parent(nullptr) { assert(m_document); assert(m_document->part()); m_document->ref(); m_startingSelection = m_document->part()->caret(); m_endingSelection = m_startingSelection; } EditCommandImpl::~EditCommandImpl() { m_document->deref(); } void EditCommandImpl::apply() { assert(m_document); assert(m_document->part()); assert(state() == NotApplied); doApply(); m_state = Applied; if (!isCompositeStep()) { m_document->part()->editor()->appliedEditing(this); } } void EditCommandImpl::unapply() { assert(m_document); assert(m_document->part()); assert(state() == Applied); doUnapply(); m_state = NotApplied; if (!isCompositeStep()) { m_document->part()->editor()->unappliedEditing(this); } } void EditCommandImpl::reapply() { assert(m_document); assert(m_document->part()); assert(state() == NotApplied); doReapply(); m_state = Applied; if (!isCompositeStep()) { m_document->part()->editor()->reappliedEditing(this); } } void EditCommandImpl::doReapply() { doApply(); } void EditCommandImpl::setStartingSelection(const Selection &s) { m_startingSelection = s; EditCommandImpl *cmd = parent(); while (cmd) { cmd->m_startingSelection = s; cmd = cmd->parent(); } } void EditCommandImpl::setEndingSelection(const Selection &s) { m_endingSelection = s; EditCommandImpl *cmd = parent(); while (cmd) { cmd->m_endingSelection = s; cmd = cmd->parent(); } } EditCommandImpl *EditCommandImpl::parent() const { return m_parent.get(); } void EditCommandImpl::setParent(EditCommandImpl *cmd) { m_parent = cmd; } //------------------------------------------------------------------------------------------ // CompositeEditCommandImpl CompositeEditCommandImpl::CompositeEditCommandImpl(DocumentImpl *document) : EditCommandImpl(document) { } CompositeEditCommandImpl::~CompositeEditCommandImpl() { } void CompositeEditCommandImpl::doUnapply() { if (m_cmds.count() == 0) { return; } for (int i = m_cmds.count() - 1; i >= 0; --i) { m_cmds[i]->unapply(); } setState(NotApplied); } void CompositeEditCommandImpl::doReapply() { if (m_cmds.count() == 0) { return; } QMutableListIterator > it(m_cmds); while (it.hasNext()) { it.next()->reapply(); } setState(Applied); } // // sugary-sweet convenience functions to help create and apply edit commands in composite commands // void CompositeEditCommandImpl::applyCommandToComposite(PassRefPtr cmd) { cmd->setStartingSelection(endingSelection());//###? cmd->setEndingSelection(endingSelection()); cmd->setParent(this); cmd->apply(); m_cmds.append(cmd); } void CompositeEditCommandImpl::insertNodeBefore(NodeImpl *insertChild, NodeImpl *refChild) { RefPtr cmd = new InsertNodeBeforeCommandImpl(document(), insertChild, refChild); applyCommandToComposite(cmd); } void CompositeEditCommandImpl::insertNodeAfter(NodeImpl *insertChild, NodeImpl *refChild) { if (refChild->parentNode()->lastChild() == refChild) { appendNode(refChild->parentNode(), insertChild); } else { assert(refChild->nextSibling()); insertNodeBefore(insertChild, refChild->nextSibling()); } } void CompositeEditCommandImpl::insertNodeAt(NodeImpl *insertChild, NodeImpl *refChild, long offset) { if (refChild->hasChildNodes() || (refChild->renderer() && refChild->renderer()->isBlockFlow())) { NodeImpl *child = refChild->firstChild(); for (long i = 0; child && i < offset; i++) { child = child->nextSibling(); } if (child) { insertNodeBefore(insertChild, child); } else { appendNode(refChild, insertChild); } } else if (refChild->caretMinOffset() >= offset) { insertNodeBefore(insertChild, refChild); } else if (refChild->isTextNode() && refChild->caretMaxOffset() > offset) { splitTextNode(static_cast(refChild), offset); insertNodeBefore(insertChild, refChild); } else { insertNodeAfter(insertChild, refChild); } } void CompositeEditCommandImpl::appendNode(NodeImpl *parent, NodeImpl *appendChild) { RefPtr cmd = new AppendNodeCommandImpl(document(), parent, appendChild); applyCommandToComposite(cmd); } void CompositeEditCommandImpl::removeNode(NodeImpl *removeChild) { RefPtr cmd = new RemoveNodeCommandImpl(document(), removeChild); applyCommandToComposite(cmd); } void CompositeEditCommandImpl::removeNodeAndPrune(NodeImpl *pruneNode, NodeImpl *stopNode) { RefPtr cmd = new RemoveNodeAndPruneCommandImpl(document(), pruneNode, stopNode); applyCommandToComposite(cmd); } void CompositeEditCommandImpl::removeNodePreservingChildren(NodeImpl *removeChild) { RefPtr cmd = new RemoveNodePreservingChildrenCommandImpl(document(), removeChild); applyCommandToComposite(cmd); } void CompositeEditCommandImpl::splitTextNode(TextImpl *text, long offset) { RefPtr cmd = new SplitTextNodeCommandImpl(document(), text, offset); applyCommandToComposite(cmd); } void CompositeEditCommandImpl::joinTextNodes(TextImpl *text1, TextImpl *text2) { RefPtr cmd = new JoinTextNodesCommandImpl(document(), text1, text2); applyCommandToComposite(cmd); } void CompositeEditCommandImpl::inputText(const DOMString &text) { RefPtr cmd = new InputTextCommandImpl(document()); applyCommandToComposite(cmd); cmd->input(text); } void CompositeEditCommandImpl::insertText(TextImpl *node, long offset, const DOMString &text) { RefPtr cmd = new InsertTextCommandImpl(document(), node, offset, text); applyCommandToComposite(cmd); } void CompositeEditCommandImpl::deleteText(TextImpl *node, long offset, long count) { RefPtr cmd = new DeleteTextCommandImpl(document(), node, offset, count); applyCommandToComposite(cmd); } void CompositeEditCommandImpl::replaceText(TextImpl *node, long offset, long count, const DOMString &replacementText) { RefPtr deleteCommand = new DeleteTextCommandImpl(document(), node, offset, count); applyCommandToComposite(deleteCommand); RefPtr insertCommand = new InsertTextCommandImpl(document(), node, offset, replacementText); applyCommandToComposite(insertCommand); } void CompositeEditCommandImpl::deleteSelection() { if (endingSelection().state() == Selection::RANGE) { RefPtr cmd = new DeleteSelectionCommandImpl(document()); applyCommandToComposite(cmd); } } void CompositeEditCommandImpl::deleteSelection(const Selection &selection) { if (selection.state() == Selection::RANGE) { RefPtr cmd = new DeleteSelectionCommandImpl(document(), selection); applyCommandToComposite(cmd); } } void CompositeEditCommandImpl::deleteCollapsibleWhitespace() { RefPtr cmd = new DeleteCollapsibleWhitespaceCommandImpl(document()); applyCommandToComposite(cmd); } void CompositeEditCommandImpl::deleteCollapsibleWhitespace(const Selection &selection) { RefPtr cmd = new DeleteCollapsibleWhitespaceCommandImpl(document(), selection); applyCommandToComposite(cmd); } void CompositeEditCommandImpl::removeCSSProperty(CSSStyleDeclarationImpl *decl, int property) { RefPtr cmd = new RemoveCSSPropertyCommandImpl(document(), decl, property); applyCommandToComposite(cmd); } void CompositeEditCommandImpl::removeNodeAttribute(ElementImpl *element, int attribute) { RefPtr cmd = new RemoveNodeAttributeCommandImpl(document(), element, attribute); applyCommandToComposite(cmd); } void CompositeEditCommandImpl::setNodeAttribute(ElementImpl *element, int attribute, const DOMString &value) { RefPtr cmd = new SetNodeAttributeCommandImpl(document(), element, attribute, value); applyCommandToComposite(cmd); } ElementImpl *CompositeEditCommandImpl::createTypingStyleElement() const { ElementImpl *styleElement = document()->createHTMLElement("SPAN"); styleElement->setAttribute(ATTR_STYLE, document()->part()->editor()->typingStyle()->cssText().implementation()); styleElement->setAttribute(ATTR_CLASS, styleSpanClassString()); return styleElement; } //========================================================================================== // Concrete commands //------------------------------------------------------------------------------------------ // AppendNodeCommandImpl AppendNodeCommandImpl::AppendNodeCommandImpl(DocumentImpl *document, NodeImpl *parentNode, NodeImpl *appendChild) : EditCommandImpl(document), m_parentNode(parentNode), m_appendChild(appendChild) { assert(m_parentNode); m_parentNode->ref(); assert(m_appendChild); m_appendChild->ref(); } AppendNodeCommandImpl::~AppendNodeCommandImpl() { if (m_parentNode) { m_parentNode->deref(); } if (m_appendChild) { m_appendChild->deref(); } } void AppendNodeCommandImpl::doApply() { assert(m_parentNode); assert(m_appendChild); int exceptionCode = 0; m_parentNode->appendChild(m_appendChild, exceptionCode); assert(exceptionCode == 0); } void AppendNodeCommandImpl::doUnapply() { assert(m_parentNode); assert(m_appendChild); assert(state() == Applied); int exceptionCode = 0; m_parentNode->removeChild(m_appendChild, exceptionCode); assert(exceptionCode == 0); } //------------------------------------------------------------------------------------------ // ApplyStyleCommandImpl ApplyStyleCommandImpl::ApplyStyleCommandImpl(DocumentImpl *document, CSSStyleDeclarationImpl *style) : CompositeEditCommandImpl(document), m_style(style) { assert(m_style); m_style->ref(); } ApplyStyleCommandImpl::~ApplyStyleCommandImpl() { assert(m_style); m_style->deref(); } static bool isBlockLevelStyle(const CSSStyleDeclarationImpl *style) { QListIterator it(*(style->values())); while (it.hasNext()) { CSSProperty *property = it.next(); switch (property->id()) { case CSS_PROP_TEXT_ALIGN: return true; /*case CSS_PROP_FONT_WEIGHT: if (strcasecmp(property->value()->cssText(), "bold") == 0) styleChange.applyBold = true; else styleChange.cssStyle += property->cssText(); break; case CSS_PROP_FONT_STYLE: { DOMString cssText(property->value()->cssText()); if (strcasecmp(cssText, "italic") == 0 || strcasecmp(cssText, "oblique") == 0) styleChange.applyItalic = true; else styleChange.cssStyle += property->cssText(); } break; default: styleChange.cssStyle += property->cssText(); break;*/ } } return false; } static void applyStyleChangeOnTheNode(ElementImpl *element, CSSStyleDeclarationImpl *style) { QScopedPointer computedStyle( element->document()->defaultView()->getComputedStyle(element, nullptr)); assert(!computedStyle.isNull()); #ifdef DEBUG_COMMANDS - qDebug() << "[change style]" << element << endl; + qDebug() << "[change style]" << element; #endif QListIterator it(*(style->values())); while (it.hasNext()) { CSSProperty *property = it.next(); CSSValueImpl *computedValue = computedStyle->getPropertyCSSValue(property->id()); DOMString newValue = property->value()->cssText(); #ifdef DEBUG_COMMANDS - qDebug() << "[new value]:" << property->cssText() << endl; - qDebug() << "[computedValue]:" << computedValue->cssText() << endl; + qDebug() << "[new value]:" << property->cssText(); + qDebug() << "[computedValue]:" << computedValue->cssText(); #endif if (strcasecmp(computedValue->cssText(), newValue)) { // we can do better and avoid parsing property element->getInlineStyleDecls()->setProperty(property->id(), newValue); } } } void ApplyStyleCommandImpl::doApply() { if (endingSelection().state() != Selection::RANGE) { return; } // adjust to the positions we want to use for applying style Position start(endingSelection().start().equivalentDownstreamPosition().equivalentRangeCompliantPosition()); Position end(endingSelection().end().equivalentUpstreamPosition()); #ifdef DEBUG_COMMANDS - qDebug() << "[APPLY STYLE]" << start << end << endl; + qDebug() << "[APPLY STYLE]" << start << end; printEnclosingBlockTree(start.node()->enclosingBlockFlowElement()); #endif if (isBlockLevelStyle(m_style)) { #ifdef DEBUG_COMMANDS - qDebug() << "[APPLY BLOCK LEVEL STYLE]" << endl; + qDebug() << "[APPLY BLOCK LEVEL STYLE]"; #endif ElementImpl *startBlock = start.node()->enclosingBlockFlowElement(); ElementImpl *endBlock = end.node()->enclosingBlockFlowElement(); #ifdef DEBUG_COMMANDS - qDebug() << startBlock << startBlock->nodeName() << endl; + qDebug() << startBlock << startBlock->nodeName(); #endif if (startBlock == endBlock && startBlock == start.node()->rootEditableElement()) { ElementImpl *block = document()->createHTMLElement("DIV"); #ifdef DEBUG_COMMANDS - qDebug() << "[Create DIV with Style:]" << m_style->cssText() << endl; + qDebug() << "[Create DIV with Style:]" << m_style->cssText(); #endif block->setAttribute(ATTR_STYLE, m_style->cssText()); for (NodeImpl *node = startBlock->firstChild(); node; node = startBlock->firstChild()) { #ifdef DEBUG_COMMANDS - qDebug() << "[reparent node]" << node << node->nodeName() << endl; + qDebug() << "[reparent node]" << node << node->nodeName(); #endif removeNode(node); appendNode(block, node); } appendNode(startBlock, block); } else if (startBlock == endBlock) { // StyleChange styleChange = computeStyleChange(Position(startBlock, 0), m_style); - //qDebug() << "[Modify block with style change:]" << styleChange.cssStyle << endl; + //qDebug() << "[Modify block with style change:]" << styleChange.cssStyle; applyStyleChangeOnTheNode(startBlock, m_style); // startBlock->setAttribute(ATTR_STYLE, styleChange.cssStyle); } return; } // remove style from the selection removeStyle(start, end); bool splitStart = splitTextAtStartIfNeeded(start, end); if (splitStart) { start = endingSelection().start(); end = endingSelection().end(); } splitTextAtEndIfNeeded(start, end); start = endingSelection().start(); end = endingSelection().end(); #ifdef DEBUG_COMMANDS - qDebug() << "[start;end]" << start << end << endl; + qDebug() << "[start;end]" << start << end; #endif if (start.node() == end.node()) { // simple case...start and end are the same node applyStyleIfNeeded(start.node(), end.node()); } else { NodeImpl *node = start.node(); while (1) { if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) { NodeImpl *runStart = node; while (1) { if (runStart->parentNode() != node->parentNode() || node->isHTMLElement() || node == end.node() || (node->renderer() && !node->renderer()->isInline())) { applyStyleIfNeeded(runStart, node); break; } node = node->traverseNextNode(); } } if (node == end.node()) { break; } node = node->traverseNextNode(); } } } //------------------------------------------------------------------------------------------ // ApplyStyleCommandImpl: style-removal helpers bool ApplyStyleCommandImpl::isHTMLStyleNode(HTMLElementImpl *elem) { QListIterator it(*(style()->values())); while (it.hasNext()) { CSSProperty *property = it.next(); switch (property->id()) { case CSS_PROP_FONT_WEIGHT: if (elem->id() == ID_B) { return true; } break; case CSS_PROP_FONT_STYLE: if (elem->id() == ID_I) { return true; } break; } } return false; } void ApplyStyleCommandImpl::removeHTMLStyleNode(HTMLElementImpl *elem) { // This node can be removed. // EDIT FIXME: This does not handle the case where the node // has attributes. But how often do people add attributes to tags? // Not so often I think. assert(elem); removeNodePreservingChildren(elem); } void ApplyStyleCommandImpl::removeCSSStyle(HTMLElementImpl *elem) { assert(elem); CSSStyleDeclarationImpl *decl = elem->inlineStyleDecls(); if (!decl) { return; } QListIterator it(*(style()->values())); while (it.hasNext()) { CSSProperty *property = it.next(); if (decl->getPropertyCSSValue(property->id())) { removeCSSProperty(decl, property->id()); } } if (elem->id() == ID_SPAN) { // Check to see if the span is one we added to apply style. // If it is, and there are no more attributes on the span other than our // class marker, remove the span. NamedAttrMapImpl *map = elem->attributes(); if (map && (map->length() == 1 || (map->length() == 2 && elem->getAttribute(ATTR_STYLE).isEmpty())) && elem->getAttribute(ATTR_CLASS) == styleSpanClassString()) { removeNodePreservingChildren(elem); } } } void ApplyStyleCommandImpl::removeStyle(const Position &start, const Position &end) { NodeImpl *node = start.node(); while (1) { NodeImpl *next = node->traverseNextNode(); if (node->isHTMLElement() && nodeFullySelected(node)) { HTMLElementImpl *elem = static_cast(node); if (isHTMLStyleNode(elem)) { removeHTMLStyleNode(elem); } else { removeCSSStyle(elem); } } if (node == end.node()) { break; } node = next; } } bool ApplyStyleCommandImpl::nodeFullySelected(const NodeImpl *node) const { assert(node); Position end(endingSelection().end().equivalentUpstreamPosition()); if (node == end.node()) { return end.offset() >= node->caretMaxOffset(); } for (NodeImpl *child = node->lastChild(); child; child = child->lastChild()) { if (child == end.node()) { return end.offset() >= child->caretMaxOffset(); } } return node == end.node() || !node->isAncestor(end.node()); } //------------------------------------------------------------------------------------------ // ApplyStyleCommandImpl: style-application helpers bool ApplyStyleCommandImpl::splitTextAtStartIfNeeded(const Position &start, const Position &end) { if (start.node()->isTextNode() && start.offset() > start.node()->caretMinOffset() && start.offset() < start.node()->caretMaxOffset()) { #ifdef DEBUG_COMMANDS - qDebug() << "[split start]" << start.offset() << start.node()->caretMinOffset() << start.node()->caretMaxOffset() << endl; + qDebug() << "[split start]" << start.offset() << start.node()->caretMinOffset() << start.node()->caretMaxOffset(); #endif long endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0; TextImpl *text = static_cast(start.node()); RefPtr cmd = new SplitTextNodeCommandImpl(document(), text, start.offset()); applyCommandToComposite(cmd); setEndingSelection(Selection(Position(start.node(), 0), Position(end.node(), end.offset() - endOffsetAdjustment))); return true; } return false; } NodeImpl *ApplyStyleCommandImpl::splitTextAtEndIfNeeded(const Position &start, const Position &end) { if (end.node()->isTextNode() && end.offset() > end.node()->caretMinOffset() && end.offset() < end.node()->caretMaxOffset()) { #ifdef DEBUG_COMMANDS - qDebug() << "[split end]" << end.offset() << end.node()->caretMinOffset() << end.node()->caretMaxOffset() << endl; + qDebug() << "[split end]" << end.offset() << end.node()->caretMinOffset() << end.node()->caretMaxOffset(); #endif TextImpl *text = static_cast(end.node()); RefPtr cmd = new SplitTextNodeCommandImpl(document(), text, end.offset()); applyCommandToComposite(cmd); NodeImpl *startNode = start.node() == end.node() ? cmd->node()->previousSibling() : start.node(); assert(startNode); setEndingSelection(Selection(Position(startNode, start.offset()), Position(cmd->node()->previousSibling(), cmd->node()->previousSibling()->caretMaxOffset()))); return cmd->node()->previousSibling(); } return end.node(); } void ApplyStyleCommandImpl::surroundNodeRangeWithElement(NodeImpl *startNode, NodeImpl *endNode, ElementImpl *element) { assert(startNode); assert(endNode); assert(element); NodeImpl *node = startNode; while (1) { NodeImpl *next = node->traverseNextNode(); if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) { removeNode(node); appendNode(element, node); } if (node == endNode) { break; } node = next; } } static bool /*ApplyStyleCommandImpl::*/checkIfNewStylingNeeded(ElementImpl *element, CSSStyleDeclarationImpl *style) { QScopedPointer computedStyle( element->document()->defaultView()->getComputedStyle(element, nullptr)); assert(!computedStyle.isNull()); #ifdef DEBUG_COMMANDS - qDebug() << "[check styling]" << element << endl; + qDebug() << "[check styling]" << element; #endif QListIterator it(*(style->values())); while (it.hasNext()) { CSSProperty *property = it.next(); CSSValueImpl *computedValue = computedStyle->getPropertyCSSValue(property->id()); DOMString newValue = property->value()->cssText(); #ifdef DEBUG_COMMANDS - qDebug() << "[new value]:" << property->cssText() << endl; - qDebug() << "[computedValue]:" << computedValue->cssText() << endl; + qDebug() << "[new value]:" << property->cssText(); + qDebug() << "[computedValue]:" << computedValue->cssText(); #endif if (strcasecmp(computedValue->cssText(), newValue)) { return true; } } return false; } void ApplyStyleCommandImpl::applyStyleIfNeeded(DOM::NodeImpl *startNode, DOM::NodeImpl *endNode) { ElementImpl *parent = Position(startNode, 0).element(); if (!checkIfNewStylingNeeded(parent, style())) { return; } ElementImpl *styleElement = nullptr; if (parent->id() == ID_SPAN && parent->firstChild() == startNode && parent->lastChild() == endNode) { styleElement = parent; } else { styleElement = document()->createHTMLElement("SPAN"); styleElement->setAttribute(ATTR_CLASS, styleSpanClassString()); insertNodeBefore(styleElement, startNode); surroundNodeRangeWithElement(startNode, endNode, styleElement); } applyStyleChangeOnTheNode(styleElement, style()); } bool ApplyStyleCommandImpl::currentlyHasStyle(const Position &pos, const CSSProperty *property) const { assert(pos.notEmpty()); - qDebug() << pos << endl; + qDebug() << pos; CSSStyleDeclarationImpl *decl = document()->defaultView()->getComputedStyle(pos.element(), nullptr); assert(decl); CSSValueImpl *value = decl->getPropertyCSSValue(property->id()); return strcasecmp(value->cssText(), property->value()->cssText()) == 0; } ApplyStyleCommandImpl::StyleChange ApplyStyleCommandImpl::computeStyleChange(const Position &insertionPoint, CSSStyleDeclarationImpl *style) { assert(insertionPoint.notEmpty()); assert(style); StyleChange styleChange; QListIterator it(*(style->values())); while (it.hasNext()) { CSSProperty *property = it.next(); #ifdef DEBUG_COMMANDS - qDebug() << "[CSS property]:" << property->cssText() << endl; + qDebug() << "[CSS property]:" << property->cssText(); #endif if (!currentlyHasStyle(insertionPoint, property)) { #ifdef DEBUG_COMMANDS - qDebug() << "[Add to style change]" << endl; + qDebug() << "[Add to style change]"; #endif switch (property->id()) { case CSS_PROP_FONT_WEIGHT: if (strcasecmp(property->value()->cssText(), "bold") == 0) { styleChange.applyBold = true; } else { styleChange.cssStyle += property->cssText(); } break; case CSS_PROP_FONT_STYLE: { DOMString cssText(property->value()->cssText()); if (strcasecmp(cssText, "italic") == 0 || strcasecmp(cssText, "oblique") == 0) { styleChange.applyItalic = true; } else { styleChange.cssStyle += property->cssText(); } } break; default: styleChange.cssStyle += property->cssText(); break; } } } return styleChange; } Position ApplyStyleCommandImpl::positionInsertionPoint(Position pos) { if (pos.node()->isTextNode() && (pos.offset() > 0 && pos.offset() < pos.node()->maxOffset())) { RefPtr split = new SplitTextNodeCommandImpl(document(), static_cast(pos.node()), pos.offset()); split->apply(); pos = Position(split->node(), 0); } #if 0 // EDIT FIXME: If modified to work with the internals of applying style, // this code can work to optimize cases where a style change is taking place on // a boundary between nodes where one of the nodes has the desired style. In other // words, it is possible for content to be merged into existing nodes rather than adding // additional markup. if (currentlyHasStyle(pos)) { return pos; } // try next node if (pos.offset() >= pos.node()->caretMaxOffset()) { NodeImpl *nextNode = pos.node()->traverseNextNode(); if (nextNode) { Position next = Position(nextNode, 0); if (currentlyHasStyle(next)) { return next; } } } // try previous node if (pos.offset() <= pos.node()->caretMinOffset()) { NodeImpl *prevNode = pos.node()->traversePreviousNode(); if (prevNode) { Position prev = Position(prevNode, prevNode->maxOffset()); if (currentlyHasStyle(prev)) { return prev; } } } #endif return pos; } //------------------------------------------------------------------------------------------ // DeleteCollapsibleWhitespaceCommandImpl DeleteCollapsibleWhitespaceCommandImpl::DeleteCollapsibleWhitespaceCommandImpl(DocumentImpl *document) : CompositeEditCommandImpl(document), m_charactersDeleted(0), m_hasSelectionToCollapse(false) { } DeleteCollapsibleWhitespaceCommandImpl::DeleteCollapsibleWhitespaceCommandImpl(DocumentImpl *document, const Selection &selection) : CompositeEditCommandImpl(document), m_charactersDeleted(0), m_selectionToCollapse(selection), m_hasSelectionToCollapse(true) { } DeleteCollapsibleWhitespaceCommandImpl::~DeleteCollapsibleWhitespaceCommandImpl() { } static bool shouldDeleteUpstreamPosition(const Position &pos) { if (!pos.node()->isTextNode()) { return false; } RenderObject *renderer = pos.node()->renderer(); if (!renderer) { return true; } TextImpl *textNode = static_cast(pos.node()); if (pos.offset() >= (long)textNode->length()) { return false; } if (pos.isLastRenderedPositionInEditableBlock()) { return false; } if (pos.isFirstRenderedPositionOnLine() || pos.isLastRenderedPositionOnLine()) { return false; } return false; // TODO we need to match DOM - Rendered offset first // RenderText *textRenderer = static_cast(renderer); // for (InlineTextBox *box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) { // if (pos.offset() < box->m_start) { // return true; // } // if (pos.offset() >= box->m_start && pos.offset() < box->m_start + box->m_len) // return false; // } // // return true; } Position DeleteCollapsibleWhitespaceCommandImpl::deleteWhitespace(const Position &pos) { Position upstream = pos.equivalentUpstreamPosition(); Position downstream = pos.equivalentDownstreamPosition(); #ifdef DEBUG_COMMANDS - qDebug() << "[pos]" << pos << endl; - qDebug() << "[upstream:downstream]" << upstream << downstream << endl; + qDebug() << "[pos]" << pos; + qDebug() << "[upstream:downstream]" << upstream << downstream; printEnclosingBlockTree(pos.node()); #endif bool del = shouldDeleteUpstreamPosition(upstream); #ifdef DEBUG_COMMANDS - qDebug() << "[delete upstream]" << del << endl; + qDebug() << "[delete upstream]" << del; #endif if (upstream == downstream) { return upstream; } #ifdef DEBUG_COMMANDS PositionIterator iter(upstream); - qDebug() << "[before print]" << endl; + qDebug() << "[before print]"; for (iter.next(); iter.current() != downstream; iter.next()) { - qDebug() << "[iterate]" << iter.current() << endl; + qDebug() << "[iterate]" << iter.current(); } - qDebug() << "[after print]" << endl; + qDebug() << "[after print]"; #endif PositionIterator it(upstream); Position deleteStart = upstream; if (!del) { deleteStart = it.peekNext(); if (deleteStart == downstream) { return upstream; } } Position endingPosition = upstream; while (it.current() != downstream) { Position next = it.peekNext(); #ifdef DEBUG_COMMANDS - qDebug() << "[iterate and delete]" << next << endl; + qDebug() << "[iterate and delete]" << next; #endif if (next.node() != deleteStart.node()) { // TODO assert(deleteStart.node()->isTextNode()); if (deleteStart.node()->isTextNode()) { TextImpl *textNode = static_cast(deleteStart.node()); unsigned long count = it.current().offset() - deleteStart.offset(); if (count == textNode->length()) { #ifdef DEBUG_COMMANDS qDebug() << " removeNodeAndPrune 1:" << textNode; #endif if (textNode == endingPosition.node()) { endingPosition = Position(next.node(), next.node()->caretMinOffset()); } removeNodeAndPrune(textNode); } else { #ifdef DEBUG_COMMANDS qDebug() << " deleteText 1:" << textNode << "t len:" << textNode->length() << "start:" << deleteStart.offset() << "del len:" << (it.current().offset() - deleteStart.offset()); #endif deleteText(textNode, deleteStart.offset(), count); } } else { #ifdef DEBUG_COMMANDS - qDebug() << "[not text node is not supported yet]" << endl; + qDebug() << "[not text node is not supported yet]"; #endif } deleteStart = next; } else if (next == downstream) { assert(deleteStart.node() == downstream.node()); assert(downstream.node()->isTextNode()); TextImpl *textNode = static_cast(deleteStart.node()); unsigned long count = downstream.offset() - deleteStart.offset(); assert(count <= textNode->length()); if (count == textNode->length()) { #ifdef DEBUG_COMMANDS qDebug() << " removeNodeAndPrune 2:" << textNode; #endif removeNodeAndPrune(textNode); } else { #ifdef DEBUG_COMMANDS qDebug() << " deleteText 2:" << textNode << "t len:" << textNode->length() << "start:" << deleteStart.offset() << "del len:" << count; #endif deleteText(textNode, deleteStart.offset(), count); m_charactersDeleted = count; endingPosition = Position(downstream.node(), downstream.offset() - m_charactersDeleted); } } it.setPosition(next); } return endingPosition; } void DeleteCollapsibleWhitespaceCommandImpl::doApply() { // If selection has not been set to a custom selection when the command was created, // use the current ending selection. if (!m_hasSelectionToCollapse) { m_selectionToCollapse = endingSelection(); } int state = m_selectionToCollapse.state(); if (state == Selection::CARET) { Position endPosition = deleteWhitespace(m_selectionToCollapse.start()); setEndingSelection(endPosition); #ifdef DEBUG_COMMANDS qDebug() << "-----------------------------------------------------"; #endif } else if (state == Selection::RANGE) { Position startPosition = deleteWhitespace(m_selectionToCollapse.start()); #ifdef DEBUG_COMMANDS qDebug() << "-----------------------------------------------------"; #endif Position endPosition = m_selectionToCollapse.end(); if (m_charactersDeleted > 0 && startPosition.node() == endPosition.node()) { #ifdef DEBUG_COMMANDS qDebug() << "adjust end position by" << m_charactersDeleted; #endif endPosition = Position(endPosition.node(), endPosition.offset() - m_charactersDeleted); } endPosition = deleteWhitespace(endPosition); setEndingSelection(Selection(startPosition, endPosition)); #ifdef DEBUG_COMMANDS qDebug() << "====================================================="; #endif } } //------------------------------------------------------------------------------------------ // DeleteSelectionCommandImpl DeleteSelectionCommandImpl::DeleteSelectionCommandImpl(DocumentImpl *document) : CompositeEditCommandImpl(document), m_hasSelectionToDelete(false) { } DeleteSelectionCommandImpl::DeleteSelectionCommandImpl(DocumentImpl *document, const Selection &selection) : CompositeEditCommandImpl(document), m_selectionToDelete(selection), m_hasSelectionToDelete(true) { } DeleteSelectionCommandImpl::~DeleteSelectionCommandImpl() { } void DeleteSelectionCommandImpl::joinTextNodesWithSameStyle() { Selection selection = endingSelection(); if (selection.state() != Selection::CARET) { return; } Position pos(selection.start()); if (!pos.node()->isTextNode()) { return; } TextImpl *textNode = static_cast(pos.node()); if (pos.offset() == 0) { PositionIterator it(pos); Position prev = it.previous(); if (prev == pos) { return; } if (prev.node()->isTextNode()) { TextImpl *prevTextNode = static_cast(prev.node()); if (textNodesAreJoinable(prevTextNode, textNode)) { joinTextNodes(prevTextNode, textNode); setEndingSelection(Position(textNode, prevTextNode->length())); #ifdef DEBUG_COMMANDS qDebug() << "joinTextNodesWithSameStyle [1]"; #endif } } } else if (pos.offset() == (long)textNode->length()) { PositionIterator it(pos); Position next = it.next(); if (next == pos) { return; } if (next.node()->isTextNode()) { TextImpl *nextTextNode = static_cast(next.node()); if (textNodesAreJoinable(textNode, nextTextNode)) { joinTextNodes(textNode, nextTextNode); setEndingSelection(Position(nextTextNode, pos.offset())); #ifdef DEBUG_COMMANDS qDebug() << "joinTextNodesWithSameStyle [2]"; #endif } } } } bool DeleteSelectionCommandImpl::containsOnlyWhitespace(const Position &start, const Position &end) { // Returns whether the range contains only whitespace characters. // This is inclusive of the start, but not of the end. PositionIterator it(start); while (!it.atEnd()) { if (!it.current().node()->isTextNode()) { return false; } const DOMString &text = static_cast(it.current().node())->data(); // EDIT FIXME: signed/unsigned mismatch if (text.length() > INT_MAX) { return false; } if (it.current().offset() < (int)text.length() && !isWS(text[it.current().offset()])) { return false; } it.next(); if (it.current() == end) { break; } } return true; } void DeleteSelectionCommandImpl::deleteContentInsideNode(NodeImpl *node, int startOffset, int endOffset) { #ifdef DEBUG_COMMANDS - qDebug() << "[Delete content inside node]" << node << startOffset << endOffset << endl; + qDebug() << "[Delete content inside node]" << node << startOffset << endOffset; #endif if (node->isTextNode()) { // check if nothing to delete if (startOffset == endOffset) { return; } // check if node is fully covered then remove node completely if (!startOffset && endOffset == node->maxOffset()) { removeNodeAndPrune(node); return; } // delete only substring deleteText(static_cast(node), startOffset, endOffset - startOffset); return; } #ifdef DEBUG_COMMANDS - qDebug() << "[non-text node] not supported" << endl; + qDebug() << "[non-text node] not supported"; #endif } void DeleteSelectionCommandImpl::deleteContentBeforeOffset(NodeImpl *node, int offset) { deleteContentInsideNode(node, 0, offset); } void DeleteSelectionCommandImpl::deleteContentAfterOffset(NodeImpl *node, int offset) { if (node->isTextNode()) { deleteContentInsideNode(node, offset, node->maxOffset()); } } void DeleteSelectionCommandImpl::doApply() { // If selection has not been set to a custom selection when the command was created, // use the current ending selection. if (!m_hasSelectionToDelete) { m_selectionToDelete = endingSelection(); } if (m_selectionToDelete.state() != Selection::RANGE) { return; } deleteCollapsibleWhitespace(m_selectionToDelete); Selection selection = endingSelection(); Position upstreamStart(selection.start().equivalentUpstreamPosition()); Position downstreamStart(selection.start().equivalentDownstreamPosition()); Position upstreamEnd(selection.end().equivalentUpstreamPosition()); Position downstreamEnd(selection.end().equivalentDownstreamPosition()); NodeImpl *startBlock = upstreamStart.node()->enclosingBlockFlowElement(); NodeImpl *endBlock = downstreamEnd.node()->enclosingBlockFlowElement(); #ifdef DEBUG_COMMANDS - qDebug() << "[Delete:Start]" << upstreamStart << downstreamStart << endl; - qDebug() << "[Delete:End]" << upstreamEnd << downstreamEnd << endl; + qDebug() << "[Delete:Start]" << upstreamStart << downstreamStart; + qDebug() << "[Delete:End]" << upstreamEnd << downstreamEnd; printEnclosingBlockTree(upstreamStart.node()); #endif if (startBlock != endBlock) { printEnclosingBlockTree(downstreamEnd.node()); } if (upstreamStart == downstreamEnd) // after collapsing whitespace, selection is empty...no work to do { return; } // remove all the nodes that are completely covered by the selection if (upstreamStart.node() != downstreamEnd.node()) { NodeImpl *node, *next; for (node = upstreamStart.node()->traverseNextNode(); node && node != downstreamEnd.node(); node = next) { #ifdef DEBUG_COMMANDS - qDebug() << "[traverse and delete]" << node << (node->renderer() && node->renderer()->isEditable()) << endl; + qDebug() << "[traverse and delete]" << node << (node->renderer() && node->renderer()->isEditable()); #endif next = node->traverseNextNode(); if (node->renderer() && node->renderer()->isEditable()) { removeNode(node); // removeAndPrune? } } } // if we have different blocks then merge content of the second into first one if (startBlock != endBlock && startBlock->parentNode() == endBlock->parentNode()) { NodeImpl *node = endBlock->firstChild(); while (node) { NodeImpl *moveNode = node; node = node->nextSibling(); removeNode(moveNode); appendNode(startBlock, moveNode); } } if (upstreamStart.node() == downstreamEnd.node()) { deleteContentInsideNode(upstreamEnd.node(), upstreamStart.offset(), downstreamEnd.offset()); } else { deleteContentAfterOffset(upstreamStart.node(), upstreamStart.offset()); deleteContentBeforeOffset(downstreamEnd.node(), downstreamEnd.offset()); } setEndingSelection(upstreamStart); #if 0 Position endingPosition; bool adjustEndingPositionDownstream = false; bool onlyWhitespace = containsOnlyWhitespace(upstreamStart, downstreamEnd); - qDebug() << "[OnlyWhitespace]" << onlyWhitespace << endl; + qDebug() << "[OnlyWhitespace]" << onlyWhitespace; bool startCompletelySelected = !onlyWhitespace && (downstreamStart.offset() <= downstreamStart.node()->caretMinOffset() && ((downstreamStart.node() != upstreamEnd.node()) || (upstreamEnd.offset() >= upstreamEnd.node()->caretMaxOffset()))); bool endCompletelySelected = !onlyWhitespace && (upstreamEnd.offset() >= upstreamEnd.node()->caretMaxOffset() && ((downstreamStart.node() != upstreamEnd.node()) || (downstreamStart.offset() <= downstreamStart.node()->caretMinOffset()))); - qDebug() << "[{start:end}CompletelySelected]" << startCompletelySelected << endCompletelySelected << endl; + qDebug() << "[{start:end}CompletelySelected]" << startCompletelySelected << endCompletelySelected; unsigned long startRenderedOffset = downstreamStart.renderedOffset(); bool startAtStartOfRootEditableElement = startRenderedOffset == 0 && downstreamStart.inFirstEditableInRootEditableElement(); bool startAtStartOfBlock = startAtStartOfRootEditableElement || (startRenderedOffset == 0 && downstreamStart.inFirstEditableInContainingEditableBlock()); bool endAtEndOfBlock = downstreamEnd.isLastRenderedPositionInEditableBlock(); - qDebug() << "[startAtStartOfRootEditableElement]" << startAtStartOfRootEditableElement << endl; - qDebug() << "[startAtStartOfBlock]" << startAtStartOfBlock << endl; - qDebug() << "[endAtEndOfBlock]" << endAtEndOfBlock << endl; + qDebug() << "[startAtStartOfRootEditableElement]" << startAtStartOfRootEditableElement; + qDebug() << "[startAtStartOfBlock]" << startAtStartOfBlock; + qDebug() << "[endAtEndOfBlock]" << endAtEndOfBlock; NodeImpl *startBlock = upstreamStart.node()->enclosingBlockFlowElement(); NodeImpl *endBlock = downstreamEnd.node()->enclosingBlockFlowElement(); bool startBlockEndBlockAreSiblings = startBlock->parentNode() == endBlock->parentNode(); - qDebug() << "[startBlockEndBlockAreSiblings]" << startBlockEndBlockAreSiblings << startBlock << endBlock << endl; + qDebug() << "[startBlockEndBlockAreSiblings]" << startBlockEndBlockAreSiblings << startBlock << endBlock; debugPosition("upstreamStart: ", upstreamStart); debugPosition("downstreamStart: ", downstreamStart); debugPosition("upstreamEnd: ", upstreamEnd); debugPosition("downstreamEnd: ", downstreamEnd); qDebug() << "start selected:" << (startCompletelySelected ? "YES" : "NO"); qDebug() << "at start block:" << (startAtStartOfBlock ? "YES" : "NO"); qDebug() << "at start root block:" << (startAtStartOfRootEditableElement ? "YES" : "NO"); qDebug() << "at end block:" << (endAtEndOfBlock ? "YES" : "NO"); qDebug() << "only whitespace:" << (onlyWhitespace ? "YES" : "NO"); // Determine where to put the caret after the deletion if (startAtStartOfBlock) { qDebug() << "ending position case 1"; endingPosition = Position(startBlock, 0); adjustEndingPositionDownstream = true; } else if (!startCompletelySelected) { qDebug() << "ending position case 2"; endingPosition = upstreamEnd; // FIXME ??????????? upstreamStart; if (upstreamStart.node()->id() == ID_BR && upstreamStart.offset() == 1) { adjustEndingPositionDownstream = true; } } else if (upstreamStart != downstreamStart) { qDebug() << "ending position case 3"; endingPosition = upstreamStart; if (upstreamStart.node()->id() == ID_BR && upstreamStart.offset() == 1) { adjustEndingPositionDownstream = true; } } // // Figure out the whitespace conversions to do // if ((startAtStartOfBlock && !endAtEndOfBlock) || (!startCompletelySelected && adjustEndingPositionDownstream)) { // convert trailing whitespace Position trailing = trailingWhitespacePosition(downstreamEnd.equivalentDownstreamPosition()); if (trailing.notEmpty()) { debugPosition("convertTrailingWhitespace: ", trailing); Position collapse = trailing.nextCharacterPosition(); if (collapse != trailing) { deleteCollapsibleWhitespace(collapse); } TextImpl *textNode = static_cast(trailing.node()); replaceText(textNode, trailing.offset(), 1, nonBreakingSpaceString()); } } else if (!startAtStartOfBlock && endAtEndOfBlock) { // convert leading whitespace Position leading = leadingWhitespacePosition(upstreamStart.equivalentUpstreamPosition()); if (leading.notEmpty()) { debugPosition("convertLeadingWhitespace: ", leading); TextImpl *textNode = static_cast(leading.node()); replaceText(textNode, leading.offset(), 1, nonBreakingSpaceString()); } } else if (!startAtStartOfBlock && !endAtEndOfBlock) { // convert contiguous whitespace Position leading = leadingWhitespacePosition(upstreamStart.equivalentUpstreamPosition()); Position trailing = trailingWhitespacePosition(downstreamEnd.equivalentDownstreamPosition()); if (leading.notEmpty() && trailing.notEmpty()) { debugPosition("convertLeadingWhitespace [contiguous]: ", leading); TextImpl *textNode = static_cast(leading.node()); replaceText(textNode, leading.offset(), 1, nonBreakingSpaceString()); } } // // Do the delete // NodeImpl *n = downstreamStart.node()->traverseNextNode(); - qDebug() << "[n]" << n << endl; + qDebug() << "[n]" << n; // work on start node if (startCompletelySelected) { qDebug() << "start node delete case 1"; removeNodeAndPrune(downstreamStart.node(), startBlock); } else if (onlyWhitespace) { // Selection only contains whitespace. This is really a special-case to // handle significant whitespace that is collapsed at the end of a line, // but also handles deleting a space in mid-line. qDebug() << "start node delete case 2"; assert(upstreamStart.node()->isTextNode()); TextImpl *text = static_cast(upstreamStart.node()); int offset = upstreamStart.offset(); // EDIT FIXME: Signed/unsigned mismatch int length = text->length(); if (length == upstreamStart.offset()) { offset--; } // FIXME ??? deleteText(text, offset, 1); } else if (downstreamStart.node()->isTextNode()) { qDebug() << "start node delete case 3"; TextImpl *text = static_cast(downstreamStart.node()); int endOffset = text == upstreamEnd.node() ? upstreamEnd.offset() : text->length(); if (endOffset > downstreamStart.offset()) { deleteText(text, downstreamStart.offset(), endOffset - downstreamStart.offset()); } } else { // we have clipped the end of a non-text element // the offset must be 1 here. if it is, do nothing and move on. qDebug() << "start node delete case 4"; assert(downstreamStart.offset() == 1); } if (n && !onlyWhitespace && downstreamStart.node() != upstreamEnd.node()) { // work on intermediate nodes while (n && n != upstreamEnd.node()) { NodeImpl *d = n; n = n->traverseNextNode(); if (d->renderer() && d->renderer()->isEditable()) { removeNodeAndPrune(d, startBlock); } } if (!n) { return; } // work on end node assert(n == upstreamEnd.node()); if (endCompletelySelected) { removeNodeAndPrune(upstreamEnd.node(), startBlock); } else if (upstreamEnd.node()->isTextNode()) { if (upstreamEnd.offset() > 0) { TextImpl *text = static_cast(upstreamEnd.node()); deleteText(text, 0, upstreamEnd.offset()); } } else { // we have clipped the beginning of a non-text element // the offset must be 0 here. if it is, do nothing and move on. assert(downstreamStart.offset() == 0); } } // Do block merge if start and end of selection are in different blocks // and the blocks are siblings. This is a first cut at this rule arrived // at by doing a bunch of edits and settling on the behavior that made // the most sense. This could change in the future as we get more // experience with how this should behave. if (startBlock != endBlock && startBlockEndBlockAreSiblings) { qDebug() << "merging content to start block"; NodeImpl *node = endBlock->firstChild(); while (node) { NodeImpl *moveNode = node; node = node->nextSibling(); removeNode(moveNode); appendNode(startBlock, moveNode); } } if (adjustEndingPositionDownstream) { qDebug() << "adjust ending position downstream"; endingPosition = endingPosition.equivalentDownstreamPosition(); } debugPosition("ending position: ", endingPosition); setEndingSelection(endingPosition); qDebug() << "-----------------------------------------------------"; #endif } //------------------------------------------------------------------------------------------ // DeleteTextCommandImpl DeleteTextCommandImpl::DeleteTextCommandImpl(DocumentImpl *document, TextImpl *node, long offset, long count) : EditCommandImpl(document), m_node(node), m_offset(offset), m_count(count) { assert(m_node); assert(m_offset >= 0); assert(m_count >= 0); m_node->ref(); } DeleteTextCommandImpl::~DeleteTextCommandImpl() { if (m_node) { m_node->deref(); } } void DeleteTextCommandImpl::doApply() { assert(m_node); int exceptionCode = 0; m_text = m_node->substringData(m_offset, m_count, exceptionCode); assert(exceptionCode == 0); m_node->deleteData(m_offset, m_count, exceptionCode); assert(exceptionCode == 0); } void DeleteTextCommandImpl::doUnapply() { assert(m_node); assert(!m_text.isEmpty()); int exceptionCode = 0; m_node->insertData(m_offset, m_text, exceptionCode); assert(exceptionCode == 0); } //------------------------------------------------------------------------------------------ // InputNewlineCommandImpl InputNewlineCommandImpl::InputNewlineCommandImpl(DocumentImpl *document) : CompositeEditCommandImpl(document) { } InputNewlineCommandImpl::~InputNewlineCommandImpl() { } void InputNewlineCommandImpl::insertNodeAfterPosition(NodeImpl *node, const Position &pos) { // Insert the BR after the caret position. In the case the // position is a block, do an append. We don't want to insert // the BR *after* the block. Position upstream(pos.equivalentUpstreamPosition()); NodeImpl *cb = pos.node()->enclosingBlockFlowElement(); if (cb == pos.node()) { appendNode(cb, node); } else { insertNodeAfter(node, pos.node()); } } void InputNewlineCommandImpl::insertNodeBeforePosition(NodeImpl *node, const Position &pos) { // Insert the BR after the caret position. In the case the // position is a block, do an append. We don't want to insert // the BR *before* the block. Position upstream(pos.equivalentUpstreamPosition()); NodeImpl *cb = pos.node()->enclosingBlockFlowElement(); if (cb == pos.node()) { appendNode(cb, node); } else { insertNodeBefore(node, pos.node()); } } void InputNewlineCommandImpl::doApply() { deleteSelection(); Selection selection = endingSelection(); int exceptionCode = 0; NodeImpl *enclosingBlock = selection.start().node()->enclosingBlockFlowElement(); - qDebug() << enclosingBlock->nodeName() << endl; + qDebug() << enclosingBlock->nodeName(); if (enclosingBlock->id() == ID_LI) { // need to insert new list item or split existing one into 2 // consider example:
  • xxx|xxx
  • (| - caret position) // result should look like:
  • xxx
  • |xx
  • // idea is to walk up to the li item and split and reattach correspondent nodes #ifdef DEBUG_COMMANDS - qDebug() << "[insert new list item]" << selection << endl; + qDebug() << "[insert new list item]" << selection; printEnclosingBlockTree(selection.start().node()); #endif Position pos(selection.start().equivalentDownstreamPosition()); NodeImpl *node = pos.node(); bool atBlockStart = pos.atStartOfContainingEditableBlock(); bool atBlockEnd = pos.isLastRenderedPositionInEditableBlock(); // split text node into 2 if we are in the middle if (node->isTextNode() && !atBlockStart && !atBlockEnd) { TextImpl *textNode = static_cast(node); TextImpl *textBeforeNode = document()->createTextNode(textNode->substringData(0, selection.start().offset(), exceptionCode)); deleteText(textNode, 0, pos.offset()); insertNodeBefore(textBeforeNode, textNode); pos = Position(textNode, 0); setEndingSelection(pos); // walk up and reattach while (true) { #ifdef DEBUG_COMMANDS - qDebug() << "[handle node]" << node << endl; + qDebug() << "[handle node]" << node; printEnclosingBlockTree(enclosingBlock->parent()); #endif NodeImpl *parent = node->parent(); // FIXME copy attributes, styles etc too RefPtr newParent = parent->cloneNode(false); insertNodeAfter(newParent.get(), parent); for (NodeImpl *nextSibling = nullptr; node; node = nextSibling) { #ifdef DEBUG_COMMANDS - qDebug() << "[reattach sibling]" << node << endl; + qDebug() << "[reattach sibling]" << node; #endif nextSibling = node->nextSibling(); removeNode(node); appendNode(newParent.get(), node); } node = newParent.get(); if (parent == enclosingBlock) { break; } } } else if (node->isTextNode()) { // insert
    node either as previous list or the next one if (atBlockStart) { ElementImpl *listItem = document()->createHTMLElement("LI"); insertNodeBefore(listItem, enclosingBlock); } else { ElementImpl *listItem = document()->createHTMLElement("LI"); insertNodeAfter(listItem, enclosingBlock); } } #ifdef DEBUG_COMMANDS - qDebug() << "[result]" << endl; + qDebug() << "[result]"; printEnclosingBlockTree(enclosingBlock->parent()); #endif // FIXME set selection after operation return; } ElementImpl *breakNode = document()->createHTMLElement("BR"); // assert(exceptionCode == 0); #ifdef DEBUG_COMMANDS - qDebug() << "[insert break]" << selection << endl; + qDebug() << "[insert break]" << selection; printEnclosingBlockTree(enclosingBlock); #endif NodeImpl *nodeToInsert = breakNode; // Handle the case where there is a typing style. if (document()->part()->editor()->typingStyle()) { int exceptionCode = 0; ElementImpl *styleElement = createTypingStyleElement(); styleElement->appendChild(breakNode, exceptionCode); assert(exceptionCode == 0); nodeToInsert = styleElement; } Position pos(selection.start().equivalentDownstreamPosition()); bool atStart = pos.offset() <= pos.node()->caretMinOffset(); bool atEndOfBlock = pos.isLastRenderedPositionInEditableBlock(); #ifdef DEBUG_COMMANDS - qDebug() << "[pos]" << pos << atStart << atEndOfBlock << endl; + qDebug() << "[pos]" << pos << atStart << atEndOfBlock; #endif if (atEndOfBlock) { #ifdef DEBUG_COMMANDS qDebug() << "input newline case 1"; #endif // Insert an "extra" BR at the end of the block. This makes the "real" BR we want // to insert appear in the rendering without any significant side effects (and no // real worries either since you can't arrow past this extra one. insertNodeAfterPosition(nodeToInsert, pos); exceptionCode = 0; ElementImpl *extraBreakNode = document()->createHTMLElement("BR"); // assert(exceptionCode == 0); insertNodeAfter(extraBreakNode, nodeToInsert); setEndingSelection(Position(extraBreakNode, 0)); } else if (atStart) { #ifdef DEBUG_COMMANDS qDebug() << "input newline case 2"; #endif // Insert node, but place the caret into index 0 of the downstream // position. This will make the caret appear after the break, and as we know // there is content at that location, this is OK. insertNodeBeforePosition(nodeToInsert, pos); setEndingSelection(Position(pos.node(), 0)); } else { // Split a text node // FIXME it's possible that we create empty text node now if we're at the end of text // maybe we should handle this case specially and not create it #ifdef DEBUG_COMMANDS qDebug() << "input newline case 3"; #endif assert(pos.node()->isTextNode()); TextImpl *textNode = static_cast(pos.node()); TextImpl *textBeforeNode = document()->createTextNode(textNode->substringData(0, selection.start().offset(), exceptionCode)); deleteText(textNode, 0, selection.start().offset()); insertNodeBefore(textBeforeNode, textNode); insertNodeBefore(nodeToInsert, textNode); setEndingSelection(Position(textNode, 0)); } } //------------------------------------------------------------------------------------------ // InputTextCommandImpl InputTextCommandImpl::InputTextCommandImpl(DocumentImpl *document) : CompositeEditCommandImpl(document), m_charactersAdded(0) { } InputTextCommandImpl::~InputTextCommandImpl() { } void InputTextCommandImpl::doApply() { } void InputTextCommandImpl::input(const DOMString &text) { execute(text); } void InputTextCommandImpl::deleteCharacter() { assert(state() == Applied); Selection selection = endingSelection(); if (!selection.start().node()->isTextNode()) { return; } int exceptionCode = 0; int offset = selection.start().offset() - 1; if (offset >= selection.start().node()->caretMinOffset()) { TextImpl *textNode = static_cast(selection.start().node()); textNode->deleteData(offset, 1, exceptionCode); assert(exceptionCode == 0); selection = Selection(Position(textNode, offset)); setEndingSelection(selection); m_charactersAdded--; } } Position InputTextCommandImpl::prepareForTextInsertion(bool adjustDownstream) { // Prepare for text input by looking at the current position. // It may be necessary to insert a text node to receive characters. Selection selection = endingSelection(); assert(selection.state() == Selection::CARET); #ifdef DEBUG_COMMANDS - qDebug() << "[prepare selection]" << selection << endl; + qDebug() << "[prepare selection]" << selection; #endif Position pos = selection.start(); if (adjustDownstream) { pos = pos.equivalentDownstreamPosition(); } else { pos = pos.equivalentUpstreamPosition(); } #ifdef DEBUG_COMMANDS - qDebug() << "[prepare position]" << pos << endl; + qDebug() << "[prepare position]" << pos; #endif if (!pos.node()->isTextNode()) { NodeImpl *textNode = document()->createEditingTextNode(""); NodeImpl *nodeToInsert = textNode; if (document()->part()->editor()->typingStyle()) { int exceptionCode = 0; ElementImpl *styleElement = createTypingStyleElement(); styleElement->appendChild(textNode, exceptionCode); assert(exceptionCode == 0); nodeToInsert = styleElement; } // Now insert the node in the right place if (pos.node()->isEditableBlock()) { qDebug() << "prepareForTextInsertion case 1"; appendNode(pos.node(), nodeToInsert); } else if (pos.node()->id() == ID_BR && pos.offset() == 1) { qDebug() << "prepareForTextInsertion case 2"; insertNodeAfter(nodeToInsert, pos.node()); } else if (pos.node()->caretMinOffset() == pos.offset()) { qDebug() << "prepareForTextInsertion case 3"; insertNodeBefore(nodeToInsert, pos.node()); } else if (pos.node()->caretMaxOffset() == pos.offset()) { qDebug() << "prepareForTextInsertion case 4"; insertNodeAfter(nodeToInsert, pos.node()); } else { assert(false); } pos = Position(textNode, 0); } else { // Handle the case where there is a typing style. if (document()->part()->editor()->typingStyle()) { if (pos.node()->isTextNode() && pos.offset() > pos.node()->caretMinOffset() && pos.offset() < pos.node()->caretMaxOffset()) { // Need to split current text node in order to insert a span. TextImpl *text = static_cast(pos.node()); RefPtr cmd = new SplitTextNodeCommandImpl(document(), text, pos.offset()); applyCommandToComposite(cmd); setEndingSelection(Position(cmd->node(), 0)); } int exceptionCode = 0; TextImpl *editingTextNode = document()->createEditingTextNode(""); ElementImpl *styleElement = createTypingStyleElement(); styleElement->appendChild(editingTextNode, exceptionCode); assert(exceptionCode == 0); NodeImpl *node = endingSelection().start().node(); if (endingSelection().start().isLastRenderedPositionOnLine()) { insertNodeAfter(styleElement, node); } else { insertNodeBefore(styleElement, node); } pos = Position(editingTextNode, 0); } } return pos; } void InputTextCommandImpl::execute(const DOMString &text) { #ifdef DEBUG_COMMANDS - qDebug() << "[execute command]" << text << endl; + qDebug() << "[execute command]" << text; #endif Selection selection = endingSelection(); #ifdef DEBUG_COMMANDS - qDebug() << "[ending selection]" << selection << endl; + qDebug() << "[ending selection]" << selection; #endif bool adjustDownstream = selection.start().isFirstRenderedPositionOnLine(); #ifdef DEBUG_COMMANDS - qDebug() << "[adjust]" << adjustDownstream << endl; + qDebug() << "[adjust]" << adjustDownstream; #endif #ifdef DEBUG_COMMANDS printEnclosingBlockTree(selection.start().node()); #endif // Delete the current selection, or collapse whitespace, as needed if (selection.state() == Selection::RANGE) { deleteSelection(); } else { deleteCollapsibleWhitespace(); } #ifdef DEBUG_COMMANDS - qDebug() << "[after collapsible whitespace deletion]" << endl; + qDebug() << "[after collapsible whitespace deletion]"; printEnclosingBlockTree(selection.start().node()); #endif // EDIT FIXME: Need to take typing style from upstream text, if any. // Make sure the document is set up to receive text Position pos = prepareForTextInsertion(adjustDownstream); #ifdef DEBUG_COMMANDS - qDebug() << "[after prepare]" << pos << endl; + qDebug() << "[after prepare]" << pos; #endif TextImpl *textNode = static_cast(pos.node()); long offset = pos.offset(); #ifdef DEBUG_COMMANDS - qDebug() << "[insert at]" << textNode << offset << endl; + qDebug() << "[insert at]" << textNode << offset; #endif // This is a temporary implementation for inserting adjoining spaces // into a document. We are working on a CSS-related whitespace solution // that will replace this some day. if (isWS(text)) { insertSpace(textNode, offset); } else { const DOMString &existingText = textNode->data(); if (textNode->length() >= 2 && offset >= 2 && isNBSP(existingText[offset - 1]) && !isWS(existingText[offset - 2])) { // DOM looks like this: // character nbsp caret // As we are about to insert a non-whitespace character at the caret // convert the nbsp to a regular space. // EDIT FIXME: This needs to be improved some day to convert back only // those nbsp's added by the editor to make rendering come out right. replaceText(textNode, offset - 1, 1, " "); } insertText(textNode, offset, text); } setEndingSelection(Position(textNode, offset + text.length())); m_charactersAdded += text.length(); } void InputTextCommandImpl::insertSpace(TextImpl *textNode, unsigned long offset) { assert(textNode); DOMString text(textNode->data()); // count up all spaces and newlines in front of the caret // delete all collapsed ones // this will work out OK since the offset we have been passed has been upstream-ized int count = 0; for (unsigned int i = offset; i < text.length(); i++) { if (isWS(text[i])) { count++; } else { break; } } if (count > 0) { // By checking the character at the downstream position, we can // check if there is a rendered WS at the caret Position pos(textNode, offset); Position downstream = pos.equivalentDownstreamPosition(); if (downstream.offset() < (long)text.length() && isWS(text[downstream.offset()])) { count--; // leave this WS in } if (count > 0) { deleteText(textNode, offset, count); } } if (offset > 0 && offset <= text.length() - 1 && !isWS(text[offset]) && !isWS(text[offset - 1])) { // insert a "regular" space insertText(textNode, offset, " "); return; } if (text.length() >= 2 && offset >= 2 && isNBSP(text[offset - 2]) && isNBSP(text[offset - 1])) { // DOM looks like this: // nbsp nbsp caret // insert a space between the two nbsps insertText(textNode, offset - 1, " "); return; } // insert an nbsp insertText(textNode, offset, nonBreakingSpaceString()); } //------------------------------------------------------------------------------------------ // InsertNodeBeforeCommandImpl InsertNodeBeforeCommandImpl::InsertNodeBeforeCommandImpl(DocumentImpl *document, NodeImpl *insertChild, NodeImpl *refChild) : EditCommandImpl(document), m_insertChild(insertChild), m_refChild(refChild) { assert(m_insertChild); m_insertChild->ref(); assert(m_refChild); m_refChild->ref(); } InsertNodeBeforeCommandImpl::~InsertNodeBeforeCommandImpl() { if (m_insertChild) { m_insertChild->deref(); } if (m_refChild) { m_refChild->deref(); } } void InsertNodeBeforeCommandImpl::doApply() { assert(m_insertChild); assert(m_refChild); assert(m_refChild->parentNode()); int exceptionCode = 0; m_refChild->parentNode()->insertBefore(m_insertChild, m_refChild, exceptionCode); assert(exceptionCode == 0); } void InsertNodeBeforeCommandImpl::doUnapply() { assert(m_insertChild); assert(m_refChild); assert(m_refChild->parentNode()); int exceptionCode = 0; m_refChild->parentNode()->removeChild(m_insertChild, exceptionCode); assert(exceptionCode == 0); } //------------------------------------------------------------------------------------------ // InsertTextCommandImpl InsertTextCommandImpl::InsertTextCommandImpl(DocumentImpl *document, TextImpl *node, long offset, const DOMString &text) : EditCommandImpl(document), m_node(node), m_offset(offset) { assert(m_node); assert(m_offset >= 0); assert(text.length() > 0); m_node->ref(); m_text = text.copy(); // make a copy to ensure that the string never changes } InsertTextCommandImpl::~InsertTextCommandImpl() { if (m_node) { m_node->deref(); } } void InsertTextCommandImpl::doApply() { assert(m_node); assert(!m_text.isEmpty()); int exceptionCode = 0; m_node->insertData(m_offset, m_text, exceptionCode); assert(exceptionCode == 0); } void InsertTextCommandImpl::doUnapply() { assert(m_node); assert(!m_text.isEmpty()); int exceptionCode = 0; m_node->deleteData(m_offset, m_text.length(), exceptionCode); assert(exceptionCode == 0); } //------------------------------------------------------------------------------------------ // JoinTextNodesCommandImpl JoinTextNodesCommandImpl::JoinTextNodesCommandImpl(DocumentImpl *document, TextImpl *text1, TextImpl *text2) : EditCommandImpl(document), m_text1(text1), m_text2(text2) { assert(m_text1); assert(m_text2); assert(m_text1->nextSibling() == m_text2); assert(m_text1->length() > 0); assert(m_text2->length() > 0); m_text1->ref(); m_text2->ref(); } JoinTextNodesCommandImpl::~JoinTextNodesCommandImpl() { if (m_text1) { m_text1->deref(); } if (m_text2) { m_text2->deref(); } } void JoinTextNodesCommandImpl::doApply() { assert(m_text1); assert(m_text2); assert(m_text1->nextSibling() == m_text2); int exceptionCode = 0; m_text2->insertData(0, m_text1->data(), exceptionCode); assert(exceptionCode == 0); m_text2->parentNode()->removeChild(m_text1, exceptionCode); assert(exceptionCode == 0); m_offset = m_text1->length(); } void JoinTextNodesCommandImpl::doUnapply() { assert(m_text2); assert(m_offset > 0); int exceptionCode = 0; m_text2->deleteData(0, m_offset, exceptionCode); assert(exceptionCode == 0); m_text2->parentNode()->insertBefore(m_text1, m_text2, exceptionCode); assert(exceptionCode == 0); assert(m_text2->previousSibling()->isTextNode()); assert(m_text2->previousSibling() == m_text1); } //------------------------------------------------------------------------------------------ // ReplaceSelectionCommandImpl ReplaceSelectionCommandImpl::ReplaceSelectionCommandImpl(DocumentImpl *document, DOM::DocumentFragmentImpl *fragment, bool selectReplacement) : CompositeEditCommandImpl(document), m_fragment(fragment), m_selectReplacement(selectReplacement) { } ReplaceSelectionCommandImpl::~ReplaceSelectionCommandImpl() { } void ReplaceSelectionCommandImpl::doApply() { NodeImpl *firstChild = m_fragment->firstChild(); NodeImpl *lastChild = m_fragment->lastChild(); Selection selection = endingSelection(); // Delete the current selection, or collapse whitespace, as needed if (selection.state() == Selection::RANGE) { deleteSelection(); } else { deleteCollapsibleWhitespace(); } selection = endingSelection(); assert(!selection.isEmpty()); if (!firstChild) { // Pasting something that didn't parse or was empty. assert(!lastChild); } else if (firstChild == lastChild && firstChild->isTextNode()) { // Simple text paste. Treat as if the text were typed. Position base = selection.base(); inputText(static_cast(firstChild)->data()); if (m_selectReplacement) { setEndingSelection(Selection(base, endingSelection().extent())); } } else { // HTML fragment paste. NodeImpl *beforeNode = firstChild; NodeImpl *node = firstChild->nextSibling(); insertNodeAt(firstChild, selection.start().node(), selection.start().offset()); // Insert the nodes from the fragment while (node) { NodeImpl *next = node->nextSibling(); insertNodeAfter(node, beforeNode); beforeNode = node; node = next; } assert(beforeNode); // Find the last leaf. NodeImpl *lastLeaf = lastChild; while (1) { NodeImpl *nextChild = lastLeaf->lastChild(); if (!nextChild) { break; } lastLeaf = nextChild; } if (m_selectReplacement) { // Find the first leaf. NodeImpl *firstLeaf = firstChild; while (1) { NodeImpl *nextChild = firstLeaf->firstChild(); if (!nextChild) { break; } firstLeaf = nextChild; } // Select what was inserted. setEndingSelection(Selection(Position(firstLeaf, firstLeaf->caretMinOffset()), Position(lastLeaf, lastLeaf->caretMaxOffset()))); } else { // Place the cursor after what was inserted. setEndingSelection(Position(lastLeaf, lastLeaf->caretMaxOffset())); } } } //------------------------------------------------------------------------------------------ // MoveSelectionCommandImpl MoveSelectionCommandImpl::MoveSelectionCommandImpl(DocumentImpl *document, DOM::DocumentFragmentImpl *fragment, DOM::Position &position) : CompositeEditCommandImpl(document), m_fragment(fragment), m_position(position) { } MoveSelectionCommandImpl::~MoveSelectionCommandImpl() { } void MoveSelectionCommandImpl::doApply() { Selection selection = endingSelection(); assert(selection.state() == Selection::RANGE); // Update the position otherwise it may become invalid after the selection is deleted. NodeImpl *positionNode = m_position.node(); long positionOffset = m_position.offset(); Position selectionEnd = selection.end(); long selectionEndOffset = selectionEnd.offset(); if (selectionEnd.node() == positionNode && selectionEndOffset < positionOffset) { positionOffset -= selectionEndOffset; Position selectionStart = selection.start(); if (selectionStart.node() == positionNode) { positionOffset += selectionStart.offset(); } } deleteSelection(); setEndingSelection(Position(positionNode, positionOffset)); RefPtr cmd = new ReplaceSelectionCommandImpl(document(), m_fragment, true); applyCommandToComposite(cmd); } //------------------------------------------------------------------------------------------ // RemoveCSSPropertyCommandImpl RemoveCSSPropertyCommandImpl::RemoveCSSPropertyCommandImpl(DocumentImpl *document, CSSStyleDeclarationImpl *decl, int property) : EditCommandImpl(document), m_decl(decl), m_property(property), m_important(false) { assert(m_decl); m_decl->ref(); } RemoveCSSPropertyCommandImpl::~RemoveCSSPropertyCommandImpl() { assert(m_decl); m_decl->deref(); } void RemoveCSSPropertyCommandImpl::doApply() { assert(m_decl); m_oldValue = m_decl->getPropertyValue(m_property); assert(!m_oldValue.isNull()); m_important = m_decl->getPropertyPriority(m_property); m_decl->removeProperty(m_property); } void RemoveCSSPropertyCommandImpl::doUnapply() { assert(m_decl); assert(!m_oldValue.isNull()); m_decl->setProperty(m_property, m_oldValue, m_important); } //------------------------------------------------------------------------------------------ // RemoveNodeAttributeCommandImpl RemoveNodeAttributeCommandImpl::RemoveNodeAttributeCommandImpl(DocumentImpl *document, ElementImpl *element, NodeImpl::Id attribute) : EditCommandImpl(document), m_element(element), m_attribute(attribute) { assert(m_element); m_element->ref(); } RemoveNodeAttributeCommandImpl::~RemoveNodeAttributeCommandImpl() { assert(m_element); m_element->deref(); } void RemoveNodeAttributeCommandImpl::doApply() { assert(m_element); m_oldValue = m_element->getAttribute(m_attribute); assert(!m_oldValue.isNull()); int exceptionCode = 0; m_element->removeAttribute(m_attribute, exceptionCode); assert(exceptionCode == 0); } void RemoveNodeAttributeCommandImpl::doUnapply() { assert(m_element); assert(!m_oldValue.isNull()); // int exceptionCode = 0; m_element->setAttribute(m_attribute, m_oldValue.implementation()); // assert(exceptionCode == 0); } //------------------------------------------------------------------------------------------ // RemoveNodeCommandImpl RemoveNodeCommandImpl::RemoveNodeCommandImpl(DocumentImpl *document, NodeImpl *removeChild) : EditCommandImpl(document), m_parent(nullptr), m_removeChild(removeChild), m_refChild(nullptr) { assert(m_removeChild); m_removeChild->ref(); m_parent = m_removeChild->parentNode(); assert(m_parent); m_parent->ref(); RefPtr children = m_parent->childNodes(); for (long i = children->length() - 1; i >= 0; --i) { NodeImpl *node = children->item(i); if (node == m_removeChild) { break; } m_refChild = node; } if (m_refChild) { m_refChild->ref(); } } RemoveNodeCommandImpl::~RemoveNodeCommandImpl() { if (m_parent) { m_parent->deref(); } if (m_removeChild) { m_removeChild->deref(); } if (m_refChild) { m_refChild->deref(); } } void RemoveNodeCommandImpl::doApply() { assert(m_parent); assert(m_removeChild); int exceptionCode = 0; m_parent->removeChild(m_removeChild, exceptionCode); assert(exceptionCode == 0); } void RemoveNodeCommandImpl::doUnapply() { assert(m_parent); assert(m_removeChild); int exceptionCode = 0; if (m_refChild) { m_parent->insertBefore(m_removeChild, m_refChild, exceptionCode); } else { m_parent->appendChild(m_removeChild, exceptionCode); } assert(exceptionCode == 0); } //------------------------------------------------------------------------------------------ // RemoveNodeAndPruneCommandImpl RemoveNodeAndPruneCommandImpl::RemoveNodeAndPruneCommandImpl(DocumentImpl *document, NodeImpl *pruneNode, NodeImpl *stopNode) : CompositeEditCommandImpl(document), m_pruneNode(pruneNode), m_stopNode(stopNode) { assert(m_pruneNode); m_pruneNode->ref(); if (m_stopNode) { m_stopNode->ref(); } } RemoveNodeAndPruneCommandImpl::~RemoveNodeAndPruneCommandImpl() { m_pruneNode->deref(); if (m_stopNode) { m_stopNode->deref(); } } void RemoveNodeAndPruneCommandImpl::doApply() { NodeImpl *editableBlock = m_pruneNode->enclosingBlockFlowElement(); NodeImpl *pruneNode = m_pruneNode; NodeImpl *node = pruneNode->traversePreviousNode(); removeNode(pruneNode); while (1) { if (node == m_stopNode || editableBlock != node->enclosingBlockFlowElement() || !shouldPruneNode(node)) { break; } pruneNode = node; node = node->traversePreviousNode(); removeNode(pruneNode); } } //------------------------------------------------------------------------------------------ // RemoveNodePreservingChildrenCommandImpl RemoveNodePreservingChildrenCommandImpl::RemoveNodePreservingChildrenCommandImpl(DocumentImpl *document, NodeImpl *node) : CompositeEditCommandImpl(document), m_node(node) { assert(m_node); m_node->ref(); } RemoveNodePreservingChildrenCommandImpl::~RemoveNodePreservingChildrenCommandImpl() { if (m_node) { m_node->deref(); } } void RemoveNodePreservingChildrenCommandImpl::doApply() { RefPtr children = node()->childNodes(); const unsigned int length = children->length(); for (unsigned int i = 0; i < length; ++i) { NodeImpl *child = children->item(0); removeNode(child); insertNodeBefore(child, node()); } removeNode(node()); } //------------------------------------------------------------------------------------------ // SetNodeAttributeCommandImpl SetNodeAttributeCommandImpl::SetNodeAttributeCommandImpl(DocumentImpl *document, ElementImpl *element, NodeImpl::Id attribute, const DOMString &value) : EditCommandImpl(document), m_element(element), m_attribute(attribute), m_value(value) { assert(m_element); m_element->ref(); assert(!m_value.isNull()); } SetNodeAttributeCommandImpl::~SetNodeAttributeCommandImpl() { if (m_element) { m_element->deref(); } } void SetNodeAttributeCommandImpl::doApply() { assert(m_element); assert(!m_value.isNull()); // int exceptionCode = 0; m_oldValue = m_element->getAttribute(m_attribute); m_element->setAttribute(m_attribute, m_value.implementation()); // assert(exceptionCode == 0); } void SetNodeAttributeCommandImpl::doUnapply() { assert(m_element); assert(!m_oldValue.isNull()); // int exceptionCode = 0; m_element->setAttribute(m_attribute, m_oldValue.implementation()); // assert(exceptionCode == 0); } //------------------------------------------------------------------------------------------ // SplitTextNodeCommandImpl SplitTextNodeCommandImpl::SplitTextNodeCommandImpl(DocumentImpl *document, TextImpl *text, long offset) : EditCommandImpl(document), m_text1(nullptr), m_text2(text), m_offset(offset) { assert(m_text2); assert(m_text2->length() > 0); m_text2->ref(); } SplitTextNodeCommandImpl::~SplitTextNodeCommandImpl() { if (m_text1) { m_text1->deref(); } if (m_text2) { m_text2->deref(); } } void SplitTextNodeCommandImpl::doApply() { assert(m_text2); assert(m_offset > 0); int exceptionCode = 0; // EDIT FIXME: This should use better smarts for figuring out which portion // of the split to copy (based on their comparative sizes). We should also // just use the DOM's splitText function. if (!m_text1) { // create only if needed. // if reapplying, this object will already exist. m_text1 = document()->createTextNode(m_text2->substringData(0, m_offset, exceptionCode)); assert(exceptionCode == 0); assert(m_text1); m_text1->ref(); } m_text2->deleteData(0, m_offset, exceptionCode); assert(exceptionCode == 0); m_text2->parentNode()->insertBefore(m_text1, m_text2, exceptionCode); assert(exceptionCode == 0); assert(m_text2->previousSibling()->isTextNode()); assert(m_text2->previousSibling() == m_text1); } void SplitTextNodeCommandImpl::doUnapply() { assert(m_text1); assert(m_text2); assert(m_text1->nextSibling() == m_text2); int exceptionCode = 0; m_text2->insertData(0, m_text1->data(), exceptionCode); assert(exceptionCode == 0); m_text2->parentNode()->removeChild(m_text1, exceptionCode); assert(exceptionCode == 0); m_offset = m_text1->length(); } //------------------------------------------------------------------------------------------ // TypingCommandImpl TypingCommandImpl::TypingCommandImpl(DocumentImpl *document) : CompositeEditCommandImpl(document), m_openForMoreTyping(true) { } TypingCommandImpl::~TypingCommandImpl() { } void TypingCommandImpl::doApply() { } void TypingCommandImpl::typingAddedToOpenCommand() { assert(document()); assert(document()->part()); document()->part()->editor()->appliedEditing(this); } void TypingCommandImpl::insertText(const DOMString &text) { if (document()->part()->editor()->typingStyle() || m_cmds.count() == 0) { RefPtr cmd = new InputTextCommandImpl(document()); applyCommandToComposite(cmd); cmd->input(text); } else { EditCommandImpl *lastCommand = m_cmds.last().get(); if (lastCommand->isInputTextCommand()) { static_cast(lastCommand)->input(text); } else { RefPtr cmd = new InputTextCommandImpl(document()); applyCommandToComposite(cmd); cmd->input(text); } } typingAddedToOpenCommand(); } void TypingCommandImpl::insertNewline() { RefPtr cmd = new InputNewlineCommandImpl(document()); applyCommandToComposite(cmd); typingAddedToOpenCommand(); } void TypingCommandImpl::issueCommandForDeleteKey() { Selection selectionToDelete = endingSelection(); assert(selectionToDelete.state() != Selection::NONE); #ifdef DEBUG_COMMANDS - qDebug() << "[selection]" << selectionToDelete << endl; + qDebug() << "[selection]" << selectionToDelete; #endif if (selectionToDelete.state() == Selection::CARET) { #ifdef DEBUG_COMMANDS - qDebug() << "[caret selection]" << endl; + qDebug() << "[caret selection]"; #endif Position pos(selectionToDelete.start()); if (pos.inFirstEditableInRootEditableElement() && pos.offset() <= pos.node()->caretMinOffset()) { // we're at the start of a root editable block...do nothing return; } selectionToDelete = Selection(pos.previousCharacterPosition(), pos); #ifdef DEBUG_COMMANDS - qDebug() << "[modified selection]" << selectionToDelete << endl; + qDebug() << "[modified selection]" << selectionToDelete; #endif } deleteSelection(selectionToDelete); typingAddedToOpenCommand(); } void TypingCommandImpl::deleteKeyPressed() { // EDIT FIXME: The ifdef'ed out code below should be re-enabled. // In order for this to happen, the deleteCharacter case // needs work. Specifically, the caret-positioning code // and whitespace-handling code in DeleteSelectionCommandImpl::doApply() // needs to be factored out so it can be used again here. // Until that work is done, issueCommandForDeleteKey() does the // right thing, but less efficiently and with the cost of more // objects. issueCommandForDeleteKey(); #if 0 if (m_cmds.count() == 0) { issueCommandForDeleteKey(); } else { EditCommand lastCommand = m_cmds.last(); if (lastCommand.commandID() == InputTextCommandID) { InputTextCommand cmd = static_cast(lastCommand); cmd.deleteCharacter(); if (cmd.charactersAdded() == 0) { removeCommand(cmd); } } else if (lastCommand.commandID() == InputNewlineCommandID) { lastCommand.unapply(); removeCommand(lastCommand); } else { issueCommandForDeleteKey(); } } #endif } void TypingCommandImpl::removeCommand(const PassRefPtr cmd) { // NOTE: If the passed-in command is the last command in the // composite, we could remove all traces of this typing command // from the system, including the undo chain. Other editors do // not do this, but we could. m_cmds.removeAll(cmd); if (m_cmds.count() == 0) { setEndingSelection(startingSelection()); } else { setEndingSelection(m_cmds.last()->endingSelection()); } } static bool isOpenForMoreTypingCommand(const EditCommandImpl *command) { return command && command->isTypingCommand() && static_cast(command)->openForMoreTyping(); } void TypingCommandImpl::deleteKeyPressed0(DocumentImpl *document) { //Editor *editor = document->part()->editor(); // FIXME reenable after properly modify selection of the lastEditCommand // if (isOpenForMoreTypingCommand(lastEditCommand)) { // static_cast(lastEditCommand).deleteKeyPressed(); // } else { RefPtr command = new TypingCommandImpl(document); command->apply(); command->deleteKeyPressed(); // } } void TypingCommandImpl::insertNewline0(DocumentImpl *document) { assert(document); Editor *ed = document->part()->editor(); assert(ed); EditCommandImpl *lastEditCommand = ed->lastEditCommand().get(); if (isOpenForMoreTypingCommand(lastEditCommand)) { static_cast(lastEditCommand)->insertNewline(); } else { RefPtr command = new TypingCommandImpl(document); command->apply(); command->insertNewline(); } } void TypingCommandImpl::insertText0(DocumentImpl *document, const DOMString &text) { #ifdef DEBUG_COMMANDS - qDebug() << "[insert text]" << text << endl; + qDebug() << "[insert text]" << text; #endif assert(document); Editor *ed = document->part()->editor(); assert(ed); EditCommandImpl *lastEditCommand = ed->lastEditCommand().get(); if (isOpenForMoreTypingCommand(lastEditCommand)) { static_cast(lastEditCommand)->insertText(text); } else { RefPtr command = new TypingCommandImpl(document); command->apply(); command->insertText(text); } } //------------------------------------------------------------------------------------------ // InsertListCommandImpl InsertListCommandImpl::InsertListCommandImpl(DocumentImpl *document, Type type) : CompositeEditCommandImpl(document), m_listType(type) { } InsertListCommandImpl::~InsertListCommandImpl() { } void InsertListCommandImpl::doApply() { #ifdef DEBUG_COMMANDS - qDebug() << "[make current selection/paragraph a list]" << endingSelection() << endl; + qDebug() << "[make current selection/paragraph a list]" << endingSelection(); #endif Position start = endingSelection().start(); Position end = endingSelection().end(); ElementImpl *startBlock = start.node()->enclosingBlockFlowElement(); ElementImpl *endBlock = end.node()->enclosingBlockFlowElement(); #ifdef DEBUG_COMMANDS - qDebug() << "[start:end blocks]" << startBlock << endBlock << endl; + qDebug() << "[start:end blocks]" << startBlock << endBlock; printEnclosingBlockTree(start.node()); #endif if (startBlock == endBlock) { if (startBlock->id() == ID_LI) { // we already have a list item, remove it then #ifdef DEBUG_COMMANDS - qDebug() << "[remove list item]" << endl; + qDebug() << "[remove list item]"; #endif NodeImpl *listBlock = startBlock->parent(); // it's either
      or
        // we need to properly split or even remove the list leaving 2 lists: // [listBlock->firstChild(), startBlock) and (startBlock, listBlock->lastChild()] if (listBlock->firstChild() == listBlock->lastChild() && listBlock->firstChild() == startBlock) { // get rid of list completely #ifdef DEBUG_COMMANDS - qDebug() << "[remove list completely]" << endl; + qDebug() << "[remove list completely]"; #endif removeNodePreservingChildren(listBlock); removeNodePreservingChildren(startBlock); } else if (!startBlock->previousSibling()) { // move nodes from this list item before the list NodeImpl *nextSibling; for (NodeImpl *node = startBlock->firstChild(); node; node = nextSibling) { nextSibling = node->nextSibling(); removeNode(node); insertNodeBefore(node, listBlock); } removeNode(startBlock); } else if (!startBlock->nextSibling()) { // move nodes from this list item after the list NodeImpl *nextSibling; for (NodeImpl *node = startBlock->lastChild(); node; node = nextSibling) { nextSibling = node->previousSibling(); removeNode(node); insertNodeAfter(node, listBlock); } removeNode(startBlock); } else { // split list into 2 and nodes from this list item goes between lists WTF::PassRefPtr newListBlock = listBlock->cloneNode(false); insertNodeAfter(newListBlock.get(), listBlock); NodeImpl *node, *nextSibling; for (node = startBlock->nextSibling(); node; node = nextSibling) { nextSibling = node->nextSibling(); removeNode(node); appendNode(newListBlock.get(), node); } for (node = startBlock->firstChild(); node; node = nextSibling) { nextSibling = node->nextSibling(); removeNode(node); insertNodeBefore(node, newListBlock.get()); } removeNode(startBlock); } } else { ElementImpl *ol = document()->createHTMLElement(m_listType == OrderedList ? "OL" : "UL"); ElementImpl *li = document()->createHTMLElement("LI"); appendNode(ol, li); NodeImpl *nextNode; for (NodeImpl *node = startBlock->firstChild(); node; node = nextNode) { #ifdef DEBUG_COMMANDS - qDebug() << "[reattach node]" << node << endl; + qDebug() << "[reattach node]" << node; #endif nextNode = node->nextSibling(); removeNode(node); appendNode(li, node); } appendNode(startBlock, ol); } } else { #ifdef DEBUG_COMMANDS - qDebug() << "[different blocks are not supported yet]" << endl; + qDebug() << "[different blocks are not supported yet]"; #endif } } void InsertListCommandImpl::insertList(DocumentImpl *document, Type type) { RefPtr insertCommand = new InsertListCommandImpl(document, type); insertCommand->apply(); } //------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------ // IndentOutdentCommandImpl IndentOutdentCommandImpl::IndentOutdentCommandImpl(DocumentImpl *document, Type type) : CompositeEditCommandImpl(document), m_commandType(type) { } IndentOutdentCommandImpl::~IndentOutdentCommandImpl() { } void IndentOutdentCommandImpl::indent() { Selection selection = endingSelection(); #ifdef DEBUG_COMMANDS - qDebug() << "[indent selection]" << selection << endl; + qDebug() << "[indent selection]" << selection; #endif NodeImpl *startBlock = selection.start().node()->enclosingBlockFlowElement(); NodeImpl *endBlock = selection.end().node()->enclosingBlockFlowElement(); if (startBlock == endBlock) { // check if selection is the list, but not fully covered if (startBlock->id() == ID_LI && (startBlock->previousSibling() || startBlock->nextSibling())) { #ifdef DEBUG_COMMANDS - qDebug() << "[modify list]" << endl; + qDebug() << "[modify list]"; #endif RefPtr newList = startBlock->parent()->cloneNode(false); insertNodeAfter(newList.get(), startBlock); removeNode(startBlock); appendNode(newList.get(), startBlock); } else { NodeImpl *blockquoteElement = document()->createHTMLElement("blockquote"); if (startBlock->id() == ID_LI) { startBlock = startBlock->parent(); NodeImpl *parent = startBlock->parent(); removeNode(startBlock); appendNode(parent, blockquoteElement); appendNode(blockquoteElement, startBlock); } else { NodeImpl *parent = startBlock->parent(); removeNode(startBlock); appendNode(parent, blockquoteElement); appendNode(blockquoteElement, startBlock); } } } else { if (startBlock->id() == ID_LI && endBlock->id() == ID_LI && startBlock->parent() == endBlock->parent()) { #ifdef DEBUG_COMMANDS - qDebug() << "[indent some items inside list]" << endl; + qDebug() << "[indent some items inside list]"; #endif RefPtr nestedList = startBlock->parent()->cloneNode(false); insertNodeBefore(nestedList.get(), startBlock); NodeImpl *nextNode = nullptr; for (NodeImpl *node = startBlock;; node = nextNode) { nextNode = node->nextSibling(); removeNode(node); appendNode(nestedList.get(), node); if (node == endBlock) { break; } } } else { #ifdef DEBUG_COMMANDS - qDebug() << "[blocks not from one list are not supported yet]" << endl; + qDebug() << "[blocks not from one list are not supported yet]"; #endif } } } static bool hasPreviousListItem(NodeImpl *node) { while (node) { node = node->previousSibling(); if (node && node->id() == ID_LI) { return true; } } return false; } static bool hasNextListItem(NodeImpl *node) { while (node) { node = node->nextSibling(); if (node && node->id() == ID_LI) { return true; } } return false; } void IndentOutdentCommandImpl::outdent() { Selection selection = endingSelection(); #ifdef DEBUG_COMMANDS - qDebug() << "[indent selection]" << selection << endl; + qDebug() << "[indent selection]" << selection; #endif NodeImpl *startBlock = selection.start().node()->enclosingBlockFlowElement(); NodeImpl *endBlock = selection.end().node()->enclosingBlockFlowElement(); if (startBlock->id() == ID_LI && endBlock->id() == ID_LI && startBlock->parent() == endBlock->parent()) { #ifdef DEBUG_COMMANDS - qDebug() << "[list items selected]" << endl; + qDebug() << "[list items selected]"; #endif bool firstItemSelected = !hasPreviousListItem(startBlock); bool lastItemSelected = !hasNextListItem(endBlock); bool listFullySelected = firstItemSelected && lastItemSelected; #ifdef DEBUG_COMMANDS - qDebug() << "[first/last item selected]" << firstItemSelected << lastItemSelected << endl; + qDebug() << "[first/last item selected]" << firstItemSelected << lastItemSelected; #endif NodeImpl *listNode = startBlock->parent(); printEnclosingBlockTree(listNode); bool hasParentList = listNode->parent()->id() == ID_OL || listNode->parent()->id() == ID_UL; if (!firstItemSelected && !lastItemSelected) { // split the list into 2 and reattach all the nodes before the first selected item to the second list RefPtr clonedList = listNode->cloneNode(false); NodeImpl *nextNode = nullptr; for (NodeImpl *node = listNode->firstChild(); node != startBlock; node = nextNode) { nextNode = node->nextSibling(); removeNode(node); appendNode(clonedList.get(), node); } insertNodeBefore(clonedList.get(), listNode); // so now the first item selected firstItemSelected = true; } NodeImpl *nextNode = nullptr; for (NodeImpl *node = firstItemSelected ? startBlock : endBlock;; node = nextNode) { nextNode = firstItemSelected ? node->nextSibling() : node->previousSibling(); removeNode(node); if (firstItemSelected) { insertNodeBefore(node, listNode); } else { insertNodeAfter(node, listNode); } if (!hasParentList && node->id() == ID_LI) { insertNodeAfter(document()->createHTMLElement("BR"), node); removeNodePreservingChildren(node); } if (node == (firstItemSelected ? endBlock : startBlock)) { break; } } if (listFullySelected) { removeNode(listNode); } return; } if (startBlock == endBlock) { if (startBlock->id() == ID_BLOCKQUOTE) { removeNodePreservingChildren(startBlock); } else { #ifdef DEBUG_COMMANDS - qDebug() << "[not the list or blockquote]" << endl; + qDebug() << "[not the list or blockquote]"; #endif } } else { #ifdef DEBUG_COMMANDS - qDebug() << "[blocks not from one list are not supported yet]" << endl; + qDebug() << "[blocks not from one list are not supported yet]"; #endif } } void IndentOutdentCommandImpl::doApply() { if (m_commandType == Indent) { indent(); } else { outdent(); } } //------------------------------------------------------------------------------------------ } // namespace khtml diff --git a/src/editing/jsediting.cpp b/src/editing/jsediting.cpp index d67ee2d..351759d 100644 --- a/src/editing/jsediting.cpp +++ b/src/editing/jsediting.cpp @@ -1,650 +1,650 @@ /* * Copyright (C) 2004 Apple Computer, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "jsediting.h" #include "editing/htmlediting_impl.h" #include "editor.h" #include "css/cssproperties.h" #include "css/cssvalues.h" #include "css/css_valueimpl.h" #include "xml/dom_selection.h" #include "xml/dom_docimpl.h" #include "dom/dom_string.h" #include "misc/khtml_partaccessor.h" #include #include using khtml::TypingCommandImpl; using khtml::InsertListCommandImpl; // #define KPAC khtml::KHTMLPartAccessor #define DEBUG_COMMANDS namespace DOM { class DocumentImpl; struct CommandImp { bool (*execFn)(KHTMLPart *part, bool userInterface, const DOMString &value); bool (*enabledFn)(KHTMLPart *part); Editor::TriState(*stateFn)(KHTMLPart *part); DOMString(*valueFn)(KHTMLPart *part); }; typedef QHash CommandDict; static CommandDict createCommandDictionary(); bool JSEditor::execCommand(const CommandImp *cmd, bool userInterface, const DOMString &value) { if (!cmd || !cmd->enabledFn) { return false; } KHTMLPart *part = m_doc->part(); if (!part) { return false; } m_doc->updateLayout(); return cmd->enabledFn(part) && cmd->execFn(part, userInterface, value); } bool JSEditor::queryCommandEnabled(const CommandImp *cmd) { if (!cmd || !cmd->enabledFn) { return false; } KHTMLPart *part = m_doc->part(); if (!part) { return false; } m_doc->updateLayout(); return cmd->enabledFn(part); } bool JSEditor::queryCommandIndeterm(const CommandImp *cmd) { if (!cmd || !cmd->enabledFn) { return false; } KHTMLPart *part = m_doc->part(); if (!part) { return false; } m_doc->updateLayout(); return cmd->stateFn(part) == Editor::MixedTriState; } bool JSEditor::queryCommandState(const CommandImp *cmd) { if (!cmd || !cmd->enabledFn) { return false; } KHTMLPart *part = m_doc->part(); if (!part) { return false; } m_doc->updateLayout(); return cmd->stateFn(part) != Editor::FalseTriState; } bool JSEditor::queryCommandSupported(const CommandImp *cmd) { return cmd != nullptr; } DOMString JSEditor::queryCommandValue(const CommandImp *cmd) { if (!cmd || !cmd->enabledFn) { return DOMString(); } KHTMLPart *part = m_doc->part(); if (!part) { return DOMString(); } m_doc->updateLayout(); return cmd->valueFn(part); } // ============================================================================================= // Private stuff static bool execStyleChange(KHTMLPart *part, int propertyID, const DOMString &propertyValue) { CSSStyleDeclarationImpl *style = new CSSStyleDeclarationImpl(nullptr); style->setProperty(propertyID, propertyValue); style->ref(); part->editor()->applyStyle(style); style->deref(); return true; } static bool execStyleChange(KHTMLPart *part, int propertyID, int propertyEnum) { CSSStyleDeclarationImpl *style = new CSSStyleDeclarationImpl(nullptr); style->setProperty(propertyID, propertyEnum); style->ref(); part->editor()->applyStyle(style); style->deref(); return true; } static bool execStyleChange(KHTMLPart *part, int propertyID, const char *propertyValue) { return execStyleChange(part, propertyID, DOMString(propertyValue)); } static Editor::TriState stateStyle(KHTMLPart *part, int propertyID, const char *desiredValue) { CSSStyleDeclarationImpl *style = new CSSStyleDeclarationImpl(nullptr); style->setProperty(propertyID, desiredValue); style->ref(); Editor::TriState state = part->editor()->selectionHasStyle(style); style->deref(); return state; } static bool selectionStartHasStyle(KHTMLPart *part, int propertyID, const char *desiredValue) { CSSStyleDeclarationImpl *style = new CSSStyleDeclarationImpl(nullptr); style->setProperty(propertyID, desiredValue); style->ref(); bool hasStyle = part->editor()->selectionStartHasStyle(style); style->deref(); return hasStyle; } static DOMString valueStyle(KHTMLPart *part, int propertyID) { return part->editor()->selectionStartStylePropertyValue(propertyID); } // ============================================================================================= // // execCommand implementations // static bool execBackColor(KHTMLPart *part, bool /*userInterface*/, const DOMString &value) { return execStyleChange(part, CSS_PROP_BACKGROUND_COLOR, value); } static bool execBold(KHTMLPart *part, bool /*userInterface*/, const DOMString &/*value*/) { bool isBold = selectionStartHasStyle(part, CSS_PROP_FONT_WEIGHT, "bold"); return execStyleChange(part, CSS_PROP_FONT_WEIGHT, isBold ? "normal" : "bold"); } static bool execCopy(KHTMLPart *part, bool /*userInterface*/, const DOMString &/*value*/) { part->editor()->copy(); return true; } static bool execCut(KHTMLPart *part, bool /*userInterface*/, const DOMString &/*value*/) { part->editor()->cut(); return true; } static bool execDelete(KHTMLPart *part, bool /*userInterface*/, const DOMString &/*value*/) { TypingCommandImpl::deleteKeyPressed0(KPAC::xmlDocImpl(part)); return true; } static bool execFontName(KHTMLPart *part, bool /*userInterface*/, const DOMString &value) { return execStyleChange(part, CSS_PROP_FONT_FAMILY, value); } static bool execFontSize(KHTMLPart *part, bool /*userInterface*/, const DOMString &value) { // This should handle sizes 1-7 like does. Who the heck designed this interface? (Rhetorical question) bool ok; int val = value.string().toInt(&ok); if (ok && val >= 1 && val <= 7) { int size; switch (val) { case 1: size = CSS_VAL_XX_SMALL; break; case 2: size = CSS_VAL_SMALL; break; case 3: size = CSS_VAL_MEDIUM; break; case 4: size = CSS_VAL_LARGE; break; case 5: size = CSS_VAL_X_LARGE; break; case 6: size = CSS_VAL_XX_LARGE; break; default: size = CSS_VAL__KHTML_XXX_LARGE; } return execStyleChange(part, CSS_PROP_FONT_SIZE, size); } return execStyleChange(part, CSS_PROP_FONT_SIZE, value); } static bool execForeColor(KHTMLPart *part, bool /*userInterface*/, const DOMString &value) { return execStyleChange(part, CSS_PROP_COLOR, value); } static bool execIndent(KHTMLPart *part, bool /*userInterface*/, const DOMString &/*value*/) { part->editor()->indent(); return true; } static bool execInsertNewline(KHTMLPart *part, bool /*userInterface*/, const DOMString &/*value*/) { TypingCommandImpl::insertNewline0(KPAC::xmlDocImpl(part)); return true; } static bool execInsertParagraph(KHTMLPart * /*part*/, bool /*userInterface*/, const DOMString &/*value*/) { // FIXME: Implement. return false; } static bool execInsertText(KHTMLPart *part, bool /*userInterface*/, const DOMString &value) { TypingCommandImpl::insertText0(KPAC::xmlDocImpl(part), value); return true; } static bool execInsertOrderedList(KHTMLPart *part, bool /*userInterface*/, const DOMString &/*value*/) { InsertListCommandImpl::insertList(KPAC::xmlDocImpl(part), InsertListCommandImpl::OrderedList); return true; } static bool execInsertUnorderedList(KHTMLPart *part, bool /*userInterface*/, const DOMString &/*value*/) { InsertListCommandImpl::insertList(KPAC::xmlDocImpl(part), InsertListCommandImpl::UnorderedList); return true; } static bool execItalic(KHTMLPart *part, bool /*userInterface*/, const DOMString &/*value*/) { bool isItalic = selectionStartHasStyle(part, CSS_PROP_FONT_STYLE, "italic"); return execStyleChange(part, CSS_PROP_FONT_STYLE, isItalic ? "normal" : "italic"); } static bool execJustifyCenter(KHTMLPart *part, bool /*userInterface*/, const DOMString &/*value*/) { return execStyleChange(part, CSS_PROP_TEXT_ALIGN, "center"); } static bool execJustifyFull(KHTMLPart *part, bool /*userInterface*/, const DOMString &/*value*/) { return execStyleChange(part, CSS_PROP_TEXT_ALIGN, "justify"); } static bool execJustifyLeft(KHTMLPart *part, bool /*userInterface*/, const DOMString &/*value*/) { return execStyleChange(part, CSS_PROP_TEXT_ALIGN, "left"); } static bool execJustifyRight(KHTMLPart *part, bool /*userInterface*/, const DOMString &/*value*/) { return execStyleChange(part, CSS_PROP_TEXT_ALIGN, "right"); } static bool execOutdent(KHTMLPart *part, bool /*userInterface*/, const DOMString &/*value*/) { part->editor()->outdent(); return true; } #ifndef NO_SUPPORT_PASTE static bool execPaste(KHTMLPart *part, bool /*userInterface*/, const DOMString &/*value*/) { part->editor()->paste(); return true; } #endif static bool execPrint(KHTMLPart *part, bool /*userInterface*/, const DOMString &/*value*/) { part->editor()->print(); return true; } static bool execRedo(KHTMLPart *part, bool /*userInterface*/, const DOMString &/*value*/) { part->editor()->redo(); return true; } static bool execSelectAll(KHTMLPart *part, bool /*userInterface*/, const DOMString &/*value*/) { part->selectAll(); return true; } static bool execStrikeThrough(KHTMLPart *part, bool /*userInterface*/, const DOMString &/*value*/) { bool isStriked = selectionStartHasStyle(part, CSS_PROP_TEXT_DECORATION, "line-through"); return execStyleChange(part, CSS_PROP_TEXT_DECORATION, isStriked ? "none" : "line-through"); } static bool execSubscript(KHTMLPart *part, bool /*userInterface*/, const DOMString &/*value*/) { return execStyleChange(part, CSS_PROP_VERTICAL_ALIGN, "sub"); } static bool execSuperscript(KHTMLPart *part, bool /*userInterface*/, const DOMString &/*value*/) { return execStyleChange(part, CSS_PROP_VERTICAL_ALIGN, "super"); } static bool execUndo(KHTMLPart *part, bool /*userInterface*/, const DOMString &/*value*/) { part->editor()->undo(); return true; } static bool execUnderline(KHTMLPart *part, bool /*userInterface*/, const DOMString &/*value*/) { bool isUnderline = selectionStartHasStyle(part, CSS_PROP_TEXT_DECORATION, "underline"); return execStyleChange(part, CSS_PROP_TEXT_DECORATION, isUnderline ? "none" : "underline"); } static bool execUnselect(KHTMLPart *part, bool /*userInterface*/, const DOMString &/*value*/) { KPAC::clearSelection(part); return true; } // ============================================================================================= // // queryCommandEnabled implementations // // It's a bit difficult to get a clear notion of the difference between // "supported" and "enabled" from reading the Microsoft documentation, but // what little I could glean from that seems to make some sense. // Supported = The command is supported by this object. // Enabled = The command is available and enabled. static bool enabled(KHTMLPart * /*part*/) { return true; } static bool enabledAnySelection(KHTMLPart *part) { return KPAC::caret(part).notEmpty(); } #ifndef NO_SUPPORT_PASTE static bool enabledPaste(KHTMLPart *part) { return part->editor()->canPaste(); } #endif static bool enabledRangeSelection(KHTMLPart *part) { return KPAC::caret(part).state() == Selection::RANGE; } static bool enabledRedo(KHTMLPart *part) { return part->editor()->canRedo(); } static bool enabledUndo(KHTMLPart *part) { return part->editor()->canUndo(); } // ============================================================================================= // // queryCommandIndeterm/State implementations // // It's a bit difficult to get a clear notion of what these methods are supposed // to do from reading the Microsoft documentation, but my current guess is this: // // queryCommandState and queryCommandIndeterm work in concert to return // the two bits of information that are needed to tell, for instance, // if the text of a selection is bold. The answer can be "yes", "no", or // "partially". // // If this is so, then queryCommandState should return "yes" in the case where // all the text is bold and "no" for non-bold or partially-bold text. // Then, queryCommandIndeterm should return "no" in the case where // all the text is either all bold or not-bold and and "yes" for partially-bold text. static Editor::TriState stateNone(KHTMLPart * /*part*/) { return Editor::FalseTriState; } static Editor::TriState stateBold(KHTMLPart *part) { return stateStyle(part, CSS_PROP_FONT_WEIGHT, "bold"); } static Editor::TriState stateItalic(KHTMLPart *part) { return stateStyle(part, CSS_PROP_FONT_STYLE, "italic"); } static Editor::TriState stateStrike(KHTMLPart *part) { return stateStyle(part, CSS_PROP_TEXT_DECORATION, "line-through"); } static Editor::TriState stateSubscript(KHTMLPart *part) { return stateStyle(part, CSS_PROP_VERTICAL_ALIGN, "sub"); } static Editor::TriState stateSuperscript(KHTMLPart *part) { return stateStyle(part, CSS_PROP_VERTICAL_ALIGN, "super"); } static Editor::TriState stateUnderline(KHTMLPart *part) { return stateStyle(part, CSS_PROP_TEXT_DECORATION, "underline"); } // ============================================================================================= // // queryCommandValue implementations // static DOMString valueNull(KHTMLPart * /*part*/) { return DOMString(); } static DOMString valueBackColor(KHTMLPart *part) { return valueStyle(part, CSS_PROP_BACKGROUND_COLOR); } static DOMString valueFontName(KHTMLPart *part) { return valueStyle(part, CSS_PROP_FONT_FAMILY); } static DOMString valueFontSize(KHTMLPart *part) { return valueStyle(part, CSS_PROP_FONT_SIZE); } static DOMString valueForeColor(KHTMLPart *part) { return valueStyle(part, CSS_PROP_COLOR); } // ============================================================================================= struct EditorCommandInfo { const char *name; CommandImp imp; }; // NOTE: strictly keep in sync with EditorCommand in editor_command.h static const EditorCommandInfo commands[] = { { "backColor", { execBackColor, enabled, stateNone, valueBackColor } }, { "bold", { execBold, enabledAnySelection, stateBold, valueNull } }, { "copy", { execCopy, enabledRangeSelection, stateNone, valueNull } }, { "cut", { execCut, enabledRangeSelection, stateNone, valueNull } }, { "delete", { execDelete, enabledAnySelection, stateNone, valueNull } }, { "fontName", { execFontName, enabledAnySelection, stateNone, valueFontName } }, { "fontSize", { execFontSize, enabledAnySelection, stateNone, valueFontSize } }, { "foreColor", { execForeColor, enabledAnySelection, stateNone, valueForeColor } }, { "indent", { execIndent, enabledAnySelection, stateNone, valueNull } }, { "insertNewline", { execInsertNewline, enabledAnySelection, stateNone, valueNull } }, { "insertOrderedList", { execInsertOrderedList, enabledAnySelection, stateNone, valueNull } }, { "insertParagraph", { execInsertParagraph, enabledAnySelection, stateNone, valueNull } }, { "insertText", { execInsertText, enabledAnySelection, stateNone, valueNull } }, { "insertUnorderedList", { execInsertUnorderedList, enabledAnySelection, stateNone, valueNull } }, { "italic", { execItalic, enabledAnySelection, stateItalic, valueNull } }, { "justifyCenter", { execJustifyCenter, enabledAnySelection, stateNone, valueNull } }, { "justifyFull", { execJustifyFull, enabledAnySelection, stateNone, valueNull } }, { "justifyLeft", { execJustifyLeft, enabledAnySelection, stateNone, valueNull } }, { "justifyNone", { execJustifyLeft, enabledAnySelection, stateNone, valueNull } }, { "justifyRight", { execJustifyRight, enabledAnySelection, stateNone, valueNull } }, { "outdent", { execOutdent, enabledAnySelection, stateNone, valueNull } }, #ifndef NO_SUPPORT_PASTE { "paste", { execPaste, enabledPaste, stateNone, valueNull } }, #else { 0, { 0, 0, 0, 0 } }, #endif { "print", { execPrint, enabled, stateNone, valueNull } }, { "redo", { execRedo, enabledRedo, stateNone, valueNull } }, { "selectAll", { execSelectAll, enabled, stateNone, valueNull } }, { "StrikeThrough", {execStrikeThrough, enabled, stateStrike, valueNull } }, { "subscript", { execSubscript, enabledAnySelection, stateSubscript, valueNull } }, { "superscript", { execSuperscript, enabledAnySelection, stateSuperscript, valueNull } }, { "underline", { execUnderline, enabledAnySelection, stateUnderline, valueNull } }, { "undo", { execUndo, enabledUndo, stateNone, valueNull } }, { "unselect", { execUnselect, enabledAnySelection, stateNone, valueNull } } // // The "unsupported" commands are listed here since they appear in the Microsoft // documentation used as the basis for the list. // // 2d-position (not supported) // absolutePosition (not supported) // blockDirLTR (not supported) // blockDirRTL (not supported) // browseMode (not supported) // clearAuthenticationCache (not supported) // createBookmark (not supported) // createLink (not supported) // dirLTR (not supported) // dirRTL (not supported) // editMode (not supported) // formatBlock (not supported) // inlineDirLTR (not supported) // inlineDirRTL (not supported) // insertButton (not supported) // insertFieldSet (not supported) // insertHorizontalRule (not supported) // insertIFrame (not supported) // insertImage (not supported) // insertInputButton (not supported) // insertInputCheckbox (not supported) // insertInputFileUpload (not supported) // insertInputHidden (not supported) // insertInputImage (not supported) // insertInputPassword (not supported) // insertInputRadio (not supported) // insertInputReset (not supported) // insertInputSubmit (not supported) // insertInputText (not supported) // insertMarquee (not supported) // insertOrderedList (not supported) // insertSelectDropDown (not supported) // insertSelectListBox (not supported) // insertTextArea (not supported) // insertUnorderedList (not supported) // liveResize (not supported) // multipleSelection (not supported) // open (not supported) // overwrite (not supported) // playImage (not supported) // refresh (not supported) // removeFormat (not supported) // removeParaFormat (not supported) // saveAs (not supported) // sizeToControl (not supported) // sizeToControlHeight (not supported) // sizeToControlWidth (not supported) // stop (not supported) // stopimage (not supported) // strikethrough (not supported) // unbookmark (not supported) // underline (not supported) // unlink (not supported) }; static CommandDict createCommandDictionary() { const int numCommands = sizeof(commands) / sizeof(commands[0]); CommandDict commandDictionary; // case-insensitive dictionary for (int i = 0; i < numCommands; ++i) { if (commands[i].name) { commandDictionary.insert(QString(commands[i].name).toLower(), &commands[i].imp); } } return commandDictionary; } const CommandImp *JSEditor::commandImp(const DOMString &command) { static CommandDict commandDictionary = createCommandDictionary(); const CommandImp *result = commandDictionary.value(command.string().toLower()); #ifdef DEBUG_COMMANDS if (!result) { - qDebug() << "[Command is not supported yet]" << command << endl; + qDebug() << "[Command is not supported yet]" << command; } #endif return result; } const CommandImp *JSEditor::commandImp(int command) { if (command < 0 || command >= int(sizeof commands / sizeof commands[0])) { return nullptr; } return &commands[command].imp; } } // namespace DOM #undef KPAC diff --git a/src/html/html_elementimpl.cpp b/src/html/html_elementimpl.cpp index 0b87ea0..8473019 100644 --- a/src/html/html_elementimpl.cpp +++ b/src/html/html_elementimpl.cpp @@ -1,705 +1,705 @@ /** * This file is part of the DOM implementation for KDE. * * Copyright (C) 1999 Lars Knoll (knoll@kde.org) * (C) 1999 Antti Koivisto (koivisto@kde.org) * (C) 2003 Dirk Mueller (mueller@kde.org) * Copyright (C) 2002 Apple Computer, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ // ------------------------------------------------------------------------- //#define DEBUG //#define DEBUG_LAYOUT //#define PAR_DEBUG //#define EVENT_DEBUG //#define UNSUPPORTED_ATTR #include "html_elementimpl.h" #include "dtd.h" #include "html_documentimpl.h" #include "htmltokenizer.h" #include #include #include #include #include #include #include #include #include #include #include #include #undef FOCUS_EVENT // for win32, MinGW using namespace DOM; using namespace khtml; HTMLElementImpl::HTMLElementImpl(DocumentImpl *doc) : ElementImpl(doc) { m_htmlCompat = doc && doc->htmlMode() != DocumentImpl::XHtml; } HTMLElementImpl::~HTMLElementImpl() { } bool HTMLElementImpl::isInline() const { if (renderer()) { return ElementImpl::isInline(); } switch (id()) { case ID_A: case ID_FONT: case ID_TT: case ID_U: case ID_B: case ID_I: case ID_S: case ID_STRIKE: case ID_BIG: case ID_SMALL: // %phrase case ID_EM: case ID_STRONG: case ID_DFN: case ID_CODE: case ID_SAMP: case ID_KBD: case ID_VAR: case ID_CITE: case ID_ABBR: case ID_ACRONYM: // %special case ID_SUB: case ID_SUP: case ID_SPAN: case ID_NOBR: case ID_WBR: return true; default: return ElementImpl::isInline(); } } DOMString HTMLElementImpl::namespaceURI() const { return DOMString(XHTML_NAMESPACE); } void HTMLElementImpl::parseAttribute(AttributeImpl *attr) { DOMString indexstring; switch (attr->id()) { case ATTR_ALIGN: if (attr->val()) { if (strcasecmp(attr->value(), "middle") == 0) { addCSSProperty(CSS_PROP_TEXT_ALIGN, CSS_VAL_CENTER); } else { addCSSProperty(CSS_PROP_TEXT_ALIGN, attr->value().lower()); } } else { removeCSSProperty(CSS_PROP_TEXT_ALIGN); } break; // the core attributes... case ATTR_ID: // unique id setHasID(); document()->incDOMTreeVersion(DocumentImpl::TV_IDNameHref); break; case ATTR_CLASS: if (attr->val()) { DOMString v = attr->value(); const QChar *characters = v.unicode(); unsigned length = v.length(); unsigned i; for (i = 0; i < length && characters[i].isSpace(); ++i) { } setHasClass(i < length); attributes()->setClass(v); } else { setHasClass(false); } document()->incDOMTreeVersion(DocumentImpl::TV_Class); break; case ATTR_NAME: document()->incDOMTreeVersion(DocumentImpl::TV_IDNameHref); break; case ATTR_CONTENTEDITABLE: setContentEditable(attr); break; case ATTR_STYLE: getInlineStyleDecls()->updateFromAttribute(attr->value()); setChanged(); break; case ATTR_TABINDEX: indexstring = getAttribute(ATTR_TABINDEX); if (attr->val()) { setTabIndex(attr->value().toInt()); } else { setNoTabIndex(); } break; // i18n attributes case ATTR_LANG: break; case ATTR_DIR: addCSSProperty(CSS_PROP_DIRECTION, attr->value().lower()); break; // standard events case ATTR_ONCLICK: setHTMLEventListener(EventImpl::KHTML_ECMA_CLICK_EVENT, document()->createHTMLEventListener(attr->value().string(), "onclick", this)); break; case ATTR_ONDBLCLICK: setHTMLEventListener(EventImpl::KHTML_ECMA_DBLCLICK_EVENT, document()->createHTMLEventListener(attr->value().string(), "ondblclick", this)); break; case ATTR_ONMOUSEDOWN: setHTMLEventListener(EventImpl::MOUSEDOWN_EVENT, document()->createHTMLEventListener(attr->value().string(), "onmousedown", this)); break; case ATTR_ONMOUSEMOVE: setHTMLEventListener(EventImpl::MOUSEMOVE_EVENT, document()->createHTMLEventListener(attr->value().string(), "onmousemove", this)); break; case ATTR_ONMOUSEOUT: setHTMLEventListener(EventImpl::MOUSEOUT_EVENT, document()->createHTMLEventListener(attr->value().string(), "onmouseout", this)); break; case ATTR_ONMOUSEOVER: setHTMLEventListener(EventImpl::MOUSEOVER_EVENT, document()->createHTMLEventListener(attr->value().string(), "onmouseover", this)); break; case ATTR_ONMOUSEUP: setHTMLEventListener(EventImpl::MOUSEUP_EVENT, document()->createHTMLEventListener(attr->value().string(), "onmouseup", this)); break; case ATTR_ONKEYDOWN: setHTMLEventListener(EventImpl::KEYDOWN_EVENT, document()->createHTMLEventListener(attr->value().string(), "onkeydown", this)); break; case ATTR_ONKEYPRESS: setHTMLEventListener(EventImpl::KEYPRESS_EVENT, document()->createHTMLEventListener(attr->value().string(), "onkeypress", this)); break; case ATTR_ONKEYUP: setHTMLEventListener(EventImpl::KEYUP_EVENT, document()->createHTMLEventListener(attr->value().string(), "onkeyup", this)); break; case ATTR_ONFOCUS: setHTMLEventListener(EventImpl::FOCUS_EVENT, document()->createHTMLEventListener(attr->value().string(), "onfocus", this)); break; case ATTR_ONBLUR: setHTMLEventListener(EventImpl::BLUR_EVENT, document()->createHTMLEventListener(attr->value().string(), "onblur", this)); break; case ATTR_ONSCROLL: setHTMLEventListener(EventImpl::SCROLL_EVENT, document()->createHTMLEventListener(attr->value().string(), "onscroll", this)); break; // other misc attributes default: #ifdef UNSUPPORTED_ATTR qDebug() << "UATTR: <" << this->nodeName().string() << "> [" - << attr->name().string() << "]=[" << attr->value().string() << "]" << endl; + << attr->name().string() << "]=[" << attr->value().string() << "]"; #endif break; } } void HTMLElementImpl::recalcStyle(StyleChange ch) { ElementImpl::recalcStyle(ch); if (m_render /*&& changed*/) { m_render->updateFromElement(); } } void HTMLElementImpl::addCSSProperty(int id, const DOMString &value) { if (!m_hasCombinedStyle) { createNonCSSDecl(); } nonCSSStyleDecls()->setProperty(id, value, false); setChanged(); } void HTMLElementImpl::addCSSProperty(int id, int value) { if (!m_hasCombinedStyle) { createNonCSSDecl(); } nonCSSStyleDecls()->setProperty(id, value, false); setChanged(); } void HTMLElementImpl::addCSSLength(int id, const DOMString &value, bool numOnly, bool multiLength) { if (!m_hasCombinedStyle) { createNonCSSDecl(); } // strip attribute garbage to avoid CSS parsing errors // ### create specialized hook that avoids parsing every // value twice! if (value.implementation()) { // match \s*[+-]?\d*(\.\d*)?[%\*]? unsigned i = 0, j = 0; QChar *s = value.implementation()->s; unsigned l = value.implementation()->l; while (i < l && s[i].isSpace()) { ++i; } if (i < l && (s[i] == '+' || s[i] == '-')) { ++i; } while (i < l && s[i].isDigit()) { ++i, ++j; } // no digits! if (j == 0) { return; } int v = qBound(-8192, QString::fromRawData(s, i).toInt(), 8191); const char *suffix = "px"; if (!numOnly || multiLength) { // look if we find a % or * while (i < l) { if (multiLength && s[i] == '*') { suffix = ""; break; } if (s[i] == '%') { suffix = "%"; break; } ++i; } } if (numOnly) { suffix = ""; } QString ns = QString::number(v) + suffix; nonCSSStyleDecls()->setLengthProperty(id, DOMString(ns), false, multiLength); setChanged(); return; } nonCSSStyleDecls()->setLengthProperty(id, value, false, multiLength); setChanged(); } static inline bool isHexDigit(const QChar &c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } static inline int toHex(const QChar &c) { return ((c >= '0' && c <= '9') ? (c.unicode() - '0') : ((c >= 'a' && c <= 'f') ? (c.unicode() - 'a' + 10) : ((c >= 'A' && c <= 'F') ? (c.unicode() - 'A' + 10) : -1))); } /* color parsing that tries to match as close as possible IE 6. */ void HTMLElementImpl::addHTMLColor(int id, const DOMString &c) { if (!m_hasCombinedStyle) { createNonCSSDecl(); } // this is the only case no color gets applied in IE. if (!c.length()) { removeCSSProperty(id); return; } if (nonCSSStyleDecls()->setProperty(id, c, false)) { return; } QString color = c.string(); // not something that fits the specs. // we're emulating IEs color parser here. It maps transparent to black, otherwise it tries to build a rgb value // out of everyhting you put in. The algorithm is experimentally determined, but seems to work for all test cases I have. // the length of the color value is rounded up to the next // multiple of 3. each part of the rgb triple then gets one third // of the length. // // Each triplet is parsed byte by byte, mapping // each number to a hex value (0-9a-fA-F to their values // everything else to 0). // // The highest non zero digit in all triplets is remembered, and // used as a normalization point to normalize to values between 0 // and 255. if (color.toLower() != "transparent") { if (color[0] == '#') { color.remove(0, 1); } int basicLength = (color.length() + 2) / 3; if (basicLength > 1) { // IE ignores colors with three digits or less // qDebug("trying to fix up color '%s'. basicLength=%d, length=%d", // color.toLatin1().constData(), basicLength, color.length() ); int colors[3] = { 0, 0, 0 }; int component = 0; int pos = 0; int maxDigit = basicLength - 1; while (component < 3) { // search forward for digits in the string int numDigits = 0; while (pos < (int)color.length() && numDigits < basicLength) { int hex = toHex(color[pos]); colors[component] = (colors[component] << 4); if (hex > 0) { colors[component] += hex; maxDigit = qMin(maxDigit, numDigits); } numDigits++; pos++; } while (numDigits++ < basicLength) { colors[component] <<= 4; } component++; } maxDigit = basicLength - maxDigit; // qDebug("color is %x %x %x, maxDigit=%d", colors[0], colors[1], colors[2], maxDigit ); // normalize to 00-ff. The highest filled digit counts, minimum is 2 digits maxDigit -= 2; colors[0] >>= 4 * maxDigit; colors[1] >>= 4 * maxDigit; colors[2] >>= 4 * maxDigit; // qDebug("normalized color is %x %x %x", colors[0], colors[1], colors[2] ); // assert( colors[0] < 0x100 && colors[1] < 0x100 && colors[2] < 0x100 ); color.sprintf("#%02x%02x%02x", colors[0], colors[1], colors[2]); // qDebug( "trying to add fixed color string '%s'", color.toLatin1().constData() ); if (nonCSSStyleDecls()->setProperty(id, DOMString(color), false)) { return; } } } nonCSSStyleDecls()->setProperty(id, CSS_VAL_BLACK, false); } void HTMLElementImpl::removeCSSProperty(int id) { if (!m_hasCombinedStyle) { return; } nonCSSStyleDecls()->setParent(document()->elementSheet()); nonCSSStyleDecls()->removeProperty(id); setChanged(); } DOMString HTMLElementImpl::innerHTML() const { QString result = ""; //Use QString to accumulate since DOMString is poor for appends for (NodeImpl *child = firstChild(); child != nullptr; child = child->nextSibling()) { DOMString kid = child->toString(); result += QString::fromRawData(kid.unicode(), kid.length()); } return result; } DOMString HTMLElementImpl::innerText() const { QString text = ""; if (!firstChild()) { return text; } const NodeImpl *n = this; // find the next text/image after the anchor, to get a position while (n) { if (n->firstChild()) { n = n->firstChild(); } else if (n->nextSibling()) { n = n->nextSibling(); } else { NodeImpl *next = nullptr; while (!next) { n = n->parentNode(); if (!n || n == (NodeImpl *)this) { goto end; } next = n->nextSibling(); } n = next; } if (n->isTextNode()) { DOMStringImpl *data = static_cast(n)->string(); text += QString::fromRawData(data->s, data->l); } } end: return text; } DocumentFragment HTMLElementImpl::createContextualFragment(const DOMString &html) { // IE originally restricted innerHTML to a small subset of elements; // and we largely matched that. Mozilla's embrace of innerHTML, however, extended // it to pretty much everything, and so the web (and HTML5) requires it now. // For now, we accept everything, but do not do context-based recovery in the parser. if (!document()->isHTMLDocument()) { return DocumentFragment(); } DocumentFragmentImpl *fragment = new DocumentFragmentImpl(docPtr()); DocumentFragment f(fragment); { HTMLTokenizer tok(docPtr(), fragment); tok.begin(); tok.write(html.string(), true); tok.end(); } // Exceptions are ignored because none ought to happen here. int ignoredExceptionCode; // we need to pop and elements and remove to // accomadate folks passing complete HTML documents to make the // child of an element. for (NodeImpl *node = fragment->firstChild(); node;) { if (node->id() == ID_HTML || node->id() == ID_BODY) { NodeImpl *firstChild = node->firstChild(); NodeImpl *child = firstChild; while (child) { NodeImpl *nextChild = child->nextSibling(); fragment->insertBefore(child, node, ignoredExceptionCode); child = nextChild; } if (!firstChild) { NodeImpl *nextNode = node->nextSibling(); fragment->removeChild(node, ignoredExceptionCode); node = nextNode; } else { fragment->removeChild(node, ignoredExceptionCode); node = firstChild; } } else if (node->id() == ID_HEAD) { NodeImpl *nextNode = node->nextSibling(); fragment->removeChild(node, ignoredExceptionCode); node = nextNode; } else { node = node->nextSibling(); } } return f; } void HTMLElementImpl::setInnerHTML(const DOMString &html, int &exceptioncode) { if (id() == ID_SCRIPT || id() == ID_STYLE) { // Script and CSS source shouldn't be parsed as HTML. removeChildren(); appendChild(document()->createTextNode(html), exceptioncode); return; } DocumentFragment fragment = createContextualFragment(html); if (fragment.isNull()) { exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; return; } // Make sure adding the new child is ok, before removing all children (#96187) checkAddChild(fragment.handle(), exceptioncode); if (exceptioncode) { return; } removeChildren(); appendChild(fragment.handle(), exceptioncode); } void HTMLElementImpl::setInnerText(const DOMString &text, int &exceptioncode) { // following the IE specs. if (endTagRequirement(id()) == FORBIDDEN) { exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; return; } // IE disallows innerHTML on inline elements. I don't see why we should have this restriction, as our // dhtml engine can cope with it. Lars //if ( isInline() ) return false; switch (id()) { case ID_COL: case ID_COLGROUP: case ID_FRAMESET: case ID_HEAD: case ID_HTML: case ID_TABLE: case ID_TBODY: case ID_TFOOT: case ID_THEAD: case ID_TR: exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; return; default: break; } removeChildren(); TextImpl *t = new TextImpl(docPtr(), text.implementation()); appendChild(t, exceptioncode); } void HTMLElementImpl::addHTMLAlignment(DOMString alignment) { //qDebug("alignment is %s", alignment.string().toLatin1().constData() ); // vertical alignment with respect to the current baseline of the text // right or left means floating images int propfloat = -1; int propvalign = -1; if (strcasecmp(alignment, "absmiddle") == 0) { propvalign = CSS_VAL_MIDDLE; } else if (strcasecmp(alignment, "absbottom") == 0) { propvalign = CSS_VAL_BOTTOM; } else if (strcasecmp(alignment, "left") == 0) { propfloat = CSS_VAL_LEFT; propvalign = CSS_VAL_TOP; } else if (strcasecmp(alignment, "right") == 0) { propfloat = CSS_VAL_RIGHT; propvalign = CSS_VAL_TOP; } else if (strcasecmp(alignment, "top") == 0) { propvalign = CSS_VAL_TOP; } else if (strcasecmp(alignment, "middle") == 0) { propvalign = CSS_VAL__KHTML_BASELINE_MIDDLE; } else if (strcasecmp(alignment, "center") == 0) { propvalign = CSS_VAL_MIDDLE; } else if (strcasecmp(alignment, "bottom") == 0) { propvalign = CSS_VAL_BASELINE; } else if (strcasecmp(alignment, "texttop") == 0) { propvalign = CSS_VAL_TEXT_TOP; } if (propfloat != -1) { addCSSProperty(CSS_PROP_FLOAT, propfloat); } if (propvalign != -1) { addCSSProperty(CSS_PROP_VERTICAL_ALIGN, propvalign); } } DOMString HTMLElementImpl::contentEditable() const { document()->updateRendering(); if (!renderer()) { return "false"; } switch (renderer()->style()->userInput()) { case UI_ENABLED: return "true"; case UI_DISABLED: case UI_NONE: return "false"; default:; } return "inherit"; } void HTMLElementImpl::setContentEditable(AttributeImpl *attr) { const DOMString &enabled = attr->value(); if (enabled.isEmpty() || strcasecmp(enabled, "true") == 0) { addCSSProperty(CSS_PROP__KHTML_USER_INPUT, CSS_VAL_ENABLED); } else if (strcasecmp(enabled, "false") == 0) { addCSSProperty(CSS_PROP__KHTML_USER_INPUT, CSS_VAL_NONE); } else if (strcasecmp(enabled, "inherit") == 0) { addCSSProperty(CSS_PROP__KHTML_USER_INPUT, CSS_VAL_INHERIT); } } void HTMLElementImpl::setContentEditable(const DOMString &enabled) { if (enabled == "inherit") { int exceptionCode; removeAttribute(ATTR_CONTENTEDITABLE, exceptionCode); } else { setAttribute(ATTR_CONTENTEDITABLE, enabled.isEmpty() ? "true" : enabled); } } DOMString HTMLElementImpl::toString() const { if (!hasChildNodes()) { DOMString result = openTagStartToString(); result += ">"; if (endTagRequirement(id()) == REQUIRED) { result += ""; } return result; } return ElementImpl::toString(); } // ------------------------------------------------------------------------- HTMLGenericElementImpl::HTMLGenericElementImpl(DocumentImpl *doc, ushort i) : HTMLElementImpl(doc) { m_localName = LocalName::fromId(i); } HTMLGenericElementImpl::HTMLGenericElementImpl(DocumentImpl *doc, LocalName l) : HTMLElementImpl(doc), m_localName(l) {} HTMLGenericElementImpl::~HTMLGenericElementImpl() { } diff --git a/src/html/htmlparser.cpp b/src/html/htmlparser.cpp index 8625c2c..938e0d9 100644 --- a/src/html/htmlparser.cpp +++ b/src/html/htmlparser.cpp @@ -1,2007 +1,2007 @@ /* This file is part of the KDE libraries Copyright (C) 1997 Martin Jones (mjones@kde.org) (C) 1997 Torben Weis (weis@kde.org) (C) 1999,2001 Lars Knoll (knoll@kde.org) (C) 2000,2001 Dirk Mueller (mueller@kde.org) (C) 2003 Apple Computer, Inc. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ //---------------------------------------------------------------------------- // // KDE HTML Widget -- HTML Parser // #define PARSER_DEBUG #include "htmlparser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Turn off gnu90 inlining to avoid linker errors #undef __GNUC_STDC_INLINE__ #undef __GNUC_GNU_INLINE__ #include #undef OPTIONAL // for win32, MinGW using namespace DOM; using namespace khtml; #ifdef PARSER_DEBUG static QString getParserPrintableName(int id) { if (id >= ID_CLOSE_TAG) { return "/" + getPrintableName(id - ID_CLOSE_TAG); } else { return getPrintableName(id); } } #endif //---------------------------------------------------------------------------- /** * @internal */ class HTMLStackElem { public: HTMLStackElem(int _id, int _level, DOM::NodeImpl *_node, bool _inline_, HTMLStackElem *_next) : id(_id), level(_level), strayTableContent(false), m_inline(_inline_), node(_node), next(_next) { node->ref(); } ~HTMLStackElem() { node->deref(); } void setNode(NodeImpl *newNode) { newNode->ref(); node->deref(); node = newNode; } int id; int level; bool strayTableContent; bool m_inline; NodeImpl *node; HTMLStackElem *next; }; /** * @internal * * The parser parses tokenized input into the document, building up the * document tree. If the document is wellformed, parsing it is * straightforward. * Unfortunately, people can't write wellformed HTML documents, so the parser * has to be tolerant about errors. * * We have to take care of the following error conditions: * 1. The element being added is explicitly forbidden inside some outer tag. * In this case we should close all tags up to the one, which forbids * the element, and add it afterwards. * 2. We are not allowed to add the element directly. It could be, that * the person writing the document forgot some tag inbetween (or that the * tag inbetween is optional...) This could be the case with the following * tags: HTML HEAD BODY TBODY TR TD LI (did I forget any?) * 3. We wan't to add a block element inside to an inline element. Close all * inline elements up to the next higher block element. * 4. If this doesn't help close elements, until we are allowed to add the * element or ignore the tag. * */ KHTMLParser::KHTMLParser(KHTMLView *_parent, DocumentImpl *doc) { //qDebug() << "parser constructor"; #if SPEED_DEBUG > 0 qt.start(); #endif HTMLWidget = _parent; document = doc; blockStack = nullptr; current = nullptr; // ID_CLOSE_TAG == Num of tags forbiddenTag = new ushort[ID_CLOSE_TAG + 1]; reset(); } KHTMLParser::KHTMLParser(DOM::DocumentFragmentImpl *i, DocumentImpl *doc) { HTMLWidget = nullptr; document = doc; forbiddenTag = new ushort[ID_CLOSE_TAG + 1]; blockStack = nullptr; current = nullptr; reset(); setCurrent(i); inBody = true; } KHTMLParser::~KHTMLParser() { #if SPEED_DEBUG > 0 qDebug() << "TIME: parsing time was = " << qt.elapsed(); #endif freeBlock(); if (current) { current->deref(); } delete [] forbiddenTag; delete isindex; } void KHTMLParser::reset() { setCurrent(document); freeBlock(); // before parsing no tags are forbidden... memset(forbiddenTag, 0, (ID_CLOSE_TAG + 1)*sizeof(ushort)); inBody = false; haveFrameSet = false; haveContent = false; haveBody = false; haveTitle = false; inSelect = false; inStrayTableContent = 0; m_inline = false; form = nullptr; map = nullptr; end = false; isindex = nullptr; discard_until = 0; } void KHTMLParser::parseToken(Token *t) { if (t->tid > 2 * ID_CLOSE_TAG) { // qDebug() << "Unknown tag!! tagID = " << t->tid; return; } if (discard_until) { if (t->tid == discard_until) { discard_until = 0; } // do not skip if (discard_until || current->id() + ID_CLOSE_TAG != t->tid) { return; } } #ifdef PARSER_DEBUG qDebug() << "\n\n==> parser: processing token " << getParserPrintableName(t->tid) << "(" << t->tid << ")" - << " current = " << getParserPrintableName(current->id()) << "(" << current->id() << ")" << endl; + << " current = " << getParserPrintableName(current->id()) << "(" << current->id() << ")"; qDebug() << "inline=" << m_inline << " inBody=" << inBody << " haveFrameSet=" << haveFrameSet << " haveContent=" << haveContent; #endif // holy shit. apparently some sites use
        instead of
        // be compatible with IE and NS if (t->tid == ID_BR + ID_CLOSE_TAG && document->inCompatMode()) { t->tid -= ID_CLOSE_TAG; } if (t->tid > ID_CLOSE_TAG) { processCloseTag(t); return; } // ignore spaces, if we're not inside a paragraph or other inline code if (t->tid == ID_TEXT && t->text) { if (inBody && !skipMode() && current->id() != ID_STYLE && current->id() != ID_TITLE && current->id() != ID_SCRIPT && !t->text->containsOnlyWhitespace()) { haveContent = true; } #ifdef PARSER_DEBUG qDebug() << "length=" << t->text->l << " text='" << QString::fromRawData(t->text->s, t->text->l) << "'"; #endif } NodeImpl *n = getElement(t); // just to be sure, and to catch currently unimplemented stuff if (!n) { return; } // set attributes if (n->isElementNode() && t->tid != ID_ISINDEX) { ElementImpl *e = static_cast(n); e->setAttributeMap(t->attrs); } // if this tag is forbidden inside the current context, pop // blocks until we are allowed to add it... while (blockStack && forbiddenTag[t->tid]) { #ifdef PARSER_DEBUG qDebug() << "t->id: " << t->tid << " is forbidden :-( "; #endif popOneBlock(); } // sometimes flat doesn't make sense switch (t->tid) { case ID_SELECT: case ID_OPTION: t->flat = false; } // the tokenizer needs the feedback for space discarding if (tagPriority(t->tid) == 0) { t->flat = true; } if (!insertNode(n, t->flat)) { // we couldn't insert the node... #ifdef PARSER_DEBUG qDebug() << "insertNode failed current=" << current->id() << ", new=" << n->id() << "!"; #endif if (map == n) { #ifdef PARSER_DEBUG qDebug() << " --> resetting map!"; #endif map = nullptr; } if (form == n) { #ifdef PARSER_DEBUG qDebug() << " --> resetting form!"; #endif form = nullptr; } delete n; } } void KHTMLParser::parseDoctypeToken(DoctypeToken *t) { // Ignore any doctype after the first. TODO It should be also ignored when processing DocumentFragment if (current != document || document->doctype()) { return; } DocumentTypeImpl *doctype = new DocumentTypeImpl(document->implementation(), document, t->name, t->publicID, t->systemID); if (!t->internalSubset.isEmpty()) { doctype->setInternalSubset(t->internalSubset); } document->addChild(doctype); // Determine parse mode here // This code more or less mimics Mozilla's implementation. // // There are three possible parse modes: // COMPAT - quirks mode emulates WinIE // and NS4. CSS parsing is also relaxed in this mode, e.g., unit types can // be omitted from numbers. // ALMOST STRICT - This mode is identical to strict mode // except for its treatment of line-height in the inline box model. For // now (until the inline box model is re-written), this mode is identical // to STANDARDS mode. // STRICT - no quirks apply. Web pages will obey the specifications to // the letter. if (!document->isHTMLDocument()) { // FIXME Could document be non-HTML? return; } DOM::HTMLDocumentImpl *htmldoc = static_cast(document); if (t->name.toLower() == "html") { if (!t->internalSubset.isEmpty() || t->publicID.isEmpty()) { // Internal subsets always denote full standards, as does // a doctype without a public ID. htmldoc->changeModes(DOM::DocumentImpl::Strict, DOM::DocumentImpl::Html4); } else { // We have to check a list of public IDs to see what we // should do. QString lowerPubID = t->publicID.toLower(); QByteArray pubIDStr = lowerPubID.toLocal8Bit(); // Look up the entry in our gperf-generated table. const PubIDInfo *doctypeEntry = findDoctypeEntry(pubIDStr.constData(), t->publicID.length()); if (!doctypeEntry) { // The DOCTYPE is not in the list. Assume strict mode. // ### Doesn't make any sense, but it's what Mozilla does. htmldoc->changeModes(DOM::DocumentImpl::Strict, DOM::DocumentImpl::Html4); } else { switch ((!t->systemID.isEmpty()) ? doctypeEntry->mode_if_sysid : doctypeEntry->mode_if_no_sysid) { case PubIDInfo::eQuirks3: htmldoc->changeModes(DOM::DocumentImpl::Compat, DOM::DocumentImpl::Html3); break; case PubIDInfo::eQuirks: htmldoc->changeModes(DOM::DocumentImpl::Compat, DOM::DocumentImpl::Html4); break; case PubIDInfo::eAlmostStandards: htmldoc->changeModes(DOM::DocumentImpl::Transitional, DOM::DocumentImpl::Html4); break; default: assert(!"Unknown parse mode"); } } } } else { // Malformed doctype implies quirks mode. htmldoc->changeModes(DOM::DocumentImpl::Compat, DOM::DocumentImpl::Html3); } } static bool isTableRelatedTag(int id) { return (id == ID_TR || id == ID_TD || id == ID_TABLE || id == ID_TBODY || id == ID_TFOOT || id == ID_THEAD || id == ID_TH); } bool KHTMLParser::insertNode(NodeImpl *n, bool flat) { int id = n->id(); //
    is never allowed inside stray table content. Always pop out of the stray table content // and close up the first table, and then start the second table as a sibling. if (inStrayTableContent && id == ID_TABLE) { popBlock(ID_TABLE); } // let's be stupid and just try to insert it. // this should work if the document is wellformed #ifdef PARSER_DEBUG NodeImpl *tmp = current; #endif NodeImpl *newNode = current->addChild(n); if (newNode) { #ifdef PARSER_DEBUG qDebug() << "added " << n->nodeName().string() << " to " << tmp->nodeName().string() << ", new current=" << newNode->nodeName().string(); #endif // We allow TABLE > FORM in dtd.cpp, but do not allow the form have children in this case if (current->id() == ID_TABLE && id == ID_FORM) { flat = true; static_cast(n)->setMalformed(true); } // don't push elements without end tag on the stack if (tagPriority(id) != 0 && !flat) { #if SPEED_DEBUG < 2 if (!n->attached() && HTMLWidget) { n->attach(); } #endif if (n->isInline()) { m_inline = true; } pushBlock(id, tagPriority(id)); setCurrent(newNode); } else { #if SPEED_DEBUG < 2 if (!n->attached() && HTMLWidget) { n->attach(); } if (n->maintainsState()) { document->registerMaintainsState(n); document->attemptRestoreState(n); } n->close(); #endif if (n->isInline()) { m_inline = true; } } #if SPEED_DEBUG < 1 if (tagPriority(id) == 0 && n->renderer()) { n->renderer()->calcMinMaxWidth(); } #endif return true; } else { #ifdef PARSER_DEBUG qDebug() << "ADDING NODE FAILED!!!! current = " << current->nodeName().string() << ", new = " << n->nodeName().string(); #endif // error handling... HTMLElementImpl *e; bool handled = false; // first switch on current element for elements with optional end-tag and inline-only content switch (current->id()) { case ID_P: case ID_DT: if (!n->isInline()) { popBlock(current->id()); return insertNode(n); } break; case ID_TITLE: popBlock(current->id()); return insertNode(n); default: break; } // switch according to the element to insert switch (id) { case ID_TR: case ID_TH: case ID_TD: if (inStrayTableContent && !isTableRelatedTag(current->id())) { // pop out to the nearest enclosing table-related tag. while (blockStack && !isTableRelatedTag(current->id())) { popOneBlock(); } return insertNode(n); } break; case ID_HEAD: // ### allow not having in at all, as per HTML spec if (!current->isDocumentNode() && current->id() != ID_HTML) { return false; } break; case ID_COMMENT: if (head) { break; } case ID_META: case ID_LINK: case ID_ISINDEX: case ID_BASE: if (!head) { createHead(); } if (head) { if (head->addChild(n)) { #if SPEED_DEBUG < 2 if (!n->attached() && HTMLWidget) { n->attach(); } #endif } return true; } break; case ID_HTML: if (!current->isDocumentNode()) { if (doc()->documentElement()->id() == ID_HTML) { // we have another element.... apply attributes to existing one // make sure we don't overwrite already existing attributes NamedAttrMapImpl *map = static_cast(n)->attributes(true); NamedAttrMapImpl *bmap = static_cast(doc()->documentElement())->attributes(false); bool changed = false; for (unsigned long l = 0; map && l < map->length(); ++l) { NodeImpl::Id attrId = map->idAt(l); DOMStringImpl *attrValue = map->valueAt(l); changed = !bmap->getValue(attrId); bmap->setValue(attrId, attrValue); } if (changed) { doc()->recalcStyle(NodeImpl::Inherit); } } return false; } break; case ID_TITLE: case ID_STYLE: if (!head) { createHead(); } if (head) { DOM::NodeImpl *newNode = head->addChild(n); if (newNode) { pushBlock(id, tagPriority(id)); setCurrent(newNode); #if SPEED_DEBUG < 2 if (!n->attached() && HTMLWidget) { n->attach(); } #endif } else { #ifdef PARSER_DEBUG qDebug() << "adding style before to body failed!!!!"; #endif discard_until = ID_STYLE + ID_CLOSE_TAG; return false; } return true; } else if (inBody) { discard_until = id + ID_CLOSE_TAG; return false; } break; case ID_SCRIPT: // if we failed to insert it, go into skip mode discard_until = id + ID_CLOSE_TAG; break; case ID_BODY: if (inBody && doc()->body()) { // we have another element.... apply attributes to existing one // make sure we don't overwrite already existing attributes // some sites use ... NamedAttrMapImpl *map = static_cast(n)->attributes(true); NamedAttrMapImpl *bmap = doc()->body()->attributes(false); bool changed = false; for (unsigned long l = 0; map && l < map->length(); ++l) { NodeImpl::Id attrId = map->idAt(l); DOMStringImpl *attrValue = map->valueAt(l); if (!bmap->getValue(attrId)) { bmap->setValue(attrId, attrValue); changed = true; } } if (changed) { doc()->recalcStyle(NodeImpl::Inherit); } } else if (current->isDocumentNode()) { break; } return false; break; // the following is a hack to move non rendered elements // outside of tables. // needed for broken constructs like
    .... case ID_INPUT: { ElementImpl *e = static_cast(n); DOMString type = e->getAttribute(ATTR_TYPE); if (strcasecmp(type, "hidden") != 0) { break; } // Fall through! } case ID_TEXT: { // Don't try to fit random white-space anywhere TextImpl *t = static_cast(n); if (t->containsOnlyWhitespace()) { return false; } // ignore text inside the following elements. switch (current->id()) { case ID_SELECT: return false; default: ; // fall through!! }; break; } case ID_DL: popBlock(ID_DT); if (current->id() == ID_DL) { e = new HTMLGenericElementImpl(document, ID_DD); insertNode(e); handled = true; } break; case ID_DT: e = new HTMLDListElementImpl(document); if (insertNode(e)) { insertNode(n); return true; } break; case ID_AREA: { if (map) { map->addChild(n); #if SPEED_DEBUG < 2 if (!n->attached() && HTMLWidget) { n->attach(); } #endif handled = true; return true; } else { return false; } } case ID_THEAD: case ID_TBODY: case ID_TFOOT: case ID_CAPTION: case ID_COLGROUP: { if (isTableRelatedTag(current->id())) { while (blockStack && current->id() != ID_TABLE && isTableRelatedTag(current->id())) { popOneBlock(); } return insertNode(n); } } default: break; } // switch on the currently active element switch (current->id()) { case ID_HTML: switch (id) { case ID_SCRIPT: case ID_STYLE: case ID_META: case ID_LINK: case ID_OBJECT: case ID_EMBED: case ID_TITLE: case ID_ISINDEX: case ID_BASE: if (!head) { head = new HTMLHeadElementImpl(document); insertNode(head.get()); handled = true; } break; case ID_TEXT: { TextImpl *t = static_cast(n); if (t->containsOnlyWhitespace()) { return false; } /* Fall through to default */ } default: if (haveFrameSet) { break; } e = new HTMLBodyElementImpl(document); startBody(); insertNode(e); handled = true; break; } break; case ID_HEAD: // we can get here only if the element is not allowed in head. if (id == ID_HTML) { return false; } else { // This means the body starts here... if (haveFrameSet) { break; } popBlock(ID_HEAD); e = new HTMLBodyElementImpl(document); startBody(); insertNode(e); handled = true; } break; case ID_BODY: break; case ID_CAPTION: // Illegal content in a caption. Close the caption and try again. popBlock(ID_CAPTION); switch (id) { case ID_THEAD: case ID_TFOOT: case ID_TBODY: case ID_TR: case ID_TD: case ID_TH: return insertNode(n, flat); } break; case ID_TABLE: case ID_THEAD: case ID_TFOOT: case ID_TBODY: case ID_TR: switch (id) { case ID_TABLE: popBlock(ID_TABLE); // end the table handled = checkChild(current->id(), id, doc()->inStrictMode()); break; default: { NodeImpl *node = current; NodeImpl *parent = node->parentNode(); // A script may have removed the current node's parent from the DOM // http://bugzilla.opendarwin.org/show_bug.cgi?id=7137 // FIXME: we should do real recovery here and re-parent with the correct node. if (!parent) { return false; } NodeImpl *parentparent = parent->parentNode(); if (n->isTextNode() || (node->id() == ID_TR && (parent->id() == ID_THEAD || parent->id() == ID_TBODY || parent->id() == ID_TFOOT) && parentparent->id() == ID_TABLE) || (!checkChild(ID_TR, id) && (node->id() == ID_THEAD || node->id() == ID_TBODY || node->id() == ID_TFOOT) && parent->id() == ID_TABLE)) { node = (node->id() == ID_TABLE) ? node : ((node->id() == ID_TR) ? parentparent : parent); NodeImpl *parent = node->parentNode(); if (!parent) { return false; } int exceptioncode = 0; #ifdef PARSER_DEBUG qDebug() << "calling insertBefore(" << n->nodeName().string() << "," << node->nodeName().string() << ")"; #endif parent->insertBefore(n, node, exceptioncode); if (exceptioncode) { #ifndef PARSER_DEBUG if (!n->isTextNode()) #endif // qDebug() << "adding content before table failed.."; break; } if (n->isElementNode() && tagPriority(id) != 0 && !flat && endTagRequirement(id) != DOM::FORBIDDEN) { pushBlock(id, tagPriority(id)); setCurrent(n); inStrayTableContent++; blockStack->strayTableContent = true; } return true; } if (current->id() == ID_TR) { e = new HTMLTableCellElementImpl(document, ID_TD); } else if (current->id() == ID_TABLE) { e = new HTMLTableSectionElementImpl(document, ID_TBODY, true /* implicit */); } else { e = new HTMLTableRowElementImpl(document); } insertNode(e); handled = true; break; } // end default } // end switch break; case ID_OBJECT: discard_until = id + ID_CLOSE_TAG; return false; case ID_UL: case ID_OL: case ID_DIR: case ID_MENU: e = new HTMLLIElementImpl(document); e->addCSSProperty(CSS_PROP_LIST_STYLE_TYPE, CSS_VAL_NONE); insertNode(e); handled = true; break; case ID_FORM: popBlock(ID_FORM); handled = true; break; case ID_SELECT: if (n->isInline()) { return false; } break; case ID_P: case ID_H1: case ID_H2: case ID_H3: case ID_H4: case ID_H5: case ID_H6: if (!n->isInline()) { popBlock(current->id()); handled = true; } break; case ID_OPTION: case ID_OPTGROUP: if (id == ID_OPTGROUP) { popBlock(current->id()); handled = true; } else if (id == ID_SELECT) { // IE treats a nested select as . Let's do the same popBlock(ID_SELECT); break; } break; // head elements in the body should be ignored. case ID_ADDRESS: case ID_COLGROUP: case ID_FONT: popBlock(current->id()); handled = true; break; default: if (current->isDocumentNode()) { DocumentImpl *doc = static_cast(current); if (!doc->documentElement()) { e = new HTMLHtmlElementImpl(document); insertNode(e); handled = true; } } else if (current->isInline()) { popInlineBlocks(); handled = true; } } // if we couldn't handle the error, just rethrow the exception... if (!handled) { //qDebug() << "Exception handler failed in HTMLPArser::insertNode()"; return false; } return insertNode(n); } } NodeImpl *KHTMLParser::getElement(Token *t) { NodeImpl *n = nullptr; switch (t->tid) { case ID_HTML: n = new HTMLHtmlElementImpl(document); break; case ID_HEAD: if (!head && (current->id() == ID_HTML || current->isDocumentNode())) { head = new HTMLHeadElementImpl(document); n = head.get(); } break; case ID_BODY: // body no longer allowed if we have a frameset if (haveFrameSet) { break; } popBlock(ID_HEAD); n = new HTMLBodyElementImpl(document); haveBody = true; startBody(); break; // head elements case ID_BASE: n = new HTMLBaseElementImpl(document); break; case ID_LINK: n = new HTMLLinkElementImpl(document); break; case ID_META: n = new HTMLMetaElementImpl(document); break; case ID_STYLE: n = new HTMLStyleElementImpl(document); break; case ID_TITLE: // only one non-empty allowed if (haveTitle) { discard_until = ID_TITLE + ID_CLOSE_TAG; break; } n = new HTMLTitleElementImpl(document); // we'll set haveTitle when closing the tag break; // frames case ID_FRAME: n = new HTMLFrameElementImpl(document); break; case ID_FRAMESET: popBlock(ID_HEAD); if (inBody && !haveFrameSet && !haveContent && !haveBody) { popBlock(ID_BODY); // ### actually for IE document.body returns the now hidden "body" element // we can't implement that behavior now because it could cause too many // regressions and the headaches are not worth the work as long as there is // no site actually relying on that detail (Dirk) if (static_cast<HTMLDocumentImpl *>(document)->body()) static_cast<HTMLDocumentImpl *>(document)->body() ->addCSSProperty(CSS_PROP_DISPLAY, CSS_VAL_NONE); inBody = false; } if ((haveBody || haveContent || haveFrameSet) && current->id() == ID_HTML) { break; } n = new HTMLFrameSetElementImpl(document); haveFrameSet = true; startBody(); break; // a bit a special case, since the frame is inlined... case ID_IFRAME: n = new HTMLIFrameElementImpl(document); break; // form elements case ID_FORM: // thou shall not nest <form> - NS/IE quirk if (form) { break; } n = form = new HTMLFormElementImpl(document, false); break; case ID_BUTTON: n = new HTMLButtonElementImpl(document, form); break; case ID_FIELDSET: n = new HTMLFieldSetElementImpl(document, form); break; case ID_INPUT: if (t->attrs && KHTMLGlobal::defaultHTMLSettings()->isAdFilterEnabled() && KHTMLGlobal::defaultHTMLSettings()->isHideAdsEnabled() && !strcasecmp(t->attrs->getValue(ATTR_TYPE), "image")) { const QString url = doc()->completeURL(DOMString(t->attrs->getValue(ATTR_SRC)).trimSpaces().string()); if (KHTMLGlobal::defaultHTMLSettings()->isAdFiltered(url)) { return nullptr; } } n = new HTMLInputElementImpl(document, form); break; case ID_ISINDEX: n = handleIsindex(t); if (!inBody) { isindex = n; n = nullptr; } else { t->flat = true; } break; case ID_KEYGEN: n = new HTMLKeygenElementImpl(document, form); break; case ID_LABEL: n = new HTMLLabelElementImpl(document); break; case ID_LEGEND: n = new HTMLLegendElementImpl(document, form); break; case ID_OPTGROUP: n = new HTMLOptGroupElementImpl(document, form); break; case ID_OPTION: popOptionalBlock(ID_OPTION); n = new HTMLOptionElementImpl(document, form); break; case ID_SELECT: inSelect = true; n = new HTMLSelectElementImpl(document, form); break; case ID_TEXTAREA: n = new HTMLTextAreaElementImpl(document, form); break; // lists case ID_DL: n = new HTMLDListElementImpl(document); break; case ID_DD: popOptionalBlock(ID_DT); popOptionalBlock(ID_DD); n = new HTMLGenericElementImpl(document, t->tid); break; case ID_DT: popOptionalBlock(ID_DD); popOptionalBlock(ID_DT); n = new HTMLGenericElementImpl(document, t->tid); break; case ID_UL: { n = new HTMLUListElementImpl(document); break; } case ID_OL: { n = new HTMLOListElementImpl(document); break; } case ID_DIR: n = new HTMLDirectoryElementImpl(document); break; case ID_MENU: n = new HTMLMenuElementImpl(document); break; case ID_LI: popOptionalBlock(ID_LI); n = new HTMLLIElementImpl(document); break; // formatting elements (block) case ID_BLOCKQUOTE: n = new HTMLGenericElementImpl(document, t->tid); break; case ID_LAYER: case ID_ILAYER: n = new HTMLLayerElementImpl(document, t->tid); break; case ID_P: case ID_DIV: n = new HTMLDivElementImpl(document, t->tid); break; case ID_H1: case ID_H2: case ID_H3: case ID_H4: case ID_H5: case ID_H6: n = new HTMLGenericElementImpl(document, t->tid); break; case ID_HR: n = new HTMLHRElementImpl(document); break; case ID_PRE: case ID_XMP: case ID_PLAINTEXT: case ID_LISTING: n = new HTMLPreElementImpl(document, t->tid); break; // font stuff case ID_BASEFONT: n = new HTMLBaseFontElementImpl(document); break; case ID_FONT: n = new HTMLFontElementImpl(document); break; // ins/del case ID_DEL: case ID_INS: n = new HTMLGenericElementImpl(document, t->tid); break; // anchor case ID_A: popBlock(ID_A); n = new HTMLAnchorElementImpl(document); break; // images case ID_IMAGE: case ID_IMG: if (t->attrs && KHTMLGlobal::defaultHTMLSettings()->isAdFilterEnabled() && KHTMLGlobal::defaultHTMLSettings()->isHideAdsEnabled()) { const QString url = doc()->completeURL(DOMString(t->attrs->getValue(ATTR_SRC)).trimSpaces().string()); if (KHTMLGlobal::defaultHTMLSettings()->isAdFiltered(url)) { return nullptr; } } n = new HTMLImageElementImpl(document, form); break; case ID_CANVAS: n = new HTMLCanvasElementImpl(document); break; case ID_MAP: map = new HTMLMapElementImpl(document); n = map; break; case ID_AREA: n = new HTMLAreaElementImpl(document); break; // objects, applets and scripts case ID_APPLET: n = new HTMLAppletElementImpl(document); break; case ID_EMBED: n = new HTMLEmbedElementImpl(document); break; case ID_OBJECT: n = new HTMLObjectElementImpl(document); break; case ID_PARAM: n = new HTMLParamElementImpl(document); break; case ID_SCRIPT: { HTMLScriptElementImpl *scriptElement = new HTMLScriptElementImpl(document); scriptElement->setCreatedByParser(true); n = scriptElement; break; } // media case ID_AUDIO: n = new HTMLAudioElement(document); break; case ID_VIDEO: n = new HTMLVideoElement(document); break; case ID_SOURCE: n = new HTMLSourceElement(document); break; // tables case ID_TABLE: n = new HTMLTableElementImpl(document); break; case ID_CAPTION: n = new HTMLTableCaptionElementImpl(document); break; case ID_COLGROUP: case ID_COL: n = new HTMLTableColElementImpl(document, t->tid); break; case ID_TR: popBlock(ID_TR); n = new HTMLTableRowElementImpl(document); break; case ID_TD: case ID_TH: popBlock(ID_TH); popBlock(ID_TD); n = new HTMLTableCellElementImpl(document, t->tid); break; case ID_TBODY: case ID_THEAD: case ID_TFOOT: popBlock(ID_THEAD); popBlock(ID_TBODY); popBlock(ID_TFOOT); n = new HTMLTableSectionElementImpl(document, t->tid, false); break; // inline elements case ID_BR: n = new HTMLBRElementImpl(document); break; case ID_Q: n = new HTMLGenericElementImpl(document, t->tid); break; // elements with no special representation in the DOM // block: case ID_ADDRESS: case ID_CENTER: n = new HTMLGenericElementImpl(document, t->tid); break; // inline // %fontstyle case ID_TT: case ID_U: case ID_B: case ID_I: case ID_S: case ID_STRIKE: case ID_BIG: case ID_SMALL: // %phrase case ID_EM: case ID_STRONG: case ID_DFN: case ID_CODE: case ID_SAMP: case ID_KBD: case ID_VAR: case ID_CITE: case ID_ABBR: case ID_ACRONYM: // %special case ID_SUB: case ID_SUP: case ID_SPAN: case ID_WBR: case ID_NOBR: if (t->tid == ID_NOBR || t->tid == ID_WBR) { popOptionalBlock(t->tid); } case ID_BDO: n = new HTMLGenericElementImpl(document, t->tid); break; // these are special, and normally not rendered case ID_NOEMBED: if (!t->flat) { n = new HTMLGenericElementImpl(document, t->tid); discard_until = ID_NOEMBED + ID_CLOSE_TAG; } return n; case ID_NOFRAMES: if (!t->flat) { n = new HTMLGenericElementImpl(document, t->tid); discard_until = ID_NOFRAMES + ID_CLOSE_TAG; } return n; case ID_NOSCRIPT: if (!t->flat) { n = new HTMLGenericElementImpl(document, t->tid); if (HTMLWidget && HTMLWidget->part()->jScriptEnabled()) { discard_until = ID_NOSCRIPT + ID_CLOSE_TAG; } } return n; case ID_NOLAYER: // discard_until = ID_NOLAYER + ID_CLOSE_TAG; return nullptr; break; case ID_MARQUEE: n = new HTMLMarqueeElementImpl(document); break; // text case ID_TEXT: // qDebug() << "ID_TEXT: \"" << DOMString(t->text).string() << "\""; n = new TextImpl(document, t->text); break; case ID_COMMENT: n = new CommentImpl(document, t->text); break; default: n = new HTMLGenericElementImpl(document, t->tid); break; // qDebug() << "Unknown tag " << t->tid << "!"; } return n; } void KHTMLParser::processCloseTag(Token *t) { // FIXME: the below only behaves according to "in body" insertion mode (HTML5 8.2.5.10) // - might need fixing when we have other insertion modes. switch (t->tid) { case ID_HTML+ID_CLOSE_TAG: case ID_BODY+ID_CLOSE_TAG: // we never trust those close tags, since stupid webpages close // them prematurely return; case ID_FORM+ID_CLOSE_TAG: // needs additional error checking. See spec. form = nullptr; if (!isElementInScope(ID_FORM)) { // Parse error. Ignore. return; } // this one is to get the right style on the body element break; case ID_MAP+ID_CLOSE_TAG: map = nullptr; break; case ID_SELECT+ID_CLOSE_TAG: inSelect = false; break; case ID_TITLE+ID_CLOSE_TAG: // Set haveTitle only if <title> isn't empty if (current->firstChild()) { haveTitle = true; } break; case ID_P+ID_CLOSE_TAG: if (!isElementInScope(ID_P)) { // Parse error. Handle as if <p> had been seen. t->tid = ID_P; parseToken(t); popBlock(ID_P); return; } break; case ID_ADDRESS+ID_CLOSE_TAG: // case ID_ARTICLE+ID_CLOSE_TAG: case ID_BLOCKQUOTE+ID_CLOSE_TAG: case ID_CENTER+ID_CLOSE_TAG: // case ID_DATAGRID+ID_CLOSE_TAG: // case ID_DETAILS+ID_CLOSE_TAG: // case ID_DIALOG+ID_CLOSE_TAG: case ID_DIR+ID_CLOSE_TAG: case ID_DIV+ID_CLOSE_TAG: case ID_DL+ID_CLOSE_TAG: case ID_FIELDSET+ID_CLOSE_TAG: // case ID_FIGURE+ID_CLOSE_TAG: // case ID_FOOTER+ID_CLOSE_TAG: // case ID_HEADER+ID_CLOSE_TAG: case ID_LISTING+ID_CLOSE_TAG: case ID_MENU+ID_CLOSE_TAG: // case ID_NAV+ID_CLOSE_TAG: case ID_OL+ID_CLOSE_TAG: case ID_PRE+ID_CLOSE_TAG: // case ID_SECTION+ID_CLOSE_TAG: case ID_UL+ID_CLOSE_TAG: case ID_DD+ID_CLOSE_TAG: case ID_DT+ID_CLOSE_TAG: case ID_LI+ID_CLOSE_TAG: case ID_APPLET+ID_CLOSE_TAG: // those four should also "Clear the list of active formatting elements case ID_BUTTON+ID_CLOSE_TAG: // up to the last marker." whenever we implement adoption agency. case ID_MARQUEE+ID_CLOSE_TAG: case ID_OBJECT+ID_CLOSE_TAG: case ID_HEAD+ID_CLOSE_TAG: // ### according to HTML5, should be treated as 'Any other end tag' // We'll do that when proper 'Any other end tag' handling is implemented. // In the meantime, test scoping at least (#170694) if (!isElementInScope(t->tid - ID_CLOSE_TAG)) { // Parse error. Ignore token. return; } break; case ID_H1: case ID_H2: case ID_H3: case ID_H4: case ID_H5: case ID_H6: if (!isHeadingInScope()) { // Parse error. Ignore token. return; } break; case ID_A: // Formatting elements - will need special handling - cf. HTML5 "adoption agency algorithm" case ID_B: // meant to replace the "residual style" handling we have now. case ID_BIG: case ID_CODE: case ID_EM: case ID_FONT: case ID_I: case ID_NOBR: case ID_S: case ID_SMALL: case ID_STRIKE: case ID_STRONG: case ID_TT: case ID_U: break; default: // otherTag = true; // FIXME: implement 'Any other end tag' handling break; } #ifdef PARSER_DEBUG qDebug() << "added the following children to " << current->nodeName().string(); NodeImpl *child = current->firstChild(); while (child != 0) { qDebug() << " " << child->nodeName().string(); child = child->nextSibling(); } #endif generateImpliedEndTags(t->tid - ID_CLOSE_TAG); popBlock(t->tid - ID_CLOSE_TAG); #ifdef PARSER_DEBUG qDebug() << "closeTag --> current = " << current->nodeName().string(); #endif } bool KHTMLParser::isResidualStyleTag(int _id) { switch (_id) { case ID_A: case ID_B: case ID_BIG: case ID_EM: case ID_FONT: case ID_I: case ID_NOBR: case ID_S: case ID_SMALL: case ID_STRIKE: case ID_STRONG: case ID_TT: case ID_U: case ID_DFN: case ID_CODE: case ID_SAMP: case ID_KBD: case ID_VAR: case ID_DEL: case ID_INS: return true; default: return false; } } bool KHTMLParser::isAffectedByResidualStyle(int _id) { if (isResidualStyleTag(_id)) { return true; } switch (_id) { case ID_P: case ID_DIV: case ID_BLOCKQUOTE: case ID_ADDRESS: case ID_H1: case ID_H2: case ID_H3: case ID_H4: case ID_H5: case ID_H6: case ID_CENTER: case ID_UL: case ID_OL: case ID_LI: case ID_DL: case ID_DT: case ID_DD: case ID_PRE: case ID_LISTING: return true; default: return false; } } void KHTMLParser::handleResidualStyleCloseTagAcrossBlocks(HTMLStackElem *elem) { // Find the element that crosses over to a higher level. // ### For now, if there is more than one, we will only make sure we close the residual style. int exceptionCode = 0; HTMLStackElem *curr = blockStack; HTMLStackElem *maxElem = nullptr; HTMLStackElem *endElem = nullptr; HTMLStackElem *prev = nullptr; HTMLStackElem *prevMaxElem = nullptr; bool advancedResidual = false; // ### if set we only close the residual style while (curr && curr != elem) { if (curr->level > elem->level) { if (!isAffectedByResidualStyle(curr->id)) { return; } if (maxElem) { advancedResidual = true; } else { endElem = curr; } maxElem = curr; prevMaxElem = prev; } prev = curr; curr = curr->next; } if (!curr || !maxElem) { return; } NodeImpl *residualElem = prev->node; NodeImpl *blockElem = prevMaxElem ? prevMaxElem->node : current; RefPtr<NodeImpl> parentElem = elem->node; // Check to see if the reparenting that is going to occur is allowed according to the DOM. // FIXME: We should either always allow it or perform an additional fixup instead of // just bailing here. // Example: <p><font><center>blah</font></center></p> isn't doing a fixup right now. if (!parentElem->childAllowed(blockElem)) { return; } if (maxElem->node->parentNode() != elem->node && !advancedResidual) { // Walk the stack and remove any elements that aren't residual style tags. These // are basically just being closed up. Example: // <font><span>Moo<p>Goo</font></p>. // In the above example, the <span> doesn't need to be reopened. It can just close. HTMLStackElem *currElem = maxElem->next; HTMLStackElem *prevElem = maxElem; while (currElem != elem) { HTMLStackElem *nextElem = currElem->next; if (!isResidualStyleTag(currElem->id)) { prevElem->next = nextElem; prevElem->setNode(currElem->node); delete currElem; } else { prevElem = currElem; } currElem = nextElem; } // We have to reopen residual tags in between maxElem and elem. An example of this case s: // <font><i>Moo<p>Foo</font>. // In this case, we need to transform the part before the <p> into: // <font><i>Moo</i></font><i> // so that the <i> will remain open. This involves the modification of elements // in the block stack. // This will also affect how we ultimately reparent the block, since we want it to end up // under the reopened residual tags (e.g., the <i> in the above example.) RefPtr<NodeImpl> prevNode = nullptr; RefPtr<NodeImpl> currNode = nullptr; currElem = maxElem; while (currElem->node != residualElem) { if (isResidualStyleTag(currElem->node->id())) { // Create a clone of this element. currNode = currElem->node->cloneNode(false); currElem->node->close(); removeForbidden(currElem->id, forbiddenTag); // Change the stack element's node to point to the clone. currElem->setNode(currNode.get()); // Attach the previous node as a child of this new node. if (prevNode) { currNode->appendChild(prevNode.get(), exceptionCode); } else { // The new parent for the block element is going to be the innermost clone. parentElem = currNode; } prevNode = currNode; } currElem = currElem->next; } // Now append the chain of new residual style elements if one exists. if (prevNode) { elem->node->appendChild(prevNode.get(), exceptionCode); } } // We need to make a clone of |residualElem| and place it just inside |blockElem|. // All content of |blockElem| is reparented to be under this clone. We then // reparent |blockElem| using real DOM calls so that attachment/detachment will // be performed to fix up the rendering tree. // So for this example: <b>...<p>Foo</b>Goo</p> // The end result will be: <b>...</b><p><b>Foo</b>Goo</p> // // Step 1: Remove |blockElem| from its parent, doing a batch detach of all the kids. SharedPtr<NodeImpl> guard(blockElem); blockElem->parentNode()->removeChild(blockElem, exceptionCode); if (!advancedResidual) { // Step 2: Clone |residualElem|. RefPtr<NodeImpl> newNode = residualElem->cloneNode(false); // Shallow clone. We don't pick up the same kids. // Step 3: Place |blockElem|'s children under |newNode|. Remove all of the children of |blockElem| // before we've put |newElem| into the document. That way we'll only do one attachment of all // the new content (instead of a bunch of individual attachments). NodeImpl *currNode = blockElem->firstChild(); while (currNode) { NodeImpl *nextNode = currNode->nextSibling(); SharedPtr<NodeImpl> guard(currNode); //Protect from deletion while moving blockElem->removeChild(currNode, exceptionCode); newNode->appendChild(currNode, exceptionCode); currNode = nextNode; // TODO - To be replaced. // Re-register form elements with currently active form, step 1 will have removed them if (form && currNode && currNode->isGenericFormElement()) { HTMLGenericFormElementImpl *e = static_cast<HTMLGenericFormElementImpl *>(currNode); form->registerFormElement(e); } } // Step 4: Place |newNode| under |blockElem|. |blockElem| is still out of the document, so no // attachment can occur yet. blockElem->appendChild(newNode.get(), exceptionCode); } // Step 5: Reparent |blockElem|. Now the full attachment of the fixed up tree takes place. parentElem->appendChild(blockElem, exceptionCode); // Step 6: Elide |elem|, since it is effectively no longer open. Also update // the node associated with the previous stack element so that when it gets popped, // it doesn't make the residual element the next current node. HTMLStackElem *currElem = maxElem; HTMLStackElem *prevElem = nullptr; while (currElem != elem) { prevElem = currElem; currElem = currElem->next; } prevElem->next = elem->next; prevElem->setNode(elem->node); delete elem; // Step 7: Reopen intermediate inlines, e.g., <b><p><i>Foo</b>Goo</p>. // In the above example, Goo should stay italic. curr = blockStack; HTMLStackElem *residualStyleStack = nullptr; while (curr && curr != endElem) { // We will actually schedule this tag for reopening // after we complete the close of this entire block. NodeImpl *currNode = current; if (isResidualStyleTag(curr->id)) { // We've overloaded the use of stack elements and are just reusing the // struct with a slightly different meaning to the variables. Instead of chaining // from innermost to outermost, we build up a list of all the tags we need to reopen // from the outermost to the innermost, i.e., residualStyleStack will end up pointing // to the outermost tag we need to reopen. // We also set curr->node to be the actual element that corresponds to the ID stored in // curr->id rather than the node that you should pop to when the element gets pulled off // the stack. popOneBlock(false); curr->setNode(currNode); curr->next = residualStyleStack; residualStyleStack = curr; } else { popOneBlock(); } curr = blockStack; } reopenResidualStyleTags(residualStyleStack, nullptr); // FIXME: Deal with stray table content some day // if it becomes necessary to do so. } void KHTMLParser::reopenResidualStyleTags(HTMLStackElem *elem, DOM::NodeImpl *malformedTableParent) { // Loop for each tag that needs to be reopened. while (elem) { // Create a shallow clone of the DOM node for this element. RefPtr<NodeImpl> newNode = elem->node->cloneNode(false); // Append the new node. In the malformed table case, we need to insert before the table, // which will be the last child. int exceptionCode = 0; if (malformedTableParent) { malformedTableParent->insertBefore(newNode.get(), malformedTableParent->lastChild(), exceptionCode); } else { current->appendChild(newNode.get(), exceptionCode); } // FIXME: Is it really OK to ignore the exceptions here? // Now push a new stack element for this node we just created. pushBlock(elem->id, elem->level); // Set our strayTableContent boolean if needed, so that the reopened tag also knows // that it is inside a malformed table. blockStack->strayTableContent = malformedTableParent != nullptr; if (blockStack->strayTableContent) { inStrayTableContent++; } // Clear our malformed table parent variable. malformedTableParent = nullptr; // Update |current| manually to point to the new node. setCurrent(newNode.get()); // Advance to the next tag that needs to be reopened. HTMLStackElem *next = elem->next; delete elem; elem = next; } } void KHTMLParser::pushBlock(int _id, int _level) { HTMLStackElem *Elem = new HTMLStackElem(_id, _level, current, m_inline, blockStack); blockStack = Elem; addForbidden(_id, forbiddenTag); } void KHTMLParser::generateImpliedEndTags(int _id) { HTMLStackElem *Elem = blockStack; int level = tagPriority(_id); while (Elem && Elem->id != _id) { HTMLStackElem *NextElem = Elem->next; if (endTagRequirement(Elem->id) == DOM::OPTIONAL && Elem->level <= level) { popOneBlock(); } else { break; } Elem = NextElem; } } void KHTMLParser::popOptionalBlock(int _id) { bool found = false; HTMLStackElem *Elem = blockStack; int level = tagPriority(_id); while (Elem) { if (Elem->id == _id) { found = true; break; } if (Elem->level > level || (endTagRequirement(Elem->id) != DOM::OPTIONAL && !isResidualStyleTag(Elem->id))) { break; } Elem = Elem->next; } if (found) { generateImpliedEndTags(_id); popBlock(_id); } } bool KHTMLParser::isElementInScope(int _id) { // HTML5 8.2.3.2 HTMLStackElem *Elem = blockStack; while (Elem && Elem->id != _id) { if (DOM::checkIsScopeBoundary(Elem->id)) { return false; } Elem = Elem->next; } return Elem; } bool KHTMLParser::isHeadingInScope() { HTMLStackElem *Elem = blockStack; while (Elem && (Elem->id < ID_H1 || Elem->id > ID_H6)) { if (DOM::checkIsScopeBoundary(Elem->id)) { return false; } Elem = Elem->next; } return Elem; } void KHTMLParser::popBlock(int _id) { HTMLStackElem *Elem = blockStack; int maxLevel = 0; #ifdef PARSER_DEBUG qDebug() << "popBlock(" << getParserPrintableName(_id) << ")"; while (Elem) { qDebug() << " > " << getParserPrintableName(Elem->id); Elem = Elem->next; } Elem = blockStack; #endif while (Elem && (Elem->id != _id)) { if (maxLevel < Elem->level) { maxLevel = Elem->level; } Elem = Elem->next; } if (!Elem) { return; } if (maxLevel > Elem->level) { // We didn't match because the tag is in a different scope, e.g., // <b><p>Foo</b>. Try to correct the problem. if (!isResidualStyleTag(_id)) { return; } return handleResidualStyleCloseTagAcrossBlocks(Elem); } bool isAffectedByStyle = isAffectedByResidualStyle(Elem->id); HTMLStackElem *residualStyleStack = nullptr; NodeImpl *malformedTableParent = nullptr; Elem = blockStack; while (Elem) { if (Elem->id == _id) { int strayTable = inStrayTableContent; popOneBlock(); Elem = nullptr; // This element was the root of some malformed content just inside an implicit or // explicit <tbody> or <tr>. // If we end up needing to reopen residual style tags, the root of the reopened chain // must also know that it is the root of malformed content inside a <tbody>/<tr>. if (strayTable && (inStrayTableContent < strayTable) && residualStyleStack) { NodeImpl *curr = current; while (curr && curr->id() != ID_TABLE) { curr = curr->parentNode(); } malformedTableParent = curr ? curr->parentNode() : nullptr; } } else { // Schedule this tag for reopening // after we complete the close of this entire block. NodeImpl *currNode = current; if (isAffectedByStyle && isResidualStyleTag(Elem->id)) { // We've overloaded the use of stack elements and are just reusing the // struct with a slightly different meaning to the variables. Instead of chaining // from innermost to outermost, we build up a list of all the tags we need to reopen // from the outermost to the innermost, i.e., residualStyleStack will end up pointing // to the outermost tag we need to reopen. // We also set Elem->node to be the actual element that corresponds to the ID stored in // Elem->id rather than the node that you should pop to when the element gets pulled off // the stack. popOneBlock(false); Elem->next = residualStyleStack; Elem->setNode(currNode); residualStyleStack = Elem; } else { popOneBlock(); } Elem = blockStack; } } reopenResidualStyleTags(residualStyleStack, malformedTableParent); } void KHTMLParser::popOneBlock(bool delBlock) { HTMLStackElem *Elem = blockStack; // we should never get here, but some bad html might cause it. #ifndef PARSER_DEBUG if (!Elem) { return; } #else qDebug() << "popping block: " << getParserPrintableName(Elem->id) << "(" << Elem->id << ")"; #endif #if SPEED_DEBUG < 1 if ((Elem->node != current)) { if (current->maintainsState() && document) { document->registerMaintainsState(current); document->attemptRestoreState(current); } current->close(); } #endif removeForbidden(Elem->id, forbiddenTag); blockStack = Elem->next; // we only set inline to false, if the element we close is a block level element. // This helps getting cases as <p><b>bla</b> <b>bla</b> right. m_inline = Elem->m_inline; if (current->id() == ID_FORM && form && inStrayTableContent) { form->setMalformed(true); } setCurrent(Elem->node); if (Elem->strayTableContent) { inStrayTableContent--; } if (delBlock) { delete Elem; } } void KHTMLParser::popInlineBlocks() { while (blockStack && current->isInline() && current->id() != ID_FONT) { popOneBlock(); } } void KHTMLParser::freeBlock() { while (blockStack) { popOneBlock(); } blockStack = nullptr; } void KHTMLParser::createHead() { if (head || !doc()->documentElement()) { return; } head = new HTMLHeadElementImpl(document); HTMLElementImpl *body = doc()->body(); int exceptioncode = 0; doc()->documentElement()->insertBefore(head.get(), body, exceptioncode); if (exceptioncode) { #ifdef PARSER_DEBUG qDebug() << "creation of head failed!!!!:" << exceptioncode; #endif delete head.get(); head = nullptr; } // If the body does not exist yet, then the <head> should be pushed as the current block. if (head && !body) { pushBlock(head->id(), tagPriority(head->id())); setCurrent(head.get()); } } NodeImpl *KHTMLParser::handleIsindex(Token *t) { NodeImpl *n; HTMLFormElementImpl *myform = form; if (!myform) { myform = new HTMLFormElementImpl(document, true); n = myform; } else { n = new HTMLDivElementImpl(document, ID_DIV); } NodeImpl *child = new HTMLHRElementImpl(document); n->addChild(child); DOMStringImpl *a = t->attrs ? t->attrs->getValue(ATTR_PROMPT) : nullptr; DOMString text = i18n("This is a searchable index. Enter search keywords: "); if (a) { text = a; } child = new TextImpl(document, text.implementation()); n->addChild(child); child = new HTMLIsIndexElementImpl(document, myform); static_cast<ElementImpl *>(child)->setAttribute(ATTR_TYPE, "khtml_isindex"); n->addChild(child); child = new HTMLHRElementImpl(document); n->addChild(child); return n; } void KHTMLParser::startBody() { if (inBody) { return; } inBody = true; if (isindex) { insertNode(isindex, true /* don't decend into this node */); isindex = nullptr; } } diff --git a/src/html/htmltokenizer.cpp b/src/html/htmltokenizer.cpp index b6b1e7e..6935b4c 100644 --- a/src/html/htmltokenizer.cpp +++ b/src/html/htmltokenizer.cpp @@ -1,2187 +1,2187 @@ /* This file is part of the KDE libraries Copyright (C) 1997 Martin Jones (mjones@kde.org) (C) 1997 Torben Weis (weis@kde.org) (C) 1998 Waldo Bastian (bastian@kde.org) (C) 1999 Lars Knoll (knoll@kde.org) (C) 1999 Antti Koivisto (koivisto@kde.org) (C) 2001-2003 Dirk Mueller (mueller@kde.org) (C) 2004-2008 Apple Computer, Inc. (C) 2006-2008 Germain Garand (germain@ebooksfrance.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ //---------------------------------------------------------------------------- // // KDE HTML Widget - Tokenizers // #define TOKEN_DEBUG 1 // #define TOKEN_DEBUG 2 #include "htmltokenizer.h" #include "html_documentimpl.h" #include "htmlparser.h" #include "dtd.h" #include <misc/loader.h> #include <khtmlview.h> #include <khtml_part.h> #include <xml/dom_docimpl.h> #include <ecma/kjs_proxy.h> #include <kcharsets.h> #include <ctype.h> #include <assert.h> #include <QtCore/QVariant> #include <QDebug> #include <stdlib.h> #include "kentities_p.h" #include "htmlprospectivetokenizer.h" #define PROSPECTIVE_TOKENIZER_ENABLED 1 using namespace khtml; static const QChar commentStart [] = { '<', '!', '-', '-', QChar::Null }; static const char doctypeStart [] = "<!doctype"; static const char publicStart [] = "public"; static const char systemStart [] = "system"; static const char scriptEnd [] = "</script"; static const char xmpEnd [] = "</xmp"; static const char styleEnd [] = "</style"; static const char textareaEnd [] = "</textarea"; static const char titleEnd [] = "</title"; #ifndef NDEBUG static const int sTokenizerChunkSize = 2048; static const int sTokenizerFastYieldDelay = 220; static const int sTokenizerYieldDelay = 650; #else static const int sTokenizerChunkSize = 4096; static const int sTokenizerFastYieldDelay = 180; static const int sTokenizerYieldDelay = 450; #endif #define KHTML_ALLOC_QCHAR_VEC( N ) (QChar*) malloc( sizeof(QChar)*( N ) ) #define KHTML_REALLOC_QCHAR_VEC(P, N ) (QChar*) realloc(P, sizeof(QChar)*( N )) #define KHTML_DELETE_QCHAR_VEC( P ) free((char*)( P )) // Full support for MS Windows extensions to Latin-1. // Technically these extensions should only be activated for pages // marked "windows-1252" or "cp1252", but // in the standard Microsoft way, these extensions infect hundreds of thousands // of web pages. Note that people with non-latin-1 Microsoft extensions // are SOL. // // See: http://www.microsoft.com/globaldev/reference/WinCP.asp // http://www.bbsinc.com/iso8859.html // http://www.obviously.com/ // // There may be better equivalents #if 0 #define fixUpChar(x) #else #define fixUpChar(x) \ switch ((x).unicode()) \ { \ case 0x80: (x) = 0x20ac; break; \ case 0x82: (x) = 0x201a; break; \ case 0x83: (x) = 0x0192; break; \ case 0x84: (x) = 0x201e; break; \ case 0x85: (x) = 0x2026; break; \ case 0x86: (x) = 0x2020; break; \ case 0x87: (x) = 0x2021; break; \ case 0x88: (x) = 0x02C6; break; \ case 0x89: (x) = 0x2030; break; \ case 0x8A: (x) = 0x0160; break; \ case 0x8b: (x) = 0x2039; break; \ case 0x8C: (x) = 0x0152; break; \ case 0x8E: (x) = 0x017D; break; \ case 0x91: (x) = 0x2018; break; \ case 0x92: (x) = 0x2019; break; \ case 0x93: (x) = 0x201C; break; \ case 0x94: (x) = 0X201D; break; \ case 0x95: (x) = 0x2022; break; \ case 0x96: (x) = 0x2013; break; \ case 0x97: (x) = 0x2014; break; \ case 0x98: (x) = 0x02DC; break; \ case 0x99: (x) = 0x2122; break; \ case 0x9A: (x) = 0x0161; break; \ case 0x9b: (x) = 0x203A; break; \ case 0x9C: (x) = 0x0153; break; \ case 0x9E: (x) = 0x017E; break; \ case 0x9F: (x) = 0x0178; break; \ default: break; \ } #endif // ---------------------------------------------------------------------------- HTMLTokenizer::HTMLTokenizer(DOM::DocumentImpl *_doc, KHTMLView *_view) { view = _view; buffer = nullptr; rawContent = nullptr; rawContentSize = rawContentMaxSize = rawContentResync = rawContentSinceLastEntity = 0; charsets = KCharsets::charsets(); parser = new KHTMLParser(_view, _doc); m_executingScript = 0; m_externalScriptsTimerId = 0; m_tokenizerYieldDelay = sTokenizerFastYieldDelay; m_yieldTimer = 0; m_prospectiveTokenizer = nullptr; onHold = false; m_documentTokenizer = true; m_hasScriptsWaitingForStylesheets = false; reset(); } HTMLTokenizer::HTMLTokenizer(DOM::DocumentImpl *_doc, DOM::DocumentFragmentImpl *i) { view = nullptr; buffer = nullptr; rawContent = nullptr; rawContentSize = rawContentMaxSize = rawContentResync = rawContentSinceLastEntity = 0; charsets = KCharsets::charsets(); parser = new KHTMLParser(i, _doc); m_executingScript = 0; m_externalScriptsTimerId = 0; m_tokenizerYieldDelay = sTokenizerFastYieldDelay; m_yieldTimer = 0; m_prospectiveTokenizer = nullptr; onHold = false; m_documentTokenizer = false; m_hasScriptsWaitingForStylesheets = false; reset(); } void HTMLTokenizer::setNormalYieldDelay() { m_tokenizerYieldDelay = sTokenizerYieldDelay; } void HTMLTokenizer::reset() { assert(m_executingScript == 0); Q_ASSERT(onHold == false); m_abort = false; while (!cachedScript.isEmpty()) { cachedScript.dequeue()->deref(this); } if (buffer) { KHTML_DELETE_QCHAR_VEC(buffer); } buffer = dest = nullptr; size = 0; if (rawContent) { KHTML_DELETE_QCHAR_VEC(rawContent); } rawContent = nullptr; rawContentSize = rawContentMaxSize = rawContentResync = 0; if (m_yieldTimer > 0) { killTimer(m_yieldTimer); m_yieldTimer = 0; } if (m_externalScriptsTimerId > 0) { killTimer(m_externalScriptsTimerId); m_externalScriptsTimerId = 0; } currToken.reset(); doctypeToken.reset(); javascript = false; } void HTMLTokenizer::begin() { m_executingScript = 0; onHold = false; reset(); size = 254; buffer = KHTML_ALLOC_QCHAR_VEC(255); dest = buffer; tag = NoTag; pending = NonePending; discard = NoneDiscard; pre = false; prePos = 0; plaintext = false; xmp = false; processingInstruction = false; script = false; escaped = false; style = false; skipLF = false; select = false; comment = false; doctype = false; doctypeComment = NoDoctypeComment; doctypeAllowComment = false; server = false; textarea = false; title = false; startTag = false; tquote = NoQuote; searchCount = 0; doctypeSearchCount = 0; doctypeSecondarySearchCount = 0; Entity = NoEntity; noMoreData = false; brokenComments = false; brokenServer = false; lineno = 0; scriptStartLineno = 0; tagStartLineno = 0; } void HTMLTokenizer::processListing(TokenizerString list) { bool old_pre = pre; // This function adds the listing 'list' as // preformatted text-tokens to the token-collection // thereby converting TABs. if (!style) { pre = true; } prePos = 0; while (!list.isEmpty()) { checkBuffer(3 * TAB_SIZE); if (skipLF && (list->unicode() != '\n')) { skipLF = false; } if (skipLF) { skipLF = false; ++list; } else if ((list->unicode() == '\n') || (list->unicode() == '\r')) { if (discard == LFDiscard) { // Ignore this LF discard = NoneDiscard; // We have discarded 1 LF } else { // Process this LF if (pending) { addPending(); } // we used to do it not at all and we want to have // it fixed for textarea. So here we are if (textarea) { prePos++; *dest++ = *list; } else { pending = LFPending; } } /* Check for MS-DOS CRLF sequence */ if (list->unicode() == '\r') { skipLF = true; } ++list; } else if ((list->unicode() == ' ') || (list->unicode() == '\t')) { if (pending) { addPending(); } if (*list == ' ') { pending = SpacePending; } else { pending = TabPending; } ++list; } else { discard = NoneDiscard; if (pending) { addPending(); } prePos++; *dest++ = *list; ++list; } } if ((pending == SpacePending) || (pending == TabPending)) { addPending(); } else { pending = NonePending; } prePos = 0; pre = old_pre; } void HTMLTokenizer::parseRawContent(TokenizerString &src) { // The 'raw content' mode is a very lax tokenizing mode // that will absorb anything but the exact closing tag // that made us enter this mode, *except* if it inside a comment. // // Any other tag or comment will be passed verbatim to the parser as part // of the content. It is used for script, style, and a few others. // assert(textarea || title || !Entity); assert(!tag); assert(xmp + textarea + title + style + script == 1); if (script) { scriptStartLineno = lineno + src.lineCount(); } if (comment) { parseComment(src); } while (!src.isEmpty()) { checkRawContentBuffer(); unsigned char ch = src->toLatin1(); if (!rawContentResync && !brokenComments && !xmp && ch == '-' && rawContentSize >= 3 && ((!textarea && !title) || rawContentSinceLastEntity >= 3) && !src.escaped() && QString::fromRawData(rawContent + rawContentSize - 3, 3) == "<!-") { comment = true; rawContent[ rawContentSize++ ] = ch; ++src; parseComment(src); continue; } if (rawContentResync && !tquote && (ch == '>')) { ++src; rawContentSize = rawContentResync - 1; rawContentResync = 0; rawContent[ rawContentSize ] = rawContent[ rawContentSize + 1 ] = 0; if (script) { scriptHandler(); } else { processListing(TokenizerString(rawContent, rawContentSize)); processToken(); if (style) { currToken.tid = ID_STYLE + ID_CLOSE_TAG; } else if (textarea) { currToken.tid = ID_TEXTAREA + ID_CLOSE_TAG; } else if (title) { currToken.tid = ID_TITLE + ID_CLOSE_TAG; } else if (xmp) { currToken.tid = ID_XMP + ID_CLOSE_TAG; } processToken(); script = style = textarea = title = xmp = false; tquote = NoQuote; rawContentSize = rawContentResync = 0; } return; } // possible end of tagname, lets check. if (!rawContentResync && !escaped && !src.escaped() && (ch == '>' || ch == '/' || ch <= ' ') && ch && rawContentSize >= searchStopperLen && ((!textarea && !title) || rawContentSinceLastEntity >= searchStopperLen) && QString::compare(QString::fromRawData(rawContent + rawContentSize - searchStopperLen, searchStopperLen), QLatin1String(searchStopper), Qt::CaseInsensitive) == 0) { // the purpose of rawContentResync is to look for an end tag that could possibly be of the form: // </endtag junk="more junk>\"><>" > // IOW, once the '</endtag' sequence has been found, the rest of the tag must still be validated, // so this micro-tokenizer switches to rawContentResync state until '>' is finally found. rawContentResync = rawContentSize - searchStopperLen + 1; tquote = NoQuote; continue; } if (rawContentResync && !escaped) { if (ch == '\"') { tquote = (tquote == NoQuote) ? DoubleQuote : ((tquote == SingleQuote) ? SingleQuote : NoQuote); } else if (ch == '\'') { tquote = (tquote == NoQuote) ? SingleQuote : (tquote == DoubleQuote) ? DoubleQuote : NoQuote; } else if (tquote != NoQuote && (ch == '\r' || ch == '\n')) { tquote = NoQuote; } } escaped = (!escaped && ch == '\\'); if (!rawContentResync && (textarea || title) && !src.escaped() && ch == '&') { QChar *rawContentDest = rawContent + rawContentSize; ++src; parseEntity(src, rawContentDest, true); rawContentSize = rawContentDest - rawContent; } else { rawContent[ rawContentSize++ ] = *src; ++src; ++rawContentSinceLastEntity; } } } void HTMLTokenizer::scriptHandler() { QString currentScriptSrc = scriptSrc; scriptSrc.clear(); processListing(TokenizerString(rawContent, rawContentSize)); QString exScript(buffer, dest - buffer); processToken(); currToken.tid = ID_SCRIPT + ID_CLOSE_TAG; processToken(); // Scripts following a frameset element should not be executed or even loaded in the case of extern scripts. bool followingFrameset = (parser->doc()->body() && parser->doc()->body()->id() == ID_FRAMESET); bool effectiveScript = !parser->skipMode() && !followingFrameset; bool deferredScript = false; if (effectiveScript) { CachedScript *cs = nullptr; // forget what we just got, load from src url instead if (!currentScriptSrc.isEmpty() && javascript) { const QString completeScriptUrl = parser->doc()->completeURL(currentScriptSrc); cs = parser->doc()->docLoader()->requestScript(completeScriptUrl, scriptSrcCharset); } if (cs) { cachedScript.enqueue(cs); pendingQueue.push(src); int scriptCount = cachedScript.count(); setSrc(TokenizerString()); rawContentSize = rawContentResync = 0; cs->ref(this); if (cachedScript.count() == scriptCount) { deferredScript = true; } } else if (currentScriptSrc.isNull()/*no src attribute*/ && view && javascript) { pendingQueue.push(src); setSrc(TokenizerString()); rawContentSize = rawContentResync = 0; scriptExecution(exScript, QString(), tagStartLineno /*scriptStartLineno*/); } else { // script was filtered or disallowed effectiveScript = false; } } script = false; rawContentSize = rawContentResync = 0; if (!effectiveScript) { return; } if (!m_executingScript && cachedScript.isEmpty()) { src.append(pendingQueue.pop()); } else if (cachedScript.isEmpty()) { write(pendingQueue.pop(), false); } else if (!deferredScript && pendingQueue.count() > 1) { TokenizerString t = pendingQueue.pop(); pendingQueue.top().prepend(t); } #if PROSPECTIVE_TOKENIZER_ENABLED if (!cachedScript.isEmpty() && !m_executingScript) { if (!m_prospectiveTokenizer) { m_prospectiveTokenizer = new ProspectiveTokenizer(parser->docPtr()); } if (!m_prospectiveTokenizer->inProgress() && !pendingQueue.isEmpty()) { m_prospectiveTokenizer->begin(); m_prospectiveTokenizer->write(pendingQueue.top()); } } #endif } void HTMLTokenizer::scriptExecution(const QString &str, const QString &scriptURL, int baseLine) { bool oldscript = script; m_executingScript++; script = false; QString url; if (scriptURL.isNull() && view) { url = static_cast<DocumentImpl *>(view->part()->document().handle())->URL().url(); } else { url = scriptURL; } if (view) { view->part()->executeScript(url, baseLine, Node(), str); } m_executingScript--; script = oldscript; } void HTMLTokenizer::parseComment(TokenizerString &src) { checkRawContentBuffer(src.length()); while (src.length()) { rawContent[ rawContentSize++ ] = *src; #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1 qDebug("comment is now: *%s*", src.toString().left(16).toLatin1().constData()); #endif if (src->unicode() == '>') { bool handleBrokenComments = brokenComments && !(script || style); bool scriptEnd = false; if (rawContentSize > 2 && rawContent[rawContentSize - 3] == '-' && rawContent[rawContentSize - 2] == '-') { scriptEnd = true; } if (handleBrokenComments || scriptEnd) { ++src; if (!(title || script || xmp || textarea || style)) { checkRawContentBuffer(); rawContent[ rawContentSize ] = 0; rawContent[ rawContentSize + 1 ] = 0; currToken.tid = ID_COMMENT; int size = scriptEnd ? rawContentSize - 3 : rawContentSize - 1; processListing(TokenizerString(rawContent, size)); processToken(); currToken.tid = ID_COMMENT + ID_CLOSE_TAG; processToken(); rawContentSize = 0; } comment = false; return; // Finished parsing comment } } ++src; } } void HTMLTokenizer::parseDoctypeComment(TokenizerString &src) { while (!src.isEmpty()) { QChar c = *src; switch (doctypeComment) { case DoctypeCommentHalfBegin: { if (c != '-') { // Ooops, it's not comment doctypeComment = DoctypeCommentBogus; return; } else { // Doctype comment begins doctypeComment = DoctypeComment; ++src; } break; } case DoctypeComment: { if (c == '-') { // Perhaps this is end of comment doctypeComment = DoctypeCommentHalfEnd; ++src; } else { // Keep scanning for '--' ++src; } break; } case DoctypeCommentHalfEnd: { if (c == '-') { // Doctype comment ends doctypeComment = DoctypeCommentEnd; return; } else { // It's not '--' ++src; doctypeComment = DoctypeComment; } break; } default: { assert(!"Undefined doctype comment state"); break; } } } } void HTMLTokenizer::parseDoctype(TokenizerString &src) { while (!src.isEmpty() && doctype) { QChar c; bool isWhitespace = false; int dontAdvance = 0; if (doctypeComment == DoctypeCommentEnd) { doctypeComment = NoDoctypeComment; isWhitespace = true; } else if (doctypeComment == DoctypeCommentBogus) { doctypeComment = NoDoctypeComment; c = '-'; dontAdvance++; } else { c = *src; if (doctypeAllowComment) { if (!doctypeComment && c == '-') { doctypeComment = DoctypeCommentHalfBegin; ++src; } if (doctypeComment) { parseDoctypeComment(src); continue; } isWhitespace = c == '\r' || c == '\n' || c == '\t' || c == ' '; } } switch (doctypeToken.state) { case DoctypeBegin: { doctypeToken.state = DoctypeBeforeName; if (isWhitespace) { // nothing } break; } case DoctypeBeforeName: { if (c == '>') { // Malformed. Just exit. doctype = false; } else if (isWhitespace) { // nothing } else { dontAdvance++; doctypeToken.state = DoctypeName; } break; } case DoctypeName: { if (c == '>') { // Valid doctype. Emit it. doctype = false; processDoctypeToken(); } else if (isWhitespace) { doctypeSearchCount = 0; // Used now to scan for PUBLIC doctypeSecondarySearchCount = 0; // Used now to scan for SYSTEM doctypeToken.state = DoctypeAfterName; } else { doctypeToken.name.append(c); } break; } case DoctypeAfterName: { if (c == '>') { // Valid doctype. Emit it. doctype = false; processDoctypeToken(); } else if (c == '[') { if (doctypeSearchCount > 0 || doctypeSecondarySearchCount > 0) { // is there any public/system indicator before? doctypeSearchCount = doctypeSecondarySearchCount = 0; doctypeToken.state = DoctypeBogus; } // Found internal subset doctypeToken.state = DoctypeInternalSubset; doctypeAllowComment = false; } else if (!isWhitespace) { if (c.toLower() == publicStart[doctypeSearchCount]) { doctypeSearchCount++; if (doctypeSearchCount == 6) // Found 'PUBLIC' sequence { doctypeToken.state = DoctypeBeforePublicID; } } else if (doctypeSearchCount > 0) { doctypeSearchCount = 0; doctypeToken.state = DoctypeBogus; } else if (c.toLower() == systemStart[doctypeSecondarySearchCount]) { doctypeSecondarySearchCount++; if (doctypeSecondarySearchCount == 6) // Found 'SYSTEM' sequence { doctypeToken.state = DoctypeBeforeSystemID; } } else { doctypeSecondarySearchCount = 0; doctypeToken.state = DoctypeBogus; } } else { // Whitespace keeps us in the after name state } break; } case DoctypeBeforePublicID: { if (c == '\"' || c == '\'') { tquote = c == '\"' ? DoubleQuote : SingleQuote; doctypeToken.state = DoctypePublicID; doctypeAllowComment = false; } else if (c == '>') { // Considered bogus. Don't process the doctype. doctype = false; } else if (isWhitespace) { // nothing } else { doctypeToken.state = DoctypeBogus; } break; } case DoctypePublicID: { if ((c == '\"' && tquote == DoubleQuote) || (c == '\'' && tquote == SingleQuote)) { doctypeToken.state = DoctypeAfterPublicID; doctypeAllowComment = true; } else if (c == '>') { // Considered bogus. Don't process the doctype. doctype = false; } else { doctypeToken.publicID.append(c); } break; } case DoctypeAfterPublicID: { if (c == '\"' || c == '\'') { tquote = c == '\"' ? DoubleQuote : SingleQuote; doctypeToken.state = DoctypeSystemID; } else if (c == '>') { // Valid doctype. Emit it now. doctype = false; processDoctypeToken(); } else if (isWhitespace) { // nothing } else if (c == '[') { // Found internal subset doctypeToken.state = DoctypeInternalSubset; doctypeAllowComment = false; } else { doctypeToken.state = DoctypeBogus; } break; } case DoctypeBeforeSystemID: { if (c == '\"' || c == '\'') { tquote = c == '\"' ? DoubleQuote : SingleQuote; doctypeToken.state = DoctypeSystemID; doctypeAllowComment = false; } else if (c == '>') { // Considered bogus. Don't process the doctype. doctype = false; } else if (isWhitespace) { // nothing } else { doctypeToken.state = DoctypeBogus; } break; } case DoctypeSystemID: { if ((c == '\"' && tquote == DoubleQuote) || (c == '\'' && tquote == SingleQuote)) { doctypeToken.state = DoctypeAfterSystemID; doctypeAllowComment = true; } else if (c == '>') { // Considered bogus. Don't process the doctype. doctype = false; } else { doctypeToken.systemID.append(c); } break; } case DoctypeAfterSystemID: { if (c == '>') { // Valid doctype. Emit it now. doctype = false; processDoctypeToken(); } else if (isWhitespace) { // nothing } else if (c == '[') { // Found internal subset doctypeToken.state = DoctypeInternalSubset; doctypeAllowComment = false; } else { doctypeToken.state = DoctypeBogus; } break; } case DoctypeInternalSubset: { if (c == ']') { // Done doctypeToken.state = DoctypeAfterInternalSubset; doctypeAllowComment = true; } else { doctypeToken.internalSubset.append(c); } break; } case DoctypeAfterInternalSubset: { if (c == '>') { // Valid doctype. Emit it now. doctype = false; processDoctypeToken(); } else if (isWhitespace) { // nothing } else { doctypeToken.state = DoctypeBogus; } break; } case DoctypeBogus: { if (c == '>') { // Done with the bogus doctype. doctype = false; } else { // Just keep scanning for '>' } break; } default: break; } if (!dontAdvance) { ++src; } else if (dontAdvance == 1) { continue; } else { // double dontAdvance++, do workaround doctypeComment = DoctypeCommentBogus; } } } void HTMLTokenizer::parseServer(TokenizerString &src) { checkRawContentBuffer(src.length()); while (!src.isEmpty()) { rawContent[ rawContentSize++ ] = *src; if (src->unicode() == '>' && rawContentSize > 1 && rawContent[rawContentSize - 2] == '%') { ++src; server = false; rawContentSize = 0; return; // Finished parsing server include } ++src; } } void HTMLTokenizer::parseProcessingInstruction(TokenizerString &src) { char oldchar = 0; while (!src.isEmpty()) { unsigned char chbegin = src->toLatin1(); if (chbegin == '\'') { tquote = tquote == SingleQuote ? NoQuote : SingleQuote; } else if (chbegin == '\"') { tquote = tquote == DoubleQuote ? NoQuote : DoubleQuote; } // Look for '?>' // some crappy sites omit the "?" before it, so // we look for an unquoted '>' instead. (IE compatible) else if (chbegin == '>' && (!tquote || oldchar == '?')) { // We got a '?>' sequence processingInstruction = false; ++src; discard = LFDiscard; return; // Finished parsing comment! } ++src; oldchar = chbegin; } } void HTMLTokenizer::parseText(TokenizerString &src) { while (!src.isEmpty()) { // do we need to enlarge the buffer? checkBuffer(); // ascii is okay because we only do ascii comparisons unsigned char chbegin = src->toLatin1(); if (skipLF && (chbegin != '\n')) { skipLF = false; } if (skipLF) { skipLF = false; ++src; } else if ((chbegin == '\n') || (chbegin == '\r')) { if (chbegin == '\r') { skipLF = true; } *dest++ = '\n'; ++src; } else { *dest++ = *src; ++src; } } } void HTMLTokenizer::parseEntity(TokenizerString &src, QChar *&dest, bool start) { if (start) { cBufferPos = 0; entityLen = 0; Entity = SearchEntity; } while (!src.isEmpty()) { ushort cc = src->unicode(); switch (Entity) { case NoEntity: return; break; case SearchEntity: if (cc == '#') { cBuffer[cBufferPos++] = cc; ++src; Entity = NumericSearch; } else { Entity = EntityName; } break; case NumericSearch: if (cc == 'x' || cc == 'X') { cBuffer[cBufferPos++] = cc; ++src; Entity = Hexadecimal; } else if (cc >= '0' && cc <= '9') { Entity = Decimal; } else { Entity = SearchSemicolon; } break; case Hexadecimal: { int uc = EntityChar.unicode(); int ll = qMin<uint>(src.length(), 8); while (ll--) { QChar csrc(src->toLower()); cc = csrc.cell(); if (csrc.row() || !((cc >= '0' && cc <= '9') || (cc >= 'a' && cc <= 'f'))) { break; } uc = uc * 16 + (cc - (cc < 'a' ? '0' : 'a' - 10)); cBuffer[cBufferPos++] = cc; ++src; } EntityChar = QChar(uc); Entity = SearchSemicolon; break; } case Decimal: { int uc = EntityChar.unicode(); int ll = qMin(src.length(), 9 - cBufferPos); while (ll--) { cc = src->cell(); if (src->row() || !(cc >= '0' && cc <= '9')) { Entity = SearchSemicolon; break; } uc = uc * 10 + (cc - '0'); cBuffer[cBufferPos++] = cc; ++src; } EntityChar = QChar(uc); if (cBufferPos == 9) { Entity = SearchSemicolon; } break; } case EntityName: { int ll = qMin(src.length(), 9 - cBufferPos); while (ll--) { QChar csrc = *src; cc = csrc.cell(); if (csrc.row() || !((cc >= 'a' && cc <= 'z') || (cc >= '0' && cc <= '9') || (cc >= 'A' && cc <= 'Z'))) { Entity = SearchSemicolon; break; } cBuffer[cBufferPos++] = cc; ++src; // be IE compatible and interpret even unterminated entities // outside tags. like "foo  stuff bla". if (tag == NoTag) { int code; const bool found = kde_findEntity(cBuffer, cBufferPos, &code); if (found && code < 256) { EntityChar = code; entityLen = cBufferPos; } } } if (cBufferPos == 9) { Entity = SearchSemicolon; } if (Entity == SearchSemicolon) { if (cBufferPos > 1) { int code; const bool found = kde_findEntity(cBuffer, cBufferPos, &code); // IE only accepts unterminated entities < 256, // Gecko accepts them all, but only outside tags if (found && (tag == NoTag || code < 256 || *src == ';')) { EntityChar = code; entityLen = cBufferPos; } } } break; } case SearchSemicolon: #ifdef TOKEN_DEBUG qDebug() << "ENTITY " << EntityChar.unicode(); #endif fixUpChar(EntityChar); if (*src == ';') { ++src; } if (!EntityChar.isNull()) { checkBuffer(); if (entityLen > 0 && entityLen < cBufferPos) { int rem = cBufferPos - entityLen; src.prepend(TokenizerString(QString::fromLatin1(cBuffer + entityLen, rem))); } src.push(EntityChar); rawContentSinceLastEntity = -1; } else { #ifdef TOKEN_DEBUG qDebug() << "unknown entity!"; #endif checkBuffer(11); // ignore the sequence, add it to the buffer as plaintext *dest++ = '&'; for (unsigned int i = 0; i < cBufferPos; i++) { dest[i] = cBuffer[i]; } dest += cBufferPos; rawContentSinceLastEntity += cBufferPos + 1; if (pre) { prePos += cBufferPos + 1; } } Entity = NoEntity; EntityChar = QChar::Null; return; }; } } void HTMLTokenizer::parseTag(TokenizerString &src) { assert(!Entity); checkRawContentBuffer(src.length()); while (!src.isEmpty()) { checkBuffer(); #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1 uint l = 0; while (l < src.length() && (src.toString()[l]).toLatin1() != '>') { l++; } qDebug("src is now: *%s*, tquote: %d", src.toString().left(l).toLatin1().constData(), tquote); #endif switch (tag) { case NoTag: return; case TagName: { #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1 qDebug("TagName"); #endif if (searchCount > 0) { if (*src == commentStart[searchCount]) { searchCount++; if (searchCount == 2) { doctypeSearchCount++; // A '!' is also part of doctype, so we are moving through that still as well } else { doctypeSearchCount = 0; } if (searchCount == 4) { #ifdef TOKEN_DEBUG qDebug() << "Found comment"; #endif // Found '<!--' sequence ++src; dest = buffer; // ignore the previous part of this tag tag = NoTag; comment = true; parseComment(src); return; // Finished parsing tag! } // cuts of high part, is okay cBuffer[cBufferPos++] = src->cell(); ++src; break; } else { searchCount = 0; // Stop looking for '<!--' sequence } } if (doctypeSearchCount > 0) { if ((*src).toLower() == doctypeStart[doctypeSearchCount]) { doctypeSearchCount++; cBuffer[cBufferPos++] = src->cell(); ++src; if (doctypeSearchCount == 9) { // Found '<!DOCTYPE' sequence tag = NoTag; doctypeAllowComment = true; doctypeComment = NoDoctypeComment; doctypeToken.reset(); doctype = true; parseDoctype(src); return; } break; } else { doctypeSearchCount = 0; // Stop looking for '<!DOCTYPE' sequence } } bool finish = false; unsigned int ll = qMin(src.length(), CBUFLEN - cBufferPos); while (ll--) { ushort curchar = src->unicode(); if (curchar <= ' ' || curchar == '>') { finish = true; break; } // this is a nasty performance trick. will work for the A-Z // characters, but not for others. if it contains one, // we fail anyway char cc = curchar; cBuffer[cBufferPos++] = cc | 0x20; ++src; } // Disadvantage: we add the possible rest of the tag // as attribute names. ### judge if this causes problems if (finish || CBUFLEN == cBufferPos) { bool beginTag; char *ptr = cBuffer; unsigned int len = cBufferPos; cBuffer[cBufferPos] = '\0'; if ((cBufferPos > 0) && (*ptr == '/')) { // End Tag beginTag = false; ptr++; len--; } else // Start Tag { beginTag = true; } // Accept empty xml tags like <br/> if (len > 1 && ptr[len - 1] == '/') { ptr[--len] = '\0'; // if it is like <br/> and not like <input/ value=foo>, take it as flat if (*src == '>') { currToken.flat = true; } } uint tagID = 0; if (!tagID) { DOMString tagName(ptr); if (Element::khtmlValidQualifiedName(tagName)) { safeLocalName = LocalName::fromString(tagName, IDS_NormalizeLower); tagID = safeLocalName.id(); } #ifdef TOKEN_DEBUG QByteArray tmp(ptr, len + 1); qDebug() << "Unknown tag: \"" << tmp.data() << "\""; #endif } if (tagID) { #ifdef TOKEN_DEBUG QByteArray tmp(ptr, len + 1); qDebug() << "found tag id=" << tagID << ": " << tmp.data(); #endif currToken.tid = beginTag ? tagID : tagID + ID_CLOSE_TAG; } dest = buffer; tag = SearchAttribute; cBufferPos = 0; } break; } case SearchAttribute: { #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1 qDebug("SearchAttribute"); #endif bool atespace = false; ushort curchar; while (!src.isEmpty()) { curchar = src->unicode(); if (curchar > ' ') { if (curchar == '<' || curchar == '>') { tag = SearchEnd; } else if (atespace && (curchar == '\'' || curchar == '"')) { tag = SearchValue; *dest++ = 0; attrName = DOMString(""); } else { tag = AttributeName; } cBufferPos = 0; break; } atespace = true; ++src; } break; } case AttributeName: { #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1 qDebug("AttributeName"); #endif ushort curchar; int ll = qMin(src.length(), CBUFLEN - cBufferPos); while (ll--) { curchar = src->unicode(); if (curchar <= '>') { if (curchar <= ' ' || curchar == '=' || curchar == '>') { unsigned int a; cBuffer[cBufferPos] = '\0'; a = LocalName::fromString(DOMString(cBuffer), IDS_NormalizeLower).id(); // ### still deep copy? if (a > ATTR_LAST_ATTR) { a = 0; } if (!a) { // did we just get /> or e.g checked/> if (curchar == '>' && cBufferPos >= 1 && cBuffer[cBufferPos - 1] == '/') { currToken.flat = true; cBuffer[cBufferPos - 1] = '\0'; if (cBufferPos > 1) { a = LocalName::fromString(DOMString(cBuffer), IDS_NormalizeLower).id(); } if (a > ATTR_LAST_ATTR) { a = 0; } cBuffer[cBufferPos - 1] = '/'; } if (!a) { attrName = DOMString(cBuffer, cBufferPos); } } dest = buffer; *dest++ = a; #ifdef TOKEN_DEBUG if (!a || (cBufferPos && *cBuffer == '!')) { qDebug() << "Unknown attribute: *" << QByteArray(cBuffer, cBufferPos + 1).data() << "*"; } else { qDebug() << "Known attribute: " << QByteArray(cBuffer, cBufferPos + 1).data(); } #endif tag = SearchEqual; break; } } cBuffer[cBufferPos++] = (curchar >= 'A' && curchar <= 'Z') ? curchar | 0x20 : curchar; ++src; } if (cBufferPos == CBUFLEN) { cBuffer[cBufferPos] = '\0'; attrName = DOMString(cBuffer, cBufferPos); dest = buffer; *dest++ = 0; tag = SearchEqual; } break; } case SearchEqual: { #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1 qDebug("SearchEqual"); #endif ushort curchar; bool atespace = false; while (!src.isEmpty()) { curchar = src->unicode(); if (curchar > ' ') { if (curchar == '=') { #ifdef TOKEN_DEBUG qDebug() << "found equal"; #endif tag = SearchValue; ++src; } else if (atespace && (curchar == '\'' || curchar == '"')) { tag = SearchValue; *dest++ = 0; attrName = DOMString(""); } else { DOMString v(""); currToken.addAttribute(parser->docPtr(), buffer, attrName, v); dest = buffer; tag = SearchAttribute; } break; } atespace = true; ++src; } break; } case SearchValue: { ushort curchar; while (!src.isEmpty()) { curchar = src->unicode(); if (curchar > ' ') { if ((curchar == '\'' || curchar == '\"')) { tquote = curchar == '\"' ? DoubleQuote : SingleQuote; tag = QuotedValue; ++src; } else { tag = Value; } break; } ++src; } break; } case QuotedValue: { #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1 qDebug("QuotedValue"); #endif ushort curchar; while (!src.isEmpty()) { checkBuffer(); curchar = src->unicode(); if (curchar <= '\'' && !src.escaped()) { // ### attributes like '&{blaa....};' are supposed to be treated as jscript. if (curchar == '&') { ++src; parseEntity(src, dest, true); break; } else if ((tquote == SingleQuote && curchar == '\'') || (tquote == DoubleQuote && curchar == '\"')) { // some <input type=hidden> rely on trailing spaces. argh while (dest > buffer + 1 && (*(dest - 1) == '\n' || *(dest - 1) == '\r')) { dest--; // remove trailing newlines } DOMString v(buffer + 1, dest - buffer - 1); currToken.addAttribute(parser->docPtr(), buffer, attrName, v); dest = buffer; tag = SearchAttribute; tquote = NoQuote; ++src; break; } } *dest++ = *src; ++src; } break; } case Value: { #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1 qDebug("Value"); #endif ushort curchar; while (!src.isEmpty()) { checkBuffer(); curchar = src->unicode(); if (curchar <= '>' && !src.escaped()) { // parse Entities if (curchar == '&') { ++src; parseEntity(src, dest, true); break; } // no quotes. Every space means end of value // '/' does not delimit in IE! // HTML5: must not contain any literal space characters, any U+0022 QUOTATION MARK (") characters, // U+0027 APOSTROPHE (') characters, U+003D EQUALS SIGN (=) characters, U+003C LESS-THAN SIGN (<) characters, // U+003E GREATER-THAN SIGN (>) characters, or U+0060 GRAVE ACCENT (`) characters, and must not be the empty string. // Real life: images.google.com uses URLs including form arguments (foo=bar) // in unquoted parameters --- with an html5 <!doctype html> DTD. // Real life takes priority, so we accept at least = if (curchar <= ' ' || curchar == '>' || curchar == '\'' || curchar == '"' || curchar == '<' || /*curchar == '=' ||*/ curchar == '`') { DOMString v(buffer + 1, dest - buffer - 1); currToken.addAttribute(parser->docPtr(), buffer, attrName, v); dest = buffer; tag = SearchAttribute; break; } } *dest++ = *src; ++src; } break; } case SearchEnd: { #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1 qDebug("SearchEnd"); #endif while (!src.isEmpty()) { if (*src == '<' || *src == '>') { break; } if (*src == '/') { currToken.flat = true; } ++src; } if (src.isEmpty() && *src != '<' && *src != '>') { break; } searchCount = 0; // Stop looking for '<!--' sequence tag = NoTag; tquote = NoQuote; if (*src == '>') { ++src; } if (!currToken.tid) { //stop if tag is unknown return; } uint tagID = currToken.tid; #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 0 qDebug() << "appending Tag: " << tagID; #endif // When parsing HTML flat tags like <div /> should // be ignored, the only exception is SCRIPT, and // tags with forbidden end-tags if (tagID < ID_CLOSE_TAG && tagID != ID_SCRIPT && DOM::endTagRequirement(tagID) != DOM::FORBIDDEN && parser->doc()->htmlMode() != DocumentImpl::XHtml) { currToken.flat = false; } bool beginTag = !currToken.flat && (tagID < ID_CLOSE_TAG); HTMLScriptElementImpl *prevScriptElem = nullptr; if (tagID >= ID_CLOSE_TAG) { tagID -= ID_CLOSE_TAG; } else if (tagID == ID_SCRIPT) { prevScriptElem = parser->currentScriptElement(); DOMStringImpl *a = nullptr; scriptSrc.clear(); scriptSrcCharset.clear(); if (currToken.attrs && /* potentially have a ATTR_SRC ? */ view && /* are we a regular tokenizer or just for innerHTML ? */ parser->doc()->view()->part()->jScriptEnabled() /* jscript allowed at all? */ ) { if ((a = currToken.attrs->getValue(ATTR_SRC))) { scriptSrc = DOMString(a).trimSpaces().string(); } if ((a = currToken.attrs->getValue(ATTR_CHARSET))) { scriptSrcCharset = DOMString(a).string().trimmed(); } if (scriptSrcCharset.isEmpty() && view) { scriptSrcCharset = parser->doc()->view()->part()->encoding(); } } javascript = true; } processToken(); if (javascript) { HTMLScriptElementImpl *sc = parser->currentScriptElement(); javascript = (sc && sc != prevScriptElem) ? sc->isValidScript() : false; } if (parser->selectMode() && beginTag) { discard = AllDiscard; } switch (tagID) { case ID_LISTING: case ID_PRE: pre = beginTag; if (beginTag) { discard = LFDiscard; } prePos = 0; break; case ID_BR: prePos = 0; break; case ID_SCRIPT: if (beginTag) { searchStopper = scriptEnd; searchStopperLen = 8; script = true; parseRawContent(src); } else if (tagID < ID_CLOSE_TAG) { // Handle <script src="foo"/> script = true; scriptHandler(); } break; case ID_STYLE: if (beginTag) { searchStopper = styleEnd; searchStopperLen = 7; style = true; parseRawContent(src); } break; case ID_TEXTAREA: if (beginTag) { searchStopper = textareaEnd; searchStopperLen = 10; textarea = true; discard = NoneDiscard; rawContentSinceLastEntity = 0; parseRawContent(src); } break; case ID_TITLE: if (beginTag) { searchStopper = titleEnd; searchStopperLen = 7; title = true; rawContentSinceLastEntity = 0; parseRawContent(src); } break; case ID_XMP: if (beginTag) { searchStopper = xmpEnd; searchStopperLen = 5; xmp = true; parseRawContent(src); } break; case ID_SELECT: select = beginTag; break; case ID_PLAINTEXT: plaintext = beginTag; break; } return; // Finished parsing tag! } } // end switch } return; } void HTMLTokenizer::addPending() { if (select && !(comment || script)) { *dest++ = ' '; } else { switch (pending) { case LFPending: *dest++ = QLatin1Char('\n'); prePos = 0; break; case SpacePending: *dest++ = QLatin1Char(' '); ++prePos; break; case TabPending: { // Don't expand tabs inside <textarea> or script int p = TAB_SIZE - (prePos % TAB_SIZE); if (textarea || script) { *dest++ = QLatin1Char('\t'); } else { for (int x = 0; x < p; x++) { *dest++ = QLatin1Char(' '); } } prePos += p; break; } case NonePending: assert(0); } } pending = NonePending; } inline bool HTMLTokenizer::continueProcessing(int &processedCount) { // We don't want to be checking elapsed time with every character, so we only check after we've // processed a certain number of characters. We also do not do suspension if we're // parsing something like innerHTML. if (!m_executingScript && processedCount > sTokenizerChunkSize && cachedScript.isEmpty()) { processedCount = 0; if (m_time.elapsed() > m_tokenizerYieldDelay && m_documentTokenizer) { m_yieldTimer = startTimer(0); m_tokenizerYieldDelay = sTokenizerFastYieldDelay; return false; } } processedCount++; return true; } #include "khtmlpart_p.h" void HTMLTokenizer::write(const TokenizerString &str, bool appendData) { #ifdef TOKEN_DEBUG qDebug() << this << " Tokenizer::write(\"" << str.toString() << "\"," << appendData << ")"; #endif if (!buffer) { return; } if ((m_executingScript && appendData) || cachedScript.count()) { // don't parse; we will do this later if (pendingQueue.isEmpty()) { pendingQueue.push(str); } else if (appendData) { pendingQueue.bottom().append(str); } else { pendingQueue.top().append(str); } #if PROSPECTIVE_TOKENIZER_ENABLED if (m_prospectiveTokenizer && m_prospectiveTokenizer->inProgress() && appendData) { m_prospectiveTokenizer->write(str); } #endif return; } #if PROSPECTIVE_TOKENIZER_ENABLED if (m_prospectiveTokenizer && m_prospectiveTokenizer->inProgress() && appendData) { m_prospectiveTokenizer->end(); } #endif if (onHold) { src.append(str); return; } if (!src.isEmpty()) { src.append(str); } else { setSrc(str); } // Once a timer is set, it has control of when the tokenizer continues. if (m_yieldTimer > 0) { return; } int processedCount = 0; m_time.start(); while (!src.isEmpty()) { if (m_abort || !continueProcessing(processedCount)) { break; } // do we need to enlarge the buffer? checkBuffer(); ushort cc = src->unicode(); if (skipLF && (cc != '\n')) { skipLF = false; } if (skipLF) { skipLF = false; ++src; } else if (Entity) { parseEntity(src, dest); } else if (plaintext) { parseText(src); } else if (script) { parseRawContent(src); } else if (style) { parseRawContent(src); } else if (xmp) { parseRawContent(src); } else if (textarea) { parseRawContent(src); } else if (title) { parseRawContent(src); } else if (comment) { parseComment(src); } else if (doctypeComment && doctypeComment != DoctypeCommentEnd && doctypeComment != DoctypeCommentBogus) { parseDoctypeComment(src); } else if (doctype) { parseDoctype(src); } else if (server) { parseServer(src); } else if (processingInstruction) { parseProcessingInstruction(src); } else if (tag) { parseTag(src); } else if (startTag) { startTag = false; switch (cc) { case '/': break; case '!': { // <!-- comment --> or <!DOCTYPE ...> searchCount = 1; // Look for '<!--' sequence to start comment... doctypeSearchCount = 1; // ... or for '<!DOCTYPE' sequence to start doctype break; } case '?': { // xml processing instruction processingInstruction = true; tquote = NoQuote; parseProcessingInstruction(src); continue; } case '%': if (!brokenServer) { // <% server stuff, handle as comment %> server = true; tquote = NoQuote; parseServer(src); continue; } // else fall through default: { if (((cc >= 'a') && (cc <= 'z')) || ((cc >= 'A') && (cc <= 'Z'))) { // Start of a Start-Tag } else { // Invalid tag // Add as is if (pending) { addPending(); } *dest = '<'; dest++; continue; } } }; // end case // According to SGML any LF immediately after a starttag, or // immediately before an endtag should be ignored. // ### Gecko and MSIE though only ignores LF immediately after // starttags and only for PRE elements -- asj (28/06-2005) if (pending) { if (!select) { addPending(); } else { pending = NonePending; } } // Cancel unused discards discard = NoneDiscard; // if (!endTag) discard = LFDiscard; processToken(); cBufferPos = 0; tag = TagName; parseTag(src); } else if (cc == '&' && !src.escaped()) { ++src; if (pending) { addPending(); } discard = NoneDiscard; parseEntity(src, dest, true); } else if (cc == '<' && !src.escaped()) { tagStartLineno = lineno + src.lineCount(); ++src; discard = NoneDiscard; startTag = true; } else if ((cc == '\n') || (cc == '\r')) { if (discard == SpaceDiscard) { discard = NoneDiscard; } if (discard == LFDiscard) { // Ignore one LF discard = NoneDiscard; } else if (discard == AllDiscard) { // Ignore } else { if (select && !script) { pending = LFPending; } else { if (pending) { addPending(); } pending = LFPending; } } /* Check for MS-DOS CRLF sequence */ if (cc == '\r') { skipLF = true; } ++src; } else if ((cc == ' ') || (cc == '\t')) { if (discard == LFDiscard) { discard = NoneDiscard; } if (discard == SpaceDiscard) { // Ignore one space discard = NoneDiscard; } else if (discard == AllDiscard) { // Ignore } else { if (select && !script) { if (!pending) { pending = SpacePending; } } else { if (pending) { addPending(); } if (cc == ' ') { pending = SpacePending; } else { pending = TabPending; } } } ++src; } else { if (pending) { addPending(); } discard = NoneDiscard; if (pre) { prePos++; } *dest = *src; fixUpChar(*dest); ++dest; ++src; } } if (noMoreData && cachedScript.isEmpty() && !m_executingScript && m_yieldTimer <= 0) { end(); // this actually causes us to be deleted } } void HTMLTokenizer::timerEvent(QTimerEvent *e) { if (e->timerId() == m_yieldTimer) { killTimer(m_yieldTimer); m_yieldTimer = 0; write(TokenizerString(), true); } else if (e->timerId() == m_externalScriptsTimerId) { if (view && view->hasLayoutPending()) { // all stylesheets are loaded but the style modifications // they triggered have yet to be applied, BBIAB return; } killTimer(m_externalScriptsTimerId); m_externalScriptsTimerId = 0; notifyFinished(nullptr); } } void HTMLTokenizer::end() { if (buffer) { // parseTag is using the buffer for different matters if (!tag) { processToken(); } if (buffer) { KHTML_DELETE_QCHAR_VEC(buffer); } if (rawContent) { KHTML_DELETE_QCHAR_VEC(rawContent); } rawContent = nullptr; rawContentSize = rawContentMaxSize = rawContentResync = 0; buffer = nullptr; } emit finishedParsing(); } void HTMLTokenizer::finish() { // The purpose of this iteration is to recover from 'raw content' tokenizing mode. // In this mode, any error such as the lack of a closing tag (for the considered element) or of a closing comment, // would result in the entire document being absorbed in one node. // When it happens, we simply put back in the input buffer what this mode's output has accumulated so far, // and retokenize after either disabling the 'raw content' mode (by setting the corresponding members to false) // or after setting a few flags disabling some lax parsing 'features' (brokenComments/brokenServer). while ((title || comment || server) && rawContent && rawContentSize) { // we've found an unmatched comment start if (comment) { brokenComments = true; } else if (server) { brokenServer = true; } checkRawContentBuffer(); rawContent[ rawContentSize ] = 0; rawContent[ rawContentSize + 1 ] = 0; int pos; QString food; if (title || style || script || textarea) { rawContentSinceLastEntity = 0; food.setUnicode(rawContent, rawContentSize); } else if (server) { food = "<"; food += QString(rawContent, rawContentSize); } else { pos = QString::fromRawData(rawContent, rawContentSize).indexOf('>'); food.setUnicode(rawContent + pos + 1, rawContentSize - pos - 1); // deep copy } KHTML_DELETE_QCHAR_VEC(rawContent); rawContent = nullptr; rawContentSize = rawContentMaxSize = rawContentResync = 0; comment = server = title = false; if (!food.isEmpty()) { write(food, true); } } // this indicates we will not receive any more data... but if we are waiting on // an external script to load, we can't finish parsing until that is done noMoreData = true; if (cachedScript.isEmpty() && !m_executingScript && !onHold && m_yieldTimer <= 0) { end(); // this actually causes us to be deleted } } void HTMLTokenizer::processToken() { KJSProxy *jsProxy = view ? view->part()->jScript() : nullptr; if (jsProxy) { jsProxy->setEventHandlerLineno(tagStartLineno); } if (dest > buffer) { #if 0 if (currToken.tid) { qDebug("unexpected token id: %d, str: *%s*", currToken.tid, QString::fromRawData(buffer, dest - buffer).toLatin1().constData()); assert(0); } #endif currToken.text = new DOMStringImpl(buffer, dest - buffer); currToken.text->ref(); if (currToken.tid != ID_COMMENT) { currToken.tid = ID_TEXT; } } else if (!currToken.tid) { currToken.reset(); if (jsProxy) { jsProxy->setEventHandlerLineno(lineno + src.lineCount()); } return; } dest = buffer; #ifdef TOKEN_DEBUG QString text; bool closing = (currToken.tid > ID_CLOSE_TAG); int rid = currToken.tid - (closing ? ID_CLOSE_TAG : 0); if (currToken.text) { text = QString::fromRawData(currToken.text->s, currToken.text->l); } qDebug() << "Token -->" << LocalName::fromId(localNamePart(rid)).toString() << "id =" << currToken.tid << "closing =" << closing; if (currToken.flat) { qDebug() << "Token is FLAT!"; } if (!text.isNull()) { qDebug() << "text: \"" << text << "\""; } unsigned long l = currToken.attrs ? currToken.attrs->length() : 0; if (l) { qDebug() << "Attributes: " << l; for (unsigned long i = 0; i < l; ++i) { NodeImpl::Id tid = currToken.attrs->idAt(i); DOMString value = currToken.attrs->valueAt(i); qDebug() << " " << tid << " " << LocalName::fromId(localNamePart(tid)).toString() - << "=\"" << value.string() << "\"" << endl; + << "=\"" << value.string() << "\""; } } #endif // In some cases, parseToken() can cause javascript code to be executed // (for example, when setting an attribute that causes an event handler // to be created). So we need to protect against re-entrancy into the parser m_executingScript++; // pass the token over to the parser, the parser DOES NOT delete the token parser->parseToken(&currToken); m_executingScript--; if (currToken.flat && currToken.tid != ID_TEXT && !parser->noSpaces()) { discard = NoneDiscard; } currToken.reset(); if (jsProxy) { jsProxy->setEventHandlerLineno(0); } } void HTMLTokenizer::processDoctypeToken() { // qDebug() << "Process DoctypeToken (name: " << doctypeToken.name << ", publicID: " << doctypeToken.publicID << ", systemID: " << doctypeToken.systemID; doctypeToken.publicID = doctypeToken.publicID.simplified(); doctypeToken.systemID = doctypeToken.systemID.simplified(); parser->parseDoctypeToken(&doctypeToken); } HTMLTokenizer::~HTMLTokenizer() { reset(); delete m_prospectiveTokenizer; delete parser; } void HTMLTokenizer::enlargeBuffer(int len) { int newsize = qMax(size * 2, size + len); int oldoffs = (dest - buffer); buffer = KHTML_REALLOC_QCHAR_VEC(buffer, newsize); dest = buffer + oldoffs; size = newsize; } void HTMLTokenizer::enlargeRawContentBuffer(int len) { int newsize = qMax(rawContentMaxSize * 2, rawContentMaxSize + len); rawContent = KHTML_REALLOC_QCHAR_VEC(rawContent, newsize); rawContentMaxSize = newsize; } void HTMLTokenizer::notifyFinished(CachedObject *finishedObj) { Q_UNUSED(finishedObj); assert(!cachedScript.isEmpty()); // Make external scripts wait for external stylesheets. // FIXME: This needs to be done for inline scripts too. m_hasScriptsWaitingForStylesheets = !parser->doc()->haveStylesheetsLoaded(); if (m_hasScriptsWaitingForStylesheets) { // qDebug() << "Delaying script execution until stylesheets have loaded."; return; } // qDebug() << (finishedObj ? "Processing an external script" : "Continuing processing of delayed external scripts"); bool done = false; m_scriptTime.start(); while (!done && cachedScript.head()->isLoaded()) { if (!continueProcessingScripts()) { break; } CachedScript *cs = cachedScript.dequeue(); DOMString scriptSource = cs->script(); #ifdef TOKEN_DEBUG qDebug() << "External script is:" << endl << scriptSource.string(); #endif setSrc(TokenizerString()); // make sure we forget about the script before we execute the new one // infinite recursion might happen otherwise QString cachedScriptUrl(cs->url().string()); cs->deref(this); scriptExecution(scriptSource.string(), cachedScriptUrl); done = cachedScript.isEmpty(); if (done) { assert(!m_hasScriptsWaitingForStylesheets); } else if (m_hasScriptsWaitingForStylesheets) { // flag has changed during the script execution, // so we need to wait for stylesheets again. done = true; } // 'script' is true when we are called synchronously from // scriptHandler(). In that case scriptHandler() will take care // of the pending queue. if (!script) { while (pendingQueue.count() > 1) { TokenizerString t = pendingQueue.pop(); pendingQueue.top().prepend(t); } if (done) { write(pendingQueue.pop(), false); } // we might be deleted at this point, do not // access any members. } } } bool HTMLTokenizer::continueProcessingScripts() { if (m_externalScriptsTimerId) { return false; } if (m_scriptTime.elapsed() > m_tokenizerYieldDelay && m_documentTokenizer) { if ((m_externalScriptsTimerId = startTimer(0))) { return false; } } return true; } void HTMLTokenizer::executeScriptsWaitingForStylesheets() { assert(parser->doc()->haveStylesheetsLoaded()); if (m_hasScriptsWaitingForStylesheets) { notifyFinished(nullptr); } } bool HTMLTokenizer::isWaitingForScripts() const { return cachedScript.count(); } bool HTMLTokenizer::isExecutingScript() const { return (m_executingScript > 0); } void HTMLTokenizer::setSrc(const TokenizerString &source) { lineno += src.lineCount(); src = source; src.resetLineCount(); } void HTMLTokenizer::setOnHold(bool _onHold) { if (onHold == _onHold) { return; } onHold = _onHold; } diff --git a/src/imload/decoders/qimageioloader.cpp b/src/imload/decoders/qimageioloader.cpp index 7657e15..70e7aa6 100644 --- a/src/imload/decoders/qimageioloader.cpp +++ b/src/imload/decoders/qimageioloader.cpp @@ -1,205 +1,205 @@ /* Large image load library -- QImageIO decoder Copyright (C) 2007-2009 Allan Sandfeld Jensen <sandfeld@kde.org> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include <QBuffer> #include <QByteArray> #include <QStringList> #include <QImageReader> #include "qimageioloader.h" #include "imageloader.h" #include "kservice.h" #include "kservicetypetrader.h" #include "QDebug" #include "imagemanager.h" namespace khtmlImLoad { class QImageIOLoader: public ImageLoader { QByteArray array; QImage image; public: QImageIOLoader() { } ~QImageIOLoader() { } int processData(uchar *data, int length) Q_DECL_OVERRIDE { //Collect data in the buffer int pos = array.size(); array.resize(array.size() + length); memcpy(array.data() + pos, data, length); return length; } int processEOF() Q_DECL_OVERRIDE { QBuffer buffer(&array); buffer.open(QIODevice::ReadOnly); QByteArray qformat = QImageReader::imageFormat(&buffer); QImageReader reader(&buffer, qformat); if (!reader.canRead()) { return Error; } QSize size = reader.size(); if (size.isValid()) { if (ImageManager::isAcceptableSize(size.width(), size.height())) { notifyImageInfo(size.width(), size.height()); } else { return Error; } } if (!reader.read(&image)) { return Error; } if (!size.isValid()) { // Might be too late by now.. if (ImageManager::isAcceptableSize(image.width(), image.height())) { notifyImageInfo(image.width(), image.height()); } else { return Error; } } ImageFormat format; if (!imageFormat(image, format)) { return Error; } notifyAppendFrame(image.width(), image.height(), format); notifyQImage(1, &image); return Done; } bool imageFormat(QImage &image, ImageFormat &format) { switch (image.format()) { case QImage::Format_RGB32: format.type = ImageFormat::Image_RGB_32; break; case QImage::Format_ARGB32: format.type = ImageFormat::Image_ARGB_32_DontPremult; break; case QImage::Format_ARGB32_Premultiplied: format.type = ImageFormat::Image_ARGB_32; break; case QImage::Format_Indexed8: format.type = ImageFormat::Image_Palette_8; format.palette = image.colorTable(); break; case QImage::Format_Mono: case QImage::Format_MonoLSB: image = image.convertToFormat(QImage::Format_Indexed8); format.type = ImageFormat::Image_Palette_8; format.palette = image.colorTable(); break; case QImage::Format_Invalid: default: // unsupported formats return false; } return true; } }; static const char *const positiveList[] = { "BMP", "TIFF", "JP2", "PNM", "EXR", "XBM", "XPM", "ICO", "SVG", "SVGZ", nullptr }; bool isSupportedFormat(QString format) { QStringList pList; for (int i = 0; positiveList[i]; i++) { pList.append(QString::fromLatin1(positiveList[i])); } return pList.contains(format, Qt::CaseInsensitive); } static QStringList s_formats; ImageLoaderProvider::Type QImageIOLoaderProvider::type() { return Foreign; } const QStringList &QImageIOLoaderProvider::mimeTypes() { if (!s_formats.isEmpty()) { return s_formats; } // QList<QByteArray> formats = QImageIOReader::supportedFormats(); KService::List services = KServiceTypeTrader::self()->query("QImageIOPlugins"); foreach (const KService::Ptr &service, services) { QStringList formats = service->property("X-KDE-ImageFormat").toStringList(); QString mimetype = service->property("X-KDE-MimeType").toString(); bool positive = false; foreach (const QString &format, formats) { if (isSupportedFormat(format)) { positive = true; break; } } if (!positive) { continue; } if (!mimetype.isEmpty()) { s_formats.append(mimetype); - // qDebug() << "QImageIO - Format supported: " << mimetype << endl; + // qDebug() << "QImageIO - Format supported: " << mimetype; } } return s_formats; } ImageLoader *QImageIOLoaderProvider::loaderFor(const QByteArray &prefix) { QByteArray pref = prefix; QBuffer prefixBuffer(&pref); prefixBuffer.open(QIODevice::ReadOnly); QByteArray format = QImageReader::imageFormat(&prefixBuffer); prefixBuffer.close(); if (format.isEmpty() || !isSupportedFormat(format)) { return nullptr; } else - // qDebug() << "QImageIO - Format guessed: " << format << endl; + // qDebug() << "QImageIO - Format guessed: " << format; { return new QImageIOLoader; } } } // namespace diff --git a/src/java/kjavaapplet.cpp b/src/java/kjavaapplet.cpp index d1f03cd..4260ad8 100644 --- a/src/java/kjavaapplet.cpp +++ b/src/java/kjavaapplet.cpp @@ -1,293 +1,293 @@ /* This file is part of the KDE project * * Copyright (C) 2000 Richard Moore <rich@kde.org> * 2000 Wynn Wilkes <wynnw@caldera.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kjavaappletwidget.h" #include "kjavaappletcontext.h" #include <klocalizedstring.h> #include <QDebug> #include <kparts/browserextension.h> #include <kparts/liveconnectextension.h> class KJavaAppletPrivate { public: bool reallyExists; bool failed; QString className; QString appName; QString baseURL; QString codeBase; QString archives; QSize size; QString windowName; KJavaApplet::AppletState state; KJavaAppletWidget *UIwidget; }; KJavaApplet::KJavaApplet(KJavaAppletWidget *_parent, KJavaAppletContext *_context) : d(new KJavaAppletPrivate), params() { d->UIwidget = _parent; d->state = UNKNOWN; d->failed = false; if (_context) { setAppletContext(_context); } d->reallyExists = false; } KJavaApplet::~KJavaApplet() { if (d->reallyExists) { context->destroy(this); } delete d; } bool KJavaApplet::isCreated() { return d->reallyExists; } void KJavaApplet::setAppletContext(KJavaAppletContext *_context) { context = _context; context->registerApplet(this); } void KJavaApplet::setAppletClass(const QString &_className) { d->className = _className; } QString &KJavaApplet::appletClass() { return d->className; } QString &KJavaApplet::parameter(const QString &name) { return params[ name ]; } void KJavaApplet::setParameter(const QString &name, const QString &value) { params.insert(name, value); } QMap<QString, QString> &KJavaApplet::getParams() { return params; } void KJavaApplet::setBaseURL(const QString &baseURL) { d->baseURL = baseURL; } QString &KJavaApplet::baseURL() { return d->baseURL; } void KJavaApplet::setCodeBase(const QString &codeBase) { d->codeBase = codeBase; } QString &KJavaApplet::codeBase() { return d->codeBase; } void KJavaApplet::setSize(QSize size) { d->size = size; } QSize KJavaApplet::size() { return d->size; } void KJavaApplet::setArchives(const QString &_archives) { d->archives = _archives; } QString &KJavaApplet::archives() { return d->archives; } void KJavaApplet::resizeAppletWidget(int width, int height) { // qDebug() << "KJavaApplet, id = " << id << ", ::resizeAppletWidget to " << width << ", " << height; QStringList sl; sl.push_back(QString::number(0)); // applet itself has id 0 sl.push_back(QString("eval")); // evaluate next script sl.push_back(QString::number(KParts::LiveConnectExtension::TypeString)); sl.push_back(QString("this.setAttribute('WIDTH',%1);this.setAttribute('HEIGHT',%2)").arg(width).arg(height)); jsData(sl); } void KJavaApplet::setAppletName(const QString &name) { d->appName = name; } void KJavaApplet::setWindowName(const QString &title) { d->windowName = title; } QString &KJavaApplet::getWindowName() { return d->windowName; } QString &KJavaApplet::appletName() { return d->appName; } void KJavaApplet::create() { if (!context->create(this)) { setFailed(); } d->reallyExists = true; } void KJavaApplet::init() { context->init(this); } void KJavaApplet::start() { context->start(this); } void KJavaApplet::stop() { context->stop(this); } int KJavaApplet::appletId() { return id; } void KJavaApplet::setAppletId(int _id) { id = _id; } void KJavaApplet::stateChange(const int newStateInt) { AppletState newState = (AppletState)newStateInt; bool ok = false; if (d->failed) { return; } switch (newState) { case CLASS_LOADED: ok = (d->state == UNKNOWN); break; case INSTANCIATED: ok = (d->state == CLASS_LOADED); if (ok) { showStatus(i18n("Initializing Applet \"%1\"...", appletName())); } break; case INITIALIZED: ok = (d->state == INSTANCIATED); if (ok) { showStatus(i18n("Starting Applet \"%1\"...", appletName())); start(); } break; case STARTED: ok = (d->state == INITIALIZED || d->state == STOPPED); if (ok) { showStatus(i18n("Applet \"%1\" started", appletName())); } break; case STOPPED: ok = (d->state == INITIALIZED || d->state == STARTED); if (ok) { showStatus(i18n("Applet \"%1\" stopped", appletName())); } break; case DESTROYED: ok = true; break; default: break; } if (ok) { d->state = newState; } else { qCritical() << "KJavaApplet::stateChange : don't want to switch from state " - << d->state << " to " << newState << endl; + << d->state << " to " << newState; } } void KJavaApplet::showStatus(const QString &msg) { QStringList args; args << msg; context->processCmd("showstatus", args); } void KJavaApplet::setFailed() { d->failed = true; } bool KJavaApplet::isAlive() const { return ( !d->failed && d->state >= INSTANCIATED && d->state < STOPPED ); } KJavaApplet::AppletState KJavaApplet::state() const { return d->state; } bool KJavaApplet::failed() const { return d->failed; } diff --git a/src/java/kjavaappletcontext.cpp b/src/java/kjavaappletcontext.cpp index 6f1bb0b..458f466 100644 --- a/src/java/kjavaappletcontext.cpp +++ b/src/java/kjavaappletcontext.cpp @@ -1,262 +1,262 @@ /* This file is part of the KDE project * * Copyright (C) 2000 Richard Moore <rich@kde.org> * 2000 Wynn Wilkes <wynnw@caldera.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kjavaappletcontext.h" #include "kjavaappletserver.h" #include "kjavaprocess.h" #include "kjavaapplet.h" #include <klocalizedstring.h> #include <kmessagebox.h> #include <QDebug> #include <QtCore/QMap> #include <QtCore/QPointer> #include <QtCore/QStringList> #include <QtCore/QRegExp> // This file was using 6002, but kdebug.areas didn't know about that number #define DEBUGAREA 6100 typedef QMap< int, QPointer<KJavaApplet> > AppletMap; // For future expansion class KJavaAppletContextPrivate { friend class KJavaAppletContext; private: AppletMap applets; }; // Static Factory Functions int KJavaAppletContext::contextCount = 0; /* Class Implementation */ KJavaAppletContext::KJavaAppletContext() : QObject(), d(new KJavaAppletContextPrivate) { server = KJavaAppletServer::allocateJavaServer(); connect(server->javaProcess(), SIGNAL(exited(int)), this, SLOT(javaProcessExited(int))); id = contextCount; server->createContext(id, this); ++contextCount; } KJavaAppletContext::~KJavaAppletContext() { server->destroyContext(id); KJavaAppletServer::freeJavaServer(); delete d; } int KJavaAppletContext::contextId() { return id; } void KJavaAppletContext::setContextId(int _id) { id = _id; } void KJavaAppletContext::registerApplet(KJavaApplet *applet) { static int appletId = 0; applet->setAppletId(++appletId); d->applets.insert(appletId, applet); } bool KJavaAppletContext::create(KJavaApplet *applet) { return server->createApplet(id, applet->appletId(), applet->appletName(), applet->appletClass(), applet->baseURL(), applet->user(), applet->password(), applet->authName(), applet->codeBase(), applet->archives(), applet->size(), applet->getParams(), applet->getWindowName()); } void KJavaAppletContext::destroy(KJavaApplet *applet) { const int appletId = applet->appletId(); d->applets.remove(appletId); server->destroyApplet(id, appletId); } void KJavaAppletContext::init(KJavaApplet *applet) { server->initApplet(id, applet->appletId()); } void KJavaAppletContext::start(KJavaApplet *applet) { server->startApplet(id, applet->appletId()); } void KJavaAppletContext::stop(KJavaApplet *applet) { server->stopApplet(id, applet->appletId()); } void KJavaAppletContext::processCmd(QString cmd, QStringList args) { received(cmd, args); } void KJavaAppletContext::received(const QString &cmd, const QStringList &arg) { // qDebug() << "KJavaAppletContext::received, cmd = >>" << cmd << "<<"; // qDebug() << "arg count = " << arg.count(); if (cmd == QLatin1String("showstatus") && !arg.empty()) { QString tmp = arg.first(); tmp.remove(QRegExp("[\n\r]")); // qDebug() << "status message = " << tmp; emit showStatus(tmp); } else if (cmd == QLatin1String("showurlinframe") && arg.count() > 1) { // qDebug() << "url = " << arg[0] << ", frame = " << arg[1]; emit showDocument(arg[0], arg[1]); } else if (cmd == QLatin1String("showdocument") && !arg.empty()) { // qDebug() << "url = " << arg.first(); emit showDocument(arg.first(), "_top"); } else if (cmd == QLatin1String("resizeapplet") && arg.count() > 2) { //arg[1] should be appletID //arg[2] should be new width //arg[3] should be new height bool ok; const int appletID = arg[0].toInt(&ok); const int width = arg[1].toInt(&ok); const int height = arg[2].toInt(&ok); if (!ok) { - qCritical() << "could not parse out parameters for resize" << endl; + qCritical() << "could not parse out parameters for resize"; } else { KJavaApplet *const tmp = d->applets[appletID]; if (tmp) { tmp->resizeAppletWidget(width, height); } } } else if (cmd.startsWith(QLatin1String("audioclip_"))) { // qDebug() << "process Audio command (not yet implemented): " << cmd << " " << arg[0]; } else if (cmd == QLatin1String("JS_Event") && arg.count() > 2) { bool ok; const int appletID = arg.first().toInt(&ok); KJavaApplet *applet; if (ok && (applet = d->applets[appletID])) { QStringList js_args(arg); js_args.pop_front(); applet->jsData(js_args); } else { - qCritical() << "parse JS event " << arg[0] << " " << arg[1] << endl; + qCritical() << "parse JS event " << arg[0] << " " << arg[1]; } } else if (cmd == QLatin1String("AppletStateNotification")) { bool ok; const int appletID = arg.first().toInt(&ok); if (ok) { KJavaApplet *const applet = d->applets[appletID]; if (applet) { const int newState = arg[1].toInt(&ok); if (ok) { applet->stateChange(newState); if (newState == KJavaApplet::INITIALIZED) { // qDebug() << "emit appletLoaded"; emit appletLoaded(); } } else { - qCritical() << "AppletStateNotification: status is not numerical" << endl; + qCritical() << "AppletStateNotification: status is not numerical"; } } else { qWarning() << "AppletStateNotification: No such Applet with ID=" << arg[0]; } } else { - qCritical() << "AppletStateNotification: Applet ID is not numerical" << endl; + qCritical() << "AppletStateNotification: Applet ID is not numerical"; } } else if (cmd == QLatin1String("AppletFailed")) { bool ok; const int appletID = arg.first().toInt(&ok); if (ok) { KJavaApplet *const applet = d->applets[appletID]; /* QString errorDetail(arg[1]); errorDetail.replace(QRegExp(":\\s*"), ":\n"); KMessageBox::detailedError(0L, i18n("Java error while loading applet."), errorDetail); */ if (applet) { applet->setFailed(); } emit appletLoaded(); } } } void KJavaAppletContext::javaProcessExited(int) { AppletMap::iterator it = d->applets.begin(); const AppletMap::iterator itEnd = d->applets.end(); for (; it != itEnd; ++it) if (!(*it).isNull() && (*it)->isCreated() && !(*it)->failed()) { (*it)->setFailed(); if ((*it)->state() < KJavaApplet::INITIALIZED) { emit appletLoaded(); } } } bool KJavaAppletContext::getMember(QStringList &args, QStringList &ret_args) { args.push_front(QString::number(id)); return server->getMember(args, ret_args); } bool KJavaAppletContext::putMember(QStringList &args) { args.push_front(QString::number(id)); return server->putMember(args); } bool KJavaAppletContext::callMember(QStringList &args, QStringList &ret_args) { args.push_front(QString::number(id)); return server->callMember(args, ret_args); } void KJavaAppletContext::derefObject(QStringList &args) { args.push_front(QString::number(id)); server->derefObject(args); } diff --git a/src/java/kjavaappletserver.cpp b/src/java/kjavaappletserver.cpp index 1eea212..932cd1f 100644 --- a/src/java/kjavaappletserver.cpp +++ b/src/java/kjavaappletserver.cpp @@ -1,867 +1,867 @@ /* This file is part of the KDE project * * Copyright (C) 2000 Richard Moore <rich@kde.org> * 2000 Wynn Wilkes <wynnw@caldera.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kjavaappletserver.h" #include "kjavaappletcontext.h" #include "kjavaprocess.h" #include "kjavadownloader.h" #include <QDebug> #include <kconfig.h> #include <kconfiggroup.h> #include <klocalizedstring.h> #include <kparts/browserextension.h> #include <kio/job.h> #include <kprotocolmanager.h> #include <qsslcertificate.h> #include <QtCore/QTimer> #include <QtCore/QPointer> #include <QtCore/QDir> #include <QtCore/QEventLoop> #include <QSslSocket> #include <QApplication> #include <QLabel> #include <QDialog> #include <QPushButton> #include <QLayout> #include <QtCore/QRegExp> #include <stdlib.h> #include <assert.h> #include <QtCore/QAbstractEventDispatcher> #include <qstandardpaths.h> #define KJAS_CREATE_CONTEXT (char)1 #define KJAS_DESTROY_CONTEXT (char)2 #define KJAS_CREATE_APPLET (char)3 #define KJAS_DESTROY_APPLET (char)4 #define KJAS_START_APPLET (char)5 #define KJAS_STOP_APPLET (char)6 #define KJAS_INIT_APPLET (char)7 #define KJAS_SHOW_DOCUMENT (char)8 #define KJAS_SHOW_URLINFRAME (char)9 #define KJAS_SHOW_STATUS (char)10 #define KJAS_RESIZE_APPLET (char)11 #define KJAS_GET_URLDATA (char)12 #define KJAS_URLDATA (char)13 #define KJAS_SHUTDOWN_SERVER (char)14 #define KJAS_JAVASCRIPT_EVENT (char)15 #define KJAS_GET_MEMBER (char)16 #define KJAS_CALL_MEMBER (char)17 #define KJAS_PUT_MEMBER (char)18 #define KJAS_DEREF_OBJECT (char)19 #define KJAS_AUDIOCLIP_PLAY (char)20 #define KJAS_AUDIOCLIP_LOOP (char)21 #define KJAS_AUDIOCLIP_STOP (char)22 #define KJAS_APPLET_STATE (char)23 #define KJAS_APPLET_FAILED (char)24 #define KJAS_DATA_COMMAND (char)25 #define KJAS_PUT_URLDATA (char)26 #define KJAS_PUT_DATA (char)27 #define KJAS_SECURITY_CONFIRM (char)28 #define KJAS_SHOW_CONSOLE (char)29 class JSStackFrame; typedef QMap< int, KJavaKIOJob * > KIOJobMap; typedef QMap< int, JSStackFrame * > JSStack; class JSStackFrame { public: JSStackFrame(JSStack &stack, QStringList &a) : jsstack(stack), args(a), ticket(counter++), ready(false), exit(false) { jsstack.insert(ticket, this); } ~JSStackFrame() { jsstack.remove(ticket); } JSStack &jsstack; QStringList &args; int ticket; bool ready : 1; bool exit : 1; static int counter; }; int JSStackFrame::counter = 0; class KJavaAppletServerPrivate { friend class KJavaAppletServer; private: KJavaAppletServerPrivate() {} ~KJavaAppletServerPrivate() { } int counter; QMap< int, QPointer<KJavaAppletContext> > contexts; QString appletLabel; JSStack jsstack; KIOJobMap kiojobs; bool javaProcessFailed; bool useKIO; //int locked_context; //QValueList<QByteArray> java_requests; }; static KJavaAppletServer *self = nullptr; KJavaAppletServer::KJavaAppletServer() : d(new KJavaAppletServerPrivate) { process = new KJavaProcess(); connect(process, SIGNAL(received(QByteArray)), this, SLOT(slotJavaRequest(QByteArray))); setupJava(process); if (process->startJava()) { d->appletLabel = i18n("Loading Applet"); d->javaProcessFailed = false; } else { d->appletLabel = i18n("Error: java executable not found"); d->javaProcessFailed = true; } } KJavaAppletServer::~KJavaAppletServer() { disconnect(process, nullptr, nullptr, nullptr); // first disconnect from process. quit(); delete process; process = nullptr; delete d; } QString KJavaAppletServer::getAppletLabel() { if (self) { return self->appletLabel(); } else { return QString(); } } QString KJavaAppletServer::appletLabel() { return d->appletLabel; } KJavaAppletServer *KJavaAppletServer::allocateJavaServer() { if (self == nullptr) { self = new KJavaAppletServer(); self->d->counter = 0; } ++(self->d->counter); return self; } void KJavaAppletServer::freeJavaServer() { --(self->d->counter); if (self->d->counter == 0) { //instead of immediately quitting here, set a timer to kill us //if there are still no servers- give us one minute //this is to prevent repeated loading and unloading of the jvm KConfig config("konquerorrc"); KConfigGroup group = config.group("Java/JavaScript Settings"); if (group.readEntry("ShutdownAppletServer", true)) { const int value = group.readEntry("AppletServerTimeout", 60); QTimer::singleShot(value * 1000, self, SLOT(checkShutdown())); } } } void KJavaAppletServer::checkShutdown() { if (self->d->counter == 0) { delete self; self = nullptr; } } void KJavaAppletServer::setupJava(KJavaProcess *p) { KConfig configFile("konquerorrc"); KConfigGroup config(&configFile, "Java/JavaScript Settings"); QString jvm_path = "java"; QString jPath = config.readPathEntry("JavaPath", QString()); if (!jPath.isEmpty() && jPath != "java") { // Cut off trailing slash if any if (jPath[jPath.length() - 1] == '/') { jPath.remove(jPath.length() - 1, 1); } QDir dir(jPath); if (dir.exists("bin/java")) { jvm_path = jPath + "/bin/java"; } else if (dir.exists("/jre/bin/java")) { jvm_path = jPath + "/jre/bin/java"; } else if (QFile::exists(jPath)) { //check here to see if they entered the whole path the java exe jvm_path = jPath; } } //check to see if jvm_path is valid and set d->appletLabel accordingly p->setJVMPath(jvm_path); // Prepare classpath variable QString kjava_class = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kf5/kjava/kjava.jar"); // qDebug() << "kjava_class = " << kjava_class; if (kjava_class.isNull()) { // Should not happen return; } QDir dir(kjava_class); dir.cdUp(); // qDebug() << "dir = " << dir.absolutePath(); const QStringList entries = dir.entryList(QDir::nameFiltersFromString("*.jar")); // qDebug() << "entries = " << entries.join( ":" ); QString classes; { QStringList::ConstIterator it = entries.begin(); const QStringList::ConstIterator itEnd = entries.end(); for (; it != itEnd; ++it) { if (!classes.isEmpty()) { classes += ':'; } classes += dir.absoluteFilePath(*it); } } p->setClasspath(classes); // Fix all the extra arguments const QString extraArgs = config.readEntry("JavaArgs"); p->setExtraArgs(extraArgs); if (config.readEntry("UseSecurityManager", true)) { QString class_file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kjava/kjava.policy"); p->setSystemProperty("java.security.policy", class_file); p->setSystemProperty("java.security.manager", "org.kde.kjas.server.KJASSecurityManager"); } d->useKIO = config.readEntry("UseKio", false); if (d->useKIO) { p->setSystemProperty("kjas.useKio", QString()); } //check for http proxies... if (KProtocolManager::useProxy()) { // only proxyForUrl honors automatic proxy scripts // we do not know the applet url here so we just use a dummy url // this is a workaround for now // FIXME const QUrl dummyURL("http://www.kde.org/"); const QString httpProxy = KProtocolManager::proxyForUrl(dummyURL); // qDebug() << "httpProxy is " << httpProxy; const QUrl url(httpProxy); p->setSystemProperty("http.proxyHost", url.host()); p->setSystemProperty("http.proxyPort", QString::number(url.port())); } //set the main class to run p->setMainClass("org.kde.kjas.server.Main"); } void KJavaAppletServer::createContext(int contextId, KJavaAppletContext *context) { // qDebug() << "createContext: " << contextId; if (d->javaProcessFailed) { return; } d->contexts.insert(contextId, context); QStringList args; args.append(QString::number(contextId)); process->send(KJAS_CREATE_CONTEXT, args); } void KJavaAppletServer::destroyContext(int contextId) { // qDebug() << "destroyContext: " << contextId; if (d->javaProcessFailed) { return; } d->contexts.remove(contextId); QStringList args; args.append(QString::number(contextId)); process->send(KJAS_DESTROY_CONTEXT, args); } bool KJavaAppletServer::createApplet(int contextId, int appletId, const QString &name, const QString &clazzName, const QString &baseURL, const QString &user, const QString &password, const QString &authname, const QString &codeBase, const QString &jarFile, QSize size, const QMap<QString, QString> ¶ms, const QString &windowTitle) { // qDebug() << "createApplet: contextId = " << contextId << endl // << " appletId = " << appletId << endl // << " name = " << name << endl // << " clazzName = " << clazzName << endl // << " baseURL = " << baseURL << endl // << " codeBase = " << codeBase << endl // << " jarFile = " << jarFile << endl // << " width = " << size.width() << endl // << " height = " << size.height() << endl; if (d->javaProcessFailed) { return false; } QStringList args; args.append(QString::number(contextId)); args.append(QString::number(appletId)); //it's ok if these are empty strings, I take care of it later... args.append(name); args.append(clazzName); args.append(baseURL); args.append(user); args.append(password); args.append(authname); args.append(codeBase); args.append(jarFile); args.append(QString::number(size.width())); args.append(QString::number(size.height())); args.append(windowTitle); //add on the number of parameter pairs... const int num = params.count(); const QString num_params = QString("%1").arg(num, 8); args.append(num_params); QMap< QString, QString >::ConstIterator it = params.begin(); const QMap< QString, QString >::ConstIterator itEnd = params.end(); for (; it != itEnd; ++it) { args.append(it.key()); args.append(it.value()); } process->send(KJAS_CREATE_APPLET, args); return true; } void KJavaAppletServer::initApplet(int contextId, int appletId) { if (d->javaProcessFailed) { return; } QStringList args; args.append(QString::number(contextId)); args.append(QString::number(appletId)); process->send(KJAS_INIT_APPLET, args); } void KJavaAppletServer::destroyApplet(int contextId, int appletId) { if (d->javaProcessFailed) { return; } QStringList args; args.append(QString::number(contextId)); args.append(QString::number(appletId)); process->send(KJAS_DESTROY_APPLET, args); } void KJavaAppletServer::startApplet(int contextId, int appletId) { if (d->javaProcessFailed) { return; } QStringList args; args.append(QString::number(contextId)); args.append(QString::number(appletId)); process->send(KJAS_START_APPLET, args); } void KJavaAppletServer::stopApplet(int contextId, int appletId) { if (d->javaProcessFailed) { return; } QStringList args; args.append(QString::number(contextId)); args.append(QString::number(appletId)); process->send(KJAS_STOP_APPLET, args); } void KJavaAppletServer::showConsole() { if (d->javaProcessFailed) { return; } QStringList args; process->send(KJAS_SHOW_CONSOLE, args); } void KJavaAppletServer::sendURLData(int loaderID, int code, const QByteArray &data) { QStringList args; args.append(QString::number(loaderID)); args.append(QString::number(code)); process->send(KJAS_URLDATA, args, data); } void KJavaAppletServer::removeDataJob(int loaderID) { const KIOJobMap::iterator it = d->kiojobs.find(loaderID); if (it != d->kiojobs.end()) { it.value()->deleteLater(); d->kiojobs.erase(it); } } void KJavaAppletServer::quit() { const QStringList args; process->send(KJAS_SHUTDOWN_SERVER, args); process->waitForFinished(10000); } void KJavaAppletServer::slotJavaRequest(const QByteArray &qb) { // qb should be one command only without the length string, // we parse out the command and it's meaning here... QString cmd; QStringList args; int index = 0; const int qb_size = qb.size(); //get the command code const char cmd_code = qb[ index++ ]; ++index; //skip the next sep //get contextID QString contextID; while (index < qb_size && qb[index] != 0) { contextID += qb[ index++ ]; } bool ok; const int ID_num = contextID.toInt(&ok); // context id or kio job id /*if (d->locked_context > -1 && ID_num != d->locked_context && (cmd_code == KJAS_JAVASCRIPT_EVENT || cmd_code == KJAS_APPLET_STATE || cmd_code == KJAS_APPLET_FAILED)) { / * Don't allow requests from other contexts if we're waiting * on a return value that can trigger JavaScript events * / d->java_requests.push_back(qb); return; }*/ ++index; //skip the sep if (cmd_code == KJAS_PUT_DATA) { // rest of the data is for kio put if (ok) { KIOJobMap::iterator it = d->kiojobs.find(ID_num); if (ok && it != d->kiojobs.end()) { QByteArray qba; qba = QByteArray::fromRawData(qb.data() + index, qb.size() - index - 1); it.value()->data(qba); qba = QByteArray::fromRawData(qb.data() + index, qb.size() - index - 1); } // qDebug() << "PutData(" << ID_num << ") size=" << qb.size() - index; } else { - qCritical() << "PutData error " << ok << endl; + qCritical() << "PutData error " << ok; } return; } //now parse out the arguments while (index < qb_size) { int sep_pos = qb.indexOf((char) 0, index); if (sep_pos < 0) { - qCritical() << "Missing separation byte" << endl; + qCritical() << "Missing separation byte"; sep_pos = qb_size; } //qDebug() << "KJavaAppletServer::slotJavaRequest: "<< QString::fromLocal8Bit( qb.data() + index, sep_pos - index ); args.append(QString::fromLocal8Bit(qb.data() + index, sep_pos - index)); index = sep_pos + 1; //skip the sep } //here I should find the context and call the method directly //instead of emitting signals switch (cmd_code) { case KJAS_SHOW_DOCUMENT: cmd = QLatin1String("showdocument"); break; case KJAS_SHOW_URLINFRAME: cmd = QLatin1String("showurlinframe"); break; case KJAS_SHOW_STATUS: cmd = QLatin1String("showstatus"); break; case KJAS_RESIZE_APPLET: cmd = QLatin1String("resizeapplet"); break; case KJAS_GET_URLDATA: if (ok && !args.empty()) { d->kiojobs.insert(ID_num, new KJavaDownloader(ID_num, args.first())); // qDebug() << "GetURLData(" << ID_num << ") url=" << args.first(); } else { - qCritical() << "GetURLData error " << ok << " args:" << args.size() << endl; + qCritical() << "GetURLData error " << ok << " args:" << args.size(); } return; case KJAS_PUT_URLDATA: if (ok && !args.empty()) { KJavaUploader *const job = new KJavaUploader(ID_num, args.first()); d->kiojobs.insert(ID_num, job); job->start(); // qDebug() << "PutURLData(" << ID_num << ") url=" << args.first(); } else { - qCritical() << "PutURLData error " << ok << " args:" << args.size() << endl; + qCritical() << "PutURLData error " << ok << " args:" << args.size(); } return; case KJAS_DATA_COMMAND: if (ok && !args.empty()) { const int cmd = args.first().toInt(&ok); KIOJobMap::iterator it = d->kiojobs.find(ID_num); if (ok && it != d->kiojobs.end()) { it.value()->jobCommand(cmd); } // qDebug() << "KIO Data command: " << ID_num << " " << args.first(); } else { - qCritical() << "KIO Data command error " << ok << " args:" << args.size() << endl; + qCritical() << "KIO Data command error " << ok << " args:" << args.size(); } return; case KJAS_JAVASCRIPT_EVENT: cmd = QLatin1String("JS_Event"); if (!args.empty()) { // qDebug() << "Javascript request: "<< contextID // << " code: " << args[0] << endl; } else { - qCritical() << "Expected args not to be empty!" << endl; + qCritical() << "Expected args not to be empty!"; } break; case KJAS_GET_MEMBER: case KJAS_PUT_MEMBER: case KJAS_CALL_MEMBER: { if (!args.empty()) { const int ticket = args[0].toInt(); JSStack::iterator it = d->jsstack.find(ticket); if (it != d->jsstack.end()) { // qDebug() << "slotJavaRequest: " << ticket; args.pop_front(); it.value()->args.operator = (args); // just in case .. it.value()->ready = true; it.value()->exit = true; } else { // qDebug() << "Error: Missed return member data"; } } else { - qCritical() << "Expected args not to be empty!" << endl; + qCritical() << "Expected args not to be empty!"; } return; } case KJAS_AUDIOCLIP_PLAY: cmd = QLatin1String("audioclip_play"); if (!args.empty()) { // qDebug() << "Audio Play: url=" << args[0]; } else { - qCritical() << "Expected args not to be empty!" << endl; + qCritical() << "Expected args not to be empty!"; } break; case KJAS_AUDIOCLIP_LOOP: cmd = QLatin1String("audioclip_loop"); if (!args.empty()) { // qDebug() << "Audio Loop: url=" << args[0]; } else { - qCritical() << "Expected args not to be empty!" << endl; + qCritical() << "Expected args not to be empty!"; } break; case KJAS_AUDIOCLIP_STOP: cmd = QLatin1String("audioclip_stop"); if (!args.empty()) { // qDebug() << "Audio Stop: url=" << args[0]; } else { - qCritical() << "Expected args not to be empty!" << endl; + qCritical() << "Expected args not to be empty!"; } break; case KJAS_APPLET_STATE: if (args.size() > 1) { // qDebug() << "Applet State Notification for Applet " << args[0] << ". New state=" << args[1]; } else { - qCritical() << "Expected args not to be empty!" << endl; + qCritical() << "Expected args not to be empty!"; } cmd = QLatin1String("AppletStateNotification"); break; case KJAS_APPLET_FAILED: if (args.size() > 1) { // qDebug() << "Applet " << args[0] << " Failed: " << args[1]; } else { - qCritical() << "Expected args not to be empty!" << endl; + qCritical() << "Expected args not to be empty!"; } cmd = QLatin1String("AppletFailed"); break; case KJAS_SECURITY_CONFIRM: { QStringList sl; QString answer("invalid"); if (!QSslSocket::supportsSsl()) { answer = "nossl"; } else if (args.size() > 2) { const int certsnr = args[1].toInt(); Q_ASSERT(args.size() > certsnr + 1); QString text; for (int i = certsnr - 1; i >= 0; --i) { const QByteArray &arg = args[i + 2].toLatin1(); QSslCertificate cert(arg); if (!cert.isNull()) { #if 0 // KDE 5 TODO: finish port if (cert.isSigner()) { text += i18n("Signed by (validation: %1)", KSSLCertificate::verifyText(cert.validate())); } else { text += i18n("Certificate (validation: %1)", KSSLCertificate::verifyText(cert.validate())); } text += "\n"; QString subject = cert.getSubject() + QChar('\n'); QRegExp reg(QString("/[A-Z]+=")); int pos = 0; while ((pos = subject.indexOf(reg, pos)) > -1) { subject.replace(pos, 1, QString("\n ")); } text += subject.mid(1); #else text += "TODO Security confirm"; #endif } } // qDebug() << "Security confirm " << args.first() << certs.count(); if (!text.isEmpty()) { answer = PermissionDialog(qApp->activeWindow()).exec(text, args[0]); } } sl.push_front(answer); sl.push_front(QString::number(ID_num)); process->send(KJAS_SECURITY_CONFIRM, sl); return; } default: return; break; } if (!ok) { - qCritical() << "could not parse out contextID to call command on" << endl; + qCritical() << "could not parse out contextID to call command on"; return; } KJavaAppletContext *const context = d->contexts[ ID_num ]; if (context) { context->processCmd(cmd, args); } else if (cmd != "AppletStateNotification") { - qCritical() << "no context object for this id" << endl; + qCritical() << "no context object for this id"; } } void KJavaAppletServer::killTimers() { QAbstractEventDispatcher::instance()->unregisterTimers(this); } void KJavaAppletServer::endWaitForReturnData() { // qDebug() << "KJavaAppletServer::endWaitForReturnData"; killTimers(); JSStack::iterator it = d->jsstack.begin(); JSStack::iterator itEnd = d->jsstack.end(); for (; it != itEnd; ++it) { it.value()->exit = true; } } void KJavaAppletServer::timerEvent(QTimerEvent *) { endWaitForReturnData(); // qDebug() << "KJavaAppletServer::timerEvent timeout"; } void KJavaAppletServer::waitForReturnData(JSStackFrame *frame) { // qDebug() << ">KJavaAppletServer::waitForReturnData"; killTimers(); startTimer(15000); while (!frame->exit) { QAbstractEventDispatcher::instance()->processEvents(QEventLoop::AllEvents | QEventLoop::WaitForMoreEvents); } if (d->jsstack.size() <= 1) { killTimers(); } // qDebug() << "<KJavaAppletServer::waitForReturnData stacksize:" << d->jsstack.size(); } bool KJavaAppletServer::getMember(QStringList &args, QStringList &ret_args) { JSStackFrame frame(d->jsstack, ret_args); args.push_front(QString::number(frame.ticket)); process->send(KJAS_GET_MEMBER, args); waitForReturnData(&frame); return frame.ready; } bool KJavaAppletServer::putMember(QStringList &args) { QStringList ret_args; JSStackFrame frame(d->jsstack, ret_args); args.push_front(QString::number(frame.ticket)); process->send(KJAS_PUT_MEMBER, args); waitForReturnData(&frame); return frame.ready && ret_args.count() > 0 && ret_args[0].toInt(); } bool KJavaAppletServer::callMember(QStringList &args, QStringList &ret_args) { JSStackFrame frame(d->jsstack, ret_args); args.push_front(QString::number(frame.ticket)); process->send(KJAS_CALL_MEMBER, args); waitForReturnData(&frame); return frame.ready; } void KJavaAppletServer::derefObject(QStringList &args) { process->send(KJAS_DEREF_OBJECT, args); } bool KJavaAppletServer::usingKIO() { return d->useKIO; } PermissionDialog::PermissionDialog(QWidget *parent) : QObject(parent), m_button("no") {} QString PermissionDialog::exec(const QString &cert, const QString &perm) { QPointer<QDialog> dialog = new QDialog(static_cast<QWidget *>(parent())); dialog->setObjectName("PermissionDialog"); QSizePolicy sizeplcy(QSizePolicy::Minimum, QSizePolicy::Minimum); sizeplcy.setHeightForWidth(dialog->sizePolicy().hasHeightForWidth()); dialog->setSizePolicy(sizeplcy); dialog->setModal(true); dialog->setWindowTitle(i18n("Security Alert")); QVBoxLayout *const dialogLayout = new QVBoxLayout(dialog); dialogLayout->setObjectName("dialogLayout"); dialogLayout->addWidget(new QLabel(i18n("Do you grant Java applet with certificate(s):"), dialog)); dialogLayout->addWidget(new QLabel(cert, dialog)); dialogLayout->addWidget(new QLabel(i18n("the following permission"), dialog)); dialogLayout->addWidget(new QLabel(perm, dialog)); QSpacerItem *const spacer2 = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding); dialogLayout->addItem(spacer2); QHBoxLayout *const buttonLayout = new QHBoxLayout(); buttonLayout->setMargin(0); buttonLayout->setObjectName("buttonLayout"); QPushButton *const no = new QPushButton(i18n("&No"), dialog); no->setObjectName("no"); no->setDefault(true); buttonLayout->addWidget(no); QPushButton *const reject = new QPushButton(i18n("&Reject All"), dialog); reject->setObjectName("reject"); buttonLayout->addWidget(reject); QPushButton *const yes = new QPushButton(i18n("&Yes"), dialog); yes->setObjectName("yes"); buttonLayout->addWidget(yes); QPushButton *const grant = new QPushButton(i18n("&Grant All"), dialog); grant->setObjectName("grant"); buttonLayout->addWidget(grant); dialogLayout->addLayout(buttonLayout); dialog->resize(dialog->minimumSizeHint()); //clearWState( WState_Polished ); connect(no, SIGNAL(clicked()), this, SLOT(clicked())); connect(reject, SIGNAL(clicked()), this, SLOT(clicked())); connect(yes, SIGNAL(clicked()), this, SLOT(clicked())); connect(grant, SIGNAL(clicked()), this, SLOT(clicked())); dialog->exec(); delete dialog; return m_button; } PermissionDialog::~PermissionDialog() {} void PermissionDialog::clicked() { m_button = sender()->objectName(); static_cast<const QWidget *>(sender())->parentWidget()->close(); } diff --git a/src/java/kjavaappletwidget.cpp b/src/java/kjavaappletwidget.cpp index 586f667..b2ec2de 100644 --- a/src/java/kjavaappletwidget.cpp +++ b/src/java/kjavaappletwidget.cpp @@ -1,148 +1,148 @@ /* This file is part of the KDE project * * Copyright (C) 2000 Richard Moore <rich@kde.org> * 2000 Wynn Wilkes <wynnw@caldera.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kjavaappletwidget.h" #include "kjavaappletserver.h" #include <kwindowsystem.h> #include <QDebug> #include <klocalizedstring.h> #include <QLabel> #include "../config-khtml.h" #if !HAVE_X11 #define QXEmbed QWidget #endif // For future expansion class KJavaAppletWidgetPrivate { friend class KJavaAppletWidget; private: QLabel *tmplabel; }; int KJavaAppletWidget::appletCount = 0; KJavaAppletWidget::KJavaAppletWidget(QWidget *parent) : QX11EmbedContainer(parent), d(new KJavaAppletWidgetPrivate) { //setProtocol(QXEmbed::XPLAIN); m_applet = new KJavaApplet(this); d->tmplabel = new QLabel(this); d->tmplabel->setText(KJavaAppletServer::getAppletLabel()); d->tmplabel->setAlignment(Qt::AlignCenter); d->tmplabel->setWordWrap(true); d->tmplabel->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); d->tmplabel->show(); m_swallowTitle.sprintf("KJAS Applet - Ticket number %u", appletCount++); m_applet->setWindowName(m_swallowTitle); } KJavaAppletWidget::~KJavaAppletWidget() { delete m_applet; delete d; } void KJavaAppletWidget::showApplet() { #if HAVE_X11 connect(KWindowSystem::self(), SIGNAL(windowAdded(WId)), this, SLOT(setWindow(WId))); //Now we send applet info to the applet server if (!m_applet->isCreated()) { m_applet->create(); } #endif } void KJavaAppletWidget::setWindow(WId w) { #pragma message ("Revive QX11EmbedContainer") #if 0 //make sure that this window has the right name, if so, embed it... KWindowInfo w_info = KWindowSystem::windowInfo(w, NET::WMVisibleName | NET::WMName); if (m_swallowTitle == w_info.name() || m_swallowTitle == w_info.visibleName()) { KWindowSystem::setState(w, NET::Hidden | NET::SkipTaskbar | NET::SkipPager); // qDebug() << "swallowing our window: " << m_swallowTitle - << ", window id = " << w << endl; + << ", window id = " << w; delete d->tmplabel; d->tmplabel = 0; // disconnect from KWM events disconnect(KWindowSystem::self(), SIGNAL(windowAdded(WId)), this, SLOT(setWindow(WId))); embedClient(w); setFocus(); } #else //TODO #endif } QSize KJavaAppletWidget::sizeHint() const { // qDebug() << "KJavaAppletWidget::sizeHint()"; QSize rval = QX11EmbedContainer::sizeHint(); if (rval.width() == 0 || rval.height() == 0) { if (width() != 0 && height() != 0) { rval = QSize(width(), height()); } } // qDebug() << "returning: (" << rval.width() << ", " << rval.height() << ")"; return rval; } void KJavaAppletWidget::resize(int w, int h) { if (d->tmplabel) { d->tmplabel->resize(w, h); m_applet->setSize(QSize(w, h)); } QX11EmbedContainer::resize(w, h); } void KJavaAppletWidget::showEvent(QShowEvent *e) { QX11EmbedContainer::showEvent(e); if (!applet()->isCreated() && !applet()->appletClass().isEmpty()) { // delayed showApplet if (applet()->size().width() <= 0) { applet()->setSize(sizeHint()); } showApplet(); } } diff --git a/src/java/kjavadownloader.cpp b/src/java/kjavadownloader.cpp index e216a61..8e14faf 100644 --- a/src/java/kjavadownloader.cpp +++ b/src/java/kjavadownloader.cpp @@ -1,303 +1,303 @@ /* This file is part of the KDE project * * Copyright (C) 2000 Richard Moore <rich@kde.org> * 2000 Wynn Wilkes <wynnw@caldera.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kjavadownloader.h" #include "kjavaappletserver.h" #include <kio/job.h> #include <kio/jobclasses.h> #include <QDebug> #include <QtCore/QFile> #include <QUrl> static const int DATA = 0; static const int FINISHED = 1; static const int ERRORCODE = 2; static const int HEADERS = 3; static const int REDIRECT = 4; static const int MIMETYPE = 5; static const int CONNECTED = 6; static const int REQUESTDATA = 7; static const int KJAS_STOP = 0; static const int KJAS_HOLD = 1; static const int KJAS_RESUME = 2; KJavaKIOJob::~KJavaKIOJob() {} void KJavaKIOJob::data(const QByteArray &) { - qCritical() << "Job id mixup" << endl; + qCritical() << "Job id mixup"; } //----------------------------------------------------------------------------- class KJavaDownloaderPrivate { friend class KJavaDownloader; public: KJavaDownloaderPrivate() : responseCode(0), isfirstdata(true) {} ~KJavaDownloaderPrivate() { delete url; if (job) { job->kill(); // KIO::Job::kill deletes itself } } private: int loaderID; QUrl *url; QByteArray file; KIO::TransferJob *job; int responseCode; bool isfirstdata; }; KJavaDownloader::KJavaDownloader(int ID, const QString &url) : d(new KJavaDownloaderPrivate) { // qDebug() << "KJavaDownloader(" << ID << ") = " << url; d->loaderID = ID; d->url = new QUrl(url); d->job = KIO::get(*d->url, KIO::NoReload, KIO::HideProgressInfo); d->job->addMetaData("PropagateHttpHeader", "true"); connect(d->job, SIGNAL(data(KIO::Job*,QByteArray)), this, SLOT(slotData(KIO::Job*,QByteArray))); connect(d->job, SIGNAL(connected(KIO::Job*)), this, SLOT(slotConnected(KIO::Job*))); connect(d->job, SIGNAL(mimetype(KIO::Job*,QString)), this, SLOT(slotMimetype(KIO::Job*,QString))); connect(d->job, SIGNAL(result(KJob*)), this, SLOT(slotResult(KJob*))); } KJavaDownloader::~KJavaDownloader() { delete d; } void KJavaDownloader::slotData(KIO::Job *, const QByteArray &qb) { //qDebug() << "slotData(" << d->loaderID << ")"; KJavaAppletServer *server = KJavaAppletServer::allocateJavaServer(); if (d->isfirstdata) { QString headers = d->job->queryMetaData("HTTP-Headers"); if (!headers.isEmpty()) { d->file.resize(headers.length()); memcpy(d->file.data(), headers.toLatin1().constData(), headers.length()); server->sendURLData(d->loaderID, HEADERS, d->file); d->file.resize(0); } d->isfirstdata = false; } if (qb.size()) { server->sendURLData(d->loaderID, DATA, qb); } KJavaAppletServer::freeJavaServer(); } void KJavaDownloader::slotConnected(KIO::Job *) { // qDebug() << "slave connected"; d->responseCode = d->job->error(); } void KJavaDownloader::slotMimetype(KIO::Job *, const QString &type) { // qDebug() << "slave mimetype " << type; } void KJavaDownloader::slotResult(KJob *) { // qDebug() << "slotResult(" << d->loaderID << ")"; KJavaAppletServer *server = KJavaAppletServer::allocateJavaServer(); if (d->job->error()) { // qDebug() << "slave had an error = " << d->job->errorString(); int code = d->job->error(); if (!code) { code = 404; } QString codestr = QString::number(code); d->file.resize(codestr.length()); memcpy(d->file.data(), codestr.toLatin1().constData(), codestr.length()); // qDebug() << "slave had an error = " << code; server->sendURLData(d->loaderID, ERRORCODE, d->file); d->file.resize(0); } else { server->sendURLData(d->loaderID, FINISHED, d->file); } d->job = nullptr; // signal KIO::Job::result deletes itself server->removeDataJob(d->loaderID); // will delete this KJavaAppletServer::freeJavaServer(); } void KJavaDownloader::jobCommand(int cmd) { if (!d->job) { return; } switch (cmd) { case KJAS_STOP: { // qDebug() << "jobCommand(" << d->loaderID << ") stop"; d->job->kill(); d->job = nullptr; // KIO::Job::kill deletes itself KJavaAppletServer *server = KJavaAppletServer::allocateJavaServer(); server->removeDataJob(d->loaderID); // will delete this KJavaAppletServer::freeJavaServer(); break; } case KJAS_HOLD: // qDebug() << "jobCommand(" << d->loaderID << ") hold"; d->job->suspend(); break; case KJAS_RESUME: // qDebug() << "jobCommand(" << d->loaderID << ") resume"; d->job->resume(); break; } } //----------------------------------------------------------------------------- class KJavaUploaderPrivate { public: KJavaUploaderPrivate() {} ~KJavaUploaderPrivate() { delete url; if (job) { job->kill(); // KIO::Job::kill deletes itself } } int loaderID; QUrl *url; QByteArray file; KIO::TransferJob *job; bool finished; }; KJavaUploader::KJavaUploader(int ID, const QString &url) : d(new KJavaUploaderPrivate) { // qDebug() << "KJavaUploader(" << ID << ") = " << url; d->loaderID = ID; d->url = new QUrl(url); d->job = nullptr; d->finished = false; } void KJavaUploader::start() { // qDebug() << "KJavaUploader::start(" << d->loaderID << ")"; KJavaAppletServer *server = KJavaAppletServer::allocateJavaServer(); // create a suspended job d->job = KIO::put(*d->url, -1, KIO::HideProgressInfo); d->job->suspend(); connect(d->job, SIGNAL(dataReq(KIO::Job*,QByteArray&)), this, SLOT(slotDataRequest(KIO::Job*,QByteArray&))); connect(d->job, SIGNAL(result(KJob*)), this, SLOT(slotResult(KJob*))); server->sendURLData(d->loaderID, CONNECTED, d->file); KJavaAppletServer::freeJavaServer(); } KJavaUploader::~KJavaUploader() { delete d; } void KJavaUploader::slotDataRequest(KIO::Job *, QByteArray &qb) { // send our data and suspend // qDebug() << "slotDataRequest(" << d->loaderID << ") finished:" << d->finished; qb.resize(d->file.size()); KJavaAppletServer *server = KJavaAppletServer::allocateJavaServer(); if (d->file.size() == 0) { d->job = nullptr; // eof, job deletes itself server->removeDataJob(d->loaderID); // will delete this } else { memcpy(qb.data(), d->file.data(), d->file.size()); d->file.resize(0); if (!d->finished) { server->sendURLData(d->loaderID, REQUESTDATA, d->file); d->job->suspend(); } } KJavaAppletServer::freeJavaServer(); } void KJavaUploader::data(const QByteArray &qb) { // qDebug() << "KJavaUploader::data(" << d->loaderID << ")"; d->file.resize(qb.size()); memcpy(d->file.data(), qb.data(), qb.size()); d->job->resume(); } void KJavaUploader::slotResult(KJob *) { // qDebug() << "slotResult(" << d->loaderID << ") job:" << d->job; if (!d->job) { return; } KJavaAppletServer *server = KJavaAppletServer::allocateJavaServer(); if (d->job->error()) { int code = d->job->error(); QString codestr = QString::number(code); d->file.resize(codestr.length()); memcpy(d->file.data(), codestr.toLatin1().constData(), codestr.length()); // qDebug() << "slave had an error " << code << ": " << d->job->errorString(); server->sendURLData(d->loaderID, ERRORCODE, d->file); d->file.resize(0); } else { // shouldn't come here - qCritical() << "slotResult(" << d->loaderID << ") job:" << d->job << endl; + qCritical() << "slotResult(" << d->loaderID << ") job:" << d->job; } d->job = nullptr; // signal KIO::Job::result deletes itself server->removeDataJob(d->loaderID); // will delete this KJavaAppletServer::freeJavaServer(); } void KJavaUploader::jobCommand(int cmd) { if (!d->job) { return; } switch (cmd) { case KJAS_STOP: { // qDebug() << "jobCommand(" << d->loaderID << ") stop"; d->finished = true; if (d->job->isSuspended()) { d->job->resume(); } break; } } } diff --git a/src/java/kjavaprocess.cpp b/src/java/kjavaprocess.cpp index ffd7228..fe6b345 100644 --- a/src/java/kjavaprocess.cpp +++ b/src/java/kjavaprocess.cpp @@ -1,278 +1,278 @@ /* This file is part of the KDE project * * Copyright (C) 2000 Richard Moore <rich@kde.org> * 2000 Wynn Wilkes <wynnw@caldera.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kjavaprocess.h" #include <QDebug> #include <kshell.h> #include <kprotocolmanager.h> #include <QtCore/QTextStream> #include <QtCore/QMap> class KJavaProcessPrivate { friend class KJavaProcess; private: QString jvmPath; QString classPath; QString mainClass; QString extraArgs; QString classArgs; QMap<QString, QString> systemProps; }; KJavaProcess::KJavaProcess(QObject *parent) : QProcess(parent), d(new KJavaProcessPrivate) { connect(this, SIGNAL(readyReadStandardOutput()), this, SLOT(slotReceivedData())); connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotExited())); connect(this, SIGNAL(error(QProcess::ProcessError)), this, SLOT(slotExited())); d->jvmPath = "java"; d->mainClass = "-help"; } KJavaProcess::~KJavaProcess() { if (state() != NotRunning) { // qDebug() << "stopping java process"; stopJava(); } delete d; } bool KJavaProcess::isRunning() { return state() != NotRunning; } bool KJavaProcess::startJava() { return invokeJVM(); } void KJavaProcess::stopJava() { killJVM(); } void KJavaProcess::setJVMPath(const QString &path) { d->jvmPath = path; } void KJavaProcess::setClasspath(const QString &classpath) { d->classPath = classpath; } void KJavaProcess::setSystemProperty(const QString &name, const QString &value) { d->systemProps.insert(name, value); } void KJavaProcess::setMainClass(const QString &className) { d->mainClass = className; } void KJavaProcess::setExtraArgs(const QString &args) { d->extraArgs = args; } void KJavaProcess::setClassArgs(const QString &args) { d->classArgs = args; } //Private Utility Functions used by the two send() methods QByteArray KJavaProcess::addArgs(char cmd_code, const QStringList &args) { //the buffer to store stuff, etc. QByteArray buff; QTextStream output(&buff, QIODevice::ReadWrite); const char sep = 0; //make space for the command size: 8 characters... const QByteArray space(" "); output << space; //write command code output << cmd_code; //store the arguments... if (args.isEmpty()) { output << sep; } else { QStringList::ConstIterator it = args.begin(); const QStringList::ConstIterator itEnd = args.end(); for (; it != itEnd; ++it) { if (!(*it).isEmpty()) { output << (*it).toLocal8Bit(); } output << sep; } } return buff; } void KJavaProcess::storeSize(QByteArray *buff) { const int size = buff->size() - 8; //subtract out the length of the size_str const QString size_str = QString("%1").arg(size, 8); // qDebug() << "KJavaProcess::storeSize, size = " << size_str; for (int i = 0; i < 8; ++i) { buff->data()[ i ] = size_str[i].toLatin1(); } } void KJavaProcess::send(char cmd_code, const QStringList &args) { if (isRunning()) { QByteArray buff = addArgs(cmd_code, args); storeSize(&buff); // qDebug() << "<KJavaProcess::send " << (int)cmd_code; write(buff); } } void KJavaProcess::send(char cmd_code, const QStringList &args, const QByteArray &data) { if (isRunning()) { // qDebug() << "KJavaProcess::send, qbytearray is size = " << data.size(); QByteArray buff = addArgs(cmd_code, args); buff += data; storeSize(&buff); write(buff); } } bool KJavaProcess::invokeJVM() { QStringList args; if (!d->classPath.isEmpty()) { args << "-classpath"; args << d->classPath; } //set the system properties, iterate through the qmap of system properties QMap<QString, QString>::ConstIterator it = d->systemProps.constBegin(); const QMap<QString, QString>::ConstIterator itEnd = d->systemProps.constEnd(); for (; it != itEnd; ++it) { if (!it.key().isEmpty()) { QString currarg = "-D" + it.key(); if (!it.value().isEmpty()) { currarg += '=' + it.value(); } args << currarg; } } //load the extra user-defined arguments if (!d->extraArgs.isEmpty()) { KShell::Errors err; args += KShell::splitArgs(d->extraArgs, KShell::AbortOnMeta, &err); if (err != KShell::NoError) { qWarning() << "Extra args for JVM cannot be parsed, arguments = " << d->extraArgs; } } args << d->mainClass; if (!d->classArgs.isNull()) { args << d->classArgs; } // qDebug() << "Invoking JVM" << d->jvmPath << "now...with arguments = " << KShell::joinArgs(args); setProcessChannelMode(QProcess::SeparateChannels); start(d->jvmPath, args); return waitForStarted(); } void KJavaProcess::killJVM() { closeReadChannel(StandardOutput); terminate(); } /* In this method, read one command and send it to the d->appletServer * then return, so we don't block the event handling */ void KJavaProcess::slotReceivedData() { //read out the length of the message, //read the message and send it to the applet server char length[9] = { 0 }; const int num_bytes = read(length, 8); if (num_bytes == -1) { - qCritical() << "could not read 8 characters for the message length!!!!" << endl; + qCritical() << "could not read 8 characters for the message length!!!!"; return; } const QString lengthstr(length); bool ok; const int num_len = lengthstr.toInt(&ok); if (!ok) { - qCritical() << "could not parse length out of: " << lengthstr << endl; + qCritical() << "could not parse length out of: " << lengthstr; return; } //now parse out the rest of the message. char *const msg = new char[num_len]; const int num_bytes_msg = read(msg, num_len); if (num_bytes_msg == -1 || num_bytes_msg != num_len) { - qCritical() << "could not read the msg, num_bytes_msg = " << num_bytes_msg << endl; + qCritical() << "could not read the msg, num_bytes_msg = " << num_bytes_msg; delete[] msg; return; } emit received(QByteArray(msg, num_len)); delete[] msg; } void KJavaProcess::slotExited() { int status = -1; if (exitStatus() == NormalExit) { status = exitCode(); } // qDebug() << "jvm exited with status " << status; emit exited(status); } diff --git a/src/khtml_caret.cpp b/src/khtml_caret.cpp index 272efd5..9bc2ff1 100644 --- a/src/khtml_caret.cpp +++ b/src/khtml_caret.cpp @@ -1,2950 +1,2949 @@ /* This file is part of the KDE project * * Copyright (C) 2003-2004 Leo Savernik <l.savernik@aon.at> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "khtml_caret_p.h" #include "html/html_documentimpl.h" namespace khtml { /** Flags representing the type of advance that has been made. * @param LeftObject a render object was left and an ascent to its parent has * taken place * @param AdvancedToSibling an actual advance to a sibling has taken place * @param EnteredObject a render object was entered by descending into it from * its parent object. */ enum ObjectAdvanceState { LeftObject = 0x01, AdvancedToSibling = 0x02, EnteredObject = 0x04 }; /** All possible states that may occur during render object traversal. * @param OutsideDescending outside of the current object, ready to descend * into children * @param InsideDescending inside the current object, descending into * children * @param InsideAscending inside the current object, ascending to parents * @param OutsideAscending outsie the current object, ascending to parents */ enum ObjectTraversalState { OutsideDescending, InsideDescending, InsideAscending, OutsideAscending }; /** Traverses the render object tree in a fine granularity. * @param obj render object * @param trav object traversal state * @param toBegin traverse towards beginning * @param base base render object which this method must not advance beyond * (0 means document) * @param state object advance state (enum ObjectAdvanceState) * @return the render object according to the state. May be the same as \c obj */ static RenderObject *traverseRenderObjects(RenderObject *obj, ObjectTraversalState &trav, bool toBegin, RenderObject *base, int &state) { RenderObject *r; switch (trav) { case OutsideDescending: trav = InsideDescending; break; case InsideDescending: r = toBegin ? obj->lastChild() : obj->firstChild(); if (r) { trav = OutsideDescending; obj = r; state |= EnteredObject; } else { trav = InsideAscending; } break; case InsideAscending: trav = OutsideAscending; break; case OutsideAscending: r = toBegin ? obj->previousSibling() : obj->nextSibling(); if (r) { trav = OutsideDescending; state |= AdvancedToSibling; } else { r = obj->parent(); if (r == base) { r = 0; } trav = InsideAscending; state |= LeftObject; } obj = r; break; }/*end switch*/ return obj; } /** Like RenderObject::objectBelow, but confined to stay within \c base. * @param obj render object to begin with * @param trav object traversal state, will be reset within this function * @param base base render object (0: no base) */ static inline RenderObject *renderObjectBelow(RenderObject *obj, ObjectTraversalState &trav, RenderObject *base) { trav = InsideDescending; int state; // we don't need the state, so we don't initialize it RenderObject *r = obj; while (r && trav != OutsideDescending) { r = traverseRenderObjects(r, trav, false, base, state); #if DEBUG_CARETMODE > 3 // qDebug() << "renderObjectBelow: r " << r << " trav " << trav; #endif } trav = InsideDescending; return r; } /** Like RenderObject::objectAbove, but confined to stay within \c base. * @param obj render object to begin with * @param trav object traversal state, will be reset within this function * @param base base render object (0: no base) */ static inline RenderObject *renderObjectAbove(RenderObject *obj, ObjectTraversalState &trav, RenderObject *base) { trav = OutsideAscending; int state; // we don't need the state, so we don't initialize it RenderObject *r = obj; while (r && trav != InsideAscending) { r = traverseRenderObjects(r, trav, true, base, state); #if DEBUG_CARETMODE > 3 // qDebug() << "renderObjectAbove: r " << r << " trav " << trav; #endif } trav = InsideAscending; return r; } /** Checks whether the given inline box matches the IndicatedFlows policy * @param box inline box to test * @return true on match */ static inline bool isIndicatedInlineBox(InlineBox *box) { // text boxes are never indicated. if (box->isInlineTextBox()) { return false; } RenderStyle *s = box->object()->style(); return s->borderLeftWidth() || s->borderRightWidth() || s->borderTopWidth() || s->borderBottomWidth() || s->paddingLeft().value() || s->paddingRight().value() || s->paddingTop().value() || s->paddingBottom().value() // ### Can inline elements have top/bottom margins? Couldn't find // it in the CSS 2 spec, but Mozilla ignores them, so we do, too. || s->marginLeft().value() || s->marginRight().value(); } /** Checks whether the given render object matches the IndicatedFlows policy * @param r render object to test * @return true on match */ static inline bool isIndicatedFlow(RenderObject *r) { RenderStyle *s = r->style(); return s->borderLeftStyle() != BNONE || s->borderRightStyle() != BNONE || s->borderTopStyle() != BNONE || s->borderBottomStyle() != BNONE // || s->paddingLeft().value() || s->paddingRight().value() // || s->paddingTop().value() || s->paddingBottom().value() // || s->marginLeft().value() || s->marginRight().value() || s->hasClip() || s->hidesOverflow() || s->backgroundColor().isValid() || s->backgroundImage(); } /** Advances to the next render object, taking into account the current * traversal state. * * @param r render object * @param trav object traversal state * @param toBegin @p true, advance towards beginning, @p false, advance toward end * @param base base render object which this method must not advance beyond * (0 means document) * @param state object advance state (enum ObjectAdvanceState) (unchanged * on LeafsOnly) * @return a pointer to the render object which we advanced to, * or 0 if the last possible object has been reached. */ static RenderObject *advanceObject(RenderObject *r, ObjectTraversalState &trav, bool toBegin, RenderObject *base, int &state) { ObjectTraversalState origtrav = trav; RenderObject *a = traverseRenderObjects(r, trav, toBegin, base, state); bool ignoreOutsideDesc = toBegin && origtrav == OutsideAscending; // render object and traversal state at which look ahead has been started RenderObject *la = 0; ObjectTraversalState latrav = trav; ObjectTraversalState lasttrav = origtrav; while (a) { #if DEBUG_CARETMODE > 5 // qDebug() << "a " << a << " trav " << trav; #endif if (a->element()) { #if DEBUG_CARETMODE > 4 // qDebug() << "a " << a << " trav " << trav << " origtrav " << origtrav << " ignoreOD " << ignoreOutsideDesc; #endif if (toBegin) { switch (origtrav) { case OutsideDescending: if (trav == InsideAscending) { return a; } if (trav == OutsideDescending) { return a; } break; case InsideDescending: if (trav == OutsideDescending) { return a; } // fall through case InsideAscending: if (trav == OutsideAscending) { return a; } break; case OutsideAscending: if (trav == OutsideAscending) { return a; } if (trav == InsideAscending && lasttrav == InsideDescending) { return a; } if (trav == OutsideDescending && !ignoreOutsideDesc) { return a; } // ignore this outside descending position, as it effectively // demarkates the same position, but remember it in case we fall off // the document. la = a; latrav = trav; ignoreOutsideDesc = false; break; }/*end switch*/ } else { switch (origtrav) { case OutsideDescending: if (trav == InsideAscending) { return a; } if (trav == OutsideDescending) { return a; } break; case InsideDescending: // if (trav == OutsideDescending) return a; // fall through case InsideAscending: // if (trav == OutsideAscending) return a; // break; case OutsideAscending: // ### what if origtrav == OA, and immediately afterwards trav // becomes OD? In this case the effective position hasn't changed -> // the caret gets stuck. Otherwise, it apparently cannot happen in // real usage patterns. if (trav == OutsideDescending) { return a; } if (trav == OutsideAscending) { if (la) { return la; } // starting lookahead here. Remember old object in case we fall off // the document. la = a; latrav = trav; } break; }/*end switch*/ }/*end if*/ }/*end if*/ lasttrav = trav; a = traverseRenderObjects(a, trav, toBegin, base, state); }/*wend*/ if (la) { trav = latrav, a = la; } return a; } /** Check whether the current render object is unsuitable in caret mode handling. * * Some render objects cannot be handled correctly in caret mode. These objects * are therefore considered to be unsuitable. The null object is suitable, as * it denotes reaching the end. * @param r current render object * @param trav current traversal state */ static inline bool isUnsuitable(RenderObject *r, ObjectTraversalState trav) { if (!r) { return false; } return r->isTableCol() || r->isTableSection() || r->isTableRow() || (r->isText() && !static_cast<RenderText *>(r)->firstTextBox()); ; Q_UNUSED(trav); } /** Advances to the next render object, taking into account the current * traversal state, but skipping render objects which are not suitable for * having placed the caret into them. * @param r render object * @param trav object traversal state (unchanged on LeafsOnly) * @param toBegin @p true, advance towards beginning, @p false, advance toward end * @param base base render object which this method must not advance beyond * (0 means document) * @param state object advance state (enum ObjectAdvanceState) (unchanged * on LeafsOnly) * @return a pointer to the advanced render object or 0 if the last possible * object has been reached. */ static inline RenderObject *advanceSuitableObject(RenderObject *r, ObjectTraversalState &trav, bool toBegin, RenderObject *base, int &state) { do { r = advanceObject(r, trav, toBegin, base, state); #if DEBUG_CARETMODE > 2 // qDebug() << "after advanceSWP: r " << r << " trav " << trav << " toBegin " << toBegin; #endif } while (isUnsuitable(r, trav)); return r; } /** * Returns the next leaf node. * * Using this function delivers leaf nodes as if the whole DOM tree * were a linear chain of its leaf nodes. * @param r dom node * @param baseElem base element not to search beyond * @return next leaf node or 0 if there are no more. */ static NodeImpl *nextLeafNode(NodeImpl *r, NodeImpl *baseElem) { NodeImpl *n = r->firstChild(); if (n) { while (n) { r = n; n = n->firstChild(); } return const_cast<NodeImpl *>(r); }/*end if*/ n = r->nextSibling(); if (n) { r = n; while (n) { r = n; n = n->firstChild(); } return const_cast<NodeImpl *>(r); }/*end if*/ n = r->parentNode(); if (n == baseElem) { n = 0; } while (n) { r = n; n = r->nextSibling(); if (n) { r = n; n = r->firstChild(); while (n) { r = n; n = n->firstChild(); } return const_cast<NodeImpl *>(r); }/*end if*/ n = r->parentNode(); if (n == baseElem) { n = 0; } }/*wend*/ return 0; } #if 0 // currently not used /** (Not part of the official DOM) * Returns the previous leaf node. * * Using this function delivers leaf nodes as if the whole DOM tree * were a linear chain of its leaf nodes. * @param r dom node * @param baseElem base element not to search beyond * @return previous leaf node or 0 if there are no more. */ static NodeImpl *prevLeafNode(NodeImpl *r, NodeImpl *baseElem) { NodeImpl *n = r->firstChild(); if (n) { while (n) { r = n; n = n->firstChild(); } return const_cast<NodeImpl *>(r); }/*end if*/ n = r->previousSibling(); if (n) { r = n; while (n) { r = n; n = n->firstChild(); } return const_cast<NodeImpl *>(r); }/*end if*/ n = r->parentNode(); if (n == baseElem) { n = 0; } while (n) { r = n; n = r->previousSibling(); if (n) { r = n; n = r->lastChild(); while (n) { r = n; n = n->lastChild(); } return const_cast<NodeImpl *>(r); }/*end if*/ n = r->parentNode(); if (n == baseElem) { n = 0; } }/*wend*/ return 0; } #endif /** Maps a DOM Range position to the corresponding caret position. * * The offset boundary is not checked for validity. * @param node DOM node * @param offset zero-based offset within node * @param r returns render object (may be 0 if DOM node has no render object) * @param r_ofs returns the appropriate offset for the found render object r * @param outside returns true when offset is applied to the outside of * \c r, or false for the inside. * @param outsideEnd return true when the caret position is at the outside end. */ void /*KHTML_NO_EXPORT*/ mapDOMPosToRenderPos(NodeImpl *node, long offset, RenderObject *&r, long &r_ofs, bool &outside, bool &outsideEnd) { if (node->nodeType() == Node::TEXT_NODE) { outside = false; outsideEnd = false; r = node->renderer(); r_ofs = offset; } else if (node->nodeType() == Node::ELEMENT_NODE || node->nodeType() == Node::DOCUMENT_NODE) { // Though offset points between two children, attach it to the visually // most suitable one (and only there, because the mapping must stay bijective) if (node->firstChild()) { outside = true; NodeImpl *child = offset <= 0 ? node->firstChild() // childNode is expensive : node->childNode((unsigned long)offset); // index was child count or out of bounds bool atEnd = !child; #if DEBUG_CARETMODE > 5 // qDebug() << "mapDTR: child " << child << "@" << (child ? child->nodeName().string() : QString()) << " atEnd " << atEnd; #endif if (atEnd) { child = node->lastChild(); } r = child->renderer(); r_ofs = 0; outsideEnd = atEnd; // Outside text nodes most likely stem from a continuation. Seek // the enclosing continued render object and use this one instead. if (r && child->nodeType() == Node::TEXT_NODE) { r = r->parent(); RenderObject *o = node->renderer(); while (o->continuation() && o->continuation() != r) { o = o->continuation(); } if (!r || o->continuation() != r) { r = child->renderer(); } }/*end if*/ // BRs cause troubles. Returns the previous render object instead, // giving it the attributes outside, outside end. if (r && r->isBR()) { r = r->objectAbove(); outsideEnd = true; }/*end if*/ } else { // Element has no children, treat offset to be inside the node. outside = false; outsideEnd = false; r = node->renderer(); r_ofs = 0; // only offset 0 possible } } else { r = 0; qWarning() << "Mapping from nodes of type " << node->nodeType() - << " not supported!" << endl; + << " not supported!"; } } /** Maps a caret position to the corresponding DOM Range position. * * @param r render object * @param r_ofs offset within render object * @param outside true when offset is interpreted to be on the outside of * \c r, or false if on the inside. * @param outsideEnd true when the caret position is at the outside end. * @param node returns DOM node * @param offset returns zero-based offset within node */ void /*KHTML_NO_EXPORT*/ mapRenderPosToDOMPos(RenderObject *r, long r_ofs, bool outside, bool outsideEnd, NodeImpl *&node, long &offset) { node = r->element(); Q_ASSERT(node); #if DEBUG_CARETMODE > 5 // qDebug() << "mapRTD: r " << r << '@' << (r ? r->renderName() : QString()) << (r && r->element() ? QString(".node ") + QString::number((unsigned)r->element(),16) + '@' + r->element()->nodeName().string() : QString()) << " outside " << outside << " outsideEnd " << outsideEnd; #endif if (node->nodeType() == Node::ELEMENT_NODE || node->nodeType() == Node::TEXT_NODE) { if (outside) { NodeImpl *parent = node->parent(); // If this is part of a continuation, use the actual node as the parent, // and the first render child as the node. if (r != node->renderer()) { RenderObject *o = node->renderer(); while (o->continuation() && o->continuation() != r) { o = o->continuation(); } if (o->continuation() == r) { parent = node; // ### What if the first render child does not map to a child of // the continued node? node = r->firstChild() ? r->firstChild()->element() : node; } }/*end if*/ if (!parent) { goto inside; } offset = (long)node->nodeIndex() + outsideEnd; node = parent; #if DEBUG_CARETMODE > 5 // qDebug() << node << "@" << (node ? node->nodeName().string() : QString()) << " offset " << offset; #endif } else { // !outside inside: offset = r_ofs; } } else { offset = 0; qWarning() << "Mapping to nodes of type " << node->nodeType() - << " not supported!" << endl; + << " not supported!"; } } /** Make sure the given node is a leaf node. */ static inline void ensureLeafNode(NodeImpl *&node, NodeImpl *base) { if (node && node->hasChildNodes()) { node = nextLeafNode(node, base); } } /** Converts a caret position to its respective object traversal state. * @param outside whether the caret is outside the object * @param atEnd whether the caret position is at the end * @param toBegin \c true when advancing towards the beginning * @param trav returns the corresponding traversal state */ static inline void mapRenderPosToTraversalState(bool outside, bool atEnd, bool toBegin, ObjectTraversalState &trav) { if (!outside) { atEnd = !toBegin; } if (!atEnd ^ toBegin) { trav = outside ? OutsideDescending : InsideDescending; } else { trav = outside ? OutsideAscending : InsideAscending; } } /** Converts a traversal state to its respective caret position * @param trav object traversal state * @param toBegin \c true when advancing towards the beginning * @param outside whether the caret is outside the object * @param atEnd whether the caret position is at the end */ static inline void mapTraversalStateToRenderPos(ObjectTraversalState trav, bool toBegin, bool &outside, bool &atEnd) { outside = false; switch (trav) { case OutsideDescending: outside = true; // fall through case InsideDescending: atEnd = toBegin; break; case OutsideAscending: outside = true; // fall through case InsideAscending: atEnd = !toBegin; break; } } /** Finds the next node that has a renderer. * * Note that if the initial @p node has a renderer, this will be returned, * regardless of the caret advance policy. * Otherwise, for the next nodes, only leaf nodes are considered. * @param node node to start with, will be updated accordingly * @param offset offset of caret within \c node * @param base base render object which this method must not advance beyond * (0 means document) * @param r_ofs return the caret offset within the returned renderer * @param outside returns whether offset is to be interpreted to the outside * (true) or the inside (false) of the render object. * @param outsideEnd returns whether the end of the outside position is meant * @return renderer or 0 if no following node has a renderer. */ static RenderObject *findRenderer(NodeImpl *&node, long offset, RenderObject *base, long &r_ofs, bool &outside, bool &outsideEnd) { if (!node) { return 0; } RenderObject *r; mapDOMPosToRenderPos(node, offset, r, r_ofs, outside, outsideEnd); #if DEBUG_CARETMODE > 2 // qDebug() << "findRenderer: node " << node << " " << (node ? node->nodeName().string() : QString()) << " offset " << offset << " r " << r << "[" << (r ? r->renderName() : QString()) << "] r_ofs " << r_ofs << " outside " << outside << " outsideEnd " << outsideEnd; #endif if (r) { return r; } NodeImpl *baseElem = base ? base->element() : 0; while (!r) { node = nextLeafNode(node, baseElem); if (!node) { break; } r = node->renderer(); if (r) { r_ofs = offset; } } #if DEBUG_CARETMODE > 3 // qDebug() << "1r " << r; #endif ObjectTraversalState trav; int state; // not used mapRenderPosToTraversalState(outside, outsideEnd, false, trav); if (r && isUnsuitable(r, trav)) { r = advanceSuitableObject(r, trav, false, base, state); mapTraversalStateToRenderPos(trav, false, outside, outsideEnd); if (r) { r_ofs = r->minOffset(); } } #if DEBUG_CARETMODE > 3 // qDebug() << "2r " << r; #endif return r; } /** returns a suitable base element * @param caretNode current node containing caret. */ static ElementImpl *determineBaseElement(NodeImpl *caretNode) { // ### for now, only body is delivered for html documents, // and 0 for xml documents. DocumentImpl *doc = caretNode->getDocument(); if (!doc) { return 0; // should not happen, but who knows. } if (doc->isHTMLDocument()) { return static_cast<HTMLDocumentImpl *>(doc)->body(); } return 0; } // == class CaretBox implementation #if DEBUG_CARETMODE > 0 void CaretBox::dump(QTextStream &ts, const QString &ind) const { ts << ind << "b@" << _box; if (_box) { ts << "<" << _box->object() << ":" << _box->object()->renderName() << ">"; }/*end if*/ ts << " " << _x << "+" << _y << "+" << _w << "*" << _h; ts << " cb@" << cb; if (cb) { ts << ":" << cb->renderName(); } ts << " " << (_outside ? (outside_end ? "oe" : "o-") : "i-"); // ts << endl; } #endif // == class CaretBoxLine implementation #if DEBUG_CARETMODE > 0 # define DEBUG_ACIB 1 #else # define DEBUG_ACIB DEBUG_CARETMODE #endif void CaretBoxLine::addConvertedInlineBox(InlineBox *box, SeekBoxParams &sbp) /*KHTML_NO_EXPORT*/ { // Generate only one outside caret box between two elements. If // coalesceOutsideBoxes is true, generating left outside boxes is inhibited. bool coalesceOutsideBoxes = false; CaretBoxIterator lastCoalescedBox; for (; box; box = box->nextOnLine()) { #if DEBUG_ACIB // qDebug() << "box " << box; // qDebug() << "box->object " << box->object(); // qDebug() << "x " << box->m_x << " y " << box->m_y << " w " << box->m_width << " h " << box->m_height << " baseline " << box->m_baseline << " ifb " << box->isInlineFlowBox() << " itb " << box->isInlineTextBox() << " rlb " << box->isRootInlineBox(); #endif // ### Why the hell can object() ever be 0?! if (!box->object()) { continue; } RenderStyle *s = box->object()->style(box->m_firstLine); // parent style for outside caret boxes RenderStyle *ps = box->parent() && box->parent()->object() ? box->parent()->object()->style(box->parent()->m_firstLine) : s; if (box->isInlineFlowBox()) { #if DEBUG_ACIB // qDebug() << "isinlineflowbox " << box; #endif InlineFlowBox *flowBox = static_cast<InlineFlowBox *>(box); bool rtl = ps->direction() == RTL; const QFontMetrics &pfm = ps->fontMetrics(); if (flowBox->includeLeftEdge()) { // If this box is to be coalesced with the outside end box of its // predecessor, then check if it is the searched box. If it is, we // substitute the outside end box. if (coalesceOutsideBoxes) { if (sbp.equalsBox(flowBox, true, false)) { sbp.it = lastCoalescedBox; Q_ASSERT(!sbp.found); sbp.found = true; } } else { addCreatedFlowBoxEdge(flowBox, pfm, true, rtl); sbp.check(preEnd()); } }/*end if*/ if (flowBox->firstChild()) { #if DEBUG_ACIB // qDebug() << "this " << this << " flowBox " << flowBox << " firstChild " << flowBox->firstChild(); // qDebug() << "== recursive invocation"; #endif addConvertedInlineBox(flowBox->firstChild(), sbp); #if DEBUG_ACIB // qDebug() << "== recursive invocation end"; #endif } else { addCreatedFlowBoxInside(flowBox, s->fontMetrics()); sbp.check(preEnd()); } if (flowBox->includeRightEdge()) { addCreatedFlowBoxEdge(flowBox, pfm, false, rtl); lastCoalescedBox = preEnd(); sbp.check(lastCoalescedBox); coalesceOutsideBoxes = true; } } else if (box->isInlineTextBox()) { #if DEBUG_ACIB // qDebug() << "isinlinetextbox " << box << (box->object() ? QString(" contains \"%1\"").arg(QConstString(static_cast<RenderText *>(box->object())->str->s+box->minOffset(), qMin(box->maxOffset() - box->minOffset(), 15L)).string()) : QString()); #endif caret_boxes.append(new CaretBox(box, false, false)); sbp.check(preEnd()); // coalescing has been interrupted coalesceOutsideBoxes = false; } else { #if DEBUG_ACIB // qDebug() << "some replaced or what " << box; #endif // must be an inline-block, inline-table, or any RenderReplaced bool rtl = ps->direction() == RTL; const QFontMetrics &pfm = ps->fontMetrics(); if (coalesceOutsideBoxes) { if (sbp.equalsBox(box, true, false)) { sbp.it = lastCoalescedBox; Q_ASSERT(!sbp.found); sbp.found = true; } } else { addCreatedInlineBoxEdge(box, pfm, true, rtl); sbp.check(preEnd()); } caret_boxes.append(new CaretBox(box, false, false)); sbp.check(preEnd()); addCreatedInlineBoxEdge(box, pfm, false, rtl); lastCoalescedBox = preEnd(); sbp.check(lastCoalescedBox); coalesceOutsideBoxes = true; }/*end if*/ }/*next box*/ } #undef DEBUG_ACIB void CaretBoxLine::addCreatedFlowBoxInside(InlineFlowBox *flowBox, const QFontMetrics &fm) /*KHTML_NO_EXPORT*/ { CaretBox *caretBox = new CaretBox(flowBox, false, false); caret_boxes.append(caretBox); // afaik an inner flow box can only have the width 0, therefore we don't // have to care for rtl or alignment // ### can empty inline elements have a width? css 2 spec isn't verbose about it caretBox->_y += flowBox->baseline() - fm.ascent(); caretBox->_h = fm.height(); } void CaretBoxLine::addCreatedFlowBoxEdge(InlineFlowBox *flowBox, const QFontMetrics &fm, bool left, bool rtl) /*KHTML_NO_EXPORT*/ { CaretBox *caretBox = new CaretBox(flowBox, true, !left); caret_boxes.append(caretBox); if (left ^ rtl) { caretBox->_x -= flowBox->paddingLeft() + flowBox->borderLeft() + 1; } else { caretBox->_x += caretBox->_w + flowBox->paddingRight() + flowBox->borderRight(); } caretBox->_y += flowBox->baseline() - fm.ascent(); caretBox->_h = fm.height(); caretBox->_w = 1; } void CaretBoxLine::addCreatedInlineBoxEdge(InlineBox *box, const QFontMetrics &fm, bool left, bool rtl) /*KHTML_NO_EXPORT*/ { CaretBox *caretBox = new CaretBox(box, true, !left); caret_boxes.append(caretBox); if (left ^ rtl) { caretBox->_x--; } else { caretBox->_x += caretBox->_w; } caretBox->_y += box->baseline() - fm.ascent(); caretBox->_h = fm.height(); caretBox->_w = 1; } CaretBoxLine *CaretBoxLine::constructCaretBoxLine(CaretBoxLineDeleter *deleter, InlineFlowBox *basicFlowBox, InlineBox *seekBox, bool seekOutside, bool seekOutsideEnd, CaretBoxIterator &iter, RenderObject *seekObject) // KHTML_NO_EXPORT { // Iterate all inline boxes within this inline flow box. // Caret boxes will be created for each // - outside begin of an inline flow box (except for the basic inline flow box) // - outside end of an inline flow box (except for the basic inline flow box) // - inside of an empty inline flow box // - outside begin of an inline box resembling a replaced element // - outside end of an inline box resembling a replaced element // - inline text box // - inline replaced box CaretBoxLine *result = new CaretBoxLine(basicFlowBox); deleter->append(result); SeekBoxParams sbp(seekBox, seekOutside, seekOutsideEnd, seekObject, iter); // iterate recursively, I'm too lazy to do it iteratively result->addConvertedInlineBox(basicFlowBox, sbp); if (!sbp.found) { sbp.it = result->end(); } return result; } CaretBoxLine *CaretBoxLine::constructCaretBoxLine(CaretBoxLineDeleter *deleter, RenderBox *cb, bool outside, bool outsideEnd, CaretBoxIterator &iter) /*KHTML_NO_EXPORT*/ { int _x = cb->xPos(); int _y = cb->yPos(); int height; int width = 1; // no override is indicated in boxes if (outside) { RenderStyle *s = cb->element() && cb->element()->parent() && cb->element()->parent()->renderer() ? cb->element()->parent()->renderer()->style() : cb->style(); bool rtl = s->direction() == RTL; const QFontMetrics &fm = s->fontMetrics(); height = fm.height(); if (!outsideEnd) { _x--; } else { _x += cb->width(); } int hl = fm.leading() / 2; int baseline = cb->baselinePosition(false); if (!cb->isReplaced() || cb->style()->display() == BLOCK) { if (!outsideEnd ^ rtl) { _y -= fm.leading() / 2; } else { _y += qMax(cb->height() - fm.ascent() - hl, 0); } } else { _y += baseline - fm.ascent() - hl; } } else { // !outside RenderStyle *s = cb->style(); const QFontMetrics &fm = s->fontMetrics(); height = fm.height(); _x += cb->borderLeft() + cb->paddingLeft(); _y += cb->borderTop() + cb->paddingTop(); // ### regard direction switch (s->textAlign()) { case LEFT: case KHTML_LEFT: case TAAUTO: // ### find out what this does case JUSTIFY: break; case CENTER: case KHTML_CENTER: _x += cb->contentWidth() / 2; break; case KHTML_RIGHT: case RIGHT: _x += cb->contentWidth(); break; }/*end switch*/ }/*end if*/ CaretBoxLine *result = new CaretBoxLine; deleter->append(result); result->caret_boxes.append(new CaretBox(_x, _y, width, height, cb, outside, outsideEnd)); iter = result->begin(); return result; } #if DEBUG_CARETMODE > 0 void CaretBoxLine::dump(QTextStream &ts, const QString &ind) const { ts << ind << "cbl: baseFlowBox@" << basefb << endl; QString ind2 = ind + " "; for (size_t i = 0; i < caret_boxes.size(); i++) { if (i > 0) { ts << endl; } caret_boxes[i]->dump(ts, ind2); } } #endif // == caret mode related helper functions /** seeks the root line box that is the parent of the given inline box. * @param b given inline box * @param base base render object which not to step over. If \c base's * inline flow box is hit before the root line box, the flow box * is returned instead. * @return the root line box or the base flow box. */ inline InlineFlowBox *seekBaseFlowBox(InlineBox *b, RenderObject *base = 0) { // Seek root line box or base inline flow box, if \c base is interfering. while (b->parent() && b->object() != base) { b = b->parent(); }/*wend*/ Q_ASSERT(b->isInlineFlowBox()); return static_cast<InlineFlowBox *>(b); } /** determines whether the given element is a block level replaced element. */ inline bool isBlockRenderReplaced(RenderObject *r) { return r->isRenderReplaced() && r->style()->display() == BLOCK; } /** determines the caret line box that contains the given position. * * If the node does not map to a render object, the function will snap to * the next suitable render object following it. * * @param node node to begin with * @param offset zero-based offset within node. * @param cblDeleter deleter for caret box lines * @param base base render object which the caret must not be placed beyond. * @param r_ofs adjusted offset within render object * @param caretBoxIt returns an iterator to the caret box that contains the * given position. * @return the determined caret box lineor 0 if either the node is 0 or * there is no inline flow box containing this node. The containing block * will still be set. If it is 0 too, @p node was invalid. */ static CaretBoxLine *findCaretBoxLine(DOM::NodeImpl *node, long offset, CaretBoxLineDeleter *cblDeleter, RenderObject *base, long &r_ofs, CaretBoxIterator &caretBoxIt) { bool outside, outsideEnd; RenderObject *r = findRenderer(node, offset, base, r_ofs, outside, outsideEnd); if (!r) { return 0; } #if DEBUG_CARETMODE > 0 // qDebug() << "=================== findCaretBoxLine"; // qDebug() << "node " << node << " offset: " << offset << " r " << r->renderName() << "[" << r << "].node " << r->element()->nodeName().string() << "[" << r->element() << "]" << " r_ofs " << r_ofs << " outside " << outside << " outsideEnd " << outsideEnd; #endif // There are two strategies to find the correct line box. (The third is failsafe) // (A) First, if node's renderer is a RenderText, we only traverse its text // runs and return the root line box (saves much time for long blocks). // This should be the case 99% of the time. // (B) Second, we derive the inline flow box directly when the renderer is // a RenderBlock, RenderInline, or blocked RenderReplaced. // (C) Otherwise, we iterate linearly through all line boxes in order to find // the renderer. // (A) if (r->isText()) do { RenderText *t = static_cast<RenderText *>(r); int dummy; InlineBox *b = t->findInlineTextBox(offset, dummy, true); // Actually b should never be 0, but some render texts don't have text // boxes, so we insert the last run as an error correction. // If there is no last run, we resort to (B) if (!b) { if (!t->lastTextBox()) { break; } b = t->lastTextBox(); }/*end if*/ Q_ASSERT(b); outside = false; // text boxes cannot have outside positions InlineFlowBox *baseFlowBox = seekBaseFlowBox(b, base); #if DEBUG_CARETMODE > 2 // qDebug() << "text-box b: " << b << " baseFlowBox: " << baseFlowBox << (b && b->object() ? QString(" contains \"%1\"").arg(QConstString(static_cast<RenderText *>(b->object())->str->s+b->minOffset(), qMin(b->maxOffset() - b->minOffset(), 15L)).string()) : QString()); #endif #if 0 if (t->containingBlock()->isListItem()) { dumpLineBoxes(static_cast<RenderFlow *>(t->containingBlock())); } #endif #if DEBUG_CARETMODE > 0 // qDebug() << "=================== end findCaretBoxLine (renderText)"; #endif return CaretBoxLine::constructCaretBoxLine(cblDeleter, baseFlowBox, b, outside, outsideEnd, caretBoxIt); } while (false); /*end if*/ // (B) bool isrepl = isBlockRenderReplaced(r); if (r->isRenderBlock() || r->isRenderInline() || isrepl) { RenderFlow *flow = static_cast<RenderFlow *>(r); InlineFlowBox *firstLineBox = isrepl ? 0 : flow->firstLineBox(); // On render blocks, if we are outside, or have a totally empty render // block, we simply construct a special caret box line. // The latter case happens only when the render block is a leaf object itself. if (isrepl || r->isRenderBlock() && (outside || !firstLineBox) || r->isRenderInline() && !firstLineBox) { #if DEBUG_CARETMODE > 0 // qDebug() << "=================== end findCaretBoxLine (box " << (outside ? (outsideEnd ? "outside end" : "outside begin") : "inside") << ")"; #endif Q_ASSERT(r->isBox()); return CaretBoxLine::constructCaretBoxLine(cblDeleter, static_cast<RenderBox *>(r), outside, outsideEnd, caretBoxIt); }/*end if*/ // qDebug() << "firstlinebox " << firstLineBox; InlineFlowBox *baseFlowBox = seekBaseFlowBox(firstLineBox, base); return CaretBoxLine::constructCaretBoxLine(cblDeleter, baseFlowBox, firstLineBox, outside, outsideEnd, caretBoxIt); }/*end if*/ RenderBlock *cb = r->containingBlock(); //if ( !cb ) return 0L; Q_ASSERT(cb); // ### which element doesn't have a block as its containing block? // Is it still possible after the RenderBlock/RenderInline merge? if (!cb->isRenderBlock()) { qWarning() << "containing block is no render block!!! crash imminent"; }/*end if*/ InlineFlowBox *flowBox = cb->firstLineBox(); // (C) // This case strikes when the element is replaced, but neither a // RenderBlock nor a RenderInline if (!flowBox) { // ### utter emergency (why is this possible at all?) // flowBox = generateDummyFlowBox(arena, cb, r); // if (ibox) *ibox = flowBox->firstChild(); // outside = outside_end = true; // qWarning() << "containing block contains no inline flow boxes!!! crash imminent"; #if DEBUG_CARETMODE > 0 // qDebug() << "=================== end findCaretBoxLine (2)"; #endif return CaretBoxLine::constructCaretBoxLine(cblDeleter, cb, outside, outsideEnd, caretBoxIt); }/*end if*/ // We iterate the inline flow boxes of the containing block until // we find the given node. This has one major flaw: it is linear, and therefore // painfully slow for really large blocks. for (; flowBox; flowBox = static_cast<InlineFlowBox *>(flowBox->nextLineBox())) { #if DEBUG_CARETMODE > 0 // qDebug() << "[scan line]"; #endif // construct a caret line box and stop when the element is contained within InlineFlowBox *baseFlowBox = seekBaseFlowBox(flowBox, base); CaretBoxLine *cbl = CaretBoxLine::constructCaretBoxLine(cblDeleter, baseFlowBox, 0, outside, outsideEnd, caretBoxIt, r); #if DEBUG_CARETMODE > 5 // qDebug() << cbl->information(); #endif if (caretBoxIt != cbl->end()) { #if DEBUG_CARETMODE > 0 // qDebug() << "=================== end findCaretBoxLine (3)"; #endif return cbl; } }/*next flowBox*/ // no inline flow box found, approximate to nearest following node. // Danger: this is O(n^2). It's only called to recover from // errors, that means, theoretically, never. (Practically, far too often :-( ) Q_ASSERT(!flowBox); CaretBoxLine *cbl = findCaretBoxLine(nextLeafNode(node, base ? base->element() : 0), 0, cblDeleter, base, r_ofs, caretBoxIt); #if DEBUG_CARETMODE > 0 // qDebug() << "=================== end findCaretBoxLine"; #endif return cbl; } /** finds the innermost table object @p r is contained within, but no * farther than @p cb. * @param r leaf element to begin with * @param cb bottom element where to stop search at least. * @return the table object or 0 if none found. */ static inline RenderTable *findTableUpTo(RenderObject *r, RenderFlow *cb) { while (r && r != cb && !r->isTable()) { r = r->parent(); } return r && r->isTable() ? static_cast<RenderTable *>(r) : 0; } /** checks whether @p r is a descendant of @p cb, or r == cb */ static inline bool isDescendant(RenderObject *r, RenderObject *cb) { while (r && r != cb) { r = r->parent(); } return r; } /** checks whether the given block contains at least one editable element. * * Warning: This function has linear complexity, and therefore is expensive. * Use it sparingly, and cache the result. * @param part part * @param cb block to be searched * @param table returns the nested table if there is one directly at the beginning * or at the end. * @param fromEnd begin search from end (default: begin from beginning) */ static bool containsEditableElement(KHTMLPart *part, RenderBlock *cb, RenderTable *&table, bool fromEnd = false) { RenderObject *r = cb; if (fromEnd) while (r->lastChild()) { r = r->lastChild(); } else while (r->firstChild()) { r = r->firstChild(); } RenderTable *tempTable = 0; table = 0; bool withinCb; // int state; // not used ObjectTraversalState trav = InsideDescending; do { bool modWithinCb = withinCb = isDescendant(r, cb); // treat cb extra, it would not be considered otherwise if (!modWithinCb) { modWithinCb = true; r = cb; } else { tempTable = findTableUpTo(r, cb); } #if DEBUG_CARETMODE > 1 // qDebug() << "cee: r " << (r ? r->renderName() : QString()) << "@" << r << " cb " << cb << " withinCb " << withinCb << " modWithinCb " << modWithinCb << " tempTable " << tempTable; #endif if (r && modWithinCb && r->element() && !isUnsuitable(r, trav) && (part->isCaretMode() || part->isEditable() || r->style()->userInput() == UI_ENABLED)) { table = tempTable; #if DEBUG_CARETMODE > 1 // qDebug() << "cee: editable"; #endif return true; }/*end if*/ // RenderObject *oldr = r; // while (r && r == oldr) // r = advanceSuitableObject(r, trav, fromEnd, cb->parent(), state); r = fromEnd ? r->objectAbove() : r->objectBelow(); } while (r && withinCb); return false; } /** checks whether the given block contains at least one editable child * element, beginning with but excluding @p start. * * Warning: This function has linear complexity, and therefore is expensive. * Use it sparingly, and cache the result. * @param part part * @param cb block to be searched * @param table returns the nested table if there is one directly before/after * the start object. * @param fromEnd begin search from end (default: begin from beginning) * @param start object after which to begin search. */ static bool containsEditableChildElement(KHTMLPart *part, RenderBlock *cb, RenderTable *&table, bool fromEnd, RenderObject *start) { int state = 0; ObjectTraversalState trav = OutsideAscending; // qDebug() << "start: " << start; RenderObject *r = start; do { r = traverseRenderObjects(r, trav, fromEnd, cb->parent(), state); } while (r && !(state & AdvancedToSibling)); // qDebug() << "r: " << r; //advanceObject(start, trav, fromEnd, cb->parent(), state); // RenderObject *oldr = r; // while (r && r == oldr) if (!r) { return false; } if (fromEnd) while (r->firstChild()) { r = r->firstChild(); } else while (r->lastChild()) { r = r->lastChild(); } // qDebug() << "child r: " << r; if (!r) { return false; } RenderTable *tempTable = 0; table = 0; bool withinCb = false; do { bool modWithinCb = withinCb = isDescendant(r, cb); // treat cb extra, it would not be considered otherwise if (!modWithinCb) { modWithinCb = true; r = cb; } else { tempTable = findTableUpTo(r, cb); } #if DEBUG_CARETMODE > 1 // qDebug() << "cece: r " << (r ? r->renderName() : QString()) << "@" << r << " cb " << cb << " withinCb " << withinCb << " modWithinCb " << modWithinCb << " tempTable " << tempTable; #endif if (r && withinCb && r->element() && !isUnsuitable(r, trav) && (part->isCaretMode() || part->isEditable() || r->style()->userInput() == UI_ENABLED)) { table = tempTable; #if DEBUG_CARETMODE > 1 // qDebug() << "cece: editable"; #endif return true; }/*end if*/ r = fromEnd ? r->objectAbove() : r->objectBelow(); } while (withinCb); return false; } // == class LinearDocument implementation LinearDocument::LinearDocument(KHTMLPart *part, NodeImpl *node, long offset, CaretAdvancePolicy advancePolicy, ElementImpl *baseElem) : node(node), offset(offset), m_part(part), advPol(advancePolicy), base(0) { if (node == 0) { return; } if (baseElem) { RenderObject *b = baseElem->renderer(); if (b && (b->isRenderBlock() || b->isRenderInline())) { base = b; } } initPreBeginIterator(); initEndIterator(); } LinearDocument::~LinearDocument() { } int LinearDocument::count() const { // FIXME: not implemented return 1; } LinearDocument::Iterator LinearDocument::current() { return LineIterator(this, node, offset); } LinearDocument::Iterator LinearDocument::begin() { NodeImpl *n = base ? base->element() : 0; if (!base) { n = node ? node->getDocument() : 0; } if (!n) { return end(); } n = n->firstChild(); if (advPol == LeafsOnly) while (n->firstChild()) { n = n->firstChild(); } if (!n) { return end(); // must be empty document or empty base element } return LineIterator(this, n, n->minOffset()); } LinearDocument::Iterator LinearDocument::preEnd() { NodeImpl *n = base ? base->element() : 0; if (!base) { n = node ? node->getDocument() : 0; } if (!n) { return preBegin(); } n = n->lastChild(); if (advPol == LeafsOnly) while (n->lastChild()) { n = n->lastChild(); } if (!n) { return preBegin(); // must be empty document or empty base element } return LineIterator(this, n, n->maxOffset()); } void LinearDocument::initPreBeginIterator() { _preBegin = LineIterator(this, 0, 0); } void LinearDocument::initEndIterator() { _end = LineIterator(this, 0, 1); } // == class LineIterator implementation CaretBoxIterator LineIterator::currentBox /*KHTML_NO_EXPORT*/; long LineIterator::currentOffset /*KHTML_NO_EXPORT*/; LineIterator::LineIterator(LinearDocument *l, DOM::NodeImpl *node, long offset) : lines(l) { // qDebug() << "LineIterator: node " << node << " offset " << offset; if (!node) { cbl = 0; return; } cbl = findCaretBoxLine(node, offset, &lines->cblDeleter, l->baseObject(), currentOffset, currentBox); // can happen on partially loaded documents #if DEBUG_CARETMODE > 0 if (!cbl) // qDebug() << "no render object found!"; #endif if (!cbl) { return; } #if DEBUG_CARETMODE > 1 // qDebug() << "LineIterator: offset " << offset << " outside " << cbl->isOutside(); #endif #if DEBUG_CARETMODE > 3 // qDebug() << cbl->information(); #endif if (currentBox == cbl->end()) { #if DEBUG_CARETMODE > 0 // qDebug() << "LineIterator: findCaretBoxLine failed"; #endif cbl = 0; }/*end if*/ } void LineIterator::nextBlock() { RenderObject *base = lines->baseObject(); bool cb_outside = cbl->isOutside(); bool cb_outside_end = cbl->isOutsideEnd(); { RenderObject *r = cbl->enclosingObject(); ObjectTraversalState trav; int state; // not used mapRenderPosToTraversalState(cb_outside, cb_outside_end, false, trav); #if DEBUG_CARETMODE > 1 // qDebug() << "nextBlock: before adv r" << r << ' ' << (r ? r->renderName() : QString()) << (r && r->isText() ? " contains \"" + QString(((RenderText *)r)->str->s, qMin(((RenderText *)r)->str->l,15)) + "\"" : QString()) << " trav " << trav << " cb_outside " << cb_outside << " cb_outside_end " << cb_outside_end; #endif r = advanceSuitableObject(r, trav, false, base, state); if (!r) { cbl = 0; return; }/*end if*/ mapTraversalStateToRenderPos(trav, false, cb_outside, cb_outside_end); #if DEBUG_CARETMODE > 1 // qDebug() << "nextBlock: after r" << r << " trav " << trav << " cb_outside " << cb_outside << " cb_outside_end " << cb_outside_end; #endif #if DEBUG_CARETMODE > 0 // qDebug() << "++: r " << r << "[" << (r?r->renderName():QString()) << "]"; #endif RenderBlock *cb; // If we hit a block or replaced object, use this as its enclosing object bool isrepl = isBlockRenderReplaced(r); if (r->isRenderBlock() || isrepl) { RenderBox *cb = static_cast<RenderBox *>(r); cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, cb, cb_outside, cb_outside_end, currentBox); #if DEBUG_CARETMODE > 0 // qDebug() << "r->isFlow is cb. continuation @" << cb->continuation(); #endif return; } else { cb = r->containingBlock(); Q_ASSERT(cb->isRenderBlock()); }/*end if*/ InlineFlowBox *flowBox = cb->firstLineBox(); #if DEBUG_CARETMODE > 0 // qDebug() << "++: flowBox " << flowBox << " cb " << cb << '[' << (cb?cb->renderName()+QString(".node ")+QString::number((unsigned)cb->element(),16)+(cb->element()?'@'+cb->element()->nodeName().string():QString()):QString()) << ']'; #endif Q_ASSERT(flowBox); if (!flowBox) { // ### utter emergency (why is this possible at all?) cb_outside = cb_outside_end = true; cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, cb, cb_outside, cb_outside_end, currentBox); return; } bool seekOutside = false, seekOutsideEnd = false; // silence gcc uninit warning CaretBoxIterator it; cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, flowBox, flowBox->firstChild(), seekOutside, seekOutsideEnd, it); } } void LineIterator::prevBlock() { RenderObject *base = lines->baseObject(); bool cb_outside = cbl->isOutside(); bool cb_outside_end = cbl->isOutsideEnd(); { RenderObject *r = cbl->enclosingObject(); if (r->isAnonymous() && !cb_outside) { cb_outside = true, cb_outside_end = false; } ObjectTraversalState trav; int state; // not used mapRenderPosToTraversalState(cb_outside, cb_outside_end, true, trav); #if DEBUG_CARETMODE > 1 // qDebug() << "prevBlock: before adv r" << r << " " << (r ? r->renderName() : QString()) << (r && r->isText() ? " contains \"" + QString(((RenderText *)r)->str->s, qMin(((RenderText *)r)->str->l,15)) + "\"" : QString()) << " trav " << trav << " cb_outside " << cb_outside << " cb_outside_end " << cb_outside_end; #endif r = advanceSuitableObject(r, trav, true, base, state); if (!r) { cbl = 0; return; }/*end if*/ mapTraversalStateToRenderPos(trav, true, cb_outside, cb_outside_end); #if DEBUG_CARETMODE > 1 // qDebug() << "prevBlock: after r" << r << " trav " << trav << " cb_outside " << cb_outside << " cb_outside_end " << cb_outside_end; #endif #if DEBUG_CARETMODE > 0 // qDebug() << "--: r " << r << "[" << (r?r->renderName():QString()) << "]"; #endif RenderBlock *cb; // If we hit a block, use this as its enclosing object bool isrepl = isBlockRenderReplaced(r); // qDebug() << "isrepl " << isrepl << " isblock " << r->isRenderBlock(); if (r->isRenderBlock() || isrepl) { RenderBox *cb = static_cast<RenderBox *>(r); cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, cb, cb_outside, cb_outside_end, currentBox); #if DEBUG_CARETMODE > 0 // qDebug() << "r->isFlow is cb. continuation @" << cb->continuation(); #endif return; } else { cb = r->containingBlock(); Q_ASSERT(cb->isRenderBlock()); }/*end if*/ InlineFlowBox *flowBox = cb->lastLineBox(); #if DEBUG_CARETMODE > 0 // qDebug() << "--: flowBox " << flowBox << " cb " << cb << "[" << (cb?cb->renderName()+QString(".node ")+QString::number((unsigned)cb->element(),16)+(cb->element()?"@"+cb->element()->nodeName().string():QString()):QString()) << "]"; #endif Q_ASSERT(flowBox); if (!flowBox) { // ### utter emergency (why is this possible at all?) cb_outside = true; cb_outside_end = false; cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, cb, cb_outside, cb_outside_end, currentBox); return; } bool seekOutside = false, seekOutsideEnd = false; // silence gcc uninit warning CaretBoxIterator it; cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, flowBox, flowBox->firstChild(), seekOutside, seekOutsideEnd, it); } } void LineIterator::advance(bool toBegin) { InlineFlowBox *flowBox = cbl->baseFlowBox(); if (flowBox) { flowBox = static_cast<InlineFlowBox *>(toBegin ? flowBox->prevLineBox() : flowBox->nextLineBox()); if (flowBox) { bool seekOutside = false, seekOutsideEnd = false; // silence gcc uninit warning CaretBoxIterator it; cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, flowBox, flowBox->firstChild(), seekOutside, seekOutsideEnd, it); }/*end if*/ }/*end if*/ // if there are no more lines in this block, move towards block to come if (!flowBox) { if (toBegin) { prevBlock(); } else { nextBlock(); } } #if DEBUG_CARETMODE > 3 if (cbl) // qDebug() << cbl->information(); #endif } // == class EditableCaretBoxIterator implementation void EditableCaretBoxIterator::advance(bool toBegin) { #if DEBUG_CARETMODE > 3 // qDebug() << "---------------" << "toBegin " << toBegin; #endif const CaretBoxIterator preBegin = cbl->preBegin(); const CaretBoxIterator end = cbl->end(); CaretBoxIterator lastbox = *this, curbox; bool islastuseable = true; // silence gcc bool iscuruseable; // Assume adjacency of caret boxes. Will be falsified later if applicable. adjacent = true; #if DEBUG_CARETMODE > 4 // qDebug() << "ebit::advance: before: " << (**this)->object() << "@" << (**this)->object()->renderName() << ".node " << (**this)->object()->element() << "[" << ((**this)->object()->element() ? (**this)->object()->element()->nodeName().string() : QString()) << "] inline " << (**this)->isInline() << " outside " << (**this)->isOutside() << " outsideEnd " << (**this)->isOutsideEnd(); #endif if (toBegin) { CaretBoxIterator::operator --(); } else { CaretBoxIterator::operator ++(); } bool curAtEnd = *this == preBegin || *this == end; curbox = *this; bool atEnd = true; if (!curAtEnd) { iscuruseable = isEditable(curbox, toBegin); if (toBegin) { CaretBoxIterator::operator --(); } else { CaretBoxIterator::operator ++(); } atEnd = *this == preBegin || *this == end; } while (!curAtEnd) { bool haslast = lastbox != end && lastbox != preBegin; bool hascoming = !atEnd; bool iscominguseable = true; // silence gcc if (!atEnd) { iscominguseable = isEditable(*this, toBegin); } if (iscuruseable) { #if DEBUG_CARETMODE > 3 // qDebug() << "ebit::advance: " << (*curbox)->object() << "@" << (*curbox)->object()->renderName() << ".node " << (*curbox)->object()->element() << "[" << ((*curbox)->object()->element() ? (*curbox)->object()->element()->nodeName().string() : QString()) << "] inline " << (*curbox)->isInline() << " outside " << (*curbox)->isOutside() << " outsideEnd " << (*curbox)->isOutsideEnd(); #endif CaretBox *box = *curbox; if (box->isOutside()) { // if this caret box represents no inline box, it is an outside box // which has to be considered unconditionally if (!box->isInline()) { break; } if (advpol == VisibleFlows) { break; } // IndicatedFlows and LeafsOnly are treated equally in caret box lines InlineBox *ibox = box->inlineBox(); // get previous inline box InlineBox *prev = box->isOutsideEnd() ? ibox : ibox->prevOnLine(); // get next inline box InlineBox *next = box->isOutsideEnd() ? ibox->nextOnLine() : ibox; const bool isprevindicated = !prev || isIndicatedInlineBox(prev); const bool isnextindicated = !next || isIndicatedInlineBox(next); const bool last = haslast && !islastuseable; const bool coming = hascoming && !iscominguseable; const bool left = !prev || prev->isInlineFlowBox() && isprevindicated || (toBegin && coming || !toBegin && last); const bool right = !next || next->isInlineFlowBox() && isnextindicated || (!toBegin && coming || toBegin && last); const bool text2indicated = toBegin && next && next->isInlineTextBox() && isprevindicated || !toBegin && prev && prev->isInlineTextBox() && isnextindicated; const bool indicated2text = !toBegin && next && next->isInlineTextBox() && prev && isprevindicated // ### this code is so broken. /*|| toBegin && prev && prev->isInlineTextBox() && isnextindicated*/; #if DEBUG_CARETMODE > 5 // qDebug() << "prev " << prev << " haslast " << haslast << " islastuseable " << islastuseable << " left " << left << " next " << next << " hascoming " << hascoming << " iscominguseable " << iscominguseable << " right " << right << " text2indicated " << text2indicated << " indicated2text " << indicated2text; #endif if (left && right && !text2indicated || indicated2text) { adjacent = false; #if DEBUG_CARETMODE > 4 // qDebug() << "left && right && !text2indicated || indicated2text"; #endif break; } } else { // inside boxes are *always* valid #if DEBUG_CARETMODE > 4 if (box->isInline()) { InlineBox *ibox = box->inlineBox(); // qDebug() << "inside " << (!ibox->isInlineFlowBox() || static_cast<InlineFlowBox *>(ibox)->firstChild() ? "non-empty" : "empty") << (isIndicatedInlineBox(ibox) ? " indicated" : "") << " adjacent=" << adjacent; } #if 0 RenderStyle *s = ibox->object()->style(); // qDebug() << "bordls " << s->borderLeftStyle() << " bordl " << (s->borderLeftStyle() != BNONE) << " bordr " << (s->borderRightStyle() != BNONE) << " bordt " << (s->borderTopStyle() != BNONE) << " bordb " << (s->borderBottomStyle() != BNONE) << " padl " << s->paddingLeft().value() << " padr " << s->paddingRight().value() << " padt " << s->paddingTop().value() << " padb " << s->paddingBottom().value() // ### Can inline elements have top/bottom margins? Couldn't find // it in the CSS 2 spec, but Mozilla ignores them, so we do, too. << " marl " << s->marginLeft().value() - << " marr " << s->marginRight().value() - << endl; + << " marr " << s->marginRight().value(); #endif #endif break; }/*end if*/ } else { if (!(*curbox)->isOutside()) { // cannot be adjacent anymore adjacent = false; } }/*end if*/ lastbox = curbox; islastuseable = iscuruseable; curbox = *this; iscuruseable = iscominguseable; curAtEnd = atEnd; if (!atEnd) { if (toBegin) { CaretBoxIterator::operator --(); } else { CaretBoxIterator::operator ++(); } atEnd = *this == preBegin || *this == end; }/*end if*/ }/*wend*/ *static_cast<CaretBoxIterator *>(this) = curbox; #if DEBUG_CARETMODE > 4 // qDebug() << "still valid? " << (*this != preBegin && *this != end); #endif #if DEBUG_CARETMODE > 3 // qDebug() << "---------------" << "end "; #endif } bool EditableCaretBoxIterator::isEditable(const CaretBoxIterator &boxit, bool fromEnd) { Q_ASSERT(boxit != cbl->end() && boxit != cbl->preBegin()); CaretBox *b = *boxit; RenderObject *r = b->object(); #if DEBUG_CARETMODE > 0 // if (b->isInlineFlowBox()) qDebug() << "b is inline flow box" << (outside ? " (outside)" : ""); // qDebug() << "isEditable r" << r << ": " << (r ? r->renderName() : QString()) << (r && r->isText() ? " contains \"" + QString(((RenderText *)r)->str->s, qMin(((RenderText *)r)->str->l,15)) + "\"" : QString()); #endif // Must check caret mode or design mode *after* r->element(), otherwise // lines without a backing DOM node get regarded, leading to a crash. // ### check should actually be in InlineBoxIterator NodeImpl *node = r->element(); ObjectTraversalState trav; mapRenderPosToTraversalState(b->isOutside(), b->isOutsideEnd(), fromEnd, trav); if (isUnsuitable(r, trav) || !node) { return false; } // generally exclude replaced elements with no children from navigation if (!b->isOutside() && r->isRenderReplaced() && !r->firstChild()) { return false; } RenderObject *eff_r = r; bool globallyNavigable = m_part->isCaretMode() || m_part->isEditable(); // calculate the parent element's editability if this inline box is outside. if (b->isOutside() && !globallyNavigable) { NodeImpl *par = node->parent(); // I wonder whether par can be 0. It shouldn't be possible if the // algorithm contained no bugs. Q_ASSERT(par); if (par) { node = par; } eff_r = node->renderer(); Q_ASSERT(eff_r); // this is a hard requirement } bool result = globallyNavigable || eff_r->style()->userInput() == UI_ENABLED; #if DEBUG_CARETMODE > 0 // qDebug() << result; #endif return result; } // == class EditableLineIterator implementation void EditableLineIterator::advance(bool toBegin) { CaretAdvancePolicy advpol = lines->advancePolicy(); LineIterator lasteditable, lastindicated; bool haslasteditable = false; bool haslastindicated = false; bool uselasteditable = false; LineIterator::advance(toBegin); while (cbl) { if (isEditable(*this)) { #if DEBUG_CARETMODE > 3 // qDebug() << "advance: " << cbl->enclosingObject() << "@" << cbl->enclosingObject()->renderName() << ".node " << cbl->enclosingObject()->element() << "[" << (cbl->enclosingObject()->element() ? cbl->enclosingObject()->element()->nodeName().string() : QString()) << "]"; #endif bool hasindicated = isIndicatedFlow(cbl->enclosingObject()); if (hasindicated) { haslastindicated = true; lastindicated = *this; } switch (advpol) { case IndicatedFlows: if (hasindicated) { goto wend; } // fall through case LeafsOnly: if (cbl->isOutside()) { break; } // fall through case VisibleFlows: goto wend; }/*end switch*/ // remember rejected editable element lasteditable = *this; haslasteditable = true; #if DEBUG_CARETMODE > 4 // qDebug() << "remembered lasteditable " << *lasteditable; #endif } else { // If this element isn't editable, but the last one was, and it was only // rejected because it didn't match the caret advance policy, force it. // Otherwise certain combinations of editable and uneditable elements // could never be reached with some policies. if (haslasteditable) { uselasteditable = true; break; } } LineIterator::advance(toBegin); }/*wend*/ wend: if (uselasteditable) { *this = haslastindicated ? lastindicated : lasteditable; } if (!cbl && haslastindicated) { *this = lastindicated; } } // == class EditableCharacterIterator implementation void EditableCharacterIterator::initFirstChar() { CaretBox *box = *ebit; InlineBox *b = box->inlineBox(); if (_offset == box->maxOffset()) { peekNext(); } else if (b && !box->isOutside() && b->isInlineTextBox()) { _char = static_cast<RenderText *>(b->object())->str->s[_offset].unicode(); } else { _char = -1; } } /** returns true when the given caret box is empty, i. e. should not * take place in caret movement. */ static inline bool isCaretBoxEmpty(CaretBox *box) { if (!box->isInline()) { return false; } InlineBox *ibox = box->inlineBox(); return ibox->isInlineFlowBox() && !static_cast<InlineFlowBox *>(ibox)->firstChild() && !isIndicatedInlineBox(ibox); } EditableCharacterIterator &EditableCharacterIterator::operator ++() { _offset++; CaretBox *box = *ebit; InlineBox *b = box->inlineBox(); long maxofs = box->maxOffset(); #if DEBUG_CARETMODE > 0 // qDebug() << "box->maxOffset() " << box->maxOffset() << " box->minOffset() " << box->minOffset(); #endif if (_offset == maxofs) { #if DEBUG_CARETMODE > 2 // qDebug() << "_offset == maxofs: " << _offset << " == " << maxofs; #endif peekNext(); } else if (_offset > maxofs) { #if DEBUG_CARETMODE > 2 // qDebug() << "_offset > maxofs: " << _offset << " > " << maxofs /*<< " _peekNext: " << _peekNext*/; #endif if (/*!_peekNext*/true) { ++ebit; if (ebit == (*_it)->end()) { // end of line reached, go to next line ++_it; #if DEBUG_CARETMODE > 3 // qDebug() << "++_it"; #endif if (_it != _it.lines->end()) { ebit = _it; box = *ebit; b = box->inlineBox(); #if DEBUG_CARETMODE > 3 // qDebug() << "box " << box << " b " << b << " isText " << box->isInlineTextBox(); #endif #if DEBUG_CARETMODE > 3 RenderObject *_r = box->object(); // qDebug() << "_r " << _r << ":" << _r->element()->nodeName().string(); #endif _offset = box->minOffset(); #if DEBUG_CARETMODE > 3 // qDebug() << "_offset " << _offset; #endif } else { b = 0; _end = true; }/*end if*/ goto readchar; }/*end if*/ }/*end if*/ bool adjacent = ebit.isAdjacent(); #if 0 // Jump over element if this one is not a text node. if (adjacent && !(*ebit)->isInlineTextBox()) { EditableCaretBoxIterator copy = ebit; ++ebit; if (ebit != (*_it)->end() && (*ebit)->isInlineTextBox() /*&& (!(*ebit)->isInlineFlowBox() || static_cast<InlineFlowBox *>(*ebit)->)*/) { adjacent = false; } else { ebit = copy; } }/*end if*/ #endif // Jump over empty elements. if (adjacent && !(*ebit)->isInlineTextBox()) { bool noemptybox = true; while (isCaretBoxEmpty(*ebit)) { noemptybox = false; EditableCaretBoxIterator copy = ebit; ++ebit; if (ebit == (*_it)->end()) { ebit = copy; break; } } if (noemptybox) { adjacent = false; } }/*end if*/ // _r = (*ebit)->object(); /*if (!_it.outside) */_offset = (*ebit)->minOffset() + adjacent; //_peekNext = 0; box = *ebit; b = box->inlineBox(); goto readchar; } else { readchar: // get character if (b && !box->isOutside() && b->isInlineTextBox() && _offset < b->maxOffset()) { _char = static_cast<RenderText *>(b->object())->str->s[_offset].unicode(); } else { _char = -1; } }/*end if*/ #if DEBUG_CARETMODE > 2 // qDebug() << "_offset: " << _offset /*<< " _peekNext: " << _peekNext*/ << " char '" << (char)_char << "'"; #endif #if DEBUG_CARETMODE > 0 if (!_end && ebit != (*_it)->end()) { CaretBox *box = *ebit; RenderObject *_r = box->object(); // qDebug() << "echit++(1): box " << box << (box && box->isInlineTextBox() ? QString(" contains \"%1\"").arg(QConstString(static_cast<RenderText *>(box->object())->str->s+box->minOffset(), box->maxOffset() - box->minOffset()).string()) : QString()) << " _r " << (_r ? _r->element()->nodeName().string() : QString("<nil>")); } #endif return *this; } EditableCharacterIterator &EditableCharacterIterator::operator --() { _offset--; //qDebug() << "--: _offset=" << _offset; CaretBox *box = *ebit; CaretBox *_peekPrev = 0; CaretBox *_peekNext = 0; InlineBox *b = box->inlineBox(); long minofs = box->minOffset(); #if DEBUG_CARETMODE > 0 // qDebug() << "box->maxOffset() " << box->maxOffset() << " box->minOffset() " << box->minOffset(); #endif if (_offset == minofs) { #if DEBUG_CARETMODE > 2 // qDebug() << "_offset == minofs: " << _offset << " == " << minofs; #endif // _peekNext = b; // get character if (b && !box->isOutside() && b->isInlineTextBox()) { _char = static_cast<RenderText *>(b->object())->text()[_offset].unicode(); } else { _char = -1; } //peekPrev(); bool do_prev = false; { EditableCaretBoxIterator copy; _peekPrev = 0; do { copy = ebit; --ebit; if (ebit == (*_it)->preBegin()) { ebit = copy; break; } } while (isCaretBoxEmpty(*ebit)); // Jump to end of previous element if it's adjacent, and a text box if (ebit.isAdjacent() && ebit != (*_it)->preBegin() && (*ebit)->isInlineTextBox()) { _peekPrev = *ebit; do_prev = true; } else { ebit = copy; } } if (do_prev) { goto prev; } } else if (_offset < minofs) { prev: #if DEBUG_CARETMODE > 2 // qDebug() << "_offset < minofs: " << _offset << " < " << minofs /*<< " _peekNext: " << _peekNext*/; #endif if (!_peekPrev) { _peekNext = *ebit; --ebit; if (ebit == (*_it)->preBegin()) { // end of line reached, go to previous line --_it; #if DEBUG_CARETMODE > 3 // qDebug() << "--_it"; #endif if (_it != _it.lines->preBegin()) { // qDebug() << "begin from end!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"; ebit = EditableCaretBoxIterator(_it, true); box = *ebit; // RenderObject *r = box->object(); #if DEBUG_CARETMODE > 3 // qDebug() << "box " << box << " b " << box->inlineBox() << " isText " << box->isInlineTextBox(); #endif _offset = box->maxOffset(); // if (!_it.outside) _offset = r->isBR() ? (*ebit)->minOffset() : (*ebit)->maxOffset(); _char = -1; #if DEBUG_CARETMODE > 0 // qDebug() << "echit--(2): box " << box << " b " << box->inlineBox() << (box->isInlineTextBox() ? QString(" contains \"%1\"").arg(QConstString(static_cast<RenderText *>(box->object())->str->s+box->minOffset(), box->maxOffset() - box->minOffset()).string()) : QString()); #endif } else { _end = true; } return *this; }/*end if*/ }/*end if*/ #if DEBUG_CARETMODE > 0 bool adjacent = ebit.isAdjacent(); // qDebug() << "adjacent " << adjacent << " _peekNext " << _peekNext << " _peekNext->isInlineTextBox: " << (_peekNext ? _peekNext->isInlineTextBox() : false) << " !((*ebit)->isInlineTextBox): " << (*ebit ? !(*ebit)->isInlineTextBox() : true); #endif #if 0 // Ignore this box if it isn't a text box, but the previous box was if (adjacent && _peekNext && _peekNext->isInlineTextBox() && !(*ebit)->isInlineTextBox()) { EditableCaretBoxIterator copy = ebit; --ebit; if (ebit == (*_it)->preBegin()) /*adjacent = false; else */{ ebit = copy; } }/*end if*/ #endif #if 0 // Jump over empty elements. if (adjacent //&& _peekNext && _peekNext->isInlineTextBox() && !(*ebit)->isInlineTextBox()) { bool noemptybox = true; while (isCaretBoxEmpty(*ebit)) { noemptybox = false; EditableCaretBoxIterator copy = ebit; --ebit; if (ebit == (*_it)->preBegin()) { ebit = copy; break; } else { _peekNext = *copy; } } if (noemptybox) { adjacent = false; } }/*end if*/ #endif #if DEBUG_CARETMODE > 0 // qDebug() << "(*ebit)->obj " << (*ebit)->object()->renderName() << "[" << (*ebit)->object() << "]" << " minOffset: " << (*ebit)->minOffset() << " maxOffset: " << (*ebit)->maxOffset(); #endif #if DEBUG_CARETMODE > 3 RenderObject *_r = (*ebit)->object(); // qDebug() << "_r " << _r << ":" << _r->element()->nodeName().string(); #endif _offset = (*ebit)->maxOffset(); // if (!_it.outside) _offset = (*ebit)->maxOffset()/* - adjacent*/; #if DEBUG_CARETMODE > 3 // qDebug() << "_offset " << _offset; #endif _peekPrev = 0; } else { #if DEBUG_CARETMODE > 0 // qDebug() << "_offset: " << _offset << " _peekNext: " << _peekNext; #endif // get character if (_peekNext && _offset >= box->maxOffset() && _peekNext->isInlineTextBox()) { _char = static_cast<RenderText *>(_peekNext->object())->text()[_peekNext->minOffset()].unicode(); } else if (b && _offset < b->maxOffset() && b->isInlineTextBox()) { _char = static_cast<RenderText *>(b->object())->text()[_offset].unicode(); } else { _char = -1; } }/*end if*/ #if DEBUG_CARETMODE > 0 if (!_end && ebit != (*_it)->preBegin()) { CaretBox *box = *ebit; // qDebug() << "echit--(1): box " << box << " b " << box->inlineBox() << (box->isInlineTextBox() ? QString(" contains \"%1\"").arg(QConstString(static_cast<RenderText *>(box->object())->str->s+box->minOffset(), box->maxOffset() - box->minOffset()).string()) : QString()); } #endif return *this; } // == class TableRowIterator implementation TableRowIterator::TableRowIterator(RenderTable *table, bool fromEnd, RenderTableSection::RowStruct *row) : sec(table, fromEnd) { // set index if (*sec) { if (fromEnd) { index = (*sec)->grid.size() - 1; } else { index = 0; } }/*end if*/ // initialize with given row if (row && *sec) { while (operator *() != row) if (fromEnd) { operator --(); } else { operator ++(); } }/*end if*/ } TableRowIterator &TableRowIterator::operator ++() { index++; if (index >= (int)(*sec)->grid.size()) { ++sec; if (*sec) { index = 0; } }/*end if*/ return *this; } TableRowIterator &TableRowIterator::operator --() { index--; if (index < 0) { --sec; if (*sec) { index = (*sec)->grid.size() - 1; } }/*end if*/ return *this; } // == class ErgonomicEditableLineIterator implementation // some decls static RenderTableCell *findNearestTableCellInRow(KHTMLPart *part, int x, RenderTableSection::RowStruct *row, bool fromEnd); /** finds the cell corresponding to absolute x-coordinate @p x in the given * table. * * If there is no direct cell, or the cell is not accessible, the function * will return the nearest suitable cell. * @param part part containing the document * @param x absolute x-coordinate * @param it table row iterator, will be adapted accordingly as more rows are * investigated. * @param fromEnd @p true to begin search from end and work towards the * beginning * @return the cell, or 0 if no editable cell was found. */ static inline RenderTableCell *findNearestTableCell(KHTMLPart *part, int x, TableRowIterator &it, bool fromEnd) { RenderTableCell *result = 0; while (*it) { result = findNearestTableCellInRow(part, x, *it, fromEnd); if (result) { break; } if (fromEnd) { --it; } else { ++it; } }/*wend*/ return result; } /** finds the nearest editable cell around the given absolute x-coordinate * * It will dive into nested tables as necessary to provide seamless navigation. * * If the cell at @p x is not editable, its left neighbor is tried, then its * right neighbor, then the left neighbor's left neighbor etc. If no * editable cell can be found, 0 is returned. * @param part khtml part * @param x absolute x-coordinate * @param row table row to be searched * @param fromEnd @p true, begin from end (applies only to nested tables) * @return the found cell or 0 if no editable cell was found */ static RenderTableCell *findNearestTableCellInRow(KHTMLPart *part, int x, RenderTableSection::RowStruct *row, bool fromEnd) { // First pass. Find spatially nearest cell. int n = (int)row->row->size(); int i; for (i = 0; i < n; i++) { RenderTableCell *cell = row->row->at(i); if (!cell || (long)cell == -1) { continue; } int absx, absy; cell->absolutePosition(absx, absy, false); // ### position: fixed? #if DEBUG_CARETMODE > 1 // qDebug() << "i/n " << i << "/" << n << " absx " << absx << " absy " << absy; #endif // I rely on the assumption that all cells are in ascending visual order // ### maybe this assumption is wrong for bidi? #if DEBUG_CARETMODE > 1 // qDebug() << "x " << x << " < " << (absx + cell->width()) << "?"; #endif if (x < absx + cell->width()) { break; } }/*next i*/ if (i >= n) { i = n - 1; } // Second pass. Find editable cell, beginning with the currently found, // extending to the left, and to the right, alternating. for (int cnt = 0; cnt < 2 * n; cnt++) { int index = i - ((cnt >> 1) + 1) * (cnt & 1) + (cnt >> 1) * !(cnt & 1); if (index < 0 || index >= n) { continue; } RenderTableCell *cell = row->row->at(index); if (!cell || (long)cell == -1) { continue; } #if DEBUG_CARETMODE > 1 // qDebug() << "index " << index << " cell " << cell; #endif RenderTable *nestedTable; if (containsEditableElement(part, cell, nestedTable, fromEnd)) { if (nestedTable) { TableRowIterator it(nestedTable, fromEnd); while (*it) { // qDebug() << "=== recursive invocation"; cell = findNearestTableCell(part, x, it, fromEnd); if (cell) { break; } if (fromEnd) { --it; } else { ++it; } }/*wend*/ }/*end if*/ return cell; }/*end if*/ }/*next i*/ return 0; } /** returns the nearest common ancestor of two objects that is a table cell, * a table section, or 0 if not inside a common table. * * If @p r1 and @p r2 belong to the same table, but different sections, @p r1's * section is returned. */ static RenderObject *commonAncestorTableSectionOrCell(RenderObject *r1, RenderObject *r2) { if (!r1 || !r2) { return 0; } RenderTableSection *sec = 0; int start_depth = 0, end_depth = 0; // First we find the depths of the two objects in the tree (start_depth, end_depth) RenderObject *n = r1; while (n->parent()) { n = n->parent(); start_depth++; }/*wend*/ n = r2; while (n->parent()) { n = n->parent(); end_depth++; }/*wend*/ // here we climb up the tree with the deeper object, until both objects have equal depth while (end_depth > start_depth) { r2 = r2->parent(); end_depth--; }/*wend*/ while (start_depth > end_depth) { r1 = r1->parent(); // if (r1->isTableSection()) sec = static_cast<RenderTableSection *>(r1); start_depth--; }/*wend*/ // Climb the tree with both r1 and r2 until they are the same while (r1 != r2) { r1 = r1->parent(); if (r1->isTableSection()) { sec = static_cast<RenderTableSection *>(r1); } r2 = r2->parent(); }/*wend*/ // At this point, we found the most approximate common ancestor. Now climb // up until the condition of the function return value is satisfied. while (r1 && !r1->isTableCell() && !r1->isTableSection() && !r1->isTable()) { r1 = r1->parent(); } return r1 && r1->isTable() ? sec : r1; } /** Finds the row that contains the given cell, directly, or indirectly * @param section section to be searched * @param cell table cell * @param row returns the row * @param directCell returns the direct cell that contains @p cell * @return the index of the row. */ static int findRowInSection(RenderTableSection *section, RenderTableCell *cell, RenderTableSection::RowStruct *&row, RenderTableCell *&directCell) { // Seek direct cell RenderObject *r = cell; while (r != section) { if (r->isTableCell()) { directCell = static_cast<RenderTableCell *>(r); } r = r->parent(); }/*wend*/ // So, and this is really nasty: As we have no indices, we have to do a // linear comparison. Oh, that sucks so much for long tables, you can't // imagine. int n = section->numRows(); for (int i = 0; i < n; i++) { row = §ion->grid[i]; // check for cell int m = row->row->size(); for (int j = 0; j < m; j++) { RenderTableCell *c = row->row->at(j); if (c == directCell) { return i; } }/*next j*/ }/*next i*/ Q_ASSERT(false); return -1; } /** finds the table that is the first direct or indirect descendant of @p block. * @param leaf object to begin search from. * @param block object to search to, or 0 to search up to top. * @return the table or 0 if there were none. */ static inline RenderTable *findFirstDescendantTable(RenderObject *leaf, RenderBlock *block) { RenderTable *result = 0; while (leaf && leaf != block) { if (leaf->isTable()) { result = static_cast<RenderTable *>(leaf); } leaf = leaf->parent(); }/*wend*/ return result; } /** looks for the table cell the given object @p r is contained within. * @return the table cell or 0 if not contained in any table. */ static inline RenderTableCell *containingTableCell(RenderObject *r) { while (r && !r->isTableCell()) { r = r->parent(); } return static_cast<RenderTableCell *>(r); } inline void ErgonomicEditableLineIterator::calcAndStoreNewLine( RenderBlock *newBlock, bool toBegin) { // take the first/last editable element in the found cell as the new // value for the iterator CaretBoxIterator it; cbl = CaretBoxLine::constructCaretBoxLine(&lines->cblDeleter, newBlock, true, toBegin, it); #if DEBUG_CARETMODE > 3 // qDebug() << cbl->information(); #endif // if (toBegin) prevBlock(); else nextBlock(); if (!cbl) { return; }/*end if*/ EditableLineIterator::advance(toBegin); } void ErgonomicEditableLineIterator::determineTopologicalElement( RenderTableCell *oldCell, RenderObject *newObject, bool toBegin) { // When we arrive here, a transition between cells has happened. // Now determine the type of the transition. This can be // (1) a transition from this cell into a table inside this cell. // (2) a transition from this cell into another cell of this table TableRowIterator it; RenderObject *commonAncestor = commonAncestorTableSectionOrCell(oldCell, newObject); #if DEBUG_CARETMODE > 1 // qDebug() << " ancestor " << commonAncestor; #endif // The whole document is treated as a table cell. if (!commonAncestor || commonAncestor->isTableCell()) { // (1) RenderTableCell *cell = static_cast<RenderTableCell *>(commonAncestor); RenderTable *table = findFirstDescendantTable(newObject, cell); #if DEBUG_CARETMODE > 0 // qDebug() << "table cell: " << cell; #endif // if there is no table, we fell out of the previous table, and are now // in some table-less block. Therefore, done. if (!table) { return; } it = TableRowIterator(table, toBegin); } else if (commonAncestor->isTableSection()) { // (2) RenderTableSection *section = static_cast<RenderTableSection *>(commonAncestor); RenderTableSection::RowStruct *row; int idx = findRowInSection(section, oldCell, row, oldCell); #if DEBUG_CARETMODE > 1 // qDebug() << "table section: row idx " << idx; #endif it = TableRowIterator(section, idx); // advance rowspan rows int rowspan = oldCell->rowSpan(); while (*it && rowspan--) { if (toBegin) { --it; } else { ++it; } }/*wend*/ } else { - kError(6201) << "Neither common cell nor section! " << commonAncestor->renderName() << endl; + kError(6201) << "Neither common cell nor section! " << commonAncestor->renderName(); // will crash on uninitialized table row iterator }/*end if*/ RenderTableCell *cell = findNearestTableCell(lines->m_part, xCoor, it, toBegin); #if DEBUG_CARETMODE > 1 // qDebug() << "findNearestTableCell result: " << cell; #endif RenderBlock *newBlock = cell; if (!cell) { Q_ASSERT(commonAncestor->isTableSection()); RenderTableSection *section = static_cast<RenderTableSection *>(commonAncestor); cell = containingTableCell(section); #if DEBUG_CARETMODE > 1 // qDebug() << "containing cell: " << cell; #endif RenderTable *nestedTable; bool editableChild = cell && containsEditableChildElement(lines->m_part, cell, nestedTable, toBegin, section->table()); if (cell && !editableChild) { #if DEBUG_CARETMODE > 1 // qDebug() << "========= recursive invocation outer ========="; #endif determineTopologicalElement(cell, cell->section(), toBegin); #if DEBUG_CARETMODE > 1 // qDebug() << "========= end recursive invocation outer ========="; #endif return; } else if (cell && nestedTable) { #if DEBUG_CARETMODE > 1 // qDebug() << "========= recursive invocation inner ========="; #endif determineTopologicalElement(cell, nestedTable, toBegin); #if DEBUG_CARETMODE > 1 // qDebug() << "========= end recursive invocation inner ========="; #endif return; } else { #if DEBUG_CARETMODE > 1 // qDebug() << "newBlock is table: " << section->table(); #endif RenderObject *r = section->table(); int state; // not used ObjectTraversalState trav = OutsideAscending; r = advanceSuitableObject(r, trav, toBegin, lines->baseObject(), state); if (!r) { cbl = 0; return; } // if (toBegin) prevBlock(); else nextBlock(); newBlock = static_cast<RenderBlock *>(!r || r->isRenderBlock() ? r : r->containingBlock()); }/*end if*/ #if 0 } else { // adapt cell so that prevBlock/nextBlock works as expected newBlock = cell; // on forward advancing, we must start from the outside end of the // previous object if (!toBegin) { RenderObject *r = newBlock; int state; // not used ObjectTraversalState trav = OutsideAscending; r = advanceSuitableObject(r, trav, true, lines->advancePolicy(), lines->baseObject(), state); newBlock = static_cast<RenderBlock *>(!r || r->isRenderBlock() ? r : r->containingBlock()); }/*end if*/ #endif }/*end if*/ calcAndStoreNewLine(newBlock, toBegin); } ErgonomicEditableLineIterator &ErgonomicEditableLineIterator::operator ++() { RenderTableCell *oldCell = containingTableCell(cbl->enclosingObject()); EditableLineIterator::operator ++(); if (*this == lines->end() || *this == lines->preBegin()) { return *this; } RenderTableCell *newCell = containingTableCell(cbl->enclosingObject()); if (!newCell || newCell == oldCell) { return *this; } determineTopologicalElement(oldCell, newCell, false); return *this; } ErgonomicEditableLineIterator &ErgonomicEditableLineIterator::operator --() { RenderTableCell *oldCell = containingTableCell(cbl->enclosingObject()); EditableLineIterator::operator --(); if (*this == lines->end() || *this == lines->preBegin()) { return *this; } RenderTableCell *newCell = containingTableCell(cbl->enclosingObject()); if (!newCell || newCell == oldCell) { return *this; } determineTopologicalElement(oldCell, newCell, true); return *this; } // == Navigational helper functions == /** seeks the caret box which contains or is the nearest to @p x * @param it line iterator pointing to line to be searched * @param cv caret view context * @param x returns the cv->origX approximation, relatively positioned to the * containing block. * @param absx returns absolute x-coordinate of containing block * @param absy returns absolute y-coordinate of containing block * @return the most suitable caret box */ static CaretBox *nearestCaretBox(LineIterator &it, CaretViewContext *cv, int &x, int &absx, int &absy) { // Find containing block RenderObject *cb = (*it)->containingBlock(); #if DEBUG_CARETMODE > 4 // qDebug() << "nearestCB: cb " << cb << "@" << (cb ? cb->renderName() : ""); #endif if (cb) { cb->absolutePosition(absx, absy); } else { absx = absy = 0; } // Otherwise find out in which inline box the caret is to be placed. // this horizontal position is to be approximated x = cv->origX - absx; CaretBox *caretBox = 0; // Inline box containing the caret // NodeImpl *lastnode = 0; // node of previously checked render object. int xPos; // x-coordinate of current inline box int oldXPos = -1; // x-coordinate of last inline box EditableCaretBoxIterator fbit = it; #if DEBUG_CARETMODE > 0 /* if (it.linearDocument()->advancePolicy() != LeafsOnly) qWarning() << "nearestInlineBox is only prepared to handle the LeafsOnly advance policy";*/ // qDebug() << "*fbit = " << *fbit; #endif // Iterate through all children for (CaretBox * b; fbit != (*it)->end(); ++fbit) { b = *fbit; #if DEBUG_CARETMODE > 0 // RenderObject *r = b->object(); // if (b->isInlineFlowBox()) qDebug() << "b is inline flow box"; // qDebug() << "approximate r" << r << ": " << (r ? r->renderName() : QString()) << (r && r->isText() ? " contains \"" + QString(((RenderText *)r)->str->s, ((RenderText *)r)->str->l) + "\"" : QString()); #endif xPos = b->xPos(); // the caret is before this box if (x < xPos) { // snap to nearest box if (oldXPos < 0 || x - (oldXPos + caretBox->width()) > xPos - x) { caretBox = b; // current box is nearer }/*end if*/ break; // Otherwise, preceding box is implicitly used } caretBox = b; // the caret is within this box if (x >= xPos && x < xPos + caretBox->width()) { break; } oldXPos = xPos; // the caret can only be after the last box which is automatically // contained in caretBox when we fall out of the loop. }/*next fbit*/ return caretBox; } /** moves the given iterator to the beginning of the next word. * * If the end is reached, the iterator will be positioned there. * @param it character iterator to be moved */ static void moveItToNextWord(EditableCharacterIterator &it) { #if DEBUG_CARETMODE > 0 // qDebug() << "%%%%%%%%%%%%%%%%%%%%% moveItToNextWord"; #endif EditableCharacterIterator copy; while (!it.isEnd() && !(*it).isSpace() && !(*it).isPunct()) { #if DEBUG_CARETMODE > 2 // qDebug() << "reading1 '" << (*it).toLatin1().constData() << "'"; #endif copy = it; ++it; } if (it.isEnd()) { it = copy; return; }/*end if*/ while (!it.isEnd() && ((*it).isSpace() || (*it).isPunct())) { #if DEBUG_CARETMODE > 2 // qDebug() << "reading2 '" << (*it).toLatin1().constData() << "'"; #endif copy = it; ++it; } if (it.isEnd()) { it = copy; } } /** moves the given iterator to the beginning of the previous word. * * If the beginning is reached, the iterator will be positioned there. * @param it character iterator to be moved */ static void moveItToPrevWord(EditableCharacterIterator &it) { if (it.isEnd()) { return; } #if DEBUG_CARETMODE > 0 // qDebug() << "%%%%%%%%%%%%%%%%%%%%% moveItToPrevWord"; #endif EditableCharacterIterator copy; // Jump over all space and punctuation characters first do { copy = it; --it; #if DEBUG_CARETMODE > 2 if (!it.isEnd()) // qDebug() << "reading1 '" << (*it).toLatin1().constData() << "'"; #endif } while (!it.isEnd() && ((*it).isSpace() || (*it).isPunct())); if (it.isEnd()) { it = copy; return; }/*end if*/ do { copy = it; --it; #if DEBUG_CARETMODE > 0 if (!it.isEnd()) // qDebug() << "reading2 '" << (*it).toLatin1().constData() << "' (" << (int)(*it).toLatin1().constData() << ") box " << it.caretBox(); #endif } while (!it.isEnd() && !(*it).isSpace() && !(*it).isPunct()); it = copy; #if DEBUG_CARETMODE > 1 if (!it.isEnd()) // qDebug() << "effective '" << (*it).toLatin1().constData() << "' (" << (int)(*it).toLatin1().constData() << ") box " << it.caretBox(); #endif } /** moves the iterator by one page. * @param ld linear document * @param it line iterator, will be updated accordingly * @param mindist minimum distance in pixel the iterator should be moved * (if possible) * @param next @p true, move downward, @p false move upward */ static void moveIteratorByPage(LinearDocument &ld, ErgonomicEditableLineIterator &it, int mindist, bool next) { // ### This whole routine plainly sucks. Use an inverse strategie for pgup/pgdn. if (it == ld.end() || it == ld.preBegin()) { return; } ErgonomicEditableLineIterator copy = it; #if DEBUG_CARETMODE > 0 // qDebug() << " mindist: " << mindist; #endif CaretBoxLine *cbl = *copy; int absx = 0, absy = 0; RenderBlock *lastcb = cbl->containingBlock(); Q_ASSERT(lastcb->isRenderBlock()); lastcb->absolutePosition(absx, absy, false); // ### what about fixed? int lastfby = cbl->begin().data()->yPos(); int lastheight = 0; int rescue = 1000; // ### this is a hack to keep stuck carets from hanging the ua do { if (next) { ++copy; } else { --copy; } if (copy == ld.end() || copy == ld.preBegin()) { break; } cbl = *copy; RenderBlock *cb = cbl->containingBlock(); int diff = 0; // ### actually flowBox->yPos() should suffice, but this is not ported // over yet from WebCore int fby = cbl->begin().data()->yPos(); if (cb != lastcb) { if (next) { diff = absy + lastfby + lastheight; cb->absolutePosition(absx, absy, false); // ### what about fixed? diff = absy - diff + fby; lastfby = 0; } else { diff = absy; cb->absolutePosition(absx, absy, false); // ### what about fixed? diff -= absy + fby + lastheight; lastfby = fby - lastheight; }/*end if*/ #if DEBUG_CARETMODE > 2 // qDebug() << "absdiff " << diff; #endif } else { diff = qAbs(fby - lastfby); }/*end if*/ #if DEBUG_CARETMODE > 2 // qDebug() << "cbl->begin().data()->yPos(): " << fby << " diff " << diff; #endif mindist -= diff; lastheight = qAbs(fby - lastfby); lastfby = fby; lastcb = cb; it = copy; #if DEBUG_CARETMODE > 0 // qDebug() << " mindist: " << mindist; #endif // trick: actually the distance is always one line short, but we cannot // calculate the height of the first line (### WebCore will make it better) // Therefore, we simply approximate that excess line by using the last // caluculated line height. } while (mindist - lastheight > 0 && --rescue); } }/*end namespace*/ diff --git a/src/khtml_part.cpp b/src/khtml_part.cpp index 67fce35..c42b63f 100644 --- a/src/khtml_part.cpp +++ b/src/khtml_part.cpp @@ -1,7720 +1,7720 @@ /* This file is part of the KDE project * * Copyright (C) 1998, 1999 Torben Weis <weis@kde.org> * 1999 Lars Knoll <knoll@kde.org> * 1999 Antti Koivisto <koivisto@kde.org> * 2000 Simon Hausmann <hausmann@kde.org> * 2000 Stefan Schimanski <1Stein@gmx.de> * 2001-2005 George Staikos <staikos@kde.org> * 2001-2003 Dirk Mueller <mueller@kde.org> * 2000-2005 David Faure <faure@kde.org> * 2002 Apple Computer, Inc. * 2010 Maksim Orlovich (maksim@kde.org) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ //#define SPEED_DEBUG #include "khtml_part.h" #include "ui_htmlpageinfo.h" #include "khtmlviewbar.h" #include "khtml_pagecache.h" #include "dom/dom_string.h" #include "dom/dom_element.h" #include "dom/dom_exception.h" #include "dom/html_document.h" #include "dom/dom2_range.h" #include "editing/editor.h" #include "html/html_documentimpl.h" #include "html/html_baseimpl.h" #include "html/html_objectimpl.h" #include "html/html_miscimpl.h" #include "html/html_imageimpl.h" #include "imload/imagemanager.h" #include "rendering/render_text.h" #include "rendering/render_frames.h" #include "rendering/render_layer.h" #include "rendering/render_position.h" #include "misc/loader.h" #include "misc/khtml_partaccessor.h" #include "xml/dom2_eventsimpl.h" #include "xml/dom2_rangeimpl.h" #include "xml/xml_tokenizer.h" #include "css/cssstyleselector.h" using namespace DOM; #include "khtmlview.h" #include <kparts/partmanager.h> #include <kparts/browseropenorsavequestion.h> #include <kparts/guiactivateevent.h> #include <kacceleratormanager.h> #include "ecma/kjs_proxy.h" #include "ecma/kjs_window.h" #include "ecma/kjs_events.h" #include "khtml_settings.h" #include "kjserrordlg.h" #include <kjs/function.h> #include <kjs/interpreter.h> #include <sys/types.h> #include <assert.h> #include <kstringhandler.h> #include <kio/job.h> #include <kio/jobuidelegate.h> #include <kio/global.h> #include <kio/pixmaploader.h> #include <kio/hostinfo.h> #include <kprotocolmanager.h> #include <QDebug> #include <kjobwidgets.h> #include <kmessagebox.h> #include <kstandardaction.h> #include <kstandardguiitem.h> #include <kactioncollection.h> #include <kmimetypetrader.h> #include <qtemporaryfile.h> #include <ktoolinvocation.h> #include <kurlauthorized.h> #include <kparts/browserinterface.h> #include <kparts/scriptableextension.h> #include <kparts/liveconnectextension.h> #include <kactionmenu.h> #include <ktoggleaction.h> #include <kcodecaction.h> #include <kselectaction.h> #include <QDBusConnection> #include <ksslinfodialog.h> #include <ksslsettings.h> #include <QDBusInterface> #include <QMimeData> #include <kfileitem.h> #include <kurifilter.h> #include <kurllabel.h> #include <kurlmimedata.h> #include <QClipboard> #include <QLocale> #include <QMenu> #include <QToolTip> #include <QDrag> #include <QMouseEvent> #include <QtCore/QFile> #include <QtCore/QMetaEnum> #include <QTextDocument> #include <QtCore/QDate> #include <QtNetwork/QSslCertificate> #include <QStatusBar> #include <QStyle> #include <qmimedatabase.h> #include <qplatformdefs.h> #include <QFileInfo> #include "khtmlpart_p.h" #include "khtml_iface.h" #include "kpassivepopup.h" #include "rendering/render_form.h" #include <kwindowsystem.h> #include <kconfiggroup.h> #include <ksharedconfig.h> #ifdef KJS_DEBUGGER #include "ecma/debugger/debugwindow.h" #endif // SVG #include <svg/SVGDocument.h> #include <qstandardpaths.h> bool KHTMLPartPrivate::s_dnsInitialised = false; // DNS prefetch settings static const int sMaxDNSPrefetchPerPage = 42; static const int sDNSPrefetchTimerDelay = 200; static const int sDNSTTLSeconds = 400; static const int sDNSCacheSize = 500; namespace khtml { class PartStyleSheetLoader : public CachedObjectClient { public: PartStyleSheetLoader(KHTMLPart *part, DOM::DOMString url, DocLoader *dl) { m_part = part; m_cachedSheet = dl->requestStyleSheet(url, QString(), "text/css", true /* "user sheet" */); if (m_cachedSheet) { m_cachedSheet->ref(this); } } virtual ~PartStyleSheetLoader() { if (m_cachedSheet) { m_cachedSheet->deref(this); } } void setStyleSheet(const DOM::DOMString &, const DOM::DOMString &sheet, const DOM::DOMString &, const DOM::DOMString &/*mimetype*/) Q_DECL_OVERRIDE { if (m_part) { m_part->setUserStyleSheet(sheet.string()); } delete this; } void error(int, const QString &) Q_DECL_OVERRIDE { delete this; } QPointer<KHTMLPart> m_part; khtml::CachedCSSStyleSheet *m_cachedSheet; }; } KHTMLPart::KHTMLPart(QWidget *parentWidget, QObject *parent, GUIProfile prof) : KParts::ReadOnlyPart(parent) { d = nullptr; KHTMLGlobal::registerPart(this); setComponentData(KHTMLGlobal::aboutData(), false); init(new KHTMLView(this, parentWidget), prof); } KHTMLPart::KHTMLPart(KHTMLView *view, QObject *parent, GUIProfile prof) : KParts::ReadOnlyPart(parent) { d = nullptr; KHTMLGlobal::registerPart(this); setComponentData(KHTMLGlobal::aboutData(), false); assert(view); if (!view->part()) { view->setPart(this); } init(view, prof); } void KHTMLPart::init(KHTMLView *view, GUIProfile prof) { if (prof == DefaultGUI) { setXMLFile("khtml.rc"); } else if (prof == BrowserViewGUI) { setXMLFile("khtml_browser.rc"); } d = new KHTMLPartPrivate(this, parent()); d->m_view = view; if (!parentPart()) { QWidget *widget = new QWidget(view->parentWidget()); widget->setObjectName("khtml_part_widget"); QVBoxLayout *layout = new QVBoxLayout(widget); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); widget->setLayout(layout); d->m_topViewBar = new KHTMLViewBar(KHTMLViewBar::Top, d->m_view, widget); d->m_bottomViewBar = new KHTMLViewBar(KHTMLViewBar::Bottom, d->m_view, widget); layout->addWidget(d->m_topViewBar); layout->addWidget(d->m_view); layout->addWidget(d->m_bottomViewBar); setWidget(widget); widget->setFocusProxy(d->m_view); } else { setWidget(view); } d->m_guiProfile = prof; d->m_extension = new KHTMLPartBrowserExtension(this); d->m_extension->setObjectName("KHTMLBrowserExtension"); d->m_hostExtension = new KHTMLPartBrowserHostExtension(this); d->m_statusBarExtension = new KParts::StatusBarExtension(this); d->m_scriptableExtension = new KJS::KHTMLPartScriptable(this); new KHTMLTextExtension(this); new KHTMLHtmlExtension(this); d->m_statusBarPopupLabel = nullptr; d->m_openableSuppressedPopups = 0; d->m_paLoadImages = nullptr; d->m_paDebugScript = nullptr; d->m_bMousePressed = false; d->m_bRightMousePressed = false; d->m_bCleared = false; if (prof == BrowserViewGUI) { d->m_paViewDocument = new QAction(i18n("View Do&cument Source"), this); actionCollection()->addAction("viewDocumentSource", d->m_paViewDocument); connect(d->m_paViewDocument, SIGNAL(triggered(bool)), this, SLOT(slotViewDocumentSource())); if (!parentPart()) { actionCollection()->setDefaultShortcut(d->m_paViewDocument,QKeySequence(Qt::CTRL + Qt::Key_U)); } d->m_paViewFrame = new QAction(i18n("View Frame Source"), this); actionCollection()->addAction("viewFrameSource", d->m_paViewFrame); connect(d->m_paViewFrame, SIGNAL(triggered(bool)), this, SLOT(slotViewFrameSource())); if (!parentPart()) { actionCollection()->setDefaultShortcut(d->m_paViewFrame,QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_U)); } d->m_paViewInfo = new QAction(i18n("View Document Information"), this); actionCollection()->addAction("viewPageInfo", d->m_paViewInfo); if (!parentPart()) { actionCollection()->setDefaultShortcut(d->m_paViewInfo, QKeySequence(Qt::CTRL + Qt::Key_I)); } connect(d->m_paViewInfo, SIGNAL(triggered(bool)), this, SLOT(slotViewPageInfo())); d->m_paSaveBackground = new QAction(i18n("Save &Background Image As..."), this); actionCollection()->addAction("saveBackground", d->m_paSaveBackground); connect(d->m_paSaveBackground, SIGNAL(triggered(bool)), this, SLOT(slotSaveBackground())); d->m_paSaveDocument = actionCollection()->addAction(KStandardAction::SaveAs, "saveDocument", this, SLOT(slotSaveDocument())); if (parentPart()) { d->m_paSaveDocument->setShortcuts(QList<QKeySequence>()); // avoid clashes } d->m_paSaveFrame = new QAction(i18n("Save &Frame As..."), this); actionCollection()->addAction("saveFrame", d->m_paSaveFrame); connect(d->m_paSaveFrame, SIGNAL(triggered(bool)), this, SLOT(slotSaveFrame())); } else { d->m_paViewDocument = nullptr; d->m_paViewFrame = nullptr; d->m_paViewInfo = nullptr; d->m_paSaveBackground = nullptr; d->m_paSaveDocument = nullptr; d->m_paSaveFrame = nullptr; } d->m_paSecurity = new QAction(i18n("SSL"), this); actionCollection()->addAction("security", d->m_paSecurity); connect(d->m_paSecurity, SIGNAL(triggered(bool)), this, SLOT(slotSecurity())); d->m_paDebugRenderTree = new QAction(i18n("Print Rendering Tree to STDOUT"), this); actionCollection()->addAction("debugRenderTree", d->m_paDebugRenderTree); connect(d->m_paDebugRenderTree, SIGNAL(triggered(bool)), this, SLOT(slotDebugRenderTree())); d->m_paDebugDOMTree = new QAction(i18n("Print DOM Tree to STDOUT"), this); actionCollection()->addAction("debugDOMTree", d->m_paDebugDOMTree); connect(d->m_paDebugDOMTree, SIGNAL(triggered(bool)), this, SLOT(slotDebugDOMTree())); QAction *paDebugFrameTree = new QAction(i18n("Print frame tree to STDOUT"), this); actionCollection()->addAction("debugFrameTree", paDebugFrameTree); connect(paDebugFrameTree, SIGNAL(triggered(bool)), this, SLOT(slotDebugFrameTree())); d->m_paStopAnimations = new QAction(i18n("Stop Animated Images"), this); actionCollection()->addAction("stopAnimations", d->m_paStopAnimations); connect(d->m_paStopAnimations, SIGNAL(triggered(bool)), this, SLOT(slotStopAnimations())); d->m_paSetEncoding = new KCodecAction(QIcon::fromTheme("character-set"), i18n("Set &Encoding"), this, true); actionCollection()->addAction("setEncoding", d->m_paSetEncoding); // d->m_paSetEncoding->setDelayed( false ); connect(d->m_paSetEncoding, SIGNAL(triggered(QString)), this, SLOT(slotSetEncoding(QString))); connect(d->m_paSetEncoding, SIGNAL(triggered(KEncodingProber::ProberType)), this, SLOT(slotAutomaticDetectionLanguage(KEncodingProber::ProberType))); if (KSharedConfig::openConfig()->hasGroup("HTML Settings")) { KConfigGroup config(KSharedConfig::openConfig(), "HTML Settings"); d->m_autoDetectLanguage = static_cast<KEncodingProber::ProberType>(config.readEntry("AutomaticDetectionLanguage", /*static_cast<int>(language) */0)); if (d->m_autoDetectLanguage == KEncodingProber::None) { const QByteArray name = QTextCodec::codecForLocale()->name().toLower(); // qWarning() << "00000000 "; if (name.endsWith("1251") || name.startsWith("koi") || name == "iso-8859-5") { d->m_autoDetectLanguage = KEncodingProber::Cyrillic; } else if (name.endsWith("1256") || name == "iso-8859-6") { d->m_autoDetectLanguage = KEncodingProber::Arabic; } else if (name.endsWith("1257") || name == "iso-8859-13" || name == "iso-8859-4") { d->m_autoDetectLanguage = KEncodingProber::Baltic; } else if (name.endsWith("1250") || name == "ibm852" || name == "iso-8859-2" || name == "iso-8859-3") { d->m_autoDetectLanguage = KEncodingProber::CentralEuropean; } else if (name.endsWith("1253") || name == "iso-8859-7") { d->m_autoDetectLanguage = KEncodingProber::Greek; } else if (name.endsWith("1255") || name == "iso-8859-8" || name == "iso-8859-8-i") { d->m_autoDetectLanguage = KEncodingProber::Hebrew; } else if (name == "jis7" || name == "eucjp" || name == "sjis") { d->m_autoDetectLanguage = KEncodingProber::Japanese; } else if (name == "gb2312" || name == "gbk" || name == "gb18030") { d->m_autoDetectLanguage = KEncodingProber::ChineseSimplified; } else if (name == "big5") { d->m_autoDetectLanguage = KEncodingProber::ChineseTraditional; } else if (name == "euc-kr") { d->m_autoDetectLanguage = KEncodingProber::Korean; } else if (name.endsWith("1254") || name == "iso-8859-9") { d->m_autoDetectLanguage = KEncodingProber::Turkish; } else if (name.endsWith("1252") || name == "iso-8859-1" || name == "iso-8859-15") { d->m_autoDetectLanguage = KEncodingProber::WesternEuropean; } else { d->m_autoDetectLanguage = KEncodingProber::Universal; } // qWarning() << "0000000end " << d->m_autoDetectLanguage << " " << QTextCodec::codecForLocale()->mibEnum(); } d->m_paSetEncoding->setCurrentProberType(d->m_autoDetectLanguage); } d->m_paUseStylesheet = new KSelectAction(i18n("Use S&tylesheet"), this); actionCollection()->addAction("useStylesheet", d->m_paUseStylesheet); connect(d->m_paUseStylesheet, SIGNAL(triggered(int)), this, SLOT(slotUseStylesheet())); if (prof == BrowserViewGUI) { d->m_paIncZoomFactor = new KHTMLZoomFactorAction(this, true, "format-font-size-more", i18n("Enlarge Font"), this); actionCollection()->addAction("incFontSizes", d->m_paIncZoomFactor); connect(d->m_paIncZoomFactor, SIGNAL(triggered(bool)), SLOT(slotIncFontSizeFast())); d->m_paIncZoomFactor->setWhatsThis(i18n("<qt>Enlarge Font<br /><br />" "Make the font in this window bigger. " "Click and hold down the mouse button for a menu with all available font sizes.</qt>")); d->m_paDecZoomFactor = new KHTMLZoomFactorAction(this, false, "format-font-size-less", i18n("Shrink Font"), this); actionCollection()->addAction("decFontSizes", d->m_paDecZoomFactor); connect(d->m_paDecZoomFactor, SIGNAL(triggered(bool)), SLOT(slotDecFontSizeFast())); d->m_paDecZoomFactor->setWhatsThis(i18n("<qt>Shrink Font<br /><br />" "Make the font in this window smaller. " "Click and hold down the mouse button for a menu with all available font sizes.</qt>")); if (!parentPart()) { // For framesets, this action also affects frames, so only // the frameset needs to define a shortcut for the action. // TODO: Why also CTRL+=? Because of http://trolltech.com/developer/knowledgebase/524/? // Nobody else does it... actionCollection()->setDefaultShortcut(d->m_paIncZoomFactor, QKeySequence("CTRL++; CTRL+=")); actionCollection()->setDefaultShortcut(d->m_paDecZoomFactor, QKeySequence(Qt::CTRL + Qt::Key_Minus)); } } d->m_paFind = actionCollection()->addAction(KStandardAction::Find, "find", this, SLOT(slotFind())); d->m_paFind->setWhatsThis(i18n("<qt>Find text<br /><br />" "Shows a dialog that allows you to find text on the displayed page.</qt>")); d->m_paFindNext = actionCollection()->addAction(KStandardAction::FindNext, "findNext", this, SLOT(slotFindNext())); d->m_paFindNext->setWhatsThis(i18n("<qt>Find next<br /><br />" "Find the next occurrence of the text that you " "have found using the <b>Find Text</b> function.</qt>")); d->m_paFindPrev = actionCollection()->addAction(KStandardAction::FindPrev, "findPrevious", this, SLOT(slotFindPrev())); d->m_paFindPrev->setWhatsThis(i18n("<qt>Find previous<br /><br />" "Find the previous occurrence of the text that you " "have found using the <b>Find Text</b> function.</qt>")); // These two actions aren't visible in the menus, but exist for the (configurable) shortcut d->m_paFindAheadText = new QAction(i18n("Find Text as You Type"), this); actionCollection()->addAction("findAheadText", d->m_paFindAheadText); actionCollection()->setDefaultShortcut(d->m_paFindAheadText, QKeySequence("/")); d->m_paFindAheadText->setToolTip(i18n("This shortcut shows the find bar, for finding text in the displayed page. It cancels the effect of \"Find Links as You Type\", which sets the \"Find links only\" option.")); d->m_paFindAheadText->setStatusTip(d->m_paFindAheadText->toolTip()); connect(d->m_paFindAheadText, SIGNAL(triggered(bool)), this, SLOT(slotFindAheadText())); d->m_paFindAheadLinks = new QAction(i18n("Find Links as You Type"), this); actionCollection()->addAction("findAheadLink", d->m_paFindAheadLinks); // The issue is that it sets the (sticky) option FindLinksOnly, so // if you trigger this shortcut once by mistake, Esc and Ctrl+F will still have the option set. // Better let advanced users configure a shortcut for this advanced option //d->m_paFindAheadLinks->setShortcut( QKeySequence("\'") ); d->m_paFindAheadLinks->setToolTip(i18n("This shortcut shows the find bar, and sets the option \"Find links only\".")); d->m_paFindAheadLinks->setStatusTip(d->m_paFindAheadLinks->toolTip()); connect(d->m_paFindAheadLinks, SIGNAL(triggered(bool)), this, SLOT(slotFindAheadLink())); if (parentPart()) { d->m_paFind->setShortcuts(QList<QKeySequence>()); // avoid clashes d->m_paFindNext->setShortcuts(QList<QKeySequence>()); // avoid clashes d->m_paFindPrev->setShortcuts(QList<QKeySequence>()); // avoid clashes d->m_paFindAheadText->setShortcuts(QList<QKeySequence>()); d->m_paFindAheadLinks->setShortcuts(QList<QKeySequence>()); } d->m_paPrintFrame = new QAction(i18n("Print Frame..."), this); actionCollection()->addAction("printFrame", d->m_paPrintFrame); d->m_paPrintFrame->setIcon(QIcon::fromTheme("document-print-frame")); connect(d->m_paPrintFrame, SIGNAL(triggered(bool)), this, SLOT(slotPrintFrame())); d->m_paPrintFrame->setWhatsThis(i18n("<qt>Print Frame<br /><br />" "Some pages have several frames. To print only a single frame, click " "on it and then use this function.</qt>")); // Warning: The name selectAll is used hardcoded by some 3rd parties to remove the // shortcut for selectAll so they do not get ambigous shortcuts. Renaming it // will either crash or render useless that workaround. It would be better // to use the name KStandardAction::name(KStandardAction::SelectAll) but we // can't for the same reason. d->m_paSelectAll = actionCollection()->addAction(KStandardAction::SelectAll, "selectAll", this, SLOT(slotSelectAll())); if (parentPart()) { // Only the frameset has the shortcut, but the slot uses the current frame. d->m_paSelectAll->setShortcuts(QList<QKeySequence>()); // avoid clashes } d->m_paToggleCaretMode = new KToggleAction(i18n("Toggle Caret Mode"), this); actionCollection()->addAction("caretMode", d->m_paToggleCaretMode); actionCollection()->setDefaultShortcut(d->m_paToggleCaretMode, QKeySequence(Qt::Key_F7)); connect(d->m_paToggleCaretMode, SIGNAL(triggered(bool)), this, SLOT(slotToggleCaretMode())); d->m_paToggleCaretMode->setChecked(isCaretMode()); if (parentPart()) { d->m_paToggleCaretMode->setShortcuts(QList<QKeySequence>()); // avoid clashes } // set the default java(script) flags according to the current host. d->m_bOpenMiddleClick = d->m_settings->isOpenMiddleClickEnabled(); d->m_bJScriptEnabled = d->m_settings->isJavaScriptEnabled(); setDebugScript(d->m_settings->isJavaScriptDebugEnabled()); d->m_bJavaEnabled = d->m_settings->isJavaEnabled(); d->m_bPluginsEnabled = d->m_settings->isPluginsEnabled(); // Set the meta-refresh flag... d->m_metaRefreshEnabled = d->m_settings->isAutoDelayedActionsEnabled(); KHTMLSettings::KSmoothScrollingMode ssm = d->m_settings->smoothScrolling(); if (ssm == KHTMLSettings::KSmoothScrollingDisabled) { d->m_view->setSmoothScrollingModeDefault(KHTMLView::SSMDisabled); } else if (ssm == KHTMLSettings::KSmoothScrollingWhenEfficient) { d->m_view->setSmoothScrollingModeDefault(KHTMLView::SSMWhenEfficient); } else { d->m_view->setSmoothScrollingModeDefault(KHTMLView::SSMEnabled); } if (d->m_bDNSPrefetchIsDefault && !onlyLocalReferences()) { KHTMLSettings::KDNSPrefetch dpm = d->m_settings->dnsPrefetch(); if (dpm == KHTMLSettings::KDNSPrefetchDisabled) { d->m_bDNSPrefetch = DNSPrefetchDisabled; } else if (dpm == KHTMLSettings::KDNSPrefetchOnlyWWWAndSLD) { d->m_bDNSPrefetch = DNSPrefetchOnlyWWWAndSLD; } else { d->m_bDNSPrefetch = DNSPrefetchEnabled; } } if (!KHTMLPartPrivate::s_dnsInitialised && d->m_bDNSPrefetch != DNSPrefetchDisabled) { KIO::HostInfo::setCacheSize(sDNSCacheSize); KIO::HostInfo::setTTL(sDNSTTLSeconds); KHTMLPartPrivate::s_dnsInitialised = true; } // all shortcuts should only be active, when this part has focus foreach (QAction *action, actionCollection()->actions()) { action->setShortcutContext(Qt::WidgetWithChildrenShortcut); } actionCollection()->associateWidget(view); connect(view, SIGNAL(zoomView(int)), SLOT(slotZoomView(int))); connect(this, SIGNAL(completed()), this, SLOT(updateActions())); connect(this, SIGNAL(completed(bool)), this, SLOT(updateActions())); connect(this, SIGNAL(started(KIO::Job*)), this, SLOT(updateActions())); // #### FIXME: the process wide loader is going to signal every part about every loaded object. // That's quite inefficient. Should be per-document-tree somehow. Even signaling to // child parts that a request from an ancestor has loaded is inefficent.. connect(khtml::Cache::loader(), SIGNAL(requestStarted(khtml::DocLoader*,khtml::CachedObject*)), this, SLOT(slotLoaderRequestStarted(khtml::DocLoader*,khtml::CachedObject*))); connect(khtml::Cache::loader(), SIGNAL(requestDone(khtml::DocLoader*,khtml::CachedObject*)), this, SLOT(slotLoaderRequestDone(khtml::DocLoader*,khtml::CachedObject*))); connect(khtml::Cache::loader(), SIGNAL(requestFailed(khtml::DocLoader*,khtml::CachedObject*)), this, SLOT(slotLoaderRequestDone(khtml::DocLoader*,khtml::CachedObject*))); connect(&d->m_progressUpdateTimer, SIGNAL(timeout()), this, SLOT(slotProgressUpdate())); findTextBegin(); //reset find variables connect(&d->m_redirectionTimer, SIGNAL(timeout()), this, SLOT(slotRedirect())); if (QDBusConnection::sessionBus().isConnected()) { new KHTMLPartIface(this); // our "adaptor" for (int i = 1;; ++i) if (QDBusConnection::sessionBus().registerObject(QString("/KHTML/%1/widget").arg(i), this)) { break; } else if (i == 0xffff) { qFatal("Something is very wrong in KHTMLPart!"); } } if (prof == BrowserViewGUI && !parentPart()) { loadPlugins(); } } KHTMLPart::~KHTMLPart() { // qDebug() << this; KConfigGroup config(KSharedConfig::openConfig(), "HTML Settings"); config.writeEntry("AutomaticDetectionLanguage", int(d->m_autoDetectLanguage)); if (d->m_manager) { // the PartManager for this part's children d->m_manager->removePart(this); } slotWalletClosed(); if (!parentPart()) { // only delete it if the top khtml_part closes removeJSErrorExtension(); } stopAutoScroll(); d->m_redirectionTimer.stop(); if (!d->m_bComplete) { closeUrl(); } disconnect(khtml::Cache::loader(), SIGNAL(requestStarted(khtml::DocLoader*,khtml::CachedObject*)), this, SLOT(slotLoaderRequestStarted(khtml::DocLoader*,khtml::CachedObject*))); disconnect(khtml::Cache::loader(), SIGNAL(requestDone(khtml::DocLoader*,khtml::CachedObject*)), this, SLOT(slotLoaderRequestDone(khtml::DocLoader*,khtml::CachedObject*))); disconnect(khtml::Cache::loader(), SIGNAL(requestFailed(khtml::DocLoader*,khtml::CachedObject*)), this, SLOT(slotLoaderRequestDone(khtml::DocLoader*,khtml::CachedObject*))); clear(); hide(); if (d->m_view) { d->m_view->m_part = nullptr; } // Have to delete this here since we forward declare it in khtmlpart_p and // at least some compilers won't call the destructor in this case. delete d->m_jsedlg; d->m_jsedlg = nullptr; if (!parentPart()) { // only delete d->m_frame if the top khtml_part closes delete d->m_frame; } else if (d->m_frame && d->m_frame->m_run) { // for kids, they may get detached while d->m_frame->m_run.data()->abort(); // resolving mimetype; cancel that if needed } delete d; d = nullptr; KHTMLGlobal::deregisterPart(this); } bool KHTMLPart::restoreURL(const QUrl &url) { // qDebug() << url; d->m_redirectionTimer.stop(); /* * That's not a good idea as it will call closeUrl() on all * child frames, preventing them from further loading. This * method gets called from restoreState() in case of a full frameset * restoral, and restoreState() calls closeUrl() before restoring * anyway. // qDebug() << "closing old URL"; closeUrl(); */ d->m_bComplete = false; d->m_bLoadEventEmitted = false; d->m_workingURL = url; // set the java(script) flags according to the current host. d->m_bJScriptEnabled = KHTMLGlobal::defaultHTMLSettings()->isJavaScriptEnabled(url.host()); setDebugScript(KHTMLGlobal::defaultHTMLSettings()->isJavaScriptDebugEnabled()); d->m_bJavaEnabled = KHTMLGlobal::defaultHTMLSettings()->isJavaEnabled(url.host()); d->m_bPluginsEnabled = KHTMLGlobal::defaultHTMLSettings()->isPluginsEnabled(url.host()); setUrl(url); d->m_restoreScrollPosition = true; disconnect(d->m_view, SIGNAL(finishedLayout()), this, SLOT(restoreScrollPosition())); connect(d->m_view, SIGNAL(finishedLayout()), this, SLOT(restoreScrollPosition())); KHTMLPageCache::self()->fetchData(d->m_cacheId, this, SLOT(slotRestoreData(QByteArray))); emit started(nullptr); return true; } static bool areUrlsForSamePage(const QUrl &url1, const QUrl &url2) { QUrl u1 = url1.adjusted(QUrl::StripTrailingSlash); u1.setFragment(QString()); if (u1.path() == QLatin1String("/")) { u1.setPath(QString()); } QUrl u2 = url2.adjusted(QUrl::StripTrailingSlash); u2.setFragment(QString()); if (u2.path() == QLatin1String("/")) { u2.setPath(QString()); } return u1 == u2; } bool KHTMLPartPrivate::isLocalAnchorJump(const QUrl &url) { // kio_help actually uses fragments to identify different pages, so // always reload with it. if (url.scheme() == QLatin1String("help")) { return false; } return url.hasFragment() && areUrlsForSamePage(url, q->url()); } void KHTMLPartPrivate::executeAnchorJump(const QUrl &url, bool lockHistory) { DOM::HashChangeEventImpl *hashChangeEvImpl = nullptr; const QString &oldRef = q->url().fragment(QUrl::FullyEncoded); const QString &newRef = url.fragment(QUrl::FullyEncoded); const bool hashChanged = (oldRef != newRef) || (oldRef.isNull() && newRef.isEmpty()); if (hashChanged) { // Note: we want to emit openUrlNotify first thing to make the history capture the old state, // however do not update history if a lock was explicitly requested, e.g. Location.replace() if (!lockHistory) { emit m_extension->openUrlNotify(); } // Create hashchange event hashChangeEvImpl = new DOM::HashChangeEventImpl(); hashChangeEvImpl->initHashChangeEvent("hashchange", true, //bubble false, //cancelable q->url().toString(), //oldURL url.toString() //newURL ); } if (!q->gotoAnchor(newRef)) { // encoded fragment q->gotoAnchor(url.fragment(QUrl::FullyDecoded)); // not encoded fragment } q->setUrl(url); emit m_extension->setLocationBarUrl(url.toDisplayString()); if (hashChangeEvImpl) { m_doc->dispatchWindowEvent(hashChangeEvImpl); } } bool KHTMLPart::openUrl(const QUrl &url) { // qDebug() << this << "opening" << url; #ifndef KHTML_NO_WALLET // Wallet forms are per page, so clear it when loading a different page if we // are not an iframe (because we store walletforms only on the topmost part). if (!parentPart()) { d->m_walletForms.clear(); } #endif d->m_redirectionTimer.stop(); // check to see if this is an "error://" URL. This is caused when an error // occurs before this part was loaded (e.g. KonqRun), and is passed to // khtmlpart so that it can display the error. if (url.scheme() == "error") { closeUrl(); if (d->m_bJScriptEnabled) { d->m_statusBarText[BarOverrideText].clear(); d->m_statusBarText[BarDefaultText].clear(); } /** * The format of the error url is that two variables are passed in the query: * error = int kio error code, errText = QString error text from kio * and the URL where the error happened is passed as a sub URL. */ const QUrl mainURL(url.fragment()); //qDebug() << "Handling error URL. URL count:" << urls.count(); if (mainURL.isValid()) { QString query = url.query(QUrl::FullyDecoded); QRegularExpression pattern("error=(\\d+)&errText=(.*)"); QRegularExpressionMatch match = pattern.match(query); int error = match.captured(1).toInt(); // error=0 isn't a valid error code, so 0 means it's missing from the URL if (error == 0) { error = KIO::ERR_UNKNOWN; } const QString errorText = match.captured(2); d->m_workingURL = mainURL; //qDebug() << "Emitting fixed URL " << d->m_workingURL; emit d->m_extension->setLocationBarUrl(d->m_workingURL.toDisplayString()); htmlError(error, errorText, d->m_workingURL); return true; } } if (!parentPart()) { // only do it for toplevel part QString host = url.isLocalFile() ? "localhost" : url.host(); QString userAgent = KProtocolManager::userAgentForHost(host); if (userAgent != KProtocolManager::userAgentForHost(QString())) { if (!d->m_statusBarUALabel) { d->m_statusBarUALabel = new KUrlLabel(d->m_statusBarExtension->statusBar()); d->m_statusBarUALabel->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum)); d->m_statusBarUALabel->setUseCursor(false); d->m_statusBarExtension->addStatusBarItem(d->m_statusBarUALabel, 0, false); d->m_statusBarUALabel->setPixmap(SmallIcon("preferences-web-browser-identification")); } d->m_statusBarUALabel->setToolTip(i18n("The fake user-agent '%1' is in use.", userAgent)); } else if (d->m_statusBarUALabel) { d->m_statusBarExtension->removeStatusBarItem(d->m_statusBarUALabel); delete d->m_statusBarUALabel; d->m_statusBarUALabel = nullptr; } } KParts::BrowserArguments browserArgs(d->m_extension->browserArguments()); KParts::OpenUrlArguments args(arguments()); // in case // a) we have no frameset (don't test m_frames.count(), iframes get in there) // b) the url is identical with the currently displayed one (except for the htmlref!) // c) the url request is not a POST operation and // d) the caller did not request to reload the page // e) there was no HTTP redirection meanwhile (testcase: webmin's software/tree.cgi) // => we don't reload the whole document and // we just jump to the requested html anchor bool isFrameSet = false; if (d->m_doc && d->m_doc->isHTMLDocument()) { HTMLDocumentImpl *htmlDoc = static_cast<HTMLDocumentImpl *>(d->m_doc); isFrameSet = htmlDoc->body() && (htmlDoc->body()->id() == ID_FRAMESET); } if (isFrameSet && d->isLocalAnchorJump(url) && browserArgs.softReload) { QList<khtml::ChildFrame *>::Iterator it = d->m_frames.begin(); const QList<khtml::ChildFrame *>::Iterator end = d->m_frames.end(); for (; it != end; ++it) { KHTMLPart *const part = qobject_cast<KHTMLPart *>((*it)->m_part.data()); if (part) { // We are reloading frames to make them jump into offsets. KParts::OpenUrlArguments partargs(part->arguments()); partargs.setReload(true); part->setArguments(partargs); part->openUrl(part->url()); } }/*next it*/ return true; } if (url.hasFragment() && !isFrameSet) { bool noReloadForced = !args.reload() && !browserArgs.redirectedRequest() && !browserArgs.doPost(); if (noReloadForced && d->isLocalAnchorJump(url)) { // qDebug() << "jumping to anchor. m_url = " << url; setUrl(url); emit started(nullptr); if (!gotoAnchor(url.fragment(QUrl::FullyEncoded))) { gotoAnchor(url.fragment(QUrl::FullyDecoded)); } d->m_bComplete = true; if (d->m_doc) { d->m_doc->setParsing(false); } // qDebug() << "completed..."; emit completed(); return true; } } // Save offset of viewport when page is reloaded to be compliant // to every other capable browser out there. if (args.reload()) { args.setXOffset(d->m_view->contentsX()); args.setYOffset(d->m_view->contentsY()); setArguments(args); } if (!d->m_restored) { closeUrl(); } d->m_restoreScrollPosition = d->m_restored; disconnect(d->m_view, SIGNAL(finishedLayout()), this, SLOT(restoreScrollPosition())); connect(d->m_view, SIGNAL(finishedLayout()), this, SLOT(restoreScrollPosition())); // Classify the mimetype. Some, like images and plugins are handled // by wrapping things up in tags, so we want to plain output the HTML, // and not start the job and all that (since we would want the // KPart or whatever to load it). // This is also the only place we need to do this, as it's for // internal iframe use, not any other clients. MimeType type = d->classifyMimeType(args.mimeType()); if (type == MimeImage || type == MimeOther) { begin(url, args.xOffset(), args.yOffset()); write(QString::fromLatin1("<html><head></head><body>")); if (type == MimeImage) { write(QString::fromLatin1("<img ")); } else { write(QString::fromLatin1("<embed ")); } write(QString::fromLatin1("src=\"")); assert(url.toString().indexOf('"') == -1); write(url.toString()); write(QString::fromLatin1("\">")); end(); return true; } // initializing m_url to the new url breaks relative links when opening such a link after this call and _before_ begin() is called (when the first // data arrives) (Simon) d->m_workingURL = url; if (url.scheme().startsWith("http") && !url.host().isEmpty() && url.path().isEmpty()) { d->m_workingURL.setPath("/"); emit d->m_extension->setLocationBarUrl(d->m_workingURL.toDisplayString()); } setUrl(d->m_workingURL); QMap<QString, QString> &metaData = args.metaData(); metaData.insert("main_frame_request", parentPart() == nullptr ? "TRUE" : "FALSE"); metaData.insert("ssl_parent_ip", d->m_ssl_parent_ip); metaData.insert("ssl_parent_cert", d->m_ssl_parent_cert); metaData.insert("PropagateHttpHeader", "true"); metaData.insert("ssl_was_in_use", d->m_ssl_in_use ? "TRUE" : "FALSE"); metaData.insert("ssl_activate_warnings", "TRUE"); metaData.insert("cross-domain", toplevelURL().toString()); if (d->m_restored) { metaData.insert("referrer", d->m_pageReferrer); d->m_cachePolicy = KIO::CC_Cache; } else if (args.reload() && !browserArgs.softReload) { d->m_cachePolicy = KIO::CC_Reload; } else { d->m_cachePolicy = KProtocolManager::cacheControl(); } if (browserArgs.doPost() && (url.scheme().startsWith("http"))) { d->m_job = KIO::http_post(url, browserArgs.postData, KIO::HideProgressInfo); d->m_job->addMetaData("content-type", browserArgs.contentType()); } else { d->m_job = KIO::get(url, KIO::NoReload, KIO::HideProgressInfo); d->m_job->addMetaData("cache", KIO::getCacheControlString(d->m_cachePolicy)); } if (widget()) { KJobWidgets::setWindow(d->m_job, widget()->topLevelWidget()); } d->m_job->addMetaData(metaData); connect(d->m_job, SIGNAL(result(KJob*)), SLOT(slotFinished(KJob*))); connect(d->m_job, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(slotData(KIO::Job*,QByteArray))); connect(d->m_job, SIGNAL(infoMessage(KJob*,QString,QString)), SLOT(slotInfoMessage(KJob*,QString))); connect(d->m_job, SIGNAL(redirection(KIO::Job*,QUrl)), SLOT(slotRedirection(KIO::Job*,QUrl))); d->m_bComplete = false; d->m_bLoadEventEmitted = false; // delete old status bar msg's from kjs (if it _was_ activated on last URL) if (d->m_bJScriptEnabled) { d->m_statusBarText[BarOverrideText].clear(); d->m_statusBarText[BarDefaultText].clear(); } // set the javascript flags according to the current url d->m_bJScriptEnabled = KHTMLGlobal::defaultHTMLSettings()->isJavaScriptEnabled(url.host()); setDebugScript(KHTMLGlobal::defaultHTMLSettings()->isJavaScriptDebugEnabled()); d->m_bJavaEnabled = KHTMLGlobal::defaultHTMLSettings()->isJavaEnabled(url.host()); d->m_bPluginsEnabled = KHTMLGlobal::defaultHTMLSettings()->isPluginsEnabled(url.host()); connect(d->m_job, SIGNAL(speed(KJob*,ulong)), this, SLOT(slotJobSpeed(KJob*,ulong))); connect(d->m_job, SIGNAL(percent(KJob*,ulong)), this, SLOT(slotJobPercent(KJob*,ulong))); connect(d->m_job, SIGNAL(result(KJob*)), this, SLOT(slotJobDone(KJob*))); d->m_jobspeed = 0; // If this was an explicit reload and the user style sheet should be used, // do a stat to see whether the stylesheet was changed in the meanwhile. if (args.reload() && !settings()->userStyleSheet().isEmpty()) { QUrl userStyleSheetUrl(settings()->userStyleSheet()); KIO::StatJob *job = KIO::stat(userStyleSheetUrl, KIO::HideProgressInfo); connect(job, SIGNAL(result(KJob*)), this, SLOT(slotUserSheetStatDone(KJob*))); } startingJob(d->m_job); emit started(nullptr); return true; } bool KHTMLPart::closeUrl() { if (d->m_job) { KHTMLPageCache::self()->cancelEntry(d->m_cacheId); d->m_job->kill(); d->m_job = nullptr; } if (d->m_doc && d->m_doc->isHTMLDocument()) { HTMLDocumentImpl *hdoc = static_cast<HTMLDocumentImpl *>(d->m_doc); if (hdoc->body() && d->m_bLoadEventEmitted) { hdoc->body()->dispatchWindowEvent(EventImpl::UNLOAD_EVENT, false, false); if (d->m_doc) { d->m_doc->updateRendering(); } d->m_bLoadEventEmitted = false; } } d->m_bComplete = true; // to avoid emitting completed() in slotFinishedParsing() (David) d->m_bLoadEventEmitted = true; // don't want that one either d->m_cachePolicy = KProtocolManager::cacheControl(); // reset cache policy disconnect(d->m_view, SIGNAL(finishedLayout()), this, SLOT(restoreScrollPosition())); KHTMLPageCache::self()->cancelFetch(this); if (d->m_doc && d->m_doc->parsing()) { // qDebug() << " was still parsing... calling end "; slotFinishedParsing(); d->m_doc->setParsing(false); } if (!d->m_workingURL.isEmpty()) { // Aborted before starting to render // qDebug() << "Aborted before starting to render, reverting location bar to " << url(); emit d->m_extension->setLocationBarUrl(url().toDisplayString()); } d->m_workingURL = QUrl(); if (d->m_doc && d->m_doc->docLoader()) { khtml::Cache::loader()->cancelRequests(d->m_doc->docLoader()); } // tell all subframes to stop as well { ConstFrameIt it = d->m_frames.constBegin(); const ConstFrameIt end = d->m_frames.constEnd(); for (; it != end; ++it) { if ((*it)->m_run) { (*it)->m_run.data()->abort(); } if (!(*it)->m_part.isNull()) { (*it)->m_part.data()->closeUrl(); } } } // tell all objects to stop as well { ConstFrameIt it = d->m_objects.constBegin(); const ConstFrameIt end = d->m_objects.constEnd(); for (; it != end; ++it) { if (!(*it)->m_part.isNull()) { (*it)->m_part.data()->closeUrl(); } } } // Stop any started redirections as well!! (DA) if (d && d->m_redirectionTimer.isActive()) { d->m_redirectionTimer.stop(); } // null node activated. emit nodeActivated(Node()); // make sure before clear() runs, we pop out of a dialog's message loop if (d->m_view) { d->m_view->closeChildDialogs(); } return true; } DOM::HTMLDocument KHTMLPart::htmlDocument() const { if (d->m_doc && d->m_doc->isHTMLDocument()) { return static_cast<HTMLDocumentImpl *>(d->m_doc); } else { return static_cast<HTMLDocumentImpl *>(nullptr); } } DOM::Document KHTMLPart::document() const { return d->m_doc; } QString KHTMLPart::documentSource() const { QString sourceStr; if (!(url().isLocalFile()) && KHTMLPageCache::self()->isComplete(d->m_cacheId)) { QByteArray sourceArray; QDataStream dataStream(&sourceArray, QIODevice::WriteOnly); KHTMLPageCache::self()->saveData(d->m_cacheId, &dataStream); QTextStream stream(sourceArray, QIODevice::ReadOnly); stream.setCodec(QTextCodec::codecForName(encoding().toLatin1().constData())); sourceStr = stream.readAll(); } else { QTemporaryFile tmpFile; if (!tmpFile.open()) { return sourceStr; } KIO::FileCopyJob *job = KIO::file_copy(url(), QUrl::fromLocalFile(tmpFile.fileName()), KIO::Overwrite); if (job->exec()) { QTextStream stream(&tmpFile); stream.setCodec(QTextCodec::codecForName(encoding().toLatin1().constData())); sourceStr = stream.readAll(); } } return sourceStr; } KParts::BrowserExtension *KHTMLPart::browserExtension() const { return d->m_extension; } KParts::BrowserHostExtension *KHTMLPart::browserHostExtension() const { return d->m_hostExtension; } KHTMLView *KHTMLPart::view() const { return d->m_view; } KHTMLViewBar *KHTMLPart::pTopViewBar() const { if (const_cast<KHTMLPart *>(this)->parentPart()) { return const_cast<KHTMLPart *>(this)->parentPart()->pTopViewBar(); } return d->m_topViewBar; } KHTMLViewBar *KHTMLPart::pBottomViewBar() const { if (const_cast<KHTMLPart *>(this)->parentPart()) { return const_cast<KHTMLPart *>(this)->parentPart()->pBottomViewBar(); } return d->m_bottomViewBar; } void KHTMLPart::setStatusMessagesEnabled(bool enable) { d->m_statusMessagesEnabled = enable; } KJS::Interpreter *KHTMLPart::jScriptInterpreter() { KJSProxy *proxy = jScript(); if (!proxy || proxy->paused()) { return nullptr; } return proxy->interpreter(); } bool KHTMLPart::statusMessagesEnabled() const { return d->m_statusMessagesEnabled; } void KHTMLPart::setJScriptEnabled(bool enable) { if (!enable && jScriptEnabled() && d->m_frame && d->m_frame->m_jscript) { d->m_frame->m_jscript->clear(); } d->m_bJScriptForce = enable; d->m_bJScriptOverride = true; } bool KHTMLPart::jScriptEnabled() const { if (onlyLocalReferences()) { return false; } if (d->m_bJScriptOverride) { return d->m_bJScriptForce; } return d->m_bJScriptEnabled; } void KHTMLPart::setDNSPrefetch(DNSPrefetch pmode) { d->m_bDNSPrefetch = pmode; d->m_bDNSPrefetchIsDefault = false; } KHTMLPart::DNSPrefetch KHTMLPart::dnsPrefetch() const { if (onlyLocalReferences()) { return DNSPrefetchDisabled; } return d->m_bDNSPrefetch; } void KHTMLPart::setMetaRefreshEnabled(bool enable) { d->m_metaRefreshEnabled = enable; } bool KHTMLPart::metaRefreshEnabled() const { return d->m_metaRefreshEnabled; } KJSProxy *KHTMLPart::jScript() { if (!jScriptEnabled()) { return nullptr; } if (!d->m_frame) { KHTMLPart *p = parentPart(); if (!p) { d->m_frame = new khtml::ChildFrame; d->m_frame->m_part = this; } else { ConstFrameIt it = p->d->m_frames.constBegin(); const ConstFrameIt end = p->d->m_frames.constEnd(); for (; it != end; ++it) if ((*it)->m_part.data() == this) { d->m_frame = *it; break; } } if (!d->m_frame) { return nullptr; } } if (!d->m_frame->m_jscript) { d->m_frame->m_jscript = new KJSProxy(d->m_frame); } d->m_frame->m_jscript->setDebugEnabled(d->m_bJScriptDebugEnabled); return d->m_frame->m_jscript; } QVariant KHTMLPart::crossFrameExecuteScript(const QString &target, const QString &script) { KHTMLPart *destpart = this; QString trg = target.toLower(); if (target == "_top") { while (destpart->parentPart()) { destpart = destpart->parentPart(); } } else if (target == "_parent") { if (parentPart()) { destpart = parentPart(); } } else if (target == "_self" || target == "_blank") { // we always allow these } else { destpart = findFrame(target); if (!destpart) { destpart = this; } } // easy way out? if (destpart == this) { return executeScript(DOM::Node(), script); } // now compare the domains if (destpart->checkFrameAccess(this)) { return destpart->executeScript(DOM::Node(), script); } // eww, something went wrong. better execute it in our frame return executeScript(DOM::Node(), script); } //Enable this to see all JS scripts being executed //#define KJS_VERBOSE KJSErrorDlg *KHTMLPart::jsErrorExtension() { if (!d->m_settings->jsErrorsEnabled()) { return nullptr; } if (parentPart()) { return parentPart()->jsErrorExtension(); } if (!d->m_statusBarJSErrorLabel) { d->m_statusBarJSErrorLabel = new KUrlLabel(d->m_statusBarExtension->statusBar()); d->m_statusBarJSErrorLabel->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum)); d->m_statusBarJSErrorLabel->setUseCursor(false); d->m_statusBarExtension->addStatusBarItem(d->m_statusBarJSErrorLabel, 0, false); d->m_statusBarJSErrorLabel->setToolTip(i18n("This web page contains coding errors.")); d->m_statusBarJSErrorLabel->setPixmap(SmallIcon("script-error")); connect(d->m_statusBarJSErrorLabel, SIGNAL(leftClickedUrl()), SLOT(launchJSErrorDialog())); connect(d->m_statusBarJSErrorLabel, SIGNAL(rightClickedUrl()), SLOT(jsErrorDialogContextMenu())); } if (!d->m_jsedlg) { d->m_jsedlg = new KJSErrorDlg; d->m_jsedlg->setURL(url().toDisplayString()); } return d->m_jsedlg; } void KHTMLPart::removeJSErrorExtension() { if (parentPart()) { parentPart()->removeJSErrorExtension(); return; } if (d->m_statusBarJSErrorLabel != nullptr) { d->m_statusBarExtension->removeStatusBarItem(d->m_statusBarJSErrorLabel); delete d->m_statusBarJSErrorLabel; d->m_statusBarJSErrorLabel = nullptr; } delete d->m_jsedlg; d->m_jsedlg = nullptr; } void KHTMLPart::disableJSErrorExtension() { removeJSErrorExtension(); // These two lines are really kind of hacky, and it sucks to do this inside // KHTML but I don't know of anything that's reasonably easy as an alternative // right now. It makes me wonder if there should be a more clean way to // contact all running "KHTML" instance as opposed to Konqueror instances too. d->m_settings->setJSErrorsEnabled(false); emit configurationChanged(); } void KHTMLPart::jsErrorDialogContextMenu() { QMenu *m = new QMenu(nullptr); m->addAction(i18n("&Hide Errors"), this, SLOT(removeJSErrorExtension())); m->addAction(i18n("&Disable Error Reporting"), this, SLOT(disableJSErrorExtension())); m->popup(QCursor::pos()); } void KHTMLPart::launchJSErrorDialog() { KJSErrorDlg *dlg = jsErrorExtension(); if (dlg) { dlg->show(); dlg->raise(); } } void KHTMLPart::launchJSConfigDialog() { QStringList args; args << "khtml_java_js"; KToolInvocation::kdeinitExec("kcmshell5", args); } QVariant KHTMLPart::executeScript(const QString &filename, int baseLine, const DOM::Node &n, const QString &script) { #ifdef KJS_VERBOSE // The script is now printed by KJS's Parser::parse qDebug() << "executeScript: caller='" << objectName() << "' filename=" << filename << " baseLine=" << baseLine /*<< " script=" << script*/; #endif KJSProxy *proxy = jScript(); if (!proxy || proxy->paused()) { return QVariant(); } KJS::Completion comp; QVariant ret = proxy->evaluate(filename, baseLine, script, n, &comp); /* * Error handling */ if (comp.complType() == KJS::Throw && comp.value()) { KJSErrorDlg *dlg = jsErrorExtension(); if (dlg) { QString msg = KJS::exceptionToString( proxy->interpreter()->globalExec(), comp.value()); dlg->addError(i18n("<qt><b>Error</b>: %1: %2</qt>", Qt::escape(filename), Qt::escape(msg))); } } // Handle immediate redirects now (e.g. location='foo') if (!d->m_redirectURL.isEmpty() && d->m_delayRedirect == -1) { // qDebug() << "executeScript done, handling immediate redirection NOW"; // Must abort tokenizer, no further script must execute. khtml::Tokenizer *t = d->m_doc->tokenizer(); if (t) { t->abort(); } d->m_redirectionTimer.setSingleShot(true); d->m_redirectionTimer.start(0); } return ret; } QVariant KHTMLPart::executeScript(const QString &script) { return executeScript(DOM::Node(), script); } QVariant KHTMLPart::executeScript(const DOM::Node &n, const QString &script) { #ifdef KJS_VERBOSE qDebug() << "caller=" << objectName() << "node=" << n.nodeName().string().toLatin1().constData() << "(" << (n.isNull() ? 0 : n.nodeType()) << ") " /* << script */; #endif KJSProxy *proxy = jScript(); if (!proxy || proxy->paused()) { return QVariant(); } ++(d->m_runningScripts); KJS::Completion comp; const QVariant ret = proxy->evaluate(QString(), 1, script, n, &comp); --(d->m_runningScripts); /* * Error handling */ if (comp.complType() == KJS::Throw && comp.value()) { KJSErrorDlg *dlg = jsErrorExtension(); if (dlg) { QString msg = KJS::exceptionToString( proxy->interpreter()->globalExec(), comp.value()); dlg->addError(i18n("<qt><b>Error</b>: node %1: %2</qt>", n.nodeName().string(), Qt::escape(msg))); } } if (!d->m_runningScripts && d->m_doc && !d->m_doc->parsing() && d->m_submitForm) { submitFormAgain(); } #ifdef KJS_VERBOSE qDebug() << "done"; #endif return ret; } void KHTMLPart::setJavaEnabled(bool enable) { d->m_bJavaForce = enable; d->m_bJavaOverride = true; } bool KHTMLPart::javaEnabled() const { if (onlyLocalReferences()) { return false; } if (d->m_bJavaOverride) { return d->m_bJavaForce; } return d->m_bJavaEnabled; } void KHTMLPart::setPluginsEnabled(bool enable) { d->m_bPluginsForce = enable; d->m_bPluginsOverride = true; } bool KHTMLPart::pluginsEnabled() const { if (onlyLocalReferences()) { return false; } if (d->m_bPluginsOverride) { return d->m_bPluginsForce; } return d->m_bPluginsEnabled; } static int s_DOMTreeIndentLevel = 0; void KHTMLPart::slotDebugDOMTree() { if (d->m_doc) { qDebug("%s", d->m_doc->toString().string().toLatin1().constData()); } // Now print the contents of the frames that contain HTML const int indentLevel = s_DOMTreeIndentLevel++; ConstFrameIt it = d->m_frames.constBegin(); const ConstFrameIt end = d->m_frames.constEnd(); for (; it != end; ++it) if (!(*it)->m_part.isNull() && (*it)->m_part.data()->inherits("KHTMLPart")) { KParts::ReadOnlyPart *const p = (*it)->m_part.data(); // qDebug() << QString().leftJustified(s_DOMTreeIndentLevel*4,' ') << "FRAME " << p->objectName() << " "; static_cast<KHTMLPart *>(p)->slotDebugDOMTree(); } s_DOMTreeIndentLevel = indentLevel; } void KHTMLPart::slotDebugScript() { if (jScript()) { jScript()->showDebugWindow(); } } void KHTMLPart::slotDebugRenderTree() { #ifndef NDEBUG if (d->m_doc) { d->m_doc->renderer()->printTree(); // dump out the contents of the rendering & DOM trees // QString dumps; // QTextStream outputStream(&dumps,QIODevice::WriteOnly); // d->m_doc->renderer()->layer()->dump( outputStream ); // qDebug() << "dump output:" << "\n" + dumps; // d->m_doc->renderer()->printLineBoxTree(); } #endif } void KHTMLPart::slotDebugFrameTree() { khtml::ChildFrame::dumpFrameTree(this); } void KHTMLPart::slotStopAnimations() { stopAnimations(); } void KHTMLPart::setAutoloadImages(bool enable) { if (d->m_doc && d->m_doc->docLoader()->autoloadImages() == enable) { return; } if (d->m_doc) { d->m_doc->docLoader()->setAutoloadImages(enable); } unplugActionList("loadImages"); if (enable) { delete d->m_paLoadImages; d->m_paLoadImages = nullptr; } else if (!d->m_paLoadImages) { d->m_paLoadImages = new QAction(i18n("Display Images on Page"), this); actionCollection()->addAction("loadImages", d->m_paLoadImages); d->m_paLoadImages->setIcon(QIcon::fromTheme("image-loading")); connect(d->m_paLoadImages, SIGNAL(triggered(bool)), this, SLOT(slotLoadImages())); } if (d->m_paLoadImages) { QList<QAction *> lst; lst.append(d->m_paLoadImages); plugActionList("loadImages", lst); } } bool KHTMLPart::autoloadImages() const { if (d->m_doc) { return d->m_doc->docLoader()->autoloadImages(); } return true; } void KHTMLPart::clear() { if (d->m_bCleared) { return; } d->m_bCleared = true; d->m_bClearing = true; { ConstFrameIt it = d->m_frames.constBegin(); const ConstFrameIt end = d->m_frames.constEnd(); for (; it != end; ++it) { // Stop HTMLRun jobs for frames if ((*it)->m_run) { (*it)->m_run.data()->abort(); } } } { ConstFrameIt it = d->m_objects.constBegin(); const ConstFrameIt end = d->m_objects.constEnd(); for (; it != end; ++it) { // Stop HTMLRun jobs for objects if ((*it)->m_run) { (*it)->m_run.data()->abort(); } } } findTextBegin(); // resets d->m_findNode and d->m_findPos d->m_mousePressNode = DOM::Node(); if (d->m_doc) { if (d->m_doc->attached()) { //the view may have detached it already d->m_doc->detach(); } } // Moving past doc so that onUnload works. if (d->m_frame && d->m_frame->m_jscript) { d->m_frame->m_jscript->clear(); } // stopping marquees if (d->m_doc && d->m_doc->renderer() && d->m_doc->renderer()->layer()) { d->m_doc->renderer()->layer()->suspendMarquees(); } if (d->m_view) { d->m_view->clear(); } // do not dereference the document before the jscript and view are cleared, as some destructors // might still try to access the document. if (d->m_doc) { d->m_doc->deref(); } d->m_doc = nullptr; delete d->m_decoder; d->m_decoder = nullptr; // We don't want to change between parts if we are going to delete all of them anyway if (partManager()) { disconnect(partManager(), SIGNAL(activePartChanged(KParts::Part*)), this, SLOT(slotActiveFrameChanged(KParts::Part*))); } if (d->m_frames.count()) { const KHTMLFrameList frames = d->m_frames; d->m_frames.clear(); ConstFrameIt it = frames.begin(); const ConstFrameIt end = frames.end(); for (; it != end; ++it) { if ((*it)->m_part) { partManager()->removePart((*it)->m_part.data()); delete(*it)->m_part.data(); } delete *it; } } d->m_suppressedPopupOriginParts.clear(); if (d->m_objects.count()) { KHTMLFrameList objects = d->m_objects; d->m_objects.clear(); ConstFrameIt oi = objects.constBegin(); const ConstFrameIt oiEnd = objects.constEnd(); for (; oi != oiEnd; ++oi) { delete(*oi)->m_part.data(); delete *oi; } } // Listen to part changes again if (partManager()) { connect(partManager(), SIGNAL(activePartChanged(KParts::Part*)), this, SLOT(slotActiveFrameChanged(KParts::Part*))); } d->clearRedirection(); d->m_redirectLockHistory = true; d->m_bClearing = false; d->m_frameNameId = 1; d->m_bFirstData = true; d->m_bMousePressed = false; if (d->editor_context.m_caretBlinkTimer >= 0) { killTimer(d->editor_context.m_caretBlinkTimer); } d->editor_context.reset(); #ifndef QT_NO_CLIPBOARD connect(qApp->clipboard(), SIGNAL(selectionChanged()), SLOT(slotClearSelection())); #endif d->m_jobPercent = 0; if (!d->m_haveEncoding) { d->m_encoding.clear(); } d->m_DNSPrefetchQueue.clear(); if (d->m_DNSPrefetchTimer > 0) { killTimer(d->m_DNSPrefetchTimer); } d->m_DNSPrefetchTimer = -1; d->m_lookedupHosts.clear(); if (d->m_DNSTTLTimer > 0) { killTimer(d->m_DNSTTLTimer); } d->m_DNSTTLTimer = -1; d->m_numDNSPrefetchedNames = 0; #ifdef SPEED_DEBUG d->m_parsetime.restart(); #endif } bool KHTMLPart::openFile() { return true; } DOM::HTMLDocumentImpl *KHTMLPart::docImpl() const { if (d && d->m_doc && d->m_doc->isHTMLDocument()) { return static_cast<HTMLDocumentImpl *>(d->m_doc); } return nullptr; } DOM::DocumentImpl *KHTMLPart::xmlDocImpl() const { if (d) { return d->m_doc; } return nullptr; } void KHTMLPart::slotInfoMessage(KJob *kio_job, const QString &msg) { assert(d->m_job == kio_job); Q_ASSERT(kio_job); Q_UNUSED(kio_job); if (!parentPart()) { setStatusBarText(msg, BarDefaultText); } } void KHTMLPart::setPageSecurity(PageSecurity sec) { emit d->m_extension->setPageSecurity(sec); } void KHTMLPart::slotData(KIO::Job *kio_job, const QByteArray &data) { assert(d->m_job == kio_job); Q_ASSERT(kio_job); Q_UNUSED(kio_job); //qDebug() << "slotData: " << data.size(); // The first data ? if (!d->m_workingURL.isEmpty()) { //qDebug() << "begin!"; // We must suspend KIO while we're inside begin() because it can cause // crashes if a window (such as kjsdebugger) goes back into the event loop, // more data arrives, and begin() gets called again (re-entered). d->m_job->suspend(); begin(d->m_workingURL, arguments().xOffset(), arguments().yOffset()); d->m_job->resume(); // CC_Refresh means : always send the server an If-Modified-Since conditional request. // This is the default cache setting and correspond to the KCM's "Keep cache in sync". // CC_Verify means : only send a conditional request if the cache expiry date is passed. // It doesn't have a KCM setter. // We override the first to the second, except when doing a soft-reload. if (d->m_cachePolicy == KIO::CC_Refresh && !d->m_extension->browserArguments().softReload) { d->m_doc->docLoader()->setCachePolicy(KIO::CC_Verify); } else { d->m_doc->docLoader()->setCachePolicy(d->m_cachePolicy); } d->m_workingURL = QUrl(); d->m_cacheId = KHTMLPageCache::self()->createCacheEntry(); // When the first data arrives, the metadata has just been made available d->m_httpHeaders = d->m_job->queryMetaData("HTTP-Headers"); QDateTime cacheCreationDate = QDateTime::fromTime_t(d->m_job->queryMetaData("cache-creation-date").toLong()); d->m_doc->docLoader()->setCacheCreationDate(cacheCreationDate); d->m_pageServices = d->m_job->queryMetaData("PageServices"); d->m_pageReferrer = d->m_job->queryMetaData("referrer"); d->m_ssl_in_use = (d->m_job->queryMetaData("ssl_in_use") == "TRUE"); { KHTMLPart *p = parentPart(); if (p && p->d->m_ssl_in_use != d->m_ssl_in_use) { while (p->parentPart()) { p = p->parentPart(); } p->setPageSecurity(NotCrypted); } } setPageSecurity(d->m_ssl_in_use ? Encrypted : NotCrypted); // Shouldn't all of this be done only if ssl_in_use == true ? (DF) d->m_ssl_parent_ip = d->m_job->queryMetaData("ssl_parent_ip"); d->m_ssl_parent_cert = d->m_job->queryMetaData("ssl_parent_cert"); d->m_ssl_peer_chain = d->m_job->queryMetaData("ssl_peer_chain"); d->m_ssl_peer_ip = d->m_job->queryMetaData("ssl_peer_ip"); d->m_ssl_cipher = d->m_job->queryMetaData("ssl_cipher"); d->m_ssl_protocol_version = d->m_job->queryMetaData("ssl_protocol_version"); d->m_ssl_cipher_used_bits = d->m_job->queryMetaData("ssl_cipher_used_bits"); d->m_ssl_cipher_bits = d->m_job->queryMetaData("ssl_cipher_bits"); d->m_ssl_cert_errors = d->m_job->queryMetaData("ssl_cert_errors"); // Check for charset meta-data QString qData = d->m_job->queryMetaData("charset"); if (!qData.isEmpty() && !d->m_haveEncoding) { // only use information if the user didn't override the settings d->m_encoding = qData; } // Support for http-refresh qData = d->m_job->queryMetaData("http-refresh"); if (!qData.isEmpty()) { d->m_doc->processHttpEquiv("refresh", qData); } // DISABLED: Support Content-Location per section 14.14 of RFC 2616. // See BR# 51185,BR# 82747 /* QString baseURL = d->m_job->queryMetaData ("content-location"); if (!baseURL.isEmpty()) d->m_doc->setBaseURL(QUrl( d->m_doc->completeURL(baseURL) )); */ // Support for Content-Language QString language = d->m_job->queryMetaData("content-language"); if (!language.isEmpty()) { d->m_doc->setContentLanguage(language); } if (!url().isLocalFile()) { // Support for http last-modified d->m_lastModified = d->m_job->queryMetaData("modified"); } else { d->m_lastModified.clear(); // done on-demand by lastModified() } } KHTMLPageCache::self()->addData(d->m_cacheId, data); write(data.data(), data.size()); } void KHTMLPart::slotRestoreData(const QByteArray &data) { // The first data ? if (!d->m_workingURL.isEmpty()) { long saveCacheId = d->m_cacheId; QString savePageReferrer = d->m_pageReferrer; QString saveEncoding = d->m_encoding; begin(d->m_workingURL, arguments().xOffset(), arguments().yOffset()); d->m_encoding = saveEncoding; d->m_pageReferrer = savePageReferrer; d->m_cacheId = saveCacheId; d->m_workingURL = QUrl(); } //qDebug() << data.size(); write(data.data(), data.size()); if (data.size() == 0) { //qDebug() << "<<end of data>>"; // End of data. if (d->m_doc && d->m_doc->parsing()) { end(); //will emit completed() } } } void KHTMLPart::showError(KJob *job) { // qDebug() << "d->m_bParsing=" << (d->m_doc && d->m_doc->parsing()) << " d->m_bComplete=" << d->m_bComplete // << " d->m_bCleared=" << d->m_bCleared; if (job->error() == KIO::ERR_NO_CONTENT) { return; } if ((d->m_doc && d->m_doc->parsing()) || d->m_workingURL.isEmpty()) { // if we got any data already job->uiDelegate()->showErrorMessage(); } else { htmlError(job->error(), job->errorText(), d->m_workingURL); } } // This is a protected method, placed here because of it's relevance to showError void KHTMLPart::htmlError(int errorCode, const QString &text, const QUrl &reqUrl) { // qDebug() << "errorCode" << errorCode << "text" << text; // make sure we're not executing any embedded JS bool bJSFO = d->m_bJScriptForce; bool bJSOO = d->m_bJScriptOverride; d->m_bJScriptForce = false; d->m_bJScriptOverride = true; begin(); QString errorName, techName, description; QStringList causes, solutions; QByteArray raw = KIO::rawErrorDetail(errorCode, text, &reqUrl); QDataStream stream(raw); stream >> errorName >> techName >> description >> causes >> solutions; QString url, protocol, datetime; // This is somewhat confusing, but we have to escape the externally- // controlled URL twice: once for i18n, and once for HTML. url = Qt::escape(Qt::escape(reqUrl.toDisplayString())); protocol = reqUrl.scheme(); datetime = QDateTime::currentDateTime().toString(Qt::DefaultLocaleLongDate); QString filename(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kf5/khtml/error.html")); QFile file(filename); bool isOpened = file.open(QIODevice::ReadOnly); if (!isOpened) { qWarning() << "Could not open error html template:" << filename; } QString html = QString(QLatin1String(file.readAll())); html.replace(QLatin1String("TITLE"), i18n("Error: %1 - %2", errorName, url)); html.replace(QLatin1String("DIRECTION"), QApplication::isRightToLeft() ? "rtl" : "ltr"); html.replace(QLatin1String("ICON_PATH"), QUrl::fromLocalFile(KIconLoader::global()->iconPath("dialog-warning", -KIconLoader::SizeHuge)).url()); QString doc = QLatin1String("<h1>"); doc += i18n("The requested operation could not be completed"); doc += QLatin1String("</h1><h2>"); doc += errorName; doc += QLatin1String("</h2>"); if (!techName.isNull()) { doc += QLatin1String("<h2>"); doc += i18n("Technical Reason: "); doc += techName; doc += QLatin1String("</h2>"); } doc += QLatin1String("<br clear=\"all\">"); doc += QLatin1String("<h3>"); doc += i18n("Details of the Request:"); doc += QLatin1String("</h3><ul><li>"); doc += i18n("URL: %1", url); doc += QLatin1String("</li><li>"); if (!protocol.isNull()) { doc += i18n("Protocol: %1", protocol); doc += QLatin1String("</li><li>"); } doc += i18n("Date and Time: %1", datetime); doc += QLatin1String("</li><li>"); doc += i18n("Additional Information: %1", text); doc += QLatin1String("</li></ul><h3>"); doc += i18n("Description:"); doc += QLatin1String("</h3><p>"); doc += description; doc += QLatin1String("</p>"); if (causes.count()) { doc += QLatin1String("<h3>"); doc += i18n("Possible Causes:"); doc += QLatin1String("</h3><ul><li>"); doc += causes.join("</li><li>"); doc += QLatin1String("</li></ul>"); } if (solutions.count()) { doc += QLatin1String("<h3>"); doc += i18n("Possible Solutions:"); doc += QLatin1String("</h3><ul><li>"); doc += solutions.join("</li><li>"); doc += QLatin1String("</li></ul>"); } html.replace(QLatin1String("TEXT"), doc); write(html); end(); d->m_bJScriptForce = bJSFO; d->m_bJScriptOverride = bJSOO; // make the working url the current url, so that reload works and // emit the progress signals to advance one step in the history // (so that 'back' works) setUrl(reqUrl); // same as d->m_workingURL d->m_workingURL = QUrl(); emit started(nullptr); emit completed(); } void KHTMLPart::slotFinished(KJob *job) { d->m_job = nullptr; d->m_jobspeed = 0L; if (job->error()) { KHTMLPageCache::self()->cancelEntry(d->m_cacheId); // The following catches errors that occur as a result of HTTP // to FTP redirections where the FTP URL is a directory. Since // KIO cannot change a redirection request from GET to LISTDIR, // we have to take care of it here once we know for sure it is // a directory... if (job->error() == KIO::ERR_IS_DIRECTORY) { emit canceled(job->errorString()); emit d->m_extension->openUrlRequest(d->m_workingURL); } else { emit canceled(job->errorString()); // TODO: what else ? checkCompleted(); showError(job); } return; } KIO::TransferJob *tjob = ::qobject_cast<KIO::TransferJob *>(job); if (tjob && tjob->isErrorPage()) { HTMLPartContainerElementImpl *elt = d->m_frame ? d->m_frame->m_partContainerElement.data() : nullptr; if (!elt) { return; } elt->partLoadingErrorNotify(); checkCompleted(); if (d->m_bComplete) { return; } } //qDebug() << "slotFinished"; KHTMLPageCache::self()->endData(d->m_cacheId); if (d->m_doc && d->m_doc->docLoader()->expireDate().isValid() && url().scheme().startsWith("http")) { KIO::http_update_cache(url(), false, d->m_doc->docLoader()->expireDate()); } d->m_workingURL = QUrl(); if (d->m_doc && d->m_doc->parsing()) { end(); //will emit completed() } } MimeType KHTMLPartPrivate::classifyMimeType(const QString &mimeStr) { // See HTML5's "5.5.1 Navigating across documents" section. if (mimeStr == "application/xhtml+xml") { return MimeXHTML; } if (mimeStr == "image/svg+xml") { return MimeSVG; } if (mimeStr == "text/html" || mimeStr.isEmpty()) { return MimeHTML; } QMimeDatabase db; QMimeType mime = db.mimeTypeForName(mimeStr); if (mime.inherits("text/xml") || mimeStr.endsWith("+xml")) { return MimeXML; } if (mime.inherits("text/plain")) { return MimeText; } if (khtmlImLoad::ImageManager::loaderDatabase()->supportedMimeTypes().contains(mimeStr)) { return MimeImage; } // Sometimes our subclasses like to handle custom mimetypes. In that case, // we want to handle them as HTML. We do that in the following cases: // 1) We're at top-level, so we were forced to open something // 2) We're an object --- this again means we were forced to open something, // as an iframe-generating-an-embed case would have us as an iframe if (!q->parentPart() || (m_frame && m_frame->m_type == khtml::ChildFrame::Object)) { return MimeHTML; } return MimeOther; } void KHTMLPart::begin(const QUrl &url, int xOffset, int yOffset) { if (d->m_view->underMouse()) { QToolTip::hideText(); // in case a previous tooltip is still shown } // No need to show this for a new page until an error is triggered if (!parentPart()) { removeJSErrorExtension(); setSuppressedPopupIndicator(false); d->m_openableSuppressedPopups = 0; foreach (KHTMLPart *part, d->m_suppressedPopupOriginParts) { if (part) { KJS::Window *w = KJS::Window::retrieveWindow(part); if (w) { w->forgetSuppressedWindows(); } } } } d->m_bCleared = false; d->m_cacheId = 0; d->m_bComplete = false; d->m_bLoadEventEmitted = false; clear(); d->m_bCleared = false; if (url.isValid()) { QString urlString = url.toString(); KHTMLGlobal::vLinks()->insert(urlString); QString urlString2 = url.toDisplayString(); if (urlString != urlString2) { KHTMLGlobal::vLinks()->insert(urlString2); } } // ### //stopParser(); KParts::OpenUrlArguments args = arguments(); args.setXOffset(xOffset); args.setYOffset(yOffset); setArguments(args); d->m_pageReferrer.clear(); d->m_referrer = url.scheme().startsWith("http") ? url.toString() : ""; setUrl(url); // Note: by now, any special mimetype besides plaintext would have been // handled specially inside openURL, so we handle their cases the same // as HTML. MimeType type = d->classifyMimeType(args.mimeType()); switch (type) { case MimeSVG: d->m_doc = DOMImplementationImpl::createSVGDocument(d->m_view); break; case MimeXML: // any XML derivative, except XHTML or SVG // ### not sure if XHTML documents served as text/xml should use DocumentImpl or HTMLDocumentImpl d->m_doc = DOMImplementationImpl::createXMLDocument(d->m_view); break; case MimeText: d->m_doc = new HTMLTextDocumentImpl(d->m_view); break; case MimeXHTML: case MimeHTML: default: d->m_doc = DOMImplementationImpl::createHTMLDocument(d->m_view); // HTML or XHTML? (#86446) static_cast<HTMLDocumentImpl *>(d->m_doc)->setHTMLRequested(type != MimeXHTML); } d->m_doc->ref(); d->m_doc->setURL(url.toString()); d->m_doc->open(); if (!d->m_doc->attached()) { d->m_doc->attach(); } d->m_doc->setBaseURL(QUrl()); d->m_doc->docLoader()->setShowAnimations(KHTMLGlobal::defaultHTMLSettings()->showAnimations()); emit docCreated(); d->m_paUseStylesheet->setItems(QStringList()); d->m_paUseStylesheet->setEnabled(false); setAutoloadImages(KHTMLGlobal::defaultHTMLSettings()->autoLoadImages()); QString userStyleSheet = KHTMLGlobal::defaultHTMLSettings()->userStyleSheet(); if (!userStyleSheet.isEmpty()) { setUserStyleSheet(QUrl(userStyleSheet)); } d->m_doc->setRestoreState(d->m_extension->browserArguments().docState); connect(d->m_doc, SIGNAL(finishedParsing()), this, SLOT(slotFinishedParsing())); emit d->m_extension->enableAction("print", true); d->m_doc->setParsing(true); } void KHTMLPart::write(const char *data, int len) { if (!d->m_decoder) { d->m_decoder = createDecoder(); } if (len == -1) { len = strlen(data); } if (len == 0) { return; } QString decoded = d->m_decoder->decodeWithBuffering(data, len); if (decoded.isEmpty()) { return; } if (d->m_bFirstData) { onFirstData(); } khtml::Tokenizer *t = d->m_doc->tokenizer(); if (t) { t->write(decoded, true); } } // ### KDE5: remove void KHTMLPart::setAlwaysHonourDoctype(bool b) { d->m_bStrictModeQuirk = !b; } void KHTMLPart::write(const QString &str) { if (str.isNull()) { return; } if (d->m_bFirstData) { // determine the parse mode if (d->m_bStrictModeQuirk) { d->m_doc->setParseMode(DocumentImpl::Strict); d->m_bFirstData = false; } else { onFirstData(); } } khtml::Tokenizer *t = d->m_doc->tokenizer(); if (t) { t->write(str, true); } } void KHTMLPart::end() { if (d->m_doc) { if (d->m_decoder) { QString decoded = d->m_decoder->flush(); if (d->m_bFirstData) { onFirstData(); } if (!decoded.isEmpty()) { write(decoded); } } d->m_doc->finishParsing(); } } void KHTMLPart::onFirstData() { assert(d->m_bFirstData); // determine the parse mode d->m_doc->determineParseMode(); d->m_bFirstData = false; // ### this is still quite hacky, but should work a lot better than the old solution // Note: decoder may be null if only write(QString) is used. if (d->m_decoder && d->m_decoder->visuallyOrdered()) { d->m_doc->setVisuallyOrdered(); } // ensure part and view shares zoom-level before styling updateZoomFactor(); d->m_doc->recalcStyle(NodeImpl::Force); } bool KHTMLPart::doOpenStream(const QString &mimeType) { QMimeDatabase db; QMimeType mime = db.mimeTypeForName(mimeType); if (mime.inherits("text/html") || mime.inherits("text/xml")) { begin(url()); return true; } return false; } bool KHTMLPart::doWriteStream(const QByteArray &data) { write(data.data(), data.size()); return true; } bool KHTMLPart::doCloseStream() { end(); return true; } void KHTMLPart::paint(QPainter *p, const QRect &rc, int yOff, bool *more) { if (!d->m_view) { return; } d->m_view->paint(p, rc, yOff, more); } void KHTMLPart::stopAnimations() { if (d->m_doc) { d->m_doc->docLoader()->setShowAnimations(KHTMLSettings::KAnimationDisabled); } ConstFrameIt it = d->m_frames.constBegin(); const ConstFrameIt end = d->m_frames.constEnd(); for (; it != end; ++it) { if (KHTMLPart *p = qobject_cast<KHTMLPart *>((*it)->m_part.data())) { p->stopAnimations(); } } } void KHTMLPart::resetFromScript() { closeUrl(); d->m_bComplete = false; d->m_bLoadEventEmitted = false; disconnect(d->m_doc, SIGNAL(finishedParsing()), this, SLOT(slotFinishedParsing())); connect(d->m_doc, SIGNAL(finishedParsing()), this, SLOT(slotFinishedParsing())); d->m_doc->setParsing(true); emit started(nullptr); } void KHTMLPart::slotFinishedParsing() { d->m_doc->setParsing(false); d->m_doc->dispatchHTMLEvent(EventImpl::KHTML_CONTENTLOADED_EVENT, true, false); checkEmitLoadEvent(); disconnect(d->m_doc, SIGNAL(finishedParsing()), this, SLOT(slotFinishedParsing())); if (!d->m_view) { return; // We are probably being destructed. } checkCompleted(); } void KHTMLPart::slotLoaderRequestStarted(khtml::DocLoader *dl, khtml::CachedObject *obj) { if (obj && obj->type() == khtml::CachedObject::Image && d->m_doc && d->m_doc->docLoader() == dl) { KHTMLPart *p = this; while (p) { KHTMLPart *const op = p; ++(p->d->m_totalObjectCount); p = p->parentPart(); if (!p && op->d->m_loadedObjects <= op->d->m_totalObjectCount && !op->d->m_progressUpdateTimer.isActive()) { op->d->m_progressUpdateTimer.setSingleShot(true); op->d->m_progressUpdateTimer.start(200); } } } } static bool isAncestorOrSamePart(KHTMLPart *p1, KHTMLPart *p2) { KHTMLPart *p = p2; do { if (p == p1) { return true; } } while ((p = p->parentPart())); return false; } void KHTMLPart::slotLoaderRequestDone(khtml::DocLoader *dl, khtml::CachedObject *obj) { if (obj && obj->type() == khtml::CachedObject::Image && d->m_doc && d->m_doc->docLoader() == dl) { KHTMLPart *p = this; while (p) { KHTMLPart *const op = p; ++(p->d->m_loadedObjects); p = p->parentPart(); if (!p && op->d->m_loadedObjects <= op->d->m_totalObjectCount && op->d->m_jobPercent <= 100 && !op->d->m_progressUpdateTimer.isActive()) { op->d->m_progressUpdateTimer.setSingleShot(true); op->d->m_progressUpdateTimer.start(200); } } } /// if we have no document, or the object is not a request of one of our children, // then our loading state can't possibly be affected : don't waste time checking for completion. if (!d->m_doc || !dl->doc()->part() || !isAncestorOrSamePart(this, dl->doc()->part())) { return; } checkCompleted(); } void KHTMLPart::slotProgressUpdate() { int percent; if (d->m_loadedObjects < d->m_totalObjectCount) { percent = d->m_jobPercent / 4 + (d->m_loadedObjects * 300) / (4 * d->m_totalObjectCount); } else { percent = d->m_jobPercent; } if (d->m_bComplete) { percent = 100; } if (d->m_statusMessagesEnabled) { if (d->m_bComplete) { emit d->m_extension->infoMessage(i18n("Page loaded.")); } else if (d->m_loadedObjects < d->m_totalObjectCount && percent >= 75) { emit d->m_extension->infoMessage(i18np("%1 Image of %2 loaded.", "%1 Images of %2 loaded.", d->m_loadedObjects, d->m_totalObjectCount)); } } emit d->m_extension->loadingProgress(percent); } void KHTMLPart::slotJobSpeed(KJob * /*job*/, unsigned long speed) { d->m_jobspeed = speed; if (!parentPart()) { setStatusBarText(jsStatusBarText(), BarOverrideText); } } void KHTMLPart::slotJobPercent(KJob * /*job*/, unsigned long percent) { d->m_jobPercent = percent; if (!parentPart()) { d->m_progressUpdateTimer.setSingleShot(true); d->m_progressUpdateTimer.start(0); } } void KHTMLPart::slotJobDone(KJob * /*job*/) { d->m_jobPercent = 100; if (!parentPart()) { d->m_progressUpdateTimer.setSingleShot(true); d->m_progressUpdateTimer.start(0); } } void KHTMLPart::slotUserSheetStatDone(KJob *_job) { using namespace KIO; if (_job->error()) { showError(_job); return; } const UDSEntry entry = dynamic_cast<KIO::StatJob *>(_job)->statResult(); const QDateTime lastModified = QDateTime::fromTime_t(entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1)); // If the filesystem supports modification times, only reload the // user-defined stylesheet if necessary - otherwise always reload. if (lastModified.isValid()) { if (d->m_userStyleSheetLastModified >= lastModified) { return; } d->m_userStyleSheetLastModified = lastModified; } setUserStyleSheet(QUrl(settings()->userStyleSheet())); } bool KHTMLPartPrivate::isFullyLoaded(bool *pendingRedirections) const { *pendingRedirections = false; // Any frame that hasn't completed yet ? ConstFrameIt it = m_frames.constBegin(); const ConstFrameIt end = m_frames.constEnd(); for (; it != end; ++it) { if (!(*it)->m_bCompleted || (*it)->m_run) { //qDebug() << this << " is waiting for " << (*it)->m_part; return false; } // Check for frames with pending redirections if ((*it)->m_bPendingRedirection) { *pendingRedirections = true; } } // Any object that hasn't completed yet ? { ConstFrameIt oi = m_objects.constBegin(); const ConstFrameIt oiEnd = m_objects.constEnd(); for (; oi != oiEnd; ++oi) if (!(*oi)->m_bCompleted) { return false; } } // Are we still parsing if (m_doc && m_doc->parsing()) { return false; } // Still waiting for images/scripts from the loader ? int requests = 0; if (m_doc && m_doc->docLoader()) { requests = khtml::Cache::loader()->numRequests(m_doc->docLoader()); } if (requests > 0) { //qDebug() << "still waiting for images/scripts from the loader - requests:" << requests; return false; } return true; } void KHTMLPart::checkCompleted() { // qDebug() << this; // qDebug() << " parsing: " << (d->m_doc && d->m_doc->parsing()); // qDebug() << " complete: " << d->m_bComplete; // restore the cursor position if (d->m_doc && !d->m_doc->parsing() && !d->m_focusNodeRestored) { if (d->m_focusNodeNumber >= 0) { d->m_doc->setFocusNode(d->m_doc->nodeWithAbsIndex(d->m_focusNodeNumber)); } d->m_focusNodeRestored = true; } bool fullyLoaded, pendingChildRedirections; fullyLoaded = d->isFullyLoaded(&pendingChildRedirections); // Are we still loading, or already have done the relevant work? if (!fullyLoaded || d->m_bComplete) { return; } // OK, completed. // Now do what should be done when we are really completed. d->m_bComplete = true; d->m_cachePolicy = KProtocolManager::cacheControl(); // reset cache policy d->m_totalObjectCount = 0; d->m_loadedObjects = 0; KHTMLPart *p = this; while (p) { KHTMLPart *op = p; p = p->parentPart(); if (!p && !op->d->m_progressUpdateTimer.isActive()) { op->d->m_progressUpdateTimer.setSingleShot(true); op->d->m_progressUpdateTimer.start(0); } } checkEmitLoadEvent(); // if we didn't do it before bool pendingAction = false; if (!d->m_redirectURL.isEmpty()) { // DA: Do not start redirection for frames here! That action is // deferred until the parent emits a completed signal. if (parentPart() == nullptr) { //qDebug() << this << " starting redirection timer"; d->m_redirectionTimer.setSingleShot(true); d->m_redirectionTimer.start(qMax(0, 1000 * d->m_delayRedirect)); } else { //qDebug() << this << " not toplevel -> not starting redirection timer. Waiting for slotParentCompleted."; } pendingAction = true; } else if (pendingChildRedirections) { pendingAction = true; } // the view will emit completed on our behalf, // either now or at next repaint if one is pending //qDebug() << this << " asks the view to emit completed. pendingAction=" << pendingAction; d->m_view->complete(pendingAction); // find the alternate stylesheets QStringList sheets; if (d->m_doc) { sheets = d->m_doc->availableStyleSheets(); } sheets.prepend(i18n("Automatic Detection")); d->m_paUseStylesheet->setItems(sheets); d->m_paUseStylesheet->setEnabled(sheets.count() > 2); if (sheets.count() > 2) { d->m_paUseStylesheet->setCurrentItem(qMax(sheets.indexOf(d->m_sheetUsed), 0)); slotUseStylesheet(); } setJSDefaultStatusBarText(QString()); #ifdef SPEED_DEBUG if (!parentPart()) { qDebug() << "DONE:" << d->m_parsetime.elapsed(); } #endif } void KHTMLPart::checkEmitLoadEvent() { bool fullyLoaded, pendingChildRedirections; fullyLoaded = d->isFullyLoaded(&pendingChildRedirections); // ### might want to wait on pendingChildRedirections here, too if (d->m_bLoadEventEmitted || !d->m_doc || !fullyLoaded) { return; } d->m_bLoadEventEmitted = true; if (d->m_doc) { d->m_doc->close(); } } const KHTMLSettings *KHTMLPart::settings() const { return d->m_settings; } #ifndef KDE_NO_COMPAT // KDE5: remove this ifndef, keep the method (renamed to baseUrl) QUrl KHTMLPart::baseURL() const { if (!d->m_doc) { return QUrl(); } return d->m_doc->baseURL(); } #endif QUrl KHTMLPart::completeURL(const QString &url) { if (!d->m_doc) { return QUrl(url); } #if 0 if (d->m_decoder) { return QUrl(d->m_doc->completeURL(url), d->m_decoder->codec()->mibEnum()); } #endif return QUrl(d->m_doc->completeURL(url)); } QString KHTMLPartPrivate::codeForJavaScriptURL(const QString &u) { return QUrl::fromPercentEncoding(u.right(u.length() - 11).toUtf8()); } void KHTMLPartPrivate::executeJavascriptURL(const QString &u) { QString script = codeForJavaScriptURL(u); // qDebug() << "script=" << script; QVariant res = q->executeScript(DOM::Node(), script); if (res.type() == QVariant::String) { q->begin(q->url()); q->setAlwaysHonourDoctype(); // Disable public API compat; it messes with doctype q->write(res.toString()); q->end(); } emit q->completed(); } bool KHTMLPartPrivate::isJavaScriptURL(const QString &url) { return url.indexOf(QLatin1String("javascript:"), 0, Qt::CaseInsensitive) == 0; } // Called by ecma/kjs_window in case of redirections from Javascript, // and by xml/dom_docimpl.cpp in case of http-equiv meta refresh. void KHTMLPart::scheduleRedirection(int delay, const QString &url, bool doLockHistory) { // qDebug() << "delay=" << delay << " url=" << url << " from=" << this->url() << "parent=" << parentPart(); // qDebug() << "current redirectURL=" << d->m_redirectURL << " with delay " << d->m_delayRedirect; // In case of JS redirections, some, such as jump to anchors, and javascript: // evaluation should actually be handled immediately, and not waiting until // the end of the script. (Besides, we don't want to abort the tokenizer for those) if (delay == -1 && d->isInPageURL(url)) { d->executeInPageURL(url, doLockHistory); return; } if (delay < 24 * 60 * 60 && (d->m_redirectURL.isEmpty() || delay <= d->m_delayRedirect)) { d->m_delayRedirect = delay; d->m_redirectURL = url; d->m_redirectLockHistory = doLockHistory; // qDebug() << " d->m_bComplete=" << d->m_bComplete; if (d->m_bComplete) { d->m_redirectionTimer.stop(); d->m_redirectionTimer.setSingleShot(true); d->m_redirectionTimer.start(qMax(0, 1000 * d->m_delayRedirect)); } } } void KHTMLPartPrivate::clearRedirection() { m_delayRedirect = 0; m_redirectURL.clear(); m_redirectionTimer.stop(); } void KHTMLPart::slotRedirect() { // qDebug() << this; QString u = d->m_redirectURL; QUrl url(u); d->clearRedirection(); if (d->isInPageURL(u)) { d->executeInPageURL(u, d->m_redirectLockHistory); return; } KParts::OpenUrlArguments args; QUrl cUrl(this->url()); // handle windows opened by JS if (openedByJS() && d->m_opener) { cUrl = d->m_opener->url(); } if (!KUrlAuthorized::authorizeUrlAction("redirect", cUrl, url)) { qWarning() << "KHTMLPart::scheduleRedirection: Redirection from " << cUrl << " to " << url << " REJECTED!"; emit completed(); return; } if (areUrlsForSamePage(url, this->url())) { args.metaData().insert("referrer", d->m_pageReferrer); } // For javascript and META-tag based redirections: // - We don't take cross-domain-ness in consideration if we are the // toplevel frame because the new URL may be in a different domain as the current URL // but that's ok. // - If we are not the toplevel frame then we check against the toplevelURL() if (parentPart()) { args.metaData().insert("cross-domain", toplevelURL().toString()); } KParts::BrowserArguments browserArgs; browserArgs.setLockHistory(d->m_redirectLockHistory); // _self: make sure we don't use any <base target=>'s if (!urlSelected(u, 0, 0, "_self", args, browserArgs)) { // urlSelected didn't open a url, so emit completed ourselves emit completed(); } } void KHTMLPart::slotRedirection(KIO::Job *, const QUrl &url) { // the slave told us that we got redirected //qDebug() << "redirection by KIO to" << url; emit d->m_extension->setLocationBarUrl(url.toDisplayString()); d->m_workingURL = url; } bool KHTMLPart::setEncoding(const QString &name, bool override) { d->m_encoding = name; d->m_haveEncoding = override; if (!url().isEmpty()) { // reload document closeUrl(); QUrl oldUrl = url(); setUrl(QUrl()); d->m_restored = true; openUrl(oldUrl); d->m_restored = false; } return true; } QString KHTMLPart::encoding() const { if (d->m_haveEncoding && !d->m_encoding.isEmpty()) { return d->m_encoding; } if (d->m_decoder && d->m_decoder->encoding()) { return QString(d->m_decoder->encoding()); } return defaultEncoding(); } QString KHTMLPart::defaultEncoding() const { QString encoding = settings()->encoding(); if (!encoding.isEmpty()) { return encoding; } // HTTP requires the default encoding to be latin1, when neither // the user nor the page requested a particular encoding. if (url().scheme().startsWith("http")) { return "iso-8859-1"; } else { return QTextCodec::codecForLocale()->name().toLower(); } } void KHTMLPart::setUserStyleSheet(const QUrl &url) { if (d->m_doc && d->m_doc->docLoader()) { (void) new khtml::PartStyleSheetLoader(this, url.toString(), d->m_doc->docLoader()); } } void KHTMLPart::setUserStyleSheet(const QString &styleSheet) { if (d->m_doc) { d->m_doc->setUserStyleSheet(styleSheet); } } bool KHTMLPart::gotoAnchor(const QString &name) { if (!d->m_doc) { return false; } HTMLCollectionImpl *anchors = new HTMLCollectionImpl(d->m_doc, HTMLCollectionImpl::DOC_ANCHORS); anchors->ref(); NodeImpl *n = anchors->namedItem(name); anchors->deref(); if (!n) { n = d->m_doc->getElementById(name); } d->m_doc->setCSSTarget(n); // Setting to null will clear the current target. // Implement the rule that "" and "top" both mean top of page. bool top = !n && (name.isEmpty() || name.toLower() == "top"); if (top) { d->m_view->setContentsPos(d->m_view->contentsX(), 0); return true; } else if (!n) { // qDebug() << name << "not found"; return false; } int x = 0, y = 0; int gox, dummy; HTMLElementImpl *a = static_cast<HTMLElementImpl *>(n); a->getUpperLeftCorner(x, y); if (x <= d->m_view->contentsX()) { gox = x - 10; } else { gox = d->m_view->contentsX(); if (x + 10 > d->m_view->contentsX() + d->m_view->visibleWidth()) { a->getLowerRightCorner(x, dummy); gox = x - d->m_view->visibleWidth() + 10; } } d->m_view->setContentsPos(gox, y); return true; } bool KHTMLPart::nextAnchor() { if (!d->m_doc) { return false; } d->m_view->focusNextPrevNode(true); return true; } bool KHTMLPart::prevAnchor() { if (!d->m_doc) { return false; } d->m_view->focusNextPrevNode(false); return true; } void KHTMLPart::setStandardFont(const QString &name) { d->m_settings->setStdFontName(name); } void KHTMLPart::setFixedFont(const QString &name) { d->m_settings->setFixedFontName(name); } void KHTMLPart::setURLCursor(const QCursor &c) { d->m_linkCursor = c; } QCursor KHTMLPart::urlCursor() const { return d->m_linkCursor; } bool KHTMLPart::onlyLocalReferences() const { return d->m_onlyLocalReferences; } void KHTMLPart::setOnlyLocalReferences(bool enable) { d->m_onlyLocalReferences = enable; } bool KHTMLPart::forcePermitLocalImages() const { return d->m_forcePermitLocalImages; } void KHTMLPart::setForcePermitLocalImages(bool enable) { d->m_forcePermitLocalImages = enable; } void KHTMLPartPrivate::setFlagRecursively( bool KHTMLPartPrivate::*flag, bool value) { // first set it on the current one this->*flag = value; // descend into child frames recursively { QList<khtml::ChildFrame *>::Iterator it = m_frames.begin(); const QList<khtml::ChildFrame *>::Iterator itEnd = m_frames.end(); for (; it != itEnd; ++it) { KHTMLPart *const part = qobject_cast<KHTMLPart *>((*it)->m_part.data()); if (part) { part->d->setFlagRecursively(flag, value); } }/*next it*/ } // do the same again for objects { QList<khtml::ChildFrame *>::Iterator it = m_objects.begin(); const QList<khtml::ChildFrame *>::Iterator itEnd = m_objects.end(); for (; it != itEnd; ++it) { KHTMLPart *const part = qobject_cast<KHTMLPart *>((*it)->m_part.data()); if (part) { part->d->setFlagRecursively(flag, value); } }/*next it*/ } } void KHTMLPart::initCaret() { // initialize caret if not used yet if (d->editor_context.m_selection.state() == Selection::NONE) { if (d->m_doc) { NodeImpl *node; if (d->m_doc->isHTMLDocument()) { HTMLDocumentImpl *htmlDoc = static_cast<HTMLDocumentImpl *>(d->m_doc); node = htmlDoc->body(); } else { node = d->m_doc; } if (!node) { return; } d->editor_context.m_selection.moveTo(Position(node, 0)); d->editor_context.m_selection.setNeedsLayout(); d->editor_context.m_selection.needsCaretRepaint(); } } } static void setCaretInvisibleIfNeeded(KHTMLPart *part) { // On contenteditable nodes, don't hide the caret if (!khtml::KHTMLPartAccessor::caret(part).caretPos().node()->isContentEditable()) { part->setCaretVisible(false); } } void KHTMLPart::setCaretMode(bool enable) { // qDebug() << enable; if (isCaretMode() == enable) { return; } d->setFlagRecursively(&KHTMLPartPrivate::m_caretMode, enable); // FIXME: this won't work on frames as expected if (!isEditable()) { if (enable) { initCaret(); setCaretVisible(true); // view()->ensureCaretVisible(); } else { setCaretInvisibleIfNeeded(this); } } } bool KHTMLPart::isCaretMode() const { return d->m_caretMode; } void KHTMLPart::setEditable(bool enable) { if (isEditable() == enable) { return; } d->setFlagRecursively(&KHTMLPartPrivate::m_designMode, enable); // FIXME: this won't work on frames as expected if (!isCaretMode()) { if (enable) { initCaret(); setCaretVisible(true); // view()->ensureCaretVisible(); } else { setCaretInvisibleIfNeeded(this); } } } bool KHTMLPart::isEditable() const { return d->m_designMode; } khtml::EditorContext *KHTMLPart::editorContext() const { return &d->editor_context; } void KHTMLPart::setCaretPosition(DOM::Node node, long offset, bool extendSelection) { Q_UNUSED(node); Q_UNUSED(offset); Q_UNUSED(extendSelection); #ifndef KHTML_NO_CARET #if 0 qDebug() << "node: " << node.handle() << " nodeName: " << node.nodeName().string() << " offset: " << offset << " extendSelection " << extendSelection; if (view()->moveCaretTo(node.handle(), offset, !extendSelection)) { emitSelectionChanged(); } view()->ensureCaretVisible(); #endif #endif // KHTML_NO_CARET } KHTMLPart::CaretDisplayPolicy KHTMLPart::caretDisplayPolicyNonFocused() const { #if 0 #ifndef KHTML_NO_CARET return (CaretDisplayPolicy)view()->caretDisplayPolicyNonFocused(); #else // KHTML_NO_CARET return CaretInvisible; #endif // KHTML_NO_CARET #endif return CaretInvisible; } void KHTMLPart::setCaretDisplayPolicyNonFocused(CaretDisplayPolicy policy) { Q_UNUSED(policy); #if 0 #ifndef KHTML_NO_CARET view()->setCaretDisplayPolicyNonFocused(policy); #endif // KHTML_NO_CARET #endif } void KHTMLPart::setCaretVisible(bool show) { if (show) { NodeImpl *caretNode = d->editor_context.m_selection.caretPos().node(); if (isCaretMode() || (caretNode && caretNode->isContentEditable())) { invalidateSelection(); enableFindAheadActions(false); } } else { if (d->editor_context.m_caretBlinkTimer >= 0) { killTimer(d->editor_context.m_caretBlinkTimer); } clearCaretRectIfNeeded(); } } void KHTMLPart::findTextBegin() { d->m_find.findTextBegin(); } bool KHTMLPart::initFindNode(bool selection, bool reverse, bool fromCursor) { return d->m_find.initFindNode(selection, reverse, fromCursor); } void KHTMLPart::slotFind() { KParts::ReadOnlyPart *part = currentFrame(); if (!part) { return; } if (!part->inherits("KHTMLPart")) { qCritical() << "part is a" << part->metaObject()->className() << ", can't do a search into it"; return; } static_cast<KHTMLPart *>(part)->findText(); } void KHTMLPart::slotFindNext() { KParts::ReadOnlyPart *part = currentFrame(); if (!part) { return; } if (!part->inherits("KHTMLPart")) { qCritical() << "part is a" << part->metaObject()->className() << ", can't do a search into it"; return; } static_cast<KHTMLPart *>(part)->findTextNext(); } void KHTMLPart::slotFindPrev() { KParts::ReadOnlyPart *part = currentFrame(); if (!part) { return; } if (!part->inherits("KHTMLPart")) { qCritical() << "part is a" << part->metaObject()->className() << ", can't do a search into it"; return; } static_cast<KHTMLPart *>(part)->findTextNext(true); // reverse } void KHTMLPart::slotFindDone() { // ### remove me } void KHTMLPart::slotFindAheadText() { KHTMLPart *part = qobject_cast<KHTMLPart *>(currentFrame()); if (!part) { return; } part->findText(); KHTMLFindBar *findBar = part->d->m_find.findBar(); findBar->setOptions(findBar->options() & ~FindLinksOnly); } void KHTMLPart::slotFindAheadLink() { KHTMLPart *part = qobject_cast<KHTMLPart *>(currentFrame()); if (!part) { return; } part->findText(); KHTMLFindBar *findBar = part->d->m_find.findBar(); findBar->setOptions(findBar->options() | FindLinksOnly); } void KHTMLPart::enableFindAheadActions(bool) { // ### remove me } void KHTMLPart::slotFindDialogDestroyed() { // ### remove me } void KHTMLPart::findText() { if (parentPart()) { return parentPart()->findText(); } d->m_find.activate(); } void KHTMLPart::findText(const QString &str, long options, QWidget *parent, KFindDialog *findDialog) { if (parentPart()) { return parentPart()->findText(str, options, parent, findDialog); } d->m_find.createNewKFind(str, options, parent, findDialog); } // New method bool KHTMLPart::findTextNext(bool reverse) { if (parentPart()) { return parentPart()->findTextNext(reverse); } return d->m_find.findTextNext(reverse); } bool KHTMLPart::pFindTextNextInThisFrame(bool reverse) { return d->m_find.findTextNext(reverse); } QString KHTMLPart::selectedTextAsHTML() const { const Selection &sel = d->editor_context.m_selection; if (!hasSelection()) { // qDebug() << "Selection is not valid. Returning empty selection"; return QString(); } if (sel.start().offset() < 0 || sel.end().offset() < 0) { // qDebug() << "invalid values for end/startOffset " << sel.start().offset() << " " << sel.end().offset(); return QString(); } DOM::Range r = selection(); if (r.isNull() || r.isDetached()) { return QString(); } int exceptioncode = 0; //ignore the result return r.handle()->toHTML(exceptioncode).string(); } QString KHTMLPart::selectedText() const { bool hasNewLine = true; bool seenTDTag = false; QString text; const Selection &sel = d->editor_context.m_selection; DOM::Node n = sel.start().node(); while (!n.isNull()) { if (n.nodeType() == DOM::Node::TEXT_NODE && n.handle()->renderer()) { DOM::DOMStringImpl *dstr = static_cast<DOM::TextImpl *>(n.handle())->renderString(); QString str(dstr->s, dstr->l); if (!str.isEmpty()) { if (seenTDTag) { text += " "; seenTDTag = false; } hasNewLine = false; if (n == sel.start().node() && n == sel.end().node()) { int s = khtml::RenderPosition::fromDOMPosition(sel.start()).renderedOffset(); int e = khtml::RenderPosition::fromDOMPosition(sel.end()).renderedOffset(); text = str.mid(s, e - s); } else if (n == sel.start().node()) { text = str.mid(khtml::RenderPosition::fromDOMPosition(sel.start()).renderedOffset()); } else if (n == sel.end().node()) { text += str.left(khtml::RenderPosition::fromDOMPosition(sel.end()).renderedOffset()); } else { text += str; } } } else { // This is our simple HTML -> ASCII transformation: unsigned short id = n.elementId(); switch (id) { case ID_TEXTAREA: text += static_cast<HTMLTextAreaElementImpl *>(n.handle())->value().string(); break; case ID_INPUT: if (static_cast<HTMLInputElementImpl *>(n.handle())->inputType() != HTMLInputElementImpl::PASSWORD) { text += static_cast<HTMLInputElementImpl *>(n.handle())->value().string(); } break; case ID_SELECT: text += static_cast<HTMLSelectElementImpl *>(n.handle())->value().string(); break; case ID_BR: text += "\n"; hasNewLine = true; break; case ID_IMG: text += static_cast<HTMLImageElementImpl *>(n.handle())->altText().string(); break; case ID_TD: break; case ID_TH: case ID_HR: case ID_OL: case ID_UL: case ID_LI: case ID_DD: case ID_DL: case ID_DT: case ID_PRE: case ID_LISTING: case ID_BLOCKQUOTE: case ID_DIV: if (!hasNewLine) { text += "\n"; } hasNewLine = true; break; case ID_P: case ID_TR: case ID_H1: case ID_H2: case ID_H3: case ID_H4: case ID_H5: case ID_H6: if (!hasNewLine) { text += "\n"; } hasNewLine = true; break; } } if (n == sel.end().node()) { break; } DOM::Node next = n.firstChild(); if (next.isNull()) { next = n.nextSibling(); } while (next.isNull() && !n.parentNode().isNull()) { n = n.parentNode(); next = n.nextSibling(); unsigned short id = n.elementId(); switch (id) { case ID_TD: seenTDTag = true; //Add two spaces after a td if then followed by text. break; case ID_TH: case ID_HR: case ID_OL: case ID_UL: case ID_LI: case ID_DD: case ID_DL: case ID_DT: case ID_PRE: case ID_LISTING: case ID_BLOCKQUOTE: case ID_DIV: seenTDTag = false; if (!hasNewLine) { text += "\n"; } hasNewLine = true; break; case ID_P: case ID_TR: case ID_H1: case ID_H2: case ID_H3: case ID_H4: case ID_H5: case ID_H6: if (!hasNewLine) { text += "\n"; } // text += "\n"; hasNewLine = true; break; } } n = next; } if (text.isEmpty()) { return QString(); } int start = 0; int end = text.length(); // Strip leading LFs while ((start < end) && (text[start] == '\n')) { ++start; } // Strip excessive trailing LFs while ((start < (end - 1)) && (text[end - 1] == '\n') && (text[end - 2] == '\n')) { --end; } return text.mid(start, end - start); } QString KHTMLPart::simplifiedSelectedText() const { QString text = selectedText(); text.replace(QChar(0xa0), ' '); // remove leading and trailing whitespace while (!text.isEmpty() && text[0].isSpace()) { text = text.mid(1); } while (!text.isEmpty() && text[text.length() - 1].isSpace()) { text.truncate(text.length() - 1); } return text; } bool KHTMLPart::hasSelection() const { return !d->editor_context.m_selection.isEmpty() && !d->editor_context.m_selection.isCollapsed(); } DOM::Range KHTMLPart::selection() const { return d->editor_context.m_selection.toRange(); } void KHTMLPart::selection(DOM::Node &s, long &so, DOM::Node &e, long &eo) const { DOM::Range r = d->editor_context.m_selection.toRange(); s = r.startContainer(); so = r.startOffset(); e = r.endContainer(); eo = r.endOffset(); } void KHTMLPart::setSelection(const DOM::Range &r) { setCaret(r); } const Selection &KHTMLPart::caret() const { return d->editor_context.m_selection; } const Selection &KHTMLPart::dragCaret() const { return d->editor_context.m_dragCaret; } void KHTMLPart::setCaret(const Selection &s, bool closeTyping) { if (d->editor_context.m_selection != s) { clearCaretRectIfNeeded(); setFocusNodeIfNeeded(s); d->editor_context.m_selection = s; notifySelectionChanged(closeTyping); } } void KHTMLPart::setDragCaret(const DOM::Selection &dragCaret) { if (d->editor_context.m_dragCaret != dragCaret) { d->editor_context.m_dragCaret.needsCaretRepaint(); d->editor_context.m_dragCaret = dragCaret; d->editor_context.m_dragCaret.needsCaretRepaint(); } } void KHTMLPart::clearSelection() { clearCaretRectIfNeeded(); setFocusNodeIfNeeded(d->editor_context.m_selection); #ifdef APPLE_CHANGES d->editor_context.m_selection.clear(); #else d->editor_context.m_selection.collapse(); #endif notifySelectionChanged(); } void KHTMLPart::invalidateSelection() { clearCaretRectIfNeeded(); d->editor_context.m_selection.setNeedsLayout(); selectionLayoutChanged(); } void KHTMLPart::setSelectionVisible(bool flag) { if (d->editor_context.m_caretVisible == flag) { return; } clearCaretRectIfNeeded(); setFocusNodeIfNeeded(d->editor_context.m_selection); d->editor_context.m_caretVisible = flag; // notifySelectionChanged(); } #if 1 void KHTMLPart::slotClearSelection() { if (!isCaretMode() && d->editor_context.m_selection.state() != Selection::NONE && !d->editor_context.m_selection.caretPos().node()->isContentEditable()) { clearCaretRectIfNeeded(); } bool hadSelection = hasSelection(); #ifdef APPLE_CHANGES d->editor_context.m_selection.clear(); #else d->editor_context.m_selection.collapse(); #endif if (hadSelection) { notifySelectionChanged(); } } #endif void KHTMLPart::clearCaretRectIfNeeded() { if (d->editor_context.m_caretPaint) { d->editor_context.m_caretPaint = false; d->editor_context.m_selection.needsCaretRepaint(); } } void KHTMLPart::setFocusNodeIfNeeded(const Selection &s) { if (!xmlDocImpl() || s.state() == Selection::NONE) { return; } NodeImpl *n = s.start().node(); NodeImpl *target = (n && n->isContentEditable()) ? n : nullptr; if (!target) { while (n && n != s.end().node()) { if (n->isContentEditable()) { target = n; break; } n = n->traverseNextNode(); } } assert(target == nullptr || target->isContentEditable()); if (target) { for (; target && !target->isFocusable(); target = target->parentNode()) { } if (target && target->isMouseFocusable()) { xmlDocImpl()->setFocusNode(target); } else if (!target || !target->focused()) { xmlDocImpl()->setFocusNode(nullptr); } } } void KHTMLPart::selectionLayoutChanged() { // kill any caret blink timer now running if (d->editor_context.m_caretBlinkTimer >= 0) { killTimer(d->editor_context.m_caretBlinkTimer); d->editor_context.m_caretBlinkTimer = -1; } // see if a new caret blink timer needs to be started if (d->editor_context.m_caretVisible && d->editor_context.m_selection.state() != Selection::NONE) { d->editor_context.m_caretPaint = isCaretMode() || d->editor_context.m_selection.caretPos().node()->isContentEditable(); if (d->editor_context.m_caretBlinks && d->editor_context.m_caretPaint) { d->editor_context.m_caretBlinkTimer = startTimer(qApp->cursorFlashTime() / 2); } d->editor_context.m_selection.needsCaretRepaint(); // make sure that caret is visible QRect r(d->editor_context.m_selection.getRepaintRect()); if (d->editor_context.m_caretPaint) { d->m_view->ensureVisible(r.x(), r.y()); } } if (d->m_doc) { d->m_doc->updateSelection(); } // Always clear the x position used for vertical arrow navigation. // It will be restored by the vertical arrow navigation code if necessary. d->editor_context.m_xPosForVerticalArrowNavigation = d->editor_context.NoXPosForVerticalArrowNavigation; } void KHTMLPart::notifySelectionChanged(bool closeTyping) { Editor *ed = d->editor_context.m_editor; selectionLayoutChanged(); if (ed) { ed->clearTypingStyle(); if (closeTyping) { ed->closeTyping(); } } emitSelectionChanged(); } void KHTMLPart::timerEvent(QTimerEvent *e) { if (e->timerId() == d->editor_context.m_caretBlinkTimer) { if (d->editor_context.m_caretBlinks && d->editor_context.m_selection.state() != Selection::NONE) { d->editor_context.m_caretPaint = !d->editor_context.m_caretPaint; d->editor_context.m_selection.needsCaretRepaint(); } } else if (e->timerId() == d->m_DNSPrefetchTimer) { // qDebug() << "will lookup " << d->m_DNSPrefetchQueue.head() << d->m_numDNSPrefetchedNames; KIO::HostInfo::prefetchHost(d->m_DNSPrefetchQueue.dequeue()); if (d->m_DNSPrefetchQueue.isEmpty()) { killTimer(d->m_DNSPrefetchTimer); d->m_DNSPrefetchTimer = -1; } } else if (e->timerId() == d->m_DNSTTLTimer) { foreach (const QString &name, d->m_lookedupHosts) { d->m_DNSPrefetchQueue.enqueue(name); } if (d->m_DNSPrefetchTimer <= 0) { d->m_DNSPrefetchTimer = startTimer(sDNSPrefetchTimerDelay); } } } bool KHTMLPart::mayPrefetchHostname(const QString &name) { if (d->m_bDNSPrefetch == DNSPrefetchDisabled) { return false; } if (d->m_numDNSPrefetchedNames >= sMaxDNSPrefetchPerPage) { return false; } if (d->m_bDNSPrefetch == DNSPrefetchOnlyWWWAndSLD) { int dots = name.count('.'); if (dots > 2 || (dots == 2 && !name.startsWith("www."))) { return false; } } if (d->m_lookedupHosts.contains(name)) { return false; } d->m_DNSPrefetchQueue.enqueue(name); d->m_lookedupHosts.insert(name); d->m_numDNSPrefetchedNames++; if (d->m_DNSPrefetchTimer < 1) { d->m_DNSPrefetchTimer = startTimer(sDNSPrefetchTimerDelay); } if (d->m_DNSTTLTimer < 1) { d->m_DNSTTLTimer = startTimer(sDNSTTLSeconds * 1000 + 1); } return true; } void KHTMLPart::paintCaret(QPainter *p, const QRect &rect) const { if (d->editor_context.m_caretPaint) { d->editor_context.m_selection.paintCaret(p, rect); } } void KHTMLPart::paintDragCaret(QPainter *p, const QRect &rect) const { d->editor_context.m_dragCaret.paintCaret(p, rect); } DOM::Editor *KHTMLPart::editor() const { if (!d->editor_context.m_editor) { const_cast<KHTMLPart *>(this)->d->editor_context.m_editor = new DOM::Editor(const_cast<KHTMLPart *>(this)); } return d->editor_context.m_editor; } void KHTMLPart::resetHoverText() { if (!d->m_overURL.isEmpty()) { // Only if we were showing a link d->m_overURL.clear(); d->m_overURLTarget.clear(); emit onURL(QString()); // revert to default statusbar text setStatusBarText(QString(), BarHoverText); emit d->m_extension->mouseOverInfo(KFileItem()); } } void KHTMLPart::overURL(const QString &url, const QString &target, bool /*shiftPressed*/) { QUrl u = completeURL(url); // special case for <a href=""> if (url.isEmpty()) { u = u.adjusted(QUrl::RemoveFilename); } emit onURL(url); if (url.isEmpty()) { setStatusBarText(Qt::escape(u.toDisplayString()), BarHoverText); return; } if (d->isJavaScriptURL(url)) { QString jscode = d->codeForJavaScriptURL(url); jscode = KStringHandler::rsqueeze(jscode, 80); // truncate if too long if (url.startsWith("javascript:window.open")) { jscode += i18n(" (In new window)"); } setStatusBarText(Qt::escape(jscode), BarHoverText); return; } KFileItem item(u, QString(), KFileItem::Unknown); emit d->m_extension->mouseOverInfo(item); const QString com = item.mimeComment(); if (!u.isValid()) { setStatusBarText(Qt::escape(u.toDisplayString()), BarHoverText); return; } if (u.isLocalFile()) { // TODO : use KIO::stat() and create a KFileItem out of its result, // to use KFileItem::statusBarText() QFileInfo info(u.toLocalFile()); bool ok = info.exists(); QString text = Qt::escape(u.toDisplayString()); QString text2 = text; if (info.isSymLink()) { QString tmp; if (com.isEmpty()) { tmp = i18n("Symbolic Link"); } else { tmp = i18n("%1 (Link)", com); } text += " -> "; QString target = info.symLinkTarget(); if (target.isEmpty()) { text2 += " "; text2 += tmp; setStatusBarText(text2, BarHoverText); return; } text += target; text += " "; text += tmp; } else if (ok && info.isFile()) { if (info.size() < 1024) { text = i18np("%2 (%1 byte)", "%2 (%1 bytes)", (long) info.size(), text2); // always put the URL last, in case it contains '%' } else { float d = (float) info.size() / 1024.0; text = i18n("%2 (%1 K)", QLocale().toString(d, 'f', 2), text2); // was %.2f } text += " "; text += com; } else if (ok && info.isDir()) { text += " "; text += com; } else { text += " "; text += com; } setStatusBarText(text, BarHoverText); } else { QString extra; if (target.toLower() == "_blank") { extra = i18n(" (In new window)"); } else if (!target.isEmpty() && (target.toLower() != "_top") && (target.toLower() != "_self") && (target.toLower() != "_parent")) { KHTMLPart *p = this; while (p->parentPart()) { p = p->parentPart(); } if (!p->frameExists(target)) { extra = i18n(" (In new window)"); } else { extra = i18n(" (In other frame)"); } } if (u.scheme() == QLatin1String("mailto")) { QString mailtoMsg /* = QString::fromLatin1("<img src=%1>").arg(locate("icon", QString::fromLatin1("locolor/16x16/actions/mail_send.png")))*/; mailtoMsg += i18n("Email to: ") + QUrl::fromPercentEncoding(u.path().toLatin1()); const QStringList queries = u.query().mid(1).split('&'); QStringList::ConstIterator it = queries.begin(); const QStringList::ConstIterator itEnd = queries.end(); for (; it != itEnd; ++it) if ((*it).startsWith(QLatin1String("subject="))) { mailtoMsg += i18n(" - Subject: ") + QUrl::fromPercentEncoding((*it).mid(8).toLatin1()); } else if ((*it).startsWith(QLatin1String("cc="))) { mailtoMsg += i18n(" - CC: ") + QUrl::fromPercentEncoding((*it).mid(3).toLatin1()); } else if ((*it).startsWith(QLatin1String("bcc="))) { mailtoMsg += i18n(" - BCC: ") + QUrl::fromPercentEncoding((*it).mid(4).toLatin1()); } mailtoMsg = Qt::escape(mailtoMsg); mailtoMsg.replace(QRegExp("([\n\r\t]|[ ]{10})"), QString()); setStatusBarText("<qt>" + mailtoMsg, BarHoverText); return; } // Is this check necessary at all? (Frerich) #if 0 else if (u.scheme() == QLatin1String("http")) { DOM::Node hrefNode = nodeUnderMouse().parentNode(); while (hrefNode.nodeName().string() != QLatin1String("A") && !hrefNode.isNull()) { hrefNode = hrefNode.parentNode(); } if (!hrefNode.isNull()) { DOM::Node hreflangNode = hrefNode.attributes().getNamedItem("HREFLANG"); if (!hreflangNode.isNull()) { QString countryCode = hreflangNode.nodeValue().string().toLower(); // Map the language code to an appropriate country code. if (countryCode == QLatin1String("en")) { countryCode = QLatin1String("gb"); } QString flagImg = QLatin1String("<img src=%1>").arg( locate("locale", QLatin1String("l10n/") + countryCode + QLatin1String("/kf5_flag.png"))); emit setStatusBarText(flagImg + u.toDisplayString() + extra); } } } #endif setStatusBarText(Qt::escape(u.toDisplayString()) + extra, BarHoverText); } } // // This executes in the active part on a click or other url selection action in // that active part. // bool KHTMLPart::urlSelected(const QString &url, int button, int state, const QString &_target, const KParts::OpenUrlArguments &_args, const KParts::BrowserArguments &_browserArgs) { KParts::OpenUrlArguments args = _args; KParts::BrowserArguments browserArgs = _browserArgs; bool hasTarget = false; QString target = _target; if (target.isEmpty() && d->m_doc) { target = d->m_doc->baseTarget(); } if (!target.isEmpty()) { hasTarget = true; } if (d->isJavaScriptURL(url)) { crossFrameExecuteScript(target, d->codeForJavaScriptURL(url)); return false; } QUrl cURL = completeURL(url); // special case for <a href=""> (IE removes filename, mozilla doesn't) if (url.isEmpty()) { cURL = cURL.adjusted(QUrl::RemoveFilename); } if (!cURL.isValid()) // ### ERROR HANDLING { return false; } // qDebug() << this << "complete URL:" << cURL << "target=" << target; if (state & Qt::ControlModifier) { emit d->m_extension->createNewWindow(cURL, args, browserArgs); return true; } if (button == Qt::LeftButton && (state & Qt::ShiftModifier)) { KIO::MetaData metaData; metaData.insert("referrer", d->m_referrer); KHTMLPopupGUIClient::saveURL(d->m_view, i18n("Save As"), cURL, metaData); return false; } if (!checkLinkSecurity(cURL, ki18n("<qt>This untrusted page links to<br /><b>%1</b>.<br />Do you want to follow the link?</qt>"), i18n("Follow"))) { return false; } browserArgs.frameName = target; args.metaData().insert("main_frame_request", parentPart() == nullptr ? "TRUE" : "FALSE"); args.metaData().insert("ssl_parent_ip", d->m_ssl_parent_ip); args.metaData().insert("ssl_parent_cert", d->m_ssl_parent_cert); args.metaData().insert("PropagateHttpHeader", "true"); args.metaData().insert("ssl_was_in_use", d->m_ssl_in_use ? "TRUE" : "FALSE"); args.metaData().insert("ssl_activate_warnings", "TRUE"); if (hasTarget && target != "_self" && target != "_top" && target != "_blank" && target != "_parent") { // unknown frame names should open in a new window. khtml::ChildFrame *frame = recursiveFrameRequest(this, cURL, args, browserArgs, false); if (frame) { args.metaData()["referrer"] = d->m_referrer; requestObject(frame, cURL, args, browserArgs); return true; } } if (!d->m_referrer.isEmpty() && !args.metaData().contains("referrer")) { args.metaData()["referrer"] = d->m_referrer; } if (button == Qt::NoButton && (state & Qt::ShiftModifier) && (state & Qt::ControlModifier)) { emit d->m_extension->createNewWindow(cURL, args, browserArgs); return true; } if (state & Qt::ShiftModifier) { KParts::WindowArgs winArgs; winArgs.setLowerWindow(true); emit d->m_extension->createNewWindow(cURL, args, browserArgs, winArgs); return true; } //If we're asked to open up an anchor in the current URL, in current window, //merely gotoanchor, and do not reload the new page. Note that this does //not apply if the URL is the same page, but without a ref if (cURL.hasFragment() && (!hasTarget || target == "_self")) { if (d->isLocalAnchorJump(cURL)) { d->executeAnchorJump(cURL, browserArgs.lockHistory()); return false; // we jumped, but we didn't open a URL } } if (!d->m_bComplete && !hasTarget) { closeUrl(); } view()->viewport()->unsetCursor(); emit d->m_extension->openUrlRequest(cURL, args, browserArgs); return true; } void KHTMLPart::slotViewDocumentSource() { QUrl currentUrl(this->url()); bool isTempFile = false; if (!(currentUrl.isLocalFile()) && KHTMLPageCache::self()->isComplete(d->m_cacheId)) { QTemporaryFile sourceFile(QDir::tempPath() + QLatin1String("/XXXXXX") + defaultExtension()); sourceFile.setAutoRemove(false); if (sourceFile.open()) { QDataStream stream(&sourceFile); KHTMLPageCache::self()->saveData(d->m_cacheId, &stream); currentUrl = QUrl::fromLocalFile(sourceFile.fileName()); isTempFile = true; } } (void) KRun::runUrl(currentUrl, QLatin1String("text/plain"), view(), isTempFile); } void KHTMLPart::slotViewPageInfo() { Ui_KHTMLInfoDlg ui; QDialog *dlg = new QDialog(nullptr); dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->setObjectName("KHTML Page Info Dialog"); ui.setupUi(dlg); KGuiItem::assign(ui._close, KStandardGuiItem::close()); connect(ui._close, SIGNAL(clicked()), dlg, SLOT(accept())); if (d->m_doc) { ui._title->setText(d->m_doc->title().string().trimmed()); } // If it's a frame, set the caption to "Frame Information" if (parentPart() && d->m_doc && d->m_doc->isHTMLDocument()) { dlg->setWindowTitle(i18n("Frame Information")); } QString editStr; if (!d->m_pageServices.isEmpty()) { editStr = i18n(" <a href=\"%1\">[Properties]</a>", d->m_pageServices); } QString squeezedURL = KStringHandler::csqueeze(url().toDisplayString(), 80); ui._url->setText("<a href=\"" + url().toString() + "\">" + squeezedURL + "</a>" + editStr); if (lastModified().isEmpty()) { ui._lastModified->hide(); ui._lmLabel->hide(); } else { ui._lastModified->setText(lastModified()); } const QString &enc = encoding(); if (enc.isEmpty()) { ui._eLabel->hide(); ui._encoding->hide(); } else { ui._encoding->setText(enc); } if (!xmlDocImpl() || xmlDocImpl()->parseMode() == DOM::DocumentImpl::Unknown) { ui._mode->hide(); ui._modeLabel->hide(); } else { switch (xmlDocImpl()->parseMode()) { case DOM::DocumentImpl::Compat: ui._mode->setText(i18nc("HTML rendering mode (see http://en.wikipedia.org/wiki/Quirks_mode)", "Quirks")); break; case DOM::DocumentImpl::Transitional: ui._mode->setText(i18nc("HTML rendering mode (see http://en.wikipedia.org/wiki/Quirks_mode)", "Almost standards")); break; case DOM::DocumentImpl::Strict: default: // others handled above ui._mode->setText(i18nc("HTML rendering mode (see http://en.wikipedia.org/wiki/Quirks_mode)", "Strict")); break; } } /* populate the list view now */ const QStringList headers = d->m_httpHeaders.split("\n"); QStringList::ConstIterator it = headers.begin(); const QStringList::ConstIterator itEnd = headers.end(); for (; it != itEnd; ++it) { const QStringList header = (*it).split(QRegExp(":[ ]+")); if (header.count() != 2) { continue; } QTreeWidgetItem *item = new QTreeWidgetItem(ui._headers); item->setText(0, header[0]); item->setText(1, header[1]); } dlg->show(); /* put no code here */ } void KHTMLPart::slotViewFrameSource() { KParts::ReadOnlyPart *frame = currentFrame(); if (!frame) { return; } QUrl url = frame->url(); bool isTempFile = false; if (!(url.isLocalFile()) && frame->inherits("KHTMLPart")) { long cacheId = static_cast<KHTMLPart *>(frame)->d->m_cacheId; if (KHTMLPageCache::self()->isComplete(cacheId)) { QTemporaryFile sourceFile(QDir::tempPath() + QLatin1String("/XXXXXX") + defaultExtension()); sourceFile.setAutoRemove(false); if (sourceFile.open()) { QDataStream stream(&sourceFile); KHTMLPageCache::self()->saveData(cacheId, &stream); url = QUrl(); url.setPath(sourceFile.fileName()); isTempFile = true; } } } (void) KRun::runUrl(url, QLatin1String("text/plain"), view(), isTempFile); } QUrl KHTMLPart::backgroundURL() const { // ### what about XML documents? get from CSS? if (!d->m_doc || !d->m_doc->isHTMLDocument()) { return QUrl(); } QString relURL = static_cast<HTMLDocumentImpl *>(d->m_doc)->body()->getAttribute(ATTR_BACKGROUND).string(); return url().resolved(QUrl(relURL)); } void KHTMLPart::slotSaveBackground() { KIO::MetaData metaData; metaData["referrer"] = d->m_referrer; KHTMLPopupGUIClient::saveURL(d->m_view, i18n("Save Background Image As"), backgroundURL(), metaData); } void KHTMLPart::slotSaveDocument() { QUrl srcURL(url()); if (srcURL.fileName().isEmpty()) { srcURL.setPath(srcURL.path() + "index" + defaultExtension()); } KIO::MetaData metaData; // Referre unknown? KHTMLPopupGUIClient::saveURL(d->m_view, i18n("Save As"), srcURL, metaData, "text/html", d->m_cacheId); } void KHTMLPart::slotSecurity() { // qDebug() << "Meta Data:" << endl // << d->m_ssl_peer_cert_subject // << endl // << d->m_ssl_peer_cert_issuer // << endl // << d->m_ssl_cipher // << endl // << d->m_ssl_cipher_desc // << endl // << d->m_ssl_cipher_version // << endl // << d->m_ssl_good_from // << endl // << d->m_ssl_good_until // << endl // << d->m_ssl_cert_state // << endl; //### reenable with new signature #if 0 KSslInfoDialog *kid = new KSslInfoDialog(d->m_ssl_in_use, widget(), "kssl_info_dlg", true); const QStringList sl = d->m_ssl_peer_chain.split('\n', QString::SkipEmptyParts); QList<QSslCertificate> certChain; bool certChainOk = d->m_ssl_in_use; if (certChainOk) { foreach (const QString &s, sl) { certChain.append(QSslCertificate(s.toLatin1())); //or is it toLocal8Bit or whatever? if (certChain.last().isNull()) { certChainOk = false; break; } } } if (certChainOk) { kid->setup(certChain, d->m_ssl_peer_ip, url().toString(), d->m_ssl_cipher, d->m_ssl_cipher_desc, d->m_ssl_cipher_version, d->m_ssl_cipher_used_bits.toInt(), d->m_ssl_cipher_bits.toInt(), (KSSLCertificate::KSSLValidation) d->m_ssl_cert_state.toInt()); } kid->exec(); //the dialog deletes itself on close #endif KSslInfoDialog *kid = new KSslInfoDialog(nullptr); //### This is boilerplate code and it's copied from SlaveInterface. QStringList sl = d->m_ssl_peer_chain.split('\x01', QString::SkipEmptyParts); QList<QSslCertificate> certChain; bool decodedOk = true; foreach (const QString &s, sl) { certChain.append(QSslCertificate(s.toLatin1())); //or is it toLocal8Bit or whatever? if (certChain.last().isNull()) { decodedOk = false; break; } } if (decodedOk || true /*H4X*/) { kid->setSslInfo(certChain, d->m_ssl_peer_ip, url().host(), d->m_ssl_protocol_version, d->m_ssl_cipher, d->m_ssl_cipher_used_bits.toInt(), d->m_ssl_cipher_bits.toInt(), KSslInfoDialog::errorsFromString(d->m_ssl_cert_errors)); // qDebug() << "Showing SSL Info dialog"; kid->exec(); // qDebug() << "SSL Info dialog closed"; } else { KMessageBox::information(nullptr, i18n("The peer SSL certificate chain " "appears to be corrupt."), i18n("SSL")); } } void KHTMLPart::slotSaveFrame() { KParts::ReadOnlyPart *frame = currentFrame(); if (!frame) { return; } QUrl srcURL(frame->url()); if (srcURL.fileName().isEmpty()) { srcURL.setPath(srcURL.path() + "index" + defaultExtension()); } KIO::MetaData metaData; // Referrer unknown? KHTMLPopupGUIClient::saveURL(d->m_view, i18n("Save Frame As"), srcURL, metaData, "text/html"); } void KHTMLPart::slotSetEncoding(const QString &enc) { d->m_autoDetectLanguage = KEncodingProber::None; setEncoding(enc, true); } void KHTMLPart::slotAutomaticDetectionLanguage(KEncodingProber::ProberType scri) { d->m_autoDetectLanguage = scri; setEncoding(QString(), false); } void KHTMLPart::slotUseStylesheet() { if (d->m_doc) { bool autoselect = (d->m_paUseStylesheet->currentItem() == 0); d->m_sheetUsed = autoselect ? QString() : d->m_paUseStylesheet->currentText(); d->m_doc->updateStyleSelector(); } } void KHTMLPart::updateActions() { bool frames = false; QList<khtml::ChildFrame *>::ConstIterator it = d->m_frames.constBegin(); const QList<khtml::ChildFrame *>::ConstIterator end = d->m_frames.constEnd(); for (; it != end; ++it) if ((*it)->m_type == khtml::ChildFrame::Frame) { frames = true; break; } if (d->m_paViewFrame) { d->m_paViewFrame->setEnabled(frames); } if (d->m_paSaveFrame) { d->m_paSaveFrame->setEnabled(frames); } if (frames) { d->m_paFind->setText(i18n("&Find in Frame...")); } else { d->m_paFind->setText(i18n("&Find...")); } KParts::Part *frame = nullptr; if (frames) { frame = currentFrame(); } bool enableFindAndSelectAll = true; if (frame) { enableFindAndSelectAll = frame->inherits("KHTMLPart"); } d->m_paFind->setEnabled(enableFindAndSelectAll); d->m_paSelectAll->setEnabled(enableFindAndSelectAll); bool enablePrintFrame = false; if (frame) { QObject *ext = KParts::BrowserExtension::childObject(frame); if (ext) { enablePrintFrame = ext->metaObject()->indexOfSlot("print()") != -1; } } d->m_paPrintFrame->setEnabled(enablePrintFrame); QString bgURL; // ### frames if (d->m_doc && d->m_doc->isHTMLDocument() && static_cast<HTMLDocumentImpl *>(d->m_doc)->body() && !d->m_bClearing) { bgURL = static_cast<HTMLDocumentImpl *>(d->m_doc)->body()->getAttribute(ATTR_BACKGROUND).string(); } if (d->m_paSaveBackground) { d->m_paSaveBackground->setEnabled(!bgURL.isEmpty()); } if (d->m_paDebugScript) { d->m_paDebugScript->setEnabled(d->m_frame ? d->m_frame->m_jscript : nullptr); } } KParts::ScriptableExtension *KHTMLPart::scriptableExtension(const DOM::NodeImpl *frame) { const ConstFrameIt end = d->m_objects.constEnd(); for (ConstFrameIt it = d->m_objects.constBegin(); it != end; ++it) if ((*it)->m_partContainerElement.data() == frame) { return (*it)->m_scriptable.data(); } return nullptr; } void KHTMLPart::loadFrameElement(DOM::HTMLPartContainerElementImpl *frame, const QString &url, const QString &frameName, const QStringList ¶ms, bool isIFrame) { //qDebug() << this << " requestFrame( ..., " << url << ", " << frameName << " )"; khtml::ChildFrame *child; FrameIt it = d->m_frames.find(frameName); if (it == d->m_frames.end()) { child = new khtml::ChildFrame; //qDebug() << "inserting new frame into frame map " << frameName; child->m_name = frameName; d->m_frames.insert(d->m_frames.end(), child); } else { child = *it; } child->m_type = isIFrame ? khtml::ChildFrame::IFrame : khtml::ChildFrame::Frame; child->m_partContainerElement = frame; child->m_params = params; // If we do not have a part, make sure we create one. if (!child->m_part) { QStringList dummy; // the list of servicetypes handled by the part is now unused. QString khtml = QString::fromLatin1("khtml"); KParts::ReadOnlyPart *part = createPart(d->m_view->viewport(), this, QString::fromLatin1("text/html"), khtml, dummy, QStringList()); // We navigate it to about:blank to setup an empty one, but we do it // before hooking up the signals and extensions, so that any sync emit // of completed by the kid doesn't cause us to be marked as completed. // (async ones are discovered by the presence of the KHTMLRun) // ### load event on the kid? navigateLocalProtocol(child, part, QUrl("about:blank")); connectToChildPart(child, part, "text/html" /* mimetype of the part, not what's being loaded */); } QUrl u = url.isEmpty() ? QUrl() : completeURL(url); // Since we don't specify args here a KHTMLRun will be used to determine the // mimetype, which will then be passed down at the bottom of processObjectRequest // inside URLArgs to the part. In our particular case, this means that we can // use that inside KHTMLPart::openUrl to route things appropriately. child->m_bCompleted = false; if (!requestObject(child, u) && !child->m_run) { child->m_bCompleted = true; } } QString KHTMLPart::requestFrameName() { return QString::fromLatin1("<!--frame %1-->").arg(d->m_frameNameId++); } bool KHTMLPart::loadObjectElement(DOM::HTMLPartContainerElementImpl *frame, const QString &url, const QString &serviceType, const QStringList ¶ms) { //qDebug() << this << "frame=" << frame; khtml::ChildFrame *child = new khtml::ChildFrame; FrameIt it = d->m_objects.insert(d->m_objects.end(), child); (*it)->m_partContainerElement = frame; (*it)->m_type = khtml::ChildFrame::Object; (*it)->m_params = params; KParts::OpenUrlArguments args; args.setMimeType(serviceType); if (!requestObject(*it, completeURL(url), args) && !(*it)->m_run) { (*it)->m_bCompleted = true; return false; } return true; } bool KHTMLPart::requestObject(khtml::ChildFrame *child, const QUrl &url, const KParts::OpenUrlArguments &_args, const KParts::BrowserArguments &browserArgs) { // we always permit javascript: URLs here since they're basically just // empty pages (and checkLinkSecurity/KAuthorized doesn't know what to do with them) if (!d->isJavaScriptURL(url.toString()) && !checkLinkSecurity(url)) { // qDebug() << this << "checkLinkSecurity refused"; return false; } if (d->m_bClearing) { return false; } if (child->m_bPreloaded) { if (child->m_partContainerElement && child->m_part) { child->m_partContainerElement.data()->setWidget(child->m_part.data()->widget()); } child->m_bPreloaded = false; return true; } //qDebug() << "child=" << child << "child->m_part=" << child->m_part; KParts::OpenUrlArguments args(_args); if (child->m_run) { // qDebug() << "navigating ChildFrame while mimetype resolution was in progress..."; child->m_run.data()->abort(); } // ### Dubious -- the whole dir/ vs. img thing if (child->m_part && !args.reload() && areUrlsForSamePage(child->m_part.data()->url(), url)) { args.setMimeType(child->m_serviceType); } child->m_browserArgs = browserArgs; child->m_args = args; // reload/soft-reload arguments are always inherited from parent child->m_args.setReload(arguments().reload()); child->m_browserArgs.softReload = d->m_extension->browserArguments().softReload; child->m_serviceName.clear(); if (!d->m_referrer.isEmpty() && !child->m_args.metaData().contains("referrer")) { child->m_args.metaData()["referrer"] = d->m_referrer; } child->m_args.metaData().insert("PropagateHttpHeader", "true"); child->m_args.metaData().insert("ssl_parent_ip", d->m_ssl_parent_ip); child->m_args.metaData().insert("ssl_parent_cert", d->m_ssl_parent_cert); child->m_args.metaData().insert("main_frame_request", parentPart() == nullptr ? "TRUE" : "FALSE"); child->m_args.metaData().insert("ssl_was_in_use", d->m_ssl_in_use ? "TRUE" : "FALSE"); child->m_args.metaData().insert("ssl_activate_warnings", "TRUE"); child->m_args.metaData().insert("cross-domain", toplevelURL().toString()); // We know the frame will be text/html if the HTML says <frame src=""> or <frame src="about:blank">, // no need to KHTMLRun to figure out the mimetype" // ### What if we're inside an XML document? if ((url.isEmpty() || url.toString() == "about:blank" || url.scheme() == "javascript") && args.mimeType().isEmpty()) { args.setMimeType(QLatin1String("text/html")); } if (args.mimeType().isEmpty()) { // qDebug() << "Running new KHTMLRun for" << this << "and child=" << child; child->m_run = new KHTMLRun(this, child, url, child->m_args, child->m_browserArgs, true); d->m_bComplete = false; // ensures we stop it in checkCompleted... return false; } else { return processObjectRequest(child, url, args.mimeType()); } } void KHTMLPart::childLoadFailure(khtml::ChildFrame *child) { child->m_bCompleted = true; if (child->m_partContainerElement) { child->m_partContainerElement.data()->partLoadingErrorNotify(); } checkCompleted(); } bool KHTMLPart::processObjectRequest(khtml::ChildFrame *child, const QUrl &_url, const QString &mimetype) { // qDebug() << "trying to create part for" << mimetype << _url; // IMPORTANT: create a copy of the url here, because it is just a reference, which was likely to be given // by an emitting frame part (emit openUrlRequest( blahurl, ... ) . A few lines below we delete the part // though -> the reference becomes invalid -> crash is likely QUrl url(_url); // khtmlrun called us with empty url + mimetype to indicate a loading error, // we obviosuly failed; but we can return true here since we don't want it // doing anything more, while childLoadFailure is enough to notify our kid. if (d->m_onlyLocalReferences || (url.isEmpty() && mimetype.isEmpty())) { childLoadFailure(child); return true; } // we also want to ignore any spurious requests due to closing when parser is being cleared. These should be // ignored entirely --- the tail end of ::clear will clean things up. if (d->m_bClearing) { return false; } if (child->m_bNotify) { child->m_bNotify = false; if (!child->m_browserArgs.lockHistory()) { emit d->m_extension->openUrlNotify(); } } QMimeDatabase db; // Now, depending on mimetype and current state of the world, we may have // to create a new part or ask the user to save things, etc. // // We need a new part if there isn't one at all (doh) or the one that's there // is not for the mimetype we're loading. // // For these new types, we may have to ask the user to save it or not // (we don't if it's navigating the same type). // Further, we will want to ask if content-disposition suggests we ask for // saving, even if we're re-navigating. if (!child->m_part || child->m_serviceType != mimetype || (child->m_run && child->m_run.data()->serverSuggestsSave())) { // We often get here if we didn't know the mimetype in advance, and had to rely // on KRun to figure it out. In this case, we let the element check if it wants to // handle this mimetype itself, for e.g. objects containing images. if (child->m_partContainerElement && child->m_partContainerElement.data()->mimetypeHandledInternally(mimetype)) { child->m_bCompleted = true; checkCompleted(); return true; } // Before attempting to load a part, check if the user wants that. // Many don't like getting ZIP files embedded. // However we don't want to ask for flash and other plugin things. // // Note: this is fine for frames, since we will merely effectively ignore // the navigation if this happens if (child->m_type != khtml::ChildFrame::Object && child->m_type != khtml::ChildFrame::IFrame) { QString suggestedFileName; int disposition = 0; if (KHTMLRun *run = child->m_run.data()) { suggestedFileName = run->suggestedFileName(); disposition = run->serverSuggestsSave() ? KParts::BrowserRun::AttachmentDisposition : KParts::BrowserRun::InlineDisposition; } KParts::BrowserOpenOrSaveQuestion dlg(widget(), url, mimetype); dlg.setSuggestedFileName(suggestedFileName); const KParts::BrowserOpenOrSaveQuestion::Result res = dlg.askEmbedOrSave(disposition); switch (res) { case KParts::BrowserOpenOrSaveQuestion::Save: KHTMLPopupGUIClient::saveURL(widget(), i18n("Save As"), url, child->m_args.metaData(), QString(), 0, suggestedFileName); // fall-through case KParts::BrowserOpenOrSaveQuestion::Cancel: child->m_bCompleted = true; checkCompleted(); return true; // done default: // Embed break; } } // Now, for frames and iframes, we always create a KHTMLPart anyway, // doing it in advance when registering the frame. So we want the // actual creation only for objects here. if (child->m_type == khtml::ChildFrame::Object) { QMimeType mime = db.mimeTypeForName(mimetype); if (mime.isValid()) { // Even for objects, however, we want to force a KHTMLPart for // html & xml, even if the normally preferred part is another one, // so that we can script the target natively via contentDocument method. if (mime.inherits("text/html") || mime.inherits("application/xml")) { // this includes xhtml and svg child->m_serviceName = "khtml"; } else { if (!pluginsEnabled()) { childLoadFailure(child); return false; } } } QStringList dummy; // the list of servicetypes handled by the part is now unused. KParts::ReadOnlyPart *part = createPart(d->m_view->viewport(), this, mimetype, child->m_serviceName, dummy, child->m_params); if (!part) { childLoadFailure(child); return false; } connectToChildPart(child, part, mimetype); } } checkEmitLoadEvent(); // Some JS code in the load event may have destroyed the part // In that case, abort if (!child->m_part) { return false; } if (child->m_bPreloaded) { if (child->m_partContainerElement && child->m_part) { child->m_partContainerElement.data()->setWidget(child->m_part.data()->widget()); } child->m_bPreloaded = false; return true; } // reload/soft-reload arguments are always inherited from parent child->m_args.setReload(arguments().reload()); child->m_browserArgs.softReload = d->m_extension->browserArguments().softReload; // make sure the part has a way to find out about the mimetype. // we actually set it in child->m_args in requestObject already, // but it's useless if we had to use a KHTMLRun instance, as the // point the run object is to find out exactly the mimetype. child->m_args.setMimeType(mimetype); child->m_part.data()->setArguments(child->m_args); // if not a frame set child as completed // ### dubious. child->m_bCompleted = child->m_type == khtml::ChildFrame::Object; if (child->m_extension) { child->m_extension.data()->setBrowserArguments(child->m_browserArgs); } return navigateChild(child, url); } bool KHTMLPart::navigateLocalProtocol(khtml::ChildFrame * /*child*/, KParts::ReadOnlyPart *inPart, const QUrl &url) { if (!qobject_cast<KHTMLPart *>(inPart)) { return false; } KHTMLPart *p = static_cast<KHTMLPart *>(static_cast<KParts::ReadOnlyPart *>(inPart)); p->begin(); // We may have to re-propagate the domain here if we go here due to navigation d->propagateInitialDomainAndBaseTo(p); // Support for javascript: sources if (d->isJavaScriptURL(url.toString())) { // See if we want to replace content with javascript: output.. QVariant res = p->executeScript(DOM::Node(), d->codeForJavaScriptURL(url.toString())); if (res.type() == QVariant::String && p->d->m_redirectURL.isEmpty()) { p->begin(); p->setAlwaysHonourDoctype(); // Disable public API compat; it messes with doctype // We recreated the document, so propagate domain again. d->propagateInitialDomainAndBaseTo(p); p->write(res.toString()); p->end(); } } else { p->setUrl(url); // we need a body element. testcase: <iframe id="a"></iframe><script>alert(a.document.body);</script> p->write("<HTML><TITLE>"); } p->end(); // we don't need to worry about child completion explicitly for KHTMLPart... // or do we? return true; } bool KHTMLPart::navigateChild(khtml::ChildFrame *child, const QUrl &url) { if (url.scheme() == "javascript" || url.toString() == "about:blank") { return navigateLocalProtocol(child, child->m_part.data(), url); } else if (!url.isEmpty()) { // qDebug() << "opening" << url << "in frame" << child->m_part; bool b = child->m_part.data()->openUrl(url); if (child->m_bCompleted) { checkCompleted(); } return b; } else { // empty URL -> no need to navigate child->m_bCompleted = true; checkCompleted(); return true; } } void KHTMLPart::connectToChildPart(khtml::ChildFrame *child, KParts::ReadOnlyPart *part, const QString &mimetype) { // qDebug() << "we:" << this << "kid:" << child << part << mimetype; part->setObjectName(child->m_name); // Cleanup any previous part for this childframe and its connections if (KParts::ReadOnlyPart *p = child->m_part.data()) { if (!qobject_cast(p) && child->m_jscript) { child->m_jscript->clear(); } partManager()->removePart(p); delete p; child->m_scriptable.clear(); } child->m_part = part; child->m_serviceType = mimetype; if (child->m_partContainerElement && part->widget()) { child->m_partContainerElement.data()->setWidget(part->widget()); } if (child->m_type != khtml::ChildFrame::Object) { partManager()->addPart(part, false); } // else // qDebug() << "AH! NO FRAME!!!!!"; if (qobject_cast(part)) { static_cast(part)->d->m_frame = child; } else if (child->m_partContainerElement) { // See if this can be scripted.. KParts::ScriptableExtension *scriptExt = KParts::ScriptableExtension::childObject(part); if (!scriptExt) { // Try to fall back to LiveConnectExtension compat KParts::LiveConnectExtension *lc = KParts::LiveConnectExtension::childObject(part); if (lc) { scriptExt = KParts::ScriptableExtension::adapterFromLiveConnect(part, lc); } } if (scriptExt) { scriptExt->setHost(d->m_scriptableExtension); } child->m_scriptable = scriptExt; } KParts::StatusBarExtension *sb = KParts::StatusBarExtension::childObject(part); if (sb) { sb->setStatusBar(d->m_statusBarExtension->statusBar()); } connect(part, SIGNAL(started(KIO::Job*)), this, SLOT(slotChildStarted(KIO::Job*))); connect(part, SIGNAL(completed()), this, SLOT(slotChildCompleted())); connect(part, SIGNAL(completed(bool)), this, SLOT(slotChildCompleted(bool))); connect(part, SIGNAL(setStatusBarText(QString)), this, SIGNAL(setStatusBarText(QString))); if (part->inherits("KHTMLPart")) { connect(this, SIGNAL(completed()), part, SLOT(slotParentCompleted())); connect(this, SIGNAL(completed(bool)), part, SLOT(slotParentCompleted())); // As soon as the child's document is created, we need to set its domain // (but we do so only once, so it can't be simply done in the child) connect(part, SIGNAL(docCreated()), this, SLOT(slotChildDocCreated())); } child->m_extension = KParts::BrowserExtension::childObject(part); if (KParts::BrowserExtension *kidBrowserExt = child->m_extension.data()) { connect(kidBrowserExt, SIGNAL(openUrlNotify()), d->m_extension, SIGNAL(openUrlNotify())); connect(kidBrowserExt, SIGNAL(openUrlRequestDelayed(QUrl,KParts::OpenUrlArguments,KParts::BrowserArguments)), this, SLOT(slotChildURLRequest(QUrl,KParts::OpenUrlArguments,KParts::BrowserArguments))); connect(kidBrowserExt, SIGNAL(createNewWindow(QUrl,KParts::OpenUrlArguments,KParts::BrowserArguments,KParts::WindowArgs,KParts::ReadOnlyPart**)), d->m_extension, SIGNAL(createNewWindow(QUrl,KParts::OpenUrlArguments,KParts::BrowserArguments,KParts::WindowArgs,KParts::ReadOnlyPart**))); connect(kidBrowserExt, SIGNAL(popupMenu(QPoint,KFileItemList,KParts::OpenUrlArguments,KParts::BrowserArguments,KParts::BrowserExtension::PopupFlags,KParts::BrowserExtension::ActionGroupMap)), d->m_extension, SIGNAL(popupMenu(QPoint,KFileItemList,KParts::OpenUrlArguments,KParts::BrowserArguments,KParts::BrowserExtension::PopupFlags,KParts::BrowserExtension::ActionGroupMap))); connect(kidBrowserExt, SIGNAL(popupMenu(QPoint,QUrl,mode_t,KParts::OpenUrlArguments,KParts::BrowserArguments,KParts::BrowserExtension::PopupFlags,KParts::BrowserExtension::ActionGroupMap)), d->m_extension, SIGNAL(popupMenu(QPoint,QUrl,mode_t,KParts::OpenUrlArguments,KParts::BrowserArguments,KParts::BrowserExtension::PopupFlags,KParts::BrowserExtension::ActionGroupMap))); connect(kidBrowserExt, SIGNAL(infoMessage(QString)), d->m_extension, SIGNAL(infoMessage(QString))); connect(kidBrowserExt, SIGNAL(requestFocus(KParts::ReadOnlyPart*)), this, SLOT(slotRequestFocus(KParts::ReadOnlyPart*))); kidBrowserExt->setBrowserInterface(d->m_extension->browserInterface()); } } KParts::ReadOnlyPart *KHTMLPart::createPart(QWidget *parentWidget, QObject *parent, const QString &mimetype, QString &serviceName, QStringList &serviceTypes, const QStringList ¶ms) { QString constr; if (!serviceName.isEmpty()) { constr.append(QString::fromLatin1("DesktopEntryName == '%1'").arg(serviceName)); } KService::List offers = KMimeTypeTrader::self()->query(mimetype, "KParts/ReadOnlyPart", constr); if (offers.isEmpty()) { int pos = mimetype.indexOf("-plugin"); if (pos < 0) { return nullptr; } QString stripped_mime = mimetype.left(pos); offers = KMimeTypeTrader::self()->query(stripped_mime, "KParts/ReadOnlyPart", constr); if (offers.isEmpty()) { return nullptr; } } KService::List::ConstIterator it = offers.constBegin(); const KService::List::ConstIterator itEnd = offers.constEnd(); for (; it != itEnd; ++it) { KService::Ptr service = (*it); KPluginLoader loader(*service); KPluginFactory *const factory = loader.factory(); if (factory) { // Turn params into a QVariantList as expected by KPluginFactory QVariantList variantlist; Q_FOREACH (const QString &str, params) { variantlist << QVariant(str); } if (service->serviceTypes().contains("Browser/View")) { variantlist << QString("Browser/View"); } KParts::ReadOnlyPart *part = factory->create(parentWidget, parent, QString(), variantlist); if (part) { serviceTypes = service->serviceTypes(); serviceName = service->name(); return part; } } else { // TODO KMessageBox::error and i18n, like in KonqFactory::createView? qWarning() << QString("There was an error loading the module %1.\nThe diagnostics is:\n%2") .arg(service->name()).arg(loader.errorString()); } } return nullptr; } KParts::PartManager *KHTMLPart::partManager() { if (!d->m_manager && d->m_view) { d->m_manager = new KParts::PartManager(d->m_view->topLevelWidget(), this); d->m_manager->setObjectName("khtml part manager"); d->m_manager->setAllowNestedParts(true); connect(d->m_manager, SIGNAL(activePartChanged(KParts::Part*)), this, SLOT(slotActiveFrameChanged(KParts::Part*))); connect(d->m_manager, SIGNAL(partRemoved(KParts::Part*)), this, SLOT(slotPartRemoved(KParts::Part*))); } return d->m_manager; } void KHTMLPart::submitFormAgain() { disconnect(this, SIGNAL(completed()), this, SLOT(submitFormAgain())); if (d->m_doc && !d->m_doc->parsing() && d->m_submitForm) { KHTMLPart::submitForm(d->m_submitForm->submitAction, d->m_submitForm->submitUrl, d->m_submitForm->submitFormData, d->m_submitForm->target, d->m_submitForm->submitContentType, d->m_submitForm->submitBoundary); } delete d->m_submitForm; d->m_submitForm = nullptr; } void KHTMLPart::submitFormProxy(const char *action, const QString &url, const QByteArray &formData, const QString &_target, const QString &contentType, const QString &boundary) { submitForm(action, url, formData, _target, contentType, boundary); } void KHTMLPart::submitForm(const char *action, const QString &url, const QByteArray &formData, const QString &_target, const QString &contentType, const QString &boundary) { // qDebug() << this << "target=" << _target << "url=" << url; if (d->m_formNotification == KHTMLPart::Only) { emit formSubmitNotification(action, url, formData, _target, contentType, boundary); return; } else if (d->m_formNotification == KHTMLPart::Before) { emit formSubmitNotification(action, url, formData, _target, contentType, boundary); } QUrl u = completeURL(url); if (!u.isValid()) { // ### ERROR HANDLING! return; } // Form security checks // /* * If these form security checks are still in this place in a month or two * I'm going to simply delete them. */ /* This is separate for a reason. It has to be _before_ all script, etc, * AND I don't want to break anything that uses checkLinkSecurity() in * other places. */ if (!d->m_submitForm) { if (u.scheme() != "https" && u.scheme() != "mailto") { if (d->m_ssl_in_use) { // Going from SSL -> nonSSL int rc = KMessageBox::warningContinueCancel(nullptr, i18n("Warning: This is a secure form but it is attempting to send your data back unencrypted." "\nA third party may be able to intercept and view this information." "\nAre you sure you wish to continue?"), i18n("Network Transmission"), KGuiItem(i18n("&Send Unencrypted"))); if (rc == KMessageBox::Cancel) { return; } } else { // Going from nonSSL -> nonSSL KSSLSettings kss(true); if (kss.warnOnUnencrypted()) { int rc = KMessageBox::warningContinueCancel(nullptr, i18n("Warning: Your data is about to be transmitted across the network unencrypted." "\nAre you sure you wish to continue?"), i18n("Network Transmission"), KGuiItem(i18n("&Send Unencrypted")), KStandardGuiItem::cancel(), "WarnOnUnencryptedForm"); // Move this setting into KSSL instead QString grpNotifMsgs = QLatin1String("Notification Messages"); KConfigGroup cg(KSharedConfig::openConfig(), grpNotifMsgs); if (!cg.readEntry("WarnOnUnencryptedForm", true)) { cg.deleteEntry("WarnOnUnencryptedForm"); cg.sync(); kss.setWarnOnUnencrypted(false); kss.save(); } if (rc == KMessageBox::Cancel) { return; } } } } if (u.scheme() == "mailto") { int rc = KMessageBox::warningContinueCancel(nullptr, i18n("This site is attempting to submit form data via email.\n" "Do you want to continue?"), i18n("Network Transmission"), KGuiItem(i18n("&Send Email")), KStandardGuiItem::cancel(), "WarnTriedEmailSubmit"); if (rc == KMessageBox::Cancel) { return; } } } // End form security checks // QString urlstring = u.toString(); if (d->isJavaScriptURL(urlstring)) { crossFrameExecuteScript(_target, d->codeForJavaScriptURL(urlstring)); return; } if (!checkLinkSecurity(u, ki18n("The form will be submitted to
    %1
    on your local filesystem.
    Do you want to submit the form?
    "), i18n("Submit"))) { return; } // OK. We're actually going to submit stuff. Clear any redirections, // we should win over them d->clearRedirection(); KParts::OpenUrlArguments args; if (!d->m_referrer.isEmpty()) { args.metaData()["referrer"] = d->m_referrer; } args.metaData().insert("PropagateHttpHeader", "true"); args.metaData().insert("ssl_parent_ip", d->m_ssl_parent_ip); args.metaData().insert("ssl_parent_cert", d->m_ssl_parent_cert); args.metaData().insert("main_frame_request", parentPart() == nullptr ? "TRUE" : "FALSE"); args.metaData().insert("ssl_was_in_use", d->m_ssl_in_use ? "TRUE" : "FALSE"); args.metaData().insert("ssl_activate_warnings", "TRUE"); //WABA: When we post a form we should treat it as the main url //the request should never be considered cross-domain //args.metaData().insert("cross-domain", toplevelURL().toString()); KParts::BrowserArguments browserArgs; browserArgs.frameName = _target.isEmpty() ? d->m_doc->baseTarget() : _target; // Handle mailto: forms if (u.scheme() == "mailto") { // 1) Check for attach= and strip it QString q = u.query().mid(1); QStringList nvps = q.split("&"); bool triedToAttach = false; QStringList::Iterator nvp = nvps.begin(); const QStringList::Iterator nvpEnd = nvps.end(); // cannot be a for loop as if something is removed we don't want to do ++nvp, as // remove returns an iterator pointing to the next item while (nvp != nvpEnd) { const QStringList pair = (*nvp).split("="); if (pair.count() >= 2) { if (pair.first().toLower() == "attach") { nvp = nvps.erase(nvp); triedToAttach = true; } else { ++nvp; } } else { ++nvp; } } if (triedToAttach) { KMessageBox::information(nullptr, i18n("This site attempted to attach a file from your computer in the form submission. The attachment was removed for your protection."), i18n("KDE"), "WarnTriedAttach"); } // 2) Append body= QString bodyEnc; if (contentType.toLower() == "multipart/form-data") { // FIXME: is this correct? I suspect not bodyEnc = QLatin1String(QUrl::toPercentEncoding(QString::fromLatin1(formData.data(), formData.size()))); } else if (contentType.toLower() == "text/plain") { // Convention seems to be to decode, and s/&/\n/ QString tmpbody = QString::fromLatin1(formData.data(), formData.size()); tmpbody.replace(QRegExp("[&]"), "\n"); tmpbody.replace(QRegExp("[+]"), " "); tmpbody = QUrl::fromPercentEncoding(tmpbody.toLatin1()); // Decode the rest of it bodyEnc = QLatin1String(QUrl::toPercentEncoding(tmpbody)); // Recode for the URL } else { bodyEnc = QLatin1String(QUrl::toPercentEncoding(QString::fromLatin1(formData.data(), formData.size()))); } nvps.append(QString("body=%1").arg(bodyEnc)); q = nvps.join("&"); u.setQuery(q); } if (strcmp(action, "get") == 0) { if (u.scheme() != "mailto") { u.setQuery(QString::fromLatin1(formData.data(), formData.size())); } browserArgs.setDoPost(false); } else { browserArgs.postData = formData; browserArgs.setDoPost(true); // construct some user headers if necessary if (contentType.isNull() || contentType == "application/x-www-form-urlencoded") { browserArgs.setContentType("Content-Type: application/x-www-form-urlencoded"); } else { // contentType must be "multipart/form-data" browserArgs.setContentType("Content-Type: " + contentType + "; boundary=" + boundary); } } if (d->m_doc->parsing() || d->m_runningScripts > 0) { if (d->m_submitForm) { // qDebug() << "ABORTING!"; return; } d->m_submitForm = new KHTMLPartPrivate::SubmitForm; d->m_submitForm->submitAction = action; d->m_submitForm->submitUrl = url; d->m_submitForm->submitFormData = formData; d->m_submitForm->target = _target; d->m_submitForm->submitContentType = contentType; d->m_submitForm->submitBoundary = boundary; connect(this, SIGNAL(completed()), this, SLOT(submitFormAgain())); } else { emit d->m_extension->openUrlRequest(u, args, browserArgs); } } void KHTMLPart::popupMenu(const QString &linkUrl) { QUrl popupURL; QUrl linkKUrl; KParts::OpenUrlArguments args; KParts::BrowserArguments browserArgs; QString referrer; KParts::BrowserExtension::PopupFlags itemflags = KParts::BrowserExtension::ShowBookmark | KParts::BrowserExtension::ShowReload; if (linkUrl.isEmpty()) { // click on background KHTMLPart *khtmlPart = this; while (khtmlPart->parentPart()) { khtmlPart = khtmlPart->parentPart(); } popupURL = khtmlPart->url(); referrer = khtmlPart->pageReferrer(); if (hasSelection()) { itemflags = KParts::BrowserExtension::ShowTextSelectionItems; } else { itemflags |= KParts::BrowserExtension::ShowNavigationItems; } } else { // click on link popupURL = completeURL(linkUrl); linkKUrl = popupURL; referrer = this->referrer(); itemflags |= KParts::BrowserExtension::IsLink; if (!(d->m_strSelectedURLTarget).isEmpty() && (d->m_strSelectedURLTarget.toLower() != "_top") && (d->m_strSelectedURLTarget.toLower() != "_self") && (d->m_strSelectedURLTarget.toLower() != "_parent")) { if (d->m_strSelectedURLTarget.toLower() == "_blank") { browserArgs.setForcesNewWindow(true); } else { KHTMLPart *p = this; while (p->parentPart()) { p = p->parentPart(); } if (!p->frameExists(d->m_strSelectedURLTarget)) { browserArgs.setForcesNewWindow(true); } } } } QMimeDatabase db; // Danger, Will Robinson. The Popup might stay around for a much // longer time than KHTMLPart. Deal with it. KHTMLPopupGUIClient *client = new KHTMLPopupGUIClient(this, linkKUrl); QPointer guard(client); QString mimetype = QLatin1String("text/html"); args.metaData()["referrer"] = referrer; if (!linkUrl.isEmpty()) { // over a link if (popupURL.isLocalFile()) { // safe to do this mimetype = db.mimeTypeForUrl(popupURL).name(); } else { // look at "extension" of link const QString fname(popupURL.fileName()); if (!fname.isEmpty() && !popupURL.hasFragment() && popupURL.query().isEmpty()) { QMimeType pmt = db.mimeTypeForFile(fname, QMimeDatabase::MatchExtension); // Further check for mime types guessed from the extension which, // on a web page, are more likely to be a script delivering content // of undecidable type. If the mime type from the extension is one // of these, don't use it. Retain the original type 'text/html'. if (!pmt.isDefault() && !pmt.inherits("application/x-perl") && !pmt.inherits("application/x-perl-module") && !pmt.inherits("application/x-php") && !pmt.inherits("application/x-python-bytecode") && !pmt.inherits("application/x-python") && !pmt.inherits("application/x-shellscript")) { mimetype = pmt.name(); } } } } args.setMimeType(mimetype); emit d->m_extension->popupMenu(QCursor::pos(), popupURL, S_IFREG /*always a file*/, args, browserArgs, itemflags, client->actionGroups()); if (!guard.isNull()) { delete client; emit popupMenu(linkUrl, QCursor::pos()); d->m_strSelectedURL.clear(); d->m_strSelectedURLTarget.clear(); } } void KHTMLPart::slotParentCompleted() { //qDebug() << this; if (!d->m_redirectURL.isEmpty() && !d->m_redirectionTimer.isActive()) { //qDebug() << this << ": starting timer for child redirection -> " << d->m_redirectURL; d->m_redirectionTimer.setSingleShot(true); d->m_redirectionTimer.start(qMax(0, 1000 * d->m_delayRedirect)); } } void KHTMLPart::slotChildStarted(KIO::Job *job) { khtml::ChildFrame *child = frame(sender()); assert(child); child->m_bCompleted = false; if (d->m_bComplete) { #if 0 // WABA: Looks like this belongs somewhere else if (!parentPart()) { // "toplevel" html document? if yes, then notify the hosting browser about the document (url) changes emit d->m_extension->openURLNotify(); } #endif d->m_bComplete = false; emit started(job); } } void KHTMLPart::slotChildCompleted() { slotChildCompleted(false); } void KHTMLPart::slotChildCompleted(bool pendingAction) { khtml::ChildFrame *child = frame(sender()); if (child) { // qDebug() << this << "child=" << child << "m_partContainerElement=" << child->m_partContainerElement; child->m_bCompleted = true; child->m_bPendingRedirection = pendingAction; child->m_args = KParts::OpenUrlArguments(); child->m_browserArgs = KParts::BrowserArguments(); // dispatch load event. We don't do that for KHTMLPart's since their internal // load will be forwarded inside NodeImpl::dispatchWindowEvent if (!qobject_cast(child->m_part)) { QTimer::singleShot(0, child->m_partContainerElement.data(), SLOT(slotEmitLoadEvent())); } } checkCompleted(); } void KHTMLPart::slotChildDocCreated() { // Set domain to the frameset's domain // This must only be done when loading the frameset initially (#22039), // not when following a link in a frame (#44162). if (KHTMLPart *htmlFrame = qobject_cast(sender())) { d->propagateInitialDomainAndBaseTo(htmlFrame); } // So it only happens once disconnect(sender(), SIGNAL(docCreated()), this, SLOT(slotChildDocCreated())); } void KHTMLPartPrivate::propagateInitialDomainAndBaseTo(KHTMLPart *kid) { // This method is used to propagate our domain and base information for // child frames, to provide them for about: or JavaScript: URLs if (m_doc && kid->d->m_doc) { DocumentImpl *kidDoc = kid->d->m_doc; if (kidDoc->origin()->isEmpty()) { kidDoc->setOrigin(m_doc->origin()); kidDoc->setBaseURL(m_doc->baseURL()); } } } void KHTMLPart::slotChildURLRequest(const QUrl &url, const KParts::OpenUrlArguments &args, const KParts::BrowserArguments &browserArgs) { khtml::ChildFrame *child = frame(sender()->parent()); KHTMLPart *callingHtmlPart = const_cast(dynamic_cast(sender()->parent())); // TODO: handle child target correctly! currently the script are always executed for the parent QString urlStr = url.toString(); if (d->isJavaScriptURL(urlStr)) { executeScript(DOM::Node(), d->codeForJavaScriptURL(urlStr)); return; } QString frameName = browserArgs.frameName.toLower(); if (!frameName.isEmpty()) { if (frameName == QLatin1String("_top")) { emit d->m_extension->openUrlRequest(url, args, browserArgs); return; } else if (frameName == QLatin1String("_blank")) { emit d->m_extension->createNewWindow(url, args, browserArgs); return; } else if (frameName == QLatin1String("_parent")) { KParts::BrowserArguments newBrowserArgs(browserArgs); newBrowserArgs.frameName.clear(); emit d->m_extension->openUrlRequest(url, args, newBrowserArgs); return; } else if (frameName != QLatin1String("_self")) { khtml::ChildFrame *_frame = recursiveFrameRequest(callingHtmlPart, url, args, browserArgs); if (!_frame) { emit d->m_extension->openUrlRequest(url, args, browserArgs); return; } child = _frame; } } if (child && child->m_type != khtml::ChildFrame::Object) { // Inform someone that we are about to show something else. child->m_bNotify = true; requestObject(child, url, args, browserArgs); } else if (frameName == "_self") { // this is for embedded objects (via ) which want to replace the current document KParts::BrowserArguments newBrowserArgs(browserArgs); newBrowserArgs.frameName.clear(); emit d->m_extension->openUrlRequest(url, args, newBrowserArgs); } } void KHTMLPart::slotRequestFocus(KParts::ReadOnlyPart *) { emit d->m_extension->requestFocus(this); } khtml::ChildFrame *KHTMLPart::frame(const QObject *obj) { assert(obj->inherits("KParts::ReadOnlyPart")); const KParts::ReadOnlyPart *const part = static_cast(obj); FrameIt it = d->m_frames.begin(); const FrameIt end = d->m_frames.end(); for (; it != end; ++it) { if ((*it)->m_part.data() == part) { return *it; } } FrameIt oi = d->m_objects.begin(); const FrameIt oiEnd = d->m_objects.end(); for (; oi != oiEnd; ++oi) { if ((*oi)->m_part.data() == part) { return *oi; } } return nullptr; } //#define DEBUG_FINDFRAME bool KHTMLPart::checkFrameAccess(KHTMLPart *callingHtmlPart) { if (callingHtmlPart == this) { return true; // trivial } if (!xmlDocImpl()) { #ifdef DEBUG_FINDFRAME qDebug() << "Empty part" << this << "URL = " << url(); #endif return false; // we are empty? } // now compare the domains if (callingHtmlPart && callingHtmlPart->xmlDocImpl() && xmlDocImpl()) { khtml::SecurityOrigin *actDomain = callingHtmlPart->xmlDocImpl()->origin(); khtml::SecurityOrigin *destDomain = xmlDocImpl()->origin(); if (actDomain->canAccess(destDomain)) { return true; } } #ifdef DEBUG_FINDFRAME else { qDebug() << "Unknown part/domain" << callingHtmlPart << "tries to access part" << this; } #endif return false; } KHTMLPart * KHTMLPart::findFrameParent(KParts::ReadOnlyPart *callingPart, const QString &f, khtml::ChildFrame **childFrame) { return d->findFrameParent(callingPart, f, childFrame, false); } KHTMLPart *KHTMLPartPrivate::findFrameParent(KParts::ReadOnlyPart *callingPart, const QString &f, khtml::ChildFrame **childFrame, bool checkForNavigation) { #ifdef DEBUG_FINDFRAME qDebug() << q << "URL =" << q->url() << "name =" << q->objectName() << "findFrameParent(" << f << ")"; #endif // Check access KHTMLPart *const callingHtmlPart = qobject_cast(callingPart); if (!callingHtmlPart) { return nullptr; } if (!checkForNavigation && !q->checkFrameAccess(callingHtmlPart)) { return nullptr; } if (!childFrame && !q->parentPart() && (q->objectName() == f)) { if (!checkForNavigation || callingHtmlPart->d->canNavigate(q)) { return q; } } FrameIt it = m_frames.find(f); const FrameIt end = m_frames.end(); if (it != end) { #ifdef DEBUG_FINDFRAME qDebug() << "FOUND!"; #endif if (!checkForNavigation || callingHtmlPart->d->canNavigate((*it)->m_part.data())) { if (childFrame) { *childFrame = *it; } return q; } } it = m_frames.begin(); for (; it != end; ++it) { if (KHTMLPart *p = qobject_cast((*it)->m_part.data())) { KHTMLPart *const frameParent = p->d->findFrameParent(callingPart, f, childFrame, checkForNavigation); if (frameParent) { return frameParent; } } } return nullptr; } KHTMLPart *KHTMLPartPrivate::top() { KHTMLPart *t = q; while (t->parentPart()) { t = t->parentPart(); } return t; } bool KHTMLPartPrivate::canNavigate(KParts::ReadOnlyPart *bCand) { if (!bCand) { // No part here (e.g. invalid url), reuse that frame return true; } KHTMLPart *b = qobject_cast(bCand); if (!b) { // Another kind of part? Not sure what to do... return false; } // HTML5 gives conditions for this (a) being able to navigate b // 1) Same domain if (q->checkFrameAccess(b)) { return true; } // 2) A is nested, with B its top if (q->parentPart() && top() == b) { return true; } // 3) B is 'auxilary' -- window.open with opener, // and A can navigate B's opener if (b->opener() && canNavigate(b->opener())) { return true; } // 4) B is not top-level, but an ancestor of it has same origin as A for (KHTMLPart *anc = b->parentPart(); anc; anc = anc->parentPart()) { if (anc->checkFrameAccess(q)) { return true; } } return false; } KHTMLPart *KHTMLPart::findFrame(const QString &f) { khtml::ChildFrame *childFrame; KHTMLPart *parentFrame = findFrameParent(this, f, &childFrame); if (parentFrame) { return qobject_cast(childFrame->m_part.data()); } return nullptr; } KParts::ReadOnlyPart *KHTMLPart::findFramePart(const QString &f) { khtml::ChildFrame *childFrame; return findFrameParent(this, f, &childFrame) ? childFrame->m_part.data() : nullptr; } KParts::ReadOnlyPart *KHTMLPart::currentFrame() const { KParts::ReadOnlyPart *part = (KParts::ReadOnlyPart *)(this); // Find active part in our frame manager, in case we are a frameset // and keep doing that (in case of nested framesets). // Just realized we could also do this recursively, calling part->currentFrame()... while (part && part->inherits("KHTMLPart") && static_cast(part)->d->m_frames.count() > 0) { KHTMLPart *frameset = static_cast(part); part = static_cast(frameset->partManager()->activePart()); if (!part) { return frameset; } } return part; } bool KHTMLPart::frameExists(const QString &frameName) { FrameIt it = d->m_frames.find(frameName); if (it == d->m_frames.end()) { return false; } // WABA: We only return true if the child actually has a frame // set. Otherwise we might find our preloaded-selve. // This happens when we restore the frameset. return (!(*it)->m_partContainerElement.isNull()); } void KHTMLPartPrivate::renameFrameForContainer(DOM::HTMLPartContainerElementImpl *cont, const QString &newName) { for (int i = 0; i < m_frames.size(); ++i) { khtml::ChildFrame *f = m_frames[i]; if (f->m_partContainerElement.data() == cont) { f->m_name = newName; } } } KJSProxy *KHTMLPart::framejScript(KParts::ReadOnlyPart *framePart) { KHTMLPart *const kp = qobject_cast(framePart); if (kp) { return kp->jScript(); } FrameIt it = d->m_frames.begin(); const FrameIt itEnd = d->m_frames.end(); for (; it != itEnd; ++it) { khtml::ChildFrame *frame = *it; if (framePart == frame->m_part.data()) { if (!frame->m_jscript) { frame->m_jscript = new KJSProxy(frame); } return frame->m_jscript; } } return nullptr; } KHTMLPart *KHTMLPart::parentPart() { return qobject_cast(parent()); } khtml::ChildFrame *KHTMLPart::recursiveFrameRequest(KHTMLPart *callingHtmlPart, const QUrl &url, const KParts::OpenUrlArguments &args, const KParts::BrowserArguments &browserArgs, bool callParent) { #ifdef DEBUG_FINDFRAME qDebug() << this << "frame = " << browserArgs.frameName << "url = " << url; #endif khtml::ChildFrame *childFrame; KHTMLPart *childPart = findFrameParent(callingHtmlPart, browserArgs.frameName, &childFrame); if (childPart) { if (childPart == this) { return childFrame; } childPart->requestObject(childFrame, url, args, browserArgs); return nullptr; } if (parentPart() && callParent) { khtml::ChildFrame *res = parentPart()->recursiveFrameRequest(callingHtmlPart, url, args, browserArgs, callParent); if (res) { parentPart()->requestObject(res, url, args, browserArgs); } } return nullptr; } #ifdef DEBUG_SAVESTATE static int s_saveStateIndentLevel = 0; #endif void KHTMLPart::saveState(QDataStream &stream) { #ifdef DEBUG_SAVESTATE QString indent = QString().leftJustified(s_saveStateIndentLevel * 4, ' '); const int indentLevel = s_saveStateIndentLevel++; qDebug() << indent << "saveState this=" << this << " '" << objectName() << "' saving URL " << url(); #endif stream << url() << (qint32)d->m_view->contentsX() << (qint32)d->m_view->contentsY() << (qint32) d->m_view->contentsWidth() << (qint32) d->m_view->contentsHeight() << (qint32) d->m_view->marginWidth() << (qint32) d->m_view->marginHeight(); // save link cursor position int focusNodeNumber; if (!d->m_focusNodeRestored) { focusNodeNumber = d->m_focusNodeNumber; } else if (d->m_doc && d->m_doc->focusNode()) { focusNodeNumber = d->m_doc->nodeAbsIndex(d->m_doc->focusNode()); } else { focusNodeNumber = -1; } stream << focusNodeNumber; // Save the doc's cache id. stream << d->m_cacheId; // Save the state of the document (Most notably the state of any forms) QStringList docState; if (d->m_doc) { docState = d->m_doc->docState(); } stream << d->m_encoding << d->m_sheetUsed << docState; stream << d->m_zoomFactor; stream << d->m_fontScaleFactor; stream << d->m_httpHeaders; stream << d->m_pageServices; stream << d->m_pageReferrer; // Save ssl data stream << d->m_ssl_in_use << d->m_ssl_peer_chain << d->m_ssl_peer_ip << d->m_ssl_cipher << d->m_ssl_protocol_version << d->m_ssl_cipher_used_bits << d->m_ssl_cipher_bits << d->m_ssl_cert_errors << d->m_ssl_parent_ip << d->m_ssl_parent_cert; QStringList frameNameLst, frameServiceTypeLst, frameServiceNameLst; QList frameURLLst; QList frameStateBufferLst; QList frameTypeLst; ConstFrameIt it = d->m_frames.constBegin(); const ConstFrameIt end = d->m_frames.constEnd(); for (; it != end; ++it) { if (!(*it)->m_part) { continue; } frameNameLst << (*it)->m_name; frameServiceTypeLst << (*it)->m_serviceType; frameServiceNameLst << (*it)->m_serviceName; frameURLLst << (*it)->m_part.data()->url(); QByteArray state; QDataStream frameStream(&state, QIODevice::WriteOnly); if ((*it)->m_extension) { (*it)->m_extension.data()->saveState(frameStream); } frameStateBufferLst << state; frameTypeLst << int((*it)->m_type); } // Save frame data stream << (quint32) frameNameLst.count(); stream << frameNameLst << frameServiceTypeLst << frameServiceNameLst << frameURLLst << frameStateBufferLst << frameTypeLst; #ifdef DEBUG_SAVESTATE s_saveStateIndentLevel = indentLevel; #endif } void KHTMLPart::restoreState(QDataStream &stream) { QUrl u; qint32 xOffset, yOffset, wContents, hContents, mWidth, mHeight; quint32 frameCount; QStringList frameNames, frameServiceTypes, docState, frameServiceNames; QList frameTypes; QList frameURLs; QList frameStateBuffers; QList fSizes; QString encoding, sheetUsed; long old_cacheId = d->m_cacheId; stream >> u >> xOffset >> yOffset >> wContents >> hContents >> mWidth >> mHeight; d->m_view->setMarginWidth(mWidth); d->m_view->setMarginHeight(mHeight); // restore link cursor position // nth node is active. value is set in checkCompleted() stream >> d->m_focusNodeNumber; d->m_focusNodeRestored = false; stream >> d->m_cacheId; stream >> encoding >> sheetUsed >> docState; d->m_encoding = encoding; d->m_sheetUsed = sheetUsed; int zoomFactor; stream >> zoomFactor; setZoomFactor(zoomFactor); int fontScaleFactor; stream >> fontScaleFactor; setFontScaleFactor(fontScaleFactor); stream >> d->m_httpHeaders; stream >> d->m_pageServices; stream >> d->m_pageReferrer; // Restore ssl data stream >> d->m_ssl_in_use >> d->m_ssl_peer_chain >> d->m_ssl_peer_ip >> d->m_ssl_cipher >> d->m_ssl_protocol_version >> d->m_ssl_cipher_used_bits >> d->m_ssl_cipher_bits >> d->m_ssl_cert_errors >> d->m_ssl_parent_ip >> d->m_ssl_parent_cert; setPageSecurity(d->m_ssl_in_use ? Encrypted : NotCrypted); stream >> frameCount >> frameNames >> frameServiceTypes >> frameServiceNames >> frameURLs >> frameStateBuffers >> frameTypes; d->m_bComplete = false; d->m_bLoadEventEmitted = false; // qDebug() << "docState.count() = " << docState.count(); // qDebug() << "m_url " << url() << " <-> " << u; // qDebug() << "m_frames.count() " << d->m_frames.count() << " <-> " << frameCount; if (d->m_cacheId == old_cacheId && signed(frameCount) == d->m_frames.count()) { // Partial restore d->m_redirectionTimer.stop(); FrameIt fIt = d->m_frames.begin(); const FrameIt fEnd = d->m_frames.end(); for (; fIt != fEnd; ++fIt) { (*fIt)->m_bCompleted = false; } fIt = d->m_frames.begin(); QStringList::ConstIterator fNameIt = frameNames.constBegin(); QStringList::ConstIterator fServiceTypeIt = frameServiceTypes.constBegin(); QStringList::ConstIterator fServiceNameIt = frameServiceNames.constBegin(); QList::ConstIterator fURLIt = frameURLs.constBegin(); QList::ConstIterator fBufferIt = frameStateBuffers.constBegin(); QList::ConstIterator fFrameTypeIt = frameTypes.constBegin(); for (; fIt != fEnd; ++fIt, ++fNameIt, ++fServiceTypeIt, ++fServiceNameIt, ++fURLIt, ++fBufferIt, ++fFrameTypeIt) { khtml::ChildFrame *const child = *fIt; // qDebug() << *fNameIt << " ---- " << *fServiceTypeIt; if (child->m_name != *fNameIt || child->m_serviceType != *fServiceTypeIt) { child->m_bPreloaded = true; child->m_name = *fNameIt; child->m_serviceName = *fServiceNameIt; child->m_type = static_cast(*fFrameTypeIt); processObjectRequest(child, *fURLIt, *fServiceTypeIt); } if (child->m_part) { child->m_bCompleted = false; if (child->m_extension && !(*fBufferIt).isEmpty()) { QDataStream frameStream(*fBufferIt); child->m_extension.data()->restoreState(frameStream); } else { child->m_part.data()->openUrl(*fURLIt); } } } KParts::OpenUrlArguments args(arguments()); args.setXOffset(xOffset); args.setYOffset(yOffset); setArguments(args); KParts::BrowserArguments browserArgs(d->m_extension->browserArguments()); browserArgs.docState = docState; d->m_extension->setBrowserArguments(browserArgs); d->m_view->resizeContents(wContents, hContents); d->m_view->setContentsPos(xOffset, yOffset); setUrl(u); } else { // Full restore. closeUrl(); // We must force a clear because we want to be sure to delete all // frames. d->m_bCleared = false; clear(); d->m_encoding = encoding; d->m_sheetUsed = sheetUsed; QStringList::ConstIterator fNameIt = frameNames.constBegin(); const QStringList::ConstIterator fNameEnd = frameNames.constEnd(); QStringList::ConstIterator fServiceTypeIt = frameServiceTypes.constBegin(); QStringList::ConstIterator fServiceNameIt = frameServiceNames.constBegin(); QList::ConstIterator fURLIt = frameURLs.constBegin(); QList::ConstIterator fBufferIt = frameStateBuffers.constBegin(); QList::ConstIterator fFrameTypeIt = frameTypes.constBegin(); for (; fNameIt != fNameEnd; ++fNameIt, ++fServiceTypeIt, ++fServiceNameIt, ++fURLIt, ++fBufferIt, ++fFrameTypeIt) { khtml::ChildFrame *const newChild = new khtml::ChildFrame; newChild->m_bPreloaded = true; newChild->m_name = *fNameIt; newChild->m_serviceName = *fServiceNameIt; newChild->m_type = static_cast(*fFrameTypeIt); // qDebug() << *fNameIt << " ---- " << *fServiceTypeIt; const FrameIt childFrame = d->m_frames.insert(d->m_frames.end(), newChild); processObjectRequest(*childFrame, *fURLIt, *fServiceTypeIt); (*childFrame)->m_bPreloaded = true; if ((*childFrame)->m_part) { if ((*childFrame)->m_extension && !(*fBufferIt).isEmpty()) { QDataStream frameStream(*fBufferIt); (*childFrame)->m_extension.data()->restoreState(frameStream); } else { (*childFrame)->m_part.data()->openUrl(*fURLIt); } } } KParts::OpenUrlArguments args(arguments()); args.setXOffset(xOffset); args.setYOffset(yOffset); setArguments(args); KParts::BrowserArguments browserArgs(d->m_extension->browserArguments()); browserArgs.docState = docState; d->m_extension->setBrowserArguments(browserArgs); if (!KHTMLPageCache::self()->isComplete(d->m_cacheId)) { d->m_restored = true; openUrl(u); d->m_restored = false; } else { restoreURL(u); } } } void KHTMLPart::show() { if (widget()) { widget()->show(); } } void KHTMLPart::hide() { if (widget()) { widget()->hide(); } } DOM::Node KHTMLPart::nodeUnderMouse() const { return d->m_view->nodeUnderMouse(); } DOM::Node KHTMLPart::nonSharedNodeUnderMouse() const { return d->m_view->nonSharedNodeUnderMouse(); } void KHTMLPart::emitSelectionChanged() { // Don't emit signals about our selection if this is a frameset; // the active frame has the selection (#187403) if (!d->m_activeFrame) { emit d->m_extension->enableAction("copy", hasSelection()); emit d->m_extension->selectionInfo(selectedText()); emit selectionChanged(); } } int KHTMLPart::zoomFactor() const { return d->m_zoomFactor; } // ### make the list configurable ? static const int zoomSizes[] = { 20, 40, 60, 80, 90, 95, 100, 105, 110, 120, 140, 160, 180, 200, 250, 300 }; static const int zoomSizeCount = (sizeof(zoomSizes) / sizeof(int)); static const int minZoom = 20; static const int maxZoom = 300; // My idea of useful stepping ;-) (LS) extern const int KHTML_NO_EXPORT fastZoomSizes[] = { 20, 50, 75, 90, 100, 120, 150, 200, 300 }; extern const int KHTML_NO_EXPORT fastZoomSizeCount = sizeof fastZoomSizes / sizeof fastZoomSizes[0]; void KHTMLPart::slotIncZoom() { zoomIn(zoomSizes, zoomSizeCount); } void KHTMLPart::slotDecZoom() { zoomOut(zoomSizes, zoomSizeCount); } void KHTMLPart::slotIncZoomFast() { zoomIn(fastZoomSizes, fastZoomSizeCount); } void KHTMLPart::slotDecZoomFast() { zoomOut(fastZoomSizes, fastZoomSizeCount); } void KHTMLPart::zoomIn(const int stepping[], int count) { int zoomFactor = d->m_zoomFactor; if (zoomFactor < maxZoom) { // find the entry nearest to the given zoomsizes for (int i = 0; i < count; ++i) if (stepping[i] > zoomFactor) { zoomFactor = stepping[i]; break; } setZoomFactor(zoomFactor); } } void KHTMLPart::zoomOut(const int stepping[], int count) { int zoomFactor = d->m_zoomFactor; if (zoomFactor > minZoom) { // find the entry nearest to the given zoomsizes for (int i = count - 1; i >= 0; --i) if (stepping[i] < zoomFactor) { zoomFactor = stepping[i]; break; } setZoomFactor(zoomFactor); } } void KHTMLPart::setZoomFactor(int percent) { // ### zooming under 100% is majorly botched, // so disable that for now. if (percent < 100) { percent = 100; } // ### if (percent < minZoom) percent = minZoom; if (percent > maxZoom) { percent = maxZoom; } if (d->m_zoomFactor == percent) { return; } d->m_zoomFactor = percent; updateZoomFactor(); } void KHTMLPart::updateZoomFactor() { if (d->m_view) { QApplication::setOverrideCursor(Qt::WaitCursor); d->m_view->setZoomLevel(d->m_zoomFactor); QApplication::restoreOverrideCursor(); } ConstFrameIt it = d->m_frames.constBegin(); const ConstFrameIt end = d->m_frames.constEnd(); for (; it != end; ++it) { if (KHTMLPart *p = qobject_cast((*it)->m_part.data())) { p->setZoomFactor(d->m_zoomFactor); } } if (d->m_guiProfile == BrowserViewGUI) { d->m_paDecZoomFactor->setEnabled(d->m_zoomFactor > minZoom); d->m_paIncZoomFactor->setEnabled(d->m_zoomFactor < maxZoom); } } void KHTMLPart::slotIncFontSize() { incFontSize(zoomSizes, zoomSizeCount); } void KHTMLPart::slotDecFontSize() { decFontSize(zoomSizes, zoomSizeCount); } void KHTMLPart::slotIncFontSizeFast() { incFontSize(fastZoomSizes, fastZoomSizeCount); } void KHTMLPart::slotDecFontSizeFast() { decFontSize(fastZoomSizes, fastZoomSizeCount); } void KHTMLPart::incFontSize(const int stepping[], int count) { int zoomFactor = d->m_fontScaleFactor; if (zoomFactor < maxZoom) { // find the entry nearest to the given zoomsizes for (int i = 0; i < count; ++i) if (stepping[i] > zoomFactor) { zoomFactor = stepping[i]; break; } setFontScaleFactor(zoomFactor); } } void KHTMLPart::decFontSize(const int stepping[], int count) { int zoomFactor = d->m_fontScaleFactor; if (zoomFactor > minZoom) { // find the entry nearest to the given zoomsizes for (int i = count - 1; i >= 0; --i) if (stepping[i] < zoomFactor) { zoomFactor = stepping[i]; break; } setFontScaleFactor(zoomFactor); } } void KHTMLPart::setFontScaleFactor(int percent) { if (percent < minZoom) { percent = minZoom; } if (percent > maxZoom) { percent = maxZoom; } if (d->m_fontScaleFactor == percent) { return; } d->m_fontScaleFactor = percent; if (d->m_view && d->m_doc) { QApplication::setOverrideCursor(Qt::WaitCursor); if (d->m_doc->styleSelector()) { d->m_doc->styleSelector()->computeFontSizes(d->m_doc->logicalDpiY(), d->m_fontScaleFactor); } d->m_doc->recalcStyle(NodeImpl::Force); QApplication::restoreOverrideCursor(); } ConstFrameIt it = d->m_frames.constBegin(); const ConstFrameIt end = d->m_frames.constEnd(); for (; it != end; ++it) { if (KHTMLPart *p = qobject_cast((*it)->m_part.data())) { p->setFontScaleFactor(d->m_fontScaleFactor); } } } int KHTMLPart::fontScaleFactor() const { return d->m_fontScaleFactor; } void KHTMLPart::slotZoomView(int delta) { if (delta < 0) { slotIncZoom(); } else { slotDecZoom(); } } void KHTMLPart::setStatusBarText(const QString &text, StatusBarPriority p) { if (!d->m_statusMessagesEnabled) { return; } d->m_statusBarText[p] = text; // shift handling ? QString tobe = d->m_statusBarText[BarHoverText]; if (tobe.isEmpty()) { tobe = d->m_statusBarText[BarOverrideText]; } if (tobe.isEmpty()) { tobe = d->m_statusBarText[BarDefaultText]; if (!tobe.isEmpty() && d->m_jobspeed) { tobe += " "; } if (d->m_jobspeed) { tobe += i18n("(%1/s)", KIO::convertSize(d->m_jobspeed)); } } tobe = "" + tobe; emit ReadOnlyPart::setStatusBarText(tobe); } void KHTMLPart::setJSStatusBarText(const QString &text) { setStatusBarText(text, BarOverrideText); } void KHTMLPart::setJSDefaultStatusBarText(const QString &text) { setStatusBarText(text, BarDefaultText); } QString KHTMLPart::jsStatusBarText() const { return d->m_statusBarText[BarOverrideText]; } QString KHTMLPart::jsDefaultStatusBarText() const { return d->m_statusBarText[BarDefaultText]; } QString KHTMLPart::referrer() const { return d->m_referrer; } QString KHTMLPart::pageReferrer() const { QUrl referrerURL = QUrl(d->m_pageReferrer); if (referrerURL.isValid()) { QString protocol = referrerURL.scheme(); if ((protocol == "http") || ((protocol == "https") && (url().scheme() == "https"))) { referrerURL.setFragment(QString()); referrerURL.setUserName(QString()); referrerURL.setPassword(QString()); return referrerURL.toString(); } } return QString(); } QString KHTMLPart::lastModified() const { if (d->m_lastModified.isEmpty() && url().isLocalFile()) { // Local file: set last-modified from the file's mtime. // Done on demand to save time when this isn't needed - but can lead // to slightly wrong results if updating the file on disk w/o reloading. QDateTime lastModif = QFileInfo(url().toLocalFile()).lastModified(); d->m_lastModified = lastModif.toString(Qt::LocalDate); } //qDebug() << d->m_lastModified; return d->m_lastModified; } void KHTMLPart::slotLoadImages() { if (d->m_doc) { d->m_doc->docLoader()->setAutoloadImages(!d->m_doc->docLoader()->autoloadImages()); } ConstFrameIt it = d->m_frames.constBegin(); const ConstFrameIt end = d->m_frames.constEnd(); for (; it != end; ++it) { if (KHTMLPart *p = qobject_cast((*it)->m_part.data())) { p->slotLoadImages(); } } } void KHTMLPart::reparseConfiguration() { KHTMLSettings *settings = KHTMLGlobal::defaultHTMLSettings(); settings->init(); setAutoloadImages(settings->autoLoadImages()); if (d->m_doc) { d->m_doc->docLoader()->setShowAnimations(settings->showAnimations()); } d->m_bOpenMiddleClick = settings->isOpenMiddleClickEnabled(); d->m_bJScriptEnabled = settings->isJavaScriptEnabled(url().host()); setDebugScript(settings->isJavaScriptDebugEnabled()); d->m_bJavaEnabled = settings->isJavaEnabled(url().host()); d->m_bPluginsEnabled = settings->isPluginsEnabled(url().host()); d->m_metaRefreshEnabled = settings->isAutoDelayedActionsEnabled(); delete d->m_settings; d->m_settings = new KHTMLSettings(*KHTMLGlobal::defaultHTMLSettings()); QApplication::setOverrideCursor(Qt::WaitCursor); khtml::CSSStyleSelector::reparseConfiguration(); if (d->m_doc) { d->m_doc->updateStyleSelector(); } QApplication::restoreOverrideCursor(); if (d->m_view) { KHTMLSettings::KSmoothScrollingMode ssm = d->m_settings->smoothScrolling(); if (ssm == KHTMLSettings::KSmoothScrollingDisabled) { d->m_view->setSmoothScrollingModeDefault(KHTMLView::SSMDisabled); } else if (ssm == KHTMLSettings::KSmoothScrollingWhenEfficient) { d->m_view->setSmoothScrollingModeDefault(KHTMLView::SSMWhenEfficient); } else { d->m_view->setSmoothScrollingModeDefault(KHTMLView::SSMEnabled); } } if (KHTMLGlobal::defaultHTMLSettings()->isAdFilterEnabled()) { runAdFilter(); } } QStringList KHTMLPart::frameNames() const { QStringList res; ConstFrameIt it = d->m_frames.constBegin(); const ConstFrameIt end = d->m_frames.constEnd(); for (; it != end; ++it) if (!(*it)->m_bPreloaded && (*it)->m_part) { res += (*it)->m_name; } return res; } QList KHTMLPart::frames() const { QList res; ConstFrameIt it = d->m_frames.constBegin(); const ConstFrameIt end = d->m_frames.constEnd(); for (; it != end; ++it) if (!(*it)->m_bPreloaded && (*it)->m_part) // ### TODO: make sure that we always create an empty // KHTMLPart for frames so this never happens. { res.append((*it)->m_part.data()); } return res; } bool KHTMLPart::openUrlInFrame(const QUrl &url, const KParts::OpenUrlArguments &args, const KParts::BrowserArguments &browserArgs) { // qDebug() << this << url; FrameIt it = d->m_frames.find(browserArgs.frameName); if (it == d->m_frames.end()) { return false; } // Inform someone that we are about to show something else. if (!browserArgs.lockHistory()) { emit d->m_extension->openUrlNotify(); } requestObject(*it, url, args, browserArgs); return true; } void KHTMLPart::setDNDEnabled(bool b) { d->m_bDnd = b; } bool KHTMLPart::dndEnabled() const { return d->m_bDnd; } void KHTMLPart::customEvent(QEvent *event) { if (khtml::MousePressEvent::test(event)) { khtmlMousePressEvent(static_cast(event)); return; } if (khtml::MouseDoubleClickEvent::test(event)) { khtmlMouseDoubleClickEvent(static_cast(event)); return; } if (khtml::MouseMoveEvent::test(event)) { khtmlMouseMoveEvent(static_cast(event)); return; } if (khtml::MouseReleaseEvent::test(event)) { khtmlMouseReleaseEvent(static_cast(event)); return; } if (khtml::DrawContentsEvent::test(event)) { khtmlDrawContentsEvent(static_cast(event)); return; } KParts::ReadOnlyPart::customEvent(event); } bool KHTMLPart::isPointInsideSelection(int x, int y) { // Treat a collapsed selection like no selection. if (d->editor_context.m_selection.state() == Selection::CARET) { return false; } if (!xmlDocImpl()->renderer()) { return false; } khtml::RenderObject::NodeInfo nodeInfo(true, true); xmlDocImpl()->renderer()->layer()->nodeAtPoint(nodeInfo, x, y); NodeImpl *innerNode = nodeInfo.innerNode(); if (!innerNode || !innerNode->renderer()) { return false; } return innerNode->isPointInsideSelection(x, y, d->editor_context.m_selection); } /** returns the position of the first inline text box of the line at * coordinate y in renderNode * * This is a helper function for line-by-line text selection. */ static bool firstRunAt(khtml::RenderObject *renderNode, int y, NodeImpl *&startNode, long &startOffset) { for (khtml::RenderObject *n = renderNode; n; n = n->nextSibling()) { if (n->isText()) { khtml::RenderText *const textRenderer = static_cast(n); for (khtml::InlineTextBox *box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) { if (box->m_y == y && textRenderer->element()) { startNode = textRenderer->element(); startOffset = box->m_start; return true; } } } if (firstRunAt(n->firstChild(), y, startNode, startOffset)) { return true; } } return false; } /** returns the position of the last inline text box of the line at * coordinate y in renderNode * * This is a helper function for line-by-line text selection. */ static bool lastRunAt(khtml::RenderObject *renderNode, int y, NodeImpl *&endNode, long &endOffset) { khtml::RenderObject *n = renderNode; if (!n) { return false; } khtml::RenderObject *next; while ((next = n->nextSibling())) { n = next; } while (1) { if (lastRunAt(n->firstChild(), y, endNode, endOffset)) { return true; } if (n->isText()) { khtml::RenderText *const textRenderer = static_cast(n); for (khtml::InlineTextBox *box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) { if (box->m_y == y && textRenderer->element()) { endNode = textRenderer->element(); endOffset = box->m_start + box->m_len; return true; } } } if (n == renderNode) { return false; } n = n->previousSibling(); } } void KHTMLPart::handleMousePressEventDoubleClick(khtml::MouseDoubleClickEvent *event) { QMouseEvent *mouse = event->qmouseEvent(); DOM::Node innerNode = event->innerNode(); Selection selection; if (mouse->button() == Qt::LeftButton && !innerNode.isNull() && innerNode.handle()->renderer() && innerNode.handle()->renderer()->shouldSelect()) { Position pos(innerNode.handle()->positionForCoordinates(event->x(), event->y()).position()); if (pos.node() && (pos.node()->nodeType() == Node::TEXT_NODE || pos.node()->nodeType() == Node::CDATA_SECTION_NODE)) { selection.moveTo(pos); selection.expandUsingGranularity(Selection::WORD); } } if (selection.state() != Selection::CARET) { d->editor_context.beginSelectingText(Selection::WORD); } setCaret(selection); startAutoScroll(); } void KHTMLPart::handleMousePressEventTripleClick(khtml::MouseDoubleClickEvent *event) { QMouseEvent *mouse = event->qmouseEvent(); DOM::Node innerNode = event->innerNode(); Selection selection; if (mouse->button() == Qt::LeftButton && !innerNode.isNull() && innerNode.handle()->renderer() && innerNode.handle()->renderer()->shouldSelect()) { Position pos(innerNode.handle()->positionForCoordinates(event->x(), event->y()).position()); if (pos.node() && (pos.node()->nodeType() == Node::TEXT_NODE || pos.node()->nodeType() == Node::CDATA_SECTION_NODE)) { selection.moveTo(pos); selection.expandUsingGranularity(Selection::LINE); } } if (selection.state() != Selection::CARET) { d->editor_context.beginSelectingText(Selection::LINE); } setCaret(selection); startAutoScroll(); } void KHTMLPart::handleMousePressEventSingleClick(khtml::MousePressEvent *event) { QMouseEvent *mouse = event->qmouseEvent(); DOM::Node innerNode = event->innerNode(); if (mouse->button() == Qt::LeftButton) { Selection sel; if (!innerNode.isNull() && innerNode.handle()->renderer() && innerNode.handle()->renderer()->shouldSelect()) { bool extendSelection = mouse->modifiers() & Qt::ShiftModifier; // Don't restart the selection when the mouse is pressed on an // existing selection so we can allow for text dragging. if (!extendSelection && isPointInsideSelection(event->x(), event->y())) { return; } Position pos(innerNode.handle()->positionForCoordinates(event->x(), event->y()).position()); if (pos.isEmpty()) { pos = Position(innerNode.handle(), innerNode.handle()->caretMinOffset()); } - // qDebug() << event->x() << event->y() << pos << endl; + // qDebug() << event->x() << event->y() << pos; sel = caret(); if (extendSelection && sel.notEmpty()) { sel.clearModifyBias(); sel.setExtent(pos); if (d->editor_context.m_selectionGranularity != Selection::CHARACTER) { sel.expandUsingGranularity(d->editor_context.m_selectionGranularity); } d->editor_context.m_beganSelectingText = true; } else { sel = pos; d->editor_context.m_selectionGranularity = Selection::CHARACTER; } } setCaret(sel); startAutoScroll(); } } void KHTMLPart::khtmlMousePressEvent(khtml::MousePressEvent *event) { DOM::DOMString url = event->url(); QMouseEvent *_mouse = event->qmouseEvent(); DOM::Node innerNode = event->innerNode(); d->m_mousePressNode = innerNode; d->m_dragStartPos = QPoint(event->x(), event->y()); if (!event->url().isNull()) { d->m_strSelectedURL = event->url().string(); d->m_strSelectedURLTarget = event->target().string(); } else { d->m_strSelectedURL.clear(); d->m_strSelectedURLTarget.clear(); } if (_mouse->button() == Qt::LeftButton || _mouse->button() == Qt::MidButton) { d->m_bMousePressed = true; #ifdef KHTML_NO_SELECTION d->m_dragLastPos = _mouse->globalPos(); #else if (_mouse->button() == Qt::LeftButton) { if ((!d->m_strSelectedURL.isNull() && !isEditable()) || (!d->m_mousePressNode.isNull() && d->m_mousePressNode.elementId() == ID_IMG)) { return; } d->editor_context.m_beganSelectingText = false; handleMousePressEventSingleClick(event); } #endif } if (_mouse->button() == Qt::RightButton) { popupMenu(d->m_strSelectedURL); // might be deleted, don't touch "this" } } void KHTMLPart::khtmlMouseDoubleClickEvent(khtml::MouseDoubleClickEvent *event) { QMouseEvent *_mouse = event->qmouseEvent(); if (_mouse->button() == Qt::LeftButton) { d->m_bMousePressed = true; d->editor_context.m_beganSelectingText = false; if (event->clickCount() == 2) { handleMousePressEventDoubleClick(event); return; } if (event->clickCount() >= 3) { handleMousePressEventTripleClick(event); return; } } } #ifndef KHTML_NO_SELECTION bool KHTMLPart::isExtendingSelection() const { // This is it, the whole detection. khtmlMousePressEvent only sets this // on LMB or MMB, but never on RMB. As text selection doesn't work for MMB, // it's sufficient to only rely on this flag to detect selection extension. return d->editor_context.m_beganSelectingText; } void KHTMLPart::extendSelectionTo(int x, int y, const DOM::Node &innerNode) { // handle making selection Position pos(innerNode.handle()->positionForCoordinates(x, y).position()); // Don't modify the selection if we're not on a node. if (pos.isEmpty()) { return; } // Restart the selection if this is the first mouse move. This work is usually // done in khtmlMousePressEvent, but not if the mouse press was on an existing selection. Selection sel = caret(); sel.clearModifyBias(); if (!d->editor_context.m_beganSelectingText) { // We are beginning a selection during press-drag, when the original click // wasn't appropriate for one. Make sure to set the granularity. d->editor_context.beginSelectingText(Selection::CHARACTER); sel.moveTo(pos); } sel.setExtent(pos); if (d->editor_context.m_selectionGranularity != Selection::CHARACTER) { sel.expandUsingGranularity(d->editor_context.m_selectionGranularity); } setCaret(sel); } #endif // KHTML_NO_SELECTION bool KHTMLPart::handleMouseMoveEventDrag(khtml::MouseMoveEvent *event) { #ifdef QT_NO_DRAGANDDROP return false; #else if (!dndEnabled()) { return false; } if ((d->m_bMousePressed && ((!d->m_strSelectedURL.isEmpty() && !isEditable()) || (!d->m_mousePressNode.isNull() && d->m_mousePressNode.elementId() == ID_IMG))) && (d->m_dragStartPos - QPoint(event->x(), event->y())).manhattanLength() > QApplication::startDragDistance()) { const DOM::DOMString url = event->url(); DOM::NodeImpl *innerNodeImpl = event->innerNode().handle(); QPixmap pix; HTMLImageElementImpl *img = nullptr; QUrl u; // qDebug("****************** Event URL: %s", url.string().toLatin1().constData()); // qDebug("****************** Event Target: %s", target.string().toLatin1().constData()); // Normal image... if (url.isEmpty() && innerNodeImpl && innerNodeImpl->id() == ID_IMG) { img = static_cast(innerNodeImpl); u = completeURL(img->getAttribute(ATTR_SRC).trimSpaces().string()); pix = KIconLoader::global()->loadIcon("image-x-generic", KIconLoader::Desktop); } else { // Text or image link... u = completeURL(d->m_strSelectedURL); pix = KIO::pixmapForUrl(u, 0, KIconLoader::Desktop, KIconLoader::SizeMedium); } u.setPassword(QString()); QDrag *drag = new QDrag(d->m_view->viewport()); QMap metaDataMap; if (!d->m_referrer.isEmpty()) { metaDataMap.insert("referrer", d->m_referrer); } QMimeData *mimeData = new QMimeData(); mimeData->setUrls(QList() << u); KUrlMimeData::setMetaData(metaDataMap, mimeData); drag->setMimeData(mimeData); if (img && img->complete()) { drag->mimeData()->setImageData(img->currentImage()); } if (!pix.isNull()) { drag->setPixmap(pix); } stopAutoScroll(); drag->start(); // when we finish our drag, we need to undo our mouse press d->m_bMousePressed = false; d->m_strSelectedURL.clear(); d->m_strSelectedURLTarget.clear(); return true; } return false; #endif // QT_NO_DRAGANDDROP } bool KHTMLPart::handleMouseMoveEventOver(khtml::MouseMoveEvent *event) { // Mouse clicked -> do nothing if (d->m_bMousePressed) { return false; } DOM::DOMString url = event->url(); // The mouse is over something if (url.length()) { DOM::DOMString target = event->target(); QMouseEvent *_mouse = event->qmouseEvent(); DOM::Node innerNode = event->innerNode(); bool shiftPressed = (_mouse->modifiers() & Qt::ShiftModifier); // Image map if (!innerNode.isNull() && innerNode.elementId() == ID_IMG) { HTMLImageElementImpl *i = static_cast(innerNode.handle()); if (i && i->isServerMap()) { khtml::RenderObject *r = i->renderer(); if (r) { int absx, absy; r->absolutePosition(absx, absy); int x(event->x() - absx), y(event->y() - absy); d->m_overURL = url.string() + QString("?%1,%2").arg(x).arg(y); d->m_overURLTarget = target.string(); overURL(d->m_overURL, target.string(), shiftPressed); return true; } } } // normal link if (d->m_overURL.isEmpty() || d->m_overURL != url || d->m_overURLTarget != target) { d->m_overURL = url.string(); d->m_overURLTarget = target.string(); overURL(d->m_overURL, target.string(), shiftPressed); } } else { // Not over a link... if (!d->m_overURL.isEmpty()) { // and we were over a link -> reset to "default statusbar text" // reset to "default statusbar text" resetHoverText(); } } return true; } void KHTMLPart::handleMouseMoveEventSelection(khtml::MouseMoveEvent *event) { // Mouse not pressed. Do nothing. if (!d->m_bMousePressed) { return; } #ifdef KHTML_NO_SELECTION if (d->m_doc && d->m_view) { QPoint diff(mouse->globalPos() - d->m_dragLastPos); if (abs(diff.x()) > 64 || abs(diff.y()) > 64) { d->m_view->scrollBy(-diff.x(), -diff.y()); d->m_dragLastPos = mouse->globalPos(); } } #else QMouseEvent *mouse = event->qmouseEvent(); DOM::Node innerNode = event->innerNode(); if ((mouse->buttons() & Qt::LeftButton) == 0 || !innerNode.handle() || !innerNode.handle()->renderer() || !innerNode.handle()->renderer()->shouldSelect()) { return; } // handle making selection extendSelectionTo(event->x(), event->y(), innerNode); #endif // KHTML_NO_SELECTION } void KHTMLPart::khtmlMouseMoveEvent(khtml::MouseMoveEvent *event) { if (handleMouseMoveEventDrag(event)) { return; } if (handleMouseMoveEventOver(event)) { return; } handleMouseMoveEventSelection(event); } void KHTMLPart::khtmlMouseReleaseEvent(khtml::MouseReleaseEvent *event) { DOM::Node innerNode = event->innerNode(); d->m_mousePressNode = DOM::Node(); if (d->m_bMousePressed) { setStatusBarText(QString(), BarHoverText); stopAutoScroll(); } // Used to prevent mouseMoveEvent from initiating a drag before // the mouse is pressed again. d->m_bMousePressed = false; #ifndef QT_NO_CLIPBOARD QMouseEvent *_mouse = event->qmouseEvent(); if ((d->m_guiProfile == BrowserViewGUI) && (_mouse->button() == Qt::MidButton) && (event->url().isNull())) { // qDebug() << "MMB shouldOpen=" << d->m_bOpenMiddleClick; if (d->m_bOpenMiddleClick) { KHTMLPart *p = this; while (p->parentPart()) { p = p->parentPart(); } p->d->m_extension->pasteRequest(); } } #endif #ifndef KHTML_NO_SELECTION { // Clear the selection if the mouse didn't move after the last mouse press. // We do this so when clicking on the selection, the selection goes away. // However, if we are editing, place the caret. if (!d->editor_context.m_beganSelectingText && d->m_dragStartPos.x() == event->x() && d->m_dragStartPos.y() == event->y() && d->editor_context.m_selection.state() == Selection::RANGE) { Selection selection; #ifdef APPLE_CHANGES if (d->editor_context.m_selection.base().node()->isContentEditable()) #endif selection.moveTo(d->editor_context.m_selection.base().node()->positionForCoordinates(event->x(), event->y()).position()); setCaret(selection); } // get selected text and paste to the clipboard #ifndef QT_NO_CLIPBOARD QString text = selectedText(); text.replace(QChar(0xa0), ' '); if (!text.isEmpty()) { disconnect(qApp->clipboard(), SIGNAL(selectionChanged()), this, SLOT(slotClearSelection())); qApp->clipboard()->setText(text, QClipboard::Selection); connect(qApp->clipboard(), SIGNAL(selectionChanged()), SLOT(slotClearSelection())); } #endif //qDebug() << "selectedText = " << text; emitSelectionChanged(); //qDebug() << "rel2: startBefEnd " << d->m_startBeforeEnd << " extAtEnd " << d->m_extendAtEnd << " (" << d->m_startOffset << ") - (" << d->m_endOffset << "), caretOfs " << d->caretOffset(); } #endif } void KHTMLPart::khtmlDrawContentsEvent(khtml::DrawContentsEvent *) { } void KHTMLPart::guiActivateEvent(KParts::GUIActivateEvent *event) { if (event->activated()) { emitSelectionChanged(); emit d->m_extension->enableAction("print", d->m_doc != nullptr); if (!d->m_settings->autoLoadImages() && d->m_paLoadImages) { QList lst; lst.append(d->m_paLoadImages); plugActionList("loadImages", lst); } } } void KHTMLPart::slotPrintFrame() { if (d->m_frames.count() == 0) { return; } KParts::ReadOnlyPart *frame = currentFrame(); if (!frame) { return; } KParts::BrowserExtension *ext = KParts::BrowserExtension::childObject(frame); if (!ext) { return; } const QMetaObject *mo = ext->metaObject(); if (mo->indexOfSlot("print()") != -1) { QMetaObject::invokeMethod(ext, "print()", Qt::DirectConnection); } } void KHTMLPart::slotSelectAll() { KParts::ReadOnlyPart *part = currentFrame(); if (part && part->inherits("KHTMLPart")) { static_cast(part)->selectAll(); } } void KHTMLPart::startAutoScroll() { connect(&d->m_scrollTimer, SIGNAL(timeout()), this, SLOT(slotAutoScroll())); d->m_scrollTimer.setSingleShot(false); d->m_scrollTimer.start(100); } void KHTMLPart::stopAutoScroll() { disconnect(&d->m_scrollTimer, SIGNAL(timeout()), this, SLOT(slotAutoScroll())); if (d->m_scrollTimer.isActive()) { d->m_scrollTimer.stop(); } } void KHTMLPart::slotAutoScroll() { if (d->m_view) { d->m_view->doAutoScroll(); } else { stopAutoScroll(); // Safety } } void KHTMLPart::runAdFilter() { if (parentPart()) { parentPart()->runAdFilter(); } if (!d->m_doc) { return; } QSetIterator it(d->m_doc->docLoader()->m_docObjects); while (it.hasNext()) { khtml::CachedObject *obj = it.next(); if (obj->type() == khtml::CachedObject::Image) { khtml::CachedImage *image = static_cast(obj); bool wasBlocked = image->m_wasBlocked; image->m_wasBlocked = KHTMLGlobal::defaultHTMLSettings()->isAdFiltered(d->m_doc->completeURL(image->url().string())); if (image->m_wasBlocked != wasBlocked) { image->do_notify(QRect(QPoint(0, 0), image->pixmap_size())); } } } if (KHTMLGlobal::defaultHTMLSettings()->isHideAdsEnabled()) { for (NodeImpl * nextNode, *node = d->m_doc; node; node = nextNode) { // We might be deleting 'node' shortly. nextNode = node->traverseNextNode(); if (node->id() == ID_IMG || node->id() == ID_IFRAME || (node->id() == ID_INPUT && static_cast(node)->inputType() == HTMLInputElementImpl::IMAGE)) { if (KHTMLGlobal::defaultHTMLSettings()->isAdFiltered(d->m_doc->completeURL(static_cast(node)->getAttribute(ATTR_SRC).trimSpaces().string()))) { // Since any kids of node will be deleted, too, fastforward nextNode // until we get outside of node. while (nextNode && nextNode->isAncestor(node)) { nextNode = nextNode->traverseNextNode(); } node->ref(); NodeImpl *parent = node->parent(); if (parent) { int exception = 0; parent->removeChild(node, exception); } node->deref(); } } } } } void KHTMLPart::selectAll() { if (!d->m_doc) { return; } NodeImpl *first; if (d->m_doc->isHTMLDocument()) { first = static_cast(d->m_doc)->body(); } else { first = d->m_doc; } NodeImpl *next; // Look for first text/cdata node that has a renderer, // or first childless replaced element while (first && !(first->renderer() && ((first->nodeType() == Node::TEXT_NODE || first->nodeType() == Node::CDATA_SECTION_NODE) || (first->renderer()->isReplaced() && !first->renderer()->firstChild())))) { next = first->firstChild(); if (!next) { next = first->nextSibling(); } while (first && !next) { first = first->parentNode(); if (first) { next = first->nextSibling(); } } first = next; } NodeImpl *last; if (d->m_doc->isHTMLDocument()) { last = static_cast(d->m_doc)->body(); } else { last = d->m_doc; } // Look for last text/cdata node that has a renderer, // or last childless replaced element // ### Instead of changing this loop, use findLastSelectableNode // in render_table.cpp (LS) while (last && !(last->renderer() && ((last->nodeType() == Node::TEXT_NODE || last->nodeType() == Node::CDATA_SECTION_NODE) || (last->renderer()->isReplaced() && !last->renderer()->lastChild())))) { next = last->lastChild(); if (!next) { next = last->previousSibling(); } while (last && !next) { last = last->parentNode(); if (last) { next = last->previousSibling(); } } last = next; } if (!first || !last) { return; } Q_ASSERT(first->renderer()); Q_ASSERT(last->renderer()); d->editor_context.m_selection.moveTo(Position(first, 0), Position(last, last->nodeValue().length())); d->m_doc->updateSelection(); emitSelectionChanged(); } bool KHTMLPart::checkLinkSecurity(const QUrl &linkURL, const KLocalizedString &message, const QString &button) { bool linkAllowed = true; if (d->m_doc) { linkAllowed = KUrlAuthorized::authorizeUrlAction("redirect", url(), linkURL); } if (!linkAllowed) { khtml::Tokenizer *tokenizer = d->m_doc->tokenizer(); if (tokenizer) { tokenizer->setOnHold(true); } int response = KMessageBox::Cancel; if (!message.isEmpty()) { // Dangerous flag makes the Cancel button the default response = KMessageBox::warningContinueCancel(nullptr, message.subs(Qt::escape(linkURL.toDisplayString())).toString(), i18n("Security Warning"), KGuiItem(button), KStandardGuiItem::cancel(), QString(), // no don't ask again info KMessageBox::Notify | KMessageBox::Dangerous); } else { KMessageBox::error(nullptr, i18n("Access by untrusted page to
    %1
    denied.
    ", Qt::escape(linkURL.toDisplayString())), i18n("Security Alert")); } if (tokenizer) { tokenizer->setOnHold(false); } return (response == KMessageBox::Continue); } return true; } void KHTMLPart::slotPartRemoved(KParts::Part *part) { // qDebug() << part; if (part == d->m_activeFrame) { d->m_activeFrame = nullptr; if (!part->inherits("KHTMLPart")) { if (factory()) { factory()->removeClient(part); } if (childClients().contains(part)) { removeChildClient(part); } } } } void KHTMLPart::slotActiveFrameChanged(KParts::Part *part) { // qDebug() << this << "part=" << part; if (part == this) { qCritical() << "strange error! we activated ourselves"; assert(false); return; } // qDebug() << "d->m_activeFrame=" << d->m_activeFrame; if (d->m_activeFrame && d->m_activeFrame->widget() && d->m_activeFrame->widget()->inherits("QFrame")) { QFrame *frame = static_cast(d->m_activeFrame->widget()); if (frame->frameStyle() != QFrame::NoFrame) { frame->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); frame->repaint(); } } if (d->m_activeFrame && !d->m_activeFrame->inherits("KHTMLPart")) { if (factory()) { factory()->removeClient(d->m_activeFrame); } removeChildClient(d->m_activeFrame); } if (part && !part->inherits("KHTMLPart")) { if (factory()) { factory()->addClient(part); } insertChildClient(part); } d->m_activeFrame = part; if (d->m_activeFrame && d->m_activeFrame->widget()->inherits("QFrame")) { QFrame *frame = static_cast(d->m_activeFrame->widget()); if (frame->frameStyle() != QFrame::NoFrame) { frame->setFrameStyle(QFrame::StyledPanel | QFrame::Plain); frame->repaint(); } // qDebug() << "new active frame " << d->m_activeFrame; } updateActions(); // (note: childObject returns 0 if the argument is 0) d->m_extension->setExtensionProxy(KParts::BrowserExtension::childObject(d->m_activeFrame)); } void KHTMLPart::setActiveNode(const DOM::Node &node) { if (!d->m_doc || !d->m_view) { return; } // Set the document's active node d->m_doc->setFocusNode(node.handle()); // Scroll the view if necessary to ensure that the new focus node is visible QRect rect = node.handle()->getRect(); d->m_view->ensureVisible(rect.right(), rect.bottom()); d->m_view->ensureVisible(rect.left(), rect.top()); } DOM::Node KHTMLPart::activeNode() const { return DOM::Node(d->m_doc ? d->m_doc->focusNode() : nullptr); } DOM::EventListener *KHTMLPart::createHTMLEventListener(QString code, QString name, NodeImpl *node, bool svg) { KJSProxy *proxy = jScript(); if (!proxy) { return nullptr; } return proxy->createHTMLEventHandler(url().toString(), name, code, node, svg); } KHTMLPart *KHTMLPart::opener() { return d->m_opener; } void KHTMLPart::setOpener(KHTMLPart *_opener) { d->m_opener = _opener; } bool KHTMLPart::openedByJS() { return d->m_openedByJS; } void KHTMLPart::setOpenedByJS(bool _openedByJS) { d->m_openedByJS = _openedByJS; } void KHTMLPart::preloadStyleSheet(const QString &url, const QString &stylesheet) { khtml::Cache::preloadStyleSheet(url, stylesheet); } void KHTMLPart::preloadScript(const QString &url, const QString &script) { khtml::Cache::preloadScript(url, script); } long KHTMLPart::cacheId() const { return d->m_cacheId; } bool KHTMLPart::restored() const { return d->m_restored; } bool KHTMLPart::pluginPageQuestionAsked(const QString &mimetype) const { // parentPart() should be const! KHTMLPart *parent = const_cast(this)->parentPart(); if (parent) { return parent->pluginPageQuestionAsked(mimetype); } return d->m_pluginPageQuestionAsked.contains(mimetype); } void KHTMLPart::setPluginPageQuestionAsked(const QString &mimetype) { if (parentPart()) { parentPart()->setPluginPageQuestionAsked(mimetype); } d->m_pluginPageQuestionAsked.append(mimetype); } KEncodingDetector *KHTMLPart::createDecoder() { KEncodingDetector *dec = new KEncodingDetector(); if (!d->m_encoding.isNull()) dec->setEncoding(d->m_encoding.toLatin1().constData(), d->m_haveEncoding ? KEncodingDetector::UserChosenEncoding : KEncodingDetector::EncodingFromHTTPHeader); else { // Inherit the default encoding from the parent frame if there is one. QByteArray defaultEncoding = (parentPart() && parentPart()->d->m_decoder) ? QByteArray(parentPart()->d->m_decoder->encoding()) : settings()->encoding().toLatin1(); dec->setEncoding(defaultEncoding.constData(), KEncodingDetector::DefaultEncoding); } if (d->m_doc) { d->m_doc->setDecoder(dec); } // convert from KEncodingProber::ProberType to KEncodingDetector::AutoDetectScript KEncodingDetector::AutoDetectScript scri; switch (d->m_autoDetectLanguage) { case KEncodingProber::None: scri = KEncodingDetector::None; break; case KEncodingProber::Universal: scri = KEncodingDetector::SemiautomaticDetection; break; case KEncodingProber::Arabic: scri = KEncodingDetector::Arabic; break; case KEncodingProber::Baltic: scri = KEncodingDetector::Baltic; break; case KEncodingProber::CentralEuropean: scri = KEncodingDetector::CentralEuropean; break; case KEncodingProber::ChineseSimplified: scri = KEncodingDetector::ChineseSimplified; break; case KEncodingProber::ChineseTraditional: scri = KEncodingDetector::ChineseTraditional; break; case KEncodingProber::Cyrillic: scri = KEncodingDetector::Cyrillic; break; case KEncodingProber::Greek: scri = KEncodingDetector::Greek; break; case KEncodingProber::Hebrew: scri = KEncodingDetector::Hebrew; break; case KEncodingProber::Japanese: scri = KEncodingDetector::Japanese; break; case KEncodingProber::Korean: scri = KEncodingDetector::Korean; break; case KEncodingProber::NorthernSaami: scri = KEncodingDetector::NorthernSaami; break; case KEncodingProber::Other: scri = KEncodingDetector::SemiautomaticDetection; break; case KEncodingProber::SouthEasternEurope: scri = KEncodingDetector::SouthEasternEurope; break; case KEncodingProber::Thai: scri = KEncodingDetector::Thai; break; case KEncodingProber::Turkish: scri = KEncodingDetector::Turkish; break; case KEncodingProber::Unicode: scri = KEncodingDetector::Unicode; break; case KEncodingProber::WesternEuropean: scri = KEncodingDetector::WesternEuropean; break; default: scri = KEncodingDetector::SemiautomaticDetection; break; } dec->setAutoDetectLanguage(scri); return dec; } void KHTMLPart::emitCaretPositionChanged(const DOM::Position &pos) { // pos must not be already converted to range-compliant coordinates Position rng_pos = pos.equivalentRangeCompliantPosition(); Node node = rng_pos.node(); emit caretPositionChanged(node, rng_pos.offset()); } void KHTMLPart::restoreScrollPosition() { const KParts::OpenUrlArguments args(arguments()); if (url().hasFragment() && !d->m_restoreScrollPosition && !args.reload()) { if (!d->m_doc || !d->m_doc->parsing()) { disconnect(d->m_view, SIGNAL(finishedLayout()), this, SLOT(restoreScrollPosition())); } if (!gotoAnchor(url().fragment(QUrl::FullyEncoded))) { gotoAnchor(url().fragment(QUrl::FullyDecoded)); } return; } // Check whether the viewport has become large enough to encompass the stored // offsets. If the document has been fully loaded, force the new coordinates, // even if the canvas is too short (can happen when user resizes the window // during loading). if ((d->m_view->contentsHeight() - d->m_view->visibleHeight()) >= args.yOffset() || d->m_bComplete) { d->m_view->setContentsPos(args.xOffset(), args.yOffset()); disconnect(d->m_view, SIGNAL(finishedLayout()), this, SLOT(restoreScrollPosition())); } } void KHTMLPart::openWallet(DOM::HTMLFormElementImpl *form) { #ifndef KHTML_NO_WALLET KHTMLPart *p; for (p = parentPart(); p && p->parentPart(); p = p->parentPart()) { } if (p) { p->openWallet(form); return; } if (onlyLocalReferences()) { // avoid triggering on local apps, thumbnails return; } if (d->m_wallet) { if (d->m_bWalletOpened) { if (d->m_wallet->isOpen()) { form->walletOpened(d->m_wallet); return; } d->m_wallet->deleteLater(); d->m_wallet = nullptr; d->m_bWalletOpened = false; } } if (!d->m_wq) { KWallet::Wallet *wallet = KWallet::Wallet::openWallet(KWallet::Wallet::NetworkWallet(), widget() ? widget()->topLevelWidget()->winId() : 0, KWallet::Wallet::Asynchronous); d->m_wq = new KHTMLWalletQueue(this); d->m_wq->wallet = wallet; connect(wallet, SIGNAL(walletOpened(bool)), d->m_wq, SLOT(walletOpened(bool))); connect(d->m_wq, SIGNAL(walletOpened(KWallet::Wallet*)), this, SLOT(walletOpened(KWallet::Wallet*))); } assert(form); d->m_wq->callers.append(KHTMLWalletQueue::Caller(form, form->document())); #endif // KHTML_NO_WALLET } void KHTMLPart::saveToWallet(const QString &key, const QMap &data) { #ifndef KHTML_NO_WALLET KHTMLPart *p; for (p = parentPart(); p && p->parentPart(); p = p->parentPart()) { } if (p) { p->saveToWallet(key, data); return; } if (d->m_wallet) { if (d->m_bWalletOpened) { if (d->m_wallet->isOpen()) { if (!d->m_wallet->hasFolder(KWallet::Wallet::FormDataFolder())) { d->m_wallet->createFolder(KWallet::Wallet::FormDataFolder()); } d->m_wallet->setFolder(KWallet::Wallet::FormDataFolder()); d->m_wallet->writeMap(key, data); return; } d->m_wallet->deleteLater(); d->m_wallet = nullptr; d->m_bWalletOpened = false; } } if (!d->m_wq) { KWallet::Wallet *wallet = KWallet::Wallet::openWallet(KWallet::Wallet::NetworkWallet(), widget() ? widget()->topLevelWidget()->winId() : 0, KWallet::Wallet::Asynchronous); d->m_wq = new KHTMLWalletQueue(this); d->m_wq->wallet = wallet; connect(wallet, SIGNAL(walletOpened(bool)), d->m_wq, SLOT(walletOpened(bool))); connect(d->m_wq, SIGNAL(walletOpened(KWallet::Wallet*)), this, SLOT(walletOpened(KWallet::Wallet*))); } d->m_wq->savers.append(qMakePair(key, data)); #endif // KHTML_NO_WALLET } void KHTMLPart::dequeueWallet(DOM::HTMLFormElementImpl *form) { #ifndef KHTML_NO_WALLET KHTMLPart *p; for (p = parentPart(); p && p->parentPart(); p = p->parentPart()) { } if (p) { p->dequeueWallet(form); return; } if (d->m_wq) { d->m_wq->callers.removeAll(KHTMLWalletQueue::Caller(form, form->document())); } #endif // KHTML_NO_WALLET } void KHTMLPart::walletOpened(KWallet::Wallet *wallet) { #ifndef KHTML_NO_WALLET assert(!d->m_wallet); assert(d->m_wq); d->m_wq->deleteLater(); // safe? d->m_wq = nullptr; if (!wallet) { d->m_bWalletOpened = false; return; } d->m_wallet = wallet; d->m_bWalletOpened = true; connect(d->m_wallet, SIGNAL(walletClosed()), SLOT(slotWalletClosed())); d->m_walletForms.clear(); if (!d->m_statusBarWalletLabel) { d->m_statusBarWalletLabel = new KUrlLabel(d->m_statusBarExtension->statusBar()); d->m_statusBarWalletLabel->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum)); d->m_statusBarWalletLabel->setUseCursor(false); d->m_statusBarExtension->addStatusBarItem(d->m_statusBarWalletLabel, 0, false); d->m_statusBarWalletLabel->setPixmap(SmallIcon("wallet-open")); connect(d->m_statusBarWalletLabel, SIGNAL(leftClickedUrl()), SLOT(launchWalletManager())); connect(d->m_statusBarWalletLabel, SIGNAL(rightClickedUrl()), SLOT(walletMenu())); } d->m_statusBarWalletLabel->setToolTip(i18n("The wallet '%1' is open and being used for form data and passwords.", KWallet::Wallet::NetworkWallet())); #endif // KHTML_NO_WALLET } KWallet::Wallet *KHTMLPart::wallet() { #ifndef KHTML_NO_WALLET KHTMLPart *p; for (p = parentPart(); p && p->parentPart(); p = p->parentPart()) ; if (p) { return p->wallet(); } return d->m_wallet; #else return 0; #endif // !KHTML_NO_WALLET } void KHTMLPart::slotWalletClosed() { #ifndef KHTML_NO_WALLET if (d->m_wallet) { d->m_wallet->deleteLater(); d->m_wallet = nullptr; } d->m_bWalletOpened = false; if (d->m_statusBarWalletLabel) { d->m_statusBarExtension->removeStatusBarItem(d->m_statusBarWalletLabel); delete d->m_statusBarWalletLabel; d->m_statusBarWalletLabel = nullptr; } #endif // KHTML_NO_WALLET } void KHTMLPart::launchWalletManager() { #ifndef KHTML_NO_WALLET QDBusInterface r("org.kde.kwalletmanager", "/kwalletmanager/MainWindow_1", "org.kde.KMainWindow"); if (!r.isValid()) { KToolInvocation::startServiceByDesktopName("kwalletmanager_show"); } else { r.call(QDBus::NoBlock, "show"); r.call(QDBus::NoBlock, "raise"); } #endif // KHTML_NO_WALLET } void KHTMLPart::walletMenu() { #ifndef KHTML_NO_WALLET QMenu *menu = new QMenu(nullptr); QActionGroup *menuActionGroup = new QActionGroup(menu); connect(menuActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(removeStoredPasswordForm(QAction*))); menu->addAction(i18n("&Close Wallet"), this, SLOT(slotWalletClosed())); if (d->m_view && d->m_view->nonPasswordStorableSite(toplevelURL().host())) { menu->addAction(i18n("&Allow storing passwords for this site"), this, SLOT(delNonPasswordStorableSite())); } // List currently removable form passwords for (QStringList::ConstIterator it = d->m_walletForms.constBegin(); it != d->m_walletForms.constEnd(); ++it) { QAction *action = menu->addAction(i18n("Remove password for form %1", *it)); action->setActionGroup(menuActionGroup); QVariant var(*it); action->setData(var); } KAcceleratorManager::manage(menu); menu->popup(QCursor::pos()); #endif // KHTML_NO_WALLET } void KHTMLPart::removeStoredPasswordForm(QAction *action) { #ifndef KHTML_NO_WALLET assert(action); assert(d->m_wallet); QVariant var(action->data()); if (var.isNull() || !var.isValid() || var.type() != QVariant::String) { return; } QString key = var.toString(); if (KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(), KWallet::Wallet::FormDataFolder(), key)) { return; // failed } if (!d->m_wallet->hasFolder(KWallet::Wallet::FormDataFolder())) { return; // failed } d->m_wallet->setFolder(KWallet::Wallet::FormDataFolder()); if (d->m_wallet->removeEntry(key)) { return; // failed } d->m_walletForms.removeAll(key); #endif // KHTML_NO_WALLET } void KHTMLPart::addWalletFormKey(const QString &walletFormKey) { #ifndef KHTML_NO_WALLET if (parentPart()) { parentPart()->addWalletFormKey(walletFormKey); return; } if (!d->m_walletForms.contains(walletFormKey)) { d->m_walletForms.append(walletFormKey); } #endif // KHTML_NO_WALLET } void KHTMLPart::delNonPasswordStorableSite() { #ifndef KHTML_NO_WALLET if (d->m_view) { d->m_view->delNonPasswordStorableSite(toplevelURL().host()); } #endif // KHTML_NO_WALLET } void KHTMLPart::saveLoginInformation(const QString &host, const QString &key, const QMap &walletMap) { #ifndef KHTML_NO_WALLET d->m_storePass.saveLoginInformation(host, key, walletMap); #endif // KHTML_NO_WALLET } void KHTMLPart::slotToggleCaretMode() { setCaretMode(d->m_paToggleCaretMode->isChecked()); } void KHTMLPart::setFormNotification(KHTMLPart::FormNotification fn) { d->m_formNotification = fn; } KHTMLPart::FormNotification KHTMLPart::formNotification() const { return d->m_formNotification; } QUrl KHTMLPart::toplevelURL() { KHTMLPart *part = this; while (part->parentPart()) { part = part->parentPart(); } if (!part) { return QUrl(); } return part->url(); } bool KHTMLPart::isModified() const { if (!d->m_doc) { return false; } return d->m_doc->unsubmittedFormChanges(); } void KHTMLPart::setDebugScript(bool enable) { unplugActionList("debugScriptList"); if (enable) { if (!d->m_paDebugScript) { d->m_paDebugScript = new QAction(i18n("JavaScript &Debugger"), this); actionCollection()->addAction("debugScript", d->m_paDebugScript); connect(d->m_paDebugScript, SIGNAL(triggered(bool)), this, SLOT(slotDebugScript())); } d->m_paDebugScript->setEnabled(d->m_frame ? d->m_frame->m_jscript : nullptr); QList lst; lst.append(d->m_paDebugScript); plugActionList("debugScriptList", lst); } d->m_bJScriptDebugEnabled = enable; } void KHTMLPart::setSuppressedPopupIndicator(bool enable, KHTMLPart *originPart) { if (parentPart()) { parentPart()->setSuppressedPopupIndicator(enable, originPart); return; } if (enable && originPart) { d->m_openableSuppressedPopups++; if (d->m_suppressedPopupOriginParts.indexOf(originPart) == -1) { d->m_suppressedPopupOriginParts.append(originPart); } } if (enable && !d->m_statusBarPopupLabel) { d->m_statusBarPopupLabel = new KUrlLabel(d->m_statusBarExtension->statusBar()); d->m_statusBarPopupLabel->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum)); d->m_statusBarPopupLabel->setUseCursor(false); d->m_statusBarExtension->addStatusBarItem(d->m_statusBarPopupLabel, 0, false); d->m_statusBarPopupLabel->setPixmap(SmallIcon("window-suppressed")); d->m_statusBarPopupLabel->setToolTip(i18n("This page was prevented from opening a new window via JavaScript.")); connect(d->m_statusBarPopupLabel, SIGNAL(leftClickedUrl()), SLOT(suppressedPopupMenu())); if (d->m_settings->jsPopupBlockerPassivePopup()) { QPixmap px; px = MainBarIcon("window-suppressed"); KPassivePopup::message(i18n("Popup Window Blocked"), i18n("This page has attempted to open a popup window but was blocked.\nYou can click on this icon in the status bar to control this behavior\nor to open the popup."), px, d->m_statusBarPopupLabel); } } else if (!enable && d->m_statusBarPopupLabel) { d->m_statusBarPopupLabel->setToolTip(""); d->m_statusBarExtension->removeStatusBarItem(d->m_statusBarPopupLabel); delete d->m_statusBarPopupLabel; d->m_statusBarPopupLabel = nullptr; } } void KHTMLPart::suppressedPopupMenu() { QMenu *m = new QMenu(nullptr); if (d->m_openableSuppressedPopups) { m->addAction(i18np("&Show Blocked Popup Window", "&Show %1 Blocked Popup Windows", d->m_openableSuppressedPopups), this, SLOT(showSuppressedPopups())); } QAction *a = m->addAction(i18n("Show Blocked Window Passive Popup &Notification"), this, SLOT(togglePopupPassivePopup())); a->setChecked(d->m_settings->jsPopupBlockerPassivePopup()); m->addAction(i18n("&Configure JavaScript New Window Policies..."), this, SLOT(launchJSConfigDialog())); m->popup(QCursor::pos()); } void KHTMLPart::togglePopupPassivePopup() { // Same hack as in disableJSErrorExtension() d->m_settings->setJSPopupBlockerPassivePopup(!d->m_settings->jsPopupBlockerPassivePopup()); emit configurationChanged(); } void KHTMLPart::showSuppressedPopups() { foreach (KHTMLPart *part, d->m_suppressedPopupOriginParts) { if (part) { KJS::Window *w = KJS::Window::retrieveWindow(part); if (w) { w->showSuppressedWindows(); w->forgetSuppressedWindows(); } } } setSuppressedPopupIndicator(false); d->m_openableSuppressedPopups = 0; d->m_suppressedPopupOriginParts.clear(); } // Extension to use for "view document source", "save as" etc. // Using the right extension can help the viewer get into the right mode (#40496) QString KHTMLPart::defaultExtension() const { if (!d->m_doc) { return ".html"; } if (!d->m_doc->isHTMLDocument()) { return ".xml"; } return d->m_doc->htmlMode() == DOM::DocumentImpl::XHtml ? ".xhtml" : ".html"; } bool KHTMLPart::inProgress() const { if (!d->m_bComplete || d->m_runningScripts || (d->m_doc && d->m_doc->parsing())) { return true; } // Any frame that hasn't completed yet ? ConstFrameIt it = d->m_frames.constBegin(); const ConstFrameIt end = d->m_frames.constEnd(); for (; it != end; ++it) { if ((*it)->m_run || !(*it)->m_bCompleted) { return true; } } return d->m_submitForm || !d->m_redirectURL.isEmpty() || d->m_redirectionTimer.isActive() || d->m_job; } using namespace KParts; #include "moc_khtmlpart_p.cpp" #ifndef KHTML_NO_WALLET #include "moc_khtml_wallet_p.cpp" #endif diff --git a/src/khtmlview.cpp b/src/khtmlview.cpp index cc56d4c..985dbfd 100644 --- a/src/khtmlview.cpp +++ b/src/khtmlview.cpp @@ -1,4523 +1,4523 @@ /* This file is part of the KDE project * * Copyright (C) 1998, 1999 Torben Weis * 1999 Lars Knoll * 1999 Antti Koivisto * 2000-2004 Dirk Mueller * 2003 Leo Savernik * 2003-2008 Apple Computer, Inc. * 2008 Allan Sandfeld Jensen * 2006-2008 Germain Garand * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "khtmlview.h" #include "khtml_part.h" #include "khtml_events.h" #include #if HAVE_X11 #include #endif #include "html/html_documentimpl.h" #include "html/html_inlineimpl.h" #include "html/html_formimpl.h" #include "html/htmltokenizer.h" #include "editing/editor.h" #include "rendering/render_arena.h" #include "rendering/render_canvas.h" #include "rendering/render_frames.h" #include "rendering/render_replaced.h" #include "rendering/render_form.h" #include "rendering/render_layer.h" #include "rendering/render_line.h" #include "rendering/render_table.h" // removeme #define protected public #include "rendering/render_text.h" #undef protected #include "xml/dom2_eventsimpl.h" #include "css/cssstyleselector.h" #include "misc/loader.h" #include "khtml_settings.h" #include "khtml_printsettings.h" #include "khtmlpart_p.h" #include #include #include #include #include #include <../khtml_version.h> #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#define DEBUG_FLICKER #include #if HAVE_X11 #include #include #elif defined(Q_OS_WIN) #include #endif #if 0 namespace khtml { void dumpLineBoxes(RenderFlow *flow); } #endif using namespace DOM; using namespace khtml; #ifndef NDEBUG static const int sFirstLayoutDelay = 520; static const int sParsingLayoutsInterval = 380; static const int sLayoutAttemptDelay = 300; #else static const int sFirstLayoutDelay = 280; static const int sParsingLayoutsInterval = 320; static const int sLayoutAttemptDelay = 200; #endif static const int sLayoutAttemptIncrement = 20; static const int sParsingLayoutsIncrement = 60; static const int sSmoothScrollTime = 128; static const int sSmoothScrollTick = 16; static const int sSmoothScrollMinStaticPixels = 320 * 200; static const int sMaxMissedDeadlines = 12; static const int sWayTooMany = -1; class KHTMLViewPrivate { friend class KHTMLView; public: enum PseudoFocusNodes { PFNone, PFTop, PFBottom }; enum StaticBackgroundState { SBNone = 0, SBPartial, SBFull }; enum CompletedState { CSNone = 0, CSFull, CSActionPending }; KHTMLViewPrivate(KHTMLView *v) : underMouse(nullptr), underMouseNonShared(nullptr), oldUnderMouse(nullptr) { postponed_autorepeat = nullptr; scrollingFromWheelTimerId = 0; smoothScrollMode = KHTMLView::SSMWhenEfficient; reset(); vpolicy = Qt::ScrollBarAsNeeded; hpolicy = Qt::ScrollBarAsNeeded; formCompletions = nullptr; prevScrollbarVisible = true; possibleTripleClick = false; emitCompletedAfterRepaint = CSNone; cursorIconWidget = nullptr; cursorIconType = KHTMLView::LINK_NORMAL; m_mouseScrollTimer = nullptr; m_mouseScrollIndicator = nullptr; contentsX = 0; contentsY = 0; view = v; } ~KHTMLViewPrivate() { delete formCompletions; delete postponed_autorepeat; if (underMouse) { underMouse->deref(); } if (underMouseNonShared) { underMouseNonShared->deref(); } if (oldUnderMouse) { oldUnderMouse->deref(); } delete cursorIconWidget; delete m_mouseScrollTimer; delete m_mouseScrollIndicator; } void reset() { if (underMouse) { underMouse->deref(); } underMouse = nullptr; if (underMouseNonShared) { underMouseNonShared->deref(); } underMouseNonShared = nullptr; if (oldUnderMouse) { oldUnderMouse->deref(); } oldUnderMouse = nullptr; linkPressed = false; staticWidget = SBNone; fixedObjectsCount = 0; staticObjectsCount = 0; tabMovePending = false; lastTabbingDirection = true; pseudoFocusNode = PFNone; zoomLevel = 100; #ifndef KHTML_NO_SCROLLBARS //We don't turn off the toolbars here //since if the user turns them //off, then chances are they want them turned //off always - even after a reset. #else vpolicy = ScrollBarAlwaysOff; hpolicy = ScrollBarAlwaysOff; #endif scrollBarMoved = false; contentsMoving = false; ignoreWheelEvents = false; scrollingFromWheel = QPoint(-1, -1); borderX = 30; borderY = 30; steps = 0; dx = dy = 0; paged = false; clickX = -1; clickY = -1; clickCount = 0; isDoubleClick = false; scrollingSelf = false; delete postponed_autorepeat; postponed_autorepeat = nullptr; layoutTimerId = 0; repaintTimerId = 0; scrollTimerId = 0; scrollSuspended = false; scrollSuspendPreActivate = false; smoothScrolling = false; smoothScrollModeIsDefault = true; shouldSmoothScroll = false; smoothScrollMissedDeadlines = 0; hasFrameset = false; complete = false; firstLayoutPending = true; #ifdef SPEED_DEBUG firstRepaintPending = true; #endif needsFullRepaint = true; dirtyLayout = false; layoutSchedulingEnabled = true; painting = false; layoutCounter = 0; layoutAttemptCounter = 0; scheduledLayoutCounter = 0; updateRegion = QRegion(); m_dialogsAllowed = true; accessKeysActivated = false; accessKeysPreActivate = false; // the view might have been built before the part it will be assigned to, // so exceptionally, we need to directly ref/deref KHTMLGlobal to // account for this transitory case. KHTMLGlobal::ref(); accessKeysEnabled = KHTMLGlobal::defaultHTMLSettings()->accessKeysEnabled(); KHTMLGlobal::deref(); emitCompletedAfterRepaint = CSNone; m_mouseEventsTarget = nullptr; m_clipHolder = nullptr; } void newScrollTimer(QWidget *view, int tid) { //qDebug() << "newScrollTimer timer " << tid; view->killTimer(scrollTimerId); scrollTimerId = tid; scrollSuspended = false; } enum ScrollDirection { ScrollLeft, ScrollRight, ScrollUp, ScrollDown }; void adjustScroller(QWidget *view, ScrollDirection direction, ScrollDirection oppositedir) { static const struct { int msec, pixels; } timings [] = { {320, 1}, {224, 1}, {160, 1}, {112, 1}, {80, 1}, {56, 1}, {40, 1}, {28, 1}, {20, 1}, {20, 2}, {20, 3}, {20, 4}, {20, 6}, {20, 8}, {0, 0} }; if (!scrollTimerId || (static_cast(scrollDirection) != direction && (static_cast(scrollDirection) != oppositedir || scrollSuspended))) { scrollTiming = 6; scrollBy = timings[scrollTiming].pixels; scrollDirection = direction; newScrollTimer(view, view->startTimer(timings[scrollTiming].msec)); } else if (scrollDirection == direction && timings[scrollTiming + 1].msec && !scrollSuspended) { scrollBy = timings[++scrollTiming].pixels; newScrollTimer(view, view->startTimer(timings[scrollTiming].msec)); } else if (scrollDirection == oppositedir) { if (scrollTiming) { scrollBy = timings[--scrollTiming].pixels; newScrollTimer(view, view->startTimer(timings[scrollTiming].msec)); } } scrollSuspended = false; } bool haveZoom() const { return zoomLevel != 100; } void startScrolling() { smoothScrolling = true; smoothScrollTimer.start(sSmoothScrollTick); shouldSmoothScroll = false; } void stopScrolling() { smoothScrollTimer.stop(); dx = dy = 0; steps = 0; updateContentsXY(); smoothScrolling = false; shouldSmoothScroll = false; } void updateContentsXY() { contentsX = QApplication::isRightToLeft() ? view->horizontalScrollBar()->maximum() - view->horizontalScrollBar()->value() : view->horizontalScrollBar()->value(); contentsY = view->verticalScrollBar()->value(); } void scrollAccessKeys(int dx, int dy) { QList wl = view->widget()->findChildren("KHTMLAccessKey"); foreach (QLabel *w, wl) { w->move(w->pos() + QPoint(dx, dy)); } } void scrollExternalWidgets(int dx, int dy) { if (visibleWidgets.isEmpty()) { return; } QHashIterator it(visibleWidgets); while (it.hasNext()) { it.next(); it.value()->move(it.value()->pos() + QPoint(dx, dy)); } } NodeImpl *underMouse; NodeImpl *underMouseNonShared; NodeImpl *oldUnderMouse; // Do not adjust bitfield enums sizes. // They are oversized because they are signed on some platforms. bool tabMovePending: 1; bool lastTabbingDirection: 1; PseudoFocusNodes pseudoFocusNode: 3; bool scrollBarMoved: 1; bool contentsMoving: 1; Qt::ScrollBarPolicy vpolicy; Qt::ScrollBarPolicy hpolicy; bool prevScrollbarVisible: 1; bool linkPressed: 1; bool ignoreWheelEvents: 1; StaticBackgroundState staticWidget: 3; int staticObjectsCount; int fixedObjectsCount; int zoomLevel; int borderX, borderY; int dx, dy; int steps; KConfig *formCompletions; int clickX, clickY, clickCount; bool isDoubleClick; bool paged; bool scrollingSelf; int contentsX, contentsY; int layoutTimerId; QKeyEvent *postponed_autorepeat; int repaintTimerId; int scrollTimerId; int scrollTiming; int scrollBy; ScrollDirection scrollDirection : 3; bool scrollSuspended : 1; bool scrollSuspendPreActivate : 1; KHTMLView::SmoothScrollingMode smoothScrollMode : 3; bool smoothScrolling : 1; bool smoothScrollModeIsDefault : 1; bool shouldSmoothScroll : 1; bool hasFrameset : 1; bool complete : 1; bool firstLayoutPending : 1; #ifdef SPEED_DEBUG bool firstRepaintPending : 1; #endif bool layoutSchedulingEnabled : 1; bool needsFullRepaint : 1; bool painting : 1; bool possibleTripleClick : 1; bool dirtyLayout : 1; bool m_dialogsAllowed : 1; short smoothScrollMissedDeadlines; int layoutCounter; int layoutAttemptCounter; int scheduledLayoutCounter; QRegion updateRegion; QTimer smoothScrollTimer; QTime smoothScrollStopwatch; QHash visibleWidgets; bool accessKeysEnabled; bool accessKeysActivated; bool accessKeysPreActivate; CompletedState emitCompletedAfterRepaint; QLabel *cursorIconWidget; KHTMLView::LinkCursor cursorIconType; // scrolling activated by MMB short m_mouseScroll_byX; short m_mouseScroll_byY; QPoint scrollingFromWheel; int scrollingFromWheelTimerId; QTimer *m_mouseScrollTimer; QWidget *m_mouseScrollIndicator; QPointer m_mouseEventsTarget; QStack *m_clipHolder; KHTMLView *view; }; #ifndef QT_NO_TOOLTIP /** calculates the client-side image map rectangle for the given image element * @param img image element * @param scrollOfs scroll offset of viewport in content coordinates * @param p position to be probed in viewport coordinates * @param r returns the bounding rectangle in content coordinates * @param s returns the title string * @return true if an appropriate area was found -- only in this case r and * s are valid, false otherwise */ static bool findImageMapRect(HTMLImageElementImpl *img, const QPoint &scrollOfs, const QPoint &p, QRect &r, QString &s) { HTMLMapElementImpl *map; if (img && img->document()->isHTMLDocument() && (map = static_cast(img->document())->getMap(img->imageMap()))) { RenderObject::NodeInfo info(true, false); RenderObject *rend = img->renderer(); int ax, ay; if (!rend || !rend->absolutePosition(ax, ay)) { return false; } // we're a client side image map bool inside = map->mapMouseEvent(p.x() - ax + scrollOfs.x(), p.y() - ay + scrollOfs.y(), rend->contentWidth(), rend->contentHeight(), info); if (inside && info.URLElement()) { HTMLAreaElementImpl *area = static_cast(info.URLElement()); Q_ASSERT(area->id() == ID_AREA); s = area->getAttribute(ATTR_TITLE).string(); QRegion reg = area->cachedRegion(); if (!s.isEmpty() && !reg.isEmpty()) { r = reg.boundingRect(); r.translate(ax, ay); return true; } } } return false; } bool KHTMLView::event(QEvent *e) { switch (e->type()) { case QEvent::ToolTip: { QHelpEvent *he = static_cast(e); QPoint p = he->pos(); DOM::NodeImpl *node = d->underMouseNonShared; QRect region; while (node) { if (node->isElementNode()) { DOM::ElementImpl *e = static_cast(node); QRect r; QString s; bool found = false; // for images, check if it is part of a client-side image map, // and query the s' title attributes, too if (e->id() == ID_IMG && !e->getAttribute(ATTR_USEMAP).isEmpty()) { found = findImageMapRect(static_cast(e), viewportToContents(QPoint(0, 0)), p, r, s); } if (!found) { s = e->getAttribute(ATTR_TITLE).string().trimmed(); r = node->getRect(); } region |= QRect(contentsToViewport(r.topLeft()), r.size()); if (!s.isEmpty()) { QToolTip::showText(he->globalPos(), Qt::convertFromPlainText(s, Qt::WhiteSpaceNormal), widget(), region); break; } } node = node->parentNode(); } // Qt makes tooltip events happen nearly immediately when a preceding one was processed in the past few seconds. // We don't want that feature to apply to web tootlips however, as it clashes with dhtml menus. // So we'll just pretend we did not process that event. return false; } case QEvent::DragEnter: case QEvent::DragMove: case QEvent::DragLeave: case QEvent::Drop: // In Qt4, one needs to both call accept() on the DND event and return // true on ::event for the candidate widget for the drop to be possible. // Apps hosting us, such as konq, can do the former but not the later. // We will do the second bit, as it's a no-op unless someone else explicitly // accepts the event. We need to skip the scrollarea to do that, // since it will just skip the events, both killing the drop, and // not permitting us to forward it up the part hiearchy in our dragEnterEvent, // etc. handlers return QWidget::event(e); case QEvent::StyleChange: case QEvent::LayoutRequest: { updateScrollBars(); return QAbstractScrollArea::event(e); } case QEvent::PaletteChange: slotPaletteChanged(); return QScrollArea::event(e); default: return QScrollArea::event(e); } } #endif KHTMLView::KHTMLView(KHTMLPart *part, QWidget *parent) : QScrollArea(parent), d(new KHTMLViewPrivate(this)) { m_medium = "screen"; m_part = part; QScrollArea::setVerticalScrollBarPolicy(d->vpolicy); QScrollArea::setHorizontalScrollBarPolicy(d->hpolicy); initWidget(); widget()->setMouseTracking(true); } KHTMLView::~KHTMLView() { closeChildDialogs(); if (m_part) { DOM::DocumentImpl *doc = m_part->xmlDocImpl(); if (doc) { doc->detach(); } } delete d; } void KHTMLView::setPart(KHTMLPart *part) { assert(part && !m_part); m_part = part; } void KHTMLView::initWidget() { // Do not access the part here. It might not be fully constructed. setFrameStyle(QFrame::NoFrame); setFocusPolicy(Qt::StrongFocus); viewport()->setFocusProxy(this); _marginWidth = -1; // undefined _marginHeight = -1; _width = 0; _height = 0; installEventFilter(this); setAcceptDrops(true); if (!widget()) { setWidget(new QWidget(this)); } widget()->setAttribute(Qt::WA_NoSystemBackground); // Do *not* remove this attribute frivolously. // You might not notice a change of behaviour in Debug builds // but removing opaque events will make QWidget::scroll fail horribly // in Release builds. widget()->setAttribute(Qt::WA_OpaquePaintEvent); verticalScrollBar()->setCursor(Qt::ArrowCursor); horizontalScrollBar()->setCursor(Qt::ArrowCursor); connect(&d->smoothScrollTimer, SIGNAL(timeout()), this, SLOT(scrollTick())); } void KHTMLView::resizeContentsToViewport() { QSize s = viewport()->size(); resizeContents(s.width(), s.height()); } // called by KHTMLPart::clear() void KHTMLView::clear() { if (d->accessKeysEnabled && d->accessKeysActivated) { accessKeysTimeout(); } viewport()->unsetCursor(); if (d->cursorIconWidget) { d->cursorIconWidget->hide(); } if (d->smoothScrolling) { d->stopScrolling(); } d->reset(); QAbstractEventDispatcher::instance()->unregisterTimers(this); emit cleared(); QScrollArea::setHorizontalScrollBarPolicy(d->hpolicy); QScrollArea::setVerticalScrollBarPolicy(d->vpolicy); verticalScrollBar()->setEnabled(false); horizontalScrollBar()->setEnabled(false); } void KHTMLView::hideEvent(QHideEvent *e) { QScrollArea::hideEvent(e); } void KHTMLView::showEvent(QShowEvent *e) { QScrollArea::showEvent(e); } void KHTMLView::setMouseEventsTarget(QWidget *w) { d->m_mouseEventsTarget = w; } QWidget *KHTMLView::mouseEventsTarget() const { return d->m_mouseEventsTarget; } void KHTMLView::setClipHolder(QStack *ch) { d->m_clipHolder = ch; } QStack *KHTMLView::clipHolder() const { return d->m_clipHolder; } int KHTMLView::contentsWidth() const { return widget() ? widget()->width() : 0; } int KHTMLView::contentsHeight() const { return widget() ? widget()->height() : 0; } void KHTMLView::resizeContents(int w, int h) { if (!widget()) { return; } widget()->resize(w, h); if (!widget()->isVisible()) { updateScrollBars(); } } int KHTMLView::contentsX() const { return d->contentsX; } int KHTMLView::contentsY() const { return d->contentsY; } int KHTMLView::visibleWidth() const { if (m_kwp->isRedirected()) { // our RenderWidget knows better if (RenderWidget *rw = m_kwp->renderWidget()) { int ret = rw->width() - rw->paddingLeft() - rw->paddingRight() - rw->borderLeft() - rw->borderRight(); if (verticalScrollBar()->isVisible()) { ret -= verticalScrollBar()->sizeHint().width(); ret = qMax(0, ret); } return ret; } } return viewport()->width(); } int KHTMLView::visibleHeight() const { if (m_kwp->isRedirected()) { // our RenderWidget knows better if (RenderWidget *rw = m_kwp->renderWidget()) { int ret = rw->height() - rw->paddingBottom() - rw->paddingTop() - rw->borderTop() - rw->borderBottom(); if (horizontalScrollBar()->isVisible()) { ret -= horizontalScrollBar()->sizeHint().height(); ret = qMax(0, ret); } return ret; } } return viewport()->height(); } void KHTMLView::setContentsPos(int x, int y) { horizontalScrollBar()->setValue(QApplication::isRightToLeft() ? horizontalScrollBar()->maximum() - x : x); verticalScrollBar()->setValue(y); } void KHTMLView::scrollBy(int x, int y) { if (d->scrollTimerId) { d->newScrollTimer(this, 0); } horizontalScrollBar()->setValue(horizontalScrollBar()->value() + x); verticalScrollBar()->setValue(verticalScrollBar()->value() + y); } QPoint KHTMLView::contentsToViewport(const QPoint &p) const { return QPoint(p.x() - contentsX(), p.y() - contentsY()); } void KHTMLView::contentsToViewport(int x, int y, int &cx, int &cy) const { QPoint p(x, y); p = contentsToViewport(p); cx = p.x(); cy = p.y(); } QPoint KHTMLView::viewportToContents(const QPoint &p) const { return QPoint(p.x() + contentsX(), p.y() + contentsY()); } void KHTMLView::viewportToContents(int x, int y, int &cx, int &cy) const { QPoint p(x, y); p = viewportToContents(p); cx = p.x(); cy = p.y(); } void KHTMLView::updateContents(int x, int y, int w, int h) { applyTransforms(x, y, w, h); if (m_kwp->isRedirected()) { QPoint off = m_kwp->absolutePos(); KHTMLView *pview = m_part->parentPart()->view(); pview->updateContents(x + off.x(), y + off.y(), w, h); } else { widget()->update(x, y, w, h); } } void KHTMLView::updateContents(const QRect &r) { updateContents(r.x(), r.y(), r.width(), r.height()); } void KHTMLView::repaintContents(int x, int y, int w, int h) { applyTransforms(x, y, w, h); if (m_kwp->isRedirected()) { QPoint off = m_kwp->absolutePos(); KHTMLView *pview = m_part->parentPart()->view(); pview->repaintContents(x + off.x(), y + off.y(), w, h); } else { widget()->repaint(x, y, w, h); } } void KHTMLView::repaintContents(const QRect &r) { repaintContents(r.x(), r.y(), r.width(), r.height()); } void KHTMLView::applyTransforms(int &x, int &y, int &w, int &h) const { if (d->haveZoom()) { const int z = d->zoomLevel; x = x * z / 100; y = y * z / 100; w = w * z / 100; h = h * z / 100; } x -= contentsX(); y -= contentsY(); } void KHTMLView::revertTransforms(int &x, int &y, int &w, int &h) const { x += contentsX(); y += contentsY(); if (d->haveZoom()) { const int z = d->zoomLevel; x = x * 100 / z; y = y * 100 / z; w = w * 100 / z; h = h * 100 / z; } } void KHTMLView::revertTransforms(int &x, int &y) const { int dummy = 0; revertTransforms(x, y, dummy, dummy); } void KHTMLView::resizeEvent(QResizeEvent * /*e*/) { updateScrollBars(); // If we didn't load anything, make white area as big as the view if (!m_part->xmlDocImpl()) { resizeContentsToViewport(); } // Viewport-dependent media queries may cause us to need completely different style information. if (m_part->xmlDocImpl() && m_part->xmlDocImpl()->styleSelector()->affectedByViewportChange()) { m_part->xmlDocImpl()->updateStyleSelector(); } if (d->layoutSchedulingEnabled) { layout(); } QApplication::sendPostedEvents(viewport(), QEvent::Paint); if (m_part && m_part->xmlDocImpl()) { if (m_part->parentPart()) { // sub-frame : queue the resize event until our toplevel is done layouting khtml::ChildFrame *cf = m_part->parentPart()->frame(m_part); if (cf && !cf->m_partContainerElement.isNull()) { cf->m_partContainerElement.data()->postResizeEvent(); } } else { // toplevel : dispatch sub-frames'resize events before our own HTMLPartContainerElementImpl::sendPostedResizeEvents(); m_part->xmlDocImpl()->dispatchWindowEvent(EventImpl::RESIZE_EVENT, false, false); } } } void KHTMLView::paintEvent(QPaintEvent *e) { QRect r = e->rect(); QRect v(contentsX(), contentsY(), visibleWidth(), visibleHeight()); QPoint off(contentsX(), contentsY()); r.translate(off); r = r.intersect(v); if (!r.isValid() || r.isEmpty()) { return; } QPainter p(widget()); p.translate(-off); if (d->haveZoom()) { p.scale(d->zoomLevel / 100., d->zoomLevel / 100.); r.setX(r.x() * 100 / d->zoomLevel); r.setY(r.y() * 100 / d->zoomLevel); r.setWidth(r.width() * 100 / d->zoomLevel); r.setHeight(r.height() * 100 / d->zoomLevel); r.adjust(-1, -1, 1, 1); } p.setClipRect(r); int ex = r.x(); int ey = r.y(); int ew = r.width(); int eh = r.height(); if (!m_part || !m_part->xmlDocImpl() || !m_part->xmlDocImpl()->renderer()) { p.fillRect(ex, ey, ew, eh, palette().brush(QPalette::Active, QPalette::Base)); return; } else if (d->complete && static_cast(m_part->xmlDocImpl()->renderer())->needsLayout()) { // an external update request happens while we have a layout scheduled unscheduleRelayout(); layout(); } else if (m_part->xmlDocImpl()->tokenizer()) { m_part->xmlDocImpl()->tokenizer()->setNormalYieldDelay(); } if (d->painting) { // qDebug() << "WARNING: paintEvent reentered! "; return; } d->painting = true; m_part->xmlDocImpl()->renderer()->layer()->paint(&p, r); if (d->hasFrameset) { NodeImpl *body = static_cast(m_part->xmlDocImpl())->body(); if (body && body->renderer() && body->id() == ID_FRAMESET) { static_cast(body->renderer())->paintFrameSetRules(&p, r); } else { d->hasFrameset = false; } } khtml::DrawContentsEvent event(&p, ex, ey, ew, eh); QApplication::sendEvent(m_part, &event); if (d->contentsMoving && !d->smoothScrolling && widget()->underMouse()) { QMouseEvent *tempEvent = new QMouseEvent(QEvent::MouseMove, widget()->mapFromGlobal(QCursor::pos()), Qt::NoButton, Qt::NoButton, Qt::NoModifier); QApplication::postEvent(widget(), tempEvent); } #ifdef SPEED_DEBUG if (d->firstRepaintPending && !m_part->parentPart()) { qDebug() << "FIRST PAINT:" << m_part->d->m_parsetime.elapsed(); } d->firstRepaintPending = false; #endif d->painting = false; } void KHTMLView::setMarginWidth(int w) { // make it update the rendering area when set _marginWidth = w; } void KHTMLView::setMarginHeight(int h) { // make it update the rendering area when set _marginHeight = h; } void KHTMLView::layout() { if (m_part && m_part->xmlDocImpl()) { DOM::DocumentImpl *document = m_part->xmlDocImpl(); khtml::RenderCanvas *canvas = static_cast(document->renderer()); if (!canvas) { return; } d->layoutSchedulingEnabled = false; d->dirtyLayout = true; // the reference object for the overflow property on canvas RenderObject *ref = nullptr; RenderObject *root = document->documentElement() ? document->documentElement()->renderer() : nullptr; if (document->isHTMLDocument()) { NodeImpl *body = static_cast(document)->body(); if (body && body->renderer() && body->id() == ID_FRAMESET) { QScrollArea::setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); QScrollArea::setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); body->renderer()->setNeedsLayout(true); d->hasFrameset = true; } else if (root) { // only apply body's overflow to canvas if root has a visible overflow ref = (!body || root->style()->hidesOverflow()) ? root : body->renderer(); } } else { ref = root; } if (ref) { if (ref->style()->overflowX() == OHIDDEN) { if (d->hpolicy == Qt::ScrollBarAsNeeded) { QScrollArea::setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } } else if (ref->style()->overflowX() == OSCROLL) { if (d->hpolicy == Qt::ScrollBarAsNeeded) { QScrollArea::setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); } } else if (horizontalScrollBarPolicy() != d->hpolicy) { QScrollArea::setHorizontalScrollBarPolicy(d->hpolicy); } if (ref->style()->overflowY() == OHIDDEN) { if (d->vpolicy == Qt::ScrollBarAsNeeded) { QScrollArea::setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } } else if (ref->style()->overflowY() == OSCROLL) { if (d->vpolicy == Qt::ScrollBarAsNeeded) { QScrollArea::setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); } } else if (verticalScrollBarPolicy() != d->vpolicy) { QScrollArea::setVerticalScrollBarPolicy(d->vpolicy); } } d->needsFullRepaint = d->firstLayoutPending; if (_height != visibleHeight() || _width != visibleWidth()) { ; d->needsFullRepaint = true; _height = visibleHeight(); _width = visibleWidth(); } canvas->layout(); emit finishedLayout(); if (d->firstLayoutPending) { // make sure firstLayoutPending is set to false now in case this layout // wasn't scheduled d->firstLayoutPending = false; verticalScrollBar()->setEnabled(true); horizontalScrollBar()->setEnabled(true); } d->layoutCounter++; if (d->accessKeysEnabled && d->accessKeysActivated) { emit hideAccessKeys(); displayAccessKeys(); } } else { _width = visibleWidth(); } if (d->layoutTimerId) { killTimer(d->layoutTimerId); } d->layoutTimerId = 0; d->layoutSchedulingEnabled = true; } void KHTMLView::closeChildDialogs() { QList dlgs = findChildren(); foreach (QDialog *dlg, dlgs) { if (dlg->testAttribute(Qt::WA_ShowModal)) { // qDebug() << "closeChildDialogs: closing dialog " << dlg; // close() ends up calling QButton::animateClick, which isn't immediate // we need something the exits the event loop immediately (#49068) dlg->reject(); } } d->m_dialogsAllowed = false; } bool KHTMLView::dialogsAllowed() { bool allowed = d->m_dialogsAllowed; KHTMLPart *p = m_part->parentPart(); if (p && p->view()) { allowed &= p->view()->dialogsAllowed(); } return allowed; } void KHTMLView::closeEvent(QCloseEvent *ev) { closeChildDialogs(); QScrollArea::closeEvent(ev); } void KHTMLView::setZoomLevel(int percent) { percent = percent < 20 ? 20 : (percent > 800 ? 800 : percent); int oldpercent = d->zoomLevel; d->zoomLevel = percent; if (percent != oldpercent) { if (d->layoutSchedulingEnabled) { layout(); } widget()->update(); } } int KHTMLView::zoomLevel() const { return d->zoomLevel; } void KHTMLView::setSmoothScrollingMode(SmoothScrollingMode m) { d->smoothScrollMode = m; d->smoothScrollModeIsDefault = false; if (d->smoothScrolling && !m) { d->stopScrolling(); } } void KHTMLView::setSmoothScrollingModeDefault(SmoothScrollingMode m) { // check for manual override if (!d->smoothScrollModeIsDefault) { return; } d->smoothScrollMode = m; if (d->smoothScrolling && !m) { d->stopScrolling(); } } KHTMLView::SmoothScrollingMode KHTMLView::smoothScrollingMode() const { return d->smoothScrollMode; } // // Event Handling // ///////////////// void KHTMLView::mousePressEvent(QMouseEvent *_mouse) { if (!m_part->xmlDocImpl()) { return; } if (d->possibleTripleClick && (_mouse->button() & Qt::MouseButtonMask) == Qt::LeftButton) { mouseDoubleClickEvent(_mouse); // it handles triple clicks too return; } int xm = _mouse->x(); int ym = _mouse->y(); revertTransforms(xm, ym); // qDebug() << "mousePressEvent: viewport=("<<_mouse->x()-contentsX()<<"/"<<_mouse->y()-contentsY()<<"), contents=(" << xm << "/" << ym << ")\n"; d->isDoubleClick = false; DOM::NodeImpl::MouseEvent mev(_mouse->buttons(), DOM::NodeImpl::MousePress); m_part->xmlDocImpl()->prepareMouseEvent(false, xm, ym, &mev); //qDebug() << "innerNode="<button() == Qt::MidButton) && !m_part->d->m_bOpenMiddleClick && !d->m_mouseScrollTimer && mev.url.isNull() && (mev.innerNode.elementId() != ID_INPUT)) { QPoint point = mapFromGlobal(_mouse->globalPos()); d->m_mouseScroll_byX = 0; d->m_mouseScroll_byY = 0; d->m_mouseScrollTimer = new QTimer(this); connect(d->m_mouseScrollTimer, SIGNAL(timeout()), this, SLOT(slotMouseScrollTimer())); if (!d->m_mouseScrollIndicator) { QPixmap pixmap(48, 48), icon; pixmap.fill(QColor(qRgba(127, 127, 127, 127))); QPainter p(&pixmap); QStyleOption option; option.rect.setRect(16, 0, 16, 16); QApplication::style()->drawPrimitive(QStyle::PE_IndicatorArrowUp, &option, &p); option.rect.setRect(0, 16, 16, 16); QApplication::style()->drawPrimitive(QStyle::PE_IndicatorArrowLeft, &option, &p); option.rect.setRect(16, 32, 16, 16); QApplication::style()->drawPrimitive(QStyle::PE_IndicatorArrowDown, &option, &p); option.rect.setRect(32, 16, 16, 16); QApplication::style()->drawPrimitive(QStyle::PE_IndicatorArrowRight, &option, &p); p.drawEllipse(23, 23, 2, 2); d->m_mouseScrollIndicator = new QWidget(this); d->m_mouseScrollIndicator->setFixedSize(48, 48); QPalette palette; palette.setBrush(d->m_mouseScrollIndicator->backgroundRole(), QBrush(pixmap)); d->m_mouseScrollIndicator->setPalette(palette); } d->m_mouseScrollIndicator->move(point.x() - 24, point.y() - 24); bool hasHorBar = visibleWidth() < contentsWidth(); bool hasVerBar = visibleHeight() < contentsHeight(); KConfigGroup cg(KSharedConfig::openConfig(), "HTML Settings"); if (cg.readEntry("ShowMouseScrollIndicator", true)) { d->m_mouseScrollIndicator->show(); d->m_mouseScrollIndicator->unsetCursor(); QBitmap mask = d->m_mouseScrollIndicator->palette().brush(d->m_mouseScrollIndicator->backgroundRole()).texture().createHeuristicMask(true); if (hasHorBar && !hasVerBar) { QBitmap bm(16, 16); bm.clear(); QPainter painter(&mask); painter.drawPixmap(QRectF(16, 0, bm.width(), bm.height()), bm, bm.rect()); painter.drawPixmap(QRectF(16, 32, bm.width(), bm.height()), bm, bm.rect()); d->m_mouseScrollIndicator->setCursor(Qt::SizeHorCursor); } else if (!hasHorBar && hasVerBar) { QBitmap bm(16, 16); bm.clear(); QPainter painter(&mask); painter.drawPixmap(QRectF(0, 16, bm.width(), bm.height()), bm, bm.rect()); painter.drawPixmap(QRectF(32, 16, bm.width(), bm.height()), bm, bm.rect()); d->m_mouseScrollIndicator->setCursor(Qt::SizeVerCursor); } else { d->m_mouseScrollIndicator->setCursor(Qt::SizeAllCursor); } d->m_mouseScrollIndicator->setMask(mask); } else { if (hasHorBar && !hasVerBar) { viewport()->setCursor(Qt::SizeHorCursor); } else if (!hasHorBar && hasVerBar) { viewport()->setCursor(Qt::SizeVerCursor); } else { viewport()->setCursor(Qt::SizeAllCursor); } } return; } else if (d->m_mouseScrollTimer) { delete d->m_mouseScrollTimer; d->m_mouseScrollTimer = nullptr; if (d->m_mouseScrollIndicator) { d->m_mouseScrollIndicator->hide(); } } if (d->clickCount > 0 && QPoint(d->clickX - xm, d->clickY - ym).manhattanLength() <= QApplication::startDragDistance()) { d->clickCount++; } else { d->clickCount = 1; d->clickX = xm; d->clickY = ym; } bool swallowEvent = dispatchMouseEvent(EventImpl::MOUSEDOWN_EVENT, mev.innerNode.handle(), mev.innerNonSharedNode.handle(), true, d->clickCount, _mouse, true, DOM::NodeImpl::MousePress); if (!swallowEvent) { emit m_part->nodeActivated(mev.innerNode); khtml::MousePressEvent event(_mouse, xm, ym, mev.url, mev.target, mev.innerNode); QApplication::sendEvent(m_part, &event); // we might be deleted after this } } void KHTMLView::mouseDoubleClickEvent(QMouseEvent *_mouse) { if (!m_part->xmlDocImpl()) { return; } int xm = _mouse->x(); int ym = _mouse->y(); revertTransforms(xm, ym); // qDebug() << "mouseDblClickEvent: x=" << xm << ", y=" << ym; d->isDoubleClick = true; DOM::NodeImpl::MouseEvent mev(_mouse->buttons(), DOM::NodeImpl::MouseDblClick); m_part->xmlDocImpl()->prepareMouseEvent(false, xm, ym, &mev); // We do the same thing as mousePressEvent() here, since the DOM does not treat // single and double-click events as separate (only the detail, i.e. number of clicks differs) if (d->clickCount > 0 && QPoint(d->clickX - xm, d->clickY - ym).manhattanLength() <= QApplication::startDragDistance()) { d->clickCount++; } else { // shouldn't happen, if Qt has the same criterias for double clicks. d->clickCount = 1; d->clickX = xm; d->clickY = ym; } bool swallowEvent = dispatchMouseEvent(EventImpl::MOUSEDOWN_EVENT, mev.innerNode.handle(), mev.innerNonSharedNode.handle(), true, d->clickCount, _mouse, true, DOM::NodeImpl::MouseDblClick); if (!swallowEvent) { khtml::MouseDoubleClickEvent event(_mouse, xm, ym, mev.url, mev.target, mev.innerNode, d->clickCount); QApplication::sendEvent(m_part, &event); } d->possibleTripleClick = true; QTimer::singleShot(QApplication::doubleClickInterval(), this, SLOT(tripleClickTimeout())); } void KHTMLView::tripleClickTimeout() { d->possibleTripleClick = false; d->clickCount = 0; } static bool targetOpensNewWindow(KHTMLPart *part, QString target) { if (!target.isEmpty() && (target.toLower() != "_top") && (target.toLower() != "_self") && (target.toLower() != "_parent")) { if (target.toLower() == "_blank") { return true; } else { while (part->parentPart()) { part = part->parentPart(); } if (!part->frameExists(target)) { return true; } } } return false; } void KHTMLView::mouseMoveEvent(QMouseEvent *_mouse) { if (d->m_mouseScrollTimer) { QPoint point = mapFromGlobal(_mouse->globalPos()); int deltaX = point.x() - d->m_mouseScrollIndicator->x() - 24; int deltaY = point.y() - d->m_mouseScrollIndicator->y() - 24; (deltaX > 0) ? d->m_mouseScroll_byX = 1 : d->m_mouseScroll_byX = -1; (deltaY > 0) ? d->m_mouseScroll_byY = 1 : d->m_mouseScroll_byY = -1; double adX = qAbs(deltaX) / 30.0; double adY = qAbs(deltaY) / 30.0; d->m_mouseScroll_byX = qMax(qMin(d->m_mouseScroll_byX * int(adX * adX), SHRT_MAX), SHRT_MIN); d->m_mouseScroll_byY = qMax(qMin(d->m_mouseScroll_byY * int(adY * adY), SHRT_MAX), SHRT_MIN); if (d->m_mouseScroll_byX == 0 && d->m_mouseScroll_byY == 0) { d->m_mouseScrollTimer->stop(); } else if (!d->m_mouseScrollTimer->isActive()) { d->m_mouseScrollTimer->start(20); } } if (!m_part->xmlDocImpl()) { return; } int xm = _mouse->x(); int ym = _mouse->y(); revertTransforms(xm, ym); DOM::NodeImpl::MouseEvent mev(_mouse->buttons(), DOM::NodeImpl::MouseMove); // Do not modify :hover/:active state while mouse is pressed. m_part->xmlDocImpl()->prepareMouseEvent(_mouse->buttons() /*readonly ?*/, xm, ym, &mev); // qDebug() << "mouse move: " << _mouse->pos() // << " button " << _mouse->button() - // << " state " << _mouse->state() << endl; + // << " state " << _mouse->state(); DOM::NodeImpl *target = mev.innerNode.handle(); DOM::NodeImpl *fn = m_part->xmlDocImpl()->focusNode(); // a widget may be the real target of this event (e.g. if a scrollbar's slider is being moved) if (d->m_mouseEventsTarget && fn && fn->renderer() && fn->renderer()->isWidget()) { target = fn; } bool swallowEvent = dispatchMouseEvent(EventImpl::MOUSEMOVE_EVENT, target, mev.innerNonSharedNode.handle(), false, 0, _mouse, true, DOM::NodeImpl::MouseMove); if (d->clickCount > 0 && QPoint(d->clickX - xm, d->clickY - ym).manhattanLength() > QApplication::startDragDistance()) { d->clickCount = 0; // moving the mouse outside the threshold invalidates the click } khtml::RenderObject *r = target ? target->renderer() : nullptr; bool setCursor = true; bool forceDefault = false; if (r && r->isWidget()) { RenderWidget *rw = static_cast(r); KHTMLWidget *kw = qobject_cast(rw->widget()) ? dynamic_cast(rw->widget()) : nullptr; if (kw && kw->m_kwp->isRedirected()) { setCursor = false; } else if (QLineEdit *le = qobject_cast(rw->widget())) { QList wl = le->findChildren("KLineEditButton"); // force arrow cursor above lineedit clear button foreach (QWidget *w, wl) { if (w->underMouse()) { forceDefault = true; break; } } } else if (QTextEdit *te = qobject_cast(rw->widget())) { if (te->verticalScrollBar()->underMouse() || te->horizontalScrollBar()->underMouse()) { forceDefault = true; } } } khtml::RenderStyle *style = (r && r->style()) ? r->style() : nullptr; QCursor c; LinkCursor linkCursor = LINK_NORMAL; switch (!forceDefault ? (style ? style->cursor() : CURSOR_AUTO) : CURSOR_DEFAULT) { case CURSOR_AUTO: if (r && r->isText() && ((m_part->d->m_bMousePressed && m_part->d->editor_context.m_beganSelectingText) || !r->isPointInsideSelection(xm, ym, m_part->caret()))) { c = QCursor(Qt::IBeamCursor); } if (mev.url.length() && m_part->settings()->changeCursor()) { c = m_part->urlCursor(); if (mev.url.string().startsWith("mailto:") && mev.url.string().indexOf('@') > 0) { linkCursor = LINK_MAILTO; } else if (targetOpensNewWindow(m_part, mev.target.string())) { linkCursor = LINK_NEWWINDOW; } } if (r && r->isFrameSet() && !static_cast(r)->noResize()) { c = QCursor(static_cast(r)->cursorShape()); } break; case CURSOR_CROSS: c = QCursor(Qt::CrossCursor); break; case CURSOR_POINTER: c = m_part->urlCursor(); if (mev.url.string().startsWith("mailto:") && mev.url.string().indexOf('@') > 0) { linkCursor = LINK_MAILTO; } else if (targetOpensNewWindow(m_part, mev.target.string())) { linkCursor = LINK_NEWWINDOW; } break; case CURSOR_PROGRESS: c = QCursor(Qt::BusyCursor); // working_cursor break; case CURSOR_MOVE: case CURSOR_ALL_SCROLL: c = QCursor(Qt::SizeAllCursor); break; case CURSOR_E_RESIZE: case CURSOR_W_RESIZE: case CURSOR_EW_RESIZE: c = QCursor(Qt::SizeHorCursor); break; case CURSOR_N_RESIZE: case CURSOR_S_RESIZE: case CURSOR_NS_RESIZE: c = QCursor(Qt::SizeVerCursor); break; case CURSOR_NE_RESIZE: case CURSOR_SW_RESIZE: case CURSOR_NESW_RESIZE: c = QCursor(Qt::SizeBDiagCursor); break; case CURSOR_NW_RESIZE: case CURSOR_SE_RESIZE: case CURSOR_NWSE_RESIZE: c = QCursor(Qt::SizeFDiagCursor); break; case CURSOR_TEXT: c = QCursor(Qt::IBeamCursor); break; case CURSOR_WAIT: c = QCursor(Qt::WaitCursor); break; case CURSOR_HELP: c = QCursor(Qt::WhatsThisCursor); break; case CURSOR_DEFAULT: break; case CURSOR_NONE: case CURSOR_NOT_ALLOWED: c = QCursor(Qt::ForbiddenCursor); break; case CURSOR_ROW_RESIZE: c = QCursor(Qt::SplitVCursor); break; case CURSOR_COL_RESIZE: c = QCursor(Qt::SplitHCursor); break; case CURSOR_VERTICAL_TEXT: case CURSOR_CONTEXT_MENU: case CURSOR_NO_DROP: case CURSOR_CELL: case CURSOR_COPY: case CURSOR_ALIAS: c = QCursor(Qt::ArrowCursor); break; } if (!setCursor && style && style->cursor() != CURSOR_AUTO) { setCursor = true; } QWidget *vp = viewport(); for (KHTMLPart *p = m_part; p; p = p->parentPart()) if (!p->parentPart()) { vp = p->view()->viewport(); } if (setCursor && (vp->cursor().shape() != c.shape() || c.shape() == Qt::BitmapCursor)) { if (c.shape() == Qt::ArrowCursor) { for (KHTMLPart *p = m_part; p; p = p->parentPart()) { p->view()->viewport()->unsetCursor(); } } else { vp->setCursor(c); } } if (linkCursor != LINK_NORMAL && isVisible() && hasFocus()) { #if HAVE_X11 if (!d->cursorIconWidget) { #if HAVE_X11 d->cursorIconWidget = new QLabel(nullptr, Qt::X11BypassWindowManagerHint); XSetWindowAttributes attr; attr.save_under = True; XChangeWindowAttributes(QX11Info::display(), d->cursorIconWidget->winId(), CWSaveUnder, &attr); #else d->cursorIconWidget = new QLabel(NULL, NULL); //TODO #endif } // Update the pixmap if need be. if (linkCursor != d->cursorIconType) { d->cursorIconType = linkCursor; QString cursorIcon; switch (linkCursor) { case LINK_MAILTO: cursorIcon = "mail-message-new"; break; case LINK_NEWWINDOW: cursorIcon = "window-new"; break; default: cursorIcon = "dialog-error"; break; } QPixmap icon_pixmap = KHTMLGlobal::iconLoader()->loadIcon(cursorIcon, KIconLoader::Small, 0, KIconLoader::DefaultState, QStringList(), nullptr, true); d->cursorIconWidget->resize(icon_pixmap.width(), icon_pixmap.height()); d->cursorIconWidget->setMask(icon_pixmap.createMaskFromColor(Qt::transparent)); d->cursorIconWidget->setPixmap(icon_pixmap); d->cursorIconWidget->update(); } QPoint c_pos = QCursor::pos(); d->cursorIconWidget->move(c_pos.x() + 15, c_pos.y() + 15); #if HAVE_X11 XRaiseWindow(QX11Info::display(), d->cursorIconWidget->winId()); QApplication::flush(); #elif defined(Q_OS_WIN) SetWindowPos(d->cursorIconWidget->winId(), HWND_TOP, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); #else //TODO? #endif d->cursorIconWidget->show(); #endif } else if (d->cursorIconWidget) { d->cursorIconWidget->hide(); } if (r && r->isWidget()) { _mouse->ignore(); } if (!swallowEvent) { khtml::MouseMoveEvent event(_mouse, xm, ym, mev.url, mev.target, mev.innerNode); QApplication::sendEvent(m_part, &event); } } void KHTMLView::mouseReleaseEvent(QMouseEvent *_mouse) { bool swallowEvent = false; int xm = _mouse->x(); int ym = _mouse->y(); revertTransforms(xm, ym); DOM::NodeImpl::MouseEvent mev(_mouse->buttons(), DOM::NodeImpl::MouseRelease); if (m_part->xmlDocImpl()) { m_part->xmlDocImpl()->prepareMouseEvent(false, xm, ym, &mev); DOM::NodeImpl *target = mev.innerNode.handle(); DOM::NodeImpl *fn = m_part->xmlDocImpl()->focusNode(); // a widget may be the real target of this event (e.g. if a scrollbar's slider is being moved) if (d->m_mouseEventsTarget && fn && fn->renderer() && fn->renderer()->isWidget()) { target = fn; } swallowEvent = dispatchMouseEvent(EventImpl::MOUSEUP_EVENT, target, mev.innerNonSharedNode.handle(), true, d->clickCount, _mouse, false, DOM::NodeImpl::MouseRelease); // clear our sticky event target on any mouseRelease event if (d->m_mouseEventsTarget) { d->m_mouseEventsTarget = nullptr; } if (d->clickCount > 0 && QPoint(d->clickX - xm, d->clickY - ym).manhattanLength() <= QApplication::startDragDistance()) { QMouseEvent me(d->isDoubleClick ? QEvent::MouseButtonDblClick : QEvent::MouseButtonRelease, _mouse->pos(), _mouse->button(), _mouse->buttons(), _mouse->modifiers()); dispatchMouseEvent(EventImpl::CLICK_EVENT, mev.innerNode.handle(), mev.innerNonSharedNode.handle(), true, d->clickCount, &me, true, DOM::NodeImpl::MouseRelease); } khtml::RenderObject *r = target ? target->renderer() : nullptr; if (r && r->isWidget()) { _mouse->ignore(); } } if (!swallowEvent) { khtml::MouseReleaseEvent event(_mouse, xm, ym, mev.url, mev.target, mev.innerNode); QApplication::sendEvent(m_part, &event); } } // returns true if event should be swallowed bool KHTMLView::dispatchKeyEvent(QKeyEvent *_ke) { if (!m_part->xmlDocImpl()) { return false; } // Pressing and releasing a key should generate keydown, keypress and keyup events // Holding it down should generated keydown, keypress (repeatedly) and keyup events // The problem here is that Qt generates two autorepeat events (keyrelease+keypress) // for autorepeating, while DOM wants only one autorepeat event (keypress), so one // of the Qt events shouldn't be passed to DOM, but it should be still filtered // out if DOM would filter the autorepeat event. Additional problem is that Qt keyrelease // events don't have text() set (Qt bug?), so DOM often would ignore the keypress event // if it was created using Qt keyrelease, but Qt autorepeat keyrelease comes // before Qt autorepeat keypress (i.e. problem whether to filter it out or not). // The solution is to filter out and postpone the Qt autorepeat keyrelease until // the following Qt keypress event comes. If DOM accepts the DOM keypress event, // the postponed event will be simply discarded. If not, it will be passed to keyPressEvent() // again, and here it will be ignored. // // Qt: Press | Release(autorepeat) Press(autorepeat) etc. | Release // DOM: Down + Press | (nothing) Press | Up // It's also possible to get only Releases. E.g. the release of alt-tab, // or when the keypresses get captured by an accel. if (_ke == d->postponed_autorepeat) { // replayed event return false; } if (_ke->type() == QEvent::KeyPress) { if (!_ke->isAutoRepeat()) { bool ret = dispatchKeyEventHelper(_ke, false); // keydown // don't send keypress even if keydown was blocked, like IE (and unlike Mozilla) if (!ret && dispatchKeyEventHelper(_ke, true)) { // keypress ret = true; } return ret; } else { // autorepeat bool ret = dispatchKeyEventHelper(_ke, true); // keypress if (!ret && d->postponed_autorepeat) { keyPressEvent(d->postponed_autorepeat); } delete d->postponed_autorepeat; d->postponed_autorepeat = nullptr; return ret; } } else { // QEvent::KeyRelease // Discard postponed "autorepeat key-release" events that didn't see // a keypress after them (e.g. due to QAccel) delete d->postponed_autorepeat; d->postponed_autorepeat = nullptr; if (!_ke->isAutoRepeat()) { return dispatchKeyEventHelper(_ke, false); // keyup } else { d->postponed_autorepeat = new QKeyEvent(_ke->type(), _ke->key(), _ke->modifiers(), _ke->text(), _ke->isAutoRepeat(), _ke->count()); if (_ke->isAccepted()) { d->postponed_autorepeat->accept(); } else { d->postponed_autorepeat->ignore(); } return true; } } } // returns true if event should be swallowed bool KHTMLView::dispatchKeyEventHelper(QKeyEvent *_ke, bool keypress) { DOM::NodeImpl *keyNode = m_part->xmlDocImpl()->focusNode(); if (keyNode) { return keyNode->dispatchKeyEvent(_ke, keypress); } else { // no focused node, send to document return m_part->xmlDocImpl()->dispatchKeyEvent(_ke, keypress); } } void KHTMLView::keyPressEvent(QKeyEvent *_ke) { // If CTRL was hit, be prepared for access keys if (d->accessKeysEnabled && _ke->key() == Qt::Key_Control && !(_ke->modifiers() & ~Qt::ControlModifier) && !d->accessKeysActivated) { d->accessKeysPreActivate = true; _ke->accept(); return; } if (_ke->key() == Qt::Key_Shift && !(_ke->modifiers() & ~Qt::ShiftModifier)) { d->scrollSuspendPreActivate = true; } // accesskey handling needs to be done before dispatching, otherwise e.g. lineedits // may eat the event if (d->accessKeysEnabled && d->accessKeysActivated) { int state = (_ke->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier)); if (state == 0 || state == Qt::ShiftModifier) { if (_ke->key() != Qt::Key_Shift) { accessKeysTimeout(); } handleAccessKey(_ke); _ke->accept(); return; } accessKeysTimeout(); _ke->accept(); return; } if (dispatchKeyEvent(_ke)) { // If either keydown or keypress was accepted by a widget, or canceled by JS, stop here. _ke->accept(); return; } int offs = (viewport()->height() < 30) ? viewport()->height() : 30; // ### ?? if (_ke->modifiers() & Qt::ShiftModifier) switch (_ke->key()) { case Qt::Key_Space: verticalScrollBar()->setValue(verticalScrollBar()->value() - viewport()->height() + offs); if (d->scrollSuspended) { d->newScrollTimer(this, 0); } break; case Qt::Key_Down: case Qt::Key_J: d->adjustScroller(this, KHTMLViewPrivate::ScrollDown, KHTMLViewPrivate::ScrollUp); break; case Qt::Key_Up: case Qt::Key_K: d->adjustScroller(this, KHTMLViewPrivate::ScrollUp, KHTMLViewPrivate::ScrollDown); break; case Qt::Key_Left: case Qt::Key_H: d->adjustScroller(this, KHTMLViewPrivate::ScrollLeft, KHTMLViewPrivate::ScrollRight); break; case Qt::Key_Right: case Qt::Key_L: d->adjustScroller(this, KHTMLViewPrivate::ScrollRight, KHTMLViewPrivate::ScrollLeft); break; } else switch (_ke->key()) { case Qt::Key_Down: case Qt::Key_J: if (!d->scrollTimerId || d->scrollSuspended) { verticalScrollBar()->setValue(verticalScrollBar()->value() + 10); } if (d->scrollTimerId) { d->newScrollTimer(this, 0); } break; case Qt::Key_Space: case Qt::Key_PageDown: d->shouldSmoothScroll = true; verticalScrollBar()->setValue(verticalScrollBar()->value() + viewport()->height() - offs); if (d->scrollSuspended) { d->newScrollTimer(this, 0); } break; case Qt::Key_Up: case Qt::Key_K: if (!d->scrollTimerId || d->scrollSuspended) { verticalScrollBar()->setValue(verticalScrollBar()->value() - 10); } if (d->scrollTimerId) { d->newScrollTimer(this, 0); } break; case Qt::Key_PageUp: d->shouldSmoothScroll = true; verticalScrollBar()->setValue(verticalScrollBar()->value() - viewport()->height() + offs); if (d->scrollSuspended) { d->newScrollTimer(this, 0); } break; case Qt::Key_Right: case Qt::Key_L: if (!d->scrollTimerId || d->scrollSuspended) { horizontalScrollBar()->setValue(horizontalScrollBar()->value() + 10); } if (d->scrollTimerId) { d->newScrollTimer(this, 0); } break; case Qt::Key_Left: case Qt::Key_H: if (!d->scrollTimerId || d->scrollSuspended) { horizontalScrollBar()->setValue(horizontalScrollBar()->value() - 10); } if (d->scrollTimerId) { d->newScrollTimer(this, 0); } break; case Qt::Key_Enter: case Qt::Key_Return: // ### FIXME: // or even better to HTMLAnchorElementImpl::event() if (m_part->xmlDocImpl()) { NodeImpl *n = m_part->xmlDocImpl()->focusNode(); if (n) { n->setActive(); } } break; case Qt::Key_Home: verticalScrollBar()->setValue(0); horizontalScrollBar()->setValue(0); if (d->scrollSuspended) { d->newScrollTimer(this, 0); } break; case Qt::Key_End: verticalScrollBar()->setValue(contentsHeight() - visibleHeight()); if (d->scrollSuspended) { d->newScrollTimer(this, 0); } break; case Qt::Key_Shift: // what are you doing here? _ke->ignore(); return; default: if (d->scrollTimerId) { d->newScrollTimer(this, 0); } _ke->ignore(); return; } _ke->accept(); } void KHTMLView::keyReleaseEvent(QKeyEvent *_ke) { if (d->scrollSuspendPreActivate && _ke->key() != Qt::Key_Shift) { d->scrollSuspendPreActivate = false; } if (_ke->key() == Qt::Key_Shift && d->scrollSuspendPreActivate && !(_ke->modifiers() & Qt::ShiftModifier)) if (d->scrollTimerId) { d->scrollSuspended = !d->scrollSuspended; if (d->scrollSuspended) { d->stopScrolling(); } } if (d->accessKeysEnabled) { if (d->accessKeysPreActivate && _ke->key() != Qt::Key_Control) { d->accessKeysPreActivate = false; } if (d->accessKeysPreActivate && !(_ke->modifiers() & Qt::ControlModifier)) { displayAccessKeys(); m_part->setStatusBarText(i18n("Access Keys activated"), KHTMLPart::BarOverrideText); d->accessKeysActivated = true; d->accessKeysPreActivate = false; _ke->accept(); return; } else if (d->accessKeysActivated) { accessKeysTimeout(); _ke->accept(); return; } } // Send keyup event if (dispatchKeyEvent(_ke)) { _ke->accept(); return; } QScrollArea::keyReleaseEvent(_ke); } bool KHTMLView::focusNextPrevChild(bool next) { // Now try to find the next child if (m_part->xmlDocImpl() && focusNextPrevNode(next)) { //if (m_part->xmlDocImpl()->focusNode()) // qDebug() << "focusNode.name: " - // << m_part->xmlDocImpl()->focusNode()->nodeName().string() << endl; + // << m_part->xmlDocImpl()->focusNode()->nodeName().string(); return true; // focus node found } // If we get here, pass tabbing control up to the next/previous child in our parent d->pseudoFocusNode = KHTMLViewPrivate::PFNone; if (m_part->parentPart() && m_part->parentPart()->view()) { return m_part->parentPart()->view()->focusNextPrevChild(next); } return QWidget::focusNextPrevChild(next); } void KHTMLView::doAutoScroll() { QPoint pos = QCursor::pos(); QPoint off; KHTMLView *v = m_kwp->isRedirected() ? m_kwp->rootViewPos(off) : this; pos = v->viewport()->mapFromGlobal(pos); pos -= off; int xm, ym; viewportToContents(pos.x(), pos.y(), xm, ym); // ### pos = QPoint(pos.x() - viewport()->x(), pos.y() - viewport()->y()); if ((pos.y() < 0) || (pos.y() > visibleHeight()) || (pos.x() < 0) || (pos.x() > visibleWidth())) { ensureVisible(xm, ym, 0, 5); #ifndef KHTML_NO_SELECTION // extend the selection while scrolling DOM::Node innerNode; if (m_part->isExtendingSelection()) { RenderObject::NodeInfo renderInfo(true/*readonly*/, false/*active*/); m_part->xmlDocImpl()->renderer()->layer() ->nodeAtPoint(renderInfo, xm, ym); innerNode = renderInfo.innerNode(); }/*end if*/ if (innerNode.handle() && innerNode.handle()->renderer() && innerNode.handle()->renderer()->shouldSelect()) { m_part->extendSelectionTo(xm, ym, innerNode); }/*end if*/ #endif // KHTML_NO_SELECTION } } // KHTML defines its own stacking order for any object and thus takes // control of widget painting whenever it can. This is called "redirection". // // Redirected widgets are placed off screen. When they are declared as a child of our view (ChildPolished event), // an event filter is installed, so as to catch any paint event and translate them as update() of the view's main widget. // // Painting also happens spontaneously within widgets. In this case, the widget would update() parts of itself. // While this ordinarily results in a paintEvent being schedduled, it is not the case with off screen widgets. // Thus update() is monitored by using the mechanism that deffers any update call happening during a paint event, // transforming it into a posted UpdateLater event. Hence the need to set Qt::WA_WState_InPaintEvent on redirected widgets. // // Once the UpdateLater event has been received, Qt::WA_WState_InPaintEvent is removed and the process continues // with the update of the corresponding rect on the view. That in turn will make our painting subsystem render() // the widget at the correct stacking position. // // For non-redirected (e.g. external) widgets, z-order is honoured through masking. cf.RenderLayer::updateWidgetMasks static void handleWidget(QWidget *w, KHTMLView *view, bool recurse = true) { if (w->isWindow()) { return; } if (!qobject_cast(w)) { w->setAttribute(Qt::WA_NoSystemBackground); } w->setAttribute(Qt::WA_WState_InPaintEvent); if (!(w->objectName() == "KLineEditButton")) { w->setAttribute(Qt::WA_OpaquePaintEvent); } w->installEventFilter(view); if (!recurse) { return; } if (qobject_cast(w)) { handleWidget(static_cast(w)->widget(), view, false); handleWidget(static_cast(w)->horizontalScrollBar(), view, false); handleWidget(static_cast(w)->verticalScrollBar(), view, false); return; } QObjectList children = w->children(); foreach (QObject *object, children) { QWidget *widget = qobject_cast(object); if (widget) { handleWidget(widget, view); } } } class KHTMLBackingStoreHackWidget : public QWidget { public: void publicEvent(QEvent *e) { QWidget::event(e); } }; bool KHTMLView::viewportEvent(QEvent *e) { switch (e->type()) { // those must not be dispatched to the specialized handlers // as widgetEvent() already took care of that case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseButtonDblClick: case QEvent::MouseMove: #ifndef QT_NO_WHEELEVENT case QEvent::Wheel: #endif case QEvent::ContextMenu: case QEvent::DragEnter: case QEvent::DragMove: case QEvent::DragLeave: case QEvent::Drop: return false; default: break; } return QScrollArea::viewportEvent(e); } static void setInPaintEventFlag(QWidget *w, bool b = true, bool recurse = true) { w->setAttribute(Qt::WA_WState_InPaintEvent, b); if (!recurse) { return; } if (qobject_cast(w)) { setInPaintEventFlag(static_cast(w)->widget(), b, false); setInPaintEventFlag(static_cast(w)->horizontalScrollBar(), b, false); setInPaintEventFlag(static_cast(w)->verticalScrollBar(), b, false); return; } foreach (QObject *cw, w->children()) { if (cw->isWidgetType() && ! static_cast(cw)->isWindow() && !(static_cast(cw)->windowModality() & Qt::ApplicationModal)) { setInPaintEventFlag(static_cast(cw), b); } } } bool KHTMLView::eventFilter(QObject *o, QEvent *e) { if (e->type() == QEvent::ShortcutOverride) { QKeyEvent *ke = (QKeyEvent *) e; if (m_part->isEditable() || m_part->isCaretMode() || (m_part->xmlDocImpl() && m_part->xmlDocImpl()->focusNode() && m_part->xmlDocImpl()->focusNode()->isContentEditable())) { if ((ke->modifiers() & Qt::ControlModifier) || (ke->modifiers() & Qt::ShiftModifier)) { switch (ke->key()) { case Qt::Key_Left: case Qt::Key_Right: case Qt::Key_Up: case Qt::Key_Down: case Qt::Key_Home: case Qt::Key_End: ke->accept(); return true; default: break; } } } } if (e->type() == QEvent::Leave) { if (d->cursorIconWidget) { d->cursorIconWidget->hide(); } m_part->resetHoverText(); } QWidget *view = widget(); if (o == view) { if (widgetEvent(e)) { return true; } else if (e->type() == QEvent::Resize) { updateScrollBars(); return false; } } else if (o->isWidgetType()) { QWidget *v = static_cast(o); QWidget *c = v; while (v && v != view) { c = v; v = v->parentWidget(); } KHTMLWidget *k = dynamic_cast(c); if (v && k && k->m_kwp->isRedirected()) { bool block = false; #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) bool isUpdate = false; #endif QWidget *w = static_cast(o); switch (e->type()) { case QEvent::UpdateRequest: { // implicitly call qt_syncBackingStore(w) static_cast(w)->publicEvent(e); block = true; break; } case QEvent::UpdateLater: #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) isUpdate = true; #endif // no break; case QEvent::Paint: if (!allowWidgetPaintEvents) { // eat the event. Like this we can control exactly when the widget // gets repainted. block = true; int x = 0, y = 0; QWidget *v = w; while (v && v->parentWidget() != view) { x += v->x(); y += v->y(); v = v->parentWidget(); } QPoint ap = k->m_kwp->absolutePos(); x += ap.x(); y += ap.y(); #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) QRect pr = isUpdate ? static_cast(e)->region().boundingRect() : static_cast(e)->rect(); bool asap = !d->contentsMoving && qobject_cast(c); if (isUpdate) { setInPaintEventFlag(w, false); if (asap) { w->repaint(static_cast(e)->region()); } else { w->update(static_cast(e)->region()); } setInPaintEventFlag(w); } // QScrollView needs fast repaints if (asap && !isUpdate && !d->painting && m_part->xmlDocImpl() && m_part->xmlDocImpl()->renderer() && !static_cast(m_part->xmlDocImpl()->renderer())->needsLayout()) { repaintContents(x + pr.x(), y + pr.y(), pr.width(), pr.height() + 1); // ### investigate that +1 (shows up when // updating e.g a textarea's blinking cursor) } else if (!d->painting) { scheduleRepaint(x + pr.x(), y + pr.y(), pr.width(), pr.height() + 1, asap); } #endif } break; case QEvent::MouseMove: case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseButtonDblClick: { if (0 && w->parentWidget() == view && !qobject_cast(w) && !::qobject_cast(w)) { QMouseEvent *me = static_cast(e); QPoint pt = w->mapTo(view, me->pos()); QMouseEvent me2(me->type(), pt, me->button(), me->buttons(), me->modifiers()); if (e->type() == QEvent::MouseMove) { mouseMoveEvent(&me2); } else if (e->type() == QEvent::MouseButtonPress) { mousePressEvent(&me2); } else if (e->type() == QEvent::MouseButtonRelease) { mouseReleaseEvent(&me2); } else { mouseDoubleClickEvent(&me2); } block = true; } break; } case QEvent::KeyPress: case QEvent::KeyRelease: if (w->parentWidget() == view && !qobject_cast(w)) { QKeyEvent *ke = static_cast(e); if (e->type() == QEvent::KeyPress) { keyPressEvent(ke); ke->accept(); } else { keyReleaseEvent(ke); ke->accept(); } block = true; } if (qobject_cast(w->parentWidget()) && e->type() == QEvent::KeyPress) { // Since keypress events on the upload widget will // be forwarded to the lineedit anyway, // block the original copy at this level to prevent // double-emissions of events it doesn't accept e->ignore(); block = true; } break; case QEvent::FocusIn: case QEvent::FocusOut: { QPoint dummy; KHTMLView *root = m_kwp->rootViewPos(dummy); if (!root) { root = this; } block = static_cast(e)->reason() != Qt::MouseFocusReason || root->underMouse(); break; } default: break; } if (block) { //qDebug("eating event"); return true; } } } // qDebug() <<"passing event on to sv event filter object=" << o->className() << " event=" << e->type(); return QScrollArea::eventFilter(o, e); } bool KHTMLView::widgetEvent(QEvent *e) { switch (e->type()) { case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseButtonDblClick: case QEvent::MouseMove: case QEvent::Paint: #ifndef QT_NO_WHEELEVENT case QEvent::Wheel: #endif case QEvent::ContextMenu: case QEvent::DragEnter: case QEvent::DragMove: case QEvent::DragLeave: case QEvent::Drop: return QFrame::event(e); case QEvent::ChildPolished: { // we need to install an event filter on all children of the widget() to // be able to get correct stacking of children within the document. QObject *c = static_cast(e)->child(); if (c->isWidgetType()) { QWidget *w = static_cast(c); // don't install the event filter on toplevels if (!(w->windowFlags() & Qt::Window) && !(w->windowModality() & Qt::ApplicationModal)) { KHTMLWidget *k = dynamic_cast(w); if (k && k->m_kwp->isRedirected()) { w->unsetCursor(); handleWidget(w, this); } } } break; } case QEvent::Move: { if (static_cast(e)->pos() != QPoint(0, 0)) { widget()->move(0, 0); updateScrollBars(); return true; } break; } default: break; } return false; } bool KHTMLView::hasLayoutPending() { return d->layoutTimerId && !d->firstLayoutPending; } DOM::NodeImpl *KHTMLView::nodeUnderMouse() const { return d->underMouse; } DOM::NodeImpl *KHTMLView::nonSharedNodeUnderMouse() const { return d->underMouseNonShared; } bool KHTMLView::scrollTo(const QRect &bounds) { d->scrollingSelf = true; // so scroll events get ignored int x, y, xe, ye; x = bounds.left(); y = bounds.top(); xe = bounds.right(); ye = bounds.bottom(); //qDebug()<<"scrolling coords: x="<pseudoFocusNode == KHTMLViewPrivate::PFTop) { newFocusNode = doc->nextFocusNode(oldFocusNode); } } else { if (oldFocusNode || d->pseudoFocusNode == KHTMLViewPrivate::PFBottom) { newFocusNode = doc->previousFocusNode(oldFocusNode); } } bool targetVisible = false; if (!newFocusNode) { if (next) { targetVisible = scrollTo(QRect(contentsX() + visibleWidth() / 2, contentsHeight() - d->borderY, 0, 0)); } else { targetVisible = scrollTo(QRect(contentsX() + visibleWidth() / 2, d->borderY, 0, 0)); } } else { // if it's an editable element, activate the caret if (!m_part->isCaretMode() && newFocusNode->isContentEditable()) { - // qDebug() << "show caret! fn: " << newFocusNode->nodeName().string() << endl; + // qDebug() << "show caret! fn: " << newFocusNode->nodeName().string(); m_part->clearCaretRectIfNeeded(); m_part->d->editor_context.m_selection.moveTo(Position(newFocusNode, 0L)); m_part->setCaretVisible(true); } else { m_part->setCaretVisible(false); - // qDebug() << "hide caret! fn: " << newFocusNode->nodeName().string() << endl; + // qDebug() << "hide caret! fn: " << newFocusNode->nodeName().string(); } m_part->notifySelectionChanged(); targetVisible = scrollTo(newFocusNode->getRect()); } if (targetVisible) { //qDebug() << " target reached.\n"; d->tabMovePending = false; m_part->xmlDocImpl()->setFocusNode(newFocusNode); if (newFocusNode) { Node guard(newFocusNode); if (!newFocusNode->hasOneRef()) { emit m_part->nodeActivated(Node(newFocusNode)); } return true; } else { d->pseudoFocusNode = next ? KHTMLViewPrivate::PFBottom : KHTMLViewPrivate::PFTop; return false; } } else { if (!d->tabMovePending) { d->lastTabbingDirection = next; } d->tabMovePending = true; return true; } } void KHTMLView::displayAccessKeys() { QVector< QChar > taken; displayAccessKeys(nullptr, this, taken, false); displayAccessKeys(nullptr, this, taken, true); } void KHTMLView::displayAccessKeys(KHTMLView *caller, KHTMLView *origview, QVector< QChar > &taken, bool use_fallbacks) { QMap< ElementImpl *, QChar > fallbacks; if (use_fallbacks) { fallbacks = buildFallbackAccessKeys(); } for (NodeImpl *n = m_part->xmlDocImpl(); n != nullptr; n = n->traverseNextNode()) { if (n->isElementNode()) { ElementImpl *en = static_cast< ElementImpl * >(n); DOMString s = en->getAttribute(ATTR_ACCESSKEY); QString accesskey; if (s.length() == 1) { QChar a = s.string()[ 0 ].toUpper(); if (qFind(taken.begin(), taken.end(), a) == taken.end()) { // !contains accesskey = a; } } if (accesskey.isNull() && fallbacks.contains(en)) { QChar a = fallbacks[ en ].toUpper(); if (qFind(taken.begin(), taken.end(), a) == taken.end()) { // !contains accesskey = QString("") + a + ""; } } if (!accesskey.isNull()) { QRect rec = en->getRect(); QLabel *lab = new QLabel(accesskey, widget()); lab->setAttribute(Qt::WA_DeleteOnClose); lab->setObjectName("KHTMLAccessKey"); connect(origview, SIGNAL(hideAccessKeys()), lab, SLOT(close())); connect(this, SIGNAL(repaintAccessKeys()), lab, SLOT(repaint())); lab->setPalette(QToolTip::palette()); lab->setLineWidth(2); lab->setFrameStyle(QFrame::Box | QFrame::Plain); lab->setMargin(3); lab->adjustSize(); lab->setParent(widget()); lab->setAutoFillBackground(true); lab->move( qMin(rec.left() + rec.width() / 2 - contentsX(), contentsWidth() - lab->width()), qMin(rec.top() + rec.height() / 2 - contentsY(), contentsHeight() - lab->height())); lab->show(); taken.append(accesskey[ 0 ]); } } } if (use_fallbacks) { return; } QList frames = m_part->frames(); foreach (KParts::ReadOnlyPart *cur, frames) { if (!qobject_cast(cur)) { continue; } KHTMLPart *part = static_cast< KHTMLPart * >(cur); if (part->view() && part->view() != caller) { part->view()->displayAccessKeys(this, origview, taken, use_fallbacks); } } // pass up to the parent if (m_part->parentPart() && m_part->parentPart()->view() && m_part->parentPart()->view() != caller) { m_part->parentPart()->view()->displayAccessKeys(this, origview, taken, use_fallbacks); } } bool KHTMLView::isScrollingFromMouseWheel() const { return d->scrollingFromWheel != QPoint(-1, -1); } void KHTMLView::accessKeysTimeout() { d->accessKeysActivated = false; d->accessKeysPreActivate = false; m_part->setStatusBarText(QString(), KHTMLPart::BarOverrideText); emit hideAccessKeys(); } // Handling of the HTML accesskey attribute. bool KHTMLView::handleAccessKey(const QKeyEvent *ev) { // Qt interprets the keyevent also with the modifiers, and ev->text() matches that, // but this code must act as if the modifiers weren't pressed QChar c; if (ev->key() >= Qt::Key_A && ev->key() <= Qt::Key_Z) { c = 'A' + ev->key() - Qt::Key_A; } else if (ev->key() >= Qt::Key_0 && ev->key() <= Qt::Key_9) { c = '0' + ev->key() - Qt::Key_0; } else { // TODO fake XKeyEvent and XLookupString ? // This below seems to work e.g. for eacute though. if (ev->text().length() == 1) { c = ev->text()[ 0 ]; } } if (c.isNull()) { return false; } return focusNodeWithAccessKey(c); } bool KHTMLView::focusNodeWithAccessKey(QChar c, KHTMLView *caller) { DocumentImpl *doc = m_part->xmlDocImpl(); if (!doc) { return false; } ElementImpl *node = doc->findAccessKeyElement(c); if (!node) { QList frames = m_part->frames(); foreach (KParts::ReadOnlyPart *cur, frames) { if (!qobject_cast(cur)) { continue; } KHTMLPart *part = static_cast< KHTMLPart * >(cur); if (part->view() && part->view() != caller && part->view()->focusNodeWithAccessKey(c, this)) { return true; } } // pass up to the parent if (m_part->parentPart() && m_part->parentPart()->view() && m_part->parentPart()->view() != caller && m_part->parentPart()->view()->focusNodeWithAccessKey(c, this)) { return true; } if (caller == nullptr) { // the active frame (where the accesskey was pressed) const QMap< ElementImpl *, QChar > fallbacks = buildFallbackAccessKeys(); for (QMap< ElementImpl *, QChar >::ConstIterator it = fallbacks.begin(); it != fallbacks.end(); ++it) if (*it == c) { node = it.key(); break; } } if (node == nullptr) { return false; } } // Scroll the view as necessary to ensure that the new focus node is visible QRect r = node->getRect(); ensureVisible(r.right(), r.bottom()); ensureVisible(r.left(), r.top()); Node guard(node); if (node->isFocusable()) { if (node->id() == ID_LABEL) { // if Accesskey is a label, give focus to the label's referrer. node = static_cast(static_cast< HTMLLabelElementImpl * >(node)->getFormElement()); if (!node) { return true; } guard = node; } // Set focus node on the document m_part->xmlDocImpl()->setFocusNode(node); if (node != nullptr && node->hasOneRef()) { // deleted, only held by guard return true; } emit m_part->nodeActivated(Node(node)); if (node != nullptr && node->hasOneRef()) { return true; } } switch (node->id()) { case ID_A: static_cast< HTMLAnchorElementImpl * >(node)->click(); break; case ID_INPUT: static_cast< HTMLInputElementImpl * >(node)->click(); break; case ID_BUTTON: static_cast< HTMLButtonElementImpl * >(node)->click(); break; case ID_AREA: static_cast< HTMLAreaElementImpl * >(node)->click(); break; case ID_TEXTAREA: break; // just focusing it is enough case ID_LEGEND: // TODO break; } return true; } static QString getElementText(NodeImpl *start, bool after) { QString ret; // nextSibling(), to go after e.g. for (NodeImpl *n = after ? start->nextSibling() : start->traversePreviousNode(); n != nullptr; n = after ? n->traverseNextNode() : n->traversePreviousNode()) { if (n->isTextNode()) { if (after) { ret += static_cast< TextImpl * >(n)->toString().string(); } else { ret.prepend(static_cast< TextImpl * >(n)->toString().string()); } } else { switch (n->id()) { case ID_A: case ID_FONT: case ID_TT: case ID_U: case ID_B: case ID_I: case ID_S: case ID_STRIKE: case ID_BIG: case ID_SMALL: case ID_EM: case ID_STRONG: case ID_DFN: case ID_CODE: case ID_SAMP: case ID_KBD: case ID_VAR: case ID_CITE: case ID_ABBR: case ID_ACRONYM: case ID_SUB: case ID_SUP: case ID_SPAN: case ID_NOBR: case ID_WBR: break; case ID_TD: if (ret.trimmed().isEmpty()) { break; } // fall through default: return ret.simplified(); } } } return ret.simplified(); } static QMap< NodeImpl *, QString > buildLabels(NodeImpl *start) { QMap< NodeImpl *, QString > ret; for (NodeImpl *n = start; n != nullptr; n = n->traverseNextNode()) { if (n->id() == ID_LABEL) { HTMLLabelElementImpl *label = static_cast< HTMLLabelElementImpl * >(n); NodeImpl *labelfor = label->getFormElement(); if (labelfor) { ret[ labelfor ] = label->innerText().string().simplified(); } } } return ret; } namespace khtml { struct AccessKeyData { ElementImpl *element; QString text; QString url; int priority; // 10(highest) - 0(lowest) }; } QMap< ElementImpl *, QChar > KHTMLView::buildFallbackAccessKeys() const { // build a list of all possible candidate elements that could use an accesskey QLinkedList< AccessKeyData > data; // Note: this has to be a list type that keep iterators valid // when other entries are removed QMap< NodeImpl *, QString > labels = buildLabels(m_part->xmlDocImpl()); QMap< QString, QChar > hrefs; for (NodeImpl *n = m_part->xmlDocImpl(); n != nullptr; n = n->traverseNextNode()) { if (n->isElementNode()) { ElementImpl *element = static_cast< ElementImpl * >(n); if (element->renderer() == nullptr) { continue; // not visible } QString text; QString url; int priority = 0; bool ignore = false; bool text_after = false; bool text_before = false; switch (element->id()) { case ID_A: url = element->getAttribute(ATTR_HREF).trimSpaces().string(); if (url.isEmpty()) { // doesn't have href, it's only an anchor continue; } text = static_cast< HTMLElementImpl * >(element)->innerText().string().simplified(); priority = 2; break; case ID_INPUT: { HTMLInputElementImpl *in = static_cast< HTMLInputElementImpl * >(element); switch (in->inputType()) { case HTMLInputElementImpl::SUBMIT: text = in->value().string(); if (text.isEmpty()) { text = i18n("Submit"); } priority = 7; break; case HTMLInputElementImpl::IMAGE: text = in->altText().string(); priority = 7; break; case HTMLInputElementImpl::BUTTON: text = in->value().string(); priority = 5; break; case HTMLInputElementImpl::RESET: text = in->value().string(); if (text.isEmpty()) { text = i18n("Reset"); } priority = 5; break; case HTMLInputElementImpl::HIDDEN: ignore = true; break; case HTMLInputElementImpl::CHECKBOX: case HTMLInputElementImpl::RADIO: text_after = true; priority = 5; break; case HTMLInputElementImpl::TEXT: case HTMLInputElementImpl::PASSWORD: case HTMLInputElementImpl::FILE: text_before = true; priority = 5; break; default: priority = 5; break; } break; } case ID_BUTTON: text = static_cast< HTMLElementImpl * >(element)->innerText().string().simplified(); switch (static_cast< HTMLButtonElementImpl * >(element)->buttonType()) { case HTMLButtonElementImpl::SUBMIT: if (text.isEmpty()) { text = i18n("Submit"); } priority = 7; break; case HTMLButtonElementImpl::RESET: if (text.isEmpty()) { text = i18n("Reset"); } priority = 5; break; default: priority = 5; break; } break; case ID_SELECT: // these don't have accesskey attribute, but quick access may be handy text_before = true; text_after = true; priority = 5; break; case ID_FRAME: ignore = true; break; default: ignore = !element->isFocusable(); priority = 2; break; } if (ignore) { continue; } // build map of manually assigned accesskeys and their targets DOMString akey = element->getAttribute(ATTR_ACCESSKEY); if (akey.length() == 1) { hrefs[url] = akey.string()[ 0 ].toUpper(); continue; // has accesskey set, ignore } if (text.isNull() && labels.contains(element)) { text = labels[ element ]; } if (text.isNull() && text_before) { text = getElementText(element, false); } if (text.isNull() && text_after) { text = getElementText(element, true); } text = text.trimmed(); // increase priority of items which have explicitly specified accesskeys in the config const QList< QPair< QString, QChar > > priorities = m_part->settings()->fallbackAccessKeysAssignments(); for (QList< QPair< QString, QChar > >::ConstIterator it = priorities.begin(); it != priorities.end(); ++it) { if (text == (*it).first) { priority = 10; } } AccessKeyData tmp = { element, text, url, priority }; data.append(tmp); } } QList< QChar > keys; for (char c = 'A'; c <= 'Z'; ++c) { keys << c; } for (char c = '0'; c <= '9'; ++c) { keys << c; } for (NodeImpl *n = m_part->xmlDocImpl(); n != nullptr; n = n->traverseNextNode()) { if (n->isElementNode()) { ElementImpl *en = static_cast< ElementImpl * >(n); DOMString s = en->getAttribute(ATTR_ACCESSKEY); if (s.length() == 1) { QChar c = s.string()[ 0 ].toUpper(); keys.removeAll(c); // remove manually assigned accesskeys } } } QMap< ElementImpl *, QChar > ret; for (int priority = 10; priority >= 0; --priority) { for (QLinkedList< AccessKeyData >::Iterator it = data.begin(); it != data.end(); ) { if ((*it).priority != priority) { ++it; continue; } if (keys.isEmpty()) { break; } QString text = (*it).text; QChar key; const QString url = (*it).url; // an identical link already has an accesskey assigned if (hrefs.contains(url)) { it = data.erase(it); continue; } if (!text.isEmpty()) { const QList< QPair< QString, QChar > > priorities = m_part->settings()->fallbackAccessKeysAssignments(); for (QList< QPair< QString, QChar > >::ConstIterator it = priorities.begin(); it != priorities.end(); ++it) if (text == (*it).first && keys.contains((*it).second)) { key = (*it).second; break; } } // try first to select the first character as the accesskey, // then first character of the following words, // and then simply the first free character if (key.isNull() && !text.isEmpty()) { const QStringList words = text.split(' '); for (QStringList::ConstIterator it = words.begin(); it != words.end(); ++it) { if (keys.contains((*it)[ 0 ].toUpper())) { key = (*it)[ 0 ].toUpper(); break; } } } if (key.isNull() && !text.isEmpty()) { for (int i = 0; i < text.length(); ++i) { if (keys.contains(text[ i ].toUpper())) { key = text[ i ].toUpper(); break; } } } if (key.isNull()) { key = keys.front(); } ret[(*it).element ] = key; keys.removeAll(key); it = data.erase(it); // assign the same accesskey also to other elements pointing to the same url if (!url.isEmpty() && !url.startsWith("javascript:", Qt::CaseInsensitive)) { for (QLinkedList< AccessKeyData >::Iterator it2 = data.begin(); it2 != data.end(); ) { if ((*it2).url == url) { ret[(*it2).element ] = key; if (it == it2) { ++it; } it2 = data.erase(it2); } else { ++it2; } } } } } return ret; } void KHTMLView::setMediaType(const QString &medium) { m_medium = medium; } QString KHTMLView::mediaType() const { return m_medium; } bool KHTMLView::pagedMode() const { return d->paged; } void KHTMLView::setWidgetVisible(RenderWidget *w, bool vis) { if (vis) { d->visibleWidgets.insert(w, w->widget()); } else { d->visibleWidgets.remove(w); } } bool KHTMLView::needsFullRepaint() const { return d->needsFullRepaint; } namespace { class QPointerDeleter { public: explicit QPointerDeleter(QObject *o) : obj(o) {} ~QPointerDeleter() { delete obj; } private: const QPointer obj; }; } void KHTMLView::print(bool quick) { if (!m_part->xmlDocImpl()) { return; } khtml::RenderCanvas *root = static_cast(m_part->xmlDocImpl()->renderer()); if (!root) { return; } QPrinter printer; QPointer dialog(new QPrintDialog(&printer, this)); QPointer printSettings(new KHTMLPrintSettings(dialog)); //XXX: doesn't save settings between prints like this dialog->setOptionTabs(QList() << printSettings.data()); const QPointerDeleter dialogDeleter(dialog); QString docname = m_part->xmlDocImpl()->URL().toDisplayString(); if (!docname.isEmpty()) { docname = KStringHandler::csqueeze(docname, 80); } if (quick || (dialog->exec() && dialog)) { /*'this' and thus dialog might have been deleted while exec()!*/ viewport()->setCursor(Qt::WaitCursor); // only viewport(), no QApplication::, otherwise we get the busy cursor in kdeprint's dialogs // set up KPrinter printer.setFullPage(false); printer.setCreator(QString("KDE %1.%2.%3 HTML Library").arg(KHTML_VERSION_MAJOR).arg(KHTML_VERSION_MINOR).arg(KHTML_VERSION_PATCH)); printer.setDocName(docname); QPainter *p = new QPainter; p->begin(&printer); khtml::setPrintPainter(p); m_part->xmlDocImpl()->setPaintDevice(&printer); QString oldMediaType = mediaType(); setMediaType("print"); // We ignore margin settings for html and body when printing // and use the default margins from the print-system // (In Qt 3.0.x the default margins are hardcoded in Qt) m_part->xmlDocImpl()->setPrintStyleSheet(printSettings->printFriendly() ? "* { background-image: none !important;" " background-color: white !important;" " color: black !important; }" "body { margin: 0px !important; }" "html { margin: 0px !important; }" : "body { margin: 0px !important; }" "html { margin: 0px !important; }" ); // qDebug() << "printing: physical page width = " << printer.width() - // << " height = " << printer.height() << endl; + // << " height = " << printer.height(); root->setStaticMode(true); root->setPagedMode(true); root->setWidth(printer.width()); // root->setHeight(printer.height()); root->setPageTop(0); root->setPageBottom(0); d->paged = true; m_part->xmlDocImpl()->styleSelector()->computeFontSizes(printer.logicalDpiY(), 100); m_part->xmlDocImpl()->updateStyleSelector(); root->setPrintImages(printSettings->printImages()); root->makePageBreakAvoidBlocks(); root->setNeedsLayoutAndMinMaxRecalc(); root->layout(); // check sizes ask for action.. (scale or clip) bool printHeader = printSettings->printHeader(); int headerHeight = 0; QFont headerFont("Sans Serif", 8); QString headerLeft = QDate::currentDate().toString(Qt::DefaultLocaleShortDate); QString headerMid = docname; QString headerRight; if (printHeader) { p->setFont(headerFont); headerHeight = (p->fontMetrics().lineSpacing() * 3) / 2; } // ok. now print the pages. // qDebug() << "printing: html page width = " << root->docWidth() - // << " height = " << root->docHeight() << endl; + // << " height = " << root->docHeight(); // qDebug() << "printing: margins left = " << printer.pageRect().left() - printer.paperRect().left() - // << " top = " << printer.pageRect().top() - printer.paperRect().top() << endl; + // << " top = " << printer.pageRect().top() - printer.paperRect().top(); // qDebug() << "printing: paper width = " << printer.width() - // << " height = " << printer.height() << endl; + // << " height = " << printer.height(); // if the width is too large to fit on the paper we just scale // the whole thing. int pageWidth = printer.width(); int pageHeight = printer.height(); p->setClipRect(0, 0, pageWidth, pageHeight); pageHeight -= headerHeight; #ifndef QT_NO_TRANSFORMATIONS bool scalePage = false; double scale = 0.0; if (root->docWidth() > printer.width()) { scalePage = true; scale = ((double) printer.width()) / ((double) root->docWidth()); pageHeight = (int)(pageHeight / scale); pageWidth = (int)(pageWidth / scale); headerHeight = (int)(headerHeight / scale); } #endif // qDebug() << "printing: scaled html width = " << pageWidth - // << " height = " << pageHeight << endl; + // << " height = " << pageHeight; root->setHeight(pageHeight); root->setPageBottom(pageHeight); root->setNeedsLayout(true); root->layoutIfNeeded(); // m_part->slotDebugRenderTree(); // Squeeze header to make it it on the page. if (printHeader) { int available_width = printer.width() - 10 - 2 * qMax(p->boundingRect(0, 0, printer.width(), p->fontMetrics().lineSpacing(), Qt::AlignLeft, headerLeft).width(), p->boundingRect(0, 0, printer.width(), p->fontMetrics().lineSpacing(), Qt::AlignLeft, headerRight).width()); if (available_width < 150) { available_width = 150; } int mid_width; int squeeze = 120; do { headerMid = KStringHandler::csqueeze(docname, squeeze); mid_width = p->boundingRect(0, 0, printer.width(), p->fontMetrics().lineSpacing(), Qt::AlignLeft, headerMid).width(); squeeze -= 10; } while (mid_width > available_width); } int top = 0; int bottom = 0; int page = 1; while (top < root->docHeight()) { if (top > 0) { printer.newPage(); } #ifndef QT_NO_TRANSFORMATIONS if (scalePage) { p->scale(scale, scale); } #endif p->save(); p->setClipRect(0, 0, pageWidth, headerHeight); if (printHeader) { int dy = p->fontMetrics().lineSpacing(); p->setPen(Qt::black); p->setFont(headerFont); headerRight = QString("#%1").arg(page); p->drawText(0, 0, printer.width(), dy, Qt::AlignLeft, headerLeft); p->drawText(0, 0, printer.width(), dy, Qt::AlignHCenter, headerMid); p->drawText(0, 0, printer.width(), dy, Qt::AlignRight, headerRight); } p->restore(); p->translate(0, headerHeight - top); bottom = top + pageHeight; root->setPageTop(top); root->setPageBottom(bottom); root->setPageNumber(page); root->layer()->paint(p, QRect(0, top, pageWidth, pageHeight)); // qDebug() << "printed: page " << page <<" bottom At = " << bottom; top = bottom; p->resetTransform(); page++; } p->end(); delete p; // and now reset the layout to the usual one... root->setPagedMode(false); root->setStaticMode(false); d->paged = false; khtml::setPrintPainter(nullptr); setMediaType(oldMediaType); m_part->xmlDocImpl()->setPaintDevice(this); m_part->xmlDocImpl()->styleSelector()->computeFontSizes(m_part->xmlDocImpl()->logicalDpiY(), m_part->fontScaleFactor()); m_part->xmlDocImpl()->updateStyleSelector(); viewport()->unsetCursor(); } } void KHTMLView::slotPaletteChanged() { if (!m_part->xmlDocImpl()) { return; } DOM::DocumentImpl *document = m_part->xmlDocImpl(); if (!document->isHTMLDocument()) { return; } khtml::RenderCanvas *root = static_cast(document->renderer()); if (!root) { return; } root->style()->resetPalette(); NodeImpl *body = static_cast(document)->body(); if (!body) { return; } body->setChanged(true); body->recalcStyle(NodeImpl::Force); } void KHTMLView::paint(QPainter *p, const QRect &rc, int yOff, bool *more) { if (!m_part->xmlDocImpl()) { return; } khtml::RenderCanvas *root = static_cast(m_part->xmlDocImpl()->renderer()); if (!root) { return; } #ifdef SPEED_DEBUG d->firstRepaintPending = false; #endif QPaintDevice *opd = m_part->xmlDocImpl()->paintDevice(); m_part->xmlDocImpl()->setPaintDevice(p->device()); root->setPagedMode(true); root->setStaticMode(true); root->setWidth(rc.width()); // save() QRegion creg = p->clipRegion(); QTransform t = p->worldTransform(); QRect w = p->window(); QRect v = p->viewport(); bool vte = p->viewTransformEnabled(); bool wme = p->worldMatrixEnabled(); p->setClipRect(rc); p->translate(rc.left(), rc.top()); double scale = ((double) rc.width() / (double) root->docWidth()); int height = (int)((double) rc.height() / scale); #ifndef QT_NO_TRANSFORMATIONS p->scale(scale, scale); #endif root->setPageTop(yOff); root->setPageBottom(yOff + height); root->layer()->paint(p, QRect(0, yOff, root->docWidth(), height)); if (more) { *more = yOff + height < root->docHeight(); } // restore() p->setWorldTransform(t); p->setWindow(w); p->setViewport(v); p->setViewTransformEnabled(vte); p->setWorldMatrixEnabled(wme); if (!creg.isEmpty()) { p->setClipRegion(creg); } else { p->setClipRegion(QRegion(), Qt::NoClip); } root->setPagedMode(false); root->setStaticMode(false); m_part->xmlDocImpl()->setPaintDevice(opd); } void KHTMLView::render(QPainter *p, const QRect &r, const QPoint &off) { #ifdef SPEED_DEBUG d->firstRepaintPending = false; #endif QRect clip(off.x() + r.x(), off.y() + r.y(), r.width(), r.height()); if (!m_part || !m_part->xmlDocImpl() || !m_part->xmlDocImpl()->renderer()) { p->fillRect(clip, palette().brush(QPalette::Active, QPalette::Base)); return; } QPaintDevice *opd = m_part->xmlDocImpl()->paintDevice(); m_part->xmlDocImpl()->setPaintDevice(p->device()); // save() QRegion creg = p->clipRegion(); QTransform t = p->worldTransform(); QRect w = p->window(); QRect v = p->viewport(); bool vte = p->viewTransformEnabled(); bool wme = p->worldMatrixEnabled(); p->setClipRect(clip); QRect rect = r.translated(contentsX(), contentsY()); p->translate(off.x() - contentsX(), off.y() - contentsY()); m_part->xmlDocImpl()->renderer()->layer()->paint(p, rect); // restore() p->setWorldTransform(t); p->setWindow(w); p->setViewport(v); p->setViewTransformEnabled(vte); p->setWorldMatrixEnabled(wme); if (!creg.isEmpty()) { p->setClipRegion(creg); } else { p->setClipRegion(QRegion(), Qt::NoClip); } m_part->xmlDocImpl()->setPaintDevice(opd); } void KHTMLView::setHasStaticBackground(bool partial) { // full static iframe is irreversible for now if (d->staticWidget == KHTMLViewPrivate::SBFull && m_kwp->isRedirected()) { return; } d->staticWidget = partial ? KHTMLViewPrivate::SBPartial : KHTMLViewPrivate::SBFull; } void KHTMLView::setHasNormalBackground() { // full static iframe is irreversible for now if (d->staticWidget == KHTMLViewPrivate::SBFull && m_kwp->isRedirected()) { return; } d->staticWidget = KHTMLViewPrivate::SBNone; } void KHTMLView::addStaticObject(bool fixed) { if (fixed) { d->fixedObjectsCount++; } else { d->staticObjectsCount++; } setHasStaticBackground(true /*partial*/); } void KHTMLView::removeStaticObject(bool fixed) { if (fixed) { d->fixedObjectsCount--; } else { d->staticObjectsCount--; } assert(d->fixedObjectsCount >= 0 && d->staticObjectsCount >= 0); if (!d->staticObjectsCount && !d->fixedObjectsCount) { setHasNormalBackground(); } else { setHasStaticBackground(true /*partial*/); } } void KHTMLView::setVerticalScrollBarPolicy(Qt::ScrollBarPolicy policy) { #ifndef KHTML_NO_SCROLLBARS d->vpolicy = policy; QScrollArea::setVerticalScrollBarPolicy(policy); #else Q_UNUSED(policy); #endif } void KHTMLView::setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy policy) { #ifndef KHTML_NO_SCROLLBARS d->hpolicy = policy; QScrollArea::setHorizontalScrollBarPolicy(policy); #else Q_UNUSED(policy); #endif } void KHTMLView::restoreScrollBar() { int ow = visibleWidth(); QScrollArea::setVerticalScrollBarPolicy(d->vpolicy); if (visibleWidth() != ow) { layout(); } d->prevScrollbarVisible = verticalScrollBar()->isVisible(); } QStringList KHTMLView::formCompletionItems(const QString &name) const { if (!m_part->settings()->isFormCompletionEnabled()) { return QStringList(); } if (!d->formCompletions) { d->formCompletions = new KConfig(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + "khtml/formcompletions"); } return d->formCompletions->group("").readEntry(name, QStringList()); } void KHTMLView::clearCompletionHistory(const QString &name) { if (!d->formCompletions) { d->formCompletions = new KConfig(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + "khtml/formcompletions"); } d->formCompletions->group("").writeEntry(name, ""); d->formCompletions->sync(); } void KHTMLView::addFormCompletionItem(const QString &name, const QString &value) { if (!m_part->settings()->isFormCompletionEnabled()) { return; } // don't store values that are all numbers or just numbers with // dashes or spaces as those are likely credit card numbers or // something similar bool cc_number(true); for (int i = 0; i < value.length(); ++i) { QChar c(value[i]); if (!c.isNumber() && c != '-' && !c.isSpace()) { cc_number = false; break; } } if (cc_number) { return; } QStringList items = formCompletionItems(name); if (!items.contains(value)) { items.prepend(value); } while ((int)items.count() > m_part->settings()->maxFormCompletionItems()) { items.erase(items.isEmpty() ? items.end() : --items.end()); } d->formCompletions->group("").writeEntry(name, items); } void KHTMLView::addNonPasswordStorableSite(const QString &host) { if (!d->formCompletions) { d->formCompletions = new KConfig(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + "khtml/formcompletions"); } KConfigGroup cg(d->formCompletions, "NonPasswordStorableSites"); QStringList sites = cg.readEntry("Sites", QStringList()); sites.append(host); cg.writeEntry("Sites", sites); cg.sync(); } void KHTMLView::delNonPasswordStorableSite(const QString &host) { if (!d->formCompletions) { d->formCompletions = new KConfig(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + "khtml/formcompletions"); } KConfigGroup cg(d->formCompletions, "NonPasswordStorableSites"); QStringList sites = cg.readEntry("Sites", QStringList()); sites.removeOne(host); cg.writeEntry("Sites", sites); cg.sync(); } bool KHTMLView::nonPasswordStorableSite(const QString &host) const { if (!d->formCompletions) { d->formCompletions = new KConfig(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + "khtml/formcompletions"); } QStringList sites = d->formCompletions->group("NonPasswordStorableSites").readEntry("Sites", QStringList()); return (sites.indexOf(host) != -1); } // returns true if event should be swallowed bool KHTMLView::dispatchMouseEvent(int eventId, DOM::NodeImpl *targetNode, DOM::NodeImpl *targetNodeNonShared, bool cancelable, int detail, QMouseEvent *_mouse, bool setUnder, int mouseEventType, int orient) { // if the target node is a text node, dispatch on the parent node - rdar://4196646 (and #76948) if (targetNode && targetNode->isTextNode()) { targetNode = targetNode->parentNode(); } if (d->underMouse) { d->underMouse->deref(); } d->underMouse = targetNode; if (d->underMouse) { d->underMouse->ref(); } if (d->underMouseNonShared) { d->underMouseNonShared->deref(); } d->underMouseNonShared = targetNodeNonShared; if (d->underMouseNonShared) { d->underMouseNonShared->ref(); } bool isWheelEvent = (mouseEventType == DOM::NodeImpl::MouseWheel); int exceptioncode = 0; int pageX = _mouse->x(); int pageY = _mouse->y(); revertTransforms(pageX, pageY); int clientX = pageX - contentsX(); int clientY = pageY - contentsY(); int screenX = _mouse->globalX(); int screenY = _mouse->globalY(); int button = -1; switch (_mouse->button()) { case Qt::LeftButton: button = 0; break; case Qt::MidButton: button = 1; break; case Qt::RightButton: button = 2; break; default: break; } if (d->accessKeysEnabled && d->accessKeysPreActivate && button != -1) { d->accessKeysPreActivate = false; } bool ctrlKey = (_mouse->modifiers() & Qt::ControlModifier); bool altKey = (_mouse->modifiers() & Qt::AltModifier); bool shiftKey = (_mouse->modifiers() & Qt::ShiftModifier); bool metaKey = (_mouse->modifiers() & Qt::MetaModifier); // mouseout/mouseover if (setUnder && d->oldUnderMouse != targetNode) { if (d->oldUnderMouse && d->oldUnderMouse->document() != m_part->xmlDocImpl()) { d->oldUnderMouse->deref(); d->oldUnderMouse = nullptr; } // send mouseout event to the old node if (d->oldUnderMouse) { // send mouseout event to the old node MouseEventImpl *me = new MouseEventImpl(EventImpl::MOUSEOUT_EVENT, true, true, m_part->xmlDocImpl()->defaultView(), 0, screenX, screenY, clientX, clientY, pageX, pageY, ctrlKey, altKey, shiftKey, metaKey, button, targetNode); me->ref(); d->oldUnderMouse->dispatchEvent(me, exceptioncode, true); me->deref(); } // send mouseover event to the new node if (targetNode) { MouseEventImpl *me = new MouseEventImpl(EventImpl::MOUSEOVER_EVENT, true, true, m_part->xmlDocImpl()->defaultView(), 0, screenX, screenY, clientX, clientY, pageX, pageY, ctrlKey, altKey, shiftKey, metaKey, button, d->oldUnderMouse); me->ref(); targetNode->dispatchEvent(me, exceptioncode, true); me->deref(); } if (d->oldUnderMouse) { d->oldUnderMouse->deref(); } d->oldUnderMouse = targetNode; if (d->oldUnderMouse) { d->oldUnderMouse->ref(); } } bool swallowEvent = false; if (targetNode) { // if the target node is a disabled widget, we don't want any full-blown mouse events if (targetNode->isGenericFormElement() && static_cast(targetNode)->disabled()) { return true; } // send the actual event bool dblclick = (eventId == EventImpl::CLICK_EVENT && _mouse->type() == QEvent::MouseButtonDblClick); MouseEventImpl *me = new MouseEventImpl(static_cast(eventId), true, cancelable, m_part->xmlDocImpl()->defaultView(), detail, screenX, screenY, clientX, clientY, pageX, pageY, ctrlKey, altKey, shiftKey, metaKey, button, nullptr, isWheelEvent ? nullptr : _mouse, dblclick, isWheelEvent ? static_cast(orient) : MouseEventImpl::ONone); me->ref(); if (!d->m_mouseEventsTarget && RenderLayer::gScrollBar && eventId == EventImpl::MOUSEDOWN_EVENT) // button is pressed inside a layer scrollbar, so make it the target for future mousemove events until released { d->m_mouseEventsTarget = RenderLayer::gScrollBar; } if (d->m_mouseEventsTarget && qobject_cast(d->m_mouseEventsTarget) && dynamic_cast(static_cast(d->m_mouseEventsTarget))) { // we have a sticky mouse event target and it is a layer's scrollbar. Forward events manually. // ### should use the dom KHTMLWidget *w = dynamic_cast(static_cast(d->m_mouseEventsTarget)); QPoint p = w->m_kwp->absolutePos(); QMouseEvent fw(_mouse->type(), QPoint(pageX, pageY) - p, _mouse->button(), _mouse->buttons(), _mouse->modifiers()); static_cast(static_cast(d->m_mouseEventsTarget))->sendEvent(&fw); if (_mouse->type() == QMouseEvent::MouseButtonPress && _mouse->button() == Qt::RightButton) { QContextMenuEvent cme(QContextMenuEvent::Mouse, p); static_cast(static_cast(d->m_mouseEventsTarget))->sendEvent(&cme); d->m_mouseEventsTarget = nullptr; } swallowEvent = true; } else { targetNode->dispatchEvent(me, exceptioncode, true); bool defaultHandled = me->defaultHandled(); if (defaultHandled || me->defaultPrevented()) { swallowEvent = true; } } if (eventId == EventImpl::MOUSEDOWN_EVENT && !me->defaultPrevented()) { // Focus should be shifted on mouse down, not on a click. -dwh // Blur current focus node when a link/button is clicked; this // is expected by some sites that rely on onChange handlers running // from form fields before the button click is processed. DOM::NodeImpl *nodeImpl = targetNode; for (; nodeImpl && !nodeImpl->isFocusable(); nodeImpl = nodeImpl->parentNode()) { } if (nodeImpl && nodeImpl->isMouseFocusable()) { m_part->xmlDocImpl()->setFocusNode(nodeImpl); } else if (!nodeImpl || !nodeImpl->focused()) { m_part->xmlDocImpl()->setFocusNode(nullptr); } } me->deref(); } return swallowEvent; } void KHTMLView::setIgnoreWheelEvents(bool e) { d->ignoreWheelEvents = e; } #ifndef QT_NO_WHEELEVENT void KHTMLView::wheelEvent(QWheelEvent *e) { // check if we should reset the state of the indicator describing if // we are currently scrolling the view as a result of wheel events if (d->scrollingFromWheel != QPoint(-1, -1) && d->scrollingFromWheel != QCursor::pos()) { d->scrollingFromWheel = d->scrollingFromWheelTimerId ? QCursor::pos() : QPoint(-1, -1); } if (d->accessKeysEnabled && d->accessKeysPreActivate) { d->accessKeysPreActivate = false; } if ((e->modifiers() & Qt::ControlModifier) == Qt::ControlModifier) { emit zoomView(- e->delta()); e->accept(); } else if (d->firstLayoutPending) { e->accept(); } else if (!m_kwp->isRedirected() && ((e->orientation() == Qt::Vertical && ((d->ignoreWheelEvents && !verticalScrollBar()->isVisible()) || (e->delta() > 0 && contentsY() <= 0) || (e->delta() < 0 && contentsY() >= contentsHeight() - visibleHeight()))) || (e->orientation() == Qt::Horizontal && ((d->ignoreWheelEvents && !horizontalScrollBar()->isVisible()) || (e->delta() > 0 && contentsX() <= 0) || (e->delta() < 0 && contentsX() >= contentsWidth() - visibleWidth())))) && m_part->parentPart()) { if (m_part->parentPart()->view()) { m_part->parentPart()->view()->wheelEvent(e); } e->ignore(); } else { int xm = e->x(); int ym = e->y(); revertTransforms(xm, ym); DOM::NodeImpl::MouseEvent mev(e->buttons(), DOM::NodeImpl::MouseWheel); m_part->xmlDocImpl()->prepareMouseEvent(false, xm, ym, &mev); MouseEventImpl::Orientation o = MouseEventImpl::OVertical; if (e->orientation() == Qt::Horizontal) { o = MouseEventImpl::OHorizontal; } QMouseEvent _mouse(QEvent::MouseMove, e->pos(), Qt::NoButton, e->buttons(), e->modifiers()); bool swallow = dispatchMouseEvent(EventImpl::KHTML_MOUSEWHEEL_EVENT, mev.innerNode.handle(), mev.innerNonSharedNode.handle(), true, -e->delta() / 40, &_mouse, true, DOM::NodeImpl::MouseWheel, o); if (swallow) { return; } d->scrollBarMoved = true; d->scrollingFromWheel = QCursor::pos(); if (d->smoothScrollMode != SSMDisabled) { d->shouldSmoothScroll = true; } if (d->scrollingFromWheelTimerId) { killTimer(d->scrollingFromWheelTimerId); } d->scrollingFromWheelTimerId = startTimer(400); if (m_part->parentPart()) { // don't propagate if we are a sub-frame and our scrollbars are already at end of range bool h = (static_cast(e)->orientation() == Qt::Horizontal); bool d = (static_cast(e)->delta() < 0); QScrollBar *hsb = horizontalScrollBar(); QScrollBar *vsb = verticalScrollBar(); if ((h && ((d && hsb->value() == hsb->maximum()) || (!d && hsb->value() == hsb->minimum()))) || (!h && ((d && vsb->value() == vsb->maximum()) || (!d && vsb->value() == vsb->minimum())))) { e->accept(); return; } } QScrollArea::wheelEvent(e); } } #endif void KHTMLView::dragEnterEvent(QDragEnterEvent *ev) { // Still overridden for BC reasons only... QScrollArea::dragEnterEvent(ev); } void KHTMLView::dropEvent(QDropEvent *ev) { // Still overridden for BC reasons only... QScrollArea::dropEvent(ev); } void KHTMLView::focusInEvent(QFocusEvent *e) { DOM::NodeImpl *fn = m_part->xmlDocImpl() ? m_part->xmlDocImpl()->focusNode() : nullptr; if (fn && fn->renderer() && fn->renderer()->isWidget() && (e->reason() != Qt::MouseFocusReason) && static_cast(fn->renderer())->widget()) { static_cast(fn->renderer())->widget()->setFocus(); } m_part->setSelectionVisible(); QScrollArea::focusInEvent(e); } void KHTMLView::focusOutEvent(QFocusEvent *e) { if (m_part) { m_part->stopAutoScroll(); m_part->setSelectionVisible(false); } if (d->cursorIconWidget) { d->cursorIconWidget->hide(); } QScrollArea::focusOutEvent(e); } void KHTMLView::scrollContentsBy(int dx, int dy) { if (!dx && !dy) { return; } if (!d->firstLayoutPending && !d->complete && m_part->xmlDocImpl() && d->layoutSchedulingEnabled) { // contents scroll while we are not complete: we need to check our layout *now* khtml::RenderCanvas *root = static_cast(m_part->xmlDocImpl()->renderer()); if (root && root->needsLayout()) { unscheduleRelayout(); layout(); } } if (d->shouldSmoothScroll && d->smoothScrollMode != SSMDisabled && m_part->xmlDocImpl() && m_part->xmlDocImpl()->renderer() && (d->smoothScrollMode != SSMWhenEfficient || d->smoothScrollMissedDeadlines != sWayTooMany)) { bool doSmoothScroll = (!d->staticWidget || d->smoothScrollMode == SSMEnabled); int numStaticPixels = 0; QRegion r = static_cast(m_part->xmlDocImpl()->renderer())->staticRegion(); // only do smooth scrolling if static region is relatively small if (!doSmoothScroll && d->staticWidget == KHTMLViewPrivate::SBPartial && r.rects().size() <= 10) { foreach (const QRect &rr, r.rects()) { numStaticPixels += rr.width() * rr.height(); } if ((numStaticPixels < sSmoothScrollMinStaticPixels) || (numStaticPixels * 8 < visibleWidth()*visibleHeight())) { doSmoothScroll = true; } } if (doSmoothScroll) { setupSmoothScrolling(dx, dy); return; } } if (underMouse() && QToolTip::isVisible()) { QToolTip::hideText(); } if (!d->scrollingSelf) { d->scrollBarMoved = true; d->contentsMoving = true; // ensure quick reset of contentsMoving flag scheduleRepaint(0, 0, 0, 0); } if (m_part->xmlDocImpl() && m_part->xmlDocImpl()->documentElement()) { m_part->xmlDocImpl()->documentElement()->dispatchHTMLEvent(EventImpl::SCROLL_EVENT, true, false); } if (QApplication::isRightToLeft()) { dx = -dx; } if (!d->smoothScrolling) { d->updateContentsXY(); } else { d->contentsX -= dx; d->contentsY -= dy; } if (widget()->pos() != QPoint(0, 0)) { // qDebug() << "Static widget wasn't positioned at (0,0). This should NOT happen. Please report this event to developers."; widget()->move(0, 0); } QWidget *w = widget(); QPoint off; if (m_kwp->isRedirected()) { // This is a redirected sub frame. Translate to root view context KHTMLView *v = m_kwp->rootViewPos(off); if (v) { w = v->widget(); } off = viewport()->mapTo(this, off); } if (d->staticWidget) { // now remove from view the external widgets that must have completely // disappeared after dx/dy scroll delta is effective if (!d->visibleWidgets.isEmpty()) { checkExternalWidgetsPosition(); } if (d->staticWidget == KHTMLViewPrivate::SBPartial && m_part->xmlDocImpl() && m_part->xmlDocImpl()->renderer()) { // static objects might be selectively repainted, like stones in flowing water QRegion r = static_cast(m_part->xmlDocImpl()->renderer())->staticRegion(); r.translate(-contentsX(), -contentsY()); QVector ar = r.rects(); for (int i = 0; i < ar.size(); ++i) { widget()->update(ar[i]); } r = QRegion(QRect(0, 0, visibleWidth(), visibleHeight())) - r; ar = r.rects(); for (int i = 0; i < ar.size(); ++i) { w->scroll(dx, dy, ar[i].translated(off)); } d->scrollExternalWidgets(dx, dy); } else { // we can't avoid a full update widget()->update(); } if (d->accessKeysActivated) { d->scrollAccessKeys(dx, dy); } return; } if (m_kwp->isRedirected()) { const QRect rect(off.x(), off.y(), visibleWidth() * d->zoomLevel / 100, visibleHeight() * d->zoomLevel / 100); w->scroll(dx, dy, rect); if (d->zoomLevel != 100) { w->update(rect); // without this update we are getting bad rendering when an iframe is zoomed in } } else { widget()->scroll(dx, dy, widget()->rect() & viewport()->rect()); } d->scrollExternalWidgets(dx, dy); if (d->accessKeysActivated) { d->scrollAccessKeys(dx, dy); } } void KHTMLView::setupSmoothScrolling(int dx, int dy) { // old or minimum speed int ddx = qMax(d->steps ? abs(d->dx) / d->steps : 0, 3); int ddy = qMax(d->steps ? abs(d->dy) / d->steps : 0, 3); // full scroll is remaining scroll plus new scroll d->dx = d->dx + dx; d->dy = d->dy + dy; if (d->dx == 0 && d->dy == 0) { d->stopScrolling(); return; } d->steps = (sSmoothScrollTime - 1) / sSmoothScrollTick + 1; if (qMax(abs(d->dx), abs(d->dy)) / d->steps < qMax(ddx, ddy)) { // Don't move slower than average 4px/step in minimum one direction // This means fewer than normal steps d->steps = qMax((abs(d->dx) + ddx - 1) / ddx, (abs(d->dy) + ddy - 1) / ddy); if (d->steps < 1) { d->steps = 1; } } d->smoothScrollStopwatch.start(); if (!d->smoothScrolling) { d->startScrolling(); scrollTick(); } } void KHTMLView::scrollTick() { if (d->dx == 0 && d->dy == 0) { d->stopScrolling(); return; } if (d->steps < 1) { d->steps = 1; } int takesteps = d->smoothScrollStopwatch.restart() / sSmoothScrollTick; int scroll_x = 0; int scroll_y = 0; if (takesteps < 1) { takesteps = 1; } if (takesteps > d->steps) { takesteps = d->steps; } for (int i = 0; i < takesteps; i++) { int ddx = (d->dx / (d->steps + 1)) * 2; int ddy = (d->dy / (d->steps + 1)) * 2; // limit step to requested scrolling distance if (abs(ddx) > abs(d->dx)) { ddx = d->dx; } if (abs(ddy) > abs(d->dy)) { ddy = d->dy; } // update remaining scroll d->dx -= ddx; d->dy -= ddy; scroll_x += ddx; scroll_y += ddy; d->steps--; } d->shouldSmoothScroll = false; scrollContentsBy(scroll_x, scroll_y); if (takesteps < 2) { d->smoothScrollMissedDeadlines = 0; } else { if (d->smoothScrollMissedDeadlines != sWayTooMany && (!m_part->xmlDocImpl() || !m_part->xmlDocImpl()->parsing())) { d->smoothScrollMissedDeadlines++; if (d->smoothScrollMissedDeadlines >= sMaxMissedDeadlines) { // we missed many deadlines in a row! // time to signal we had enough.. d->smoothScrollMissedDeadlines = sWayTooMany; } } } } void KHTMLView::addChild(QWidget *child, int x, int y) { if (!child) { return; } if (child->parent() != widget()) { child->setParent(widget()); } // ### handle pseudo-zooming of non-redirected widgets (e.g. just resize'em) child->move(x - contentsX(), y - contentsY()); } void KHTMLView::timerEvent(QTimerEvent *e) { // qDebug() << "timer event " << e->timerId(); if (e->timerId() == d->scrollTimerId) { if (d->scrollSuspended) { return; } switch (d->scrollDirection) { case KHTMLViewPrivate::ScrollDown: if (contentsY() + visibleHeight() >= contentsHeight()) { d->newScrollTimer(this, 0); } else { verticalScrollBar()->setValue(verticalScrollBar()->value() + d->scrollBy); } break; case KHTMLViewPrivate::ScrollUp: if (contentsY() <= 0) { d->newScrollTimer(this, 0); } else { verticalScrollBar()->setValue(verticalScrollBar()->value() - d->scrollBy); } break; case KHTMLViewPrivate::ScrollRight: if (contentsX() + visibleWidth() >= contentsWidth()) { d->newScrollTimer(this, 0); } else { horizontalScrollBar()->setValue(horizontalScrollBar()->value() + d->scrollBy); } break; case KHTMLViewPrivate::ScrollLeft: if (contentsX() <= 0) { d->newScrollTimer(this, 0); } else { horizontalScrollBar()->setValue(horizontalScrollBar()->value() - d->scrollBy); } break; } return; } else if (e->timerId() == d->scrollingFromWheelTimerId) { killTimer(d->scrollingFromWheelTimerId); d->scrollingFromWheelTimerId = 0; } else if (e->timerId() == d->layoutTimerId) { if (d->firstLayoutPending && d->layoutAttemptCounter < 4 && (!m_part->xmlDocImpl() || !m_part->xmlDocImpl()->readyForLayout())) { d->layoutAttemptCounter++; killTimer(d->layoutTimerId); d->layoutTimerId = 0; scheduleRelayout(); return; } layout(); d->scheduledLayoutCounter++; if (d->firstLayoutPending) { d->firstLayoutPending = false; verticalScrollBar()->setEnabled(true); horizontalScrollBar()->setEnabled(true); } } d->contentsMoving = false; if (m_part->xmlDocImpl()) { DOM::DocumentImpl *document = m_part->xmlDocImpl(); khtml::RenderCanvas *root = static_cast(document->renderer()); if (root && root->needsLayout()) { if (d->repaintTimerId) { killTimer(d->repaintTimerId); } d->repaintTimerId = 0; scheduleRelayout(); return; } } if (d->repaintTimerId) { killTimer(d->repaintTimerId); } d->repaintTimerId = 0; QRect updateRegion; const QVector rects = d->updateRegion.rects(); d->updateRegion = QRegion(); if (rects.size()) { updateRegion = rects[0]; } for (int i = 1; i < rects.size(); ++i) { QRect newRegion = updateRegion.unite(rects[i]); if (2 * newRegion.height() > 3 * updateRegion.height()) { repaintContents(updateRegion); updateRegion = rects[i]; } else { updateRegion = newRegion; } } if (!updateRegion.isNull()) { repaintContents(updateRegion); } // As widgets can only be accurately positioned during painting, every layout might // dissociate a widget from its RenderWidget. E.g: if a RenderWidget was visible before layout, but the layout // pushed it out of the viewport, it will not be repainted, and consequently it's associated widget won't be repositioned. // Thus we need to check each supposedly 'visible' widget at the end of layout, and remove it in case it's no more in sight. if (d->dirtyLayout && !d->visibleWidgets.isEmpty()) { checkExternalWidgetsPosition(); } d->dirtyLayout = false; emit repaintAccessKeys(); if (d->emitCompletedAfterRepaint) { bool full = d->emitCompletedAfterRepaint == KHTMLViewPrivate::CSFull; d->emitCompletedAfterRepaint = KHTMLViewPrivate::CSNone; if (full) { emit m_part->completed(); } else { emit m_part->completed(true); } } } void KHTMLView::checkExternalWidgetsPosition() { QWidget *w; QRect visibleRect(contentsX(), contentsY(), visibleWidth(), visibleHeight()); QList toRemove; QHashIterator it(d->visibleWidgets); while (it.hasNext()) { int xp = 0, yp = 0; it.next(); RenderWidget *rw = static_cast(it.key()); if (!rw->absolutePosition(xp, yp) || !visibleRect.intersects(QRect(xp, yp, it.value()->width(), it.value()->height()))) { toRemove.append(rw); } } foreach (RenderWidget *r, toRemove) if ((w = d->visibleWidgets.take(r))) { w->move(0, -500000); } } void KHTMLView::scheduleRelayout(khtml::RenderObject * /*clippedObj*/) { if (!d->layoutSchedulingEnabled || d->layoutTimerId) { return; } int time = 0; if (d->firstLayoutPending) { // Any repaint happening while we have no content blanks the viewport ("white flash"). // Hence the need to delay the first layout as much as we can. // Only if the document gets stuck for too long in incomplete state will we allow the blanking. time = d->layoutAttemptCounter ? sLayoutAttemptDelay + sLayoutAttemptIncrement * d->layoutAttemptCounter : sFirstLayoutDelay; } else if (m_part->xmlDocImpl() && m_part->xmlDocImpl()->parsing()) { // Delay between successive layouts in parsing mode. // Increment reflects the decaying importance of visual feedback over time. time = qMin(2000, sParsingLayoutsInterval + d->scheduledLayoutCounter * sParsingLayoutsIncrement); } d->layoutTimerId = startTimer(time); } void KHTMLView::unscheduleRelayout() { if (!d->layoutTimerId) { return; } killTimer(d->layoutTimerId); d->layoutTimerId = 0; } void KHTMLView::unscheduleRepaint() { if (!d->repaintTimerId) { return; } killTimer(d->repaintTimerId); d->repaintTimerId = 0; } void KHTMLView::scheduleRepaint(int x, int y, int w, int h, bool asap) { bool parsing = !m_part->xmlDocImpl() || m_part->xmlDocImpl()->parsing(); // qDebug() << "parsing " << parsing; // qDebug() << "complete " << d->complete; int time = parsing && !d->firstLayoutPending ? 150 : (!asap ? (!d->complete ? 80 : 20) : 0); #ifdef DEBUG_FLICKER QPainter p; p.begin(viewport()); int vx, vy; contentsToViewport(x, y, vx, vy); p.fillRect(vx, vy, w, h, Qt::red); p.end(); #endif d->updateRegion = d->updateRegion.unite(QRect(x, y, w, h)); if (asap && !parsing) { unscheduleRepaint(); } if (!d->repaintTimerId) { d->repaintTimerId = startTimer(time); } // qDebug() << "starting timer " << time; } void KHTMLView::complete(bool pendingAction) { // qDebug() << "KHTMLView::complete()"; d->complete = true; // is there a relayout pending? if (d->layoutTimerId) { // qDebug() << "requesting relayout now"; // do it now killTimer(d->layoutTimerId); d->layoutTimerId = startTimer(0); d->emitCompletedAfterRepaint = pendingAction ? KHTMLViewPrivate::CSActionPending : KHTMLViewPrivate::CSFull; } // is there a repaint pending? if (d->repaintTimerId) { // qDebug() << "requesting repaint now"; // do it now killTimer(d->repaintTimerId); d->repaintTimerId = startTimer(0); d->emitCompletedAfterRepaint = pendingAction ? KHTMLViewPrivate::CSActionPending : KHTMLViewPrivate::CSFull; } if (!d->emitCompletedAfterRepaint) { if (!pendingAction) { emit m_part->completed(); } else { emit m_part->completed(true); } } } void KHTMLView::updateScrollBars() { const QWidget *view = widget(); if (!view) { return; } QSize p = viewport()->size(); QSize m = maximumViewportSize(); if (m.expandedTo(view->size()) == m) { p = m; // no scroll bars needed } QSize v = view->size(); horizontalScrollBar()->setRange(0, v.width() - p.width()); horizontalScrollBar()->setPageStep(p.width()); verticalScrollBar()->setRange(0, v.height() - p.height()); verticalScrollBar()->setPageStep(p.height()); if (!d->smoothScrolling) { d->updateContentsXY(); } } void KHTMLView::slotMouseScrollTimer() { horizontalScrollBar()->setValue(horizontalScrollBar()->value() + d->m_mouseScroll_byX); verticalScrollBar()->setValue(verticalScrollBar()->value() + d->m_mouseScroll_byY); } static DOM::Position positionOfLineBoundary(const DOM::Position &pos, bool toEnd) { Selection sel = pos; sel.expandUsingGranularity(Selection::LINE); return toEnd ? sel.end() : sel.start(); } inline static DOM::Position positionOfLineBegin(const DOM::Position &pos) { return positionOfLineBoundary(pos, false); } inline static DOM::Position positionOfLineEnd(const DOM::Position &pos) { return positionOfLineBoundary(pos, true); } bool KHTMLView::caretKeyPressEvent(QKeyEvent *_ke) { EditorContext *ec = &m_part->d->editor_context; Selection &caret = ec->m_selection; Position old_pos = caret.caretPos(); Position pos = old_pos; bool recalcXPos = true; bool handled = true; bool ctrl = _ke->modifiers() & Qt::ControlModifier; bool shift = _ke->modifiers() & Qt::ShiftModifier; switch (_ke->key()) { // -- Navigational keys case Qt::Key_Down: pos = old_pos.nextLinePosition(caret.xPosForVerticalArrowNavigation(Selection::EXTENT)); recalcXPos = false; break; case Qt::Key_Up: pos = old_pos.previousLinePosition(caret.xPosForVerticalArrowNavigation(Selection::EXTENT)); recalcXPos = false; break; case Qt::Key_Left: pos = ctrl ? old_pos.previousWordPosition() : old_pos.previousCharacterPosition(); break; case Qt::Key_Right: pos = ctrl ? old_pos.nextWordPosition() : old_pos.nextCharacterPosition(); break; case Qt::Key_PageDown: // moveCaretNextPage(); ### break; case Qt::Key_PageUp: // moveCaretPrevPage(); ### break; case Qt::Key_Home: if (ctrl) /*moveCaretToDocumentBoundary(false)*/; // ### else { pos = positionOfLineBegin(old_pos); } break; case Qt::Key_End: if (ctrl) /*moveCaretToDocumentBoundary(true)*/; // ### else { pos = positionOfLineEnd(old_pos); } break; default: handled = false; }/*end switch*/ if (pos != old_pos) { m_part->clearCaretRectIfNeeded(); caret.moveTo(shift ? caret.nonCaretPos() : pos, pos); int old_x = caret.xPosForVerticalArrowNavigation(Selection::CARETPOS); m_part->selectionLayoutChanged(); // restore old x-position to prevent recalculation if (!recalcXPos) { m_part->d->editor_context.m_xPosForVerticalArrowNavigation = old_x; } m_part->emitCaretPositionChanged(pos); // ### check when to emit it m_part->notifySelectionChanged(); } if (handled) { _ke->accept(); } return handled; } #undef DEBUG_CARETMODE diff --git a/src/rendering/RenderPath.cpp b/src/rendering/RenderPath.cpp index d540e9c..00662ba 100644 --- a/src/rendering/RenderPath.cpp +++ b/src/rendering/RenderPath.cpp @@ -1,475 +1,475 @@ /* Copyright (C) 2004, 2005, 2007 Nikolas Zimmermann 2004, 2005 Rob Buis 2005, 2007 Eric Seidel This file is part of the KDE project This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License aint with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "wtf/Platform.h" #include "Document.h" #if ENABLE(SVG) #include "RenderPath.h" #include #include "FloatPoint.h" /*#include "GraphicsContext.h" #include "PointerEventsHitRules.h"*/ #include "RenderSVGContainer.h" #include "SVGPaintServer.h" #include "SVGRenderSupport.h" /*#include "SVGResourceFilter.h" #include "SVGResourceMarker.h" #include "SVGResourceMasker.h"*/ #include "SVGStyledTransformableElement.h" #include "SVGTransformList.h" #include "SVGURIReference.h" #include namespace WebCore { // RenderPath RenderPath::RenderPath(RenderStyle *style, SVGStyledTransformableElement *node) : RenderObject(node) { ASSERT(style != nullptr); Q_UNUSED(style); ASSERT(static_cast(node)->isStyledTransformable()); } RenderPath::~RenderPath() { } AffineTransform RenderPath::localTransform() const { return m_localTransform; } FloatPoint RenderPath::mapAbsolutePointToLocal(const FloatPoint &point) const { // FIXME: does it make sense to map incoming points with the inverse of the // absolute transform? double localX; double localY; absoluteTransform().inverse().map(point.x(), point.y(), &localX, &localY); return FloatPoint::narrowPrecision(localX, localY); } bool RenderPath::fillContains(const FloatPoint &point, bool requiresFill) const { if (m_path.isEmpty()) { return false; } if (requiresFill && !SVGPaintServer::fillPaintServer(style(), this)) { return false; } return m_path.contains(point, style()->svgStyle()->fillRule()); } FloatRect RenderPath::relativeBBox(bool includeStroke) const { if (m_path.isEmpty()) { return FloatRect(); } if (includeStroke) { if (m_strokeBbox.isEmpty()) /*m_strokeBbox = strokeBBox();*/ { return m_strokeBbox; } } if (m_fillBBox.isEmpty()) { m_fillBBox = m_path.boundingRect(); } return m_fillBBox; } void RenderPath::setPath(const Path &newPath) { m_path = newPath; m_strokeBbox = FloatRect(); m_fillBBox = FloatRect(); } const Path &RenderPath::path() const { return m_path; } bool RenderPath::calculateLocalTransform() { AffineTransform oldTransform = m_localTransform; m_localTransform = static_cast(element())->animatedLocalTransform(); return (m_localTransform != oldTransform); } void RenderPath::layout() { IntRect oldBounds; IntRect oldOutlineBox; bool checkForRepaint = /*checkForRepaintDuringLayout() && */selfNeedsLayout(); if (checkForRepaint) { oldBounds = m_absoluteBounds; //oldOutlineBox = absoluteOutlineBox(); } calculateLocalTransform(); setPath(static_cast(element())->toPathData()); m_absoluteBounds = absoluteClippedOverflowRect(); setWidth(m_absoluteBounds.width()); setHeight(m_absoluteBounds.height()); /*if (checkForRepaint) repaintAfterLayoutIfNeeded(oldBounds, oldOutlineBox);*/ setNeedsLayout(false); } IntRect RenderPath::absoluteClippedOverflowRect() { return IntRect(0, 0, 100, 100); /*FloatRect repaintRect = absoluteTransform().mapRect(relativeBBox(true)); // Markers can expand the bounding box repaintRect.unite(m_markerBounds); #if ENABLE(SVG_FILTERS) // Filters can expand the bounding box SVGResourceFilter* filter = getFilterById(document(), SVGURIReference::getTarget(style()->svgStyle()->filter())); if (filter) repaintRect.unite(filter->filterBBoxForItemBBox(repaintRect)); #endif if (!repaintRect.isEmpty()) repaintRect.inflate(1); // inflate 1 pixel for antialiasing return enclosingIntRect(repaintRect);*/ } bool RenderPath::requiresLayer() const { return false; } short RenderPath::lineHeight(bool b) const { Q_UNUSED(b); return static_cast(relativeBBox(true).height()); } short RenderPath::baselinePosition(bool b) const { Q_UNUSED(b); return static_cast(relativeBBox(true).height()); } static inline void fillAndStrokePath(const Path &path, QPainter *painter, RenderStyle *style, RenderPath *object) { /*context->beginPath();*/ SVGPaintServer *fillPaintServer = SVGPaintServer::fillPaintServer(style, object); if (fillPaintServer) { /*context->addPath(path);*/ fillPaintServer->draw(painter, path.platformPath(), object, ApplyToFillTargetType); } SVGPaintServer *strokePaintServer = SVGPaintServer::strokePaintServer(style, object); if (strokePaintServer) { /*context->addPath(path); // path is cleared when filled.*/ strokePaintServer->draw(painter, path.platformPath(), object, ApplyToStrokeTargetType); } } void RenderPath::paint(PaintInfo &paintInfo, int, int) { paintInfo.p->save(); paintInfo.p->setWorldMatrix(localTransform(), true); SVGResourceFilter *filter = nullptr; prepareToRenderSVGContent(this, paintInfo, FloatRect(), filter/*boundingBox, filter*/); if (paintInfo.phase == PaintActionForeground) { fillAndStrokePath(m_path, paintInfo.p, style(), this); } paintInfo.p->restore(); #if 0 /*if (paintInfo.context->paintingDisabled() || style()->visibility() == HIDDEN || m_path.isEmpty()) return;*/ //paintInfo.context->save(); /*paintInfo.context->concatCTM(localTransform());*/ //paintInfo.p->fillRect(0, 0, 50, 50, QBrush(Qt::yellow)); /*paintInfo.p->setPen(Qt::blue); - // qDebug() << "path:" << *m_path.platformPath() << endl; + // qDebug() << "path:" << *m_path.platformPath(); paintInfo.p->drawPath(*m_path.platformPath());*/ /*SVGResourceFilter* filter = 0; FloatRect boundingBox = relativeBBox(true); if (paintInfo.phase == PaintPhaseForeground) { PaintInfo savedInfo(paintInfo); prepareToRenderSVGContent(this, paintInfo, boundingBox, filter); if (style()->svgStyle()->shapeRendering() == SR_CRISPEDGES) paintInfo.context->setUseAntialiasing(false); fillAndStrokePath(m_path, paintInfo.context, style(), this); if (static_cast(element())->supportsMarkers()) m_markerBounds = drawMarkersIfNeeded(paintInfo.context, paintInfo.rect, m_path); finishRenderSVGContent(this, paintInfo, boundingBox, filter, savedInfo.context); } if ((paintInfo.phase == PaintPhaseOutline || paintInfo.phase == PaintPhaseSelfOutline) && style()->outlineWidth()) paintOutline(paintInfo.context, static_cast(boundingBox.x()), static_cast(boundingBox.y()), static_cast(boundingBox.width()), static_cast(boundingBox.height()), style());*/ //paintInfo.context->restore(); #endif } /*void RenderPath::addFocusRingRects(GraphicsContext* graphicsContext, int, int) { graphicsContext->addFocusRingRect(enclosingIntRect(relativeBBox(true))); }*/ void RenderPath::absoluteRects(Vector &rects, int, int, bool) { rects.append(absoluteClippedOverflowRect()); } /*bool RenderPath::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int _x, int _y, int, int, HitTestAction hitTestAction) { // We only draw in the forground phase, so we only hit-test then. if (hitTestAction != HitTestForeground) return false; IntPoint absolutePoint(_x, _y); PointerEventsHitRules hitRules(PointerEventsHitRules::SVG_PATH_HITTESTING, style()->svgStyle()->pointerEvents()); bool isVisible = (style()->visibility() == VISIBLE); if (isVisible || !hitRules.requireVisible) { FloatPoint hitPoint = mapAbsolutePointToLocal(absolutePoint); if ((hitRules.canHitStroke && (style()->svgStyle()->hasStroke() || !hitRules.requireStroke) && strokeContains(hitPoint, hitRules.requireStroke)) || (hitRules.canHitFill && (style()->svgStyle()->hasFill() || !hitRules.requireFill) && fillContains(hitPoint, hitRules.requireFill))) { updateHitTestResult(result, absolutePoint); return true; } } return false; }*/ /*enum MarkerType { Start, Mid, End }; struct MarkerData { FloatPoint origin; FloatPoint subpathStart; double strokeWidth; FloatPoint inslopePoints[2]; FloatPoint outslopePoints[2]; MarkerType type; SVGResourceMarker* marker; }; struct DrawMarkersData { DrawMarkersData(GraphicsContext*, SVGResourceMarker* startMarker, SVGResourceMarker* midMarker, double strokeWidth); GraphicsContext* context; int elementIndex; MarkerData previousMarkerData; SVGResourceMarker* midMarker; }; DrawMarkersData::DrawMarkersData(GraphicsContext* c, SVGResourceMarker *start, SVGResourceMarker *mid, double strokeWidth) : context(c) , elementIndex(0) , midMarker(mid) { previousMarkerData.origin = FloatPoint(); previousMarkerData.subpathStart = FloatPoint(); previousMarkerData.strokeWidth = strokeWidth; previousMarkerData.marker = start; previousMarkerData.type = Start; } static void drawMarkerWithData(GraphicsContext* context, MarkerData &data) { if (!data.marker) return; FloatPoint inslopeChange = data.inslopePoints[1] - FloatSize(data.inslopePoints[0].x(), data.inslopePoints[0].y()); FloatPoint outslopeChange = data.outslopePoints[1] - FloatSize(data.outslopePoints[0].x(), data.outslopePoints[0].y()); double inslope = rad2deg(atan2(inslopeChange.y(), inslopeChange.x())); double outslope = rad2deg(atan2(outslopeChange.y(), outslopeChange.x())); double angle = 0.0; switch (data.type) { case Start: angle = outslope; break; case Mid: angle = (inslope + outslope) / 2; break; case End: angle = inslope; } data.marker->draw(context, FloatRect(), data.origin.x(), data.origin.y(), data.strokeWidth, angle); } static inline void updateMarkerDataForElement(MarkerData& previousMarkerData, const PathElement* element) { FloatPoint* points = element->points; switch (element->type) { case PathElementAddQuadCurveToPoint: // TODO previousMarkerData.origin = points[1]; break; case PathElementAddCurveToPoint: previousMarkerData.inslopePoints[0] = points[1]; previousMarkerData.inslopePoints[1] = points[2]; previousMarkerData.origin = points[2]; break; case PathElementMoveToPoint: previousMarkerData.subpathStart = points[0]; case PathElementAddLineToPoint: previousMarkerData.inslopePoints[0] = previousMarkerData.origin; previousMarkerData.inslopePoints[1] = points[0]; previousMarkerData.origin = points[0]; break; case PathElementCloseSubpath: previousMarkerData.inslopePoints[0] = previousMarkerData.origin; previousMarkerData.inslopePoints[1] = points[0]; previousMarkerData.origin = previousMarkerData.subpathStart; previousMarkerData.subpathStart = FloatPoint(); } } static void drawStartAndMidMarkers(void* info, const PathElement* element) { DrawMarkersData& data = *reinterpret_cast(info); int elementIndex = data.elementIndex; MarkerData& previousMarkerData = data.previousMarkerData; FloatPoint* points = element->points; // First update the outslope for the previous element previousMarkerData.outslopePoints[0] = previousMarkerData.origin; previousMarkerData.outslopePoints[1] = points[0]; // Draw the marker for the previous element if (elementIndex != 0) drawMarkerWithData(data.context, previousMarkerData); // Update our marker data for this element updateMarkerDataForElement(previousMarkerData, element); if (elementIndex == 1) { // After drawing the start marker, switch to drawing mid markers previousMarkerData.marker = data.midMarker; previousMarkerData.type = Mid; } data.elementIndex++; } FloatRect RenderPath::drawMarkersIfNeeded(GraphicsContext* context, const FloatRect& rect, const Path& path) const { Document* doc = document(); SVGElement* svgElement = static_cast(element()); ASSERT(svgElement && svgElement->document() && svgElement->isStyled()); SVGStyledElement* styledElement = static_cast(svgElement); const SVGRenderStyle* svgStyle = style()->svgStyle(); AtomicString startMarkerId(SVGURIReference::getTarget(svgStyle->startMarker())); AtomicString midMarkerId(SVGURIReference::getTarget(svgStyle->midMarker())); AtomicString endMarkerId(SVGURIReference::getTarget(svgStyle->endMarker())); SVGResourceMarker* startMarker = getMarkerById(doc, startMarkerId); SVGResourceMarker* midMarker = getMarkerById(doc, midMarkerId); SVGResourceMarker* endMarker = getMarkerById(doc, endMarkerId); if (!startMarker && !startMarkerId.isEmpty()) svgElement->document()->accessSVGExtensions()->addPendingResource(startMarkerId, styledElement); else if (startMarker) startMarker->addClient(styledElement); if (!midMarker && !midMarkerId.isEmpty()) svgElement->document()->accessSVGExtensions()->addPendingResource(midMarkerId, styledElement); else if (midMarker) midMarker->addClient(styledElement); if (!endMarker && !endMarkerId.isEmpty()) svgElement->document()->accessSVGExtensions()->addPendingResource(endMarkerId, styledElement); else if (endMarker) endMarker->addClient(styledElement); if (!startMarker && !midMarker && !endMarker) return FloatRect(); double strokeWidth = SVGRenderStyle::cssPrimitiveToLength(this, svgStyle->strokeWidth(), 1.0f); DrawMarkersData data(context, startMarker, midMarker, strokeWidth); path.apply(&data, drawStartAndMidMarkers); data.previousMarkerData.marker = endMarker; data.previousMarkerData.type = End; drawMarkerWithData(context, data.previousMarkerData); // We know the marker boundaries, only after they're drawn! // Otherwhise we'd need to do all the marker calculation twice // once here (through paint()) and once in absoluteClippedOverflowRect(). FloatRect bounds; if (startMarker) bounds.unite(startMarker->cachedBounds()); if (midMarker) bounds.unite(midMarker->cachedBounds()); if (endMarker) bounds.unite(endMarker->cachedBounds()); return bounds; }*/ } #endif // ENABLE(SVG) diff --git a/src/rendering/RenderSVGInlineText.cpp b/src/rendering/RenderSVGInlineText.cpp index 7805529..97fcb83 100644 --- a/src/rendering/RenderSVGInlineText.cpp +++ b/src/rendering/RenderSVGInlineText.cpp @@ -1,177 +1,177 @@ /* * This file is part of the WebKit project. * * Copyright (C) 2006 Oliver Hunt * (C) 2006 Apple Computer Inc. * (C) 2007 Nikolas Zimmermann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #include "wtf/Platform.h" #if ENABLE(SVG) #include "RenderSVGInlineText.h" #include "FloatConversion.h" //#include "RenderBlock.h" #include "render_block.h" // khtml #include "RenderSVGRoot.h" #include "SVGInlineTextBox.h" #include "SVGRootInlineBox.h" namespace WebCore { using namespace khtml; static inline bool isChildOfHiddenContainer(RenderObject *start) { while (start) { if (start->isSVGHiddenContainer()) { return true; } start = start->parent(); } return false; } RenderSVGInlineText::RenderSVGInlineText(Node *n, DOMStringImpl *str) : RenderText(n, str) { } void RenderSVGInlineText::absoluteRects(Vector &rects, int, int, bool) { rects.append(computeAbsoluteRectForRange(0, stringLength())); } IntRect RenderSVGInlineText::selectionRect(bool) { ASSERT(!needsLayout()); IntRect rect; if (selectionState() == SelectionNone) { return rect; } // Early exit if we're ie. a within a section. if (isChildOfHiddenContainer(this)) { return rect; } // Now calculate startPos and endPos for painting selection. // We include a selection while endPos > 0 int startPos, endPos; if (selectionState() == SelectionInside) { // We are fully selected. startPos = 0; endPos = stringLength(); } else { selectionStartEnd(startPos, endPos); if (selectionState() == SelectionStart) { endPos = stringLength(); } else if (selectionState() == SelectionEnd) { startPos = 0; } } if (startPos == endPos) { return rect; } return computeAbsoluteRectForRange(startPos, endPos); } IntRect RenderSVGInlineText::computeAbsoluteRectForRange(int startPos, int endPos) { Q_UNUSED(startPos); Q_UNUSED(endPos); IntRect rect; RenderBlock *cb = containingBlock(); if (!cb || !cb->container()) { return rect; } RenderSVGRoot *root = findSVGRootObject(parent()); if (!root) { return rect; } /*FIXME khtml for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) rect.unite(box->selectionRect(0, 0, startPos, endPos));*/ // Mimic RenderBox::computeAbsoluteRepaintRect() functionality. But only the subset needed for SVG and respecting SVG transformations. int x, y; cb->container()->absolutePosition(x, y); // Remove HTML parent translation offsets here! These need to be retrieved from the RenderSVGRoot object. // But do take the containingBlocks's container position into account, ie. SVG text in scrollable
    . AffineTransform htmlParentCtm = root->RenderContainer::absoluteTransform(); FloatRect fixedRect(narrowPrecisionToFloat(rect.x() + x - xPos() - htmlParentCtm.e()), narrowPrecisionToFloat(rect.y() + y - yPos() - htmlParentCtm.f()), rect.width(), rect.height()); return enclosingIntRect(absoluteTransform().mapRect(fixedRect)); } InlineTextBox *RenderSVGInlineText::createInlineTextBox() { - // qDebug() << "allocate" << endl; + // qDebug() << "allocate"; return new(renderArena()) SVGInlineTextBox(this); } /*IntRect RenderSVGInlineText::caretRect(int offset, EAffinity affinity, int* extraWidthToEndOfLine) { // SVG doesn't have any editable content where a caret rect would be needed return IntRect(); }*/ /*VisiblePosition RenderSVGInlineText::positionForCoordinates(int x, int y) { SVGInlineTextBox* textBox = static_cast(firstTextBox()); if (!textBox || textLength() == 0) return VisiblePosition(element(), 0, DOWNSTREAM); SVGRootInlineBox* rootBox = textBox->svgRootInlineBox(); RenderObject* object = rootBox ? rootBox->object() : 0; if (!object) return VisiblePosition(element(), 0, DOWNSTREAM); int offset = 0; for (SVGInlineTextBox* box = textBox; box; box = static_cast(box->nextTextBox())) { if (box->svgCharacterHitsPosition(x + object->xPos(), y + object->yPos(), offset)) { // If we're not at the end/start of the box, stop looking for other selected boxes. if (!box->m_reversed) { if (offset <= (int) box->end() + 1) break; } else { if (offset > (int) box->start()) break; } } } return VisiblePosition(element(), offset, DOWNSTREAM); }*/ } #endif // ENABLE(SVG) diff --git a/src/rendering/RenderSVGRoot.cpp b/src/rendering/RenderSVGRoot.cpp index 7acadb9..fcbc1ea 100644 --- a/src/rendering/RenderSVGRoot.cpp +++ b/src/rendering/RenderSVGRoot.cpp @@ -1,376 +1,376 @@ /* Copyright (C) 2004, 2005, 2007 Nikolas Zimmermann 2004, 2005, 2007 Rob Buis 2007 Eric Seidel This file is part of the KDE project This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License aint with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "wtf/Platform.h" #include "Document.h" #if ENABLE(SVG) #include "RenderSVGRoot.h" /*#include "GraphicsContext.h" #include "RenderPath.h" #include "RenderSVGContainer.h" #include "RenderView.h"*/ #include "SVGLength.h" /*#include "SVGRenderSupport.h" #include "SVGResourceClipper.h" #include "SVGResourceFilter.h" #include "SVGResourceMasker.h"*/ #include "SVGSVGElement.h" #include "SVGStyledElement.h" /*#include "SVGURIReference.h"*/ namespace WebCore { RenderSVGRoot::RenderSVGRoot(SVGStyledElement *node) : RenderBox(node) /*RenderContainer(node)*/ { setReplaced(true); } RenderSVGRoot::~RenderSVGRoot() { } short RenderSVGRoot::lineHeight(bool b) const { Q_UNUSED(b); return height() + marginTop() + marginBottom(); } short RenderSVGRoot::baselinePosition(bool b) const { Q_UNUSED(b); return height() + marginTop() + marginBottom(); } void RenderSVGRoot::calcMinMaxWidth() { // KHTMLAssert( !minMaxKnown() ); int width = calcReplacedWidth() + paddingLeft() + paddingRight() + borderLeft() + borderRight(); if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent())) { m_minWidth = 0; m_maxWidth = width; } else { m_minWidth = m_maxWidth = width; } setMinMaxKnown(); } void RenderSVGRoot::layout() { ASSERT(needsLayout()); calcViewport(); // Arbitrary affine transforms are incompatible with LayoutState. //FIXME vtokarev: view()->disableLayoutState(); //canvas()->disableLayoutState(); /*IntRect oldBounds = m_absoluteBounds; IntRect oldOutlineBox; bool checkForRepaint = checkForRepaintDuringLayout() && selfNeedsLayout(); if (checkForRepaint) oldOutlineBox = absoluteOutlineBox();*/ calcWidth(); calcHeight(); //m_absoluteBounds = absoluteClippedOverflowRect(); SVGSVGElement *svg = static_cast(element()); m_width = static_cast(m_width * svg->currentScale()); m_height = static_cast(m_height * svg->currentScale()); for (RenderObject *child = firstChild(); child; child = child->nextSibling()) { // ### TODO: we only want to relayout if our size/transform changed kids if (child->isText()) { continue; } child->setNeedsLayout(true); child->layoutIfNeeded(); // ASSERT(!child->needsLayout()); } /*if (checkForRepaint) repaintAfterLayoutIfNeeded(oldBounds, oldOutlineBox);*/ //FIXME vtokarev view()->enableLayoutState(); //canvas()->enableLayoutState(); setNeedsLayout(false); } /*void RenderSVGRoot::applyContentTransforms(PaintInfo& paintInfo, int parentX, int parentY) { // Translate from parent offsets (html renderers) to a relative transform (svg renderers) IntPoint origin; origin.move(parentX, parentY); origin.move(m_x, m_y); origin.move(borderLeft(), borderTop()); origin.move(paddingLeft(), paddingTop()); if (origin.x() || origin.y()) { paintInfo.context->concatCTM(AffineTransform().translate(origin.x(), origin.y())); paintInfo.rect.move(-origin.x(), -origin.y()); } // Respect scroll offset caused by html parents AffineTransform ctm = RenderContainer::absoluteTransform(); paintInfo.rect.move(static_cast(ctm.e()), static_cast(ctm.f())); SVGSVGElement* svg = static_cast(element()); paintInfo.context->concatCTM(AffineTransform().scale(svg->currentScale())); if (!viewport().isEmpty()) { if (style()->overflowX() != OVISIBLE) paintInfo.context->clip(enclosingIntRect(viewport())); // FIXME: Eventually we'll want float-precision clipping paintInfo.context->concatCTM(AffineTransform().translate(viewport().x(), viewport().y())); } paintInfo.context->concatCTM(AffineTransform().translate(svg->currentTranslate().x(), svg->currentTranslate().y())); }*/ void RenderSVGRoot::paint(PaintInfo &paintInfo, int parentX, int parentY) { calcViewport(); //SVGSVGElement* svg = static_cast(element()); if (viewport().width() <= 0. || viewport().height() <= 0.) { return; } if (shouldPaintBackgroundOrBorder()) { paintBoxDecorations(paintInfo, m_x + parentX, m_y + parentY); } RenderObject::PaintInfo childPaintInfo(paintInfo); childPaintInfo.p->save(); childPaintInfo.p->setRenderHint(QPainter::Antialiasing); childPaintInfo.p->setRenderHint(QPainter::SmoothPixmapTransform); RenderBox::paint(childPaintInfo, 0, 0); childPaintInfo.p->restore(); #if 0 - // qDebug() << "in paint()" << endl; + // qDebug() << "in paint()"; /*if (!paintInfo.context) { GraphicsContext context(paintInfo.p); paintInfo.context = &context; - // qDebug() << "context:" << &context << endl; + // qDebug() << "context:" << &context; }*/ if (paintInfo.context->paintingDisabled()) { return; } calcViewport(); SVGSVGElement *svg = static_cast(element()); // A value of zero disables rendering of the element. - // qDebug() << "viewport:" << viewport().width() << viewport().height() << endl; + // qDebug() << "viewport:" << viewport().width() << viewport().height(); if (viewport().width() <= 0. || viewport().height() <= 0.) { return; } - // qDebug() << "painting:" << parentX << parentY << viewport().width() << viewport().height() << endl; + // qDebug() << "painting:" << parentX << parentY << viewport().width() << viewport().height(); // This should only exist for renderers /*if (hasBoxDecorations() && (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection))*/ paintBoxDecorations(paintInfo, m_x + parentX, m_y + parentY); /*if (!firstChild()) { #if ENABLE(SVG_FILTERS) // Spec: groups w/o children still may render filter content. const SVGRenderStyle* svgStyle = style()->svgStyle(); AtomicString filterId(SVGURIReference::getTarget(svgStyle->filter())); SVGResourceFilter* filter = getFilterById(document(), filterId); if (!filter) #endif return; }*/ paintInfo.p->fillRect(parentX, parentY, viewport().width(), viewport().height(), QBrush(Qt::green)); RenderObject::PaintInfo childPaintInfo(paintInfo); childPaintInfo.context->save(); - // qDebug() << "context= " << childPaintInfo.context << "parent=" << paintInfo.context << endl; + // qDebug() << "context= " << childPaintInfo.context << "parent=" << paintInfo.context; /*applyContentTransforms(childPaintInfo, parentX, parentY); SVGResourceFilter* filter = 0; FloatRect boundingBox = relativeBBox(true); if (childPaintInfo.phase == PaintPhaseForeground) prepareToRenderSVGContent(this, childPaintInfo, boundingBox, filter);*/ //childPaintInfo.context->concatCTM(svg->viewBoxToViewTransform(width(), height())); /*RenderContainer*/RenderBox::paint(childPaintInfo, 0, 0); /*if (childPaintInfo.phase == PaintPhaseForeground) finishRenderSVGContent(this, childPaintInfo, boundingBox, filter, paintInfo.context);*/ childPaintInfo.context->restore(); /*if ((childPaintInfo.phase == PaintPhaseOutline || childPaintInfo.phase == PaintPhaseSelfOutline) && style()->outlineWidth() && style()->visibility() == VISIBLE) paintOutline(childPaintInfo.context, m_absoluteBounds.x(), m_absoluteBounds.y(), m_absoluteBounds.width(), m_absoluteBounds.height(), style());*/ #endif } FloatRect RenderSVGRoot::viewport() const { return m_viewport; } void RenderSVGRoot::calcViewport() { SVGElement *svgelem = static_cast(element()); if (svgelem->hasTagName(SVGNames::svgTag)) { SVGSVGElement *svg = static_cast(element()); if (!selfNeedsLayout() && !svg->hasRelativeValues()) { return; } float w, h; SVGLength width = svg->width(); if (width.unitType() == LengthTypePercentage && svg->hasSetContainerSize()) { w = svg->relativeWidthValue(); } else { w = width.value(); } SVGLength height = svg->height(); if (height.unitType() == LengthTypePercentage && svg->hasSetContainerSize()) { h = svg->relativeHeightValue(); } else { h = height.value(); } m_viewport = FloatRect(0, 0, w, h); } } IntRect RenderSVGRoot::absoluteClippedOverflowRect() { /*IntRect repaintRect; for (RenderObject* current = firstChild(); current != 0; current = current->nextSibling()) repaintRect.unite(current->absoluteClippedOverflowRect()); #if ENABLE(SVG_FILTERS) // Filters can expand the bounding box SVGResourceFilter* filter = getFilterById(document(), SVGURIReference::getTarget(style()->svgStyle()->filter())); if (filter) repaintRect.unite(enclosingIntRect(filter->filterBBoxForItemBBox(repaintRect))); #endif return repaintRect;*/ ASSERT(false); return IntRect(); } /*void RenderSVGRoot::addFocusRingRects(GraphicsContext* graphicsContext, int, int) { graphicsContext->addFocusRingRect(m_absoluteBounds); }*/ void RenderSVGRoot::absoluteRects(Vector &rects, int, int) { Q_UNUSED(rects); /*for (RenderObject* current = firstChild(); current != 0; current = current->nextSibling()) current->absoluteRects(rects, 0, 0);*/ } AffineTransform RenderSVGRoot::absoluteTransform() const { AffineTransform ctm = RenderContainer::absoluteTransform(); ctm.translate(m_x, m_y); SVGSVGElement *svg = static_cast(element()); ctm.scale(svg->currentScale()); ctm.translate(svg->currentTranslate().x(), svg->currentTranslate().y()); ctm.translate(viewport().x(), viewport().y()); return svg->viewBoxToViewTransform(width(), height()) * ctm; } FloatRect RenderSVGRoot::relativeBBox(bool includeStroke) const { Q_UNUSED(includeStroke); FloatRect rect; //RenderObject* current = firstChild(); /*for (; current != 0; current = current->nextSibling()) { FloatRect childBBox = current->relativeBBox(includeStroke); FloatRect mappedBBox = current->localTransform().mapRect(childBBox); // can have a viewBox contributing to the bbox if (current->isSVGContainer()) mappedBBox = static_cast(current)->viewportTransform().mapRect(mappedBBox); rect.unite(mappedBBox); }*/ return rect; } AffineTransform RenderSVGRoot::localTransform() const { return AffineTransform(); } /*bool RenderSVGRoot::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int _x, int _y, int _tx, int _ty, HitTestAction hitTestAction) { AffineTransform ctm = RenderContainer::absoluteTransform(); int sx = (_tx - static_cast(ctm.e())); // scroll offset int sy = (_ty - static_cast(ctm.f())); // scroll offset if (!viewport().isEmpty() && style()->overflowX() == OHIDDEN && style()->overflowY() == OHIDDEN) { int tx = m_x - _tx + sx; int ty = m_y - _ty + sy; // Check if we need to do anything at all. IntRect overflowBox = overflowRect(false); overflowBox.move(tx, ty); ctm.translate(viewport().x(), viewport().y()); double localX, localY; ctm.inverse().map(_x - _tx, _y - _ty, &localX, &localY); if (!overflowBox.contains((int)localX, (int)localY)) return false; } for (RenderObject* child = lastChild(); child; child = child->previousSibling()) { if (child->nodeAtPoint(request, result, _x - sx, _y - sy, 0, 0, hitTestAction)) { updateHitTestResult(result, IntPoint(_x - _tx, _y - _ty)); return true; } } // Spec: Only graphical elements can be targeted by the mouse, period. // 16.4: "If there are no graphics elements whose relevant graphics content is under the pointer (i.e., there is no target element), the event is not dispatched." return false; }*/ } #endif // ENABLE(SVG) diff --git a/src/rendering/RenderSVGText.cpp b/src/rendering/RenderSVGText.cpp index 1385342..0f96b12 100644 --- a/src/rendering/RenderSVGText.cpp +++ b/src/rendering/RenderSVGText.cpp @@ -1,223 +1,223 @@ /* * This file is part of the WebKit project. * * Copyright (C) 2006 Apple Computer, Inc. * 2006 Alexander Kellett * 2006 Oliver Hunt * 2007 Nikolas Zimmermann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #include "wtf/Platform.h" #if ENABLE(SVG) #include "RenderSVGText.h" #include "FloatConversion.h" /*#include "GraphicsContext.h" #include "PointerEventsHitRules.h"*/ #include "RenderSVGRoot.h" /*#include "SimpleFontData.h"*/ #include "SVGLengthList.h" #include "SVGResourceFilter.h" #include "SVGRootInlineBox.h" #include "SVGTextElement.h" #include "SVGTransformList.h" #include "SVGURIReference.h" namespace WebCore { RenderSVGText::RenderSVGText(SVGTextElement *node) : RenderSVGBlock(node) { } IntRect RenderSVGText::absoluteClippedOverflowRect() { FloatRect repaintRect = absoluteTransform().mapRect(relativeBBox(true)); #if ENABLE(SVG_FILTERS) // Filters can expand the bounding box SVGResourceFilter *filter = getFilterById(document(), SVGURIReference::getTarget(style()->svgStyle()->filter())); if (filter) { repaintRect.unite(filter->filterBBoxForItemBBox(repaintRect)); } #endif if (!repaintRect.isEmpty()) { repaintRect.inflate(1); // inflate 1 pixel for antialiasing } return enclosingIntRect(repaintRect); } bool RenderSVGText::requiresLayer() const { return false; } bool RenderSVGText::calculateLocalTransform() { AffineTransform oldTransform = m_localTransform; m_localTransform = static_cast(element())->animatedLocalTransform(); return (oldTransform != m_localTransform); } void RenderSVGText::layout() { ASSERT(needsLayout()); // FIXME: This is a hack to avoid the RenderBlock::layout() partial repainting code which is not (yet) SVG aware setNeedsLayout(true); IntRect oldBounds; IntRect oldOutlineBox; /*bool checkForRepaint = checkForRepaintDuringLayout(); if (checkForRepaint) { oldBounds = m_absoluteBounds; oldOutlineBox = absoluteOutlineBox(); }*/ // Best guess for a relative starting point SVGTextElement *text = static_cast(element()); int xOffset = (int)(text->x()->getFirst().value()); int yOffset = (int)(text->y()->getFirst().value()); setPos(xOffset, yOffset); calculateLocalTransform(); RenderBlock::layout(); m_absoluteBounds = absoluteClippedOverflowRect(); /*bool repainted = false; if (checkForRepaint) repainted = repaintAfterLayoutIfNeeded(oldBounds, oldOutlineBox);*/ setNeedsLayout(false); } InlineBox *RenderSVGText::createInlineBox(bool makePlaceHolderBox, bool isRootLineBox/*, bool isOnlyRun*/) { Q_UNUSED(makePlaceHolderBox); Q_UNUSED(isRootLineBox); - // qDebug() << "createInlineBox" << makePlaceHolderBox << isRootLineBox << endl; + // qDebug() << "createInlineBox" << makePlaceHolderBox << isRootLineBox; ASSERT(!isInlineFlow()); InlineFlowBox *flowBox = new(renderArena()) SVGRootInlineBox(this); if (!m_firstLineBox) { m_firstLineBox = m_lastLineBox = flowBox; } else { m_lastLineBox->setNextLineBox(flowBox); flowBox->setPreviousLineBox(m_lastLineBox); m_lastLineBox = flowBox; } return flowBox; } /*bool RenderSVGText::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int _x, int _y, int _tx, int _ty, HitTestAction hitTestAction) { PointerEventsHitRules hitRules(PointerEventsHitRules::SVG_TEXT_HITTESTING, style()->svgStyle()->pointerEvents()); bool isVisible = (style()->visibility() == VISIBLE); if (isVisible || !hitRules.requireVisible) { if ((hitRules.canHitStroke && (style()->svgStyle()->hasStroke() || !hitRules.requireStroke)) || (hitRules.canHitFill && (style()->svgStyle()->hasFill() || !hitRules.requireFill))) { AffineTransform totalTransform = absoluteTransform(); double localX, localY; totalTransform.inverse().map(_x, _y, &localX, &localY); FloatPoint hitPoint(_x, _y); return RenderBlock::nodeAtPoint(request, result, (int)localX, (int)localY, _tx, _ty, hitTestAction); } } return false; }*/ void RenderSVGText::absoluteRects(Vector &rects, int, int, bool) { RenderSVGRoot *root = findSVGRootObject(parent()); if (!root) { return; } int x, y; absolutePosition(x, y); AffineTransform htmlParentCtm = root->RenderContainer::absoluteTransform(); // Don't use relativeBBox here, as it's unites the selection rects. Makes it hard // to spot errors, if there are any using WebInspector. Individually feed them into 'rects'. for (InlineRunBox *runBox = firstLineBox(); runBox; runBox = runBox->nextLineBox()) { ASSERT(runBox->isInlineFlowBox()); InlineFlowBox *flowBox = static_cast(runBox); for (InlineBox *box = flowBox->firstChild(); box; box = box->nextOnLine()) { FloatRect boxRect(box->xPos(), box->yPos(), box->width(), box->height()); boxRect.move(narrowPrecisionToFloat(x - htmlParentCtm.e()), narrowPrecisionToFloat(y - htmlParentCtm.f())); rects.append(enclosingIntRect(absoluteTransform().mapRect(boxRect))); } } } void RenderSVGText::paint(PaintInfo &paintInfo, int, int) { RenderObject::PaintInfo pi(paintInfo); //FIXME khtml pi.rect = absoluteTransform().inverse().mapRect(pi.rect); RenderBlock::paint(pi, 0, 0); } FloatRect RenderSVGText::relativeBBox(bool includeStroke) const { FloatRect repaintRect; for (InlineRunBox *runBox = firstLineBox(); runBox; runBox = runBox->nextLineBox()) { ASSERT(runBox->isInlineFlowBox()); InlineFlowBox *flowBox = static_cast(runBox); for (InlineBox *box = flowBox->firstChild(); box; box = box->nextOnLine()) { repaintRect.unite(FloatRect(box->xPos(), box->yPos(), box->width(), box->height())); } } // SVG needs to include the strokeWidth(), not the textStrokeWidth(). if (includeStroke && style()->svgStyle()->hasStroke()) { float strokeWidth = SVGRenderStyle::cssPrimitiveToLength(this, style()->svgStyle()->strokeWidth(), 0.0f); #if ENABLE(SVG_FONTS) /*FIXME khtml const Font& font = style()->font(); if (font.primaryFont()->isSVGFont()) { float scale = font.unitsPerEm() > 0 ? font.size() / font.unitsPerEm() : 0.0f; if (scale != 0.0f) strokeWidth /= scale; }*/ #endif repaintRect.inflate(strokeWidth); } repaintRect.move(xPos(), yPos()); return repaintRect; } } #endif // ENABLE(SVG) diff --git a/src/rendering/SVGInlineTextBox.cpp b/src/rendering/SVGInlineTextBox.cpp index 8f44529..1941616 100644 --- a/src/rendering/SVGInlineTextBox.cpp +++ b/src/rendering/SVGInlineTextBox.cpp @@ -1,613 +1,613 @@ /** * This file is part of the DOM implementation for KDE. * * Copyright (C) 2007 Rob Buis * (C) 2007 Nikolas Zimmermann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #include "wtf/Platform.h" #if ENABLE(SVG) #include "SVGInlineTextBox.h" #include "Document.h" /*#include "Editor.h" #include "Frame.h" #include "GraphicsContext.h" #include "InlineFlowBox.h" #include "Range.h"*/ #include "SVGPaintServer.h" #include "SVGRootInlineBox.h" /*#include "Text.h"*/ #include "render_line.h" #include #include using std::max; namespace WebCore { SVGInlineTextBox::SVGInlineTextBox(RenderObject *obj) : InlineTextBox(obj) { } int SVGInlineTextBox::selectionTop() { return m_y; } int SVGInlineTextBox::selectionHeight() { return m_height; } SVGRootInlineBox *SVGInlineTextBox::svgRootInlineBox() const { - // qDebug() << "find inline box" << endl; + // qDebug() << "find inline box"; // Find associated root inline box InlineFlowBox *parentBox = parent(); while (parentBox && !parentBox->isRootInlineBox()) { parentBox = parentBox->parent(); } ASSERT(parentBox); ASSERT(parentBox->isRootInlineBox()); if (!parentBox->isSVGRootInlineBox()) { return nullptr; } return static_cast(parentBox); } float SVGInlineTextBox::calculateGlyphWidth(RenderStyle *style, int offset, int extraCharsAvailable, int &charsConsumed, String &glyphName) const { ASSERT(style); return style->htmlFont().floatWidth(renderText()->text(), offset, 1, extraCharsAvailable, charsConsumed, glyphName); //return style->htmlFont().floatWidth(svgTextRunForInlineTextBox(renderText()->text() + offset, 1, style, this, 0), extraCharsAvailable, charsConsumed, glyphName); } float SVGInlineTextBox::calculateGlyphHeight(RenderStyle *style, int offset, int extraCharsAvailable) const { Q_UNUSED(offset); Q_UNUSED(extraCharsAvailable); ASSERT(style); // This is just a guess, and the only purpose of this function is to centralize this hack. // In real-life top-top-bottom scripts this won't be enough, I fear. return style->htmlFont().ascent() + style->htmlFont().descent(); } FloatRect SVGInlineTextBox::calculateGlyphBoundaries(RenderStyle *style, int offset, const SVGChar &svgChar) const { const Font &font = style->htmlFont(); // Take RTL text into account and pick right glyph width/height. float glyphWidth = 0.0f; // FIXME: account for multi-character glyphs int charsConsumed; String glyphName; if (!m_reversed) { glyphWidth = calculateGlyphWidth(style, offset, 0, charsConsumed, glyphName); } else { glyphWidth = calculateGlyphWidth(style, start() + end() - offset, 0, charsConsumed, glyphName); } float x1 = svgChar.x; float x2 = svgChar.x + glyphWidth; float y1 = svgChar.y - font.ascent(); float y2 = svgChar.y + font.descent(); FloatRect glyphRect(x1, y1, x2 - x1, y2 - y1); // Take per-character transformations into account AffineTransform ctm = svgChar.characterTransform(); if (!ctm.isIdentity()) { glyphRect = ctm.mapRect(glyphRect); } return glyphRect; } // Helper class for closestCharacterToPosition() struct SVGInlineTextBoxClosestCharacterToPositionWalker { SVGInlineTextBoxClosestCharacterToPositionWalker(int x, int y) : m_character(nullptr) , m_distance(FLT_MAX) , m_x(x) , m_y(y) , m_offset(0) { } void chunkPortionCallback(SVGInlineTextBox *textBox, int startOffset, const AffineTransform &chunkCtm, const Vector::iterator &start, const Vector::iterator &end) { RenderStyle *style = textBox->renderText()->style(); Vector::iterator closestCharacter = nullptr; unsigned int closestOffset = UINT_MAX; for (Vector::iterator it = start; it != end; ++it) { if (it->isHidden()) { continue; } unsigned int newOffset = textBox->start() + (it - start) + startOffset; FloatRect glyphRect = chunkCtm.mapRect(textBox->calculateGlyphBoundaries(style, newOffset, *it)); // Take RTL text into account and pick right glyph width/height. // NOTE: This offset has to be corrected _after_ calling calculateGlyphBoundaries if (textBox->m_reversed) { newOffset = textBox->start() + textBox->end() - newOffset; } // Calculate distances relative to the glyph mid-point. I hope this is accurate enough. float xDistance = glyphRect.x() + glyphRect.width() / 2.0f - m_x; float yDistance = glyphRect.y() - glyphRect.height() / 2.0f - m_y; float newDistance = sqrtf(xDistance * xDistance + yDistance * yDistance); if (newDistance <= m_distance) { m_distance = newDistance; closestOffset = newOffset; closestCharacter = it; } } if (closestOffset != UINT_MAX) { // Record current chunk, if it contains the current closest character next to the mouse. m_character = closestCharacter; m_offset = closestOffset; } } SVGChar *character() const { return m_character; } int offset() const { if (!m_character) { return 0; } return m_offset; } private: Vector::iterator m_character; float m_distance; int m_x; int m_y; int m_offset; }; // Helper class for selectionRect() struct SVGInlineTextBoxSelectionRectWalker { SVGInlineTextBoxSelectionRectWalker() { } void chunkPortionCallback(SVGInlineTextBox *textBox, int startOffset, const AffineTransform &chunkCtm, const Vector::iterator &start, const Vector::iterator &end) { RenderStyle *style = textBox->renderText()->style(); for (Vector::iterator it = start; it != end; ++it) { if (it->isHidden()) { continue; } unsigned int newOffset = textBox->start() + (it - start) + startOffset; m_selectionRect.unite(textBox->calculateGlyphBoundaries(style, newOffset, *it)); } m_selectionRect = chunkCtm.mapRect(m_selectionRect); } FloatRect selectionRect() const { return m_selectionRect; } private: FloatRect m_selectionRect; }; SVGChar *SVGInlineTextBox::closestCharacterToPosition(int x, int y, int &offset) const { SVGRootInlineBox *rootBox = svgRootInlineBox(); if (!rootBox) { return nullptr; } SVGInlineTextBoxClosestCharacterToPositionWalker walkerCallback(x, y); SVGTextChunkWalker walker(&walkerCallback, &SVGInlineTextBoxClosestCharacterToPositionWalker::chunkPortionCallback); rootBox->walkTextChunks(&walker, this); offset = walkerCallback.offset(); return walkerCallback.character(); } bool SVGInlineTextBox::svgCharacterHitsPosition(int x, int y, int &offset) const { SVGChar *charAtPosPtr = closestCharacterToPosition(x, y, offset); if (!charAtPosPtr) { return false; } SVGChar &charAtPos = *charAtPosPtr; RenderStyle *style = renderText()->style(m_firstLine); FloatRect glyphRect = calculateGlyphBoundaries(style, offset, charAtPos); if (m_reversed) { offset++; } // FIXME: todo list // (#13910) This code does not handle bottom-to-top/top-to-bottom vertical text. // Check whether y position hits the current character if (y < charAtPos.y - glyphRect.height() || y > charAtPos.y) { return false; } // Check whether x position hits the current character if (x < charAtPos.x) { if (offset > 0 && !m_reversed) { return true; } else if (offset < (int) end() && m_reversed) { return true; } return false; } // If we are past the last glyph of this box, don't mark it as 'hit' anymore. if (x >= charAtPos.x + glyphRect.width() && offset == (int) end()) { return false; } // Snap to character at half of it's advance if (x >= charAtPos.x + glyphRect.width() / 2.0) { offset += m_reversed ? -1 : 1; } return true; } int SVGInlineTextBox::offsetForPosition(int x, bool includePartialGlyphs) const { Q_UNUSED(x); Q_UNUSED(includePartialGlyphs); // SVG doesn't use the offset <-> position selection system. ASSERT_NOT_REACHED(); return 0; } int SVGInlineTextBox::positionForOffset(int offset) const { Q_UNUSED(offset); // SVG doesn't use the offset <-> position selection system. ASSERT_NOT_REACHED(); return 0; } /*bool SVGInlineTextBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int x, int y, int tx, int ty) { ASSERT(!isLineBreak()); IntRect rect = selectionRect(0, 0, 0, len()); if (object()->style()->visibility() == VISIBLE && rect.contains(x, y)) { object()->updateHitTestResult(result, IntPoint(x - tx, y - ty)); return true; } return false; }*/ IntRect SVGInlineTextBox::selectionRect(int, int, int startPos, int endPos) { if (startPos >= endPos) { return IntRect(); } // TODO: Actually respect startPos/endPos - we're returning the _full_ selectionRect // here. This won't lead to visible bugs, but to extra work being done. Investigate. SVGRootInlineBox *rootBox = svgRootInlineBox(); if (!rootBox) { return IntRect(); } SVGInlineTextBoxSelectionRectWalker walkerCallback; SVGTextChunkWalker walker(&walkerCallback, &SVGInlineTextBoxSelectionRectWalker::chunkPortionCallback); rootBox->walkTextChunks(&walker, this); return enclosingIntRect(walkerCallback.selectionRect()); } void SVGInlineTextBox::paintCharacters(RenderObject::PaintInfo &paintInfo, int tx, int ty, const SVGChar &svgChar, const UChar *chars, int length, SVGPaintServer *activePaintServer) { Q_UNUSED(tx); Q_UNUSED(ty); Q_UNUSED(chars); Q_UNUSED(length); Q_UNUSED(activePaintServer); - // qDebug() << "paint character" << endl; + // qDebug() << "paint character"; /*FIXME khtml if (object()->style()->visibility() != VISIBLE || paintInfo.phase == PaintPhaseOutline) return; ASSERT(paintInfo.phase != PaintPhaseSelfOutline && paintInfo.phase != PaintPhaseChildOutlines);*/ RenderText *text = renderText(); ASSERT(text); // Determine whether or not we're selected. bool haveSelection = text->selectionState() != RenderObject::SelectionNone; if (!haveSelection && paintInfo.phase == PaintActionSelection) // When only painting the selection, don't bother to paint if there is none. { return; } // Determine whether or not we have a composition. /*bool containsComposition = text->document()->frame()->editor()->compositionNode() == text->node(); bool useCustomUnderlines = containsComposition && text->document()->frame()->editor()->compositionUsesCustomUnderlines();*/ // Set our font RenderStyle *styleToUse = text->style(m_firstLine); const Font *font = &styleToUse->htmlFont(); if (styleToUse->font() != paintInfo.p->font()) { paintInfo.p->setFont(styleToUse->font()); } AffineTransform ctm = svgChar.characterTransform(); if (!ctm.isIdentity()) { paintInfo.p->setWorldMatrix(ctm, true); } //paintInfo.context->concatCTM(ctm); // 1. Paint backgrounds behind text if needed. Examples of such backgrounds include selection // and marked text. if (true/*paintInfo.phase != PaintPhaseSelection && !isPrinting*/) { #if PLATFORM(MAC) // Custom highlighters go behind everything else. if (styleToUse->highlight() != nullAtom && !paintInfo.context->paintingDisabled()) { paintCustomHighlight(tx, ty, styleToUse->highlight()); } #endif /*FIXME khtml if (containsComposition && !useCustomUnderlines) paintCompositionBackground(paintInfo.context, tx, ty, styleToUse, font, text->document()->frame()->editor()->compositionStart(), text->document()->frame()->editor()->compositionEnd()); paintDocumentMarkers(paintInfo.context, tx, ty, styleToUse, font, true);*/ /*if (haveSelection && !useCustomUnderlines) { int boxStartOffset = chars - text->characters() - start(); //FIXME khtml paintSelection(boxStartOffset, svgChar, chars, length, paintInfo.context, styleToUse, font); }*/ } // Set a text shadow if we have one. // FIXME: Support multiple shadow effects. Need more from the CG API before // we can do this. //bool setShadow = false; if (styleToUse->textShadow()) { /*FIXME khtml paintInfo.context->setShadow(IntSize(styleToUse->textShadow()->x, styleToUse->textShadow()->y), styleToUse->textShadow()->blur, styleToUse->textShadow()->color); setShadow = true;*/ } IntPoint origin((int) svgChar.x, (int) svgChar.y); - // qDebug() << "origin: " << svgChar.x << svgChar.y << endl; + // qDebug() << "origin: " << svgChar.x << svgChar.y; //TextRun run = svgTextRunForInlineTextBox(chars, length, styleToUse, this, svgChar.x); #if ENABLE(SVG_FONTS) // SVG Fonts need access to the paint server used to draw the current text chunk. // They need to be able to call renderPath() on a SVGPaintServer object. //FIXME khtml run.setActivePaintServer(activePaintServer); #endif //FIXME khtml paintInfo.context->drawText(run, origin); // qDebug() << "font size:" << font->getFontDef().size; // qDebug() << "text:" << QString::fromRawData(renderText()->string()->s + m_start, m_len); font->drawText(paintInfo.p, svgChar.x, svgChar.y, renderText()->string()->s, renderText()->string()->l, m_start, m_len, m_toAdd, m_reversed ? Qt::RightToLeft : Qt::LeftToRight); if (true/*paintInfo.phase != PaintPhaseSelection*/) { //FIXME khtml paintDocumentMarkers(paintInfo.context, tx, ty, styleToUse, font, false); /*if (useCustomUnderlines) { const Vector& underlines = text->document()->frame()->editor()->customCompositionUnderlines(); size_t numUnderlines = underlines.size(); for (size_t index = 0; index < numUnderlines; ++index) { const CompositionUnderline& underline = underlines[index]; if (underline.endOffset <= start()) // underline is completely before this run. This might be an underline that sits // before the first run we draw, or underlines that were within runs we skipped // due to truncation. continue; if (underline.startOffset <= end()) { // underline intersects this run. Paint it. //FIXME khtml paintCompositionUnderline(paintInfo.context, tx, ty, underline); if (underline.endOffset > end() + 1) // underline also runs into the next run. Bail now, no more marker advancement. break; } else // underline is completely after this run, bail. A later run will paint it. break; } }*/ } /*if (setShadow) paintInfo.context->clearShadow();*/ if (!ctm.isIdentity()) { paintInfo.p->setWorldMatrix(ctm.inverse(), true); } //paintInfo.context->concatCTM(ctm.inverse()); } void SVGInlineTextBox::paintSelection(int boxStartOffset, const SVGChar &svgChar, const UChar *chars, int length, khtml::RenderObject::PaintInfo &p, RenderStyle *style, const Font *f) { Q_UNUSED(boxStartOffset); Q_UNUSED(svgChar); Q_UNUSED(chars); Q_UNUSED(length); Q_UNUSED(p); Q_UNUSED(style); Q_UNUSED(f); /*if (selectionState() == RenderObject::SelectionNone) return; int startPos, endPos; selectionStartEnd(startPos, endPos); if (startPos >= endPos) return; Color textColor = style->color(); Color color = object()->selectionBackgroundColor(); if (!color.isValid() || color.alpha() == 0) return; // If the text color ends up being the same as the selection background, invert the selection // background. This should basically never happen, since the selection has transparency. if (textColor == color) color = Color(0xff - color.red(), 0xff - color.green(), 0xff - color.blue()); // Map from text box positions and a given start offset to chunk positions // 'boxStartOffset' represents the beginning of the text chunk. if ((startPos > boxStartOffset && endPos > boxStartOffset + length) || boxStartOffset >= endPos) return; if (endPos > boxStartOffset + length) endPos = boxStartOffset + length; if (startPos < boxStartOffset) startPos = boxStartOffset; ASSERT(startPos >= boxStartOffset); ASSERT(endPos <= boxStartOffset + length); ASSERT(startPos < endPos); p->save(); int adjust = startPos >= boxStartOffset ? boxStartOffset : 0; p->drawHighlightForText(svgTextRunForInlineTextBox(textObject()->text()->characters() + start() + boxStartOffset, length, style, this, svgChar.x), IntPoint((int) svgChar.x, (int) svgChar.y - f->ascent()), f->ascent() + f->descent(), color, startPos - adjust, endPos - adjust); p->restore();*/ } static inline Path pathForDecoration(ETextDecoration decoration, RenderObject *object, float x, float y, float width) { Q_UNUSED(decoration); float thickness = SVGRenderStyle::cssPrimitiveToLength(object, object->style()->svgStyle()->strokeWidth(), 1.0f); const Font &font = object->style()->htmlFont(); thickness = max(thickness * powf(font.getFontDef().size, 2.0f) / font.unitsPerEm(), 1.0f); if (decoration == UNDERLINE) { y += thickness * 1.5f; // For compatibility with Batik/Opera } else if (decoration == OVERLINE) { y += thickness; } float halfThickness = thickness / 2.0f; return Path::createRectangle(FloatRect(x + halfThickness, y, width - 2.0f * halfThickness, thickness)); } void SVGInlineTextBox::paintDecoration(ETextDecoration decoration, khtml::RenderObject::PaintInfo &pI, int tx, int ty, int width, const SVGChar &svgChar, const SVGTextDecorationInfo &info) { Q_UNUSED(decoration); Q_UNUSED(pI); Q_UNUSED(tx); Q_UNUSED(ty); Q_UNUSED(width); Q_UNUSED(svgChar); Q_UNUSED(info); /*FIXME if (object()->style()->visibility() != VISIBLE) return; // This function does NOT accept combinated text decorations. It's meant to be invoked for just one. ASSERT(decoration == TDNONE || decoration == UNDERLINE || decoration == OVERLINE || decoration == LINE_THROUGH || decoration == BLINK); bool isFilled = info.fillServerMap.contains(decoration); bool isStroked = info.strokeServerMap.contains(decoration); if (!isFilled && !isStroked) return; if (decoration == UNDERLINE) ty += m_baseline; else if (decoration == LINE_THROUGH) ty += 2 * m_baseline / 3; context->save(); context->beginPath(); AffineTransform ctm = svgChar.characterTransform(); if (!ctm.isIdentity()) context->concatCTM(ctm); if (isFilled) { if (RenderObject* fillObject = info.fillServerMap.get(decoration)) { if (SVGPaintServer* fillPaintServer = SVGPaintServer::fillPaintServer(fillObject->style(), fillObject)) { context->addPath(pathForDecoration(decoration, fillObject, tx, ty, width)); fillPaintServer->draw(context, fillObject, ApplyToFillTargetType); } } } if (isStroked) { if (RenderObject* strokeObject = info.strokeServerMap.get(decoration)) { if (SVGPaintServer* strokePaintServer = SVGPaintServer::strokePaintServer(strokeObject->style(), strokeObject)) { context->addPath(pathForDecoration(decoration, strokeObject, tx, ty, width)); strokePaintServer->draw(context, strokeObject, ApplyToStrokeTargetType); } } } context->restore();*/ } } // namespace WebCore #endif diff --git a/src/rendering/SVGRootInlineBox.cpp b/src/rendering/SVGRootInlineBox.cpp index c92a0b4..9d7953e 100644 --- a/src/rendering/SVGRootInlineBox.cpp +++ b/src/rendering/SVGRootInlineBox.cpp @@ -1,1802 +1,1802 @@ /* * This file is part of the WebKit project. * * Copyright (C) 2006 Oliver Hunt * (C) 2006 Apple Computer Inc. * (C) 2007 Nikolas Zimmermann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #include "wtf/Platform.h" #if ENABLE(SVG) #include "SVGRootInlineBox.h" /*#include "Editor.h" #include "Frame.h" #include "GraphicsContext.h"*/ #include "RenderSVGRoot.h" #include "SVGInlineFlowBox.h" #include "SVGInlineTextBox.h" #include "SVGFontElement.h" #include "SVGPaintServer.h" #include "SVGRenderStyleDefs.h" #include "SVGRenderSupport.h" #include "SVGResourceFilter.h" #include "SVGTextPositioningElement.h" #include "SVGURIReference.h" //#include "Text.h" //#include "UnicodeRange.h" #include #include // Text chunk creation is complex and the whole process // can easily be traced by setting this variable > 0. #define DEBUG_CHUNK_BUILDING 0 namespace WebCore { static inline bool isVerticalWritingMode(const SVGRenderStyle *style) { return style->writingMode() == WM_TBRL || style->writingMode() == WM_TB; } static inline EAlignmentBaseline dominantBaselineToShift(bool isVerticalText, const RenderObject *text, const Font &font) { ASSERT(text); const SVGRenderStyle *style = text->style() ? text->style()->svgStyle() : nullptr; ASSERT(style); const SVGRenderStyle *parentStyle = text->parent() && text->parent()->style() ? text->parent()->style()->svgStyle() : nullptr; EDominantBaseline baseline = style->dominantBaseline(); if (baseline == DB_AUTO) { if (isVerticalText) { baseline = DB_CENTRAL; } else { baseline = DB_ALPHABETIC; } } switch (baseline) { case DB_USE_SCRIPT: // TODO: The dominant-baseline and the baseline-table components are set by // determining the predominant script of the character data content. return AB_ALPHABETIC; case DB_NO_CHANGE: { if (parentStyle) { return dominantBaselineToShift(isVerticalText, text->parent(), font); } ASSERT_NOT_REACHED(); return AB_AUTO; } case DB_RESET_SIZE: { if (parentStyle) { return dominantBaselineToShift(isVerticalText, text->parent(), font); } ASSERT_NOT_REACHED(); return AB_AUTO; } case DB_IDEOGRAPHIC: return AB_IDEOGRAPHIC; case DB_ALPHABETIC: return AB_ALPHABETIC; case DB_HANGING: return AB_HANGING; case DB_MATHEMATICAL: return AB_MATHEMATICAL; case DB_CENTRAL: return AB_CENTRAL; case DB_MIDDLE: return AB_MIDDLE; case DB_TEXT_AFTER_EDGE: return AB_TEXT_AFTER_EDGE; case DB_TEXT_BEFORE_EDGE: return AB_TEXT_BEFORE_EDGE; default: ASSERT_NOT_REACHED(); return AB_AUTO; } } static inline float alignmentBaselineToShift(bool isVerticalText, const RenderObject *text, const Font &font) { ASSERT(text); const SVGRenderStyle *style = text->style() ? text->style()->svgStyle() : nullptr; ASSERT(style); const SVGRenderStyle *parentStyle = text->parent() && text->parent()->style() ? text->parent()->style()->svgStyle() : nullptr; EAlignmentBaseline baseline = style->alignmentBaseline(); if (baseline == AB_AUTO) { if (parentStyle && style->dominantBaseline() == DB_AUTO) { baseline = dominantBaselineToShift(isVerticalText, text->parent(), font); } else { baseline = dominantBaselineToShift(isVerticalText, text, font); } ASSERT(baseline != AB_AUTO); } // Note: http://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling switch (baseline) { case AB_BASELINE: { if (parentStyle) { return dominantBaselineToShift(isVerticalText, text->parent(), font); } return 0.0f; } case AB_BEFORE_EDGE: case AB_TEXT_BEFORE_EDGE: return font.ascent(); case AB_MIDDLE: return font.xHeight() / 2.0f; case AB_CENTRAL: // Not needed, we're taking this into account already for vertical text! // return (font.ascent() - font.descent()) / 2.0f; return 0.0f; case AB_AFTER_EDGE: case AB_TEXT_AFTER_EDGE: case AB_IDEOGRAPHIC: return font.descent(); case AB_ALPHABETIC: return 0.0f; case AB_HANGING: return font.ascent() * 8.0f / 10.0f; case AB_MATHEMATICAL: return font.ascent() / 2.0f; default: ASSERT_NOT_REACHED(); return 0.0f; } } static inline float glyphOrientationToAngle(const SVGRenderStyle *svgStyle, bool isVerticalText, const UChar &character) { Q_UNUSED(character); switch (isVerticalText ? svgStyle->glyphOrientationVertical() : svgStyle->glyphOrientationHorizontal()) { case GO_AUTO: { // Spec: Fullwidth ideographic and fullwidth Latin text will be set with a glyph-orientation of 0-degrees. // Text which is not fullwidth will be set with a glyph-orientation of 90-degrees. /*FIXME: khtml porting; unsigned int unicodeRange = findCharUnicodeRange(character); if (unicodeRange == cRangeSetLatin || unicodeRange == cRangeArabic) return 90.0f;*/ return 0.0f; } case GO_90DEG: return 90.0f; case GO_180DEG: return 180.0f; case GO_270DEG: return 270.0f; case GO_0DEG: default: return 0.0f; } } static inline bool glyphOrientationIsMultiplyOf180Degrees(float orientationAngle) { return fabsf(fmodf(orientationAngle, 180.0f)) == 0.0f; } static inline float calculateGlyphAdvanceAndShiftRespectingOrientation(bool isVerticalText, float orientationAngle, float glyphWidth, float glyphHeight, const Font &font, SVGChar &svgChar, float &xOrientationShift, float &yOrientationShift) { bool orientationIsMultiplyOf180Degrees = glyphOrientationIsMultiplyOf180Degrees(orientationAngle); // The function is based on spec requirements: // // Spec: If the 'glyph-orientation-horizontal' results in an orientation angle that is not a multiple of // of 180 degrees, then the current text position is incremented according to the vertical metrics of the glyph. // // Spec: If if the 'glyph-orientation-vertical' results in an orientation angle that is not a multiple of // 180 degrees,then the current text position is incremented according to the horizontal metrics of the glyph. // vertical orientation handling if (isVerticalText) { if (orientationAngle == 0.0f) { xOrientationShift = -glyphWidth / 2.0f; yOrientationShift = font.ascent(); } else if (orientationAngle == 90.0f) { xOrientationShift = -glyphHeight; yOrientationShift = font.descent(); svgChar.orientationShiftY = -font.ascent(); } else if (orientationAngle == 270.0f) { xOrientationShift = glyphHeight; yOrientationShift = font.descent(); svgChar.orientationShiftX = -glyphWidth; svgChar.orientationShiftY = -font.ascent(); } else if (orientationAngle == 180.0f) { yOrientationShift = font.ascent(); svgChar.orientationShiftX = -glyphWidth / 2.0f; svgChar.orientationShiftY = font.ascent() - font.descent(); } // vertical advance calculation if (orientationAngle != 0.0f && !orientationIsMultiplyOf180Degrees) { return glyphWidth; } return glyphHeight; } // horizontal orientation handling if (orientationAngle == 90.0f) { xOrientationShift = glyphWidth / 2.0f; yOrientationShift = -font.descent(); svgChar.orientationShiftX = -glyphWidth / 2.0f - font.descent(); svgChar.orientationShiftY = font.descent(); } else if (orientationAngle == 270.0f) { xOrientationShift = -glyphWidth / 2.0f; yOrientationShift = -font.descent(); svgChar.orientationShiftX = -glyphWidth / 2.0f + font.descent(); svgChar.orientationShiftY = glyphHeight; } else if (orientationAngle == 180.0f) { xOrientationShift = glyphWidth / 2.0f; svgChar.orientationShiftX = -glyphWidth / 2.0f; svgChar.orientationShiftY = font.ascent() - font.descent(); } // horizontal advance calculation if (orientationAngle != 0.0f && !orientationIsMultiplyOf180Degrees) { return glyphHeight; } return glyphWidth; } static inline void startTextChunk(SVGTextChunkLayoutInfo &info) { info.chunk.boxes.clear(); info.chunk.boxes.append(SVGInlineBoxCharacterRange()); info.chunk.start = info.it; info.assignChunkProperties = true; } static inline void closeTextChunk(SVGTextChunkLayoutInfo &info) { ASSERT(!info.chunk.boxes.last().isOpen()); ASSERT(info.chunk.boxes.last().isClosed()); info.chunk.end = info.it; ASSERT(info.chunk.end >= info.chunk.start); info.svgTextChunks.append(info.chunk); } RenderSVGRoot *findSVGRootObject(RenderObject *start) { // Find associated root inline box while (start && !start->isSVGRoot()) { start = start->parent(); } ASSERT(start); ASSERT(start->isSVGRoot()); return static_cast(start); } static inline FloatPoint topLeftPositionOfCharacterRange(Vector &chars) { return topLeftPositionOfCharacterRange(chars.begin(), chars.end()); } FloatPoint topLeftPositionOfCharacterRange(Vector::iterator it, Vector::iterator end) { float lowX = FLT_MAX, lowY = FLT_MAX; for (; it != end; ++it) { if (it->isHidden()) { continue; } float x = (*it).x; float y = (*it).y; if (x < lowX) { lowX = x; } if (y < lowY) { lowY = y; } } return FloatPoint(lowX, lowY); } /* FIXME // Helper function static float calculateKerning(RenderObject* item) { Q_UNUSED(item); const Font& font = item->style()->font(); const SVGRenderStyle* svgStyle = item->style()->svgStyle(); float kerning = 0.0f; if (CSSPrimitiveValue* primitive = static_cast(svgStyle->kerning())) { kerning = primitive->getFloatValue(); if (primitive->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE && font.pixelSize() > 0) kerning = kerning / 100.0f * font.pixelSize(); } return kerning; } */ // Helper class for paint() struct SVGRootInlineBoxPaintWalker { SVGRootInlineBoxPaintWalker(SVGRootInlineBox *rootBox, SVGResourceFilter *rootFilter, RenderObject::PaintInfo paintInfo, int tx, int ty) : m_rootBox(rootBox) , m_chunkStarted(false) , m_paintInfo(paintInfo) , m_savedInfo(paintInfo) , m_boundingBox(tx + rootBox->xPos(), ty + rootBox->yPos(), rootBox->width(), rootBox->height()) , m_filter(nullptr) , m_rootFilter(rootFilter) , m_fillPaintServer(nullptr) , m_strokePaintServer(nullptr) , m_fillPaintServerObject(nullptr) , m_strokePaintServerObject(nullptr) , m_tx(tx) , m_ty(ty) { } ~SVGRootInlineBoxPaintWalker() { ASSERT(!m_filter); ASSERT(!m_fillPaintServer); ASSERT(!m_fillPaintServerObject); ASSERT(!m_strokePaintServer); ASSERT(!m_strokePaintServerObject); ASSERT(!m_chunkStarted); } void teardownFillPaintServer() { if (!m_fillPaintServer) { return; } m_fillPaintServer->teardown(m_paintInfo.p, nullptr, m_fillPaintServerObject, ApplyToFillTargetType, true); m_fillPaintServer = nullptr; m_fillPaintServerObject = nullptr; } void teardownStrokePaintServer() { if (!m_strokePaintServer) { return; } m_strokePaintServer->teardown(m_paintInfo.p, nullptr, m_strokePaintServerObject, ApplyToStrokeTargetType, true); m_strokePaintServer = nullptr; m_strokePaintServerObject = nullptr; } void chunkStartCallback(InlineBox *box) { ASSERT(!m_chunkStarted); m_chunkStarted = true; InlineFlowBox *flowBox = box->parent(); // Initialize text rendering RenderObject *object = flowBox->object(); ASSERT(object); m_savedInfo = m_paintInfo; //m_paintInfo.context->save(); if (!flowBox->isRootInlineBox()) { ;//FIXME m_paintInfo.context->concatCTM(m_rootBox->object()->localTransform()); } //m_paintInfo.context->concatCTM(object->localTransform()); m_paintInfo.p->setWorldMatrix(object->localTransform(), true); if (!flowBox->isRootInlineBox()) { prepareToRenderSVGContent(object, m_paintInfo, m_boundingBox, m_filter, m_rootFilter); // FIXME khtml m_paintInfo.rect = object->localTransform().inverse().mapRect(m_paintInfo.rect); } } void chunkEndCallback(InlineBox *box) { ASSERT(m_chunkStarted); m_chunkStarted = false; InlineFlowBox *flowBox = box->parent(); RenderObject *object = flowBox->object(); ASSERT(object); // Clean up last used paint server teardownFillPaintServer(); teardownStrokePaintServer(); // Finalize text rendering if (!flowBox->isRootInlineBox()) { //FIXME khtml finishRenderSVGContent(object, m_paintInfo, m_boundingBox, m_filter, m_savedInfo.context); m_filter = nullptr; } // Restore context & repaint rect //FIXME m_paintInfo.context->restore(); //FIXME m_paintInfo.rect = m_savedInfo.rect; } bool chunkSetupFillCallback(InlineBox *box) { InlineFlowBox *flowBox = box->parent(); // Setup fill paint server RenderObject *object = flowBox->object(); ASSERT(object); ASSERT(!m_strokePaintServer); teardownFillPaintServer(); m_fillPaintServer = SVGPaintServer::fillPaintServer(object->style(), object); if (m_fillPaintServer) { m_fillPaintServer->setup(m_paintInfo.p, nullptr, object, ApplyToFillTargetType, true); m_fillPaintServerObject = object; return true; } return false; } bool chunkSetupStrokeCallback(InlineBox *box) { InlineFlowBox *flowBox = box->parent(); // Setup stroke paint server RenderObject *object = flowBox->object(); ASSERT(object); // If we're both stroked & filled, teardown fill paint server before stroking. teardownFillPaintServer(); teardownStrokePaintServer(); m_strokePaintServer = SVGPaintServer::strokePaintServer(object->style(), object); if (m_strokePaintServer) { m_strokePaintServer->setup(m_paintInfo.p, nullptr, object, ApplyToStrokeTargetType, true); m_strokePaintServerObject = object; return true; } return false; } void chunkPortionCallback(SVGInlineTextBox *textBox, int startOffset, const AffineTransform &chunkCtm, const Vector::iterator &start, const Vector::iterator &end) { Q_UNUSED(chunkCtm); - //qDebug() << "text chunk rendering code here" << endl; + //qDebug() << "text chunk rendering code here"; RenderText *text = textBox->/*textObject()*/renderText(); ASSERT(text); RenderStyle *styleToUse = text->style(/*textBox->isFirstLineStyle()*/); ASSERT(styleToUse); startOffset += textBox->start(); int textDecorations = styleToUse->textDecorationsInEffect(); // khtml int textWidth = 0; IntPoint decorationOrigin; SVGTextDecorationInfo info; /*FIXME khtml if (!chunkCtm.isIdentity()) m_paintInfo.context->concatCTM(chunkCtm);*/ for (Vector::iterator it = start; it != end; ++it) { if (it->isHidden()) { continue; } // Determine how many characters - starting from the current - can be drawn at once. Vector::iterator itSearch = it + 1; while (itSearch != end) { if (itSearch->drawnSeperated || itSearch->isHidden()) { break; } itSearch++; } /*FIXME khtmlconst*/ UChar *stringStart = text->text() + startOffset + (it - start); unsigned int stringLength = itSearch - it; // Paint decorations, that have to be drawn before the text gets drawn if (textDecorations != TDNONE /*FIXME khtml && m_paintInfo.phase != PaintPhaseSelection*/) { //khtml textWidth = styleToUse->font().width(svgTextRunForInlineTextBox(stringStart, stringLength, styleToUse, textBox, (*it).x)); //FIXME khtml textWidth = styleToUse->htmlFont().width(stringStart, stringLength, 0, stringLength, false /*fast algo*/); decorationOrigin = IntPoint((int)(*it).x, (int)(*it).y - styleToUse->htmlFont().ascent()); info = m_rootBox->retrievePaintServersForTextDecoration(text); } /*if (textDecorations & UNDERLINE && textWidth != 0.0f) textBox->paintDecoration(UNDERLINE, m_paintInfo.context, decorationOrigin.x(), decorationOrigin.y(), textWidth, *it, info); if (textDecorations & OVERLINE && textWidth != 0.0f) textBox->paintDecoration(OVERLINE, m_paintInfo.context, decorationOrigin.x(), decorationOrigin.y(), textWidth, *it, info);*/ // Paint text SVGPaintServer *activePaintServer = m_fillPaintServer; if (!activePaintServer) { activePaintServer = m_strokePaintServer; } ASSERT(activePaintServer); textBox->paintCharacters(m_paintInfo, m_tx, m_ty, *it, stringStart, stringLength, activePaintServer); // Paint decorations, that have to be drawn afterwards /* FIXME khtml if (textDecorations & LINE_THROUGH && textWidth != 0.0f) textBox->paintDecoration(LINE_THROUGH, m_paintInfo.context, decorationOrigin.x(), decorationOrigin.y(), textWidth, *it, info);*/ // Skip processed characters it = itSearch - 1; } /* FIXME khtml if (!chunkCtm.isIdentity()) m_paintInfo.context->concatCTM(chunkCtm.inverse());*/ } private: SVGRootInlineBox *m_rootBox; bool m_chunkStarted : 1; RenderObject::PaintInfo m_paintInfo; RenderObject::PaintInfo m_savedInfo; FloatRect m_boundingBox; SVGResourceFilter *m_filter; SVGResourceFilter *m_rootFilter; SVGPaintServer *m_fillPaintServer; SVGPaintServer *m_strokePaintServer; RenderObject *m_fillPaintServerObject; RenderObject *m_strokePaintServerObject; int m_tx; int m_ty; }; void SVGRootInlineBox::paint(RenderObject::PaintInfo &paintInfo, int tx, int ty) { //if (paintInfo.context->paintingDisabled() || paintInfo.phase != PaintPhaseForeground) // return; RenderObject::PaintInfo savedInfo(paintInfo); //paintInfo.context->save(); SVGResourceFilter *filter = nullptr; FloatRect boundingBox(tx + xPos(), ty + yPos(), width(), height()); // Initialize text rendering paintInfo.p->setWorldMatrix(object()->localTransform(), true); prepareToRenderSVGContent(object(), paintInfo, boundingBox, filter); paintInfo.p->setWorldMatrix(object()->localTransform().inverse(), true); - //qDebug() << "paint at" << tx << ty << endl; - //qDebug() << "pos: (" << (tx + xPos()) << "," << (ty + yPos()) << ")" << endl; - //qDebug() << "size: " << width() << "x" << height() << endl; + //qDebug() << "paint at" << tx << ty; + //qDebug() << "pos: (" << (tx + xPos()) << "," << (ty + yPos()) << ")"; + //qDebug() << "size: " << width() << "x" << height(); // Render text, chunk-by-chunk SVGRootInlineBoxPaintWalker walkerCallback(this, filter, paintInfo, tx, ty); SVGTextChunkWalker walker(&walkerCallback, &SVGRootInlineBoxPaintWalker::chunkPortionCallback, &SVGRootInlineBoxPaintWalker::chunkStartCallback, &SVGRootInlineBoxPaintWalker::chunkEndCallback, &SVGRootInlineBoxPaintWalker::chunkSetupFillCallback, &SVGRootInlineBoxPaintWalker::chunkSetupStrokeCallback); walkTextChunks(&walker); // Finalize text rendering //FIXME khtml finishRenderSVGContent(object(), paintInfo, boundingBox, filter, savedInfo.context); //paintInfo.context->restore(); } int SVGRootInlineBox::placeBoxesHorizontally(int, int &leftPosition, int &rightPosition, bool &) { // Remove any offsets caused by RTL text layout leftPosition = 0; rightPosition = 0; return 0; } void SVGRootInlineBox::verticallyAlignBoxes(int &heightOfBlock) { // height is set by layoutInlineBoxes. heightOfBlock = height(); } float cummulatedWidthOfInlineBoxCharacterRange(SVGInlineBoxCharacterRange &range) { ASSERT(!range.isOpen()); ASSERT(range.isClosed()); ASSERT(range.box->isInlineTextBox()); InlineTextBox *textBox = static_cast(range.box); RenderText *text = textBox->renderText(); RenderStyle *style = text->style(); return style->htmlFont().floatWidth(text->text(), textBox->start() + range.startOffset, range.endOffset - range.startOffset); } float cummulatedHeightOfInlineBoxCharacterRange(SVGInlineBoxCharacterRange &range) { ASSERT(!range.isOpen()); ASSERT(range.isClosed()); ASSERT(range.box->isInlineTextBox()); InlineTextBox *textBox = static_cast(range.box); RenderText *text = textBox->renderText(); const khtml::Font &font = text->style()->htmlFont(); return (range.endOffset - range.startOffset) * (font.ascent() + font.descent()); return 0; } /*TextRun svgTextRunForInlineTextBox(const UChar* c, int len, RenderStyle* style, const InlineTextBox* textBox, float xPos) { ASSERT(textBox); ASSERT(style); TextRun run(c, len, false, static_cast(xPos), textBox->toAdd(), textBox->m_reversed, textBox->m_dirOverride || style->visuallyOrdered()); #if ENABLE(SVG_FONTS) run.setReferencingRenderObject(textBox->textObject()->parent()); #endif // We handle letter & word spacing ourselves run.disableSpacing(); return run; }*/ static float cummulatedWidthOrHeightOfTextChunk(SVGTextChunk &chunk, bool calcWidthOnly) { float length = 0.0f; Vector::iterator charIt = chunk.start; Vector::iterator it = chunk.boxes.begin(); Vector::iterator end = chunk.boxes.end(); for (; it != end; ++it) { SVGInlineBoxCharacterRange &range = *it; // qDebug() << "box range:" << range.startOffset << range.endOffset; SVGInlineTextBox *box = static_cast(range.box); RenderStyle *style = box->object()->style(); for (int i = range.startOffset; i < range.endOffset; ++i) { ASSERT(charIt <= chunk.end); // Determine how many characters - starting from the current - can be measured at once. // Important for non-absolute positioned non-latin1 text (ie. Arabic) where ie. the width // of a string is not the sum of the boundaries of all contained glyphs. Vector::iterator itSearch = charIt + 1; Vector::iterator endSearch = charIt + range.endOffset - i; while (itSearch != endSearch) { // No need to check for 'isHidden()' here as this function is not called for text paths. if (itSearch->drawnSeperated) { break; } itSearch++; } unsigned int positionOffset = itSearch - charIt; // Calculate width/height of subrange SVGInlineBoxCharacterRange subRange; subRange.box = range.box; subRange.startOffset = i; subRange.endOffset = i + positionOffset; // qDebug() << "got subrange:" << subRange.startOffset << subRange.endOffset; if (calcWidthOnly) { length += cummulatedWidthOfInlineBoxCharacterRange(subRange); } else { length += cummulatedHeightOfInlineBoxCharacterRange(subRange); } // Calculate gap between the previous & current range // ABCD - we need to take the gaps between A & B into account // so add "40" as width, and analogous for B & C, add "20" as width. if (itSearch > chunk.start && itSearch < chunk.end) { SVGChar &lastCharacter = *(itSearch - 1); SVGChar ¤tCharacter = *itSearch; int offset = box->m_reversed ? box->end() - i - positionOffset + 1 : box->start() + i + positionOffset - 1; // FIXME: does this need to change to handle multichar glyphs? int charsConsumed = 1; String glyphName; if (calcWidthOnly) { float lastGlyphWidth = box->calculateGlyphWidth(style, offset, 0, charsConsumed, glyphName); length += currentCharacter.x - lastCharacter.x - lastGlyphWidth; } else { float lastGlyphHeight = box->calculateGlyphHeight(style, offset, 0); length += currentCharacter.y - lastCharacter.y - lastGlyphHeight; } } // Advance processed characters i += positionOffset - 1; charIt = itSearch; } } ASSERT(charIt == chunk.end); return length; } static float cummulatedWidthOfTextChunk(SVGTextChunk &chunk) { return cummulatedWidthOrHeightOfTextChunk(chunk, true); } static float cummulatedHeightOfTextChunk(SVGTextChunk &chunk) { return cummulatedWidthOrHeightOfTextChunk(chunk, false); } static float calculateTextAnchorShiftForTextChunk(SVGTextChunk &chunk, ETextAnchor anchor) { float shift = 0.0f; if (chunk.isVerticalText) { shift = cummulatedHeightOfTextChunk(chunk); } else { shift = cummulatedWidthOfTextChunk(chunk); } // qDebug() << anchor << shift << TA_MIDDLE; if (anchor == TA_MIDDLE) { shift *= -0.5f; } else { shift *= -1.0f; } return shift; } static void applyTextAnchorToTextChunk(SVGTextChunk &chunk) { // This method is not called for chunks containing chars aligned on a path. // -> all characters are visible, no need to check for "isHidden()" anywhere. if (chunk.anchor == TA_START) { return; } float shift = calculateTextAnchorShiftForTextChunk(chunk, chunk.anchor); // Apply correction to chunk Vector::iterator chunkIt = chunk.start; for (; chunkIt != chunk.end; ++chunkIt) { SVGChar &curChar = *chunkIt; if (chunk.isVerticalText) { curChar.y += shift; } else { curChar.x += shift; } } // Move inline boxes Vector::iterator boxIt = chunk.boxes.begin(); Vector::iterator boxEnd = chunk.boxes.end(); for (; boxIt != boxEnd; ++boxIt) { SVGInlineBoxCharacterRange &range = *boxIt; InlineBox *curBox = range.box; ASSERT(curBox->isInlineTextBox()); ASSERT(curBox->parent() && (curBox->parent()->isRootInlineBox() || curBox->parent()->isInlineFlowBox())); // Move target box if (chunk.isVerticalText) { curBox->setYPos(curBox->yPos() + static_cast(shift)); } else { curBox->setXPos(curBox->xPos() + static_cast(shift)); } } } static float calculateTextLengthCorrectionForTextChunk(SVGTextChunk &chunk, ELengthAdjust lengthAdjust, float &computedLength) { - //qDebug() << "text length" << endl; + //qDebug() << "text length"; if (chunk.textLength <= 0.0f) { return 0.0f; } float computedWidth = cummulatedWidthOfTextChunk(chunk); float computedHeight = cummulatedHeightOfTextChunk(chunk); if ((computedWidth <= 0.0f && !chunk.isVerticalText) || (computedHeight <= 0.0f && chunk.isVerticalText)) { return 0.0f; } if (chunk.isVerticalText) { computedLength = computedHeight; } else { computedLength = computedWidth; } if (lengthAdjust == SVGTextContentElement::LENGTHADJUST_SPACINGANDGLYPHS) { if (chunk.isVerticalText) { chunk.ctm.scale(1.0f, chunk.textLength / computedLength); } else { chunk.ctm.scale(chunk.textLength / computedLength, 1.0f); } return 0.0f; } return (chunk.textLength - computedLength) / float(chunk.end - chunk.start); } static void applyTextLengthCorrectionToTextChunk(SVGTextChunk &chunk) { // This method is not called for chunks containing chars aligned on a path. // -> all characters are visible, no need to check for "isHidden()" anywhere. // lengthAdjust="spacingAndGlyphs" is handled by modifying chunk.ctm float computedLength = 0.0f; float spacingToApply = calculateTextLengthCorrectionForTextChunk(chunk, chunk.lengthAdjust, computedLength); if (!chunk.ctm.isIdentity() && chunk.lengthAdjust == SVGTextContentElement::LENGTHADJUST_SPACINGANDGLYPHS) { SVGChar &firstChar = *(chunk.start); // Assure we apply the chunk scaling in the right origin AffineTransform newChunkCtm; newChunkCtm.translate(firstChar.x, firstChar.y); newChunkCtm = chunk.ctm * newChunkCtm; newChunkCtm.translate(-firstChar.x, -firstChar.y); chunk.ctm = newChunkCtm; } // Apply correction to chunk if (spacingToApply != 0.0f) { Vector::iterator chunkIt = chunk.start; for (; chunkIt != chunk.end; ++chunkIt) { SVGChar &curChar = *chunkIt; curChar.drawnSeperated = true; if (chunk.isVerticalText) { curChar.y += (chunkIt - chunk.start) * spacingToApply; } else { curChar.x += (chunkIt - chunk.start) * spacingToApply; } } } } void SVGRootInlineBox::computePerCharacterLayoutInformation() { - //qDebug() << "computePerCharacterLayoutInformation()" << endl; + //qDebug() << "computePerCharacterLayoutInformation()"; // Clean up any previous layout information m_svgChars.clear(); m_svgTextChunks.clear(); // Build layout information for all contained render objects SVGCharacterLayoutInfo info(m_svgChars); - //qDebug() << "before build layout info" << endl; + //qDebug() << "before build layout info"; buildLayoutInformation(this, info); - //qDebug() << "after build layout info" << endl; + //qDebug() << "after build layout info"; // Now all layout information are available for every character // contained in any of our child inline/flow boxes. Build list // of text chunks now, to be able to apply text-anchor shifts. buildTextChunks(m_svgChars, m_svgTextChunks, this); - //qDebug() << "after build text chunks" << endl; + //qDebug() << "after build text chunks"; // Layout all text chunks // text-anchor needs to be applied to individual chunks. layoutTextChunks(); - //qDebug() << "after layout text chunks" << endl; + //qDebug() << "after layout text chunks"; // Finally the top left position of our box is known. // Propagate this knownledge to our RenderSVGText parent. FloatPoint topLeft = topLeftPositionOfCharacterRange(m_svgChars); object()->setPos((int) floorf(topLeft.x()), (int) floorf(topLeft.y())); // Layout all InlineText/Flow boxes // BEWARE: This requires the root top/left position to be set correctly before! - //qDebug() << "before layout inline boxes" << endl; + //qDebug() << "before layout inline boxes"; layoutInlineBoxes(); - //qDebug() << "at the end" << endl; + //qDebug() << "at the end"; } void SVGRootInlineBox::buildLayoutInformation(InlineFlowBox *start, SVGCharacterLayoutInfo &info) { if (start->isRootInlineBox()) { ASSERT(start->object()->element()->hasTagName(SVGNames::textTag)); SVGTextPositioningElement *positioningElement = static_cast(start->object()->element()); ASSERT(positioningElement); ASSERT(positioningElement->parentNode()); info.addLayoutInformation(positioningElement); } LastGlyphInfo lastGlyph; for (InlineBox *curr = start->firstChild(); curr; curr = curr->nextOnLine()) { if (curr->object()->isText()) { buildLayoutInformationForTextBox(info, static_cast(curr), lastGlyph); } else { ASSERT(curr->isInlineFlowBox()); InlineFlowBox *flowBox = static_cast(curr); bool isAnchor = flowBox->object()->element()->hasTagName(SVGNames::aTag); bool isTextPath = flowBox->object()->element()->hasTagName(SVGNames::textPathTag); if (!isTextPath && !isAnchor) { SVGTextPositioningElement *positioningElement = static_cast(flowBox->object()->element()); ASSERT(positioningElement); ASSERT(positioningElement->parentNode()); info.addLayoutInformation(positioningElement); } else if (!isAnchor) { info.setInPathLayout(true); // Handle text-anchor/textLength on path, which is special. SVGTextContentElement *textContent = nullptr; Node *node = flowBox->object()->element(); if (node && node->isSVGElement()) { textContent = static_cast(node); } ASSERT(textContent); ELengthAdjust lengthAdjust = (ELengthAdjust) textContent->lengthAdjust(); ETextAnchor anchor = flowBox->object()->style()->svgStyle()->textAnchor(); float textAnchorStartOffset = 0.0f; // Initialize sub-layout. We need to create text chunks from the textPath // children using our standard layout code, to be able to measure the // text length using our normal methods and not textPath specific hacks. Vector tempChars; Vector tempChunks; SVGCharacterLayoutInfo tempInfo(tempChars); buildLayoutInformation(flowBox, tempInfo); buildTextChunks(tempChars, tempChunks, flowBox); Vector::iterator it = tempChunks.begin(); Vector::iterator end = tempChunks.end(); AffineTransform ctm; float computedLength = 0.0f; for (; it != end; ++it) { SVGTextChunk &chunk = *it; // Apply text-length calculation info.pathExtraAdvance += calculateTextLengthCorrectionForTextChunk(chunk, lengthAdjust, computedLength); if (lengthAdjust == SVGTextContentElement::LENGTHADJUST_SPACINGANDGLYPHS) { info.pathTextLength += computedLength; info.pathChunkLength += chunk.textLength; } // Calculate text-anchor start offset if (anchor == TA_START) { continue; } textAnchorStartOffset += calculateTextAnchorShiftForTextChunk(chunk, anchor); } info.addLayoutInformation(flowBox, textAnchorStartOffset); } float shiftxSaved = info.shiftx; float shiftySaved = info.shifty; buildLayoutInformation(flowBox, info); info.processedChunk(shiftxSaved, shiftySaved); if (isTextPath) { info.setInPathLayout(false); } } } } void SVGRootInlineBox::layoutInlineBoxes() { int lowX = INT_MAX; int lowY = INT_MAX; int highX = INT_MIN; int highY = INT_MIN; // Layout all child boxes Vector::iterator it = m_svgChars.begin(); layoutInlineBoxes(this, it, lowX, highX, lowY, highY); ASSERT(it == m_svgChars.end()); } void SVGRootInlineBox::layoutInlineBoxes(InlineFlowBox *start, Vector::iterator &it, int &lowX, int &highX, int &lowY, int &highY) { for (InlineBox *curr = start->firstChild(); curr; curr = curr->nextOnLine()) { RenderStyle *style = curr->object()->style(); const Font &font = style->htmlFont(); if (curr->object()->isText()) { SVGInlineTextBox *textBox = static_cast(curr); unsigned length = textBox->len(); SVGChar curChar = *it; ASSERT(it != m_svgChars.end()); FloatRect stringRect; for (unsigned i = 0; i < length; ++i) { ASSERT(it != m_svgChars.end()); if (it->isHidden()) { ++it; continue; } stringRect.unite(textBox->calculateGlyphBoundaries(style, textBox->start() + i, *it)); ++it; } IntRect enclosedStringRect = enclosingIntRect(stringRect); int minX = enclosedStringRect.x(); int maxX = minX + enclosedStringRect.width(); int minY = enclosedStringRect.y(); int maxY = minY + enclosedStringRect.height(); curr->setXPos(minX - object()->xPos()); curr->setWidth(enclosedStringRect.width()); curr->setYPos(minY - object()->yPos()); curr->setBaseline(font.ascent()); curr->setHeight(enclosedStringRect.height()); if (minX < lowX) { lowX = minX; } if (maxX > highX) { highX = maxX; } if (minY < lowY) { lowY = minY; } if (maxY > highY) { highY = maxY; } } else { ASSERT(curr->isInlineFlowBox()); int minX = INT_MAX; int minY = INT_MAX; int maxX = INT_MIN; int maxY = INT_MIN; InlineFlowBox *flowBox = static_cast(curr); layoutInlineBoxes(flowBox, it, minX, maxX, minY, maxY); curr->setXPos(minX - object()->xPos()); curr->setWidth(maxX - minX); curr->setYPos(minY - object()->yPos()); curr->setBaseline(font.ascent()); curr->setHeight(maxY - minY); if (minX < lowX) { lowX = minX; } if (maxX > highX) { highX = maxX; } if (minY < lowY) { lowY = minY; } if (maxY > highY) { highY = maxY; } } } if (start->isRootInlineBox()) { int top = lowY - object()->yPos(); //int bottom = highY - object()->yPos(); start->setXPos(lowX - object()->xPos()); start->setYPos(top); start->setWidth(highX - lowX); start->setHeight(highY - lowY); /*FIXME start->setVerticalOverflowPositions(top, bottom); start->setVerticalSelectionPositions(top, bottom);*/ } } void SVGRootInlineBox::buildLayoutInformationForTextBox(SVGCharacterLayoutInfo &info, InlineTextBox *textBox, LastGlyphInfo &lastGlyph) { Q_UNUSED(lastGlyph); RenderText *text = textBox->renderText(); ASSERT(text); RenderStyle *style = text->style(/*textBox->isFirstLineStyle()*/); ASSERT(style); const Font &font = style->htmlFont(); SVGInlineTextBox *svgTextBox = static_cast(textBox); unsigned length = textBox->len(); const SVGRenderStyle *svgStyle = style->svgStyle(); bool isVerticalText = isVerticalWritingMode(svgStyle); int charsConsumed = 0; for (unsigned i = 0; i < length; i += charsConsumed) { SVGChar svgChar; if (info.inPathLayout()) { svgChar.pathData = SVGCharOnPath::create(); } float glyphWidth = 0.0f; float glyphHeight = 0.0f; int extraCharsAvailable = length - i - 1; String unicodeStr; String glyphName; if (textBox->m_reversed) { glyphWidth = svgTextBox->calculateGlyphWidth(style, textBox->end() - i, extraCharsAvailable, charsConsumed, glyphName); glyphHeight = svgTextBox->calculateGlyphHeight(style, textBox->end() - i, extraCharsAvailable); unicodeStr = String(textBox->renderText()->text()/*->characters()*/ + textBox->end() - i, charsConsumed); } else { glyphWidth = svgTextBox->calculateGlyphWidth(style, textBox->start() + i, extraCharsAvailable, charsConsumed, glyphName); glyphHeight = svgTextBox->calculateGlyphHeight(style, textBox->start() + i, extraCharsAvailable); unicodeStr = String(textBox->renderText()->text()/*->characters()*/ + textBox->start() + i, charsConsumed); } bool assignedX = false; bool assignedY = false; if (info.xValueAvailable() && (!info.inPathLayout() || (info.inPathLayout() && !isVerticalText))) { if (!isVerticalText) { svgChar.newTextChunk = true; } assignedX = true; svgChar.drawnSeperated = true; info.curx = info.xValueNext(); } if (info.yValueAvailable() && (!info.inPathLayout() || (info.inPathLayout() && isVerticalText))) { if (isVerticalText) { svgChar.newTextChunk = true; } assignedY = true; svgChar.drawnSeperated = true; info.cury = info.yValueNext(); } float dx = 0.0f; float dy = 0.0f; // Apply x-axis shift if (info.dxValueAvailable()) { svgChar.drawnSeperated = true; dx = info.dxValueNext(); info.dx += dx; if (!info.inPathLayout()) { info.curx += dx; } } // Apply y-axis shift if (info.dyValueAvailable()) { svgChar.drawnSeperated = true; dy = info.dyValueNext(); info.dy += dy; if (!info.inPathLayout()) { info.cury += dy; } } // Take letter & word spacing and kerning into account float spacing = 0;//FIXME font.letterSpacing() + calculateKerning(textBox->object()->element()->renderer()); const UChar *currentCharacter = text->text()/*->characters()*/ + (textBox->m_reversed ? textBox->end() - i : textBox->start() + i); #if 0 // FIXME (see below) const UChar *lastCharacter = 0; if (textBox->m_reversed) { if (i < textBox->end()) { lastCharacter = text->text()/*characters()*/ + textBox->end() - i + 1; } } else { if (i > 0) { lastCharacter = text->text()/*characters()*/ + textBox->start() + i - 1; } } #endif if (info.nextDrawnSeperated || spacing != 0.0f) { info.nextDrawnSeperated = false; svgChar.drawnSeperated = true; } /*FIXME if (currentCharacter && Font::treatAsSpace(*currentCharacter) && lastCharacter && !Font::treatAsSpace(*lastCharacter)) { spacing += font.wordSpacing(); if (spacing != 0.0f && !info.inPathLayout()) info.nextDrawnSeperated = true; }*/ float orientationAngle = glyphOrientationToAngle(svgStyle, isVerticalText, *currentCharacter); float xOrientationShift = 0.0f; float yOrientationShift = 0.0f; float glyphAdvance = calculateGlyphAdvanceAndShiftRespectingOrientation(isVerticalText, orientationAngle, glyphWidth, glyphHeight, font, svgChar, xOrientationShift, yOrientationShift); // Handle textPath layout mode if (info.inPathLayout()) { float extraAdvance = isVerticalText ? dy : dx; float newOffset = FLT_MIN; if (assignedX && !isVerticalText) { newOffset = info.curx; } else if (assignedY && isVerticalText) { newOffset = info.cury; } float correctedGlyphAdvance = glyphAdvance; // Handle lengthAdjust="spacingAndGlyphs" by specifying per-character scale operations if (info.pathTextLength > 0.0f && info.pathChunkLength > 0.0f) { if (isVerticalText) { svgChar.pathData->yScale = info.pathChunkLength / info.pathTextLength; spacing *= svgChar.pathData->yScale; correctedGlyphAdvance *= svgChar.pathData->yScale; } else { svgChar.pathData->xScale = info.pathChunkLength / info.pathTextLength; spacing *= svgChar.pathData->xScale; correctedGlyphAdvance *= svgChar.pathData->xScale; } } // Handle letter & word spacing on text path float pathExtraAdvance = info.pathExtraAdvance; info.pathExtraAdvance += spacing; svgChar.pathData->hidden = !info.nextPathLayoutPointAndAngle(correctedGlyphAdvance, extraAdvance, newOffset); svgChar.drawnSeperated = true; info.pathExtraAdvance = pathExtraAdvance; } // Apply rotation if (info.angleValueAvailable()) { info.angle = info.angleValueNext(); } // Apply baseline-shift if (info.baselineShiftValueAvailable()) { svgChar.drawnSeperated = true; float shift = info.baselineShiftValueNext(); if (isVerticalText) { info.shiftx += shift; } else { info.shifty -= shift; } } // Take dominant-baseline / alignment-baseline into account yOrientationShift += alignmentBaselineToShift(isVerticalText, text, font); svgChar.x = info.curx; svgChar.y = info.cury; svgChar.angle = info.angle; // For text paths any shift (dx/dy/baseline-shift) has to be applied after the rotation if (!info.inPathLayout()) { svgChar.x += info.shiftx + xOrientationShift; svgChar.y += info.shifty + yOrientationShift; if (orientationAngle != 0.0f) { svgChar.angle += orientationAngle; } if (svgChar.angle != 0.0f) { svgChar.drawnSeperated = true; } } else { svgChar.pathData->orientationAngle = orientationAngle; if (isVerticalText) { svgChar.angle -= 90.0f; } svgChar.pathData->xShift = info.shiftx + xOrientationShift; svgChar.pathData->yShift = info.shifty + yOrientationShift; // Translate to glyph midpoint if (isVerticalText) { svgChar.pathData->xShift += info.dx; svgChar.pathData->yShift -= glyphAdvance / 2.0f; } else { svgChar.pathData->xShift -= glyphAdvance / 2.0f; svgChar.pathData->yShift += info.dy; } } double kerning = 0.0; #if ENABLE(SVG_FONTS) /*FIXME khtml SVGFontElement* svgFont = 0; if (style->font().isSVGFont()) svgFont = style->font().svgFont(); if (lastGlyph.isValid && style->font().isSVGFont()) { SVGHorizontalKerningPair kerningPair; if (svgFont->getHorizontalKerningPairForStringsAndGlyphs(lastGlyph.unicode, lastGlyph.glyphName, unicodeStr, glyphName, kerningPair)) kerning = kerningPair.kerning; } if (style->font().isSVGFont()) { lastGlyph.unicode = unicodeStr; lastGlyph.glyphName = glyphName; lastGlyph.isValid = true; } else lastGlyph.isValid = false;*/ #endif svgChar.x -= (float)kerning; // Advance to new position if (isVerticalText) { svgChar.drawnSeperated = true; info.cury += glyphAdvance + spacing; } else { info.curx += glyphAdvance + spacing - (float)kerning; } // Advance to next character group for (int k = 0; k < charsConsumed; ++k) { info.svgChars.append(svgChar); info.processedSingleCharacter(); svgChar.drawnSeperated = false; svgChar.newTextChunk = false; } } } void SVGRootInlineBox::buildTextChunks(Vector &svgChars, Vector &svgTextChunks, InlineFlowBox *start) { SVGTextChunkLayoutInfo info(svgTextChunks); info.it = svgChars.begin(); info.chunk.start = svgChars.begin(); info.chunk.end = svgChars.begin(); buildTextChunks(svgChars, start, info); ASSERT(info.it == svgChars.end()); } void SVGRootInlineBox::buildTextChunks(Vector &svgChars, InlineFlowBox *start, SVGTextChunkLayoutInfo &info) { #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " -> buildTextChunks(start=%p)\n", start); #endif for (InlineBox *curr = start->firstChild(); curr; curr = curr->nextOnLine()) { if (curr->object()->isText()) { InlineTextBox *textBox = static_cast(curr); unsigned length = textBox->len(); if (!length) { continue; } #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " -> Handle inline text box (%p) with %i characters (start: %i, end: %i), handlingTextPath=%i\n", textBox, length, textBox->start(), textBox->end(), (int) info.handlingTextPath); #endif RenderText *text = textBox->renderText(); ASSERT(text); ASSERT(text->element()); SVGTextContentElement *textContent = nullptr; Node *node = text->element()->parent(); if (node && node->isSVGElement()) { textContent = static_cast(node); } ASSERT(textContent); // Start new character range for the first chunk bool isFirstCharacter = info.svgTextChunks.isEmpty() && info.chunk.start == info.it && info.chunk.start == info.chunk.end; if (isFirstCharacter) { ASSERT(info.chunk.boxes.isEmpty()); info.chunk.boxes.append(SVGInlineBoxCharacterRange()); } else { ASSERT(!info.chunk.boxes.isEmpty()); } // Walk string to find out new chunk positions, if existent for (unsigned i = 0; i < length; ++i) { ASSERT(info.it != svgChars.end()); SVGInlineBoxCharacterRange &range = info.chunk.boxes.last(); if (range.isOpen()) { range.box = curr; range.startOffset = (i == 0 ? 0 : i - 1); #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " | -> Range is open! box=%p, startOffset=%i\n", range.box, range.startOffset); #endif } // If a new (or the first) chunk has been started, record it's text-anchor and writing mode. if (info.assignChunkProperties) { info.assignChunkProperties = false; info.chunk.isVerticalText = isVerticalWritingMode(text->style()->svgStyle()); info.chunk.isTextPath = info.handlingTextPath; info.chunk.anchor = text->style()->svgStyle()->textAnchor(); info.chunk.textLength = textContent->textLength().value(); info.chunk.lengthAdjust = (ELengthAdjust) textContent->lengthAdjust(); #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " | -> Assign chunk properties, isVerticalText=%i, anchor=%i\n", info.chunk.isVerticalText, info.chunk.anchor); #endif } if (i > 0 && !isFirstCharacter && (*info.it).newTextChunk) { // Close mid chunk & character range ASSERT(!range.isOpen()); ASSERT(!range.isClosed()); range.endOffset = i; closeTextChunk(info); #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " | -> Close mid-text chunk, at endOffset: %i and starting new mid chunk!\n", range.endOffset); #endif // Prepare for next chunk, if we're not at the end startTextChunk(info); if (i + 1 == length) { #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " | -> Record last chunk of inline text box!\n"); #endif startTextChunk(info); SVGInlineBoxCharacterRange &range = info.chunk.boxes.last(); info.assignChunkProperties = false; info.chunk.isVerticalText = isVerticalWritingMode(text->style()->svgStyle()); info.chunk.isTextPath = info.handlingTextPath; info.chunk.anchor = text->style()->svgStyle()->textAnchor(); info.chunk.textLength = textContent->textLength().value(); info.chunk.lengthAdjust = (ELengthAdjust) textContent->lengthAdjust(); range.box = curr; range.startOffset = i; ASSERT(!range.isOpen()); ASSERT(!range.isClosed()); } } // This should only hold true for the first character of the first chunk if (isFirstCharacter) { isFirstCharacter = false; } ++info.it; } #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " -> Finished inline text box!\n"); #endif SVGInlineBoxCharacterRange &range = info.chunk.boxes.last(); if (!range.isOpen() && !range.isClosed()) { #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " -> Last range not closed - closing with endOffset: %i\n", length); #endif // Current text chunk is not yet closed. Finish the current range, but don't start a new chunk. range.endOffset = length; if (info.it != svgChars.end()) { #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " -> Not at last character yet!\n"); #endif // If we're not at the end of the last box to be processed, and if the next // character starts a new chunk, then close the current chunk and start a new one. if ((*info.it).newTextChunk) { #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " -> Next character starts new chunk! Closing current chunk, and starting a new one...\n"); #endif closeTextChunk(info); startTextChunk(info); } else { // Just start a new character range info.chunk.boxes.append(SVGInlineBoxCharacterRange()); #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " -> Next character does NOT start a new chunk! Starting new character range...\n"); #endif } } else { #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " -> Closing final chunk! Finished processing!\n"); #endif // Close final chunk, once we're at the end of the last box closeTextChunk(info); } } } else { ASSERT(curr->isInlineFlowBox()); InlineFlowBox *flowBox = static_cast(curr); bool isTextPath = flowBox->object()->element()->hasTagName(SVGNames::textPathTag); #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " -> Handle inline flow box (%p), isTextPath=%i\n", flowBox, (int) isTextPath); #endif if (isTextPath) { info.handlingTextPath = true; } buildTextChunks(svgChars, flowBox, info); if (isTextPath) { info.handlingTextPath = false; } } } #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " <- buildTextChunks(start=%p)\n", start); #endif } const Vector &SVGRootInlineBox::svgTextChunks() const { return m_svgTextChunks; } void SVGRootInlineBox::layoutTextChunks() { Vector::iterator it = m_svgTextChunks.begin(); Vector::iterator end = m_svgTextChunks.end(); for (; it != end; ++it) { SVGTextChunk &chunk = *it; #if DEBUG_CHUNK_BUILDING > 0 { fprintf(stderr, "Handle TEXT CHUNK! anchor=%i, textLength=%f, lengthAdjust=%i, isVerticalText=%i, isTextPath=%i start=%p, end=%p -> dist: %i\n", (int) chunk.anchor, chunk.textLength, (int) chunk.lengthAdjust, (int) chunk.isVerticalText, (int) chunk.isTextPath, chunk.start, chunk.end, (unsigned int)(chunk.end - chunk.start)); Vector::iterator boxIt = chunk.boxes.begin(); Vector::iterator boxEnd = chunk.boxes.end(); unsigned int i = 0; for (; boxIt != boxEnd; ++boxIt) { SVGInlineBoxCharacterRange &range = *boxIt; i++; fprintf(stderr, " -> RANGE %i STARTOFFSET: %i, ENDOFFSET: %i, BOX: %p\n", i, range.startOffset, range.endOffset, range.box); } } #endif if (chunk.isTextPath) { continue; } // text-path & textLength, with lengthAdjust="spacing" is already handled for textPath layouts. applyTextLengthCorrectionToTextChunk(chunk); // text-anchor is already handled for textPath layouts. applyTextAnchorToTextChunk(chunk); } } static inline void addPaintServerToTextDecorationInfo(ETextDecoration decoration, SVGTextDecorationInfo &info, RenderObject *object) { if (object->style()->svgStyle()->hasFill()) { info.fillServerMap.set(decoration, object); } if (object->style()->svgStyle()->hasStroke()) { info.strokeServerMap.set(decoration, object); } } SVGTextDecorationInfo SVGRootInlineBox::retrievePaintServersForTextDecoration(RenderObject *start) { ASSERT(start); Vector parentChain; while ((start = start->parent())) { parentChain.prepend(start); // Stop at our direct parent. if (start->isSVGText()) { break; } } Vector::iterator it = parentChain.begin(); Vector::iterator end = parentChain.end(); SVGTextDecorationInfo info; for (; it != end; ++it) { RenderObject *object = *it; ASSERT(object); RenderStyle *style = object->style(); ASSERT(style); int decorations = style->textDecoration(); if (decorations != NONE) { if (decorations & OVERLINE) { addPaintServerToTextDecorationInfo(OVERLINE, info, object); } if (decorations & UNDERLINE) { addPaintServerToTextDecorationInfo(UNDERLINE, info, object); } if (decorations & LINE_THROUGH) { addPaintServerToTextDecorationInfo(LINE_THROUGH, info, object); } } } return info; } void SVGRootInlineBox::walkTextChunks(SVGTextChunkWalkerBase *walker, const SVGInlineTextBox *textBox) { ASSERT(walker); Vector::iterator it = m_svgTextChunks.begin(); Vector::iterator itEnd = m_svgTextChunks.end(); for (; it != itEnd; ++it) { SVGTextChunk &curChunk = *it; Vector::iterator boxIt = curChunk.boxes.begin(); Vector::iterator boxEnd = curChunk.boxes.end(); InlineBox *lastNotifiedBox = nullptr; InlineBox *prevBox = nullptr; unsigned int chunkOffset = 0; bool startedFirstChunk = false; for (; boxIt != boxEnd; ++boxIt) { SVGInlineBoxCharacterRange &range = *boxIt; ASSERT(range.box->isInlineTextBox()); SVGInlineTextBox *rangeTextBox = static_cast(range.box); if (textBox && rangeTextBox != textBox) { chunkOffset += range.endOffset - range.startOffset; continue; } // Eventually notify that we started a new chunk if (!textBox && !startedFirstChunk) { startedFirstChunk = true; lastNotifiedBox = range.box; walker->start(range.box); } else { // Eventually apply new style, as this chunk spans multiple boxes (with possible different styling) if (prevBox && prevBox != range.box) { lastNotifiedBox = range.box; walker->end(prevBox); walker->start(lastNotifiedBox); } } unsigned int length = range.endOffset - range.startOffset; Vector::iterator itCharBegin = curChunk.start + chunkOffset; Vector::iterator itCharEnd = curChunk.start + chunkOffset + length; ASSERT(itCharEnd <= curChunk.end); // Process this chunk portion if (textBox) { (*walker)(rangeTextBox, range.startOffset, curChunk.ctm, itCharBegin, itCharEnd); } else { if (walker->setupFill(range.box)) { (*walker)(rangeTextBox, range.startOffset, curChunk.ctm, itCharBegin, itCharEnd); } if (walker->setupStroke(range.box)) { (*walker)(rangeTextBox, range.startOffset, curChunk.ctm, itCharBegin, itCharEnd); } } chunkOffset += length; if (!textBox) { prevBox = range.box; } } if (!textBox && startedFirstChunk) { walker->end(lastNotifiedBox); } } } } // namespace WebCore #endif // ENABLE(SVG) diff --git a/src/rendering/bidi.cpp b/src/rendering/bidi.cpp index 8728437..f75124f 100644 --- a/src/rendering/bidi.cpp +++ b/src/rendering/bidi.cpp @@ -1,2645 +1,2645 @@ /** * This file is part of the html renderer for KDE. * * Copyright (C) 2000-2003 Lars Knoll (knoll@kde.org) * (C) 2003-2007 Apple Computer, Inc. * (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com) * (C) 2007-2009 Germain Garand (germain@ebooksfrance.org) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #include "rendering/bidi.h" #include "rendering/break_lines.h" #include "rendering/render_block.h" #include "rendering/render_text.h" #include "rendering/render_arena.h" #include "rendering/render_layer.h" #include "rendering/render_canvas.h" #include "xml/dom_docimpl.h" #include #include "QDebug" #include // SVG #include "rendering/SVGRootInlineBox.h" #include "rendering/SVGInlineTextBox.h" #define BIDI_DEBUG 0 //#define DEBUG_LINEBREAKS //#define PAGE_DEBUG namespace khtml { // an iterator which goes through a BidiParagraph struct BidiIterator { BidiIterator() : par(nullptr), obj(nullptr), pos(0), endOfInline(false) {} BidiIterator(RenderBlock *_par, RenderObject *_obj, unsigned int _pos, bool eoi = false) : par(_par), obj(_obj), pos(_pos), endOfInline(eoi) {} void increment(BidiState *bidi = nullptr, bool skipInlines = true); bool atEnd() const; const QChar ¤t() const; QChar::Direction direction() const; RenderBlock *par; RenderObject *obj; unsigned int pos; bool endOfInline; }; struct BidiState { BidiState() : context(nullptr) {} BidiIterator sor; BidiIterator eor; BidiIterator last; BidiIterator current; BidiContext *context; BidiStatus status; }; // Used to track a list of chained bidi runs. static BidiRun *sFirstBidiRun; static BidiRun *sLastBidiRun; static int sBidiRunCount; static BidiRun *sCompactFirstBidiRun; static BidiRun *sCompactLastBidiRun; static int sCompactBidiRunCount; static bool sBuildingCompactRuns; // Midpoint globals. The goal is not to do any allocation when dealing with // these midpoints, so we just keep an array around and never clear it. We track // the number of items and position using the two other variables. static QVector *smidpoints; static uint sNumMidpoints; static uint sCurrMidpoint; static bool betweenMidpoints; static bool isLineEmpty = true; static bool previousLineBrokeAtBR = false; static QChar::Direction dir = QChar::DirON; static bool emptyRun = true; static int numSpaces; static void embed(QChar::Direction d, BidiState &bidi); static void appendRun(BidiState &bidi); static int getBPMWidth(int childValue, Length cssUnit) { if (!cssUnit.isAuto()) { return (cssUnit.isFixed() ? cssUnit.value() : childValue); } return 0; } static int getBorderPaddingMargin(RenderObject *child, bool endOfInline) { RenderStyle *cstyle = child->style(); int result = 0; bool leftSide = (cstyle->direction() == LTR) ? !endOfInline : endOfInline; result += getBPMWidth((leftSide ? child->marginLeft() : child->marginRight()), (leftSide ? cstyle->marginLeft() : cstyle->marginRight())); result += getBPMWidth((leftSide ? child->paddingLeft() : child->paddingRight()), (leftSide ? cstyle->paddingLeft() : cstyle->paddingRight())); result += leftSide ? child->borderLeft() : child->borderRight(); return result; } #ifndef NDEBUG static bool inBidiRunDetach; #endif void BidiRun::detach(RenderArena *renderArena) { #ifndef NDEBUG inBidiRunDetach = true; #endif delete this; #ifndef NDEBUG inBidiRunDetach = false; #endif // Recover the size left there for us by operator delete and free the memory. renderArena->free(*(size_t *)this, this); } void *BidiRun::operator new(size_t sz, RenderArena *renderArena) throw() { return renderArena->allocate(sz); } void BidiRun::operator delete(void *ptr, size_t sz) { assert(inBidiRunDetach); // Stash size where detach can find it. *(size_t *)ptr = sz; } static void deleteBidiRuns(RenderArena *arena) { if (!sFirstBidiRun) { return; } BidiRun *curr = sFirstBidiRun; while (curr) { BidiRun *s = curr->nextRun; curr->detach(arena); curr = s; } sFirstBidiRun = nullptr; sLastBidiRun = nullptr; sBidiRunCount = 0; } // --------------------------------------------------------------------- /* a small helper class used internally to resolve Bidi embedding levels. Each line of text caches the embedding level at the start of the line for faster relayouting */ BidiContext::BidiContext(unsigned char l, QChar::Direction e, BidiContext *p, bool o) : level(l), override(o), dir(e) { parent = p; if (p) { p->ref(); basicDir = p->basicDir; } else { basicDir = e; } count = 0; } BidiContext::~BidiContext() { if (parent) { parent->deref(); } } void BidiContext::ref() const { count++; } void BidiContext::deref() const { count--; if (count <= 0) { delete this; } } // --------------------------------------------------------------------- inline bool operator==(const BidiContext &c1, const BidiContext &c2) { if (&c1 == &c2) { return true; } if (c1.level != c2.level || c1.override != c2.override || c1.dir != c2.dir || c1.basicDir != c2.basicDir) { return false; } if (!c1.parent) { return !c2.parent; } return c2.parent && *c1.parent == *c2.parent; } inline bool operator==(const BidiIterator &it1, const BidiIterator &it2) { if (it1.pos != it2.pos) { return false; } if (it1.obj != it2.obj) { return false; } return true; } inline bool operator!=(const BidiIterator &it1, const BidiIterator &it2) { if (it1.pos != it2.pos) { return true; } if (it1.obj != it2.obj) { return true; } return false; } inline bool operator==(const BidiStatus &status1, const BidiStatus &status2) { return status1.eor == status2.eor && status1.last == status2.last && status1.lastStrong == status2.lastStrong; } inline bool operator!=(const BidiStatus &status1, const BidiStatus &status2) { return !(status1 == status2); } // when modifying this function, make sure you check InlineMinMaxIterator::next() as well. static inline RenderObject *Bidinext(RenderObject *par, RenderObject *current, BidiState *bidi = nullptr, bool skipInlines = true, bool *endOfInline = nullptr) { RenderObject *next = nullptr; bool oldEndOfInline = endOfInline ? *endOfInline : false; if (oldEndOfInline) { *endOfInline = false; } while (current != nullptr) { //qDebug() << "current = " << current; if (!oldEndOfInline && !current->isFloating() && !current->isReplaced() && !current->isPositioned()) { next = current->firstChild(); if (next && bidi) { EUnicodeBidi ub = next->style()->unicodeBidi(); if (ub != UBNormal && !emptyRun) { EDirection dir = next->style()->direction(); QChar::Direction d = (ub == Embed ? (dir == RTL ? QChar::DirRLE : QChar::DirLRE) : (dir == RTL ? QChar::DirRLO : QChar::DirLRO)); embed(d, *bidi); } } } if (!next) { if (!skipInlines && !oldEndOfInline && current->isInlineFlow() && endOfInline) { next = current; *endOfInline = true; break; } while (current && current != par) { next = current->nextSibling(); if (next) { break; } if (bidi && current->style()->unicodeBidi() != UBNormal && !emptyRun) { embed(QChar::DirPDF, *bidi); } current = current->parent(); if (!skipInlines && current && current != par && current->isInlineFlow() && endOfInline) { next = current; *endOfInline = true; break; } } } if (!next) { break; } if (next->isText() || next->isBR() || next->isFloating() || next->isReplaced() || next->isPositioned() || next->isGlyph() || ((!skipInlines || !next->firstChild()) // Always return EMPTY inlines. && next->isInlineFlow())) { break; } current = next; next = nullptr; } return next; } static RenderObject *first(RenderObject *par, BidiState *bidi, bool skipInlines = true) { if (!par->firstChild()) { return nullptr; } RenderObject *o = par->firstChild(); if (o->isInlineFlow()) { if (skipInlines && o->firstChild()) { o = Bidinext(par, o, bidi, skipInlines); } else { return o; // Never skip empty inlines. } } if (o && !o->isText() && !o->isBR() && !o->isReplaced() && !o->isFloating() && !o->isPositioned() && !o->isGlyph()) { o = Bidinext(par, o, bidi, skipInlines); } return o; } inline void BidiIterator::increment(BidiState *bidi, bool skipInlines) { if (!obj) { return; } if (obj->isText()) { pos++; if (pos >= static_cast(obj)->stringLength()) { obj = Bidinext(par, obj, bidi, skipInlines); pos = 0; } } else { obj = Bidinext(par, obj, bidi, skipInlines, &endOfInline); pos = 0; } } inline bool BidiIterator::atEnd() const { if (!obj) { return true; } return false; } const QChar &BidiIterator::current() const { static QChar nonBreakingSpace(0xA0); if (!obj || !obj->isText()) { return nonBreakingSpace; } RenderText *text = static_cast(obj); if (!text->text()) { return nonBreakingSpace; } return text->text()[pos]; } inline QChar::Direction BidiIterator::direction() const { if (!obj || !obj->isText()) { return QChar::DirON; } RenderText *renderTxt = static_cast(obj); if (pos >= renderTxt->stringLength()) { return QChar::DirON; } return renderTxt->text()[pos].direction(); } // ------------------------------------------------------------------------------------------------- static void addRun(BidiRun *bidiRun) { if (!sFirstBidiRun) { sFirstBidiRun = sLastBidiRun = bidiRun; } else { sLastBidiRun->nextRun = bidiRun; sLastBidiRun = bidiRun; } sBidiRunCount++; bidiRun->compact = sBuildingCompactRuns; // Compute the number of spaces in this run, if (bidiRun->obj && bidiRun->obj->isText()) { RenderText *text = static_cast(bidiRun->obj); if (text->text()) { for (int i = bidiRun->start; i < bidiRun->stop; i++) { const QChar c = text->text()[i]; if (c.unicode() == '\n' || c.category() == QChar::Separator_Space) { numSpaces++; } } } } } static void reverseRuns(int start, int end) { if (start >= end) { return; } assert(start >= 0 && end < sBidiRunCount); // Get the item before the start of the runs to reverse and put it in // |beforeStart|. |curr| should point to the first run to reverse. BidiRun *curr = sFirstBidiRun; BidiRun *beforeStart = nullptr; int i = 0; while (i < start) { i++; beforeStart = curr; curr = curr->nextRun; } BidiRun *startRun = curr; while (i < end) { i++; curr = curr->nextRun; } BidiRun *endRun = curr; BidiRun *afterEnd = curr->nextRun; i = start; curr = startRun; BidiRun *newNext = afterEnd; while (i <= end) { // Do the reversal. BidiRun *next = curr->nextRun; curr->nextRun = newNext; newNext = curr; curr = next; i++; } // Now hook up beforeStart and afterEnd to the newStart and newEnd. if (beforeStart) { beforeStart->nextRun = endRun; } else { sFirstBidiRun = endRun; } startRun->nextRun = afterEnd; if (!afterEnd) { sLastBidiRun = startRun; } } static void chopMidpointsAt(RenderObject *obj, uint pos) { if (!sNumMidpoints) { return; } BidiIterator *midpoints = smidpoints->data(); for (uint i = 0; i < sNumMidpoints; i++) { const BidiIterator &point = midpoints[i]; if (point.obj == obj && point.pos == pos) { sNumMidpoints = i; break; } } } static void checkMidpoints(BidiIterator &lBreak) { // Check to see if our last midpoint is a start point beyond the line break. If so, // shave it off the list, and shave off a trailing space if the previous end point isn't // white-space: pre. if (lBreak.obj && sNumMidpoints && sNumMidpoints % 2 == 0) { BidiIterator *midpoints = smidpoints->data(); BidiIterator &endpoint = midpoints[sNumMidpoints - 2]; const BidiIterator &startpoint = midpoints[sNumMidpoints - 1]; BidiIterator currpoint = endpoint; while (!currpoint.atEnd() && currpoint != startpoint && currpoint != lBreak) { currpoint.increment(); } if (currpoint == lBreak) { // We hit the line break before the start point. Shave off the start point. sNumMidpoints--; if (!endpoint.obj->style()->preserveWS()) { if (endpoint.obj->isText()) { // Don't shave a character off the endpoint if it was from a soft hyphen. RenderText *textObj = static_cast(endpoint.obj); if (endpoint.pos + 1 < textObj->length() && textObj->text()[endpoint.pos + 1].unicode() == SOFT_HYPHEN) { return; } } endpoint.pos--; } } } } static void addMidpoint(const BidiIterator &midpoint) { if (!smidpoints) { return; } if (smidpoints->size() <= (int)sNumMidpoints) { smidpoints->resize(sNumMidpoints + 10); } BidiIterator *midpoints = smidpoints->data(); // do not place midpoints in inline flows that are going to be skipped by the bidi iteration process. // Place them at the next non-skippable object instead. // #### eventually, we may want to have the same iteration in bidi and in findNextLineBreak, // then this extra complexity can go away. if (midpoint.obj && midpoint.obj->isInlineFlow() && (midpoint.obj->firstChild() || midpoint.endOfInline)) { BidiIterator n = midpoint; n.increment(); assert(!n.endOfInline); // we'll recycle the endOfInline flag to mean : don't include this stop point, stop right before it. // this is necessary because we just advanced our position to skip an inline, so we passed the real stop point n.endOfInline = true; if (!n.atEnd()) { midpoints[sNumMidpoints++] = n; } } else { assert(!midpoint.endOfInline); midpoints[sNumMidpoints++] = midpoint; } } static void appendRunsForObject(int start, int end, RenderObject *obj, BidiState &bidi) { if (start > end || obj->isFloating() || (obj->isPositioned() && !obj->hasStaticX() && !obj->hasStaticY())) { return; } bool haveNextMidpoint = (smidpoints && sCurrMidpoint < sNumMidpoints); BidiIterator nextMidpoint; if (haveNextMidpoint) { nextMidpoint = smidpoints->at(sCurrMidpoint); } if (betweenMidpoints) { if (!(haveNextMidpoint && nextMidpoint.obj == obj)) { return; } // This is a new start point. Stop ignoring objects and // adjust our start. betweenMidpoints = false; start = nextMidpoint.pos; sCurrMidpoint++; if (start < end) { return appendRunsForObject(start, end, obj, bidi); } } else { if (!smidpoints || !haveNextMidpoint || (obj != nextMidpoint.obj)) { addRun(new(obj->renderArena()) BidiRun(start, end, obj, bidi.context, dir)); return; } // An end midpoint has been encountered within our object. We // need to go ahead and append a run with our endpoint. if (int(nextMidpoint.pos + 1) <= end) { betweenMidpoints = true; sCurrMidpoint++; if (nextMidpoint.pos != UINT_MAX) { // UINT_MAX means stop at the object and don't include any of it. if (!nextMidpoint.endOfInline) // In this context, this flag means the stop point is exclusive, not inclusive (see addMidpoint). addRun(new(obj->renderArena()) BidiRun(start, nextMidpoint.pos + 1, obj, bidi.context, dir)); return appendRunsForObject(nextMidpoint.pos + 1, end, obj, bidi); } } else { addRun(new(obj->renderArena()) BidiRun(start, end, obj, bidi.context, dir)); } } } static void appendRun(BidiState &bidi) { if (emptyRun) { return; } #if BIDI_DEBUG > 1 qDebug() << "appendRun: dir=" << (int)dir; #endif int start = bidi.sor.pos; RenderObject *obj = bidi.sor.obj; while (obj && obj != bidi.eor.obj) { appendRunsForObject(start, obj->length(), obj, bidi); start = 0; obj = Bidinext(bidi.sor.par, obj); } if (obj) { appendRunsForObject(start, bidi.eor.pos + 1, obj, bidi); } bidi.eor.increment(); bidi.sor = bidi.eor; dir = QChar::DirON; bidi.status.eor = QChar::DirON; } static void embed(QChar::Direction d, BidiState &bidi) { #if BIDI_DEBUG > 1 qDebug("*** embed dir=%d emptyrun=%d", d, emptyRun); #endif if (d == QChar::DirPDF) { BidiContext *c = bidi.context->parent; if (c) { if (bidi.eor != bidi.last) { appendRun(bidi); bidi.eor = bidi.last; } appendRun(bidi); emptyRun = true; bidi.status.last = bidi.context->dir; bidi.context->deref(); bidi.context = c; if (bidi.context->override) { dir = bidi.context->dir; } else { dir = QChar::DirON; } bidi.status.lastStrong = bidi.context->dir; } } else { QChar::Direction runDir; if (d == QChar::DirRLE || d == QChar::DirRLO) { runDir = QChar::DirR; } else { runDir = QChar::DirL; } bool override; if (d == QChar::DirLRO || d == QChar::DirRLO) { override = true; } else { override = false; } unsigned char level = bidi.context->level; if (runDir == QChar::DirR) { if (level % 2) { // we have an odd level level += 2; } else { level++; } } else { if (level % 2) { // we have an odd level level++; } else { level += 2; } } if (level < 61) { if (bidi.eor != bidi.last) { appendRun(bidi); bidi.eor = bidi.last; } appendRun(bidi); emptyRun = true; bidi.context = new BidiContext(level, runDir, bidi.context, override); bidi.context->ref(); dir = runDir; bidi.status.last = runDir; bidi.status.lastStrong = runDir; bidi.status.eor = runDir; } } } InlineFlowBox *RenderBlock::createLineBoxes(RenderObject *obj) { // See if we have an unconstructed line box for this object that is also // the last item on the line. KHTMLAssert(obj->isInlineFlow() || obj == this); RenderFlow *flow = static_cast(obj); // Get the last box we made for this render object. InlineFlowBox *box = flow->lastLineBox(); // If this box is constructed then it is from a previous line, and we need // to make a new box for our line. If this box is unconstructed but it has // something following it on the line, then we know we have to make a new box // as well. In this situation our inline has actually been split in two on // the same line (this can happen with very fancy language mixtures). if (!box || box->isConstructed() || box->nextOnLine()) { // We need to make a new box for this render object. Once // made, we need to place it at the end of the current line. InlineBox *newBox = obj->createInlineBox(false, obj == this); KHTMLAssert(newBox->isInlineFlowBox()); box = static_cast(newBox); box->setFirstLineStyleBit(m_firstLine); // We have a new box. Append it to the inline box we get by constructing our // parent. If we have hit the block itself, then |box| represents the root // inline box for the line, and it doesn't have to be appended to any parent // inline. if (obj != this) { InlineFlowBox *parentBox = createLineBoxes(obj->parent()); parentBox->addToLine(box); } } return box; } RootInlineBox *RenderBlock::constructLine(const BidiIterator &/*start*/, const BidiIterator &end) { if (!sFirstBidiRun) { return nullptr; // We had no runs. Don't make a root inline box at all. The line is empty. } InlineFlowBox *parentBox = nullptr; for (BidiRun *r = sFirstBidiRun; r; r = r->nextRun) { // Create a box for our object. r->box = r->obj->createInlineBox(r->obj->isPositioned(), false); // If we have no parent box yet, or if the run is not simply a sibling, // then we need to construct inline boxes as necessary to properly enclose the // run's inline box. if (!parentBox || (parentBox->object() != r->obj->parent())) // Create new inline boxes all the way back to the appropriate insertion point. { parentBox = createLineBoxes(r->obj->parent()); } // Append the inline box to this line. parentBox->addToLine(r->box); } // We should have a root inline box. It should be unconstructed and // be the last continuation of our line list. KHTMLAssert(lastLineBox() && !lastLineBox()->isConstructed()); // Set bits on our inline flow boxes that indicate which sides should // paint borders/margins/padding. This knowledge will ultimately be used when // we determine the horizontal positions and widths of all the inline boxes on // the line. RenderObject *endObject = nullptr; bool lastLine = !end.obj; if (end.obj && end.pos == 0) { endObject = end.obj; } lastLineBox()->determineSpacingForFlowBoxes(lastLine, endObject); // Now mark the line boxes as being constructed. lastLineBox()->setConstructed(); // Return the last line. return lastRootBox(); } void RenderBlock::computeHorizontalPositionsForLine(InlineFlowBox *lineBox, BidiState &bidi) { // First determine our total width. int totWidth = lineBox->getFlowSpacingWidth(); BidiRun *r = nullptr; for (r = sFirstBidiRun; r; r = r->nextRun) { if (r->obj->isPositioned()) { continue; // Positioned objects are only participating to figure out their } // correct static x position. They have no effect on the width. if (r->obj->isText()) { r->box->setWidth(static_cast(r->obj)->width(r->start, r->stop - r->start, m_firstLine)); } else if (!r->obj->isInlineFlow()) { r->obj->calcWidth(); // Is this really needed or the object width is already correct here ? r->box->setWidth(r->obj->width()); totWidth += r->obj->marginLeft() + r->obj->marginRight(); } totWidth += r->box->width(); } // Armed with the total width of the line (without justification), // we now examine our text-align property in order to determine where to position the // objects horizontally. The total width of the line can be increased if we end up // justifying text. int x = leftOffset(m_height); int availableWidth = lineWidth(m_height); switch (style()->textAlign()) { case LEFT: case KHTML_LEFT: if (style()->direction() == RTL && totWidth > availableWidth) { x -= (totWidth - availableWidth); } numSpaces = 0; break; case JUSTIFY: if (numSpaces != 0 && !bidi.current.atEnd() && !bidi.current.obj->isBR()) { break; } // fall through case TAAUTO: numSpaces = 0; // for right to left fall through to right aligned if (bidi.context->basicDir == QChar::DirL) { break; } case RIGHT: case KHTML_RIGHT: if (style()->direction() == RTL || totWidth < availableWidth) { x += availableWidth - totWidth; } numSpaces = 0; break; case CENTER: case KHTML_CENTER: int xd = (availableWidth - totWidth) / 2; x += xd > 0 ? xd : 0; numSpaces = 0; break; } if (numSpaces > 0) { for (r = sFirstBidiRun; r; r = r->nextRun) { int spaceAdd = 0; if (numSpaces > 0 && r->obj->isText()) { // get the number of spaces in the run int spaces = 0; for (int i = r->start; i < r->stop; i++) { const QChar c = static_cast(r->obj)->text()[i]; if (c.category() == QChar::Separator_Space || c == '\n') { spaces++; } } KHTMLAssert(spaces <= numSpaces); // Only justify text with white-space: normal. if (r->obj->style()->whiteSpace() == NORMAL) { spaceAdd = (availableWidth - totWidth) * spaces / numSpaces; spaceAdd = qMax(0, spaceAdd); static_cast(r->box)->setSpaceAdd(spaceAdd); totWidth += spaceAdd; } numSpaces -= spaces; } } } // The widths of all runs are now known. We can now place every inline box (and // compute accurate widths for the inline flow boxes). int rightPos = lineBox->placeBoxesHorizontally(x); if (rightPos > m_overflowWidth) { m_overflowWidth = rightPos; // FIXME: Work for rtl overflow also. } if (x < 0) { m_overflowLeft = qMin(m_overflowLeft, x); } } void RenderBlock::computeVerticalPositionsForLine(RootInlineBox *lineBox) { lineBox->verticallyAlignBoxes(m_height); lineBox->setBlockHeight(m_height); // Check for page-breaks if (canvas()->pagedMode() && !lineBox->afterPageBreak()) // If we get a page-break we might need to redo the line-break if (clearLineOfPageBreaks(lineBox) && hasFloats()) { return; } // See if the line spilled out. If so set overflow height accordingly. int bottomOfLine = lineBox->bottomOverflow(); if (bottomOfLine > m_height && bottomOfLine > m_overflowHeight) { m_overflowHeight = bottomOfLine; } bool beforeContent = true; // Now make sure we place replaced render objects correctly. for (BidiRun *r = sFirstBidiRun; r; r = r->nextRun) { // For positioned placeholders, cache the static Y position an object with non-inline display would have. // Either it is unchanged if it comes before any real linebox, or it must clear the current line (already accounted in m_height). // This value will be picked up by position() if relevant. if (r->obj->isPositioned()) { r->box->setYPos(beforeContent && r->obj->isBox() ? static_cast(r->obj)->staticY() : m_height); } else if (beforeContent) { beforeContent = false; } // Position is used to properly position both replaced elements and // to update the static normal flow x/y of positioned elements. r->obj->position(r->box, r->start, r->stop - r->start, r->level % 2); } } bool RenderBlock::clearLineOfPageBreaks(InlineFlowBox *lineBox) { bool doPageBreak = false; // Check for physical page-breaks int xpage = crossesPageBreak(lineBox->topOverflow(), lineBox->bottomOverflow()); if (xpage) { #ifdef PAGE_DEBUG qDebug() << renderName() << " Line crosses to page " << xpage; qDebug() << renderName() << " at pos " << lineBox->yPos() << " height " << lineBox->height(); #endif doPageBreak = true; // check page-break-inside if (!style()->pageBreakInside()) { if (parent()->canClear(this, PageBreakNormal)) { setNeedsPageClear(true); doPageBreak = false; } #ifdef PAGE_DEBUG else { qDebug() << "Ignoring page-break-inside: avoid"; } #endif } // check orphans int orphans = 0; InlineRunBox *box = lineBox->prevLineBox(); while (box && orphans < style()->orphans()) { orphans++; box = box->prevLineBox(); } if (orphans == 0) { setNeedsPageClear(true); doPageBreak = false; } else if (orphans < style()->orphans()) { #ifdef PAGE_DEBUG qDebug() << "Orphans: " << orphans; #endif // Orphans is a level 2 page-break rule and can be broken only // if the break is physically required. if (parent()->canClear(this, PageBreakHarder)) { // move block instead setNeedsPageClear(true); doPageBreak = false; } #ifdef PAGE_DEBUG else { qDebug() << "Ignoring violated orphans"; } #endif } if (doPageBreak) { #ifdef PAGE_DEBUG int oldYPos = lineBox->yPos(); #endif int pTop = pageTopAfter(lineBox->yPos()); m_height = pTop; lineBox->setAfterPageBreak(true); lineBox->verticallyAlignBoxes(m_height); if (lineBox->yPos() < pTop) { // ### serious crap. render_line is sometimes placing lines too high // qDebug() << "page top overflow by repositioned line"; int heightIncrease = pTop - lineBox->yPos(); m_height = pTop + heightIncrease; lineBox->verticallyAlignBoxes(m_height); } #ifdef PAGE_DEBUG qDebug() << "Cleared line " << lineBox->yPos() - oldYPos << "px"; #endif setContainsPageBreak(true); } } return doPageBreak; } // collects one line of the paragraph and transforms it to visual order void RenderBlock::bidiReorderLine(const BidiIterator &start, const BidiIterator &end, BidiState &bidi) { if (start == end) { if (start.current() == '\n') { m_height += lineHeight(m_firstLine); } return; } #if BIDI_DEBUG > 1 qDebug() << "reordering Line from " << start.obj << "/" << start.pos << " to " << end.obj << "/" << end.pos; #endif sFirstBidiRun = nullptr; sLastBidiRun = nullptr; sBidiRunCount = 0; // context->ref(); dir = QChar::DirON; emptyRun = true; numSpaces = 0; bidi.current = start; bidi.last = bidi.current; bool atEnd = false; while (1) { QChar::Direction dirCurrent; if (atEnd) { //qDebug() << "atEnd"; BidiContext *c = bidi.context; if (bidi.current.atEnd()) while (c->parent) { c = c->parent; } dirCurrent = c->dir; } else if (bidi.context->override) { dirCurrent = bidi.context->dir; } else { dirCurrent = bidi.current.direction(); } #ifndef QT_NO_UNICODETABLES #if BIDI_DEBUG > 1 qDebug() << "directions: dir=" << (int)dir << " current=" << (int)dirCurrent << " last=" << bidi.status.last << " eor=" << bidi.status.eor << " lastStrong=" << bidi.status.lastStrong << " embedding=" << (int)bidi.context->dir << " level =" << (int)bidi.context->level; #endif switch (dirCurrent) { // embedding and overrides (X1-X9 in the Bidi specs) case QChar::DirRLE: case QChar::DirLRE: case QChar::DirRLO: case QChar::DirLRO: case QChar::DirPDF: embed(dirCurrent, bidi); break; // strong types case QChar::DirL: if (dir == QChar::DirON) { dir = QChar::DirL; } switch (bidi.status.last) { case QChar::DirL: bidi.eor = bidi.current; bidi.status.eor = QChar::DirL; break; case QChar::DirR: case QChar::DirAL: case QChar::DirEN: case QChar::DirAN: appendRun(bidi); break; case QChar::DirES: case QChar::DirET: case QChar::DirCS: case QChar::DirBN: case QChar::DirB: case QChar::DirS: case QChar::DirWS: case QChar::DirON: if (bidi.status.eor != QChar::DirL) { //last stuff takes embedding dir if (bidi.context->dir == QChar::DirL || bidi.status.lastStrong == QChar::DirL) { if (bidi.status.eor != QChar::DirEN && bidi.status.eor != QChar::DirAN && bidi.status.eor != QChar::DirON) { appendRun(bidi); } dir = QChar::DirL; bidi.eor = bidi.current; bidi.status.eor = QChar::DirL; } else { if (bidi.status.eor == QChar::DirEN || bidi.status.eor == QChar::DirAN) { dir = bidi.status.eor; appendRun(bidi); } dir = QChar::DirR; bidi.eor = bidi.last; appendRun(bidi); dir = QChar::DirL; bidi.status.eor = QChar::DirL; } } else { bidi.eor = bidi.current; bidi.status.eor = QChar::DirL; } default: break; } bidi.status.lastStrong = QChar::DirL; break; case QChar::DirAL: case QChar::DirR: if (dir == QChar::DirON) { dir = QChar::DirR; } switch (bidi.status.last) { case QChar::DirR: case QChar::DirAL: bidi.eor = bidi.current; bidi.status.eor = QChar::DirR; break; case QChar::DirL: case QChar::DirEN: case QChar::DirAN: appendRun(bidi); dir = QChar::DirR; bidi.eor = bidi.current; bidi.status.eor = QChar::DirR; break; case QChar::DirES: case QChar::DirET: case QChar::DirCS: case QChar::DirBN: case QChar::DirB: case QChar::DirS: case QChar::DirWS: case QChar::DirON: if (!(bidi.status.eor == QChar::DirR) && !(bidi.status.eor == QChar::DirAL)) { //last stuff takes embedding dir if (bidi.context->dir == QChar::DirR || bidi.status.lastStrong == QChar::DirR || bidi.status.lastStrong == QChar::DirAL) { appendRun(bidi); dir = QChar::DirR; bidi.eor = bidi.current; bidi.status.eor = QChar::DirR; } else { dir = QChar::DirL; bidi.eor = bidi.last; appendRun(bidi); dir = QChar::DirR; bidi.status.eor = QChar::DirR; } } else { bidi.eor = bidi.current; bidi.status.eor = QChar::DirR; } default: break; } bidi.status.lastStrong = dirCurrent; break; // weak types: case QChar::DirNSM: // ### if @sor, set dir to dirSor break; case QChar::DirEN: if (!(bidi.status.lastStrong == QChar::DirAL)) { // if last strong was AL change EN to AN if (dir == QChar::DirON) { dir = QChar::DirL; } switch (bidi.status.last) { case QChar::DirET: if (bidi.status.lastStrong == QChar::DirR || bidi.status.lastStrong == QChar::DirAL) { appendRun(bidi); dir = QChar::DirEN; bidi.status.eor = QChar::DirEN; } // fall through case QChar::DirEN: case QChar::DirL: bidi.eor = bidi.current; bidi.status.eor = dirCurrent; break; case QChar::DirR: case QChar::DirAL: case QChar::DirAN: appendRun(bidi); bidi.status.eor = QChar::DirEN; dir = QChar::DirEN; break; case QChar::DirES: case QChar::DirCS: if (bidi.status.eor == QChar::DirEN) { bidi.eor = bidi.current; break; } case QChar::DirBN: case QChar::DirB: case QChar::DirS: case QChar::DirWS: case QChar::DirON: if (bidi.status.eor == QChar::DirR) { // neutrals go to R bidi.eor = bidi.last; appendRun(bidi); dir = QChar::DirEN; bidi.status.eor = QChar::DirEN; } else if (bidi.status.eor == QChar::DirL || (bidi.status.eor == QChar::DirEN && bidi.status.lastStrong == QChar::DirL)) { bidi.eor = bidi.current; bidi.status.eor = dirCurrent; } else { // numbers on both sides, neutrals get right to left direction if (dir != QChar::DirL) { appendRun(bidi); bidi.eor = bidi.last; dir = QChar::DirR; appendRun(bidi); dir = QChar::DirEN; bidi.status.eor = QChar::DirEN; } else { bidi.eor = bidi.current; bidi.status.eor = dirCurrent; } } default: break; } break; } case QChar::DirAN: dirCurrent = QChar::DirAN; if (dir == QChar::DirON) { dir = QChar::DirAN; } switch (bidi.status.last) { case QChar::DirL: case QChar::DirAN: bidi.eor = bidi.current; bidi.status.eor = QChar::DirAN; break; case QChar::DirR: case QChar::DirAL: case QChar::DirEN: appendRun(bidi); dir = QChar::DirAN; bidi.status.eor = QChar::DirAN; break; case QChar::DirCS: if (bidi.status.eor == QChar::DirAN) { bidi.eor = bidi.current; break; } case QChar::DirES: case QChar::DirET: case QChar::DirBN: case QChar::DirB: case QChar::DirS: case QChar::DirWS: case QChar::DirON: if (bidi.status.eor == QChar::DirR) { // neutrals go to R bidi.eor = bidi.last; appendRun(bidi); dir = QChar::DirAN; bidi.status.eor = QChar::DirAN; } else if (bidi.status.eor == QChar::DirL || (bidi.status.eor == QChar::DirEN && bidi.status.lastStrong == QChar::DirL)) { bidi.eor = bidi.current; bidi.status.eor = dirCurrent; } else { // numbers on both sides, neutrals get right to left direction if (dir != QChar::DirL) { appendRun(bidi); bidi.eor = bidi.last; dir = QChar::DirR; appendRun(bidi); dir = QChar::DirAN; bidi.status.eor = QChar::DirAN; } else { bidi.eor = bidi.current; bidi.status.eor = dirCurrent; } } default: break; } break; case QChar::DirES: case QChar::DirCS: break; case QChar::DirET: if (bidi.status.last == QChar::DirEN) { dirCurrent = QChar::DirEN; bidi.eor = bidi.current; bidi.status.eor = dirCurrent; break; } break; // boundary neutrals should be ignored case QChar::DirBN: break; // neutrals case QChar::DirB: // ### what do we do with newline and paragraph separators that come to here? break; case QChar::DirS: // ### implement rule L1 break; case QChar::DirWS: break; case QChar::DirON: break; default: break; } //cout << " after: dir=" << // dir << " current=" << dirCurrent << " last=" << status.last << " eor=" << status.eor << " lastStrong=" << status.lastStrong << " embedding=" << context->dir << endl; if (bidi.current.atEnd()) { break; } // set status.last as needed. switch (dirCurrent) { case QChar::DirET: case QChar::DirES: case QChar::DirCS: case QChar::DirS: case QChar::DirWS: case QChar::DirON: switch (bidi.status.last) { case QChar::DirL: case QChar::DirR: case QChar::DirAL: case QChar::DirEN: case QChar::DirAN: bidi.status.last = dirCurrent; break; default: bidi.status.last = QChar::DirON; } break; case QChar::DirNSM: case QChar::DirBN: // ignore these break; case QChar::DirEN: if (bidi.status.last == QChar::DirL) { break; } // fall through default: bidi.status.last = dirCurrent; } #endif if (atEnd) { break; } bidi.last = bidi.current; if (emptyRun) { bidi.sor = bidi.current; bidi.eor = bidi.current; emptyRun = false; } // this causes the operator ++ to open and close embedding levels as needed // for the CSS unicode-bidi property bidi.current.increment(&bidi); if (bidi.current == end) { if (emptyRun) { break; } atEnd = true; } } #if BIDI_DEBUG > 0 qDebug() << "reached end of line current=" << bidi.current.obj << "/" << bidi.current.pos - << ", eor=" << bidi.eor.obj << "/" << bidi.eor.pos << endl; + << ", eor=" << bidi.eor.obj << "/" << bidi.eor.pos; #endif if (!emptyRun && bidi.sor != bidi.current) { bidi.eor = bidi.last; appendRun(bidi); } // reorder line according to run structure... // first find highest and lowest levels uchar levelLow = 128; uchar levelHigh = 0; BidiRun *r = sFirstBidiRun; while (r) { if (r->level > levelHigh) { levelHigh = r->level; } if (r->level < levelLow) { levelLow = r->level; } r = r->nextRun; } // implements reordering of the line (L2 according to Bidi spec): // L2. From the highest level found in the text to the lowest odd level on each line, // reverse any contiguous sequence of characters that are at that level or higher. // reversing is only done up to the lowest odd level if (!(levelLow % 2)) { levelLow++; } int count = sBidiRunCount - 1; // do not reverse for visually ordered web sites if (!style()->visuallyOrdered()) { while (levelHigh >= levelLow) { int i = 0; BidiRun *currRun = sFirstBidiRun; while (i < count) { while (i < count && currRun && currRun->level < levelHigh) { i++; currRun = currRun->nextRun; } int start = i; while (i <= count && currRun && currRun->level >= levelHigh) { i++; currRun = currRun->nextRun; } int end = i - 1; reverseRuns(start, end); } levelHigh--; } } #if BIDI_DEBUG > 0 qDebug() << "visual order is:"; for (BidiRun *curr = sFirstBidiRun; curr; curr = curr->nextRun) { qDebug() << " " << curr; } #endif } void RenderBlock::layoutInlineChildren(bool relayoutChildren, int breakBeforeLine) { BidiState bidi; m_overflowHeight = 0; invalidateVerticalPosition(); #ifdef DEBUG_LAYOUT QTime qt; qt.start(); qDebug() << renderName() << " layoutInlineChildren( " << this << " )"; #endif #if BIDI_DEBUG > 1 || defined( DEBUG_LINEBREAKS ) qDebug() << " ------- bidi start " << this << " -------"; #endif m_height = borderTop() + paddingTop(); int toAdd = borderBottom() + paddingBottom(); if (m_layer && scrollsOverflowX() && style()->height().isAuto()) { toAdd += m_layer->horizontalScrollbarHeight(); } // Figure out if we should clear our line boxes. bool fullLayout = !firstLineBox() || !firstChild() || selfNeedsLayout() || relayoutChildren || hasFloats(); if (fullLayout) { deleteInlineBoxes(); } // Text truncation only kicks in if your overflow isn't visible and your // text-overflow-mode isn't clip. bool hasTextOverflow = style()->textOverflow() && hasOverflowClip(); // Walk all the lines and delete our ellipsis line boxes if they exist. if (hasTextOverflow) { deleteEllipsisLineBoxes(); } if (firstChild()) { // layout replaced elements RenderObject *o = first(this, nullptr, false); while (o) { invalidateVerticalPosition(); if (!fullLayout && o->markedForRepaint()) { o->repaintDuringLayout(); o->setMarkedForRepaint(false); } if (o->isReplaced() || o->isFloating() || o->isPositioned()) { if ((!o->isPositioned() || o->isPosWithStaticDim()) && (relayoutChildren || o->style()->width().isPercent() || o->style()->height().isPercent())) { o->setChildNeedsLayout(true, false); } if (o->isPositioned()) { if (!o->inPosObjectList()) { o->containingBlock()->insertPositionedObject(o); } if (fullLayout) { static_cast(o)->RenderBox::deleteInlineBoxes(); } } else { if (fullLayout || o->needsLayout()) { static_cast(o)->RenderBox::dirtyInlineBoxes(fullLayout); } o->layoutIfNeeded(); } } else { if (fullLayout || o->selfNeedsLayout()) { o->dirtyInlineBoxes(fullLayout); o->setMarkedForRepaint(false); } o->setNeedsLayout(false); } o = Bidinext(this, o, nullptr, false); } BidiContext *startEmbed; if (style()->direction() == LTR) { startEmbed = new BidiContext(0, QChar::DirL); bidi.status.eor = QChar::DirL; } else { startEmbed = new BidiContext(1, QChar::DirR); bidi.status.eor = QChar::DirR; } startEmbed->ref(); bidi.status.lastStrong = QChar::DirON; bidi.status.last = QChar::DirON; bidi.context = startEmbed; // We want to skip ahead to the first dirty line BidiIterator start; RootInlineBox *startLine = determineStartPosition(fullLayout, start, bidi); // Then look forward to see if we can find a clean area that is clean up to the end. BidiIterator cleanLineStart; BidiStatus cleanLineBidiStatus; BidiContext *cleanLineBidiContext = nullptr; int endLineYPos = 0; RootInlineBox *endLine = (fullLayout || !startLine) ? nullptr : determineEndPosition(startLine, cleanLineStart, cleanLineBidiStatus, cleanLineBidiContext, endLineYPos); // Extract the clean area. We will add it back if we determine that we're able to // synchronize after relayouting the dirty area. if (endLine) for (RootInlineBox *line = endLine; line; line = line->nextRootBox()) { line->extractLine(); } // Delete the dirty area. if (startLine) { RenderArena *arena = renderArena(); RootInlineBox *box = startLine; while (box) { RootInlineBox *next = box->nextRootBox(); box->deleteLine(arena); box = next; } startLine = nullptr; } BidiIterator end = start; bool endLineMatched = false; m_firstLine = true; if (!smidpoints) { smidpoints = new QVector; } sNumMidpoints = 0; sCurrMidpoint = 0; sCompactFirstBidiRun = sCompactLastBidiRun = nullptr; sCompactBidiRunCount = 0; previousLineBrokeAtBR = true; int lineCount = 0; bool pagebreakHint = false; int oldPos = 0; BidiIterator oldStart; BidiState oldBidi; const bool pagedMode = canvas()->pagedMode(); while (!end.atEnd()) { start = end; if (endLine && (endLineMatched = matchedEndLine(start, bidi.status, bidi.context, cleanLineStart, cleanLineBidiStatus, cleanLineBidiContext, endLine, endLineYPos))) { break; } lineCount++; betweenMidpoints = false; isLineEmpty = true; pagebreakHint = false; if (pagedMode) { oldPos = m_height; oldStart = start; oldBidi = bidi; } if (lineCount == breakBeforeLine) { m_height = pageTopAfter(oldPos); pagebreakHint = true; } redo_linebreak: end = findNextLineBreak(start, bidi); if (start.atEnd()) { deleteBidiRuns(renderArena()); break; } if (!isLineEmpty) { bidiReorderLine(start, end, bidi); // Now that the runs have been ordered, we create the line boxes. // At the same time we figure out where border/padding/margin should be applied for // inline flow boxes. RootInlineBox *lineBox = nullptr; if (sBidiRunCount) { lineBox = constructLine(start, end); if (lineBox) { lineBox->setEndsWithBreak(previousLineBrokeAtBR); if (pagebreakHint) { lineBox->setAfterPageBreak(true); } // Now we position all of our text runs horizontally. computeHorizontalPositionsForLine(lineBox, bidi); // Now position our text runs vertically. computeVerticalPositionsForLine(lineBox); // SVG if (lineBox->isSVGRootInlineBox()) { - //qDebug() << "svgrootinline box:" << endl; + //qDebug() << "svgrootinline box:"; WebCore::SVGRootInlineBox *svgLineBox = static_cast(lineBox); svgLineBox->computePerCharacterLayoutInformation(); } deleteBidiRuns(renderArena()); if (lineBox->afterPageBreak() && hasFloats() && !pagebreakHint) { start = end = oldStart; bidi = oldBidi; m_height = pageTopAfter(oldPos); deleteLastLineBox(renderArena()); pagebreakHint = true; goto redo_linebreak; } } } if (end == start || (end.obj && end.obj->isBR() && !start.obj->isBR())) { end.increment(&bidi); } else if (end.obj && end.obj->style()->preserveLF() && end.current() == QChar('\n')) { end.increment(&bidi); } if (lineBox) { lineBox->setLineBreakInfo(end.obj, end.pos, bidi.status, bidi.context); } m_firstLine = false; newLine(); } sNumMidpoints = 0; sCurrMidpoint = 0; sCompactFirstBidiRun = sCompactLastBidiRun = nullptr; sCompactBidiRunCount = 0; } startEmbed->deref(); //embed->deref(); if (endLine) { if (endLineMatched) { // Attach all the remaining lines, and then adjust their y-positions as needed. for (RootInlineBox *line = endLine; line; line = line->nextRootBox()) { line->attachLine(); } // Now apply the offset to each line if needed. int delta = m_height - endLineYPos; if (delta) { for (RootInlineBox *line = endLine; line; line = line->nextRootBox()) { line->adjustPosition(0, delta); } } m_height = lastRootBox()->blockHeight(); } else { // Delete all the remaining lines. InlineRunBox *line = endLine; RenderArena *arena = renderArena(); while (line) { InlineRunBox *next = line->nextLineBox(); line->deleteLine(arena); line = next; } } } } sNumMidpoints = 0; sCurrMidpoint = 0; // If we violate widows page-breaking rules, we set a hint and relayout. // Note that the widows rule might still be violated afterwards if the lines have become wider if (canvas()->pagedMode() && containsPageBreak() && breakBeforeLine == 0) { int orphans = 0; int widows = 0; // find breaking line InlineRunBox *lineBox = firstLineBox(); while (lineBox) { if (lineBox->isInlineFlowBox()) { InlineFlowBox *flowBox = static_cast(lineBox); if (flowBox->afterPageBreak()) { break; } } orphans++; lineBox = lineBox->nextLineBox(); } InlineFlowBox *pageBreaker = static_cast(lineBox); if (!pageBreaker) { goto no_break; } // count widows while (lineBox && widows < style()->widows()) { if (lineBox->hasTextChildren()) { widows++; } lineBox = lineBox->nextLineBox(); } // Widows rule broken and more orphans left to use if (widows < style()->widows() && orphans > 0) { // qDebug() << "Widows: " << widows; // Check if we have enough orphans after respecting widows count int newOrphans = orphans - (style()->widows() - widows); if (newOrphans < style()->orphans()) { if (parent()->canClear(this, PageBreakHarder)) { // Relayout to remove incorrect page-break setNeedsPageClear(true); setContainsPageBreak(false); layoutInlineChildren(relayoutChildren, -1); return; } } else { // Set hint and try again layoutInlineChildren(relayoutChildren, newOrphans + 1); return; } } } no_break: // in case we have a float on the last line, it might not be positioned up to now. // This has to be done before adding in the bottom border/padding, or the float will // include the padding incorrectly. -dwh positionNewFloats(); // Now add in the bottom border/padding. m_height += toAdd; // Always make sure this is at least our height. m_overflowHeight = qMax(m_height, m_overflowHeight); // See if any lines spill out of the block. If so, we need to update our overflow width. checkLinesForOverflow(); // See if we have any lines that spill out of our block. If we do, then we will // possibly need to truncate text. if (hasTextOverflow) { checkLinesForTextOverflow(); } #if BIDI_DEBUG > 1 qDebug() << " ------- bidi end " << this << " -------"; #endif //qDebug() << "RenderBlock::layoutInlineChildren time used " << qt.elapsed(); //qDebug() << "height = " << m_height; } RootInlineBox *RenderBlock::determineStartPosition(bool fullLayout, BidiIterator &start, BidiState &bidi) { RootInlineBox *curr = nullptr; RootInlineBox *last = nullptr; RenderObject *startObj = nullptr; int pos = 0; if (fullLayout) { // Nuke all our lines. // ### should be done already at this point... assert( !firstRootBox() ) if (firstRootBox()) { RenderArena *arena = renderArena(); curr = firstRootBox(); while (curr) { RootInlineBox *next = curr->nextRootBox(); curr->deleteLine(arena); curr = next; } assert(!firstLineBox() && !lastLineBox()); } } else { int cnt = 0; for (curr = firstRootBox(); curr && !curr->isDirty(); curr = curr->nextRootBox()) { cnt++; } if (curr) { // qDebug() << "found dirty line at " << cnt; // We have a dirty line. if (RootInlineBox *prevRootBox = curr->prevRootBox()) { // We have a previous line. if (!prevRootBox->endsWithBreak() || (prevRootBox->lineBreakObj()->isText() && prevRootBox->lineBreakPos() >= static_cast(prevRootBox->lineBreakObj())->stringLength())) // The previous line didn't break cleanly or broke at a newline // that has been deleted, so treat it as dirty too. { curr = prevRootBox; } } } else { // qDebug() << "No dirty line found"; // No dirty lines were found. // If the last line didn't break cleanly, treat it as dirty. if (lastRootBox() && !lastRootBox()->endsWithBreak()) { curr = lastRootBox(); } } // If we have no dirty lines, then last is just the last root box. last = curr ? curr->prevRootBox() : lastRootBox(); } m_firstLine = !last; previousLineBrokeAtBR = !last || last->endsWithBreak(); if (last) { m_height = last->blockHeight(); startObj = last->lineBreakObj(); pos = last->lineBreakPos(); bidi.status = last->lineBreakBidiStatus(); } else { startObj = first(this, &bidi, false); } start = BidiIterator(this, startObj, pos); return curr; } RootInlineBox *RenderBlock::determineEndPosition(RootInlineBox *startLine, BidiIterator &cleanLineStart, BidiStatus &cleanLineBidiStatus, BidiContext *cleanLineBidiContext, int &yPos) { RootInlineBox *last = nullptr; if (!startLine) { last = nullptr; } else { for (RootInlineBox *curr = startLine->nextRootBox(); curr; curr = curr->nextRootBox()) { if (curr->isDirty()) { last = nullptr; } else if (!last) { last = curr; } } } if (!last) { return nullptr; } RootInlineBox *prev = last->prevRootBox(); cleanLineStart = BidiIterator(this, prev->lineBreakObj(), prev->lineBreakPos()); cleanLineBidiStatus = prev->lineBreakBidiStatus(); cleanLineBidiContext = prev->lineBreakBidiContext(); yPos = prev->blockHeight(); return last; } bool RenderBlock::matchedEndLine(const BidiIterator &start, const BidiStatus &status, BidiContext *context, const BidiIterator &endLineStart, const BidiStatus &endLineStatus, BidiContext *endLineContext, RootInlineBox *&endLine, int &endYPos) { if (start == endLineStart) { return status == endLineStatus && endLineContext && (*context == *endLineContext); } else { // The first clean line doesn't match, but we can check a handful of following lines to try // to match back up. static int numLines = 8; // The # of lines we're willing to match against. RootInlineBox *line = endLine; for (int i = 0; i < numLines && line; i++, line = line->nextRootBox()) { if (line->lineBreakObj() == start.obj && line->lineBreakPos() == start.pos) { // We have a match. if ((line->lineBreakBidiStatus() != status) || (line->lineBreakBidiContext() != context)) { return false; // ...but the bidi state doesn't match. } RootInlineBox *result = line->nextRootBox(); // Set our yPos to be the block height of endLine. if (result) { endYPos = line->blockHeight(); } // Now delete the lines that we failed to sync. RootInlineBox *boxToDelete = endLine; RenderArena *arena = renderArena(); while (boxToDelete && boxToDelete != result) { RootInlineBox *next = boxToDelete->nextRootBox(); boxToDelete->deleteLine(arena); boxToDelete = next; } endLine = result; return result; } } } return false; } static void setStaticPosition(RenderBlock *p, RenderObject *o, bool *needToSetStaticX = nullptr, bool *needToSetStaticY = nullptr) { // If our original display wasn't an inline type, then we can // determine our static x position now. bool nssx, nssy; bool isInlineType = o->style()->isOriginalDisplayInlineType(); nssx = o->hasStaticX(); if (nssx && o->isBox()) { static_cast(o)->setStaticX(o->parent()->style()->direction() == LTR ? p->borderLeft() + p->paddingLeft() : p->borderRight() + p->paddingRight()); nssx = isInlineType; } // If our original display was an INLINE type, then we can // determine our static y position now. nssy = o->hasStaticY(); if (nssy && o->isBox()) { static_cast(o)->setStaticY(p->height()); nssy = !isInlineType; } if (needToSetStaticX) { *needToSetStaticX = nssx; } if (needToSetStaticY) { *needToSetStaticY = nssy; } } static inline bool requiresLineBox(BidiIterator &it) { if (it.obj->isFloatingOrPositioned()) { return false; } if (it.obj->isInlineFlow()) { return (getBorderPaddingMargin(it.obj, it.endOfInline) != 0); } if (it.obj->isText() && !static_cast(it.obj)->length()) { return false; } if (it.obj->style()->preserveWS() || it.obj->isBR()) { return true; } switch (it.current().unicode()) { case 0x0009: // ASCII tab case 0x000A: // ASCII line feed case 0x000C: // ASCII form feed case 0x0020: // ASCII space case 0x200B: // Zero-width space return false; } return true; } bool RenderBlock::inlineChildNeedsLineBox(RenderObject *inlineObj) // WC: generatesLineBoxesForInlineChild { assert(inlineObj->parent() == this); BidiIterator it(this, inlineObj, 0); while (!it.atEnd() && !requiresLineBox(it)) { it.increment(nullptr, false /*skipInlines*/); } return !it.atEnd(); } void RenderBlock::fitBelowFloats(int widthToFit, int &availableWidth) { assert(widthToFit > availableWidth); int floatBottom; int lastFloatBottom = m_height; int newLineWidth = availableWidth; while (true) { floatBottom = nearestFloatBottom(lastFloatBottom); if (!floatBottom) { break; } newLineWidth = lineWidth(floatBottom); lastFloatBottom = floatBottom; if (newLineWidth >= widthToFit) { break; } } if (newLineWidth > availableWidth) { m_height = lastFloatBottom; availableWidth = newLineWidth; #ifdef DEBUG_LINEBREAKS qDebug() << " new position at " << m_height << " newWidth " << availableWidth; #endif } } BidiIterator RenderBlock::findNextLineBreak(BidiIterator &start, BidiState &bidi) { int width = lineWidth(m_height); int w = 0; // the width from the start of the line up to the currently chosen breaking opportunity int tmpW = 0; // the accumulated width since the last chosen breaking opportunity #ifdef DEBUG_LINEBREAKS qDebug() << "findNextLineBreak: line at " << m_height << " line width " << width; qDebug() << "sol: " << start.obj << " " << start.pos; #endif BidiIterator posStart = start; bool hadPosStart = false; // Skip initial whitespace while (!start.atEnd() && !requiresLineBox(start)) { if (start.obj->isFloating() || start.obj->isPosWithStaticDim()) { RenderObject *o = start.obj; // add to special objects... if (o->isFloating()) { insertFloatingObject(o); positionNewFloats(); width = lineWidth(m_height); } else if (o->isPositioned()) { // add midpoints to have positioned objects at the correct static location // while still skipping initial whitespace. if (!hadPosStart) { hadPosStart = true; posStart = start; // include this object then stop addMidpoint(BidiIterator(nullptr, o, 0)); } else { // start/stop addMidpoint(BidiIterator(nullptr, o, 0)); addMidpoint(BidiIterator(nullptr, o, 0)); } setStaticPosition(this, o); } } start.increment(&bidi, false /*skipInlines*/); } if (hadPosStart && !start.atEnd()) { addMidpoint(start); } if (start.atEnd()) { if (hadPosStart) { start = posStart; posStart.increment(); return posStart; } return start; } // This variable says we have encountered an object after which initial whitespace should be ignored (e.g. InlineFlows at the beginning of a line). // Either we have nothing to do, if there is no whitespace after the object... or we have to enter the ignoringSpaces state. // This dilemma will be resolved when we have a peek at the next object. bool checkShouldIgnoreInitialWhitespace = false; // This variable is used only if whitespace isn't set to PRE, and it tells us whether // or not we are currently ignoring whitespace. bool ignoringSpaces = false; BidiIterator ignoreStart; // This variable tracks whether the very last character we saw was a space. We use // this to detect when we encounter a second space so we know we have to terminate // a run. bool currentCharacterIsSpace = false; // This variable tracks whether there is space still available on the line for floating objects. // Once a floating object does not fit, we wait till next linebreak before positioning more floats. bool floatsFitOnLine = true; RenderObject *trailingSpaceObject = nullptr; BidiIterator lBreak = start; InlineMinMaxIterator it(start.par, start.obj, start.endOfInline, false /*skipPositioned*/); InlineMinMaxIterator lastIt = it; int pos = start.pos; bool prevLineBrokeCleanly = previousLineBrokeAtBR; previousLineBrokeAtBR = false; RenderObject *o = it.current; while (o) { #ifdef DEBUG_LINEBREAKS qDebug() << "new object " << o << " width = " << w << " tmpw = " << tmpW; #endif if (o->isBR()) { if (w + tmpW <= width) { lBreak.obj = o; lBreak.pos = 0; lBreak.endOfInline = it.endOfInline; // A
    always breaks a line, so don't let the line be collapsed // away. Also, the space at the end of a line with a
    does not // get collapsed away. It only does this if the previous line broke // cleanly. Otherwise the
    has no effect on whether the line is // empty or not. if (prevLineBrokeCleanly) { isLineEmpty = false; } trailingSpaceObject = nullptr; previousLineBrokeAtBR = true; if (!isLineEmpty) { // only check the clear status for non-empty lines. EClear clear = o->style()->clear(); if (clear != CNONE) { m_clearStatus = (EClear)(m_clearStatus | clear); } } } goto end; } if (o->isFloatingOrPositioned()) { // add to special objects... if (o->isFloating()) { insertFloatingObject(o); // check if it fits in the current line. // If it does, position it now, otherwise, position // it after moving to next line (in newLine() func) if (floatsFitOnLine && o->width() + o->marginLeft() + o->marginRight() + w + tmpW <= width) { positionNewFloats(); width = lineWidth(m_height); } else { floatsFitOnLine = false; } } else if (o->isPositioned() && o->isPosWithStaticDim()) { bool needToSetStaticX; bool needToSetStaticY; setStaticPosition(this, o, &needToSetStaticX, &needToSetStaticY); // If we're ignoring spaces, we have to stop and include this object and // then start ignoring spaces again. if (needToSetStaticX || needToSetStaticY) { trailingSpaceObject = nullptr; ignoreStart.obj = o; ignoreStart.pos = 0; if (ignoringSpaces) { addMidpoint(ignoreStart); // Stop ignoring spaces. addMidpoint(ignoreStart); // Start ignoring again. } } } } else if (o->isInlineFlow()) { tmpW += getBorderPaddingMargin(o, it.endOfInline); if (isLineEmpty) { isLineEmpty = !tmpW; } if (o->isWordBreak()) { // #### shouldn't be an InlineFlow! w += tmpW; tmpW = 0; lBreak.obj = o; lBreak.pos = 0; lBreak.endOfInline = it.endOfInline; } else if (!it.endOfInline) { // this is the beginning of the line (other non-initial inline flows are handled directly when // incrementing the iterator below). We want to skip initial whitespace as much as possible. checkShouldIgnoreInitialWhitespace = true; } } else if (o->isReplaced() || o->isGlyph()) { EWhiteSpace currWS = o->style()->whiteSpace(); EWhiteSpace lastWS = lastIt.current->style()->whiteSpace(); // WinIE marquees have different whitespace characteristics by default when viewed from // the outside vs. the inside. Text inside is NOWRAP, and so we altered the marquee's // style to reflect this, but we now have to get back to the original whitespace value // for the marquee when checking for line breaking. if (o->isHTMLMarquee() && o->layer() && o->layer()->marquee()) { currWS = o->layer()->marquee()->whiteSpace(); } if (lastIt.current->isHTMLMarquee() && lastIt.current->layer() && lastIt.current->layer()->marquee()) { lastWS = lastIt.current->layer()->marquee()->whiteSpace(); } // Break on replaced elements if either has normal white-space. if (currWS == NORMAL || lastWS == NORMAL) { w += tmpW; tmpW = 0; lBreak.obj = o; lBreak.pos = 0; lBreak.endOfInline = false; } tmpW += o->width() + o->marginLeft() + o->marginRight(); if (ignoringSpaces) { BidiIterator startMid(nullptr, o, 0); addMidpoint(startMid); } isLineEmpty = false; ignoringSpaces = false; currentCharacterIsSpace = false; trailingSpaceObject = nullptr; if (o->isListMarker()) { checkShouldIgnoreInitialWhitespace = true; } } else if (o->isText()) { RenderText *t = static_cast(o); int strlen = t->stringLength(); int len = strlen - pos; QChar *str = t->text(); const Font *f = t->htmlFont(m_firstLine); // proportional font, needs a bit more work. int lastSpace = pos; bool autoWrap = o->style()->autoWrap(); bool preserveWS = o->style()->preserveWS(); bool preserveLF = o->style()->preserveLF(); #ifdef APPLE_CHANGES int wordSpacing = o->style()->wordSpacing(); #endif bool nextIsSoftBreakable = false; bool checkBreakWord = autoWrap && (o->style()->wordWrap() == WWBREAKWORD); while (len) { bool previousCharacterIsSpace = currentCharacterIsSpace; bool isSoftBreakable = nextIsSoftBreakable; nextIsSoftBreakable = false; const QChar c = str[pos]; currentCharacterIsSpace = c.unicode() == ' '; checkBreakWord &= !w; // only break words when no other breaking opportunity exists earlier // on the line (even within the text object we are currently processing) if (preserveWS || !currentCharacterIsSpace) { isLineEmpty = false; } // Check for soft hyphens. Go ahead and ignore them. if (c.unicode() == SOFT_HYPHEN && pos > 0) { nextIsSoftBreakable = true; if (!ignoringSpaces) { // Ignore soft hyphens BidiIterator endMid(nullptr, o, pos - 1); addMidpoint(endMid); // Add the width up to but not including the hyphen. tmpW += t->width(lastSpace, pos - lastSpace, f); // For wrapping text only, include the hyphen. We need to ensure it will fit // on the line if it shows when we break. if (o->style()->autoWrap()) { const QChar softHyphen(0x00ad); tmpW += f->charWidth(&softHyphen, 1, 0, true); } BidiIterator startMid(nullptr, o, pos + 1); addMidpoint(startMid); } pos++; len--; lastSpace = pos; // Cheesy hack to prevent adding in widths of the run twice. continue; } #ifdef APPLE_CHANGES // KDE applies wordspacing differently bool applyWordSpacing = false; #endif if (ignoringSpaces) { // We need to stop ignoring spaces, if we encounter a non-space or // a run that doesn't collapse spaces. if (!currentCharacterIsSpace || preserveWS) { // Stop ignoring spaces and begin at this // new point. ignoringSpaces = false; lastSpace = pos; // e.g., "Foo goo", don't add in any of the ignored spaces. BidiIterator startMid(nullptr, o, pos); addMidpoint(startMid); } else { // Just keep ignoring these spaces. pos++; len--; continue; } } bool isbreakablePosition = (preserveLF && c.unicode() == '\n') || (autoWrap && (isBreakable(str, pos, strlen) || isSoftBreakable)); if (isbreakablePosition || checkBreakWord) { tmpW += t->width(lastSpace, pos - lastSpace, f); #ifdef APPLE_CHANGES applyWordSpacing = (wordSpacing && currentCharacterIsSpace && !previousCharacterIsSpace && !t->containsOnlyWhitespace(pos + 1, strlen - (pos + 1))); #endif #ifdef DEBUG_LINEBREAKS qDebug() << "found space at " << pos << " in string '" << QString(str, strlen).toLatin1().constData() << "' adding " << tmpW << " new width = " << w; #endif if (!w && autoWrap && tmpW > width) { fitBelowFloats(tmpW, width); } if (autoWrap) { if (w + tmpW > width) { if (checkBreakWord && pos) { lBreak.obj = o; lBreak.pos = pos - 1; lBreak.endOfInline = false; } goto end; } else if ((pos > 1 && str[pos - 1].unicode() == SOFT_HYPHEN)) // Subtract the width of the soft hyphen out since we fit on a line. { tmpW -= t->width(pos - 1, 1, f); } } if (preserveLF && (str + pos)->unicode() == '\n') { lBreak.obj = o; lBreak.pos = pos; lBreak.endOfInline = false; #ifdef DEBUG_LINEBREAKS qDebug() << "forced break sol: " << start.obj << " " << start.pos << " end: " << lBreak.obj << " " << lBreak.pos << " width=" << w; #endif return lBreak; } if (autoWrap && isbreakablePosition) { w += tmpW; tmpW = 0; lBreak.obj = o; lBreak.pos = pos; lBreak.endOfInline = false; } lastSpace = pos; #ifdef APPLE_CHANGES if (applyWordSpacing) { w += wordSpacing; } #endif } if (!ignoringSpaces && !preserveWS) { // If we encounter a second space, we need to go ahead and break up this run // and enter a mode where we start collapsing spaces. if (currentCharacterIsSpace && previousCharacterIsSpace) { ignoringSpaces = true; // We just entered a mode where we are ignoring // spaces. Create a midpoint to terminate the run // before the second space. addMidpoint(ignoreStart); lastSpace = pos; } } if (currentCharacterIsSpace && !previousCharacterIsSpace) { ignoreStart.obj = o; ignoreStart.pos = pos; } if (!preserveWS && currentCharacterIsSpace && !ignoringSpaces) { trailingSpaceObject = o; } else if (preserveWS || !currentCharacterIsSpace) { trailingSpaceObject = nullptr; } pos++; len--; } if (!ignoringSpaces) { // We didn't find any space that would be beyond the line |width|. // Lets add to |tmpW| the remaining width since the last space we found. // Before we test this new |tmpW| however, we will have to look ahead to check // if the next object/position can serve as a line breaking opportunity. tmpW += t->width(lastSpace, pos - lastSpace, f); if (checkBreakWord && !w && pos && tmpW > width) { // Avoid doing the costly lookahead for break-word, // since we know we are allowed to break. lBreak.obj = o; lBreak.pos = pos - 1; lBreak.endOfInline = false; goto end; } } } else { KHTMLAssert(false); } InlineMinMaxIterator savedIt = lastIt; lastIt = it; o = it.next(); // Advance the iterator to the next non-inline-flow while (o && o->isInlineFlow() && !o->isWordBreak()) { tmpW += getBorderPaddingMargin(o, it.endOfInline); if (isLineEmpty) { isLineEmpty = !tmpW; } o = it.next(); } // All code below, until the end of the loop, is looking ahead the |it| object we just // advanced to, comparing it to the previous object |lastIt|. if (checkShouldIgnoreInitialWhitespace) { // Check if we should switch to ignoringSpaces state if (!style()->preserveWS() && it.current && it.current->isText()) { const RenderText *rt = static_cast(it.current); if (rt->stringLength() > 0 && (rt->text()[0].category() == QChar::Separator_Space || rt->text()[0] == '\n')) { currentCharacterIsSpace = true; ignoringSpaces = true; BidiIterator endMid(nullptr, lastIt.current, 0); addMidpoint(endMid); } } checkShouldIgnoreInitialWhitespace = false; } bool autoWrap = lastIt.current->style()->autoWrap(); bool canBreak = !lBreak.obj || !lBreak.obj->isInlineFlow() || !lBreak.obj->firstChild(); bool checkForBreak = autoWrap; if (canBreak) { if (!autoWrap && w && w + tmpW > width && lBreak.obj && !lastIt.current->style()->preserveLF()) // ### needs explanation { checkForBreak = true; } else if (it.current && lastIt.current->isText() && it.current->isText() && !it.current->isBR()) { // We are looking ahead the next text object to see if it continues a word started previously, // or is a line-breaking opportunity. if (autoWrap || it.current->style()->autoWrap()) { if (currentCharacterIsSpace) // "s top" // _ ^ { checkForBreak = true; } else { // either "continue" or "s top" // _ ^ _ ^ checkForBreak = false; RenderText *nextText = static_cast(it.current); if (nextText->stringLength() != 0) { QChar c = nextText->text()[0]; // If the next item is a space, then we may try to break. // Otherwise the next text run continues our word (and so it needs to // keep adding to |tmpW|). if (c == ' ' || c == '\t' || (c == '\n' && !it.current->style()->preserveLF())) { checkForBreak = true; } } bool willFitOnLine = (w + tmpW <= width); if (!willFitOnLine && !w) { fitBelowFloats(tmpW, width); willFitOnLine = tmpW <= width; } bool canPlaceOnLine = willFitOnLine || !autoWrap; if (canPlaceOnLine && checkForBreak) { w += tmpW; tmpW = 0; lBreak.obj = it.current; lBreak.pos = 0; lBreak.endOfInline = it.endOfInline; } } } } if (checkForBreak && (w + tmpW > width)) { // if we have floats, try to get below them. if (currentCharacterIsSpace && !ignoringSpaces && !lastIt.current->style()->preserveWS()) { trailingSpaceObject = nullptr; } if (w) { goto end; } fitBelowFloats(tmpW, width); // |width| may have been adjusted because we got shoved down past a float (thus // giving us more room), so we need to retest, and only jump to // the end label if we still don't fit on the line. -dwh if (w + tmpW > width) { it = lastIt; lastIt = savedIt; o = it.current; goto end; } } } if (!lastIt.current->isFloatingOrPositioned() && lastIt.current->isReplaced() && lastIt.current->style()->autoWrap()) { // Go ahead and add in tmpW. w += tmpW; tmpW = 0; lBreak.obj = o; lBreak.pos = 0; lBreak.endOfInline = it.endOfInline; } // Clear out our character space bool, since inline
    s don't collapse whitespace
             // with adjacent inline normal/nowrap spans.
             if (lastIt.current->style()->preserveWS()) {
                 currentCharacterIsSpace = false;
             }
     
             pos = 0;
         }
     
     #ifdef DEBUG_LINEBREAKS
         qDebug() << "end of par, width = " << width << " linewidth = " << w + tmpW;
     #endif
         if (w + tmpW <= width || (lastIt.current && !lastIt.current->style()->autoWrap())) {
             lBreak.obj = nullptr;
             lBreak.pos = 0;
             lBreak.endOfInline = false;
         }
     
     end:
         if (lBreak == start && !lBreak.obj->isBR()) {
             // Having an |lBreak| identical to our |start| at this point means the first suitable
             // break point |it.current| that we found was past |width|, so we jumped to the |end| label
             // before we could set this (overflowing) breaking opportunity. Let's set it now.
             if (style()->whiteSpace() == PRE) {
                 // FIXME: Don't really understand this case.
                 if (pos != 0) {
                     lBreak.obj = o;
                     lBreak.pos = pos - 1;
                     lBreak.endOfInline = it.endOfInline;
                 } else {
                     lBreak.obj = lastIt.current;
                     lBreak.pos = lastIt.current->isText() ? lastIt.current->length() : 0;
                     lBreak.endOfInline = lastIt.endOfInline;
                 }
             } else if (lBreak.obj) {
                 lBreak.obj = o;
                 lBreak.pos = (o && o->isText() ? pos : 0);
                 lBreak.endOfInline = it.endOfInline;
             }
         }
     
         if (hadPosStart) {
             start = posStart;
         }
     
         if (lBreak == start) {
             // make sure we consume at least one char/object.
             lBreak.increment();
         }
     
     #ifdef DEBUG_LINEBREAKS
         qDebug() << "regular break sol: " << start.obj << " " << start.pos << "   end: " << lBreak.obj << " " << lBreak.pos << "   width=" << w;
     #endif
     
         // Sanity check our midpoints.
         checkMidpoints(lBreak);
     
         if (trailingSpaceObject) {
             // This object is either going to be part of the last midpoint, or it is going
             // to be the actual endpoint.  In both cases we just decrease our pos by 1 level to
             // exclude the space, allowing it to - in effect - collapse into the newline.
             if (sNumMidpoints % 2 == 1) {
                 BidiIterator *midpoints = smidpoints->data();
                 midpoints[sNumMidpoints - 1].pos--;
             }
             //else if (lBreak.pos > 0)
             //    lBreak.pos--;
             else if (lBreak.obj == nullptr && trailingSpaceObject->isText()) {
                 // Add a new end midpoint that stops right at the very end.
                 RenderText *text = static_cast(trailingSpaceObject);
                 unsigned pos = text->length() >= 2 ? text->length() - 2 : UINT_MAX;
                 BidiIterator endMid(nullptr, trailingSpaceObject, pos);
                 addMidpoint(endMid);
             }
         }
     
         // We might have made lBreak an iterator that points past the end
         // of the object. Do this adjustment to make it point to the start
         // of the next object instead to avoid confusing the rest of the
         // code.
         if (lBreak.pos > 0) {
             lBreak.pos--;
             lBreak.increment();
         }
     
         if (lBreak.obj && lBreak.pos >= 2 && lBreak.obj->isText()) {
             // For soft hyphens on line breaks, we have to chop out the midpoints that made us
             // ignore the hyphen so that it will render at the end of the line.
             QChar c = static_cast(lBreak.obj)->text()[lBreak.pos - 1];
             if (c.unicode() == SOFT_HYPHEN) {
                 chopMidpointsAt(lBreak.obj, lBreak.pos - 2);
             }
         }
     
         return lBreak;
     }
     
     void RenderBlock::checkLinesForOverflow()
     {
         for (RootInlineBox *curr = static_cast(firstLineBox()); curr; curr = static_cast(curr->nextLineBox())) {
     //         m_overflowLeft = min(curr->leftOverflow(), m_overflowLeft);
             m_overflowTop = qMin(curr->topOverflow(), m_overflowTop);
     //         m_overflowWidth = max(curr->rightOverflow(), m_overflowWidth);
             m_overflowHeight = qMax(curr->bottomOverflow(), m_overflowHeight);
         }
     }
     
     void RenderBlock::deleteEllipsisLineBoxes()
     {
         for (RootInlineBox *curr = firstRootBox(); curr; curr = curr->nextRootBox()) {
             curr->clearTruncation();
         }
     }
     
     void RenderBlock::checkLinesForTextOverflow()
     {
         // Determine the width of the ellipsis using the current font.
         QChar ellipsis = 0x2026; // FIXME: CSS3 says this is configurable, also need to use 0x002E (FULL STOP) if 0x2026 not renderable
         static QString ellipsisStr(ellipsis);
         const Font &firstLineFont = style(true)->htmlFont();
         const Font &font = style()->htmlFont();
         int firstLineEllipsisWidth = firstLineFont.charWidth(&ellipsis, 1, 0, true /*fast algo*/);
         int ellipsisWidth = (font == firstLineFont) ? firstLineEllipsisWidth : font.charWidth(&ellipsis, 1, 0, true /*fast algo*/);
     
         // For LTR text truncation, we want to get the right edge of our padding box, and then we want to see
         // if the right edge of a line box exceeds that.  For RTL, we use the left edge of the padding box and
         // check the left edge of the line box to see if it is less
         // Include the scrollbar for overflow blocks, which means we want to use "contentWidth()"
         bool ltr = style()->direction() == LTR;
         for (RootInlineBox *curr = firstRootBox(); curr; curr = curr->nextRootBox()) {
             int blockEdge = ltr ? rightOffset(curr->yPos()) : leftOffset(curr->yPos());
             int lineBoxEdge = ltr ? curr->xPos() + curr->width() : curr->xPos();
             if ((ltr && lineBoxEdge > blockEdge) || (!ltr && lineBoxEdge < blockEdge)) {
                 // This line spills out of our box in the appropriate direction.  Now we need to see if the line
                 // can be truncated.  In order for truncation to be possible, the line must have sufficient space to
                 // accommodate our truncation string, and no replaced elements (images, tables) can overlap the ellipsis
                 // space.
                 int width = curr == firstRootBox() ? firstLineEllipsisWidth : ellipsisWidth;
                 if (curr->canAccommodateEllipsis(ltr, blockEdge, lineBoxEdge, width)) {
                     curr->placeEllipsis(ellipsisStr, ltr, blockEdge, width);
                 }
             }
         }
     }
     
     // For --enable-final
     #undef BIDI_DEBUG
     #undef DEBUG_LINEBREAKS
     #undef DEBUG_LAYOUT
     
     }
    diff --git a/src/rendering/render_block.cpp b/src/rendering/render_block.cpp
    index 805ddd0..c0aadb1 100644
    --- a/src/rendering/render_block.cpp
    +++ b/src/rendering/render_block.cpp
    @@ -1,3766 +1,3765 @@
     /*
      * This file is part of the render object implementation for KHTML.
      *
      * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org)
      *           (C) 1999-2003 Antti Koivisto (koivisto@kde.org)
      *           (C) 2002-2003 Dirk Mueller (mueller@kde.org)
      *           (C) 2003-2009 Apple Computer, Inc.
      *           (C) 2004-2009 Germain Garand (germain@ebooksfrance.org)
      *           (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com)
      *           (C) 2006 Charles Samuels (charles@kde.org)
      *
      * This library is free software; you can redistribute it and/or
      * modify it under the terms of the GNU Library General Public
      * License as published by the Free Software Foundation; either
      * version 2 of the License, or (at your option) any later version.
      *
      * This library is distributed in the hope that it will be useful,
      * but WITHOUT ANY WARRANTY; without even the implied warranty of
      * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      * Library General Public License for more details.
      *
      * You should have received a copy of the GNU Library General Public License
      * along with this library; see the file COPYING.LIB.  If not, write to
      * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
      * Boston, MA 02110-1301, USA.
      *
      */
     
     //#define DEBUG
     //#define DEBUG_LAYOUT
     //#define BOX_DEBUG
     //#define FLOAT_DEBUG
     //#define PAGE_DEBUG
     
     #include "render_block.h"
     
     #include 
     #include 
     #include "render_text.h"
     #include "render_table.h"
     #include "render_canvas.h"
     #include "render_layer.h"
     #include "rendering/render_position.h"
     
     #include 
     #include 
     #include 
     #include 
     
     #include 
     #include 
     
     using namespace DOM;
     
     namespace khtml
     {
     
     // -------------------------------------------------------------------------------------------------------
     
     // Our MarginInfo state used when laying out block children.
     RenderBlock::MarginInfo::MarginInfo(RenderBlock *block, int top, int bottom)
     {
         // Whether or not we can collapse our own margins with our children.  We don't do this
         // if we had any border/padding (obviously), if we're the root or HTML elements, or if
         // we're positioned, floating, a table cell.
         m_canCollapseWithChildren = !block->isCanvas() && !block->isRoot() && !block->isPositioned() &&
                                     !block->isFloating() && !block->isTableCell() && !block->hasOverflowClip() && !block->isInlineBlockOrInlineTable();
     
         m_canCollapseTopWithChildren = m_canCollapseWithChildren && (top == 0) /*&& block->style()->marginTopCollapse() != MSEPARATE */;
     
         // If any height other than auto is specified in CSS, then we don't collapse our bottom
         // margins with our children's margins.  To do otherwise would be to risk odd visual
         // effects when the children overflow out of the parent block and yet still collapse
         // with it.  We also don't collapse if we have any bottom border/padding.
         m_canCollapseBottomWithChildren = m_canCollapseWithChildren && (bottom == 0) &&
                                           (block->style()->height().isAuto() && block->style()->height().isZero()) /*&& block->style()->marginBottomCollapse() != MSEPARATE*/;
     
         m_quirkContainer = block->isTableCell() || block->isBody() /*|| block->style()->marginTopCollapse() == MDISCARD ||
             block->style()->marginBottomCollapse() == MDISCARD*/;
     
         m_atTopOfBlock = true;
         m_atBottomOfBlock = false;
     
         m_posMargin = m_canCollapseTopWithChildren ? block->maxTopMargin(true) : 0;
         m_negMargin = m_canCollapseTopWithChildren ? block->maxTopMargin(false) : 0;
     
         m_selfCollapsingBlockClearedFloat = false;
     
         m_topQuirk = m_bottomQuirk = m_determinedTopQuirk = false;
     }
     
     // -------------------------------------------------------------------------------------------------------
     
     RenderBlock::RenderBlock(DOM::NodeImpl *node)
         : RenderFlow(node)
     {
         m_childrenInline = true;
         m_floatingObjects = nullptr;
         m_positionedObjects = nullptr;
         m_firstLine = false;
         m_avoidPageBreak = false;
         m_clearStatus = CNONE;
         m_maxTopPosMargin = m_maxTopNegMargin = m_maxBottomPosMargin = m_maxBottomNegMargin = 0;
         m_topMarginQuirk = m_bottomMarginQuirk = false;
         m_overflowHeight = m_overflowWidth = 0;
         m_overflowLeft = m_overflowTop = 0;
     }
     
     RenderBlock::~RenderBlock()
     {
         if (m_floatingObjects) {
             QListIterator it(*m_floatingObjects);
             while (it.hasNext()) {
                 delete it.next();
             }
         }
         delete m_floatingObjects;
         delete m_positionedObjects;
     }
     
     void RenderBlock::setStyle(RenderStyle *_style)
     {
         setReplaced(_style->isDisplayReplacedType());
     
         RenderFlow::setStyle(_style);
     
         // ### we could save this call when the change only affected
         // non inherited properties
         RenderObject *child = firstChild();
         while (child != nullptr) {
             if (child->isAnonymousBlock()) {
                 RenderStyle *newStyle = new RenderStyle();
                 newStyle->inheritFrom(style());
                 newStyle->setDisplay(BLOCK);
                 child->setStyle(newStyle);
             }
             child = child->nextSibling();
         }
     
         if (attached()) {
             // Update generated content and ::inside
             updateReplacedContent();
             // Update pseudos for :before and :after
             updatePseudoChildren();
         }
     
         // handled by close() during parsing
         // ### remove close move upto updatePseudo
         if (!document()->parsing()) {
             updateFirstLetter();
         }
     }
     
     // Attach handles initial setStyle that requires parent nodes
     void RenderBlock::attach()
     {
         RenderFlow::attach();
     
         updateReplacedContent();
         updatePseudoChildren();
     }
     
     static inline bool isFirstLetterPunct(const QChar *c)
     {
         // CSS2.1/3 definition for ::first-letter doesn't include Pc or Pd.
         if (c->isPunct()) {
             QChar::Category cat = c->category();
             return cat != QChar::Punctuation_Connector &&
                    cat != QChar::Punctuation_Dash;
         }
         return false;
     }
     
     void RenderBlock::updateFirstLetter()
     {
         // Only blocks with inline-children can generate a first-letter
         if (!childrenInline() || !firstChild()) {
             return;
         }
     
         // Don't recurse
         if (style()->styleType() == RenderStyle::FIRST_LETTER) {
             return;
         }
     
         // The first-letter style is inheritable.
         RenderStyle *pseudoStyle = style()->getPseudoStyle(RenderStyle::FIRST_LETTER);
         RenderObject *o = this;
         while (o && !pseudoStyle) {
             // ### We should ignore empty preceding siblings
             if (o->parent() && o->parent()->firstChild() == this) {
                 o = o->parent();
             } else {
                 break;
             }
             pseudoStyle = o->style()->getPseudoStyle(RenderStyle::FIRST_LETTER);
         };
     
         // FIXME: Currently we don't delete first-letters, this is
         // handled instead in NodeImpl::diff by issuing Detach on first-letter changes.
         if (!pseudoStyle) {
             return;
         }
     
         // Drill into inlines looking for our first text child.
         RenderObject *firstText = firstChild();
         while (firstText && firstText->needsLayout() && !firstText->isFloating() && !firstText->isRenderBlock() && !firstText->isReplaced() && !firstText->isText())
             // ### We should skip first children with only white-space and punctuation
         {
             firstText = firstText->firstChild();
         }
     
         if (firstText && firstText->isText() && !firstText->isBR()) {
             RenderObject *firstLetterObject = nullptr;
             // Find the old first-letter
             if (firstText->parent()->style()->styleType() == RenderStyle::FIRST_LETTER) {
                 firstLetterObject = firstText->parent();
             }
     
             // Force inline display (except for floating first-letters)
             pseudoStyle->setDisplay(pseudoStyle->isFloating() ? BLOCK : INLINE);
             pseudoStyle->setPosition(PSTATIC);   // CSS2 says first-letter can't be positioned.
     
             if (firstLetterObject != nullptr) {
                 firstLetterObject->setStyle(pseudoStyle);
                 RenderStyle *newStyle = new RenderStyle();
                 newStyle->inheritFrom(pseudoStyle);
                 firstText->setStyle(newStyle);
                 return;
             }
     
             RenderText *textObj = static_cast(firstText);
             RenderObject *firstLetterContainer = firstText->parent();
     
             firstLetterObject = RenderFlow::createFlow(node(), pseudoStyle, renderArena());
             firstLetterObject->setIsAnonymous(true);
             firstLetterContainer->addChild(firstLetterObject, firstLetterContainer->firstChild());
     
             // if this object is the result of a :begin, then the text may have not been
             // generated yet if it is a counter
             if (textObj->recalcMinMax()) {
                 textObj->recalcMinMaxWidths();
             }
     
             // The original string is going to be either a generated content string or a DOM node's
             // string.  We want the original string before it got transformed in case first-letter has
             // no text-transform or a different text-transform applied to it.
             DOMStringImpl *oldText = textObj->originalString();
             if (!oldText) {
                 oldText = textObj->string();
             }
             // ### In theory a first-letter can stretch across multiple text objects, if they only contain
             // punctuation and white-space
             if (oldText->l >= 1) {
                 oldText->ref();
                 // begin: we need skip leading whitespace so that RenderBlock::findNextLineBreak
                 // won't think we're continuing from a previous run
                 unsigned int begin = 0; // the position that first-letter begins
                 unsigned int length = 0; // the position that "the rest" begins
                 while (length < oldText->l && (oldText->s + length)->isSpace()) {
                     length++;
                 }
                 begin = length;
                 while (length < oldText->l &&
                         (isFirstLetterPunct(oldText->s + length) || (oldText->s + length)->isSpace())) {
                     length++;
                 }
                 if (length < oldText->l &&
                         !((oldText->s + length)->isSpace() || isFirstLetterPunct(oldText->s + length))) {
                     length++;
                 }
                 while (length < oldText->l && isFirstLetterPunct(oldText->s + length)) {
                     length++;
                 }
     
                 // we need to generated a remainingText object even if no text is left
                 // because it holds the place and style for the old textObj
                 RenderTextFragment *remainingText =
                     new(renderArena()) RenderTextFragment(textObj->node(), oldText, length, oldText->l - length);
                 remainingText->setIsAnonymous(textObj->isAnonymous());
                 remainingText->setStyle(textObj->style());
                 if (remainingText->element()) {
                     remainingText->element()->setRenderer(remainingText);
                 }
     
                 RenderObject *nextObj = textObj->nextSibling();
                 textObj->detach();
                 firstLetterContainer->addChild(remainingText, nextObj);
     
                 RenderTextFragment *letter =
                     new(renderArena()) RenderTextFragment(remainingText->node(), oldText, begin, length - begin);
                 letter->setIsAnonymous(remainingText->isAnonymous());
                 RenderStyle *newStyle = new RenderStyle();
                 newStyle->inheritFrom(pseudoStyle);
                 letter->setStyle(newStyle);
                 firstLetterObject->addChild(letter);
                 oldText->deref();
     
                 remainingText->setFirstLetter(letter);
             }
             firstLetterObject->close();
         }
     }
     
     void RenderBlock::addChildToFlow(RenderObject *newChild, RenderObject *beforeChild)
     {
         // Make sure we don't append things after :after-generated content if we have it.
         if (!beforeChild && lastChild() && lastChild()->style()->styleType() == RenderStyle::AFTER) {
             beforeChild = lastChild();
         }
     
         bool madeBoxesNonInline = false;
     
         // If the requested beforeChild is not one of our children, then this is most likely because
         // there is an anonymous block box within this object that contains the beforeChild. So
         // just insert the child into the anonymous block box instead of here. This may also be
         // needed in cases of things like anonymous tables.
         if (beforeChild && beforeChild->parent() != this) {
     
             KHTMLAssert(beforeChild->parent());
     
             // In the special case where we are prepending a block-level element before
             // something contained inside an anonymous block, we can just prepend it before
             // the anonymous block.
             if (!newChild->isInline() && beforeChild->parent()->isAnonymousBlock() &&
                     beforeChild->parent()->parent() == this &&
                     beforeChild->parent()->firstChild() == beforeChild) {
                 return addChildToFlow(newChild, beforeChild->parent());
             }
     
             // Otherwise find our kid inside which the beforeChild is, and delegate to it.
             // This may be many levels deep due to anonymous tables, table sections, etc.
             RenderObject *responsible = beforeChild->parent();
             while (responsible->parent() != this) {
                 responsible = responsible->parent();
             }
     
             return responsible->addChild(newChild, beforeChild);
         }
     
         // prevent elements that haven't received a layout yet from getting painted by pushing
         // them far above the top of the page
         if (!newChild->isInline()) {
             newChild->setPos(newChild->xPos(), -500000);
         }
     
         // A block has to either have all of its children inline, or all of its children as blocks.
         // So, if our children are currently inline and a block child has to be inserted, we move all our
         // inline children into anonymous block boxes
         if (m_childrenInline && !newChild->isInline() && !newChild->isFloatingOrPositioned()) {
             // This is a block with inline content. Wrap the inline content in anonymous blocks.
             makeChildrenNonInline(beforeChild);
             madeBoxesNonInline = true;
     
             if (beforeChild && beforeChild->parent() != this) {
                 beforeChild = beforeChild->parent();
                 KHTMLAssert(beforeChild->isAnonymousBlock());
                 KHTMLAssert(beforeChild->parent() == this);
             }
         } else if (!m_childrenInline && !newChild->isFloatingOrPositioned()) {
             // If we're inserting an inline child but all of our children are blocks, then we have to make sure
             // it is put into an anomyous block box. We try to use an existing anonymous box if possible, otherwise
             // a new one is created and inserted into our list of children in the appropriate position.
             if (newChild->isInline()) {
                 if (beforeChild) {
                     if (beforeChild->previousSibling() && beforeChild->previousSibling()->isAnonymousBlock()) {
                         beforeChild->previousSibling()->addChild(newChild);
                         return;
                     }
                 } else {
                     if (m_last && m_last->isAnonymousBlock()) {
                         m_last->addChild(newChild);
                         return;
                     }
                 }
     
                 // no suitable existing anonymous box - create a new one
                 RenderBlock *newBox = createAnonymousBlock();
                 RenderBox::addChild(newBox, beforeChild);
                 newBox->addChild(newChild);
     
                 //the above may actually destroy newBox in case an anonymous
                 //table got created, and made the anonymous block redundant.
                 //so look up what to hide indirectly.
                 RenderObject *toHide = newChild;
                 while (toHide->parent() != this) {
                     toHide = toHide->parent();
                 }
     
                 toHide->setPos(toHide->xPos(), -500000);
                 return;
             } else {
                 // We are adding another block child... if the current last child is an anonymous box
                 // then it needs to be closed.
                 // ### get rid of the closing thing altogether this will only work during initial parsing
                 if (lastChild() && lastChild()->isAnonymous()) {
                     lastChild()->close();
                 }
             }
         }
     
         RenderBox::addChild(newChild, beforeChild);
         // ### care about aligned stuff
     
         if (madeBoxesNonInline && isAnonymousBlock()) {
             parent()->removeSuperfluousAnonymousBlockChild(this);
         }
         // we might be deleted now
     }
     
     static void getInlineRun(RenderObject *start, RenderObject *stop,
                              RenderObject *&inlineRunStart,
                              RenderObject *&inlineRunEnd)
     {
         // Beginning at |start| we find the largest contiguous run of inlines that
         // we can.  We denote the run with start and end points, |inlineRunStart|
         // and |inlineRunEnd|.  Note that these two values may be the same if
         // we encounter only one inline.
         //
         // We skip any non-inlines we encounter as long as we haven't found any
         // inlines yet.
         //
         //
         // |stop| indicates a non-inclusive stop point.  Regardless of whether |stop|
         // is inline or not, we will not include it in a run with inlines before it.  It's as though we encountered
         // a non-inline.
     
         RenderObject *curr = start;
         bool sawInline;
         do {
             while (curr && !(curr->isInline() || curr->isFloatingOrPositioned())) {
                 curr = curr->nextSibling();
             }
     
             inlineRunStart = inlineRunEnd = curr;
     
             if (!curr) {
                 return;    // No more inline children to be found.
             }
     
             sawInline = curr->isInline();
     
             curr = curr->nextSibling();
             while (curr && (curr->isInline() || curr->isFloatingOrPositioned()) && (curr != stop)) {
                 inlineRunEnd = curr;
                 if (curr->isInline()) {
                     sawInline = true;
                 }
                 curr = curr->nextSibling();
             }
         } while (!sawInline);
     
     }
     
     void RenderBlock::deleteLineBoxTree()
     {
         InlineFlowBox *line = m_firstLineBox;
         InlineFlowBox *nextLine;
         while (line) {
             nextLine = line->nextFlowBox();
             line->deleteLine(renderArena());
             line = nextLine;
         }
         m_firstLineBox = m_lastLineBox = nullptr;
     }
     
     short RenderBlock::baselinePosition(bool firstLine) const
     {
         // CSS2.1-10.8.1 "The baseline of an 'inline-block' is the baseline of its last line box
         // in the normal flow, unless it has either no in-flow line boxes or if its 'overflow'
         // property has a computed value other than 'visible', in which case the baseline is the bottom margin edge."
     
         if (isReplaced() && !hasOverflowClip() && !needsLayout()) {
             int res = getBaselineOfLastLineBox();
             if (res != -1) {
                 return  res + marginTop();
             }
         }
         return RenderBox::baselinePosition(firstLine);
     }
     
     int RenderBlock::getBaselineOfLastLineBox() const
     {
         if (!isBlockFlow()) {
             return -1;
         }
     
         if (childrenInline()) {
     //       if (!firstLineBox() && hasLineIfEmpty())
     //            return RenderFlow::baselinePosition(true) + borderTop() + paddingTop();
             if (lastLineBox()) {
                 return lastLineBox()->yPos() + lastLineBox()->baseline();
             }
             return -1;
         } else {
     //        bool haveNormalFlowChild = false;
             for (RenderObject *curr = lastChild(); curr; curr = curr->previousSibling()) {
                 if (!curr->isFloatingOrPositioned() && curr->isBlockFlow()) {
     //                haveNormalFlowChild = true;
                     int result = static_cast(curr)->getBaselineOfLastLineBox();
                     if (result != -1) {
                         return curr->yPos() + result;    // Translate to our coordinate space.
                     }
                 }
             }
     //        if (!haveNormalFlowChild && isRenderButton()) // hasLineIfEmpty()
     //            return RenderFlow::baselinePosition(true) + borderTop() + paddingTop();
         }
     
         return -1;
     }
     
     void RenderBlock::makeChildrenNonInline(RenderObject *insertionPoint)
     {
         // makeChildrenNonInline takes a block whose children are *all* inline and it
         // makes sure that inline children are coalesced under anonymous
         // blocks.  If |insertionPoint| is defined, then it represents the insertion point for
         // the new block child that is causing us to have to wrap all the inlines.  This
         // means that we cannot coalesce inlines before |insertionPoint| with inlines following
         // |insertionPoint|, because the new child is going to be inserted in between the inlines,
         // splitting them.
         KHTMLAssert(isReplacedBlock() || !isInline());
         KHTMLAssert(!insertionPoint || insertionPoint->parent() == this);
     
         deleteLineBoxTree();
     
         m_childrenInline = false;
     
         RenderObject *child = firstChild();
     
         while (child) {
             RenderObject *inlineRunStart, *inlineRunEnd;
             getInlineRun(child, insertionPoint, inlineRunStart, inlineRunEnd);
     
             if (!inlineRunStart) {
                 break;
             }
     
             child = inlineRunEnd->nextSibling();
     
             RenderBlock *box = createAnonymousBlock();
             insertChildNode(box, inlineRunStart);
             RenderObject *o = inlineRunStart;
             while (o != inlineRunEnd) {
                 RenderObject *no = o;
                 o = no->nextSibling();
                 box->appendChildNode(removeChildNode(no));
             }
             box->appendChildNode(removeChildNode(inlineRunEnd));
             box->close();
             box->setPos(box->xPos(), -500000);
         }
     }
     
     void RenderBlock::makePageBreakAvoidBlocks()
     {
         KHTMLAssert(!childrenInline());
         KHTMLAssert(canvas()->pagedMode());
     
         RenderObject *breakAfter = firstChild();
         RenderObject *breakBefore = breakAfter ? breakAfter->nextSibling() : nullptr;
     
         RenderBlock *pageRun = nullptr;
     
         // ### Should follow margin-collapsing rules, skipping self-collapsing blocks
         // and exporting page-breaks from first/last child when collapsing with parent margin.
         while (breakAfter) {
             if (breakAfter->isRenderBlock() && !breakAfter->childrenInline()) {
                 static_cast(breakAfter)->makePageBreakAvoidBlocks();
             }
             EPageBreak pbafter = breakAfter->style()->pageBreakAfter();
             EPageBreak pbbefore = breakBefore ? breakBefore->style()->pageBreakBefore() : PBALWAYS;
             if ((pbafter == PBAVOID && pbbefore == PBAVOID) ||
                     (pbafter == PBAVOID && pbbefore == PBAUTO) ||
                     (pbafter == PBAUTO && pbbefore == PBAVOID)) {
                 if (!pageRun) {
                     pageRun = createAnonymousBlock();
                     pageRun->m_avoidPageBreak = true;
                     pageRun->setChildrenInline(false);
                 }
                 pageRun->appendChildNode(removeChildNode(breakAfter));
             } else {
                 if (pageRun) {
                     pageRun->appendChildNode(removeChildNode(breakAfter));
                     pageRun->close();
                     insertChildNode(pageRun, breakBefore);
                     pageRun = nullptr;
                 }
             }
             breakAfter = breakBefore;
             breakBefore = breakBefore ? breakBefore->nextSibling() : nullptr;
         }
     
         // recurse into positioned block children as well.
         if (m_positionedObjects) {
             RenderObject *obj;
             QListIterator it(*m_positionedObjects);
             while (it.hasNext()) {
                 obj = it.next();
                 if (obj->isRenderBlock() && !obj->childrenInline()) {
                     static_cast(obj)->makePageBreakAvoidBlocks();
                 }
             }
         }
     
         // recurse into floating block children.
         if (m_floatingObjects) {
             FloatingObject *obj;
             QListIterator it(*m_floatingObjects);
             while (it.hasNext()) {
                 obj = it.next();
                 if (obj->node->isRenderBlock() && !obj->node->childrenInline()) {
                     static_cast(obj->node)->makePageBreakAvoidBlocks();
                 }
             }
         }
     }
     
     void RenderBlock::removeChild(RenderObject *oldChild)
     {
         // If this child is a block, and if our previous and next siblings are
         // both anonymous blocks with inline content, then we can go ahead and
         // fold the inline content back together.
         RenderObject *prev = oldChild->previousSibling();
         RenderObject *next = oldChild->nextSibling();
         RenderObject *lc = nullptr;
         bool mergedBlocks = false;
         bool checkContinuationMerge = false;
         if (!documentBeingDestroyed() && !isInline() && !oldChild->isInline() && !oldChild->continuation()) {
             if (prev && prev->isAnonymousBlock() && prev->childrenInline() &&
                     next && next->isAnonymousBlock() && next->childrenInline()) {
                 // Take all the children out of the |next| block and put them in
                 // the |prev| block.
                 RenderObject *o = next->firstChild();
                 while (o) {
                     RenderObject *no = o;
                     o = no->nextSibling();
                     prev->appendChildNode(next->removeChildNode(no));
                 }
     
                 // Detach the now-empty block.
                 static_cast(next)->deleteLineBoxTree();
                 next->detach();
     
                 mergedBlocks = true;
             }
     
             // Check if there are continuations we could merge
             checkContinuationMerge = (mergedBlocks || (!prev && !next)) && continuation() && isAnonymousBlock() && continuation()->isRenderInline() &&
                                      previousSibling() && previousSibling()->isAnonymousBlock() && (lc = previousSibling()->lastChild());
     
             if (checkContinuationMerge) {
                 while (lc->lastChild() && lc->continuation()) {
                     lc = lc->lastChild();
                 }
                 checkContinuationMerge = lc->isRenderInline() && lc->continuation() && (lc->continuation() == this);
             }
             if (checkContinuationMerge) {
                 RenderObject *prev = lc->parent();
                 RenderObject *cont = continuation()->parent();
                 while (prev && cont) {
                     if (prev == previousSibling() && cont == nextSibling() && cont->isAnonymousBlock()) {
                         break;
                     }
                     if (!prev->continuation() || prev->continuation() != cont) {
                         checkContinuationMerge = false;
                         break;
                     }
                     prev = prev->parent();
                     cont = cont->parent();
                 }
             }
         }
     
         RenderFlow::removeChild(oldChild);
     
         if (mergedBlocks && prev && !prev->previousSibling() && !prev->nextSibling()) {
             // The remerge has knocked us down to containing only a single anonymous
             // box.  We can go ahead and pull the content right back up into our
             // box.
             RenderBlock *anonBlock = static_cast(prev);
             m_childrenInline = true;
             RenderObject *o = anonBlock->firstChild();
             while (o) {
                 RenderObject *no = o;
                 o = no->nextSibling();
                 appendChildNode(anonBlock->removeChildNode(no));
             }
     
             // Detach the now-empty block.
             anonBlock->deleteLineBoxTree();
             anonBlock->detach();
         }
         if (checkContinuationMerge && ((!prev && !next) || m_childrenInline)) {
             // |oldChild| was a block that split an inline into continuations.
             // Now that we only have inline content left, we may merge back those continuations
             // into a single inline.
             assert(lc->isRenderInline());
             RenderFlow *prev = static_cast(lc);
             while (RenderFlow *next = prev->continuation()) {
                 RenderObject *o = next->firstChild();
                 while (o) {
                     RenderObject *no = o;
                     o = no->nextSibling();
                     prev->appendChildNode(next->removeChildNode(no));
                 }
                 prev->setContinuation(next->continuation());
                 next->setContinuation(nullptr);
                 if (next != this) {
                     next->detach();
                     prev = static_cast(prev->parent());
                     assert(!prev || prev->isRenderInline() || prev->isRenderBlock());
                 }
             }
             assert(nextSibling() && nextSibling()->isAnonymousBlock());
             if (!nextSibling()->firstChild()) {
                 static_cast(nextSibling())->deleteLineBoxTree();
                 nextSibling()->detach();
             }
             deleteLineBoxTree();
             detach();
         }
     }
     
     bool RenderBlock::isSelfCollapsingBlock() const
     {
         // We are not self-collapsing if we
         // (a) have a non-zero height according to layout (an optimization to avoid wasting time)
         // (b) are a table,
         // (c) have border/padding,
         // (d) have a min-height
         if (m_height > 0 ||
                 isTable() || (borderBottom() + paddingBottom() + borderTop() + paddingTop()) != 0 ||
                 style()->minHeight().isPositive()) {
             return false;
         }
     
         bool hasAutoHeight = style()->height().isAuto();
         if (style()->height().isPercent() && !style()->htmlHacks()) {
             hasAutoHeight = true;
             for (RenderBlock *cb = containingBlock(); !cb->isCanvas(); cb = cb->containingBlock()) {
                 if (cb->style()->height().isFixed() || cb->isTableCell()) {
                     hasAutoHeight = false;
                 }
             }
         }
     
         // If the height is 0 or auto, then whether or not we are a self-collapsing block depends
         // on whether we have content that is all self-collapsing or not.
         if (hasAutoHeight || ((style()->height().isFixed() || style()->height().isPercent()) && style()->height().isZero())) {
             // If the block has inline children, see if we generated any line boxes.  If we have any
             // line boxes, then we can't be self-collapsing, since we have content.
             if (childrenInline()) {
                 return !firstLineBox();
             }
     
             // Whether or not we collapse is dependent on whether all our normal flow children
             // are also self-collapsing.
             for (RenderObject *child = firstChild(); child; child = child->nextSibling()) {
                 if (child->isFloatingOrPositioned()) {
                     continue;
                 }
                 if (!child->isSelfCollapsingBlock()) {
                     return false;
                 }
             }
             return true;
         }
         return false;
     }
     
     void RenderBlock::layout()
     {
         // Table cells call layoutBlock directly, so don't add any logic here.  Put code into
         // layoutBlock().
         layoutBlock(false);
     }
     
     void RenderBlock::layoutBlock(bool relayoutChildren)
     {
         if (isInline() && !isReplacedBlock()) {
             setNeedsLayout(false);
             return;
         }
         //    qDebug() << renderName() << " " << this << "::layoutBlock() start";
         //     QTime t;
         //     t.start();
         KHTMLAssert(needsLayout());
         KHTMLAssert(minMaxKnown());
     
         if (canvas()->pagedMode()) {
             relayoutChildren = true;
         }
     
         if (markedForRepaint()) {
             repaintDuringLayout();
             setMarkedForRepaint(false);
         }
     
         if (!relayoutChildren && posChildNeedsLayout() && !normalChildNeedsLayout() && !selfNeedsLayout()) {
             // All we have to is lay out our positioned objects.
             layoutPositionedObjects(relayoutChildren);
             if (hasOverflowClip()) {
                 m_layer->checkScrollbarsAfterLayout();
             }
             setNeedsLayout(false);
             return;
         }
     
         int oldWidth = m_width;
     
         calcWidth();
         m_overflowWidth = m_width;
         m_overflowLeft = 0;
         if (style()->direction() == LTR) {
             int cw = 0;
             if (style()->textIndent().isPercent()) {
                 cw = containingBlock()->contentWidth();
             }
             m_overflowLeft = qMin(0, style()->textIndent().minWidth(cw));
         }
     
         if (oldWidth != m_width) {
             relayoutChildren = true;
         }
     
         //     qDebug() << floatingObjects << "," << oldWidth << ","
         //                     << m_width << ","<< needsLayout() << "," << isAnonymousBox() << ","
    -    //                     << isPositioned() << endl;
    +    //                     << isPositioned();
     
     #ifdef DEBUG_LAYOUT
         qDebug() << renderName() << "(RenderBlock) " << this << " ::layout() width=" << m_width << ", needsLayout=" << needsLayout();
         if (containingBlock() == static_cast(this)) {
             qDebug() << renderName() << ": containingBlock == this";
         }
     #endif
     
         clearFloats();
     
         int previousHeight = m_height;
         m_height = 0;
         m_overflowHeight = 0;
         m_clearStatus = CNONE;
     
         // We use four values, maxTopPos, maxPosNeg, maxBottomPos, and maxBottomNeg, to track
         // our current maximal positive and negative margins.  These values are used when we
         // are collapsed with adjacent blocks, so for example, if you have block A and B
         // collapsing together, then you'd take the maximal positive margin from both A and B
         // and subtract it from the maximal negative margin from both A and B to get the
         // true collapsed margin.  This algorithm is recursive, so when we finish layout()
         // our block knows its current maximal positive/negative values.
         //
         // Start out by setting our margin values to our current margins.  Table cells have
         // no margins, so we don't fill in the values for table cells.
         if (!isTableCell()) {
             initMaxMarginValues();
     
             m_topMarginQuirk = style()->marginTop().isQuirk();
             m_bottomMarginQuirk = style()->marginBottom().isQuirk();
     
             if (element() && element()->id() == ID_FORM && static_cast(element())->isMalformed())
                 // See if this form is malformed (i.e., unclosed). If so, don't give the form
                 // a bottom margin.
             {
                 m_maxBottomPosMargin = m_maxBottomNegMargin = 0;
             }
         }
     
         if (scrollsOverflow() && m_layer) {
             // For overflow:scroll blocks, ensure we have both scrollbars in place always.
             if (style()->overflowX() == OSCROLL) {
                 m_layer->showScrollbar(Qt::Horizontal, true);
             }
             if (style()->overflowY() == OSCROLL) {
                 m_layer->showScrollbar(Qt::Vertical, true);
             }
         }
     
         setContainsPageBreak(false);
     
         if (childrenInline()) {
             layoutInlineChildren(relayoutChildren);
         } else {
             layoutBlockChildren(relayoutChildren);
         }
     
         // Expand our intrinsic height to encompass floats.
         int toAdd = borderBottom() + paddingBottom();
         if (m_layer && scrollsOverflowX() && style()->height().isAuto()) {
             toAdd += m_layer->horizontalScrollbarHeight();
         }
         if (floatBottom() + toAdd > m_height && (isFloatingOrPositioned() || flowAroundFloats())) {
             m_overflowHeight = m_height = floatBottom() + toAdd;
         }
     
         int oldHeight = m_height;
         calcHeight();
         if (oldHeight != m_height) {
             m_overflowHeight -= toAdd;
             if (m_layer && scrollsOverflowY()) {
                 // overflow-height only includes padding-bottom when it scrolls
                 m_overflowHeight += paddingBottom();
             }
             // If the block got expanded in size, then increase our overflowheight to match.
             if (m_overflowHeight < m_height) {
                 m_overflowHeight = m_height;
             }
         }
         if (previousHeight != m_height) {
             relayoutChildren = true;
         }
     
         if (isTableCell()) {
             // Table cells need to grow to accommodate both overhanging floats and
             // blocks that have overflowed content.
             // Check for an overhanging float first.
             // FIXME: This needs to look at the last flow, not the last child.
             if (lastChild() && lastChild()->hasOverhangingFloats() && !lastChild()->hasOverflowClip()) {
                 KHTMLAssert(lastChild()->isRenderBlock());
                 m_height = lastChild()->yPos() + static_cast(lastChild())->floatBottom();
                 m_height += borderBottom() + paddingBottom();
             }
     
             if (m_overflowHeight > m_height && !hasOverflowClip()) {
                 m_height = m_overflowHeight + borderBottom() + paddingBottom();
             }
         }
     
         if (hasOverhangingFloats() && ((isFloating() && style()->height().isAuto()) || isTableCell())) {
             m_height = floatBottom();
             m_height += borderBottom() + paddingBottom();
         }
     
         if (canvas()->pagedMode()) {
     #ifdef PAGE_DEBUG
             qDebug() << renderName() << " Page Bottom: " << pageTopAfter(0);
             qDebug() << renderName() << " Bottom: " << m_height;
     #endif
             bool needsPageBreak = false;
             int xpage = crossesPageBreak(0, m_height);
             if (xpage) {
                 needsPageBreak = true;
     #ifdef PAGE_DEBUG
                 qDebug() << renderName() << " crosses to page " << xpage;
     #endif
             }
             if (needsPageBreak && !containsPageBreak()) {
                 setNeedsPageClear(true);
     #ifdef PAGE_DEBUG
                 qDebug() << renderName() << " marked for page-clear";
     #endif
             }
         }
     
         layoutPositionedObjects(relayoutChildren);
     
         // Always ensure our overflow width/height are at least as large as our width/height.
         m_overflowWidth = qMax(m_overflowWidth, (int)m_width);
         m_overflowHeight = qMax(m_overflowHeight, m_height);
     
         // Update our scrollbars if we're overflow:auto/scroll now that we know if
         // we overflow or not.
         if (hasOverflowClip() && m_layer) {
             m_layer->checkScrollbarsAfterLayout();
         }
     
         setNeedsLayout(false);
     }
     
     void RenderBlock::adjustPositionedBlock(RenderObject *child, const MarginInfo &marginInfo)
     {
         if (child->isBox() && child->hasStaticX()) {
             if (style()->direction() == LTR) {
                 static_cast(child)->setStaticX(borderLeft() + paddingLeft());
             } else {
                 static_cast(child)->setStaticX(borderRight() + paddingRight());
             }
         }
     
         if (child->isBox() && child->hasStaticY()) {
             int y = m_height;
             if (!marginInfo.canCollapseWithTop()) {
                 child->calcVerticalMargins();
                 int marginTop = child->marginTop();
                 int collapsedTopPos = marginInfo.posMargin();
                 int collapsedTopNeg = marginInfo.negMargin();
                 if (marginTop > 0) {
                     if (marginTop > collapsedTopPos) {
                         collapsedTopPos = marginTop;
                     }
                 } else {
                     if (-marginTop > collapsedTopNeg) {
                         collapsedTopNeg = -marginTop;
                     }
                 }
                 y += (collapsedTopPos - collapsedTopNeg) - marginTop;
             }
             static_cast(child)->setStaticY(y);
         }
     }
     
     void RenderBlock::adjustFloatingBlock(const MarginInfo &marginInfo)
     {
         // The float should be positioned taking into account the bottom margin
         // of the previous flow.  We add that margin into the height, get the
         // float positioned properly, and then subtract the margin out of the
         // height again.  In the case of self-collapsing blocks, we always just
         // use the top margins, since the self-collapsing block collapsed its
         // own bottom margin into its top margin.
         //
         // Note also that the previous flow may collapse its margin into the top of
         // our block.  If this is the case, then we do not add the margin in to our
         // height when computing the position of the float.   This condition can be tested
         // for by simply calling canCollapseWithTop.  See
         // http://www.hixie.ch/tests/adhoc/css/box/block/margin-collapse/046.html for
         // an example of this scenario.
         int marginOffset = marginInfo.canCollapseWithTop() ? 0 : marginInfo.margin();
         m_height += marginOffset;
         positionNewFloats();
         m_height -= marginOffset;
     }
     
     RenderObject *RenderBlock::handleSpecialChild(RenderObject *child, const MarginInfo &marginInfo, CompactInfo &compactInfo, bool &handled)
     {
         // Handle positioned children first.
         RenderObject *next = handlePositionedChild(child, marginInfo, handled);
         if (handled) {
             return next;
         }
     
         // Handle floating children next.
         next = handleFloatingChild(child, marginInfo, handled);
         if (handled) {
             return next;
         }
     
         // See if we have a compact element.  If we do, then try to tuck the compact element into the margin space of the next block.
         next = handleCompactChild(child, compactInfo, marginInfo, handled);
         if (handled) {
             return next;
         }
     
         // Finally, see if we have a run-in element.
         return handleRunInChild(child, handled);
     }
     
     RenderObject *RenderBlock::handlePositionedChild(RenderObject *child, const MarginInfo &marginInfo, bool &handled)
     {
         if (child->isPositioned()) {
             handled = true;
             if (!child->inPosObjectList()) {
                 child->containingBlock()->insertPositionedObject(child);
             }
             adjustPositionedBlock(child, marginInfo);
             return child->nextSibling();
         }
         return nullptr;
     }
     
     RenderObject *RenderBlock::handleFloatingChild(RenderObject *child, const MarginInfo &marginInfo, bool &handled)
     {
         if (child->isFloating()) {
             handled = true;
             insertFloatingObject(child);
             adjustFloatingBlock(marginInfo);
             return child->nextSibling();
         }
         return nullptr;
     }
     
     static inline bool isAnonymousWhitespace(RenderObject *o)
     {
         if (!o->isAnonymous()) {
             return false;
         }
         RenderObject *fc = o->firstChild();
         return fc && fc == o->lastChild() && fc->isText() && static_cast(fc)->stringLength() == 1 &&
                static_cast(fc)->text()[0].unicode() == ' ';
     }
     
     RenderObject *RenderBlock::handleCompactChild(RenderObject *child, CompactInfo &compactInfo, const MarginInfo &marginInfo, bool &handled)
     {
         if (!child->isCompact()) {
             return nullptr;
         }
         // FIXME: We only deal with one compact at a time.  It is unclear what should be
         // done if multiple contiguous compacts are encountered.  For now we assume that
         // compact A followed by another compact B should simply be treated as block A.
         if (!compactInfo.compact() && (child->childrenInline() || child->isReplaced())) {
             // Get the next non-positioned/non-floating RenderBlock.
             RenderObject *next = child->nextSibling();
             RenderObject *curr = next;
             while (curr && (curr->isFloatingOrPositioned() || isAnonymousWhitespace(curr) || curr->isAnonymousBlock())) {
                 curr = curr->nextSibling();
             }
             if (curr && curr->isRenderBlock() && !curr->isCompact() && !curr->isRunIn()) {
                 curr->calcWidth(); // So that horizontal margins are correct.
                 // Need to compute margins for the child as though it is a block.
                 child->style()->setDisplay(BLOCK);
                 child->calcWidth();
                 child->style()->setDisplay(COMPACT);
     
                 int childMargins = child->marginLeft() + child->marginRight();
                 int margin = style()->direction() == LTR ? curr->marginLeft() : curr->marginRight();
                 if (margin >= (childMargins + child->maxWidth())) {
                     // The compact will fit in the margin.
                     handled = true;
                     compactInfo.set(child, curr);
                     child->layoutIfNeeded();
                     int off = marginInfo.margin();
                     m_height += off + curr->marginTop() < child->marginTop() ?
                                 child->marginTop() - curr->marginTop() - off : 0;
     
                     child->setPos(0, 0); // This position will be updated to reflect the compact's
                     // desired position and the line box for the compact will
                     // pick that position up.
                     return next;
                 }
             }
         }
         child->style()->setDisplay(BLOCK);
         child->layoutIfNeeded();
         child->style()->setDisplay(COMPACT);
         return nullptr;
     }
     
     void RenderBlock::adjustSizeForCompactIfNeeded(RenderObject *child, CompactInfo &compactInfo)
     {
         // if the compact is bigger than the block it was run into
         // then "this" block should take the height of the compact
         if (compactInfo.matches(child)) {
             // We have a compact child to squeeze in.
             RenderObject *compactChild = compactInfo.compact();
             if (compactChild->height() > child->height()) {
                 m_height += compactChild->height() - child->height();
             }
         }
     }
     
     void RenderBlock::insertCompactIfNeeded(RenderObject *child, CompactInfo &compactInfo)
     {
         if (compactInfo.matches(child)) {
             // We have a compact child to squeeze in.
             RenderObject *compactChild = compactInfo.compact();
             int compactXPos = borderLeft() + paddingLeft() + compactChild->marginLeft();
             if (style()->direction() == RTL) {
                 compactChild->calcWidth(); // have to do this because of the capped maxwidth
                 compactXPos = width() - borderRight() - paddingRight() -
                               compactChild->width() - compactChild->marginRight();
             }
     
             int compactYPos = child->yPos() + child->borderTop() + child->paddingTop()
                               - compactChild->paddingTop() - compactChild->borderTop();
             int adj = 0;
             KHTMLAssert(child->isRenderBlock());
             InlineRunBox *b = static_cast(child)->firstLineBox();
             InlineRunBox *c = static_cast(compactChild)->firstLineBox();
             if (b && c) {
                 // adjust our vertical position
                 int vpos = compactChild->getVerticalPosition(true, child);
                 if (vpos == PositionBottom) {
                     adj = b->height() > c->height() ? (b->height() + b->yPos() - c->height() - c->yPos()) : 0;
                 } else if (vpos == PositionTop) {
                     adj = b->yPos() - c->yPos();
                 } else {
                     adj = vpos;
                 }
                 compactYPos += adj;
             }
             Length newLineHeight(qMax(compactChild->lineHeight(true) + adj, (int)child->lineHeight(true)), khtml::Fixed);
             child->style()->setLineHeight(newLineHeight);
             child->setNeedsLayout(true, false);
             child->layout();
     
             compactChild->setPos(compactXPos, compactYPos); // Set the x position.
             compactInfo.clear();
         }
     }
     
     RenderObject *RenderBlock::handleRunInChild(RenderObject *child, bool &handled)
     {
         if (!child->isRunIn()) {
             return nullptr;
         }
         // See if we have a run-in element with inline children.  If the
         // children aren't inline, then just treat the run-in as a normal
         // block.
         if (child->childrenInline() || child->isReplaced()) {
             // Get the next non-positioned/non-floating RenderBlock.
             RenderObject *curr = child->nextSibling();
             while (curr && (curr->isFloatingOrPositioned() || isAnonymousWhitespace(curr) || curr->isAnonymousBlock())) {
                 curr = curr->nextSibling();
             }
             if (curr && (curr->isRenderBlock() && curr->childrenInline() && !curr->isCompact() && !curr->isRunIn())) {
                 // The block acts like an inline, so just null out its
                 // position.
                 handled = true;
                 child->setInline(true);
                 child->setPos(0, 0);
     
                 // Remove the child.
                 RenderObject *next = child->nextSibling();
                 removeChildNode(child);
     
                 // Now insert the child under |curr|.
                 curr->insertChildNode(child, curr->firstChild());
                 return next;
             }
         }
         return nullptr;
     }
     
     int RenderBlock::collapseMargins(RenderObject *child, MarginInfo &marginInfo, int yPosEstimate)
     {
         Q_UNUSED(yPosEstimate);
         // Get our max pos and neg top margins.
         int posTop = child->maxTopMargin(true);
         int negTop = child->maxTopMargin(false);
     
         // For self-collapsing blocks, collapse our bottom margins into our
         // top to get new posTop and negTop values.
         if (child->isSelfCollapsingBlock()) {
             posTop = qMax(posTop, (int)child->maxBottomMargin(true));
             negTop = qMax(negTop, (int)child->maxBottomMargin(false));
         }
     
         // See if the top margin is quirky. We only care if this child has
         // margins that will collapse with us.
         bool topQuirk = child->isTopMarginQuirk() /*|| style()->marginTopCollapse() == MDISCARD*/;
     
         if (marginInfo.canCollapseWithTop()) {
             // This child is collapsing with the top of the
             // block.  If it has larger margin values, then we need to update
             // our own maximal values.
             if (!style()->htmlHacks() || !marginInfo.quirkContainer() || !topQuirk) {
                 m_maxTopPosMargin = qMax(posTop, (int)m_maxTopPosMargin);
                 m_maxTopNegMargin = qMax(negTop, (int)m_maxTopNegMargin);
             }
     
             // The minute any of the margins involved isn't a quirk, don't
             // collapse it away, even if the margin is smaller (www.webreference.com
             // has an example of this, a 
    with 0.8em author-specified inside // a
    inside a
    apply to the border box of the cell. // Percentages don't need to be handled since they're always treated this way (even when specified on the cells). if (w.isFixed() && w.isPositive()) { w = Length(qMax(0, w.value() - borderLeft() - borderRight() - paddingLeft() - paddingRight()), Fixed); } } return w; } void RenderTableCell::calcMinMaxWidth() { KHTMLAssert(!minMaxKnown()); #ifdef DEBUG_LAYOUT qDebug() << renderName() << "(TableCell)::calcMinMaxWidth() known=" << minMaxKnown(); #endif if (section()->needCellRecalc) { section()->recalcCells(); } RenderBlock::calcMinMaxWidth(); if (element() && style()->whiteSpace() == NORMAL) { // See if nowrap was set. Length w = styleOrColWidth(); DOMString nowrap = static_cast(element())->getAttribute(ATTR_NOWRAP); if (!nowrap.isNull() && w.isFixed() && m_minWidth < w.value()) // Nowrap is set, but we didn't actually use it because of the // fixed width set on the cell. Even so, it is a WinIE/Moz trait // to make the minwidth of the cell into the fixed width. They do this // even in strict mode, so do not make this a quirk. Affected the top // of hiptop.com. { m_minWidth = w.value(); } } setMinMaxKnown(); } void RenderTableCell::calcWidth() { } void RenderTableCell::setWidth(int width) { if (width != m_width) { m_width = width; m_widthChanged = true; } } void RenderTableCell::layout() { layoutBlock(m_widthChanged); m_widthChanged = false; } void RenderTableCell::close() { RenderBlock::close(); #ifdef DEBUG_LAYOUT qDebug() << renderName() << "(RenderTableCell)::close() total height =" << m_height; #endif } bool RenderTableCell::requiresLayer() const { // table-cell display is never positioned (css 2.1-9.7) return style()->opacity() < 1.0f || hasOverflowClip() || isRelPositioned(); } void RenderTableCell::repaintRectangle(int x, int y, int w, int h, Priority p, bool f) { RenderBlock::repaintRectangle(x, y, w, h + _topExtra + _bottomExtra, p, f); } int RenderTableCell::pageTopAfter(int y) const { return parent()->pageTopAfter(y + m_y + _topExtra) - (m_y + _topExtra); } short RenderTableCell::baselinePosition(bool) const { RenderObject *o = firstChild(); int offset = paddingTop() + borderTop(); if (!o) { return offset + contentHeight(); } while (o->firstChild()) { if (!o->isInline()) { offset += o->paddingTop() + o->borderTop(); } o = o->firstChild(); } if (!o->isInline()) { return paddingTop() + borderTop() + contentHeight(); } offset += o->baselinePosition(true); return offset; } void RenderTableCell::setStyle(RenderStyle *newStyle) { if (parent() && section() && style() && style()->height() != newStyle->height()) { section()->setNeedCellRecalc(); } newStyle->setDisplay(TABLE_CELL); RenderBlock::setStyle(newStyle); setShouldPaintBackgroundOrBorder(true); if (newStyle->whiteSpace() == KHTML_NOWRAP) { // Figure out if we are really nowrapping or if we should just // use normal instead. If the width of the cell is fixed, then // we don't actually use NOWRAP. if (newStyle->width().isFixed()) { newStyle->setWhiteSpace(NORMAL); } else { newStyle->setWhiteSpace(NOWRAP); } } } bool RenderTableCell::nodeAtPoint(NodeInfo &info, int _x, int _y, int _tx, int _ty, HitTestAction hitTestAction, bool inside) { int tx = _tx + m_x; int ty = _ty + m_y; // also include the top and bottom extra space inside |= hitTestAction != HitTestChildrenOnly && style()->visibility() != HIDDEN && (_y >= ty) && (_y < ty + height() + _topExtra + _bottomExtra) && (_x >= tx) && (_x < tx + width()); return RenderBlock::nodeAtPoint(info, _x, _y, _tx, _ty, hitTestAction, inside); } // The following rules apply for resolving conflicts and figuring out which border // to use. // (1) Borders with the 'border-style' of 'hidden' take precedence over all other conflicting // borders. Any border with this value suppresses all borders at this location. // (2) Borders with a style of 'none' have the lowest priority. Only if the border properties of all // the elements meeting at this edge are 'none' will the border be omitted (but note that 'none' is // the default value for the border style.) // (3) If none of the styles are 'hidden' and at least one of them is not 'none', then narrow borders // are discarded in favor of wider ones. If several have the same 'border-width' then styles are preferred // in this order: 'double', 'solid', 'dashed', 'dotted', 'ridge', 'outset', 'groove', and the lowest: 'inset'. // (4) If border styles differ only in color, then a style set on a cell wins over one on a row, // which wins over a row group, column, column group and, lastly, table. It is undefined which color // is used when two elements of the same type disagree. static CollapsedBorderValue compareBorders(const CollapsedBorderValue &border1, const CollapsedBorderValue &border2) { // Sanity check the values passed in. If either is null, return the other. if (!border2.exists()) { return border1; } if (!border1.exists()) { return border2; } // Rule #1 above. if (border1.style() == BHIDDEN || border2.style() == BHIDDEN) { return CollapsedBorderValue(); // No border should exist at this location. } // Rule #2 above. A style of 'none' has lowest priority and always loses to any other border. if (border2.style() == BNONE) { return border1; } if (border1.style() == BNONE) { return border2; } // The first part of rule #3 above. Wider borders win. if (border1.width() != border2.width()) { return border1.width() > border2.width() ? border1 : border2; } // The borders have equal width. Sort by border style. if (border1.style() != border2.style()) { return border1.style() > border2.style() ? border1 : border2; } // The border have the same width and style. Rely on precedence (cell over row over row group, etc.) return border1.precedence >= border2.precedence ? border1 : border2; } CollapsedBorderValue RenderTableCell::collapsedLeftBorder(bool rtl) const { RenderTable *tableElt = table(); bool leftmostColumn; if (!rtl) { leftmostColumn = col() == 0; } else { int effCol = tableElt->colToEffCol(col() + colSpan() - 1); leftmostColumn = effCol == tableElt->numEffCols() - 1; } // For border left, we need to check, in order of precedence: // (1) Our left border. CollapsedBorderValue result(&style()->borderLeft(), BCELL); // (2) The right border of the cell to the left. RenderTableCell *prevCell = rtl ? tableElt->cellAfter(this) : tableElt->cellBefore(this); if (prevCell) { result = compareBorders(result, CollapsedBorderValue(&prevCell->style()->borderRight(), BCELL)); if (!result.exists()) { return result; } } else if (leftmostColumn) { // (3) Our row's left border. result = compareBorders(result, CollapsedBorderValue(&parent()->style()->borderLeft(), BROW)); if (!result.exists()) { return result; } // (4) Our row group's left border. result = compareBorders(result, CollapsedBorderValue(§ion()->style()->borderLeft(), BROWGROUP)); if (!result.exists()) { return result; } } // (5) Our column and column group's left borders. bool startColEdge; bool endColEdge; RenderTableCol *colElt = table()->colElement(col() + (rtl ? colSpan() - 1 : 0), &startColEdge, &endColEdge); if (colElt && (!rtl ? startColEdge : endColEdge)) { result = compareBorders(result, CollapsedBorderValue(&colElt->style()->borderLeft(), BCOL)); if (!result.exists()) { return result; } if (colElt->parent()->isTableCol() && (!rtl ? !colElt->previousSibling() : !colElt->nextSibling())) { result = compareBorders(result, CollapsedBorderValue(&colElt->parent()->style()->borderLeft(), BCOLGROUP)); if (!result.exists()) { return result; } } } // (6) The previous column's right border. if (!leftmostColumn) { colElt = table()->colElement(col() + (rtl ? colSpan() : -1), &startColEdge, &endColEdge); if (colElt && (!rtl ? endColEdge : startColEdge)) { result = compareBorders(result, CollapsedBorderValue(&colElt->style()->borderRight(), BCOL)); if (!result.exists()) { return result; } } } else { // (7) The table's left border. result = compareBorders(result, CollapsedBorderValue(&table()->style()->borderLeft(), BTABLE)); if (!result.exists()) { return result; } } return result; } CollapsedBorderValue RenderTableCell::collapsedRightBorder(bool rtl) const { RenderTable *tableElt = table(); bool rightmostColumn; if (rtl) { rightmostColumn = col() == 0; } else { int effCol = tableElt->colToEffCol(col() + colSpan() - 1); rightmostColumn = effCol == tableElt->numEffCols() - 1; } // For border right, we need to check, in order of precedence: // (1) Our right border. CollapsedBorderValue result = CollapsedBorderValue(&style()->borderRight(), BCELL); // (2) The left border of the cell to the right. if (!rightmostColumn) { RenderTableCell *nextCell = rtl ? tableElt->cellBefore(this) : tableElt->cellAfter(this); if (nextCell && nextCell->style()) { result = compareBorders(result, CollapsedBorderValue(&nextCell->style()->borderLeft(), BCELL)); if (!result.exists()) { return result; } } } else { // (3) Our row's right border. result = compareBorders(result, CollapsedBorderValue(&parent()->style()->borderRight(), BROW)); if (!result.exists()) { return result; } // (4) Our row group's right border. result = compareBorders(result, CollapsedBorderValue(§ion()->style()->borderRight(), BROWGROUP)); if (!result.exists()) { return result; } } // (5) Our column and column group's right borders. bool startColEdge; bool endColEdge; RenderTableCol *colElt = tableElt->colElement(col() + (rtl ? 0 : colSpan() - 1), &startColEdge, &endColEdge); if (colElt && (!rtl ? endColEdge : startColEdge)) { result = compareBorders(result, CollapsedBorderValue(&colElt->style()->borderRight(), BCOL)); if (!result.exists()) { return result; } if (colElt->parent()->isTableCol() && (!rtl ? !colElt->nextSibling() : !colElt->previousSibling())) { result = compareBorders(result, CollapsedBorderValue(&colElt->parent()->style()->borderRight(), BCOLGROUP)); if (!result.exists()) { return result; } } } // (6) The next column's left border. if (!rightmostColumn) { colElt = tableElt->colElement(col() + (rtl ? -1 : colSpan()), &startColEdge, &endColEdge); if (colElt && (!rtl ? startColEdge : endColEdge)) { result = compareBorders(result, CollapsedBorderValue(&colElt->style()->borderLeft(), BCOL)); if (!result.exists()) { return result; } } } else { // (7) The table's right border. result = compareBorders(result, CollapsedBorderValue(&tableElt->style()->borderRight(), BTABLE)); if (!result.exists()) { return result; } } return result; } CollapsedBorderValue RenderTableCell::collapsedTopBorder() const { // For border top, we need to check, in order of precedence: // (1) Our top border. CollapsedBorderValue result = CollapsedBorderValue(&style()->borderTop(), BCELL); RenderTableCell *prevCell = table()->cellAbove(this); if (prevCell) { // (2) A previous cell's bottom border. result = compareBorders(result, CollapsedBorderValue(&prevCell->style()->borderBottom(), BCELL)); if (!result.exists()) { return result; } } // (3) Our row's top border. result = compareBorders(result, CollapsedBorderValue(&parent()->style()->borderTop(), BROW)); if (!result.exists()) { return result; } // (4) The previous row's bottom border. if (prevCell) { RenderObject *prevRow = nullptr; if (prevCell->section() == section()) { prevRow = parent()->previousSibling(); } else { prevRow = prevCell->section()->lastChild(); } if (prevRow) { result = compareBorders(result, CollapsedBorderValue(&prevRow->style()->borderBottom(), BROW)); if (!result.exists()) { return result; } } } // Now check row groups. RenderTableSection *currSection = section(); if (row() == 0) { // (5) Our row group's top border. result = compareBorders(result, CollapsedBorderValue(&currSection->style()->borderTop(), BROWGROUP)); if (!result.exists()) { return result; } // (6) Previous row group's bottom border. currSection = table()->sectionAbove(currSection); if (currSection) { result = compareBorders(result, CollapsedBorderValue(&currSection->style()->borderBottom(), BROWGROUP)); if (!result.exists()) { return result; } } } if (!currSection) { // (8) Our column and column group's top borders. RenderTableCol *colElt = table()->colElement(col()); if (colElt) { result = compareBorders(result, CollapsedBorderValue(&colElt->style()->borderTop(), BCOL)); if (!result.exists()) { return result; } if (colElt->parent()->isTableCol()) { result = compareBorders(result, CollapsedBorderValue(&colElt->parent()->style()->borderTop(), BCOLGROUP)); if (!result.exists()) { return result; } } } // (9) The table's top border. result = compareBorders(result, CollapsedBorderValue(&table()->style()->borderTop(), BTABLE)); if (!result.exists()) { return result; } } return result; } CollapsedBorderValue RenderTableCell::collapsedBottomBorder() const { // For border top, we need to check, in order of precedence: // (1) Our bottom border. CollapsedBorderValue result = CollapsedBorderValue(&style()->borderBottom(), BCELL); RenderTableCell *nextCell = table()->cellBelow(this); if (nextCell) { // (2) A following cell's top border. result = compareBorders(result, CollapsedBorderValue(&nextCell->style()->borderTop(), BCELL)); if (!result.exists()) { return result; } } // (3) Our row's bottom border. (FIXME: Deal with rowspan!) result = compareBorders(result, CollapsedBorderValue(&parent()->style()->borderBottom(), BROW)); if (!result.exists()) { return result; } // (4) The next row's top border. if (nextCell) { result = compareBorders(result, CollapsedBorderValue(&nextCell->parent()->style()->borderTop(), BROW)); if (!result.exists()) { return result; } } // Now check row groups. RenderTableSection *currSection = section(); if (row() + rowSpan() >= static_cast(currSection)->numRows()) { // (5) Our row group's bottom border. result = compareBorders(result, CollapsedBorderValue(&currSection->style()->borderBottom(), BROWGROUP)); if (!result.exists()) { return result; } // (6) Following row group's top border. currSection = table()->sectionBelow(currSection); if (currSection) { result = compareBorders(result, CollapsedBorderValue(&currSection->style()->borderTop(), BROWGROUP)); if (!result.exists()) { return result; } } } if (!currSection) { // (8) Our column and column group's bottom borders. RenderTableCol *colElt = table()->colElement(col()); if (colElt) { result = compareBorders(result, CollapsedBorderValue(&colElt->style()->borderBottom(), BCOL)); if (!result.exists()) { return result; } if (colElt->parent()->isTableCol()) { result = compareBorders(result, CollapsedBorderValue(&colElt->parent()->style()->borderBottom(), BCOLGROUP)); if (!result.exists()) { return result; } } } // (9) The table's bottom border. result = compareBorders(result, CollapsedBorderValue(&table()->style()->borderBottom(), BTABLE)); if (!result.exists()) { return result; } } return result; } int RenderTableCell::borderLeft() const { if (table()->collapseBorders()) { CollapsedBorderValue border = collapsedLeftBorder(table()->style()->direction() == RTL); if (border.exists()) { return (border.width() + 1) / 2; // Give the extra pixel to top and left. } return 0; } return RenderBlock::borderLeft(); } int RenderTableCell::borderRight() const { if (table()->collapseBorders()) { CollapsedBorderValue border = collapsedRightBorder(table()->style()->direction() == RTL); if (border.exists()) { return border.width() / 2; } return 0; } return RenderBlock::borderRight(); } int RenderTableCell::borderTop() const { if (table()->collapseBorders()) { CollapsedBorderValue border = collapsedTopBorder(); if (border.exists()) { return (border.width() + 1) / 2; // Give the extra pixel to top and left. } return 0; } return RenderBlock::borderTop(); } int RenderTableCell::borderBottom() const { if (table()->collapseBorders()) { CollapsedBorderValue border = collapsedBottomBorder(); if (border.exists()) { return border.width() / 2; } return 0; } return RenderBlock::borderBottom(); } #ifdef BOX_DEBUG #include static void outlineBox(QPainter *p, int _tx, int _ty, int w, int h) { p->setPen(QPen(QColor("yellow"), 3, Qt::DotLine)); p->setBrush(Qt::NoBrush); p->drawRect(_tx, _ty, w, h); } #endif void RenderTableCell::paint(PaintInfo &pI, int _tx, int _ty) { #ifdef TABLE_PRINT qDebug() << renderName() << "(RenderTableCell)::paint() w/h = (" << width() << "/" << height() << ")" << " _y/_h=" << pI.r.y() << "/" << pI.r.height(); #endif if (needsLayout()) { return; } _tx += m_x; _ty += m_y/* + _topExtra*/; RenderTable *tbl = table(); // check if we need to do anything at all... int os = qMax(tbl->currentBorderStyle() ? (tbl->currentBorderStyle()->border->width + 1) / 2 : 0, 2 * maximalOutlineSize(pI.phase)); if ((_ty >= pI.r.y() + pI.r.height() + os) || (_ty + _topExtra + m_height + _bottomExtra <= pI.r.y() - os)) { return; } if (pI.phase == PaintActionOutline) { paintOutline(pI.p, _tx, _ty, width(), height() + borderTopExtra() + borderBottomExtra(), style()); } if (pI.phase == PaintActionCollapsedTableBorders && style()->visibility() == VISIBLE) { int w = width(); int h = height() + borderTopExtra() + borderBottomExtra(); paintCollapsedBorder(pI.p, _tx, _ty, w, h); } else { RenderBlock::paintObject(pI, _tx, _ty + _topExtra, false); } #ifdef BOX_DEBUG ::outlineBox(pI.p, _tx, _ty - _topExtra, width(), height() + borderTopExtra() + borderBottomExtra()); #endif } static EBorderStyle collapsedBorderStyle(EBorderStyle style) { if (style == OUTSET) { style = GROOVE; } else if (style == INSET) { style = RIDGE; } return style; } struct CollapsedBorder { CollapsedBorder() {} CollapsedBorderValue border; RenderObject::BorderSide side; bool shouldPaint; int x1; int y1; int x2; int y2; EBorderStyle style; }; class CollapsedBorders { public: CollapsedBorders() : count(0) {} void addBorder(const CollapsedBorderValue &b, RenderObject::BorderSide s, bool paint, int _x1, int _y1, int _x2, int _y2, EBorderStyle _style) { if (b.exists() && paint) { borders[count].border = b; borders[count].side = s; borders[count].shouldPaint = paint; borders[count].x1 = _x1; borders[count].x2 = _x2; borders[count].y1 = _y1; borders[count].y2 = _y2; borders[count].style = _style; count++; } } CollapsedBorder *nextBorder() { for (int i = 0; i < count; i++) { if (borders[i].border.exists() && borders[i].shouldPaint) { borders[i].shouldPaint = false; return &borders[i]; } } return nullptr; } CollapsedBorder borders[4]; int count; }; static void addBorderStyle(QList &borderStyles, CollapsedBorderValue borderValue) { if (!borderValue.exists() || borderStyles.contains(borderValue)) { return; } QList::Iterator it = borderStyles.begin(); QList::Iterator end = borderStyles.end(); for (; it != end; ++it) { CollapsedBorderValue result = compareBorders(*it, borderValue); if (result == *it) { borderStyles.insert(it, borderValue); return; } } borderStyles.append(borderValue); } void RenderTableCell::collectBorders(QList &borderStyles) { bool rtl = table()->style()->direction() == RTL; addBorderStyle(borderStyles, collapsedLeftBorder(rtl)); addBorderStyle(borderStyles, collapsedRightBorder(rtl)); addBorderStyle(borderStyles, collapsedTopBorder()); addBorderStyle(borderStyles, collapsedBottomBorder()); } void RenderTableCell::paintCollapsedBorder(QPainter *p, int _tx, int _ty, int w, int h) { if (!table()->currentBorderStyle()) { return; } bool rtl = table()->style()->direction() == RTL; CollapsedBorderValue leftVal = collapsedLeftBorder(rtl); CollapsedBorderValue rightVal = collapsedRightBorder(rtl); CollapsedBorderValue topVal = collapsedTopBorder(); CollapsedBorderValue bottomVal = collapsedBottomBorder(); // Adjust our x/y/width/height so that we paint the collapsed borders at the correct location. int topWidth = topVal.width(); int bottomWidth = bottomVal.width(); int leftWidth = leftVal.width(); int rightWidth = rightVal.width(); _tx -= leftWidth / 2; _ty -= topWidth / 2; w += leftWidth / 2 + (rightWidth + 1) / 2; h += topWidth / 2 + (bottomWidth + 1) / 2; bool tt = topVal.isTransparent(); bool bt = bottomVal.isTransparent(); bool rt = rightVal.isTransparent(); bool lt = leftVal.isTransparent(); EBorderStyle ts = collapsedBorderStyle(topVal.style()); EBorderStyle bs = collapsedBorderStyle(bottomVal.style()); EBorderStyle ls = collapsedBorderStyle(leftVal.style()); EBorderStyle rs = collapsedBorderStyle(rightVal.style()); bool render_t = ts > BHIDDEN && !tt && (topVal.precedence != BCELL || *topVal.border == style()->borderTop()); bool render_l = ls > BHIDDEN && !lt && (leftVal.precedence != BCELL || *leftVal.border == style()->borderLeft()); bool render_r = rs > BHIDDEN && !rt && (rightVal.precedence != BCELL || *rightVal.border == style()->borderRight()); bool render_b = bs > BHIDDEN && !bt && (bottomVal.precedence != BCELL || *bottomVal.border == style()->borderBottom()); // We never paint diagonals at the joins. We simply let the border with the highest // precedence paint on top of borders with lower precedence. CollapsedBorders borders; borders.addBorder(topVal, BSTop, render_t, _tx, _ty, _tx + w, _ty + topWidth, ts); borders.addBorder(bottomVal, BSBottom, render_b, _tx, _ty + h - bottomWidth, _tx + w, _ty + h, bs); borders.addBorder(leftVal, BSLeft, render_l, _tx, _ty, _tx + leftWidth, _ty + h, ls); borders.addBorder(rightVal, BSRight, render_r, _tx + w - rightWidth, _ty, _tx + w, _ty + h, rs); for (CollapsedBorder *border = borders.nextBorder(); border; border = borders.nextBorder()) { if (border->border == *table()->currentBorderStyle()) drawBorder(p, border->x1, border->y1, border->x2, border->y2, border->side, border->border.color(), style()->color(), border->style, 0, 0); } } void RenderTableCell::paintBackgroundsBehindCell(PaintInfo &pI, int _tx, int _ty, RenderObject *bgObj) { if (!bgObj || style()->visibility() != VISIBLE) { return; } RenderTable *tableElt = table(); int w = bgObj->width(); int h = bgObj->height() + bgObj->borderTopExtra() + bgObj->borderBottomExtra(); int cellx = _tx; int celly = _ty; int cellw = w; int cellh = h; if (bgObj != this) { cellx += m_x; celly += m_y; cellw = width(); cellh = height() + borderTopExtra() + borderBottomExtra(); } QRect cr; cr.setX(qMax(cellx, pI.r.x())); cr.setY(qMax(celly, pI.r.y())); cr.setWidth(cellx < pI.r.x() ? qMax(0, cellw - (pI.r.x() - cellx)) : qMin(pI.r.width(), cellw)); cr.setHeight(celly < pI.r.y() ? qMax(0, cellh - (pI.r.y() - celly)) : qMin(pI.r.height(), cellh)); QColor c = bgObj->style()->backgroundColor(); const BackgroundLayer *bgLayer = bgObj->style()->backgroundLayers(); if (bgLayer->hasImage() || c.isValid()) { // We have to clip here because the background would paint // on top of the borders otherwise. This only matters for cells and rows. bool hasLayer = bgObj->layer() && (bgObj == this || bgObj == parent()); if (hasLayer && tableElt->collapseBorders()) { pI.p->save(); QRect clipRect(cellx + borderLeft(), celly + borderTop(), cellw - borderLeft() - borderRight(), cellh - borderTop() - borderBottom()); clipRect = pI.p->combinedMatrix().mapRect(clipRect); QRegion creg(clipRect); QRegion old = pI.p->clipRegion(); if (!old.isEmpty()) { creg = old.intersect(creg); } pI.p->setClipRegion(creg); } KHTMLAssert(bgObj->isBox()); static_cast(bgObj)->paintAllBackgrounds(pI.p, c, bgLayer, cr, _tx, _ty, w, h); if (hasLayer && tableElt->collapseBorders()) { pI.p->restore(); } } } void RenderTableCell::paintBoxDecorations(PaintInfo &pI, int _tx, int _ty) { RenderTable *tableElt = table(); bool drawBorders = true; // Moz paints bgcolor/bgimage on
    . if (!marginInfo.determinedTopQuirk() && !topQuirk && (posTop - negTop)) { m_topMarginQuirk = false; marginInfo.setDeterminedTopQuirk(true); } if (!marginInfo.determinedTopQuirk() && topQuirk && marginTop() == 0) // We have no top margin and our top child has a quirky margin. // We will pick up this quirky margin and pass it through. // This deals with the

    case. // Don't do this for a block that split two inlines though. You do // still apply margins in this case. { m_topMarginQuirk = true; } } if (marginInfo.quirkContainer() && marginInfo.atTopOfBlock() && (posTop - negTop)) { marginInfo.setTopQuirk(topQuirk); } int ypos = m_height; if (child->isSelfCollapsingBlock()) { // This child has no height. We need to compute our // position before we collapse the child's margins together, // so that we can get an accurate position for the zero-height block. int collapsedTopPos = qMax(marginInfo.posMargin(), (int)child->maxTopMargin(true)); int collapsedTopNeg = qMax(marginInfo.negMargin(), (int)child->maxTopMargin(false)); marginInfo.setMargin(collapsedTopPos, collapsedTopNeg); // Now collapse the child's margins together, which means examining our // bottom margin values as well. marginInfo.setPosMarginIfLarger(child->maxBottomMargin(true)); marginInfo.setNegMarginIfLarger(child->maxBottomMargin(false)); if (!marginInfo.canCollapseWithTop()) // We need to make sure that the position of the self-collapsing block // is correct, since it could have overflowing content // that needs to be positioned correctly (e.g., a block that // had a specified height of 0 but that actually had subcontent). { ypos = m_height + collapsedTopPos - collapsedTopNeg; } } else { #ifdef APPLE_CHANGES if (child->style()->marginTopCollapse() == MSEPARATE) { m_height += marginInfo.margin() + child->marginTop(); ypos = m_height; } else #endif if (!marginInfo.atTopOfBlock() || (!marginInfo.canCollapseTopWithChildren() && (!style()->htmlHacks() || !marginInfo.quirkContainer() || !marginInfo.topQuirk()))) { // We're collapsing with a previous sibling's margins and not // with the top of the block. m_height += qMax(marginInfo.posMargin(), posTop) - qMax(marginInfo.negMargin(), negTop); ypos = m_height; } marginInfo.setPosMargin(child->maxBottomMargin(true)); marginInfo.setNegMargin(child->maxBottomMargin(false)); if (marginInfo.margin()) { marginInfo.setBottomQuirk(child->isBottomMarginQuirk() /*|| style()->marginBottomCollapse() == MDISCARD*/); } marginInfo.setSelfCollapsingBlockClearedFloat(false); } return ypos; } int RenderBlock::clearFloatsIfNeeded(RenderObject *child, MarginInfo &marginInfo, int oldTopPosMargin, int oldTopNegMargin, int yPos) { int heightIncrease = getClearDelta(child, yPos); if (heightIncrease) { // Increase our height by the amount we had to clear. bool selfCollapsing = child->isSelfCollapsingBlock(); if (!selfCollapsing) { m_height += heightIncrease; } else { // For self-collapsing blocks that clear, they may end up collapsing // into the bottom of the parent block. We simulate this behavior by // setting our positive margin value to compensate for the clear. marginInfo.setPosMargin(qMax(0, child->yPos() - m_height)); marginInfo.setNegMargin(0); marginInfo.setSelfCollapsingBlockClearedFloat(true); } if (marginInfo.canCollapseWithTop()) { // We can no longer collapse with the top of the block since a clear // occurred. The empty blocks collapse into the cleared block. // FIXME: This isn't quite correct. Need clarification for what to do // if the height the cleared block is offset by is smaller than the // margins involved. m_maxTopPosMargin = oldTopPosMargin; m_maxTopNegMargin = oldTopNegMargin; marginInfo.setAtTopOfBlock(false); } /* // If our value of clear caused us to be repositioned vertically to be // underneath a float, we might have to do another layout to take into account // the extra space we now have available. if (!selfCollapsing && !child->style()->width().isFixed() && child->usesLineWidth()) // The child's width is a percentage of the line width. // When the child shifts to clear an item, its width can // change (because it has more available line width). // So go ahead and mark the item as dirty. child->setChildNeedsLayout(true); if (!child->flowAroundFloats() && child->hasFloats()) child->markAllDescendantsWithFloatsForLayout(); child->layoutIfNeeded(); */ return yPos + heightIncrease; } return yPos; } bool RenderBlock::canClear(RenderObject *child, PageBreakLevel level) { KHTMLAssert(child->parent() && child->parent() == this); KHTMLAssert(canvas()->pagedMode()); // Positioned elements cannot be moved. Only normal flow and floating. if (child->isPositioned() || child->isRelPositioned()) { return false; } switch (level) { case PageBreakNormal: // check page-break-inside: avoid if (!style()->pageBreakInside()) // we cannot, but can our parent? if (!parent()->canClear(this, level)) { return false; } case PageBreakHarder: // check page-break-after/before: avoid if (m_avoidPageBreak) // we cannot, but can our parent? if (!parent()->canClear(this, level)) { return false; } case PageBreakForced: // child is larger than page-height and is forced to break if (child->height() > canvas()->pageHeight()) { return false; } return true; } assert(false); return false; } void RenderBlock::clearPageBreak(RenderObject *child, int pageBottom) { KHTMLAssert(child->parent() && child->parent() == this); KHTMLAssert(canvas()->pagedMode()); if (child->yPos() >= pageBottom) { return; } int heightIncrease = 0; heightIncrease = pageBottom - child->yPos(); // ### should never happen, canClear should have been called to detect it. if (child->height() > canvas()->pageHeight()) { // qDebug() << "### child is too large to clear: " << child->height() << " > " << canvas()->pageHeight(); return; } // The child needs to be lowered. Move the child so that it just clears the break. child->setPos(child->xPos(), pageBottom); #ifdef PAGE_DEBUG qDebug() << "Cleared block " << heightIncrease << "px"; #endif // Increase our height by the amount we had to clear. m_height += heightIncrease; // We might have to do another layout to take into account // the extra space we now have available. if (!child->style()->width().isFixed() && child->usesLineWidth()) // The child's width is a percentage of the line width. // When the child shifts to clear a page-break, its width can // change (because it has more available line width). // So go ahead and mark the item as dirty. { child->setChildNeedsLayout(true); } if (!child->flowAroundFloats() && child->hasFloats()) { child->markAllDescendantsWithFloatsForLayout(); } if (child->containsPageBreak()) { child->setNeedsLayout(true); } child->layoutIfNeeded(); child->setAfterPageBreak(true); } int RenderBlock::estimateVerticalPosition(RenderObject *child, const MarginInfo &marginInfo) { // FIXME: We need to eliminate the estimation of vertical position, because // when it's wrong we sometimes trigger a pathological relayout if there are // intruding floats. int yPosEstimate = m_height; if (!marginInfo.canCollapseWithTop()) { int childMarginTop = child->selfNeedsLayout() ? child->marginTop() : child->collapsedMarginTop(); yPosEstimate += qMax(marginInfo.margin(), childMarginTop); } yPosEstimate += getClearDelta(child, yPosEstimate); return yPosEstimate; } void RenderBlock::determineHorizontalPosition(RenderObject *child) { if (style()->direction() == LTR) { int xPos = borderLeft() + paddingLeft(); if (m_layer && scrollsOverflowY() && m_layer->hasReversedScrollbar()) { xPos += m_layer->verticalScrollbarWidth(); } // Add in our left margin. int chPos = xPos + child->marginLeft(); // Some objects (e.g., tables, horizontal rules, overflow:auto blocks) avoid floats. They need // to shift over as necessary to dodge any floats that might get in the way. if (child->flowAroundFloats()) { int leftOff = leftOffset(m_height); if (style()->textAlign() != KHTML_CENTER && !child->style()->marginLeft().isAuto()) { if (child->marginLeft() < 0) { leftOff += child->marginLeft(); } chPos = qMax(chPos, leftOff); // Let the float sit in the child's margin if it can fit. } else if (leftOff != xPos) { // The object is shifting right. The object might be centered, so we need to // recalculate our horizontal margins. Note that the containing block content // width computation will take into account the delta between |leftOff| and |xPos| // so that we can just pass the content width in directly to the |calcHorizontalMargins| // function. static_cast(child)->calcHorizontalMargins(child->style()->marginLeft(), child->style()->marginRight(), lineWidth(child->yPos())); chPos = leftOff + child->marginLeft(); } } child->setPos(chPos, child->yPos()); } else { int xPos = m_width - borderRight() - paddingRight(); if (m_layer && scrollsOverflowY() && !m_layer->hasReversedScrollbar()) { xPos -= m_layer->verticalScrollbarWidth(); } int chPos = xPos - (child->width() + child->marginRight()); if (child->flowAroundFloats()) { int rightOff = rightOffset(m_height); if (style()->textAlign() != KHTML_CENTER && !child->style()->marginRight().isAuto()) { if (child->marginRight() < 0) { rightOff -= child->marginRight(); } chPos = qMin(chPos, rightOff - child->width()); // Let the float sit in the child's margin if it can fit. } else if (rightOff != xPos) { // The object is shifting left. The object might be centered, so we need to // recalculate our horizontal margins. Note that the containing block content // width computation will take into account the delta between |rightOff| and |xPos| // so that we can just pass the content width in directly to the |calcHorizontalMargins| // function. static_cast(child)->calcHorizontalMargins(child->style()->marginLeft(), child->style()->marginRight(), lineWidth(child->yPos())); chPos = rightOff - child->marginRight() - child->width(); } } child->setPos(chPos, child->yPos()); } } void RenderBlock::setCollapsedBottomMargin(const MarginInfo &marginInfo) { if (marginInfo.canCollapseWithBottom() && !marginInfo.canCollapseWithTop()) { // Update our max pos/neg bottom margins, since we collapsed our bottom margins // with our children. m_maxBottomPosMargin = qMax((int)m_maxBottomPosMargin, marginInfo.posMargin()); m_maxBottomNegMargin = qMax((int)m_maxBottomNegMargin, marginInfo.negMargin()); if (!marginInfo.bottomQuirk()) { m_bottomMarginQuirk = false; } if (marginInfo.bottomQuirk() && marginBottom() == 0) // We have no bottom margin and our last child has a quirky margin. // We will pick up this quirky margin and pass it through. // This deals with the

    case. { m_bottomMarginQuirk = true; } } } void RenderBlock::handleBottomOfBlock(int top, int bottom, MarginInfo &marginInfo) { // If our last flow was a self-collapsing block that cleared a float, then we don't // collapse it with the bottom of the block. if (!marginInfo.selfCollapsingBlockClearedFloat()) { marginInfo.setAtBottomOfBlock(true); } // If we can't collapse with children then go ahead and add in the bottom margin. if (!marginInfo.canCollapseWithBottom() && !marginInfo.canCollapseWithTop() && (!style()->htmlHacks() || !marginInfo.quirkContainer() || !marginInfo.bottomQuirk())) { m_height += marginInfo.margin(); } // Now add in our bottom border/padding. m_height += bottom; // Negative margins can cause our height to shrink below our minimal height (border/padding). // If this happens, ensure that the computed height is increased to the minimal height. m_height = qMax(m_height, top + bottom); // Always make sure our overflow height is at least our height. m_overflowHeight = qMax(m_height, m_overflowHeight); // Update our bottom collapsed margin info. setCollapsedBottomMargin(marginInfo); } void RenderBlock::layoutBlockChildren(bool relayoutChildren) { #ifdef DEBUG_LAYOUT qDebug() << renderName() << " layoutBlockChildren( " << this << " ), relayoutChildren=" << relayoutChildren; #endif int top = borderTop() + paddingTop(); int bottom = borderBottom() + paddingBottom(); if (m_layer && scrollsOverflowX() && style()->height().isAuto()) { bottom += m_layer->horizontalScrollbarHeight(); } m_height = m_overflowHeight = top; // The margin struct caches all our current margin collapsing state. // The compact struct caches state when we encounter compacts. MarginInfo marginInfo(this, top, bottom); CompactInfo compactInfo; // Fieldsets need to find their legend and position it inside the border of the object. // The legend then gets skipped during normal layout. RenderObject *legend = layoutLegend(relayoutChildren); PageBreakInfo pageBreakInfo(pageTopAfter(0)); int previousFloatBottom = 0; RenderObject *child = firstChild(); while (child != nullptr) { if (legend == child) { child = child->nextSibling(); continue; // Skip the legend, since it has already been positioned up in the fieldset's border. } int oldTopPosMargin = m_maxTopPosMargin; int oldTopNegMargin = m_maxTopNegMargin; // make sure we relayout children if we need it. if ((!child->isPositioned() || child->isPosWithStaticDim()) && (relayoutChildren || (child->isReplaced() && (child->style()->width().isPercent() || child->style()->height().isPercent())) || (child->isRenderBlock() && child->style()->height().isPercent()) || (child->isBody() && child->style()->htmlHacks()))) { child->setChildNeedsLayout(true); } // Handle the four types of special elements first. These include positioned content, floating content, compacts and // run-ins. When we encounter these four types of objects, we don't actually lay them out as normal flow blocks. bool handled = false; RenderObject *next = handleSpecialChild(child, marginInfo, compactInfo, handled); if (handled) { child = next; continue; } // The child is a normal flow object. Compute its vertical margins now. child->calcVerticalMargins(); #ifdef APPLE_CHANGES /* margin-*-collapse not merged yet */ // Do not allow a collapse if the margin top collapse style is set to SEPARATE. if (child->style()->marginTopCollapse() == MSEPARATE) { marginInfo.setAtTopOfBlock(false); marginInfo.clearMargin(); } #endif // Try to guess our correct y position. In most cases this guess will // be correct. Only if we're wrong (when we compute the real y position) // will we have to potentially relayout. int yPosEstimate = estimateVerticalPosition(child, marginInfo); bool markDescendantsWithFloats = false; if (yPosEstimate != child->yPos() && !child->flowAroundFloats() && child->hasFloats()) { markDescendantsWithFloats = true; } else if (!child->flowAroundFloats() || child->usesLineWidth()) { // If an element might be affected by the presence of floats, then always mark it for // layout. int fb = qMax(previousFloatBottom, floatBottom()); if (fb > yPosEstimate) { markDescendantsWithFloats = true; } } if (child->isRenderBlock()) { if (markDescendantsWithFloats) { child->markAllDescendantsWithFloatsForLayout(); } previousFloatBottom = qMax(previousFloatBottom, child->yPos() + static_cast(child)->floatBottom()); } // Go ahead and position the child as though it didn't collapse with the top. child->setPos(child->xPos(), yPosEstimate); child->layoutIfNeeded(); // Now determine the correct ypos based on examination of collapsing margin // values. int yBeforeClear = collapseMargins(child, marginInfo, yPosEstimate); // Now check for clear. int yAfterClear = clearFloatsIfNeeded(child, marginInfo, oldTopPosMargin, oldTopNegMargin, yBeforeClear); child->setPos(child->xPos(), yAfterClear); // Now we have a final y position. See if it really does end up being different from our estimate. if (yAfterClear != yPosEstimate) { if (child->usesLineWidth()) { // The child's width depends on the line width. // When the child shifts to clear an item, its width can // change (because it has more available line width). // So go ahead and mark the item as dirty. child->setChildNeedsLayout(true, false); } if (!child->flowAroundFloats() && child->hasFloats()) { child->markAllDescendantsWithFloatsForLayout(); } // Our guess was wrong. Make the child lay itself out again. child->layoutIfNeeded(); } // We are no longer at the top of the block if we encounter a non-empty child. // This has to be done after checking for clear, so that margins can be reset if a clear occurred. if (marginInfo.atTopOfBlock() && !child->isSelfCollapsingBlock()) { marginInfo.setAtTopOfBlock(false); } // Now place the child in the correct horizontal position determineHorizontalPosition(child); adjustSizeForCompactIfNeeded(child, compactInfo); // Update our height now that the child has been placed in the correct position. m_height += child->height(); #ifdef APPLE_CHANGES if (child->style()->marginBottomCollapse() == MSEPARATE) { m_height += child->marginBottom(); marginInfo.clearMargin(); } #endif // Check for page-breaks if (canvas()->pagedMode()) { clearChildOfPageBreaks(child, pageBreakInfo, marginInfo); } if (child->hasOverhangingFloats() && !child->flowAroundFloats()) { // need to add the child's floats to our floating objects list, but not in the case where // overflow is auto/scroll addOverHangingFloats(static_cast(child), -child->xPos(), -child->yPos(), true); } // See if this child has made our overflow need to grow. int effX = child->effectiveXPos(); int effY = child->effectiveYPos(); m_overflowWidth = qMax(effX + child->effectiveWidth(), m_overflowWidth); m_overflowLeft = qMin(effX, m_overflowLeft); m_overflowHeight = qMax(effY + child->effectiveHeight(), m_overflowHeight); m_overflowTop = qMin(effY, m_overflowTop); // Insert our compact into the block margin if we have one. insertCompactIfNeeded(child, compactInfo); child = child->nextSibling(); } // The last child had forced page-break-after if (pageBreakInfo.forcePageBreak()) { m_height = pageBreakInfo.pageBottom(); } // Now do the handling of the bottom of the block, adding in our bottom border/padding and // determining the correct collapsed bottom margin information. handleBottomOfBlock(top, bottom, marginInfo); setNeedsLayout(false); } void RenderBlock::clearChildOfPageBreaks(RenderObject *child, PageBreakInfo &pageBreakInfo, MarginInfo &marginInfo) { (void)marginInfo; int childTop = child->yPos(); int childBottom = child->yPos() + child->height(); #ifdef PAGE_DEBUG qDebug() << renderName() << " ChildTop: " << childTop << " ChildBottom: " << childBottom; #endif bool forcePageBreak = pageBreakInfo.forcePageBreak() || child->style()->pageBreakBefore() == PBALWAYS; #ifdef PAGE_DEBUG if (forcePageBreak) { qDebug() << renderName() << "Forced break required"; } #endif int xpage = crossesPageBreak(childTop, childBottom); if (xpage || forcePageBreak) { if (!forcePageBreak && child->containsPageBreak() && !child->needsPageClear()) { #ifdef PAGE_DEBUG qDebug() << renderName() << " Child contains page-break to page " << xpage; #endif // ### Actually this assumes floating children are breaking/clearing // nicely as well. setContainsPageBreak(true); } else { bool doBreak = true; // don't break before the first child or when page-break-inside is avoid if (!forcePageBreak && (!style()->pageBreakInside() || m_avoidPageBreak || child == firstChild())) { if (parent() && parent()->canClear(this, (m_avoidPageBreak) ? PageBreakHarder : PageBreakNormal)) { #ifdef PAGE_DEBUG qDebug() << renderName() << "Avoid page-break inside"; #endif child->setNeedsPageClear(false); setNeedsPageClear(true); doBreak = false; } #ifdef PAGE_DEBUG else { qDebug() << renderName() << "Ignoring page-break avoid"; } #endif } if (doBreak) { #ifdef PAGE_DEBUG qDebug() << renderName() << " Clearing child of page-break"; qDebug() << renderName() << " child top of page " << xpage; #endif clearPageBreak(child, pageBreakInfo.pageBottom()); child->setNeedsPageClear(false); setContainsPageBreak(true); } } pageBreakInfo.setPageBottom(pageBreakInfo.pageBottom() + canvas()->pageHeight()); } else if (child->yPos() >= pageBreakInfo.pageBottom()) { bool doBreak = true; #ifdef PAGE_DEBUG qDebug() << "Page-break between children"; #endif if (!style()->pageBreakInside() || m_avoidPageBreak) { if (parent() && parent()->canClear(this, (m_avoidPageBreak) ? PageBreakHarder : PageBreakNormal)) { #ifdef PAGE_DEBUG qDebug() << "Avoid page-break inside"; #endif child->setNeedsPageClear(false); setNeedsPageClear(true); doBreak = false; } #ifdef PAGE_DEBUG else { qDebug() << "Ignoring page-break avoid"; } #endif } if (doBreak) { // Break between children setContainsPageBreak(true); // ### Should collapse top-margin with page-margin } pageBreakInfo.setPageBottom(pageBreakInfo.pageBottom() + canvas()->pageHeight()); } // Do we need a forced page-break before next child? pageBreakInfo.setForcePageBreak(false); if (child->style()->pageBreakAfter() == PBALWAYS) { pageBreakInfo.setForcePageBreak(true); } } void RenderBlock::layoutPositionedObjects(bool relayoutChildren) { if (m_positionedObjects) { for (int i = 0; i < m_positionedObjects->size(); i++) { // size() can grow during loop RenderObject *const r = m_positionedObjects->at(i); if (r->markedForRepaint()) { r->repaintDuringLayout(); r->setMarkedForRepaint(false); } if (relayoutChildren || (r->isPosWithStaticDim() && r->parent() != this && r->parent()->isBlockFlow())) { r->setChildNeedsLayout(true, false); r->dirtyFormattingContext(false); } r->layoutIfNeeded(); } } } void RenderBlock::paint(PaintInfo &pI, int _tx, int _ty) { _tx += m_x; _ty += m_y; // check if we need to do anything at all... if (!isRoot() && !isInlineFlow() && !isRelPositioned() && !isPositioned()) { int h = m_overflowHeight; int yPos = _ty; if (m_floatingObjects && floatBottom() > h) { h = floatBottom(); } yPos += overflowTop(); int os = maximalOutlineSize(pI.phase); if ((yPos > pI.r.bottom() + os) || (_ty + h <= pI.r.y() - os)) { return; } } paintObject(pI, _tx, _ty); } void RenderBlock::paintObject(PaintInfo &pI, int _tx, int _ty, bool shouldPaintOutline) { #ifdef DEBUG_LAYOUT qDebug() << renderName() << "(RenderBlock) " << this << " ::paintObject() w/h = (" << width() << "/" << height() << ")"; #endif // If we're a repositioned run-in, don't paint background/borders. bool inlineFlow = isInlineFlow(); // 1. paint background, borders etc if (!inlineFlow && (pI.phase == PaintActionElementBackground || pI.phase == PaintActionChildBackground) && shouldPaintBackgroundOrBorder() && style()->visibility() == VISIBLE) { paintBoxDecorations(pI, _tx, _ty); } if (pI.phase == PaintActionElementBackground) { return; } if (pI.phase == PaintActionChildBackgrounds) { pI.phase = PaintActionChildBackground; } // 2. paint contents int scrolledX = _tx; int scrolledY = _ty; if (hasOverflowClip() && m_layer) { m_layer->subtractScrollOffset(scrolledX, scrolledY); } if (childrenInline()) { paintLines(pI, scrolledX, scrolledY); } else { for (RenderObject *child = firstChild(); child; child = child->nextSibling()) if (!child->layer() && !child->isFloating()) { child->paint(pI, scrolledX, scrolledY); } } // 3. paint floats. if (!inlineFlow && (pI.phase == PaintActionFloat || pI.phase == PaintActionSelection)) { paintFloats(pI, scrolledX, scrolledY, pI.phase == PaintActionSelection); } // 4. paint outline. if (shouldPaintOutline && !inlineFlow && pI.phase == PaintActionOutline && style()->outlineWidth() && style()->visibility() == VISIBLE) { paintOutline(pI.p, _tx, _ty, width(), height(), style()); } // 5. paint caret. /* If the caret's node's render object's containing block is this block, and the paint action is PaintActionForeground, then paint the caret. */ if ((!canvas()->hasSelection() && pI.phase == PaintActionForeground) || pI.phase == PaintActionSelection) { KHTMLPart *part = document()->part(); const Selection &s = part->caret(); NodeImpl *baseNode = s.extent().node(); RenderObject *renderer = baseNode ? baseNode->renderer() : nullptr; if (renderer && renderer->containingBlock() == this && (part->isCaretMode() || baseNode->isContentEditable())) { part->paintCaret(pI.p, pI.r); part->paintDragCaret(pI.p, pI.r); } } #ifdef BOX_DEBUG if (style() && style()->visibility() == VISIBLE) { if (isAnonymous()) { outlineBox(pI.p, _tx, _ty, "green"); } if (isFloating()) { outlineBox(pI.p, _tx, _ty, "yellow"); } else { outlineBox(pI.p, _tx, _ty); } } #endif } QRegion RenderBlock::visibleFloatingRegion(int x, int y) const { if (!m_floatingObjects) { return QRegion(); } FloatingObject *fo; QRegion r; QListIterator it(*m_floatingObjects); while (it.hasNext()) { fo = it.next(); if (!fo->noPaint && !fo->node->layer() && fo->node->style()->visibility() == VISIBLE) { const RenderStyle *s = fo->node->style(); int ow = s->outlineSize(); if (s->backgroundImage() || s->backgroundColor().isValid() || s->hasBorder() || fo->node->isReplaced() || ow) { r += QRect(x - ow + fo->left + fo->node->marginLeft(), y - ow + fo->startY + fo->node->marginTop(), fo->width + ow * 2 - fo->node->marginLeft() - fo->node->marginRight(), fo->endY - fo->startY + ow * 2 - fo->node->marginTop() - fo->node->marginBottom()); } else { r += fo->node->visibleFlowRegion(x + fo->left + fo->node->marginLeft(), y + fo->startY + fo->node->marginTop()); } } } return r; } void RenderBlock::paintFloats(PaintInfo &pI, int _tx, int _ty, bool paintSelection) { if (!m_floatingObjects) { return; } FloatingObject *r; QListIterator it(*m_floatingObjects); while (it.hasNext()) { r = it.next(); // Only paint the object if our noPaint flag isn't set. if (r->node->isFloating() && !r->noPaint && !r->node->layer()) { PaintAction oldphase = pI.phase; if (paintSelection) { pI.phase = PaintActionSelection; r->node->paint(pI, _tx + r->left - r->node->xPos() + r->node->marginLeft(), _ty + r->startY - r->node->yPos() + r->node->marginTop()); } else { pI.phase = PaintActionElementBackground; r->node->paint(pI, _tx + r->left - r->node->xPos() + r->node->marginLeft(), _ty + r->startY - r->node->yPos() + r->node->marginTop()); pI.phase = PaintActionChildBackgrounds; r->node->paint(pI, _tx + r->left - r->node->xPos() + r->node->marginLeft(), _ty + r->startY - r->node->yPos() + r->node->marginTop()); pI.phase = PaintActionFloat; r->node->paint(pI, _tx + r->left - r->node->xPos() + r->node->marginLeft(), _ty + r->startY - r->node->yPos() + r->node->marginTop()); pI.phase = PaintActionForeground; r->node->paint(pI, _tx + r->left - r->node->xPos() + r->node->marginLeft(), _ty + r->startY - r->node->yPos() + r->node->marginTop()); pI.phase = PaintActionOutline; r->node->paint(pI, _tx + r->left - r->node->xPos() + r->node->marginLeft(), _ty + r->startY - r->node->yPos() + r->node->marginTop()); } pI.phase = oldphase; } } } void RenderBlock::insertPositionedObject(RenderObject *o) { // Create the list of special objects if we don't aleady have one if (!m_positionedObjects) { m_positionedObjects = new QList; } // Create the special object entry & append it to the list m_positionedObjects->append(o); o->setInPosObjectList(); } void RenderBlock::removePositionedObject(RenderObject *o) { if (m_positionedObjects) { m_positionedObjects->removeAll(o); if (m_positionedObjects->isEmpty()) { delete m_positionedObjects; m_positionedObjects = nullptr; } } o->setInPosObjectList(false); } void RenderBlock::insertFloatingObject(RenderObject *o) { // Create the list of special objects if we don't aleady have one if (!m_floatingObjects) { m_floatingObjects = new QList; } else { // Don't insert the object again if it's already in the list QListIterator it(*m_floatingObjects); FloatingObject *f; while (it.hasNext()) { f = it.next(); if (f->node == o) { return; } } } // Create the special object entry & append it to the list FloatingObject *newObj; if (o->isFloating()) { // floating object o->layoutIfNeeded(); if (o->style()->floating() & FLEFT) { newObj = new FloatingObject(FloatingObject::FloatLeft); } else { newObj = new FloatingObject(FloatingObject::FloatRight); } newObj->startY = -500000; newObj->endY = -500000; newObj->width = o->width() + o->marginLeft() + o->marginRight(); } else { // We should never get here, as insertFloatingObject() should only ever be called with floating // objects. KHTMLAssert(false); newObj = nullptr; // keep gcc's uninitialized variable warnings happy } newObj->node = o; m_floatingObjects->append(newObj); } void RenderBlock::removeFloatingObject(RenderObject *o) { if (m_floatingObjects) { QMutableListIterator it(*m_floatingObjects); while (it.hasNext()) { if (it.next()->node == o) { delete it.peekPrevious(); it.remove(); } } } } void RenderBlock::positionNewFloats() { if (!m_floatingObjects) { return; } QListIterator it(*m_floatingObjects); it.toBack(); if (!it.hasPrevious() || it.previous()->startY != -500000) { return; } FloatingObject *lastFloat; while (1) { lastFloat = it.hasPrevious() ? it.previous() : nullptr; if (!lastFloat || lastFloat->startY != -500000) { if (lastFloat) { it.next(); } break; } } int y = m_height; // the float can not start above the y position of the last positioned float. if (lastFloat && lastFloat->startY > y) { y = lastFloat->startY; } KHTMLAssert(it.hasNext()); FloatingObject *f = it.next(); while (f) { //skip elements copied from elsewhere and positioned elements if (f->node->containingBlock() != this) { f = it.hasNext() ? it.next() : nullptr; continue; } RenderObject *o = f->node; int _height = o->height() + o->marginTop() + o->marginBottom(); // floats avoid page-breaks if (canvas()->pagedMode()) { int top = y; int bottom = y + o->height(); if (crossesPageBreak(top, bottom) && o->height() < canvas()->pageHeight()) { int newY = pageTopAfter(top); #ifdef PAGE_DEBUG qDebug() << renderName() << " clearing float " << newY - y << "px"; #endif y = newY; } } int ro = rightOffset(); // Constant part of right offset. int lo = leftOffset(); // Constant part of left offset. int fwidth = f->width; // The width we look for. //qDebug() << " Object width: " << fwidth << " available width: " << ro - lo; // in quirk mode, floated auto-width tables try to fit within remaining linewidth bool ftQuirk = o->isTable() && style()->htmlHacks() && o->style()->width().isAuto(); if (ftQuirk) { fwidth = qMin(o->minWidth() + o->marginLeft() + o->marginRight(), fwidth); } if (ro - lo < fwidth) { fwidth = ro - lo; // Never look for more than what will be available. } if (o->style()->clear() & CLEFT) { y = qMax(leftBottom(), y); } if (o->style()->clear() & CRIGHT) { y = qMax(rightBottom(), y); } bool canClearLine; if (o->style()->floating() & FLEFT) { int heightRemainingLeft = 1; int heightRemainingRight = 1; int fx = leftRelOffset(y, lo, false, &heightRemainingLeft, &canClearLine); if (canClearLine) { while (rightRelOffset(y, ro, false, &heightRemainingRight) - fx < fwidth) { y += qMin(heightRemainingLeft, heightRemainingRight); fx = leftRelOffset(y, lo, false, &heightRemainingLeft); } } if (ftQuirk && (rightRelOffset(y, ro, false) - fx < f->width)) { o->setPos(o->xPos(), y + o->marginTop()); o->setChildNeedsLayout(true, false); o->layoutIfNeeded(); _height = o->height() + o->marginTop() + o->marginBottom(); f->width = o->width() + o->marginLeft() + o->marginRight(); } f->left = fx; //qDebug() << "positioning left aligned float at (" << fx + o->marginLeft() << "/" << y + o->marginTop() << ") fx=" << fx; o->setPos(fx + o->marginLeft(), y + o->marginTop()); } else { int heightRemainingLeft = 1; int heightRemainingRight = 1; int fx = rightRelOffset(y, ro, false, &heightRemainingRight, &canClearLine); if (canClearLine) { while (fx - leftRelOffset(y, lo, false, &heightRemainingLeft) < fwidth) { y += qMin(heightRemainingLeft, heightRemainingRight); fx = rightRelOffset(y, ro, false, &heightRemainingRight); } } if (ftQuirk && (fx - leftRelOffset(y, lo, false) < f->width)) { o->setPos(o->xPos(), y + o->marginTop()); o->setChildNeedsLayout(true, false); o->layoutIfNeeded(); _height = o->height() + o->marginTop() + o->marginBottom(); f->width = o->width() + o->marginLeft() + o->marginRight(); } f->left = fx - f->width; //qDebug() << "positioning right aligned float at (" << fx - o->marginRight() - o->width() << "/" << y + o->marginTop() << ")"; o->setPos(fx - o->marginRight() - o->width(), y + o->marginTop()); } if (m_layer && hasOverflowClip()) { if (o->xPos() + o->width() > m_overflowWidth) { m_overflowWidth = o->xPos() + o->width(); } else if (o->xPos() < m_overflowLeft) { m_overflowLeft = o->xPos(); } } f->startY = y; f->endY = f->startY + _height; //qDebug() << "floatingObject x/y= (" << f->left << "/" << f->startY << "-" << f->width << "/" << f->endY - f->startY << ")"; f = it.hasNext() ? it.next() : nullptr; } } void RenderBlock::newLine() { positionNewFloats(); // set y position int newY = 0; switch (m_clearStatus) { case CLEFT: newY = leftBottom(); break; case CRIGHT: newY = rightBottom(); break; case CBOTH: newY = floatBottom(); default: break; } if (m_height < newY) { // qDebug() << "adjusting y position"; m_height = newY; } m_clearStatus = CNONE; } int RenderBlock::leftOffset() const { int left = borderLeft() + paddingLeft(); if (m_layer && scrollsOverflowY() && m_layer->hasReversedScrollbar()) { left += m_layer->verticalScrollbarWidth(); } return left; } int RenderBlock::leftRelOffset(int y, int fixedOffset, bool applyTextIndent, int *heightRemaining, bool *canClearLine) const { int left = fixedOffset; if (canClearLine) { *canClearLine = true; } if (m_floatingObjects) { if (heightRemaining) { *heightRemaining = 1; } FloatingObject *r; QListIterator it(*m_floatingObjects); while (it.hasNext()) { r = it.next(); //qDebug() <<(void *)this << " left: sy, ey, x, w " << r->startY << "," << r->endY << "," << r->left << "," << r->width << " "; if (r->startY <= y && r->endY > y && r->type == FloatingObject::FloatLeft && r->left + r->width > left) { left = r->left + r->width; if (heightRemaining) { *heightRemaining = r->endY - y; } if (canClearLine) { *canClearLine = (r->node->style()->floating() != FLEFT_ALIGN); } } } } if (applyTextIndent && m_firstLine && style()->direction() == LTR) { int cw = 0; if (style()->textIndent().isPercent()) { cw = containingBlock()->contentWidth(); } left += style()->textIndent().minWidth(cw); } //qDebug() << "leftOffset(" << y << ") = " << left; return left; } int RenderBlock::rightOffset() const { int right = m_width - borderRight() - paddingRight(); if (m_layer && scrollsOverflowY() && !m_layer->hasReversedScrollbar()) { right -= m_layer->verticalScrollbarWidth(); } return right; } int RenderBlock::rightRelOffset(int y, int fixedOffset, bool applyTextIndent, int *heightRemaining, bool *canClearLine) const { int right = fixedOffset; if (canClearLine) { *canClearLine = true; } if (m_floatingObjects) { if (heightRemaining) { *heightRemaining = 1; } FloatingObject *r; QListIterator it(*m_floatingObjects); while (it.hasNext()) { r = it.next(); //qDebug() << "right: sy, ey, x, w " << r->startY << "," << r->endY << "," << r->left << "," << r->width << " "; if (r->startY <= y && r->endY > y && r->type == FloatingObject::FloatRight && r->left < right) { right = r->left; if (heightRemaining) { *heightRemaining = r->endY - y; } if (canClearLine) { *canClearLine = (r->node->style()->floating() != FRIGHT_ALIGN); } } } } if (applyTextIndent && m_firstLine && style()->direction() == RTL) { int cw = 0; if (style()->textIndent().isPercent()) { cw = containingBlock()->contentWidth(); } right -= style()->textIndent().minWidth(cw); } //qDebug() << "rightOffset(" << y << ") = " << right; return right; } unsigned short RenderBlock::lineWidth(int y, bool *canClearLine) const { //qDebug() << "lineWidth(" << y << ")=" << rightOffset(y) - leftOffset(y); int result; if (canClearLine) { bool rightCanClearLine; bool leftCanClearLine; result = rightOffset(y, &rightCanClearLine) - leftOffset(y, &leftCanClearLine); *canClearLine = rightCanClearLine && leftCanClearLine; } else { result = rightOffset(y) - leftOffset(y); } return (result < 0) ? 0 : result; } int RenderBlock::nearestFloatBottom(int height) const { if (!m_floatingObjects) { return 0; } int bottom = INT_MAX; FloatingObject *r; QListIterator it(*m_floatingObjects); while (it.hasNext()) { r = it.next(); if (r->endY > height) { bottom = qMin(r->endY, bottom); } } return bottom == INT_MAX ? 0 : bottom; } int RenderBlock::floatBottom() const { if (!m_floatingObjects) { return 0; } int bottom = 0; FloatingObject *r; QListIterator it(*m_floatingObjects); while (it.hasNext()) { r = it.next(); if (r->endY > bottom) { bottom = r->endY; } } return bottom; } int RenderBlock::lowestPosition(bool includeOverflowInterior, bool includeSelf) const { int bottom = RenderFlow::lowestPosition(includeOverflowInterior, includeSelf); if (!includeOverflowInterior && hasOverflowClip()) { return bottom; } if (includeSelf && m_overflowHeight > bottom) { bottom = m_overflowHeight; } if (m_floatingObjects) { FloatingObject *r; QListIterator it(*m_floatingObjects); while (it.hasNext()) { r = it.next(); if (!r->noPaint) { int lp = r->startY + r->node->marginTop() + r->node->lowestPosition(false); bottom = qMax(bottom, lp); } } } bottom = qMax(bottom, lowestAbsolutePosition()); if (!includeSelf && lastLineBox()) { int lp = lastLineBox()->yPos() + lastLineBox()->height(); bottom = qMax(bottom, lp); } return bottom; } int RenderBlock::lowestAbsolutePosition() const { if (!m_positionedObjects) { return 0; } // Fixed positioned objects do not scroll and thus should not constitute // part of the lowest position. int bottom = 0; RenderObject *r; QListIterator it(*m_positionedObjects); while (it.hasNext()) { r = it.next(); if (r->style()->position() == PFIXED) { continue; } int lp = r->yPos() + r->lowestPosition(false); bottom = qMax(bottom, lp); } return bottom; } int RenderBlock::rightmostPosition(bool includeOverflowInterior, bool includeSelf) const { int right = RenderFlow::rightmostPosition(includeOverflowInterior, includeSelf); if (!includeOverflowInterior && hasOverflowClip()) { return right; } if (includeSelf && m_overflowWidth > right) { right = m_overflowWidth; } if (m_floatingObjects) { FloatingObject *r; QListIterator it(*m_floatingObjects); while (it.hasNext()) { r = it.next(); if (!r->noPaint) { int rp = r->left + r->node->marginLeft() + r->node->rightmostPosition(false); right = qMax(right, rp); } } } right = qMax(right, rightmostAbsolutePosition()); if (!includeSelf && firstLineBox()) { for (InlineRunBox *currBox = firstLineBox(); currBox; currBox = currBox->nextLineBox()) { int rp = currBox->xPos() + currBox->width(); right = qMax(right, rp); } } return right; } int RenderBlock::rightmostAbsolutePosition() const { if (!m_positionedObjects) { return 0; } int right = 0; RenderObject *r; QListIterator it(*m_positionedObjects); while (it.hasNext()) { r = it.next(); if (r->style()->position() == PFIXED) { continue; } int rp = r->xPos() + r->rightmostPosition(false); right = qMax(right, rp); } return right; } int RenderBlock::leftmostPosition(bool includeOverflowInterior, bool includeSelf) const { int left = RenderFlow::leftmostPosition(includeOverflowInterior, includeSelf); if (!includeOverflowInterior && hasOverflowClip()) { return left; } if (includeSelf && m_overflowLeft < left) { left = m_overflowLeft; } if (m_floatingObjects) { FloatingObject *r; QListIterator it(*m_floatingObjects); while (it.hasNext()) { r = it.next(); if (!r->noPaint) { int lp = r->left + r->node->marginLeft() + r->node->leftmostPosition(false); left = qMin(left, lp); } } } left = qMin(left, leftmostAbsolutePosition()); if (!includeSelf && firstLineBox()) { for (InlineRunBox *currBox = firstLineBox(); currBox; currBox = currBox->nextLineBox()) { left = qMin(left, (int)currBox->xPos()); } } return left; } int RenderBlock::leftmostAbsolutePosition() const { if (!m_positionedObjects) { return 0; } int left = 0; RenderObject *r; QListIterator it(*m_positionedObjects); while (it.hasNext()) { r = it.next(); if (r->style()->position() == PFIXED) { continue; } int lp = r->xPos() + r->leftmostPosition(false); left = qMin(left, lp); } return left; } int RenderBlock::highestPosition(bool includeOverflowInterior, bool includeSelf) const { int top = RenderFlow::highestPosition(includeOverflowInterior, includeSelf); if (!includeOverflowInterior && hasOverflowClip()) { return top; } if (includeSelf && m_overflowTop < top) { top = m_overflowTop; } if (m_floatingObjects) { FloatingObject *r; QListIterator it(*m_floatingObjects); while (it.hasNext()) { r = it.next(); if (!r->noPaint) { int hp = r->startY + r->node->marginTop() + r->node->highestPosition(false); top = qMin(top, hp); } } } top = qMin(top, highestAbsolutePosition()); if (!includeSelf && firstLineBox()) { top = qMin(top, (int)firstLineBox()->yPos()); } return top; } int RenderBlock::highestAbsolutePosition() const { if (!m_positionedObjects) { return 0; } int top = 0; RenderObject *r; QListIterator it(*m_positionedObjects); while (it.hasNext()) { r = it.next(); if (r->style()->position() == PFIXED) { continue; } int hp = r->yPos() + r->highestPosition(false); hp = qMin(top, hp); } return top; } int RenderBlock::leftBottom() { if (!m_floatingObjects) { return 0; } int bottom = 0; FloatingObject *r; QListIterator it(*m_floatingObjects); while (it.hasNext()) { r = it.next(); if (r->endY > bottom && r->type == FloatingObject::FloatLeft) { bottom = r->endY; } } return bottom; } int RenderBlock::rightBottom() { if (!m_floatingObjects) { return 0; } int bottom = 0; FloatingObject *r; QListIterator it(*m_floatingObjects); while (it.hasNext()) { r = it.next(); if (r->endY > bottom && r->type == FloatingObject::FloatRight) { bottom = r->endY; } } return bottom; } void RenderBlock::clearFloats() { if (m_floatingObjects) { QListIterator it(*m_floatingObjects); while (it.hasNext()) { delete it.next(); } m_floatingObjects->clear(); } // we are done if the element defines a new block formatting context if (flowAroundFloats() || isRoot() || isCanvas() || isFloatingOrPositioned() || isTableCell()) { return; } RenderObject *prev = previousSibling(); // find the element to copy the floats from // pass non-flows // pass fAF's bool parentHasFloats = false; while (prev) { if (!prev->isRenderBlock() || prev->isFloatingOrPositioned() || prev->flowAroundFloats()) { if (prev->isFloating() && parent()->isRenderBlock()) { parentHasFloats = true; } prev = prev->previousSibling(); } else { break; } } int offset = m_y; if (parentHasFloats) { addOverHangingFloats(static_cast(parent()), parent()->borderLeft() + parent()->paddingLeft(), offset, false); } int xoffset = 0; if (prev) { if (prev->isTableCell()) { return; } offset -= prev->yPos(); } else { prev = parent(); if (!prev) { return; } xoffset += prev->borderLeft() + prev->paddingLeft(); } //qDebug() << "RenderBlock::clearFloats found previous "<< (void *)this << " prev=" << (void *)prev; // add overhanging special objects from the previous RenderBlock if (!prev->isRenderBlock()) { return; } RenderBlock *flow = static_cast(prev); if (!flow->m_floatingObjects) { return; } if (flow->floatBottom() > offset) { addOverHangingFloats(flow, xoffset, offset, false); } } void RenderBlock::addOverHangingFloats(RenderBlock *flow, int xoff, int offset, bool child) { #ifdef DEBUG_LAYOUT qDebug() << (void *)this << ": adding overhanging floats xoff=" << xoff << " offset=" << offset << " child=" << child; #endif // Prevent floats from being added to the canvas by the root element, e.g., . if (!flow->m_floatingObjects || (child && flow->isRoot())) { return; } // if I am clear of my floats, don't add them // the CSS spec also mentions that child floats // are not cleared. if (!child && style()->clear() == CBOTH) { return; } QListIterator it(*flow->m_floatingObjects); FloatingObject *r; while (it.hasNext()) { r = it.next(); if (!child && r->type == FloatingObject::FloatLeft && style()->clear() == CLEFT) { continue; } if (!child && r->type == FloatingObject::FloatRight && style()->clear() == CRIGHT) { continue; } if ((!child && r->endY > offset) || (child && flow->yPos() + r->endY > height())) { if (child && !r->crossedLayer) { if (flow->enclosingLayer() == enclosingLayer()) { // Set noPaint to true only if we didn't cross layers. r->noPaint = true; } else { r->crossedLayer = true; } } // don't insert it twice! if (!containsFloat(r->node)) { FloatingObject *floatingObj = new FloatingObject(KDE_CAST_BF_ENUM(FloatingObject::Type, r->type)); floatingObj->startY = r->startY - offset; floatingObj->endY = r->endY - offset; floatingObj->left = r->left - xoff; // Applying the child's margin makes no sense in the case where the child was passed in. // since his own margin was added already through the subtraction of the |xoff| variable // above. |xoff| will equal -flow->marginLeft() in this case, so it's already been taken // into account. Only apply this code if |child| is false, since otherwise the left margin // will get applied twice. -dwh if (!child && flow != parent()) { floatingObj->left += flow->marginLeft(); } if (!child) { floatingObj->left -= marginLeft(); floatingObj->noPaint = true; } else { floatingObj->noPaint = (r->crossedLayer || !r->noPaint); floatingObj->crossedLayer = r->crossedLayer; } floatingObj->width = r->width; floatingObj->node = r->node; if (!m_floatingObjects) { m_floatingObjects = new QList; } m_floatingObjects->append(floatingObj); #ifdef DEBUG_LAYOUT qDebug() << "addOverHangingFloats x/y= (" << floatingObj->left << "/" << floatingObj->startY << "-" << floatingObj->width << "/" << floatingObj->endY - floatingObj->startY << ")"; #endif } } } } bool RenderBlock::containsFloat(RenderObject *o) const { if (m_floatingObjects) { QListIterator it(*m_floatingObjects); while (it.hasNext()) { if (it.next()->node == o) { return true; } } } return false; } void RenderBlock::markAllDescendantsWithFloatsForLayout(RenderObject *floatToRemove) { dirtyFormattingContext(false); setChildNeedsLayout(true); if (floatToRemove) { removeFloatingObject(floatToRemove); } // Iterate over our children and mark them as needed. if (!childrenInline()) { for (RenderObject *child = firstChild(); child; child = child->nextSibling()) { if (isBlockFlow() && !child->isFloatingOrPositioned() && ((floatToRemove ? child->containsFloat(floatToRemove) : child->hasFloats()) || child->usesLineWidth())) { child->markAllDescendantsWithFloatsForLayout(floatToRemove); } } } } int RenderBlock::getClearDelta(RenderObject *child, int yPos) { if (!hasFloats()) { return 0; } //qDebug() << "getClearDelta on child " << child << " oldheight=" << m_height; bool clearSet = child->style()->clear() != CNONE; int bottom = 0; switch (child->style()->clear()) { case CNONE: break; case CLEFT: bottom = leftBottom(); break; case CRIGHT: bottom = rightBottom(); break; case CBOTH: bottom = floatBottom(); break; } // We also clear floats if we are too big to sit on the same line as // a float, and happen to flow around floats. int result = clearSet ? qMax(0, bottom - yPos) : 0; if (!result && child->flowAroundFloats()) { bool canClear = true; bool needsRecalc = child->usesLineWidth(); int cury = yPos; int childw = 0; int aw = contentWidth(); #if 0 // this is a silly Gecko compatibility hack - enable only if it becomes // necessary, and then check regularly with new Gecko versions if (!style()->hasBorder()) { RenderObject *ps = child; while ((ps = ps->previousSibling())) { if (!ps->isFloating() && !ps->isPositioned()) { break; } } if (!ps) { return 0; } } #endif while (true) { int curw = lineWidth(cury, &canClear); if (!canClear || curw == aw) { return cury - yPos; } if (!childw || needsRecalc) { int oy = child->yPos(); int ow = child->width(); child->setPos(child->xPos(), cury); child->calcWidth(); childw = child->width(); child->setPos(child->xPos(), oy); child->setWidth(ow); } if (childw <= curw) { return cury - yPos; } if (!(cury = nearestFloatBottom(cury))) { return 0; } } } return result; } bool RenderBlock::isPointInScrollbar(int _x, int _y, int _tx, int _ty) { if (!scrollsOverflow() || !m_layer) { return false; } if (m_layer->verticalScrollbarWidth()) { bool rtl = QApplication::isRightToLeft(); QRect vertRect(_tx + (rtl ? borderLeft() : width() - borderRight() - m_layer->verticalScrollbarWidth()), _ty + borderTop() - borderTopExtra(), m_layer->verticalScrollbarWidth(), height() + borderTopExtra() + borderBottomExtra() - borderTop() - borderBottom()); if (vertRect.contains(_x, _y)) { RenderLayer::gScrollBar = m_layer->verticalScrollbar(); return true; } } if (m_layer->horizontalScrollbarHeight()) { QRect horizRect(_tx + borderLeft(), _ty + height() - borderBottom() + borderBottomExtra() - m_layer->horizontalScrollbarHeight(), width() - borderLeft() - borderRight(), m_layer->horizontalScrollbarHeight()); if (horizRect.contains(_x, _y)) { RenderLayer::gScrollBar = m_layer->horizontalScrollbar(); return true; } } return false; } bool RenderBlock::nodeAtPoint(NodeInfo &info, int _x, int _y, int _tx, int _ty, HitTestAction hitTestAction, bool inBox) { bool inScrollbar = isPointInScrollbar(_x, _y, _tx + xPos(), _ty + yPos()); if (inScrollbar && hitTestAction != HitTestChildrenOnly) { inBox = true; } if (hitTestAction != HitTestSelfOnly && m_floatingObjects && !inScrollbar) { int stx = _tx + xPos(); int sty = _ty + yPos(); if (hasOverflowClip() && m_layer) { m_layer->subtractScrollOffset(stx, sty); } FloatingObject *o; QListIterator it(*m_floatingObjects); it.toBack(); while (it.hasPrevious()) { o = it.previous(); if (!o->noPaint && !o->node->layer()) inBox |= o->node->nodeAtPoint(info, _x, _y, stx + o->left + o->node->marginLeft() - o->node->xPos(), sty + o->startY + o->node->marginTop() - o->node->yPos(), HitTestAll); } } inBox |= RenderFlow::nodeAtPoint(info, _x, _y, _tx, _ty, hitTestAction, inBox); return inBox; } RenderPosition RenderBlock::positionForBox(InlineBox *box, bool start) const { if (!box) { return RenderPosition(); } if (!box->object()->element()) { return RenderPosition(element(), start ? caretMinOffset() : caretMaxOffset()); } if (!box->isInlineTextBox()) { return RenderPosition(box->object()->element(), start ? box->object()->caretMinOffset() : box->object()->caretMaxOffset()); } InlineTextBox *textBox = static_cast(box); return RenderPosition(box->object()->element(), start ? textBox->start() : textBox->start() + textBox->len()); } RenderPosition RenderBlock::positionForRenderer(RenderObject *renderer, bool start) const { if (!renderer) { return RenderPosition(element(), 0); } NodeImpl *node = renderer->element() ? renderer->element() : element(); if (!node) { return RenderPosition(); } long offset = start ? node->caretMinOffset() : node->caretMaxOffset(); return RenderPosition(node, offset); } RenderPosition RenderBlock::positionForCoordinates(int _x, int _y) { if (isTable()) { return RenderFlow::positionForCoordinates(_x, _y); } int absx, absy; absolutePosition(absx, absy); int top = absy + borderTop() + paddingTop(); int bottom = top + contentHeight(); if (_y < top) // y coordinate is above block { return positionForRenderer(firstLeafChild(), true); } if (_y >= bottom) // y coordinate is below block { return positionForRenderer(lastLeafChild(), false); } if (childrenInline()) { if (!firstRootBox()) { return Position(element(), 0); } if (_y >= top && _y < absy + firstRootBox()->topOverflow()) // y coordinate is above first root line box { return positionForBox(firstRootBox()->firstLeafChild(), true); } // look for the closest line box in the root box which is at the passed-in y coordinate for (RootInlineBox *root = firstRootBox(); root; root = root->nextRootBox()) { top = absy + root->topOverflow(); // set the bottom based on whether there is a next root box if (root->nextRootBox()) { bottom = absy + root->nextRootBox()->topOverflow(); } else { bottom = absy + root->bottomOverflow(); } // check if this root line box is located at this y coordinate if (_y >= top && _y < bottom && root->firstChild()) { InlineBox *closestBox = root->closestLeafChildForXPos(_x, absx); if (closestBox) { // pass the box a y position that is inside it return closestBox->object()->positionForCoordinates(_x, absy + closestBox->m_y); } } } if (lastRootBox()) // y coordinate is below last root line box { return positionForBox(lastRootBox()->lastLeafChild(), false); } return RenderPosition(element(), 0); } // see if any child blocks exist at this y coordinate for (RenderObject *renderer = firstChild(); renderer; renderer = renderer->nextSibling()) { if (renderer->isFloatingOrPositioned()) { continue; } renderer->absolutePosition(absx, top); RenderObject *next = renderer->nextSibling(); while (next && next->isFloatingOrPositioned()) { next = next->nextSibling(); } if (next) { next->absolutePosition(absx, bottom); } else { bottom = top + contentHeight(); } if (_y >= top && _y < bottom) { return renderer->positionForCoordinates(_x, _y); } } // pass along to the first child if (firstChild()) { return firstChild()->positionForCoordinates(_x, _y); } // still no luck...return this render object's element and offset 0 return RenderPosition(element(), 0); } void RenderBlock::calcMinMaxWidth() { KHTMLAssert(!minMaxKnown()); #ifdef DEBUG_LAYOUT qDebug() << renderName() << "(RenderBlock)::calcMinMaxWidth() this=" << this; #endif if (!isTableCell() && style()->width().isFixed() && style()->width().isPositive()) { m_minWidth = m_maxWidth = calcContentWidth(style()->width().value()); } else { m_minWidth = 0; m_maxWidth = 0; bool noWrap = !style()->autoWrap(); if (childrenInline()) { calcInlineMinMaxWidth(); } else { calcBlockMinMaxWidth(); } if (m_maxWidth < m_minWidth) { m_maxWidth = m_minWidth; } if (noWrap && childrenInline()) { m_minWidth = m_maxWidth; // A horizontal marquee with inline children has no minimum width. if (style()->overflowX() == OMARQUEE && m_layer && m_layer->marquee() && m_layer->marquee()->isHorizontal() && !m_layer->marquee()->isUnfurlMarquee()) { m_minWidth = 0; } } if (isTableCell()) { Length w = static_cast(this)->styleOrColWidth(); if (w.isFixed() && w.isPositive()) { m_maxWidth = qMax((int)m_minWidth, calcContentWidth(w.value())); } } } if (style()->minWidth().isFixed() && style()->minWidth().isPositive()) { m_maxWidth = qMax(m_maxWidth, (int)calcContentWidth(style()->minWidth().value())); m_minWidth = qMax(m_minWidth, (short)calcContentWidth(style()->minWidth().value())); } if (style()->maxWidth().isFixed() && !style()->maxWidth().isUndefined()) { m_maxWidth = qMin(m_maxWidth, (int)calcContentWidth(style()->maxWidth().value())); m_minWidth = qMin(m_minWidth, (short)calcContentWidth(style()->maxWidth().value())); } int toAdd = 0; toAdd = borderLeft() + borderRight() + paddingLeft() + paddingRight(); m_minWidth += toAdd; m_maxWidth += toAdd; setMinMaxKnown(); //qDebug() << "Text::calcMinMaxWidth(" << this << "): min = " << m_minWidth << " max = " << m_maxWidth; // ### compare with min/max width set in style sheet... } // bidi.cpp defines the following functions too. // Maybe these should not be static, after all... static int getBPMWidth(int childValue, Length cssUnit) { if (!cssUnit.isAuto()) { return (cssUnit.isFixed() ? cssUnit.value() : childValue); } return 0; } static int getBorderPaddingMargin(RenderObject *child, bool endOfInline) { RenderStyle *cstyle = child->style(); int result = 0; bool leftSide = (cstyle->direction() == LTR) ? !endOfInline : endOfInline; result += getBPMWidth((leftSide ? child->marginLeft() : child->marginRight()), (leftSide ? cstyle->marginLeft() : cstyle->marginRight())); result += getBPMWidth((leftSide ? child->paddingLeft() : child->paddingRight()), (leftSide ? cstyle->paddingLeft() : cstyle->paddingRight())); result += leftSide ? child->borderLeft() : child->borderRight(); return result; } static void stripTrailingSpace(bool preserveWS, int &inlineMax, int &inlineMin, RenderObject *trailingSpaceChild) { if (!preserveWS && trailingSpaceChild && trailingSpaceChild->isText()) { // Collapse away the trailing space at the end of a block. RenderText *t = static_cast(trailingSpaceChild); const Font *f = t->htmlFont(false); QChar space[1]; space[0] = ' '; int spaceWidth = f->charWidth(space, 1, 0, true/*fast algo*/); inlineMax -= spaceWidth; if (inlineMin > inlineMax) { inlineMin = inlineMax; } } } void RenderBlock::calcInlineMinMaxWidth() { int inlineMax = 0; int inlineMin = 0; int cw = containingBlock()->contentWidth(); // If we are at the start of a line, we want to ignore all white-space. // Also strip spaces if we previously had text that ended in a trailing space. bool stripFrontSpaces = true; bool isTcQuirk = isTableCell() && style()->htmlHacks() && style()->width().isAuto(); RenderObject *trailingSpaceChild = nullptr; bool autoWrap, oldAutoWrap; autoWrap = oldAutoWrap = style()->autoWrap(); InlineMinMaxIterator childIterator(this, this); bool addedTextIndent = false; // Only gets added in once. RenderObject *prevFloat = nullptr; while (RenderObject *child = childIterator.next()) { autoWrap = child->isReplaced() ? child->parent()->style()->autoWrap() : child->style()->autoWrap(); if (!child->isBR()) { // Step One: determine whether or not we need to go ahead and // terminate our current line. Each discrete chunk can become // the new min-width, if it is the widest chunk seen so far, and // it can also become the max-width. // Children fall into three categories: // (1) An inline flow object. These objects always have a min/max of 0, // and are included in the iteration solely so that their margins can // be added in. // // (2) An inline non-text non-flow object, e.g., an inline replaced element. // These objects can always be on a line by themselves, so in this situation // we need to go ahead and break the current line, and then add in our own // margins and min/max width on its own line, and then terminate the line. // // (3) A text object. Text runs can have breakable characters at the start, // the middle or the end. They may also lose whitespace off the front if // we're already ignoring whitespace. In order to compute accurate min-width // information, we need three pieces of information. // (a) the min-width of the first non-breakable run. Should be 0 if the text string // starts with whitespace. // (b) the min-width of the last non-breakable run. Should be 0 if the text string // ends with whitespace. // (c) the min/max width of the string (trimmed for whitespace). // // If the text string starts with whitespace, then we need to go ahead and // terminate our current line (unless we're already in a whitespace stripping // mode. // // If the text string has a breakable character in the middle, but didn't start // with whitespace, then we add the width of the first non-breakable run and // then end the current line. We then need to use the intermediate min/max width // values (if any of them are larger than our current min/max). We then look at // the width of the last non-breakable run and use that to start a new line // (unless we end in whitespace). RenderStyle *cstyle = child->style(); int childMin = 0; int childMax = 0; if (!child->isText()) { // Case (1) and (2). Inline replaced and inline flow elements. if (child->isInlineFlow()) { // Add in padding/border/margin from the appropriate side of // the element. int bpm = getBorderPaddingMargin(child, childIterator.endOfInline); childMin += bpm; childMax += bpm; inlineMin += childMin; inlineMax += childMax; if (child->isWordBreak()) { // End a line and start a new line. m_minWidth = qMax(inlineMin, (int)m_minWidth); inlineMin = 0; } } else { // Inline replaced elements add in their margins to their min/max values. int margins = 0; LengthType type = cstyle->marginLeft().type(); if (type == Fixed) { margins += cstyle->marginLeft().value(); } type = cstyle->marginRight().type(); if (type == Fixed) { margins += cstyle->marginRight().value(); } childMin += margins; childMax += margins; } } if (!child->isRenderInline() && !child->isText()) { // Case (2). Inline replaced elements and floats. // Common wrapping quirk bool qBreak = isTcQuirk && !child->isFloating(); childMin += child->minWidth(); childMax += child->maxWidth(); // Check our "clear" setting. bool clearPreviousFloat = false; if (child->isFloating()) { if (prevFloat && (((prevFloat->style()->floating() & FLEFT) && (child->style()->clear() & CLEFT)) || ((prevFloat->style()->floating() & FRIGHT) && (child->style()->clear() & CRIGHT)))) { clearPreviousFloat = true; } prevFloat = child; } if ((!qBreak && (autoWrap || oldAutoWrap)) || clearPreviousFloat) { if (m_minWidth < inlineMin) { m_minWidth = inlineMin; } inlineMin = 0; } // If we're supposed to clear the previous float, then terminate maxwidth as well. if (clearPreviousFloat) { m_maxWidth = qMax(inlineMax, m_maxWidth); inlineMax = 0; } // Add in text-indent. This is added in only once. int ti = 0; if (!addedTextIndent) { addedTextIndent = true; ti = style()->textIndent().minWidth(cw); childMin += ti; childMax += ti; } // Add our width to the max. inlineMax += childMax; if ((!autoWrap && !child->isFloating()) || qBreak) { inlineMin += childMin; } else { // Now check our line. m_minWidth = qMax(childMin, (int)m_minWidth); // Now start a new line. inlineMin = 0; } // We are no longer stripping whitespace at the start of // a line. if (!child->isFloating()) { stripFrontSpaces = false; trailingSpaceChild = nullptr; } } else if (child->isText()) { // Case (3). Text. RenderText *t = static_cast(child); // Determine if we have a breakable character. Pass in // whether or not we should ignore any spaces at the front // of the string. If those are going to be stripped out, // then they shouldn't be considered in the breakable char // check. bool hasBreakableChar, hasBreak; int beginMin, endMin; bool beginWS, endWS; int beginMax, endMax; t->trimmedMinMaxWidth(beginMin, beginWS, endMin, endWS, hasBreakableChar, hasBreak, beginMax, endMax, childMin, childMax, stripFrontSpaces); // This text object is insignificant and will not be rendered. Just // continue. if (!hasBreak && childMax == 0) { continue; } if (stripFrontSpaces) { trailingSpaceChild = child; } else { trailingSpaceChild = nullptr; } // Add in text-indent. This is added in only once. int ti = 0; if (!addedTextIndent) { addedTextIndent = true; ti = style()->textIndent().minWidth(cw); childMin += ti; beginMin += ti; childMax += ti; beginMax += ti; } // If we have no breakable characters at all, // then this is the easy case. We add ourselves to the current // min and max and continue. if (!hasBreakableChar) { inlineMin += childMin; } else { // We have a breakable character. Now we need to know if // we start and end with whitespace. if (beginWS) { // Go ahead and end the current line. if (m_minWidth < inlineMin) { m_minWidth = inlineMin; } } else { inlineMin += beginMin; if (m_minWidth < inlineMin) { m_minWidth = inlineMin; } childMin -= ti; } inlineMin = childMin; if (endWS) { // We end in whitespace, which means we can go ahead // and end our current line. if (m_minWidth < inlineMin) { m_minWidth = inlineMin; } inlineMin = 0; } else { if (m_minWidth < inlineMin) { m_minWidth = inlineMin; } inlineMin = endMin; } } if (hasBreak) { inlineMax += beginMax; if (m_maxWidth < inlineMax) { m_maxWidth = inlineMax; } if (m_maxWidth < childMax) { m_maxWidth = childMax; } inlineMax = endMax; } else { inlineMax += childMax; } } // Ignore spaces after a list marker. if (child->isListMarker()) { stripFrontSpaces = true; } } else { if (m_minWidth < inlineMin) { m_minWidth = inlineMin; } if (m_maxWidth < inlineMax) { m_maxWidth = inlineMax; } inlineMin = inlineMax = 0; stripFrontSpaces = true; trailingSpaceChild = nullptr; } oldAutoWrap = autoWrap; } stripTrailingSpace(style()->preserveWS(), inlineMax, inlineMin, trailingSpaceChild); if (m_minWidth < inlineMin) { m_minWidth = inlineMin; } if (m_maxWidth < inlineMax) { m_maxWidth = inlineMax; } // qDebug() << "m_minWidth=" << m_minWidth - // << " m_maxWidth=" << m_maxWidth << endl; + // << " m_maxWidth=" << m_maxWidth; } // Use a very large value (in effect infinite). #define BLOCK_MAX_WIDTH 15000 void RenderBlock::calcBlockMinMaxWidth() { bool nowrap = !style()->autoWrap(); RenderObject *child = firstChild(); int floatLeftWidth = 0, floatRightWidth = 0; while (child != nullptr) { // positioned children don't affect the minmaxwidth if (child->isPositioned()) { child = child->nextSibling(); continue; } if (child->isFloating() || child->flowAroundFloats()) { int floatTotalWidth = floatLeftWidth + floatRightWidth; if (child->style()->clear() & CLEFT) { m_maxWidth = qMax(floatTotalWidth, m_maxWidth); floatLeftWidth = 0; } if (child->style()->clear() & CRIGHT) { m_maxWidth = qMax(floatTotalWidth, m_maxWidth); floatRightWidth = 0; } } Length ml = child->style()->marginLeft(); Length mr = child->style()->marginRight(); // Call calcWidth on the child to ensure that our margins are // up to date. This method can be called before the child has actually // calculated its margins (which are computed inside calcWidth). if (ml.isPercent() || mr.isPercent()) { calcWidth(); } // A margin basically has three types: fixed, percentage, and auto (variable). // Auto margins simply become 0 when computing min/max width. // Fixed margins can be added in as is. // Percentage margins are computed as a percentage of the width we calculated in // the calcWidth call above. In this case we use the actual cached margin values on // the RenderObject itself. int margin = 0, marginLeft = 0, marginRight = 0; if (ml.isFixed()) { marginLeft += ml.value(); } else if (ml.isPercent()) { marginLeft += child->marginLeft(); } if (mr.isFixed()) { marginRight += mr.value(); } else if (mr.isPercent()) { marginRight += child->marginRight(); } margin = marginLeft + marginRight; int w = child->minWidth() + margin; if (m_minWidth < w) { m_minWidth = w; } // IE ignores tables for calculation of nowrap. Makes some sense. if (nowrap && !child->isTable() && m_maxWidth < w) { m_maxWidth = w; } w = child->maxWidth() + margin; if (!child->isFloating()) { if (child->flowAroundFloats()) { // Determine a left and right max value based on whether or not the floats can fit in the // margins of the object. For negative margins, we will attempt to overlap the float if the negative margin // is smaller than the float width. int maxLeft = marginLeft > 0 ? qMax(floatLeftWidth, marginLeft) : floatLeftWidth + marginLeft; int maxRight = marginRight > 0 ? qMax(floatRightWidth, marginRight) : floatRightWidth + marginRight; w = child->maxWidth() + maxLeft + maxRight; w = qMax(w, floatLeftWidth + floatRightWidth); } else { m_maxWidth = qMax(floatLeftWidth + floatRightWidth, m_maxWidth); } floatLeftWidth = floatRightWidth = 0; } if (child->isFloating()) { if (style()->floating() & FLEFT) { floatLeftWidth += w; } else { floatRightWidth += w; } } else if (m_maxWidth < w) { m_maxWidth = w; } // A very specific WinIE quirk. // Example: /*

    */ // In the above example, the inner absolute positioned block should have a computed width // of 100px because of the table. // We can achieve this effect by making the maxwidth of blocks that contain tables // with percentage widths be infinite (as long as they are not inside a table cell). if (style()->htmlHacks() && child->style()->width().isPercent() && !isTableCell() && child->isTable() && m_maxWidth < BLOCK_MAX_WIDTH) { RenderBlock *cb = containingBlock(); while (!cb->isCanvas() && !cb->isTableCell()) { cb = cb->containingBlock(); } if (!cb->isTableCell()) { m_maxWidth = BLOCK_MAX_WIDTH; } } child = child->nextSibling(); } m_maxWidth = qMax(floatLeftWidth + floatRightWidth, m_maxWidth); } void RenderBlock::close() { if (lastChild() && lastChild()->isAnonymousBlock()) { lastChild()->close(); } updateFirstLetter(); RenderFlow::close(); } int RenderBlock::getBaselineOfFirstLineBox() { if (m_firstLineBox) { return m_firstLineBox->yPos() + m_firstLineBox->baseline(); } if (isInline()) { return -1; // We're inline and had no line box, so we have no baseline we can return. } for (RenderObject *curr = firstChild(); curr; curr = curr->nextSibling()) { int result = curr->getBaselineOfFirstLineBox(); if (result != -1) { return curr->yPos() + result; // Translate to our coordinate space. } } return -1; } InlineFlowBox *RenderBlock::getFirstLineBox() { if (m_firstLineBox) { return m_firstLineBox; } if (isInline()) { return nullptr; // We're inline and had no line box, so we have no baseline we can return. } for (RenderObject *curr = firstChild(); curr; curr = curr->nextSibling()) { InlineFlowBox *result = curr->getFirstLineBox(); if (result) { return result; } } return nullptr; } bool RenderBlock::inRootBlockContext() const { if (isTableCell() || isFloatingOrPositioned() || hasOverflowClip()) { return false; } if (isRoot() || isCanvas()) { return true; } return containingBlock()->inRootBlockContext(); } const char *RenderBlock::renderName() const { if (isFloating()) { return "RenderBlock (floating)"; } if (isPositioned()) { return "RenderBlock (positioned)"; } if (isAnonymousBlock() && m_avoidPageBreak) { return "RenderBlock (avoidPageBreak)"; } if (isAnonymousBlock()) { return "RenderBlock (anonymous)"; } else if (isAnonymous()) { return "RenderBlock (generated)"; } if (isRelPositioned()) { return "RenderBlock (relative positioned)"; } if (style() && style()->display() == COMPACT) { return "RenderBlock (compact)"; } if (style() && style()->display() == RUN_IN) { return "RenderBlock (run-in)"; } return "RenderBlock"; } #ifdef ENABLE_DUMP void RenderBlock::printTree(int indent) const { RenderFlow::printTree(indent); if (m_floatingObjects) { QListIterator it(*m_floatingObjects); FloatingObject *r; while (it.hasNext()) { r = it.next(); QString s; s.fill(' ', indent); qDebug() << s << renderName() << ": " << (r->type == FloatingObject::FloatLeft ? "FloatLeft" : "FloatRight") << - "[" << r->node->renderName() << ": " << (void *)r->node << "] (" << r->startY << " - " << r->endY << ")" << "width: " << r->width << - endl; + "[" << r->node->renderName() << ": " << (void *)r->node << "] (" << r->startY << " - " << r->endY << ")" << "width: " << r->width; } } } void RenderBlock::dump(QTextStream &stream, const QString &ind) const { RenderFlow::dump(stream, ind); if (m_childrenInline) { stream << QLatin1String(" childrenInline"); } // FIXME: currently only print pre to not mess up regression if (style()->preserveWS()) { stream << " pre"; } if (m_firstLine) { stream << QLatin1String(" firstLine"); } if (m_floatingObjects && !m_floatingObjects->isEmpty()) { stream << QLatin1String(" special("); QListIterator it(*m_floatingObjects); FloatingObject *r; bool first = true; while (it.hasNext()) { r = it.next(); if (!first) { stream << QLatin1Char(','); } stream << r->node->renderName(); first = false; } stream << QLatin1Char(')'); } // ### EClear m_clearStatus } #endif #undef DEBUG #undef DEBUG_LAYOUT #undef BOX_DEBUG } // namespace khtml diff --git a/src/rendering/render_container.cpp b/src/rendering/render_container.cpp index 57068f8..cb3e1cd 100644 --- a/src/rendering/render_container.cpp +++ b/src/rendering/render_container.cpp @@ -1,791 +1,791 @@ /** * This file is part of the html renderer for KDE. * * Copyright (C) 2001-2003 Lars Knoll (knoll@kde.org) * (C) 2001 Antti Koivisto (koivisto@kde.org) * (C) 2000-2003 Dirk Mueller (mueller@kde.org) * (C) 2002-2007 Apple Computer, Inc. * (C) 2007 Germain Garand (germain@ebooksfrance.org) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ //#define DEBUG_LAYOUT #include "rendering/render_container.h" #include "rendering/render_table.h" #include "rendering/render_text.h" #include "rendering/render_image.h" #include "rendering/render_canvas.h" #include "rendering/render_generated.h" #include "rendering/render_inline.h" #include "rendering/render_layer.h" #include "rendering/render_position.h" #include "xml/dom_docimpl.h" #include "xml/dom_position.h" #include "css/css_valueimpl.h" #include #include #include using DOM::Position; using namespace khtml; RenderContainer::RenderContainer(DOM::NodeImpl *node) : RenderObject(node) { m_first = nullptr; m_last = nullptr; } void RenderContainer::addChild(RenderObject *newChild, RenderObject *beforeChild) { #ifdef DEBUG_LAYOUT // qDebug() << this << ": " << renderName() << "(RenderObject)::addChild( " << newChild << ": " << - newChild->renderName() << ", " << (beforeChild ? beforeChild->renderName() : "0") << " )" << endl; + // newChild->renderName() << ", " << (beforeChild ? beforeChild->renderName() : "0") << " )"; #endif // protect ourselves from deletion setDoNotDelete(true); bool needsTable = false; if (!newChild->isText() && !newChild->isReplaced()) { switch (newChild->style()->display()) { case INLINE: case BLOCK: case LIST_ITEM: case RUN_IN: case COMPACT: case INLINE_BLOCK: case TABLE: case INLINE_TABLE: break; case TABLE_COLUMN: if (isTableCol()) { break; } // nobreak case TABLE_COLUMN_GROUP: case TABLE_CAPTION: case TABLE_ROW_GROUP: case TABLE_HEADER_GROUP: case TABLE_FOOTER_GROUP: //qDebug() << "adding section"; if (!isTable()) { needsTable = true; } break; case TABLE_ROW: //qDebug() << "adding row"; if (!isTableSection()) { needsTable = true; } break; case TABLE_CELL: //qDebug() << "adding cell"; if (!isTableRow()) { needsTable = true; } // I'm not 100% sure this is the best way to fix this, but without this // change we recurse infinitely when trying to render the CSS2 test page: // http://www.bath.ac.uk/%7Epy8ieh/internet/eviltests/htmlbodyheadrendering2.html. if (isTableCell() && !firstChild() && !newChild->isTableCell()) { needsTable = false; } break; case NONE: // RenderHtml and some others can have display:none // KHTMLAssert(false); break; } } if (needsTable) { RenderTable *table; RenderObject *last = beforeChild ? beforeChild->previousSibling() : lastChild(); if (last && last->isTable() && last->isAnonymous()) { table = static_cast(last); } else { //qDebug() << "creating anonymous table, before=" << beforeChild; table = new(renderArena()) RenderTable(document() /* is anonymous */); RenderStyle *newStyle = new RenderStyle(); newStyle->inheritFrom(style()); newStyle->setDisplay(TABLE); newStyle->setFlowAroundFloats(true); table->setParent(this); // so it finds the arena table->setStyle(newStyle); table->setParent(nullptr); addChild(table, beforeChild); } table->addChild(newChild); } else { // just add it... insertChildNode(newChild, beforeChild); } if (newChild->isText() && newChild->style()->textTransform() == CAPITALIZE) { DOM::DOMStringImpl *textToTransform = static_cast(newChild)->originalString(); if (textToTransform) { static_cast(newChild)->setText(textToTransform, true); } } newChild->attach(); setDoNotDelete(false); } RenderObject *RenderContainer::removeChildNode(RenderObject *oldChild) { KHTMLAssert(oldChild->parent() == this); bool inCleanup = documentBeingDestroyed(); if (!inCleanup) { oldChild->setNeedsLayoutAndMinMaxRecalc(); // Dirty the containing block chain oldChild->setNeedsLayout(false); // The child itself does not need to layout - it's going away. // Repaint, so that the area exposed when the child // disappears gets repainted properly. if (oldChild->height() && oldChild->width()) { oldChild->repaint(); } } // detach the place holder box if (oldChild->isBox()) { RenderBox *rb = static_cast(oldChild); InlineBox *ph = rb->placeHolderBox(); if (ph) { ph->detach(rb->renderArena(), inCleanup /*NoRemove*/); rb->setPlaceHolderBox(nullptr); } } if (!inCleanup) { // if we remove visible child from an invisible parent, we don't know the layer visibility any more RenderLayer *layer = nullptr; if (m_style->visibility() != VISIBLE && oldChild->style()->visibility() == VISIBLE && !oldChild->layer()) { layer = enclosingLayer(); if (layer) { layer->dirtyVisibleContentStatus(); } } // Keep our layer hierarchy updated. if (oldChild->firstChild() || oldChild->layer()) { if (!layer) { layer = enclosingLayer(); } oldChild->removeLayers(layer); } // remove the child from any special layout lists oldChild->removeFromObjectLists(); // keep our fixed object lists updated. if (oldChild->style()->hasFixedBackgroundImage() || oldChild->style()->position() == PFIXED) { if (oldChild->style()->hasFixedBackgroundImage()) { canvas()->removeStaticObject(oldChild); } if (oldChild->style()->position() == PFIXED) { canvas()->removeStaticObject(oldChild, true); } } if (oldChild->isPosWithStaticDim() && childrenInline()) { dirtyLinesFromChangedChild(oldChild); } // We are about to take out node from the rendering tree and therefore // it's possible that we're modifying the line box tree too. // In order to properly recalculate it later we need // to delete all the boxes from the current flow of the removed child. (vtokarev) // In particular that's relevant when we're merging split inline flow. (continuations) // We're taking the render objects from one block and insert into another // so we have to force line box tree recalculation if (oldChild->isInline()) { if (oldChild->isText()) { InlineTextBox *box = static_cast(oldChild)->firstTextBox(); InlineTextBox *nextTextBox; assert(!box || box->parent()); // delete all the text boxes for (; box; box = nextTextBox) { nextTextBox = box->nextTextBox(); box->remove(); box->deleteLine(renderArena()); } } else if (oldChild->isInlineFlow()) { InlineFlowBox *box = static_cast(oldChild)->firstLineBox(); InlineFlowBox *nextFlowBox; assert(!box || box->parent()); // delete all the flow for (; box; box = nextFlowBox) { nextFlowBox = box->nextFlowBox(); box->remove(); box->deleteLine(renderArena()); } } } // if oldChild is the start or end of the selection, then clear // the selection to avoid problems of invalid pointers // ### This is not the "proper" solution... ideally the selection // ### should be maintained based on DOM Nodes and a Range, which // ### gets adjusted appropriately when nodes are deleted/inserted // ### near etc. But this at least prevents crashes caused when // ### the start or end of the selection is deleted and then // ### accessed when the user next selects something. if (oldChild->isSelectionBorder()) { canvas()->clearSelection(); } } // remove the child from the render-tree if (oldChild->previousSibling()) { oldChild->previousSibling()->setNextSibling(oldChild->nextSibling()); } if (oldChild->nextSibling()) { oldChild->nextSibling()->setPreviousSibling(oldChild->previousSibling()); } if (m_first == oldChild) { m_first = oldChild->nextSibling(); } if (m_last == oldChild) { m_last = oldChild->previousSibling(); } oldChild->setPreviousSibling(nullptr); oldChild->setNextSibling(nullptr); oldChild->setParent(nullptr); return oldChild; } void RenderContainer::setStyle(RenderStyle *_style) { RenderObject::setStyle(_style); // If we are a pseudo-container we need to restyle the children if (style()->isGenerated()) { // ### we could save this call when the change only affected // non inherited properties RenderStyle *pseudoStyle = new RenderStyle(); pseudoStyle->inheritFrom(style()); pseudoStyle->ref(); RenderObject *child = firstChild(); while (child != nullptr) { child->setStyle(pseudoStyle); child = child->nextSibling(); } pseudoStyle->deref(); } } static bool inUpdatePseudoChildren = false; void RenderContainer::updatePseudoChildren() { if (inUpdatePseudoChildren) { return; } inUpdatePseudoChildren = true; // In CSS2, before/after pseudo-content cannot nest. Check this first. // Remove when CSS 3 Generated Content becomes Candidate Recommendation if (style()->styleType() == RenderStyle::BEFORE || style()->styleType() == RenderStyle::AFTER) { return; } updatePseudoChild(RenderStyle::BEFORE); updatePseudoChild(RenderStyle::AFTER); // updatePseudoChild(RenderStyle::MARKER, marker()); inUpdatePseudoChildren = false; } void RenderContainer::updatePseudoChild(RenderStyle::PseudoId type) { // The head manages generated content for its continuations if (isInlineContinuation()) { return; } RenderStyle *pseudo = style()->getPseudoStyle(type); RenderObject *child = pseudoContainer(type); // Whether or not we currently have generated content attached. bool oldContentPresent = child && (child->style()->styleType() == type); // Whether or not we now want generated content. bool newContentWanted = pseudo && pseudo->display() != NONE; // No generated content if (!oldContentPresent && !newContentWanted) { return; } bool movedContent = (type == RenderStyle::AFTER && isRenderInline() && continuation()); // Whether or not we want the same old content. bool sameOldContent = oldContentPresent && newContentWanted && !movedContent && (child->style()->contentDataEquivalent(pseudo)); // No change in content, update style if (sameOldContent) { child->setStyle(pseudo); return; } // If we don't want generated content any longer, or if we have generated content, // but it's no longer identical to the new content data we want to build // render objects for, then we nuke all of the old generated content. if (oldContentPresent && (!newContentWanted || !sameOldContent)) { // The child needs to be removed. oldContentPresent = false; child->detach(); child = nullptr; } // If we have no pseudo-style or if the pseudo's display type is NONE, then we // have no generated content and can now return. if (!newContentWanted) { return; } // Generated content consists of a single container that houses multiple children (specified // by the content property). This pseudo container gets the pseudo style set on it. RenderContainer *pseudoContainer = nullptr; pseudoContainer = RenderFlow::createFlow(element(), pseudo, renderArena()); pseudoContainer->setIsAnonymous(true); pseudoContainer->createGeneratedContent(); // Only add the container if it had content if (pseudoContainer->firstChild()) { addPseudoContainer(pseudoContainer); pseudoContainer->close(); } } void RenderContainer::createGeneratedContent() { RenderStyle *pseudo = style(); RenderStyle *style = new RenderStyle(); style->ref(); style->inheritFrom(pseudo); // Now walk our list of generated content and create render objects for every type // we encounter. for (ContentData *contentData = pseudo->contentData(); contentData; contentData = contentData->_nextContent) { if (contentData->_contentType == CONTENT_TEXT) { RenderText *t = new(renderArena()) RenderText(node(), nullptr); t->setIsAnonymous(true); t->setStyle(style); t->setText(contentData->contentText()); addChild(t); } else if (contentData->_contentType == CONTENT_OBJECT) { RenderImage *img = new(renderArena()) RenderImage(node()); img->setIsAnonymous(true); img->setStyle(style); img->setContentObject(contentData->contentObject()); addChild(img); } else if (contentData->_contentType == CONTENT_COUNTER) { // really a counter or just a glyph? EListStyleType type = (EListStyleType)contentData->contentCounter()->listStyle(); RenderObject *t = nullptr; if (isListStyleCounted(type)) { t = new(renderArena()) RenderCounter(node(), contentData->contentCounter()); } else { t = new(renderArena()) RenderGlyph(node(), type); } t->setIsAnonymous(true); t->setStyle(style); addChild(t); } else if (contentData->_contentType == CONTENT_QUOTE) { RenderQuote *t = new(renderArena()) RenderQuote(node(), contentData->contentQuote()); t->setIsAnonymous(true); t->setStyle(style); addChild(t); } } style->deref(); } RenderContainer *RenderContainer::pseudoContainer(RenderStyle::PseudoId type) const { RenderObject *child = nullptr; switch (type) { case RenderStyle::AFTER: child = lastChild(); break; case RenderStyle::BEFORE: child = firstChild(); break; case RenderStyle::REPLACED: child = lastChild(); if (child && child->style()->styleType() == RenderStyle::AFTER) { child = child->previousSibling(); } break; default: child = nullptr; } if (child && child->style()->styleType() == type) { assert(child->isRenderBlock() || child->isRenderInline()); return static_cast(child); } if (type == RenderStyle::AFTER) { // check continuations if (continuation()) { return continuation()->pseudoContainer(type); } } if (child && child->isAnonymousBlock()) { return static_cast(child)->pseudoContainer(type); } return nullptr; } void RenderContainer::addPseudoContainer(RenderObject *child) { RenderStyle::PseudoId type = child->style()->styleType(); switch (type) { case RenderStyle::AFTER: { RenderObject *o = this; while (o->continuation()) { o = o->continuation(); } // Coalesce inlines if (child->style()->display() == INLINE && o->lastChild() && o->lastChild()->isAnonymousBlock()) { o->lastChild()->addChild(child, nullptr); } else { o->addChild(child, nullptr); } break; } case RenderStyle::BEFORE: // Coalesce inlines if (child->style()->display() == INLINE && firstChild() && firstChild()->isAnonymousBlock()) { firstChild()->addChild(child, firstChild()->firstChild()); } else { addChild(child, firstChild()); } break; case RenderStyle::REPLACED: addChild(child, pseudoContainer(RenderStyle::AFTER)); break; default: break; } } void RenderContainer::updateReplacedContent() { // Only for normal elements if (!style() || style()->styleType() != RenderStyle::NOPSEUDO) { return; } // delete old generated content RenderContainer *container = pseudoContainer(RenderStyle::REPLACED); if (container) { container->detach(); } if (style()->useNormalContent()) { return; } // create generated content RenderStyle *pseudo = style()->getPseudoStyle(RenderStyle::REPLACED); if (!pseudo) { pseudo = new RenderStyle(); pseudo->inheritFrom(style()); pseudo->setStyleType(RenderStyle::REPLACED); } if (pseudo->useNormalContent()) { pseudo->setContentData(style()->contentData()); } container = RenderFlow::createFlow(node(), pseudo, renderArena()); container->setIsAnonymous(true); container->createGeneratedContent(); addChild(container, pseudoContainer(RenderStyle::AFTER)); } void RenderContainer::appendChildNode(RenderObject *newChild) { KHTMLAssert(newChild->parent() == nullptr); newChild->setParent(this); RenderObject *lChild = lastChild(); if (lChild) { newChild->setPreviousSibling(lChild); lChild->setNextSibling(newChild); } else { setFirstChild(newChild); } setLastChild(newChild); // Keep our layer hierarchy updated. Optimize for the common case where we don't have any children // and don't have a layer attached to ourselves. RenderLayer *layer = nullptr; if (newChild->firstChild() || newChild->layer()) { layer = enclosingLayer(); newChild->addLayers(layer, newChild); // keep our fixed object lists updated. if (newChild->style() && (newChild->style()->hasFixedBackgroundImage() || newChild->style()->position() == PFIXED)) { if (newChild->style()->hasFixedBackgroundImage()) { canvas()->addStaticObject(newChild); } if (newChild->style()->position() == PFIXED) { canvas()->addStaticObject(newChild, true); } } } // if the new child is visible but this object was not, tell the layer it has some visible content // that needs to be drawn and layer visibility optimization can't be used if (style()->visibility() != VISIBLE && newChild->style()->visibility() == VISIBLE && !newChild->layer()) { if (!layer) { layer = enclosingLayer(); } if (layer) { layer->setHasVisibleContent(true); } } if (!newChild->isFloatingOrPositioned() && childrenInline()) { dirtyLinesFromChangedChild(newChild); } newChild->setNeedsLayoutAndMinMaxRecalc(); // Goes up the containing block hierarchy. if (!normalChildNeedsLayout()) { // We may supply the static position for an absolute positioned child. if (newChild->firstChild() || newChild->isPosWithStaticDim() || !newChild->isPositioned()) { setChildNeedsLayout(true); } else { assert(!newChild->inPosObjectList()); newChild->containingBlock()->insertPositionedObject(newChild); } } } void RenderContainer::insertChildNode(RenderObject *child, RenderObject *beforeChild) { if (!beforeChild) { appendChildNode(child); return; } KHTMLAssert(!child->parent()); while (beforeChild->parent() != this && beforeChild->parent()->isAnonymous()) { beforeChild = beforeChild->parent(); } KHTMLAssert(beforeChild->parent() == this); if (beforeChild == firstChild()) { setFirstChild(child); } RenderObject *prev = beforeChild->previousSibling(); child->setNextSibling(beforeChild); beforeChild->setPreviousSibling(child); if (prev) { prev->setNextSibling(child); } child->setPreviousSibling(prev); child->setParent(this); // Keep our layer hierarchy updated. Optimize for the common case where we don't have any children // and don't have a layer attached to ourselves. RenderLayer *layer = nullptr; if (child->firstChild() || child->layer()) { layer = enclosingLayer(); child->addLayers(layer, child); // keep our fixed object lists updated. if (child->style() && (child->style()->hasFixedBackgroundImage() || child->style()->position() == PFIXED)) { if (child->style()->hasFixedBackgroundImage()) { canvas()->addStaticObject(child); } if (child->style()->position() == PFIXED) { canvas()->addStaticObject(child, true); } } } // if the new child is visible but this object was not, tell the layer it has some visible content // that needs to be drawn and layer visibility optimization can't be used if (style()->visibility() != VISIBLE && child->style()->visibility() == VISIBLE && !child->layer()) { if (!layer) { layer = enclosingLayer(); } if (layer) { layer->setHasVisibleContent(true); } } if (!child->isFloating() && childrenInline()) { dirtyLinesFromChangedChild(child); } child->setNeedsLayoutAndMinMaxRecalc(); if (!normalChildNeedsLayout()) { // We may supply the static position for an absolute positioned child. if (child->firstChild() || child->isPosWithStaticDim() || !child->isPositioned()) { setChildNeedsLayout(true); } else { assert(!child->inPosObjectList()); child->containingBlock()->insertPositionedObject(child); } } } void RenderContainer::layout() { KHTMLAssert(needsLayout()); KHTMLAssert(minMaxKnown()); const bool pagedMode = canvas()->pagedMode(); RenderObject *child = firstChild(); while (child) { if (pagedMode) { child->setNeedsLayout(true); } child->layoutIfNeeded(); if (child->containsPageBreak()) { setContainsPageBreak(true); } if (child->needsPageClear()) { setNeedsPageClear(true); } child = child->nextSibling(); } setNeedsLayout(false); } void RenderContainer::removeSuperfluousAnonymousBlockChild(RenderObject *child) { KHTMLAssert(child->parent() == this && child->isAnonymousBlock()); if (child->doNotDelete() || child->continuation()) { return; } RenderObject *childSFirstChild = child->firstChild(); RenderObject *childSLastChild = child->lastChild(); if (childSFirstChild) { RenderObject *o = childSFirstChild; while (o) { o->setParent(this); o = o->nextSibling(); } childSFirstChild->setPreviousSibling(child->previousSibling()); childSLastChild->setNextSibling(child->nextSibling()); if (child->previousSibling()) { child->previousSibling()->setNextSibling(childSFirstChild); } if (child->nextSibling()) { child->nextSibling()->setPreviousSibling(childSLastChild); } if (child == firstChild()) { m_first = childSFirstChild; } if (child == lastChild()) { m_last = childSLastChild; } } else { if (child->previousSibling()) { child->previousSibling()->setNextSibling(child->nextSibling()); } if (child->nextSibling()) { child->nextSibling()->setPreviousSibling(child->previousSibling()); } if (child == firstChild()) { m_first = child->nextSibling(); } if (child == lastChild()) { m_last = child->previousSibling(); } } child->setParent(nullptr); child->setPreviousSibling(nullptr); child->setNextSibling(nullptr); if (!child->isText()) { RenderContainer *c = static_cast(child); c->m_first = nullptr; c->m_next = nullptr; } child->detach(); } RenderPosition RenderContainer::positionForCoordinates(int _x, int _y) { // no children...return this render object's element, if there is one, and offset 0 if (!firstChild()) { return RenderPosition(element(), 0); } // look for the geometrically-closest child and pass off to that child int min = INT_MAX; RenderObject *closestRenderer = firstChild(); for (RenderObject *renderer = firstChild(); renderer; renderer = renderer->nextSibling()) { int absx, absy; renderer->absolutePosition(absx, absy); int top = absy + borderTop() + paddingTop(); int bottom = top + renderer->contentHeight(); int left = absx + borderLeft() + paddingLeft(); int right = left + renderer->contentWidth(); int cmp; cmp = abs(_y - top); if (cmp < min) { closestRenderer = renderer; min = cmp; } cmp = abs(_y - bottom); if (cmp < min) { closestRenderer = renderer; min = cmp; } cmp = abs(_x - left); if (cmp < min) { closestRenderer = renderer; min = cmp; } cmp = abs(_x - right); if (cmp < min) { closestRenderer = renderer; min = cmp; } } return closestRenderer->positionForCoordinates(_x, _y); } #undef DEBUG_LAYOUT diff --git a/src/rendering/render_flow.cpp b/src/rendering/render_flow.cpp index 4313cc1..0e620d1 100644 --- a/src/rendering/render_flow.cpp +++ b/src/rendering/render_flow.cpp @@ -1,660 +1,660 @@ /** * This file is part of the html renderer for KDE. * * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) * (C) 1999-2003 Antti Koivisto (koivisto@kde.org) * (C) 2002-2003 Dirk Mueller (mueller@kde.org) * (C) 2003-2007 Apple Computer, Inc. * (C) 2007 Germain Garand (germain@ebooksfrance.org) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ // ------------------------------------------------------------------------- #include "render_flow.h" #include #include #include #include "render_text.h" #include "render_table.h" #include "render_canvas.h" #include "render_inline.h" #include "render_block.h" #include "render_arena.h" #include "render_line.h" #include #include #include #include using namespace DOM; using namespace khtml; RenderFlow *RenderFlow::createFlow(DOM::NodeImpl *node, RenderStyle *style, RenderArena *arena) { RenderFlow *result; if (style->display() == INLINE) { result = new(arena) RenderInline(node); } else { result = new(arena) RenderBlock(node); } result->setStyle(style); return result; } RenderFlow *RenderFlow::continuationBefore(const RenderObject *beforeChild) { if (beforeChild && beforeChild->parent() == this) { return this; } RenderFlow *curr = continuation(); RenderFlow *nextToLast = this; RenderFlow *last = this; while (curr) { if (beforeChild && beforeChild->parent() == curr) { if (curr->firstChild() == beforeChild) { return last; } return curr; } nextToLast = last; last = curr; curr = curr->continuation(); } if (!beforeChild && !last->firstChild()) { return nextToLast; } return last; } void RenderFlow::addChildWithContinuation(RenderObject *newChild, RenderObject *beforeChild) { RenderFlow *flow = continuationBefore(beforeChild); RenderObject *bc = beforeChild; while (bc && bc->parent() != flow && !bc->parent()->isAnonymousBlock()) { // skip implicit containers around beforeChild bc = bc->parent(); } RenderFlow *beforeChildParent = bc ? static_cast(bc->parent()) : (flow->continuation() ? flow->continuation() : flow); if (newChild->isFloatingOrPositioned()) { return beforeChildParent->addChildToFlow(newChild, beforeChild); } // A continuation always consists of two potential candidates: an inline or an anonymous // block box holding block children. bool childInline = newChild->isInline(); bool bcpInline = beforeChildParent->isInline(); bool flowInline = flow->isInline(); if (flow == beforeChildParent) { return flow->addChildToFlow(newChild, beforeChild); } else { // The goal here is to match up if we can, so that we can coalesce and create the // minimal # of continuations needed for the inline. if (childInline == bcpInline) { return beforeChildParent->addChildToFlow(newChild, beforeChild); } else if (flowInline == childInline) { return flow->addChildToFlow(newChild, nullptr); // Just treat like an append. } else { return beforeChildParent->addChildToFlow(newChild, beforeChild); } } } void RenderFlow::addChild(RenderObject *newChild, RenderObject *beforeChild) { #ifdef DEBUG_LAYOUT // qDebug() << renderName() << "(RenderFlow)::addChild( " << newChild->renderName() << - ", " << (beforeChild ? beforeChild->renderName() : "0") << " )" << endl; + // ", " << (beforeChild ? beforeChild->renderName() : "0") << " )"; // qDebug() << "current height = " << m_height; #endif if (continuation()) { return addChildWithContinuation(newChild, beforeChild); } return addChildToFlow(newChild, beforeChild); } void RenderFlow::extractLineBox(InlineFlowBox *box) { m_lastLineBox = box->prevFlowBox(); if (box == m_firstLineBox) { m_firstLineBox = nullptr; } if (box->prevLineBox()) { box->prevLineBox()->setNextLineBox(nullptr); } box->setPreviousLineBox(nullptr); for (InlineRunBox *curr = box; curr; curr = curr->nextLineBox()) { curr->setExtracted(); } } void RenderFlow::attachLineBox(InlineFlowBox *box) { if (m_lastLineBox) { m_lastLineBox->setNextLineBox(box); box->setPreviousLineBox(m_lastLineBox); } else { m_firstLineBox = box; } InlineFlowBox *last = box; for (InlineFlowBox *curr = box; curr; curr = curr->nextFlowBox()) { curr->setExtracted(false); last = curr; } m_lastLineBox = last; } void RenderFlow::removeInlineBox(InlineBox *_box) { if (_box->isInlineFlowBox()) { InlineFlowBox *box = static_cast(_box); if (box == m_firstLineBox) { m_firstLineBox = box->nextFlowBox(); } if (box == m_lastLineBox) { m_lastLineBox = box->prevFlowBox(); } if (box->nextLineBox()) { box->nextLineBox()->setPreviousLineBox(box->prevLineBox()); } if (box->prevLineBox()) { box->prevLineBox()->setNextLineBox(box->nextLineBox()); } } RenderBox::removeInlineBox(_box); } void RenderFlow::deleteInlineBoxes(RenderArena *arena) { if (m_firstLineBox) { if (!arena) { arena = renderArena(); } InlineRunBox *curr = m_firstLineBox, *next = nullptr; while (curr) { next = curr->nextLineBox(); if (!curr->isPlaceHolderBox()) { curr->detach(arena, true /*noRemove*/); } curr = next; } m_firstLineBox = nullptr; m_lastLineBox = nullptr; } } void RenderFlow::dirtyInlineBoxes(bool fullLayout, bool isRootLineBox) { if (!isRootLineBox && (isReplaced() || isPositioned())) { return RenderBox::dirtyInlineBoxes(fullLayout, isRootLineBox); } if (fullLayout) { deleteInlineBoxes(); } else { for (InlineRunBox *curr = firstLineBox(); curr; curr = curr->nextLineBox()) { curr->dirtyInlineBoxes(); } } } void RenderFlow::deleteLastLineBox(RenderArena *arena) { if (m_lastLineBox) { if (!arena) { arena = renderArena(); } InlineRunBox *curr = m_lastLineBox, *prev = m_lastLineBox; if (m_firstLineBox == m_lastLineBox) { m_firstLineBox = m_lastLineBox = nullptr; } else { prev = curr->prevLineBox(); while (!prev->isInlineFlowBox()) { prev = prev->prevLineBox(); prev->detach(arena); } m_lastLineBox = static_cast(prev); prev->setNextLineBox(nullptr); } if (curr->parent()) { curr->parent()->removeFromLine(curr); } curr->detach(arena); } } InlineBox *RenderFlow::createInlineBox(bool makePlaceHolderBox, bool isRootLineBox) { if (!isRootLineBox && (isReplaced() || makePlaceHolderBox)) { // Inline tables and inline blocks return RenderBox::createInlineBox(false, false); // (or positioned element placeholders). } InlineFlowBox *flowBox = nullptr; if (isInlineFlow()) { flowBox = new(renderArena()) InlineFlowBox(this); } else { flowBox = new(renderArena()) RootInlineBox(this); } if (!m_firstLineBox) { m_firstLineBox = m_lastLineBox = flowBox; } else { m_lastLineBox->setNextLineBox(flowBox); flowBox->setPreviousLineBox(m_lastLineBox); m_lastLineBox = flowBox; } return flowBox; } void RenderFlow::dirtyLinesFromChangedChild(RenderObject *child) { if (!parent() || (selfNeedsLayout() && !isInlineFlow()) || isTable()) { return; } // If we have no first line box, then just bail early. if (!firstLineBox()) { // For an empty inline, propagate the check up to our parent, unless the parent // is already dirty. if (isInline() && !parent()->selfNeedsLayout() && parent()->isInlineFlow()) { static_cast(parent())->dirtyLinesFromChangedChild(this); } return; } // Try to figure out which line box we belong in. First try to find a previous // line box by examining our siblings. If we didn't find a line box, then use our // parent's first line box. RootInlineBox *box = nullptr; RenderObject *curr = nullptr; for (curr = child->previousSibling(); curr; curr = curr->previousSibling()) { if (curr->isFloatingOrPositioned()) { continue; } if (curr->isReplaced() && curr->isBox()) { InlineBox *placeHolderBox = static_cast(curr)->placeHolderBox(); if (placeHolderBox) { box = placeHolderBox->root(); } } else if (curr->isText()) { InlineTextBox *textBox = static_cast(curr)->lastTextBox(); if (textBox) { box = textBox->root(); } } else if (curr->isInlineFlow()) { InlineRunBox *runBox = static_cast(curr)->lastLineBox(); if (runBox) { box = runBox->root(); } } if (box) { break; } } if (!box) { box = firstLineBox()->root(); } // If we found a line box, then dirty it. if (box) { RootInlineBox *adjacentBox; box->markDirty(); // dirty the adjacent lines that might be affected // NOTE: we dirty the previous line because RootInlineBox objects cache // the address of the first object on the next line after a BR, which we may be // invalidating here. For more info, see how RenderBlock::layoutInlineChildren // calls setLineBreakInfo with the result of findNextLineBreak. findNextLineBreak, // despite the name, actually returns the first RenderObject after the BR. adjacentBox = box->prevRootBox(); if (adjacentBox) { adjacentBox->markDirty(); } if (child->isBR() || (curr && curr->isBR())) { adjacentBox = box->nextRootBox(); if (adjacentBox) { adjacentBox->markDirty(); } } } } QList< QRectF > RenderFlow::getClientRects() { if (isRenderInline() && isInlineFlow()) { QList list; InlineFlowBox *child = firstLineBox(); if (child) { int x = 0, y = 0; absolutePosition(x,y); do { QRectF rect(x + child->xPos(), y + child->yPos(), child->width(), child->height()); list.append(clientRectToViewport(rect)); child = child->nextFlowBox(); } while (child); } // In case our flow is splitted by blocks for (RenderObject *cont = continuation(); cont; cont = cont->continuation()) { list.append(cont->getClientRects()); } // Empty Flow, return the Flow itself if (list.isEmpty()) { return RenderObject::getClientRects(); } return list; } else { return RenderObject::getClientRects(); } } void RenderFlow::detach() { if (continuation()) { continuation()->detach(); } m_continuation = nullptr; // Make sure to destroy anonymous children first while they are still connected to the rest of the tree, so that they will // properly dirty line boxes that they are removed from. Effects that do :before/:after only on hover could crash otherwise. detachRemainingChildren(); if (!documentBeingDestroyed()) { if (m_firstLineBox) { // We can't wait for RenderContainer::destroy to clear the selection, // because by then we will have nuked the line boxes. if (isSelectionBorder()) { canvas()->clearSelection(); } // If line boxes are contained inside a root, that means we're an inline. // In that case, we need to remove all the line boxes so that the parent // lines aren't pointing to deleted children. If the first line box does // not have a parent that means they are either already disconnected or // root lines that can just be destroyed without disconnecting. if (m_firstLineBox->parent()) { for (InlineRunBox *box = m_firstLineBox; box; box = box->nextLineBox()) { box->remove(); } } // If we are an anonymous block, then our line boxes might have children // that will outlast this block. In the non-anonymous block case those // children will be destroyed by the time we return from this function. if (isAnonymousBlock()) { for (InlineFlowBox *box = m_firstLineBox; box; box = box->nextFlowBox()) { while (InlineBox *childBox = box->firstChild()) { childBox->remove(); } } } } else if (isInline() && parent()) // empty inlines propagate linebox dirtying to the parent { parent()->dirtyLinesFromChangedChild(this); } } deleteInlineBoxes(); RenderBox::detach(); } void RenderFlow::paintLines(PaintInfo &i, int _tx, int _ty) { // Only paint during the foreground/selection phases. if (i.phase != PaintActionForeground && i.phase != PaintActionSelection && i.phase != PaintActionOutline) { return; } if (!firstLineBox()) { return; } // We can check the first box and last box and avoid painting if we don't // intersect. This is a quick short-circuit that we can take to avoid walking any lines. // FIXME: This check is flawed in two extremely obscure ways. // (1) If some line in the middle has a huge overflow, it might actually extend below the last line. // (2) The overflow from an inline block on a line is not reported to the line. int maxOutlineSize = maximalOutlineSize(i.phase); int yPos = firstLineBox()->root()->topOverflow() - maxOutlineSize; int h = maxOutlineSize + lastLineBox()->root()->bottomOverflow() - yPos; yPos += _ty; if ((yPos >= i.r.y() + i.r.height()) || (yPos + h <= i.r.y())) { return; } for (InlineFlowBox *curr = firstLineBox(); curr; curr = curr->nextFlowBox()) { yPos = curr->root()->topOverflow() - maxOutlineSize; h = curr->root()->bottomOverflow() + maxOutlineSize - yPos; yPos += _ty; if ((yPos < i.r.y() + i.r.height()) && (yPos + h > i.r.y())) { curr->paint(i, _tx, _ty); } } if (i.phase == PaintActionOutline && i.outlineObjects) { foreach (RenderFlow *oo, *i.outlineObjects) if (oo->isRenderInline()) { static_cast(oo)->paintOutlines(i.p, _tx, _ty); } i.outlineObjects->clear(); } } bool RenderFlow::hitTestLines(NodeInfo &i, int x, int y, int tx, int ty, HitTestAction hitTestAction) { (void) hitTestAction; /* if (hitTestAction != HitTestForeground) // ### port hitTest return false; */ if (!firstLineBox()) { return false; } // We can check the first box and last box and avoid hit testing if we don't // contain the point. This is a quick short-circuit that we can take to avoid walking any lines. // FIXME: This check is flawed in two extremely obscure ways. // (1) If some line in the middle has a huge overflow, it might actually extend below the last line. // (2) The overflow from an inline block on a line is not reported to the line. if ((y >= ty + lastLineBox()->root()->bottomOverflow()) || (y < ty + firstLineBox()->root()->topOverflow())) { return false; } // See if our root lines contain the point. If so, then we hit test // them further. Note that boxes can easily overlap, so we can't make any assumptions // based off positions of our first line box or our last line box. for (InlineFlowBox *curr = lastLineBox(); curr; curr = curr->prevFlowBox()) { if (y >= ty + curr->root()->topOverflow() && y < ty + curr->root()->bottomOverflow()) { bool inside = curr->nodeAtPoint(i, x, y, tx, ty); if (inside) { setInnerNode(i); return true; } } } return false; } void RenderFlow::repaint(Priority prior) { if (isInlineFlow()) { // Find our leftmost position. int left = 0; // root inline box not reliably availabe during relayout int top = firstLineBox() ? ( needsLayout() ? firstLineBox()->xPos() : firstLineBox()->root()->topOverflow() ) : 0; for (InlineRunBox *curr = firstLineBox(); curr; curr = curr->nextLineBox()) if (curr == firstLineBox() || curr->xPos() < left) { left = curr->xPos(); } // Now invalidate a rectangle. int ow = style() ? style()->outlineSize() : 0; // We need to add in the relative position offsets of any inlines (including us) up to our // containing block. RenderBlock *cb = containingBlock(); for (RenderObject *inlineFlow = this; inlineFlow && inlineFlow->isInlineFlow() && inlineFlow != cb; inlineFlow = inlineFlow->parent()) { if (inlineFlow->style() && inlineFlow->style()->position() == PRELATIVE && inlineFlow->layer()) { KHTMLAssert(inlineFlow->isBox()); static_cast(inlineFlow)->relativePositionOffset(left, top); } } RootInlineBox *lastRoot = lastLineBox() && !needsLayout() ? lastLineBox()->root() : nullptr; containingBlock()->repaintRectangle(-ow + left, -ow + top, width() + ow * 2, (lastRoot ? lastRoot->bottomOverflow() - top : height()) + ow * 2, prior); } else { if (firstLineBox() && firstLineBox()->topOverflow() < 0) { int ow = style() ? style()->outlineSize() : 0; repaintRectangle(-ow, -ow + firstLineBox()->topOverflow(), effectiveWidth() + ow * 2, effectiveHeight() + ow * 2, prior); } else { return RenderBox::repaint(prior); } } } int RenderFlow::lowestPosition(bool includeOverflowInterior, bool includeSelf) const { int bottom = includeSelf && m_width > 0 ? m_height : 0; if (!includeOverflowInterior && hasOverflowClip()) { return bottom; } // FIXME: Come up with a way to use the layer tree to avoid visiting all the kids. // For now, we have to descend into all the children, since we may have a huge abs div inside // a tiny rel div buried somewhere deep in our child tree. In this case we have to get to // the abs div. for (RenderObject *c = firstChild(); c; c = c->nextSibling()) { if (!c->isFloatingOrPositioned() && !c->isText() && !c->isInlineFlow()) { int lp = c->yPos() + c->lowestPosition(false); bottom = qMax(bottom, lp); } } if (includeSelf && isRelPositioned()) { int x = 0; relativePositionOffset(x, bottom); } return bottom; } int RenderFlow::rightmostPosition(bool includeOverflowInterior, bool includeSelf) const { int right = includeSelf && m_height > 0 ? m_width : 0; if (!includeOverflowInterior && hasOverflowClip()) { return right; } // FIXME: Come up with a way to use the layer tree to avoid visiting all the kids. // For now, we have to descend into all the children, since we may have a huge abs div inside // a tiny rel div buried somewhere deep in our child tree. In this case we have to get to // the abs div. for (RenderObject *c = firstChild(); c; c = c->nextSibling()) { if (!c->isFloatingOrPositioned() && !c->isText() && !c->isInlineFlow()) { int rp = c->xPos() + c->rightmostPosition(false); right = qMax(right, rp); } } if (includeSelf && isRelPositioned()) { int y = 0; relativePositionOffset(right, y); } return right; } int RenderFlow::leftmostPosition(bool includeOverflowInterior, bool includeSelf) const { int left = includeSelf && m_height > 0 ? 0 : m_width; if (!includeOverflowInterior && hasOverflowClip()) { return left; } // FIXME: Come up with a way to use the layer tree to avoid visiting all the kids. // For now, we have to descend into all the children, since we may have a huge abs div inside // a tiny rel div buried somewhere deep in our child tree. In this case we have to get to // the abs div. for (RenderObject *c = firstChild(); c; c = c->nextSibling()) { if (!c->isFloatingOrPositioned() && !c->isText() && !c->isInlineFlow()) { int lp = c->xPos() + c->leftmostPosition(false); left = qMin(left, lp); } } if (includeSelf && isRelPositioned()) { int y = 0; relativePositionOffset(left, y); } return left; } int RenderFlow::highestPosition(bool includeOverflowInterior, bool includeSelf) const { int top = RenderBox::highestPosition(includeOverflowInterior, includeSelf); if (!includeOverflowInterior && hasOverflowClip()) { return top; } // FIXME: Come up with a way to use the layer tree to avoid visiting all the kids. // For now, we have to descend into all the children, since we may have a huge abs div inside // a tiny rel div buried somewhere deep in our child tree. In this case we have to get to // the abs div. for (RenderObject *c = firstChild(); c; c = c->nextSibling()) { if (!c->isFloatingOrPositioned() && !c->isText() && !c->isInlineFlow()) { int hp = c->yPos() + c->highestPosition(false); top = qMin(top, hp); } } if (includeSelf && isRelPositioned()) { int x = 0; relativePositionOffset(x, top); } return top; } diff --git a/src/rendering/render_form.cpp b/src/rendering/render_form.cpp index 52941f6..1f30d86 100644 --- a/src/rendering/render_form.cpp +++ b/src/rendering/render_form.cpp @@ -1,2577 +1,2577 @@ /* * This file is part of the DOM implementation for KDE. * * Copyright (C) 1999 Lars Knoll (knoll@kde.org) * (C) 1999 Antti Koivisto (koivisto@kde.org) * (C) 2000 Dirk Mueller (mueller@kde.org) * (C) 2006 Maksim Orlovich (maksim@kde.org) * (C) 2007-2009 Germain Garand (germain@ebooksfrance.org) * (C) 2007 Mitz Pettel (mitz@webkit.org) * (C) 2007 Charles Samuels (charles@kde.org) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #include "render_form.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace khtml; using namespace DOM; // ----------------- proxy style used to apply some CSS properties to native Qt widgets ----------------- struct KHTMLProxyStyle : public QProxyStyle { KHTMLProxyStyle(QStyle *parent) : QProxyStyle(parent) { noBorder = false; left = right = top = bottom = 0; clearButtonOverlay = 0; } QRect subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const Q_DECL_OVERRIDE { QRect r = QProxyStyle::subElementRect(element, option, widget); switch (element) { case QStyle::SE_PushButtonContents: case QStyle::SE_LineEditContents: case QStyle::SE_ShapedFrameContents: r.adjust(left, top, -qMax(0, right - clearButtonOverlay), -bottom); default: break; } return r; } void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const Q_DECL_OVERRIDE { if (element == QStyle::CE_ComboBoxLabel) { const QStyleOptionComboBox *o = qstyleoption_cast(option); if (o) { QStyleOptionComboBox comboOpt = *o; comboOpt.currentText = comboOpt.currentText.trimmed(); // by default combobox label is drawn left justified, vertical centered // translate it to reflect padding values comboOpt.rect.translate(left, (top - bottom) / 2); if (noBorder) { // Need to expand a bit for some styles comboOpt.rect.adjust(-1, -2, 1, 2); comboOpt.state &= ~State_On; } return QProxyStyle::drawControl(element, &comboOpt, painter, widget); } } QProxyStyle::drawControl(element, option, painter, widget); } void drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, QPainter *painter, const QWidget *widget) const Q_DECL_OVERRIDE { if ((cc == QStyle::CC_ComboBox) && noBorder) { if (const QStyleOptionComboBox *cbOpt = qstyleoption_cast(opt)) { bool enabled = (cbOpt->state & State_Enabled); QColor color = cbOpt->palette.color(QPalette::ButtonText); painter->save(); painter->setBackgroundMode(Qt::TransparentMode); painter->setPen(color); painter->setRenderHint(QPainter::Antialiasing); // Drop down indicator QRect arrowRect = QProxyStyle::subControlRect(cc, opt, SC_ComboBoxArrow, widget); arrowRect.setTop(cbOpt->rect.top()); arrowRect.setBottom(cbOpt->rect.bottom()); arrowRect.setRight(cbOpt->rect.right() - 1); if (enabled && (cbOpt->state & State_On)) { arrowRect.translate(1, 1); // push effect } //if (!enabled) color = color.lighter(); painter->setBrush(enabled ? QBrush(color, Qt::SolidPattern) : Qt::NoBrush); QPolygon cbArrowDown; cbArrowDown.setPoints(6, 3, -2, 4, -2, 0, 2, -4, -2, -3, -2, 0, 1); cbArrowDown.translate((arrowRect.x() + (arrowRect.width() >> 1)), (arrowRect.y() + (arrowRect.height() >> 1))); painter->drawPolygon(cbArrowDown); // Focus rect (from qcleanlooksstyle) if (enabled && (cbOpt->state & State_HasFocus)) { QRect focusRect = QProxyStyle::subElementRect(SE_ComboBoxFocusRect, cbOpt, widget); focusRect.adjust(0, -2, 0, 2); painter->setBrush(QBrush(color, Qt::Dense4Pattern)); painter->setBrushOrigin(focusRect.topLeft()); painter->setPen(Qt::NoPen); const QRect rects[4] = { QRect(focusRect.left(), focusRect.top(), focusRect.width(), 1), // Top QRect(focusRect.left(), focusRect.bottom(), focusRect.width(), 1), // Bottom QRect(focusRect.left(), focusRect.top(), 1, focusRect.height()), // Left QRect(focusRect.right(), focusRect.top(), 1, focusRect.height()) // Right }; painter->drawRects(rects, 4); } painter->restore(); return; } } QProxyStyle::drawComplexControl(cc, opt, painter, widget); } QRect subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, SubControl sc, const QWidget *widget) const Q_DECL_OVERRIDE { // Make sure we give combo popup's enough room to display contents; // Qt doesn't do this by default if (cc == QStyle::CC_ComboBox && sc == SC_ComboBoxListBoxPopup) { const QComboBox *cb = qobject_cast(widget); const QStyleOptionComboBox *cbOpt = qstyleoption_cast(opt); if (cb && cbOpt) { QFontMetrics fm = cb->fontMetrics(); // Compute content width; Qt uses the usual +4 magic number for icon/text margin int maxW = 0; for (int c = 0; c < cb->count(); ++c) { int iw = fm.width(cb->itemText(c)); if (!cb->itemIcon(c).isNull()) { iw += 4 + cb->iconSize().width(); } maxW = qMax(maxW, iw); } // Now let sizeFromContent add in extra stuff. maxW = QProxyStyle::sizeFromContents(QStyle::CT_ComboBox, opt, QSize(maxW, 1), widget).width(); // How much more room do we need for the text? int extraW = maxW > cbOpt->rect.width() ? maxW - cbOpt->rect.width() : 0; QRect r = QProxyStyle::subControlRect(cc, opt, sc, widget); r.setWidth(r.width() + extraW); return r; } } return QProxyStyle::subControlRect(cc, opt, sc, widget); } int left, right, top, bottom; int clearButtonOverlay; bool noBorder; }; // --------------------------------------------------------------------- RenderFormElement::RenderFormElement(HTMLGenericFormElementImpl *element) : RenderWidget(element) // , m_state(0) , m_proxyStyle(nullptr) , m_exposeInternalPadding(false) , m_isOxygenStyle(false) { // init RenderObject attributes setInline(true); // our object is Inline } RenderFormElement::~RenderFormElement() {} void RenderFormElement::setStyle(RenderStyle *_style) { RenderWidget::setStyle(_style); setPadding(); if (!shouldDisableNativeBorders()) { // When the widget shows native border, clipping background to border // results in a nasty rendering effects if (style()->backgroundLayers()->backgroundClip() == BGBORDER) { style()->accessBackgroundLayers()->setBackgroundClip(BGPADDING); } m_isOxygenStyle = QApplication::style()->objectName().contains("oxygen"); } } int RenderFormElement::paddingTop() const { return (!includesPadding() || m_exposeInternalPadding) ? RenderWidget::paddingTop() : 0; } int RenderFormElement::paddingBottom() const { return (!includesPadding() || m_exposeInternalPadding) ? RenderWidget::paddingBottom() : 0; } int RenderFormElement::paddingLeft() const { return (!includesPadding() || m_exposeInternalPadding) ? RenderWidget::paddingLeft() : 0; } int RenderFormElement::paddingRight() const { return (!includesPadding() || m_exposeInternalPadding) ? RenderWidget::paddingRight() : 0; } bool RenderFormElement::includesPadding() const { return true; } void RenderFormElement::setPadding() { if (!includesPadding()) { return; } KHTMLProxyStyle *style = static_cast(getProxyStyle()); style->left = RenderWidget::paddingLeft(); style->right = RenderWidget::paddingRight(); style->top = RenderWidget::paddingTop(); style->bottom = RenderWidget::paddingBottom(); } QProxyStyle *RenderFormElement::getProxyStyle() { assert(widget()); if (m_proxyStyle) { return m_proxyStyle; } m_proxyStyle = new KHTMLProxyStyle(widget()->style()); widget()->setStyle(m_proxyStyle); return m_proxyStyle; } short RenderFormElement::baselinePosition(bool f) const { return RenderWidget::baselinePosition(f) - 2 - style()->fontMetrics().descent(); } void RenderFormElement::setQWidget(QWidget *w) { // Avoid dangling proxy pointer when we switch widgets. // the widget will cleanup the proxy, as it is its kid. m_proxyStyle = nullptr; // sets the Qt Object Name for the purposes // of setPadding() -- this is because QStyleSheet // will propagate children of 'w' even if they are toplevel, like // the "find" dialog or the popup menu w->setObjectName("RenderFormElementWidget"); RenderWidget::setQWidget(w); } void RenderFormElement::updateFromElement() { m_widget->setEnabled(!element()->disabled()); // If we've disabled a focused element, clear its focus, // so Qt doesn't do funny stuff like let one type into a disabled // line edit. if (element()->disabled() && element()->focused()) { document()->quietResetFocus(); } RenderWidget::updateFromElement(); } // Some form widgets apply the padding internally (i.e. as if they were // some kind of inline-block). Thus we only want to expose that padding // while layouting (so that width/height calculations are correct), and // then pretend it does not exist, as it is beyond the replaced edge and // thus should not affect other calculations. void RenderFormElement::calcMinMaxWidth() { m_exposeInternalPadding = true; RenderWidget::calcMinMaxWidth(); m_exposeInternalPadding = false; } void RenderFormElement::calcWidth() { m_exposeInternalPadding = true; RenderWidget::calcWidth(); m_exposeInternalPadding = false; } void RenderFormElement::calcHeight() { m_exposeInternalPadding = true; RenderWidget::calcHeight(); m_exposeInternalPadding = false; } void RenderFormElement::layout() { KHTMLAssert(needsLayout()); KHTMLAssert(minMaxKnown()); // minimum height m_height = 0; calcWidth(); calcHeight(); if (m_widget) resizeWidget(m_width - borderLeft() - borderRight() - paddingLeft() - paddingRight(), m_height - borderTop() - borderBottom() - paddingTop() - paddingBottom()); setNeedsLayout(false); } int RenderFormElement::calcContentWidth(int w) const { if (!shouldDisableNativeBorders()) { if (style()->boxSizing() == CONTENT_BOX) { int nativeBorderWidth = m_widget->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, m_widget); return RenderBox::calcContentWidth(w) + 2 * nativeBorderWidth; } } return RenderBox::calcContentWidth(w); } int RenderFormElement::calcContentHeight(int h) const { if (!shouldDisableNativeBorders()) { if (style()->boxSizing() == CONTENT_BOX) { int nativeBorderWidth = m_widget->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, m_widget); return RenderBox::calcContentHeight(h) + 2 * nativeBorderWidth; } } return RenderBox::calcContentHeight(h); } void RenderFormElement::paintOneBackground(QPainter *p, const QColor &c, const BackgroundLayer *bgLayer, QRect clipr, int _tx, int _ty, int w, int height) { int fudge = 0; if (!shouldDisableNativeBorders()) { fudge = m_isOxygenStyle ? 3 : 1; } paintBackgroundExtended(p, c, bgLayer, clipr, _tx, _ty, w, height, fudge ? fudge : borderLeft(), fudge ? fudge : borderRight(), RenderWidget::paddingLeft(), RenderWidget::paddingRight(), fudge ? fudge : borderTop(), fudge ? fudge : borderBottom(), RenderWidget::paddingTop(), RenderWidget::paddingBottom()); } Qt::Alignment RenderFormElement::textAlignment() const { switch (style()->textAlign()) { case LEFT: case KHTML_LEFT: return Qt::AlignLeft; case RIGHT: case KHTML_RIGHT: return Qt::AlignRight; case CENTER: case KHTML_CENTER: return Qt::AlignHCenter; case JUSTIFY: // Just fall into the auto code for justify. case TAAUTO: return style()->direction() == RTL ? Qt::AlignRight : Qt::AlignLeft; } assert(false); // Should never be reached. return Qt::AlignLeft; } // ------------------------------------------------------------------------- RenderButton::RenderButton(HTMLGenericFormElementImpl *element) : RenderFormElement(element) { m_hasTextIndentHack = false; } short RenderButton::baselinePosition(bool f) const { int ret = (height() - RenderWidget::paddingTop() - RenderWidget::paddingBottom() + 1) / 2; ret += marginTop() + RenderWidget::paddingTop(); ret += ((fontMetrics(f).ascent()) / 2) - 1; return ret; } void RenderButton::layout() { RenderFormElement::layout(); bool needsTextIndentHack = false; if (!style()->width().isAuto()) { // check if we need to simulate the effect of a popular // button text hiding 'trick' that makes use of negative text-indent, // which we do not support on form widgets. int ti = style()->textIndent().minWidth(containingBlockWidth()); if (m_widget->width() <= qAbs(ti)) { needsTextIndentHack = true; } } if (m_hasTextIndentHack != needsTextIndentHack) { m_hasTextIndentHack = needsTextIndentHack; updateFromElement(); } } void RenderButton::setStyle(RenderStyle *style) { RenderFormElement::setStyle(style); if (shouldDisableNativeBorders()) { // we paint the borders ourselves on this button, // remove the widget's native ones. KHTMLProxyStyle *style = static_cast(getProxyStyle()); style->noBorder = true; } } // ------------------------------------------------------------------------------- RenderCheckBox::RenderCheckBox(HTMLInputElementImpl *element) : RenderButton(element) { CheckBoxWidget *b = new CheckBoxWidget(view()->widget()); //b->setAutoMask(true); b->setMouseTracking(true); setQWidget(b); // prevent firing toggled() signals on initialization b->setChecked(element->checked()); connect(b, SIGNAL(stateChanged(int)), this, SLOT(slotStateChanged(int))); m_ignoreStateChanged = false; } void RenderCheckBox::calcMinMaxWidth() { KHTMLAssert(!minMaxKnown()); QCheckBox *cb = static_cast(m_widget); QSize s(qMin(22, qMax(14, cb->style()->pixelMetric(QStyle::PM_IndicatorWidth))), qMin(22, qMax(12, cb->style()->pixelMetric(QStyle::PM_IndicatorHeight)))); setIntrinsicWidth(s.width()); setIntrinsicHeight(s.height()); RenderButton::calcMinMaxWidth(); } void RenderCheckBox::updateFromElement() { if (widget()->isChecked() != element()->checked()) { m_ignoreStateChanged = true; widget()->setChecked(element()->checked()); m_ignoreStateChanged = false; } RenderButton::updateFromElement(); } void RenderCheckBox::slotStateChanged(int state) { if (m_ignoreStateChanged) { return; } element()->setChecked(state == Qt::Checked); } bool RenderCheckBox::handleEvent(const DOM::EventImpl &ev) { switch (ev.id()) { case EventImpl::DOMFOCUSIN_EVENT: case EventImpl::DOMFOCUSOUT_EVENT: case EventImpl::MOUSEMOVE_EVENT: case EventImpl::MOUSEOUT_EVENT: case EventImpl::MOUSEOVER_EVENT: return RenderButton::handleEvent(ev); default: break; } return false; } // ------------------------------------------------------------------------------- RenderRadioButton::RenderRadioButton(HTMLInputElementImpl *element) : RenderButton(element) { RadioButtonWidget *b = new RadioButtonWidget(view()->widget()); b->setMouseTracking(true); b->setAutoExclusive(false); setQWidget(b); // prevent firing toggled() signals on initialization b->setChecked(element->checked()); connect(b, SIGNAL(toggled(bool)), this, SLOT(slotToggled(bool))); m_ignoreToggled = false; } void RenderRadioButton::updateFromElement() { m_ignoreToggled = true; widget()->setChecked(element()->checked()); m_ignoreToggled = false; RenderButton::updateFromElement(); } void RenderRadioButton::calcMinMaxWidth() { KHTMLAssert(!minMaxKnown()); QRadioButton *rb = static_cast(m_widget); QSize s(qMin(22, qMax(14, rb->style()->pixelMetric(QStyle::PM_ExclusiveIndicatorWidth))), qMin(20, qMax(12, rb->style()->pixelMetric(QStyle::PM_ExclusiveIndicatorHeight)))); setIntrinsicWidth(s.width()); setIntrinsicHeight(s.height()); RenderButton::calcMinMaxWidth(); } void RenderRadioButton::slotToggled(bool /*activated*/) { if (m_ignoreToggled) { return; } } bool RenderRadioButton::handleEvent(const DOM::EventImpl &ev) { switch (ev.id()) { case EventImpl::DOMFOCUSIN_EVENT: case EventImpl::DOMFOCUSOUT_EVENT: case EventImpl::MOUSEMOVE_EVENT: case EventImpl::MOUSEOUT_EVENT: case EventImpl::MOUSEOVER_EVENT: return RenderButton::handleEvent(ev); default: break; } return false; } // ------------------------------------------------------------------------------- const QLatin1String sBorderNoneSheet("QPushButton{border:none}"); RenderSubmitButton::RenderSubmitButton(HTMLInputElementImpl *element) : RenderButton(element) { PushButtonWidget *p = new PushButtonWidget(view()->widget()); setQWidget(p); //p->setAutoMask(true); p->setMouseTracking(true); p->setDefault(false); p->setAutoDefault(false); } static inline void setStyleSheet_helper(const QString &s, QWidget *w) { // ### buggy Qt stylesheets mess with the widget palette. // force it again after any stylesheet update. QPalette pal = w->palette(); w->setStyleSheet(s); w->setPalette(pal); } void RenderSubmitButton::setPadding() { // Proxy styling doesn't work well enough for buttons. // Use stylesheets instead. tests/css/button-padding-top.html assert(!m_proxyStyle); if (!includesPadding()) { return; } if (!RenderWidget::paddingLeft() && !RenderWidget::paddingRight() && !RenderWidget::paddingTop() && !RenderWidget::paddingBottom()) { setStyleSheet_helper((shouldDisableNativeBorders() ? sBorderNoneSheet : QString()), widget()); return; } setStyleSheet_helper( QString("QPushButton{padding-left:%1px; padding-right:%2px; padding-top:%3px; padding-bottom:%4px}") .arg(RenderWidget::paddingLeft()) .arg(RenderWidget::paddingRight()) .arg(RenderWidget::paddingTop()) .arg(RenderWidget::paddingBottom()) + (shouldDisableNativeBorders() ? sBorderNoneSheet : QString()) , widget()); } void RenderSubmitButton::setStyle(RenderStyle *style) { // Proxy styling doesn't work well enough for buttons. // Use stylesheets instead. tests/css/button-padding-top.html assert(!m_proxyStyle); RenderFormElement::setStyle(style); QString s = widget()->styleSheet(); if (shouldDisableNativeBorders()) { // we paint the borders ourselves on this button, // remove the widget's native ones. if (!s.contains(sBorderNoneSheet)) { s.append(sBorderNoneSheet); setStyleSheet_helper(s, widget()); } } else { setStyleSheet_helper(s.remove(sBorderNoneSheet), widget()); } } QString RenderSubmitButton::rawText() { QString value = element()->valueWithDefault().string(); value = value.trimmed(); QString raw; for (int i = 0; i < value.length(); i++) { raw += value[i]; if (value[i] == '&') { raw += '&'; } } return raw; } bool RenderSubmitButton::canHaveBorder() const { // ### TODO would be nice to be able to // return style()->hasBackgroundImage() here, // depending on a config option (e.g. 'favour usability/integration over aspect') // so that only buttons with both a custom border // and a background image are drawn without native styling. // // This would go in the same place, gui wise, as a choice of b/w default color scheme, // versus native color scheme. return true; } void RenderSubmitButton::calcMinMaxWidth() { KHTMLAssert(!minMaxKnown()); QString raw = rawText(); QPushButton *pb = static_cast(m_widget); pb->setText(raw); pb->setFont(style()->font()); bool empty = raw.isEmpty(); if (empty) { raw = QLatin1Char('X'); } QFontMetrics fm = pb->fontMetrics(); QSize ts = fm.size(Qt::TextShowMnemonic, raw); //Oh boy. QStyleOptionButton butOpt; butOpt.init(pb); butOpt.text = raw; QSize s = pb->style()->sizeFromContents(QStyle::CT_PushButton, &butOpt, ts, pb); s = s.expandedTo(QApplication::globalStrut()); int margin = pb->style()->pixelMetric(QStyle::PM_ButtonMargin) + pb->style()->pixelMetric(QStyle::PM_DefaultFrameWidth) * 2; int w = ts.width() + margin; int h = s.height(); assert(includesPadding()); int hpadding = RenderWidget::paddingLeft() + RenderWidget::paddingRight(); int vpadding = RenderWidget::paddingTop() + RenderWidget::paddingBottom(); // add 30% margins to the width (heuristics to make it look similar to IE) // ### FIXME BASELINE: we could drop this emulation and adopt Mozilla style buttons // (+/- padding: 0px 8px 0px 8px) - IE is most often in a separate css // code path nowadays, so we have wider buttons than other engines. int toAdd = (w * 13 / 10) - w - hpadding; toAdd = qMax(0, toAdd); w += toAdd; if (shouldDisableNativeBorders()) { // we paint the borders ourselves, so let's override our height to something saner h = ts.height(); } else { h -= vpadding; } s = QSize(w, h).expandedTo(QApplication::globalStrut()); setIntrinsicWidth(s.width()); setIntrinsicHeight(s.height()); RenderButton::calcMinMaxWidth(); } void RenderSubmitButton::updateFromElement() { QString oldText = static_cast(m_widget)->text(); QString newText = rawText(); static_cast(m_widget)->setText(newText); if (oldText != newText) { setNeedsLayoutAndMinMaxRecalc(); } RenderFormElement::updateFromElement(); } short RenderSubmitButton::baselinePosition(bool f) const { int ret = (height() - RenderWidget::paddingTop() - RenderWidget::paddingBottom() + 1) / 2; ret += marginTop() + RenderWidget::paddingTop(); ret += ((fontMetrics(f).ascent()) / 2) - 2; return ret; } // ------------------------------------------------------------------------------- RenderResetButton::RenderResetButton(HTMLInputElementImpl *element) : RenderSubmitButton(element) { } // ------------------------------------------------------------------------------- namespace khtml { class CompletionWidget: public KCompletionBox { public: CompletionWidget(QWidget *parent = nullptr) : KCompletionBox(parent) {} QPoint globalPositionHint() const Q_DECL_OVERRIDE { QWidget *pw = parentWidget(); KHTMLWidget *kwp = dynamic_cast(pw); if (!kwp) { - qDebug() << "CompletionWidget has no KHTMLWidget parent" << endl; + qDebug() << "CompletionWidget has no KHTMLWidget parent"; return KCompletionBox::globalPositionHint(); } QPoint dest; KHTMLView *v = kwp->m_kwp->rootViewPos(dest); QPoint ret; if (v) { ret = v->mapToGlobal(dest + QPoint(0, pw->height())); int zoomLevel = v->zoomLevel(); if (zoomLevel != 100) { ret.setX(ret.x()*zoomLevel / 100); ret.setY(ret.y()*zoomLevel / 100); } } return ret; } }; } LineEditWidget::LineEditWidget(DOM::HTMLInputElementImpl *input, KHTMLView *view, QWidget *parent) : KLineEdit(parent), m_input(input), m_view(view) { m_kwp->setIsRedirected(true); setMouseTracking(true); KActionCollection *ac = new KActionCollection(this); m_spellAction = KStandardAction::spelling(this, SLOT(slotCheckSpelling()), ac); setCompletionBox(new CompletionWidget(this)); completionBox()->setObjectName("completion box"); completionBox()->setFont(font()); } LineEditWidget::~LineEditWidget() { } void LineEditWidget::slotCheckSpelling() { if (text().isEmpty()) { return; } Sonnet::Dialog *spellDialog = new Sonnet::Dialog(new Sonnet::BackgroundChecker(this), nullptr); connect(spellDialog, SIGNAL(replace(QString,int,QString)), this, SLOT(spellCheckerCorrected(QString,int,QString))); connect(spellDialog, SIGNAL(misspelling(QString,int)), this, SLOT(spellCheckerMisspelling(QString,int))); connect(spellDialog, SIGNAL(done(QString)), this, SLOT(slotSpellCheckDone(QString))); connect(spellDialog, SIGNAL(cancel()), this, SLOT(spellCheckerFinished())); connect(spellDialog, SIGNAL(stop()), this, SLOT(spellCheckerFinished())); spellDialog->setBuffer(text()); spellDialog->show(); } void LineEditWidget::spellCheckerMisspelling(const QString &_text, int pos) { highLightWord(_text.length(), pos); } void LineEditWidget::setFocus() { KLineEdit::setFocus(); end(false); } void LineEditWidget::highLightWord(unsigned int length, unsigned int pos) { setSelection(pos, length); } void LineEditWidget::spellCheckerCorrected(const QString &old, int pos, const QString &corr) { if (old != corr) { setSelection(pos, old.length()); insert(corr); setSelection(pos, corr.length()); } } void LineEditWidget::spellCheckerFinished() { } void LineEditWidget::slotSpellCheckDone(const QString &s) { if (s != text()) { setText(s); } } namespace khtml { /** * @internal */ class WebShortcutCreator { public: /** * @short Creates a Web Shourtcut without using kdebase SearchProvider class. * It is used by LineEditWidget. */ static bool createWebShortcut(QString query); private: static bool askData(QString &name, QString &keys); static void createFile(QString query, QString name, QString keys); }; bool WebShortcutCreator::createWebShortcut(QString query) { QString name = i18n("New Web Shortcut"); QString keys; if (askData(name, keys)) { bool isOk; do { //It's going to be checked if the keys have already been assigned isOk = true; QStringList keyList(keys.split(',')); KService::List providers = KServiceTypeTrader::self()->query("SearchProvider"); foreach (const KService::Ptr &provider, providers) { if (!isOk) { break; } foreach (const QString &s, provider->property("Keys").toStringList()) { if (!isOk) { break; } foreach (const QString &t, keys) { if (!isOk) { break; } if (s == t) { KMessageBox::sorry(nullptr, i18n("%1 is already assigned to %2", s, provider->name()), i18n("Error")); isOk = false; } } } } if (!isOk && !askData(name, keys)) { return false; } } while (!isOk); createFile(query, name, keys); return true; } else { return false; } } void WebShortcutCreator::createFile(QString query, QString name, QString keys) { // SearchProvider class is part of kdebase, so the file is written as // an standard desktop file. QString fileName(keys); QString dir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/kservices5/searchproviders"; QDir().mkpath(dir); while (QFile::exists(dir + fileName + ".desktop")) { fileName += '_'; } KDesktopFile f(dir + fileName + ".desktop"); f.desktopGroup().writeEntry("Keys", keys); f.desktopGroup().writeEntry("Type", "Service"); f.desktopGroup().writeEntry("ServiceTypes", "SearchProvider"); f.desktopGroup().writeEntry("Name", name); f.desktopGroup().writeEntry("Query", query); f.sync(); KBuildSycocaProgressDialog::rebuildKSycoca(nullptr); } bool WebShortcutCreator::askData(QString &name, QString &keys) { QDialog *dialog = new QDialog(); dialog->setWindowTitle(name); QVBoxLayout *mainLayout = new QVBoxLayout(); dialog->setLayout(mainLayout); QHBoxLayout *layout = new QHBoxLayout(); mainLayout->addLayout(layout); QLabel *label = new QLabel(i18n("Search &provider name:"), dialog); layout->addWidget(label); QLineEdit *nameEdit = new QLineEdit(i18n("New search provider"), dialog); label->setBuddy(nameEdit); layout->addWidget(nameEdit); layout = new QHBoxLayout(); mainLayout->addLayout(layout); label = new QLabel(i18n("UR&I shortcuts:"), dialog); layout->addWidget(label); QLineEdit *keysEdit = new QLineEdit(dialog); label->setBuddy(keysEdit); layout->addWidget(keysEdit); QDialogButtonBox *buttonBox = new QDialogButtonBox(dialog); buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); QObject::connect(buttonBox, SIGNAL(accepted()), dialog, SLOT(accept())); QObject::connect(buttonBox, SIGNAL(rejected()), dialog, SLOT(reject())); mainLayout->addWidget(buttonBox); bool res = dialog->exec(); if (res) { name = nameEdit->text(); keys = keysEdit->text(); } delete dialog; return res; } } void LineEditWidget::slotCreateWebShortcut() { QString queryName(m_input->name().string()); HTMLFormElementImpl *form = m_input->form(); QUrl url(form->action().string()); QUrl baseUrl(m_view->part()->baseURL().url() + '?'); if (url.path().isEmpty()) { url.setPath(baseUrl.path()); } if (url.host().isEmpty()) { url.setScheme(baseUrl.scheme()); url.setHost(baseUrl.host()); } NodeImpl *node; HTMLInputElementImpl *inputNode; for (unsigned long i = 0; (node = form->elements()->item(i)); i++) { inputNode = dynamic_cast(node); if (inputNode) { if ((!inputNode->name().string().size()) || (inputNode->name().string() == queryName)) { continue; } else { switch (inputNode->inputType()) { case HTMLInputElementImpl::CHECKBOX: case HTMLInputElementImpl::RADIO: if (!inputNode->checked()) { break; } case HTMLInputElementImpl::TEXT: case HTMLInputElementImpl::PASSWORD: case HTMLInputElementImpl::HIDDEN: url.addQueryItem(inputNode->name().string(), inputNode->value().string()); default: break; } } } } QString query(url.url()); if (!query.contains("?")) { query += '?'; //This input is the only one of the form } query += '&' + queryName + "=\\{@}"; WebShortcutCreator::createWebShortcut(query); } void LineEditWidget::contextMenuEvent(QContextMenuEvent *e) { QMenu *popup = createStandardContextMenu(); if (!popup) { return; } if (m_input->autoComplete()) { popup->addSeparator(); QAction *act = popup->addAction(QIcon::fromTheme("edit-clear-history"), i18n("Clear &History")); act->setEnabled(compObj() && !compObj()->isEmpty()); connect(act, SIGNAL(triggered()), this, SLOT(clearHistoryActivated())); } if (echoMode() == QLineEdit::Normal && !isReadOnly()) { popup->addSeparator(); popup->addAction(m_spellAction); m_spellAction->setEnabled(!text().isEmpty()); } if (!m_view->part()->onlyLocalReferences()) { popup->addSeparator(); QAction *act = popup->addAction(i18n("Create Web Shortcut")); connect(act, SIGNAL(triggered()), this, SLOT(slotCreateWebShortcut())); } emit aboutToShowContextMenu(popup); popup->exec(e->globalPos()); delete popup; } void LineEditWidget::clearHistoryActivated() { m_view->clearCompletionHistory(m_input->name().string()); if (compObj()) { compObj()->clear(); } } bool LineEditWidget::event(QEvent *e) { if (KLineEdit::event(e)) { return true; } #if 0 if (e->type() == QEvent::AccelAvailable && isReadOnly()) { QKeyEvent *ke = (QKeyEvent *) e; if (ke->modifiers() & Qt::ControlModifier) { switch (ke->key()) { case Qt::Key_Left: case Qt::Key_Right: case Qt::Key_Up: case Qt::Key_Down: case Qt::Key_Home: case Qt::Key_End: ke->accept(); default: break; } } } #endif return false; } void LineEditWidget::mouseMoveEvent(QMouseEvent *e) { // hack to prevent Qt from calling setCursor on the widget setDragEnabled(false); KLineEdit::mouseMoveEvent(e); setDragEnabled(true); } // ----------------------------------------------------------------------------- RenderLineEdit::RenderLineEdit(HTMLInputElementImpl *element) : RenderFormElement(element), m_blockElementUpdates(false) { LineEditWidget *edit = new LineEditWidget(element, view(), view()->widget()); connect(edit, SIGNAL(returnPressed()), this, SLOT(slotReturnPressed())); connect(edit, SIGNAL(textChanged(QString)), this, SLOT(slotTextChanged(QString))); if (element->inputType() == HTMLInputElementImpl::PASSWORD) { edit->setEchoMode(QLineEdit::Password); } if (element->autoComplete()) { QStringList completions = view()->formCompletionItems(element->name().string()); if (completions.count()) { edit->completionObject()->setItems(completions); edit->setContextMenuPolicy(Qt::NoContextMenu); edit->completionBox()->setTabHandling(false); } } setQWidget(edit); } short RenderLineEdit::baselinePosition(bool f) const { bool hasFrame = static_cast(widget())->hasFrame(); int bTop = hasFrame ? 0 : borderTop(); int bBottom = hasFrame ? 0 : borderBottom(); int ret = (height() - RenderWidget::paddingTop() - RenderWidget::paddingBottom() - bTop - bBottom + 1) / 2; ret += marginTop() + RenderWidget::paddingTop() + bTop; ret += ((fontMetrics(f).ascent()) / 2) - 2; return ret; } void RenderLineEdit::setStyle(RenderStyle *_style) { RenderFormElement::setStyle(_style); if (widget()->alignment() != textAlignment()) { widget()->setAlignment(textAlignment()); } bool showClearButton = (!shouldDisableNativeBorders() && !_style->hasBackgroundImage()); if (!showClearButton && widget()->isClearButtonShown()) { widget()->setClearButtonShown(false); } else if (showClearButton && !widget()->isClearButtonShown()) { widget()->setClearButtonShown(true); QObjectList children = widget()->children(); foreach (QObject *object, children) { QWidget *w = qobject_cast(object); if (w && !w->isWindow() && (w->objectName() == "KLineEditButton")) { // this duplicates KHTMLView's handleWidget but this widget // is created on demand, so it might not be here at ChildPolished time w->installEventFilter(view()); } } } if (m_proxyStyle) { static_cast(m_proxyStyle)->clearButtonOverlay = qMax(0, widget()->clearButtonUsedSize().width()); } } void RenderLineEdit::highLightWord(unsigned int length, unsigned int pos) { LineEditWidget *w = static_cast(m_widget); if (w) { w->highLightWord(length, pos); } } void RenderLineEdit::slotReturnPressed() { // don't submit the form when return was pressed in a completion-popup KCompletionBox *box = widget()->completionBox(false); if (box && box->isVisible() && box->currentRow() != -1) { box->hide(); return; } // Emit onChange if necessary // Works but might not be enough, dirk said he had another solution at // hand (can't remember which) - David handleFocusOut(); HTMLFormElementImpl *fe = element()->form(); if (fe) { fe->submitFromKeyboard(); } } void RenderLineEdit::handleFocusOut() { if (widget() && widget()->isModified()) { element()->onChange(); widget()->setModified(false); } } void RenderLineEdit::calcMinMaxWidth() { KHTMLAssert(!minMaxKnown()); const QFontMetrics &fm = style()->fontMetrics(); QSize s; int size = (element()->size() > 0) ? (element()->size() + 1) : 17; // "some" int h = fm.lineSpacing(); int w = (fm.height() * size) / 2; // on average a character cell is twice as tall as it is wide QStyleOptionFrame opt; opt.initFrom(widget()); if (widget()->hasFrame()) { opt.lineWidth = widget()->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, widget()); } s = QSize(w, qMax(h, 14)); s = widget()->style()->sizeFromContents(QStyle::CT_LineEdit, &opt, s, widget()); s = s.expandedTo(QApplication::globalStrut()); setIntrinsicWidth(s.width()); setIntrinsicHeight(s.height()); RenderFormElement::calcMinMaxWidth(); } void RenderLineEdit::updateFromElement() { int ml = element()->maxLength(); if (ml < 0) { ml = 32767; } if (widget()->maxLength() != ml) { widget()->setMaxLength(ml); } if (element()->value().string() != widget()->text()) { m_blockElementUpdates = true; // Do not block signals here (#188374) int pos = widget()->cursorPosition(); widget()->setText(element()->value().string()); widget()->setCursorPosition(pos); m_blockElementUpdates = false; } widget()->setReadOnly(element()->readOnly()); widget()->setPlaceholderText(element()->placeholder().string().remove(QLatin1Char('\n')).remove(QLatin1Char('\r'))); RenderFormElement::updateFromElement(); } void RenderLineEdit::slotTextChanged(const QString &string) { if (m_blockElementUpdates) { return; } // don't use setValue here! element()->m_value = string.isNull() ? DOMString("") : string; element()->m_unsubmittedFormChange = true; } void RenderLineEdit::select() { static_cast(m_widget)->selectAll(); } long RenderLineEdit::selectionStart() { LineEditWidget *w = static_cast(m_widget); if (w->hasSelectedText()) { return w->selectionStart(); } else { return w->cursorPosition(); } } long RenderLineEdit::selectionEnd() { LineEditWidget *w = static_cast(m_widget); if (w->hasSelectedText()) { return w->selectionStart() + w->selectedText().length(); } else { return w->cursorPosition(); } } void RenderLineEdit::setSelectionStart(long pos) { LineEditWidget *w = static_cast(m_widget); //See whether we have a non-empty selection now. long end = selectionEnd(); if (end > pos) { w->setSelection(pos, end - pos); } w->setCursorPosition(pos); } void RenderLineEdit::setSelectionEnd(long pos) { LineEditWidget *w = static_cast(m_widget); //See whether we have a non-empty selection now. long start = selectionStart(); if (start < pos) { w->setSelection(start, pos - start); } w->setCursorPosition(pos); } void RenderLineEdit::setSelectionRange(long start, long end) { LineEditWidget *w = static_cast(m_widget); w->setCursorPosition(end); w->setSelection(start, end - start); } // --------------------------------------------------------------------------- RenderFieldset::RenderFieldset(HTMLGenericFormElementImpl *element) : RenderBlock(element) { m_intrinsicWidth = 0; } void RenderFieldset::calcMinMaxWidth() { RenderBlock::calcMinMaxWidth(); if (style()->htmlHacks()) { if (RenderObject *legend = findLegend()) { int legendMinWidth = legend->minWidth(); Length legendMarginLeft = legend->style()->marginLeft(); Length legendMarginRight = legend->style()->marginLeft(); if (legendMarginLeft.isFixed()) { legendMinWidth += legendMarginLeft.value(); } if (legendMarginRight.isFixed()) { legendMinWidth += legendMarginRight.value(); } m_intrinsicWidth = qMax((int)m_minWidth, legendMinWidth + paddingLeft() + paddingRight() + borderLeft() + borderRight()); } } } RenderObject *RenderFieldset::layoutLegend(bool relayoutChildren) { RenderObject *legend = findLegend(); if (legend) { if (relayoutChildren) { legend->setNeedsLayout(true); } legend->layoutIfNeeded(); int xPos = borderLeft() + paddingLeft() + legend->marginLeft(); if (style()->direction() == RTL) { xPos = m_width - paddingRight() - borderRight() - legend->width() - legend->marginRight(); } int b = borderTop(); int h = legend->height(); legend->setPos(xPos, qMax((b - h) / 2, 0)); m_height = qMax(b, h) + paddingTop(); } return legend; } RenderObject *RenderFieldset::findLegend() const { for (RenderObject *legend = firstChild(); legend; legend = legend->nextSibling()) { if (!legend->isFloatingOrPositioned() && legend->element() && legend->element()->id() == ID_LEGEND) { return legend; } } return nullptr; } void RenderFieldset::paintBoxDecorations(PaintInfo &pI, int _tx, int _ty) { //qDebug() << renderName() << "::paintDecorations()"; RenderObject *legend = findLegend(); if (!legend) { return RenderBlock::paintBoxDecorations(pI, _tx, _ty); } int w = width(); int h = height() + borderTopExtra() + borderBottomExtra(); int yOff = (legend->yPos() > 0) ? 0 : (legend->height() - borderTop()) / 2; int legendBottom = _ty + legend->yPos() + legend->height(); h -= yOff; _ty += yOff - borderTopExtra(); QRect cr = QRect(_tx, _ty, w, h).intersected(pI.r); paintOneBackground(pI.p, style()->backgroundColor(), style()->backgroundLayers(), cr, _tx, _ty, w, h); if (style()->hasBorder()) { paintBorderMinusLegend(pI.p, _tx, _ty, w, h, style(), legend->xPos(), legend->width(), legendBottom); } } void RenderFieldset::paintBorderMinusLegend(QPainter *p, int _tx, int _ty, int w, int h, const RenderStyle *style, int lx, int lw, int lb) { const QColor &tc = style->borderTopColor(); const QColor &bc = style->borderBottomColor(); EBorderStyle ts = style->borderTopStyle(); EBorderStyle bs = style->borderBottomStyle(); EBorderStyle ls = style->borderLeftStyle(); EBorderStyle rs = style->borderRightStyle(); bool render_t = ts > BHIDDEN; bool render_l = ls > BHIDDEN; bool render_r = rs > BHIDDEN; bool render_b = bs > BHIDDEN; int borderLeftWidth = style->borderLeftWidth(); int borderRightWidth = style->borderRightWidth(); if (render_t) { if (lx >= borderLeftWidth) drawBorder(p, _tx, _ty, _tx + lx, _ty + style->borderTopWidth(), BSTop, tc, style->color(), ts, (render_l && (ls == DOTTED || ls == DASHED || ls == DOUBLE) ? style->borderLeftWidth() : 0), 0); if (lx + lw <= w - borderRightWidth) drawBorder(p, _tx + lx + lw, _ty, _tx + w, _ty + style->borderTopWidth(), BSTop, tc, style->color(), ts, 0, (render_r && (rs == DOTTED || rs == DASHED || rs == DOUBLE) ? style->borderRightWidth() : 0)); } if (render_b) drawBorder(p, _tx, _ty + h - style->borderBottomWidth(), _tx + w, _ty + h, BSBottom, bc, style->color(), bs, (render_l && (ls == DOTTED || ls == DASHED || ls == DOUBLE) ? style->borderLeftWidth() : 0), (render_r && (rs == DOTTED || rs == DASHED || rs == DOUBLE) ? style->borderRightWidth() : 0)); if (render_l) { const QColor &lc = style->borderLeftColor(); bool ignore_top = (tc == lc) && (ls >= OUTSET) && (ts == DOTTED || ts == DASHED || ts == SOLID || ts == OUTSET); bool ignore_bottom = (bc == lc) && (ls >= OUTSET) && (bs == DOTTED || bs == DASHED || bs == SOLID || bs == INSET); int startY = _ty; if (lx < borderLeftWidth && lx + lw > 0) { // The legend intersects the border. ignore_top = true; startY = lb; } drawBorder(p, _tx, startY, _tx + borderLeftWidth, _ty + h, BSLeft, lc, style->color(), ls, ignore_top ? 0 : style->borderTopWidth(), ignore_bottom ? 0 : style->borderBottomWidth()); } if (render_r) { const QColor &rc = style->borderRightColor(); bool ignore_top = (tc == rc) && (rs >= DOTTED || rs == INSET) && (ts == DOTTED || ts == DASHED || ts == SOLID || ts == OUTSET); bool ignore_bottom = (bc == rc) && (rs >= DOTTED || rs == INSET) && (bs == DOTTED || bs == DASHED || bs == SOLID || bs == INSET); int startY = _ty; if (lx < w && lx + lw > w - borderRightWidth) { // The legend intersects the border. ignore_top = true; startY = lb; } drawBorder(p, _tx + w - borderRightWidth, startY, _tx + w, _ty + h, BSRight, rc, style->color(), rs, ignore_top ? 0 : style->borderTopWidth(), ignore_bottom ? 0 : style->borderBottomWidth()); } } void RenderFieldset::setStyle(RenderStyle *_style) { RenderBlock::setStyle(_style); // WinIE renders fieldsets with display:inline like they're inline-blocks. For us, // an inline-block is just a block element with replaced set to true and inline set // to true. Ensure that if we ended up being inline that we set our replaced flag // so that we're treated like an inline-block. if (isInline()) { setReplaced(true); } } // ------------------------------------------------------------------------- RenderFileButton::RenderFileButton(HTMLInputElementImpl *element) : RenderFormElement(element) { FileButtonWidget *w = new FileButtonWidget(view()->widget()); w->setMode(KFile::File | KFile::ExistingOnly); w->lineEdit()->setCompletionBox(new CompletionWidget(w)); w->completionObject()->setDir(QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation))); connect(w->lineEdit(), SIGNAL(returnPressed()), this, SLOT(slotReturnPressed())); connect(w->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(slotTextChanged(QString))); connect(w, SIGNAL(urlSelected(QUrl)), this, SLOT(slotUrlSelected())); setQWidget(w); m_haveFocus = false; } short RenderFileButton::baselinePosition(bool f) const { int bTop = borderTop(); int bBottom = borderBottom(); int ret = (height() - paddingTop() - paddingBottom() - bTop - bBottom + 1) / 2; ret += marginTop() + paddingTop() + bTop; ret += ((fontMetrics(f).ascent()) / 2) - 2; return ret; } void RenderFileButton::calcMinMaxWidth() { KHTMLAssert(!minMaxKnown()); const QFontMetrics &fm = style()->fontMetrics(); int size = (element()->size() > 0) ? (element()->size() + 1) : 17; // "some" int h = fm.lineSpacing(); int w = (fm.height() * size) / 2; // on average a character cell is twice as tall as it is wide KLineEdit *edit = widget()->lineEdit(); QStyleOptionFrame opt; opt.initFrom(edit); if (edit->hasFrame()) { opt.lineWidth = edit->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, edit); } QSize s(w, qMax(h, 14)); s = edit->style()->sizeFromContents(QStyle::CT_LineEdit, &opt, s, edit); s = s.expandedTo(QApplication::globalStrut()); QSize bs = widget()->minimumSizeHint() - edit->minimumSizeHint(); setIntrinsicWidth(s.width() + bs.width()); setIntrinsicHeight(qMax(s.height(), bs.height())); RenderFormElement::calcMinMaxWidth(); } void RenderFileButton::handleFocusOut() { if (widget()->lineEdit() && widget()->lineEdit()->isModified()) { element()->onChange(); widget()->lineEdit()->setModified(false); } } void RenderFileButton::updateFromElement() { KLineEdit *edit = widget()->lineEdit(); bool blocked = edit->blockSignals(true); edit->setText(element()->value().string()); edit->blockSignals(blocked); edit->setModified(false); RenderFormElement::updateFromElement(); } void RenderFileButton::slotReturnPressed() { // don't submit the form when return was pressed in a completion-popup KCompletionBox *box = widget()->lineEdit()->completionBox(false); if (box && box->isVisible() && box->currentRow() != -1) { box->hide(); return; } handleFocusOut(); if (element()->form()) { element()->form()->submitFromKeyboard(); } } void RenderFileButton::slotTextChanged(const QString &/*string*/) { element()->m_value = QUrl(widget()->url()).toDisplayString(QUrl::PreferLocalFile); } void RenderFileButton::slotUrlSelected() { element()->onChange(); } void RenderFileButton::select() { widget()->lineEdit()->selectAll(); } // ------------------------------------------------------------------------- RenderLabel::RenderLabel(HTMLGenericFormElementImpl *element) : RenderFormElement(element) { } // ------------------------------------------------------------------------- RenderLegend::RenderLegend(HTMLGenericFormElementImpl *element) : RenderBlock(element) { } // ------------------------------------------------------------------------------- bool ListBoxWidget::event(QEvent *event) { // accept all wheel events so that they are not propagated to the view // once either end of the list is reached. bool ret = QListWidget::event(event); if (event->type() == QEvent::Wheel) { event->accept(); ret = true; } return ret; } ComboBoxWidget::ComboBoxWidget(QWidget *parent) : KComboBox(false, parent) { m_kwp->setIsRedirected(true); //setAutoMask(true); if (view()) { view()->installEventFilter(this); } setMouseTracking(true); } void ComboBoxWidget::showPopup() { QPoint p = pos(); QPoint dest(p); QWidget *parent = parentWidget(); KHTMLView *v = m_kwp->rootViewPos(dest); int zoomLevel = v ? v->zoomLevel() : 100; if (zoomLevel != 100) { if (v) { // we need to place the popup even lower on the screen, take in count the widget is bigger // now, so we add also the difference between the original height, and the zoomed height dest.setY(dest.y() + (sizeHint().height() * zoomLevel / 100 - sizeHint().height())); } } bool blocked = blockSignals(true); if (v != parent) { setParent(v); } move(dest); blockSignals(blocked); KComboBox::showPopup(); blocked = blockSignals(true); if (v != parent) { setParent(parent); // undo side effect of setParent() show(); } move(p); blockSignals(blocked); } void ComboBoxWidget::hidePopup() { KComboBox::hidePopup(); } bool ComboBoxWidget::event(QEvent *e) { if (KComboBox::event(e)) { return true; } if (e->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(e); switch (ke->key()) { case Qt::Key_Return: case Qt::Key_Enter: showPopup(); ke->accept(); return true; default: return false; } } return false; } bool ComboBoxWidget::eventFilter(QObject *dest, QEvent *e) { if (dest == view() && e->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(e); bool forward = false; switch (ke->key()) { case Qt::Key_Tab: forward = true; // fall through case Qt::Key_Backtab: // ugly hack. emulate popdownlistbox() (private in QComboBox) // we re-use ke here to store the reference to the generated event. ke = new QKeyEvent(QEvent::KeyPress, Qt::Key_Escape, Qt::NoModifier); QApplication::sendEvent(dest, ke); focusNextPrevChild(forward); delete ke; return true; default: return KComboBox::eventFilter(dest, e); } } return KComboBox::eventFilter(dest, e); } void ComboBoxWidget::keyPressEvent(QKeyEvent *e) { // Normally, widgets are not sent Tab keys this way in the first // place as they are handled by QWidget::event() for focus handling // already. But we get our events via EventPropagator::sendEvent() // directly. Ignore them so that HTMLGenericFormElementImpl:: // defaultEventHandler() can call focusNextPrev(). if (e->key() == Qt::Key_Tab || e->key() == Qt::Key_Backtab) { e->ignore(); return; } KComboBox::keyPressEvent(e); } // ------------------------------------------------------------------------- RenderSelect::RenderSelect(HTMLSelectElementImpl *element) : RenderFormElement(element) { m_ignoreSelectEvents = false; m_multiple = element->multiple(); m_size = element->size(); m_useListBox = (m_multiple || m_size > 1); m_selectionChanged = true; m_optionsChanged = true; if (m_useListBox) { setQWidget(createListBox()); } else { setQWidget(createComboBox()); getProxyStyle(); // We always need it to make sure popups are big enough } } void RenderSelect::clearItemFlags(int index, Qt::ItemFlags flags) { if (m_useListBox) { QListWidgetItem *item = static_cast(m_widget)->item(index); item->setFlags(item->flags() & ~flags); } else { KComboBox *combo = static_cast(m_widget); if (QStandardItemModel *model = qobject_cast(combo->model())) { QStandardItem *item = model->item(index); item->setFlags(item->flags() & ~flags); } } } void RenderSelect::setStyle(RenderStyle *_style) { RenderFormElement::setStyle(_style); if (!m_useListBox) { KHTMLProxyStyle *proxyStyle = static_cast(getProxyStyle()); proxyStyle->noBorder = shouldDisableNativeBorders(); } } void RenderSelect::updateFromElement() { m_ignoreSelectEvents = true; // change widget type bool oldMultiple = m_multiple; unsigned oldSize = m_size; bool oldListbox = m_useListBox; m_multiple = element()->multiple(); m_size = element()->size(); m_useListBox = (m_multiple || m_size > 1); if (oldMultiple != m_multiple || oldSize != m_size) { if (m_useListBox != oldListbox) { // type of select has changed if (m_useListBox) { setQWidget(createListBox()); } else { setQWidget(createComboBox()); } // Call setStyle() to fix unwanted font size change (#142722) // and to update our proxy style properties setStyle(style()); } if (m_useListBox && oldMultiple != m_multiple) { static_cast(m_widget)->setSelectionMode(m_multiple ? QListWidget::ExtendedSelection : QListWidget::SingleSelection); } m_selectionChanged = true; m_optionsChanged = true; } // update contents listbox/combobox based on options in m_element if (m_optionsChanged) { if (element()->m_recalcListItems) { element()->recalcListItems(); } const QVector listItems = element()->listItems(); int listIndex; if (m_useListBox) { static_cast(m_widget)->clear(); } else { static_cast(m_widget)->clear(); } for (listIndex = 0; listIndex < int(listItems.size()); listIndex++) { if (listItems[listIndex]->id() == ID_OPTGROUP) { DOMString text = listItems[listIndex]->getAttribute(ATTR_LABEL); if (text.isNull()) { text = ""; } text = text.implementation()->collapseWhiteSpace(false, false); if (m_useListBox) { QListWidgetItem *item = new QListWidgetItem(QString(text.implementation()->s, text.implementation()->l)); static_cast(m_widget)->insertItem(listIndex, item); } else { static_cast(m_widget)->insertItem(listIndex, QString(text.implementation()->s, text.implementation()->l)); } bool disabled = !listItems[listIndex]->getAttribute(ATTR_DISABLED).isNull(); if (disabled) { clearItemFlags(listIndex, Qt::ItemIsSelectable | Qt::ItemIsEnabled); } else { clearItemFlags(listIndex, Qt::ItemIsSelectable); } } else if (listItems[listIndex]->id() == ID_OPTION) { HTMLOptionElementImpl *optElem = static_cast(listItems[listIndex]); DOMString domText = optElem->text(); // Prefer label if set DOMString label = optElem->getAttribute(ATTR_LABEL); if (!label.isEmpty()) { domText = label; } domText = domText.implementation()->collapseWhiteSpace(false, false); QString text; ElementImpl *parentOptGroup = optElem->parentNode()->id() == ID_OPTGROUP ? static_cast(optElem->parentNode()) : nullptr; if (parentOptGroup) { text = QLatin1String(" ") + domText.string(); } else { text = domText.string(); } if (m_useListBox) { static_cast(m_widget)->insertItem(listIndex, text); } else { static_cast(m_widget)->insertItem(listIndex, text); } bool disabled = !optElem->getAttribute(ATTR_DISABLED).isNull(); if (parentOptGroup) { disabled = disabled || !parentOptGroup->getAttribute(ATTR_DISABLED).isNull(); } if (disabled) { clearItemFlags(listIndex, Qt::ItemIsSelectable | Qt::ItemIsEnabled); } } else { KHTMLAssert(false); } m_selectionChanged = true; } // QComboBox caches the size hint unless you call setFont (ref: TT docu) if (!m_useListBox) { KComboBox *that = static_cast(m_widget); that->setFont(that->font()); } setNeedsLayoutAndMinMaxRecalc(); m_optionsChanged = false; } // update selection if (m_selectionChanged) { updateSelection(); } m_ignoreSelectEvents = false; RenderFormElement::updateFromElement(); } short RenderSelect::baselinePosition(bool f) const { if (m_useListBox) { return RenderFormElement::baselinePosition(f); } int bTop = shouldDisableNativeBorders() ? borderTop() : 0; int bBottom = shouldDisableNativeBorders() ? borderBottom() : 0; int ret = (height() - RenderWidget::paddingTop() - RenderWidget::paddingBottom() - bTop - bBottom + 1) / 2; ret += marginTop() + RenderWidget::paddingTop() + bTop; ret += ((fontMetrics(f).ascent()) / 2) - 2; return ret; } void RenderSelect::calcMinMaxWidth() { KHTMLAssert(!minMaxKnown()); if (m_optionsChanged) { updateFromElement(); } // ### ugly HACK FIXME!!! setMinMaxKnown(); layoutIfNeeded(); setNeedsLayoutAndMinMaxRecalc(); // ### end FIXME RenderFormElement::calcMinMaxWidth(); } void RenderSelect::layout() { KHTMLAssert(needsLayout()); KHTMLAssert(minMaxKnown()); // ### maintain selection properly between type/size changes, and work // out how to handle multiselect->singleselect (probably just select // first selected one) // calculate size if (m_useListBox) { QListWidget *w = static_cast(m_widget); int width = 0; int height = 0; QAbstractItemModel *m = w->model(); QAbstractItemDelegate *d = w->itemDelegate(); QStyleOptionViewItem so; so.font = w->font(); for (int rowIndex = 0; rowIndex < w->count(); rowIndex++) { QModelIndex mi = m->index(rowIndex, 0); QSize s = d->sizeHint(so, mi); width = qMax(width, s.width()); height = qMax(height, s.height()); } if (!height) { height = w->fontMetrics().height(); } if (!width) { width = w->fontMetrics().width('x'); } int size = m_size; // check if multiple and size was not given or invalid // Internet Exploder sets size to qMin(number of elements, 4) // Netscape seems to simply set it to "number of elements" // the average of that is IMHO qMin(number of elements, 10) // so I did that ;-) if (size < 1) { size = qMin(w->count(), 10); } QStyleOptionFrameV3 opt; opt.initFrom(w); opt.lineWidth = w->lineWidth(); opt.midLineWidth = w->midLineWidth(); opt.frameShape = w->frameShape(); QRect r = w->style()->subElementRect(QStyle::SE_ShapedFrameContents, &opt, w); QRect o = opt.rect; int hfw = (r.left() - o.left()) + (o.right() - r.right()); int vfw = (r.top() - o.top()) + (o.bottom() - r.bottom()); width += hfw + w->verticalScrollBar()->sizeHint().width(); // FIXME BASELINE: the 3 lines below could be removed. int lhs = m_widget->style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing); if (lhs > 0) { width += lhs; } height = size * height + vfw; assert(includesPadding()); width -= RenderWidget::paddingLeft() + RenderWidget::paddingRight(); height -= RenderWidget::paddingTop() + RenderWidget::paddingBottom(); setIntrinsicWidth(width); setIntrinsicHeight(height); } else { QSize s(m_widget->sizeHint()); int w = s.width(); int h = s.height(); if (shouldDisableNativeBorders()) { const int dfw = 2 * m_widget->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, m_widget); w -= dfw; h -= dfw; } setIntrinsicWidth(w); setIntrinsicHeight(h); } /// uuh, ignore the following line.. setNeedsLayout(true); RenderFormElement::layout(); // and now disable the widget in case there is no
    s in quirks mode even if // empty-cells are on. Fixes regression on #43426, attachment #354 if (!tableElt->collapseBorders() && style()->emptyCells() == HIDE && !firstChild()) { drawBorders = false; } if (!style()->htmlHacks() && !drawBorders) { return; } _ty -= borderTopExtra(); // Paint our cell background. paintBackgroundsBehindCell(pI, _tx, _ty, this); int w = width(); int h = height() + borderTopExtra() + borderBottomExtra(); if (drawBorders && style()->hasBorder() && !tableElt->collapseBorders()) { paintBorder(pI.p, _tx, _ty, w, h, style()); } } #ifdef ENABLE_DUMP void RenderTableCell::dump(QTextStream &stream, const QString &ind) const { RenderFlow::dump(stream, ind); stream << " row=" << _row; stream << " col=" << _col; stream << " rSpan=" << rSpan; stream << " cSpan=" << cSpan; // *stream << " nWrap=" << nWrap; } #endif // ------------------------------------------------------------------------- RenderTableCol::RenderTableCol(DOM::NodeImpl *node) : RenderBox(node), m_span(1) { // init RenderObject attributes setInline(true); // our object is not Inline updateFromElement(); } void RenderTableCol::updateFromElement() { DOM::NodeImpl *node = element(); if (node && (node->id() == ID_COL || node->id() == ID_COLGROUP)) { DOM::HTMLTableColElementImpl *tc = static_cast(node); m_span = tc->span(); } else { m_span = !(style() && style()->display() == TABLE_COLUMN_GROUP); } } #ifdef ENABLE_DUMP void RenderTableCol::dump(QTextStream &stream, const QString &ind) const { RenderContainer::dump(stream, ind); stream << " _span=" << m_span; } #endif // ------------------------------------------------------------------------- TableSectionIterator::TableSectionIterator(RenderTable *table, bool fromEnd) { if (fromEnd) { sec = table->foot; if (sec) { return; } sec = static_cast(table->lastChild()); while (sec && (!sec->isTableSection() || sec == table->head || sec == table->foot)) { sec = static_cast(sec->previousSibling()); } if (sec) { return; } sec = table->head; } else { sec = table->head; if (sec) { return; } sec = static_cast(table->firstChild()); while (sec && (!sec->isTableSection() || sec == table->head || sec == table->foot)) { sec = static_cast(sec->nextSibling()); } if (sec) { return; } sec = table->foot; }/*end if*/ } TableSectionIterator &TableSectionIterator::operator ++() { RenderTable *table = sec->table(); if (sec == table->head) { sec = static_cast(table->firstChild()); while (sec && (!sec->isTableSection() || sec == table->head || sec == table->foot)) { sec = static_cast(sec->nextSibling()); } if (sec) { return *this; } } else if (sec == table->foot) { sec = nullptr; return *this; } else { do { sec = static_cast(sec->nextSibling()); } while (sec && (!sec->isTableSection() || sec == table->head || sec == table->foot)); if (sec) { return *this; } }/*end if*/ sec = table->foot; return *this; } TableSectionIterator &TableSectionIterator::operator --() { RenderTable *table = sec->table(); if (sec == table->foot) { sec = static_cast(table->lastChild()); while (sec && (!sec->isTableSection() || sec == table->head || sec == table->foot)) { sec = static_cast(sec->previousSibling()); } if (sec) { return *this; } } else if (sec == table->head) { sec = nullptr; return *this; } else { do { sec = static_cast(sec->previousSibling()); } while (sec && (!sec->isTableSection() || sec == table->head || sec == table->foot)); if (sec) { return *this; } }/*end if*/ sec = table->foot; return *this; } #undef TABLE_DEBUG #undef DEBUG_LAYOUT #undef BOX_DEBUG diff --git a/src/rendering/render_text.cpp b/src/rendering/render_text.cpp index c2b3686..3c8d818 100644 --- a/src/rendering/render_text.cpp +++ b/src/rendering/render_text.cpp @@ -1,2000 +1,2000 @@ /** * This file is part of the DOM implementation for KDE. * * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) * (C) 2000-2003 Dirk Mueller (mueller@kde.org) * (C) 2003, 2006 Apple Computer, Inc. * (C) 2004-2005 Allan Sandfeld Jensen (kde@carewolf.com) * (C) 2008 Germain Garand (germain@ebooksfrance.org) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ //#define DEBUG_LAYOUT //#define BIDI_DEBUG #include "render_text.h" #include "render_canvas.h" #include "break_lines.h" #include "render_arena.h" #include "rendering/render_position.h" #include #include #include #include #include #include #include #include #include #include #include #if HAVE_ALLOCA_H // explicitly included for systems that don't provide it in stdlib.h or malloc.h # include #else # if HAVE_MALLOC_H # include # else # include # endif #endif using namespace khtml; using namespace DOM; #ifndef NDEBUG static bool inInlineTextBoxDetach; #endif void InlineTextBox::detach(RenderArena *renderArena, bool noRemove) { if (!noRemove) { remove(); } #ifndef NDEBUG inInlineTextBoxDetach = true; #endif delete this; #ifndef NDEBUG inInlineTextBoxDetach = false; #endif // Recover the size left there for us by operator delete and free the memory. renderArena->free(*(size_t *)this, this); } void *InlineTextBox::operator new(size_t sz, RenderArena *renderArena) throw() { return renderArena->allocate(sz); } void InlineTextBox::operator delete(void *ptr, size_t sz) { assert(inInlineTextBoxDetach); #ifdef KHTML_USE_ARENA_ALLOCATOR // Stash size where detach can find it. *(size_t *)ptr = sz; #endif } void InlineTextBox::selectionStartEnd(int &sPos, int &ePos) { int startPos, endPos; if (object()->selectionState() == RenderObject::SelectionInside) { startPos = 0; endPos = renderText()->string()->l; } else { renderText()->selectionStartEnd(startPos, endPos); if (object()->selectionState() == RenderObject::SelectionStart) { endPos = renderText()->string()->l; } else if (object()->selectionState() == RenderObject::SelectionEnd) { startPos = 0; } } sPos = qMax(startPos - m_start, 0); ePos = qMin(endPos - m_start, (int)m_len); } RenderObject::SelectionState InlineTextBox::selectionState() { RenderObject::SelectionState state = object()->selectionState(); if (state == RenderObject::SelectionStart || state == RenderObject::SelectionEnd || state == RenderObject::SelectionBoth) { int startPos, endPos; renderText()->selectionStartEnd(startPos, endPos); bool start = (state != RenderObject::SelectionEnd && startPos >= m_start && startPos < m_start + m_len); bool end = (state != RenderObject::SelectionStart && endPos > m_start && endPos <= m_start + m_len); if (start && end) { state = RenderObject::SelectionBoth; } else if (start) { state = RenderObject::SelectionStart; } else if (end) { state = RenderObject::SelectionEnd; } else if ((state == RenderObject::SelectionEnd || startPos < m_start) && (state == RenderObject::SelectionStart || endPos > m_start + m_len)) { state = RenderObject::SelectionInside; } } return state; } void InlineTextBox::paint(RenderObject::PaintInfo &i, int tx, int ty) { if (object()->isBR() || object()->style()->visibility() != VISIBLE || m_truncation == cFullTruncation || i.phase == PaintActionOutline) { return; } if (i.phase == PaintActionSelection && object()->selectionState() == RenderObject::SelectionNone) // When only painting the selection, don't bother to paint if there is none. { return; } int xPos = tx + m_x; int w = width(); if ((xPos >= i.r.x() + i.r.width()) || (xPos + w <= i.r.x())) { return; } // Set our font. RenderStyle *styleToUse = object()->style(m_firstLine); int d = styleToUse->textDecorationsInEffect(); if (styleToUse->font() != i.p->font()) { i.p->setFont(styleToUse->font()); } const Font *font = &styleToUse->htmlFont(); bool haveSelection = selectionState() != RenderObject::SelectionNone; // Now calculate startPos and endPos, for painting selection. // We paint selection while endPos > 0 int ePos = 0, sPos = 0; if (haveSelection && !object()->canvas()->staticMode()) { selectionStartEnd(sPos, ePos); } i.p->setPen(styleToUse->color()); if (m_len > 0 && i.phase != PaintActionSelection) { int endPoint = m_len; if (m_truncation != cNoTruncation) { endPoint = m_truncation - m_start; } if (styleToUse->textShadow()) { paintShadow(i.p, font, tx, ty, styleToUse->textShadow()); } if (!haveSelection || sPos != 0 || ePos != m_len) { font->drawText(i.p, m_x + tx, m_y + ty + m_baseline, renderText()->string()->s, renderText()->string()->l, m_start, endPoint, m_toAdd, m_reversed ? Qt::RightToLeft : Qt::LeftToRight); } } if (d != TDNONE && i.phase != PaintActionSelection && styleToUse->htmlHacks()) { i.p->setPen(styleToUse->color()); paintDecoration(i.p, font, tx, ty, d); } if (haveSelection && i.phase == PaintActionSelection) { //qDebug() << this << " paintSelection with startPos=" << sPos << " endPos=" << ePos; if (sPos < ePos) { paintSelection(font, renderText(), i.p, styleToUse, tx, ty, sPos, ePos, d); } } } /** returns the proper ::selection pseudo style for the given element * @return the style or 0 if no ::selection pseudo applies. */ inline const RenderStyle *retrieveSelectionPseudoStyle(const RenderObject *obj) { // http://www.w3.org/Style/CSS/Test/CSS3/Selectors/20021129/html/tests/css3-modsel-162.html // is of the opinion that ::selection of parent elements is also to be applied // to children, so let's do it. while (obj) { const RenderStyle *style = obj->style()->getPseudoStyle(RenderStyle::SELECTION); if (style) { return style; } obj = obj->parent(); }/*wend*/ return nullptr; } void InlineTextBox::paintSelection(const Font *f, RenderText *text, QPainter *p, RenderStyle *style, int tx, int ty, int startPos, int endPos, int deco) { if (startPos > m_len) { return; } if (startPos < 0) { startPos = 0; } QColor hc; QColor hbg; const RenderStyle *pseudoStyle = retrieveSelectionPseudoStyle(text); if (pseudoStyle) { // ### support outline (mandated by CSS3) // ### support background-image? (optional by CSS3) if (pseudoStyle->backgroundColor().isValid()) { hbg = pseudoStyle->backgroundColor(); } hc = pseudoStyle->color(); } else { hc = style->palette().color(QPalette::Active, QPalette::HighlightedText); hbg = style->palette().color(QPalette::Active, QPalette::Highlight); // ### should be at most retrieved once per render text QColor bg = khtml::retrieveBackgroundColor(text); // It may happen that the contrast is -- well -- virtually non existent. // In this case, simply swap the colors, thus in compliance with // NN4 (win32 only), IE, and Mozilla. if (!khtml::hasSufficientContrast(hbg, bg)) { qSwap(hc, hbg); } } p->setPen(hc); //qDebug() << "textRun::painting(" << QString::fromRawData(text->str->s + m_start, m_len).left(30) << ") at(" << m_x+tx << "/" << m_y+ty << ")"; const bool needClipping = startPos != 0 || endPos != m_len; if (needClipping) { p->save(); int visualSelectionStart = f->width(text->str->s, text->str->l, m_start, startPos, false, m_start, m_start + m_len, m_toAdd); int visualSelectionEnd = f->width(text->str->s, text->str->l, m_start, endPos, false, m_start, m_start + m_len, m_toAdd); int visualSelectionWidth = visualSelectionEnd - visualSelectionStart; if (m_reversed) { visualSelectionStart = f->width(text->str->s, text->str->l, m_start, m_len, false) - visualSelectionEnd; } QRect selectionRect(m_x + tx + visualSelectionStart, m_y + ty, visualSelectionWidth, height()); QRegion r(selectionRect); if (p->hasClipping()) { r &= p->clipRegion(); } p->setClipRegion(r, Qt::IntersectClip); } f->drawText(p, m_x + tx, m_y + ty + m_baseline, text->str->s, text->str->l, m_start, m_len, m_toAdd, m_reversed ? Qt::RightToLeft : Qt::LeftToRight, needClipping ? 0 : startPos, needClipping ? m_len : endPos, hbg, m_y + ty, height(), deco); if (needClipping) { p->restore(); } } void InlineTextBox::paintDecoration(QPainter *pt, const Font *f, int _tx, int _ty, int deco) { _tx += m_x; _ty += m_y; if (m_truncation == cFullTruncation) { return; } int width = m_width - 1; if (m_truncation != cNoTruncation) { width = static_cast(m_object)->width(m_start, m_truncation - m_start, m_firstLine); } RenderObject *p = object(); QColor underline, overline, linethrough; p->getTextDecorationColors(deco, underline, overline, linethrough, p->style()->htmlHacks()); if (deco & UNDERLINE) { pt->setPen(underline); f->drawDecoration(pt, _tx, _ty, baseline(), width, height(), Font::UNDERLINE); } if (deco & OVERLINE) { pt->setPen(overline); f->drawDecoration(pt, _tx, _ty, baseline(), width, height(), Font::OVERLINE); } if (deco & LINE_THROUGH) { pt->setPen(linethrough); f->drawDecoration(pt, _tx, _ty, baseline(), width, height(), Font::LINE_THROUGH); } // NO! Do NOT add BLINK! It is the most annouing feature of Netscape, and IE has a reason not to // support it. Lars } void InlineTextBox::paintShadow(QPainter *pt, const Font *f, int _tx, int _ty, const ShadowData *shadow) { int x = m_x + _tx + shadow->x; int y = m_y + _ty + shadow->y; const RenderText *text = renderText(); if (shadow->blur <= 0) { QColor c = pt->pen().color(); pt->setPen(shadow->color); f->drawText(pt, x, y + m_baseline, text->str->s, text->str->l, m_start, m_len, m_toAdd, m_reversed ? Qt::RightToLeft : Qt::LeftToRight); pt->setPen(c); } else { const int thickness = shadow->blur; const int w = m_width + 2 * thickness; const int h = m_height + 2 * thickness; const QRgb color = shadow->color.rgba(); const int gray = qGray(color); const bool inverse = (gray < 100); const QRgb bgColor = (inverse) ? qRgb(255, 255, 255) : qRgb(0, 0, 0); QImage img(w, h, QImage::Format_RGB32); img.fill(bgColor); QPainter p; p.begin(&img); p.setPen(shadow->color); p.setFont(pt->font()); f->drawText(&p, thickness, thickness + m_baseline, text->str->s, text->str->l, m_start, m_len, m_toAdd, m_reversed ? Qt::RightToLeft : Qt::LeftToRight); p.end(); int md = thickness * thickness; // max-dist^2 // blur map (division cache) float *bmap = (float *)alloca(sizeof(float) * (md + 1)); for (int n = 0; n <= md; n++) { float f; f = n / (float)(md + 1); f = 1.0 - f * f; bmap[n] = f; } float factor = 0.0; // maximal potential opacity-sum for (int n = -thickness; n <= thickness; n++) for (int m = -thickness; m <= thickness; m++) { int d = n * n + m * m; if (d <= md) { factor += bmap[d]; } } // arbitratry factor adjustment to make shadows solid. factor = factor / 1.333; // alpha map float *amap = (float *)alloca(sizeof(float) * (h * w)); memset(amap, 0, h * w * (sizeof(float))); for (int j = thickness; j < h - thickness; j++) { const QRgb *line = (QRgb *)img.scanLine(j); for (int i = thickness; i < w - thickness; i++) { QRgb col = line[i]; if (col == bgColor) { continue; } float g = qGray(col); if (inverse) { g = (255 - g) / (255 - gray); } else { g = g / gray; } for (int n = -thickness; n <= thickness; n++) { for (int m = -thickness; m <= thickness; m++) { int d = n * n + m * m; if (d > md) { continue; } float f = bmap[d]; amap[(i + m) + (j + n)*w] += (g * f); } } } } QImage res(w, h, QImage::Format_ARGB32); int r = qRed(color); int g = qGreen(color); int b = qBlue(color); // divide by factor factor = 1.0 / factor; for (int j = 0; j < h; j++) { QRgb *line = (QRgb *)res.scanLine(j); for (int i = 0; i < w; i++) { int a = (int)(amap[i + j * w] * factor * 255.0); if (a > 255) { a = 255; } line[i] = qRgba(r, g, b, a); } } pt->drawImage(x - thickness, y - thickness, res, 0, 0, -1, -1, Qt::DiffuseAlphaDither | Qt::ColorOnly | Qt::PreferDither); } // Paint next shadow effect if (shadow->next) { paintShadow(pt, f, _tx, _ty, shadow->next); } } /** * Distributes pixels to justify text. * @param numSpaces spaces left, will be decremented by one * @param toAdd number of pixels left to be distributed, will have the * amount of pixels distributed during this call subtracted. * @return number of pixels to distribute */ static inline int justifyWidth(int &numSpaces, int &toAdd) { int a = 0; if (numSpaces) { a = toAdd / numSpaces; toAdd -= a; numSpaces--; }/*end if*/ return a; } FindSelectionResult InlineTextBox::checkSelectionPoint(int _x, int _y, int _tx, int _ty, int &offset) { // qDebug() << "InlineTextBox::checkSelectionPoint " << this << " _x=" << _x << " _y=" << _y -// << " _tx+m_x=" << _tx+m_x << " _ty+m_y=" << _ty+m_y << endl; +// << " _tx+m_x=" << _tx+m_x << " _ty+m_y=" << _ty+m_y; offset = 0; if (_y < _ty + m_y) { return SelectionPointBefore; // above -> before } if (_y > _ty + m_y + m_height) { // below -> after // Set the offset to the max offset = m_len; return SelectionPointAfter; } if (_x > _tx + m_x + m_width) { // to the right return SelectionPointAfterInLine; } // The Y matches, check if we're on the left if (_x < _tx + m_x) { return SelectionPointBeforeInLine; } // consider spacing for justified text int toAdd = m_toAdd; RenderText *text = static_cast(object()); Q_ASSERT(text->isText()); bool justified = text->style()->textAlign() == JUSTIFY && toAdd > 0; int numSpaces = 0; if (justified) { for (int i = 0; i < m_len; i++) if (text->str->s[m_start + i].category() == QChar::Separator_Space) { numSpaces++; } }/*end if*/ int delta = _x - (_tx + m_x); //qDebug() << "InlineTextBox::checkSelectionPoint delta=" << delta; int pos = 0; const Font *f = text->htmlFont(m_firstLine); if (m_reversed) { delta -= m_width; while (pos < m_len) { int w = f->charWidth(text->str->s, text->str->l, m_start + pos, text->isSimpleText()); if (justified && text->str->s[m_start + pos].category() == QChar::Separator_Space) { w += justifyWidth(numSpaces, toAdd); } int w2 = w / 2; w -= w2; delta += w2; if (delta >= 0) { break; } pos++; delta += w; } } else { while (pos < m_len) { int w = f->charWidth(text->str->s, text->str->l, m_start + pos, text->isSimpleText()); if (justified && text->str->s[m_start + pos].category() == QChar::Separator_Space) { w += justifyWidth(numSpaces, toAdd); } int w2 = w / 2; w -= w2; delta -= w2; if (delta <= 0) { break; } pos++; delta -= w; } } // qDebug() << " Text --> inside at position " << pos; offset = pos; return SelectionPointInside; } long InlineTextBox::caretMinOffset() const { return m_start; } long InlineTextBox::caretMaxOffset() const { return m_start + m_len; } unsigned long InlineTextBox::caretMaxRenderedOffset() const { return m_start + m_len; } int InlineTextBox::offsetForPoint(int _x, int &ax) const { // Do binary search for finding out offset, saves some time for long // runs. int start = 0; int end = m_len; ax = m_x; int offset = (start + end) / 2; while (end - start > 0) { // always snap to the right column. This makes up for "jumpy" vertical // navigation. if (end - start == 1) { start = end; } offset = (start + end) / 2; ax = m_x + widthFromStart(offset); if (ax > _x) { end = offset; } else if (ax < _x) { start = offset; } else { break; } } return m_start + offset; } int InlineTextBox::widthFromStart(int pos) const { // gasp! sometimes pos is i < 0 which crashes Font::width - // qDebug() << this << pos << endl; + // qDebug() << this << pos; pos = qMax(pos, 0); const RenderText *t = renderText(); Q_ASSERT(t->isText()); const Font *f = t->htmlFont(m_firstLine); const QFontMetrics &fm = t->fontMetrics(m_firstLine); int numSpaces = 0; // consider spacing for justified text bool justified = t->style()->textAlign() == JUSTIFY; //qDebug() << "InlineTextBox::width(int)"; if (justified && m_toAdd > 0) do { //qDebug() << "justify"; // const QString cstr = QString::fromRawData(t->str->s + m_start, m_len); for (int i = 0; i < m_len; i++) if (t->str->s[m_start + i].category() == QChar::Separator_Space) { numSpaces++; } if (numSpaces == 0) { break; } int toAdd = m_toAdd; int w = 0; // accumulated width int start = 0; // start of non-space sequence int current = 0; // current position while (current < pos) { // add spacing while (current < pos && t->str->s[m_start + current].category() == QChar::Separator_Space) { w += f->getWordSpacing(); w += f->getLetterSpacing(); w += justifyWidth(numSpaces, toAdd); w += fm.width(' '); // ### valid assumption? (LS) current++; start++; }/*wend*/ if (current >= pos) { break; } // seek next space while (current < pos && t->str->s[m_start + current].category() != QChar::Separator_Space) { current++; } // check run without spaces if (current > start) { w += f->width(t->str->s + m_start, m_len, start, current - start, false); start = current; } } return w; } while (false); /*end if*/ //qDebug() << "default"; // else use existing width function - // qDebug() << "result width:" << f->width(t->str->s + m_start, m_len, 0, pos, false) << endl; + // qDebug() << "result width:" << f->width(t->str->s + m_start, m_len, 0, pos, false); return f->width(t->str->s + m_start, m_len, 0, pos, false); } void InlineTextBox::deleteLine(RenderArena *arena) { static_cast(m_object)->removeTextBox(this); detach(arena, true /*noRemove*/); } void InlineTextBox::extractLine() { if (m_extracted) { return; } static_cast(m_object)->extractTextBox(this); } void InlineTextBox::attachLine() { if (!m_extracted) { return; } static_cast(m_object)->attachTextBox(this); } int InlineTextBox::placeEllipsisBox(bool ltr, int blockEdge, int ellipsisWidth, bool &foundBox) { if (foundBox) { m_truncation = cFullTruncation; return -1; } int ellipsisX = ltr ? blockEdge - ellipsisWidth : blockEdge + ellipsisWidth; // For LTR, if the left edge of the ellipsis is to the left of our text run, then we are the run that will get truncated. if (ltr) { if (ellipsisX <= m_x) { // Too far. Just set full truncation, but return -1 and let the ellipsis just be placed at the edge of the box. m_truncation = cFullTruncation; foundBox = true; return -1; } if (ellipsisX < m_x + m_width) { if (m_reversed) { return -1; // FIXME: Support LTR truncation when the last run is RTL someday. } foundBox = true; int ax; int offset = offsetForPoint(ellipsisX, ax) - 1; if (offset <= m_start) { // No characters should be rendered. Set ourselves to full truncation and place the ellipsis at the min of our start // and the ellipsis edge. m_truncation = cFullTruncation; return qMin(ellipsisX, (int)m_x); } // Set the truncation index on the text run. The ellipsis needs to be placed just after the last visible character. m_truncation = offset; return widthFromStart(offset - m_start); } } else { // FIXME: Support RTL truncation someday, including both modes (when the leftmost run on the line is either RTL or LTR) } return -1; } // ----------------------------------------------------------------------------- RenderText::RenderText(DOM::NodeImpl *node, DOMStringImpl *_str) : RenderObject(node) { // init RenderObject attributes setRenderText(); // our object inherits from RenderText m_minWidth = -1; m_maxWidth = -1; str = _str; if (str) { str->ref(); } KHTMLAssert(!str || !str->l || str->s); m_selectionState = SelectionNone; m_hasReturn = true; m_isSimpleText = false; m_firstTextBox = m_lastTextBox = nullptr; #ifdef DEBUG_LAYOUT const QString cstr = QString::fromRawData(str->s, str->l); qDebug() << "RenderText ctr( " << cstr.length() << " ) '" << cstr << "'"; #endif } void RenderText::setStyle(RenderStyle *_style) { if (style() != _style) { bool changedText = ((!style() && (_style->textTransform() != TTNONE || !_style->preserveLF() || !_style->preserveWS())) || (style() && (style()->textTransform() != _style->textTransform() || style()->whiteSpace() != _style->whiteSpace()))); RenderObject::setStyle(_style); m_lineHeight = RenderObject::lineHeight(false); if (!isBR() && changedText) { DOM::DOMStringImpl *textToTransform = originalString(); if (textToTransform) { setText(textToTransform, true); } } } } RenderText::~RenderText() { if (str) { str->deref(); } assert(!m_firstTextBox); assert(!m_lastTextBox); } void RenderText::detach() { if (!documentBeingDestroyed()) { if (firstTextBox()) { if (isBR()) { RootInlineBox *next = firstTextBox()->root()->nextRootBox(); if (next) { next->markDirty(); } } for (InlineTextBox *box = firstTextBox(); box; box = box->nextTextBox()) { box->remove(); } } else if (parent()) { parent()->dirtyLinesFromChangedChild(this); } } deleteInlineBoxes(); RenderObject::detach(); } void RenderText::extractTextBox(InlineTextBox *box) { m_lastTextBox = box->prevTextBox(); if (box == m_firstTextBox) { m_firstTextBox = nullptr; } if (box->prevTextBox()) { box->prevTextBox()->setNextLineBox(nullptr); } box->setPreviousLineBox(nullptr); for (InlineRunBox *curr = box; curr; curr = curr->nextLineBox()) { curr->setExtracted(); } } void RenderText::attachTextBox(InlineTextBox *box) { if (m_lastTextBox) { m_lastTextBox->setNextLineBox(box); box->setPreviousLineBox(m_lastTextBox); } else { m_firstTextBox = box; } InlineTextBox *last = box; for (InlineTextBox *curr = box; curr; curr = curr->nextTextBox()) { curr->setExtracted(false); last = curr; } m_lastTextBox = last; } void RenderText::removeTextBox(InlineTextBox *box) { if (box == m_firstTextBox) { m_firstTextBox = box->nextTextBox(); } if (box == m_lastTextBox) { m_lastTextBox = box->prevTextBox(); } if (box->nextTextBox()) { box->nextTextBox()->setPreviousLineBox(box->prevTextBox()); } if (box->prevTextBox()) { box->prevTextBox()->setNextLineBox(box->nextTextBox()); } } void RenderText::removeInlineBox(InlineBox *_box) { KHTMLAssert(_box->isInlineTextBox()); removeTextBox(static_cast(_box)); } void RenderText::deleteInlineBoxes(RenderArena * /*arena*/) { if (firstTextBox()) { RenderArena *arena = renderArena(); InlineTextBox *next; for (InlineTextBox *curr = firstTextBox(); curr; curr = next) { next = curr->nextTextBox(); curr->detach(arena, true /*noRemove*/); } m_firstTextBox = m_lastTextBox = nullptr; } } void RenderText::dirtyInlineBoxes(bool fullLayout, bool) { if (fullLayout) { deleteInlineBoxes(); } else { for (InlineTextBox *box = firstTextBox(); box; box = box->nextTextBox()) { box->dirtyInlineBoxes(); } } } bool RenderText::isTextFragment() const { return false; } DOM::DOMStringImpl *RenderText::originalString() const { return element() ? element()->string() : nullptr; } const InlineTextBox *RenderText::findInlineTextBox(int offset, int &pos, bool checkFirstLetter) const { Q_UNUSED(checkFirstLetter); // The text boxes point to parts of the rendertext's str string // (they don't include '\n') // Find the text box that includes the character at @p offset // and return pos, which is the position of the char in the run. if (!m_firstTextBox) { return nullptr; } InlineTextBox *s = m_firstTextBox; int off = s->m_len; while (offset > off && s->nextTextBox()) { s = s->nextTextBox(); off = s->m_start + s->m_len; } // we are now in the correct text run if (offset >= s->m_start && offset < s->m_start + s->m_len) { pos = offset - s->m_start; } else { pos = (offset > off ? s->m_len : s->m_len - (off - offset)); } return s; } bool RenderText::nodeAtPoint(NodeInfo &info, int _x, int _y, int _tx, int _ty, HitTestAction /*hitTestAction*/, bool /*inBox*/) { assert(parent()); bool inside = false; if (style()->visibility() != HIDDEN) { for (InlineTextBox *s = firstTextBox(); s; s = s->nextTextBox()) { if ((_y >= _ty + s->m_y) && (_y < _ty + s->m_y + s->m_height) && (_x >= _tx + s->m_x) && (_x < _tx + s->m_x + s->m_width)) { inside = true; break; } } } // #### ported over from Safari. Can this happen at all? (lars) if (inside && element()) { if (info.innerNode() && info.innerNode()->renderer() && !info.innerNode()->renderer()->isInline()) { // Within the same layer, inlines are ALWAYS fully above blocks. Change inner node. info.setInnerNode(element()); // Clear everything else. info.setInnerNonSharedNode(nullptr); info.setURLElement(nullptr); } if (!info.innerNode()) { info.setInnerNode(element()); } if (!info.innerNonSharedNode()) { info.setInnerNonSharedNode(element()); } } return inside; } FindSelectionResult RenderText::checkSelectionPoint(int _x, int _y, int _tx, int _ty, DOM::NodeImpl *&node, int &offset, SelPointState &) { // qDebug() << "RenderText::checkSelectionPoint " << this << " _x=" << _x << " _y=" << _y -// << " _tx=" << _tx << " _ty=" << _ty << endl; +// << " _tx=" << _tx << " _ty=" << _ty; //qDebug() << renderName() << "::checkSelectionPoint x=" << xPos() << " y=" << yPos() << " w=" << width() << " h=" << height(); NodeImpl *lastNode = nullptr; int lastOffset = 0; FindSelectionResult lastResult = SelectionPointAfter; for (InlineTextBox *s = firstTextBox(); s; s = s->nextTextBox()) { FindSelectionResult result; // #### ? // result = s->checkSelectionPoint(_x, _y, _tx, _ty, offset); if (_y < _ty + s->m_y) { result = SelectionPointBefore; } else if (_y >= _ty + s->m_y + s->height()) { result = SelectionPointAfterInLine; } else if (_x < _tx + s->m_x) { result = SelectionPointBeforeInLine; } else if (_x >= _tx + s->m_x + s->width()) { result = SelectionPointAfterInLine; } else { int dummy; result = SelectionPointInside; // offsetForPoint shifts to the right: correct it offset = s->offsetForPoint(_x - _tx, dummy) - 1; } // qDebug() << "RenderText::checkSelectionPoint " << this << " line " << si << " result=" << result << " offset=" << offset; if (result == SelectionPointInside) { // x,y is inside the textrun // offset += s->m_start; // add the offset from the previous lines // qDebug() << "RenderText::checkSelectionPoint inside -> " << offset; node = element(); return SelectionPointInside; } else if (result == SelectionPointBefore) { if (!lastNode) { // x,y is before the textrun -> stop here offset = 0; // qDebug() << "RenderText::checkSelectionPoint " << this << "before us -> returning Before"; node = element(); return SelectionPointBefore; } } else if (result == SelectionPointBeforeInLine) { offset = s->m_start; node = element(); return SelectionPointInside; } else if (result == SelectionPointAfterInLine) { lastOffset = s->m_start + s->m_len; lastNode = element(); lastResult = result; // no return here } } if (lastNode) { offset = lastOffset; node = lastNode; // qDebug() << "RenderText::checkSelectionPoint: lastNode " << lastNode << " lastOffset " << lastOffset; return lastResult; } // set offset to max offset = str->l; //qDebug("setting node to %p", element()); node = element(); // qDebug() << "RenderText::checkSelectionPoint: node " << node << " offset " << offset; return SelectionPointAfter; } unsigned RenderText::convertToDOMPosition(unsigned position) const { if (isBR()) { return 0; } /*const */DOMStringImpl *domString = originalString(); /*const */DOMStringImpl *renderedString = string(); if (domString == renderedString) { - // qDebug() << "[rendered == dom]" << position << endl; + // qDebug() << "[rendered == dom]" << position; return position; } /* // qDebug() << "[convert]" << position << endl << DOMString(domString) << endl - << DOMString(renderedString) << endl;*/ + << DOMString(renderedString);*/ if (!domString || !renderedString) { return position; } unsigned domLength = domString->length(); unsigned i = 0, j = 0; for (; i < domLength && j < position;) { bool isRenderedSpace = renderedString->unicode()[j].isSpace(); bool isDOMSpace = domString->unicode()[i].isSpace(); if (isRenderedSpace && isDOMSpace) { ++i; ++j; continue; } if (isRenderedSpace) { ++j; continue; } if (isDOMSpace) { ++i; continue; } ++i; ++j; } - // qDebug() << "[result]" << i << endl; + // qDebug() << "[result]" << i; return i; } unsigned RenderText::convertToRenderedPosition(unsigned position) const { if (isBR()) { return 0; } /*const */DOMStringImpl *domString = originalString(); /*const */DOMStringImpl *renderedString = string(); if (domString == renderedString) { - // qDebug() << "[rendered == dom]" << position << endl; + // qDebug() << "[rendered == dom]" << position; return position; } /* // qDebug() << "[convert]" << position << endl << DOMString(domString) << endl - << DOMString(renderedString) << endl;*/ + << DOMString(renderedString);*/ if (!domString || !renderedString) { return position; } unsigned renderedLength = renderedString->length(); unsigned i = 0, j = 0; for (; i < position && j < renderedLength;) { bool isRenderedSpace = renderedString->unicode()[j].isSpace(); bool isDOMSpace = domString->unicode()[i].isSpace(); if (isRenderedSpace && isDOMSpace) { ++i; ++j; continue; } if (isRenderedSpace) { ++j; continue; } if (isDOMSpace) { ++i; continue; } ++i; ++j; } - // qDebug() << "[result]" << j << endl; + // qDebug() << "[result]" << j; return j; } RenderPosition RenderText::positionForCoordinates(int _x, int _y) { - // qDebug() << this << _x << _y << endl; + // qDebug() << this << _x << _y; if (!firstTextBox() || stringLength() == 0) { return Position(element(), 0); } int absx, absy; containingBlock()->absolutePosition(absx, absy); - // qDebug() << "absolute(" << absx << absy << ")" << endl; + // qDebug() << "absolute(" << absx << absy << ")"; if (_y < absy + firstTextBox()->root()->bottomOverflow() && _x < absx + firstTextBox()->m_x) { // at the y coordinate of the first line or above // and the x coordinate is to the left than the first text box left edge return RenderPosition(element(), firstTextBox()->m_start); } if (_y >= absy + lastTextBox()->root()->topOverflow() && _x >= absx + lastTextBox()->m_x + lastTextBox()->m_width) { // at the y coordinate of the last line or below // and the x coordinate is to the right than the last text box right edge return RenderPosition(element(), lastTextBox()->m_start + lastTextBox()->m_len); } for (InlineTextBox *box = firstTextBox(); box; box = box->nextTextBox()) { - // qDebug() << "[check box]" << box << endl; + // qDebug() << "[check box]" << box; if (_y >= absy + box->root()->topOverflow() && _y < absy + box->root()->bottomOverflow()) { if (_x < absx + box->m_x + box->m_width) { // and the x coordinate is to the left of the right edge of this box // check to see if position goes in this box int offset; box->checkSelectionPoint(_x, absy + box->yPos(), absx, absy, offset); - // qDebug() << "offset" << offset << endl; + // qDebug() << "offset" << offset; if (offset != -1) { - // qDebug() << "return" << Position(element(), convertToDOMPosition(offset + box->m_start)) << endl; + // qDebug() << "return" << Position(element(), convertToDOMPosition(offset + box->m_start)); return RenderPosition(element(), offset + box->m_start); } } else if (!box->prevOnLine() && _x < absx + box->m_x) // box is first on line // and the x coordinate is to the left than the first text box left edge { return RenderPosition(element(), box->m_start); } else if (!box->nextOnLine() && _x >= absx + box->m_x + box->m_width) // box is last on line // and the x coordinate is to the right than the last text box right edge { return RenderPosition(element(), box->m_start + box->m_len); } } } return RenderPosition(element(), 0); } void RenderText::caretPos(int offset, int flags, int &_x, int &_y, int &width, int &height) const { - // qDebug() << offset << flags << endl; + // qDebug() << offset << flags; if (!m_firstTextBox) { _x = _y = height = -1; width = 1; return; } int pos; const InlineTextBox *s = findInlineTextBox(offset, pos, true); const RenderText *t = s->renderText(); // qDebug() << "offset="<s[offset]) : 1; - // qDebug() << "CFOverride" << width << endl; + // qDebug() << "CFOverride" << width; }/*end if*/ #if 0 // qDebug() << "_x="<<_x << " s->m_x="<m_x << " s->m_start" << s->m_start - << " s->m_len" << s->m_len << " _y=" << _y << endl; + << " s->m_len" << s->m_len << " _y=" << _y; #endif int absx, absy; if (absolutePosition(absx, absy)) { //qDebug() << "absx=" << absx << " absy=" << absy; _x += absx; _y += absy; } else { // we don't know our absolute position, and there is no point returning // just a relative one _x = _y = -1; } } long RenderText::caretMinOffset() const { if (!m_firstTextBox) { return 0; } // FIXME: it is *not* guaranteed that the first run contains the lowest offset // Either make this a linear search (slow), // or maintain an index (needs much mem), // or calculate and store it in bidi.cpp (needs calculation even if not needed) // (LS) return m_firstTextBox->m_start; } long RenderText::caretMaxOffset() const { InlineTextBox *box = m_lastTextBox; if (!box) { return str->l; } int maxOffset = box->m_start + box->m_len; // ### slow for (box = box->prevTextBox(); box; box = box->prevTextBox()) { maxOffset = qMax(maxOffset, box->m_start + box->m_len); } return maxOffset; } unsigned long RenderText::caretMaxRenderedOffset() const { int l = 0; // ### no guarantee that the order is ascending for (InlineTextBox *box = firstTextBox(); box; box = box->nextTextBox()) { l += box->m_len; } return l; } InlineBox *RenderText::inlineBox(long offset) { // ### make educated guess about most likely position for (InlineTextBox *box = firstTextBox(); box; box = box->nextTextBox()) { if (offset >= box->m_start && offset <= box->m_start + box->m_len) { return box; } else if (offset < box->m_start) { // The offset we're looking for is before this node // this means the offset must be in content that is // not rendered. return box->prevTextBox() ? box->prevTextBox() : firstTextBox(); } } return nullptr; } bool RenderText::absolutePosition(int &xPos, int &yPos, bool) const { return RenderObject::absolutePosition(xPos, yPos, false); } bool RenderText::posOfChar(int chr, int &x, int &y) const { if (!parent()) { return false; } parent()->absolutePosition(x, y, false); int pos; const InlineTextBox *s = findInlineTextBox(chr, pos); if (s) { // s is the line containing the character x += s->m_x; // this is the x of the beginning of the line, but it's good enough for now y += s->m_y; return true; } return false; } static bool isSimpleChar(const unsigned short c) { // Exclude ranges with many Mn/Me/Mc and the various combining diacriticals ranges. // Unicode version used is 4.1.0 // General Combining Diacritical Marks if (c < 0x300) { return true; } if (c <= 0x36F) { return false; } // Cyrillic's if (c < 0x483) { return true; } if (c <= 0x489) { return false; } // Hebrew's if (c < 0x0591) { return true; } if (c <= 0x05C7 && !(c == 0x05BE || c == 0x05C0 || c == 0x05C3 || c == 0x05C6)) { return false; } // Unicode range 6 to 11 (Arabic to Korean Hangul) if (c < 0x0600) { return true; } if (c <= 0x11F9) { return false; } // Unicode range 17 to 1A (Tagalog to Buginese) // (also excl. Ethiopic Combining Gemination Mark) if (c < 0x1700 && c != 0x135F) { return true; } if (c <= 0x1A1F) { return false; } // Combining Diacritical Marks Supplement if (c < 0x1DC0) { return true; } if (c <= 0x1DFF) { return false; } // Diacritical Marks for Symbols if (c < 0x20D0) { return true; } if (c <= 0x20EB) { return false; } // Combining Half Marks if (c < 0xFE20) { return true; } if (c <= 0xFE2F) { return false; } return true; } void RenderText::calcMinMaxWidth() { KHTMLAssert(!minMaxKnown()); // ### calc Min and Max width... m_minWidth = m_beginMinWidth = m_endMinWidth = 0; m_maxWidth = 0; if (isBR()) { return; } int currMinWidth = 0; int currMaxWidth = 0; m_isSimpleText = true; m_hasBreakableChar = m_hasBreak = m_hasBeginWS = m_hasEndWS = false; // ### not 100% correct for first-line const Font *f = htmlFont(false); int wordSpacing = style()->wordSpacing(); int len = str->l; bool isSpace = false; bool firstWord = true; bool firstLine = true; for (int i = 0; i < len; i++) { unsigned short c = str->s[i].unicode(); bool isNewline = false; // If line-breaks survive to here they are preserved if (c == '\n') { if (style()->preserveLF()) { m_hasBreak = true; isNewline = true; isSpace = false; } else { isSpace = true; } } else { isSpace = c == ' '; } if ((isSpace || isNewline) && i == 0) { m_hasBeginWS = true; } if ((isSpace || isNewline) && i == len - 1) { m_hasEndWS = true; } if (i && c == SOFT_HYPHEN) { continue; } int wordlen = 0; while (i + wordlen < len && (i + wordlen == 0 || str->s[i + wordlen].unicode() != SOFT_HYPHEN) && !(isBreakable(str->s, i + wordlen, str->l))) { // check if we may use the simpler algorithm for estimating text width m_isSimpleText = (m_isSimpleText && isSimpleChar(str->s[i + wordlen].unicode())); wordlen++; } if (wordlen) { int w = f->width(str->s, str->l, i, wordlen, m_isSimpleText); currMinWidth += w; currMaxWidth += w; // Add in wordspacing to our maxwidth, but not if this is the last word. if (wordSpacing && !containsOnlyWhitespace(i + wordlen, len - (i + wordlen))) { currMaxWidth += wordSpacing; } if (firstWord) { firstWord = false; m_beginMinWidth = w; } m_endMinWidth = w; if (currMinWidth > m_minWidth) { m_minWidth = currMinWidth; } currMinWidth = 0; i += wordlen - 1; } else { // Nowrap can never be broken, so don't bother setting the // breakable character boolean. Pre can only be broken if we encounter a newline. if (style()->autoWrap() || isNewline) { m_hasBreakableChar = true; } if (currMinWidth > m_minWidth) { m_minWidth = currMinWidth; } currMinWidth = 0; if (isNewline) { // Only set if isPre was true and we saw a newline. if (firstLine) { firstLine = false; if (!style()->autoWrap()) { m_beginMinWidth = currMaxWidth; } } if (currMaxWidth > m_maxWidth) { m_maxWidth = currMaxWidth; } currMaxWidth = 0; } else { currMaxWidth += f->charWidth(str->s, str->l, i + wordlen, m_isSimpleText); } } } if (currMinWidth > m_minWidth) { m_minWidth = currMinWidth; } if (currMaxWidth > m_maxWidth) { m_maxWidth = currMaxWidth; } if (!style()->autoWrap()) { m_minWidth = m_maxWidth; if (style()->preserveLF()) { if (firstLine) { m_beginMinWidth = m_maxWidth; } m_endMinWidth = currMaxWidth; } } setMinMaxKnown(); //qDebug() << "Text::calcMinMaxWidth(): min = " << m_minWidth << " max = " << m_maxWidth; } int RenderText::minXPos() const { if (!m_firstTextBox) { return 0; } int retval = 6666666; for (InlineTextBox *box = firstTextBox(); box; box = box->nextTextBox()) { retval = qMin(retval, static_cast(box->m_x)); } return retval; } int RenderText::inlineXPos() const { return minXPos(); } int RenderText::inlineYPos() const { return m_firstTextBox ? m_firstTextBox->yPos() : 0; } const QFont &RenderText::font() { return style()->font(); } void RenderText::setText(DOMStringImpl *text, bool force) { if (!force && str == text) { return; } setTextInternal(text); } void RenderText::setTextInternal(DOMStringImpl *text) { DOMStringImpl *oldstr = str; if (text && style()) { str = text->collapseWhiteSpace(style()->preserveLF(), style()->preserveWS()); } else { str = text; } if (str) { str->ref(); } if (oldstr) { oldstr->deref(); } if (str && style()) { oldstr = str; switch (style()->textTransform()) { case CAPITALIZE: { RenderObject *o; bool runOnString = false; // find previous non-empty text renderer if one exists for (o = previousRenderer(); o; o = o->previousRenderer()) { if (!o->isInlineFlow()) { if (!o->isText()) { break; } DOMStringImpl *prevStr = static_cast(o)->string(); // !prevStr can happen with css like "content:open-quote;" if (!prevStr) { break; } if (prevStr->length() == 0) { continue; } QChar c = (*prevStr)[prevStr->length() - 1]; if (!c.isSpace()) { runOnString = true; } break; } } str = str->capitalize(runOnString); break; } case UPPERCASE: str = str->upper(); break; case LOWERCASE: str = str->lower(); break; case TTNONE: default: break; } str->ref(); oldstr->deref(); } // ### what should happen if we change the text of a // RenderBR object ? KHTMLAssert(!isBR() || (str->l == 1 && (*str->s) == '\n')); KHTMLAssert(!str->l || str->s); if (parent()) { setNeedsLayoutAndMinMaxRecalc(); } #ifdef BIDI_DEBUG QString cstr = QString::fromRawData(str->s, str->l); qDebug() << "RenderText::setText( " << cstr.length() << " ) '" << cstr << "'"; #endif } int RenderText::height() const { int retval = 0; if (firstTextBox()) #ifdef I_LOVE_UPDATING_TESTREGRESSIONS_BASELINES retval = lastTextBox()->m_y + lastTextBox()->height() - firstTextBox()->m_y; #else retval = lastTextBox()->m_y + m_lineHeight - firstTextBox()->m_y; else { retval = metrics(false).height(); } #endif return retval; } short RenderText::lineHeight(bool firstLine) const { if (firstLine) { return RenderObject::lineHeight(firstLine); } return m_lineHeight; } short RenderText::baselinePosition(bool firstLine) const { const QFontMetrics &fm = metrics(firstLine); return fm.ascent() + (lineHeight(firstLine) - fm.height()) / 2; } InlineBox *RenderText::createInlineBox(bool, bool isRootLineBox) { KHTMLAssert(!isRootLineBox); Q_UNUSED(isRootLineBox); InlineTextBox *textBox = new(renderArena()) InlineTextBox(this); if (!m_firstTextBox) { m_firstTextBox = m_lastTextBox = textBox; } else { m_lastTextBox->setNextLineBox(textBox); textBox->setPreviousLineBox(m_lastTextBox); m_lastTextBox = textBox; } return textBox; } void RenderText::position(InlineBox *box, int from, int len, bool reverse) { //qDebug() << "position: from="<= 0x20 && u < 0x7F) { result += c; } else { QString hex; hex.sprintf("\\x{%X}", u); result += hex; } } } result += '"'; return result; } static void writeTextRun(QTextStream &ts, const RenderText &o, const InlineTextBox &run) { ts << "text run at (" << run.m_x << "," << run.m_y << ") width " << run.m_width << ": " << quoteAndEscapeNonPrintables(o.data().string().mid(run.m_start, run.m_len)); } void RenderText::dump(QTextStream &stream, const QString &ind) const { RenderObject::dump(stream, ind); for (InlineTextBox *box = firstTextBox(); box; box = box->nextTextBox()) { stream << endl << ind << " "; writeTextRun(stream, *this, *box); } } #endif RenderTextFragment::RenderTextFragment(DOM::NodeImpl *_node, DOM::DOMStringImpl *_str, int startOffset, int endOffset) : RenderText(_node, _str->substring(startOffset, endOffset)), m_start(startOffset), m_end(endOffset), m_generatedContentStr(nullptr), m_firstLetter(nullptr) {} RenderTextFragment::RenderTextFragment(DOM::NodeImpl *_node, DOM::DOMStringImpl *_str) : RenderText(_node, _str), m_start(0), m_firstLetter(nullptr) { m_generatedContentStr = _str; if (_str) { _str->ref(); m_end = _str->l; } else { m_end = 0; } } RenderTextFragment::~RenderTextFragment() { if (m_generatedContentStr) { m_generatedContentStr->deref(); } } void RenderTextFragment::detach() { if (m_firstLetter) { m_firstLetter->detach(); } RenderText::detach(); } bool RenderTextFragment::isTextFragment() const { return true; } DOM::DOMStringImpl *RenderTextFragment::originalString() const { DOM::DOMStringImpl *result = nullptr; if (element()) { result = element()->string(); } else { result = contentString(); } if (result && (start() > 0 || start() < result->l)) { result = result->substring(start(), end()); } return result; } void RenderTextFragment::setTextInternal(DOM::DOMStringImpl *text) { if (m_firstLetter) { m_firstLetter->detach(); m_firstLetter = nullptr; } RenderText::setTextInternal(text); } #undef BIDI_DEBUG #undef DEBUG_LAYOUT diff --git a/src/svg/SVGDocumentExtensions.cpp b/src/svg/SVGDocumentExtensions.cpp index 494008b..d9b34f1 100644 --- a/src/svg/SVGDocumentExtensions.cpp +++ b/src/svg/SVGDocumentExtensions.cpp @@ -1,201 +1,201 @@ /* Copyright (C) 2006 Apple Computer, Inc. 2006 Nikolas Zimmermann 2007 Rob Buis This file is part of the WebKit project This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #if ENABLE(SVG) #include "SVGDocumentExtensions.h" /*#include "AtomicString.h" #include "Console.h" #include "DOMWindow.h"*/ //#include "Document.h" #include "xml/Document.h" //#include "EventListener.h" #include "dom/dom2_events.h" /*#include "Frame.h" #include "FrameLoader.h" #include "Page.h"*/ #include "SVGSVGElement.h" /*#include "SMILTimeContainer.h" #include "XMLTokenizer.h"*/ #include "kjs_proxy.h" #include "khtml_part.h" namespace WebCore { SVGDocumentExtensions::SVGDocumentExtensions(Document *doc) : m_doc(doc) { } SVGDocumentExtensions::~SVGDocumentExtensions() { /*deleteAllValues(m_pendingResources);*/ deleteAllValues(m_elementInstances); } EventListener *SVGDocumentExtensions::createSVGEventListener(const DOMString &functionName, const DOMString &code, DOM::NodeImpl *node) { /*if (Frame* frame = m_doc->frame()) if (frame->scriptProxy()->isEnabled()) return frame->scriptProxy()->createSVGEventHandler(functionName, code, node);*/ if (!m_doc || !m_doc->part()) { return nullptr; } - // qDebug() << "create listener: (" << code << functionName << node << ")" << endl; + // qDebug() << "create listener: (" << code << functionName << node << ")"; return m_doc->part()->createHTMLEventListener(code.string(), functionName.string(), node, true/*svg*/); } void SVGDocumentExtensions::addTimeContainer(SVGSVGElement *element) { Q_UNUSED(element); /*m_timeContainers.add(element);*/ } void SVGDocumentExtensions::removeTimeContainer(SVGSVGElement *element) { Q_UNUSED(element); /*m_timeContainers.remove(element);*/ } void SVGDocumentExtensions::startAnimations() { // FIXME: Eventually every "Time Container" will need a way to latch on to some global timer // starting animations for a document will do this "latching" #if ENABLE(SVG_ANIMATION) HashSet::iterator end = m_timeContainers.end(); for (HashSet::iterator itr = m_timeContainers.begin(); itr != end; ++itr) { (*itr)->timeContainer()->begin(); } #endif } void SVGDocumentExtensions::pauseAnimations() { HashSet::iterator end = m_timeContainers.end(); for (HashSet::iterator itr = m_timeContainers.begin(); itr != end; ++itr) { (*itr)->pauseAnimations(); } } void SVGDocumentExtensions::unpauseAnimations() { HashSet::iterator end = m_timeContainers.end(); for (HashSet::iterator itr = m_timeContainers.begin(); itr != end; ++itr) { (*itr)->unpauseAnimations(); } } void SVGDocumentExtensions::reportWarning(const String &message) { Q_UNUSED(message); /*if (Frame* frame = m_doc->frame()) frame->domWindow()->console()->addMessage(JSMessageSource, ErrorMessageLevel, "Warning: " + message, m_doc->tokenizer() ? m_doc->tokenizer()->lineNumber() : 1, String());*/ } void SVGDocumentExtensions::reportError(const String &message) { Q_UNUSED(message); /*if (Frame* frame = m_doc->frame()) frame->domWindow()->console()->addMessage(JSMessageSource, ErrorMessageLevel, "Error: " + message, m_doc->tokenizer() ? m_doc->tokenizer()->lineNumber() : 1, String());*/ } void SVGDocumentExtensions::addPendingResource(const AtomicString &id, SVGStyledElement *obj) { ASSERT(obj); Q_UNUSED(obj); if (id.isEmpty()) { return; } /*if (m_pendingResources.contains(id)) m_pendingResources.get(id)->add(obj); else { HashSet* set = new HashSet(); set->add(obj); m_pendingResources.add(id, set); }*/ } bool SVGDocumentExtensions::isPendingResource(const AtomicString &id) const { Q_UNUSED(id); /*if (id.isEmpty()) return false; return m_pendingResources.contains(id);*/ ASSERT(false); return false; } std::unique_ptr > SVGDocumentExtensions::removePendingResource(const AtomicString &id) { Q_UNUSED(id); /*ASSERT(m_pendingResources.contains(id)); std::unique_ptr > set(m_pendingResources.get(id)); m_pendingResources.remove(id); return set;*/ ASSERT(false); return std::unique_ptr >(); } void SVGDocumentExtensions::mapInstanceToElement(SVGElementInstance *instance, SVGElement *element) { ASSERT(instance); ASSERT(element); if (m_elementInstances.contains(element)) { m_elementInstances.get(element)->add(instance); } else { HashSet *set = new HashSet(); set->add(instance); m_elementInstances.add(element, set); } } void SVGDocumentExtensions::removeInstanceMapping(SVGElementInstance *instance, SVGElement *element) { ASSERT(instance); if (!m_elementInstances.contains(element)) { return; } m_elementInstances.get(element)->remove(instance); } HashSet *SVGDocumentExtensions::instancesForElement(SVGElement *element) const { ASSERT(element); return m_elementInstances.get(element); } } #endif diff --git a/src/svg/SVGElement.cpp b/src/svg/SVGElement.cpp index 4636e34..3965cdb 100644 --- a/src/svg/SVGElement.cpp +++ b/src/svg/SVGElement.cpp @@ -1,327 +1,327 @@ /* Copyright (C) 2004, 2005, 2006, 2007 Nikolas Zimmermann 2004, 2005, 2006, 2008 Rob Buis This file is part of the KDE project This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "wtf/Platform.h" #if ENABLE(SVG) #include "SVGElement.h" /*#include "DOMImplementation.h"*/ #include "Document.h" /*#include "Event.h" #include "EventListener.h" #include "EventNames.h" #include "FrameView.h" #include "HTMLNames.h" #include "PlatformString.h" #include "RenderObject.h" #include "SVGDocumentExtensions.h" #include "SVGElementInstance.h"*/ #include "SVGNames.h" #include "SVGSVGElement.h" /*#include "SVGURIReference.h" #include "SVGUseElement.h" #include "RegisteredEventListener.h"*/ namespace WebCore { using namespace DOM; //using namespace HTMLNames; //using namespace EventNames; SVGElement::SVGElement(const QualifiedName &tagName, Document *doc) : StyledElement(doc) // it's wrong, remove it!!! FIXME: vtokarev // : StyledElement(tagName, doc) // , m_shadowParent(0) { m_prefix = tagName.prefixId(); } SVGElement::~SVGElement() { } bool SVGElement::isSupported(StringImpl *feature, StringImpl *version) const { return DOMImplementation::hasFeature(feature, version); } String SVGElement::attrid() const { return getAttribute(idAttr); } void SVGElement::setId(const String &value, ExceptionCode &) { setAttribute(idAttr, value); } String SVGElement::xmlbase() const { return getAttribute(ATTR_XML_BASE); } void SVGElement::setXmlbase(const String &value, ExceptionCode &) { setAttribute(ATTR_XML_BASE, value); } SVGSVGElement *SVGElement::ownerSVGElement() const { Node *n = isShadowNode() ? const_cast(this)->shadowParentNode() : parentNode(); while (n) { if (/*n->hasTagName(SVGNames::svgTag)*/n->id() == SVGNames::svgTag.id()) { return static_cast(n); } n = n->isShadowNode() ? n->shadowParentNode() : n->parentNode(); } return nullptr; } SVGElement *SVGElement::viewportElement() const { // This function needs shadow tree support - as RenderSVGContainer uses this function // to determine the "overflow" property. on wouldn't work otherwhise. /*Node* n = isShadowNode() ? const_cast(this)->shadowParentNode() : parentNode(); while (n) { if (n->hasTagName(SVGNames::svgTag) || n->hasTagName(SVGNames::imageTag) || n->hasTagName(SVGNames::symbolTag)) return static_cast(n); n = n->isShadowNode() ? n->shadowParentNode() : n->parentNode(); }*/ return nullptr; } void SVGElement::addSVGEventListener(/*const AtomicString& eventType*/const EventImpl::EventId &eventType, const Attribute *attr) { - // qDebug() << "add listener for: " << EventName::fromId(eventType).toString() << endl; + // qDebug() << "add listener for: " << EventName::fromId(eventType).toString(); Element::setHTMLEventListener(EventName::fromId(eventType), document()->accessSVGExtensions()-> createSVGEventListener(attr->localName().string(), attr->value(), this)); } void SVGElement::parseMappedAttribute(MappedAttribute *attr) { // standard events if (/*attr->name() == onloadAttr*/attr->id() == ATTR_ONLOAD) { addSVGEventListener(/*loadEvent*/EventImpl::LOAD_EVENT, attr); } else if (/*attr->name() == onclickAttr*/attr->id() == ATTR_ONCLICK) { addSVGEventListener(/*clickEvent*/EventImpl::CLICK_EVENT, attr); } else /*if (attr->name() == onmousedownAttr) addSVGEventListener(mousedownEvent, attr); else if (attr->name() == onmousemoveAttr) addSVGEventListener(mousemoveEvent, attr); else if (attr->name() == onmouseoutAttr) addSVGEventListener(mouseoutEvent, attr); else if (attr->name() == onmouseoverAttr) addSVGEventListener(mouseoverEvent, attr); else if (attr->name() == onmouseupAttr) addSVGEventListener(mouseupEvent, attr); else if (attr->name() == SVGNames::onfocusinAttr) addSVGEventListener(DOMFocusInEvent, attr); else if (attr->name() == SVGNames::onfocusoutAttr) addSVGEventListener(DOMFocusOutEvent, attr); else if (attr->name() == SVGNames::onactivateAttr) addSVGEventListener(DOMActivateEvent, attr); else*/ if (attr->id() == ATTR_ID) { setHasID(); document()->incDOMTreeVersion(DocumentImpl::TV_IDNameHref); } else { StyledElement::parseAttribute(attr); } } bool SVGElement::haveLoadedRequiredResources() { Node *child = firstChild(); while (child) { if (child->isSVGElement() && !static_cast(child)->haveLoadedRequiredResources()) { return false; } child = child->nextSibling(); } return true; } static bool hasLoadListener(SVGElement *node) { Node *currentNode = node; while (currentNode && currentNode->isElementNode()) { QList *list = static_cast(currentNode)->localEventListeners(); if (list) { QList::Iterator end = list->end(); for (QList::Iterator it = list->begin(); it != end; ++it) if ((*it).useCapture || (*it).eventName.id() == EventImpl::LOAD_EVENT/* || currentNode == node*/) { return true; } /*if ((*it)->eventType() == loadEvent && (*it)->useCapture() == true || currentNode == node) return true;*/ } currentNode = currentNode->parentNode(); } return false; } void SVGElement::sendSVGLoadEventIfPossible(bool sendParentLoadEvents) { - // qDebug() << "send svg load event" << endl; + // qDebug() << "send svg load event"; RefPtr currentTarget = this; - // qDebug() << currentTarget << currentTarget->haveLoadedRequiredResources() << endl; + // qDebug() << currentTarget << currentTarget->haveLoadedRequiredResources(); while (currentTarget && currentTarget->haveLoadedRequiredResources()) { RefPtr parent; if (sendParentLoadEvents) { parent = currentTarget->parentNode(); // save the next parent to dispatch too incase dispatching the event changes the tree } - // qDebug() << hasLoadListener(currentTarget.get()) << endl; + // qDebug() << hasLoadListener(currentTarget.get()); if (hasLoadListener(currentTarget.get())) { //Event* event = new Event(EventImpl::LOAD_EVENT, true/*false*/, false); //event->setTarget(currentTarget.get()); //ExceptionCode ignored = 0; //dispatchGenericEvent(/*currentTarget.get(), */event, ignored/*, false*/); dispatchHTMLEvent(EventImpl::LOAD_EVENT, false, false); } currentTarget = (parent && parent->isSVGElement()) ? static_pointer_cast(parent) : RefPtr(); } } void SVGElement::finishParsingChildren() { // finishParsingChildren() is called when the close tag is reached for an element (e.g. ) // we send SVGLoad events here if we can, otherwise they'll be sent when any required loads finish sendSVGLoadEventIfPossible(); } bool SVGElement::childShouldCreateRenderer(Node *child) const { if (child->isSVGElement()) { return static_cast(child)->isValid(); } return false; } void SVGElement::insertedIntoDocument() { StyledElement::insertedIntoDocument(); /*SVGDocumentExtensions* extensions = document()->accessSVGExtensions(); String resourceId = SVGURIReference::getTarget(id()); if (extensions->isPendingResource(resourceId)) { std::unique_ptr > clients(extensions->removePendingResource(resourceId)); if (clients->isEmpty()) return; HashSet::const_iterator it = clients->begin(); const HashSet::const_iterator end = clients->end(); for (; it != end; ++it) (*it)->buildPendingResource(); SVGResource::invalidateClients(*clients); }*/ } static Node *shadowTreeParentElementForShadowTreeElement(Node *node) { for (Node *n = node; n; n = n->parentNode()) { /*if (n->isShadowNode()) return n->shadowParentNode();*/ } return nullptr; } bool SVGElement::dispatchEvent(Event *e, ExceptionCode &ec, bool tempEvent) { Q_UNUSED(e); Q_UNUSED(ec); Q_UNUSED(tempEvent); - // qDebug() << "dispatch event" << endl; + // qDebug() << "dispatch event"; // TODO: This function will be removed in a follow-up patch! /*EventTarget* target = this; Node* useNode = shadowTreeParentElementForShadowTreeElement(this); // If we are a hidden shadow tree element, the target must // point to our corresponding SVGElementInstance object if (useNode) { ASSERT(useNode->hasTagName(SVGNames::useTag)); SVGUseElement* use = static_cast(useNode); SVGElementInstance* instance = use->instanceForShadowTreeElement(this); if (instance) target = instance; } e->setTarget(target); RefPtr view = document()->view(); return EventTargetNode::dispatchGenericEvent(this, e, ec, tempEvent);*/ ASSERT(false); return false; } void SVGElement::attributeChanged(Attribute *attr, bool preserveDecls) { ASSERT(attr); if (!attr) { return; } StyledElement::attributeChanged(attr, preserveDecls); svgAttributeChanged(attr->name()); } // for KHTML compatibility void SVGElement::addCSSProperty(Attribute *attr, int id, const String &value) { Q_UNUSED(attr); - // qDebug() << "called with: " << id << " " << value << endl; + // qDebug() << "called with: " << id << " " << value; /* WARNING: copy&past'ed from HTMLElementImpl class */ if (!m_hasCombinedStyle) { createNonCSSDecl(); } nonCSSStyleDecls()->setProperty(id, value, false); setChanged(); } void SVGElement::addCSSProperty(Attribute *attr, int id, int value) { Q_UNUSED(attr); - // qDebug() << "called with: " << id << " " << value << endl; + // qDebug() << "called with: " << id << " " << value; /* WARNING: copy&past'ed from HTMLElementImpl class */ if (!m_hasCombinedStyle) { createNonCSSDecl(); } nonCSSStyleDecls()->setProperty(id, value, false); setChanged(); } } #endif // ENABLE(SVG) diff --git a/src/svg/SVGGradientElement.cpp b/src/svg/SVGGradientElement.cpp index a339838..2c9a482 100644 --- a/src/svg/SVGGradientElement.cpp +++ b/src/svg/SVGGradientElement.cpp @@ -1,164 +1,164 @@ /* Copyright (C) 2004, 2005, 2006, 2008 Nikolas Zimmermann 2004, 2005, 2006, 2007 Rob Buis This file is part of the KDE project This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "wtf/Platform.h" #if ENABLE(SVG) #include "SVGGradientElement.h" #include "css/cssstyleselector.h" #include "RenderPath.h" #include "RenderSVGHiddenContainer.h" #include "SVGNames.h" #include "SVGPaintServerLinearGradient.h" #include "SVGPaintServerRadialGradient.h" #include "SVGStopElement.h" #include "SVGTransformList.h" #include "SVGTransformable.h" #include "SVGUnitTypes.h" namespace WebCore { SVGGradientElement::SVGGradientElement(const QualifiedName &tagName, Document *doc) : SVGStyledElement(tagName, doc) , SVGURIReference() , SVGExternalResourcesRequired() , m_spreadMethod(0) , m_gradientUnits(SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) , m_gradientTransform(SVGTransformList::create(SVGNames::gradientTransformAttr)) { } SVGGradientElement::~SVGGradientElement() { } ANIMATED_PROPERTY_DEFINITIONS(SVGGradientElement, int, Enumeration, enumeration, GradientUnits, gradientUnits, SVGNames::gradientUnitsAttr, m_gradientUnits) ANIMATED_PROPERTY_DEFINITIONS(SVGGradientElement, SVGTransformList *, TransformList, transformList, GradientTransform, gradientTransform, SVGNames::gradientTransformAttr, m_gradientTransform.get()) ANIMATED_PROPERTY_DEFINITIONS(SVGGradientElement, int, Enumeration, enumeration, SpreadMethod, spreadMethod, SVGNames::spreadMethodAttr, m_spreadMethod) void SVGGradientElement::parseMappedAttribute(MappedAttribute *attr) { if (attr->name() == SVGNames::gradientUnitsAttr) { if (attr->value() == "userSpaceOnUse") { setGradientUnitsBaseValue(SVGUnitTypes::SVG_UNIT_TYPE_USERSPACEONUSE); } else if (attr->value() == "objectBoundingBox") { setGradientUnitsBaseValue(SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX); } } else if (attr->name() == SVGNames::gradientTransformAttr) { SVGTransformList *gradientTransforms = gradientTransformBaseValue(); if (!SVGTransformable::parseTransformAttribute(gradientTransforms, attr->value())) { ExceptionCode ec = 0; gradientTransforms->clear(ec); } } else if (attr->name() == SVGNames::spreadMethodAttr) { if (attr->value() == "reflect") { setSpreadMethodBaseValue(SVG_SPREADMETHOD_REFLECT); } else if (attr->value() == "repeat") { setSpreadMethodBaseValue(SVG_SPREADMETHOD_REPEAT); } else if (attr->value() == "pad") { setSpreadMethodBaseValue(SVG_SPREADMETHOD_PAD); } } else { if (SVGURIReference::parseMappedAttribute(attr)) { return; } if (SVGExternalResourcesRequired::parseMappedAttribute(attr)) { return; } SVGStyledElement::parseMappedAttribute(attr); } } void SVGGradientElement::svgAttributeChanged(const QualifiedName &attrName) { SVGStyledElement::svgAttributeChanged(attrName); if (!m_resource) { return; } if (attrName == SVGNames::gradientUnitsAttr || attrName == SVGNames::gradientTransformAttr || attrName == SVGNames::spreadMethodAttr || SVGURIReference::isKnownAttribute(attrName) || SVGExternalResourcesRequired::isKnownAttribute(attrName) || SVGStyledElement::isKnownAttribute(attrName)) { m_resource->invalidate(); } } void SVGGradientElement::childrenChanged(bool changedByParser, Node *beforeChange, Node *afterChange, int childCountDelta) { SVGStyledElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta); if (m_resource) { m_resource->invalidate(); } } RenderObject *SVGGradientElement::createRenderer(RenderArena *arena, RenderStyle *) { return new(arena) RenderSVGHiddenContainer(this); } SVGResource *SVGGradientElement::canvasResource() { - // qDebug() << "request gradient paint server" << endl; + // qDebug() << "request gradient paint server"; if (!m_resource) { if (gradientType() == LinearGradientPaintServer) { m_resource = SVGPaintServerLinearGradient::create(this); } else { m_resource = SVGPaintServerRadialGradient::create(this); } } return m_resource.get(); } Vector SVGGradientElement::buildStops() const { Vector stops; for (Node *n = firstChild(); n; n = n->nextSibling()) { SVGElement *element = n->isSVGElement() ? static_cast(n) : nullptr; if (element && element->isGradientStop()) { SVGStopElement *stop = static_cast(element); float stopOffset = stop->offset(); RenderStyle *stopStyle = stop->computedStyle(); QColor color = stopStyle->svgStyle()->stopColor(); float opacity = stopStyle->svgStyle()->stopOpacity(); stops.append(makeGradientStop(stopOffset, QColor(color.red(), color.green(), color.blue(), int(opacity * 255.)))); } } return stops; } } #endif // ENABLE(SVG) diff --git a/src/svg/SVGLinearGradientElement.cpp b/src/svg/SVGLinearGradientElement.cpp index 3bcfaee..85a1141 100644 --- a/src/svg/SVGLinearGradientElement.cpp +++ b/src/svg/SVGLinearGradientElement.cpp @@ -1,188 +1,188 @@ /* Copyright (C) 2004, 2005, 2006, 2008 Nikolas Zimmermann 2004, 2005, 2006, 2007 Rob Buis This file is part of the KDE project This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "wtf/Platform.h" #if ENABLE(SVG) #include "SVGLinearGradientElement.h" #include "FloatPoint.h" #include "LinearGradientAttributes.h" #include "SVGLength.h" #include "SVGNames.h" #include "SVGPaintServerLinearGradient.h" #include "SVGTransform.h" #include "SVGTransformList.h" #include "SVGUnitTypes.h" namespace WebCore { SVGLinearGradientElement::SVGLinearGradientElement(const QualifiedName &tagName, Document *doc) : SVGGradientElement(tagName, doc) , m_x1(this, LengthModeWidth) , m_y1(this, LengthModeHeight) , m_x2(this, LengthModeWidth) , m_y2(this, LengthModeHeight) { // Spec: If the attribute is not specified, the effect is as if a value of "100%" were specified. setX2BaseValue(SVGLength(this, LengthModeWidth, "100%")); } SVGLinearGradientElement::~SVGLinearGradientElement() { } ANIMATED_PROPERTY_DEFINITIONS(SVGLinearGradientElement, SVGLength, Length, length, X1, x1, SVGNames::x1Attr, m_x1) ANIMATED_PROPERTY_DEFINITIONS(SVGLinearGradientElement, SVGLength, Length, length, Y1, y1, SVGNames::y1Attr, m_y1) ANIMATED_PROPERTY_DEFINITIONS(SVGLinearGradientElement, SVGLength, Length, length, X2, x2, SVGNames::x2Attr, m_x2) ANIMATED_PROPERTY_DEFINITIONS(SVGLinearGradientElement, SVGLength, Length, length, Y2, y2, SVGNames::y2Attr, m_y2) void SVGLinearGradientElement::parseMappedAttribute(MappedAttribute *attr) { if (attr->name() == SVGNames::x1Attr) { setX1BaseValue(SVGLength(this, LengthModeWidth, attr->value())); } else if (attr->name() == SVGNames::y1Attr) { setY1BaseValue(SVGLength(this, LengthModeHeight, attr->value())); } else if (attr->name() == SVGNames::x2Attr) { setX2BaseValue(SVGLength(this, LengthModeWidth, attr->value())); } else if (attr->name() == SVGNames::y2Attr) { setY2BaseValue(SVGLength(this, LengthModeHeight, attr->value())); } else { SVGGradientElement::parseMappedAttribute(attr); } } void SVGLinearGradientElement::svgAttributeChanged(const QualifiedName &attrName) { SVGGradientElement::svgAttributeChanged(attrName); if (!m_resource) { return; } if (attrName == SVGNames::x1Attr || attrName == SVGNames::y1Attr || attrName == SVGNames::x2Attr || attrName == SVGNames::y2Attr) { m_resource->invalidate(); } } void SVGLinearGradientElement::buildGradient() const { LinearGradientAttributes attributes = collectGradientProperties(); // If we didn't find any gradient containing stop elements, ignore the request. if (attributes.stops().isEmpty()) { return; } RefPtr linearGradient = WTF::static_pointer_cast(m_resource); linearGradient->setGradientStops(attributes.stops()); linearGradient->setBoundingBoxMode(attributes.boundingBoxMode()); linearGradient->setGradientSpreadMethod(attributes.spreadMethod()); linearGradient->setGradientTransform(attributes.gradientTransform()); linearGradient->setGradientStart(FloatPoint::narrowPrecision(attributes.x1(), attributes.y1())); linearGradient->setGradientEnd(FloatPoint::narrowPrecision(attributes.x2(), attributes.y2())); } LinearGradientAttributes SVGLinearGradientElement::collectGradientProperties() const { LinearGradientAttributes attributes; HashSet processedGradients; bool isLinear = true; const SVGGradientElement *current = this; while (current) { if (!attributes.hasSpreadMethod() && current->hasAttribute(SVGNames::spreadMethodAttr)) { attributes.setSpreadMethod((SVGGradientSpreadMethod) current->spreadMethod()); } if (!attributes.hasBoundingBoxMode() && current->hasAttribute(SVGNames::gradientUnitsAttr)) { attributes.setBoundingBoxMode(current->getAttribute(SVGNames::gradientUnitsAttr) == "objectBoundingBox"); } if (!attributes.hasGradientTransform() && current->hasAttribute(SVGNames::gradientTransformAttr)) { attributes.setGradientTransform(current->gradientTransform()->consolidate().matrix()); } if (!attributes.hasStops()) { const Vector &stops(current->buildStops()); - // qDebug() << "stops.isEmpty()" << stops.isEmpty() << endl; + // qDebug() << "stops.isEmpty()" << stops.isEmpty(); if (!stops.isEmpty()) { attributes.setStops(stops); } } if (isLinear) { const SVGLinearGradientElement *linear = static_cast(current); if (!attributes.hasX1() && current->hasAttribute(SVGNames::x1Attr)) { attributes.setX1(linear->x1().valueAsPercentage()); } if (!attributes.hasY1() && current->hasAttribute(SVGNames::y1Attr)) { attributes.setY1(linear->y1().valueAsPercentage()); } if (!attributes.hasX2() && current->hasAttribute(SVGNames::x2Attr)) { attributes.setX2(linear->x2().valueAsPercentage()); } if (!attributes.hasY2() && current->hasAttribute(SVGNames::y2Attr)) { attributes.setY2(linear->y2().valueAsPercentage()); } } processedGradients.add(current); // Respect xlink:href, take attributes from referenced element Node *refNode = ownerDocument()->getElementById(SVGURIReference::getTarget(current->href())); if (refNode && (refNode->hasTagName(SVGNames::linearGradientTag) || refNode->hasTagName(SVGNames::radialGradientTag))) { current = static_cast(const_cast(refNode)); // int ec; - // qDebug() << "take attributes from" << current->getAttributeNS("", "id", ec) << endl; + // qDebug() << "take attributes from" << current->getAttributeNS("", "id", ec); // Cycle detection if (processedGradients.contains(current)) { return LinearGradientAttributes(); } isLinear = current->gradientType() == LinearGradientPaintServer; } else { current = nullptr; } } return attributes; } // KHTML ElementImpl pure virtual method quint32 SVGLinearGradientElement::id() const { return SVGNames::linearGradientTag.id(); } } #endif // ENABLE(SVG) diff --git a/src/svg/SVGPolyElement.cpp b/src/svg/SVGPolyElement.cpp index 25e23d1..4bc7418 100644 --- a/src/svg/SVGPolyElement.cpp +++ b/src/svg/SVGPolyElement.cpp @@ -1,140 +1,140 @@ /* Copyright (C) 2004, 2005, 2006, 2008 Nikolas Zimmermann 2004, 2005, 2006, 2007 Rob Buis This file is part of the KDE project This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "wtf/Platform.h" #if ENABLE(SVG) #include "SVGPolyElement.h" #include "Document.h" #include "FloatPoint.h" #include "RenderPath.h" #include "SVGNames.h" #include "SVGParserUtilities.h" #include "SVGPointList.h" namespace WebCore { SVGPolyElement::SVGPolyElement(const QualifiedName &tagName, Document *doc) : SVGStyledTransformableElement(tagName, doc) , SVGTests() , SVGLangSpace() , SVGExternalResourcesRequired() , SVGAnimatedPoints() , m_ignoreAttributeChanges(false) { } SVGPolyElement::~SVGPolyElement() { } SVGPointList *SVGPolyElement::points() const { if (!m_points) { m_points = SVGPointList::create(SVGNames::pointsAttr); } return m_points.get(); } SVGPointList *SVGPolyElement::animatedPoints() const { // FIXME! return nullptr; } void SVGPolyElement::parseMappedAttribute(MappedAttribute *attr) { const AtomicString &value = attr->value(); if (attr->name() == SVGNames::pointsAttr) { ExceptionCode ec = 0; points()->clear(ec); if (!pointsListFromSVGData(points(), value)) { points()->clear(ec); /*FIXME khtml document()->accessSVGExtensions()->reportError("Problem parsing points=\"" + value.string() + "\"");*/ } } else { if (SVGTests::parseMappedAttribute(attr)) { return; } if (SVGLangSpace::parseMappedAttribute(attr)) { return; } if (SVGExternalResourcesRequired::parseMappedAttribute(attr)) { return; } SVGStyledTransformableElement::parseMappedAttribute(attr); } } void SVGPolyElement::svgAttributeChanged(const QualifiedName &attrName) { if (m_ignoreAttributeChanges) { return; } SVGStyledTransformableElement::svgAttributeChanged(attrName); if (!renderer()) { return; } if (attrName == SVGNames::pointsAttr) { m_ignoreAttributeChanges = true; renderer()->setNeedsLayout(true); //ExceptionCode ec = 0; // Spec: Additionally, the 'points' attribute on the original element // accessed via the XML DOM (e.g., using the getAttribute() method call) // will reflect any changes made to points. - // qDebug() << "fixme!!!!!!!!!" << endl; + // qDebug() << "fixme!!!!!!!!!"; /*FIXME KHTML String _points; int len = points()->numberOfItems(); for (int i = 0; i < len; ++i) { FloatPoint p = points()->getItem(i, ec); _points += String::format("%.6lg %.6lg ", p.x(), p.y()); } if (RefPtr attr = const_cast(this)->getAttributeNode(SVGNames::pointsAttr.localName())) { ExceptionCode ec = 0; attr->setValue(_points, ec); }*/ m_ignoreAttributeChanges = false; return; } if (SVGTests::isKnownAttribute(attrName) || SVGLangSpace::isKnownAttribute(attrName) || SVGExternalResourcesRequired::isKnownAttribute(attrName) || SVGStyledTransformableElement::isKnownAttribute(attrName)) { renderer()->setNeedsLayout(true); } } } #endif // ENABLE(SVG) diff --git a/src/svg/SVGRectElement.cpp b/src/svg/SVGRectElement.cpp index 59fac3d..7efa418 100644 --- a/src/svg/SVGRectElement.cpp +++ b/src/svg/SVGRectElement.cpp @@ -1,150 +1,150 @@ /* Copyright (C) 2004, 2005, 2006, 2008 Nikolas Zimmermann 2004, 2005, 2006, 2007 Rob Buis This file is part of the KDE project This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "wtf/Platform.h" #if ENABLE(SVG) #include "SVGRectElement.h" #include "RenderPath.h" #include "SVGLength.h" #include "SVGNames.h" namespace WebCore { SVGRectElement::SVGRectElement(const QualifiedName &tagName, Document *doc) : SVGStyledTransformableElement(tagName, doc) , SVGTests() , SVGLangSpace() , SVGExternalResourcesRequired() , m_x(this, LengthModeWidth) , m_y(this, LengthModeHeight) , m_width(this, LengthModeWidth) , m_height(this, LengthModeHeight) , m_rx(this, LengthModeWidth) , m_ry(this, LengthModeHeight) { } SVGRectElement::~SVGRectElement() { } ANIMATED_PROPERTY_DEFINITIONS(SVGRectElement, SVGLength, Length, length, X, x, SVGNames::xAttr, m_x) ANIMATED_PROPERTY_DEFINITIONS(SVGRectElement, SVGLength, Length, length, Y, y, SVGNames::yAttr, m_y) ANIMATED_PROPERTY_DEFINITIONS(SVGRectElement, SVGLength, Length, length, Width, width, SVGNames::widthAttr, m_width) ANIMATED_PROPERTY_DEFINITIONS(SVGRectElement, SVGLength, Length, length, Height, height, SVGNames::heightAttr, m_height) ANIMATED_PROPERTY_DEFINITIONS(SVGRectElement, SVGLength, Length, length, Rx, rx, SVGNames::rxAttr, m_rx) ANIMATED_PROPERTY_DEFINITIONS(SVGRectElement, SVGLength, Length, length, Ry, ry, SVGNames::ryAttr, m_ry) void SVGRectElement::parseMappedAttribute(MappedAttribute *attr) { - // qDebug() << "called with" << attr->localName() << attr->value() << endl; + // qDebug() << "called with" << attr->localName() << attr->value(); if (attr->name() == SVGNames::xAttr) { setXBaseValue(SVGLength(this, LengthModeWidth, attr->value())); } else if (attr->name() == SVGNames::yAttr) { setYBaseValue(SVGLength(this, LengthModeHeight, attr->value())); } else if (attr->name() == SVGNames::rxAttr) { setRxBaseValue(SVGLength(this, LengthModeWidth, attr->value())); if (rx().value() < 0.0) { document()->accessSVGExtensions()->reportError("A negative value for rect is not allowed"); } } else if (attr->name() == SVGNames::ryAttr) { setRyBaseValue(SVGLength(this, LengthModeHeight, attr->value())); if (ry().value() < 0.0) { document()->accessSVGExtensions()->reportError("A negative value for rect is not allowed"); } } else if (attr->name() == SVGNames::widthAttr) { setWidthBaseValue(SVGLength(this, LengthModeWidth, attr->value())); if (width().value() < 0.0) { document()->accessSVGExtensions()->reportError("A negative value for rect is not allowed"); } } else if (attr->name() == SVGNames::heightAttr) { setHeightBaseValue(SVGLength(this, LengthModeHeight, attr->value())); if (height().value() < 0.0) { document()->accessSVGExtensions()->reportError("A negative value for rect is not allowed"); } } else { if (SVGTests::parseMappedAttribute(attr)) { return; } if (SVGLangSpace::parseMappedAttribute(attr)) { return; } if (SVGExternalResourcesRequired::parseMappedAttribute(attr)) { return; } SVGStyledTransformableElement::parseMappedAttribute(attr); } } void SVGRectElement::svgAttributeChanged(const QualifiedName &attrName) { SVGStyledTransformableElement::svgAttributeChanged(attrName); if (!renderer()) { return; } if (attrName == SVGNames::xAttr || attrName == SVGNames::yAttr || attrName == SVGNames::widthAttr || attrName == SVGNames::heightAttr || attrName == SVGNames::rxAttr || attrName == SVGNames::ryAttr || SVGTests::isKnownAttribute(attrName) || SVGLangSpace::isKnownAttribute(attrName) || SVGExternalResourcesRequired::isKnownAttribute(attrName) || SVGStyledTransformableElement::isKnownAttribute(attrName)) { renderer()->setNeedsLayout(true); } } Path SVGRectElement::toPathData() const { FloatRect rect(x().value(), y().value(), width().value(), height().value()); bool hasRx = hasAttribute(SVGNames::rxAttr); bool hasRy = hasAttribute(SVGNames::ryAttr); if (hasRx || hasRy) { float _rx = hasRx ? rx().value() : ry().value(); float _ry = hasRy ? ry().value() : rx().value(); return Path::createRoundedRectangle(rect, FloatSize(_rx, _ry)); } return Path::createRectangle(rect); } bool SVGRectElement::hasRelativeValues() const { return (x().isRelative() || width().isRelative() || y().isRelative() || height().isRelative() || rx().isRelative() || ry().isRelative()); } // KHTML ElementImpl pure virtual method quint32 SVGRectElement::id() const { return SVGNames::rectTag.id(); } } #endif // ENABLE(SVG) diff --git a/src/svg/SVGSVGElement.cpp b/src/svg/SVGSVGElement.cpp index 1846ea1..960c6fb 100644 --- a/src/svg/SVGSVGElement.cpp +++ b/src/svg/SVGSVGElement.cpp @@ -1,598 +1,598 @@ /* Copyright (C) 2004, 2005, 2006 Nikolas Zimmermann 2004, 2005, 2006, 2007 Rob Buis 2007 Apple Inc. All rights reserved. This file is part of the KDE project This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #if ENABLE(SVG) #include "SVGSVGElement.h" #include "AffineTransform.h" /*#include "CSSHelper.h"*/ #include "css/csshelper.h" /*#include "CSSPropertyNames.h"*/ #include "Document.h" //#include "EventListener.h" #include "dom/dom2_events.h" /*#include "EventNames.h"*/ #include "FloatConversion.h" #include "FloatRect.h" /*#include "Frame.h" #include "HTMLNames.h" #include "RenderSVGViewportContainer.h"*/ #include "RenderSVGRoot.h" #include "SVGAngle.h" #include "SVGLength.h" #include "SVGNames.h" #include "SVGPreserveAspectRatio.h" #include "SVGTransform.h" #include "SVGTransformList.h" /*#include "SVGViewElement.h"*/ #include "SVGViewSpec.h" /*#include "SVGZoomEvent.h" #include "SelectionController.h" #include "SMILTimeContainer.h"*/ #include "khtml_part.h" namespace WebCore { /*using namespace HTMLNames; using namespace EventNames;*/ using namespace SVGNames; SVGSVGElement::SVGSVGElement(const QualifiedName &tagName, Document *doc) : SVGStyledLocatableElement(tagName, doc) , SVGTests() , SVGLangSpace() , SVGExternalResourcesRequired() , SVGFitToViewBox() , SVGZoomAndPan() , m_x(this, LengthModeWidth) , m_y(this, LengthModeHeight) , m_width(this, LengthModeWidth) , m_height(this, LengthModeHeight) , m_useCurrentView(false) /*, m_timeContainer(SMILTimeContainer::create(this)) , m_viewSpec(0)*/ { setWidthBaseValue(SVGLength(this, LengthModeWidth, "100%")); setHeightBaseValue(SVGLength(this, LengthModeHeight, "100%")); //doc->registerForCacheCallbacks(this); } SVGSVGElement::~SVGSVGElement() { /*document()->unregisterForCacheCallbacks(this); // There are cases where removedFromDocument() is not called. // see ContainerNode::removeAllChildren, called by it's destructor. document()->accessSVGExtensions()->removeTimeContainer(this);*/ } ANIMATED_PROPERTY_DEFINITIONS(SVGSVGElement, SVGLength, Length, length, X, x, SVGNames::xAttr, m_x) ANIMATED_PROPERTY_DEFINITIONS(SVGSVGElement, SVGLength, Length, length, Y, y, SVGNames::yAttr, m_y) ANIMATED_PROPERTY_DEFINITIONS(SVGSVGElement, SVGLength, Length, length, Width, width, SVGNames::widthAttr, m_width) ANIMATED_PROPERTY_DEFINITIONS(SVGSVGElement, SVGLength, Length, length, Height, height, SVGNames::heightAttr, m_height) DOMString SVGSVGElement::contentScriptType() const { static const DOMString defaultValue("text/ecmascript"); DOMString n = getAttribute(contentScriptTypeAttr); return n.isNull() ? defaultValue : n; } void SVGSVGElement::setContentScriptType(const DOMString &type) { setAttribute(SVGNames::contentScriptTypeAttr, type); } DOMString SVGSVGElement::contentStyleType() const { static const DOMString defaultValue("text/css"); const DOMString n = getAttribute(contentStyleTypeAttr); return n.isNull() ? defaultValue : n; } void SVGSVGElement::setContentStyleType(const DOMString &type) { setAttribute(SVGNames::contentStyleTypeAttr, type); } bool SVGSVGElement::hasSetContainerSize() const { // For now, we interpret % dimensions only if we're a top-level SVG element nested inside // an another part. ### might even want to check if we're the documentElement; this // will also need changes with handling return isOutermostSVG() && document()->part()->parentPart(); } IntSize SVGSVGElement::containerSize() const { if (KHTMLView *v = document()->view()) { return IntSize(v->visibleWidth(), v->visibleHeight()); } else { return IntSize(300, 150); } } FloatRect SVGSVGElement::viewport() const { double _x = 0.0; double _y = 0.0; if (!isOutermostSVG()) { _x = x().value(); _y = y().value(); } float w = width().value(); float h = height().value(); AffineTransform viewBox = viewBoxToViewTransform(w, h); double wDouble = w; double hDouble = h; viewBox.map(_x, _y, &_x, &_y); viewBox.map(w, h, &wDouble, &hDouble); return FloatRect::narrowPrecision(_x, _y, wDouble, hDouble); } int SVGSVGElement::relativeWidthValue() const { SVGLength w = width(); if (w.unitType() != LengthTypePercentage) { return 0; } return static_cast(w.valueAsPercentage() * containerSize().width()); } int SVGSVGElement::relativeHeightValue() const { SVGLength h = height(); if (h.unitType() != LengthTypePercentage) { return 0; } return static_cast(h.valueAsPercentage() * containerSize().height()); } float SVGSVGElement::pixelUnitToMillimeterX() const { // 2.54 / cssPixelsPerInch gives CM. return (2.54f / cssPixelsPerInch) * 10.0f; } float SVGSVGElement::pixelUnitToMillimeterY() const { // 2.54 / cssPixelsPerInch gives CM. return (2.54f / cssPixelsPerInch) * 10.0f; } float SVGSVGElement::screenPixelToMillimeterX() const { return pixelUnitToMillimeterX(); } float SVGSVGElement::screenPixelToMillimeterY() const { return pixelUnitToMillimeterY(); } bool SVGSVGElement::useCurrentView() const { return m_useCurrentView; } void SVGSVGElement::setUseCurrentView(bool currentView) { m_useCurrentView = currentView; } SVGViewSpec *SVGSVGElement::currentView() const { if (!m_viewSpec) { m_viewSpec.set(new SVGViewSpec(this)); } return m_viewSpec.get(); } float SVGSVGElement::currentScale() const { /*if (document() && document()->frame()) return document()->frame()->zoomFactor();*/ return 1.0f; } void SVGSVGElement::setCurrentScale(float scale) { Q_UNUSED(scale); /*if (document() && document()->frame()) document()->frame()->setZoomFactor(scale, true);*/ } FloatPoint SVGSVGElement::currentTranslate() const { return m_translation; } void SVGSVGElement::setCurrentTranslate(const FloatPoint &translation) { m_translation = translation; if (parentNode() == document() && document()->renderer()) { document()->renderer()->repaint(); } } void SVGSVGElement::addSVGWindowEventListener(const AtomicString &eventType, const Attribute *attr) { Q_UNUSED(eventType); // FIXME: None of these should be window events long term. // Once we propertly support SVGLoad, etc. RefPtr listener = document()->accessSVGExtensions()-> createSVGEventListener(attr->localName().string(), attr->value(), this); //document()->setHTMLWindowEventListener(eventType, listener.release()); } void SVGSVGElement::parseMappedAttribute(MappedAttribute *attr) { - // qDebug() << "parse attribute: " << attr->localName() << attr->value() << endl; + // qDebug() << "parse attribute: " << attr->localName() << attr->value(); if (!nearestViewportElement()) { // Only handle events if we're the outermost element /*if (attr->name() == onunloadAttr) addSVGWindowEventListener(unloadEvent, attr); else if (attr->name() == onabortAttr) addSVGWindowEventListener(abortEvent, attr); else if (attr->name() == onerrorAttr) addSVGWindowEventListener(errorEvent, attr); else if (attr->name() == onresizeAttr) addSVGWindowEventListener(resizeEvent, attr); else if (attr->name() == onscrollAttr) addSVGWindowEventListener(scrollEvent, attr); else if (attr->name() == SVGNames::onzoomAttr) addSVGWindowEventListener(zoomEvent, attr);*/ } if (attr->name() == SVGNames::xAttr) { setXBaseValue(SVGLength(this, LengthModeWidth, attr->value())); } else if (attr->name() == SVGNames::yAttr) { setYBaseValue(SVGLength(this, LengthModeHeight, attr->value())); } else if (attr->name() == SVGNames::widthAttr) { - // qDebug() << "set width" << attr->value() << endl; + // qDebug() << "set width" << attr->value(); setWidthBaseValue(SVGLength(this, LengthModeWidth, attr->value())); addCSSProperty(attr, CSSPropertyWidth, attr->value()); /*if (width().value() < 0.0) document()->accessSVGExtensions()->reportError("A negative value for svg attribute is not allowed");*/ } else if (attr->name() == SVGNames::heightAttr) { - // qDebug() << "set height" << attr->value() << endl; + // qDebug() << "set height" << attr->value(); setHeightBaseValue(SVGLength(this, LengthModeHeight, attr->value())); addCSSProperty(attr, CSSPropertyHeight, attr->value()); /*if (height().value() < 0.0) document()->accessSVGExtensions()->reportError("A negative value for svg attribute is not allowed");*/ } else { /*if (SVGTests::parseMappedAttribute(attr)) return; if (SVGLangSpace::parseMappedAttribute(attr)) return; if (SVGExternalResourcesRequired::parseMappedAttribute(attr)) return; if (SVGFitToViewBox::parseMappedAttribute(attr)) return; if (SVGZoomAndPan::parseMappedAttribute(attr)) return;*/ SVGStyledLocatableElement::parseMappedAttribute(attr); } } void SVGSVGElement::svgAttributeChanged(const QualifiedName &attrName) { SVGStyledElement::svgAttributeChanged(attrName); if (!renderer()) { return; } /*if (attrName == SVGNames::xAttr || attrName == SVGNames::yAttr || attrName == SVGNames::widthAttr || attrName == SVGNames::heightAttr || SVGTests::isKnownAttribute(attrName) || SVGLangSpace::isKnownAttribute(attrName) || SVGExternalResourcesRequired::isKnownAttribute(attrName) || SVGFitToViewBox::isKnownAttribute(attrName) || SVGZoomAndPan::isKnownAttribute(attrName) || SVGStyledLocatableElement::isKnownAttribute(attrName)) renderer()->setNeedsLayout(true);*/ } unsigned long SVGSVGElement::suspendRedraw(unsigned long /* max_wait_milliseconds */) { // FIXME: Implement me (see bug 11275) return 0; } void SVGSVGElement::unsuspendRedraw(unsigned long /* suspend_handle_id */, ExceptionCode & /*ec*/) { // if suspend_handle_id is not found, throw exception // FIXME: Implement me (see bug 11275) } void SVGSVGElement::unsuspendRedrawAll() { // FIXME: Implement me (see bug 11275) } void SVGSVGElement::forceRedraw() { // FIXME: Implement me (see bug 11275) } DOM::NodeListImpl *SVGSVGElement::getIntersectionList(const FloatRect &rect, SVGElement *) { Q_UNUSED(rect); // FIXME: Implement me (see bug 11274) return nullptr; } DOM::NodeListImpl *SVGSVGElement::getEnclosureList(const FloatRect &rect, SVGElement *) { Q_UNUSED(rect); // FIXME: Implement me (see bug 11274) return nullptr; } bool SVGSVGElement::checkIntersection(SVGElement *element, const FloatRect &rect) { Q_UNUSED(element); // TODO : take into account pointer-events? // FIXME: Why is element ignored?? // FIXME: Implement me (see bug 11274) return rect.intersects(getBBox()); } bool SVGSVGElement::checkEnclosure(SVGElement *element, const FloatRect &rect) { Q_UNUSED(element); // TODO : take into account pointer-events? // FIXME: Why is element ignored?? // FIXME: Implement me (see bug 11274) return rect.contains(getBBox()); } void SVGSVGElement::deselectAll() { //document()->frame()->selectionController()->clear(); } float SVGSVGElement::createSVGNumber() { return 0.0f; } SVGLength SVGSVGElement::createSVGLength() { return SVGLength(); } SVGAngle *SVGSVGElement::createSVGAngle() { return new SVGAngle(); } FloatPoint SVGSVGElement::createSVGPoint() { return FloatPoint(); } AffineTransform SVGSVGElement::createSVGMatrix() { return AffineTransform(); } FloatRect SVGSVGElement::createSVGRect() { return FloatRect(); } SVGTransform SVGSVGElement::createSVGTransform() { return SVGTransform(); } SVGTransform SVGSVGElement::createSVGTransformFromMatrix(const AffineTransform &matrix) { return SVGTransform(matrix); } AffineTransform SVGSVGElement::getCTM() const { AffineTransform mat; if (!isOutermostSVG()) { mat.translate(x().value(), y().value()); } if (attributes()->getNamedItem(SVGNames::viewBoxAttr)) { AffineTransform viewBox = viewBoxToViewTransform(width().value(), height().value()); mat = viewBox * mat; } return mat; } AffineTransform SVGSVGElement::getScreenCTM() const { /*document()->updateLayoutIgnorePendingStylesheets(); float rootX = 0.0f; float rootY = 0.0f; if (RenderObject* renderer = this->renderer()) { renderer = renderer->parent(); if (isOutermostSVG()) { int tx = 0; int ty = 0; if (renderer) renderer->absolutePosition(tx, ty, true); rootX += tx; rootY += ty; } else { rootX += x().value(); rootY += y().value(); } } AffineTransform mat = SVGStyledLocatableElement::getScreenCTM(); mat.translate(rootX, rootY); if (attributes()->getNamedItem(SVGNames::viewBoxAttr)) { AffineTransform viewBox = viewBoxToViewTransform(width().value(), height().value()); mat = viewBox * mat; } return mat;*/ ASSERT(false); return AffineTransform(); } RenderObject *SVGSVGElement::createRenderer(RenderArena *arena, RenderStyle *) { - // qDebug() << "create RenderSVGRoot from element" << endl; + // qDebug() << "create RenderSVGRoot from element"; return new(arena) RenderSVGRoot(this); /*if (isOutermostSVG()) return new (arena) RenderSVGRoot(this); else return new (arena) RenderSVGViewportContainer(this);*/ } void SVGSVGElement::insertedIntoDocument() { document()->accessSVGExtensions()->addTimeContainer(this); SVGStyledLocatableElement::insertedIntoDocument(); } void SVGSVGElement::removedFromDocument() { document()->accessSVGExtensions()->removeTimeContainer(this); SVGStyledLocatableElement::removedFromDocument(); } void SVGSVGElement::pauseAnimations() { /*if (!m_timeContainer->isPaused()) m_timeContainer->pause();*/ } void SVGSVGElement::unpauseAnimations() { /*if (m_timeContainer->isPaused()) m_timeContainer->resume();*/ } bool SVGSVGElement::animationsPaused() const { //return m_timeContainer->isPaused(); ASSERT(false); return false; } float SVGSVGElement::getCurrentTime() const { //return narrowPrecisionToFloat(m_timeContainer->elapsed().value()); ASSERT(false); return 0.0; } void SVGSVGElement::setCurrentTime(float /* seconds */) { // FIXME: Implement me, bug 12073 } bool SVGSVGElement::hasRelativeValues() const { return (x().isRelative() || width().isRelative() || y().isRelative() || height().isRelative()); } bool SVGSVGElement::isOutermostSVG() const { // This is true whenever this is the outermost SVG, even if there are HTML elements outside it return !parentNode()->isSVGElement(); } AffineTransform SVGSVGElement::viewBoxToViewTransform(float viewWidth, float viewHeight) const { FloatRect viewBoxRect; if (useCurrentView()) { if (currentView()) { // what if we should use it but it is not set? viewBoxRect = currentView()->viewBox(); } } else { viewBoxRect = viewBox(); } if (!viewBoxRect.width() || !viewBoxRect.height()) { return AffineTransform(); } AffineTransform ctm = preserveAspectRatio()->getCTM(viewBoxRect.x(), viewBoxRect.y(), viewBoxRect.width(), viewBoxRect.height(), 0, 0, viewWidth, viewHeight); if (useCurrentView() && currentView()) { return currentView()->transform()->concatenate().matrix() * ctm; } return ctm; } /*void SVGSVGElement::inheritViewAttributes(SVGViewElement* viewElement) { setUseCurrentView(true); if (viewElement->hasAttribute(SVGNames::viewBoxAttr)) currentView()->setViewBox(viewElement->viewBox()); else currentView()->setViewBox(viewBox()); if (viewElement->hasAttribute(SVGNames::preserveAspectRatioAttr)) { currentView()->preserveAspectRatio()->setAlign(viewElement->preserveAspectRatio()->align()); currentView()->preserveAspectRatio()->setMeetOrSlice(viewElement->preserveAspectRatio()->meetOrSlice()); } else { currentView()->preserveAspectRatio()->setAlign(preserveAspectRatio()->align()); currentView()->preserveAspectRatio()->setMeetOrSlice(preserveAspectRatio()->meetOrSlice()); } if (viewElement->hasAttribute(SVGNames::zoomAndPanAttr)) currentView()->setZoomAndPan(viewElement->zoomAndPan()); renderer()->setNeedsLayout(true); }*/ void SVGSVGElement::willSaveToCache() { //pauseAnimations(); } void SVGSVGElement::willRestoreFromCache() { //unpauseAnimations(); } // KHTML stuff quint32 SVGSVGElement::id() const { return SVGNames::svgTag.id(); } } #endif // ENABLE(SVG) diff --git a/src/svg/SVGStopElement.cpp b/src/svg/SVGStopElement.cpp index 1fbb4d8..6f492c5 100644 --- a/src/svg/SVGStopElement.cpp +++ b/src/svg/SVGStopElement.cpp @@ -1,78 +1,78 @@ /* Copyright (C) 2004, 2005, 2007, 2008 Nikolas Zimmermann 2004, 2005, 2006, 2007 Rob Buis This file is part of the KDE project This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "wtf/Platform.h" #if ENABLE(SVG) #include "SVGStopElement.h" #include "Document.h" #include "RenderSVGGradientStop.h" #include "SVGGradientElement.h" #include "SVGNames.h" namespace WebCore { SVGStopElement::SVGStopElement(const QualifiedName &tagName, Document *doc) : SVGStyledElement(tagName, doc) , m_offset(0.0f) { } SVGStopElement::~SVGStopElement() { } ANIMATED_PROPERTY_DEFINITIONS(SVGStopElement, float, Number, number, Offset, offset, SVGNames::offsetAttr, m_offset) void SVGStopElement::parseMappedAttribute(MappedAttribute *attr) { if (attr->name() == SVGNames::offsetAttr) { const String &value = attr->value(); - // qDebug() << "parse offset:" << value << endl; + // qDebug() << "parse offset:" << value; if (value.endsWith("%")) { setOffsetBaseValue(value.substring(0, value.length() - 1).toFloat() / 100.0f); } else { setOffsetBaseValue(value.toFloat()); } setChanged(); } else { SVGStyledElement::parseMappedAttribute(attr); } } RenderObject *SVGStopElement::createRenderer(RenderArena *arena, RenderStyle *) { return new(arena) RenderSVGGradientStop(this); } // KHTML ElementImpl pure virtual method quint32 SVGStopElement::id() const { return SVGNames::stopTag.id(); } } #endif // ENABLE(SVG) diff --git a/src/svg/SVGStyleElement.cpp b/src/svg/SVGStyleElement.cpp index dd640a1..eed8cbf 100644 --- a/src/svg/SVGStyleElement.cpp +++ b/src/svg/SVGStyleElement.cpp @@ -1,160 +1,160 @@ /* Copyright (C) 2004, 2005 Nikolas Zimmermann 2004, 2005, 2006, 2007 Rob Buis Copyright (C) 2006 Apple Computer, Inc. This file is part of the KDE project This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "wtf/Platform.h" #if ENABLE(SVG) #include "SVGStyleElement.h" //#include "CSSStyleSheet.h" #include "Document.h" #include "ExceptionCode.h" //#include "HTMLNames.h" //#include "XMLNames.h" namespace WebCore { //using namespace HTMLNames; SVGStyleElement::SVGStyleElement(const QualifiedName &tagName, Document *doc) : SVGElement(tagName, doc) , m_createdByParser(false) , m_sheet(nullptr) { } DOMString SVGStyleElement::xmlspace() const { // ### shouldn't this provide default? return getAttribute(ATTR_XML_SPACE); } void SVGStyleElement::setXmlspace(const DOMString &, ExceptionCode &ec) { ec = DOMException::NO_MODIFICATION_ALLOWED_ERR; } const DOMString SVGStyleElement::type() const { static const DOMString defaultValue("text/css"); DOMString n = getAttribute(ATTR_TYPE); return n.isNull() ? defaultValue : n; } void SVGStyleElement::setType(const DOMString &, ExceptionCode &ec) { ec = DOMException::NO_MODIFICATION_ALLOWED_ERR; } const DOMString SVGStyleElement::media() const { static const DOMString defaultValue("all"); DOMString n = getAttribute(ATTR_MEDIA); return n.isNull() ? defaultValue : n; } void SVGStyleElement::setMedia(const DOMString &, ExceptionCode &ec) { ec = DOMException::NO_MODIFICATION_ALLOWED_ERR; } String SVGStyleElement::title() const { return getAttribute(ATTR_TITLE); } void SVGStyleElement::setTitle(const DOMString &, ExceptionCode &ec) { ec = DOMException::NO_MODIFICATION_ALLOWED_ERR; } void SVGStyleElement::parseMappedAttribute(MappedAttribute *attr) { - // qDebug() << "parse: " << attr->id() << attr->localName() << attr->value() << endl; + // qDebug() << "parse: " << attr->id() << attr->localName() << attr->value(); if (/*attr->name() == titleAttr*/attr->id() == ATTR_TITLE && m_sheet) ;//FIXME m_sheet->setTitle(attr->value()); else { SVGElement::parseMappedAttribute(attr); } } void SVGStyleElement::finishParsingChildren() { //StyleElement::sheet(this); m_createdByParser = false; SVGElement::finishParsingChildren(); } void SVGStyleElement::insertedIntoDocument() { SVGElement::insertedIntoDocument(); /*if (!m_createdByParser) StyleElement::insertedIntoDocument(document(), this);*/ // Copy implementation from HTMLStyleElementImpl for KHTML - // qDebug() << "not implemented" << endl; + // qDebug() << "not implemented"; } void SVGStyleElement::removedFromDocument() { SVGElement::removedFromDocument(); /*StyleElement::removedFromDocument(document());*/ // Copy implementation from HTMLStyleElementImpl for KHTML - // qDebug() << "not implemented" << endl; + // qDebug() << "not implemented"; } void SVGStyleElement::childrenChanged(bool changedByParser, Node *beforeChange, Node *afterChange, int childCountDelta) { Q_UNUSED(changedByParser); Q_UNUSED(beforeChange); Q_UNUSED(afterChange); Q_UNUSED(childCountDelta); //SVGElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta); SVGElement::childrenChanged(); /*StyleElement::process(this);*/ // Copy implementation from HTMLStyleElementImpl for KHTML - // qDebug() << "not implemented" << endl; + // qDebug() << "not implemented"; } StyleSheet *SVGStyleElement::sheet() { //return StyleElement::sheet(this); return m_sheet; } bool SVGStyleElement::sheetLoaded() { //document()->removePendingSheet(); document()->styleSheetLoaded(); return true; } // khtml compatibility methods: quint32 SVGStyleElement::id() const { return SVGNames::styleTag.id(); } } #endif // ENABLE(SVG) diff --git a/src/svg/SVGTextElement.cpp b/src/svg/SVGTextElement.cpp index de6fd18..5b2fbea 100644 --- a/src/svg/SVGTextElement.cpp +++ b/src/svg/SVGTextElement.cpp @@ -1,145 +1,145 @@ /* Copyright (C) 2004, 2005, 2007, 2008 Nikolas Zimmermann 2004, 2005, 2006, 2008 Rob Buis This file is part of the KDE project This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "wtf/Platform.h" #if ENABLE(SVG) #include "SVGTextElement.h" #include "AffineTransform.h" #include "FloatRect.h" #include "RenderSVGText.h" #include "SVGLengthList.h" #include "SVGRenderStyle.h" #include "SVGTSpanElement.h" #include "SVGTransformList.h" namespace WebCore { SVGTextElement::SVGTextElement(const QualifiedName &tagName, Document *doc) : SVGTextPositioningElement(tagName, doc) , SVGTransformable() , m_transform(SVGTransformList::create(SVGNames::transformAttr)) { } SVGTextElement::~SVGTextElement() { } ANIMATED_PROPERTY_DEFINITIONS(SVGTextElement, SVGTransformList *, TransformList, transformList, Transform, transform, SVGNames::transformAttr, m_transform.get()) void SVGTextElement::parseMappedAttribute(MappedAttribute *attr) { if (attr->name() == SVGNames::transformAttr) { SVGTransformList *localTransforms = transformBaseValue(); ExceptionCode ec = 0; localTransforms->clear(ec); if (!SVGTransformable::parseTransformAttribute(localTransforms, attr->value())) { localTransforms->clear(ec); } else { setTransformBaseValue(localTransforms); if (renderer()) { renderer()->setNeedsLayout(true); // should be in setTransformBaseValue } } } else { SVGTextPositioningElement::parseMappedAttribute(attr); } } SVGElement *SVGTextElement::nearestViewportElement() const { return SVGTransformable::nearestViewportElement(this); } SVGElement *SVGTextElement::farthestViewportElement() const { return SVGTransformable::farthestViewportElement(this); } FloatRect SVGTextElement::getBBox() const { return SVGTransformable::getBBox(this); } AffineTransform SVGTextElement::getScreenCTM() const { return SVGTransformable::getScreenCTM(this); } AffineTransform SVGTextElement::getCTM() const { return SVGTransformable::getCTM(this); } AffineTransform SVGTextElement::animatedLocalTransform() const { return m_supplementalTransform ? transform()->concatenate().matrix() **m_supplementalTransform : transform()->concatenate().matrix(); } AffineTransform *SVGTextElement::supplementalTransform() { if (!m_supplementalTransform) { m_supplementalTransform.set(new AffineTransform()); } return m_supplementalTransform.get(); } RenderObject *SVGTextElement::createRenderer(RenderArena *arena, RenderStyle *style) { Q_UNUSED(style); - // qDebug() << "create renderer for " << endl; + // qDebug() << "create renderer for "; return new(arena) RenderSVGText(this); } bool SVGTextElement::childShouldCreateRenderer(Node *child) const { if (child->isTextNode() #if ENABLE(SVG_FONTS) || child->hasTagName(SVGNames::altGlyphTag) #endif || child->hasTagName(SVGNames::tspanTag) || child->hasTagName(SVGNames::trefTag) || child->hasTagName(SVGNames::aTag) || child->hasTagName(SVGNames::textPathTag)) { return true; } return false; } void SVGTextElement::svgAttributeChanged(const QualifiedName &attrName) { SVGTextPositioningElement::svgAttributeChanged(attrName); if (!renderer()) { return; } if (SVGTextPositioningElement::isKnownAttribute(attrName)) { renderer()->setNeedsLayout(true); } } } #endif // ENABLE(SVG) diff --git a/src/svg/SVGTextPositioningElement.cpp b/src/svg/SVGTextPositioningElement.cpp index f002aff..02188bc 100644 --- a/src/svg/SVGTextPositioningElement.cpp +++ b/src/svg/SVGTextPositioningElement.cpp @@ -1,85 +1,85 @@ /* Copyright (C) 2004, 2005, 2008 Nikolas Zimmermann 2004, 2005, 2006, 2007, 2008 Rob Buis This file is part of the KDE project This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "wtf/Platform.h" #if ENABLE(SVG) #include "SVGTextPositioningElement.h" #include "SVGLengthList.h" #include "SVGNames.h" #include "SVGNumberList.h" namespace WebCore { SVGTextPositioningElement::SVGTextPositioningElement(const QualifiedName &tagName, Document *doc) : SVGTextContentElement(tagName, doc) , m_x(SVGLengthList::create(SVGNames::xAttr)) , m_y(SVGLengthList::create(SVGNames::yAttr)) , m_dx(SVGLengthList::create(SVGNames::dxAttr)) , m_dy(SVGLengthList::create(SVGNames::dyAttr)) , m_rotate(SVGNumberList::create(SVGNames::rotateAttr)) { } SVGTextPositioningElement::~SVGTextPositioningElement() { } ANIMATED_PROPERTY_DEFINITIONS(SVGTextPositioningElement, SVGLengthList *, LengthList, lengthList, X, x, SVGNames::xAttr, m_x.get()) ANIMATED_PROPERTY_DEFINITIONS(SVGTextPositioningElement, SVGLengthList *, LengthList, lengthList, Y, y, SVGNames::yAttr, m_y.get()) ANIMATED_PROPERTY_DEFINITIONS(SVGTextPositioningElement, SVGLengthList *, LengthList, lengthList, Dx, dx, SVGNames::dxAttr, m_dx.get()) ANIMATED_PROPERTY_DEFINITIONS(SVGTextPositioningElement, SVGLengthList *, LengthList, lengthList, Dy, dy, SVGNames::dyAttr, m_dy.get()) ANIMATED_PROPERTY_DEFINITIONS(SVGTextPositioningElement, SVGNumberList *, NumberList, numberList, Rotate, rotate, SVGNames::rotateAttr, m_rotate.get()) void SVGTextPositioningElement::parseMappedAttribute(MappedAttribute *attr) { - // qDebug() << "parse:" << attr->localName() << attr->value() << endl; + // qDebug() << "parse:" << attr->localName() << attr->value(); if (attr->name() == SVGNames::xAttr) { xBaseValue()->parse(attr->value(), this, LengthModeWidth); } else if (attr->name() == SVGNames::yAttr) { yBaseValue()->parse(attr->value(), this, LengthModeHeight); } else if (attr->name() == SVGNames::dxAttr) { dxBaseValue()->parse(attr->value(), this, LengthModeWidth); } else if (attr->name() == SVGNames::dyAttr) { dyBaseValue()->parse(attr->value(), this, LengthModeHeight); } else if (attr->name() == SVGNames::rotateAttr) { rotateBaseValue()->parse(attr->value()); } else { SVGTextContentElement::parseMappedAttribute(attr); } } bool SVGTextPositioningElement::isKnownAttribute(const QualifiedName &attrName) { return (attrName.matches(SVGNames::xAttr) || attrName.matches(SVGNames::yAttr) || attrName.matches(SVGNames::dxAttr) || attrName.matches(SVGNames::dyAttr) || attrName.matches(SVGNames::rotateAttr) || SVGTextContentElement::isKnownAttribute(attrName)); } } #endif // ENABLE(SVG) diff --git a/src/svg/SVGURIReference.cpp b/src/svg/SVGURIReference.cpp index edd9ca2..e669d8a 100644 --- a/src/svg/SVGURIReference.cpp +++ b/src/svg/SVGURIReference.cpp @@ -1,77 +1,77 @@ /* Copyright (C) 2004, 2005, 2008 Nikolas Zimmermann 2004, 2005, 2006 Rob Buis This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "wtf/Platform.h" #if ENABLE(SVG) #include "SVGURIReference.h" #include "SVGNames.h" #include "SVGStyledElement.h" #include "XLinkNames.h" namespace WebCore { SVGURIReference::SVGURIReference() { } SVGURIReference::~SVGURIReference() { } ANIMATED_PROPERTY_DEFINITIONS_WITH_CONTEXT(SVGURIReference, String, String, string, Href, href, XLinkNames::hrefAttr, m_href) bool SVGURIReference::parseMappedAttribute(MappedAttribute *attr) { - // qDebug() << "parse" << attr->localName() << attr->value() << endl; + // qDebug() << "parse" << attr->localName() << attr->value(); if (attr->id() == ATTR_XLINK_HREF) { - // qDebug() << "set href base value" << attr->value() << endl; + // qDebug() << "set href base value" << attr->value(); setHrefBaseValue(attr->value()); return true; } return false; } bool SVGURIReference::isKnownAttribute(const QualifiedName &attrName) { return attrName.matches(XLinkNames::hrefAttr); } String SVGURIReference::getTarget(const String &url) { if (url.startsWith("url(")) { // URI References, ie. fill:url(#target) unsigned int start = url.find('#') + 1; unsigned int end = url.reverseFind(')'); return url.substring(start, end - start); } else if (url.find('#') > -1) { // format is #target unsigned int start = url.find('#') + 1; return url.substring(start, url.length() - start); } else { // Normal Reference, ie. style="color-profile:changeColor" return url; } } } #endif // ENABLE(SVG) diff --git a/src/svg/graphics/SVGPaintServerLinearGradient.cpp b/src/svg/graphics/SVGPaintServerLinearGradient.cpp index ee191d1..2bc4fe8 100644 --- a/src/svg/graphics/SVGPaintServerLinearGradient.cpp +++ b/src/svg/graphics/SVGPaintServerLinearGradient.cpp @@ -1,76 +1,76 @@ /* * Copyright (C) 2006 Nikolas Zimmermann * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "wtf/Platform.h" #if ENABLE(SVG) #include "SVGPaintServerLinearGradient.h" //#include "SVGRenderTreeAsText.h" namespace WebCore { SVGPaintServerLinearGradient::SVGPaintServerLinearGradient(const SVGGradientElement *owner) : SVGPaintServerGradient(owner) { - // qDebug() << "create LinearGradient Paint server" << endl; + // qDebug() << "create LinearGradient Paint server"; } SVGPaintServerLinearGradient::~SVGPaintServerLinearGradient() { } FloatPoint SVGPaintServerLinearGradient::gradientStart() const { return m_start; } void SVGPaintServerLinearGradient::setGradientStart(const FloatPoint &start) { m_start = start; } FloatPoint SVGPaintServerLinearGradient::gradientEnd() const { return m_end; } void SVGPaintServerLinearGradient::setGradientEnd(const FloatPoint &end) { m_end = end; } /*TextStream& SVGPaintServerLinearGradient::externalRepresentation(TextStream& ts) const { ts << "[type=LINEAR-GRADIENT] "; SVGPaintServerGradient::externalRepresentation(ts); ts << " [start=" << gradientStart() << "]" << " [end=" << gradientEnd() << "]"; return ts; }*/ } // namespace WebCore #endif diff --git a/src/svg/graphics/qt/SVGPaintServerGradientQt.cpp b/src/svg/graphics/qt/SVGPaintServerGradientQt.cpp index 9328d83..5209866 100644 --- a/src/svg/graphics/qt/SVGPaintServerGradientQt.cpp +++ b/src/svg/graphics/qt/SVGPaintServerGradientQt.cpp @@ -1,116 +1,116 @@ /* Copyright (C) 2006 Nikolas Zimmermann This file is part of the KDE project This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License aint with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "wtf/Platform.h" #if ENABLE(SVG) #include "SVGPaintServerGradient.h" /*#include "GraphicsContext.h"*/ #include "RenderObject.h" #include "RenderStyle.h" #include "SVGGradientElement.h" #include #include #include #include namespace WebCore { // Helper function used by linear & radial gradient void SVGPaintServerGradient::fillColorArray(QGradient &gradient, const Vector &stops, float opacity) const { - // qDebug() << stops.size() << endl; + // qDebug() << stops.size(); for (unsigned i = 0; i < stops.size(); ++i) { float offset = stops[i].first; QColor color = stops[i].second; - // qDebug() << "offset" << offset << "color" << color << endl; + // qDebug() << "offset" << offset << "color" << color; QColor c(color.red(), color.green(), color.blue()); c.setAlpha(int(color.alpha() * opacity)); gradient.setColorAt(offset, c); } } bool SVGPaintServerGradient::setup(QPainter *painter, QPainterPath *painterPath, const RenderObject *object, SVGPaintTargetType type, bool isPaintingText) const { Q_UNUSED(isPaintingText); - // qDebug() << "!!!!!!!" << endl; + // qDebug() << "!!!!!!!"; m_ownerElement->buildGradient(); /*QPainter* painter(context ? context->platformContext() : 0); Q_ASSERT(painter);*/ /*QPainterPath* path(context ? context->currentPath() : 0); Q_ASSERT(path);*/ RenderStyle *renderStyle = object->style(); QGradient gradient = setupGradient(painter, painterPath, object); painter->setPen(Qt::NoPen); painter->setBrush(Qt::NoBrush); if (spreadMethod() == SPREADMETHOD_REPEAT) { gradient.setSpread(QGradient::RepeatSpread); } else if (spreadMethod() == SPREADMETHOD_REFLECT) { gradient.setSpread(QGradient::ReflectSpread); } else { gradient.setSpread(QGradient::PadSpread); } double opacity = 1.0; - // qDebug() << "type: " << type << (type & ApplyToFillTargetType) << endl; + // qDebug() << "type: " << type << (type & ApplyToFillTargetType); if ((type & ApplyToFillTargetType) && renderStyle->svgStyle()->hasFill()) { fillColorArray(gradient, gradientStops(), opacity); QBrush brush(gradient); brush.setMatrix(gradientTransform()); painter->setBrush(brush); /* FIXME khtml context->setFillRule(renderStyle->svgStyle()->fillRule());*/ } if ((type & ApplyToStrokeTargetType) && renderStyle->svgStyle()->hasStroke()) { fillColorArray(gradient, gradientStops(), opacity); QPen pen; QBrush brush(gradient); brush.setMatrix(gradientTransform()); setPenProperties(object, renderStyle, pen); pen.setBrush(brush); painter->setPen(pen); } return true; } } // namespace WebCore #endif diff --git a/src/svg/graphics/qt/SVGPaintServerRadialGradientQt.cpp b/src/svg/graphics/qt/SVGPaintServerRadialGradientQt.cpp index 5b1918f..3ad91a9 100644 --- a/src/svg/graphics/qt/SVGPaintServerRadialGradientQt.cpp +++ b/src/svg/graphics/qt/SVGPaintServerRadialGradientQt.cpp @@ -1,101 +1,101 @@ /* Copyright (C) 2006 Nikolas Zimmermann This file is part of the KDE project This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License aint with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "wtf/Platform.h" #if ENABLE(SVG) #include "SVGPaintServerRadialGradient.h" /*#include "GraphicsContext.h"*/ #include "RenderPath.h" #include #include #include #include namespace WebCore { QGradient SVGPaintServerRadialGradient::setupGradient(QPainter *painter, QPainterPath *painterPath, const RenderObject *object) const { Q_UNUSED(object); //QPainter* painter(context ? context->platformContext() : 0); //Q_ASSERT(painter); //QPainterPath* path(context ? context->currentPath() : 0); //Q_ASSERT(path); QPainterPath *path(painterPath); //RenderStyle* renderStyle = object->style(); QMatrix mat = painter->matrix(); double cx, fx, cy, fy, r; if (boundingBoxMode()) { QRectF bbox = path->boundingRect(); cx = double(bbox.left()) + (double(gradientCenter().x() / 100.0) * double(bbox.width())); cy = double(bbox.top()) + (double(gradientCenter().y() / 100.0) * double(bbox.height())); fx = double(bbox.left()) + (double(gradientFocal().x() / 100.0) * double(bbox.width())) - cx; fy = double(bbox.top()) + (double(gradientFocal().y() / 100.0) * double(bbox.height())) - cy; r = double(gradientRadius() / 100.0) * (sqrt(pow(bbox.width(), 2) + pow(bbox.height(), 2))); float width = bbox.width(); float height = bbox.height(); int diff = int(width - height); // allow slight tolerance if (!(diff > -2 && diff < 2)) { // make elliptical or circular depending on bbox aspect ratio float ratioX = (width / height); float ratioY = (height / width); mat.scale((width > height) ? 1 : ratioX, (width > height) ? ratioY : 1); } } else { cx = gradientCenter().x(); cy = gradientCenter().y(); fx = gradientFocal().x(); fy = gradientFocal().y(); fx -= cx; fy -= cy; r = gradientRadius(); } if (sqrt(fx * fx + fy * fy) > r) { // Spec: If (fx, fy) lies outside the circle defined by (cx, cy) and r, set (fx, fy) // to the point of intersection of the line through (fx, fy) and the circle. double angle = atan2(fy, fx); fx = int(cos(angle) * r) - 1; fy = int(sin(angle) * r) - 1; } - // qDebug() << "cx:" << cx << "cy:" << cy << "radius:" << gradientRadius() << "fx:" << fx + cx << "fy:" << fy + cy << endl; + // qDebug() << "cx:" << cx << "cy:" << cy << "radius:" << gradientRadius() << "fx:" << fx + cx << "fy:" << fy + cy; QRadialGradient gradient(QPointF(cx, cy), gradientRadius(), QPointF(fx + cx, fy + cy)); return gradient; } } // namespace WebCore #endif diff --git a/src/ui/findbar/khtmlfind.cpp b/src/ui/findbar/khtmlfind.cpp index ff26517..67c84bd 100644 --- a/src/ui/findbar/khtmlfind.cpp +++ b/src/ui/findbar/khtmlfind.cpp @@ -1,621 +1,621 @@ /* This file is part of the KDE project * * Copyright (C) 2008 Bernhard Beschow * (C) 2009 Germain Garand * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "khtmlfind_p.h" #include "khtml_part.h" #include "khtmlviewbar.h" #include "khtmlfindbar.h" #include "dom/html_document.h" #include "html/html_documentimpl.h" #include "rendering/render_text.h" #include "rendering/render_replaced.h" #include "xml/dom_selection.h" #include "khtmlview.h" #include #include "rendering/render_form.h" #define d this using khtml::RenderPosition; using namespace DOM; KHTMLFind::KHTMLFind(KHTMLPart *part, KHTMLFind *parent) : m_part(part), m_find(nullptr), m_parent(parent), m_findDialog(nullptr) { connect(part, SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged())); } KHTMLFind::~KHTMLFind() { d->m_find = nullptr; // deleted by its parent, the view. } void KHTMLFind::findTextBegin() { d->m_findPos = -1; d->m_findNode = nullptr; d->m_findPosEnd = -1; d->m_findNodeEnd = nullptr; d->m_findPosStart = -1; d->m_findNodeStart = nullptr; d->m_findNodePrevious = nullptr; delete d->m_find; d->m_find = nullptr; } bool KHTMLFind::initFindNode(bool selection, bool reverse, bool fromCursor) { if (m_part->document().isNull()) { return false; } DOM::NodeImpl *firstNode = nullptr; if (m_part->document().isHTMLDocument()) { firstNode = m_part->htmlDocument().body().handle(); } else { firstNode = m_part->document().handle(); } if (!firstNode) { //qDebug() << "no first node (body or doc) -> return false"; return false; } if (selection && m_part->hasSelection()) { //qDebug() << "using selection"; const Selection &sel = m_part->caret(); if (!fromCursor) { d->m_findNode = reverse ? sel.end().node() : sel.start().node(); d->m_findPos = reverse ? sel.end().offset() : sel.start().offset(); } d->m_findNodeEnd = reverse ? sel.start().node() : sel.end().node(); d->m_findPosEnd = reverse ? sel.start().offset() : sel.end().offset(); d->m_findNodeStart = !reverse ? sel.start().node() : sel.end().node(); d->m_findPosStart = !reverse ? sel.start().offset() : sel.end().offset(); d->m_findNodePrevious = d->m_findNodeStart; } else { // whole document //qDebug() << "whole doc"; if (!fromCursor) { d->m_findNode = firstNode; d->m_findPos = reverse ? -1 : 0; } d->m_findNodeEnd = reverse ? firstNode : nullptr; d->m_findPosEnd = reverse ? 0 : -1; d->m_findNodeStart = !reverse ? firstNode : nullptr; d->m_findPosStart = !reverse ? 0 : -1; d->m_findNodePrevious = d->m_findNodeStart; if (reverse) { // Need to find out the really last object, to start from it khtml::RenderObject *obj = d->m_findNode ? d->m_findNode->renderer() : nullptr; if (obj) { // find the last object in the render tree while (obj->lastChild()) { obj = obj->lastChild(); } // now get the last object with a NodeImpl associated while (!obj->element() && obj->objectAbove()) { obj = obj->objectAbove(); } d->m_findNode = obj->element(); } } } return true; } void KHTMLFind::deactivate() { // qDebug(); d->m_lastFindState.options = d->m_findDialog->options(); d->m_lastFindState.history = d->m_findDialog->findHistory(); if (!m_parent) { d->m_findDialog->hide(); d->m_findDialog->disconnect(); d->m_findDialog->deleteLater(); } d->m_findDialog = nullptr; // if the selection is limited to a single link, that link gets focus const DOM::Selection sel = m_part->caret(); if (sel.start().node() == sel.end().node()) { bool isLink = false; // checks whether the node has a parent DOM::NodeImpl *parent = sel.start().node(); while (parent) { if (parent->nodeType() == Node::ELEMENT_NODE && parent->id() == ID_A) { isLink = true; break; } parent = parent->parentNode(); } if (isLink == true) { static_cast(m_part->document().handle())->setFocusNode(parent); } } } void KHTMLFind::slotFindDestroyed() { d->m_find = nullptr; } void KHTMLFind::activate() { // First do some init to make sure we can search in this frame if (m_part->document().isNull()) { return; } // Raise if already opened if (d->m_findDialog && !m_parent) { m_part->pBottomViewBar()->showBarWidget(d->m_findDialog); return; } // The lineedit of the dialog would make khtml lose its selection, otherwise #ifndef QT_NO_CLIPBOARD disconnect(qApp->clipboard(), SIGNAL(selectionChanged()), m_part, SLOT(slotClearSelection())); #endif if (m_parent) { d->m_findDialog = m_parent->findBar(); } else { // Now show the dialog in which the user can choose options. d->m_findDialog = new KHTMLFindBar(m_part->widget()); d->m_findDialog->setHasSelection(m_part->hasSelection()); d->m_findDialog->setHasCursor(d->m_findNode != nullptr); #if 0 if (d->m_findNode) { // has a cursor -> default to 'FromCursor' d->m_lastFindState.options |= KFind::FromCursor; } #endif // TODO? optionsDialog.setPattern( d->m_lastFindState.text ); d->m_findDialog->setFindHistory(d->m_lastFindState.history); d->m_findDialog->setOptions(d->m_lastFindState.options); d->m_findDialog->setFocus(); d->m_lastFindState.options = -1; // force update in findTextNext d->m_lastFindState.last_dir = -1; m_part->pBottomViewBar()->addBarWidget(d->m_findDialog); m_part->pBottomViewBar()->showBarWidget(d->m_findDialog); connect(d->m_findDialog, SIGNAL(searchChanged()), this, SLOT(slotSearchChanged())); connect(d->m_findDialog, SIGNAL(findNextClicked()), this, SLOT(slotFindNext())); connect(d->m_findDialog, SIGNAL(findPreviousClicked()), this, SLOT(slotFindPrevious())); connect(d->m_findDialog, SIGNAL(hideMe()), this, SLOT(deactivate())); } #ifndef QT_NO_CLIPBOARD connect(qApp->clipboard(), SIGNAL(selectionChanged()), m_part, SLOT(slotClearSelection())); #endif if (m_findDialog) { createNewKFind(m_findDialog->pattern(), 0 /*options*/, m_findDialog, nullptr); } else if (m_parent && m_parent->find()) { createNewKFind(m_parent->find()->pattern(), m_parent->find()->options(), static_cast(m_parent->find()->parent()), nullptr); } } // ### this crawling through the render tree sucks. There should be another way to // do that. static inline KHTMLPart *innerPart(khtml::RenderObject *ro) { if (!ro || !ro->isWidget() || ro->isFormElement()) { return nullptr; } KHTMLView *v = qobject_cast(static_cast(ro)->widget()); return v ? v->part() : nullptr; } static inline KHTMLPart *innerPartFromNode(DOM::NodeImpl *node) { return (node && node->renderer() ? innerPart(node->renderer()) : nullptr); } void KHTMLFind::createNewKFind(const QString &str, long options, QWidget *parent, KFindDialog *findDialog) { // First do some init to make sure we can search in this frame if (m_part->document().isNull()) { return; } if (m_findNode) { if (KHTMLPart *p = innerPartFromNode(m_findNode)) { p->clearSelection(); p->findTextBegin(); } } // Create the KFind object delete d->m_find; d->m_find = new KFind(str, options, parent, findDialog); d->m_find->closeFindNextDialog(); // we use KFindDialog non-modal, so we don't want other dlg popping up connect(d->m_find, SIGNAL(highlight(QString,int,int)), this, SLOT(slotHighlight(QString,int,int))); connect(d->m_find, SIGNAL(destroyed()), this, SLOT(slotFindDestroyed())); //connect(d->m_find, SIGNAL(findNext()), // this, SLOT(slotFindNext()) ); if (!findDialog) { d->m_lastFindState.options = options; initFindNode(options & KFind::SelectedText, options & KFind::FindBackwards, options & KFind::FromCursor); } } bool KHTMLFind::findTextNext(bool reverse) { if (!d->m_find) { // We didn't show the find dialog yet, let's do it then (#49442) activate(); // FIXME Ugly hack: activate() may not create KFind object, so check whether it was created if (!d->m_find) { return false; } // It also means the user is trying to match a previous pattern, so try and // restore the last saved pattern. if (!m_parent && (!d->m_findDialog || !d->m_findDialog->restoreLastPatternFromHistory())) { return false; } } long options = 0; if (d->m_findDialog) { // 0 when we close the dialog // there is a search dialog // make sure pattern from search dialog is used // (### in fact pattern changes should always trigger a reconstruction of the KFind object cf. slotSearchChanged // - so make this an assert) if ((d->m_find->pattern() != d->m_findDialog->pattern())) { d->m_find->setPattern(d->m_findDialog->pattern()); d->m_find->resetCounts(); } // make sure options from search dialog are used options = d->m_findDialog->options(); if (d->m_lastFindState.options != options) { d->m_find->setOptions(options); if (options & KFind::SelectedText) { //#### FIXME find in selection for frames! Q_ASSERT(m_part->hasSelection()); } long difference = d->m_lastFindState.options ^ options; if (difference & (KFind::SelectedText | KFind::FromCursor)) { // Important options changed -> reset search range (void) initFindNode(options & KFind::SelectedText, options & KFind::FindBackwards, options & KFind::FromCursor); } d->m_lastFindState.options = options; } } else { // no dialog options = d->m_lastFindState.options; } // only adopt options for search direction manually if (reverse) { options = options ^ KFind::FindBackwards; } // make sure our options are used by KFind if (d->m_find->options() != options) { d->m_find->setOptions(options); } // Changing find direction. Start and end nodes must be switched. // Additionally since d->m_findNode points after the last node // that was searched, it needs to be "after" it in the opposite direction. if (d->m_lastFindState.last_dir != -1 && bool(d->m_lastFindState.last_dir) != bool(options & KFind::FindBackwards)) { qSwap(d->m_findNodeEnd, d->m_findNodeStart); qSwap(d->m_findPosEnd, d->m_findPosStart); qSwap(d->m_findNode, d->m_findNodePrevious); // d->m_findNode now point at the end of the last searched line - advance one node khtml::RenderObject *obj = d->m_findNode ? d->m_findNode->renderer() : nullptr; khtml::RenderObject *end = d->m_findNodeEnd ? d->m_findNodeEnd->renderer() : nullptr; if (obj == end) { obj = nullptr; } else if (obj) { do { obj = (options & KFind::FindBackwards) ? obj->objectAbove() : obj->objectBelow(); } while (obj && (!obj->element() || obj->isInlineContinuation())); } if (obj) { d->m_findNode = obj->element(); } else { // already at end, start again (void) initFindNode(options & KFind::SelectedText, options & KFind::FindBackwards, options & KFind::FromCursor); } } d->m_lastFindState.last_dir = (options & KFind::FindBackwards) ? 1 : 0; int numMatchesOld = m_find->numMatches(); KFind::Result res = KFind::NoMatch; khtml::RenderObject *obj = d->m_findNode ? d->m_findNode->renderer() : nullptr; khtml::RenderObject *end = d->m_findNodeEnd ? d->m_findNodeEnd->renderer() : nullptr; //qDebug() << "obj=" << obj << " end=" << end; while (res == KFind::NoMatch) { if (d->m_find->needData()) { if (!obj) { //qDebug() << "obj=0 -> done"; break; // we're done } //qDebug() << " gathering data"; // First make up the QString for the current 'line' (i.e. up to \n) // We also want to remember the DOMNode for every portion of the string. // We store this in an index->node list. d->m_stringPortions.clear(); bool newLine = false; QString str; DOM::NodeImpl *lastNode = d->m_findNode; while (obj && !newLine) { // Grab text from render object QString s; if (obj->renderName() == QLatin1String("RenderTextArea")) { s = static_cast(obj)->text(); s = s.replace(0xa0, ' '); } else if (obj->renderName() == QLatin1String("RenderLineEdit")) { khtml::RenderLineEdit *parentLine = static_cast(obj); if (parentLine->widget()->echoMode() == QLineEdit::Normal) { s = parentLine->widget()->text(); } s = s.replace(0xa0, ' '); } else if (obj->isText()) { bool isLink = false; // checks whether the node has a parent if (options & KHTMLPart::FindLinksOnly) { DOM::NodeImpl *parent = obj->element(); while (parent) { if (parent->nodeType() == Node::ELEMENT_NODE && parent->id() == ID_A) { isLink = true; break; } parent = parent->parentNode(); } } else { isLink = true; } if (isLink) { s = static_cast(obj)->data().string(); s = s.replace(0xa0, ' '); } } else if (KHTMLPart *p = innerPart(obj)) { if (p->pFindTextNextInThisFrame(reverse)) { numMatchesOld++; res = KFind::Match; lastNode = obj->element(); break; } } else if (obj->isBR()) { s = '\n'; } else if (!obj->isInline() && !str.isEmpty()) { s = '\n'; } if (lastNode == d->m_findNodeEnd) { s.truncate(d->m_findPosEnd); } if (!s.isEmpty()) { newLine = s.indexOf('\n') != -1; // did we just get a newline? if (!(options & KFind::FindBackwards)) { //qDebug() << "StringPortion: " << index << "-" << index+s.length()-1 << " -> " << lastNode; d->m_stringPortions.append(StringPortion(str.length(), lastNode)); str += s; } else { // KFind itself can search backwards, so str must not be built backwards for (QList::Iterator it = d->m_stringPortions.begin(); it != d->m_stringPortions.end(); ++it) { (*it).index += s.length(); } d->m_stringPortions.prepend(StringPortion(0, lastNode)); str.prepend(s); } } // Compare obj and end _after_ we processed the 'end' node itself if (obj == end) { obj = nullptr; } else { // Move on to next object (note: if we found a \n already, then obj (and lastNode) // will point to the _next_ object, i.e. they are in advance. do { // We advance until the next RenderObject that has a NodeImpl as its element(). // Otherwise (if we keep the 'last node', and it has a '\n') we might be stuck // on that object forever... obj = (options & KFind::FindBackwards) ? obj->objectAbove() : obj->objectBelow(); } while (obj && (!obj->element() || obj->isInlineContinuation())); } if (obj) { lastNode = obj->element(); } else { lastNode = nullptr; } } // end while if (!str.isEmpty()) { d->m_find->setData(str, d->m_findPos); } d->m_findPos = -1; // not used during the findnext loops. Only during init. d->m_findNodePrevious = d->m_findNode; d->m_findNode = lastNode; } if (!d->m_find->needData() && !(res == KFind::Match)) { // happens if str was empty // Let KFind inspect the text fragment, and emit highlighted if a match is found res = d->m_find->find(); } } // end while if (res == KFind::NoMatch) { // i.e. we're done // qDebug() << "No more matches."; if (!(options & KHTMLPart::FindNoPopups) && d->m_find->shouldRestart()) { // qDebug() << "Restarting"; initFindNode(false, options & KFind::FindBackwards, false); d->m_find->resetCounts(); findTextNext(reverse); } else { // really done // qDebug() << "Finishing"; //delete d->m_find; //d->m_find = 0L; initFindNode(false, options & KFind::FindBackwards, false); d->m_find->resetCounts(); d->m_part->clearSelection(); } // qDebug() << "Dialog closed."; } if (m_findDialog != nullptr) { m_findDialog->setFoundMatch(res == KFind::Match); m_findDialog->setAtEnd(m_find->numMatches() < numMatchesOld); } return res == KFind::Match; } void KHTMLFind::slotHighlight(const QString & /*text*/, int index, int length) { //qDebug() << "slotHighlight index=" << index << " length=" << length; QList::Iterator it = d->m_stringPortions.begin(); const QList::Iterator itEnd = d->m_stringPortions.end(); QList::Iterator prev = it; // We stop at the first portion whose index is 'greater than', and then use the previous one while (it != itEnd && (*it).index <= index) { prev = it; ++it; } Q_ASSERT(prev != itEnd); DOM::NodeImpl *node = (*prev).node; Q_ASSERT(node); Selection sel(RenderPosition(node, index - (*prev).index).position()); khtml::RenderObject *obj = node->renderer(); khtml::RenderTextArea *renderTextArea = nullptr; khtml::RenderLineEdit *renderLineEdit = nullptr; Q_ASSERT(obj); if (obj) { int x = 0, y = 0; if (obj->renderName() == QLatin1String("RenderTextArea")) { renderTextArea = static_cast(obj); } if (obj->renderName() == QLatin1String("RenderLineEdit")) { renderLineEdit = static_cast(obj); } if (!renderLineEdit && !renderTextArea) //if (static_cast(node->renderer()) // ->posOfChar(d->m_startOffset, x, y)) { int dummy; static_cast(node->renderer()) ->caretPos(RenderPosition::fromDOMPosition(sel.start()).renderedOffset(), false, x, y, dummy, dummy); // more precise than posOfChar //qDebug() << "topleft: " << x << "," << y; if (x != -1 || y != -1) { int gox = m_part->view()->contentsX(); if (x + 50 > m_part->view()->contentsX() + m_part->view()->visibleWidth()) { gox = x - m_part->view()->visibleWidth() + 50; } if (x - 10 < m_part->view()->contentsX()) { gox = x - m_part->view()->visibleWidth() - 10; } if (gox < 0) { gox = 0; } m_part->view()->setContentsPos(gox, y - 50); } } } // Now look for end node it = prev; // no need to start from beginning again while (it != itEnd && (*it).index < index + length) { prev = it; ++it; } Q_ASSERT(prev != itEnd); sel.moveTo(sel.start(), RenderPosition((*prev).node, index + length - (*prev).index).position()); #if 0 // qDebug() << "slotHighlight: " << d->m_selectionStart.handle() << "," << d->m_startOffset << " - " << - d->m_selectionEnd.handle() << "," << d->m_endOffset << endl; + d->m_selectionEnd.handle() << "," << d->m_endOffset; it = d->m_stringPortions.begin(); for (; it != d->m_stringPortions.end(); ++it) // qDebug() << " StringPortion: from index=" << (*it).index << " -> node=" << (*it).node; #endif if (renderTextArea) { renderTextArea->highLightWord(length, sel.end().offset() - length); } else if (renderLineEdit) { renderLineEdit->highLightWord(length, sel.end().offset() - length); } else { m_part->setCaret(sel); // d->m_doc->updateSelection(); if (sel.end().node()->renderer()) { int x, y, height, dummy; static_cast(sel.end().node()->renderer()) ->caretPos(RenderPosition::fromDOMPosition(sel.end()).renderedOffset(), false, x, y, dummy, height); // more precise than posOfChar //qDebug() << "bottomright: " << x << "," << y+height; } } m_part->emitSelectionChanged(); } void KHTMLFind::slotSelectionChanged() { if (d->m_findDialog) { d->m_findDialog->setHasSelection(m_part->hasSelection()); } } void KHTMLFind::slotSearchChanged() { createNewKFind(m_findDialog->pattern(), m_findDialog->options(), m_findDialog, nullptr); findTextNext(); } void KHTMLFind::slotFindNext() { findTextNext(); } void KHTMLFind::slotFindPrevious() { findTextNext(true); // find backwards } diff --git a/src/xml/dom_docimpl.cpp b/src/xml/dom_docimpl.cpp index eca670f..c3b50e4 100644 --- a/src/xml/dom_docimpl.cpp +++ b/src/xml/dom_docimpl.cpp @@ -1,3419 +1,3419 @@ /** * This file is part of the DOM implementation for KDE. * * Copyright (C) 1999 Lars Knoll (knoll@kde.org) * (C) 1999 Antti Koivisto (koivisto@kde.org) * (C) 2001 Dirk Mueller (mueller@kde.org) * (C) 2002-2006 Apple Computer, Inc. * (C) 2006 Allan Sandfeld Jensen (kde@carewolf.com) * (C) 2005 Frerich Raabe * (C) 2010 Maksim Orlovich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "dom_docimpl.h" #include #include "dom_textimpl.h" #include "dom_xmlimpl.h" #include "dom2_rangeimpl.h" #include "dom2_eventsimpl.h" #include "xml_tokenizer.h" #include #include "dom_restyler.h" #include #include #include #include #include #include #include //Added by qt3to4: #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // SVG (WebCore) #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef FOCUS_EVENT // for win32, MinGW template class QStack; using namespace DOM; using namespace khtml; // ------------------------------------------------------------------------ DOMImplementationImpl::DOMImplementationImpl() { } DOMImplementationImpl::~DOMImplementationImpl() { } bool DOMImplementationImpl::hasFeature(const DOMString &feature, const DOMString &version) { // ### update when we (fully) support the relevant features QString lower = feature.string().toLower(); if ((lower == "html" || lower == "xml") && (version.isEmpty() || version == "1.0" || version == "2.0")) { return true; } // ## Do we support Core Level 3 ? if ((lower == "core") && (version.isEmpty() || version == "2.0")) { return true; } if ((lower == "traversal") && (version.isEmpty() || version == "2.0")) { return true; } if ((lower == "css") && (version.isEmpty() || version == "2.0")) { return true; } if ((lower == "events" || lower == "uievents" || lower == "mouseevents" || lower == "mutationevents" || lower == "htmlevents" || lower == "textevents") && (version.isEmpty() || version == "2.0" || version == "3.0")) { return true; } if (lower == "selectors-api" && version == "1.0") { return true; } return false; } DocumentTypeImpl *DOMImplementationImpl::createDocumentType(const DOMString &qualifiedName, const DOMString &publicId, const DOMString &systemId, int &exceptioncode) { // Not mentioned in spec: throw NAMESPACE_ERR if no qualifiedName supplied if (qualifiedName.isNull()) { exceptioncode = DOMException::NAMESPACE_ERR; return nullptr; } // INVALID_CHARACTER_ERR: Raised if the specified qualified name contains an illegal character. if (!Element::khtmlValidQualifiedName(qualifiedName)) { exceptioncode = DOMException::INVALID_CHARACTER_ERR; return nullptr; } // NAMESPACE_ERR: Raised if the qualifiedName is malformed. // Added special case for the empty string, which seems to be a common pre-DOM2 misuse if (!qualifiedName.isEmpty() && Element::khtmlMalformedQualifiedName(qualifiedName)) { exceptioncode = DOMException::NAMESPACE_ERR; return nullptr; } return new DocumentTypeImpl(this, nullptr, qualifiedName, publicId, systemId); } DocumentImpl *DOMImplementationImpl::createDocument(const DOMString &namespaceURI, const DOMString &qualifiedName, DocumentTypeImpl *dtype, KHTMLView *v, int &exceptioncode) { exceptioncode = 0; if (!checkQualifiedName(qualifiedName, namespaceURI, nullptr, true/*nameCanBeNull*/, true /*nameCanBeEmpty, see #61650*/, &exceptioncode)) { return nullptr; } // WRONG_DOCUMENT_ERR: Raised if doctype has already been used with a different document or was // created from a different implementation. // We elide the "different implementation" case here, since we're not doing interop // of different implementations, and different impl objects exist only for // isolation reasons if (dtype && dtype->document()) { exceptioncode = DOMException::WRONG_DOCUMENT_ERR; return nullptr; } // ### view can be 0 which can cause problems DocumentImpl *doc; if (namespaceURI == XHTML_NAMESPACE) { doc = new HTMLDocumentImpl(v); } else { doc = new DocumentImpl(v); } if (dtype) { dtype->setDocument(doc); doc->appendChild(dtype, exceptioncode); } // the document must be created empty if all parameters are null // (or empty for qName/nsURI as a tolerance) - see DOM 3 Core. if (dtype || !qualifiedName.isEmpty() || !namespaceURI.isEmpty()) { ElementImpl *element = doc->createElementNS(namespaceURI, qualifiedName); doc->appendChild(element, exceptioncode); if (exceptioncode) { delete element; delete doc; return nullptr; } } return doc; } CSSStyleSheetImpl *DOMImplementationImpl::createCSSStyleSheet(DOMStringImpl *title, DOMStringImpl *media, int &/*exceptioncode*/) { // ### TODO : media could have wrong syntax, in which case we should // generate an exception. CSSStyleSheetImpl *parent = nullptr; CSSStyleSheetImpl *sheet = new CSSStyleSheetImpl(parent, DOMString()); sheet->setMedia(new MediaListImpl(sheet, media, true /*fallbackToDescriptor*/)); sheet->setTitle(DOMString(title)); return sheet; } DocumentImpl *DOMImplementationImpl::createDocument(KHTMLView *v) { DocumentImpl *doc = new DocumentImpl(v); return doc; } XMLDocumentImpl *DOMImplementationImpl::createXMLDocument(KHTMLView *v) { XMLDocumentImpl *doc = new XMLDocumentImpl(v); return doc; } HTMLDocumentImpl *DOMImplementationImpl::createHTMLDocument(KHTMLView *v) { HTMLDocumentImpl *doc = new HTMLDocumentImpl(v); return doc; } // create SVG document WebCore::SVGDocument *DOMImplementationImpl::createSVGDocument(KHTMLView *v) { WebCore::SVGDocument *doc = new WebCore::SVGDocument(v); return doc; } HTMLDocumentImpl *DOMImplementationImpl::createHTMLDocument(const DOMString &title) { HTMLDocumentImpl *r = createHTMLDocument(nullptr /* ### create a view otherwise it doesn't work */); r->open(); r->write(QLatin1String("") + title.string() + QLatin1String("")); return r; } // ------------------------------------------------------------------------ ElementMappingCache::ElementMappingCache(): m_dict() { } ElementMappingCache::~ElementMappingCache() { qDeleteAll(m_dict); } void ElementMappingCache::add(const DOMString &id, ElementImpl *nd) { if (id.isEmpty()) { return; } ItemInfo *info = m_dict.value(id); if (info) { info->ref++; info->nd = nullptr; //Now ambigous } else { ItemInfo *info = new ItemInfo(); info->ref = 1; info->nd = nd; m_dict.insert(id, info); } } void ElementMappingCache::set(const DOMString &id, ElementImpl *nd) { if (id.isEmpty()) { return; } assert(m_dict.contains(id)); ItemInfo *info = m_dict.value(id); info->nd = nd; } void ElementMappingCache::remove(const DOMString &id, ElementImpl *nd) { if (id.isEmpty()) { return; } assert(m_dict.contains(id)); ItemInfo *info = m_dict.value(id); info->ref--; if (info->ref == 0) { m_dict.take(id); delete info; } else { if (info->nd == nd) { info->nd = nullptr; } } } bool ElementMappingCache::contains(const DOMString &id) { if (id.isEmpty()) { return false; } return m_dict.contains(id); } ElementMappingCache::ItemInfo *ElementMappingCache::get(const DOMString &id) { if (id.isEmpty()) { return nullptr; } return m_dict.value(id); } typedef QList ChangedDocuments; Q_GLOBAL_STATIC(ChangedDocuments, s_changedDocuments) // KHTMLView might be 0 DocumentImpl::DocumentImpl(KHTMLView *v) : NodeBaseImpl(nullptr), m_svgExtensions(nullptr), m_counterDict(), m_imageLoadEventTimer(0) { m_document.resetSkippingRef(this); //Make document return us.. m_selfOnlyRefCount = 0; m_paintDevice = nullptr; //m_decoderMibEnum = 0; m_textColor = Qt::black; m_view = v; m_renderArena.reset(); KHTMLGlobal::registerDocumentImpl(this); if (v) { m_docLoader = new DocLoader(v->part(), this); setPaintDevice(m_view); } else { m_docLoader = new DocLoader(nullptr, this); } visuallyOrdered = false; m_bParsing = false; m_docChanged = false; m_elemSheet = nullptr; m_tokenizer = nullptr; m_decoder = nullptr; m_doctype = nullptr; m_implementation = nullptr; pMode = Strict; hMode = XHtml; m_htmlCompat = false; m_textColor = "#000000"; m_focusNode = nullptr; m_hoverNode = nullptr; m_activeNode = nullptr; m_defaultView = new AbstractViewImpl(this); m_defaultView->ref(); m_listenerTypes = 0; m_styleSheets = new StyleSheetListImpl(this); m_styleSheets->ref(); m_addedStyleSheets = nullptr; m_inDocument = true; m_styleSelectorDirty = false; m_styleSelector = nullptr; m_styleSheetListDirty = true; m_inStyleRecalc = false; m_pendingStylesheets = 0; m_ignorePendingStylesheets = false; m_async = true; m_hadLoadError = false; m_docLoading = false; m_bVariableLength = false; m_inSyncLoad = nullptr; m_loadingXMLDoc = nullptr; m_documentElement = nullptr; m_cssTarget = nullptr; m_jsEditor = nullptr; m_dynamicDomRestyler = new khtml::DynamicDomRestyler(); m_stateRestorePos = 0; m_windowEventTarget = new WindowEventTargetImpl(this); m_windowEventTarget->ref(); for (int c = 0; c < NumTreeVersions; ++c) { m_domTreeVersions[c] = 0; } } void DocumentImpl::removedLastRef() { if (m_selfOnlyRefCount) { /* In this case, the only references to us are from children, so we have a cycle. We'll try to break it by disconnecting the children from us; this sucks/is wrong, but it's pretty much the best we can do without tracing. Of course, if dumping the children causes the refcount from them to drop to 0 we can get killed right here, so better hold a temporary reference, too */ DocPtr guard(this); // we must make sure not to be retaining any of our children through // these extra pointers or we will create a reference cycle if (m_doctype) { m_doctype->deref(); m_doctype = nullptr; } if (m_cssTarget) { m_cssTarget->deref(); m_cssTarget = nullptr; } if (m_focusNode) { m_focusNode->deref(); m_focusNode = nullptr; } if (m_hoverNode) { m_hoverNode->deref(); m_hoverNode = nullptr; } if (m_activeNode) { m_activeNode->deref(); m_activeNode = nullptr; } if (m_documentElement) { m_documentElement->deref(); m_documentElement = nullptr; } removeChildren(); delete m_tokenizer; m_tokenizer = nullptr; } else { delete this; } } DocumentImpl::~DocumentImpl() { //Important: if you need to remove stuff here, //you may also have to fix removedLastRef() above - M.O. assert(!m_render); QHashIterator it(m_nodeListCache); while (it.hasNext()) { it.next().value()->deref(); } if (m_loadingXMLDoc) { m_loadingXMLDoc->deref(this); } if (s_changedDocuments() && m_docChanged) { s_changedDocuments()->removeAll(this); } delete m_tokenizer; m_document.resetSkippingRef(nullptr); delete m_styleSelector; delete m_docLoader; if (m_elemSheet) { m_elemSheet->deref(); } if (m_doctype) { m_doctype->deref(); } if (m_implementation) { m_implementation->deref(); } delete m_dynamicDomRestyler; delete m_jsEditor; m_defaultView->deref(); m_styleSheets->deref(); if (m_addedStyleSheets) { m_addedStyleSheets->deref(); } if (m_cssTarget) { m_cssTarget->deref(); } if (m_focusNode) { m_focusNode->deref(); } if (m_hoverNode) { m_hoverNode->deref(); } if (m_activeNode) { m_activeNode->deref(); } if (m_documentElement) { m_documentElement->deref(); } m_windowEventTarget->deref(); qDeleteAll(m_counterDict); m_renderArena.reset(); KHTMLGlobal::deregisterDocumentImpl(this); } DOMImplementationImpl *DocumentImpl::implementation() const { if (!m_implementation) { m_implementation = new DOMImplementationImpl(); m_implementation->ref(); } return m_implementation; } void DocumentImpl::childrenChanged() { // invalidate the document element we have cached in case it was replaced if (m_documentElement) { m_documentElement->deref(); } m_documentElement = nullptr; // same for m_docType if (m_doctype) { m_doctype->deref(); } m_doctype = nullptr; } ElementImpl *DocumentImpl::documentElement() const { if (!m_documentElement) { NodeImpl *n = firstChild(); while (n && n->nodeType() != Node::ELEMENT_NODE) { n = n->nextSibling(); } m_documentElement = static_cast(n); if (m_documentElement) { m_documentElement->ref(); } } return m_documentElement; } DocumentTypeImpl *DocumentImpl::doctype() const { if (!m_doctype) { NodeImpl *n = firstChild(); while (n && n->nodeType() != Node::DOCUMENT_TYPE_NODE) { n = n->nextSibling(); } m_doctype = static_cast(n); if (m_doctype) { m_doctype->ref(); } } return m_doctype; } ElementImpl *DocumentImpl::createElement(const DOMString &name, int *pExceptioncode) { if (pExceptioncode && !Element::khtmlValidQualifiedName(name)) { *pExceptioncode = DOMException::INVALID_CHARACTER_ERR; return nullptr; } PrefixName prefix; LocalName localName; bool htmlCompat = htmlMode() != XHtml; splitPrefixLocalName(name, prefix, localName, htmlCompat); XMLElementImpl *e = new XMLElementImpl(document(), emptyNamespaceName, localName, prefix); e->setHTMLCompat(htmlCompat); // Not a real HTML element, but inside an html-compat doc all tags are uppercase. return e; } AttrImpl *DocumentImpl::createAttribute(const DOMString &tagName, int *pExceptioncode) { if (pExceptioncode && !Element::khtmlValidAttrName(tagName)) { *pExceptioncode = DOMException::INVALID_CHARACTER_ERR; return nullptr; } PrefixName prefix; LocalName localName; bool htmlCompat = (htmlMode() != XHtml); splitPrefixLocalName(tagName, prefix, localName, htmlCompat); AttrImpl *attr = new AttrImpl(nullptr, document(), NamespaceName::fromId(emptyNamespace), localName, prefix, DOMString("").implementation()); attr->setHTMLCompat(htmlCompat); return attr; } DocumentFragmentImpl *DocumentImpl::createDocumentFragment() { return new DocumentFragmentImpl(docPtr()); } CommentImpl *DocumentImpl::createComment(DOMStringImpl *data) { return new CommentImpl(docPtr(), data); } CDATASectionImpl *DocumentImpl::createCDATASection(DOMStringImpl *data, int &exceptioncode) { if (isHTMLDocument()) { exceptioncode = DOMException::NOT_SUPPORTED_ERR; return nullptr; } return new CDATASectionImpl(docPtr(), data); } ProcessingInstructionImpl *DocumentImpl::createProcessingInstruction(const DOMString &target, DOMStringImpl *data) { return new ProcessingInstructionImpl(docPtr(), target, data); } EntityReferenceImpl *DocumentImpl::createEntityReference(const DOMString &name, int &exceptioncode) { if (isHTMLDocument()) { exceptioncode = DOMException::NOT_SUPPORTED_ERR; return nullptr; } return new EntityReferenceImpl(docPtr(), name.implementation()); } EditingTextImpl *DocumentImpl::createEditingTextNode(const DOMString &text) { return new EditingTextImpl(docPtr(), text); } NodeImpl *DocumentImpl::importNode(NodeImpl *importedNode, bool deep, int &exceptioncode) { NodeImpl *result = nullptr; // Not mentioned in spec: throw NOT_FOUND_ERR if evt is null if (!importedNode) { exceptioncode = DOMException::NOT_FOUND_ERR; return nullptr; } if (importedNode->nodeType() == Node::ELEMENT_NODE) { // Why not use cloneNode? ElementImpl *otherElem = static_cast(importedNode); NamedAttrMapImpl *otherMap = static_cast(importedNode)->attributes(true); ElementImpl *tempElementImpl; tempElementImpl = createElementNS(otherElem->namespaceURI(), otherElem->nonCaseFoldedTagName()); tempElementImpl->setHTMLCompat(htmlMode() != XHtml && otherElem->htmlCompat()); result = tempElementImpl; if (otherMap) { for (unsigned i = 0; i < otherMap->length(); i++) { AttrImpl *otherAttr = otherMap->attributeAt(i).createAttr(otherElem, otherElem->docPtr()); tempElementImpl->setAttributeNS(otherAttr->namespaceURI(), otherAttr->name(), otherAttr->nodeValue(), exceptioncode); if (exceptioncode != 0) { break; // ### properly cleanup here } } } } else if (importedNode->nodeType() == Node::TEXT_NODE) { result = createTextNode(static_cast(importedNode)->string()); deep = false; } else if (importedNode->nodeType() == Node::CDATA_SECTION_NODE) { result = createCDATASection(static_cast(importedNode)->string(), exceptioncode); deep = false; } else if (importedNode->nodeType() == Node::ENTITY_REFERENCE_NODE) { result = createEntityReference(importedNode->nodeName(), exceptioncode); } else if (importedNode->nodeType() == Node::PROCESSING_INSTRUCTION_NODE) { result = createProcessingInstruction(importedNode->nodeName(), importedNode->nodeValue().implementation()); deep = false; } else if (importedNode->nodeType() == Node::COMMENT_NODE) { result = createComment(static_cast(importedNode)->string()); deep = false; } else if (importedNode->nodeType() == Node::DOCUMENT_FRAGMENT_NODE) { result = createDocumentFragment(); } else { exceptioncode = DOMException::NOT_SUPPORTED_ERR; } //### FIXME: This should handle Attributes, and a few other things if (deep && result) { for (Node n = importedNode->firstChild(); !n.isNull(); n = n.nextSibling()) { result->appendChild(importNode(n.handle(), true, exceptioncode), exceptioncode); } } return result; } ElementImpl *DocumentImpl::createElementNS(const DOMString &_namespaceURI, const DOMString &_qualifiedName, int *pExceptioncode) { ElementImpl *e = nullptr; int colonPos = -2; // check NAMESPACE_ERR/INVALID_CHARACTER_ERR if (pExceptioncode && !checkQualifiedName(_qualifiedName, _namespaceURI, &colonPos, false/*nameCanBeNull*/, false/*nameCanBeEmpty*/, pExceptioncode)) { return nullptr; } DOMString prefix, localName; splitPrefixLocalName(_qualifiedName.implementation(), prefix, localName, colonPos); if (_namespaceURI == SVG_NAMESPACE) { e = createSVGElement(QualifiedName(prefix, localName, _namespaceURI)); if (e) { return e; } if (!e) { qWarning() << "svg element" << localName << "either is not supported by khtml or it's not a proper svg element"; } } // Regardless of document type (even for HTML), this method will only create HTML // elements if given the namespace explicitly. Further, this method is always // case sensitive, again, even in HTML; however .tagName will case-normalize // in HTML regardless if (_namespaceURI == XHTML_NAMESPACE) { e = createHTMLElement(localName, false /* case sensitive */); int _exceptioncode = 0; if (!prefix.isNull()) { e->setPrefix(prefix, _exceptioncode); } if (_exceptioncode) { if (pExceptioncode) { *pExceptioncode = _exceptioncode; } delete e; return nullptr; } } if (!e) { e = new XMLElementImpl(document(), NamespaceName::fromString(_namespaceURI), LocalName::fromString(localName), PrefixName::fromString(prefix)); } return e; } AttrImpl *DocumentImpl::createAttributeNS(const DOMString &_namespaceURI, const DOMString &_qualifiedName, int *pExceptioncode) { int colonPos = -2; // check NAMESPACE_ERR/INVALID_CHARACTER_ERR if (pExceptioncode && !checkQualifiedName(_qualifiedName, _namespaceURI, &colonPos, false/*nameCanBeNull*/, false/*nameCanBeEmpty*/, pExceptioncode)) { return nullptr; } PrefixName prefix; LocalName localName; bool htmlCompat = _namespaceURI.isNull() && htmlMode() != XHtml; splitPrefixLocalName(_qualifiedName, prefix, localName, false, colonPos); AttrImpl *attr = new AttrImpl(nullptr, document(), NamespaceName::fromString(_namespaceURI), localName, prefix, DOMString("").implementation()); attr->setHTMLCompat(htmlCompat); return attr; } ElementImpl *DocumentImpl::getElementById(const DOMString &elementId) const { ElementMappingCache::ItemInfo *info = m_getElementByIdCache.get(elementId); if (!info) { return nullptr; } //See if cache has an unambiguous answer. if (info->nd) { return info->nd; } //Now we actually have to walk. QStack nodeStack; NodeImpl *current = _first; while (1) { if (!current) { if (nodeStack.isEmpty()) { break; } current = nodeStack.pop(); current = current->nextSibling(); } else { if (current->isElementNode()) { ElementImpl *e = static_cast(current); if (e->getAttribute(ATTR_ID) == elementId) { info->nd = e; return e; } } NodeImpl *child = current->firstChild(); if (child) { nodeStack.push(current); current = child; } else { current = current->nextSibling(); } } } assert(0); //If there is no item with such an ID, we should never get here //qDebug() << "WARNING: *DocumentImpl::getElementById not found " << elementId.string(); return nullptr; } void DocumentImpl::setTitle(const DOMString &_title) { if (_title == m_title && !m_title.isNull()) { return; } m_title = _title; QString titleStr = m_title.string(); for (int i = 0; i < titleStr.length(); ++i) if (titleStr[i] < ' ') { titleStr[i] = ' '; } titleStr = titleStr.simplified(); if (view() && !view()->part()->parentPart()) { if (titleStr.isEmpty()) { // empty title... set window caption as the URL QUrl url = m_url; url.setFragment(QString()); url.setQuery(QString()); titleStr = url.toDisplayString(); } emit view()->part()->setWindowCaption(titleStr); } } DOMString DocumentImpl::nodeName() const { return "#document"; } unsigned short DocumentImpl::nodeType() const { return Node::DOCUMENT_NODE; } ElementImpl *DocumentImpl::createHTMLElement(const DOMString &name, bool caseInsensitive) { LocalName localname = LocalName::fromString(name, caseInsensitive ? IDS_NormalizeLower : IDS_CaseSensitive); uint id = localname.id(); ElementImpl *n = nullptr; switch (id) { case ID_HTML: n = new HTMLHtmlElementImpl(docPtr()); break; case ID_HEAD: n = new HTMLHeadElementImpl(docPtr()); break; case ID_BODY: n = new HTMLBodyElementImpl(docPtr()); break; // head elements case ID_BASE: n = new HTMLBaseElementImpl(docPtr()); break; case ID_LINK: n = new HTMLLinkElementImpl(docPtr()); break; case ID_META: n = new HTMLMetaElementImpl(docPtr()); break; case ID_STYLE: n = new HTMLStyleElementImpl(docPtr()); break; case ID_TITLE: n = new HTMLTitleElementImpl(docPtr()); break; // frames case ID_FRAME: n = new HTMLFrameElementImpl(docPtr()); break; case ID_FRAMESET: n = new HTMLFrameSetElementImpl(docPtr()); break; case ID_IFRAME: n = new HTMLIFrameElementImpl(docPtr()); break; // form elements // ### FIXME: we need a way to set form dependency after we have made the form elements case ID_FORM: n = new HTMLFormElementImpl(docPtr(), false); break; case ID_BUTTON: n = new HTMLButtonElementImpl(docPtr()); break; case ID_FIELDSET: n = new HTMLFieldSetElementImpl(docPtr()); break; case ID_INPUT: n = new HTMLInputElementImpl(docPtr()); break; case ID_ISINDEX: n = new HTMLIsIndexElementImpl(docPtr()); break; case ID_LABEL: n = new HTMLLabelElementImpl(docPtr()); break; case ID_LEGEND: n = new HTMLLegendElementImpl(docPtr()); break; case ID_OPTGROUP: n = new HTMLOptGroupElementImpl(docPtr()); break; case ID_OPTION: n = new HTMLOptionElementImpl(docPtr()); break; case ID_SELECT: n = new HTMLSelectElementImpl(docPtr()); break; case ID_TEXTAREA: n = new HTMLTextAreaElementImpl(docPtr()); break; // lists case ID_DL: n = new HTMLDListElementImpl(docPtr()); break; case ID_DD: n = new HTMLGenericElementImpl(docPtr(), id); break; case ID_DT: n = new HTMLGenericElementImpl(docPtr(), id); break; case ID_UL: n = new HTMLUListElementImpl(docPtr()); break; case ID_OL: n = new HTMLOListElementImpl(docPtr()); break; case ID_DIR: n = new HTMLDirectoryElementImpl(docPtr()); break; case ID_MENU: n = new HTMLMenuElementImpl(docPtr()); break; case ID_LI: n = new HTMLLIElementImpl(docPtr()); break; // formatting elements (block) case ID_DIV: case ID_P: n = new HTMLDivElementImpl(docPtr(), id); break; case ID_BLOCKQUOTE: case ID_H1: case ID_H2: case ID_H3: case ID_H4: case ID_H5: case ID_H6: n = new HTMLGenericElementImpl(docPtr(), id); break; case ID_HR: n = new HTMLHRElementImpl(docPtr()); break; case ID_PLAINTEXT: case ID_XMP: case ID_PRE: case ID_LISTING: n = new HTMLPreElementImpl(docPtr(), id); break; // font stuff case ID_BASEFONT: n = new HTMLBaseFontElementImpl(docPtr()); break; case ID_FONT: n = new HTMLFontElementImpl(docPtr()); break; // ins/del case ID_DEL: case ID_INS: n = new HTMLGenericElementImpl(docPtr(), id); break; // anchor case ID_A: n = new HTMLAnchorElementImpl(docPtr()); break; // images case ID_IMG: case ID_IMAGE: // legacy name n = new HTMLImageElementImpl(docPtr()); break; case ID_CANVAS: n = new HTMLCanvasElementImpl(docPtr()); break; case ID_MAP: n = new HTMLMapElementImpl(docPtr()); /*n = map;*/ break; case ID_AREA: n = new HTMLAreaElementImpl(docPtr()); break; // objects, applets and scripts case ID_APPLET: n = new HTMLAppletElementImpl(docPtr()); break; case ID_OBJECT: n = new HTMLObjectElementImpl(docPtr()); break; case ID_EMBED: n = new HTMLEmbedElementImpl(docPtr()); break; case ID_PARAM: n = new HTMLParamElementImpl(docPtr()); break; case ID_SCRIPT: n = new HTMLScriptElementImpl(docPtr()); break; // media case ID_AUDIO: n = new HTMLAudioElement(docPtr()); break; case ID_VIDEO: n = new HTMLVideoElement(docPtr()); break; case ID_SOURCE: n = new HTMLSourceElement(docPtr()); break; // tables case ID_TABLE: n = new HTMLTableElementImpl(docPtr()); break; case ID_CAPTION: n = new HTMLTableCaptionElementImpl(docPtr()); break; case ID_COLGROUP: case ID_COL: n = new HTMLTableColElementImpl(docPtr(), id); break; case ID_TR: n = new HTMLTableRowElementImpl(docPtr()); break; case ID_TD: case ID_TH: n = new HTMLTableCellElementImpl(docPtr(), id); break; case ID_THEAD: case ID_TBODY: case ID_TFOOT: n = new HTMLTableSectionElementImpl(docPtr(), id, false); break; // inline elements case ID_BR: n = new HTMLBRElementImpl(docPtr()); break; case ID_WBR: n = new HTMLWBRElementImpl(docPtr()); break; case ID_Q: n = new HTMLGenericElementImpl(docPtr(), id); break; // elements with no special representation in the DOM // block: case ID_ADDRESS: case ID_CENTER: n = new HTMLGenericElementImpl(docPtr(), id); break; // inline // %fontstyle case ID_TT: case ID_U: case ID_B: case ID_I: case ID_S: case ID_STRIKE: case ID_BIG: case ID_SMALL: // %phrase case ID_EM: case ID_STRONG: case ID_DFN: case ID_CODE: case ID_SAMP: case ID_KBD: case ID_VAR: case ID_CITE: case ID_ABBR: case ID_ACRONYM: // %special case ID_SUB: case ID_SUP: case ID_SPAN: case ID_NOBR: case ID_BDO: case ID_NOFRAMES: case ID_NOSCRIPT: case ID_NOEMBED: case ID_NOLAYER: n = new HTMLGenericElementImpl(docPtr(), id); break; case ID_MARQUEE: n = new HTMLMarqueeElementImpl(docPtr()); break; // text case ID_TEXT: // qDebug() << "Use document->createTextNode()"; break; default: n = new HTMLGenericElementImpl(docPtr(), localname); break; } assert(n); return n; } // SVG ElementImpl *DocumentImpl::createSVGElement(const QualifiedName &name) { uint id = name.localNameId().id(); - // qDebug() << getPrintableName(name.id()) << endl; - // qDebug() << "svg text: " << getPrintableName(WebCore::SVGNames::textTag.id()) << endl; + // qDebug() << getPrintableName(name.id()); + // qDebug() << "svg text: " << getPrintableName(WebCore::SVGNames::textTag.id()); ElementImpl *n = nullptr; switch (id) { case ID_TEXTPATH: n = new WebCore::SVGTextPathElement(name, docPtr()); break; case ID_TSPAN: n = new WebCore::SVGTSpanElement(name, docPtr()); break; case ID_HKERN: n = new WebCore::SVGHKernElement(name, docPtr()); break; case ID_ALTGLYPH: n = new WebCore::SVGAltGlyphElement(name, docPtr()); break; case ID_FONT: n = new WebCore::SVGFontElement(name, docPtr()); break; } if (id == WebCore::SVGNames::svgTag.localNameId().id()) { n = new WebCore::SVGSVGElement(name, docPtr()); } if (id == WebCore::SVGNames::rectTag.localNameId().id()) { n = new WebCore::SVGRectElement(name, docPtr()); } if (id == WebCore::SVGNames::circleTag.localNameId().id()) { n = new WebCore::SVGCircleElement(name, docPtr()); } if (id == WebCore::SVGNames::ellipseTag.localNameId().id()) { n = new WebCore::SVGEllipseElement(name, docPtr()); } if (id == WebCore::SVGNames::polylineTag.localNameId().id()) { n = new WebCore::SVGPolylineElement(name, docPtr()); } if (id == WebCore::SVGNames::polygonTag.localNameId().id()) { n = new WebCore::SVGPolygonElement(name, docPtr()); } if (id == WebCore::SVGNames::pathTag.localNameId().id()) { n = new WebCore::SVGPathElement(name, docPtr()); } if (id == WebCore::SVGNames::defsTag.localNameId().id()) { n = new WebCore::SVGDefsElement(name, docPtr()); } if (id == WebCore::SVGNames::linearGradientTag.localNameId().id()) { n = new WebCore::SVGLinearGradientElement(name, docPtr()); } if (id == WebCore::SVGNames::radialGradientTag.localNameId().id()) { n = new WebCore::SVGRadialGradientElement(name, docPtr()); } if (id == WebCore::SVGNames::stopTag.localNameId().id()) { n = new WebCore::SVGStopElement(name, docPtr()); } if (id == WebCore::SVGNames::clipPathTag.localNameId().id()) { n = new WebCore::SVGClipPathElement(name, docPtr()); } if (id == WebCore::SVGNames::gTag.localNameId().id()) { n = new WebCore::SVGGElement(name, docPtr()); } if (id == WebCore::SVGNames::useTag.localNameId().id()) { n = new WebCore::SVGUseElement(name, docPtr()); } if (id == WebCore::SVGNames::lineTag.localNameId().id()) { n = new WebCore::SVGLineElement(name, docPtr()); } if (id == WebCore::SVGNames::textTag.localNameId().id()) { n = new WebCore::SVGTextElement(name, docPtr()); } if (id == WebCore::SVGNames::aTag.localNameId().id()) { n = new WebCore::SVGAElement(name, docPtr()); } if (id == WebCore::SVGNames::scriptTag.localNameId().id()) { n = new WebCore::SVGScriptElement(name, docPtr()); } if (id == WebCore::SVGNames::descTag.localNameId().id()) { n = new WebCore::SVGDescElement(name, docPtr()); } if (id == WebCore::SVGNames::titleTag.localNameId().id()) { n = new WebCore::SVGTitleElement(name, docPtr()); } if (id == makeId(svgNamespace, ID_STYLE)) { n = new WebCore::SVGStyleElement(name, docPtr()); } return n; } void DocumentImpl::attemptRestoreState(NodeImpl *n) { if (!n->isElementNode()) { return; } ElementImpl *el = static_cast(n); if (m_stateRestorePos >= m_state.size()) { return; } // Grab the state and element info.. QString idStr = m_state[m_stateRestorePos]; QString nmStr = m_state[m_stateRestorePos + 1]; QString tpStr = m_state[m_stateRestorePos + 2]; QString stStr = m_state[m_stateRestorePos + 3]; // Make sure it matches! if (idStr.toUInt() != el->id()) { return; } if (nmStr != el->getAttribute(ATTR_NAME).string()) { return; } if (tpStr != el->getAttribute(ATTR_TYPE).string()) { return; } m_stateRestorePos += 4; if (!stStr.isNull()) { el->restoreState(stStr); } } QStringList DocumentImpl::docState() { QStringList s; for (QListIterator it(m_maintainsState); it.hasNext();) { NodeImpl *n = it.next(); if (!n->isElementNode()) { continue; } ElementImpl *el = static_cast(n); // Encode the element ID, as well as the name and type attributes s.append(QString::number(el->id())); s.append(el->getAttribute(ATTR_NAME).string()); s.append(el->getAttribute(ATTR_TYPE).string()); s.append(el->state()); } return s; } bool DocumentImpl::unsubmittedFormChanges() { for (QListIterator it(m_maintainsState); it.hasNext();) { NodeImpl *node = it.next(); if (node->isGenericFormElement() && static_cast(node)->unsubmittedFormChanges()) { return true; } } return false; } RangeImpl *DocumentImpl::createRange() { return new RangeImpl(docPtr()); } NodeIteratorImpl *DocumentImpl::createNodeIterator(NodeImpl *root, unsigned long whatToShow, NodeFilterImpl *filter, bool entityReferenceExpansion, int &exceptioncode) { if (!root) { exceptioncode = DOMException::NOT_SUPPORTED_ERR; return nullptr; } return new NodeIteratorImpl(root, whatToShow, filter, entityReferenceExpansion); } TreeWalkerImpl *DocumentImpl::createTreeWalker(NodeImpl *root, unsigned long whatToShow, NodeFilterImpl *filter, bool entityReferenceExpansion, int &exceptioncode) { if (!root) { exceptioncode = DOMException::NOT_SUPPORTED_ERR; return nullptr; } return new TreeWalkerImpl(root, whatToShow, filter, entityReferenceExpansion); } void DocumentImpl::setDocumentChanged(bool b) { if (b && !m_docChanged) { s_changedDocuments()->append(this); } else if (!b && m_docChanged) { s_changedDocuments()->removeAll(this); } m_docChanged = b; } void DocumentImpl::recalcStyle(StyleChange change) { // qDebug("recalcStyle(%p)", this); // QTime qt; // qt.start(); if (m_inStyleRecalc) { return; // Guard against re-entrancy. -dwh } m_inStyleRecalc = true; if (!m_render) { goto bail_out; } if (change == Force) { RenderStyle *oldStyle = m_render->style(); if (oldStyle) { oldStyle->ref(); } RenderStyle *_style = new RenderStyle(); _style->setDisplay(BLOCK); _style->setVisuallyOrdered(visuallyOrdered); // ### make the font stuff _really_ work!!!! (??) FontDef fontDef = FontDef(); // Initial fontDef.size is 0 fontDef.size = m_styleSelector->fontSizes()[3]; _style->setFontDef(fontDef); _style->htmlFont().update(0); if (inCompatMode()) { _style->setHtmlHacks(true); // enable html specific rendering tricks } StyleChange ch = diff(_style, oldStyle); if (m_render && ch != NoChange) { m_render->setStyle(_style); } else { delete _style; } if (oldStyle) { oldStyle->deref(); } } NodeImpl *n; for (n = _first; n; n = n->nextSibling()) if (change >= Inherit || n->hasChangedChild() || n->changed()) { n->recalcStyle(change); } //qDebug() << "TIME: recalcStyle() dt=" << qt.elapsed(); if (changed() && m_view) { m_view->layout(); } bail_out: setChanged(false); setHasChangedChild(false); setDocumentChanged(false); m_inStyleRecalc = false; } void DocumentImpl::updateRendering() { if (!hasChangedChild()) { return; } // QTime time; // time.start(); // qDebug() << "UPDATERENDERING: "; StyleChange change = NoChange; #if 0 if (m_styleSelectorDirty) { recalcStyleSelector(); change = Force; } #endif recalcStyle(change); // qDebug() << "UPDATERENDERING time used="<isEmpty()) { DocumentImpl *it = s_changedDocuments()->takeFirst(); if (it->isDocumentChanged()) { it->updateRendering(); } } } void DocumentImpl::updateLayout() { if (ElementImpl *oe = ownerElement()) { oe->document()->updateLayout(); } bool oldIgnore = m_ignorePendingStylesheets; if (!haveStylesheetsLoaded()) { m_ignorePendingStylesheets = true; updateStyleSelector(); } updateRendering(); // Only do a layout if changes have occurred that make it necessary. if (m_view && renderer() && renderer()->needsLayout()) { m_view->layout(); } m_ignorePendingStylesheets = oldIgnore; } void DocumentImpl::attach() { assert(!attached()); if (m_view) { setPaintDevice(m_view); } if (!m_renderArena) { m_renderArena.reset(new RenderArena()); } // Create the rendering tree assert(!m_styleSelector); m_styleSelector = new CSSStyleSelector(this, m_usersheet, m_styleSheets, m_url, !inCompatMode()); m_render = new(m_renderArena.get()) RenderCanvas(this, m_view); m_styleSelector->computeFontSizes(m_paintDevice->logicalDpiY(), m_view ? m_view->part()->fontScaleFactor() : 100); recalcStyle(Force); RenderObject *render = m_render; m_render = nullptr; NodeBaseImpl::attach(); m_render = render; } void DocumentImpl::detach() { RenderObject *render = m_render; // indicate destruction mode, i.e. attached() but m_render == 0 m_render = nullptr; delete m_tokenizer; m_tokenizer = nullptr; // Empty out these lists as a performance optimization m_imageLoadEventDispatchSoonList.clear(); m_imageLoadEventDispatchingList.clear(); NodeBaseImpl::detach(); if (render) { render->detach(); } m_view = nullptr; m_renderArena.reset(); } void DocumentImpl::setVisuallyOrdered() { visuallyOrdered = true; if (m_render) { m_render->style()->setVisuallyOrdered(true); } } void DocumentImpl::setSelection(NodeImpl *s, int sp, NodeImpl *e, int ep) { if (m_render) { static_cast(m_render)->setSelection(s->renderer(), sp, e->renderer(), ep); } } void DocumentImpl::clearSelection() { if (m_render) { static_cast(m_render)->clearSelection(); } } void DocumentImpl::updateSelection() { if (!m_render) { return; } RenderCanvas *canvas = static_cast(m_render); Selection s = part()->caret(); if (s.isEmpty() || s.state() == Selection::CARET) { canvas->clearSelection(); } else { RenderObject *startRenderer = s.start().node() ? s.start().node()->renderer() : nullptr; RenderObject *endRenderer = s.end().node() ? s.end().node()->renderer() : nullptr; RenderPosition renderedStart = RenderPosition::fromDOMPosition(s.start()); RenderPosition renderedEnd = RenderPosition::fromDOMPosition(s.end()); static_cast(m_render)->setSelection(startRenderer, renderedStart.renderedOffset(), endRenderer, renderedEnd.renderedOffset()); } } khtml::Tokenizer *DocumentImpl::createTokenizer() { return new khtml::XMLTokenizer(docPtr(), m_view); } int DocumentImpl::logicalDpiY() { return m_paintDevice->logicalDpiY(); } void DocumentImpl::open(bool clearEventListeners) { if (parsing()) { return; } if (m_tokenizer) { close(); } delete m_tokenizer; m_tokenizer = nullptr; KHTMLView *view = m_view; bool was_attached = attached(); if (was_attached) { detach(); } removeChildren(); childrenChanged(); // Reset m_documentElement, m_doctype delete m_styleSelector; m_styleSelector = nullptr; m_view = view; if (was_attached) { attach(); } if (clearEventListeners) { windowEventTarget()->listenerList().clear(); } m_tokenizer = createTokenizer(); //m_decoderMibEnum = 0; connect(m_tokenizer, SIGNAL(finishedParsing()), this, SIGNAL(finishedParsing())); m_tokenizer->begin(); } HTMLElementImpl *DocumentImpl::body() const { NodeImpl *de = documentElement(); if (!de) { return nullptr; } // try to prefer a FRAMESET element over BODY NodeImpl *body = nullptr; for (NodeImpl *i = de->firstChild(); i; i = i->nextSibling()) { if (i->id() == ID_FRAMESET) { return static_cast(i); } if (i->id() == ID_BODY) { body = i; } } return static_cast(body); } void DocumentImpl::close() { if (parsing() && hasVariableLength() && m_tokenizer) { m_tokenizer->finish(); } else if (parsing() || !m_tokenizer) { return; } if (m_render) { m_render->close(); } // on an explicit document.close(), the tokenizer might still be waiting on scripts, // and in that case we don't want to destroy it because that will prevent the // scripts from getting processed. if (m_tokenizer && !m_tokenizer->isWaitingForScripts() && !m_tokenizer->isExecutingScript()) { delete m_tokenizer; m_tokenizer = nullptr; } if (m_view) { m_view->part()->checkEmitLoadEvent(); } } void DocumentImpl::write(const DOMString &text) { write(text.string()); } void DocumentImpl::write(const QString &text) { if (!m_tokenizer) { open(); if (m_view) { m_view->part()->resetFromScript(); } setHasVariableLength(); } m_tokenizer->write(text, false); } void DocumentImpl::writeln(const DOMString &text) { write(text); write(DOMString("\n")); } void DocumentImpl::finishParsing() { if (m_tokenizer) { m_tokenizer->finish(); } } QString DocumentImpl::completeURL(const QString &url) const { if (url.startsWith(QLatin1Char('#'))) { const QString ref = QUrl::fromPercentEncoding(url.mid(1).toUtf8()); QUrl u = baseURL(); if (ref.isEmpty()) { u.setFragment(""); } else { u.setFragment(ref, QUrl::DecodedMode); } } return baseURL().resolved(QUrl(url)).toString(); } void DocumentImpl::setUserStyleSheet(const QString &sheet) { if (m_usersheet != sheet) { m_usersheet = sheet; updateStyleSelector(); } } CSSStyleSheetImpl *DocumentImpl::elementSheet() { if (!m_elemSheet) { m_elemSheet = new CSSStyleSheetImpl(this, baseURL().url()); m_elemSheet->ref(); } return m_elemSheet; } void DocumentImpl::determineParseMode() { // For XML documents, use strict parse mode pMode = Strict; hMode = XHtml; m_htmlCompat = false; // qDebug() << " using strict parseMode"; } NodeImpl *DocumentImpl::nextFocusNode(NodeImpl *fromNode) { short fromTabIndex; if (!fromNode) { // No starting node supplied; begin with the top of the document NodeImpl *n; int lowestTabIndex = SHRT_MAX + 1; for (n = this; n != nullptr; n = n->traverseNextNode()) { if (n->isTabFocusable()) { if ((n->tabIndex() > 0) && (n->tabIndex() < lowestTabIndex)) { lowestTabIndex = n->tabIndex(); } } } if (lowestTabIndex == SHRT_MAX + 1) { lowestTabIndex = 0; } // Go to the first node in the document that has the desired tab index for (n = this; n != nullptr; n = n->traverseNextNode()) { if (n->isTabFocusable() && (n->tabIndex() == lowestTabIndex)) { return n; } } return nullptr; } else { fromTabIndex = fromNode->tabIndex(); } if (fromTabIndex == 0) { // Just need to find the next selectable node after fromNode (in document order) that doesn't have a tab index NodeImpl *n = fromNode->traverseNextNode(); while (n && !(n->isTabFocusable() && n->tabIndex() == 0)) { n = n->traverseNextNode(); } return n; } else { // Find the lowest tab index out of all the nodes except fromNode, that is greater than or equal to fromNode's // tab index. For nodes with the same tab index as fromNode, we are only interested in those that come after // fromNode in document order. // If we don't find a suitable tab index, the next focus node will be one with a tab index of 0. int lowestSuitableTabIndex = SHRT_MAX + 1; NodeImpl *n; bool reachedFromNode = false; for (n = this; n != nullptr; n = n->traverseNextNode()) { if (n->isTabFocusable() && ((reachedFromNode && (n->tabIndex() >= fromTabIndex)) || (!reachedFromNode && (n->tabIndex() > fromTabIndex))) && (n->tabIndex() < lowestSuitableTabIndex) && (n != fromNode)) { // We found a selectable node with a tab index at least as high as fromNode's. Keep searching though, // as there may be another node which has a lower tab index but is still suitable for use. lowestSuitableTabIndex = n->tabIndex(); } if (n == fromNode) { reachedFromNode = true; } } if (lowestSuitableTabIndex == SHRT_MAX + 1) { // No next node with a tab index -> just take first node with tab index of 0 NodeImpl *n = this; while (n && !(n->isTabFocusable() && n->tabIndex() == 0)) { n = n->traverseNextNode(); } return n; } // Search forwards from fromNode for (n = fromNode->traverseNextNode(); n != nullptr; n = n->traverseNextNode()) { if (n->isTabFocusable() && (n->tabIndex() == lowestSuitableTabIndex)) { return n; } } // The next node isn't after fromNode, start from the beginning of the document for (n = this; n != fromNode; n = n->traverseNextNode()) { if (n->isTabFocusable() && (n->tabIndex() == lowestSuitableTabIndex)) { return n; } } assert(false); // should never get here return nullptr; } } NodeImpl *DocumentImpl::previousFocusNode(NodeImpl *fromNode) { NodeImpl *lastNode = this; while (lastNode->lastChild()) { lastNode = lastNode->lastChild(); } if (!fromNode) { // No starting node supplied; begin with the very last node in the document NodeImpl *n; int highestTabIndex = 0; for (n = lastNode; n != nullptr; n = n->traversePreviousNode()) { if (n->isTabFocusable()) { if (n->tabIndex() == 0) { return n; } else if (n->tabIndex() > highestTabIndex) { highestTabIndex = n->tabIndex(); } } } // No node with a tab index of 0; just go to the last node with the highest tab index for (n = lastNode; n != nullptr; n = n->traversePreviousNode()) { if (n->isTabFocusable() && (n->tabIndex() == highestTabIndex)) { return n; } } return nullptr; } else { short fromTabIndex = fromNode->tabIndex(); if (fromTabIndex == 0) { // Find the previous selectable node before fromNode (in document order) that doesn't have a tab index NodeImpl *n = fromNode->traversePreviousNode(); while (n && !(n->isTabFocusable() && n->tabIndex() == 0)) { n = n->traversePreviousNode(); } if (n) { return n; } // No previous nodes with a 0 tab index, go to the last node in the document that has the highest tab index int highestTabIndex = 0; for (n = this; n != nullptr; n = n->traverseNextNode()) { if (n->isTabFocusable() && (n->tabIndex() > highestTabIndex)) { highestTabIndex = n->tabIndex(); } } if (highestTabIndex == 0) { return nullptr; } for (n = lastNode; n != nullptr; n = n->traversePreviousNode()) { if (n->isTabFocusable() && (n->tabIndex() == highestTabIndex)) { return n; } } assert(false); // should never get here return nullptr; } else { // Find the lowest tab index out of all the nodes except fromNode, that is less than or equal to fromNode's // tab index. For nodes with the same tab index as fromNode, we are only interested in those before // fromNode. // If we don't find a suitable tab index, then there will be no previous focus node. short highestSuitableTabIndex = 0; NodeImpl *n; bool reachedFromNode = false; for (n = this; n != nullptr; n = n->traverseNextNode()) { if (n->isTabFocusable() && ((!reachedFromNode && (n->tabIndex() <= fromTabIndex)) || (reachedFromNode && (n->tabIndex() < fromTabIndex))) && (n->tabIndex() > highestSuitableTabIndex) && (n != fromNode)) { // We found a selectable node with a tab index no higher than fromNode's. Keep searching though, as // there may be another node which has a higher tab index but is still suitable for use. highestSuitableTabIndex = n->tabIndex(); } if (n == fromNode) { reachedFromNode = true; } } if (highestSuitableTabIndex == 0) { // No previous node with a tab index. Since the order specified by HTML is nodes with tab index > 0 // first, this means that there is no previous node. return nullptr; } // Search backwards from fromNode for (n = fromNode->traversePreviousNode(); n != nullptr; n = n->traversePreviousNode()) { if (n->isTabFocusable() && (n->tabIndex() == highestSuitableTabIndex)) { return n; } } // The previous node isn't before fromNode, start from the end of the document for (n = lastNode; n != fromNode; n = n->traversePreviousNode()) { if (n->isTabFocusable() && (n->tabIndex() == highestSuitableTabIndex)) { return n; } } assert(false); // should never get here return nullptr; } } } ElementImpl *DocumentImpl::findAccessKeyElement(QChar c) { c = c.toUpper(); for (NodeImpl *n = this; n != nullptr; n = n->traverseNextNode()) { if (n->isElementNode()) { ElementImpl *en = static_cast< ElementImpl * >(n); DOMString s = en->getAttribute(ATTR_ACCESSKEY); if (s.length() == 1 && s[ 0 ].toUpper() == c) { return en; } } } return nullptr; } int DocumentImpl::nodeAbsIndex(NodeImpl *node) { assert(node->document() == this); int absIndex = 0; for (NodeImpl *n = node; n && n != this; n = n->traversePreviousNode()) { absIndex++; } return absIndex; } NodeImpl *DocumentImpl::nodeWithAbsIndex(int absIndex) { NodeImpl *n = this; for (int i = 0; n && (i < absIndex); i++) { n = n->traverseNextNode(); } return n; } void DocumentImpl::processHttpEquiv(const DOMString &equiv, const DOMString &content) { assert(!equiv.isNull() && !content.isNull()); KHTMLView *v = document()->view(); if (strcasecmp(equiv, "refresh") == 0 && v && v->part()->metaRefreshEnabled()) { // get delay and url QString str = content.string().trimmed(); int pos = str.indexOf(QRegExp("[;,]")); if (pos == -1) { pos = str.indexOf(QRegExp("[ \t]")); } bool ok = false; int delay = qMax(0, content.implementation()->toInt(&ok)); if (!ok && str.length() && str[0] == '.') { ok = true; } if (pos == -1) { // There can be no url (David) if (ok) { v->part()->scheduleRedirection(delay, v->part()->url().toString()); } } else { pos++; while (pos < str.length() && str[pos].isSpace()) { pos++; } str = str.mid(pos); if (str.indexOf("url", 0, Qt::CaseInsensitive) == 0) { str = str.mid(3); } str = str.trimmed(); if (str.length() && str[0] == '=') { str = str.mid(1).trimmed(); } while (str.length() && (str[str.length() - 1] == ';' || str[str.length() - 1] == ',')) { str.resize(str.length() - 1); } str = DOMString(str).trimSpaces().string(); const QString newURL = document()->completeURL(str); if (ok) { v->part()->scheduleRedirection(delay, newURL, delay < 2 || newURL == URL().url()); } } } else if (strcasecmp(equiv, "expires") == 0) { if (m_docLoader) { QString str = content.string().trimmed(); //QDateTime can't convert from a RFCDate format string QDateTime expire_date = QDateTime::fromString(str, Qt::RFC2822Date); if (!expire_date.isValid()) { qint64 seconds = str.toLongLong(); if (seconds != 0) { m_docLoader->setRelativeExpireDate(seconds); } else { expire_date = QDateTime::currentDateTime(); // expire now m_docLoader->setExpireDate(expire_date); } } } } else if (v && (strcasecmp(equiv, "pragma") == 0 || strcasecmp(equiv, "cache-control") == 0)) { QString str = content.string().toLower().trimmed(); QUrl url = v->part()->url(); if ((str == "no-cache") && url.scheme().startsWith(QLatin1String("http"))) { KIO::http_update_cache(url, true, QDateTime::fromTime_t(0)); } } else if ((strcasecmp(equiv, "set-cookie") == 0)) { // ### make setCookie work on XML documents too; e.g. in case of HTMLDocumentImpl *d = static_cast(this); d->setCookie(content); } else if (strcasecmp(equiv, "default-style") == 0) { // HTML 4.0 14.3.2 // http://www.hixie.ch/tests/evil/css/import/main/preferred.html m_preferredStylesheetSet = content; updateStyleSelector(); } else if (strcasecmp(equiv, "content-language") == 0) { m_contentLanguage = content.string(); } } bool DocumentImpl::prepareMouseEvent(bool readonly, int _x, int _y, MouseEvent *ev) { if (m_render) { assert(m_render->isCanvas()); RenderObject::NodeInfo renderInfo(readonly, ev->type == MousePress); bool isInside = m_render->layer()->nodeAtPoint(renderInfo, _x, _y); ev->innerNode = renderInfo.innerNode(); ev->innerNonSharedNode = renderInfo.innerNonSharedNode(); if (renderInfo.URLElement()) { assert(renderInfo.URLElement()->isElementNode()); //qDebug("urlnode: %s (%d)", getTagName(renderInfo.URLElement()->id()).string().toLatin1().constData(), renderInfo.URLElement()->id()); ElementImpl *e = static_cast(renderInfo.URLElement()); DOMString href = e->getAttribute(ATTR_HREF).trimSpaces(); DOMString target = e->getAttribute(ATTR_TARGET); if (!target.isNull() && !href.isNull()) { ev->target = target; ev->url = href; } else { ev->url = href; } } if (!readonly) { updateRendering(); } return isInside; } return false; } // DOM Section 1.1.1 bool DocumentImpl::childTypeAllowed(unsigned short type) { switch (type) { case Node::ATTRIBUTE_NODE: case Node::CDATA_SECTION_NODE: case Node::DOCUMENT_FRAGMENT_NODE: case Node::DOCUMENT_NODE: case Node::ENTITY_NODE: case Node::ENTITY_REFERENCE_NODE: case Node::NOTATION_NODE: case Node::TEXT_NODE: // case Node::XPATH_NAMESPACE_NODE: return false; case Node::COMMENT_NODE: case Node::PROCESSING_INSTRUCTION_NODE: return true; case Node::DOCUMENT_TYPE_NODE: case Node::ELEMENT_NODE: // Documents may contain no more than one of each of these. // (One Element and one DocumentType.) for (NodeImpl *c = firstChild(); c; c = c->nextSibling()) if (c->nodeType() == type) { return false; } return true; } return false; } WTF::PassRefPtr DocumentImpl::cloneNode(bool deep) { #if 0 NodeImpl *dtn = m_doctype->cloneNode(deep); DocumentTypeImpl *dt = static_cast(dtn); #endif int exceptioncode; WTF::RefPtr clone = DOMImplementationImpl::createDocument("", "", nullptr, nullptr, exceptioncode); assert(exceptioncode == 0); // ### attributes, styles, ... if (deep) { cloneChildNodes(clone.get()); } return clone; } // This method is called whenever a top-level stylesheet has finished loading. void DocumentImpl::styleSheetLoaded() { // Make sure we knew this sheet was pending, and that our count isn't out of sync. assert(m_pendingStylesheets > 0); m_pendingStylesheets--; updateStyleSelector(); if (!m_pendingStylesheets && m_tokenizer) { m_tokenizer->executeScriptsWaitingForStylesheets(); } } void DocumentImpl::addPendingSheet() { m_pendingStylesheets++; } DOMString DocumentImpl::selectedStylesheetSet() const { if (!view()) { return DOMString(); } return view()->part()->d->m_sheetUsed; } void DocumentImpl::setSelectedStylesheetSet(const DOMString &s) { // this code is evil if (view() && view()->part()->d->m_sheetUsed != s.string()) { view()->part()->d->m_sheetUsed = s.string(); updateStyleSelector(); } } void DocumentImpl::addStyleSheet(StyleSheetImpl *sheet, int *exceptioncode) { int excode = 0; if (!m_addedStyleSheets) { m_addedStyleSheets = new StyleSheetListImpl; m_addedStyleSheets->ref(); } m_addedStyleSheets->add(sheet); if (sheet->isCSSStyleSheet()) { updateStyleSelector(); } if (exceptioncode) { *exceptioncode = excode; } } void DocumentImpl::removeStyleSheet(StyleSheetImpl *sheet, int *exceptioncode) { int excode = 0; bool removed = false; bool is_css = sheet->isCSSStyleSheet(); if (m_addedStyleSheets) { bool in_main_list = !sheet->hasOneRef(); removed = m_addedStyleSheets->styleSheets.removeAll(sheet); sheet->deref(); if (m_addedStyleSheets->styleSheets.count() == 0) { bool reset = m_addedStyleSheets->hasOneRef(); m_addedStyleSheets->deref(); if (reset) { m_addedStyleSheets = nullptr; } } // remove from main list, too if (in_main_list) { m_styleSheets->remove(sheet); } } if (removed) { if (is_css) { updateStyleSelector(); } } else { excode = DOMException::NOT_FOUND_ERR; } if (exceptioncode) { *exceptioncode = excode; } } void DocumentImpl::updateStyleSelector(bool shallow) { // qDebug() << "PENDING " << m_pendingStylesheets; // Don't bother updating, since we haven't loaded all our style info yet. if (m_pendingStylesheets > 0) { // ... however, if the list of stylesheets changed, mark it as dirty // so DOM ops can get an up-to-date version. if (!shallow) { m_styleSheetListDirty = true; } return; } if (!shallow) { rebuildStyleSheetList(); } rebuildStyleSelector(); recalcStyle(Force); #if 0 m_styleSelectorDirty = true; #endif if (renderer()) { renderer()->setNeedsLayoutAndMinMaxRecalc(); } } bool DocumentImpl::readyForLayout() const { return renderer() && haveStylesheetsLoaded() && (!isHTMLDocument() || (body() && body()->renderer())); } void DocumentImpl::rebuildStyleSheetList(bool force) { if (!m_render || !attached()) { // Unless we're forced due to CSS DOM ops, we don't have to compute info // when there is nothing to display if (!force) { m_styleSheetListDirty = true; return; } } // Mark us as clean, as we can call add on the list below, forcing us to re-enter m_styleSheetListDirty = false; QList oldStyleSheets = m_styleSheets->styleSheets; m_styleSheets->styleSheets.clear(); QString sheetUsed = view() ? view()->part()->d->m_sheetUsed.replace("&&", "&") : QString(); bool autoselect = sheetUsed.isEmpty(); if (autoselect && !m_preferredStylesheetSet.isEmpty()) { sheetUsed = m_preferredStylesheetSet.string(); } NodeImpl *n; for (int i = 0; i < 2; i++) { m_availableSheets.clear(); m_availableSheets << i18n("Basic Page Style"); bool canResetSheet = false; QString title; for (n = this; n; n = n->traverseNextNode()) { StyleSheetImpl *sheet = nullptr; if (n->nodeType() == Node::PROCESSING_INSTRUCTION_NODE) { // Processing instruction (XML documents only) ProcessingInstructionImpl *pi = static_cast(n); sheet = pi->sheet(); if (!sheet && !pi->localHref().isEmpty()) { // Processing instruction with reference to an element in this document - e.g. // , with the element // heading { color: red; } at some location in // the document ElementImpl *elem = getElementById(pi->localHref()); if (elem) { DOMString sheetText(""); NodeImpl *c; for (c = elem->firstChild(); c; c = c->nextSibling()) { if (c->nodeType() == Node::TEXT_NODE || c->nodeType() == Node::CDATA_SECTION_NODE) { sheetText += c->nodeValue(); } } CSSStyleSheetImpl *cssSheet = new CSSStyleSheetImpl(this); cssSheet->parseString(sheetText); pi->setStyleSheet(cssSheet); sheet = cssSheet; } } if (sheet) { title = sheet->title().string(); if ((autoselect || title != sheetUsed) && sheet->disabled()) { sheet = nullptr; } else if (!title.isEmpty() && !pi->isAlternate() && sheetUsed.isEmpty()) { sheetUsed = title; sheet->setDisabled(false); } } } else if (n->isHTMLElement() && (n->id() == ID_LINK || n->id() == ID_STYLE)) { if (n->id() == ID_LINK) { HTMLLinkElementImpl *l = static_cast(n); if (l->isCSSStyleSheet()) { sheet = l->sheet(); if (sheet || l->isLoading() || l->isAlternate()) { title = l->getAttribute(ATTR_TITLE).string(); } if ((autoselect || title != sheetUsed) && l->isDisabled()) { sheet = nullptr; } else if (!title.isEmpty() && !l->isAlternate() && sheetUsed.isEmpty()) { sheetUsed = title; l->setDisabled(false); } } } else { //