Merge m-c to b-i

This commit is contained in:
Phil Ringnalda 2013-10-13 10:31:54 -07:00
commit 184d322543
111 changed files with 4017 additions and 707 deletions

View File

@ -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

View File

@ -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();
},

View File

@ -68,6 +68,7 @@ let DebuggerView = {
this.GlobalSearch.initialize();
this._initializeVariablesView();
this._initializeEditor(deferred.resolve);
document.title = L10N.getStr("DebuggerWindowTitle");
return deferred.promise;
},

View File

@ -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.");

View File

@ -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

View File

@ -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..

View File

@ -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

View File

@ -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>

View File

@ -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'

View File

@ -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>

View File

@ -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)

View File

@ -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>

View File

@ -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();
});
};

View File

@ -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>

View File

@ -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>

View File

@ -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();

View File

@ -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();

View File

@ -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;

View File

@ -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");

View File

@ -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)

View File

@ -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),

View File

@ -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;

View File

@ -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");

View File

@ -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.

View File

@ -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",

View File

@ -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();

View File

@ -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();

View File

@ -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) {

View File

@ -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

View 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>

View File

@ -176,3 +176,4 @@ load 895311-1.svg
load 897342-1.svg
load 898909-1.svg
load 898951-1.svg
load 919371-1.xhtml

View File

@ -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();
}

View File

@ -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)
{
}

View File

@ -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.

View File

@ -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);
}
}

View File

@ -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),

View File

@ -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;
}

View File

@ -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;
};
}

View File

@ -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()
*

View File

@ -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_ */

View File

@ -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",

View File

@ -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,

View File

@ -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)

View File

@ -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.

View File

@ -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. */

View File

@ -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
}

View File

@ -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)

View File

@ -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) {

View File

@ -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) {

View File

@ -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);

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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

View File

@ -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 \

View File

@ -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.
*

View File

@ -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) {

View File

@ -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);

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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);
}

View 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();
}
}
}

View 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);
}
}
}

View 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;
}
}

View File

@ -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.

View File

@ -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;

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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);
}
}
}

View File

@ -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"

View File

@ -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>

View File

@ -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 .
*

View File

@ -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();

View File

@ -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

View File

@ -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);

View File

@ -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();
}

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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": "",

View File

@ -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": "",

View File

@ -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?",

View File

@ -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

View File

@ -0,0 +1 @@
content roboextender base/

View File

@ -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/";

View File

@ -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) {

View File

@ -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

View File

@ -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 }
};

View File

@ -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

View File

@ -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__

View 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

View File

@ -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"
%}

View File

@ -20,6 +20,7 @@ PARALLEL_DIRS += [
'downloads',
'exthelper',
'filepicker',
'finalizationwitness',
'find',
'intl',
'jsdownloads',

View File

@ -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.

View 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) {

View File

@ -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];
}
}

View File

@ -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();
}

View File

@ -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

View File

@ -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