mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge m-c to b-i
This commit is contained in:
commit
184d322543
@ -299,6 +299,7 @@
|
||||
@BINPATH@/components/spellchecker.xpt
|
||||
@BINPATH@/components/storage.xpt
|
||||
@BINPATH@/components/telemetry.xpt
|
||||
@BINPATH@/components/toolkit_finalizationwitness.xpt
|
||||
@BINPATH@/components/toolkitprofile.xpt
|
||||
#ifdef MOZ_ENABLE_XREMOTE
|
||||
@BINPATH@/components/toolkitremote.xpt
|
||||
|
@ -685,6 +685,11 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
|
||||
}
|
||||
// The container is not empty and an actual item was selected.
|
||||
DebuggerView.setEditorLocation(sourceItem.value);
|
||||
|
||||
// Set window title.
|
||||
let script = sourceItem.value.split(" -> ").pop();
|
||||
document.title = L10N.getFormatStr("DebuggerWindowScriptTitle", script);
|
||||
|
||||
this.maybeShowBlackBoxMessage();
|
||||
},
|
||||
|
||||
|
@ -68,6 +68,7 @@ let DebuggerView = {
|
||||
this.GlobalSearch.initialize();
|
||||
this._initializeVariablesView();
|
||||
this._initializeEditor(deferred.resolve);
|
||||
document.title = L10N.getStr("DebuggerWindowTitle");
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
@ -19,6 +19,9 @@ function test() {
|
||||
gEditor = gDebugger.DebuggerView.editor;
|
||||
gSources = gDebugger.DebuggerView.Sources;
|
||||
|
||||
ok(gDebugger.document.title.endsWith(EXAMPLE_URL + gLabel1),
|
||||
"Title with first source is correct.");
|
||||
|
||||
waitForSourceAndCaretAndScopes(gPanel, "-02.js", 6)
|
||||
.then(testSourcesDisplay)
|
||||
.then(testSwitchPaused1)
|
||||
@ -64,6 +67,9 @@ function testSourcesDisplay() {
|
||||
is(gEditor.getText().search(/debugger/), 172,
|
||||
"The second source is displayed.");
|
||||
|
||||
ok(gDebugger.document.title.endsWith(EXAMPLE_URL + gLabel2),
|
||||
"Title with second source is correct.");
|
||||
|
||||
ok(isCaretPos(gPanel, 6),
|
||||
"Editor caret location is correct.");
|
||||
|
||||
|
@ -305,6 +305,7 @@
|
||||
@BINPATH@/components/shistory.xpt
|
||||
@BINPATH@/components/spellchecker.xpt
|
||||
@BINPATH@/components/storage.xpt
|
||||
@BINPATH@/components/toolkit_finalizationwitness.xpt
|
||||
@BINPATH@/components/toolkitprofile.xpt
|
||||
#ifdef MOZ_ENABLE_XREMOTE
|
||||
@BINPATH@/components/toolkitremote.xpt
|
||||
|
@ -15,6 +15,14 @@
|
||||
# displayed inside the developer tools window and in the Developer Tools Menu.
|
||||
ToolboxDebugger.label=Debugger
|
||||
|
||||
# LOCALIZATION NOTE (DebuggerWindowTitle):
|
||||
# The title displayed for the debugger window.
|
||||
DebuggerWindowTitle=Browser Debugger
|
||||
|
||||
# LOCALIZATION NOTE (DebuggerWindowScriptTitle):
|
||||
# The title displayed for the debugger window when a script is selected.
|
||||
DebuggerWindowScriptTitle=Browser Debugger - %S
|
||||
|
||||
# LOCALIZATION NOTE (ToolboxDebugger.tooltip):
|
||||
# This string is displayed in the tooltip of the tab when the debugger is
|
||||
# displayed inside the developer tools window..
|
||||
|
@ -2678,30 +2678,24 @@ nsDocument::InitCSP(nsIChannel* aChannel)
|
||||
}
|
||||
|
||||
// While we are supporting both CSP 1.0 and the x- headers, the 1.0 headers
|
||||
// can coexist with x- headers. If both exist, they're both enforced, but
|
||||
// there's a warning posted in the web console that the x-headers are going
|
||||
// away.
|
||||
// take priority. If both are present, the x-* headers are ignored.
|
||||
|
||||
// ----- if there's a full-strength CSP header, apply it.
|
||||
if (!cspOldHeaderValue.IsEmpty()) {
|
||||
if (!cspHeaderValue.IsEmpty()) {
|
||||
rv = AppendCSPFromHeader(csp, cspHeaderValue, selfURI, false, true);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
} else if (!cspOldHeaderValue.IsEmpty()) {
|
||||
rv = AppendCSPFromHeader(csp, cspOldHeaderValue, selfURI, false, false);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
if (!cspHeaderValue.IsEmpty()) {
|
||||
rv = AppendCSPFromHeader(csp, cspHeaderValue, selfURI, false, true);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
// ----- if there's a report-only CSP header, apply it.
|
||||
if (!cspOldROHeaderValue.IsEmpty()) {
|
||||
rv = AppendCSPFromHeader(csp, cspOldROHeaderValue, selfURI, true, false);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
if (!cspROHeaderValue.IsEmpty()) {
|
||||
rv = AppendCSPFromHeader(csp, cspROHeaderValue, selfURI, true, true);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
} else if (!cspOldROHeaderValue.IsEmpty()) {
|
||||
rv = AppendCSPFromHeader(csp, cspOldROHeaderValue, selfURI, true, false);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
// ----- Enforce frame-ancestor policy on any applied policies
|
||||
|
@ -1,5 +1,6 @@
|
||||
<html>
|
||||
<body>
|
||||
<img src="http://example.org/nonexistent.jpg"></img>
|
||||
<img src="http://example.org/prefixed.jpg"></img>
|
||||
<img src="/unprefixed.jpg"></img>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,2 +1,2 @@
|
||||
X-Content-Security-Policy: default-src 'self' ; img-src 'self' http://example.org
|
||||
X-Content-Security-Policy: default-src 'none' ; img-src http://example.org
|
||||
Content-Security-Policy: default-src 'self'
|
||||
|
@ -2,6 +2,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for Correctly Handling Both Pre-1.0 and 1.0 Content Security Policy Headers</title>
|
||||
<!-- When both headers are present, we should ignore the pre-1.0 header and
|
||||
only recognize the 1.0 spec-compliant header. -->
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
@ -13,9 +15,12 @@
|
||||
<iframe style="width:200px;height:200px;" id='cspframe'></iframe>
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
var loadedImgURL = "http://example.org/nonexistent.jpg";
|
||||
var prefixedHeaderImgURL = "http://example.org/prefixed.jpg";
|
||||
var unprefixedHeaderImgURL = "http://mochi.test:8888/unprefixed.jpg";
|
||||
var testsRun = 0;
|
||||
var totalTests = 2;
|
||||
|
||||
// This is used to watch the blocked data bounce off CSP and allowed data
|
||||
// This is used to watch the blocked data bounce off CSP and allowed data
|
||||
// get sent out to the wire.
|
||||
function examiner() {
|
||||
SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
|
||||
@ -28,25 +33,21 @@ examiner.prototype = {
|
||||
return;
|
||||
|
||||
if (topic === "http-on-modify-request") {
|
||||
// the load was allowed, this is a fail, the Content-Security Policy
|
||||
// should not allow the load
|
||||
var asciiSpec = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIHttpChannel"), "URI.asciiSpec");
|
||||
if (asciiSpec != loadedImgURL) return;
|
||||
|
||||
ok(false, "the Content-Security Policy header does not allow the load, the X-Content-Security header should be ignored");
|
||||
window.examiner.remove();
|
||||
SimpleTest.finish();
|
||||
if (asciiSpec == prefixedHeaderImgURL || asciiSpec == unprefixedHeaderImgURL) {
|
||||
is(asciiSpec, unprefixedHeaderImgURL, "Load was allowed - should be allowed by unprefixed header (blocked by prefixed)");
|
||||
testRan();
|
||||
}
|
||||
}
|
||||
|
||||
if (topic === "csp-on-violate-policy") {
|
||||
// the load was blocked, this is a pass, the Content-Security-Policy
|
||||
// header doesn't allow the load, but the X-Content-Security-Header does
|
||||
var asciiSpec = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIURI"), "asciiSpec");
|
||||
if (asciiSpec != loadedImgURL) return;
|
||||
|
||||
ok(true, "Load was blocked - the Content-Security-Policy header doesn't allow the load, the X-Content-Security-Header does but should have been ignored");
|
||||
window.examiner.remove();
|
||||
SimpleTest.finish();
|
||||
if (asciiSpec == prefixedHeaderImgURL || asciiSpec == unprefixedHeaderImgURL) {
|
||||
is(asciiSpec, prefixedHeaderImgURL, "Load was blocked - the Content-Security-Policy header doesn't allow the load, the X-Content-Security-Header does but should have been ignored");
|
||||
testRan();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -59,17 +60,21 @@ examiner.prototype = {
|
||||
}
|
||||
|
||||
window.examiner = new examiner();
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
// save this for last so that our listeners are registered.
|
||||
// ... this loads the testbed of good and bad requests.
|
||||
function testRan() {
|
||||
testsRun++;
|
||||
if (testsRun == totalTests) {
|
||||
window.examiner.remove();
|
||||
SimpleTest.finish();
|
||||
}
|
||||
}
|
||||
|
||||
SpecialPowers.pushPrefEnv(
|
||||
{'set':[["security.csp.speccompliant", true]]},
|
||||
function() {
|
||||
// save this for last so that our listeners are registered.
|
||||
// ... this loads the testbed of good and bad requests.
|
||||
document.getElementById('cspframe').src = 'file_bothCSPheaders.html';
|
||||
{'set':[["security.csp.speccompliant", true]]},
|
||||
function loadTestRequests() {
|
||||
var cspframe = document.getElementById('cspframe');
|
||||
cspframe.src = 'file_bothCSPheaders.html';
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
@ -89,9 +89,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=338583
|
||||
e.target.hits['fn_other_event_name']++;
|
||||
}
|
||||
|
||||
var domBranch;
|
||||
var oldPrefVal;
|
||||
|
||||
var gEventSourceObj1 = null, gEventSourceObj1_e, gEventSourceObj1_f;
|
||||
var gEventSourceObj2 = null;
|
||||
var gEventSourceObj3_a = null, gEventSourceObj3_b = null,
|
||||
@ -250,6 +247,12 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=338583
|
||||
}
|
||||
|
||||
function doTest3_b(test_id) {
|
||||
// currently no support yet for local files for b2g/Android mochitest, see bug 838726
|
||||
if (navigator.appVersion.indexOf("Android") != -1 || SpecialPowers.Services.appinfo.name == "B2G") {
|
||||
setTestHasFinished(test_id);
|
||||
return;
|
||||
}
|
||||
|
||||
var xhr = new XMLHttpRequest;
|
||||
xhr.open("GET", "/dynamic/getMyDirectory.sjs", false);
|
||||
xhr.send();
|
||||
@ -462,7 +465,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=338583
|
||||
function doTest5_c(test_id)
|
||||
{
|
||||
// credentials using the auth cache and cookies
|
||||
var xhr = SpecialPowers.createSystemXHR();
|
||||
var xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
|
||||
xhr.withCredentials = true;
|
||||
// also, test mixed mode UI
|
||||
xhr.open("GET", "https://example.com/tests/content/base/test/file_restrictedEventSource.sjs?test=user1_xhr", true, "user 1", "password 1");
|
||||
@ -491,7 +494,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=338583
|
||||
|
||||
function doTest5_d(test_id)
|
||||
{
|
||||
var xhr = SpecialPowers.createSystemXHR();
|
||||
var xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
|
||||
xhr.withCredentials = true;
|
||||
xhr.open("GET", "https://example.com/tests/content/base/test/file_restrictedEventSource.sjs?test=user2_xhr", true, "user 2", "password 2");
|
||||
xhr.send();
|
||||
@ -519,7 +522,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=338583
|
||||
function doTest5_e(test_id)
|
||||
{
|
||||
// credentials using the auth cache and cookies
|
||||
var xhr = SpecialPowers.createSystemXHR();
|
||||
var xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
|
||||
xhr.withCredentials = true;
|
||||
xhr.open("GET", "http://example.org/tests/content/base/test/file_restrictedEventSource.sjs?test=user1_xhr", true, "user 1", "password 1");
|
||||
xhr.send();
|
||||
@ -547,7 +550,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=338583
|
||||
|
||||
function doTest5_f(test_id)
|
||||
{
|
||||
var xhr = SpecialPowers.createSystemXHR();
|
||||
var xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
|
||||
xhr.withCredentials = true;
|
||||
xhr.open("GET", "http://example.org/tests/content/base/test/file_restrictedEventSource.sjs?test=user2_xhr", true, "user 2", "password 2");
|
||||
xhr.send();
|
||||
@ -614,7 +617,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=338583
|
||||
gEventSourceObj7.msg_received[1] == "delayed1" &&
|
||||
gEventSourceObj7.msg_received[2] == "delayed2", "Test 7 failed");
|
||||
|
||||
SpecialPowers.setBoolPref("dom.server-events.enabled", oldPrefVal);
|
||||
document.getElementById('waitSpan').innerHTML = '';
|
||||
setTestHasFinished(test_id);
|
||||
}, parseInt(8000*stress_factor));
|
||||
@ -623,13 +625,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=338583
|
||||
function doTest()
|
||||
{
|
||||
// Allow all cookies, then run the actual test
|
||||
SpecialPowers.pushPrefEnv({"set": [["network.cookie.cookieBehavior", 0]]}, doTestCallback);
|
||||
SpecialPowers.pushPrefEnv({"set": [["network.cookie.cookieBehavior", 0], ["dom.server-events.enabled", true]]}, function() { SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], doTestCallback);});
|
||||
}
|
||||
|
||||
|
||||
function doTestCallback()
|
||||
{
|
||||
oldPrefVal = SpecialPowers.getBoolPref("dom.server-events.enabled");
|
||||
SpecialPowers.setBoolPref("dom.server-events.enabled", true);
|
||||
|
||||
// we get a good stress_factor by testing 10 setTimeouts and some float
|
||||
// arithmetic taking my machine as stress_factor==1 (time=589)
|
||||
|
@ -21,12 +21,21 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=426308
|
||||
|
||||
const SJS_URL = "http://example.org:80/tests/content/base/test/bug426308-redirect.sjs";
|
||||
|
||||
var req = SpecialPowers.createSystemXHR();
|
||||
req.open("GET", SJS_URL + "?" + window.location.href, false);
|
||||
req.send(null);
|
||||
function startTest() {
|
||||
var req = new XMLHttpRequest({mozAnon: false, mozSystem: true});
|
||||
req.open("GET", SJS_URL + "?" + window.location.href, false);
|
||||
req.send(null);
|
||||
|
||||
is(req.status, 200, "Redirect did not happen");
|
||||
is(req.status, 200, "Redirect did not happen");
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
addLoadEvent(function() {
|
||||
SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], startTest);
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
@ -51,7 +51,7 @@ function createDoc() {
|
||||
function xhrDoc(idx) {
|
||||
return function() {
|
||||
// Defy same-origin restrictions!
|
||||
var xhr = SpecialPowers.createSystemXHR();
|
||||
var xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
|
||||
xhr.open("GET", docSources[idx], false);
|
||||
xhr.send();
|
||||
return xhr.responseXML;
|
||||
@ -87,6 +87,10 @@ function doTest(idx) {
|
||||
}
|
||||
|
||||
addLoadEvent(function() {
|
||||
SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], startTest);
|
||||
});
|
||||
|
||||
function startTest() {
|
||||
// sanity check
|
||||
isnot("", null, "Shouldn't be equal!");
|
||||
|
||||
@ -104,7 +108,7 @@ addLoadEvent(function() {
|
||||
xhr.abort();
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
@ -19,7 +19,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=804395
|
||||
<script type="application/javascript">
|
||||
|
||||
function test200() {
|
||||
var xhr = SpecialPowers.createSystemXHR();
|
||||
var xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
|
||||
xhr.open('GET', 'jar:http://example.org/tests/content/base/test/file_bug804395.jar!/foo.bar', true);
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState == 4) {
|
||||
@ -31,7 +31,7 @@ function test200() {
|
||||
}
|
||||
|
||||
function test404() {
|
||||
var xhr = SpecialPowers.createSystemXHR();
|
||||
var xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
|
||||
xhr.open('GET', 'jar:http://example.org/tests/content/base/test/file_bug804395.jar!/foo.do_not_exist', true);
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState == 4) {
|
||||
@ -43,7 +43,7 @@ function test404() {
|
||||
}
|
||||
|
||||
function test0() {
|
||||
var xhr = SpecialPowers.createSystemXHR();
|
||||
var xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
|
||||
xhr.open('GET', 'jar:http://example.org/tests/content/base/test/file_bug804395.jar!/foo.bar', true);
|
||||
ok(xhr.status == 0, "Not Sent request must have status 0");
|
||||
runTests();
|
||||
@ -61,9 +61,11 @@ function runTests() {
|
||||
}
|
||||
|
||||
/** Test for Bug 804395 **/
|
||||
runTests();
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
addLoadEvent(function() {
|
||||
SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], runTests);
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
@ -49,37 +49,47 @@ var headers = [
|
||||
];
|
||||
var i, request;
|
||||
|
||||
// Try setting headers in unprivileged context
|
||||
request = new XMLHttpRequest();
|
||||
request.open("GET", window.location.href);
|
||||
for (i = 0; i < headers.length; i++)
|
||||
request.setRequestHeader(headers[i], "test" + i);
|
||||
function startTest() {
|
||||
// Try setting headers in unprivileged context
|
||||
request = new XMLHttpRequest();
|
||||
request.open("GET", window.location.href);
|
||||
for (i = 0; i < headers.length; i++)
|
||||
request.setRequestHeader(headers[i], "test" + i);
|
||||
|
||||
// Read out headers
|
||||
var channel = SpecialPowers.wrap(request).channel.QueryInterface(SpecialPowers.Ci.nsIHttpChannel);
|
||||
for (i = 0; i < headers.length; i++) {
|
||||
// Retrieving Content-Length will throw an exception
|
||||
var value = null;
|
||||
try {
|
||||
value = channel.getRequestHeader(headers[i]);
|
||||
// Read out headers
|
||||
var channel = SpecialPowers.wrap(request).channel.QueryInterface(SpecialPowers.Ci.nsIHttpChannel);
|
||||
for (i = 0; i < headers.length; i++) {
|
||||
// Retrieving Content-Length will throw an exception
|
||||
var value = null;
|
||||
try {
|
||||
value = channel.getRequestHeader(headers[i]);
|
||||
}
|
||||
catch(e) {}
|
||||
|
||||
isnot(value, "test" + i, "Setting " + headers[i] + " header in unprivileged context");
|
||||
}
|
||||
catch(e) {}
|
||||
|
||||
isnot(value, "test" + i, "Setting " + headers[i] + " header in unprivileged context");
|
||||
// Try setting headers in privileged context
|
||||
request = new XMLHttpRequest({mozAnon: false, mozSystem: true});
|
||||
request.open("GET", window.location.href);
|
||||
for (i = 0; i < headers.length; i++)
|
||||
request.setRequestHeader(headers[i], "test" + i);
|
||||
|
||||
// Read out headers
|
||||
var channel = SpecialPowers.wrap(request).channel.QueryInterface(SpecialPowers.Ci.nsIHttpChannel);
|
||||
for (i = 0; i < headers.length; i++) {
|
||||
var value = channel.getRequestHeader(headers[i]);
|
||||
is(value, "test" + i, "Setting " + headers[i] + " header in privileged context");
|
||||
}
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
// Try setting headers in privileged context
|
||||
request = SpecialPowers.createSystemXHR();
|
||||
request.open("GET", window.location.href);
|
||||
for (i = 0; i < headers.length; i++)
|
||||
request.setRequestHeader(headers[i], "test" + i);
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
// Read out headers
|
||||
var channel = request.channel.QueryInterface(SpecialPowers.Ci.nsIHttpChannel);
|
||||
for (i = 0; i < headers.length; i++) {
|
||||
var value = channel.getRequestHeader(headers[i]);
|
||||
is(value, "test" + i, "Setting " + headers[i] + " header in privileged context");
|
||||
}
|
||||
addLoadEvent(function() {
|
||||
SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], startTest);
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
@ -24,6 +24,10 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=843725
|
||||
* various key events while it is in various states.
|
||||
**/
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
// Turn off Spatial Navigation because it hijacks arrow keydown events:
|
||||
SpecialPowers.setBoolPref("snav.enabled", false);
|
||||
|
||||
SimpleTest.waitForFocus(function() {
|
||||
test();
|
||||
SimpleTest.finish();
|
||||
|
@ -26,6 +26,10 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=853525
|
||||
* ugly rounding errors.
|
||||
**/
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
// Turn off spatial navigation because it hijacks arrow keydown events:
|
||||
SpecialPowers.setBoolPref("snav.enabled", false);
|
||||
|
||||
SimpleTest.waitForFocus(function() {
|
||||
test();
|
||||
SimpleTest.finish();
|
||||
|
@ -22,6 +22,9 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633058
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
// Turn off Spatial Navigation so that the 'keypress' event fires.
|
||||
SpecialPowers.setBoolPref('snav.enabled', false);
|
||||
|
||||
SimpleTest.waitForFocus(function() {
|
||||
var nbExpectedKeyPress = 8;
|
||||
var inputGotKeyPress = 0;
|
||||
|
@ -19,6 +19,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=674558
|
||||
|
||||
/** Test for Bug 674558 **/
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
// Turn off spatial navigation because it hijacks VK_RIGHT and VK_LEFT keydown
|
||||
// events.
|
||||
SpecialPowers.setBoolPref("snav.enabled", false);
|
||||
|
||||
SimpleTest.waitForFocus(function() {
|
||||
function textAreaCtor() {
|
||||
return document.createElement("textarea");
|
||||
|
@ -694,7 +694,9 @@ nsresult nsGeolocationService::Init()
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_WIDGET_COCOA
|
||||
mProvider = new CoreLocationLocationProvider();
|
||||
if (Preferences::GetBool("geo.provider.use_corelocation", false)) {
|
||||
mProvider = new CoreLocationLocationProvider();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Override platform-specific providers with the default (network)
|
||||
|
@ -15,6 +15,10 @@
|
||||
#include "sys/stat.h"
|
||||
#endif // defined(XP_UNIX)
|
||||
|
||||
#if defined(XP_LINUX)
|
||||
#include <linux/fadvise.h>
|
||||
#endif // defined(XP_LINUX)
|
||||
|
||||
#if defined(XP_MACOSX)
|
||||
#include "copyfile.h"
|
||||
#endif // defined(XP_MACOSX)
|
||||
@ -377,6 +381,10 @@ static const dom::ConstantSpec gLibcProperties[] =
|
||||
INT_CONSTANT(AT_SYMLINK_NOFOLLOW),
|
||||
#endif //defined(AT_SYMLINK_NOFOLLOW)
|
||||
|
||||
#if defined(POSIX_FADV_SEQUENTIAL)
|
||||
INT_CONSTANT(POSIX_FADV_SEQUENTIAL),
|
||||
#endif //defined(POSIX_FADV_SEQUENTIAL)
|
||||
|
||||
// access
|
||||
#if defined(F_OK)
|
||||
INT_CONSTANT(F_OK),
|
||||
|
@ -41,6 +41,10 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=265203
|
||||
|
||||
/** Test for Bug 265203 **/
|
||||
|
||||
// Turn off spatial navigation because it hijacks VK_RIGHT and VK_LEFT keydown
|
||||
// events
|
||||
SpecialPowers.setBoolPref("snav.enabled", false);
|
||||
|
||||
var gTestStarted = false;
|
||||
var expectedResult = [ null, 0, null ];
|
||||
var nextTest;
|
||||
|
@ -23,7 +23,13 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=795785
|
||||
|
||||
<script class="testbody" type="application/javascript">
|
||||
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
// Turn off spatial navigation because it hijacks arrow key events and VK_RETURN
|
||||
// events.
|
||||
SpecialPowers.setBoolPref("snav.enabled", false);
|
||||
|
||||
SimpleTest.waitForFocus(runTests);
|
||||
|
||||
var textarea = document.getElementById("textarea");
|
||||
|
@ -4647,11 +4647,12 @@ nsCSSFrameConstructor::ConstructFrameWithAnonymousChild(
|
||||
// Create the outer frame:
|
||||
nsIFrame* newFrame = aConstructor(mPresShell, styleContext);
|
||||
|
||||
nsIFrame* geometricParent =
|
||||
aState.GetGeometricParent(styleContext->StyleDisplay(),
|
||||
aParentFrame);
|
||||
|
||||
InitAndRestoreFrame(aState, content, geometricParent, newFrame);
|
||||
InitAndRestoreFrame(aState, content,
|
||||
aCandidateRootFrame ?
|
||||
aState.GetGeometricParent(styleContext->StyleDisplay(),
|
||||
aParentFrame) :
|
||||
aParentFrame,
|
||||
newFrame);
|
||||
|
||||
// Create the pseudo SC for the anonymous wrapper child as a child of the SC:
|
||||
nsRefPtr<nsStyleContext> scForAnon;
|
||||
@ -4666,7 +4667,8 @@ nsCSSFrameConstructor::ConstructFrameWithAnonymousChild(
|
||||
// Put the newly created frames into the right child list
|
||||
SetInitialSingleChild(newFrame, innerFrame);
|
||||
|
||||
aState.AddChild(newFrame, aFrameItems, content, styleContext, aParentFrame);
|
||||
aState.AddChild(newFrame, aFrameItems, content, styleContext, aParentFrame,
|
||||
aCandidateRootFrame, aCandidateRootFrame);
|
||||
|
||||
if (!mRootElementFrame && aCandidateRootFrame) {
|
||||
// The frame we're constructing will be the root element frame.
|
||||
|
@ -28,6 +28,10 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=345267
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
/** Test for Bug 345267 **/
|
||||
|
||||
// Turn off Spatial Navigation to stop if from hijacking "left" keypress event.
|
||||
SpecialPowers.setBoolPref('snav.enabled', false);
|
||||
|
||||
is($("d1").value, "abcde",
|
||||
"Displayed initial value should not be truncated by maxlength");
|
||||
is($("u1").value, "abcdef",
|
||||
|
@ -74,6 +74,9 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=365410
|
||||
|
||||
/** Test for Bug 365410 **/
|
||||
|
||||
// Turn off spatial nav so that it does not hijack the up and down events.
|
||||
SpecialPowers.setBoolPref("snav.enabled", false);
|
||||
|
||||
function pageUpDownTest(id,index) {
|
||||
var elm = document.getElementById(id);
|
||||
elm.focus();
|
||||
|
@ -37,6 +37,9 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=563642
|
||||
|
||||
/** Test for Bug 563642 **/
|
||||
|
||||
// Turn off Spatial Navigation because it hijacks down and up key events.
|
||||
SpecialPowers.setBoolPref("snav.enabled", false);
|
||||
|
||||
function pageUpDownTest(id,index) {
|
||||
var elm = document.getElementById(id);
|
||||
elm.focus();
|
||||
|
@ -11,6 +11,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=291082
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<script type="application/javascript">
|
||||
/** Test for Bug 291082 **/
|
||||
|
||||
|
||||
// Turn off Spatial Navigation because it hijacks arrow keydown events.
|
||||
SpecialPowers.setBoolPref("snav.enabled", false);
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function preventDefault(event) {
|
||||
|
@ -18,7 +18,7 @@ load 307979-1.html
|
||||
load 309322-1.html
|
||||
load 309322-2.html
|
||||
load 309322-3.html
|
||||
load 309322-4.html
|
||||
skip load 309322-4.html # Bug 859424
|
||||
load 310556-1.xhtml
|
||||
load 321224.xul
|
||||
load 322780-1.xul
|
||||
|
5
layout/svg/crashtests/919371-1.xhtml
Normal file
5
layout/svg/crashtests/919371-1.xhtml
Normal file
@ -0,0 +1,5 @@
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<marker style="position: absolute;" />
|
||||
</svg>
|
||||
</html>
|
@ -176,3 +176,4 @@ load 895311-1.svg
|
||||
load 897342-1.svg
|
||||
load 898909-1.svg
|
||||
load 898951-1.svg
|
||||
load 919371-1.xhtml
|
||||
|
@ -119,7 +119,7 @@ struct cubeb_stream
|
||||
uint32_t buffer_frame_count;
|
||||
/* Resampler instance. If this is !NULL, resampling should happen. */
|
||||
SpeexResamplerState * resampler;
|
||||
/* Buffer to resample from, into the upmix buffer or the final buffer. */
|
||||
/* Buffer to resample from, into the mix buffer or the final buffer. */
|
||||
float * resampling_src_buffer;
|
||||
/* Pointer to the function used to refill the buffer, depending
|
||||
* on the respective samplerate of the stream and the mix. */
|
||||
@ -128,9 +128,10 @@ struct cubeb_stream
|
||||
uint32_t leftover_frame_count;
|
||||
uint32_t leftover_frame_size;
|
||||
float * leftover_frames_buffer;
|
||||
/* upmix buffer of size |buffer_frame_count * bytes_per_frame / 2|. */
|
||||
float * upmix_buffer;
|
||||
/* Number of bytes per frame. Prefer to use frames_to_bytes_before_upmix. */
|
||||
/* Buffer used to downmix or upmix to the number of channels the mixer has.
|
||||
* its size is |buffer_frame_count * bytes_per_frame * mixer_channels|. */
|
||||
float * mix_buffer;
|
||||
/* Number of bytes per frame. Prefer to use frames_to_bytes_before_mix. */
|
||||
uint8_t bytes_per_frame;
|
||||
/* True if the stream is draining. */
|
||||
bool draining;
|
||||
@ -139,7 +140,12 @@ struct cubeb_stream
|
||||
namespace {
|
||||
bool should_upmix(cubeb_stream * stream)
|
||||
{
|
||||
return stream->upmix_buffer;
|
||||
return stream->mix_params.channels > stream->stream_params.channels;
|
||||
}
|
||||
|
||||
bool should_downmix(cubeb_stream * stream)
|
||||
{
|
||||
return stream->mix_params.channels < stream->stream_params.channels;
|
||||
}
|
||||
|
||||
/* Upmix function, copies a mono channel in two interleaved
|
||||
@ -179,10 +185,23 @@ upmix(T * in, long inframes, T * out, int32_t in_channels, int32_t out_channels)
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void
|
||||
downmix_to_stereo(T * in, long inframes, T * out, int32_t in_channels)
|
||||
{
|
||||
/* We could use a downmix matrix here, applying mixing weight based on the
|
||||
* channel, but directsound and winmm simply drop the channels that cannot be
|
||||
* rendered by the hardware, so we do the same for consistency. */
|
||||
for (int32_t i = 0; i < inframes; i++) {
|
||||
out[i * 2] = in[i * in_channels];
|
||||
out[i * 2 + 1] = in[i * in_channels + 1];
|
||||
}
|
||||
}
|
||||
|
||||
/* This returns the size of a frame in the stream,
|
||||
* before the eventual upmix occurs. */
|
||||
static size_t
|
||||
frame_to_bytes_before_upmix(cubeb_stream * stm, size_t frames)
|
||||
frame_to_bytes_before_mix(cubeb_stream * stm, size_t frames)
|
||||
{
|
||||
size_t stream_frame_size = stm->stream_params.channels * sizeof(float);
|
||||
return stream_frame_size * frames;
|
||||
@ -202,7 +221,7 @@ refill_with_resampling(cubeb_stream * stm, float * data, long frames_needed)
|
||||
long frame_requested = before_resampling - stm->leftover_frame_count;
|
||||
|
||||
size_t leftover_bytes =
|
||||
frame_to_bytes_before_upmix(stm, stm->leftover_frame_count);
|
||||
frame_to_bytes_before_mix(stm, stm->leftover_frame_count);
|
||||
|
||||
/* Copy the previous leftover frames to the front of the buffer. */
|
||||
memcpy(stm->resampling_src_buffer, stm->leftover_frames_buffer, leftover_bytes);
|
||||
@ -218,11 +237,11 @@ refill_with_resampling(cubeb_stream * stm, float * data, long frames_needed)
|
||||
uint32_t in_frames = before_resampling;
|
||||
uint32_t out_frames = frames_needed;
|
||||
|
||||
/* if we need to upmix after resampling, resample into
|
||||
* the upmix buffer to avoid a copy */
|
||||
/* If we need to upmix after resampling, resample into the mix buffer to
|
||||
* avoid a copy. */
|
||||
float * resample_dest;
|
||||
if (should_upmix(stm)) {
|
||||
resample_dest = stm->upmix_buffer;
|
||||
if (should_upmix(stm) || should_downmix(stm)) {
|
||||
resample_dest = stm->mix_buffer;
|
||||
} else {
|
||||
resample_dest = data;
|
||||
}
|
||||
@ -236,11 +255,11 @@ refill_with_resampling(cubeb_stream * stm, float * data, long frames_needed)
|
||||
/* Copy the leftover frames to buffer for the next time. */
|
||||
stm->leftover_frame_count = before_resampling - in_frames;
|
||||
size_t unresampled_bytes =
|
||||
frame_to_bytes_before_upmix(stm, stm->leftover_frame_count);
|
||||
frame_to_bytes_before_mix(stm, stm->leftover_frame_count);
|
||||
|
||||
uint8_t * leftover_frames_start =
|
||||
reinterpret_cast<uint8_t *>(stm->resampling_src_buffer);
|
||||
leftover_frames_start += frame_to_bytes_before_upmix(stm, in_frames);
|
||||
leftover_frames_start += frame_to_bytes_before_mix(stm, in_frames);
|
||||
|
||||
assert(stm->leftover_frame_count <= stm->leftover_frame_size);
|
||||
memcpy(stm->leftover_frames_buffer, leftover_frames_start, unresampled_bytes);
|
||||
@ -252,17 +271,20 @@ refill_with_resampling(cubeb_stream * stm, float * data, long frames_needed)
|
||||
if (should_upmix(stm)) {
|
||||
upmix(resample_dest, out_frames, data,
|
||||
stm->stream_params.channels, stm->mix_params.channels);
|
||||
} else if (should_downmix(stm)) {
|
||||
downmix_to_stereo(resample_dest, out_frames, data,
|
||||
stm->stream_params.channels);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
refill(cubeb_stream * stm, float * data, long frames_needed)
|
||||
{
|
||||
/* If we need to upmix after resampling, get the data into
|
||||
* the upmix buffer to avoid a copy. */
|
||||
/* If we need to upmix/downmix, get the data into the mix buffer to avoid a
|
||||
* copy, then do the processing process. */
|
||||
float * dest;
|
||||
if (should_upmix(stm)) {
|
||||
dest = stm->upmix_buffer;
|
||||
if (should_upmix(stm) || should_downmix(stm)) {
|
||||
dest = stm->mix_buffer;
|
||||
} else {
|
||||
dest = data;
|
||||
}
|
||||
@ -277,6 +299,8 @@ refill(cubeb_stream * stm, float * data, long frames_needed)
|
||||
if (should_upmix(stm)) {
|
||||
upmix(dest, got, data,
|
||||
stm->stream_params.channels, stm->mix_params.channels);
|
||||
} else {
|
||||
downmix_to_stereo(dest, got, data, stm->stream_params.channels);
|
||||
}
|
||||
}
|
||||
|
||||
@ -514,13 +538,14 @@ void wasapi_stream_destroy(cubeb_stream * stm);
|
||||
static void
|
||||
handle_channel_layout(cubeb_stream * stm, WAVEFORMATEX ** mix_format, const cubeb_stream_params * stream_params)
|
||||
{
|
||||
/* Common case: the hardware supports stereo, and the stream is mono or
|
||||
* stereo. Easy. */
|
||||
if ((*mix_format)->nChannels == 2 &&
|
||||
stream_params->channels <= 2) {
|
||||
/* Common case: the hardware is stereo. Up-mixing and down-mixing will be
|
||||
* handled in the callback. */
|
||||
if ((*mix_format)->nChannels == 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Otherwise, the hardware supports more than two channels. */
|
||||
|
||||
/* The docs say that GetMixFormat is always of type WAVEFORMATEXTENSIBLE [1],
|
||||
* so the reinterpret_cast below should be safe. In practice, this is not
|
||||
* true, and we just want to bail out and let the rest of the code find a good
|
||||
@ -559,7 +584,7 @@ handle_channel_layout(cubeb_stream * stm, WAVEFORMATEX ** mix_format, const cub
|
||||
|
||||
if (hr == S_FALSE) {
|
||||
/* Not supported, but WASAPI gives us a suggestion. Use it, and handle the
|
||||
* eventual upmix ourselve */
|
||||
* eventual upmix/downmix ourselve */
|
||||
LOG("Using WASAPI suggested format: channels: %d", closest->nChannels);
|
||||
CoTaskMemFree(*mix_format);
|
||||
*mix_format = closest;
|
||||
@ -596,11 +621,6 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
|
||||
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
/* we don't support more that two channels for now. */
|
||||
if (stream_params.channels > 2) {
|
||||
return CUBEB_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
cubeb_stream * stm = (cubeb_stream *)calloc(1, sizeof(cubeb_stream));
|
||||
|
||||
assert(stm);
|
||||
@ -680,7 +700,7 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
|
||||
* that is, the samples not consumed by the resampler that we will end up
|
||||
* using next time the render callback is called. */
|
||||
stm->leftover_frame_size = static_cast<uint32_t>(ceilf(1 / resampling_rate * 2) + 1);
|
||||
stm->leftover_frames_buffer = (float *)malloc(frame_to_bytes_before_upmix(stm, stm->leftover_frame_size));
|
||||
stm->leftover_frames_buffer = (float *)malloc(frame_to_bytes_before_mix(stm, stm->leftover_frame_size));
|
||||
|
||||
stm->refill_function = &refill_with_resampling;
|
||||
} else {
|
||||
@ -712,8 +732,8 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
|
||||
|
||||
assert(stm->mix_params.channels >= 2);
|
||||
|
||||
if (stm->mix_params.channels != stm->stream_params.channels) {
|
||||
stm->upmix_buffer = (float *) malloc(frame_to_bytes_before_upmix(stm, stm->buffer_frame_count));
|
||||
if (should_upmix(stm) || should_downmix(stm)) {
|
||||
stm->mix_buffer = (float *) malloc(frame_to_bytes_before_mix(stm, stm->buffer_frame_count));
|
||||
}
|
||||
|
||||
/* If we are going to resample, we will end up needing a buffer
|
||||
@ -722,7 +742,7 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
|
||||
* factor and the channel layout into account. */
|
||||
if (stm->resampler) {
|
||||
size_t frames_needed = static_cast<size_t>(frame_count_at_rate(stm->buffer_frame_count, resampling_rate));
|
||||
stm->resampling_src_buffer = (float *)malloc(frame_to_bytes_before_upmix(stm, frames_needed));
|
||||
stm->resampling_src_buffer = (float *)malloc(frame_to_bytes_before_mix(stm, frames_needed));
|
||||
}
|
||||
|
||||
hr = stm->client->SetEventHandle(stm->refill_event);
|
||||
@ -783,7 +803,7 @@ void wasapi_stream_destroy(cubeb_stream * stm)
|
||||
|
||||
free(stm->leftover_frames_buffer);
|
||||
free(stm->resampling_src_buffer);
|
||||
free(stm->upmix_buffer);
|
||||
free(stm->mix_buffer);
|
||||
free(stm);
|
||||
CoUninitialize();
|
||||
}
|
||||
|
@ -57,12 +57,28 @@ struct VideoCodecConfig
|
||||
int mType;
|
||||
std::string mName;
|
||||
uint32_t mRtcpFbTypes;
|
||||
unsigned int mMaxFrameSize;
|
||||
unsigned int mMaxFrameRate;
|
||||
|
||||
VideoCodecConfig(int type,
|
||||
std::string name,
|
||||
int rtcpFbTypes): mType(type),
|
||||
mName(name),
|
||||
mRtcpFbTypes(rtcpFbTypes)
|
||||
mRtcpFbTypes(rtcpFbTypes),
|
||||
mMaxFrameSize(0),
|
||||
mMaxFrameRate(0)
|
||||
{
|
||||
}
|
||||
|
||||
VideoCodecConfig(int type,
|
||||
std::string name,
|
||||
int rtcpFbTypes,
|
||||
unsigned int max_fs,
|
||||
unsigned int max_fr): mType(type),
|
||||
mName(name),
|
||||
mRtcpFbTypes(rtcpFbTypes),
|
||||
mMaxFrameSize(max_fs),
|
||||
mMaxFrameRate(max_fr)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -220,6 +220,18 @@ public:
|
||||
const std::vector<VideoCodecConfig* >& recvCodecConfigList) = 0;
|
||||
|
||||
|
||||
/**
|
||||
* These methods allow unit tests to double-check that the
|
||||
* max-fs and max-fr related settings are as expected.
|
||||
*/
|
||||
virtual unsigned short SendingWidth() = 0;
|
||||
|
||||
virtual unsigned short SendingHeight() = 0;
|
||||
|
||||
virtual unsigned int SendingMaxFs() = 0;
|
||||
|
||||
virtual unsigned int SendingMaxFr() = 0;
|
||||
|
||||
/**
|
||||
* These methods allow unit tests to double-check that the
|
||||
* rtcp-fb settings are as expected.
|
||||
|
@ -16,6 +16,9 @@
|
||||
#include "AndroidJNIWrapper.h"
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <math.h>
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
static const char* logTag ="WebrtcVideoSessionConduit";
|
||||
@ -616,6 +619,71 @@ WebrtcVideoConduit::SelectSendResolution(unsigned short width,
|
||||
{
|
||||
// XXX This will do bandwidth-resolution adaptation as well - bug 877954
|
||||
|
||||
// Limit resolution to max-fs while keeping same aspect ratio as the
|
||||
// incoming image.
|
||||
if (mCurSendCodecConfig && mCurSendCodecConfig->mMaxFrameSize)
|
||||
{
|
||||
unsigned int cur_fs, max_width, max_height, mb_width, mb_height, mb_max;
|
||||
|
||||
mb_width = (width + 15) >> 4;
|
||||
mb_height = (height + 15) >> 4;
|
||||
|
||||
cur_fs = mb_width * mb_height;
|
||||
|
||||
// Limit resolution to max_fs, but don't scale up.
|
||||
if (cur_fs > mCurSendCodecConfig->mMaxFrameSize)
|
||||
{
|
||||
double scale_ratio;
|
||||
|
||||
scale_ratio = sqrt((double) mCurSendCodecConfig->mMaxFrameSize /
|
||||
(double) cur_fs);
|
||||
|
||||
mb_width = mb_width * scale_ratio;
|
||||
mb_height = mb_height * scale_ratio;
|
||||
|
||||
// Adjust mb_width and mb_height if they were truncated to zero.
|
||||
if (mb_width == 0) {
|
||||
mb_width = 1;
|
||||
mb_height = std::min(mb_height, mCurSendCodecConfig->mMaxFrameSize);
|
||||
}
|
||||
if (mb_height == 0) {
|
||||
mb_height = 1;
|
||||
mb_width = std::min(mb_width, mCurSendCodecConfig->mMaxFrameSize);
|
||||
}
|
||||
}
|
||||
|
||||
// Limit width/height seperately to limit effect of extreme aspect ratios.
|
||||
mb_max = (unsigned) sqrt(8 * (double) mCurSendCodecConfig->mMaxFrameSize);
|
||||
|
||||
max_width = 16 * std::min(mb_width, mb_max);
|
||||
max_height = 16 * std::min(mb_height, mb_max);
|
||||
|
||||
if (width * max_height > max_width * height)
|
||||
{
|
||||
if (width > max_width)
|
||||
{
|
||||
// Due to the value is truncated to integer here and forced to even
|
||||
// value later, adding 1 to improve accuracy.
|
||||
height = max_width * height / width + 1;
|
||||
width = max_width;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (height > max_height)
|
||||
{
|
||||
// Due to the value is truncated to integer here and forced to even
|
||||
// value later, adding 1 to improve accuracy.
|
||||
width = max_height * width / height + 1;
|
||||
height = max_height;
|
||||
}
|
||||
}
|
||||
|
||||
// Favor even multiples of pixels for width and height.
|
||||
width = std::max(width & ~1, 2);
|
||||
height = std::max(height & ~1, 2);
|
||||
}
|
||||
|
||||
// Adapt to getUserMedia resolution changes
|
||||
// check if we need to reconfigure the sending resolution
|
||||
if (mSendingWidth != width || mSendingHeight != height)
|
||||
@ -850,6 +918,10 @@ WebrtcVideoConduit::CodecConfigToWebRTCCodec(const VideoCodecConfig* codecInfo,
|
||||
{
|
||||
cinst.plType = codecInfo->mType;
|
||||
// leave width/height alone; they'll be overridden on the first frame
|
||||
if (codecInfo->mMaxFrameRate > 0)
|
||||
{
|
||||
cinst.maxFramerate = codecInfo->mMaxFrameRate;
|
||||
}
|
||||
cinst.minBitrate = 200;
|
||||
cinst.startBitrate = 300;
|
||||
cinst.maxBitrate = 2000;
|
||||
@ -892,8 +964,10 @@ WebrtcVideoConduit::CheckCodecsForMatch(const VideoCodecConfig* curCodecConfig,
|
||||
return false;
|
||||
}
|
||||
|
||||
if(curCodecConfig->mType == codecInfo->mType &&
|
||||
curCodecConfig->mName.compare(codecInfo->mName) == 0)
|
||||
if(curCodecConfig->mType == codecInfo->mType &&
|
||||
curCodecConfig->mName.compare(codecInfo->mName) == 0 &&
|
||||
curCodecConfig->mMaxFrameSize == codecInfo->mMaxFrameSize &&
|
||||
curCodecConfig->mMaxFrameRate == codecInfo->mMaxFrameRate)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@ -947,6 +1021,8 @@ WebrtcVideoConduit::DumpCodecDB() const
|
||||
{
|
||||
CSFLogDebug(logTag,"Payload Name: %s", mRecvCodecList[i]->mName.c_str());
|
||||
CSFLogDebug(logTag,"Payload Type: %d", mRecvCodecList[i]->mType);
|
||||
CSFLogDebug(logTag,"Payload Max Frame Size: %d", mRecvCodecList[i]->mMaxFrameSize);
|
||||
CSFLogDebug(logTag,"Payload Max Frame Rate: %d", mRecvCodecList[i]->mMaxFrameRate);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,6 +152,27 @@ public:
|
||||
|
||||
virtual int DeliverFrame(unsigned char*,int, uint32_t , int64_t);
|
||||
|
||||
unsigned short SendingWidth() {
|
||||
return mSendingWidth;
|
||||
}
|
||||
|
||||
unsigned short SendingHeight() {
|
||||
return mSendingHeight;
|
||||
}
|
||||
|
||||
unsigned int SendingMaxFs() {
|
||||
if(mCurSendCodecConfig) {
|
||||
return mCurSendCodecConfig->mMaxFrameSize;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned int SendingMaxFr() {
|
||||
if(mCurSendCodecConfig) {
|
||||
return mCurSendCodecConfig->mMaxFrameRate;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
WebrtcVideoConduit():
|
||||
mVideoEngine(nullptr),
|
||||
|
@ -24,6 +24,10 @@
|
||||
#include "cpr_stdlib.h"
|
||||
#include "cpr_string.h"
|
||||
#include "mozilla/SyncRunnable.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "nsIPrefService.h"
|
||||
#include "nsIPrefBranch.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
@ -70,6 +74,7 @@ int VcmSIPCCBinding::gAudioCodecMask = 0;
|
||||
int VcmSIPCCBinding::gVideoCodecMask = 0;
|
||||
nsIThread *VcmSIPCCBinding::gMainThread = NULL;
|
||||
nsIEventTarget *VcmSIPCCBinding::gSTSThread = NULL;
|
||||
nsCOMPtr<nsIPrefBranch> VcmSIPCCBinding::gBranch = NULL;
|
||||
|
||||
static mozilla::RefPtr<TransportFlow> vcmCreateTransportFlow(
|
||||
sipcc::PeerConnectionImpl *pc,
|
||||
@ -102,6 +107,12 @@ VcmSIPCCBinding::VcmSIPCCBinding ()
|
||||
{
|
||||
delete gSelf;//delete is NULL safe, so I don't need to check if it's NULL
|
||||
gSelf = this;
|
||||
nsresult rv;
|
||||
|
||||
nsCOMPtr<nsIPrefService> prefs = do_GetService("@mozilla.org/preferences-service;1", &rv);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
gBranch = do_QueryInterface(prefs);
|
||||
}
|
||||
}
|
||||
|
||||
class VcmIceOpaque : public NrIceOpaque {
|
||||
@ -133,6 +144,8 @@ VcmSIPCCBinding::~VcmSIPCCBinding ()
|
||||
gSTSThread,
|
||||
WrapRunnable(this, &VcmSIPCCBinding::disconnect_all),
|
||||
true);
|
||||
|
||||
gBranch = NULL;
|
||||
}
|
||||
|
||||
void VcmSIPCCBinding::CandidateReady(NrIceMediaStream* stream,
|
||||
@ -235,6 +248,11 @@ void VcmSIPCCBinding::connectCandidateSignal(
|
||||
&VcmSIPCCBinding::CandidateReady);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPrefBranch> VcmSIPCCBinding::getPrefBranch()
|
||||
{
|
||||
return gBranch;
|
||||
}
|
||||
|
||||
/* static */
|
||||
AudioTermination * VcmSIPCCBinding::getAudioTermination()
|
||||
{
|
||||
@ -2249,7 +2267,9 @@ static int vcmTxStartICE_m(cc_mcapid_t mcap_id,
|
||||
config_raw = new mozilla::VideoCodecConfig(
|
||||
payload->remote_rtp_pt,
|
||||
ccsdpCodecName(payload->codec_type),
|
||||
payload->video.rtcp_fb_types);
|
||||
payload->video.rtcp_fb_types,
|
||||
payload->video.max_fs,
|
||||
payload->video.max_fr);
|
||||
|
||||
// Take possession of this pointer
|
||||
mozilla::ScopedDeletePtr<mozilla::VideoCodecConfig> config(config_raw);
|
||||
@ -3053,3 +3073,47 @@ int vcmDisableRtcpComponent(const char *peerconnection, int level) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
static short vcmGetVideoMaxFs_m(uint16_t codec,
|
||||
int32_t *max_fs) {
|
||||
nsCOMPtr<nsIPrefBranch> branch = VcmSIPCCBinding::getPrefBranch();
|
||||
if (branch && NS_SUCCEEDED(branch->GetIntPref("media.navigator.video.max_fs",
|
||||
max_fs))) {
|
||||
return 0;
|
||||
}
|
||||
return VCM_ERROR;
|
||||
}
|
||||
|
||||
short vcmGetVideoMaxFs(uint16_t codec,
|
||||
int32_t *max_fs) {
|
||||
short ret;
|
||||
|
||||
mozilla::SyncRunnable::DispatchToThread(VcmSIPCCBinding::getMainThread(),
|
||||
WrapRunnableNMRet(&vcmGetVideoMaxFs_m,
|
||||
codec,
|
||||
max_fs,
|
||||
&ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
static short vcmGetVideoMaxFr_m(uint16_t codec,
|
||||
int32_t *max_fr) {
|
||||
nsCOMPtr<nsIPrefBranch> branch = VcmSIPCCBinding::getPrefBranch();
|
||||
if (branch && NS_SUCCEEDED(branch->GetIntPref("media.navigator.video.max_fr",
|
||||
max_fr))) {
|
||||
return 0;
|
||||
}
|
||||
return VCM_ERROR;
|
||||
}
|
||||
|
||||
short vcmGetVideoMaxFr(uint16_t codec,
|
||||
int32_t *max_fr) {
|
||||
short ret;
|
||||
|
||||
mozilla::SyncRunnable::DispatchToThread(VcmSIPCCBinding::getMainThread(),
|
||||
WrapRunnableNMRet(&vcmGetVideoMaxFr_m,
|
||||
codec,
|
||||
max_fr,
|
||||
&ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@ extern "C"
|
||||
|
||||
class nsIThread;
|
||||
class nsIEventTarget;
|
||||
class nsIPrefBranch;
|
||||
|
||||
namespace mozilla {
|
||||
class NrIceMediaStream;
|
||||
@ -69,6 +70,8 @@ namespace CSF
|
||||
|
||||
static void connectCandidateSignal(mozilla::NrIceMediaStream* stream);
|
||||
|
||||
static nsCOMPtr<nsIPrefBranch> getPrefBranch();
|
||||
|
||||
private:
|
||||
void CandidateReady(mozilla::NrIceMediaStream* stream,
|
||||
const std::string& candidate);
|
||||
@ -80,6 +83,7 @@ namespace CSF
|
||||
static int gVideoCodecMask;
|
||||
static nsIThread *gMainThread;
|
||||
static nsIEventTarget *gSTSThread;
|
||||
static nsCOMPtr<nsIPrefBranch> gBranch;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -571,6 +571,28 @@ sip_config_local_supported_codecs_get (rtp_ptype aSupportedCodecs[],
|
||||
return count;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
config_get_video_max_fs(const rtp_ptype codec)
|
||||
{
|
||||
uint32_t max_fs;
|
||||
|
||||
if(vcmGetVideoMaxFs(codec, (int32_t *) &max_fs) == 0) {
|
||||
return max_fs;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
config_get_video_max_fr(const rtp_ptype codec)
|
||||
{
|
||||
uint32_t max_fr;
|
||||
|
||||
if(vcmGetVideoMaxFr(codec, (int32_t *) &max_fr) == 0) {
|
||||
return max_fr;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* sip_config_local_supported_codecs_get()
|
||||
*
|
||||
|
@ -292,5 +292,7 @@ int sip_minimum_config_check(void);
|
||||
void config_set_codec_table(int codec_mask);
|
||||
int sip_config_get_keepalive_expires();
|
||||
rtp_ptype sip_config_preferred_codec(void);
|
||||
uint32_t config_get_video_max_fs(const rtp_ptype codec);
|
||||
uint32_t config_get_video_max_fr(const rtp_ptype codec);
|
||||
|
||||
#endif /* PROT_CONFIGMGR_H_ */
|
||||
|
@ -1146,6 +1146,8 @@ gsmsdp_set_video_media_attributes (uint32_t media_type, void *cc_sdp_p, uint16_t
|
||||
{
|
||||
uint16_t a_inst;
|
||||
void *sdp_p = ((cc_sdp_t*)cc_sdp_p)->src_sdp;
|
||||
int max_fs = 0;
|
||||
int max_fr = 0;
|
||||
|
||||
switch (media_type) {
|
||||
case RTP_H263:
|
||||
@ -1182,6 +1184,31 @@ gsmsdp_set_video_media_attributes (uint32_t media_type, void *cc_sdp_p, uint16_t
|
||||
SIPSDP_ATTR_ENCNAME_VP8);
|
||||
(void) sdp_attr_set_rtpmap_clockrate(sdp_p, level, 0, a_inst,
|
||||
RTPMAP_VIDEO_CLOCKRATE);
|
||||
|
||||
max_fs = config_get_video_max_fs((rtp_ptype) media_type);
|
||||
max_fr = config_get_video_max_fr((rtp_ptype) media_type);
|
||||
|
||||
if (max_fs || max_fr) {
|
||||
if (sdp_add_new_attr(sdp_p, level, 0, SDP_ATTR_FMTP, &a_inst)
|
||||
!= SDP_SUCCESS) {
|
||||
GSM_ERR_MSG("Failed to add attribute");
|
||||
return;
|
||||
}
|
||||
|
||||
(void) sdp_attr_set_fmtp_payload_type(sdp_p, level, 0, a_inst,
|
||||
payload_number);
|
||||
|
||||
if (max_fs) {
|
||||
(void) sdp_attr_set_fmtp_max_fs(sdp_p, level, 0, a_inst,
|
||||
max_fs);
|
||||
}
|
||||
|
||||
if (max_fr) {
|
||||
(void) sdp_attr_set_fmtp_max_fr(sdp_p, level, 0, a_inst,
|
||||
max_fr);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
GSM_DEBUG("gsmsdp_set_video_media_attributes- populate attribs %d", payload_number );
|
||||
@ -3416,23 +3443,19 @@ gsmsdp_negotiate_codec (fsmdef_dcb_t *dcb_p, cc_sdp_t *sdp_p,
|
||||
|
||||
/* This should ultimately use RFC 6236 a=imageattr
|
||||
if present */
|
||||
switch (codec) {
|
||||
case RTP_VP8:
|
||||
payload_info->video.width = 640;
|
||||
payload_info->video.height = 480;
|
||||
break;
|
||||
case RTP_I420:
|
||||
payload_info->video.width = 176;
|
||||
payload_info->video.height = 144;
|
||||
break;
|
||||
default:
|
||||
GSM_DEBUG(DEB_L_C_F_PREFIX"codec=%d not setting "
|
||||
"codec parameters (not implemented)\n",
|
||||
DEB_L_C_F_PREFIX_ARGS(GSM, dcb_p->line,
|
||||
dcb_p->call_id, fname), codec);
|
||||
payload_info->video.width = -1;
|
||||
payload_info->video.height = -1;
|
||||
}
|
||||
|
||||
payload_info->video.width = 0;
|
||||
payload_info->video.height = 0;
|
||||
|
||||
/* Set maximum frame size */
|
||||
payload_info->video.max_fs = 0;
|
||||
sdp_attr_get_fmtp_max_fs(sdp_p->dest_sdp, level, 0, 1,
|
||||
&payload_info->video.max_fs);
|
||||
|
||||
/* Set maximum frame rate */
|
||||
payload_info->video.max_fr = 0;
|
||||
sdp_attr_get_fmtp_max_fr(sdp_p->dest_sdp, level, 0, 1,
|
||||
&payload_info->video.max_fr);
|
||||
} /* end video */
|
||||
|
||||
GSM_DEBUG(DEB_L_C_F_PREFIX"codec= %d",
|
||||
|
@ -405,6 +405,7 @@ typedef enum {
|
||||
SDP_USE_IN_BAND_FEC,
|
||||
SDP_MAX_CODED_AUDIO_BW,
|
||||
SDP_CBR,
|
||||
SDP_MAX_FR,
|
||||
SDP_MAX_FMTP_PARAM,
|
||||
SDP_FMTP_PARAM_UNKNOWN
|
||||
} sdp_fmtp_codec_param_e;
|
||||
@ -680,6 +681,7 @@ typedef struct sdp_fmtp {
|
||||
|
||||
u32 max_mbps;
|
||||
u32 max_fs;
|
||||
u32 max_fr;
|
||||
u32 max_cpb;
|
||||
u32 max_dpb;
|
||||
u32 max_br;
|
||||
@ -1518,6 +1520,12 @@ extern sdp_result_e sdp_attr_set_fmtp_max_fs (void *sdp_ptr,
|
||||
u16 inst_num,
|
||||
u32 max_fs);
|
||||
|
||||
extern sdp_result_e sdp_attr_set_fmtp_max_fr (void *sdp_ptr,
|
||||
u16 level,
|
||||
u8 cap_num,
|
||||
u16 inst_num,
|
||||
u32 max_fr);
|
||||
|
||||
extern sdp_result_e sdp_attr_set_fmtp_max_cpb (void *sdp_ptr,
|
||||
u16 level,
|
||||
u8 cap_num,
|
||||
@ -1694,6 +1702,8 @@ extern sdp_result_e sdp_attr_get_fmtp_max_mbps (void *sdp_ptr, u16 level,
|
||||
u8 cap_num, u16 inst_num, u32 *val);
|
||||
extern sdp_result_e sdp_attr_get_fmtp_max_fs (void *sdp_ptr, u16 level,
|
||||
u8 cap_num, u16 inst_num, u32 *val);
|
||||
extern sdp_result_e sdp_attr_get_fmtp_max_fr (void *sdp_ptr, u16 level,
|
||||
u8 cap_num, u16 inst_num, u32 *val);
|
||||
extern sdp_result_e sdp_attr_get_fmtp_max_cpb (void *sdp_ptr, u16 level,
|
||||
u8 cap_num, u16 inst_num, u32 *val);
|
||||
extern sdp_result_e sdp_attr_get_fmtp_max_dpb (void *sdp_ptr, u16 level,
|
||||
|
@ -1222,7 +1222,7 @@ sdp_result_e sdp_parse_attr_fmtp (sdp_t *sdp_p, sdp_attr_t *attr_p,
|
||||
if (result1 != SDP_SUCCESS) {
|
||||
fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
|
||||
if (result1 != SDP_SUCCESS) {
|
||||
sdp_attr_fmtp_no_value(sdp_p, "max_fs");
|
||||
sdp_attr_fmtp_no_value(sdp_p, "max-fs");
|
||||
SDP_FREE(temp_ptr);
|
||||
return SDP_INVALID_PARAMETER;
|
||||
}
|
||||
@ -1234,7 +1234,7 @@ sdp_result_e sdp_parse_attr_fmtp (sdp_t *sdp_p, sdp_attr_t *attr_p,
|
||||
strtoul_result = strtoul(tok, &strtoul_end, 10);
|
||||
|
||||
if (errno || tok == strtoul_end || strtoul_result == 0 || strtoul_result > UINT_MAX) {
|
||||
sdp_attr_fmtp_invalid_value(sdp_p, "max_fs", tok);
|
||||
sdp_attr_fmtp_invalid_value(sdp_p, "max-fs", tok);
|
||||
SDP_FREE(temp_ptr);
|
||||
return SDP_INVALID_PARAMETER;
|
||||
}
|
||||
@ -1692,7 +1692,32 @@ sdp_result_e sdp_parse_attr_fmtp (sdp_t *sdp_p, sdp_attr_t *attr_p,
|
||||
fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
|
||||
fmtp_p->cbr = (u16) strtoul_result;
|
||||
codec_info_found = TRUE;
|
||||
|
||||
} else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[49].name,
|
||||
sdp_fmtp_codec_param[49].strlen) == 0) {
|
||||
fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t",
|
||||
&result1);
|
||||
if (result1 != SDP_SUCCESS) {
|
||||
fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp),
|
||||
" \t", &result1);
|
||||
if (result1 != SDP_SUCCESS) {
|
||||
sdp_attr_fmtp_no_value(sdp_p, "max-fr");
|
||||
SDP_FREE(temp_ptr);
|
||||
return SDP_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
tok = tmp;
|
||||
tok++;
|
||||
errno = 0;
|
||||
strtoul_result = strtoul(tok, &strtoul_end, 10);
|
||||
if (errno || tok == strtoul_end || strtoul_result == 0 ||
|
||||
strtoul_result > UINT_MAX) {
|
||||
sdp_attr_fmtp_invalid_value(sdp_p, "max-fr", tok);
|
||||
SDP_FREE(temp_ptr);
|
||||
return SDP_INVALID_PARAMETER;
|
||||
}
|
||||
fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
|
||||
fmtp_p->max_fr = (u32) strtoul_result;
|
||||
codec_info_found = TRUE;
|
||||
} else if (fmtp_ptr != NULL && *fmtp_ptr == '\n') {
|
||||
temp=PL_strtok_r(tmp, ";", &strtok_state);
|
||||
if (temp) {
|
||||
@ -1999,6 +2024,8 @@ sdp_result_e sdp_build_attr_fmtp (sdp_t *sdp_p, sdp_attr_t *attr_p, flex_string
|
||||
|
||||
FMTP_BUILD_UNSIGNED(fmtp_p->max_fs > 0, "max-fs", fmtp_p->max_fs)
|
||||
|
||||
FMTP_BUILD_UNSIGNED(fmtp_p->max_fr > 0, "max-fr", fmtp_p->max_fr)
|
||||
|
||||
FMTP_BUILD_UNSIGNED(fmtp_p->max_cpb > 0, "max-cpb", fmtp_p->max_cpb)
|
||||
|
||||
FMTP_BUILD_UNSIGNED(fmtp_p->max_dpb > 0, "max-dpb", fmtp_p->max_dpb)
|
||||
|
@ -457,6 +457,7 @@ void sdp_copy_attr_fields (sdp_attr_t *src_attr_p, sdp_attr_t *dst_attr_p)
|
||||
|
||||
dst_attr_p->attr.fmtp.max_mbps = src_attr_p->attr.fmtp.max_mbps;
|
||||
dst_attr_p->attr.fmtp.max_fs = src_attr_p->attr.fmtp.max_fs;
|
||||
dst_attr_p->attr.fmtp.max_fr = src_attr_p->attr.fmtp.max_fr;
|
||||
dst_attr_p->attr.fmtp.max_cpb = src_attr_p->attr.fmtp.max_cpb;
|
||||
dst_attr_p->attr.fmtp.max_dpb = src_attr_p->attr.fmtp.max_dpb;
|
||||
dst_attr_p->attr.fmtp.max_br = src_attr_p->attr.fmtp.max_br;
|
||||
@ -862,6 +863,7 @@ sdp_result_e sdp_copy_attr (void *src_sdp_ptr, void *dst_sdp_ptr,
|
||||
|
||||
new_attr_p->attr.fmtp.max_mbps = src_attr_p->attr.fmtp.max_mbps;
|
||||
new_attr_p->attr.fmtp.max_fs = src_attr_p->attr.fmtp.max_fs;
|
||||
new_attr_p->attr.fmtp.max_fr = src_attr_p->attr.fmtp.max_fr;
|
||||
new_attr_p->attr.fmtp.max_cpb = src_attr_p->attr.fmtp.max_cpb;
|
||||
new_attr_p->attr.fmtp.max_dpb = src_attr_p->attr.fmtp.max_dpb;
|
||||
new_attr_p->attr.fmtp.max_br = src_attr_p->attr.fmtp.max_br;
|
||||
@ -6361,6 +6363,39 @@ sdp_result_e sdp_attr_set_fmtp_max_fs (void *sdp_ptr, u16 level,
|
||||
}
|
||||
}
|
||||
|
||||
sdp_result_e sdp_attr_set_fmtp_max_fr (void *sdp_ptr, u16 level,
|
||||
u8 cap_num, u16 inst_num,
|
||||
u32 max_fr)
|
||||
{
|
||||
sdp_t *sdp_p = (sdp_t *)sdp_ptr;
|
||||
sdp_attr_t *attr_p;
|
||||
sdp_fmtp_t *fmtp_p;
|
||||
|
||||
if (sdp_verify_sdp_ptr(sdp_p) == FALSE) {
|
||||
return (SDP_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
attr_p = sdp_find_attr(sdp_p, level, cap_num, SDP_ATTR_FMTP, inst_num);
|
||||
if (attr_p == NULL) {
|
||||
if (sdp_p->debug_flag[SDP_DEBUG_ERRORS]) {
|
||||
CSFLogError(logTag, "%s fmtp attribute, level %u instance %u "
|
||||
"not found.", sdp_p->debug_str, level, inst_num);
|
||||
}
|
||||
sdp_p->conf_p->num_invalid_param++;
|
||||
return (SDP_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
fmtp_p = &(attr_p->attr.fmtp);
|
||||
fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
|
||||
|
||||
if (max_fr > 0) {
|
||||
fmtp_p->max_fr = max_fr;
|
||||
return (SDP_SUCCESS);
|
||||
} else {
|
||||
return (SDP_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
sdp_result_e sdp_attr_set_fmtp_max_br (void *sdp_ptr, u16 level,
|
||||
u8 cap_num, u16 inst_num,
|
||||
u32 max_br)
|
||||
@ -8265,6 +8300,41 @@ sdp_result_e sdp_attr_get_fmtp_max_fs (void *sdp_ptr, u16 level,
|
||||
}
|
||||
}
|
||||
|
||||
/* Function: sdp_attr_get_fmtp_max_fr
|
||||
* Description: Gets the value of the fmtp attribute- max-fr parameter
|
||||
* Parameters: sdp_ptr The SDP handle returned by sdp_init_description.
|
||||
* level The level to check for the attribute.
|
||||
* cap_num The capability number associated with the
|
||||
* attribute if any. If none, should be zero.
|
||||
* inst_num The attribute instance number to check.
|
||||
* Returns: max-fr value.
|
||||
*/
|
||||
|
||||
sdp_result_e sdp_attr_get_fmtp_max_fr (void *sdp_ptr, u16 level,
|
||||
u8 cap_num, u16 inst_num, u32 *val)
|
||||
{
|
||||
sdp_t *sdp_p = (sdp_t *)sdp_ptr;
|
||||
sdp_attr_t *attr_p;
|
||||
|
||||
if (sdp_verify_sdp_ptr(sdp_p) == FALSE) {
|
||||
return (SDP_INVALID_SDP_PTR);
|
||||
}
|
||||
|
||||
attr_p = sdp_find_attr(sdp_p, level, cap_num, SDP_ATTR_FMTP,
|
||||
inst_num);
|
||||
if (attr_p == NULL) {
|
||||
if (sdp_p->debug_flag[SDP_DEBUG_ERRORS]) {
|
||||
CSFLogError(logTag, "%s fmtp attribute, level %u instance %u "
|
||||
"not found.", sdp_p->debug_str, level, inst_num);
|
||||
}
|
||||
sdp_p->conf_p->num_invalid_param++;
|
||||
return (SDP_INVALID_PARAMETER);
|
||||
} else {
|
||||
*val = attr_p->attr.fmtp.max_fr;
|
||||
return (SDP_SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
/* Function: sdp_attr_get_fmtp_max_cpb
|
||||
* Description: Gets the value of the fmtp attribute- max-cpb parameter for H.264 codec
|
||||
* Parameters: sdp_ptr The SDP handle returned by sdp_init_description.
|
||||
|
@ -400,7 +400,8 @@ const sdp_namearray_t sdp_fmtp_codec_param[SDP_MAX_FMTP_PARAM] =
|
||||
{"stereo", sizeof("stereo")}, /* 45 */
|
||||
{"useinbandfec", sizeof("useinbandfec")}, /* 46 */
|
||||
{"maxcodedaudiobandwidth", sizeof("maxcodedaudiobandwidth")}, /* 47 */
|
||||
{"cbr", sizeof("cbr")} /* 48 */
|
||||
{"cbr", sizeof("cbr")}, /* 48 */
|
||||
{"max-fr", sizeof("max-fr")} /* 49 */
|
||||
} ;
|
||||
|
||||
/* Note: These *must* be in the same order as the enum type. */
|
||||
|
@ -186,6 +186,8 @@ typedef struct
|
||||
int width;
|
||||
int height;
|
||||
uint32_t rtcp_fb_types;
|
||||
uint32_t max_fs; /* Max frame size */
|
||||
uint32_t max_fr; /* Max frame rate */
|
||||
} video;
|
||||
};
|
||||
|
||||
@ -1047,6 +1049,10 @@ int vcmOnSdpParseError(const char *peercconnection, const char *message);
|
||||
*/
|
||||
int vcmDisableRtcpComponent(const char *peerconnection, int level);
|
||||
|
||||
short vcmGetVideoMaxFs(uint16_t codec, int32_t *max_fs);
|
||||
|
||||
short vcmGetVideoMaxFr(uint16_t codec, int32_t *max_fs);
|
||||
|
||||
//Using C++ for gips. This is the end of extern "C" above.
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <fstream>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
#include <math.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
@ -709,6 +710,152 @@ class TransportConduitTest : public ::testing::Test
|
||||
|
||||
}
|
||||
|
||||
void DumpMaxFs(int orig_width, int orig_height, int max_fs,
|
||||
int new_width, int new_height)
|
||||
{
|
||||
cerr << "Applying max_fs=" << max_fs << " to input resolution " <<
|
||||
orig_width << "x" << orig_height << endl;
|
||||
cerr << "New resolution: " << new_width << "x" << new_height << endl;
|
||||
cerr << endl;
|
||||
}
|
||||
|
||||
// Calculate new resolution for sending video by applying max-fs constraint.
|
||||
void GetVideoResolutionWithMaxFs(int orig_width, int orig_height, int max_fs,
|
||||
int *new_width, int *new_height)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
// Get pointer to VideoSessionConduit.
|
||||
mVideoSession = mozilla::VideoSessionConduit::Create();
|
||||
if( !mVideoSession )
|
||||
ASSERT_NE(mVideoSession, (void*)NULL);
|
||||
|
||||
// Configure send codecs on the conduit.
|
||||
mozilla::VideoCodecConfig cinst1(120, "VP8", 0, max_fs, 0);
|
||||
|
||||
err = mVideoSession->ConfigureSendMediaCodec(&cinst1);
|
||||
ASSERT_EQ(mozilla::kMediaConduitNoError, err);
|
||||
|
||||
// Send one frame.
|
||||
MOZ_ASSERT(!(orig_width & 1));
|
||||
MOZ_ASSERT(!(orig_height & 1));
|
||||
int len = ((orig_width * orig_height) * 3 / 2);
|
||||
uint8_t* frame = (uint8_t*) PR_MALLOC(len);
|
||||
|
||||
memset(frame, COLOR, len);
|
||||
mVideoSession->SendVideoFrame((unsigned char*)frame,
|
||||
len,
|
||||
orig_width,
|
||||
orig_height,
|
||||
mozilla::kVideoI420,
|
||||
0);
|
||||
PR_Free(frame);
|
||||
|
||||
// Get the new resolution as adjusted by the max-fs constraint.
|
||||
*new_width = mVideoSession->SendingWidth();
|
||||
*new_height = mVideoSession->SendingHeight();
|
||||
}
|
||||
|
||||
void TestVideoConduitMaxFs()
|
||||
{
|
||||
int orig_width, orig_height, width, height, max_fs;
|
||||
|
||||
// No limitation.
|
||||
cerr << "Test no max-fs limition" << endl;
|
||||
orig_width = 640;
|
||||
orig_height = 480;
|
||||
max_fs = 0;
|
||||
GetVideoResolutionWithMaxFs(orig_width, orig_height, max_fs, &width, &height);
|
||||
DumpMaxFs(orig_width, orig_height, max_fs, width, height);
|
||||
ASSERT_EQ(width, 640);
|
||||
ASSERT_EQ(height, 480);
|
||||
|
||||
// VGA to QVGA.
|
||||
cerr << "Test resizing from VGA to QVGA" << endl;
|
||||
orig_width = 640;
|
||||
orig_height = 480;
|
||||
max_fs = 300;
|
||||
GetVideoResolutionWithMaxFs(orig_width, orig_height, max_fs, &width, &height);
|
||||
DumpMaxFs(orig_width, orig_height, max_fs, width, height);
|
||||
ASSERT_EQ(width, 320);
|
||||
ASSERT_EQ(height, 240);
|
||||
|
||||
// Extreme input resolution.
|
||||
cerr << "Test extreme input resolution" << endl;
|
||||
orig_width = 3072;
|
||||
orig_height = 100;
|
||||
max_fs = 300;
|
||||
GetVideoResolutionWithMaxFs(orig_width, orig_height, max_fs, &width, &height);
|
||||
DumpMaxFs(orig_width, orig_height, max_fs, width, height);
|
||||
ASSERT_EQ(width, 768);
|
||||
ASSERT_EQ(height, 26);
|
||||
|
||||
// Small max-fs.
|
||||
cerr << "Test small max-fs (case 1)" << endl;
|
||||
orig_width = 8;
|
||||
orig_height = 32;
|
||||
max_fs = 1;
|
||||
GetVideoResolutionWithMaxFs(orig_width, orig_height, max_fs, &width, &height);
|
||||
DumpMaxFs(orig_width, orig_height, max_fs, width, height);
|
||||
ASSERT_EQ(width, 4);
|
||||
ASSERT_EQ(height, 16);
|
||||
|
||||
// Small max-fs.
|
||||
cerr << "Test small max-fs (case 2)" << endl;
|
||||
orig_width = 4;
|
||||
orig_height = 50;
|
||||
max_fs = 1;
|
||||
GetVideoResolutionWithMaxFs(orig_width, orig_height, max_fs, &width, &height);
|
||||
DumpMaxFs(orig_width, orig_height, max_fs, width, height);
|
||||
ASSERT_EQ(width, 2);
|
||||
ASSERT_EQ(height, 16);
|
||||
|
||||
// Small max-fs.
|
||||
cerr << "Test small max-fs (case 3)" << endl;
|
||||
orig_width = 872;
|
||||
orig_height = 136;
|
||||
max_fs = 3;
|
||||
GetVideoResolutionWithMaxFs(orig_width, orig_height, max_fs, &width, &height);
|
||||
DumpMaxFs(orig_width, orig_height, max_fs, width, height);
|
||||
ASSERT_EQ(width, 48);
|
||||
ASSERT_EQ(height, 8);
|
||||
|
||||
// Small max-fs.
|
||||
cerr << "Test small max-fs (case 4)" << endl;
|
||||
orig_width = 160;
|
||||
orig_height = 8;
|
||||
max_fs = 5;
|
||||
GetVideoResolutionWithMaxFs(orig_width, orig_height, max_fs, &width, &height);
|
||||
DumpMaxFs(orig_width, orig_height, max_fs, width, height);
|
||||
ASSERT_EQ(width, 80);
|
||||
ASSERT_EQ(height, 4);
|
||||
|
||||
// Random values.
|
||||
for (int i = 0; i < 30; i++) {
|
||||
max_fs = rand() % 1000;
|
||||
orig_width = ((rand() % 2000) & ~1) + 2;
|
||||
orig_height = ((rand() % 2000) & ~1) + 2;
|
||||
// Potential crash on small resolution, see bug 919979.
|
||||
if (orig_width * orig_height <= 20) {
|
||||
cerr << "Temporarily skip resolution " << orig_width << "x" <<
|
||||
orig_height << endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
GetVideoResolutionWithMaxFs(orig_width, orig_height, max_fs,
|
||||
&width, &height);
|
||||
if (max_fs > 0 &&
|
||||
ceil(width / 16.) * ceil(height / 16.) > max_fs) {
|
||||
DumpMaxFs(orig_width, orig_height, max_fs, width, height);
|
||||
ADD_FAILURE();
|
||||
}
|
||||
if ((width & 1) || (height & 1)) {
|
||||
DumpMaxFs(orig_width, orig_height, max_fs, width, height);
|
||||
ADD_FAILURE();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
//Audio Conduit Test Objects
|
||||
mozilla::RefPtr<mozilla::AudioSessionConduit> mAudioSession;
|
||||
@ -744,6 +891,10 @@ TEST_F(TransportConduitTest, TestVideoConduitCodecAPI) {
|
||||
TestVideoConduitCodecAPI();
|
||||
}
|
||||
|
||||
TEST_F(TransportConduitTest, TestVideoConduitMaxFs) {
|
||||
TestVideoConduitMaxFs();
|
||||
}
|
||||
|
||||
} // end namespace
|
||||
|
||||
int main(int argc, char **argv)
|
||||
|
@ -190,6 +190,41 @@ class SdpTest : public ::testing::Test {
|
||||
return inst_num;
|
||||
}
|
||||
|
||||
u16 AddNewFmtpMaxFs(int level, u32 max_fs) {
|
||||
u16 inst_num = 0;
|
||||
EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_FMTP,
|
||||
&inst_num), SDP_SUCCESS);
|
||||
EXPECT_EQ(sdp_attr_set_fmtp_payload_type(sdp_ptr_, level, 0, inst_num,
|
||||
120), SDP_SUCCESS);
|
||||
EXPECT_EQ(sdp_attr_set_fmtp_max_fs(sdp_ptr_, level, 0, inst_num, max_fs),
|
||||
SDP_SUCCESS);
|
||||
return inst_num;
|
||||
}
|
||||
|
||||
u16 AddNewFmtpMaxFr(int level, u32 max_fr) {
|
||||
u16 inst_num = 0;
|
||||
EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_FMTP,
|
||||
&inst_num), SDP_SUCCESS);
|
||||
EXPECT_EQ(sdp_attr_set_fmtp_payload_type(sdp_ptr_, level, 0, inst_num,
|
||||
120), SDP_SUCCESS);
|
||||
EXPECT_EQ(sdp_attr_set_fmtp_max_fr(sdp_ptr_, level, 0, inst_num, max_fr),
|
||||
SDP_SUCCESS);
|
||||
return inst_num;
|
||||
}
|
||||
|
||||
u16 AddNewFmtpMaxFsFr(int level, u32 max_fs, u32 max_fr) {
|
||||
u16 inst_num = 0;
|
||||
EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_FMTP,
|
||||
&inst_num), SDP_SUCCESS);
|
||||
EXPECT_EQ(sdp_attr_set_fmtp_payload_type(sdp_ptr_, level, 0, inst_num,
|
||||
120), SDP_SUCCESS);
|
||||
EXPECT_EQ(sdp_attr_set_fmtp_max_fs(sdp_ptr_, level, 0, inst_num, max_fs),
|
||||
SDP_SUCCESS);
|
||||
EXPECT_EQ(sdp_attr_set_fmtp_max_fr(sdp_ptr_, level, 0, inst_num, max_fr),
|
||||
SDP_SUCCESS);
|
||||
return inst_num;
|
||||
}
|
||||
|
||||
protected:
|
||||
int final_level_;
|
||||
void *config_p_;
|
||||
@ -689,6 +724,45 @@ TEST_F(SdpTest, parseRtcpFbAllPayloads) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(SdpTest, parseFmtpMaxFs) {
|
||||
u32 val = 0;
|
||||
ParseSdp(kVideoSdp + "a=fmtp:120 max-fs=300;max-fr=30\r\n");
|
||||
ASSERT_EQ(sdp_attr_get_fmtp_max_fs(sdp_ptr_, 1, 0, 1, &val), SDP_SUCCESS);
|
||||
ASSERT_EQ(val, 300);
|
||||
}
|
||||
|
||||
TEST_F(SdpTest, parseFmtpMaxFr) {
|
||||
u32 val = 0;
|
||||
ParseSdp(kVideoSdp + "a=fmtp:120 max-fs=300;max-fr=30\r\n");
|
||||
ASSERT_EQ(sdp_attr_get_fmtp_max_fr(sdp_ptr_, 1, 0, 1, &val), SDP_SUCCESS);
|
||||
ASSERT_EQ(val, 30);
|
||||
}
|
||||
|
||||
TEST_F(SdpTest, addFmtpMaxFs) {
|
||||
InitLocalSdp();
|
||||
int level = AddNewMedia(SDP_MEDIA_VIDEO);
|
||||
AddNewFmtpMaxFs(level, 300);
|
||||
std::string body = SerializeSdp();
|
||||
ASSERT_NE(body.find("a=fmtp:120 max-fs=300\r\n"), std::string::npos);
|
||||
}
|
||||
|
||||
TEST_F(SdpTest, addFmtpMaxFr) {
|
||||
InitLocalSdp();
|
||||
int level = AddNewMedia(SDP_MEDIA_VIDEO);
|
||||
AddNewFmtpMaxFr(level, 30);
|
||||
std::string body = SerializeSdp();
|
||||
ASSERT_NE(body.find("a=fmtp:120 max-fr=30\r\n"), std::string::npos);
|
||||
}
|
||||
|
||||
TEST_F(SdpTest, addFmtpMaxFsFr) {
|
||||
InitLocalSdp();
|
||||
int level = AddNewMedia(SDP_MEDIA_VIDEO);
|
||||
AddNewFmtpMaxFsFr(level, 300, 30);
|
||||
std::string body = SerializeSdp();
|
||||
ASSERT_NE(body.find("a=fmtp:120 max-fs=300;max-fr=30\r\n"),
|
||||
std::string::npos);
|
||||
}
|
||||
|
||||
} // End namespace test.
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
@ -26,6 +26,9 @@
|
||||
#include "PeerConnectionCtx.h"
|
||||
#include "runnable_utils.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "nsIPrefService.h"
|
||||
#include "nsIPrefBranch.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsIIOService.h"
|
||||
#include "nsIDNSService.h"
|
||||
@ -114,6 +117,7 @@ enum sdpTestFlags
|
||||
SHOULD_REJECT_AUDIO = (1<<3),
|
||||
SHOULD_OMIT_AUDIO = (1<<4),
|
||||
DONT_CHECK_AUDIO = (1<<5),
|
||||
SHOULD_CHECK_AUDIO = (1<<6),
|
||||
|
||||
SHOULD_SEND_VIDEO = (1<<8),
|
||||
SHOULD_RECV_VIDEO = (1<<9),
|
||||
@ -121,6 +125,7 @@ enum sdpTestFlags
|
||||
SHOULD_REJECT_VIDEO = (1<<11),
|
||||
SHOULD_OMIT_VIDEO = (1<<12),
|
||||
DONT_CHECK_VIDEO = (1<<13),
|
||||
SHOULD_CHECK_VIDEO = (1<<14),
|
||||
|
||||
SHOULD_INCLUDE_DATA = (1 << 16),
|
||||
DONT_CHECK_DATA = (1 << 17),
|
||||
@ -128,14 +133,17 @@ enum sdpTestFlags
|
||||
SHOULD_SENDRECV_AUDIO = SHOULD_SEND_AUDIO | SHOULD_RECV_AUDIO,
|
||||
SHOULD_SENDRECV_VIDEO = SHOULD_SEND_VIDEO | SHOULD_RECV_VIDEO,
|
||||
SHOULD_SENDRECV_AV = SHOULD_SENDRECV_AUDIO | SHOULD_SENDRECV_VIDEO,
|
||||
SHOULD_CHECK_AV = SHOULD_CHECK_AUDIO | SHOULD_CHECK_VIDEO,
|
||||
|
||||
AUDIO_FLAGS = SHOULD_SEND_AUDIO | SHOULD_RECV_AUDIO
|
||||
| SHOULD_INACTIVE_AUDIO | SHOULD_REJECT_AUDIO
|
||||
| DONT_CHECK_AUDIO | SHOULD_OMIT_AUDIO,
|
||||
| DONT_CHECK_AUDIO | SHOULD_OMIT_AUDIO
|
||||
| SHOULD_CHECK_AUDIO,
|
||||
|
||||
VIDEO_FLAGS = SHOULD_SEND_VIDEO | SHOULD_RECV_VIDEO
|
||||
| SHOULD_INACTIVE_VIDEO | SHOULD_REJECT_VIDEO
|
||||
| DONT_CHECK_VIDEO | SHOULD_OMIT_VIDEO
|
||||
| SHOULD_CHECK_VIDEO
|
||||
};
|
||||
|
||||
enum offerAnswerFlags
|
||||
@ -1151,6 +1159,12 @@ private:
|
||||
case 0:
|
||||
ASSERT_EQ(sdp.find("a=rtpmap:109 opus/48000"), std::string::npos);
|
||||
break;
|
||||
case SHOULD_CHECK_AUDIO:
|
||||
ASSERT_NE(sdp.find("a=rtpmap:109 opus/48000"), std::string::npos);
|
||||
if (offer) {
|
||||
ASSERT_NE(sdp.find("a=rtpmap:0 PCMU/8000"), std::string::npos);
|
||||
}
|
||||
break;
|
||||
case SHOULD_SEND_AUDIO:
|
||||
ASSERT_NE(sdp.find("a=rtpmap:109 opus/48000"), std::string::npos);
|
||||
ASSERT_NE(sdp.find(" 0-15\r\na=sendonly"), std::string::npos);
|
||||
@ -1193,6 +1207,9 @@ private:
|
||||
case 0:
|
||||
ASSERT_EQ(sdp.find("a=rtpmap:120 VP8/90000"), std::string::npos);
|
||||
break;
|
||||
case SHOULD_CHECK_VIDEO:
|
||||
ASSERT_NE(sdp.find("a=rtpmap:120 VP8/90000"), std::string::npos);
|
||||
break;
|
||||
case SHOULD_SEND_VIDEO:
|
||||
ASSERT_NE(sdp.find("a=rtpmap:120 VP8/90000\r\na=sendonly"),
|
||||
std::string::npos);
|
||||
@ -1540,6 +1557,50 @@ public:
|
||||
kBogusSrflxAddress, kBogusSrflxPort);
|
||||
}
|
||||
|
||||
// Check max-fs and max-fr in SDP
|
||||
void CheckMaxFsFrSdp(const std::string sdp,
|
||||
int format,
|
||||
int max_fs,
|
||||
int max_fr) {
|
||||
ParsedSDP sdpWrapper(sdp);
|
||||
std::stringstream ss;
|
||||
ss << "a=fmtp:" << format;
|
||||
std::vector<std::string> lines = sdpWrapper.GetLines(ss.str());
|
||||
|
||||
// Both max-fs and max-fr not exist
|
||||
if (lines.empty()) {
|
||||
ASSERT_EQ(max_fs, 0);
|
||||
ASSERT_EQ(max_fr, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// At most one instance allowed for each format
|
||||
ASSERT_EQ(lines.size(), 1U);
|
||||
|
||||
std::string line = lines.front();
|
||||
|
||||
// Make sure that max-fs doesn't exist
|
||||
if (max_fs == 0) {
|
||||
ASSERT_EQ(line.find("max-fs="), std::string::npos);
|
||||
}
|
||||
// Check max-fs value
|
||||
if (max_fs > 0) {
|
||||
std::stringstream ss;
|
||||
ss << "max-fs=" << max_fs;
|
||||
ASSERT_NE(line.find(ss.str()), std::string::npos);
|
||||
}
|
||||
// Make sure that max-fr doesn't exist
|
||||
if (max_fr == 0) {
|
||||
ASSERT_EQ(line.find("max-fr="), std::string::npos);
|
||||
}
|
||||
// Check max-fr value
|
||||
if (max_fr > 0) {
|
||||
std::stringstream ss;
|
||||
ss << "max-fr=" << max_fr;
|
||||
ASSERT_NE(line.find(ss.str()), std::string::npos);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
bool init_;
|
||||
ScopedDeletePtr<SignalingAgent> a1_; // Canonically "caller"
|
||||
@ -1549,6 +1610,17 @@ public:
|
||||
uint16_t stun_port_;
|
||||
};
|
||||
|
||||
class FsFrPrefClearer {
|
||||
public:
|
||||
FsFrPrefClearer(nsCOMPtr<nsIPrefBranch> prefs): mPrefs(prefs) {}
|
||||
~FsFrPrefClearer() {
|
||||
mPrefs->ClearUserPref("media.navigator.video.max_fs");
|
||||
mPrefs->ClearUserPref("media.navigator.video.max_fr");
|
||||
}
|
||||
private:
|
||||
nsCOMPtr<nsIPrefBranch> mPrefs;
|
||||
};
|
||||
|
||||
TEST_F(SignalingTest, JustInit)
|
||||
{
|
||||
}
|
||||
@ -3328,6 +3400,167 @@ TEST_F(SignalingTest, hugeSdp)
|
||||
a2_->CreateAnswer(constraints, offer, OFFER_AV);
|
||||
}
|
||||
|
||||
// Test max_fs and max_fr prefs have proper impact on SDP offer
|
||||
TEST_F(SignalingTest, MaxFsFrInOffer)
|
||||
{
|
||||
EnsureInit();
|
||||
|
||||
sipcc::MediaConstraints constraints;
|
||||
|
||||
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
|
||||
ASSERT_TRUE(prefs);
|
||||
FsFrPrefClearer prefClearer(prefs);
|
||||
|
||||
prefs->SetIntPref("media.navigator.video.max_fs", 300);
|
||||
prefs->SetIntPref("media.navigator.video.max_fr", 30);
|
||||
|
||||
a1_->CreateOffer(constraints, OFFER_AV, SHOULD_CHECK_AV);
|
||||
|
||||
// Verify that SDP contains correct max-fs and max-fr
|
||||
CheckMaxFsFrSdp(a1_->offer(), 120, 300, 30);
|
||||
}
|
||||
|
||||
// Test max_fs and max_fr prefs have proper impact on SDP answer
|
||||
TEST_F(SignalingTest, MaxFsFrInAnswer)
|
||||
{
|
||||
EnsureInit();
|
||||
|
||||
sipcc::MediaConstraints constraints;
|
||||
|
||||
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
|
||||
ASSERT_TRUE(prefs);
|
||||
FsFrPrefClearer prefClearer(prefs);
|
||||
|
||||
// We don't want max_fs and max_fr prefs impact SDP at this moment
|
||||
prefs->SetIntPref("media.navigator.video.max_fs", 0);
|
||||
prefs->SetIntPref("media.navigator.video.max_fr", 0);
|
||||
|
||||
a1_->CreateOffer(constraints, OFFER_AV, SHOULD_CHECK_AV);
|
||||
|
||||
// SDP should not contain max-fs and max-fr here
|
||||
CheckMaxFsFrSdp(a1_->offer(), 120, 0, 0);
|
||||
|
||||
a2_->SetRemote(TestObserver::OFFER, a1_->offer());
|
||||
|
||||
prefs->SetIntPref("media.navigator.video.max_fs", 600);
|
||||
prefs->SetIntPref("media.navigator.video.max_fr", 60);
|
||||
|
||||
a2_->CreateAnswer(constraints, a1_->offer(), OFFER_AV | ANSWER_AV);
|
||||
|
||||
// Verify that SDP contains correct max-fs and max-fr
|
||||
CheckMaxFsFrSdp(a2_->answer(), 120, 600, 60);
|
||||
}
|
||||
|
||||
// Test SDP offer has proper impact on callee's codec configuration
|
||||
TEST_F(SignalingTest, MaxFsFrCalleeCodec)
|
||||
{
|
||||
EnsureInit();
|
||||
|
||||
sipcc::MediaConstraints constraints;
|
||||
|
||||
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
|
||||
ASSERT_TRUE(prefs);
|
||||
FsFrPrefClearer prefClearer(prefs);
|
||||
|
||||
// We don't want max_fs and max_fr prefs impact SDP at this moment
|
||||
prefs->SetIntPref("media.navigator.video.max_fs", 0);
|
||||
prefs->SetIntPref("media.navigator.video.max_fr", 0);
|
||||
|
||||
a1_->CreateOffer(constraints, OFFER_AV, SHOULD_CHECK_AV);
|
||||
|
||||
ParsedSDP sdpWrapper(a1_->offer());
|
||||
|
||||
sdpWrapper.ReplaceLine("a=rtpmap:120",
|
||||
"a=rtpmap:120 VP8/90000\r\na=fmtp:120 max-fs=300;max-fr=30\r\n");
|
||||
|
||||
std::cout << "Modified SDP " << std::endl
|
||||
<< indent(sdpWrapper.getSdp()) << std::endl;
|
||||
|
||||
// Double confirm that SDP offer contains correct max-fs and max-fr
|
||||
CheckMaxFsFrSdp(sdpWrapper.getSdp(), 120, 300, 30);
|
||||
|
||||
a1_->SetLocal(TestObserver::OFFER, sdpWrapper.getSdp());
|
||||
a2_->SetRemote(TestObserver::OFFER, sdpWrapper.getSdp());
|
||||
|
||||
a2_->CreateAnswer(constraints, sdpWrapper.getSdp(), OFFER_AV | ANSWER_AV);
|
||||
|
||||
// SDP should not contain max-fs and max-fr here
|
||||
CheckMaxFsFrSdp(a2_->answer(), 120, 0, 0);
|
||||
|
||||
a2_->SetLocal(TestObserver::ANSWER, a2_->answer());
|
||||
a1_->SetRemote(TestObserver::ANSWER, a2_->answer());
|
||||
|
||||
ASSERT_TRUE_WAIT(a1_->IceCompleted() == true, kDefaultTimeout);
|
||||
ASSERT_TRUE_WAIT(a2_->IceCompleted() == true, kDefaultTimeout);
|
||||
|
||||
// Checking callee's video sending configuration does respect max-fs and
|
||||
// max-fr in SDP offer.
|
||||
mozilla::RefPtr<mozilla::MediaPipeline> pipeline =
|
||||
a2_->GetMediaPipeline(1, 0, 1);
|
||||
ASSERT_TRUE(pipeline);
|
||||
mozilla::MediaSessionConduit *conduit = pipeline->Conduit();
|
||||
ASSERT_TRUE(conduit);
|
||||
ASSERT_EQ(conduit->type(), mozilla::MediaSessionConduit::VIDEO);
|
||||
mozilla::VideoSessionConduit *video_conduit =
|
||||
static_cast<mozilla::VideoSessionConduit*>(conduit);
|
||||
|
||||
ASSERT_EQ(video_conduit->SendingMaxFs(), (unsigned short) 300);
|
||||
ASSERT_EQ(video_conduit->SendingMaxFr(), (unsigned short) 30);
|
||||
}
|
||||
|
||||
// Test SDP answer has proper impact on caller's codec configuration
|
||||
TEST_F(SignalingTest, MaxFsFrCallerCodec)
|
||||
{
|
||||
EnsureInit();
|
||||
|
||||
sipcc::MediaConstraints constraints;
|
||||
|
||||
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
|
||||
ASSERT_TRUE(prefs);
|
||||
FsFrPrefClearer prefClearer(prefs);
|
||||
|
||||
// We don't want max_fs and max_fr prefs impact SDP at this moment
|
||||
prefs->SetIntPref("media.navigator.video.max_fs", 0);
|
||||
prefs->SetIntPref("media.navigator.video.max_fr", 0);
|
||||
|
||||
a1_->CreateOffer(constraints, OFFER_AV, SHOULD_CHECK_AV);
|
||||
a1_->SetLocal(TestObserver::OFFER, a1_->offer());
|
||||
a2_->SetRemote(TestObserver::OFFER, a1_->offer());
|
||||
|
||||
a2_->CreateAnswer(constraints, a1_->offer(), OFFER_AV | ANSWER_AV);
|
||||
|
||||
ParsedSDP sdpWrapper(a2_->answer());
|
||||
|
||||
sdpWrapper.ReplaceLine("a=rtpmap:120",
|
||||
"a=rtpmap:120 VP8/90000\r\na=fmtp:120 max-fs=600;max-fr=60\r\n");
|
||||
|
||||
std::cout << "Modified SDP " << std::endl
|
||||
<< indent(sdpWrapper.getSdp()) << std::endl;
|
||||
|
||||
// Double confirm that SDP answer contains correct max-fs and max-fr
|
||||
CheckMaxFsFrSdp(sdpWrapper.getSdp(), 120, 600, 60);
|
||||
|
||||
a2_->SetLocal(TestObserver::ANSWER, sdpWrapper.getSdp());
|
||||
a1_->SetRemote(TestObserver::ANSWER, sdpWrapper.getSdp());
|
||||
|
||||
ASSERT_TRUE_WAIT(a1_->IceCompleted() == true, kDefaultTimeout);
|
||||
ASSERT_TRUE_WAIT(a2_->IceCompleted() == true, kDefaultTimeout);
|
||||
|
||||
// Checking caller's video sending configuration does respect max-fs and
|
||||
// max-fr in SDP answer.
|
||||
mozilla::RefPtr<mozilla::MediaPipeline> pipeline =
|
||||
a1_->GetMediaPipeline(1, 0, 1);
|
||||
ASSERT_TRUE(pipeline);
|
||||
mozilla::MediaSessionConduit *conduit = pipeline->Conduit();
|
||||
ASSERT_TRUE(conduit);
|
||||
ASSERT_EQ(conduit->type(), mozilla::MediaSessionConduit::VIDEO);
|
||||
mozilla::VideoSessionConduit *video_conduit =
|
||||
static_cast<mozilla::VideoSessionConduit*>(conduit);
|
||||
|
||||
ASSERT_EQ(video_conduit->SendingMaxFs(), (unsigned short) 600);
|
||||
ASSERT_EQ(video_conduit->SendingMaxFr(), (unsigned short) 60);
|
||||
}
|
||||
|
||||
} // End namespace test.
|
||||
|
||||
bool is_color_terminal(const char *terminal) {
|
||||
|
@ -787,3 +787,5 @@ pref("general.useragent.override.youtube.com", "Android; Tablet;#Android; Mobile
|
||||
// When true, phone number linkification is enabled.
|
||||
pref("browser.ui.linkify.phone", false);
|
||||
|
||||
// Enables/disables Spatial Navigation
|
||||
pref("snav.enabled", true);
|
||||
|
@ -57,7 +57,6 @@ import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
@ -597,7 +596,7 @@ abstract public class BrowserApp extends GeckoApp
|
||||
String title = tab.getDisplayTitle();
|
||||
Bitmap favicon = tab.getFavicon();
|
||||
if (url != null && title != null) {
|
||||
GeckoAppShell.createShortcut(title, url, url, favicon == null ? null : favicon, "");
|
||||
GeckoAppShell.createShortcut(title, url, url, favicon, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -710,11 +709,11 @@ abstract public class BrowserApp extends GeckoApp
|
||||
return true;
|
||||
}
|
||||
|
||||
Favicons.loadFavicon(url, tab.getFaviconURL(), 0,
|
||||
Favicons.getFaviconForSize(url, tab.getFaviconURL(), Integer.MAX_VALUE, LoadFaviconTask.FLAG_PERSIST,
|
||||
new OnFaviconLoadedListener() {
|
||||
@Override
|
||||
public void onFaviconLoaded(String url, Bitmap favicon) {
|
||||
GeckoAppShell.createShortcut(title, url, url, favicon == null ? null : favicon, "");
|
||||
public void onFaviconLoaded(String pageUrl, String faviconURL, Bitmap favicon) {
|
||||
GeckoAppShell.createShortcut(title, url, url, favicon, "");
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1280,7 +1279,7 @@ abstract public class BrowserApp extends GeckoApp
|
||||
}
|
||||
|
||||
// If this tab is already selected, just hide the home pager.
|
||||
if (tabs.isSelectedTab(tabs.getTab(tabId))) {
|
||||
if (tabs.isSelectedTabId(tabId)) {
|
||||
hideHomePager();
|
||||
} else {
|
||||
tabs.selectTab(tabId);
|
||||
@ -1330,28 +1329,30 @@ abstract public class BrowserApp extends GeckoApp
|
||||
private void loadFavicon(final Tab tab) {
|
||||
maybeCancelFaviconLoad(tab);
|
||||
|
||||
int flags = LoadFaviconTask.FLAG_SCALE | ( (tab.isPrivate() || tab.getErrorType() != Tab.ErrorType.NONE) ? 0 : LoadFaviconTask.FLAG_PERSIST);
|
||||
int id = Favicons.loadFavicon(tab.getURL(), tab.getFaviconURL(), flags,
|
||||
new OnFaviconLoadedListener() {
|
||||
final int tabFaviconSize = getResources().getDimensionPixelSize(R.dimen.browser_toolbar_favicon_size);
|
||||
|
||||
@Override
|
||||
public void onFaviconLoaded(String pageUrl, Bitmap favicon) {
|
||||
// Leave favicon UI untouched if we failed to load the image
|
||||
// for some reason.
|
||||
if (favicon == null)
|
||||
return;
|
||||
int flags = (tab.isPrivate() || tab.getErrorType() != Tab.ErrorType.NONE) ? 0 : LoadFaviconTask.FLAG_PERSIST;
|
||||
int id = Favicons.getFaviconForSize(tab.getURL(), tab.getFaviconURL(), tabFaviconSize, flags,
|
||||
new OnFaviconLoadedListener() {
|
||||
@Override
|
||||
public void onFaviconLoaded(String pageUrl, String faviconURL, Bitmap favicon) {
|
||||
// If we failed to load a favicon, we use the default favicon instead.
|
||||
if (favicon == null) {
|
||||
favicon = Favicons.sDefaultFavicon;
|
||||
}
|
||||
|
||||
// The tab might be pointing to another URL by the time the
|
||||
// favicon is finally loaded, in which case we simply ignore it.
|
||||
if (!tab.getURL().equals(pageUrl))
|
||||
return;
|
||||
// The tab might be pointing to another URL by the time the
|
||||
// favicon is finally loaded, in which case we simply ignore it.
|
||||
// See also: Bug 920331.
|
||||
if (!tab.getURL().equals(pageUrl)) {
|
||||
return;
|
||||
}
|
||||
|
||||
tab.updateFavicon(favicon);
|
||||
tab.setFaviconLoadId(Favicons.NOT_LOADING);
|
||||
|
||||
Tabs.getInstance().notifyListeners(tab, Tabs.TabEvents.FAVICON);
|
||||
}
|
||||
});
|
||||
tab.updateFavicon(favicon);
|
||||
tab.setFaviconLoadId(Favicons.NOT_LOADING);
|
||||
Tabs.getInstance().notifyListeners(tab, Tabs.TabEvents.FAVICON);
|
||||
}
|
||||
});
|
||||
|
||||
tab.setFaviconLoadId(id);
|
||||
}
|
||||
@ -1359,9 +1360,6 @@ abstract public class BrowserApp extends GeckoApp
|
||||
private void maybeCancelFaviconLoad(Tab tab) {
|
||||
int faviconLoadId = tab.getFaviconLoadId();
|
||||
|
||||
if (faviconLoadId == Favicons.NOT_LOADING)
|
||||
return;
|
||||
|
||||
// Cancel pending favicon load task
|
||||
Favicons.cancelFaviconLoad(faviconLoadId);
|
||||
|
||||
|
@ -1121,7 +1121,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
||||
image = Bitmap.createScaledBitmap(image, mFaviconSize, mFaviconSize, false);
|
||||
mFavicon.setImageBitmap(image);
|
||||
} else {
|
||||
mFavicon.setImageResource(R.drawable.favicon);
|
||||
mFavicon.setImageBitmap(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1192,7 +1192,11 @@ abstract public class GeckoApp
|
||||
ThreadUtils.setUiThread(Thread.currentThread(), new Handler());
|
||||
|
||||
Tabs.getInstance().attachToContext(this);
|
||||
Favicons.attachToContext(this);
|
||||
try {
|
||||
Favicons.attachToContext(this);
|
||||
} catch (Exception e) {
|
||||
Log.e(LOGTAG, "Exception starting favicon cache. Corrupt resources?", e);
|
||||
}
|
||||
|
||||
// When we detect a locale change, we need to restart Gecko, which
|
||||
// actually means restarting the entire application. This logic should
|
||||
|
@ -76,6 +76,9 @@ FENNEC_JAVA_FILES = \
|
||||
DoorHanger.java \
|
||||
DoorHangerPopup.java \
|
||||
EditBookmarkDialog.java \
|
||||
favicons/cache/FaviconCache.java \
|
||||
favicons/cache/FaviconCacheElement.java \
|
||||
favicons/cache/FaviconsForURL.java \
|
||||
favicons/Favicons.java \
|
||||
favicons/LoadFaviconTask.java \
|
||||
favicons/OnFaviconLoadedListener.java \
|
||||
|
@ -7,7 +7,6 @@ package org.mozilla.gecko;
|
||||
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.home.HomePager;
|
||||
import org.mozilla.gecko.ReaderModeUtils;
|
||||
import org.mozilla.gecko.sync.setup.SyncAccounts;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
@ -17,7 +16,6 @@ import org.json.JSONObject;
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.accounts.OnAccountsUpdateListener;
|
||||
import android.app.Activity;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.ContentObserver;
|
||||
@ -272,6 +270,11 @@ public class Tabs implements GeckoEventListener {
|
||||
return tab != null && tab == mSelectedTab;
|
||||
}
|
||||
|
||||
public boolean isSelectedTabId(int tabId) {
|
||||
final Tab selected = mSelectedTab;
|
||||
return selected != null && selected.getId() == tabId;
|
||||
}
|
||||
|
||||
public synchronized Tab getTab(int id) {
|
||||
if (mTabs.size() == 0)
|
||||
return null;
|
||||
@ -607,6 +610,15 @@ public class Tabs implements GeckoEventListener {
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int getTabIdForUrl(String url) {
|
||||
return getTabIdForUrl(url, Tabs.getInstance().getSelectedTab().isPrivate());
|
||||
}
|
||||
|
||||
public synchronized Tab getTabForUrl(String url) {
|
||||
int tabId = getTabIdForUrl(url);
|
||||
return getTab(tabId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a tab with the given URL in the currently selected tab.
|
||||
*
|
||||
|
@ -88,10 +88,6 @@ public class BrowserDB {
|
||||
|
||||
public Bitmap getFaviconForUrl(ContentResolver cr, String uri);
|
||||
|
||||
public byte[] getFaviconBytesForUrl(ContentResolver cr, String uri);
|
||||
|
||||
public Cursor getFaviconsForUrls(ContentResolver cr, List<String> urls);
|
||||
|
||||
public String getFaviconUrlForHistoryUrl(ContentResolver cr, String url);
|
||||
|
||||
public void updateFaviconForUrl(ContentResolver cr, String pageUri, Bitmap favicon, String faviconUri);
|
||||
@ -242,16 +238,8 @@ public class BrowserDB {
|
||||
sDb.removeReadingListItemWithURL(cr, uri);
|
||||
}
|
||||
|
||||
public static Bitmap getFaviconForUrl(ContentResolver cr, String uri) {
|
||||
return sDb.getFaviconForUrl(cr, uri);
|
||||
}
|
||||
|
||||
public static byte[] getFaviconBytesForUrl(ContentResolver cr, String uri) {
|
||||
return sDb.getFaviconBytesForUrl(cr, uri);
|
||||
}
|
||||
|
||||
public static Cursor getFaviconsForUrls(ContentResolver cr, List<String> urls) {
|
||||
return sDb.getFaviconsForUrls(cr, urls);
|
||||
public static Bitmap getFaviconForFaviconUrl(ContentResolver cr, String faviconURL) {
|
||||
return sDb.getFaviconForUrl(cr, faviconURL);
|
||||
}
|
||||
|
||||
public static String getFaviconUrlForHistoryUrl(ContentResolver cr, String url) {
|
||||
|
@ -3015,6 +3015,11 @@ public class BrowserProvider extends ContentProvider {
|
||||
values.remove(Favicons.PAGE_URL);
|
||||
}
|
||||
|
||||
// If no URL is provided, insert using the default one.
|
||||
if (TextUtils.isEmpty(faviconUrl) && !TextUtils.isEmpty(pageUrl)) {
|
||||
values.put(Favicons.URL, org.mozilla.gecko.favicons.Favicons.guessDefaultFaviconURL(pageUrl));
|
||||
}
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
values.put(Favicons.DATE_CREATED, now);
|
||||
values.put(Favicons.DATE_MODIFIED, now);
|
||||
|
@ -696,33 +696,30 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
||||
new String[] { String.valueOf(id) });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the favicon from the database, if any, associated with the given favicon URL. (That is,
|
||||
* the URL of the actual favicon image, not the URL of the page with which the favicon is associated.)
|
||||
* @param cr The ContentResolver to use.
|
||||
* @param faviconURL The URL of the favicon to fetch from the database.
|
||||
* @return The decoded Bitmap from the database, if any. null if none is stored.
|
||||
*/
|
||||
@Override
|
||||
public Bitmap getFaviconForUrl(ContentResolver cr, String uri) {
|
||||
final byte[] b = getFaviconBytesForUrl(cr, uri);
|
||||
if (b == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return BitmapUtils.decodeByteArray(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getFaviconBytesForUrl(ContentResolver cr, String uri) {
|
||||
public Bitmap getFaviconForUrl(ContentResolver cr, String faviconURL) {
|
||||
Cursor c = null;
|
||||
byte[] b = null;
|
||||
|
||||
try {
|
||||
c = cr.query(mCombinedUriWithProfile,
|
||||
new String[] { Combined.FAVICON },
|
||||
Combined.URL + " = ?",
|
||||
new String[] { uri },
|
||||
c = cr.query(mFaviconsUriWithProfile,
|
||||
new String[] { Favicons.DATA },
|
||||
Favicons.URL + " = ?",
|
||||
new String[] { faviconURL },
|
||||
null);
|
||||
|
||||
if (!c.moveToFirst()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final int faviconIndex = c.getColumnIndexOrThrow(Combined.FAVICON);
|
||||
final int faviconIndex = c.getColumnIndexOrThrow(Favicons.DATA);
|
||||
b = c.getBlob(faviconIndex);
|
||||
} finally {
|
||||
if (c != null) {
|
||||
@ -730,7 +727,11 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
||||
}
|
||||
}
|
||||
|
||||
return b;
|
||||
if (b == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return BitmapUtils.decodeByteArray(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -754,28 +755,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor getFaviconsForUrls(ContentResolver cr, List<String> urls) {
|
||||
StringBuilder selection = new StringBuilder();
|
||||
selection.append(Favicons.URL + " IN (");
|
||||
|
||||
for (int i = 0; i < urls.size(); i++) {
|
||||
final String url = urls.get(i);
|
||||
|
||||
if (i > 0)
|
||||
selection.append(", ");
|
||||
|
||||
DatabaseUtils.appendEscapedSQLString(selection, url);
|
||||
}
|
||||
|
||||
selection.append(")");
|
||||
|
||||
return cr.query(mCombinedUriWithProfile,
|
||||
new String[] { Combined.URL, Combined.FAVICON },
|
||||
selection.toString(),
|
||||
null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFaviconForUrl(ContentResolver cr, String pageUri,
|
||||
Bitmap favicon, String faviconUri) {
|
||||
|
@ -5,8 +5,13 @@
|
||||
|
||||
package org.mozilla.gecko.favicons;
|
||||
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.text.TextUtils;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Tab;
|
||||
import org.mozilla.gecko.Tabs;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.favicons.cache.FaviconCache;
|
||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
@ -15,6 +20,8 @@ import android.graphics.Bitmap;
|
||||
import android.support.v4.util.LruCache;
|
||||
import android.util.Log;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
@ -24,73 +31,212 @@ import java.util.Set;
|
||||
public class Favicons {
|
||||
private static final String LOGTAG = "GeckoFavicons";
|
||||
|
||||
// Size of the favicon bitmap cache, in bytes (Counting payload only).
|
||||
public static final int FAVICON_CACHE_SIZE_BYTES = 512 * 1024;
|
||||
|
||||
// Number of URL mappings from page URL to Favicon URL to cache in memory.
|
||||
public static final int PAGE_URL_MAPPINGS_TO_STORE = 128;
|
||||
|
||||
public static final int NOT_LOADING = 0;
|
||||
public static final int FAILED_EXPIRY_NEVER = -1;
|
||||
public static final int FLAG_PERSIST = 1;
|
||||
public static final int FLAG_SCALE = 2;
|
||||
|
||||
private static int sFaviconSmallSize = -1;
|
||||
private static int sFaviconLargeSize = -1;
|
||||
|
||||
protected static Context sContext;
|
||||
|
||||
// The default Favicon to show if no other can be found.
|
||||
public static Bitmap sDefaultFavicon;
|
||||
|
||||
// The density-adjusted default Favicon dimensions.
|
||||
public static int sDefaultFaviconSize;
|
||||
|
||||
private static final Map<Integer, LoadFaviconTask> sLoadTasks = Collections.synchronizedMap(new HashMap<Integer, LoadFaviconTask>());
|
||||
private static final LruCache<String, Bitmap> sFaviconCache = new LruCache<String, Bitmap>(1024 * 1024) {
|
||||
@Override
|
||||
protected int sizeOf(String url, Bitmap image) {
|
||||
return image.getRowBytes() * image.getHeight();
|
||||
}
|
||||
};
|
||||
|
||||
// A cache of the Favicon which have recently failed to download - prevents us from repeatedly
|
||||
// trying to download a Favicon when doing so is currently impossible.
|
||||
private static final LruCache<String, Long> sFailedCache = new LruCache<String, Long>(64);
|
||||
// Cache to hold mappings between page URLs and Favicon URLs. Used to avoid going to the DB when
|
||||
// doing so is not necessary.
|
||||
private static final LruCache<String, String> sPageURLMappings = new LruCache<String, String>(PAGE_URL_MAPPINGS_TO_STORE);
|
||||
|
||||
// A cache holding the dominant colours of favicons - used by FaviconView to fill the extra space
|
||||
// around a Favicon when it is asked to render a Favicon small than the view.
|
||||
private static final LruCache<String, Integer> sColorCache = new LruCache<String, Integer>(256);
|
||||
static void dispatchResult(final String pageUrl, final Bitmap image,
|
||||
public static String getFaviconURLForPageURLFromCache(String pageURL) {
|
||||
return sPageURLMappings.get(pageURL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the given pageUrl->faviconUrl mapping into the memory cache of such mappings.
|
||||
* Useful for short-circuiting local database access.
|
||||
*/
|
||||
public static void putFaviconURLForPageURLInCache(String pageURL, String faviconURL) {
|
||||
sPageURLMappings.put(pageURL, faviconURL);
|
||||
}
|
||||
|
||||
private static FaviconCache sFaviconsCache;
|
||||
static void dispatchResult(final String pageUrl, final String faviconURL, final Bitmap image,
|
||||
final OnFaviconLoadedListener listener) {
|
||||
if (pageUrl != null && image != null)
|
||||
putFaviconInMemCache(pageUrl, image);
|
||||
|
||||
// We want to always run the listener on UI thread
|
||||
ThreadUtils.postToUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (listener != null)
|
||||
listener.onFaviconLoaded(pageUrl, image);
|
||||
if (listener != null) {
|
||||
listener.onFaviconLoaded(pageUrl, faviconURL, image);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static String getFaviconUrlForPageUrl(String pageUrl) {
|
||||
return BrowserDB.getFaviconUrlForHistoryUrl(sContext.getContentResolver(), pageUrl);
|
||||
/**
|
||||
* Get a Favicon as close as possible to the target dimensions for the URL provided.
|
||||
* If a result is instantly available from the cache, it is returned and the listener is invoked.
|
||||
* Otherwise, the result is drawn from the database or network and the listener invoked when the
|
||||
* result becomes available.
|
||||
*
|
||||
* @param pageURL Page URL for which a Favicon is desired.
|
||||
* @param faviconURL URL of the Favicon to be downloaded, if known. If none provided, an educated
|
||||
* guess is made by the system.
|
||||
* @param targetSize Target size of the returned Favicon
|
||||
* @param listener Listener to call with the result of the load operation, if the result is not
|
||||
* immediately available.
|
||||
* @return The id of the asynchronous task created, NOT_LOADING if none is created.
|
||||
*/
|
||||
public static int getFaviconForSize(String pageURL, String faviconURL, int targetSize, int flags, OnFaviconLoadedListener listener) {
|
||||
// If there's no favicon URL given, try and hit the cache with the default one.
|
||||
String cacheURL = faviconURL;
|
||||
if (cacheURL == null) {
|
||||
cacheURL = guessDefaultFaviconURL(pageURL);
|
||||
}
|
||||
|
||||
// If it's something we can't even figure out a default URL for, just give up.
|
||||
if (cacheURL == null) {
|
||||
dispatchResult(pageURL, null, sDefaultFavicon, listener);
|
||||
return NOT_LOADING;
|
||||
}
|
||||
|
||||
Bitmap cachedIcon = getSizedFaviconFromCache(cacheURL, targetSize);
|
||||
if (cachedIcon != null) {
|
||||
dispatchResult(pageURL, cacheURL, cachedIcon, listener);
|
||||
return NOT_LOADING;
|
||||
}
|
||||
|
||||
// Check if favicon has failed.
|
||||
if (sFaviconsCache.isFailedFavicon(cacheURL)) {
|
||||
dispatchResult(pageURL, cacheURL, sDefaultFavicon, listener);
|
||||
return NOT_LOADING;
|
||||
}
|
||||
|
||||
// Failing that, try and get one from the database or internet.
|
||||
return loadUncachedFavicon(pageURL, faviconURL, flags, targetSize, listener);
|
||||
}
|
||||
|
||||
public static int loadFavicon(String pageUrl, String faviconUrl, int flags,
|
||||
OnFaviconLoadedListener listener) {
|
||||
/**
|
||||
* Returns the cached Favicon closest to the target size if any exists or is coercible. Returns
|
||||
* null otherwise. Does not query the database or network for the Favicon is the result is not
|
||||
* immediately available.
|
||||
*
|
||||
* @param faviconURL URL of the Favicon to query for.
|
||||
* @param targetSize The desired size of the returned Favicon.
|
||||
* @return The cached Favicon, rescaled to be as close as possible to the target size, if any exists.
|
||||
* null if no applicable Favicon exists in the cache.
|
||||
*/
|
||||
public static Bitmap getSizedFaviconFromCache(String faviconURL, int targetSize) {
|
||||
return sFaviconsCache.getFaviconForDimensions(faviconURL, targetSize);
|
||||
}
|
||||
|
||||
// Handle the case where page url is empty
|
||||
if (pageUrl == null || pageUrl.length() == 0) {
|
||||
dispatchResult(null, null, listener);
|
||||
return -1;
|
||||
/**
|
||||
* Attempts to find a Favicon for the provided page URL from either the mem cache or the database.
|
||||
* Does not need an explicit favicon URL, since, as we are accessing the database anyway, we
|
||||
* can query the history DB for the Favicon URL.
|
||||
* Handy for easing the transition from caching with page URLs to caching with Favicon URLs.
|
||||
*
|
||||
* A null result is passed to the listener if no value is locally available. The Favicon is not
|
||||
* added to the failure cache.
|
||||
*
|
||||
* @param pageURL Page URL for which a Favicon is wanted.
|
||||
* @param targetSize Target size of the desired Favicon to pass to the cache query
|
||||
* @param callback Callback to fire with the result.
|
||||
* @return The job ID of the spawned async task, if any.
|
||||
*/
|
||||
public static int getSizedFaviconForPageFromLocal(final String pageURL, final int targetSize, final OnFaviconLoadedListener callback) {
|
||||
// Firstly, try extremely hard to cheat.
|
||||
// Have we cached this favicon URL? If we did, we can consult the memcache right away.
|
||||
String targetURL = sPageURLMappings.get(pageURL);
|
||||
if (targetURL != null) {
|
||||
// Check if favicon has failed.
|
||||
if (sFaviconsCache.isFailedFavicon(targetURL)) {
|
||||
dispatchResult(pageURL, targetURL, null, callback);
|
||||
return NOT_LOADING;
|
||||
}
|
||||
|
||||
// Do we have a Favicon in the cache for this favicon URL?
|
||||
Bitmap result = getSizedFaviconFromCache(targetURL, targetSize);
|
||||
if (result != null) {
|
||||
// Victory - immediate response!
|
||||
dispatchResult(pageURL, targetURL, result, callback);
|
||||
return NOT_LOADING;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if favicon has failed
|
||||
if (isFailedFavicon(pageUrl)) {
|
||||
dispatchResult(pageUrl, null, listener);
|
||||
return -1;
|
||||
// No joy using in-memory resources. Go to background thread and ask the database.
|
||||
LoadFaviconTask task = new LoadFaviconTask(ThreadUtils.getBackgroundHandler(), pageURL, targetURL, 0, callback, targetSize, true);
|
||||
int taskId = task.getId();
|
||||
sLoadTasks.put(taskId, task);
|
||||
task.execute();
|
||||
return taskId;
|
||||
}
|
||||
|
||||
public static int getSizedFaviconForPageFromLocal(final String pageURL, final OnFaviconLoadedListener callback) {
|
||||
return getSizedFaviconForPageFromLocal(pageURL, sDefaultFaviconSize, callback);
|
||||
}
|
||||
/**
|
||||
* Helper method to determine the URL of the Favicon image for a given page URL by querying the
|
||||
* history database. Should only be called from the background thread - does database access.
|
||||
*
|
||||
* @param pageURL The URL of a webpage with a Favicon.
|
||||
* @return The URL of the Favicon used by that webpage, according to either the History database
|
||||
* or a somewhat educated guess.
|
||||
*/
|
||||
public static String getFaviconUrlForPageUrl(String pageURL) {
|
||||
// Attempt to determine the Favicon URL from the Tabs datastructure. Can dodge having to use
|
||||
// the database sometimes by doing this.
|
||||
String targetURL;
|
||||
Tab theTab = Tabs.getInstance().getTabForUrl(pageURL);
|
||||
if (theTab != null) {
|
||||
targetURL = theTab.getFaviconURL();
|
||||
if (targetURL != null) {
|
||||
return targetURL;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if favicon is mem cached
|
||||
Bitmap image = getFaviconFromMemCache(pageUrl);
|
||||
if (image != null) {
|
||||
dispatchResult(pageUrl, image, listener);
|
||||
return -1;
|
||||
targetURL = BrowserDB.getFaviconUrlForHistoryUrl(sContext.getContentResolver(), pageURL);
|
||||
if (targetURL == null) {
|
||||
// Nothing in the history database. Fall back to the default URL and hope for the best.
|
||||
targetURL = guessDefaultFaviconURL(pageURL);
|
||||
}
|
||||
return targetURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to create an async job to load a Favicon which does not exist in the memcache.
|
||||
* Contains logic to prevent the repeated loading of Favicons which have previously failed.
|
||||
* There is no support for recovery from transient failures.
|
||||
*
|
||||
* @param pageUrl URL of the page for which to load a Favicon. If null, no job is created.
|
||||
* @param faviconUrl The URL of the Favicon to load. If null, an attempt to infer the value from
|
||||
* the history database will be made, and ultimately an attempt to guess will
|
||||
* be made.
|
||||
* @param flags Flags to be used by the LoadFaviconTask while loading. Currently only one flag
|
||||
* is supported, LoadFaviconTask.FLAG_PERSIST.
|
||||
* If FLAG_PERSIST is set and the Favicon is ultimately loaded from the internet,
|
||||
* the downloaded Favicon is subsequently stored in the local database.
|
||||
* If FLAG_PERSIST is unset, the downloaded Favicon is stored only in the memcache.
|
||||
* FLAG_PERSIST has no effect on loads which come from the database.
|
||||
* @param listener The OnFaviconLoadedListener to invoke with the result of this Favicon load.
|
||||
* @return The id of the LoadFaviconTask handling this job.
|
||||
*/
|
||||
private static int loadUncachedFavicon(String pageUrl, String faviconUrl, int flags, int targetSize, OnFaviconLoadedListener listener) {
|
||||
// Handle the case where we have no page url.
|
||||
if (TextUtils.isEmpty(pageUrl)) {
|
||||
dispatchResult(null, null, null, listener);
|
||||
return NOT_LOADING;
|
||||
}
|
||||
|
||||
LoadFaviconTask task = new LoadFaviconTask(ThreadUtils.getBackgroundHandler(), pageUrl, faviconUrl, flags, listener);
|
||||
LoadFaviconTask task = new LoadFaviconTask(ThreadUtils.getBackgroundHandler(), pageUrl, faviconUrl, flags, listener, targetSize, false);
|
||||
|
||||
int taskId = task.getId();
|
||||
sLoadTasks.put(taskId, task);
|
||||
@ -100,44 +246,29 @@ public class Favicons {
|
||||
return taskId;
|
||||
}
|
||||
|
||||
public static Bitmap getFaviconFromMemCache(String pageUrl) {
|
||||
// If for some reason the key is null, simply return null
|
||||
// and avoid an exception on the mem cache (see bug 813546)
|
||||
if (pageUrl == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return sFaviconCache.get(pageUrl);
|
||||
public static void putFaviconInMemCache(String pageUrl, Bitmap image) {
|
||||
sFaviconsCache.putSingleFavicon(pageUrl, image);
|
||||
}
|
||||
|
||||
public static void putFaviconInMemCache(String pageUrl, Bitmap image) {
|
||||
sFaviconCache.put(pageUrl, image);
|
||||
public static void putFaviconsInMemCache(String pageUrl, Iterator<Bitmap> images) {
|
||||
sFaviconsCache.putFavicons(pageUrl, images);
|
||||
}
|
||||
|
||||
public static void clearMemCache() {
|
||||
sFaviconCache.evictAll();
|
||||
sFaviconsCache.evictAll();
|
||||
sPageURLMappings.evictAll();
|
||||
}
|
||||
|
||||
public static boolean isFailedFavicon(String pageUrl) {
|
||||
Long fetchTime = sFailedCache.get(pageUrl);
|
||||
if (fetchTime == null)
|
||||
return false;
|
||||
// We don't have any other rules yet.
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void putFaviconInFailedCache(String pageUrl, long fetchTime) {
|
||||
sFailedCache.put(pageUrl, fetchTime);
|
||||
}
|
||||
|
||||
public static void clearFailedCache() {
|
||||
sFailedCache.evictAll();
|
||||
public static void putFaviconInFailedCache(String faviconURL) {
|
||||
sFaviconsCache.putFailed(faviconURL);
|
||||
}
|
||||
|
||||
public static boolean cancelFaviconLoad(int taskId) {
|
||||
Log.d(LOGTAG, "Requesting cancelation of favicon load (" + taskId + ")");
|
||||
if (taskId == NOT_LOADING) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean cancelled = false;
|
||||
boolean cancelled;
|
||||
synchronized (sLoadTasks) {
|
||||
if (!sLoadTasks.containsKey(taskId))
|
||||
return false;
|
||||
@ -161,47 +292,92 @@ public class Favicons {
|
||||
int taskId = iter.next();
|
||||
cancelFaviconLoad(taskId);
|
||||
}
|
||||
sLoadTasks.clear();
|
||||
}
|
||||
|
||||
LoadFaviconTask.closeHTTPClient();
|
||||
}
|
||||
|
||||
public static boolean isLargeFavicon(Bitmap image) {
|
||||
return image.getWidth() > sFaviconSmallSize || image.getHeight() > sFaviconSmallSize;
|
||||
/**
|
||||
* Get the dominant colour of the Favicon at the URL given, if any exists in the cache.
|
||||
*
|
||||
* @param url The URL of the Favicon, to be used as the cache key for the colour value.
|
||||
* @return The dominant colour of the provided Favicon.
|
||||
*/
|
||||
public static int getFaviconColor(String url) {
|
||||
return sFaviconsCache.getDominantColor(url);
|
||||
}
|
||||
|
||||
public static Bitmap scaleImage(Bitmap image) {
|
||||
// If the icon is larger than 16px, scale it to sFaviconLargeSize.
|
||||
// Otherwise, scale it to sFaviconSmallSize.
|
||||
if (isLargeFavicon(image)) {
|
||||
image = Bitmap.createScaledBitmap(image, sFaviconLargeSize, sFaviconLargeSize, false);
|
||||
} else {
|
||||
image = Bitmap.createScaledBitmap(image, sFaviconSmallSize, sFaviconSmallSize, false);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
public static int getFaviconColor(Bitmap image, String key) {
|
||||
Integer color = sColorCache.get(key);
|
||||
if (color != null) {
|
||||
return color;
|
||||
}
|
||||
|
||||
color = BitmapUtils.getDominantColor(image);
|
||||
sColorCache.put(key, color);
|
||||
return color;
|
||||
}
|
||||
|
||||
public static void attachToContext(Context context) {
|
||||
/**
|
||||
* Called by GeckoApp on startup to pass this class a reference to the GeckoApp object used as
|
||||
* the application's Context.
|
||||
* Consider replacing with references to a staticly held reference to the GeckoApp object.
|
||||
*
|
||||
* @param context A reference to the GeckoApp instance.
|
||||
*/
|
||||
public static void attachToContext(Context context) throws Exception {
|
||||
sContext = context;
|
||||
if (sFaviconSmallSize < 0) {
|
||||
sFaviconSmallSize = Math.round(sContext.getResources().getDimension(R.dimen.favicon_size_small));
|
||||
|
||||
// Decode the default Favicon ready for use.
|
||||
sDefaultFavicon = BitmapFactory.decodeResource(context.getResources(), R.drawable.favicon);
|
||||
if (sDefaultFavicon == null) {
|
||||
throw new Exception("Null default favicon was returned from the resources system!");
|
||||
}
|
||||
if (sFaviconLargeSize < 0) {
|
||||
sFaviconLargeSize = Math.round(sContext.getResources().getDimension(R.dimen.favicon_size_large));
|
||||
|
||||
sDefaultFaviconSize = context.getResources().getDimensionPixelSize(R.dimen.favicon_bg);
|
||||
sFaviconsCache = new FaviconCache(FAVICON_CACHE_SIZE_BYTES, context.getResources().getDimensionPixelSize(R.dimen.favicon_largest_interesting_size));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get the default Favicon URL for a given pageURL. Generally: somewhere.com/favicon.ico
|
||||
*
|
||||
* @param pageURL Page URL for which a default Favicon URL is requested
|
||||
* @return The default Favicon URL.
|
||||
*/
|
||||
public static String guessDefaultFaviconURL(String pageURL) {
|
||||
// Special-casing for about: pages. The favicon for about:pages which don't provide a link tag
|
||||
// is bundled in the database, keyed only by page URL, hence the need to return the page URL
|
||||
// here. If the database ever migrates to stop being silly in this way, this can plausibly
|
||||
// be removed.
|
||||
if (pageURL.startsWith("about:") || pageURL.startsWith("jar:")) {
|
||||
return pageURL;
|
||||
}
|
||||
|
||||
try {
|
||||
// Fall back to trying "someScheme:someDomain.someExtension/favicon.ico".
|
||||
URI u = new URI(pageURL);
|
||||
return new URI(u.getScheme(),
|
||||
u.getAuthority(),
|
||||
"/favicon.ico", null,
|
||||
null).toString();
|
||||
} catch (URISyntaxException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void removeLoadTask(long taskId) {
|
||||
sLoadTasks.remove(taskId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to wrap FaviconCache.isFailedFavicon for use by LoadFaviconTask.
|
||||
*
|
||||
* @param faviconURL Favicon URL to check for failure.
|
||||
*/
|
||||
static boolean isFailedFavicon(String faviconURL) {
|
||||
return sFaviconsCache.isFailedFavicon(faviconURL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sidestep the cache and get, from either the database or the internet, the largest available
|
||||
* Favicon for the given page URL. Useful for creating homescreen shortcuts without being limited
|
||||
* by possibly low-resolution values in the cache.
|
||||
* Deduces the favicon URL from the history database and, ultimately, guesses.
|
||||
*
|
||||
* @param url Page URL to get a large favicon image fro.
|
||||
* @param onFaviconLoadedListener Listener to call back with the result.
|
||||
*/
|
||||
public static void getLargestFaviconForPage(String url, OnFaviconLoadedListener onFaviconLoadedListener) {
|
||||
loadUncachedFavicon(url, null, 0, -1, onFaviconLoadedListener);
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,9 @@ import android.content.ContentResolver;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.http.AndroidHttpClient;
|
||||
import android.os.Handler;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
@ -22,12 +24,15 @@ import org.mozilla.gecko.util.ThreadUtils;
|
||||
import org.mozilla.gecko.util.UiAsyncTask;
|
||||
import static org.mozilla.gecko.favicons.Favicons.sContext;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* Class representing the asynchronous task to load a Favicon which is not currently in the in-memory
|
||||
@ -38,8 +43,13 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
public class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> {
|
||||
private static final String LOGTAG = "LoadFaviconTask";
|
||||
|
||||
// Access to this map needs to be synchronized prevent multiple jobs loading the same favicon
|
||||
// from executing concurrently.
|
||||
private static final HashMap<String, LoadFaviconTask> loadsInFlight = new HashMap<String, LoadFaviconTask>();
|
||||
|
||||
public static final int FLAG_PERSIST = 1;
|
||||
public static final int FLAG_SCALE = 2;
|
||||
private static final int MAX_REDIRECTS_TO_FOLLOW = 5;
|
||||
|
||||
private static AtomicInteger mNextFaviconLoadId = new AtomicInteger(0);
|
||||
private int mId;
|
||||
@ -48,25 +58,39 @@ public class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> {
|
||||
private OnFaviconLoadedListener mListener;
|
||||
private int mFlags;
|
||||
|
||||
private final boolean mOnlyFromLocal;
|
||||
|
||||
// Assuming square favicons, judging by width only is acceptable.
|
||||
protected int mTargetWidth;
|
||||
private LinkedList<LoadFaviconTask> mChainees;
|
||||
private boolean mIsChaining;
|
||||
|
||||
static AndroidHttpClient sHttpClient = AndroidHttpClient.newInstance(GeckoAppShell.getGeckoInterface().getDefaultUAString());
|
||||
|
||||
public LoadFaviconTask(Handler backgroundThreadHandler,
|
||||
String aPageUrl, String aFaviconUrl, int aFlags,
|
||||
OnFaviconLoadedListener aListener) {
|
||||
String pageUrl, String faviconUrl, int flags,
|
||||
OnFaviconLoadedListener listener) {
|
||||
this(backgroundThreadHandler, pageUrl, faviconUrl, flags, listener, -1, false);
|
||||
}
|
||||
public LoadFaviconTask(Handler backgroundThreadHandler,
|
||||
String pageUrl, String faviconUrl, int flags,
|
||||
OnFaviconLoadedListener aListener, int targetSize, boolean fromLocal) {
|
||||
super(backgroundThreadHandler);
|
||||
|
||||
mId = mNextFaviconLoadId.incrementAndGet();
|
||||
|
||||
mPageUrl = aPageUrl;
|
||||
mFaviconUrl = aFaviconUrl;
|
||||
mPageUrl = pageUrl;
|
||||
mFaviconUrl = faviconUrl;
|
||||
mListener = aListener;
|
||||
mFlags = aFlags;
|
||||
mFlags = flags;
|
||||
mTargetWidth = targetSize;
|
||||
mOnlyFromLocal = fromLocal;
|
||||
}
|
||||
|
||||
// Runs in background thread
|
||||
private Bitmap loadFaviconFromDb() {
|
||||
ContentResolver resolver = sContext.getContentResolver();
|
||||
return BrowserDB.getFaviconForUrl(resolver, mPageUrl);
|
||||
return BrowserDB.getFaviconForFaviconUrl(resolver, mFaviconUrl);
|
||||
}
|
||||
|
||||
// Runs in background thread
|
||||
@ -79,50 +103,87 @@ public class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> {
|
||||
BrowserDB.updateFaviconForUrl(resolver, mPageUrl, favicon, mFaviconUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for trying the download request to grab a Favicon.
|
||||
* @param faviconURI URL of Favicon to try and download
|
||||
* @return The HttpResponse containing the downloaded Favicon if successful, null otherwise.
|
||||
*/
|
||||
private HttpResponse tryDownload(URI faviconURI) throws URISyntaxException, IOException {
|
||||
HashSet<String> visitedLinkSet = new HashSet<String>();
|
||||
visitedLinkSet.add(faviconURI.toString());
|
||||
return tryDownloadRecurse(faviconURI, visitedLinkSet);
|
||||
}
|
||||
private HttpResponse tryDownloadRecurse(URI faviconURI, HashSet<String> visited) throws URISyntaxException, IOException {
|
||||
if (visited.size() == MAX_REDIRECTS_TO_FOLLOW) {
|
||||
return null;
|
||||
}
|
||||
|
||||
HttpGet request = new HttpGet(faviconURI);
|
||||
HttpResponse response = sHttpClient.execute(request);
|
||||
if (response == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (response.getStatusLine() != null) {
|
||||
|
||||
// Was the response a failure?
|
||||
int status = response.getStatusLine().getStatusCode();
|
||||
|
||||
// Handle HTTP status codes requesting a redirect.
|
||||
if (status >= 300 && status < 400) {
|
||||
Header header = response.getFirstHeader("Location");
|
||||
|
||||
// Handle mad webservers.
|
||||
if (header == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String newURI = header.getValue();
|
||||
if (newURI == null || newURI.equals(faviconURI.toString())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (visited.contains(newURI)) {
|
||||
// Already been redirected here - abort.
|
||||
return null;
|
||||
}
|
||||
|
||||
visited.add(newURI);
|
||||
return tryDownloadRecurse(new URI(newURI), visited);
|
||||
}
|
||||
|
||||
if (status >= 400) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
// Runs in background thread
|
||||
private Bitmap downloadFavicon(URL targetFaviconURL) {
|
||||
private Bitmap downloadFavicon(URI targetFaviconURI) {
|
||||
if (mFaviconUrl.startsWith("jar:jar:")) {
|
||||
return GeckoJarReader.getBitmap(sContext.getResources(), mFaviconUrl);
|
||||
}
|
||||
|
||||
URI uri;
|
||||
try {
|
||||
uri = targetFaviconURL.toURI();
|
||||
} catch (URISyntaxException e) {
|
||||
Log.d(LOGTAG, "Could not get URI for favicon");
|
||||
// only get favicons for HTTP/HTTPS
|
||||
String scheme = targetFaviconURI.getScheme();
|
||||
if (!"http".equals(scheme) && !"https".equals(scheme)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// only get favicons for HTTP/HTTPS
|
||||
String scheme = uri.getScheme();
|
||||
if (!"http".equals(scheme) && !"https".equals(scheme))
|
||||
return null;
|
||||
|
||||
// skia decoder sometimes returns null; workaround is to use BufferedHttpEntity
|
||||
// http://groups.google.com/group/android-developers/browse_thread/thread/171b8bf35dbbed96/c3ec5f45436ceec8?lnk=raot
|
||||
Bitmap image = null;
|
||||
try {
|
||||
HttpGet request = new HttpGet(targetFaviconURL.toURI());
|
||||
HttpResponse response = sHttpClient.execute(request);
|
||||
if (response == null)
|
||||
// Try the URL we were given.
|
||||
HttpResponse response = tryDownload(targetFaviconURI);
|
||||
if (response == null) {
|
||||
return null;
|
||||
if (response.getStatusLine() != null) {
|
||||
// Was the response a failure?
|
||||
int status = response.getStatusLine().getStatusCode();
|
||||
if (status >= 400) {
|
||||
Favicons.putFaviconInFailedCache(mPageUrl, Favicons.FAILED_EXPIRY_NEVER);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
HttpEntity entity = response.getEntity();
|
||||
if (entity == null)
|
||||
if (entity == null) {
|
||||
return null;
|
||||
if (entity.getContentType() != null) {
|
||||
// Is the content type valid? Might be a captive portal.
|
||||
String contentType = entity.getContentType().getValue();
|
||||
if (contentType.indexOf("image") == -1)
|
||||
return null;
|
||||
}
|
||||
|
||||
BufferedHttpEntity bufferedEntity = new BufferedHttpEntity(entity);
|
||||
@ -145,69 +206,194 @@ public class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> {
|
||||
|
||||
@Override
|
||||
protected Bitmap doInBackground(Void... unused) {
|
||||
Bitmap image;
|
||||
|
||||
if (isCancelled())
|
||||
if (isCancelled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
URL faviconURLToDownload;
|
||||
String storedFaviconUrl;
|
||||
boolean isUsingDefaultURL = false;
|
||||
|
||||
// Handle the case of malformed favicon URL
|
||||
try {
|
||||
// If favicon is empty, fallback to default favicon URI
|
||||
if (mFaviconUrl == null || mFaviconUrl.length() == 0) {
|
||||
// Handle the case of malformed URL
|
||||
URL targetPageURL = new URL(mPageUrl);
|
||||
// Handle the case of malformed favicon URL.
|
||||
// If favicon is empty, fall back to the stored one.
|
||||
if (TextUtils.isEmpty(mFaviconUrl)) {
|
||||
// Try to get the favicon URL from the memory cache.
|
||||
storedFaviconUrl = Favicons.getFaviconURLForPageURLFromCache(mPageUrl);
|
||||
|
||||
faviconURLToDownload = new URL(targetPageURL.getProtocol(), targetPageURL.getAuthority(), "/favicon.ico");
|
||||
mFaviconUrl = faviconURLToDownload.toString();
|
||||
} else {
|
||||
faviconURLToDownload = new URL(mFaviconUrl);
|
||||
// If that failed, try to get the URL from the database.
|
||||
if (storedFaviconUrl == null) {
|
||||
storedFaviconUrl = Favicons.getFaviconUrlForPageUrl(mPageUrl);
|
||||
if (storedFaviconUrl != null) {
|
||||
// If that succeeded, cache the URL loaded from the database in memory.
|
||||
Favicons.putFaviconURLForPageURLInCache(mPageUrl, storedFaviconUrl);
|
||||
}
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
Log.d(LOGTAG, "The provided favicon URL is not valid");
|
||||
|
||||
// If we found a faviconURL - use it.
|
||||
if (storedFaviconUrl != null) {
|
||||
mFaviconUrl = storedFaviconUrl;
|
||||
} else {
|
||||
// If we don't have a stored one, fall back to the default.
|
||||
mFaviconUrl = Favicons.guessDefaultFaviconURL(mPageUrl);
|
||||
isUsingDefaultURL = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if favicon has failed - if so, give up. We need this check because, sometimes, we
|
||||
// didn't know the real Favicon URL until we asked the database.
|
||||
if (Favicons.isFailedFavicon(mFaviconUrl)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isCancelled())
|
||||
if (isCancelled()) {
|
||||
return null;
|
||||
|
||||
String storedFaviconUrl = Favicons.getFaviconUrlForPageUrl(mPageUrl);
|
||||
if (storedFaviconUrl != null && storedFaviconUrl.equals(mFaviconUrl)) {
|
||||
image = loadFaviconFromDb();
|
||||
if (image != null && image.getWidth() > 0 && image.getHeight() > 0)
|
||||
return ((mFlags & FLAG_SCALE) != 0) ? Favicons.scaleImage(image) : image;
|
||||
}
|
||||
|
||||
if (isCancelled())
|
||||
return null;
|
||||
Bitmap image;
|
||||
// Determine if there is already an ongoing task to fetch the Favicon we desire.
|
||||
// If there is, just join the queue and wait for it to finish. If not, we carry on.
|
||||
synchronized(loadsInFlight) {
|
||||
// Another load of the current Favicon is already underway
|
||||
LoadFaviconTask existingTask = loadsInFlight.get(mFaviconUrl);
|
||||
if (existingTask != null && !existingTask.isCancelled()) {
|
||||
existingTask.chainTasks(this);
|
||||
mIsChaining = true;
|
||||
|
||||
image = downloadFavicon(faviconURLToDownload);
|
||||
// If we are chaining, we want to keep the first task started to do this job as the one
|
||||
// in the hashmap so subsequent tasks will add themselves to its chaining list.
|
||||
return null;
|
||||
}
|
||||
|
||||
// We do not want to update the hashmap if the task has chained - other tasks need to
|
||||
// chain onto the same parent task.
|
||||
loadsInFlight.put(mFaviconUrl, this);
|
||||
}
|
||||
|
||||
if (isCancelled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
image = loadFaviconFromDb();
|
||||
if (image != null && image.getWidth() > 0 && image.getHeight() > 0) {
|
||||
return image;
|
||||
}
|
||||
|
||||
if (mOnlyFromLocal || isCancelled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
image = downloadFavicon(new URI(mFaviconUrl));
|
||||
} catch (URISyntaxException e) {
|
||||
Log.e(LOGTAG, "The provided favicon URL is not valid");
|
||||
return null;
|
||||
}
|
||||
|
||||
// If we're not already trying the default URL, try it now.
|
||||
if (image == null && !isUsingDefaultURL) {
|
||||
try {
|
||||
image = downloadFavicon(new URI(Favicons.guessDefaultFaviconURL(mPageUrl)));
|
||||
} catch (URISyntaxException e){
|
||||
// Not interesting. It was an educated guess, anyway.
|
||||
}
|
||||
}
|
||||
|
||||
if (image != null && image.getWidth() > 0 && image.getHeight() > 0) {
|
||||
saveFaviconToDb(image);
|
||||
image = ((mFlags & FLAG_SCALE) != 0) ? Favicons.scaleImage(image) : image;
|
||||
} else {
|
||||
image = null;
|
||||
Favicons.putFaviconInFailedCache(mFaviconUrl);
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(final Bitmap image) {
|
||||
protected void onPostExecute(Bitmap image) {
|
||||
if (mIsChaining) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Put what we got in the memcache.
|
||||
Favicons.putFaviconInMemCache(mFaviconUrl, image);
|
||||
|
||||
// Process the result, scale for the listener, etc.
|
||||
processResult(image);
|
||||
|
||||
synchronized (loadsInFlight) {
|
||||
// Prevent any other tasks from chaining on this one.
|
||||
loadsInFlight.remove(mFaviconUrl);
|
||||
}
|
||||
|
||||
// Since any update to mChainees is done while holding the loadsInFlight lock, once we reach
|
||||
// this point no further updates to that list can possibly take place (As far as other tasks
|
||||
// are concerned, there is no longer a task to chain from. The above block will have waited
|
||||
// for any tasks that were adding themselves to the list before reaching this point.)
|
||||
|
||||
// As such, I believe we're safe to do the following without holding the lock.
|
||||
// This is nice - we do not want to take the lock unless we have to anyway, and chaining rarely
|
||||
// actually happens outside of the strange situations unit tests create.
|
||||
|
||||
// Share the result with all chained tasks.
|
||||
if (mChainees != null) {
|
||||
for (LoadFaviconTask t : mChainees) {
|
||||
t.processResult(image);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processResult(Bitmap image) {
|
||||
Favicons.removeLoadTask(mId);
|
||||
Favicons.dispatchResult(mPageUrl, image, mListener);
|
||||
|
||||
Bitmap scaled = image;
|
||||
|
||||
// Notify listeners, scaling if required.
|
||||
if (mTargetWidth != -1 && image != null && image.getWidth() != mTargetWidth) {
|
||||
scaled = Favicons.getSizedFaviconFromCache(mFaviconUrl, mTargetWidth);
|
||||
}
|
||||
|
||||
Favicons.dispatchResult(mPageUrl, mFaviconUrl, scaled, mListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
Favicons.removeLoadTask(mId);
|
||||
|
||||
synchronized(loadsInFlight) {
|
||||
// Only remove from the hashmap if the task there is the one that's being canceled.
|
||||
// Cancellation of a task that would have chained is not interesting to the hashmap.
|
||||
final LoadFaviconTask primary = loadsInFlight.get(mFaviconUrl);
|
||||
if (primary == this) {
|
||||
loadsInFlight.remove(mFaviconUrl);
|
||||
return;
|
||||
}
|
||||
if (primary == null) {
|
||||
// This shouldn't happen.
|
||||
return;
|
||||
}
|
||||
if (primary.mChainees != null) {
|
||||
primary.mChainees.remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
// Note that we don't call the listener callback if the
|
||||
// favicon load is cancelled.
|
||||
}
|
||||
|
||||
/**
|
||||
* When the result of this job is ready, also notify the chainee of the result.
|
||||
* Used for aggregating concurrent requests for the same Favicon into a single actual request.
|
||||
* (Don't want to download a hundred instances of Google's Favicon at once, for example).
|
||||
* The loadsInFlight lock must be held when calling this function.
|
||||
*
|
||||
* @param aChainee LoadFaviconTask
|
||||
*/
|
||||
private void chainTasks(LoadFaviconTask aChainee) {
|
||||
if (mChainees == null) {
|
||||
mChainees = new LinkedList<LoadFaviconTask>();
|
||||
}
|
||||
|
||||
mChainees.add(aChainee);
|
||||
}
|
||||
|
||||
int getId() {
|
||||
return mId;
|
||||
}
|
||||
|
@ -10,5 +10,5 @@ import android.graphics.Bitmap;
|
||||
* Interface to be implemented by objects wishing to listen for favicon load completion events.
|
||||
*/
|
||||
public interface OnFaviconLoadedListener {
|
||||
void onFaviconLoaded(String url, Bitmap favicon);
|
||||
void onFaviconLoaded(String url, String faviconURL, Bitmap favicon);
|
||||
}
|
||||
|
636
mobile/android/base/favicons/cache/FaviconCache.java
vendored
Normal file
636
mobile/android/base/favicons/cache/FaviconCache.java
vendored
Normal file
@ -0,0 +1,636 @@
|
||||
/* 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/. */
|
||||
|
||||
package org.mozilla.gecko.favicons.cache;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.util.Log;
|
||||
import org.mozilla.gecko.favicons.Favicons;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Implements a Least-Recently-Used cache for Favicons, keyed by Favicon URL.
|
||||
*
|
||||
* When a favicon at a particular URL is decoded, it will yield one or more bitmaps.
|
||||
* While in memory, these bitmaps are stored in a list, sorted in ascending order of size, in a
|
||||
* FaviconsForURL object.
|
||||
* The collection of FaviconsForURL objects currently in the cache is stored in mBackingMap, keyed
|
||||
* by favicon URL.
|
||||
*
|
||||
* FaviconsForURL provides a method for obtaining the smallest icon larger than a given size - the
|
||||
* most appropriate icon for a particular size.
|
||||
* It also distinguishes between "primary" favicons (Ones that have merely been extracted from a
|
||||
* file downloaded from the website) and "secondary" favicons (Ones that have been computed locally
|
||||
* as resized versions of primary favicons.).
|
||||
*
|
||||
* FaviconsForURL is also responsible for storing URL-specific, as opposed to favicon-specific,
|
||||
* information. For the purposes of this cache, the simplifying assumption that the dominant colour
|
||||
* for all favicons served from a particular favicon URL shall be the same is made. (To violate this
|
||||
* would mandate serving an ICO or similar file with multiple radically different images in it - an
|
||||
* ill-advised and extremely uncommon use-case, for sure.)
|
||||
* The dominant colour information is updated as the element is being added to the cache - typically
|
||||
* on the background thread.
|
||||
* Also present here are the download timestamp and isFailed flag. Upon failure, the flag is set.
|
||||
* A constant exists in this file to specify the maximum time permitted between failures before
|
||||
* a retry is again permitted.
|
||||
*
|
||||
* TODO: Expiry of Favicons from the favicon database cache is not implemented. (Bug 914296)
|
||||
*
|
||||
* A typical request to the cache will consist of a Favicon URL and a target size. The FaviconsForURL
|
||||
* object for that URL will be obtained, queried for a favicon matching exactly the needed size, and
|
||||
* if successful, the result is returned.
|
||||
* If unsuccessful, the object is asked to return the smallest available primary favicon larger than
|
||||
* the target size. If this step works, the result is downscaled to create a new secondary favicon,
|
||||
* which is then stored (So subsequent requests will succeed at the first step) and returned.
|
||||
* If that step fails, the object finally walks backwards through its sequence of favicons until it
|
||||
* finds the largest primary favicon smaller than the target. This is then upscaled by a maximum of
|
||||
* 2x towards the target size, and the result cached and returned as above.
|
||||
*
|
||||
* The bitmaps themselves are encapsulated inside FaviconCacheElement objects. These objects contain,
|
||||
* as well as the bitmap, a pointer to the encapsulating FaviconsForURL object (Used by the LRU
|
||||
* culler), the size of the encapsulated image, a flag indicating if this is a primary favicon, and
|
||||
* a flag indicating if the entry is invalid.
|
||||
* All FaviconCacheElement objects are tracked in the mOrdering LinkedList. This is used to record
|
||||
* LRU information about FaviconCacheElements. In particular, the most recently used FaviconCacheElement
|
||||
* will be at the start of the list, the least recently used at the end of the list.
|
||||
*
|
||||
* When the cache runs out of space, it removes FaviconCacheElements starting from the end of the list
|
||||
* until a sufficient amount of space has been freed.
|
||||
* When a secondary favicon is removed in this way, it is simply deleted from its parent FaviconsForURLs
|
||||
* object's list of available favicons.
|
||||
* The backpointer field on the FaviconCacheElement is used to remove the element from the encapsulating
|
||||
* FaviconsForURL object, when this is required.
|
||||
* When a primary favicon is removed, its invalid flag is set to true and its bitmap payload is set
|
||||
* to null (So it is available for freeing by the garbage collector). This reduces the memory footprint
|
||||
* of the icon to essentially zero, but keeps track of which primary favicons exist for this favicon
|
||||
* URL.
|
||||
* If a subsequent request comes in for that favicon URL, it is then known that a primary of those
|
||||
* dimensions is available, just that it is not in the cache. The system is then able to load the
|
||||
* primary back into the cache from the database (Where the entirety of the initially encapsulating
|
||||
* container-formatted image file is stored).
|
||||
* If this were not done, then when processing requests after the culling of primary favicons it would
|
||||
* be impossible to distinguish between the nonexistence of a primary and the nonexistence of a primary
|
||||
* in the cache without querying the database.
|
||||
*
|
||||
* The implementation is safe to use from multiple threads and, while is it not entirely strongly
|
||||
* consistent all of the time, you almost certainly don't care.
|
||||
* The thread-safety implementation used is approximately MRSW with semaphores. An extra semaphore
|
||||
* is used to grant mutual exclusion over reordering operations from reader threads (Who thus gain
|
||||
* a quasi-writer status to do such limited mutation as is necessary).
|
||||
*
|
||||
* Reads which race with writes are liable to not see the ongoing write. The cache may return a
|
||||
* stale or now-removed value to the caller. Returned values are never invalid, even in the face
|
||||
* of concurrent reading and culling.
|
||||
*/
|
||||
public class FaviconCache {
|
||||
private static final String LOGTAG = "FaviconCache";
|
||||
|
||||
// The number of spaces to allocate for favicons in each node.
|
||||
private static final int NUM_FAVICON_SIZES = 4;
|
||||
|
||||
// Dimensions of the largest favicon to store in the cache. Everything is downscaled to this.
|
||||
public final int mMaxCachedWidth;
|
||||
|
||||
// Retry failed favicons after 20 minutes.
|
||||
public static final long FAILURE_RETRY_MILLISECONDS = 1000 * 60 * 20;
|
||||
|
||||
// Map relating Favicon URLs with objects representing decoded favicons.
|
||||
// Since favicons may be container formats holding multiple icons, the underlying type holds a
|
||||
// sorted list of bitmap payloads in ascending order of size. The underlying type may be queried
|
||||
// for the least larger payload currently present.
|
||||
private final ConcurrentHashMap<String, FaviconsForURL> mBackingMap = new ConcurrentHashMap<String, FaviconsForURL>();
|
||||
|
||||
// A linked list used to implement a queue, defining the LRU properties of the cache. Elements
|
||||
// contained within the various FaviconsForURL objects are held here, the least recently used
|
||||
// of which at the end of the list. When space needs to be reclaimed, the appropriate bitmap is
|
||||
// culled.
|
||||
private final LinkedList<FaviconCacheElement> mOrdering = new LinkedList<FaviconCacheElement>();
|
||||
|
||||
// The above structures, if used correctly, enable this cache to exhibit LRU semantics across all
|
||||
// favicon payloads in the system, as well as enabling the dynamic selection from the cache of
|
||||
// the primary bitmap most suited to the requested size (in cases where multiple primary bitmaps
|
||||
// are provided by the underlying file format).
|
||||
|
||||
// Current size, in bytes, of the bitmap data present in the cache.
|
||||
private final AtomicInteger mCurrentSize = new AtomicInteger(0);
|
||||
|
||||
// The maximum quantity, in bytes, of bitmap data which may be stored in the cache.
|
||||
private final int mMaxSizeBytes;
|
||||
|
||||
// Tracks the number of ongoing read operations. Enables the first one in to lock writers out and
|
||||
// the last one out to let them in.
|
||||
private final AtomicInteger mOngoingReads = new AtomicInteger(0);
|
||||
|
||||
// Used to ensure transaction fairness - each txn acquires and releases this as the first operation.
|
||||
// The effect is an orderly, inexpensive ordering enforced on txns to prevent writer starvation.
|
||||
private final Semaphore mTurnSemaphore = new Semaphore(1);
|
||||
|
||||
// A deviation from the usual MRSW solution - this semaphore is used to guard modification to the
|
||||
// ordering map. This allows for read transactions to update the most-recently-used value without
|
||||
// needing to take out the write lock.
|
||||
private final Semaphore mReorderingSemaphore = new Semaphore(1);
|
||||
|
||||
// The semaphore one must acquire in order to perform a write.
|
||||
private final Semaphore mWriteLock = new Semaphore(1);
|
||||
|
||||
/**
|
||||
* Called by txns performing only reads as they start. Prevents writer starvation with a turn
|
||||
* semaphore and locks writers out if this is the first concurrent reader txn starting up.
|
||||
*/
|
||||
private void startRead() {
|
||||
mTurnSemaphore.acquireUninterruptibly();
|
||||
mTurnSemaphore.release();
|
||||
|
||||
if (mOngoingReads.incrementAndGet() == 1) {
|
||||
// First one in. Wait for writers to finish and lock them out.
|
||||
mWriteLock.acquireUninterruptibly();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An alternative to startWrite to be used when in a read transaction and wanting to upgrade it
|
||||
* to a write transaction. Such a transaction should be terminated with finishWrite.
|
||||
*/
|
||||
private void upgradeReadToWrite() {
|
||||
mTurnSemaphore.acquireUninterruptibly();
|
||||
if (mOngoingReads.decrementAndGet() == 0) {
|
||||
mWriteLock.release();
|
||||
}
|
||||
mWriteLock.acquireUninterruptibly();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by transactions performing only reads as they finish. Ensures that if this is the last
|
||||
* concluding read transaction then then writers are subsequently allowed in.
|
||||
*/
|
||||
private void finishRead() {
|
||||
if (mOngoingReads.decrementAndGet() == 0) {
|
||||
mWriteLock.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by writer transactions upon start. Ensures fairness and then obtains the write lock.
|
||||
* Upon return, no other txns will be executing concurrently.
|
||||
*/
|
||||
private void startWrite() {
|
||||
mTurnSemaphore.acquireUninterruptibly();
|
||||
mWriteLock.acquireUninterruptibly();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by a concluding write transaction - unlocks the structure.
|
||||
*/
|
||||
private void finishWrite() {
|
||||
mTurnSemaphore.release();
|
||||
mWriteLock.release();
|
||||
}
|
||||
|
||||
public FaviconCache(int maxSize, int maxWidthToCache) {
|
||||
mMaxSizeBytes = maxSize;
|
||||
mMaxCachedWidth = maxWidthToCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the provided favicon URL is marked as a failure (Has failed to load before -
|
||||
* such icons get blacklisted for a time to prevent us endlessly retrying.)
|
||||
*
|
||||
* @param faviconURL Favicon URL to check if failed in memcache.
|
||||
* @return true if this favicon is blacklisted, false otherwise.
|
||||
*/
|
||||
public boolean isFailedFavicon(String faviconURL) {
|
||||
startRead();
|
||||
|
||||
boolean isExpired = false;
|
||||
boolean isAborting = false;
|
||||
|
||||
try {
|
||||
// If we don't have it in the cache, it certainly isn't a known failure.
|
||||
if (!mBackingMap.containsKey(faviconURL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FaviconsForURL container = mBackingMap.get(faviconURL);
|
||||
|
||||
// If the has failed flag is not set, it's certainly not a known failure.
|
||||
if (!container.mHasFailed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final long failureTimestamp = container.mDownloadTimestamp;
|
||||
|
||||
// Calculate elapsed time since the failing download.
|
||||
final long failureDiff = System.currentTimeMillis() - failureTimestamp;
|
||||
|
||||
// If long enough has passed, mark it as no longer a failure.
|
||||
if (failureDiff > FAILURE_RETRY_MILLISECONDS) {
|
||||
isExpired = true;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} catch (Exception unhandled) {
|
||||
// Handle any exception thrown and return the locks to a sensible state.
|
||||
finishRead();
|
||||
|
||||
// Flag to prevent finally from doubly-unlocking.
|
||||
isAborting = true;
|
||||
Log.e(LOGTAG, "FaviconCache exception!", unhandled);
|
||||
return false;
|
||||
} finally {
|
||||
if (!isAborting) {
|
||||
if (isExpired) {
|
||||
// No longer expired.
|
||||
upgradeReadToWrite();
|
||||
} else {
|
||||
finishRead();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
recordRemoved(mBackingMap.get(faviconURL));
|
||||
mBackingMap.remove(faviconURL);
|
||||
return false;
|
||||
} finally {
|
||||
finishWrite();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the indicated page URL as a failed Favicon until the provided time.
|
||||
*
|
||||
* @param faviconURL Page URL for which a Favicon load has failed.
|
||||
*/
|
||||
public void putFailed(String faviconURL) {
|
||||
startWrite();
|
||||
|
||||
if (mBackingMap.containsKey(faviconURL)) {
|
||||
recordRemoved(mBackingMap.get(faviconURL));
|
||||
}
|
||||
|
||||
FaviconsForURL container = new FaviconsForURL(0, true);
|
||||
mBackingMap.put(faviconURL, container);
|
||||
|
||||
finishWrite();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a Favicon for the given URL as close as possible to the size provided.
|
||||
* If an icon of the given size is already in the cache, it is returned.
|
||||
* If an icon of the given size is not in the cache but a larger unscaled image does exist in
|
||||
* the cache, we downscale the larger image to the target size and cache the result.
|
||||
* If there is no image of the required size, null is returned.
|
||||
*
|
||||
* @param faviconURL The URL for which a Favicon is desired. Must not be null.
|
||||
* @param targetSize The size of the desired favicon.
|
||||
* @return A favicon of the requested size for the requested URL, or null if none cached.
|
||||
*/
|
||||
public Bitmap getFaviconForDimensions(String faviconURL, int targetSize) {
|
||||
if (faviconURL == null) {
|
||||
Log.e(LOGTAG, "You passed a null faviconURL to getFaviconForDimensions. Don't.");
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean doingWrites = false;
|
||||
boolean shouldComputeColour = false;
|
||||
boolean isAborting = false;
|
||||
final Bitmap newBitmap;
|
||||
final FaviconsForURL container;
|
||||
|
||||
startRead();
|
||||
|
||||
try {
|
||||
if (!mBackingMap.containsKey(faviconURL)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
container = mBackingMap.get(faviconURL);
|
||||
|
||||
FaviconCacheElement cacheElement;
|
||||
|
||||
int cacheElementIndex = container.getNextHighestIndex(targetSize);
|
||||
|
||||
// cacheElementIndex now holds either the index of the next least largest bitmap from
|
||||
// targetSize, or -1 if targetSize > all bitmaps.
|
||||
if (cacheElementIndex != -1) {
|
||||
// If cacheElementIndex is not the sentinel value, then it is a valid index into mFavicons.
|
||||
cacheElement = container.mFavicons.get(cacheElementIndex);
|
||||
|
||||
if (cacheElement.mInvalidated) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If we found exactly what we wanted - we're done.
|
||||
if (cacheElement.mImageSize == targetSize) {
|
||||
setMostRecentlyUsed(cacheElement);
|
||||
return cacheElement.mFaviconPayload;
|
||||
}
|
||||
} else {
|
||||
// We requested an image larger than all primaries. Set the element to start the search
|
||||
// from to the element beyond the end of the array, so the search runs backwards.
|
||||
cacheElementIndex = container.mFavicons.size();
|
||||
}
|
||||
|
||||
// We did not find exactly what we wanted, but now have set cacheElementIndex to the index
|
||||
// where what we want should live in the list. We now request the next least larger primary
|
||||
// from the cache. We will downscale this to our target size.
|
||||
|
||||
// If there is no such primary, we'll upscale the next least smaller one instead.
|
||||
cacheElement = container.getNextPrimary(cacheElementIndex);
|
||||
|
||||
|
||||
if (cacheElement == null) {
|
||||
// The primary has been invalidated! Fail! Need to get it back from the database.
|
||||
return null;
|
||||
}
|
||||
|
||||
// Having got this far, we'll be needing to write the new secondary to the cache, which
|
||||
// involves us falling through to the next try block. This flag lets us do this (Other
|
||||
// paths prior to this end in returns.)
|
||||
doingWrites = true;
|
||||
|
||||
// Scaling logic...
|
||||
Bitmap largestElementBitmap = cacheElement.mFaviconPayload;
|
||||
int largestSize = cacheElement.mImageSize;
|
||||
|
||||
if (largestSize >= targetSize) {
|
||||
// The largest we have is larger than the target - downsize to target.
|
||||
newBitmap = Bitmap.createScaledBitmap(largestElementBitmap, targetSize, targetSize, true);
|
||||
} else {
|
||||
// Our largest primary is smaller than the desired size. Upscale by a maximum of 2x.
|
||||
// largestSize now reflects the maximum size we can upscale to.
|
||||
largestSize *= 2;
|
||||
|
||||
if (largestSize >= targetSize) {
|
||||
// Perfect! We can upscale by less than 2x and reach the needed size. Do it.
|
||||
newBitmap = Bitmap.createScaledBitmap(largestElementBitmap, targetSize, targetSize, true);
|
||||
} else {
|
||||
// We don't have enough information to make the target size look nonterrible. Best effort:
|
||||
newBitmap = Bitmap.createScaledBitmap(largestElementBitmap, largestSize, largestSize, true);
|
||||
|
||||
shouldComputeColour = true;
|
||||
}
|
||||
}
|
||||
} catch (Exception unhandled) {
|
||||
isAborting = true;
|
||||
|
||||
// Handle any exception thrown and return the locks to a sensible state.
|
||||
finishRead();
|
||||
|
||||
// Flag to prevent finally from doubly-unlocking.
|
||||
Log.e(LOGTAG, "FaviconCache exception!", unhandled);
|
||||
return null;
|
||||
} finally {
|
||||
if (!isAborting) {
|
||||
if (doingWrites) {
|
||||
upgradeReadToWrite();
|
||||
} else {
|
||||
finishRead();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (shouldComputeColour) {
|
||||
// And since we failed, we'll need the dominant colour.
|
||||
container.ensureDominantColor();
|
||||
}
|
||||
|
||||
// While the image might not actually BE that size, we set the size field to the target
|
||||
// because this is the best image you can get for a request of that size using the Favicon
|
||||
// information provided by this website.
|
||||
// This way, subsequent requests hit straight away.
|
||||
FaviconCacheElement newElement = container.addSecondary(newBitmap, targetSize);
|
||||
|
||||
setMostRecentlyUsed(newElement);
|
||||
|
||||
mCurrentSize.addAndGet(newElement.sizeOf());
|
||||
} finally {
|
||||
finishWrite();
|
||||
}
|
||||
|
||||
return newBitmap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the cache for the dominant colour stored for the Favicon URL provided, if any.
|
||||
*
|
||||
* @param key The URL of the Favicon for which a dominant colour is desired.
|
||||
* @return The cached dominant colour, or null if none is cached.
|
||||
*/
|
||||
public int getDominantColor(String key) {
|
||||
startRead();
|
||||
|
||||
try {
|
||||
if (!mBackingMap.containsKey(key)) {
|
||||
Log.w(LOGTAG, "Cannot compute dominant color of non-cached favicon " + key);
|
||||
finishRead();
|
||||
return 0xFFFFFF;
|
||||
}
|
||||
|
||||
FaviconsForURL element = mBackingMap.get(key);
|
||||
|
||||
return element.ensureDominantColor();
|
||||
} finally {
|
||||
finishRead();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all payloads stored in the given container from the LRU cache. Must be called while
|
||||
* holding the write lock.
|
||||
*
|
||||
* @param wasRemoved The container to purge from the cache.
|
||||
*/
|
||||
private void recordRemoved(FaviconsForURL wasRemoved) {
|
||||
// If there was an existing value, strip it from the insertion-order cache.
|
||||
if (wasRemoved == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int sizeRemoved = 0;
|
||||
|
||||
for (FaviconCacheElement e : wasRemoved.mFavicons) {
|
||||
sizeRemoved += e.sizeOf();
|
||||
mOrdering.remove(e);
|
||||
}
|
||||
|
||||
mCurrentSize.addAndGet(-sizeRemoved);
|
||||
}
|
||||
|
||||
private Bitmap produceCacheableBitmap(Bitmap favicon) {
|
||||
// Never cache the default Favicon, or the null Favicon.
|
||||
if (favicon == Favicons.sDefaultFavicon || favicon == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Some sites serve up insanely huge Favicons (Seen 512x512 ones...)
|
||||
// While we want to cache nice big icons, we apply a limit based on screen density for the
|
||||
// sake of space.
|
||||
if (favicon.getWidth() > mMaxCachedWidth) {
|
||||
return Bitmap.createScaledBitmap(favicon, mMaxCachedWidth, mMaxCachedWidth, true);
|
||||
}
|
||||
return favicon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an existing element as the most recently used element. May be called from either type of
|
||||
* transaction.
|
||||
*
|
||||
* @param element The element that is to become the most recently used one.
|
||||
*/
|
||||
private void setMostRecentlyUsed(FaviconCacheElement element) {
|
||||
mReorderingSemaphore.acquireUninterruptibly();
|
||||
mOrdering.remove(element);
|
||||
mOrdering.offer(element);
|
||||
mReorderingSemaphore.release();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the provided bitmap to the cache as the only available primary for this URL.
|
||||
* Should never be called with scaled Favicons. The input is assumed to be an unscaled Favicon.
|
||||
*
|
||||
* @param faviconURL The URL of the Favicon being stored.
|
||||
* @param aFavicon The Favicon to store.
|
||||
*/
|
||||
public void putSingleFavicon(String faviconURL, Bitmap aFavicon) {
|
||||
Bitmap favicon = produceCacheableBitmap(aFavicon);
|
||||
if (favicon == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a fresh container for the favicons associated with this URL. Allocate extra slots
|
||||
// in the underlying ArrayList in case multiple secondary favicons are later created.
|
||||
// Currently set to the number of favicon sizes used in the UI, plus 1, at time of writing.
|
||||
// Ought to be tuned as things change for maximal performance.
|
||||
FaviconsForURL toInsert = new FaviconsForURL(NUM_FAVICON_SIZES);
|
||||
|
||||
// Create the cache element for the single element we are inserting, and configure it.
|
||||
FaviconCacheElement newElement = toInsert.addPrimary(favicon);
|
||||
|
||||
startWrite();
|
||||
try {
|
||||
// Set the new element as the most recently used one.
|
||||
setMostRecentlyUsed(newElement);
|
||||
|
||||
mCurrentSize.addAndGet(newElement.sizeOf());
|
||||
|
||||
// Update the value in the LruCache...
|
||||
FaviconsForURL wasRemoved;
|
||||
wasRemoved = mBackingMap.put(faviconURL, toInsert);
|
||||
|
||||
recordRemoved(wasRemoved);
|
||||
} finally {
|
||||
finishWrite();
|
||||
}
|
||||
|
||||
cullIfRequired();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the collection of primary favicons for the given URL to the provided collection of bitmaps.
|
||||
*
|
||||
* @param faviconURL The URL from which the favicons originate.
|
||||
* @param favicons A List of favicons decoded from this URL.
|
||||
*/
|
||||
public void putFavicons(String faviconURL, Iterator<Bitmap> favicons) {
|
||||
// We don't know how many icons we'll have - let's just take a guess.
|
||||
FaviconsForURL toInsert = new FaviconsForURL(5 * NUM_FAVICON_SIZES);
|
||||
int sizeGained = 0;
|
||||
|
||||
while (favicons.hasNext()) {
|
||||
Bitmap favicon = produceCacheableBitmap(favicons.next());
|
||||
if (favicon == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
FaviconCacheElement newElement = toInsert.addPrimary(favicon);
|
||||
sizeGained += newElement.sizeOf();
|
||||
}
|
||||
|
||||
startRead();
|
||||
|
||||
boolean abortingRead = false;
|
||||
|
||||
// Not using setMostRecentlyUsed, because the elements are known to be new. This can be done
|
||||
// without taking the write lock, via the magic of the reordering semaphore.
|
||||
mReorderingSemaphore.acquireUninterruptibly();
|
||||
try {
|
||||
for (FaviconCacheElement newElement : toInsert.mFavicons) {
|
||||
mOrdering.offer(newElement);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
abortingRead = true;
|
||||
mReorderingSemaphore.release();
|
||||
finishRead();
|
||||
|
||||
Log.e(LOGTAG, "Favicon cache exception!", e);
|
||||
return;
|
||||
} finally {
|
||||
if (!abortingRead) {
|
||||
mReorderingSemaphore.release();
|
||||
upgradeReadToWrite();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
mCurrentSize.addAndGet(sizeGained);
|
||||
|
||||
// Update the value in the LruCache...
|
||||
recordRemoved(mBackingMap.put(faviconURL, toInsert));
|
||||
} finally {
|
||||
finishWrite();
|
||||
}
|
||||
|
||||
cullIfRequired();
|
||||
}
|
||||
|
||||
/**
|
||||
* If cache too large, drop stuff from the cache to get the size back into the acceptable range.
|
||||
* Otherwise, do nothing.
|
||||
*/
|
||||
private void cullIfRequired() {
|
||||
Log.d(LOGTAG, "Favicon cache fullness: " + mCurrentSize.get() + '/' + mMaxSizeBytes);
|
||||
|
||||
if (mCurrentSize.get() <= mMaxSizeBytes) {
|
||||
return;
|
||||
}
|
||||
|
||||
startWrite();
|
||||
try {
|
||||
while (mCurrentSize.get() > mMaxSizeBytes) {
|
||||
// Cull the least recently used element.
|
||||
|
||||
FaviconCacheElement victim;
|
||||
victim = mOrdering.poll();
|
||||
|
||||
mCurrentSize.addAndGet(-victim.sizeOf());
|
||||
victim.onEvictedFromCache();
|
||||
|
||||
Log.d(LOGTAG, "After cull: " + mCurrentSize.get() + '/' + mMaxSizeBytes);
|
||||
}
|
||||
} finally {
|
||||
finishWrite();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge all elements from the FaviconCache. Handy if you want to reclaim some memory.
|
||||
*/
|
||||
public void evictAll() {
|
||||
startWrite();
|
||||
|
||||
try {
|
||||
mBackingMap.clear();
|
||||
mOrdering.clear();
|
||||
} finally {
|
||||
finishWrite();
|
||||
}
|
||||
}
|
||||
}
|
115
mobile/android/base/favicons/cache/FaviconCacheElement.java
vendored
Normal file
115
mobile/android/base/favicons/cache/FaviconCacheElement.java
vendored
Normal file
@ -0,0 +1,115 @@
|
||||
/* 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/. */
|
||||
|
||||
package org.mozilla.gecko.favicons.cache;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
/**
|
||||
* Objects stored in the Favicon cache - allow for the bitmap to be tagged to indicate if it has
|
||||
* been scaled. Unscaled bitmaps are not included in the scaled-bitmap cache's size calculation.
|
||||
*/
|
||||
public class FaviconCacheElement implements Comparable<FaviconCacheElement> {
|
||||
// Was this Favicon computed via scaling another primary Favicon, or is this a primary Favicon?
|
||||
final boolean mIsPrimary;
|
||||
|
||||
// The Favicon bitmap.
|
||||
Bitmap mFaviconPayload;
|
||||
|
||||
// If set, mFaviconPayload is absent. Since the underlying ICO may contain multiple primary
|
||||
// payloads, primary payloads are never truly deleted from the cache, but instead have their
|
||||
// payload deleted and this flag set on their FaviconCacheElement. That way, the cache always
|
||||
// has a record of the existence of a primary payload, even if it is no longer in the cache.
|
||||
// This means that when a request comes in that will be best served using a primary that is in
|
||||
// the database but no longer cached, we know that it exists and can go get it (Useful when ICO
|
||||
// support is added).
|
||||
volatile boolean mInvalidated;
|
||||
|
||||
final int mImageSize;
|
||||
|
||||
// Used for LRU pruning.
|
||||
final FaviconsForURL mBackpointer;
|
||||
|
||||
public FaviconCacheElement(Bitmap payload, boolean isPrimary, int imageSize, FaviconsForURL backpointer) {
|
||||
mFaviconPayload = payload;
|
||||
mIsPrimary = isPrimary;
|
||||
mImageSize = imageSize;
|
||||
mBackpointer = backpointer;
|
||||
}
|
||||
|
||||
public FaviconCacheElement(Bitmap payload, boolean isPrimary, FaviconsForURL backpointer) {
|
||||
mFaviconPayload = payload;
|
||||
mIsPrimary = isPrimary;
|
||||
mBackpointer = backpointer;
|
||||
|
||||
if (payload != null) {
|
||||
mImageSize = payload.getWidth();
|
||||
} else {
|
||||
mImageSize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public int sizeOf() {
|
||||
if (mInvalidated) {
|
||||
return 0;
|
||||
}
|
||||
return mFaviconPayload.getRowBytes() * mFaviconPayload.getHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establish an ordering on FaviconCacheElements based on size and validity. An element is
|
||||
* considered "greater than" another if it is valid and the other is not, or if it contains a
|
||||
* larger payload.
|
||||
*
|
||||
* @param another The FaviconCacheElement to compare to this one.
|
||||
* @return -1 if this element is less than the given one, 1 if the other one is larger than this
|
||||
* and 0 if both are of equal value.
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(FaviconCacheElement another) {
|
||||
if (mInvalidated && !another.mInvalidated) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!mInvalidated && another.mInvalidated) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (mInvalidated) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
final int w1 = mImageSize;
|
||||
final int w2 = another.mImageSize;
|
||||
if (w1 > w2) {
|
||||
return 1;
|
||||
} else if (w2 > w1) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this element is evicted from the cache.
|
||||
*
|
||||
* If primary, drop the payload and set invalid. If secondary, just unlink from parent node.
|
||||
*/
|
||||
public void onEvictedFromCache() {
|
||||
if (mIsPrimary) {
|
||||
// So we keep a record of which primaries exist in the database for this URL, we
|
||||
// don't actually delete the entry for primaries. Instead, we delete their payload
|
||||
// and flag them as invalid. This way, we can later figure out that what a request
|
||||
// really want is one of the primaries that have been dropped from the cache, and we
|
||||
// can go get it.
|
||||
mInvalidated = true;
|
||||
mFaviconPayload = null;
|
||||
} else {
|
||||
// Secondaries don't matter - just delete them.
|
||||
if (mBackpointer == null) {
|
||||
return;
|
||||
}
|
||||
mBackpointer.mFavicons.remove(this);
|
||||
}
|
||||
}
|
||||
}
|
146
mobile/android/base/favicons/cache/FaviconsForURL.java
vendored
Normal file
146
mobile/android/base/favicons/cache/FaviconsForURL.java
vendored
Normal file
@ -0,0 +1,146 @@
|
||||
/* 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/. */
|
||||
|
||||
package org.mozilla.gecko.favicons.cache;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.util.Log;
|
||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
public class FaviconsForURL {
|
||||
private static final String LOGTAG = "FaviconForURL";
|
||||
|
||||
private volatile int mDominantColor = -1;
|
||||
|
||||
final long mDownloadTimestamp;
|
||||
final ArrayList<FaviconCacheElement> mFavicons;
|
||||
|
||||
public final boolean mHasFailed;
|
||||
|
||||
public FaviconsForURL(int size) {
|
||||
this(size, false);
|
||||
}
|
||||
|
||||
public FaviconsForURL(int size, boolean hasFailed) {
|
||||
mHasFailed = hasFailed;
|
||||
mDownloadTimestamp = System.currentTimeMillis();
|
||||
mFavicons = new ArrayList<FaviconCacheElement>(size);
|
||||
}
|
||||
|
||||
public FaviconCacheElement addSecondary(Bitmap favicon, int imageSize) {
|
||||
return addInternal(favicon, false, imageSize);
|
||||
}
|
||||
|
||||
public FaviconCacheElement addPrimary(Bitmap favicon) {
|
||||
return addInternal(favicon, true, favicon.getWidth());
|
||||
}
|
||||
|
||||
private FaviconCacheElement addInternal(Bitmap favicon, boolean isPrimary, int imageSize) {
|
||||
FaviconCacheElement c = new FaviconCacheElement(favicon, isPrimary, imageSize, this);
|
||||
|
||||
int index = Collections.binarySearch(mFavicons, c);
|
||||
if (index < 0) {
|
||||
index = 0;
|
||||
}
|
||||
mFavicons.add(index, c);
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index of the smallest image in this collection larger than or equal to
|
||||
* the given target size.
|
||||
*
|
||||
* @param targetSize Minimum size for the desired result.
|
||||
* @return The index of the smallest image larger than the target size, or -1 if none exists.
|
||||
*/
|
||||
public int getNextHighestIndex(int targetSize) {
|
||||
// Create a dummy object to hold the target value for comparable.
|
||||
FaviconCacheElement dummy = new FaviconCacheElement(null, false, targetSize, null);
|
||||
|
||||
int index = Collections.binarySearch(mFavicons, dummy);
|
||||
|
||||
// The search routine returns the index of an element equal to dummy, if present.
|
||||
// Otherwise, it returns -x - 1, where x is the index in the ArrayList where dummy would be
|
||||
// inserted if the list were to remain sorted.
|
||||
if (index < 0) {
|
||||
index++;
|
||||
index = -index;
|
||||
}
|
||||
|
||||
// index is now 'x', as described above.
|
||||
|
||||
// The routine will return mFavicons.size() as the index iff dummy is larger than all elements
|
||||
// present (So the "index at which it should be inserted" is the index after the end.
|
||||
// In this case, we set the sentinel value -1 to indicate that we just requested something
|
||||
// larger than all primaries.
|
||||
if (index == mFavicons.size()) {
|
||||
index = -1;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next valid primary icon from this collection, starting at the given index.
|
||||
* If the appropriate icon is found, but is invalid, we return null - the proper response is to
|
||||
* reacquire the primary from the database.
|
||||
* If no icon is found, the search is repeated going backwards from the start index to find any
|
||||
* primary at all (The input index may be a secondary which is larger than the actual available
|
||||
* primary.)
|
||||
*
|
||||
* @param fromIndex The index into mFavicons from which to start the search.
|
||||
* @return The FaviconCacheElement of the next valid primary from the given index. If none exists,
|
||||
* then returns the previous valid primary. If none exists, returns null (Insanity.).
|
||||
*/
|
||||
public FaviconCacheElement getNextPrimary(final int fromIndex) {
|
||||
final int numIcons = mFavicons.size();
|
||||
|
||||
int searchIndex = fromIndex;
|
||||
while (searchIndex < numIcons) {
|
||||
FaviconCacheElement element = mFavicons.get(searchIndex);
|
||||
|
||||
if (element.mIsPrimary) {
|
||||
if (element.mInvalidated) {
|
||||
// TODO: Replace with `return null` when ICO decoder is introduced.
|
||||
break;
|
||||
}
|
||||
return element;
|
||||
}
|
||||
searchIndex++;
|
||||
}
|
||||
|
||||
// No larger primary available. Let's look for smaller ones...
|
||||
searchIndex = fromIndex - 1;
|
||||
while (searchIndex >= 0) {
|
||||
FaviconCacheElement element = mFavicons.get(searchIndex);
|
||||
|
||||
if (element.mIsPrimary) {
|
||||
if (element.mInvalidated) {
|
||||
return null;
|
||||
}
|
||||
return element;
|
||||
}
|
||||
searchIndex--;
|
||||
}
|
||||
|
||||
Log.e(LOGTAG, "No primaries found in Favicon cache structure. This is madness!");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the dominant colour field is populated for this favicon.
|
||||
*/
|
||||
public int ensureDominantColor() {
|
||||
if (mDominantColor == -1) {
|
||||
mDominantColor = BitmapUtils.getDominantColor(getNextPrimary(0).mFaviconPayload);
|
||||
}
|
||||
|
||||
return mDominantColor;
|
||||
}
|
||||
}
|
@ -416,7 +416,7 @@ public class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
|
||||
/* This is invoked by JNI on the gecko thread */
|
||||
DisplayPortMetrics getDisplayPort(boolean pageSizeUpdate, boolean isBrowserContentDisplayed, int tabId, ImmutableViewportMetrics metrics) {
|
||||
Tabs tabs = Tabs.getInstance();
|
||||
if (tabs.isSelectedTab(tabs.getTab(tabId)) && isBrowserContentDisplayed) {
|
||||
if (isBrowserContentDisplayed && tabs.isSelectedTabId(tabId)) {
|
||||
// for foreground tabs, send the viewport update unless the document
|
||||
// displayed is different from the content document. In that case, just
|
||||
// calculate the display port.
|
||||
|
@ -5,29 +5,19 @@
|
||||
|
||||
package org.mozilla.gecko.home;
|
||||
|
||||
import org.mozilla.gecko.favicons.Favicons;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Tabs;
|
||||
import org.mozilla.gecko.db.BrowserContract.Bookmarks;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.db.BrowserDB.URLColumns;
|
||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
import org.mozilla.gecko.home.BookmarksListAdapter.OnRefreshFolderListener;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
@ -16,7 +16,6 @@ import org.mozilla.gecko.ReaderModeUtils;
|
||||
import org.mozilla.gecko.Tabs;
|
||||
import org.mozilla.gecko.db.BrowserContract.Combined;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
import org.mozilla.gecko.home.HomeListView.HomeContextMenuInfo;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
import org.mozilla.gecko.util.UiAsyncTask;
|
||||
@ -114,7 +113,7 @@ abstract class HomeFragment extends Fragment {
|
||||
return false;
|
||||
}
|
||||
|
||||
HomeContextMenuInfo info = (HomeContextMenuInfo) menuInfo;
|
||||
final HomeContextMenuInfo info = (HomeContextMenuInfo) menuInfo;
|
||||
final Context context = getActivity().getApplicationContext();
|
||||
|
||||
final int itemId = item.getItemId();
|
||||
@ -133,7 +132,14 @@ abstract class HomeFragment extends Fragment {
|
||||
return false;
|
||||
}
|
||||
|
||||
new AddToLauncherTask(info.url, info.getDisplayTitle()).execute();
|
||||
// Fetch the largest cacheable icon size.
|
||||
Favicons.getLargestFaviconForPage(info.url, new OnFaviconLoadedListener() {
|
||||
@Override
|
||||
public void onFaviconLoaded(String url, String faviconURL, Bitmap favicon) {
|
||||
GeckoAppShell.createShortcut(info.getDisplayTitle(), info.url, favicon, "");
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -219,35 +225,6 @@ abstract class HomeFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
private static class AddToLauncherTask extends UiAsyncTask<Void, Void, String> {
|
||||
private final String mUrl;
|
||||
private final String mTitle;
|
||||
|
||||
public AddToLauncherTask(String url, String title) {
|
||||
super(ThreadUtils.getBackgroundHandler());
|
||||
|
||||
mUrl = url;
|
||||
mTitle = title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String doInBackground(Void... params) {
|
||||
return Favicons.getFaviconUrlForPageUrl(mUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostExecute(String faviconUrl) {
|
||||
OnFaviconLoadedListener listener = new OnFaviconLoadedListener() {
|
||||
@Override
|
||||
public void onFaviconLoaded(String url, Bitmap favicon) {
|
||||
GeckoAppShell.createShortcut(mTitle, mUrl, favicon, "");
|
||||
}
|
||||
};
|
||||
|
||||
Favicons.loadFavicon(mUrl, faviconUrl, 0, listener);
|
||||
}
|
||||
}
|
||||
|
||||
private static class RemoveBookmarkTask extends UiAsyncTask<Void, Void, Void> {
|
||||
private final Context mContext;
|
||||
private final int mId;
|
||||
|
@ -37,12 +37,16 @@ public class TopSitesGridItemView extends RelativeLayout {
|
||||
// Data backing this view.
|
||||
private String mTitle;
|
||||
private String mUrl;
|
||||
private String mFaviconURL;
|
||||
|
||||
private Bitmap mThumbnail;
|
||||
|
||||
// Pinned state.
|
||||
private boolean mIsPinned = false;
|
||||
|
||||
// Empty state.
|
||||
private boolean mIsEmpty = true;
|
||||
private int mLoadId = Favicons.NOT_LOADING;
|
||||
|
||||
public TopSitesGridItemView(Context context) {
|
||||
this(context, null);
|
||||
@ -150,6 +154,8 @@ public class TopSitesGridItemView extends RelativeLayout {
|
||||
displayThumbnail(R.drawable.favicon);
|
||||
return;
|
||||
}
|
||||
mThumbnail = thumbnail;
|
||||
Favicons.cancelFaviconLoad(mLoadId);
|
||||
|
||||
mThumbnailView.setScaleType(ScaleType.CENTER_CROP);
|
||||
mThumbnailView.setImageBitmap(thumbnail);
|
||||
@ -161,16 +167,27 @@ public class TopSitesGridItemView extends RelativeLayout {
|
||||
*
|
||||
* @param favicon The favicon to show as thumbnail.
|
||||
*/
|
||||
public void displayFavicon(Bitmap favicon) {
|
||||
public void displayFavicon(Bitmap favicon, String faviconURL) {
|
||||
if (mThumbnail != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (favicon == null) {
|
||||
// Should show default favicon.
|
||||
displayThumbnail(R.drawable.favicon);
|
||||
return;
|
||||
}
|
||||
|
||||
if (faviconURL != null) {
|
||||
mFaviconURL = faviconURL;
|
||||
}
|
||||
|
||||
mThumbnailView.setScaleType(ScaleType.CENTER);
|
||||
mThumbnailView.setImageBitmap(favicon);
|
||||
mThumbnailView.setBackgroundColor(Favicons.getFaviconColor(favicon, mUrl));
|
||||
|
||||
if (mFaviconURL != null) {
|
||||
mThumbnailView.setBackgroundColor(Favicons.getFaviconColor(mFaviconURL));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -190,4 +207,9 @@ public class TopSitesGridItemView extends RelativeLayout {
|
||||
// Refresh for state change.
|
||||
refreshDrawableState();
|
||||
}
|
||||
|
||||
public void setLoadId(int aLoadId) {
|
||||
Favicons.cancelFaviconLoad(mLoadId);
|
||||
mLoadId = aLoadId;
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import org.mozilla.gecko.db.BrowserContract.Thumbnails;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.db.BrowserDB.URLColumns;
|
||||
import org.mozilla.gecko.db.BrowserDB.TopSitesCursorWrapper;
|
||||
import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
|
||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
import org.mozilla.gecko.home.HomeListView.HomeContextMenuInfo;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
@ -32,7 +33,6 @@ import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
@ -112,22 +112,6 @@ public class TopSitesPage extends HomeFragment {
|
||||
// Time in ms until the Gecko thread is reset to normal priority.
|
||||
private static final long PRIORITY_RESET_TIMEOUT = 10000;
|
||||
|
||||
/**
|
||||
* Class to hold the bitmap of cached thumbnails/favicons.
|
||||
*/
|
||||
public static class Thumbnail {
|
||||
// Thumbnail or favicon.
|
||||
private final boolean isThumbnail;
|
||||
|
||||
// Bitmap of thumbnail/favicon.
|
||||
private final Bitmap bitmap;
|
||||
|
||||
public Thumbnail(Bitmap bitmap, boolean isThumbnail) {
|
||||
this.bitmap = bitmap;
|
||||
this.isThumbnail = isThumbnail;
|
||||
}
|
||||
}
|
||||
|
||||
public static TopSitesPage newInstance() {
|
||||
return new TopSitesPage();
|
||||
}
|
||||
@ -531,7 +515,7 @@ public class TopSitesPage extends HomeFragment {
|
||||
|
||||
public class TopSitesGridAdapter extends CursorAdapter {
|
||||
// Cache to store the thumbnails.
|
||||
private Map<String, Thumbnail> mThumbnails;
|
||||
private Map<String, Bitmap> mThumbnails;
|
||||
|
||||
public TopSitesGridAdapter(Context context, Cursor cursor) {
|
||||
super(context, cursor);
|
||||
@ -554,7 +538,7 @@ public class TopSitesPage extends HomeFragment {
|
||||
*
|
||||
* @param thumbnails A map of urls and their thumbnail bitmaps.
|
||||
*/
|
||||
public void updateThumbnails(Map<String, Thumbnail> thumbnails) {
|
||||
public void updateThumbnails(Map<String, Bitmap> thumbnails) {
|
||||
mThumbnails = thumbnails;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
@ -572,7 +556,7 @@ public class TopSitesPage extends HomeFragment {
|
||||
pinned = ((TopSitesCursorWrapper) cursor).isPinned();
|
||||
}
|
||||
|
||||
TopSitesGridItemView view = (TopSitesGridItemView) bindView;
|
||||
final TopSitesGridItemView view = (TopSitesGridItemView) bindView;
|
||||
view.setTitle(title);
|
||||
view.setUrl(url);
|
||||
view.setPinned(pinned);
|
||||
@ -581,14 +565,18 @@ public class TopSitesPage extends HomeFragment {
|
||||
if (TextUtils.isEmpty(url)) {
|
||||
view.displayThumbnail(R.drawable.top_site_add);
|
||||
} else {
|
||||
// Show the thumbnail.
|
||||
Thumbnail thumbnail = (mThumbnails != null ? mThumbnails.get(url) : null);
|
||||
if (thumbnail == null) {
|
||||
view.displayThumbnail(null);
|
||||
} else if (thumbnail.isThumbnail) {
|
||||
view.displayThumbnail(thumbnail.bitmap);
|
||||
// Show the thumbnail, if any.
|
||||
Bitmap thumbnail = (mThumbnails != null ? mThumbnails.get(url) : null);
|
||||
if (thumbnail != null) {
|
||||
view.displayThumbnail(thumbnail);
|
||||
} else {
|
||||
view.displayFavicon(thumbnail.bitmap);
|
||||
// If we have no thumbnail, attempt to show a Favicon instead.
|
||||
view.setLoadId(Favicons.getSizedFaviconForPageFromLocal(url, new OnFaviconLoadedListener() {
|
||||
@Override
|
||||
public void onFaviconLoaded(String url, String faviconURL, Bitmap favicon) {
|
||||
view.displayFavicon(favicon, faviconURL);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -665,8 +653,8 @@ public class TopSitesPage extends HomeFragment {
|
||||
/**
|
||||
* An AsyncTaskLoader to load the thumbnails from a cursor.
|
||||
*/
|
||||
private static class ThumbnailsLoader extends AsyncTaskLoader<Map<String, Thumbnail>> {
|
||||
private Map<String, Thumbnail> mThumbnails;
|
||||
private static class ThumbnailsLoader extends AsyncTaskLoader<Map<String, Bitmap>> {
|
||||
private Map<String, Bitmap> mThumbnails;
|
||||
private ArrayList<String> mUrls;
|
||||
|
||||
public ThumbnailsLoader(Context context, ArrayList<String> urls) {
|
||||
@ -675,7 +663,7 @@ public class TopSitesPage extends HomeFragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Thumbnail> loadInBackground() {
|
||||
public Map<String, Bitmap> loadInBackground() {
|
||||
if (mUrls == null || mUrls.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
@ -688,7 +676,7 @@ public class TopSitesPage extends HomeFragment {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Map<String, Thumbnail> thumbnails = new HashMap<String, Thumbnail>();
|
||||
final Map<String, Bitmap> thumbnails = new HashMap<String, Bitmap>();
|
||||
|
||||
try {
|
||||
final int urlIndex = cursor.getColumnIndexOrThrow(Thumbnails.URL);
|
||||
@ -713,29 +701,17 @@ public class TopSitesPage extends HomeFragment {
|
||||
break;
|
||||
}
|
||||
|
||||
thumbnails.put(url, new Thumbnail(bitmap, true));
|
||||
thumbnails.put(url, bitmap);
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
// Query the DB for favicons for the urls without thumbnails.
|
||||
for (String url : mUrls) {
|
||||
if (!thumbnails.containsKey(url)) {
|
||||
final Bitmap bitmap = BrowserDB.getFaviconForUrl(cr, url);
|
||||
if (bitmap != null) {
|
||||
// Favicons.scaleImage can return several different size favicons,
|
||||
// but will at least prevent this from being too large.
|
||||
thumbnails.put(url, new Thumbnail(Favicons.scaleImage(bitmap), false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return thumbnails;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deliverResult(Map<String, Thumbnail> thumbnails) {
|
||||
public void deliverResult(Map<String, Bitmap> thumbnails) {
|
||||
if (isReset()) {
|
||||
mThumbnails = null;
|
||||
return;
|
||||
@ -765,7 +741,7 @@ public class TopSitesPage extends HomeFragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled(Map<String, Thumbnail> thumbnails) {
|
||||
public void onCanceled(Map<String, Bitmap> thumbnails) {
|
||||
mThumbnails = null;
|
||||
}
|
||||
|
||||
@ -783,14 +759,14 @@ public class TopSitesPage extends HomeFragment {
|
||||
/**
|
||||
* Loader callbacks for the thumbnails on TopSitesGridView.
|
||||
*/
|
||||
private class ThumbnailsLoaderCallbacks implements LoaderCallbacks<Map<String, Thumbnail>> {
|
||||
private class ThumbnailsLoaderCallbacks implements LoaderCallbacks<Map<String, Bitmap>> {
|
||||
@Override
|
||||
public Loader<Map<String, Thumbnail>> onCreateLoader(int id, Bundle args) {
|
||||
public Loader<Map<String, Bitmap>> onCreateLoader(int id, Bundle args) {
|
||||
return new ThumbnailsLoader(getActivity(), args.getStringArrayList(THUMBNAILS_URLS_KEY));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Map<String, Thumbnail>> loader, Map<String, Thumbnail> thumbnails) {
|
||||
public void onLoadFinished(Loader<Map<String, Bitmap>> loader, Map<String, Bitmap> thumbnails) {
|
||||
if (mGridAdapter != null) {
|
||||
mGridAdapter.updateThumbnails(thumbnails);
|
||||
}
|
||||
@ -801,7 +777,7 @@ public class TopSitesPage extends HomeFragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Map<String, Thumbnail>> loader) {
|
||||
public void onLoaderReset(Loader<Map<String, Bitmap>> loader) {
|
||||
if (mGridAdapter != null) {
|
||||
mGridAdapter.updateThumbnails(null);
|
||||
}
|
||||
|
@ -5,24 +5,20 @@
|
||||
|
||||
package org.mozilla.gecko.home;
|
||||
|
||||
import android.util.Log;
|
||||
import org.mozilla.gecko.favicons.Favicons;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Tab;
|
||||
import org.mozilla.gecko.Tabs;
|
||||
import org.mozilla.gecko.db.BrowserContract.Combined;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.db.BrowserDB.URLColumns;
|
||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
import org.mozilla.gecko.util.UiAsyncTask;
|
||||
import org.mozilla.gecko.widget.FaviconView;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Gravity;
|
||||
@ -41,12 +37,19 @@ public class TwoLinePageRow extends LinearLayout
|
||||
private int mUrlIconId;
|
||||
private int mBookmarkIconId;
|
||||
private boolean mShowIcons;
|
||||
private int mLoadFaviconJobId = Favicons.NOT_LOADING;
|
||||
|
||||
// Listener for handling Favicon loads.
|
||||
private final OnFaviconLoadedListener mFaviconListener = new OnFaviconLoadedListener() {
|
||||
@Override
|
||||
public void onFaviconLoaded(String url, String faviconURL, Bitmap favicon) {
|
||||
setFaviconWithUrl(favicon, faviconURL);
|
||||
}
|
||||
};
|
||||
|
||||
// The URL for the page corresponding to this view.
|
||||
private String mPageUrl;
|
||||
|
||||
private LoadFaviconTask mLoadFaviconTask;
|
||||
|
||||
public TwoLinePageRow(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
@ -81,8 +84,6 @@ public class TwoLinePageRow extends LinearLayout
|
||||
Tabs.unregisterOnTabsChangedListener(TwoLinePageRow.this);
|
||||
}
|
||||
});
|
||||
|
||||
cancelLoadFaviconTask();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -118,7 +119,11 @@ public class TwoLinePageRow extends LinearLayout
|
||||
}
|
||||
|
||||
private void setFaviconWithUrl(Bitmap favicon, String url) {
|
||||
mFavicon.updateImage(favicon, url);
|
||||
if (favicon == null) {
|
||||
mFavicon.showDefaultFavicon();
|
||||
} else {
|
||||
mFavicon.updateImage(favicon, url);
|
||||
}
|
||||
}
|
||||
|
||||
private void setBookmarkIcon(int bookmarkIconId) {
|
||||
@ -139,16 +144,6 @@ public class TwoLinePageRow extends LinearLayout
|
||||
updateDisplayedUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels any pending favicon loading task associated with this view.
|
||||
*/
|
||||
private void cancelLoadFaviconTask() {
|
||||
if (mLoadFaviconTask != null) {
|
||||
mLoadFaviconTask.cancel(true);
|
||||
mLoadFaviconTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the page URL with "Switch to tab" if there is already a tab open with that URL.
|
||||
* Only looks for tabs that are either private or non-private, depending on the current
|
||||
@ -181,106 +176,46 @@ public class TwoLinePageRow extends LinearLayout
|
||||
int urlIndex = cursor.getColumnIndexOrThrow(URLColumns.URL);
|
||||
final String url = cursor.getString(urlIndex);
|
||||
|
||||
if (mShowIcons) {
|
||||
final int bookmarkIdIndex = cursor.getColumnIndex(Combined.BOOKMARK_ID);
|
||||
if (bookmarkIdIndex != -1) {
|
||||
final long bookmarkId = cursor.getLong(bookmarkIdIndex);
|
||||
final int displayIndex = cursor.getColumnIndex(Combined.DISPLAY);
|
||||
|
||||
final int display;
|
||||
if (displayIndex != -1) {
|
||||
display = cursor.getInt(displayIndex);
|
||||
} else {
|
||||
display = Combined.DISPLAY_NORMAL;
|
||||
}
|
||||
|
||||
// The bookmark id will be 0 (null in database) when the url
|
||||
// is not a bookmark.
|
||||
if (bookmarkId == 0) {
|
||||
setBookmarkIcon(NO_ICON);
|
||||
} else if (display == Combined.DISPLAY_READER) {
|
||||
setBookmarkIcon(R.drawable.ic_url_bar_reader);
|
||||
} else {
|
||||
setBookmarkIcon(R.drawable.ic_url_bar_star);
|
||||
}
|
||||
} else {
|
||||
setBookmarkIcon(NO_ICON);
|
||||
}
|
||||
}
|
||||
|
||||
// No point updating the below things if URL has not changed. Prevents evil Favicon flicker.
|
||||
if (url.equals(mPageUrl)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the URL instead of an empty title for consistency with the normal URL
|
||||
// bar view - this is the equivalent of getDisplayTitle() in Tab.java
|
||||
setTitle(TextUtils.isEmpty(title) ? url : title);
|
||||
|
||||
// No need to do extra work if the URL associated with this view
|
||||
// hasn't changed.
|
||||
if (TextUtils.equals(mPageUrl, url)) {
|
||||
return;
|
||||
}
|
||||
// Blank the Favicon, so we don't show the wrong Favicon if we scroll and miss DB.
|
||||
mFavicon.clearImage();
|
||||
mLoadFaviconJobId = Favicons.getSizedFaviconForPageFromLocal(url, mFaviconListener);
|
||||
|
||||
updateDisplayedUrl(url);
|
||||
cancelLoadFaviconTask();
|
||||
|
||||
// First, try to find the favicon in the memory cache. If it's not
|
||||
// cached yet, try to load it from the database, off main thread.
|
||||
final Bitmap favicon = Favicons.getFaviconFromMemCache(url);
|
||||
if (favicon != null) {
|
||||
setFaviconWithUrl(favicon, url);
|
||||
} else {
|
||||
// Show blank image until the new favicon finishes loading
|
||||
mFavicon.clearImage();
|
||||
|
||||
mLoadFaviconTask = new LoadFaviconTask(TwoLinePageRow.this, url);
|
||||
|
||||
// Try to use a thread pool instead of serial execution of tasks
|
||||
// to add more throughput to the favicon loading routines.
|
||||
if (Build.VERSION.SDK_INT >= 11) {
|
||||
mLoadFaviconTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
} else {
|
||||
mLoadFaviconTask.execute();
|
||||
}
|
||||
}
|
||||
|
||||
// Don't show bookmark/reading list icon, if not needed.
|
||||
if (!mShowIcons) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int bookmarkIdIndex = cursor.getColumnIndex(Combined.BOOKMARK_ID);
|
||||
if (bookmarkIdIndex != -1) {
|
||||
final long bookmarkId = cursor.getLong(bookmarkIdIndex);
|
||||
final int displayIndex = cursor.getColumnIndex(Combined.DISPLAY);
|
||||
|
||||
final int display;
|
||||
if (displayIndex != -1) {
|
||||
display = cursor.getInt(displayIndex);
|
||||
} else {
|
||||
display = Combined.DISPLAY_NORMAL;
|
||||
}
|
||||
|
||||
// The bookmark id will be 0 (null in database) when the url
|
||||
// is not a bookmark.
|
||||
if (bookmarkId == 0) {
|
||||
setBookmarkIcon(NO_ICON);
|
||||
} else if (display == Combined.DISPLAY_READER) {
|
||||
setBookmarkIcon(R.drawable.ic_url_bar_reader);
|
||||
} else {
|
||||
setBookmarkIcon(R.drawable.ic_url_bar_star);
|
||||
}
|
||||
} else {
|
||||
setBookmarkIcon(NO_ICON);
|
||||
}
|
||||
}
|
||||
|
||||
void onFaviconLoaded(Bitmap favicon, String url) {
|
||||
if (TextUtils.equals(mPageUrl, url)) {
|
||||
setFaviconWithUrl(favicon, url);
|
||||
}
|
||||
|
||||
mLoadFaviconTask = null;
|
||||
}
|
||||
|
||||
private static class LoadFaviconTask extends AsyncTask<Void, Void, Bitmap> {
|
||||
private final TwoLinePageRow mRow;
|
||||
private final String mUrl;
|
||||
|
||||
public LoadFaviconTask(TwoLinePageRow row, String url) {
|
||||
mRow = row;
|
||||
mUrl = url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bitmap doInBackground(Void... params) {
|
||||
Bitmap favicon = Favicons.getFaviconFromMemCache(mUrl);
|
||||
if (favicon == null) {
|
||||
final ContentResolver cr = mRow.getContext().getContentResolver();
|
||||
|
||||
final Bitmap faviconFromDb = BrowserDB.getFaviconForUrl(cr, mUrl);
|
||||
if (faviconFromDb != null) {
|
||||
favicon = Favicons.scaleImage(faviconFromDb);
|
||||
Favicons.putFaviconInMemCache(mUrl, favicon);
|
||||
}
|
||||
}
|
||||
|
||||
return favicon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostExecute(Bitmap favicon) {
|
||||
mRow.onFaviconLoaded(favicon, mUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -141,8 +141,7 @@
|
||||
android:layout_marginLeft="8dip"
|
||||
android:paddingLeft="4dip"
|
||||
android:paddingRight="4dip"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:src="@drawable/favicon"/>
|
||||
android:layout_gravity="center_vertical"/>
|
||||
|
||||
<ImageButton android:id="@+id/site_security"
|
||||
style="@style/UrlBar.ImageButton"
|
||||
|
@ -19,6 +19,13 @@
|
||||
<dimen name="favicon_size_large">32dp</dimen>
|
||||
<dimen name="favicon_bg">32dp</dimen>
|
||||
<dimen name="favicon_bg_radius">1dp</dimen>
|
||||
<!-- Set the upper limit on the size of favicon that will be processed. Favicons larger than
|
||||
this will be downscaled to this value. If you need to use larger Favicons (Due to a UI
|
||||
redesign sometime after this is written) you should increase this value to the largest
|
||||
commonly-used size of favicon and, performance permitting, fetch the remainder from the
|
||||
database. The largest available size is always stored in the database, regardless of this
|
||||
value.-->
|
||||
<dimen name="favicon_largest_interesting_size">32dp</dimen>
|
||||
|
||||
<!-- Page Row height -->
|
||||
<dimen name="page_row_height">64dp</dimen>
|
||||
|
@ -98,7 +98,7 @@ abstract class AboutHomeTest extends BaseTest {
|
||||
protected View getDisplayedBookmark(String url) {
|
||||
openAboutHomeTab(AboutHomeTabs.BOOKMARKS);
|
||||
ListView bookmarksTabList = findListViewWithTag("bookmarks");
|
||||
waitForListToLoad(bookmarksTabList);
|
||||
waitForNonEmptyListToLoad(bookmarksTabList);
|
||||
ListAdapter adapter = bookmarksTabList.getAdapter();
|
||||
if (adapter != null) {
|
||||
for (int i = 0; i < adapter.getCount(); i++ ) {
|
||||
@ -127,11 +127,13 @@ abstract class AboutHomeTest extends BaseTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the given ListView to have a non-empty adapter.
|
||||
* Waits for the given ListView to have a non-empty adapter and be populated
|
||||
* with a minimum number of items.
|
||||
*
|
||||
* This method will return false if the given ListView or its adapter are null.
|
||||
* This method will return false if the given ListView or its adapter is null,
|
||||
* or if the ListView does not have the minimum number of items.
|
||||
*/
|
||||
protected boolean waitForListToLoad(final ListView listView) {
|
||||
protected boolean waitForListToLoad(final ListView listView, final int minSize) {
|
||||
Condition listWaitCondition = new Condition() {
|
||||
@Override
|
||||
public boolean isSatisfied() {
|
||||
@ -144,12 +146,16 @@ abstract class AboutHomeTest extends BaseTest {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (adapter.getCount() > 0);
|
||||
return (listView.getCount() - listView.getHeaderViewsCount() >= minSize);
|
||||
}
|
||||
};
|
||||
return waitForCondition(listWaitCondition, MAX_WAIT_MS);
|
||||
}
|
||||
|
||||
protected boolean waitForNonEmptyListToLoad(final ListView listView) {
|
||||
return waitForListToLoad(listView, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an active ListView with the specified tag .
|
||||
*
|
||||
|
@ -44,7 +44,7 @@ public class testBookmarklets extends AboutHomeTest {
|
||||
openAboutHomeTab(AboutHomeTabs.BOOKMARKS);
|
||||
|
||||
ListView bookmarks = findListViewWithTag("bookmarks");
|
||||
mAsserter.is(waitForListToLoad(bookmarks), true, "list is properly loaded");
|
||||
mAsserter.is(waitForNonEmptyListToLoad(bookmarks), true, "list is properly loaded");
|
||||
|
||||
int width = mDriver.getGeckoWidth();
|
||||
int height = mDriver.getGeckoHeight();
|
||||
|
@ -33,7 +33,7 @@ public class testHistory extends AboutHomeTest {
|
||||
openAboutHomeTab(AboutHomeTabs.MOST_RECENT);
|
||||
|
||||
final ListView hList = findListViewWithTag("most_recent");
|
||||
mAsserter.is(waitForListToLoad(hList), true, "list is properly loaded");
|
||||
mAsserter.is(waitForNonEmptyListToLoad(hList), true, "list is properly loaded");
|
||||
|
||||
// Click on the history item and wait for the page to load
|
||||
// wait for the history list to be populated
|
||||
|
@ -72,7 +72,7 @@ public class testShareLink extends AboutHomeTest {
|
||||
openAboutHomeTab(AboutHomeTabs.BOOKMARKS);
|
||||
|
||||
ListView bookmarksList = findListViewWithTag("bookmarks");
|
||||
mAsserter.is(waitForListToLoad(bookmarksList), true, "list is properly loaded");
|
||||
mAsserter.is(waitForNonEmptyListToLoad(bookmarksList), true, "list is properly loaded");
|
||||
|
||||
View bookmarksItem = bookmarksList.getChildAt(bookmarksList.getHeaderViewsCount());
|
||||
mSolo.clickLongOnView(bookmarksItem);
|
||||
@ -101,7 +101,7 @@ public class testShareLink extends AboutHomeTest {
|
||||
mActions.drag(width / 2, width / 2, height - 10, height / 2);
|
||||
|
||||
ListView topSitesList = findListViewWithTag("top_sites");
|
||||
mAsserter.is(waitForListToLoad(topSitesList), true, "list is properly loaded");
|
||||
mAsserter.is(waitForNonEmptyListToLoad(topSitesList), true, "list is properly loaded");
|
||||
View mostVisitedItem = topSitesList.getChildAt(topSitesList.getHeaderViewsCount());
|
||||
mSolo.clickLongOnView(mostVisitedItem);
|
||||
verifySharePopup(shareOptions,"top_sites");
|
||||
@ -110,7 +110,7 @@ public class testShareLink extends AboutHomeTest {
|
||||
openAboutHomeTab(AboutHomeTabs.MOST_RECENT);
|
||||
|
||||
ListView mostRecentList = findListViewWithTag("most_recent");
|
||||
mAsserter.is(waitForListToLoad(mostRecentList), true, "list is properly loaded");
|
||||
mAsserter.is(waitForNonEmptyListToLoad(mostRecentList), true, "list is properly loaded");
|
||||
|
||||
// Getting second child after header views because the first is the "Today" label
|
||||
View mostRecentItem = mostRecentList.getChildAt(mostRecentList.getHeaderViewsCount() + 1);
|
||||
|
@ -106,7 +106,11 @@ public class FaviconView extends ImageView {
|
||||
* space.
|
||||
*/
|
||||
private void showBackground() {
|
||||
int color = Favicons.getFaviconColor(mIconBitmap, mIconKey);
|
||||
int color = Favicons.getFaviconColor(mIconKey);
|
||||
if (color == -1) {
|
||||
hideBackground();
|
||||
return;
|
||||
}
|
||||
color = Color.argb(70, Color.red(color), Color.green(color), Color.blue(color));
|
||||
final Drawable drawable = getResources().getDrawable(R.drawable.favicon_bg);
|
||||
drawable.setColorFilter(color, Mode.SRC_ATOP);
|
||||
@ -152,7 +156,7 @@ public class FaviconView extends ImageView {
|
||||
formatImage();
|
||||
}
|
||||
|
||||
private void showDefaultFavicon() {
|
||||
public void showDefaultFavicon() {
|
||||
setImageResource(R.drawable.favicon);
|
||||
hideBackground();
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ Cu.import("resource://gre/modules/JNI.jsm");
|
||||
Cu.import('resource://gre/modules/Payment.jsm');
|
||||
Cu.import("resource://gre/modules/PermissionPromptHelper.jsm");
|
||||
Cu.import("resource://gre/modules/ContactService.jsm");
|
||||
Cu.import("resource://gre/modules/SpatialNavigation.jsm");
|
||||
|
||||
#ifdef ACCESSIBILITY
|
||||
Cu.import("resource://gre/modules/accessibility/AccessFu.jsm");
|
||||
@ -4111,6 +4112,9 @@ var BrowserEventHandler = {
|
||||
BrowserApp.deck.addEventListener("touchstart", this, true);
|
||||
BrowserApp.deck.addEventListener("click", InputWidgetHelper, true);
|
||||
BrowserApp.deck.addEventListener("click", SelectHelper, true);
|
||||
|
||||
SpatialNavigation.init(BrowserApp.deck, null);
|
||||
|
||||
document.addEventListener("MozMagnifyGesture", this, true);
|
||||
|
||||
Services.prefs.addObserver("browser.zoom.reflowOnZoom", this, false);
|
||||
|
@ -243,6 +243,7 @@
|
||||
@BINPATH@/components/spellchecker.xpt
|
||||
@BINPATH@/components/storage.xpt
|
||||
@BINPATH@/components/telemetry.xpt
|
||||
@BINPATH@/components/toolkit_finalizationwitness.xpt
|
||||
@BINPATH@/components/toolkitprofile.xpt
|
||||
#ifdef MOZ_ENABLE_XREMOTE
|
||||
@BINPATH@/components/toolkitremote.xpt
|
||||
|
@ -230,6 +230,8 @@ pref("media.navigator.video.default_width",640);
|
||||
pref("media.navigator.video.default_height",480);
|
||||
pref("media.navigator.video.default_fps",30);
|
||||
pref("media.navigator.video.default_minfps",10);
|
||||
pref("media.navigator.video.max_fs", 0); // unrestricted
|
||||
pref("media.navigator.video.max_fr", 0); // unrestricted
|
||||
pref("media.peerconnection.enabled", true);
|
||||
pref("media.navigator.permission.disabled", false);
|
||||
pref("media.peerconnection.default_iceservers", "[{\"url\": \"stun:23.21.150.121\"}]");
|
||||
@ -4464,3 +4466,6 @@ pref("urlclassifier.malware_table", "goog-malware-shavar");
|
||||
pref("urlclassifier.phish_table", "goog-phish-shavar");
|
||||
pref("urlclassifier.download_block_table", "goog-badbinurl-shavar");
|
||||
pref("urlclassifier.download_allow_table", "goog-downloadwhite-digest256");
|
||||
|
||||
// Turn off Spatial navigation by default.
|
||||
pref("snav.enabled", false);
|
||||
|
@ -36,7 +36,6 @@
|
||||
"content/base/test/test_websocket_hello.html": "",
|
||||
"content/base/test/test_x-frame-options.html": "",
|
||||
"content/base/test/test_xhr_abort_after_load.html": "",
|
||||
"content/base/test/test_xhr_forbidden_headers.html": "",
|
||||
"content/base/test/test_xhr_progressevents.html": "",
|
||||
"content/base/test/websocket_hybi/test_receive-arraybuffer.html": "",
|
||||
"content/base/test/websocket_hybi/test_receive-blob.html": "",
|
||||
|
@ -37,7 +37,6 @@
|
||||
"content/base/test/test_websocket_hello.html": "",
|
||||
"content/base/test/test_x-frame-options.html": "",
|
||||
"content/base/test/test_xhr_abort_after_load.html": "",
|
||||
"content/base/test/test_xhr_forbidden_headers.html": "",
|
||||
"content/base/test/test_xhr_progressevents.html": "",
|
||||
"content/base/test/websocket_hybi/test_receive-arraybuffer.html": "",
|
||||
"content/base/test/websocket_hybi/test_receive-blob.html": "",
|
||||
|
@ -92,8 +92,7 @@
|
||||
"content/base/test/test_bug431701.html":"xmlhttprequest causes crash, bug 902271",
|
||||
"content/base/test/test_bug422537.html":"xmlhttprequest causes crash, bug 902271",
|
||||
|
||||
"content/base/test/test_bug338583.html":"43 total - bug 901343, specialpowers.wrap issue createsystemxhr",
|
||||
"content/base/test/test_bug804395.html":"bug 901343, specialpowers.wrap issue createsystemxhr",
|
||||
"content/base/test/test_bug338583.html":"https not working, bug 907770",
|
||||
|
||||
"content/base/test/test_bug475156.html":"36 total - bug 902611",
|
||||
"content/base/test/test_bug422403-1.html":"bug 901343, specialpowers.wrap issue [nsIChannel.open]",
|
||||
@ -249,7 +248,6 @@
|
||||
"content/base/test/test_bug422403-2.xhtml":"",
|
||||
"content/base/test/test_bug424359-1.html":"",
|
||||
"content/base/test/test_bug424359-2.html":"",
|
||||
"content/base/test/test_bug426308.html":"",
|
||||
"content/base/test/test_mixed_content_blocker_bug803225.html":"",
|
||||
"content/html/document/test/test_non-ascii-cookie.html":"",
|
||||
|
||||
@ -321,7 +319,7 @@
|
||||
"dom/media/tests/mochitest/test_peerConnection_throwInCallbacks.html":"",
|
||||
|
||||
"dom/network/tests/test_networkstats_basics.html":"Will be fixed in bug 858005",
|
||||
"dom/permission/tests/test_permission_basics.html":"Bug 907770",
|
||||
"dom/permission/tests/test_permission_basics.html":"https not working, bug 907770",
|
||||
|
||||
"dom/tests/mochitest/bugs/test_bug335976.xhtml":"",
|
||||
"dom/tests/mochitest/bugs/test_bug369306.html":"test timed out, can't focus back from popup window to opener?",
|
||||
|
@ -3,9 +3,19 @@
|
||||
# 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/.
|
||||
|
||||
DEPTH = ../../..
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
relativesrcdir = testing/mochitest/roboextender
|
||||
TESTPATH = $(topsrcdir)/mobile/android/base/tests/roboextender
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
|
||||
_TEST_FILES = \
|
||||
bootstrap.js \
|
||||
install.rdf \
|
||||
chrome.manifest \
|
||||
$(NULL)
|
||||
|
||||
TEST_EXTENSIONS_DIR = $(DEPTH)/_tests/testing/mochitest/extensions
|
||||
@ -15,4 +25,6 @@ include $(topsrcdir)/config/rules.mk
|
||||
libs:: $(_TEST_FILES)
|
||||
$(MKDIR) -p $(TEST_EXTENSIONS_DIR)/roboextender@mozilla.org
|
||||
$(INSTALL) $(foreach f,$^,"$f") $(TEST_EXTENSIONS_DIR)/roboextender@mozilla.org/
|
||||
|
||||
$(MKDIR) -p $(TEST_EXTENSIONS_DIR)/roboextender@mozilla.org/base
|
||||
$(MKDIR) -p $(TESTPATH)
|
||||
-cp $(TESTPATH)/* $(TEST_EXTENSIONS_DIR)/roboextender@mozilla.org/base
|
||||
|
1
testing/mochitest/roboextender/chrome.manifest
Normal file
1
testing/mochitest/roboextender/chrome.manifest
Normal file
@ -0,0 +1 @@
|
||||
content roboextender base/
|
@ -78,11 +78,6 @@ function starttest(){
|
||||
// Test a DOMWindowUtils method and property
|
||||
is(SpecialPowers.DOMWindowUtils.getClassName(window), "Proxy");
|
||||
is(SpecialPowers.DOMWindowUtils.docCharsetIsForced, false);
|
||||
|
||||
//Run the createSystemXHR method
|
||||
var xhr = SpecialPowers.createSystemXHR();
|
||||
ok(xhr, "createSystemXHR should not return null");
|
||||
is(xhr.readyState, XMLHttpRequest.UNSENT, "createSystemXHR should create an unsent XMLHttpRequest object");
|
||||
|
||||
// QueryInterface and getPrivilegedProps tests
|
||||
is(SpecialPowers.can_QI(SpecialPowers), false);
|
||||
@ -99,7 +94,7 @@ function starttest(){
|
||||
|
||||
// Try some basic stuff with XHR.
|
||||
var xhr2 = SpecialPowers.Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(SpecialPowers.Ci.nsIXMLHttpRequest);
|
||||
is(xhr.readyState, XMLHttpRequest.UNSENT, "Should be able to get props off privileged objects");
|
||||
is(xhr2.readyState, XMLHttpRequest.UNSENT, "Should be able to get props off privileged objects");
|
||||
var testURI = SpecialPowers.Cc['@mozilla.org/network/standard-url;1']
|
||||
.createInstance(SpecialPowers.Ci.nsIURI);
|
||||
testURI.spec = "http://www.foobar.org/";
|
||||
|
@ -1178,10 +1178,6 @@ SpecialPowersAPI.prototype = {
|
||||
this._getMUDV(window).stopEmulatingMedium();
|
||||
},
|
||||
|
||||
createSystemXHR: function() {
|
||||
return this.wrap(Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest));
|
||||
},
|
||||
|
||||
snapshotWindowWithOptions: function (win, rect, bgcolor, options) {
|
||||
var el = this.window.get().document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
|
||||
if (rect === undefined) {
|
||||
|
@ -28,6 +28,7 @@ SHARED_LIBRARY_LIBS = \
|
||||
../jsdownloads/src/$(LIB_PREFIX)jsdownloads_s.$(LIB_SUFFIX) \
|
||||
../protobuf/$(LIB_PREFIX)protobuf_s.$(LIB_SUFFIX) \
|
||||
../intl/$(LIB_PREFIX)intl_s.$(LIB_SUFFIX) \
|
||||
../finalizationwitness/$(LIB_PREFIX)finalizationwitness_s.$(LIB_SUFFIX) \
|
||||
$(NULL)
|
||||
|
||||
ifndef MOZ_DISABLE_PARENTAL_CONTROLS
|
||||
|
@ -34,6 +34,7 @@
|
||||
#endif
|
||||
|
||||
#include "nsBrowserStatusFilter.h"
|
||||
#include "mozilla/FinalizationWitnessService.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@ -85,6 +86,7 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsBrowserStatusFilter)
|
||||
#if defined(USE_MOZ_UPDATER)
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(nsUpdateProcessor)
|
||||
#endif
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(FinalizationWitnessService)
|
||||
|
||||
NS_DEFINE_NAMED_CID(NS_TOOLKIT_APPSTARTUP_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_USERINFO_CID);
|
||||
@ -109,6 +111,7 @@ NS_DEFINE_NAMED_CID(NS_CHARSETMENU_CID);
|
||||
#if defined(USE_MOZ_UPDATER)
|
||||
NS_DEFINE_NAMED_CID(NS_UPDATEPROCESSOR_CID);
|
||||
#endif
|
||||
NS_DEFINE_NAMED_CID(FINALIZATIONWITNESSSERVICE_CID);
|
||||
|
||||
static const mozilla::Module::CIDEntry kToolkitCIDs[] = {
|
||||
{ &kNS_TOOLKIT_APPSTARTUP_CID, false, nullptr, nsAppStartupConstructor },
|
||||
@ -134,6 +137,7 @@ static const mozilla::Module::CIDEntry kToolkitCIDs[] = {
|
||||
#if defined(USE_MOZ_UPDATER)
|
||||
{ &kNS_UPDATEPROCESSOR_CID, false, nullptr, nsUpdateProcessorConstructor },
|
||||
#endif
|
||||
{ &kFINALIZATIONWITNESSSERVICE_CID, false, NULL, FinalizationWitnessServiceConstructor },
|
||||
{ nullptr }
|
||||
};
|
||||
|
||||
@ -162,6 +166,7 @@ static const mozilla::Module::ContractIDEntry kToolkitContracts[] = {
|
||||
#if defined(USE_MOZ_UPDATER)
|
||||
{ NS_UPDATEPROCESSOR_CONTRACTID, &kNS_UPDATEPROCESSOR_CID },
|
||||
#endif
|
||||
{ FINALIZATIONWITNESSSERVICE_CONTRACTID, &kFINALIZATIONWITNESSSERVICE_CID },
|
||||
{ nullptr }
|
||||
};
|
||||
|
||||
|
@ -0,0 +1,213 @@
|
||||
/* 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/. */
|
||||
|
||||
#include "FinalizationWitnessService.h"
|
||||
|
||||
#include "nsString.h"
|
||||
#include "jsapi.h"
|
||||
#include "js/CallNonGenericMethod.h"
|
||||
#include "mozJSComponentLoader.h"
|
||||
#include "nsZipArchive.h"
|
||||
|
||||
#include "mozilla/Scoped.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "mozilla/NullPtr.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
|
||||
// Implementation of nsIFinalizationWitnessService
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* An event meant to be dispatched to the main thread upon finalization
|
||||
* of a FinalizationWitness, unless method |forget()| has been called.
|
||||
*
|
||||
* Held as private data by each instance of FinalizationWitness.
|
||||
* Important note: we maintain the invariant that these private data
|
||||
* slots are already addrefed.
|
||||
*/
|
||||
class FinalizationEvent MOZ_FINAL: public nsRunnable
|
||||
{
|
||||
public:
|
||||
FinalizationEvent(const char* aTopic,
|
||||
const jschar* aValue)
|
||||
: mTopic(aTopic)
|
||||
, mValue(aValue)
|
||||
{ }
|
||||
|
||||
NS_METHOD Run() {
|
||||
nsCOMPtr<nsIObserverService> observerService =
|
||||
mozilla::services::GetObserverService();
|
||||
if (!observerService) {
|
||||
// This is either too early or, more likely, too late for notifications.
|
||||
// Bail out.
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
(void)observerService->
|
||||
NotifyObservers(nullptr, mTopic.get(), mValue.get());
|
||||
return NS_OK;
|
||||
}
|
||||
private:
|
||||
/**
|
||||
* The topic on which to broadcast the notification of finalization.
|
||||
*
|
||||
* Deallocated on the main thread.
|
||||
*/
|
||||
const nsCString mTopic;
|
||||
|
||||
/**
|
||||
* The result of converting the exception to a string.
|
||||
*
|
||||
* Deallocated on the main thread.
|
||||
*/
|
||||
const nsString mValue;
|
||||
};
|
||||
|
||||
enum {
|
||||
WITNESS_SLOT_EVENT,
|
||||
WITNESS_INSTANCES_SLOTS
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract the FinalizationEvent from an instance of FinalizationWitness
|
||||
* and clear the slot containing the FinalizationEvent.
|
||||
*/
|
||||
already_AddRefed<FinalizationEvent>
|
||||
ExtractFinalizationEvent(JSObject *objSelf)
|
||||
{
|
||||
JS::Value slotEvent = JS_GetReservedSlot(objSelf, WITNESS_SLOT_EVENT);
|
||||
if (slotEvent.isUndefined()) {
|
||||
// Forget() has been called
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
JS_SetReservedSlot(objSelf, WITNESS_SLOT_EVENT, JS::UndefinedValue());
|
||||
|
||||
return dont_AddRef(static_cast<FinalizationEvent*>(slotEvent.toPrivate()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalizer for instances of FinalizationWitness.
|
||||
*
|
||||
* Unless method Forget() has been called, the finalizer displays an error
|
||||
* message.
|
||||
*/
|
||||
void Finalize(JSFreeOp *fop, JSObject *objSelf)
|
||||
{
|
||||
nsRefPtr<FinalizationEvent> event = ExtractFinalizationEvent(objSelf);
|
||||
if (event == nullptr) {
|
||||
// Forget() has been called
|
||||
return;
|
||||
}
|
||||
|
||||
// Notify observers. Since we are executed during garbage-collection,
|
||||
// we need to dispatch the notification to the main thread.
|
||||
(void)NS_DispatchToMainThread(event);
|
||||
// We may fail at dispatching to the main thread if we arrive too late
|
||||
// during shutdown. In that case, there is not much we can do.
|
||||
}
|
||||
|
||||
static const JSClass sWitnessClass = {
|
||||
"FinalizationWitness",
|
||||
JSCLASS_HAS_RESERVED_SLOTS(WITNESS_INSTANCES_SLOTS),
|
||||
JS_PropertyStub /* addProperty */,
|
||||
JS_DeletePropertyStub /* delProperty */,
|
||||
JS_PropertyStub /* getProperty */,
|
||||
JS_StrictPropertyStub /* setProperty */,
|
||||
JS_EnumerateStub /* enumerate */,
|
||||
JS_ResolveStub /* resolve */,
|
||||
JS_ConvertStub /* convert */,
|
||||
Finalize /* finalize */
|
||||
};
|
||||
|
||||
bool IsWitness(JS::Handle<JS::Value> v)
|
||||
{
|
||||
return v.isObject() && JS_GetClass(&v.toObject()) == &sWitnessClass;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* JS method |forget()|
|
||||
*
|
||||
* === JS documentation
|
||||
*
|
||||
* Neutralize the witness. Once this method is called, the witness will
|
||||
* never report any error.
|
||||
*/
|
||||
bool ForgetImpl(JSContext* cx, JS::CallArgs args)
|
||||
{
|
||||
if (args.length() != 0) {
|
||||
JS_ReportError(cx, "forget() takes no arguments");
|
||||
return false;
|
||||
}
|
||||
JS::Rooted<JS::Value> valSelf(cx, args.thisv());
|
||||
JS::Rooted<JSObject*> objSelf(cx, &valSelf.toObject());
|
||||
|
||||
nsRefPtr<FinalizationEvent> event = ExtractFinalizationEvent(objSelf);
|
||||
if (event == nullptr) {
|
||||
JS_ReportError(cx, "forget() called twice");
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Forget(JSContext *cx, unsigned argc, JS::Value *vp)
|
||||
{
|
||||
JS::CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return JS::CallNonGenericMethod<IsWitness, ForgetImpl>(cx, args);
|
||||
}
|
||||
|
||||
static const JSFunctionSpec sWitnessClassFunctions[] = {
|
||||
JS_FN("forget", Forget, 0, JSPROP_READONLY | JSPROP_PERMANENT),
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS1(FinalizationWitnessService, nsIFinalizationWitnessService)
|
||||
|
||||
/**
|
||||
* Create a new Finalization Witness.
|
||||
*
|
||||
* A finalization witness is an object whose sole role is to notify
|
||||
* observers when it is gc-ed. Once the witness is created, call its
|
||||
* method |forget()| to prevent the observers from being notified.
|
||||
*
|
||||
* @param aTopic The notification topic.
|
||||
* @param aValue The notification value. Converted to a string.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
NS_IMETHODIMP
|
||||
FinalizationWitnessService::Make(const char* aTopic,
|
||||
const PRUnichar* aValue,
|
||||
JSContext* aCx,
|
||||
JS::Value *aRetval) {
|
||||
MOZ_ASSERT(aRetval);
|
||||
|
||||
JS::Rooted<JSObject*> objResult(aCx, JS_NewObject(aCx, &sWitnessClass, nullptr, nullptr));
|
||||
if (!objResult) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
if (!JS_DefineFunctions(aCx, objResult, sWitnessClassFunctions)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsRefPtr<FinalizationEvent> event = new FinalizationEvent(aTopic, aValue);
|
||||
|
||||
// Transfer ownership of the addrefed |event| to |objResult|.
|
||||
JS_SetReservedSlot(objResult, WITNESS_SLOT_EVENT,
|
||||
JS::PrivateValue(event.forget().get()));
|
||||
|
||||
aRetval->setObject(*objResult);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
@ -0,0 +1,26 @@
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_finalizationwitnessservice_h__
|
||||
#define mozilla_finalizationwitnessservice_h__
|
||||
|
||||
#include "nsIFinalizationWitnessService.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/**
|
||||
* XPConnect initializer, for use in the main thread.
|
||||
*/
|
||||
class FinalizationWitnessService MOZ_FINAL : public nsIFinalizationWitnessService
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIFINALIZATIONWITNESSSERVICE
|
||||
private:
|
||||
void operator=(const FinalizationWitnessService* other) MOZ_DELETE;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_finalizationwitnessservice_h__
|
28
toolkit/components/finalizationwitness/moz.build
Normal file
28
toolkit/components/finalizationwitness/moz.build
Normal file
@ -0,0 +1,28 @@
|
||||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
MODULE = 'finalizationwitness'
|
||||
|
||||
CPP_SOURCES += [
|
||||
'FinalizationWitnessService.cpp',
|
||||
]
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'nsIFinalizationWitnessService.idl',
|
||||
]
|
||||
|
||||
XPIDL_MODULE = 'toolkit_finalizationwitness'
|
||||
|
||||
EXPORTS.mozilla += [
|
||||
'FinalizationWitnessService.h',
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'/js/xpconnect/loader',
|
||||
]
|
||||
|
||||
LIBRARY_NAME = 'finalizationwitness_s'
|
||||
LIBXUL_LIBRARY = True
|
@ -0,0 +1,35 @@
|
||||
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=40: */
|
||||
/* 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/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
|
||||
[scriptable, uuid(15686f9d-483e-4361-98cd-37f1e8f1e61d)]
|
||||
interface nsIFinalizationWitnessService: nsISupports
|
||||
{
|
||||
/**
|
||||
* Create a new Finalization Witness.
|
||||
*
|
||||
* A finalization witness is an object whose sole role is to
|
||||
* broadcast when it is garbage-collected. Once the witness is
|
||||
* created, call method its method |forget()| to prevent the
|
||||
* broadcast.
|
||||
*
|
||||
* @param aTopic The topic that the witness will broadcast using
|
||||
* Services.obs.
|
||||
* @param aString The string that the witness will broadcast.
|
||||
* @return An object with a single method |forget()|.
|
||||
*/
|
||||
[implicit_jscontext]
|
||||
jsval make(in string aTopic, in wstring aString);
|
||||
};
|
||||
|
||||
%{ C++
|
||||
|
||||
#define FINALIZATIONWITNESSSERVICE_CID {0x15686f9d,0x483e,0x4361,{0x98,0xcd,0x37,0xf1,0xe8,0xf1,0xe6,0x1d}}
|
||||
#define FINALIZATIONWITNESSSERVICE_CONTRACTID "@mozilla.org/toolkit/finalizationwitness;1"
|
||||
|
||||
%}
|
@ -20,6 +20,7 @@ PARALLEL_DIRS += [
|
||||
'downloads',
|
||||
'exthelper',
|
||||
'filepicker',
|
||||
'finalizationwitness',
|
||||
'find',
|
||||
'intl',
|
||||
'jsdownloads',
|
||||
|
@ -98,48 +98,10 @@ if (!("localProfileDir" in OS.Constants.Path)) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A global constant used as a default refs parameter value when cloning.
|
||||
*/
|
||||
const noRefs = [];
|
||||
|
||||
/**
|
||||
* Return a shallow clone of the enumerable properties of an object.
|
||||
*
|
||||
* Utility used whenever normalizing options requires making (shallow)
|
||||
* changes to an option object. The copy ensures that we do not modify
|
||||
* a client-provided object by accident.
|
||||
*
|
||||
* Note: to reference and not copy specific fields, provide an optional
|
||||
* |refs| argument containing their names.
|
||||
*
|
||||
* @param {JSON} object Options to be cloned.
|
||||
* @param {Array} refs An optional array of field names to be passed by
|
||||
* reference instead of copying.
|
||||
*/
|
||||
let clone = function clone(object, refs = noRefs) {
|
||||
let result = {};
|
||||
// Make a reference between result[key] and object[key].
|
||||
let refer = function refer(result, key, object) {
|
||||
Object.defineProperty(result, key, {
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
return object[key];
|
||||
},
|
||||
set: function(value) {
|
||||
object[key] = value;
|
||||
}
|
||||
});
|
||||
};
|
||||
for (let k in object) {
|
||||
if (refs.indexOf(k) < 0) {
|
||||
result[k] = object[k];
|
||||
} else {
|
||||
refer(result, k, object);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
let clone = SharedAll.clone;
|
||||
|
||||
let worker = new PromiseWorker(
|
||||
"resource://gre/modules/osfile/osfile_async_worker.js", LOG);
|
||||
@ -421,9 +383,8 @@ File.prototype = {
|
||||
// If |buffer| is a typed array and there is no |bytes| options, we
|
||||
// need to extract the |byteLength| now, as it will be lost by
|
||||
// communication
|
||||
if (isTypedArray(buffer) && (!options || !("bytes" in options))) {
|
||||
// Preserve the reference to |outExecutionDuration| option if it is
|
||||
// passed.
|
||||
if (isTypedArray(buffer) && !("bytes" in options)) {
|
||||
// Preserve reference to option |outExecutionDuration|, if it is passed.
|
||||
options = clone(options, ["outExecutionDuration"]);
|
||||
options.bytes = buffer.byteLength;
|
||||
}
|
||||
@ -459,9 +420,8 @@ File.prototype = {
|
||||
// If |buffer| is a typed array and there is no |bytes| options,
|
||||
// we need to extract the |byteLength| now, as it will be lost
|
||||
// by communication
|
||||
if (isTypedArray(buffer) && (!options || !("bytes" in options))) {
|
||||
// Preserve the reference to |outExecutionDuration| option if it is
|
||||
// passed.
|
||||
if (isTypedArray(buffer)) {
|
||||
// Preserve reference to option |outExecutionDuration|, if it is passed.
|
||||
options = clone(options, ["outExecutionDuration"]);
|
||||
options.bytes = buffer.byteLength;
|
||||
}
|
||||
@ -482,13 +442,14 @@ File.prototype = {
|
||||
* @param {number=} bytes If unspecified, read all the remaining bytes from
|
||||
* this file. If specified, read |bytes| bytes, or less if the file does not
|
||||
* contain that many bytes.
|
||||
* @param {JSON} options
|
||||
* @return {promise}
|
||||
* @resolves {Uint8Array} An array containing the bytes read.
|
||||
*/
|
||||
read: function read(nbytes) {
|
||||
read: function read(nbytes, options = {}) {
|
||||
let promise = Scheduler.post("File_prototype_read",
|
||||
[this._fdmsg,
|
||||
nbytes]);
|
||||
nbytes, options]);
|
||||
return promise.then(
|
||||
function onSuccess(data) {
|
||||
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
||||
@ -693,6 +654,11 @@ File.makeDir = function makeDir(path, options) {
|
||||
* @param {number=} bytes Optionally, an upper bound to the number of bytes
|
||||
* to read.
|
||||
* @param {JSON} options Additional options.
|
||||
* - {boolean} sequential A flag that triggers a population of the page cache
|
||||
* with data from a file so that subsequent reads from that file would not
|
||||
* block on disk I/O. If |true| or unspecified, inform the system that the
|
||||
* contents of the file will be read in order. Otherwise, make no such
|
||||
* assumption. |true| by default.
|
||||
*
|
||||
* @resolves {Uint8Array} A buffer holding the bytes
|
||||
* read from the file.
|
||||
|
@ -271,7 +271,7 @@ if (this.Components) {
|
||||
});
|
||||
},
|
||||
read: function read(path, bytes, options) {
|
||||
let data = File.read(Type.path.fromMsg(path), bytes);
|
||||
let data = File.read(Type.path.fromMsg(path), bytes, options);
|
||||
return new Transfer({buffer: data.buffer, byteOffset: data.byteOffset, byteLength: data.byteLength}, [data.buffer]);
|
||||
},
|
||||
exists: function exists(path) {
|
||||
|
@ -34,6 +34,7 @@ if (typeof Components != "undefined") {
|
||||
|
||||
let EXPORTED_SYMBOLS = [
|
||||
"LOG",
|
||||
"clone",
|
||||
"Config",
|
||||
"Constants",
|
||||
"Type",
|
||||
@ -162,6 +163,46 @@ let LOG = function (...args) {
|
||||
|
||||
exports.LOG = LOG;
|
||||
|
||||
/**
|
||||
* Return a shallow clone of the enumerable properties of an object.
|
||||
*
|
||||
* Utility used whenever normalizing options requires making (shallow)
|
||||
* changes to an option object. The copy ensures that we do not modify
|
||||
* a client-provided object by accident.
|
||||
*
|
||||
* Note: to reference and not copy specific fields, provide an optional
|
||||
* |refs| argument containing their names.
|
||||
*
|
||||
* @param {JSON} object Options to be cloned.
|
||||
* @param {Array} refs An optional array of field names to be passed by
|
||||
* reference instead of copying.
|
||||
*/
|
||||
let clone = function (object, refs = []) {
|
||||
let result = {};
|
||||
// Make a reference between result[key] and object[key].
|
||||
let refer = function refer(result, key, object) {
|
||||
Object.defineProperty(result, key, {
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
return object[key];
|
||||
},
|
||||
set: function(value) {
|
||||
object[key] = value;
|
||||
}
|
||||
});
|
||||
};
|
||||
for (let k in object) {
|
||||
if (refs.indexOf(k) < 0) {
|
||||
result[k] = object[k];
|
||||
} else {
|
||||
refer(result, k, object);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
exports.clone = clone;
|
||||
|
||||
///////////////////// Abstractions above js-ctypes
|
||||
|
||||
/**
|
||||
@ -974,6 +1015,7 @@ exports.OS = {
|
||||
Constants: exports.Constants,
|
||||
Shared: {
|
||||
LOG: LOG,
|
||||
clone: clone,
|
||||
Type: Type,
|
||||
HollowStructure: HollowStructure,
|
||||
Error: OSError,
|
||||
@ -1015,4 +1057,3 @@ if (typeof Components != "undefined") {
|
||||
this[symbol] = exports[symbol];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@ if (typeof Components != "undefined") {
|
||||
exports.OS = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm").OS;
|
||||
|
||||
let LOG = exports.OS.Shared.LOG.bind(OS.Shared, "Shared front-end");
|
||||
let clone = exports.OS.Shared.clone;
|
||||
|
||||
/**
|
||||
* Code shared by implementations of File.
|
||||
@ -44,17 +45,17 @@ AbstractFile.prototype = {
|
||||
* Read bytes from this file to a new buffer.
|
||||
*
|
||||
* @param {number=} bytes If unspecified, read all the remaining bytes from
|
||||
* this file. If specified, read |bytes| bytes, or less if the file does not
|
||||
* this file. If specified, read |bytes| bytes, or less if the file does notclone
|
||||
* contain that many bytes.
|
||||
* @param {JSON} options
|
||||
* @return {Uint8Array} An array containing the bytes read.
|
||||
*/
|
||||
read: function read(bytes) {
|
||||
if (bytes == null) {
|
||||
bytes = this.stat().size;
|
||||
}
|
||||
let buffer = new Uint8Array(bytes);
|
||||
let size = this.readTo(buffer, {bytes: bytes});
|
||||
if (size == bytes) {
|
||||
read: function read(bytes, options = {}) {
|
||||
options = clone(options);
|
||||
options.bytes = bytes == null ? this.stat().size : bytes;
|
||||
let buffer = new Uint8Array(options.bytes);
|
||||
let size = this.readTo(buffer, options);
|
||||
if (size == options.bytes) {
|
||||
return buffer;
|
||||
} else {
|
||||
return buffer.subarray(0, size);
|
||||
@ -292,14 +293,15 @@ AbstractFile.normalizeOpenMode = function normalizeOpenMode(mode) {
|
||||
* @param {string} path The path to the file.
|
||||
* @param {number=} bytes Optionally, an upper bound to the number of bytes
|
||||
* to read.
|
||||
* @param {JSON} options Optionally contains additional options.
|
||||
*
|
||||
* @return {Uint8Array} A buffer holding the bytes
|
||||
* and the number of bytes read from the file.
|
||||
*/
|
||||
AbstractFile.read = function read(path, bytes) {
|
||||
AbstractFile.read = function read(path, bytes, options = {}) {
|
||||
let file = exports.OS.File.open(path);
|
||||
try {
|
||||
return file.read(bytes);
|
||||
return file.read(bytes, options);
|
||||
} finally {
|
||||
file.close();
|
||||
}
|
||||
|
@ -447,6 +447,14 @@
|
||||
/*buf*/ Types.void_t.out_ptr,
|
||||
/*nbytes*/Types.size_t);
|
||||
|
||||
UnixFile.posix_fadvise =
|
||||
declareFFI("posix_fadvise", ctypes.default_abi,
|
||||
/*return*/ Types.int,
|
||||
/*fd*/ Types.fd,
|
||||
/*offset*/ Types.off_t,
|
||||
/*len*/ Types.off_t,
|
||||
/*advise*/ Types.int);
|
||||
|
||||
if (OS.Constants.libc._DARWIN_FEATURE_64_BIT_INODE) {
|
||||
// Special case for MacOS X 10.5+
|
||||
// Symbol name "readdir" still exists but is used for a
|
||||
|
@ -94,6 +94,13 @@
|
||||
* @throws {OS.File.Error} In case of I/O error.
|
||||
*/
|
||||
File.prototype._read = function _read(buffer, nbytes, options) {
|
||||
// Populate the page cache with data from a file so the subsequent reads
|
||||
// from that file will not block on disk I/O.
|
||||
if (typeof(UnixFile.posix_fadvise) === 'function' &&
|
||||
(options.sequential || !("sequential" in options))) {
|
||||
UnixFile.posix_fadvise(this.fd, 0, nbytes,
|
||||
OS.Constants.libc.POSIX_FADV_SEQUENTIAL);
|
||||
}
|
||||
return throw_on_negative("read",
|
||||
UnixFile.read(this.fd, buffer, nbytes)
|
||||
);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user