void CSSAnimations::calculateTransitionActiveInterpolations(CSSAnimationUpdate& update, const Element* animatingElement, double timelineCurrentTime) { ElementAnimations* elementAnimations = animatingElement ? animatingElement->elementAnimations() : nullptr; AnimationStack* animationStack = elementAnimations ? &elementAnimations->defaultStack() : nullptr; ActiveInterpolationsMap activeInterpolationsForTransitions; if (update.newTransitions().isEmpty() && update.cancelledTransitions().isEmpty()) { activeInterpolationsForTransitions = AnimationStack::activeInterpolations(animationStack, 0, 0, KeyframeEffect::TransitionPriority, timelineCurrentTime); } else { HeapVector<Member<InertEffect>> newTransitions; for (const auto& entry : update.newTransitions()) newTransitions.append(entry.value.effect.get()); HeapHashSet<Member<const Animation>> cancelledAnimations; if (!update.cancelledTransitions().isEmpty()) { ASSERT(elementAnimations); const TransitionMap& transitionMap = elementAnimations->cssAnimations().m_transitions; for (CSSPropertyID id : update.cancelledTransitions()) { ASSERT(transitionMap.contains(id)); cancelledAnimations.add(transitionMap.get(id).animation.get()); } } activeInterpolationsForTransitions = AnimationStack::activeInterpolations(animationStack, &newTransitions, &cancelledAnimations, KeyframeEffect::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 NodeSet::traversalSort() const { HeapHashSet<Member<Node>> nodes; bool containsAttributeNodes = false; unsigned nodeCount = m_nodes.size(); DCHECK_GT(nodeCount, 1u); for (unsigned i = 0; i < nodeCount; ++i) { Node* node = m_nodes[i].get(); nodes.add(node); if (node->isAttributeNode()) containsAttributeNodes = true; } HeapVector<Member<Node>> sortedNodes; sortedNodes.reserveInitialCapacity(nodeCount); for (Node& n : NodeTraversal::startsAt(*findRootNode(m_nodes.first()))) { if (nodes.contains(&n)) sortedNodes.append(&n); if (!containsAttributeNodes || !n.isElementNode()) continue; Element* element = toElement(&n); AttributeCollection attributes = element->attributes(); for (auto& attribute : attributes) { Attr* attr = element->attrIfExists(attribute.name()); if (attr && nodes.contains(attr)) sortedNodes.append(attr); } } DCHECK_EQ(sortedNodes.size(), nodeCount); const_cast<HeapVector<Member<Node>>&>(m_nodes).swap(sortedNodes); }
void V8MutationObserver::visitDOMWrapper(v8::Isolate* isolate, ScriptWrappable* scriptWrappable, const v8::Persistent<v8::Object>& wrapper) { MutationObserver* observer = scriptWrappable->toImpl<MutationObserver>(); HeapHashSet<Member<Node>> observedNodes = observer->getObservedNodes(); for (HeapHashSet<Member<Node>>::iterator it = observedNodes.begin(); it != observedNodes.end(); ++it) { v8::UniqueId id(reinterpret_cast<intptr_t>(V8GCController::opaqueRootForGC(isolate, *it))); isolate->SetReferenceFromGroup(id, wrapper); } }
TEST_F(AnimationEffectStackTest, CancelledAnimations) { HeapHashSet<Member<const Animation>> cancelledAnimations; Animation* animation = play(makeKeyframeEffect(makeEffectModel(CSSPropertyFontSize, AnimatableDouble::create(1))), 0); cancelledAnimations.add(animation); play(makeKeyframeEffect( makeEffectModel(CSSPropertyZIndex, AnimatableDouble::create(2))), 0); ActiveInterpolationsMap result = EffectStack::activeInterpolations( &element->elementAnimations()->effectStack(), 0, &cancelledAnimations, KeyframeEffectReadOnly::DefaultPriority); EXPECT_EQ(1u, result.size()); EXPECT_TRUE(interpolationValue(result, CSSPropertyZIndex) ->equals(AnimatableDouble::create(2).get())); }
std::unique_ptr<MessagePortChannelArray> MessagePort::disentanglePorts( ExecutionContext* context, const MessagePortArray& ports, ExceptionState& exceptionState) { if (!ports.size()) return nullptr; HeapHashSet<Member<MessagePort>> visited; // Walk the incoming array - if there are any duplicate ports, or null ports // or cloned ports, throw an error (per section 8.3.3 of the HTML5 spec). for (unsigned i = 0; i < ports.size(); ++i) { MessagePort* port = ports[i]; if (!port || port->isNeutered() || visited.contains(port)) { String type; if (!port) type = "null"; else if (port->isNeutered()) type = "already neutered"; else type = "a duplicate"; exceptionState.throwDOMException( DataCloneError, "Port at index " + String::number(i) + " is " + type + "."); return nullptr; } visited.add(port); } UseCounter::count(context, UseCounter::MessagePortsTransferred); // Passed-in ports passed validity checks, so we can disentangle them. std::unique_ptr<MessagePortChannelArray> portArray = wrapUnique(new MessagePortChannelArray(ports.size())); for (unsigned i = 0; i < ports.size(); ++i) (*portArray)[i] = ports[i]->disentangle(); return portArray; }
void SVGPatternElement::collectPatternAttributes(PatternAttributes& attributes) const { HeapHashSet<Member<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 ScopedStyleResolver::collectFeaturesTo( RuleFeatureSet& features, HeapHashSet<Member<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 AXTable::addChildren() { ASSERT(!isDetached()); if (!isAXTable()) { AXLayoutObject::addChildren(); return; } ASSERT(!m_haveChildren); m_haveChildren = true; if (!m_layoutObject || !m_layoutObject->isTable()) return; LayoutTable* table = toLayoutTable(m_layoutObject); AXObjectCacheImpl& axCache = axObjectCache(); Node* tableNode = table->node(); if (!isHTMLTableElement(tableNode)) return; // Add caption if (HTMLTableCaptionElement* caption = toHTMLTableElement(tableNode)->caption()) { AXObject* captionObject = axCache.getOrCreate(caption); if (captionObject && !captionObject->accessibilityIsIgnored()) m_children.append(captionObject); } // Go through all the available sections to pull out the rows and add them as children. table->recalcSectionsIfNeeded(); LayoutTableSection* tableSection = table->topSection(); if (!tableSection) return; LayoutTableSection* initialTableSection = tableSection; while (tableSection) { HeapHashSet<Member<AXObject>> appendedRows; unsigned numRows = tableSection->numRows(); for (unsigned rowIndex = 0; rowIndex < numRows; ++rowIndex) { LayoutTableRow* layoutRow = tableSection->rowLayoutObjectAt(rowIndex); if (!layoutRow) continue; AXObject* rowObject = axCache.getOrCreate(layoutRow); if (!rowObject || !rowObject->isTableRow()) continue; AXTableRow* row = toAXTableRow(rowObject); // We need to check every cell for a new row, because cell spans // can cause us to miss rows if we just check the first column. if (appendedRows.contains(row)) continue; row->setRowIndex(static_cast<int>(m_rows.size())); m_rows.append(row); if (!row->accessibilityIsIgnored()) m_children.append(row); appendedRows.add(row); } tableSection = table->sectionBelow(tableSection, SkipEmptySections); } // make the columns based on the number of columns in the first body unsigned length = initialTableSection->numEffectiveColumns(); for (unsigned i = 0; i < length; ++i) { AXTableColumn* column = toAXTableColumn(axCache.getOrCreate(ColumnRole)); column->setColumnIndex((int)i); column->setParent(this); m_columns.append(column); if (!column->accessibilityIsIgnored()) m_children.append(column); } AXObject* headerContainerObject = headerContainer(); if (headerContainerObject && !headerContainerObject->accessibilityIsIgnored()) m_children.append(headerContainerObject); }
static void sortBlock(unsigned from, unsigned to, HeapVector<NodeSetVector>& parentMatrix, bool mayContainAttributeNodes) { // Should not call this function with less that two nodes to sort. DCHECK_LT(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. HeapHashSet<Member<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); DCHECK_NE(previousGroupEnd, groupEnd); previousGroupEnd = groupEnd; #if DCHECK_IS_ON() parentNodes.remove(n); #endif } } DCHECK(parentNodes.isEmpty()); }
SMILTime SMILTimeContainer::updateAnimations(SMILTime elapsed, bool seekToTime) { ASSERT(document().isActive()); 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(); HeapHashSet<ElementAttributePair> invalidKeys; using AnimationsVector = HeapVector<Member<SVGSMILElement>>; AnimationsVector animationsToApply; AnimationsVector scheduledAnimationsInSameGroup; for (const auto& entry : m_scheduledAnimations) { if (!entry.key.first || entry.value->isEmpty()) { invalidKeys.add(entry.key); continue; } // 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. copyToVector(*entry.value, scheduledAnimationsInSameGroup); std::sort(scheduledAnimationsInSameGroup.begin(), scheduledAnimationsInSameGroup.end(), PriorityCompare(elapsed)); SVGSMILElement* resultElement = nullptr; for (const auto& itAnimation : scheduledAnimationsInSameGroup) { SVGSMILElement* animation = itAnimation.get(); ASSERT(animation->timeContainer() == this); ASSERT(animation->targetElement()); ASSERT(animation->hasValidAttributeName()); ASSERT(animation->hasValidAttributeType()); // Results are accumulated to the first animation that animates and contributes to a particular element/attribute pair. if (!resultElement) { resultElement = animation; resultElement->lockAnimatedType(); } // This will calculate the contribution from the animation and add it to the resultElement. if (!animation->progress(elapsed, resultElement, seekToTime) && resultElement == animation) { resultElement->unlockAnimatedType(); resultElement->clearAnimatedType(); resultElement = nullptr; } SMILTime nextFireTime = animation->nextProgressTime(); if (nextFireTime.isFinite()) earliestFireTime = std::min(nextFireTime, earliestFireTime); } if (resultElement) { animationsToApply.append(resultElement); resultElement->unlockAnimatedType(); } } 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]->inShadowIncludingDocument() && animationsToApply[i]->isSVGDiscardElement()) { SVGSMILElement* animDiscard = animationsToApply[i]; SVGElement* targetElement = animDiscard->targetElement(); if (targetElement && targetElement->inShadowIncludingDocument()) { targetElement->remove(IGNORE_EXCEPTION); ASSERT(!targetElement->inShadowIncludingDocument()); } if (animDiscard->inShadowIncludingDocument()) { animDiscard->remove(IGNORE_EXCEPTION); ASSERT(!animDiscard->inShadowIncludingDocument()); } } } return earliestFireTime; }