Bug 1344609 - Pseudo classes support. r=rillian
MozReview-Commit-ID: 6ygjXvlbyJW
--- a/dom/html/TextTrackManager.cpp
+++ b/dom/html/TextTrackManager.cpp
@@ -293,16 +293,42 @@ TextTrackManager::UpdateCueDisplay()
}
} else if (overlay->Length() > 0) {
WEBVTT_LOG("UpdateCueDisplay EmptyString");
nsContentUtils::SetNodeTextContent(overlay, EmptyString(), true);
}
}
void
+TextTrackManager::ApplyPseudoClasses()
+{
+ WEBVTT_LOG("ApplyPseudoClasses");
+
+ if (!mMediaElement || !mTextTracks) {
+ return;
+ }
+
+ nsIFrame* frame = mMediaElement->GetPrimaryFrame();
+ nsVideoFrame* videoFrame = do_QueryFrame(frame);
+ if (!videoFrame) {
+ return;
+ }
+
+ nsCOMPtr<nsIContent> overlay = videoFrame->GetCaptionOverlay();
+ if (!overlay) {
+ return;
+ }
+ nsPIDOMWindowInner* window = mMediaElement->OwnerDoc()->GetInnerWindow();
+ if (window) {
+ sParserWrapper->ApplyPseudoClasses(window, overlay,
+ mMediaElement->CurrentTime());
+ }
+}
+
+void
TextTrackManager::NotifyCueAdded(TextTrackCue& aCue)
{
WEBVTT_LOG("NotifyCueAdded");
if (mNewCues) {
mNewCues->AddCue(aCue);
}
DispatchTimeMarchesOn();
ReportTelemetryForCue();
@@ -702,16 +728,17 @@ TextTrackManager::TimeMarchesOn()
for (uint32_t i = 0; i < otherCues->Length(); ++i) {
if ((*otherCues)[i]->GetActive()) {
c2 = false;
break;
}
}
bool c3 = (missedCues->Length() == 0);
if (c1 && c2 && c3) {
+ ApplyPseudoClasses();
mLastTimeMarchesOnCalled = currentPlaybackTime;
WEBVTT_LOG("TimeMarchesOn step 7 return, mLastTimeMarchesOnCalled %lf", mLastTimeMarchesOnCalled);
return;
}
// Step 8. Respect PauseOnExit flag if not seek.
if (hasNormalPlayback) {
for (uint32_t i = 0; i < otherCues->Length(); ++i) {
@@ -803,16 +830,17 @@ TextTrackManager::TimeMarchesOn()
}
}
mLastTimeMarchesOnCalled = currentPlaybackTime;
mLastActiveCues = currentCues;
// Step 18.
UpdateCueDisplay();
+ ApplyPseudoClasses();
}
void
TextTrackManager::NotifyCueUpdated(TextTrackCue *aCue)
{
// TODO: Add/Reorder the cue to mNewCues if we have some optimization?
WEBVTT_LOG("NotifyCueUpdated");
DispatchTimeMarchesOn();
--- a/dom/html/TextTrackManager.h
+++ b/dom/html/TextTrackManager.h
@@ -108,16 +108,18 @@ public:
private:
/**
* Converts the TextTrackCue's cuetext into a tree of DOM objects
* and attaches it to a div on its owning TrackElement's
* MediaElement's caption overlay.
*/
void UpdateCueDisplay();
+ void ApplyPseudoClasses();
+
// List of the TextTrackManager's owning HTMLMediaElement's TextTracks.
RefPtr<TextTrackList> mTextTracks;
// List of text track objects awaiting loading.
RefPtr<TextTrackList> mPendingTextTracks;
// List of newly introduced Text Track cues.
// Contain all cues for a MediaElement. Not sorted.
RefPtr<TextTrackCueList> mNewCues;
--- a/dom/media/webvtt/WebVTTParserWrapper.js
+++ b/dom/media/webvtt/WebVTTParserWrapper.js
@@ -54,16 +54,21 @@ WebVTTParserWrapper.prototype =
return WebVTT.convertCueToDOMTree(window, cue.text);
},
processCues: function(window, cues, overlay, controls)
{
WebVTT.processCues(window, cues, overlay, controls);
},
+ applyPseudoClasses: function(window, overlay, timestamp)
+ {
+ WebVTT.applyPseudoClasses(window, overlay, timestamp);
+ },
+
classDescription: "Wrapper for the JS WebVTT implementation (vtt.js)",
classID: Components.ID(WEBVTTPARSERWRAPPER_CID),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebVTTParserWrapper]),
classInfo: XPCOMUtils.generateCI({
classID: WEBVTTPARSERWRAPPER_CID,
contractID: WEBVTTPARSERWRAPPER_CONTRACTID,
interfaces: [Ci.nsIWebVTTParserWrapper]
})
--- a/dom/media/webvtt/nsIWebVTTParserWrapper.idl
+++ b/dom/media/webvtt/nsIWebVTTParserWrapper.idl
@@ -76,13 +76,24 @@ interface nsIWebVTTParserWrapper : nsISu
* and containing div element.
* @param cues An array of VTTCues who need there display state to be
* computed.
* @param overlay The HTMLElement that the cues will be displayed within.
* @param controls The video control element that will affect cues position.
*/
void processCues(in mozIDOMWindow window, in nsIVariant cues,
in nsISupports overlay, in nsISupports controls);
+
+ /**
+ * Apply pseudo classes :past and :future to the overlay.
+ * @param window A window object with which it will create the DOM tree
+ * and containing div element.
+ * @param overlay The root of the cues will be displayed within.
+ * @param timestamp The video element playback time.
+ */
+ void applyPseudoClasses(in mozIDOMWindow window,
+ in nsISupports overlay,
+ in double timestamp);
};
%{C++
#define NS_WEBVTTPARSERWRAPPER_CONTRACTID "@mozilla.org/webvttParserWrapper;1"
%}
--- a/dom/media/webvtt/vtt.jsm
+++ b/dom/media/webvtt/vtt.jsm
@@ -23,16 +23,19 @@ this.EXPORTED_SYMBOLS = ["WebVTT"];
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var Cu = Components.utils;
Cu.import('resource://gre/modules/Services.jsm');
+const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const nodeFilterConstants = require("devtools/shared/dom-node-filter-constants");
+const nodeConstants = require("devtools/shared/dom-node-constants");
(function(global) {
var _objCreate = Object.create || (function() {
function F() {}
return function(o) {
if (arguments.length !== 1) {
throw new Error('Object.create shim only accepts one parameter.');
@@ -399,16 +402,18 @@ Cu.import('resource://gre/modules/Servic
tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) {
tagStack.pop();
current = current.parentNode;
}
// Otherwise just ignore the end tag.
continue;
}
var ts = collectTimeStamp(t.substr(1, t.length - 2));
+ // TODO: verify the ts is valid or not, compare to the cue.startTime endTime?
+ // TODO: ensure the all ts are monotonic inscrease?
var node;
if (ts) {
// Timestamps are lead nodes as well.
node = window.document.createProcessingInstruction("timestamp", normalizedTimeStamp(ts));
current.appendChild(node);
continue;
}
var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/);
@@ -957,30 +962,108 @@ Cu.import('resource://gre/modules/Servic
}
for (var i = 0; i < cues.length; i++) {
cue = cues[i];
// Compute the intial position and styles of the cue div.
styleBox = new CueStyleBox(window, cue, styleOptions);
styleBox.cueDiv.style.setProperty("--cue-font-size", fontSize + "px ");
+ styleBox.cueDiv.lastTimestamp = undefined;
paddedOverlay.appendChild(styleBox.div);
// Move the cue div to it's correct line position.
moveBoxToLinePosition(window, styleBox, containerBox, boxPositions);
// Remember the computed div so that we don't have to recompute it later
// if we don't have too.
cue.displayState = styleBox.div;
boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox));
}
})();
};
+ WebVTT.applyPseudoClasses = function(window, overlay, playbackTime) {
+ var paddedOverlay = overlay.firstChild;
+ if (!paddedOverlay)
+ return;
+ // The cueRoot map to the styleBox.div, and the cueDiv map to styleBox.cueDiv
+ for (var cueRoot of paddedOverlay.childNodes) {
+ var cueDiv = cueRoot.firstChild;
+ if (!cueDiv) {
+ continue;
+ }
+
+ var timestampWalker = window.document.createTreeWalker(cueDiv,
+ nodeFilterConstants.SHOW_PROCESSING_INSTRUCTION, null, false);
+ var targetNode;
+ var lastNode = timestampWalker.nextNode();
+ // The cue doesn't have timestamp node inside.
+ if (!lastNode) {
+ // TODO: Do we still need to apply :past or :future?
+ continue;
+ }
+
+ // Compare playbackTime to the find the timestamp node.
+ if (playbackTime < collectTimeStamp(lastNode.data)) {
+ targetNode = lastNode;
+ } else {
+ targetNode = null;
+ while (timestampWalker.nextNode()) {
+ var node = timestampWalker.currentNode;
+ if (collectTimeStamp(node.data) > playbackTime) {
+ targetNode = node;
+ break;
+ }
+ lastNode = node;
+ }
+ }
+
+ // The cue had already applied pseudo-classes.
+ if (targetNode && targetNode.data === cueDiv.lastTimestamp) {
+ continue;
+ } else if (targetNode == null && cueDiv.lastTimestamp == null) {
+ continue;
+ }
+ // Remember the targetNode.data
+ if (targetNode) {
+ cueDiv.lastTimestamp = targetNode.data;
+ } else {
+ cueDiv.lastTimestamp = null;
+ }
+
+ // targetNode is null means playbackTime are greater than all timestamp nodes.
+ // We should apply all nodes to :past
+ var textWalker = window.document.createTreeWalker(cueDiv,
+ nodeFilterConstants.SHOW_TEXT | nodeFilterConstants.SHOW_PROCESSING_INSTRUCTION,
+ null, false);
+ // apply :past
+ while (textWalker.nextNode()) {
+ var node = textWalker.currentNode;
+ if (node.nodeType == nodeConstants.TEXT_NODE) {
+ // TODO: apply :past
+ dump("apply :past\n");
+ dump(node.data + "\n");
+ } else if (node === targetNode){
+ break;
+ }
+ }
+ // apply :future
+ while(textWalker.nextNode()) {
+ var node = textWalker.currentNode;
+ if (node.nodeType == nodeConstants.TEXT_NODE) {
+ // TODO: apply :future
+ dump("apply :future\n");
+ dump(node.data + "\n");
+ }
+ }
+ }
+ };
+
WebVTT.Parser = function(window, decoder) {
this.window = window;
this.state = "INITIAL";
this.buffer = "";
this.decoder = decoder || new TextDecoder("utf8");
this.regionList = [];
};