bool SVGLinearGradientElement::collectGradientAttributes(LinearGradientAttributes& attributes) { if (!renderer()) return false; WillBeHeapHashSet<RawPtrWillBeMember<SVGGradientElement> > processedGradients; SVGGradientElement* current = this; setGradientAttributes(current, attributes); processedGradients.add(current); while (true) { // Respect xlink:href, take attributes from referenced element Node* refNode = SVGURIReference::targetElementFromIRIString(current->href()->currentValue()->value(), treeScope()); if (refNode && isSVGGradientElement(*refNode)) { current = toSVGGradientElement(refNode); // Cycle detection if (processedGradients.contains(current)) return true; if (!current->renderer()) return false; setGradientAttributes(current, attributes, isSVGLinearGradientElement(*current)); processedGradients.add(current); } else { return true; } } ASSERT_NOT_REACHED(); return false; }
void MutationObserverRegistration::addRegistrationNodesToSet(WillBeHeapHashSet<RawPtrWillBeMember<Node>>& nodes) const { ASSERT(m_registrationNode); nodes.add(m_registrationNode.get()); if (!m_transientRegistrationNodes) return; for (NodeHashSet::const_iterator iter = m_transientRegistrationNodes->begin(); iter != m_transientRegistrationNodes->end(); ++iter) nodes.add(iter->get()); }
void ScriptRunner::executeScripts() { RefPtrWillBeRawPtr<Document> protect(m_document.get()); WillBeHeapDeque<RawPtrWillBeMember<ScriptLoader>> scriptLoaders; scriptLoaders.swap(m_scriptsToExecuteSoon); WillBeHeapHashSet<RawPtrWillBeMember<ScriptLoader>> inorderSet; while (!m_scriptsToExecuteInOrder.isEmpty() && m_scriptsToExecuteInOrder.first()->isReady()) { ScriptLoader* script = m_scriptsToExecuteInOrder.takeFirst(); inorderSet.add(script); scriptLoaders.append(script); } while (!scriptLoaders.isEmpty()) { scriptLoaders.takeFirst()->execute(); m_document->decrementLoadEventDelayCount(); if (yieldForHighPriorityWork()) break; } // If we have to yield, we must re-enqueue any scriptLoaders back onto the front of // m_scriptsToExecuteInOrder or m_scriptsToExecuteSoon depending on where the script // came from. // NOTE a yield followed by a notifyScriptReady(... ASYNC_EXECUTION) will result in that script executing // before any pre-existing ScriptsToExecuteInOrder. while (!scriptLoaders.isEmpty()) { ScriptLoader* script = scriptLoaders.takeLast(); if (inorderSet.contains(script)) m_scriptsToExecuteInOrder.prepend(script); else m_scriptsToExecuteSoon.prepend(script); } }
void CSSAnimations::calculateTransitionActiveInterpolations(CSSAnimationUpdate* update, const Element* animatingElement, double timelineCurrentTime) { ActiveAnimations* activeAnimations = animatingElement ? animatingElement->activeAnimations() : nullptr; AnimationStack* animationStack = activeAnimations ? &activeAnimations->defaultStack() : nullptr; WillBeHeapHashMap<CSSPropertyID, RefPtrWillBeMember<Interpolation>> activeInterpolationsForTransitions; if (update->newTransitions().isEmpty() && update->cancelledTransitions().isEmpty()) { activeInterpolationsForTransitions = AnimationStack::activeInterpolations(animationStack, 0, 0, Animation::TransitionPriority, timelineCurrentTime); } else { WillBeHeapVector<RawPtrWillBeMember<InertAnimation>> newTransitions; for (const auto& entry : update->newTransitions()) newTransitions.append(entry.value.animation.get()); WillBeHeapHashSet<RawPtrWillBeMember<const AnimationPlayer>> cancelledAnimationPlayers; if (!update->cancelledTransitions().isEmpty()) { ASSERT(activeAnimations); const TransitionMap& transitionMap = activeAnimations->cssAnimations().m_transitions; for (CSSPropertyID id : update->cancelledTransitions()) { ASSERT(transitionMap.contains(id)); cancelledAnimationPlayers.add(transitionMap.get(id).player.get()); } } activeInterpolationsForTransitions = AnimationStack::activeInterpolations(animationStack, &newTransitions, &cancelledAnimationPlayers, Animation::TransitionPriority, timelineCurrentTime); } // Properties being animated by animations don't get values from transitions applied. if (!update->activeInterpolationsForAnimations().isEmpty() && !activeInterpolationsForTransitions.isEmpty()) { for (const auto& entry : update->activeInterpolationsForAnimations()) activeInterpolationsForTransitions.remove(entry.key); } update->adoptActiveInterpolationsForTransitions(activeInterpolationsForTransitions); }
void findGoodTouchTargets(const IntRect& touchBox, LocalFrame* mainFrame, Vector<IntRect>& goodTargets, WillBeHeapVector<RawPtrWillBeMember<Node> >& highlightNodes) { goodTargets.clear(); int touchPointPadding = ceil(std::max(touchBox.width(), touchBox.height()) * 0.5); IntPoint touchPoint = touchBox.center(); IntPoint contentsPoint = mainFrame->view()->windowToContents(touchPoint); HitTestResult result = mainFrame->eventHandler().hitTestResultAtPoint(contentsPoint, HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::ConfusingAndOftenMisusedDisallowShadowContent, IntSize(touchPointPadding, touchPointPadding)); const WillBeHeapListHashSet<RefPtrWillBeMember<Node> >& hitResults = result.rectBasedTestResult(); // Blacklist nodes that are container of disambiguated nodes. // It is not uncommon to have a clickable <div> that contains other clickable objects. // This heuristic avoids excessive disambiguation in that case. WillBeHeapHashSet<RawPtrWillBeMember<Node> > blackList; for (WillBeHeapListHashSet<RefPtrWillBeMember<Node> >::const_iterator it = hitResults.begin(); it != hitResults.end(); ++it) { // Ignore any Nodes that can't be clicked on. RenderObject* renderer = it->get()->renderer(); if (!renderer || !it->get()->willRespondToMouseClickEvents()) continue; // Blacklist all of the Node's containers. for (RenderBlock* container = renderer->containingBlock(); container; container = container->containingBlock()) { Node* containerNode = container->node(); if (!containerNode) continue; if (!blackList.add(containerNode).isNewEntry) break; } } WillBeHeapHashMap<RawPtrWillBeMember<Node>, TouchTargetData> touchTargets; float bestScore = 0; for (WillBeHeapListHashSet<RefPtrWillBeMember<Node> >::const_iterator it = hitResults.begin(); it != hitResults.end(); ++it) { for (Node* node = it->get(); node; node = node->parentNode()) { if (blackList.contains(node)) continue; if (node->isDocumentNode() || isHTMLHtmlElement(*node) || isHTMLBodyElement(*node)) break; if (node->willRespondToMouseClickEvents()) { TouchTargetData& targetData = touchTargets.add(node, TouchTargetData()).storedValue->value; targetData.windowBoundingBox = boundingBoxForEventNodes(node); targetData.score = scoreTouchTarget(touchPoint, touchPointPadding, targetData.windowBoundingBox); bestScore = std::max(bestScore, targetData.score); break; } } } for (WillBeHeapHashMap<RawPtrWillBeMember<Node>, TouchTargetData>::iterator it = touchTargets.begin(); it != touchTargets.end(); ++it) { // Currently the scoring function uses the overlap area with the fat point as the score. // We ignore the candidates that has less than 1/2 overlap (we consider not really ambiguous enough) than the best candidate to avoid excessive popups. if (it->value.score < bestScore * 0.5) continue; goodTargets.append(it->value.windowBoundingBox); highlightNodes.append(it->key); } }
TEST_F(AnimationAnimationStackTest, CancelledAnimationPlayers) { WillBeHeapHashSet<RawPtrWillBeMember<const AnimationPlayer>> cancelledAnimationPlayers; RefPtrWillBeRawPtr<AnimationPlayer> player = play(makeAnimation(makeAnimationEffect(CSSPropertyFontSize, AnimatableDouble::create(1))).get(), 0); cancelledAnimationPlayers.add(player.get()); play(makeAnimation(makeAnimationEffect(CSSPropertyZIndex, AnimatableDouble::create(2))).get(), 0); WillBeHeapHashMap<CSSPropertyID, RefPtrWillBeMember<Interpolation>> result = AnimationStack::activeInterpolations(&element->elementAnimations()->defaultStack(), 0, &cancelledAnimationPlayers, Animation::DefaultPriority, 0); EXPECT_EQ(1u, result.size()); EXPECT_TRUE(interpolationValue(result.get(CSSPropertyZIndex))->equals(AnimatableDouble::create(2).get())); }
bool SVGRadialGradientElement::collectGradientAttributes(RadialGradientAttributes& attributes) { if (!renderer()) return false; WillBeHeapHashSet<RawPtrWillBeMember<SVGGradientElement> > processedGradients; SVGGradientElement* current = this; setGradientAttributes(current, attributes); processedGradients.add(current); while (true) { // Respect xlink:href, take attributes from referenced element Node* refNode = SVGURIReference::targetElementFromIRIString(current->href()->currentValue()->value(), treeScope()); if (refNode && isSVGGradientElement(*refNode)) { current = toSVGGradientElement(refNode); // Cycle detection if (processedGradients.contains(current)) break; if (!current->renderer()) return false; setGradientAttributes(current, attributes, isSVGRadialGradientElement(*current)); processedGradients.add(current); } else { break; } } // Handle default values for fx/fy if (!attributes.hasFx()) attributes.setFx(attributes.cx()); if (!attributes.hasFy()) attributes.setFy(attributes.cy()); return true; }
void ScopedStyleResolver::collectFeaturesTo(RuleFeatureSet& features, WillBeHeapHashSet<RawPtrWillBeMember<const StyleSheetContents>>& visitedSharedStyleSheetContents) const { for (size_t i = 0; i < m_authorStyleSheets.size(); ++i) { ASSERT(m_authorStyleSheets[i]->ownerNode()); StyleSheetContents* contents = m_authorStyleSheets[i]->contents(); if (contents->hasOneClient() || visitedSharedStyleSheetContents.add(contents).isNewEntry) features.add(contents->ruleSet().features()); } if (!m_treeBoundaryCrossingRuleSet) return; for (const auto& rules : *m_treeBoundaryCrossingRuleSet) features.add(rules->m_ruleSet->features()); }
void RadioButtonGroup::add(HTMLInputElement* button) { ASSERT(button->type() == InputTypeNames::radio); if (!m_members.add(button).isNewEntry) return; bool groupWasValid = isValid(); if (button->isRequired()) ++m_requiredCount; if (button->checked()) setCheckedButton(button); bool groupIsValid = isValid(); if (groupWasValid != groupIsValid) { setNeedsValidityCheckForAllButtons(); } else if (!groupIsValid) { // A radio button not in a group is always valid. We need to make it // invalid only if the group is invalid. button->setNeedsValidityCheck(); } }
void NodeSet::traversalSort() const { WillBeHeapHashSet<RawPtrWillBeMember<Node> > nodes; bool containsAttributeNodes = false; unsigned nodeCount = m_nodes.size(); ASSERT(nodeCount > 1); for (unsigned i = 0; i < nodeCount; ++i) { Node* node = m_nodes[i].get(); nodes.add(node); if (node->isAttributeNode()) containsAttributeNodes = true; } WillBeHeapVector<RefPtrWillBeMember<Node> > sortedNodes; sortedNodes.reserveInitialCapacity(nodeCount); for (Node* n = findRootNode(m_nodes.first().get()); n; n = NodeTraversal::next(*n)) { if (nodes.contains(n)) sortedNodes.append(n); if (!containsAttributeNodes || !n->isElementNode()) continue; Element* element = toElement(n); if (!element->hasAttributes()) continue; AttributeCollection attributes = element->attributes(); AttributeCollection::const_iterator end = attributes.end(); for (AttributeCollection::const_iterator it = attributes.begin(); it != end; ++it) { RefPtrWillBeRawPtr<Attr> attr = element->attrIfExists(it->name()); if (attr && nodes.contains(attr.get())) sortedNodes.append(attr); } } ASSERT(sortedNodes.size() == nodeCount); const_cast<WillBeHeapVector<RefPtrWillBeMember<Node> >&>(m_nodes).swap(sortedNodes); }
void LocationPath::evaluate(EvaluationContext& context, NodeSet& nodes) const { bool resultIsSorted = nodes.isSorted(); for (unsigned i = 0; i < m_steps.size(); i++) { Step* step = m_steps[i]; NodeSet* newNodes = NodeSet::create(); WillBeHeapHashSet<RawPtrWillBeMember<Node>> newNodesSet; bool needToCheckForDuplicateNodes = !nodes.subtreesAreDisjoint() || (step->axis() != Step::ChildAxis && step->axis() != Step::SelfAxis && step->axis() != Step::DescendantAxis && step->axis() != Step::DescendantOrSelfAxis && step->axis() != Step::AttributeAxis); if (needToCheckForDuplicateNodes) resultIsSorted = false; // This is a simplified check that can be improved to handle more cases. if (nodes.subtreesAreDisjoint() && (step->axis() == Step::ChildAxis || step->axis() == Step::SelfAxis)) newNodes->markSubtreesDisjoint(true); for (unsigned j = 0; j < nodes.size(); j++) { NodeSet* matches = NodeSet::create(); step->evaluate(context, nodes[j], *matches); if (!matches->isSorted()) resultIsSorted = false; for (size_t nodeIndex = 0; nodeIndex < matches->size(); ++nodeIndex) { Node* node = (*matches)[nodeIndex]; if (!needToCheckForDuplicateNodes || newNodesSet.add(node).isNewEntry) newNodes->append(node); } } nodes.swap(*newNodes); } nodes.markSorted(resultIsSorted); }
void SVGPatternElement::collectPatternAttributes(PatternAttributes& attributes) const { WillBeHeapHashSet<RawPtrWillBeMember<const SVGPatternElement>> processedPatterns; const SVGPatternElement* current = this; while (true) { setPatternAttributes(current, attributes); processedPatterns.add(current); // Respect xlink:href, take attributes from referenced element Node* refNode = SVGURIReference::targetElementFromIRIString(current->hrefString(), treeScope()); // Only consider attached SVG pattern elements. if (!isSVGPatternElement(refNode) || !refNode->layoutObject()) break; current = toSVGPatternElement(refNode); // Cycle detection if (processedPatterns.contains(current)) break; } }
void NodeSet::traversalSort() const { WillBeHeapHashSet<RawPtrWillBeMember<Node>> nodes; bool containsAttributeNodes = false; unsigned nodeCount = m_nodes.size(); ASSERT(nodeCount > 1); for (unsigned i = 0; i < nodeCount; ++i) { Node* node = m_nodes[i].get(); nodes.add(node); if (node->isAttributeNode()) containsAttributeNodes = true; } WillBeHeapVector<RefPtrWillBeMember<Node>> sortedNodes; sortedNodes.reserveInitialCapacity(nodeCount); for (Node& n : NodeTraversal::startsAt(findRootNode(m_nodes.first().get()))) { if (nodes.contains(&n)) sortedNodes.append(&n); if (!containsAttributeNodes || !n.isElementNode()) continue; Element* element = toElement(&n); AttributeCollection attributes = element->attributes(); for (auto& attribute : attributes) { RefPtrWillBeRawPtr<Attr> attr = element->attrIfExists(attribute.name()); if (attr && nodes.contains(attr.get())) sortedNodes.append(attr); } } ASSERT(sortedNodes.size() == nodeCount); const_cast<WillBeHeapVector<RefPtrWillBeMember<Node>>&>(m_nodes).swap(sortedNodes); }
static void sortBlock(unsigned from, unsigned to, WillBeHeapVector<NodeSetVector>& parentMatrix, bool mayContainAttributeNodes) { // Should not call this function with less that two nodes to sort. ASSERT(from + 1 < to); unsigned minDepth = UINT_MAX; for (unsigned i = from; i < to; ++i) { unsigned depth = parentMatrix[i].size() - 1; if (minDepth > depth) minDepth = depth; } // Find the common ancestor. unsigned commonAncestorDepth = minDepth; Node* commonAncestor; while (true) { commonAncestor = parentWithDepth(commonAncestorDepth, parentMatrix[from]); if (commonAncestorDepth == 0) break; bool allEqual = true; for (unsigned i = from + 1; i < to; ++i) { if (commonAncestor != parentWithDepth(commonAncestorDepth, parentMatrix[i])) { allEqual = false; break; } } if (allEqual) break; --commonAncestorDepth; } if (commonAncestorDepth == minDepth) { // One of the nodes is the common ancestor => it is the first in // document order. Find it and move it to the beginning. for (unsigned i = from; i < to; ++i) { if (commonAncestor == parentMatrix[i][0]) { parentMatrix[i].swap(parentMatrix[from]); if (from + 2 < to) sortBlock(from + 1, to, parentMatrix, mayContainAttributeNodes); return; } } } if (mayContainAttributeNodes && commonAncestor->isElementNode()) { // The attribute nodes and namespace nodes of an element occur before // the children of the element. The namespace nodes are defined to occur // before the attribute nodes. The relative order of namespace nodes is // implementation-dependent. The relative order of attribute nodes is // implementation-dependent. unsigned sortedEnd = from; // FIXME: namespace nodes are not implemented. for (unsigned i = sortedEnd; i < to; ++i) { Node* n = parentMatrix[i][0]; if (n->isAttributeNode() && toAttr(n)->ownerElement() == commonAncestor) parentMatrix[i].swap(parentMatrix[sortedEnd++]); } if (sortedEnd != from) { if (to - sortedEnd > 1) sortBlock(sortedEnd, to, parentMatrix, mayContainAttributeNodes); return; } } // Children nodes of the common ancestor induce a subdivision of our // node-set. Sort it according to this subdivision, and recursively sort // each group. WillBeHeapHashSet<RawPtrWillBeMember<Node> > parentNodes; for (unsigned i = from; i < to; ++i) parentNodes.add(parentWithDepth(commonAncestorDepth + 1, parentMatrix[i])); unsigned previousGroupEnd = from; unsigned groupEnd = from; for (Node* n = commonAncestor->firstChild(); n; n = n->nextSibling()) { // If parentNodes contains the node, perform a linear search to move its // children in the node-set to the beginning. if (parentNodes.contains(n)) { for (unsigned i = groupEnd; i < to; ++i) { if (parentWithDepth(commonAncestorDepth + 1, parentMatrix[i]) == n) parentMatrix[i].swap(parentMatrix[groupEnd++]); } if (groupEnd - previousGroupEnd > 1) sortBlock(previousGroupEnd, groupEnd, parentMatrix, mayContainAttributeNodes); ASSERT(previousGroupEnd != groupEnd); previousGroupEnd = groupEnd; #ifndef NDEBUG parentNodes.remove(n); #endif } } ASSERT(parentNodes.isEmpty()); }
SMILTime SMILTimeContainer::updateAnimations(SMILTime elapsed, bool seekToTime) { SMILTime earliestFireTime = SMILTime::unresolved(); #if ENABLE(ASSERT) // This boolean will catch any attempts to schedule/unschedule scheduledAnimations during this critical section. // Similarly, any elements removed will unschedule themselves, so this will catch modification of animationsToApply. m_preventScheduledAnimationsChanges = true; #endif if (m_documentOrderIndexesDirty) updateDocumentOrderIndexes(); WillBeHeapHashSet<ElementAttributePair> invalidKeys; using AnimationsVector = WillBeHeapVector<RefPtrWillBeMember<SVGSMILElement>>; AnimationsVector animationsToApply; for (const auto& entry : m_scheduledAnimations) { if (!entry.key.first || entry.value->isEmpty()) { invalidKeys.add(entry.key); continue; } AnimationsLinkedHashSet* scheduled = entry.value.get(); // Sort according to priority. Elements with later begin time have higher priority. // In case of a tie, document order decides. // FIXME: This should also consider timing relationships between the elements. Dependents // have higher priority. AnimationsVector scheduledAnimations; copyToVector(*scheduled, scheduledAnimations); std::sort(scheduledAnimations.begin(), scheduledAnimations.end(), PriorityCompare(elapsed)); SVGSMILElement* resultElement = nullptr; for (const auto& itAnimation : scheduledAnimations) { SVGSMILElement* animation = itAnimation.get(); ASSERT(animation->timeContainer() == this); ASSERT(animation->targetElement()); ASSERT(animation->hasValidAttributeName()); // Results are accumulated to the first animation that animates and contributes to a particular element/attribute pair. // FIXME: we should ensure that resultElement is of an appropriate type. if (!resultElement) { if (!animation->hasValidAttributeType()) continue; resultElement = animation; } // This will calculate the contribution from the animation and add it to the resultsElement. if (!animation->progress(elapsed, resultElement, seekToTime) && resultElement == animation) resultElement = nullptr; SMILTime nextFireTime = animation->nextProgressTime(); if (nextFireTime.isFinite()) earliestFireTime = std::min(nextFireTime, earliestFireTime); } if (resultElement) animationsToApply.append(resultElement); } m_scheduledAnimations.removeAll(invalidKeys); std::sort(animationsToApply.begin(), animationsToApply.end(), PriorityCompare(elapsed)); unsigned animationsToApplySize = animationsToApply.size(); if (!animationsToApplySize) { #if ENABLE(ASSERT) m_preventScheduledAnimationsChanges = false; #endif return earliestFireTime; } // Apply results to target elements. for (unsigned i = 0; i < animationsToApplySize; ++i) animationsToApply[i]->applyResultsToTarget(); #if ENABLE(ASSERT) m_preventScheduledAnimationsChanges = false; #endif for (unsigned i = 0; i < animationsToApplySize; ++i) { if (animationsToApply[i]->inDocument() && animationsToApply[i]->isSVGDiscardElement()) { RefPtrWillBeRawPtr<SVGSMILElement> animDiscard = animationsToApply[i]; RefPtrWillBeRawPtr<SVGElement> targetElement = animDiscard->targetElement(); if (targetElement && targetElement->inDocument()) { targetElement->remove(IGNORE_EXCEPTION); ASSERT(!targetElement->inDocument()); } if (animDiscard->inDocument()) { animDiscard->remove(IGNORE_EXCEPTION); ASSERT(!animDiscard->inDocument()); } } } return earliestFireTime; }