bool SelectorChecker::checkOne(const SelectorCheckingContext& context) const { Element* const & element = context.element; const CSSSelector* const & selector = context.selector; ASSERT(element); ASSERT(selector); if (selector->m_match == CSSSelector::Tag) return SelectorChecker::tagMatches(element, selector->tagQName()); if (selector->m_match == CSSSelector::Class) return element->hasClass() && element->classNames().contains(selector->value()); if (selector->m_match == CSSSelector::Id) return element->hasID() && element->idForStyleResolution() == selector->value(); if (selector->isAttributeSelector()) { const QualifiedName& attr = selector->attribute(); if (!element->hasAttributes()) return false; bool caseSensitive = !m_documentIsHTML || HTMLDocument::isCaseSensitiveAttribute(attr); if (!anyAttributeMatches(element, static_cast<CSSSelector::Match>(selector->m_match), attr, selector->value(), caseSensitive)) return false; } if (selector->m_match == CSSSelector::PseudoClass) { // Handle :not up front. if (selector->pseudoType() == CSSSelector::PseudoNot) { const CSSSelectorList* selectorList = selector->selectorList(); // FIXME: We probably should fix the parser and make it never produce :not rules with missing selector list. if (!selectorList) return false; SelectorCheckingContext subContext(context); subContext.isSubSelector = true; for (subContext.selector = selectorList->first(); subContext.selector; subContext.selector = subContext.selector->tagHistory()) { // :not cannot nest. I don't really know why this is a // restriction in CSS3, but it is, so let's honor it. // the parser enforces that this never occurs ASSERT(subContext.selector->pseudoType() != CSSSelector::PseudoNot); // We select between :visited and :link when applying. We don't know which one applied (or not) yet. if (subContext.selector->pseudoType() == CSSSelector::PseudoVisited || (subContext.selector->pseudoType() == CSSSelector::PseudoLink && subContext.visitedMatchType == VisitedMatchEnabled)) return true; if (!checkOne(subContext)) return true; } } else if (context.hasScrollbarPseudo) { // CSS scrollbars match a specific subset of pseudo classes, and they have specialized rules for each // (since there are no elements involved). return checkScrollbarPseudoClass(context, element->document(), selector); } else if (context.hasSelectionPseudo) { if (selector->pseudoType() == CSSSelector::PseudoWindowInactive) return !element->document()->page()->focusController()->isActive(); } // Normal element pseudo class checking. switch (selector->pseudoType()) { // Pseudo classes: case CSSSelector::PseudoNot: break; // Already handled up above. case CSSSelector::PseudoEmpty: { bool result = true; for (Node* n = element->firstChild(); n; n = n->nextSibling()) { if (n->isElementNode()) { result = false; break; } if (n->isTextNode()) { Text* textNode = toText(n); if (!textNode->data().isEmpty()) { result = false; break; } } } if (m_mode == ResolvingStyle) { element->setStyleAffectedByEmpty(); if (context.elementStyle) context.elementStyle->setEmptyState(result); else if (element->renderStyle() && (element->document()->styleSheetCollection()->usesSiblingRules() || element->renderStyle()->unique())) element->renderStyle()->setEmptyState(result); } return result; } case CSSSelector::PseudoFirstChild: // first-child matches the first child that is an element if (Element* parentElement = element->parentElement()) { bool result = isFirstChildElement(element); if (m_mode == ResolvingStyle) { RenderStyle* childStyle = context.elementStyle ? context.elementStyle : element->renderStyle(); parentElement->setChildrenAffectedByFirstChildRules(); if (result && childStyle) childStyle->setFirstChildState(); } return result; } break; case CSSSelector::PseudoFirstOfType: // first-of-type matches the first element of its type if (Element* parentElement = element->parentElement()) { bool result = isFirstOfType(element, element->tagQName()); if (m_mode == ResolvingStyle) parentElement->setChildrenAffectedByForwardPositionalRules(); return result; } break; case CSSSelector::PseudoLastChild: // last-child matches the last child that is an element if (Element* parentElement = element->parentElement()) { bool result = parentElement->isFinishedParsingChildren() && isLastChildElement(element); if (m_mode == ResolvingStyle) { RenderStyle* childStyle = context.elementStyle ? context.elementStyle : element->renderStyle(); parentElement->setChildrenAffectedByLastChildRules(); if (result && childStyle) childStyle->setLastChildState(); } return result; } break; case CSSSelector::PseudoLastOfType: // last-of-type matches the last element of its type if (Element* parentElement = element->parentElement()) { if (m_mode == ResolvingStyle) parentElement->setChildrenAffectedByBackwardPositionalRules(); if (!parentElement->isFinishedParsingChildren()) return false; return isLastOfType(element, element->tagQName()); } break; case CSSSelector::PseudoOnlyChild: if (Element* parentElement = element->parentElement()) { bool firstChild = isFirstChildElement(element); bool onlyChild = firstChild && parentElement->isFinishedParsingChildren() && isLastChildElement(element); if (m_mode == ResolvingStyle) { RenderStyle* childStyle = context.elementStyle ? context.elementStyle : element->renderStyle(); parentElement->setChildrenAffectedByFirstChildRules(); parentElement->setChildrenAffectedByLastChildRules(); if (firstChild && childStyle) childStyle->setFirstChildState(); if (onlyChild && childStyle) childStyle->setLastChildState(); } return onlyChild; } break; case CSSSelector::PseudoOnlyOfType: // FIXME: This selector is very slow. if (Element* parentElement = element->parentElement()) { if (m_mode == ResolvingStyle) { parentElement->setChildrenAffectedByForwardPositionalRules(); parentElement->setChildrenAffectedByBackwardPositionalRules(); } if (!parentElement->isFinishedParsingChildren()) return false; return isFirstOfType(element, element->tagQName()) && isLastOfType(element, element->tagQName()); } break; case CSSSelector::PseudoNthChild: if (!selector->parseNth()) break; if (Element* parentElement = element->parentElement()) { int count = 1 + countElementsBefore(element); if (m_mode == ResolvingStyle) { RenderStyle* childStyle = context.elementStyle ? context.elementStyle : element->renderStyle(); element->setChildIndex(count); if (childStyle) childStyle->setUnique(); parentElement->setChildrenAffectedByForwardPositionalRules(); } if (selector->matchNth(count)) return true; } break; case CSSSelector::PseudoNthOfType: if (!selector->parseNth()) break; if (Element* parentElement = element->parentElement()) { int count = 1 + countElementsOfTypeBefore(element, element->tagQName()); if (m_mode == ResolvingStyle) parentElement->setChildrenAffectedByForwardPositionalRules(); if (selector->matchNth(count)) return true; } break; case CSSSelector::PseudoNthLastChild: if (!selector->parseNth()) break; if (Element* parentElement = element->parentElement()) { if (m_mode == ResolvingStyle) parentElement->setChildrenAffectedByBackwardPositionalRules(); if (!parentElement->isFinishedParsingChildren()) return false; int count = 1 + countElementsAfter(element); if (selector->matchNth(count)) return true; } break; case CSSSelector::PseudoNthLastOfType: if (!selector->parseNth()) break; if (Element* parentElement = element->parentElement()) { if (m_mode == ResolvingStyle) parentElement->setChildrenAffectedByBackwardPositionalRules(); if (!parentElement->isFinishedParsingChildren()) return false; int count = 1 + countElementsOfTypeAfter(element, element->tagQName()); if (selector->matchNth(count)) return true; } break; case CSSSelector::PseudoTarget: if (element == element->document()->cssTarget()) return true; break; case CSSSelector::PseudoAny: { SelectorCheckingContext subContext(context); subContext.isSubSelector = true; PseudoId ignoreDynamicPseudo = NOPSEUDO; for (subContext.selector = selector->selectorList()->first(); subContext.selector; subContext.selector = CSSSelectorList::next(subContext.selector)) { if (match(subContext, ignoreDynamicPseudo) == SelectorMatches) return true; } } break; case CSSSelector::PseudoAutofill: if (!element->isFormControlElement()) break; if (HTMLInputElement* inputElement = element->toInputElement()) return inputElement->isAutofilled(); break; case CSSSelector::PseudoAnyLink: case CSSSelector::PseudoLink: // :visited and :link matches are separated later when applying the style. Here both classes match all links... return element->isLink(); case CSSSelector::PseudoVisited: // ...except if :visited matching is disabled for ancestor/sibling matching. return element->isLink() && context.visitedMatchType == VisitedMatchEnabled; case CSSSelector::PseudoDrag: if (m_mode == ResolvingStyle) { if (context.elementStyle) context.elementStyle->setAffectedByDrag(); else element->setChildrenAffectedByDrag(true); } if (element->renderer() && element->renderer()->isDragging()) return true; break; case CSSSelector::PseudoFocus: return matchesFocusPseudoClass(element); case CSSSelector::PseudoHover: // If we're in quirks mode, then hover should never match anchors with no // href and *:hover should not match anything. This is important for sites like wsj.com. if (m_strictParsing || context.isSubSelector || (selector->m_match == CSSSelector::Tag && selector->tagQName() != anyQName() && !isHTMLAnchorElement(element)) || element->isLink()) { if (m_mode == ResolvingStyle) { if (context.elementStyle) context.elementStyle->setAffectedByHover(); else element->setChildrenAffectedByHover(true); } if (element->hovered() || InspectorInstrumentation::forcePseudoState(element, CSSSelector::PseudoHover)) return true; } break; case CSSSelector::PseudoActive: // If we're in quirks mode, then :active should never match anchors with no // href and *:active should not match anything. if (m_strictParsing || context.isSubSelector || (selector->m_match == CSSSelector::Tag && selector->tagQName() != anyQName() && !isHTMLAnchorElement(element)) || element->isLink()) { if (m_mode == ResolvingStyle) { if (context.elementStyle) context.elementStyle->setAffectedByActive(); else element->setChildrenAffectedByActive(true); } if (element->active() || InspectorInstrumentation::forcePseudoState(element, CSSSelector::PseudoActive)) return true; } break; case CSSSelector::PseudoEnabled: if (element->isFormControlElement() || isHTMLOptionElement(element) || isHTMLOptGroupElement(element)) return !element->isDisabledFormControl(); break; case CSSSelector::PseudoFullPageMedia: return element->document() && element->document()->isMediaDocument(); break; case CSSSelector::PseudoDefault: return element->isDefaultButtonForForm(); case CSSSelector::PseudoDisabled: if (element->isFormControlElement() || isHTMLOptionElement(element) || isHTMLOptGroupElement(element)) return element->isDisabledFormControl(); break; case CSSSelector::PseudoReadOnly: return element->matchesReadOnlyPseudoClass(); case CSSSelector::PseudoReadWrite: return element->matchesReadWritePseudoClass(); case CSSSelector::PseudoOptional: return element->isOptionalFormControl(); case CSSSelector::PseudoRequired: return element->isRequiredFormControl(); case CSSSelector::PseudoValid: element->document()->setContainsValidityStyleRules(); return element->willValidate() && element->isValidFormControlElement(); case CSSSelector::PseudoInvalid: element->document()->setContainsValidityStyleRules(); return element->willValidate() && !element->isValidFormControlElement(); case CSSSelector::PseudoChecked: { // Even though WinIE allows checked and indeterminate to co-exist, the CSS selector spec says that // you can't be both checked and indeterminate. We will behave like WinIE behind the scenes and just // obey the CSS spec here in the test for matching the pseudo. HTMLInputElement* inputElement = element->toInputElement(); if (inputElement && inputElement->shouldAppearChecked() && !inputElement->shouldAppearIndeterminate()) return true; if (isHTMLOptionElement(element) && toHTMLOptionElement(element)->selected()) return true; break; } case CSSSelector::PseudoIndeterminate: return element->shouldAppearIndeterminate(); case CSSSelector::PseudoRoot: if (element == element->document()->documentElement()) return true; break; case CSSSelector::PseudoLang: { AtomicString value; #if ENABLE(VIDEO_TRACK) if (element->isWebVTTElement()) value = toWebVTTElement(element)->language(); else #endif value = element->computeInheritedLanguage(); const AtomicString& argument = selector->argument(); if (value.isEmpty() || !value.startsWith(argument, false)) break; if (value.length() != argument.length() && value[argument.length()] != '-') break; return true; } #if ENABLE(FULLSCREEN_API) case CSSSelector::PseudoFullScreen: // While a Document is in the fullscreen state, and the document's current fullscreen // element is an element in the document, the 'full-screen' pseudoclass applies to // that element. Also, an <iframe>, <object> or <embed> element whose child browsing // context's Document is in the fullscreen state has the 'full-screen' pseudoclass applied. if (element->isFrameElementBase() && element->containsFullScreenElement()) return true; if (!element->document()->webkitIsFullScreen()) return false; return element == element->document()->webkitCurrentFullScreenElement(); case CSSSelector::PseudoAnimatingFullScreenTransition: if (element != element->document()->webkitCurrentFullScreenElement()) return false; return element->document()->isAnimatingFullScreen(); case CSSSelector::PseudoFullScreenAncestor: return element->containsFullScreenElement(); case CSSSelector::PseudoFullScreenDocument: // While a Document is in the fullscreen state, the 'full-screen-document' pseudoclass applies // to all elements of that Document. if (!element->document()->webkitIsFullScreen()) return false; return true; #endif #if ENABLE(IFRAME_SEAMLESS) case CSSSelector::PseudoSeamlessDocument: // While a document is rendered in a seamless iframe, the 'seamless-document' pseudoclass applies // to all elements of that Document. return element->document()->shouldDisplaySeamlesslyWithParent(); #endif case CSSSelector::PseudoInRange: element->document()->setContainsValidityStyleRules(); return element->isInRange(); case CSSSelector::PseudoOutOfRange: element->document()->setContainsValidityStyleRules(); return element->isOutOfRange(); #if ENABLE(VIDEO_TRACK) case CSSSelector::PseudoFutureCue: return (element->isWebVTTElement() && !toWebVTTElement(element)->isPastNode()); case CSSSelector::PseudoPastCue: return (element->isWebVTTElement() && toWebVTTElement(element)->isPastNode()); #endif case CSSSelector::PseudoScope: { const Node* contextualReferenceNode = !context.scope || context.behaviorAtBoundary == CrossesBoundary ? element->document()->documentElement() : context.scope; if (element == contextualReferenceNode) return true; break; } case CSSSelector::PseudoHorizontal: case CSSSelector::PseudoVertical: case CSSSelector::PseudoDecrement: case CSSSelector::PseudoIncrement: case CSSSelector::PseudoStart: case CSSSelector::PseudoEnd: case CSSSelector::PseudoDoubleButton: case CSSSelector::PseudoSingleButton: case CSSSelector::PseudoNoButton: case CSSSelector::PseudoCornerPresent: return false; case CSSSelector::PseudoUnknown: case CSSSelector::PseudoNotParsed: default: ASSERT_NOT_REACHED(); break; } return false; } #if ENABLE(VIDEO_TRACK) else if (selector->m_match == CSSSelector::PseudoElement && selector->pseudoType() == CSSSelector::PseudoCue) { SelectorCheckingContext subContext(context); subContext.isSubSelector = true; PseudoId ignoreDynamicPseudo = NOPSEUDO; const CSSSelector* const & selector = context.selector; for (subContext.selector = selector->selectorList()->first(); subContext.selector; subContext.selector = CSSSelectorList::next(subContext.selector)) { if (match(subContext, ignoreDynamicPseudo) == SelectorMatches) return true; } return false; } #endif // ### add the rest of the checks... return true; }
bool SVGTransformable::parseTransformAttribute(SVGTransformList* list, const AtomicString& transform) { const UChar* start = transform.characters(); const UChar* end = start + transform.length(); return parseTransformAttribute(list, start, end); }
static bool attributeValueMatches(const Attribute* attributeItem, CSSSelector::Match match, const AtomicString& selectorValue, bool caseSensitive) { const AtomicString& value = attributeItem->value(); if (value.isNull()) return false; switch (match) { case CSSSelector::Exact: if (caseSensitive ? selectorValue != value : !equalIgnoringCase(selectorValue, value)) return false; break; case CSSSelector::List: { // Ignore empty selectors or selectors containing spaces if (selectorValue.contains(' ') || selectorValue.isEmpty()) return false; unsigned startSearchAt = 0; while (true) { size_t foundPos = value.find(selectorValue, startSearchAt, caseSensitive); if (foundPos == notFound) return false; if (!foundPos || value[foundPos - 1] == ' ') { unsigned endStr = foundPos + selectorValue.length(); if (endStr == value.length() || value[endStr] == ' ') break; // We found a match. } // No match. Keep looking. startSearchAt = foundPos + 1; } break; } case CSSSelector::Contain: if (!value.contains(selectorValue, caseSensitive) || selectorValue.isEmpty()) return false; break; case CSSSelector::Begin: if (!value.startsWith(selectorValue, caseSensitive) || selectorValue.isEmpty()) return false; break; case CSSSelector::End: if (!value.endsWith(selectorValue, caseSensitive) || selectorValue.isEmpty()) return false; break; case CSSSelector::Hyphen: if (value.length() < selectorValue.length()) return false; if (!value.startsWith(selectorValue, caseSensitive)) return false; // It they start the same, check for exact match or following '-': if (value.length() != selectorValue.length() && value[selectorValue.length()] != '-') return false; break; case CSSSelector::PseudoClass: case CSSSelector::PseudoElement: default: break; } return true; }
bool SVGTransformable::parseTransformAttribute(SVGTransformList* list, const AtomicString& transform) { double x[] = {0, 0, 0, 0, 0, 0}; int nr = 0, required = 0, optional = 0; const UChar* currTransform = transform.characters(); const UChar* end = currTransform + transform.length(); bool delimParsed = false; while (currTransform < end) { delimParsed = false; unsigned short type = SVGTransform::SVG_TRANSFORM_UNKNOWN; skipOptionalSpaces(currTransform, end); if (currTransform >= end) return false; if (*currTransform == 's') { if (skipString(currTransform, end, skewXDesc, sizeof(skewXDesc) / sizeof(UChar))) { required = 1; optional = 0; type = SVGTransform::SVG_TRANSFORM_SKEWX; } else if (skipString(currTransform, end, skewYDesc, sizeof(skewYDesc) / sizeof(UChar))) { required = 1; optional = 0; type = SVGTransform::SVG_TRANSFORM_SKEWY; } else if (skipString(currTransform, end, scaleDesc, sizeof(scaleDesc) / sizeof(UChar))) { required = 1; optional = 1; type = SVGTransform::SVG_TRANSFORM_SCALE; } else return false; } else if (skipString(currTransform, end, translateDesc, sizeof(translateDesc) / sizeof(UChar))) { required = 1; optional = 1; type = SVGTransform::SVG_TRANSFORM_TRANSLATE; } else if (skipString(currTransform, end, rotateDesc, sizeof(rotateDesc) / sizeof(UChar))) { required = 1; optional = 2; type = SVGTransform::SVG_TRANSFORM_ROTATE; } else if (skipString(currTransform, end, matrixDesc, sizeof(matrixDesc) / sizeof(UChar))) { required = 6; optional = 0; type = SVGTransform::SVG_TRANSFORM_MATRIX; } else return false; if ((nr = parseTransformParamList(currTransform, end, x, required, optional)) < 0) return false; SVGTransform t; switch (type) { case SVGTransform::SVG_TRANSFORM_SKEWX: t.setSkewX(narrowPrecisionToFloat(x[0])); break; case SVGTransform::SVG_TRANSFORM_SKEWY: t.setSkewY(narrowPrecisionToFloat(x[0])); break; case SVGTransform::SVG_TRANSFORM_SCALE: if (nr == 1) // Spec: if only one param given, assume uniform scaling t.setScale(narrowPrecisionToFloat(x[0]), narrowPrecisionToFloat(x[0])); else t.setScale(narrowPrecisionToFloat(x[0]), narrowPrecisionToFloat(x[1])); break; case SVGTransform::SVG_TRANSFORM_TRANSLATE: if (nr == 1) // Spec: if only one param given, assume 2nd param to be 0 t.setTranslate(narrowPrecisionToFloat(x[0]), 0); else t.setTranslate(narrowPrecisionToFloat(x[0]), narrowPrecisionToFloat(x[1])); break; case SVGTransform::SVG_TRANSFORM_ROTATE: if (nr == 1) t.setRotate(narrowPrecisionToFloat(x[0]), 0, 0); else t.setRotate(narrowPrecisionToFloat(x[0]), narrowPrecisionToFloat(x[1]), narrowPrecisionToFloat(x[2])); break; case SVGTransform::SVG_TRANSFORM_MATRIX: t.setMatrix(AffineTransform(x[0], x[1], x[2], x[3], x[4], x[5])); break; } ExceptionCode ec = 0; list->appendItem(t, ec); skipOptionalSpaces(currTransform, end); if (currTransform < end && *currTransform == ',') { delimParsed = true; currTransform++; } skipOptionalSpaces(currTransform, end); } return !delimParsed; }
void ValidationMessageClientImpl::showValidationMessage(const Element& anchor, const String& message) { if (message.isEmpty()) { hideValidationMessage(anchor); return; } if (!anchor.renderBox()) return; if (m_currentAnchor) hideValidationMessage(*m_currentAnchor); m_currentAnchor = &anchor; IntRect anchorInRootView = currentView()->contentsToRootView(anchor.pixelSnappedBoundingBox()); m_lastAnchorRectInScreen = currentView()->hostWindow()->rootViewToScreen(anchorInRootView); m_lastPageScaleFactor = m_webView.pageScaleFactor(); m_message = message; WebTextDirection dir = m_currentAnchor->renderer()->style()->direction() == RTL ? WebTextDirectionRightToLeft : WebTextDirectionLeftToRight; AtomicString title = m_currentAnchor->fastGetAttribute(HTMLNames::titleAttr); if (m_client) m_client->showValidationMessage(anchorInRootView, m_message, title, dir); m_webView.client()->showValidationMessage(anchorInRootView, m_message, title, dir); const double minimumSecondToShowValidationMessage = 5.0; const double secondPerCharacter = 0.05; const double statusCheckInterval = 0.1; m_finishTime = monotonicallyIncreasingTime() + std::max(minimumSecondToShowValidationMessage, (message.length() + title.length()) * secondPerCharacter); // FIXME: We should invoke checkAnchorStatus actively when layout, scroll, // or page scale change happen. m_timer.startRepeating(statusCheckInterval); }
static inline bool containsHTMLSpace(const AtomicString& string) { if (LIKELY(string.is8Bit())) return containsHTMLSpaceTemplate<LChar>(string.characters8(), string.length()); return containsHTMLSpaceTemplate<UChar>(string.characters16(), string.length()); }
bool SelectorChecker::checkPseudoClass(const SelectorCheckingContext& context) const { ASSERT(context.element); Element& element = *context.element; ASSERT(context.selector); const CSSSelector& selector = *context.selector; ASSERT(selector.match() == CSSSelector::PseudoClass); // Normal element pseudo class checking. switch (selector.pseudoType()) { case CSSSelector::PseudoFocus: if (m_mode == ResolvingStyle) { if (context.elementStyle) context.elementStyle->setAffectedByFocus(); } return matchesFocusPseudoClass(element); case CSSSelector::PseudoHover: if (m_mode == ResolvingStyle) { if (context.elementStyle) context.elementStyle->setAffectedByHover(); } return element.hovered(); case CSSSelector::PseudoActive: if (m_mode == ResolvingStyle) { if (context.elementStyle) context.elementStyle->setAffectedByActive(); } return element.active(); case CSSSelector::PseudoLang: { AtomicString value = element.computeInheritedLanguage(); const AtomicString& argument = selector.argument(); if (value.isEmpty() || !value.startsWith(argument, false)) break; if (value.length() != argument.length() && value[argument.length()] != '-') break; return true; } case CSSSelector::PseudoUnresolved: return element.isUnresolvedCustomElement(); case CSSSelector::PseudoHost: { if (m_mode == SharingRules) return true; // :host only matches a shadow host when :host is in a shadow tree of the shadow host. if (!context.scope) return false; const ContainerNode* shadowHost = context.scope->shadowHost(); if (!shadowHost || shadowHost != element) return false; ASSERT(element.shadow()); // For empty parameter case, i.e. just :host or :host(). if (!selector.selectorList()) return true; SelectorCheckingContext subContext(context); subContext.contextFlags = TreatShadowHostAsNormalScope; for (subContext.selector = selector.selectorList()->first(); subContext.selector; subContext.selector = CSSSelectorList::next(*subContext.selector)) { if (match(subContext)) return true; } return false; } case CSSSelector::PseudoUnknown: case CSSSelector::PseudoNotParsed: case CSSSelector::PseudoUserAgentCustomElement: return false; } ASSERT_NOT_REACHED(); return false; }