TEST_F(MemoryCacheTest, ResourceMapIsolation) { ResourcePtr<FakeResource> resource1 = new FakeResource(ResourceRequest("http://test/resource"), Resource::Raw); memoryCache()->add(resource1.get()); ResourcePtr<FakeResource> resource2 = new FakeResource(ResourceRequest("http://test/resource"), Resource::Raw); resource2->setCacheIdentifier("foo"); memoryCache()->add(resource2.get()); EXPECT_TRUE(memoryCache()->contains(resource1.get())); EXPECT_TRUE(memoryCache()->contains(resource2.get())); const KURL url = KURL(ParsedURLString, "http://test/resource"); EXPECT_EQ(resource1.get(), memoryCache()->resourceForURL(url)); EXPECT_EQ(resource1.get(), memoryCache()->resourceForURL(url, memoryCache()->defaultCacheIdentifier())); EXPECT_EQ(resource2.get(), memoryCache()->resourceForURL(url, "foo")); EXPECT_EQ(0, memoryCache()->resourceForURL(KURL())); ResourcePtr<FakeResource> resource3 = new FakeResource(ResourceRequest("http://test/resource"), Resource::Raw); resource3->setCacheIdentifier("foo"); memoryCache()->remove(resource2.get()); memoryCache()->add(resource3.get()); EXPECT_TRUE(memoryCache()->contains(resource1.get())); EXPECT_FALSE(memoryCache()->contains(resource2.get())); EXPECT_TRUE(memoryCache()->contains(resource3.get())); ResourcePtr<FakeResource> resource4 = new FakeResource(ResourceRequest("http://test/resource"), Resource::Raw); resource4->setCacheIdentifier("foo"); memoryCache()->replace(resource4.get(), resource3.get()); EXPECT_TRUE(memoryCache()->contains(resource1.get())); EXPECT_FALSE(memoryCache()->contains(resource3.get())); EXPECT_TRUE(memoryCache()->contains(resource4.get())); WillBeHeapVector<RawPtrWillBeMember<Resource>> resources = memoryCache()->resourcesForURL(url); EXPECT_EQ(2u, resources.size()); memoryCache()->evictResources(); EXPECT_FALSE(memoryCache()->contains(resource1.get())); EXPECT_FALSE(memoryCache()->contains(resource3.get())); }
bool HTMLFormControlElement::reportValidity() { WillBeHeapVector<RefPtrWillBeMember<HTMLFormControlElement>> unhandledInvalidControls; bool isValid = checkValidity(&unhandledInvalidControls, CheckValidityDispatchInvalidEvent); if (isValid || unhandledInvalidControls.isEmpty()) return isValid; ASSERT(unhandledInvalidControls.size() == 1); ASSERT(unhandledInvalidControls[0].get() == this); // Update layout now before calling isFocusable(), which has // !layoutObject()->needsLayout() assertion. document().updateLayoutIgnorePendingStylesheets(); if (isFocusable()) { showValidationMessage(); return false; } if (document().frame()) { String message("An invalid form control with name='%name' is not focusable."); message.replace("%name", name()); document().addConsoleMessage(ConsoleMessage::create(RenderingMessageSource, ErrorMessageLevel, message)); } return false; }
PassRefPtrWillBeRawPtr<MHTMLArchive> MHTMLArchive::create(const KURL& url, SharedBuffer* data) { // For security reasons we only load MHTML pages from local URLs. if (!SchemeRegistry::shouldTreatURLSchemeAsLocal(url.protocol())) return nullptr; MHTMLParser parser(data); WillBeHeapVector<RefPtrWillBeMember<ArchiveResource>> resources = parser.parseArchive(); if (resources.isEmpty()) return nullptr; // Invalid MHTML file. RefPtrWillBeRawPtr<MHTMLArchive> archive = adoptRefWillBeNoop(new MHTMLArchive); // The first document suitable resource is the main resource of the top frame. for (size_t i = 0; i < resources.size(); ++i) { const AtomicString& mimeType = resources[i]->mimeType(); if (archive->mainResource() || !MIMETypeRegistry::isSupportedNonImageMIMEType(mimeType) || MIMETypeRegistry::isSupportedJavaScriptMIMEType(mimeType) || mimeType == "text/css") archive->addSubresource(resources[i].get()); else archive->setMainResource(resources[i].get()); } return archive.release(); }
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; unsigned attributeCount = element->attributeCount(); for (unsigned i = 0; i < attributeCount; ++i) { RefPtrWillBeRawPtr<Attr> attr = element->attrIfExists(element->attributeItem(i).name()); if (attr && nodes.contains(attr.get())) sortedNodes.append(attr); } } ASSERT(sortedNodes.size() == nodeCount); const_cast<WillBeHeapVector<RefPtrWillBeMember<Node> >&>(m_nodes).swap(sortedNodes); }
void FontFace::setLoadStatus(LoadStatus status) { m_status = status; ASSERT(m_status != Error || m_error); if (m_status == Loaded || m_status == Error) { if (m_loadedProperty) { if (m_status == Loaded) m_loadedProperty->resolve(this); else m_loadedProperty->reject(m_error.get()); } WillBeHeapVector<RefPtrWillBeMember<LoadFontCallback>> callbacks; m_callbacks.swap(callbacks); for (size_t i = 0; i < callbacks.size(); ++i) { if (m_status == Loaded) callbacks[i]->notifyLoaded(this); else callbacks[i]->notifyError(this); } } }
static String selectMisspellingAsync(LocalFrame* selectedFrame, DocumentMarker& marker) { VisibleSelection selection = selectedFrame->selection().selection(); if (!selection.isCaretOrRange()) return String(); // Caret and range selections always return valid normalized ranges. RefPtrWillBeRawPtr<Range> selectionRange = selection.toNormalizedRange(); WillBeHeapVector<DocumentMarker*> markers = selectedFrame->document()->markers().markersInRange(selectionRange.get(), DocumentMarker::MisspellingMarkers()); if (markers.size() != 1) return String(); marker = *markers[0]; // Cloning a range fails only for invalid ranges. RefPtrWillBeRawPtr<Range> markerRange = selectionRange->cloneRange(); markerRange->setStart(markerRange->startContainer(), marker.startOffset()); markerRange->setEnd(markerRange->endContainer(), marker.endOffset()); if (markerRange->text().stripWhiteSpace(&IsWhiteSpaceOrPunctuation) != selectionRange->text().stripWhiteSpace(&IsWhiteSpaceOrPunctuation)) return String(); return markerRange->text(); }
int SimplifyMarkupCommand::pruneSubsequentAncestorsToRemove(WillBeHeapVector<RefPtrWillBeMember<ContainerNode>>& nodesToRemove, size_t startNodeIndex) { size_t pastLastNodeToRemove = startNodeIndex + 1; for (; pastLastNodeToRemove < nodesToRemove.size(); ++pastLastNodeToRemove) { if (nodesToRemove[pastLastNodeToRemove - 1]->parentNode() != nodesToRemove[pastLastNodeToRemove]) break; ASSERT(nodesToRemove[pastLastNodeToRemove]->firstChild() == nodesToRemove[pastLastNodeToRemove]->lastChild()); } ContainerNode* highestAncestorToRemove = nodesToRemove[pastLastNodeToRemove - 1].get(); RefPtrWillBeRawPtr<ContainerNode> parent = highestAncestorToRemove->parentNode(); if (!parent) // Parent has already been removed. return -1; if (pastLastNodeToRemove == startNodeIndex + 1) return 0; removeNode(nodesToRemove[startNodeIndex], AssumeContentIsAlwaysEditable); insertNodeBefore(nodesToRemove[startNodeIndex], highestAncestorToRemove, AssumeContentIsAlwaysEditable); removeNode(highestAncestorToRemove, AssumeContentIsAlwaysEditable); return pastLastNodeToRemove - startNodeIndex - 1; }
bool HTMLFormElement::checkInvalidControlsAndCollectUnhandled(WillBeHeapVector<RefPtrWillBeMember<HTMLFormControlElement>>* unhandledInvalidControls, CheckValidityEventBehavior eventBehavior) { RefPtrWillBeRawPtr<HTMLFormElement> protector(this); // Copy associatedElements because event handlers called from // HTMLFormControlElement::checkValidity() might change associatedElements. const FormAssociatedElement::List& associatedElements = this->associatedElements(); WillBeHeapVector<RefPtrWillBeMember<FormAssociatedElement>> elements; elements.reserveCapacity(associatedElements.size()); for (unsigned i = 0; i < associatedElements.size(); ++i) elements.append(associatedElements[i]); int invalidControlsCount = 0; for (unsigned i = 0; i < elements.size(); ++i) { if (elements[i]->form() == this && elements[i]->isFormControlElement()) { HTMLFormControlElement* control = toHTMLFormControlElement(elements[i].get()); if (control->isSubmittableElement() && !control->checkValidity(unhandledInvalidControls, eventBehavior) && control->formOwner() == this) { ++invalidControlsCount; if (!unhandledInvalidControls && eventBehavior == CheckValidityDispatchNoEvent) return true; } } } return invalidControlsCount; }
void HTMLFormElement::anonymousNamedGetter(const AtomicString& name, RadioNodeListOrElement& returnValue) { // Call getNamedElements twice, first time check if it has a value // and let HTMLFormElement update its cache. // See issue: 867404 { WillBeHeapVector<RefPtrWillBeMember<Element>> elements; getNamedElements(name, elements); if (elements.isEmpty()) return; } // Second call may return different results from the first call, // but if the first the size cannot be zero. WillBeHeapVector<RefPtrWillBeMember<Element>> elements; getNamedElements(name, elements); ASSERT(!elements.isEmpty()); bool onlyMatchImg = !elements.isEmpty() && isHTMLImageElement(*elements.first()); if (onlyMatchImg) { UseCounter::count(document(), UseCounter::FormNameAccessForImageElement); // The following code has performance impact, but it should be small // because <img> access via <form> name getter is rarely used. for (auto& element : elements) { if (isHTMLImageElement(*element) && !element->isDescendantOf(this)) { UseCounter::count(document(), UseCounter::FormNameAccessForNonDescendantImageElement); break; } } } if (elements.size() == 1) { returnValue.setElement(elements.at(0)); return; } returnValue.setRadioNodeList(radioNodeList(name, onlyMatchImg)); }
void MutationObserver::deliver() { ASSERT(!shouldBeSuspended()); // Calling clearTransientRegistrations() can modify m_registrations, so it's necessary // to make a copy of the transient registrations before operating on them. WillBeHeapVector<RawPtrWillBeMember<MutationObserverRegistration>, 1> transientRegistrations; for (auto& registration : m_registrations) { if (registration->hasTransientRegistrations()) transientRegistrations.append(registration); } for (size_t i = 0; i < transientRegistrations.size(); ++i) transientRegistrations[i]->clearTransientRegistrations(); if (m_records.isEmpty()) return; MutationRecordVector records; records.swap(m_records); InspectorInstrumentation::willDeliverMutationRecords(m_callback->executionContext(), this); m_callback->call(records, this); InspectorInstrumentation::didDeliverMutationRecords(m_callback->executionContext()); }
void PageAnimator::serviceScriptedAnimations(double monotonicAnimationStartTime) { m_animationFramePending = false; TemporaryChange<bool> servicing(m_servicingAnimations, true); for (RefPtr<Frame> frame = m_page->mainFrame(); frame; frame = frame->tree().traverseNext()) { if (frame->isLocalFrame()) { RefPtr<LocalFrame> localFrame = toLocalFrame(frame.get()); localFrame->view()->serviceScrollAnimations(); DocumentAnimations::updateAnimationTimingForAnimationFrame(*localFrame->document(), monotonicAnimationStartTime); SVGDocumentExtensions::serviceOnAnimationFrame(*localFrame->document(), monotonicAnimationStartTime); } } WillBeHeapVector<RefPtrWillBeMember<Document> > documents; for (Frame* frame = m_page->mainFrame(); frame; frame = frame->tree().traverseNext()) { if (frame->isLocalFrame()) documents.append(toLocalFrame(frame)->document()); } for (size_t i = 0; i < documents.size(); ++i) documents[i]->serviceScriptedAnimations(monotonicAnimationStartTime); }
void HTMLTextAreaElement::setDefaultValue(const String& defaultValue) { RefPtrWillBeRawPtr<Node> protectFromMutationEvents(this); // To preserve comments, remove only the text nodes, then add a single text node. WillBeHeapVector<RefPtrWillBeMember<Node>> textNodes; for (Node* n = firstChild(); n; n = n->nextSibling()) { if (n->isTextNode()) textNodes.append(n); } size_t size = textNodes.size(); for (size_t i = 0; i < size; ++i) removeChild(textNodes[i].get(), IGNORE_EXCEPTION); // Normalize line endings. String value = defaultValue; value.replace("\r\n", "\n"); value.replace('\r', '\n'); insertBefore(document().createTextNode(value), firstChild(), IGNORE_EXCEPTION); if (!m_isDirty) setNonDirtyValue(value); }
void BreakBlockquoteCommand::doApply() { if (endingSelection().isNone()) return; // Delete the current selection. if (endingSelection().isRange()) deleteSelection(false, false); // This is a scenario that should never happen, but we want to // make sure we don't dereference a null pointer below. ASSERT(!endingSelection().isNone()); if (endingSelection().isNone()) return; VisiblePosition visiblePos = endingSelection().visibleStart(); // pos is a position equivalent to the caret. We use downstream() so that pos will // be in the first node that we need to move (there are a few exceptions to this, see below). Position pos = mostForwardCaretPosition(endingSelection().start()); // Find the top-most blockquote from the start. HTMLQuoteElement* topBlockquote = toHTMLQuoteElement(highestEnclosingNodeOfType(pos, isMailHTMLBlockquoteElement)); if (!topBlockquote || !topBlockquote->parentNode()) return; RefPtrWillBeRawPtr<HTMLBRElement> breakElement = createBreakElement(document()); bool isLastVisPosInNode = isLastVisiblePositionInNode(visiblePos, topBlockquote); // If the position is at the beginning of the top quoted content, we don't need to break the quote. // Instead, insert the break before the blockquote, unless the position is as the end of the the quoted content. if (isFirstVisiblePositionInNode(visiblePos, topBlockquote) && !isLastVisPosInNode) { insertNodeBefore(breakElement.get(), topBlockquote); setEndingSelection(VisibleSelection(positionBeforeNode(breakElement.get()), TextAffinity::Downstream, endingSelection().isDirectional())); rebalanceWhitespace(); return; } // Insert a break after the top blockquote. insertNodeAfter(breakElement.get(), topBlockquote); // If we're inserting the break at the end of the quoted content, we don't need to break the quote. if (isLastVisPosInNode) { setEndingSelection(VisibleSelection(positionBeforeNode(breakElement.get()), TextAffinity::Downstream, endingSelection().isDirectional())); rebalanceWhitespace(); return; } // Don't move a line break just after the caret. Doing so would create an extra, empty paragraph // in the new blockquote. if (lineBreakExistsAtVisiblePosition(visiblePos)) { // TODO(yosin) We should use |PositionMoveType::Character| for // |nextPositionOf()| to avoid editing middle of character. pos = nextPositionOf(pos, PositionMoveType::CodePoint); } // Adjust the position so we don't split at the beginning of a quote. while (isFirstVisiblePositionInNode(createVisiblePosition(pos), toHTMLQuoteElement(enclosingNodeOfType(pos, isMailHTMLBlockquoteElement)))) { // TODO(yosin) We should use |PositionMoveType::Character| for // |previousPositionOf()| to avoid editing middle character. pos = previousPositionOf(pos, PositionMoveType::CodePoint); } // startNode is the first node that we need to move to the new blockquote. Node* startNode = pos.anchorNode(); ASSERT(startNode); // Split at pos if in the middle of a text node. if (startNode->isTextNode()) { Text* textNode = toText(startNode); int textOffset = pos.computeOffsetInContainerNode(); if ((unsigned)textOffset >= textNode->length()) { startNode = NodeTraversal::next(*startNode); ASSERT(startNode); } else if (textOffset > 0) { splitTextNode(textNode, textOffset); } } else if (pos.computeEditingOffset() > 0) { Node* childAtOffset = NodeTraversal::childAt(*startNode, pos.computeEditingOffset()); startNode = childAtOffset ? childAtOffset : NodeTraversal::next(*startNode); ASSERT(startNode); } // If there's nothing inside topBlockquote to move, we're finished. if (!startNode->isDescendantOf(topBlockquote)) { setEndingSelection(VisibleSelection(createVisiblePosition(firstPositionInOrBeforeNode(startNode)), endingSelection().isDirectional())); return; } // Build up list of ancestors in between the start node and the top blockquote. WillBeHeapVector<RefPtrWillBeMember<Element>> ancestors; for (Element* node = startNode->parentElement(); node && node != topBlockquote; node = node->parentElement()) ancestors.append(node); // Insert a clone of the top blockquote after the break. RefPtrWillBeRawPtr<Element> clonedBlockquote = topBlockquote->cloneElementWithoutChildren(); insertNodeAfter(clonedBlockquote.get(), breakElement.get()); // Clone startNode's ancestors into the cloned blockquote. // On exiting this loop, clonedAncestor is the lowest ancestor // that was cloned (i.e. the clone of either ancestors.last() // or clonedBlockquote if ancestors is empty). RefPtrWillBeRawPtr<Element> clonedAncestor = clonedBlockquote; for (size_t i = ancestors.size(); i != 0; --i) { RefPtrWillBeRawPtr<Element> clonedChild = ancestors[i - 1]->cloneElementWithoutChildren(); // Preserve list item numbering in cloned lists. if (isHTMLOListElement(*clonedChild)) { Node* listChildNode = i > 1 ? ancestors[i - 2].get() : startNode; // The first child of the cloned list might not be a list item element, // find the first one so that we know where to start numbering. while (listChildNode && !isHTMLLIElement(*listChildNode)) listChildNode = listChildNode->nextSibling(); if (isListItem(listChildNode)) setNodeAttribute(clonedChild, startAttr, AtomicString::number(toLayoutListItem(listChildNode->layoutObject())->value())); } appendNode(clonedChild.get(), clonedAncestor.get()); clonedAncestor = clonedChild; } moveRemainingSiblingsToNewParent(startNode, 0, clonedAncestor); if (!ancestors.isEmpty()) { // Split the tree up the ancestor chain until the topBlockquote // Throughout this loop, clonedParent is the clone of ancestor's parent. // This is so we can clone ancestor's siblings and place the clones // into the clone corresponding to the ancestor's parent. RefPtrWillBeRawPtr<Element> ancestor = nullptr; RefPtrWillBeRawPtr<Element> clonedParent = nullptr; for (ancestor = ancestors.first(), clonedParent = clonedAncestor->parentElement(); ancestor && ancestor != topBlockquote; ancestor = ancestor->parentElement(), clonedParent = clonedParent->parentElement()) moveRemainingSiblingsToNewParent(ancestor->nextSibling(), 0, clonedParent); // If the startNode's original parent is now empty, remove it Element* originalParent = ancestors.first().get(); if (!originalParent->hasChildren()) removeNode(originalParent); } // Make sure the cloned block quote renders. addBlockPlaceholderIfNeeded(clonedBlockquote.get()); // Put the selection right before the break. setEndingSelection(VisibleSelection(positionBeforeNode(breakElement.get()), TextAffinity::Downstream, endingSelection().isDirectional())); rebalanceWhitespace(); }
void AutomaticTrackSelection::performAutomaticTextTrackSelection(const TrackGroup& group) { ASSERT(group.tracks.size()); // First, find the track in the group that should be enabled (if any). WillBeHeapVector<RefPtrWillBeMember<TextTrack>> currentlyEnabledTracks; RefPtrWillBeRawPtr<TextTrack> trackToEnable = nullptr; RefPtrWillBeRawPtr<TextTrack> defaultTrack = nullptr; RefPtrWillBeRawPtr<TextTrack> preferredTrack = nullptr; RefPtrWillBeRawPtr<TextTrack> fallbackTrack = nullptr; int highestTrackScore = 0; for (size_t i = 0; i < group.tracks.size(); ++i) { RefPtrWillBeRawPtr<TextTrack> textTrack = group.tracks[i]; if (m_configuration.disableCurrentlyEnabledTracks && textTrack->mode() == TextTrack::showingKeyword()) currentlyEnabledTracks.append(textTrack); int trackScore = textTrackSelectionScore(*textTrack); if (textTrack->kind() == preferredTrackKind()) trackScore += 1; if (trackScore) { // * If the text track kind is subtitles or captions and the user has indicated an interest in having a // track with this text track kind, text track language, and text track label enabled, and there is no // other text track in the media element's list of text tracks with a text track kind of either subtitles // or captions whose text track mode is showing // Let the text track mode be showing. if (trackScore > highestTrackScore) { preferredTrack = textTrack; highestTrackScore = trackScore; } if (!defaultTrack && textTrack->isDefault()) defaultTrack = textTrack; if (!fallbackTrack) fallbackTrack = textTrack; } else if (!group.visibleTrack && !defaultTrack && textTrack->isDefault()) { // * If the track element has a default attribute specified, and there is no other text track in the media // element's list of text tracks whose text track mode is showing or showing by default // Let the text track mode be showing by default. defaultTrack = textTrack; } } if (m_configuration.textTrackKindUserPreference != TextTrackKindUserPreference::Default) trackToEnable = preferredTrack; if (!trackToEnable && defaultTrack) trackToEnable = defaultTrack; if (!trackToEnable && m_configuration.forceEnableSubtitleOrCaptionTrack && group.kind == TrackGroup::CaptionsAndSubtitles) trackToEnable = fallbackTrack ? fallbackTrack : group.tracks[0]; if (currentlyEnabledTracks.size()) { for (size_t i = 0; i < currentlyEnabledTracks.size(); ++i) { RefPtrWillBeRawPtr<TextTrack> textTrack = currentlyEnabledTracks[i]; if (textTrack != trackToEnable) textTrack->setMode(TextTrack::disabledKeyword()); } } if (trackToEnable) trackToEnable->setMode(TextTrack::showingKeyword()); }
void ScopedStyleResolver::addKeyframeRules(const RuleSet& ruleSet) { const WillBeHeapVector<RawPtrWillBeMember<StyleRuleKeyframes>> keyframesRules = ruleSet.keyframesRules(); for (unsigned i = 0; i < keyframesRules.size(); ++i) addKeyframeStyle(keyframesRules[i]); }
StyleSheetInvalidationAnalysis::StyleSheetInvalidationAnalysis(const TreeScope& treeScope, const WillBeHeapVector<RawPtrWillBeMember<StyleSheetContents>>& sheets) : m_treeScope(&treeScope) { for (unsigned i = 0; i < sheets.size() && !m_dirtiesAllStyle; ++i) analyzeStyleSheet(sheets[i]); }
void MutableStylePropertySet::addParsedProperties(const WillBeHeapVector<CSSProperty, 256>& properties) { m_propertyVector.reserveCapacity(m_propertyVector.size() + properties.size()); for (unsigned i = 0; i < properties.size(); ++i) addParsedProperty(properties[i]); }
void CueTimeline::updateActiveCues(double movieTime) { // 4.8.10.8 Playing the media resource // If the current playback position changes while the steps are running, // then the user agent must wait for the steps to complete, and then must // immediately rerun the steps. if (ignoreUpdateRequests()) return; HTMLMediaElement& mediaElement = this->mediaElement(); // https://html.spec.whatwg.org/#time-marches-on // 1 - Let current cues be a list of cues, initialized to contain all the // cues of all the hidden, showing, or showing by default text tracks of the // media element (not the disabled ones) whose start times are less than or // equal to the current playback position and whose end times are greater // than the current playback position. CueList currentCues; // The user agent must synchronously unset [the text track cue active] flag // whenever ... the media element's readyState is changed back to HAVE_NOTHING. if (mediaElement.readyState() != HTMLMediaElement::HAVE_NOTHING && mediaElement.webMediaPlayer()) currentCues = m_cueTree.allOverlaps(m_cueTree.createInterval(movieTime, movieTime)); CueList previousCues; CueList missedCues; // 2 - Let other cues be a list of cues, initialized to contain all the cues // of hidden, showing, and showing by default text tracks of the media // element that are not present in current cues. previousCues = m_currentlyActiveCues; // 3 - Let last time be the current playback position at the time this // algorithm was last run for this media element, if this is not the first // time it has run. double lastTime = m_lastUpdateTime; double lastSeekTime = mediaElement.lastSeekTime(); // 4 - If the current playback position has, since the last time this // algorithm was run, only changed through its usual monotonic increase // during normal playback, then let missed cues be the list of cues in other // cues whose start times are greater than or equal to last time and whose // end times are less than or equal to the current playback position. // Otherwise, let missed cues be an empty list. if (lastTime >= 0 && lastSeekTime < movieTime) { CueList potentiallySkippedCues = m_cueTree.allOverlaps(m_cueTree.createInterval(lastTime, movieTime)); for (CueInterval cue : potentiallySkippedCues) { // Consider cues that may have been missed since the last seek time. if (cue.low() > std::max(lastSeekTime, lastTime) && cue.high() < movieTime) missedCues.append(cue); } } m_lastUpdateTime = movieTime; // 5 - If the time was reached through the usual monotonic increase of the // current playback position during normal playback, and if the user agent // has not fired a timeupdate event at the element in the past 15 to 250ms // and is not still running event handlers for such an event, then the user // agent must queue a task to fire a simple event named timeupdate at the // element. (In the other cases, such as explicit seeks, relevant events get // fired as part of the overall process of changing the current playback // position.) if (!mediaElement.seeking() && lastSeekTime < lastTime) mediaElement.scheduleTimeupdateEvent(true); // Explicitly cache vector sizes, as their content is constant from here. size_t currentCuesSize = currentCues.size(); size_t missedCuesSize = missedCues.size(); size_t previousCuesSize = previousCues.size(); // 6 - If all of the cues in current cues have their text track cue active // flag set, none of the cues in other cues have their text track cue active // flag set, and missed cues is empty, then abort these steps. bool activeSetChanged = missedCuesSize; for (size_t i = 0; !activeSetChanged && i < previousCuesSize; ++i) { if (!currentCues.contains(previousCues[i]) && previousCues[i].data()->isActive()) activeSetChanged = true; } for (CueInterval currentCue : currentCues) { // Notify any cues that are already active of the current time to mark // past and future nodes. Any inactive cues have an empty display state; // they will be notified of the current time when the display state is // updated. if (currentCue.data()->isActive()) currentCue.data()->updatePastAndFutureNodes(movieTime); else activeSetChanged = true; } if (!activeSetChanged) return; // 7 - If the time was reached through the usual monotonic increase of the // current playback position during normal playback, and there are cues in // other cues that have their text track cue pause-on-exi flag set and that // either have their text track cue active flag set or are also in missed // cues, then immediately pause the media element. for (size_t i = 0; !mediaElement.paused() && i < previousCuesSize; ++i) { if (previousCues[i].data()->pauseOnExit() && previousCues[i].data()->isActive() && !currentCues.contains(previousCues[i])) mediaElement.pause(); } for (size_t i = 0; !mediaElement.paused() && i < missedCuesSize; ++i) { if (missedCues[i].data()->pauseOnExit()) mediaElement.pause(); } // 8 - Let events be a list of tasks, initially empty. Each task in this // list will be associated with a text track, a text track cue, and a time, // which are used to sort the list before the tasks are queued. WillBeHeapVector<std::pair<double, RawPtrWillBeMember<TextTrackCue>>> eventTasks; // 8 - Let affected tracks be a list of text tracks, initially empty. WillBeHeapVector<RawPtrWillBeMember<TextTrack>> affectedTracks; for (size_t i = 0; i < missedCuesSize; ++i) { // 9 - For each text track cue in missed cues, prepare an event named enter // for the TextTrackCue object with the text track cue start time. eventTasks.append(std::make_pair(missedCues[i].data()->startTime(), missedCues[i].data())); // 10 - For each text track [...] in missed cues, prepare an event // named exit for the TextTrackCue object with the with the later of // the text track cue end time and the text track cue start time. // Note: An explicit task is added only if the cue is NOT a zero or // negative length cue. Otherwise, the need for an exit event is // checked when these tasks are actually queued below. This doesn't // affect sorting events before dispatch either, because the exit // event has the same time as the enter event. if (missedCues[i].data()->startTime() < missedCues[i].data()->endTime()) eventTasks.append(std::make_pair(missedCues[i].data()->endTime(), missedCues[i].data())); } for (size_t i = 0; i < previousCuesSize; ++i) { // 10 - For each text track cue in other cues that has its text // track cue active flag set prepare an event named exit for the // TextTrackCue object with the text track cue end time. if (!currentCues.contains(previousCues[i])) eventTasks.append(std::make_pair(previousCues[i].data()->endTime(), previousCues[i].data())); } for (size_t i = 0; i < currentCuesSize; ++i) { // 11 - For each text track cue in current cues that does not have its // text track cue active flag set, prepare an event named enter for the // TextTrackCue object with the text track cue start time. if (!previousCues.contains(currentCues[i])) eventTasks.append(std::make_pair(currentCues[i].data()->startTime(), currentCues[i].data())); } // 12 - Sort the tasks in events in ascending time order (tasks with earlier // times first). nonCopyingSort(eventTasks.begin(), eventTasks.end(), eventTimeCueCompare); for (size_t i = 0; i < eventTasks.size(); ++i) { if (!affectedTracks.contains(eventTasks[i].second->track())) affectedTracks.append(eventTasks[i].second->track()); // 13 - Queue each task in events, in list order. // Each event in eventTasks may be either an enterEvent or an exitEvent, // depending on the time that is associated with the event. This // correctly identifies the type of the event, if the startTime is // less than the endTime in the cue. if (eventTasks[i].second->startTime() >= eventTasks[i].second->endTime()) { mediaElement.scheduleEvent(createEventWithTarget(EventTypeNames::enter, eventTasks[i].second)); mediaElement.scheduleEvent(createEventWithTarget(EventTypeNames::exit, eventTasks[i].second)); } else { bool isEnterEvent = eventTasks[i].first == eventTasks[i].second->startTime(); AtomicString eventName = isEnterEvent ? EventTypeNames::enter : EventTypeNames::exit; mediaElement.scheduleEvent(createEventWithTarget(eventName, eventTasks[i].second)); } } // 14 - Sort affected tracks in the same order as the text tracks appear in // the media element's list of text tracks, and remove duplicates. nonCopyingSort(affectedTracks.begin(), affectedTracks.end(), trackIndexCompare); // 15 - For each text track in affected tracks, in the list order, queue a // task to fire a simple event named cuechange at the TextTrack object, and, ... for (size_t i = 0; i < affectedTracks.size(); ++i) { mediaElement.scheduleEvent(createEventWithTarget(EventTypeNames::cuechange, affectedTracks[i])); // ... if the text track has a corresponding track element, to then fire a // simple event named cuechange at the track element as well. if (affectedTracks[i]->trackType() == TextTrack::TrackElement) { HTMLTrackElement* trackElement = static_cast<LoadableTextTrack*>(affectedTracks[i].get())->trackElement(); ASSERT(trackElement); mediaElement.scheduleEvent(createEventWithTarget(EventTypeNames::cuechange, trackElement)); } } // 16 - Set the text track cue active flag of all the cues in the current // cues, and unset the text track cue active flag of all the cues in the // other cues. for (size_t i = 0; i < currentCuesSize; ++i) currentCues[i].data()->setIsActive(true); for (size_t i = 0; i < previousCuesSize; ++i) { if (!currentCues.contains(previousCues[i])) { TextTrackCue* cue = previousCues[i].data(); cue->setIsActive(false); cue->removeDisplayTree(); } } // Update the current active cues. m_currentlyActiveCues = currentCues; mediaElement.updateTextTrackDisplay(); }
void EventPath::calculatePath() { ASSERT(m_node); ASSERT(m_nodeEventContexts.isEmpty()); m_node->updateDistribution(); // For performance and memory usage reasons we want to store the // path using as few bytes as possible and with as few allocations // as possible which is why we gather the data on the stack before // storing it in a perfectly sized m_nodeEventContexts Vector. WillBeHeapVector<RawPtrWillBeMember<Node>, 64> nodesInPath; Node* current = m_node; nodesInPath.append(current); while (current) { if (m_event && current->keepEventInNode(m_event)) break; WillBeHeapVector<RawPtrWillBeMember<InsertionPoint>, 8> insertionPoints; collectDestinationInsertionPoints(*current, insertionPoints); if (!insertionPoints.isEmpty()) { for (const auto& insertionPoint : insertionPoints) { if (insertionPoint->isShadowInsertionPoint()) { ShadowRoot* containingShadowRoot = insertionPoint->containingShadowRoot(); ASSERT(containingShadowRoot); if (!containingShadowRoot->isOldest()) nodesInPath.append(containingShadowRoot->olderShadowRoot()); } nodesInPath.append(insertionPoint); } current = insertionPoints.last(); continue; } if (current->isChildOfV1ShadowHost()) { if (HTMLSlotElement* slot = current->assignedSlot()) { current = slot; nodesInPath.append(current); continue; } } if (current->isShadowRoot()) { if (m_event && shouldStopAtShadowRoot(*m_event, *toShadowRoot(current), *m_node)) break; current = current->shadowHost(); #if !ENABLE(OILPAN) // TODO(kochi): crbug.com/507413 This check is necessary when some asynchronous event // is queued while its shadow host is removed and the shadow root gets the event // immediately after it. When Oilpan is enabled, this situation does not happen. // Except this case, shadow root's host is assumed to be non-null. if (current) nodesInPath.append(current); #else nodesInPath.append(current); #endif } else { current = current->parentNode(); if (current) nodesInPath.append(current); } } m_nodeEventContexts.reserveCapacity(nodesInPath.size()); for (Node* nodeInPath : nodesInPath) { m_nodeEventContexts.append(NodeEventContext(nodeInPath, eventTargetRespectingTargetRules(*nodeInPath))); } }