Merge fx-team to m-c a=merge

This commit is contained in:
Wes Kocher 2014-07-18 18:31:46 -07:00
commit 764c4772f3
20 changed files with 683 additions and 69 deletions

View File

@ -431,6 +431,7 @@ skip-if = e10s # Bug 940206 - nsIWebContentHandlerRegistrar::registerProtocolHan
[browser_no_mcb_on_http_site.js]
skip-if = e10s # Bug 516755 - SessionStore disabled for e10s
[browser_bug1003461-switchtab-override.js]
[browser_bug1024133-switchtab-override-keynav.js]
[browser_bug1025195_switchToTabHavingURI_ignoreFragment.js]
[browser_addCertException.js]
skip-if = e10s # Bug ?????? - test directly manipulates content (content.document.getElementById)

View File

@ -0,0 +1,52 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
add_task(function* test_switchtab_override_keynav() {
let testURL = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
info("Opening first tab");
let tab = gBrowser.addTab(testURL);
let tabLoadDeferred = Promise.defer();
whenTabLoaded(tab, tabLoadDeferred.resolve);
yield tabLoadDeferred.promise;
info("Opening and selecting second tab");
let secondTab = gBrowser.selectedTab = gBrowser.addTab();
registerCleanupFunction(() => {
try {
gBrowser.removeTab(tab);
gBrowser.removeTab(secondTab);
} catch(ex) { /* tabs may have already been closed in case of failure */ }
return promiseClearHistory();
});
info("Wait for autocomplete")
let searchDeferred = Promise.defer();
let onSearchComplete = gURLBar.onSearchComplete;
registerCleanupFunction(() => {
gURLBar.onSearchComplete = onSearchComplete;
});
gURLBar.onSearchComplete = function () {
ok(gURLBar.popupOpen, "The autocomplete popup is correctly open");
onSearchComplete.apply(gURLBar);
searchDeferred.resolve();
}
gURLBar.focus();
gURLBar.value = "dummy_pag";
EventUtils.synthesizeKey("e" , {});
yield searchDeferred.promise;
info("Select first autocomplete popup entry");
EventUtils.synthesizeKey("VK_DOWN" , {});
ok(/moz-action:switchtab/.test(gURLBar.value), "switch to tab entry found");
info("Shift+left on switch-to-tab entry");
EventUtils.synthesizeKey("VK_SHIFT" , { type: "keydown" });
EventUtils.synthesizeKey("VK_LEFT", { shiftKey: true });
EventUtils.synthesizeKey("VK_SHIFT" , { type: "keyup" });
ok(!/moz-action:switchtab/.test(gURLBar.inputField.value), "switch to tab should be hidden");
});

View File

