зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1048688
- videos without an extension in the source URL aren't castable r=wesj
This commit is contained in:
Родитель
f977c02132
Коммит
23050006c4
|
@ -83,18 +83,24 @@ function execute_video_test(test) {
|
||||||
let element = browser.contentDocument.getElementById(test.id);
|
let element = browser.contentDocument.getElementById(test.id);
|
||||||
if (element) {
|
if (element) {
|
||||||
let [x, y] = middle(element);
|
let [x, y] = middle(element);
|
||||||
let video = chromeWin.CastingApps.getVideo(element, x, y);
|
do_test_pending();
|
||||||
if (video) {
|
do_print("Starting to getVideo");
|
||||||
let matchPoster = (test.poster == video.poster);
|
chromeWin.CastingApps.getVideo(element, x, y, (video) => {
|
||||||
let matchSource = (test.source == video.source);
|
do_print("got a Video");
|
||||||
ok(matchPoster && matchSource && test.pass, test.text);
|
if (video) {
|
||||||
} else {
|
let matchPoster = (test.poster == video.poster);
|
||||||
ok(!test.pass, test.text);
|
let matchSource = (test.source == video.source);
|
||||||
}
|
ok(matchPoster && matchSource && test.pass, test.text);
|
||||||
|
} else {
|
||||||
|
ok(!test.pass, test.text);
|
||||||
|
}
|
||||||
|
do_test_finished();
|
||||||
|
run_next_test();
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
ok(false, "test element not found: [" + test.id + "]");
|
ok(false, "test element not found: [" + test.id + "]");
|
||||||
|
run_next_test();
|
||||||
}
|
}
|
||||||
run_next_test();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let videoTest;
|
let videoTest;
|
||||||
|
|
|
@ -246,12 +246,12 @@ var CastingApps = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.getVideo(video, 0, 0)) {
|
this.getVideo(video, 0, 0, (aBundle) => {
|
||||||
return;
|
// Let the binding know casting is allowed
|
||||||
}
|
if (aBundle) {
|
||||||
|
this._sendEventToVideo(aBundle.element, { allow: true });
|
||||||
// Let the binding know casting is allowed
|
}
|
||||||
this._sendEventToVideo(video, { allow: true });
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
handleVideoBindingCast: function handleVideoBindingCast(aTab, aEvent) {
|
handleVideoBindingCast: function handleVideoBindingCast(aTab, aEvent) {
|
||||||
|
@ -274,43 +274,77 @@ var CastingApps = {
|
||||||
return Services.io.newURI(aURL, aOriginCharset, aBaseURI);
|
return Services.io.newURI(aURL, aOriginCharset, aBaseURI);
|
||||||
},
|
},
|
||||||
|
|
||||||
getVideo: function(aElement, aX, aY) {
|
allowableExtension: function(aURI, aExtensions) {
|
||||||
|
return (aURI instanceof Ci.nsIURL) && aExtensions.indexOf(aURI.fileExtension) != -1;
|
||||||
|
},
|
||||||
|
|
||||||
|
allowableMimeType: function(aType, aTypes) {
|
||||||
|
return aTypes.indexOf(aType) != -1;
|
||||||
|
},
|
||||||
|
|
||||||
|
// This method will look at the aElement (or try to find a video at aX, aY) that has
|
||||||
|
// a castable source. If found, aCallback will be called with a JSON meta bundle. If
|
||||||
|
// no castable source was found, aCallback is called with null.
|
||||||
|
getVideo: function(aElement, aX, aY, aCallback) {
|
||||||
let extensions = SimpleServiceDiscovery.getSupportedExtensions();
|
let extensions = SimpleServiceDiscovery.getSupportedExtensions();
|
||||||
let types = SimpleServiceDiscovery.getSupportedMimeTypes();
|
let types = SimpleServiceDiscovery.getSupportedMimeTypes();
|
||||||
|
|
||||||
// Fast path: Is the given element a video element
|
// Fast path: Is the given element a video element?
|
||||||
let video = this._getVideo(aElement, types, extensions);
|
if (aElement instanceof HTMLVideoElement) {
|
||||||
if (video) {
|
// If we found a video element, no need to look further, even if no
|
||||||
return video;
|
// castable video source is found.
|
||||||
|
this._getVideo(aElement, types, extensions, aCallback);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Maybe this is an overlay, with the video element under it.
|
||||||
|
// Use the (x, y) location to guess at a <video> element.
|
||||||
|
|
||||||
// The context menu system will keep walking up the DOM giving us a chance
|
// The context menu system will keep walking up the DOM giving us a chance
|
||||||
// to find an element we match. When it hits <html> things can go BOOM.
|
// to find an element we match. When it hits <html> things can go BOOM.
|
||||||
try {
|
try {
|
||||||
// Maybe this is an overlay, with the video element under it
|
|
||||||
// Use the (x, y) location to guess at a <video> element
|
|
||||||
let elements = aElement.ownerDocument.querySelectorAll("video");
|
let elements = aElement.ownerDocument.querySelectorAll("video");
|
||||||
for (let element of elements) {
|
for (let element of elements) {
|
||||||
// Look for a video element contained in the overlay bounds
|
// Look for a video element contained in the overlay bounds
|
||||||
let rect = element.getBoundingClientRect();
|
let rect = element.getBoundingClientRect();
|
||||||
if (aY >= rect.top && aX >= rect.left && aY <= rect.bottom && aX <= rect.right) {
|
if (aY >= rect.top && aX >= rect.left && aY <= rect.bottom && aX <= rect.right) {
|
||||||
video = this._getVideo(element, types, extensions);
|
// Once we find a <video> under the overlay, we check it and exit.
|
||||||
if (video) {
|
this._getVideo(element, types, extensions, aCallback);
|
||||||
break;
|
return;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
|
|
||||||
// Could be null
|
|
||||||
return video;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_getVideo: function(aElement, aTypes, aExtensions) {
|
_getContentTypeForURI: function(aURI, aCallback) {
|
||||||
if (!(aElement instanceof HTMLVideoElement)) {
|
let channel = Services.io.newChannelFromURI(aURI);
|
||||||
return null;
|
let listener = {
|
||||||
}
|
onStartRequest: function(request, context) {
|
||||||
|
switch (channel.responseStatus) {
|
||||||
|
case 301:
|
||||||
|
case 302:
|
||||||
|
case 303:
|
||||||
|
request.cancel(0);
|
||||||
|
let location = channel.getResponseHeader("Location");
|
||||||
|
CastingApps._getContentTypeForURI(CastingApps.makeURI(location), aCallback);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
aCallback(channel.contentType);
|
||||||
|
request.cancel(0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onStopRequest: function(request, context, statusCode) {},
|
||||||
|
onDataAvailable: function(request, context, stream, offset, count) {}
|
||||||
|
};
|
||||||
|
channel.asyncOpen(listener, null)
|
||||||
|
},
|
||||||
|
|
||||||
|
// Because this method uses a callback, make sure we return ASAP if we know
|
||||||
|
// we have a castable video source.
|
||||||
|
_getVideo: function(aElement, aTypes, aExtensions, aCallback) {
|
||||||
|
// Keep a list of URIs we need for an async mimetype check
|
||||||
|
let asyncURIs = [];
|
||||||
|
|
||||||
// Grab the poster attribute from the <video>
|
// Grab the poster attribute from the <video>
|
||||||
let posterURL = aElement.poster;
|
let posterURL = aElement.poster;
|
||||||
|
@ -327,8 +361,20 @@ var CastingApps = {
|
||||||
// Use the file extension to guess the mime type
|
// Use the file extension to guess the mime type
|
||||||
let sourceURI = this.makeURI(sourceURL, null, this.makeURI(aElement.baseURI));
|
let sourceURI = this.makeURI(sourceURL, null, this.makeURI(aElement.baseURI));
|
||||||
if (this.allowableExtension(sourceURI, aExtensions)) {
|
if (this.allowableExtension(sourceURI, aExtensions)) {
|
||||||
return { element: aElement, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI};
|
aCallback({ element: aElement, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (aElement.type) {
|
||||||
|
// Fast sync check
|
||||||
|
if (this.allowableMimeType(aElement.type, aTypes)) {
|
||||||
|
aCallback({ element: aElement, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI, type: aElement.type });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delay the async check until we sync scan all possible URIs
|
||||||
|
asyncURIs.push(sourceURI);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next, look to see if there is a <source> child element that meets
|
// Next, look to see if there is a <source> child element that meets
|
||||||
|
@ -339,23 +385,77 @@ var CastingApps = {
|
||||||
|
|
||||||
// Using the type attribute is our ideal way to guess the mime type. Otherwise,
|
// Using the type attribute is our ideal way to guess the mime type. Otherwise,
|
||||||
// fallback to using the file extension to guess the mime type
|
// fallback to using the file extension to guess the mime type
|
||||||
if (this.allowableMimeType(sourceNode.type, aTypes) || this.allowableExtension(sourceURI, aExtensions)) {
|
if (this.allowableExtension(sourceURI, aExtensions)) {
|
||||||
return { element: aElement, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI, type: sourceNode.type };
|
aCallback({ element: aElement, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI, type: sourceNode.type });
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sourceNode.type) {
|
||||||
|
// Fast sync check
|
||||||
|
if (this.allowableMimeType(sourceNode.type, aTypes)) {
|
||||||
|
aCallback({ element: aElement, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI, type: sourceNode.type });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delay the async check until we sync scan all possible URIs
|
||||||
|
asyncURIs.push(sourceURI);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
// If we didn't find a good URI directly, let's look using async methods
|
||||||
|
// As soon as we find a good sourceURL, avoid firing the callback any further
|
||||||
|
aCallback.fired = false;
|
||||||
|
for (let sourceURI of asyncURIs) {
|
||||||
|
// Do an async fetch to figure out the mimetype of the source video
|
||||||
|
this._getContentTypeForURI(sourceURI, (aType) => {
|
||||||
|
if (!aCallback.fired && this.allowableMimeType(aType, aTypes)) {
|
||||||
|
aCallback.fired = true;
|
||||||
|
aCallback({ element: aElement, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI, type: aType });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we didn't find any castable source, let's send back a signal
|
||||||
|
if (!aCallback.fired) {
|
||||||
|
aCallback(null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// This code depends on handleVideoBindingAttached setting mozAllowCasting
|
||||||
|
// so we can quickly figure out if the video is castable
|
||||||
|
isVideoCastable: function(aElement, aX, aY) {
|
||||||
|
// Use the flag set when the <video> binding was created as the check
|
||||||
|
if (aElement instanceof HTMLVideoElement) {
|
||||||
|
return aElement.mozAllowCasting;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is called by the context menu system and the system will keep
|
||||||
|
// walking up the DOM giving us a chance to find an element we match.
|
||||||
|
// When it hits <html> things can go BOOM.
|
||||||
|
try {
|
||||||
|
// Maybe this is an overlay, with the video element under it
|
||||||
|
// Use the (x, y) location to guess at a <video> element
|
||||||
|
let elements = aElement.ownerDocument.querySelectorAll("video");
|
||||||
|
for (let element of elements) {
|
||||||
|
// Look for a video element contained in the overlay bounds
|
||||||
|
let rect = element.getBoundingClientRect();
|
||||||
|
if (aY >= rect.top && aX >= rect.left && aY <= rect.bottom && aX <= rect.right) {
|
||||||
|
// Use the flag set when the <video> binding was created as the check
|
||||||
|
return element.mozAllowCasting;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(e) {}
|
||||||
|
|
||||||
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
filterCast: {
|
filterCast: {
|
||||||
matches: function(aElement, aX, aY) {
|
matches: function(aElement, aX, aY) {
|
||||||
|
// This behavior matches the pageaction: As long as a video is castable,
|
||||||
|
// we can cast it, even if it's already being cast to a device.
|
||||||
if (SimpleServiceDiscovery.services.length == 0)
|
if (SimpleServiceDiscovery.services.length == 0)
|
||||||
return false;
|
return false;
|
||||||
let video = CastingApps.getVideo(aElement, aX, aY);
|
return CastingApps.isVideoCastable(aElement, aX, aY);
|
||||||
if (CastingApps.session) {
|
|
||||||
return (video && CastingApps.session.data.source != video.source);
|
|
||||||
}
|
|
||||||
return (video != null);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -495,13 +595,16 @@ var CastingApps = {
|
||||||
|
|
||||||
openExternal: function(aElement, aX, aY) {
|
openExternal: function(aElement, aX, aY) {
|
||||||
// Start a second screen media service
|
// Start a second screen media service
|
||||||
let video = this.getVideo(aElement, aX, aY);
|
this.getVideo(aElement, aX, aY, this._openExternal.bind(this));
|
||||||
if (!video) {
|
},
|
||||||
|
|
||||||
|
_openExternal: function(aVideo) {
|
||||||
|
if (!aVideo) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterFunc(service) {
|
function filterFunc(aService) {
|
||||||
return this.allowableExtension(video.sourceURI, service.extensions) || this.allowableMimeType(video.type, service.types);
|
return this.allowableExtension(aVideo.sourceURI, aService.extensions) || this.allowableMimeType(aVideo.type, aService.types);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.prompt(function(aService) {
|
this.prompt(function(aService) {
|
||||||
|
@ -513,11 +616,12 @@ var CastingApps = {
|
||||||
if (!app)
|
if (!app)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
video.title = aElement.ownerDocument.defaultView.top.document.title;
|
if (aVideo.element) {
|
||||||
if (video.element) {
|
aVideo.title = aVideo.element.ownerDocument.defaultView.top.document.title;
|
||||||
|
|
||||||
// If the video is currently playing on the device, pause it
|
// If the video is currently playing on the device, pause it
|
||||||
if (!video.element.paused) {
|
if (!aVideo.element.paused) {
|
||||||
video.element.pause();
|
aVideo.element.pause();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -539,11 +643,11 @@ var CastingApps = {
|
||||||
app: app,
|
app: app,
|
||||||
remoteMedia: aRemoteMedia,
|
remoteMedia: aRemoteMedia,
|
||||||
data: {
|
data: {
|
||||||
title: video.title,
|
title: aVideo.title,
|
||||||
source: video.source,
|
source: aVideo.source,
|
||||||
poster: video.poster
|
poster: aVideo.poster
|
||||||
},
|
},
|
||||||
videoRef: Cu.getWeakReference(video.element)
|
videoRef: Cu.getWeakReference(aVideo.element)
|
||||||
};
|
};
|
||||||
}.bind(this), this);
|
}.bind(this), this);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
@ -601,21 +705,5 @@ var CastingApps = {
|
||||||
if (status == "completed") {
|
if (status == "completed") {
|
||||||
this.closeExternal();
|
this.closeExternal();
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
allowableExtension: function(aURI, aExtensions) {
|
|
||||||
if (aURI && aURI instanceof Ci.nsIURL) {
|
|
||||||
for (let x in aExtensions) {
|
|
||||||
if (aURI.fileExtension == aExtensions[x]) return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
allowableMimeType: function(aType, aTypes) {
|
|
||||||
for (let x in aTypes) {
|
|
||||||
if (aType == aTypes[x]) return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
Загрузка…
Ссылка в новой задаче