summaryrefslogtreecommitdiff
path: root/Source/WebCore/html/track/VTTRegion.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/html/track/VTTRegion.cpp')
-rw-r--r--Source/WebCore/html/track/VTTRegion.cpp428
1 files changed, 428 insertions, 0 deletions
diff --git a/Source/WebCore/html/track/VTTRegion.cpp b/Source/WebCore/html/track/VTTRegion.cpp
new file mode 100644
index 000000000..0578bf912
--- /dev/null
+++ b/Source/WebCore/html/track/VTTRegion.cpp
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2013 Google Inc. All rights reserved.
+ * Copyright (C) 2014 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "VTTRegion.h"
+
+#if ENABLE(VIDEO_TRACK)
+
+#include "ClientRect.h"
+#include "DOMTokenList.h"
+#include "ElementChildIterator.h"
+#include "ExceptionCode.h"
+#include "HTMLDivElement.h"
+#include "HTMLParserIdioms.h"
+#include "Logging.h"
+#include "RenderElement.h"
+#include "VTTCue.h"
+#include "VTTScanner.h"
+#include "WebVTTParser.h"
+#include <wtf/MathExtras.h>
+
+namespace WebCore {
+
+// The default values are defined within the WebVTT Regions Spec.
+// https://dvcs.w3.org/hg/text-tracks/raw-file/default/608toVTT/region.html
+
+// Default region line-height (vh units)
+static const float lineHeight = 5.33;
+
+// Default scrolling animation time period (s).
+static const float scrollTime = 0.433;
+
+VTTRegion::VTTRegion(ScriptExecutionContext& context)
+ : ContextDestructionObserver(&context)
+ , m_id(emptyString())
+ , m_scrollTimer(*this, &VTTRegion::scrollTimerFired)
+{
+}
+
+VTTRegion::~VTTRegion()
+{
+}
+
+void VTTRegion::setTrack(TextTrack* track)
+{
+ m_track = track;
+}
+
+void VTTRegion::setId(const String& id)
+{
+ m_id = id;
+}
+
+ExceptionOr<void> VTTRegion::setWidth(double value)
+{
+ if (!(value >= 0 && value <= 100))
+ return Exception { INDEX_SIZE_ERR };
+ m_width = value;
+ return { };
+}
+
+ExceptionOr<void> VTTRegion::setHeight(int value)
+{
+ if (value < 0)
+ return Exception { INDEX_SIZE_ERR };
+ m_heightInLines = value;
+ return { };
+}
+
+ExceptionOr<void> VTTRegion::setRegionAnchorX(double value)
+{
+ if (!(value >= 0 && value <= 100))
+ return Exception { INDEX_SIZE_ERR };
+ m_regionAnchor.setX(value);
+ return { };
+}
+
+ExceptionOr<void> VTTRegion::setRegionAnchorY(double value)
+{
+ if (!(value >= 0 && value <= 100))
+ return Exception { INDEX_SIZE_ERR };
+ m_regionAnchor.setY(value);
+ return { };
+}
+
+ExceptionOr<void> VTTRegion::setViewportAnchorX(double value)
+{
+ if (!(value >= 0 && value <= 100))
+ return Exception { INDEX_SIZE_ERR };
+ m_viewportAnchor.setX(value);
+ return { };
+}
+
+ExceptionOr<void> VTTRegion::setViewportAnchorY(double value)
+{
+ if (!(value >= 0 && value <= 100))
+ return Exception { INDEX_SIZE_ERR };
+ m_viewportAnchor.setY(value);
+ return { };
+}
+
+static const AtomicString& upKeyword()
+{
+ static NeverDestroyed<const AtomicString> upKeyword("up", AtomicString::ConstructFromLiteral);
+ return upKeyword;
+}
+
+const AtomicString& VTTRegion::scroll() const
+{
+ return m_scroll ? upKeyword() : emptyAtom;
+}
+
+ExceptionOr<void> VTTRegion::setScroll(const AtomicString& value)
+{
+ if (value.isEmpty()) {
+ m_scroll = false;
+ return { };
+ }
+ if (value == upKeyword()) {
+ m_scroll = true;
+ return { };
+ }
+ return Exception { SYNTAX_ERR };
+}
+
+void VTTRegion::updateParametersFromRegion(const VTTRegion& other)
+{
+ m_heightInLines = other.m_heightInLines;
+ m_width = other.m_width;
+ m_regionAnchor = other.m_regionAnchor;
+ m_viewportAnchor = other.m_viewportAnchor;
+ m_scroll = other.m_scroll;
+}
+
+void VTTRegion::setRegionSettings(const String& inputString)
+{
+ m_settings = inputString;
+ VTTScanner input(inputString);
+
+ while (!input.isAtEnd()) {
+ input.skipWhile<WebVTTParser::isValidSettingDelimiter>();
+ if (input.isAtEnd())
+ break;
+
+ // Scan the name part.
+ RegionSetting name = scanSettingName(input);
+
+ // Verify that we're looking at a '='.
+ if (name == None || !input.scan('=')) {
+ input.skipUntil<isHTMLSpace<UChar>>();
+ continue;
+ }
+
+ // Scan the value part.
+ parseSettingValue(name, input);
+ }
+}
+
+VTTRegion::RegionSetting VTTRegion::scanSettingName(VTTScanner& input)
+{
+ if (input.scan("id"))
+ return Id;
+ if (input.scan("height"))
+ return Height;
+ if (input.scan("width"))
+ return Width;
+ if (input.scan("viewportanchor"))
+ return ViewportAnchor;
+ if (input.scan("regionanchor"))
+ return RegionAnchor;
+ if (input.scan("scroll"))
+ return Scroll;
+
+ return None;
+}
+
+static inline bool parsedEntireRun(const VTTScanner& input, const VTTScanner::Run& run)
+{
+ return input.isAt(run.end());
+}
+
+void VTTRegion::parseSettingValue(RegionSetting setting, VTTScanner& input)
+{
+ VTTScanner::Run valueRun = input.collectUntil<isHTMLSpace<UChar>>();
+
+ switch (setting) {
+ case Id: {
+ String stringValue = input.extractString(valueRun);
+ if (stringValue.find("-->") == notFound)
+ m_id = stringValue;
+ break;
+ }
+ case Width: {
+ float floatWidth;
+ if (WebVTTParser::parseFloatPercentageValue(input, floatWidth) && parsedEntireRun(input, valueRun))
+ m_width = floatWidth;
+ else
+ LOG(Media, "VTTRegion::parseSettingValue, invalid Width");
+ break;
+ }
+ case Height: {
+ int number;
+ if (input.scanDigits(number) && parsedEntireRun(input, valueRun))
+ m_heightInLines = number;
+ else
+ LOG(Media, "VTTRegion::parseSettingValue, invalid Height");
+ break;
+ }
+ case RegionAnchor: {
+ FloatPoint anchor;
+ if (WebVTTParser::parseFloatPercentageValuePair(input, ',', anchor) && parsedEntireRun(input, valueRun))
+ m_regionAnchor = anchor;
+ else
+ LOG(Media, "VTTRegion::parseSettingValue, invalid RegionAnchor");
+ break;
+ }
+ case ViewportAnchor: {
+ FloatPoint anchor;
+ if (WebVTTParser::parseFloatPercentageValuePair(input, ',', anchor) && parsedEntireRun(input, valueRun))
+ m_viewportAnchor = anchor;
+ else
+ LOG(Media, "VTTRegion::parseSettingValue, invalid ViewportAnchor");
+ break;
+ }
+ case Scroll:
+ if (input.scanRun(valueRun, upKeyword()))
+ m_scroll = true;
+ else
+ LOG(Media, "VTTRegion::parseSettingValue, invalid Scroll");
+ break;
+ case None:
+ break;
+ }
+
+ input.skipRun(valueRun);
+}
+
+const AtomicString& VTTRegion::textTrackCueContainerScrollingClass()
+{
+ static NeverDestroyed<const AtomicString> trackRegionCueContainerScrollingClass("scrolling", AtomicString::ConstructFromLiteral);
+
+ return trackRegionCueContainerScrollingClass;
+}
+
+const AtomicString& VTTRegion::textTrackCueContainerShadowPseudoId()
+{
+ static NeverDestroyed<const AtomicString> trackRegionCueContainerPseudoId("-webkit-media-text-track-region-container", AtomicString::ConstructFromLiteral);
+
+ return trackRegionCueContainerPseudoId;
+}
+
+const AtomicString& VTTRegion::textTrackRegionShadowPseudoId()
+{
+ static NeverDestroyed<const AtomicString> trackRegionShadowPseudoId("-webkit-media-text-track-region", AtomicString::ConstructFromLiteral);
+
+ return trackRegionShadowPseudoId;
+}
+
+void VTTRegion::appendTextTrackCueBox(Ref<VTTCueBox>&& displayBox)
+{
+ ASSERT(m_cueContainer);
+
+ if (m_cueContainer->contains(displayBox.ptr()))
+ return;
+
+ m_cueContainer->appendChild(displayBox);
+ displayLastTextTrackCueBox();
+}
+
+void VTTRegion::displayLastTextTrackCueBox()
+{
+ ASSERT(m_cueContainer);
+
+ // The container needs to be rendered, if it is not empty and the region is not currently scrolling.
+ if (!m_cueContainer->renderer() || !m_cueContainer->hasChildNodes() || m_scrollTimer.isActive())
+ return;
+
+ // If it's a scrolling region, add the scrolling class.
+ if (isScrollingRegion())
+ m_cueContainer->classList().add(textTrackCueContainerScrollingClass());
+
+ float regionBottom = m_regionDisplayTree->getBoundingClientRect()->bottom();
+
+ // Find first cue that is not entirely displayed and scroll it upwards.
+ for (auto& child : childrenOfType<Element>(*m_cueContainer)) {
+ Ref<ClientRect> rect = child.getBoundingClientRect();
+ float childTop = rect->top();
+ float childBottom = rect->bottom();
+
+ if (regionBottom >= childBottom)
+ continue;
+
+ float height = childBottom - childTop;
+
+ m_currentTop -= std::min(height, childBottom - regionBottom);
+ m_cueContainer->setInlineStyleProperty(CSSPropertyTop, m_currentTop, CSSPrimitiveValue::CSS_PX);
+
+ startTimer();
+ break;
+ }
+}
+
+void VTTRegion::willRemoveTextTrackCueBox(VTTCueBox* box)
+{
+ LOG(Media, "VTTRegion::willRemoveTextTrackCueBox");
+ ASSERT(m_cueContainer->contains(box));
+
+ double boxHeight = box->getBoundingClientRect()->bottom() - box->getBoundingClientRect()->top();
+
+ m_cueContainer->classList().remove(textTrackCueContainerScrollingClass());
+
+ m_currentTop += boxHeight;
+ m_cueContainer->setInlineStyleProperty(CSSPropertyTop, m_currentTop, CSSPrimitiveValue::CSS_PX);
+}
+
+HTMLDivElement& VTTRegion::getDisplayTree()
+{
+ if (!m_regionDisplayTree) {
+ m_regionDisplayTree = HTMLDivElement::create(downcast<Document>(*m_scriptExecutionContext));
+ prepareRegionDisplayTree();
+ }
+
+ return *m_regionDisplayTree;
+}
+
+void VTTRegion::prepareRegionDisplayTree()
+{
+ ASSERT(m_regionDisplayTree);
+
+ // 7.2 Prepare region CSS boxes
+
+ // FIXME: Change the code below to use viewport units when
+ // http://crbug/244618 is fixed.
+
+ // Let regionWidth be the text track region width.
+ // Let width be 'regionWidth vw' ('vw' is a CSS unit)
+ m_regionDisplayTree->setInlineStyleProperty(CSSPropertyWidth, m_width, CSSPrimitiveValue::CSS_PERCENTAGE);
+
+ // Let lineHeight be '0.0533vh' ('vh' is a CSS unit) and regionHeight be
+ // the text track region height. Let height be 'lineHeight' multiplied
+ // by regionHeight.
+ double height = lineHeight * m_heightInLines;
+ m_regionDisplayTree->setInlineStyleProperty(CSSPropertyHeight, height, CSSPrimitiveValue::CSS_VH);
+
+ // Let viewportAnchorX be the x dimension of the text track region viewport
+ // anchor and regionAnchorX be the x dimension of the text track region
+ // anchor. Let leftOffset be regionAnchorX multiplied by width divided by
+ // 100.0. Let left be leftOffset subtracted from 'viewportAnchorX vw'.
+ double leftOffset = m_regionAnchor.x() * m_width / 100;
+ m_regionDisplayTree->setInlineStyleProperty(CSSPropertyLeft, m_viewportAnchor.x() - leftOffset, CSSPrimitiveValue::CSS_PERCENTAGE);
+
+ // Let viewportAnchorY be the y dimension of the text track region viewport
+ // anchor and regionAnchorY be the y dimension of the text track region
+ // anchor. Let topOffset be regionAnchorY multiplied by height divided by
+ // 100.0. Let top be topOffset subtracted from 'viewportAnchorY vh'.
+ double topOffset = m_regionAnchor.y() * height / 100;
+ m_regionDisplayTree->setInlineStyleProperty(CSSPropertyTop, m_viewportAnchor.y() - topOffset, CSSPrimitiveValue::CSS_PERCENTAGE);
+
+ // The cue container is used to wrap the cues and it is the object which is
+ // gradually scrolled out as multiple cues are appended to the region.
+ m_cueContainer = HTMLDivElement::create(downcast<Document>(*m_scriptExecutionContext));
+ m_cueContainer->setInlineStyleProperty(CSSPropertyTop, 0.0f, CSSPrimitiveValue::CSS_PX);
+
+ m_cueContainer->setPseudo(textTrackCueContainerShadowPseudoId());
+ m_regionDisplayTree->appendChild(*m_cueContainer);
+
+ // 7.5 Every WebVTT region object is initialised with the following CSS
+ m_regionDisplayTree->setPseudo(textTrackRegionShadowPseudoId());
+}
+
+void VTTRegion::startTimer()
+{
+ LOG(Media, "VTTRegion::startTimer");
+
+ if (m_scrollTimer.isActive())
+ return;
+
+ double duration = isScrollingRegion() ? scrollTime : 0;
+ m_scrollTimer.startOneShot(duration);
+}
+
+void VTTRegion::stopTimer()
+{
+ LOG(Media, "VTTRegion::stopTimer");
+
+ if (m_scrollTimer.isActive())
+ m_scrollTimer.stop();
+}
+
+void VTTRegion::scrollTimerFired()
+{
+ LOG(Media, "VTTRegion::scrollTimerFired");
+
+ stopTimer();
+ displayLastTextTrackCueBox();
+}
+
+} // namespace WebCore
+
+#endif