@ -146,10 +146,13 @@
this._value = aValue;
var returnValue = aValue;
var action = this._parseActionUrl(aValue);
// Don't put back the action if we are invoked while override actions
// is active.
if (action && this._numNoActionsKeys <= 0) {
if (action) {
returnValue = action.param;
}
// Set the actiontype only if the user is not overriding actions.
if (action && this._noActionsKeys.size == 0) {
this.setAttribute("actiontype", action.type);
} else {
this.removeAttribute("actiontype");
@ -717,14 +720,14 @@
]]></body>
</method>
<field name="_numNoActionsKeys"><![CDATA[
0
<field name="_noActionsKeys"><![CDATA[
new Set();
]]></field>
<method name="_clearNoActions">
<parameter name="aURL"/>
<body><![CDATA[
this._numNoActionsKeys = 0;
this._noActionsKeys.clear();
this.popup.removeAttribute("noactions");
let action = this._parseActionUrl(this._value);
if (action)
@ -746,19 +749,22 @@
<handler event="keydown"><![CDATA[
if ((event.keyCode === KeyEvent.DOM_VK_ALT ||
event.keyCode === KeyEvent.DOM_VK_SHIFT) &&
this.popup.selectedIndex >= 0) {
this._numNoActionsKeys++;
this.popup.setAttribute("noactions", "true");
this.removeAttribute("actiontype");
this.popup.selectedIndex >= 0 &&
!this._noActionsKeys.has(event.keyCode)) {
if (this._noActionsKeys.size == 0) {
this.popup.setAttribute("noactions", "true");
this.removeAttribute("actiontype");
}
this._noActionsKeys.add(event.keyCode);
}
]]></handler>
<handler event="keyup"><![CDATA[
if ((event.keyCode === KeyEvent.DOM_VK_ALT ||
event.keyCode === KeyEvent.DOM_VK_SHIFT) &&
this._numNoActionsKeys > 0) {
this._numNoActionsKeys--;
if (this._numNoActionsKeys == 0)
this._noActionsKeys.has(event.keyCode)) {
this._noActionsKeys.delete(event.keyCode);
if (this._noActionsKeys.size == 0)
this._clearNoActions();
}
]]></handler>

View File

@ -14,3 +14,16 @@ dictionary InspectorRGBTriple {
octet g = 0;
octet b = 0;
};
dictionary InspectorRGBATuple {
/*
* NOTE: This tuple is in the normal 0-255-sized RGB space but can be
* fractional and may extend outside the 0-255 range.
*
* a is in the range 0 - 1.
*/
double r = 0;
double g = 0;
double b = 0;
double a = 1;
};

View File

@ -39,9 +39,12 @@
#include "nsCSSRuleProcessor.h"
#include "mozilla/dom/InspectorUtilsBinding.h"
#include "mozilla/dom/ToJSValue.h"
#include "nsCSSParser.h"
#include "nsCSSProps.h"
#include "nsCSSValue.h"
#include "nsColor.h"
#include "nsStyleSet.h"
#include "nsStyleUtil.h"
using namespace mozilla;
using namespace mozilla::css;
@ -795,6 +798,66 @@ inDOMUtils::RgbToColorName(uint8_t aR, uint8_t aG, uint8_t aB,
return NS_OK;
}
NS_IMETHODIMP
inDOMUtils::ColorToRGBA(const nsAString& aColorString, JSContext* aCx,
JS::MutableHandle<JS::Value> aValue)
{
nscolor color = 0;
nsCSSParser cssParser;
nsCSSValue cssValue;
bool isColor = cssParser.ParseColorString(aColorString, nullptr, 0,
cssValue, true);
if (!isColor) {
aValue.setNull();
return NS_OK;
}
nsRuleNode::ComputeColor(cssValue, nullptr, nullptr, color);
InspectorRGBATuple tuple;
tuple.mR = NS_GET_R(color);
tuple.mG = NS_GET_G(color);
tuple.mB = NS_GET_B(color);
tuple.mA = nsStyleUtil::ColorComponentToFloat(NS_GET_A(color));
if (!ToJSValue(aCx, tuple, aValue)) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
NS_IMETHODIMP
inDOMUtils::IsValidCSSColor(const nsAString& aColorString, bool *_retval)
{
nsCSSParser cssParser;
nsCSSValue cssValue;
*_retval = cssParser.ParseColorString(aColorString, nullptr, 0, cssValue, true);
return NS_OK;
}
NS_IMETHODIMP
inDOMUtils::CssPropertyIsValid(const nsAString& aPropertyName,
const nsAString& aPropertyValue,
bool *_retval)
{
nsCSSProperty propertyID =
nsCSSProps::LookupProperty(aPropertyName, nsCSSProps::eIgnoreEnabledState);
if (propertyID == eCSSProperty_UNKNOWN) {
*_retval = false;
return NS_OK;
}
// Get a parser, parse the property.
nsCSSParser parser;
*_retval = parser.IsValueValidForProperty(propertyID, aPropertyValue);
return NS_OK;
}
NS_IMETHODIMP
inDOMUtils::GetBindingURLs(nsIDOMElement *aElement, nsIArray **_retval)
{

View File

@ -17,7 +17,7 @@ interface nsIDOMFontFaceList;
interface nsIDOMRange;
interface nsIDOMCSSStyleSheet;
[scriptable, uuid(fd529e53-f734-4d15-83ce-d545a631d668)]
[scriptable, uuid(bd6b3dee-b8dd-40c7-a40a-ad8455b49917)]
interface inIDOMUtils : nsISupports
{
// CSS utilities
@ -69,8 +69,25 @@ interface inIDOMUtils : nsISupports
jsval colorNameToRGB(in DOMString aColorName);
AString rgbToColorName(in octet aR, in octet aG, in octet aB);
// Convert a given CSS color string to rgba. Returns null on failure or an
// InspectorRGBATuple on success.
//
// NOTE: Converting a color to RGBA may be lossy when converting from some
// formats e.g. CMYK.
[implicit_jscontext]
jsval colorToRGBA(in DOMString aColorString);
// Check whether a given color is a valid CSS color.
bool isValidCSSColor(in AString aColorString);
// Utilities for obtaining information about a CSS property.
// Check whether a CSS property and value are a valid combination. If the
// property is pref-disabled it will still be processed.
// aPropertyName: A property name e.g. "color"
// aPropertyValue: A property value e.g. "red" or "red !important"
bool cssPropertyIsValid(in AString aPropertyName, in AString aPropertyValue);
// Get a list of the longhands corresponding to the given CSS property. If
// the property is a longhand already, just returns the property itself.
// Throws on unsupported property names.

View File

@ -12,5 +12,8 @@ support-files = bug856317.css
[test_bug856317.html]
[test_bug877690.html]
[test_bug1006595.html]
[test_color_to_rgba.html]
[test_is_valid_css_color.html]
[test_css_property_is_valid.html]
[test_get_all_style_sheets.html]
[test_isinheritableproperty.html]

View File

@ -0,0 +1,50 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test inDOMUtils::ColorToRGBA</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript;version=1.8">
let utils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"]
.getService(SpecialPowers.Ci.inIDOMUtils);
testColor("red", {r:255, g:0, b:0, a:1});
testColor("#f00", {r:255, g:0, b:0, a:1});
testColor("#ff0000", {r:255, g:0, b:0, a:1});
testColor("ff0000", null);
testColor("rgb(255,0,0)", {r:255, g:0, b:0, a:1});
testColor("rgba(255,0,0,0.7)", {r:255, g:0, b:0, a:0.7});
testColor("rgb(255,0,0,0.7)", null);
testColor("rgb(50%,75%,60%)", {r:128, g:191, b:153, a:1});
testColor("rgba(100%,50%,25%,0.7)", {r:255, g:128, b:64, a:0.7});
testColor("hsl(320,30%,10%)", {r:33, g:17, b:28, a:1});
testColor("hsla(170,60%,40%,0.9)", {r:40, g:163, b:142, a:0.9});
function testColor(color, expected) {
let rgb = utils.colorToRGBA(color);
if (rgb === null) {
ok(expected === null, "color: " + color + " returns null");
return;
}
let {r, g, b, a} = rgb;
is(r, expected.r, "color: " + color + ", red component is converted correctly");
is(g, expected.g, "color: " + color + ", green component is converted correctly");
is(b, expected.b, "color: " + color + ", blue component is converted correctly");
is(Math.round(a * 10) / 10, expected.a, "color: " + color + ", alpha component is a converted correctly");
}
</script>
</head>
<body>
<h1>Test inDOMUtils::ColorToRGBA</h1>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

View File

@ -0,0 +1,100 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test inDOMUtils::CssPropertyIsValid</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript;version=1.8">
let utils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"]
.getService(SpecialPowers.Ci.inIDOMUtils);
let tests = [
{
property: "color",
value: "red",
expected: true
},
{
property: "display",
value: "none",
expected: true
},
{
property: "display",
value: "red",
expected: false
},
{
property: "displayx",
value: "none",
expected: false
},
{
property: "border",
value: "1px solid blue",
expected: true
},
{
property: "border",
value: "1 solid blue",
expected: false
},
{
property: "border",
value: "1px underline blue",
expected: false
},
{
property: "border",
value: "1px solid",
expected: true
},
{
property: "color",
value: "blue !important",
expected: true
},
{
property: "color",
value: "blue ! important",
expected: true
},
{
property: "color",
value: "blue !impoxxxrtant",
expected: false
},
{
property: "color",
value: "red; background:green;",
expected: false
},
{
property: "content",
value: "\"hello\"",
expected: true
}
];
for (let {property, value, expected} of tests) {
let valid = utils.cssPropertyIsValid(property, value);
if (expected) {
ok(valid, property + ":" + value + " is valid");
} else {
ok(!valid, property + ":" + value + " is not valid");
}
}
</script>
</head>
<body>
<h1>Test inDOMUtils::CssPropertyIsValid</h1>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

View File

@ -0,0 +1,90 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test inDOMUtils::isValidCSSColor</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript;version=1.8">
let utils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"]
.getService(SpecialPowers.Ci.inIDOMUtils);
// Color names
let colors = utils.getCSSValuesForProperty("color");
let notColor = ["hsl", "hsla", "inherit", "initial", "rgb", "rgba", "unset"];
for (let color of colors) {
if (notColor.indexOf(color) !== -1) {
continue;
}
ok(utils.isValidCSSColor(color), color + " is a valid color");
ok(!utils.isValidCSSColor("xxx" + color), "xxx" + color + " is not a valid color");
}
// rgb(a)
for (let i = 0; i <= 265; i++) {
ok(utils.isValidCSSColor("rgb(" + i + ",0,0)"), "rgb(" + i + ",0,0) is a valid color");
ok(utils.isValidCSSColor("rgb(0," + i + ",0)"), "rgb(0," + i + ",0) is a valid color");
ok(utils.isValidCSSColor("rgb(0,0," + i + ")"), "rgb(0,0," + i + ") is a valid color");
ok(utils.isValidCSSColor("rgba(" + i + ",0,0,0.2)"), "rgba(" + i + ",0,0,0.2) is a valid color");
ok(utils.isValidCSSColor("rgba(0," + i + ",0,0.5)"), "rgba(0," + i + ",0,0.5) is a valid color");
ok(utils.isValidCSSColor("rgba(0,0," + i + ",0.7)"), "rgba(0,0," + i + ",0.7) is a valid color");
ok(!utils.isValidCSSColor("rgbxxx(" + i + ",0,0)"), "rgbxxx(" + i + ",0,0) is not a valid color");
ok(!utils.isValidCSSColor("rgbxxx(0," + i + ",0)"), "rgbxxx(0," + i + ",0) is not a valid color");
ok(!utils.isValidCSSColor("rgbxxx(0,0," + i + ")"), "rgbxxx(0,0," + i + ") is not a valid color");
}
// rgb(a) (%)
for (let i = 0; i <= 110; i++) {
ok(utils.isValidCSSColor("rgb(" + i + "%,0%,0%)"), "rgb(" + i + "%,0%,0%) is a valid color");
ok(utils.isValidCSSColor("rgb(0%," + i + "%,0%)"), "rgb(0%," + i + "%,0%) is a valid color");
ok(utils.isValidCSSColor("rgb(0%,0%," + i + "%)"), "rgb(0%,0%," + i + "%) is a valid color");
ok(utils.isValidCSSColor("rgba(" + i + "%,0%,0%,0.2)"), "rgba(" + i + "%,0%,0%,0.2) is a valid color");
ok(utils.isValidCSSColor("rgba(0%," + i + "%,0%,0.5)"), "rgba(0%," + i + "%,0%,0.5) is a valid color");
ok(utils.isValidCSSColor("rgba(0%,0%," + i + "%,0.7)"), "rgba(0%,0%," + i + "%,0.7) is a valid color");
ok(!utils.isValidCSSColor("rgbaxxx(" + i + "%,0%,0%,0.2)"), "rgbaxxx(" + i + "%,0%,0%,0.2) is not a valid color");
ok(!utils.isValidCSSColor("rgbaxxx(0%," + i + "%,0%,0.5)"), "rgbaxxx(0%," + i + "%,0%,0.5) is not a valid color");
ok(!utils.isValidCSSColor("rgbaxxx(0%,0%," + i + "%,0.7)"), "rgbaxxx(0%,0%," + i + "%,0.7) is not a valid color");
}
// hsl(a)
for (let i = 0; i <= 370; i++) {
ok(utils.isValidCSSColor("hsl(" + i + ",30%,10%)"), "rgb(" + i + ",30%,10%) is a valid color");
ok(utils.isValidCSSColor("hsla(" + i + ",60%,70%,0.2)"), "rgba(" + i + ",60%,70%,0.2) is a valid color");
}
for (let i = 0; i <= 110; i++) {
ok(utils.isValidCSSColor("hsl(100," + i + "%,20%)"), "hsl(100," + i + "%,20%) is a valid color");
ok(utils.isValidCSSColor("hsla(100,20%," + i + "%,0.6)"), "hsla(100,20%," + i + "%,0.6) is a valid color");
}
// hex
for (let i = 0; i <= 255; i++) {
let hex = (i).toString(16);
if (hex.length === 1) {
hex = 0 + hex;
}
ok(utils.isValidCSSColor("#" + hex + "7777"), "#" + hex + "7777 is a valid color");
ok(utils.isValidCSSColor("#77" + hex + "77"), "#77" + hex + "77 is a valid color");
ok(utils.isValidCSSColor("#7777" + hex), "#7777" + hex + " is a valid color");
}
ok(!utils.isValidCSSColor("#kkkkkk"), "#kkkkkk is not a valid color");
// short hex
for (let i = 0; i <= 16; i++) {
let hex = (i).toString(16);
ok(utils.isValidCSSColor("#" + hex + hex + hex), "#" + hex + hex + hex + " is a valid color");
}
ok(!utils.isValidCSSColor("#ggg"), "#ggg is not a valid color");
</script>
</head>
<body>
<h1>Test inDOMUtils::isValidCSSColor</h1>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

View File

@ -177,7 +177,8 @@ public:
bool ParseColorString(const nsSubstring& aBuffer,
nsIURI* aURL, // for error reporting
uint32_t aLineNumber, // for error reporting
nsCSSValue& aValue);
nsCSSValue& aValue,
bool aSuppressErrors /* false */);
nsresult ParseSelectorString(const nsSubstring& aSelectorString,
nsIURI* aURL, // for error reporting
@ -216,6 +217,8 @@ public:
nsIPrincipal* aSheetPrincipal,
nsCSSValue& aValue);
bool IsValueValidForProperty(const nsCSSProperty aPropID,
const nsAString& aPropValue);
typedef nsCSSParser::VariableEnumFunc VariableEnumFunc;
@ -1707,15 +1710,24 @@ bool
CSSParserImpl::ParseColorString(const nsSubstring& aBuffer,
nsIURI* aURI, // for error reporting
uint32_t aLineNumber, // for error reporting
nsCSSValue& aValue)
nsCSSValue& aValue,
bool aSuppressErrors /* false */)
{
nsCSSScanner scanner(aBuffer, aLineNumber);
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
InitScanner(scanner, reporter, aURI, aURI, nullptr);
nsAutoSuppressErrors suppressErrors(this, aSuppressErrors);
// Parse a color, and check that there's nothing else after it.
bool colorParsed = ParseColor(aValue) && !GetToken(true);
OUTPUT_ERROR();
if (aSuppressErrors) {
CLEAR_ERROR();
} else {
OUTPUT_ERROR();
}
ReleaseScanner();
return colorParsed;
}
@ -14668,6 +14680,47 @@ CSSParserImpl::ParseValueWithVariables(CSSVariableDeclarations::Type* aType,
return true;
}
bool
CSSParserImpl::IsValueValidForProperty(const nsCSSProperty aPropID,
const nsAString& aPropValue)
{
mData.AssertInitialState();
mTempData.AssertInitialState();
nsCSSScanner scanner(aPropValue, 0);
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, nullptr);
InitScanner(scanner, reporter, nullptr, nullptr, nullptr);
nsAutoSuppressErrors suppressErrors(this);
mSection = eCSSSection_General;
scanner.SetSVGMode(false);
// Check for unknown properties
if (eCSSProperty_UNKNOWN == aPropID) {
ReleaseScanner();
return false;
}
// Check that the property and value parse successfully
bool parsedOK = ParseProperty(aPropID);
// Check for priority
parsedOK = parsedOK && ParsePriority() != ePriority_Error;
// We should now be at EOF
parsedOK = parsedOK && !GetToken(true);
mTempData.ClearProperty(aPropID);
mTempData.AssertInitialState();
mData.AssertInitialState();
CLEAR_ERROR();
ReleaseScanner();
return parsedOK;
}
} // anonymous namespace
// Recycling of parser implementation objects
@ -14860,10 +14913,11 @@ bool
nsCSSParser::ParseColorString(const nsSubstring& aBuffer,
nsIURI* aURI,
uint32_t aLineNumber,
nsCSSValue& aValue)
nsCSSValue& aValue,
bool aSuppressErrors /* false */)
{
return static_cast<CSSParserImpl*>(mImpl)->
ParseColorString(aBuffer, aURI, aLineNumber, aValue);
ParseColorString(aBuffer, aURI, aLineNumber, aValue, aSuppressErrors);
}
nsresult
@ -14981,3 +15035,12 @@ nsCSSParser::ParseCounterDescriptor(nsCSSCounterDesc aDescID,
ParseCounterDescriptor(aDescID, aBuffer,
aSheetURL, aBaseURL, aSheetPrincipal, aValue);
}
bool
nsCSSParser::IsValueValidForProperty(const nsCSSProperty aPropID,
const nsAString& aPropValue)
{
return static_cast<CSSParserImpl*>(mImpl)->
IsValueValidForProperty(aPropID, aPropValue);
}

View File

@ -191,7 +191,8 @@ public:
bool ParseColorString(const nsSubstring& aBuffer,
nsIURI* aURL,
uint32_t aLineNumber,
nsCSSValue& aValue);
nsCSSValue& aValue,
bool aSuppressErrors = false);
/**
* Parse aBuffer into a selector list. On success, caller must
@ -296,6 +297,10 @@ public:
nsIPrincipal* aSheetPrincipal,
nsCSSValue& aValue);
// Check whether a given value can be applied to a property.
bool IsValueValidForProperty(const nsCSSProperty aPropID,
const nsAString& aPropValue);
protected:
// This is a CSSParserImpl*, but if we expose that type name in this
// header, we can't put the type definition (in nsCSSParser.cpp) in

View File

@ -26,7 +26,9 @@ function discovery_observer(subject, topic, data) {
var testTarget = {
target: "test:service",
factory: function(service) { /* dummy */ }
factory: function(service) { /* dummy */ },
types: ["video/mp4"],
extensions: ["mp4"]
};
add_test(function test_default() {

View File

@ -46,8 +46,10 @@ let videoDiscoveryTests = [
{ id: "simple-mp4", source: "http://mochi.test:8888/simple.mp4", poster: "http://mochi.test:8888/simple.png", text: "simple video with mp4 src" },
{ id: "simple-fail", pass: false, text: "simple video with no mp4 src" },
{ id: "with-sources-mp4", source: "http://mochi.test:8888/simple.mp4", text: "video with mp4 extension source child" },
{ id: "with-sources-webm", source: "http://mochi.test:8888/simple.webm", text: "video with webm extension source child" },
{ id: "with-sources-fail", pass: false, text: "video with no mp4 extension source child" },
{ id: "with-sources-mimetype", source: "http://mochi.test:8888/simple-video-mp4", text: "video with mp4 mimetype source child" },
{ id: "with-sources-mimetype-mp4", source: "http://mochi.test:8888/simple-video-mp4", text: "video with mp4 mimetype source child" },
{ id: "with-sources-mimetype-webm", source: "http://mochi.test:8888/simple-video-webm", text: "video with webm mimetype source child" },
{ id: "video-overlay", source: "http://mochi.test:8888/simple.mp4", text: "div overlay covering a simple video with mp4 src" }
];

View File

@ -31,18 +31,29 @@
<source src="/simple.mp4">
</video>
<!-- FAIL: source list uses a mp4 extension -->
<video id="with-sources-fail">
<!-- PASS: source list uses a webm extension -->
<video id="with-sources-webm">
<source src="/simple.ogg">
<source src="/simple.webm">
</video>
<!-- FAIL: source list has no mp4 or webm extension -->
<video id="with-sources-fail">
<source src="/simple.ogg">
</video>
<!-- PASS: source list uses a mp4 mimetype -->
<video id="with-sources-mimetype">
<video id="with-sources-mimetype-mp4">
<source src="/simple-video-ogg" type="video/ogg">
<source src="/simple-video-mp4" type="video/mp4">
</video>
<!-- PASS: source list uses a webm mimetype -->
<video id="with-sources-mimetype-webm">
<source src="/simple-video-ogg" type="video/ogg">
<source src="/simple-video-webm" type="video/webm">
</video>
<!-- PASS: source list uses a mp4 mimetype and extra data -->
<video id="with-sources-mimetype-plus">
<source src="/simple-video-ogg" type="video/ogg">

View File

@ -1,3 +1,4 @@
// -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
@ -10,7 +11,9 @@ var rokuTarget = {
factory: function(aService) {
Cu.import("resource://gre/modules/RokuApp.jsm");
return new RokuApp(aService);
}
},
types: ["video/mp4"],
extensions: ["mp4"]
};
var fireflyTarget = {
@ -22,7 +25,9 @@ var fireflyTarget = {
factory: function(aService) {
Cu.import("resource://gre/modules/FireflyApp.jsm");
return new FireflyApp(aService);
}
},
types: ["video/mp4", "video/webm"],
extensions: ["mp4", "webm"]
};
var mediaPlayerTarget = {
@ -30,7 +35,9 @@ var mediaPlayerTarget = {
factory: function(aService) {
Cu.import("resource://gre/modules/MediaPlayerApp.jsm");
return new MediaPlayerApp(aService);
}
},
types: ["video/mp4", "video/webm", "application/x-mpegurl"],
extensions: ["mp4", "webm", "m3u", "m3u8"]
};
var CastingApps = {
@ -173,8 +180,11 @@ var CastingApps = {
},
getVideo: function(aElement, aX, aY) {
let extensions = SimpleServiceDiscovery.getSupportedExtensions();
let types = SimpleServiceDiscovery.getSupportedMimeTypes();
// Fast path: Is the given element a video element
let video = this._getVideo(aElement);
let video = this._getVideo(aElement, types, extensions);
if (video) {
return video;
}
@ -189,7 +199,7 @@ var CastingApps = {
// 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) {
video = this._getVideo(element);
video = this._getVideo(element, types, extensions);
if (video) {
break;
}
@ -201,18 +211,11 @@ var CastingApps = {
return video;
},
_getVideo: function(aElement) {
_getVideo: function(aElement, aTypes, aExtensions) {
if (!(aElement instanceof HTMLVideoElement)) {
return null;
}
// Given the hardware support for H264, let's only look for 'mp4' sources
function allowableExtension(aURI) {
if (aURI && aURI instanceof Ci.nsIURL) {
return (aURI.fileExtension == "mp4");
}
return false;
}
// Grab the poster attribute from the <video>
let posterURL = aElement.poster;
@ -228,8 +231,8 @@ var CastingApps = {
if (sourceURL) {
// Use the file extension to guess the mime type
let sourceURI = this.makeURI(sourceURL, null, this.makeURI(aElement.baseURI));
if (allowableExtension(sourceURI)) {
return { element: aElement, source: sourceURI.spec, poster: posterURL };
if (this.allowableExtension(sourceURI, aExtensions)) {
return { element: aElement, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI};
}
}
@ -241,8 +244,8 @@ var CastingApps = {
// 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
if (sourceNode.type == "video/mp4" || allowableExtension(sourceURI)) {
return { element: aElement, source: sourceURI.spec, poster: posterURL };
if (this.allowableMimeType(sourceNode.type, aTypes) || this.allowableExtension(sourceURI, aExtensions)) {
return { element: aElement, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI, type: sourceNode.type };
}
}
@ -361,21 +364,25 @@ var CastingApps = {
}
},
prompt: function(aCallback) {
prompt: function(aCallback, aFilterFunc) {
let items = [];
let filteredServices = [];
SimpleServiceDiscovery.services.forEach(function(aService) {
let item = {
label: aService.friendlyName,
selected: false
};
items.push(item);
if (!aFilterFunc || aFilterFunc(aService)) {
filteredServices.push(aService);
items.push(item);
}
});
let prompt = new Prompt({
title: Strings.browser.GetStringFromName("casting.prompt")
}).setSingleChoiceItems(items).show(function(data) {
let selected = data.button;
let service = selected == -1 ? null : SimpleServiceDiscovery.services[selected];
let service = selected == -1 ? null : filteredServices[selected];
if (aCallback)
aCallback(service);
});
@ -394,6 +401,10 @@ var CastingApps = {
return;
}
function filterFunc(service) {
return this.allowableExtension(video.sourceURI, service.extensions) || this.allowableMimeType(video.type, service.types);
}
this.prompt(function(aService) {
if (!aService)
return;
@ -438,7 +449,7 @@ var CastingApps = {
}.bind(this), this);
}.bind(this));
}.bind(this));
}.bind(this));
}.bind(this), filterFunc.bind(this));
},
closeExternal: function() {
@ -487,5 +498,21 @@ var CastingApps = {
if (status == "completed") {
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;
}
};

View File

@ -1,4 +1,4 @@
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
// -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
@ -243,6 +243,26 @@ var SimpleServiceDiscovery = {
}
},
getSupportedExtensions: function() {
let extensions = [];
this._targets.forEach(function(target) {
extensions = extensions.concat(target.extensions);
}, this);
return extensions.filter(function(extension, pos) {
return extensions.indexOf(extension) == pos;
});
},
getSupportedMimeTypes: function() {
let types = [];
this._targets.forEach(function(target) {
types = types.concat(target.types);
}, this);
return types.filter(function(type, pos) {
return types.indexOf(type) == pos;
});
},
registerTarget: function registerTarget(aTarget) {
// We must have "target" and "factory" defined
if (!("target" in aTarget) || !("factory" in aTarget)) {
@ -291,6 +311,9 @@ var SimpleServiceDiscovery = {
get services() {
let array = [];
for (let [key, service] of this._services) {
let target = this._targets.get(service.target);
service.extensions = target.extensions;
service.types = target.types;
array.push(service);
}
return array;

View File

@ -134,12 +134,111 @@ Transport.prototype = {
};
/**
* Manages the local device's name. The name can be generated in serveral
* platform-specific ways (see |_generate|). The aim is for each device on the
* same local network to have a unique name. If the Settings API is available,
* the name is saved there to persist across reboots.
*/
function LocalDevice() {
this._name = LocalDevice.UNKNOWN;
if ("@mozilla.org/settingsService;1" in Cc) {
this._settings =
Cc["@mozilla.org/settingsService;1"].getService(Ci.nsISettingsService);
Services.obs.addObserver(this, "mozsettings-changed", false);
}
this._get(); // Trigger |_get| to load name eagerly
}
LocalDevice.SETTING = "devtools.discovery.device";
LocalDevice.UNKNOWN = "unknown";
LocalDevice.prototype = {
_get: function() {
if (!this._settings) {
// Without Settings API, just generate a name and stop, since the value
// can't be persisted.
this._generate();
return;
}
// Initial read of setting value
this._settings.createLock().get(LocalDevice.SETTING, {
handle: (_, name) => {
if (name && name !== LocalDevice.UNKNOWN) {
this._name = name;
log("Device: " + this._name);
return;
}
// No existing name saved, so generate one.
this._generate();
},
handleError: () => log("Failed to get device name setting")
});
},
/**
* Generate a new device name from various platform-specific properties.
* Triggers the |name| setter to persist if needed.
*/
_generate: function() {
if (Services.appinfo.widgetToolkit == "gonk") {
// For Gonk devices, create one from the device name plus a little
// randomness. The goal is just to distinguish devices in an office
// environment where many people may have the same device model for
// testing purposes (which would otherwise all report the same name).
let name = libcutils.property_get("ro.product.device");
// Pick a random number from [0, 2^32)
let randomID = Math.floor(Math.random() * Math.pow(2, 32));
// To hex and zero pad
randomID = ("00000000" + randomID.toString(16)).slice(-8);
this.name = name + "-" + randomID;
} else {
this.name = sysInfo.get("host");
}
},
/**
* Observe any changes that might be made via the Settings app
*/
observe: function(subject, topic, data) {
if (topic !== "mozsettings-changed") {
return;
}
let setting = JSON.parse(data);
if (setting.key !== LocalDevice.SETTING) {
return;
}
this._name = setting.value;
log("Device: " + this._name);
},
get name() {
return this._name;
},
set name(name) {
if (!this._settings) {
this._name = name;
log("Device: " + this._name);
return;
}
// Persist to Settings API
// The new value will be seen and stored by the observer above
this._settings.createLock().set(LocalDevice.SETTING, name, {
handle: () => {},
handleError: () => log("Failed to set device name setting")
});
}
};
function Discovery() {
EventEmitter.decorate(this);
this.localServices = {};
this.remoteServices = {};
this.device = { name: "unknown" };
this.device = new LocalDevice();
this.replyTimeout = REPLY_TIMEOUT;
// Defaulted to Transport, but can be altered by tests
@ -158,8 +257,6 @@ function Discovery() {
this._purgeMissingDevices = this._purgeMissingDevices.bind(this);
Services.obs.addObserver(this, "network-active-changed", false);
this._getSystemInfo();
}
Discovery.prototype = {
@ -238,24 +335,6 @@ Discovery.prototype = {
setTimeout(this._purgeMissingDevices, this.replyTimeout);
},
/**
* Determine a unique name to identify the current device.
*/
_getSystemInfo: function() {
// TODO Bug 1027787: Uniquify device name somehow?
try {
if (Services.appinfo.widgetToolkit == "gonk") {
this.device.name = libcutils.property_get("ro.product.device");
} else {
this.device.name = sysInfo.get("host");
}
log("Device: " + this.device.name);
} catch(e) {
log("Failed to get system info");
this.device.name = "unknown";
}
},
get Transport() {
return this._factories.Transport;
},

View File

@ -69,7 +69,13 @@ TestTransport.prototype = {
// Use TestTransport instead of the usual Transport
discovery._factories.Transport = TestTransport;
discovery.device.name = "test-device";
// Ignore name generation on b2g and force a fixed value
Object.defineProperty(discovery.device, "name", {
get: function() {
return "test-device";
}
});
function run_test() {
run_next_test();

View File

@ -3,3 +3,4 @@ head =
tail =
[test_discovery.js]
skip-if = toolkit == 'gonk' && debug # Debug doesn't like settings read