merge m-c to fx-team

This commit is contained in:
Tim Taubert 2014-01-12 10:25:39 +01:00
commit 3dab13ddf4
69 changed files with 1112 additions and 614 deletions

View File

@ -22,4 +22,4 @@
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
# don't change CLOBBER for WebIDL changes any more.
Bug 944533 requires clobber to force a Proguard refresh
Bug 958185 requires clobber on Android to force a Proguard refresh

View File

@ -58,6 +58,22 @@ this.SessionFile = {
write: function (aData) {
return SessionFileInternal.write(aData);
},
/**
* Gather telemetry statistics.
*
*
* Most of the work is done off the main thread but there is a main
* thread cost involved to send data to the worker thread. This method
* should therefore be called only when we know that it will not disrupt
* the user's experience, e.g. on idle-daily.
*
* @return {Promise}
* @promise {object} An object holding all the information to be submitted
* to Telemetry.
*/
gatherTelemetry: function(aData) {
return SessionFileInternal.gatherTelemetry(aData);
},
/**
* Writes the initial state to disk again only to change the session's load
* state. This must only be called once, it will throw an error otherwise.
@ -122,6 +138,14 @@ let SessionFileInternal = {
});
},
gatherTelemetry: function(aStateString) {
return Task.spawn(function() {
let msg = yield SessionWorker.post("gatherTelemetry", [aStateString]);
this._recordTelemetry(msg.telemetry);
throw new Task.Result(msg.telemetry);
}.bind(this));
},
write: function (aData) {
if (this._isClosed) {
return Promise.reject(new Error("SessionFile is closed"));
@ -177,8 +201,18 @@ let SessionFileInternal = {
},
_recordTelemetry: function(telemetry) {
for (let histogramId in telemetry){
Telemetry.getHistogramById(histogramId).add(telemetry[histogramId]);
for (let id of Object.keys(telemetry)){
let value = telemetry[id];
let samples = [];
if (Array.isArray(value)) {
samples.push(...value);
} else {
samples.push(value);
}
let histogram = Telemetry.getHistogramById(id);
for (let sample of samples) {
histogram.add(sample);
}
}
}
};
@ -196,9 +230,12 @@ let SessionWorker = (function () {
// Decode any serialized error
if (error instanceof PromiseWorker.WorkerError) {
throw OS.File.Error.fromMsg(error.data);
} else {
throw error;
}
// Extract something meaningful from ErrorEvent
if (error instanceof ErrorEvent) {
throw new Error(error.message, error.filename, error.lineno);
}
throw error;
}
);
}

View File

@ -35,7 +35,8 @@ const OBSERVING = [
"quit-application-requested", "quit-application-granted",
"browser-lastwindow-close-granted",
"quit-application", "browser:purge-session-history",
"browser:purge-domain-data"
"browser:purge-domain-data",
"gather-telemetry",
];
// XUL Window properties to (re)store
@ -587,6 +588,9 @@ let SessionStoreInternal = {
case "nsPref:changed": // catch pref changes
this.onPrefChange(aData);
break;
case "gather-telemetry":
this.onGatherTelemetry();
break;
}
},
@ -1457,6 +1461,16 @@ let SessionStoreInternal = {
this.saveStateDelayed(aWindow);
},
onGatherTelemetry: function() {
// On the first gather-telemetry notification of the session,
// gather telemetry data.
Services.obs.removeObserver(this, "gather-telemetry");
this.fillTabCachesAsynchronously().then(function() {
let stateString = SessionStore.getBrowserState();
return SessionFile.gatherTelemetry(stateString);
});
},
/* ........ nsISessionStore API .............. */
getBrowserState: function ssi_getBrowserState() {

View File

@ -130,6 +130,16 @@ let Agent = {
return ret;
},
/**
* Extract all sorts of useful statistics from a state string,
* for use with Telemetry.
*
* @return {object}
*/
gatherTelemetry: function (stateString) {
return Statistics.collect(stateString);
},
/**
* Writes the session state to disk again but changes session.state to
* 'running' before doing so. This is intended to be called only once, shortly
@ -236,3 +246,144 @@ let Agent = {
function isNoSuchFileEx(aReason) {
return aReason instanceof OS.File.Error && aReason.becauseNoSuchFile;
}
/**
* Estimate the number of bytes that a data structure will use on disk
* once serialized.
*/
function getByteLength(str) {
return Encoder.encode(JSON.stringify(str)).byteLength;
}
/**
* Tools for gathering statistics on a state string.
*/
let Statistics = {
collect: function(stateString) {
let start = Date.now();
let TOTAL_PREFIX = "FX_SESSION_RESTORE_TOTAL_";
let INDIVIDUAL_PREFIX = "FX_SESSION_RESTORE_INDIVIDUAL_";
let SIZE_SUFFIX = "_SIZE_BYTES";
let state = JSON.parse(stateString);
// Gather all data
let subsets = {};
this.gatherSimpleData(state, subsets);
this.gatherComplexData(state, subsets);
// Extract telemetry
let telemetry = {};
for (let k of Object.keys(subsets)) {
let obj = subsets[k];
telemetry[TOTAL_PREFIX + k + SIZE_SUFFIX] = getByteLength(obj);
if (Array.isArray(obj)) {
let size = obj.map(getByteLength);
telemetry[INDIVIDUAL_PREFIX + k + SIZE_SUFFIX] = size;
}
}
let stop = Date.now();
telemetry["FX_SESSION_RESTORE_EXTRACTING_STATISTICS_DURATION_MS"] = stop - start;
return {
telemetry: telemetry
};
},
/**
* Collect data that doesn't require a recursive walk through the
* data structure.
*/
gatherSimpleData: function(state, subsets) {
// The subset of sessionstore.js dealing with open windows
subsets.OPEN_WINDOWS = state.windows;
// The subset of sessionstore.js dealing with closed windows
subsets.CLOSED_WINDOWS = state._closedWindows;
// The subset of sessionstore.js dealing with closed tabs
// in open windows
subsets.CLOSED_TABS_IN_OPEN_WINDOWS = [];
// The subset of sessionstore.js dealing with cookies
// in both open and closed windows
subsets.COOKIES = [];
for (let winData of state.windows) {
let closedTabs = winData._closedTabs || [];
subsets.CLOSED_TABS_IN_OPEN_WINDOWS.push(...closedTabs);
let cookies = winData.cookies || [];
subsets.COOKIES.push(...cookies);
}
for (let winData of state._closedWindows) {
let cookies = winData.cookies || [];
subsets.COOKIES.push(...cookies);
}
},
/**
* Walk through a data structure, recursively.
*
* @param {object} root The object from which to start walking.
* @param {function(key, value)} cb Callback, called for each
* item except the root. Returns |true| to walk the subtree rooted
* at |value|, |false| otherwise */
walk: function(root, cb) {
if (!root || typeof root !== "object") {
return;
}
for (let k of Object.keys(root)) {
let obj = root[k];
let stepIn = cb(k, obj);
if (stepIn) {
this.walk(obj, cb);
}
}
},
/**
* Collect data that requires walking through the data structure
*/
gatherComplexData: function(state, subsets) {
// The subset of sessionstore.js dealing with DOM storage
subsets.DOM_STORAGE = [];
// The subset of sessionstore.js storing form data
subsets.FORMDATA = [];
// The subset of sessionstore.js storing POST data in history
subsets.POSTDATA = [];
// The subset of sessionstore.js storing history
subsets.HISTORY = [];
this.walk(state, function(k, value) {
let dest;
switch (k) {
case "entries":
subsets.HISTORY.push(value);
return true;
case "storage":
subsets.DOM_STORAGE.push(value);
// Never visit storage, it's full of weird stuff
return false;
case "formdata":
subsets.FORMDATA.push(value);
// Never visit formdata, it's full of weird stuff
return false;
case "postdata_b64":
subsets.POSTDATA.push(value);
return false; // Nothing to visit anyway
case "cookies": // Don't visit these places, they are full of weird stuff
case "extData":
return false;
default:
return true;
}
});
return subsets;
},
};

View File

@ -295,12 +295,8 @@ let TabStateCacheInternal = {
for (let key of Object.keys(newData)) {
let value = newData[key];
if (value === null) {
// Remove the field if the value is null.
this.removeField(browser, key);
delete data[key];
} else {
// Update the field otherwise.
this.updateField(browser, key, value);
data[key] = value;
}
}

View File

@ -70,6 +70,7 @@ support-files =
[browser_sessionStorage.js]
[browser_swapDocShells.js]
[browser_tabStateCache.js]
[browser_telemetry.js]
[browser_upgrade_backup.js]
[browser_windowRestore_perwindowpb.js]
[browser_248970_b_perwindowpb.js]

View File

@ -3,7 +3,7 @@
"use strict";
const INITIAL_VALUE = "initial-value-" + Date.now();
const INITIAL_VALUE = "browser_broadcast.js-initial-value-" + Date.now();
/**
* This test ensures we won't lose tab data queued in the content script when

View File

@ -3,7 +3,7 @@
let Imports = {};
Cu.import("resource:///modules/sessionstore/SessionSaver.jsm", Imports);
let {SessionSaver} = Imports;
let {Task, SessionSaver} = Imports;
add_task(function cleanup() {
info("Forgetting closed tabs");

View File

@ -0,0 +1,266 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
let tmp = {};
Cu.import("resource:///modules/sessionstore/SessionFile.jsm", tmp);
Cu.import("resource:///modules/sessionstore/TabStateCache.jsm", tmp);
let {SessionFile, TabStateCache} = tmp;
// Shortcuts for histogram names
let Keys = {};
for (let k of ["HISTORY", "FORMDATA", "OPEN_WINDOWS", "CLOSED_WINDOWS", "CLOSED_TABS_IN_OPEN_WINDOWS", "DOM_STORAGE", "POSTDATA"]) {
Keys[k] = "FX_SESSION_RESTORE_TOTAL_" + k + "_SIZE_BYTES";
}
function lt(a, b, message) {
isnot(a, undefined, message + " (sanity check)");
isnot(b, undefined, message + " (sanity check)");
ok(a < b, message + " ( " + a + " < " + b + ")");
}
function gt(a, b, message) {
isnot(a, undefined, message + " (sanity check)");
isnot(b, undefined, message + " (sanity check)");
ok(a > b, message + " ( " + a + " > " + b + ")");
}
add_task(function init() {
for (let i = ss.getClosedWindowCount() - 1; i >= 0; --i) {
ss.forgetClosedWindow(i);
}
for (let i = ss.getClosedTabCount(window) - 1; i >= 0; --i) {
ss.forgetClosedTab(window, i);
}
});
/**
* Test that Telemetry collection doesn't cause any error.
*/
add_task(function() {
info("Checking a little bit of consistency");
let statistics = yield promiseStats();
for (let k of Object.keys(statistics)) {
let data = statistics[k];
info("Data for " + k + ": " + data);
if (Array.isArray(data)) {
ok(data.every(x => x >= 0), "Data for " + k + " is >= 0");
} else {
ok(data >= 0, "Data for " + k + " is >= 0");
}
}
});
/**
* Test HISTORY key.
*/
add_task(function history() {
let KEY = Keys.HISTORY;
let tab = gBrowser.addTab("http://example.org:80/?");
yield promiseBrowserLoaded(tab.linkedBrowser);
try {
SyncHandlers.get(tab.linkedBrowser).flush();
let statistics = yield promiseStats();
info("Now changing history");
tab.linkedBrowser.contentWindow.history.pushState({foo:1}, "ref");
SyncHandlers.get(tab.linkedBrowser).flush();
let statistics2 = yield promiseStats();
// We have changed history, so it must have increased
isnot(statistics[KEY], undefined, "Key was defined");
isnot(statistics2[KEY], undefined, "Key is still defined");
gt(statistics2[KEY], statistics[KEY], "The total size of HISTORY has increased");
// Almost nothing else should
for (let k of ["FORMDATA", "DOM_STORAGE", "CLOSED_WINDOWS", "CLOSED_TABS_IN_OPEN_WINDOWS"]) {
is(statistics2[Keys[k]], statistics[Keys[k]], "The total size of " + k + " has not increased");
}
} finally {
if (tab) {
gBrowser.removeTab(tab);
}
}
});
/**
* Test CLOSED_TABS_IN_OPEN_WINDOWS key.
*/
add_task(function close_tab() {
let KEY = Keys.CLOSED_TABS_IN_OPEN_WINDOWS;
let tab = gBrowser.addTab("http://example.org:80/?close_tab");
yield promiseBrowserLoaded(tab.linkedBrowser);
try {
SyncHandlers.get(tab.linkedBrowser).flush();
let statistics = yield promiseStats();
info("Now closing a tab");
gBrowser.removeTab(tab);
tab = null;
let statistics2 = yield promiseStats();
isnot(statistics[KEY], undefined, "Key was defined");
isnot(statistics2[KEY], undefined, "Key is still defined");
gt(statistics2[KEY], statistics[KEY], "The total size of CLOSED_TABS_IN_OPEN_WINDOWS has increased");
// Almost nothing else should change
for (let k of ["FORMDATA", "DOM_STORAGE", "CLOSED_WINDOWS"]) {
is(statistics2[Keys[k]], statistics[Keys[k]], "The total size of " + k + " has not increased");
}
} finally {
if (tab) {
gBrowser.removeTab(tab);
}
}
});
/**
* Test OPEN_WINDOWS key.
*/
add_task(function open_window() {
let KEY = Keys.OPEN_WINDOWS;
let win;
try {
let statistics = yield promiseStats();
win = yield promiseNewWindowLoaded("http://example.org:80/?open_window");
let statistics2 = yield promiseStats();
isnot(statistics[KEY], undefined, "Key was defined");
isnot(statistics2[KEY], undefined, "Key is still defined");
gt(statistics2[KEY], statistics[KEY], "The total size of OPEN_WINDOWS has increased");
// Almost nothing else should change
for (let k of ["FORMDATA", "DOM_STORAGE", "CLOSED_WINDOWS", "CLOSED_TABS_IN_OPEN_WINDOWS"]) {
is(statistics2[Keys[k]], statistics[Keys[k]], "The total size of " + k + " has not increased");
}
} finally {
if (win) {
yield promiseWindowClosed(win);
}
}
});
/**
* Test CLOSED_WINDOWS key.
*/
add_task(function close_window() {
let KEY = Keys.CLOSED_WINDOWS;
let win = yield promiseNewWindowLoaded("http://example.org:80/?close_window");
// We need to add something to the window, otherwise it won't be saved
let tab = win.gBrowser.addTab("http://example.org:80/?close_tab");
yield promiseBrowserLoaded(tab.linkedBrowser);
try {
let statistics = yield promiseStats();
yield promiseWindowClosed(win);
win = null;
let statistics2 = yield promiseStats();
isnot(statistics[KEY], undefined, "Key was defined");
isnot(statistics2[KEY], undefined, "Key is still defined");
gt(statistics2[KEY], statistics[KEY], "The total size of CLOSED_WINDOWS has increased");
lt(statistics2[Keys.OPEN_WINDOWS], statistics[Keys.OPEN_WINDOWS], "The total size of OPEN_WINDOWS has decreased");
// Almost nothing else should change
for (let k of ["FORMDATA", "DOM_STORAGE", "CLOSED_TABS_IN_OPEN_WINDOWS"]) {
is(statistics2[Keys[k]], statistics[Keys[k]], "The total size of " + k + " has not increased");
}
} finally {
if (win) {
yield promiseWindowClosed(win);
}
}
});
/**
* Test DOM_STORAGE key.
*/
add_task(function dom_storage() {
let KEY = Keys.DOM_STORAGE;
let tab = gBrowser.addTab("http://example.org:80/?dom_storage");
yield promiseBrowserLoaded(tab.linkedBrowser);
try {
SyncHandlers.get(tab.linkedBrowser).flush();
let statistics = yield promiseStats();
info("Now adding some storage");
yield modifySessionStorage(tab.linkedBrowser, {foo: "bar"});
SyncHandlers.get(tab.linkedBrowser).flush();
let statistics2 = yield promiseStats();
isnot(statistics[KEY], undefined, "Key was defined");
isnot(statistics2[KEY], undefined, "Key is still defined");
gt(statistics2[KEY], statistics[KEY], "The total size of DOM_STORAGE has increased");
// Almost nothing else should change
for (let k of ["CLOSED_TABS_IN_OPEN_WINDOWS", "FORMDATA", "CLOSED_WINDOWS"]) {
is(statistics2[Keys[k]], statistics[Keys[k]], "The total size of " + k + " has not increased");
}
} finally {
if (tab) {
gBrowser.removeTab(tab);
}
}
});
/**
* Test FORMDATA key.
*/
add_task(function formdata() {
let KEY = Keys.FORMDATA;
let tab = gBrowser.addTab("data:text/html;charset=utf-8,<input%20id='input'>");
yield promiseBrowserLoaded(tab.linkedBrowser);
try {
SyncHandlers.get(tab.linkedBrowser).flush();
let statistics = yield promiseStats();
info("Now changing form data");
yield modifyFormData(tab.linkedBrowser, {input: "This is some form data "});
SyncHandlers.get(tab.linkedBrowser).flush();
TabStateCache.delete(tab.linkedBrowser);
let statistics2 = yield promiseStats();
isnot(statistics[KEY], undefined, "Key was defined");
isnot(statistics2[KEY], undefined, "Key is still defined");
gt(statistics2[KEY], statistics[KEY], "The total size of FORMDATA has increased");
// Almost nothing else should
for (let k of ["DOM_STORAGE", "CLOSED_WINDOWS", "CLOSED_TABS_IN_OPEN_WINDOWS"]) {
is(statistics2[Keys[k]], statistics[Keys[k]], "The total size of " + k + " has not increased");
}
} finally {
if (tab) {
gBrowser.removeTab(tab);
}
}
});
/**
* Get the latest statistics.
*/
function promiseStats() {
let state = ss.getBrowserState();
info("Stats: " + state);
return SessionFile.gatherTelemetry(state);
}
function modifySessionStorage(browser, data) {
browser.messageManager.sendAsyncMessage("ss-test:modifySessionStorage", data);
return promiseContentMessage(browser, "ss-test:MozStorageChanged");
}
function modifyFormData(browser, data) {
browser.messageManager.sendAsyncMessage("ss-test:modifyFormData", data);
return promiseContentMessage(browser, "ss-test:modifyFormData:done");
}

View File

@ -39,6 +39,13 @@ addMessageListener("ss-test:modifySessionStorage2", function (msg) {
}
});
addMessageListener("ss-test:modifyFormData", function (msg) {
for (let id of Object.keys(msg.data)) {
content.document.getElementById(id).value = msg.data[id];
}
sendSyncMessage("ss-test:modifyFormData:done");
});
addMessageListener("ss-test:purgeDomainData", function ({data: domain}) {
Services.obs.notifyObservers(null, "browser:purge-domain-data", domain);
content.setTimeout(() => sendAsyncMessage("ss-test:purgeDomainData"));

View File

@ -617,9 +617,9 @@
@BINPATH@/browser/chrome.manifest
@BINPATH@/browser/chrome/browser@JAREXT@
@BINPATH@/browser/chrome/browser.manifest
#ifdef NIGHTLY_BUILD
@BINPATH@/browser/chrome/pdfjs.manifest
@BINPATH@/browser/chrome/pdfjs/*
#ifdef NIGHTLY_BUILD
@BINPATH@/browser/chrome/shumway.manifest
@BINPATH@/browser/chrome/shumway/*
#endif
@ -845,9 +845,9 @@ bin/libfreebl_32int64_3.so
#ifdef NIGHTLY_BUILD
@BINPATH@/metro/chrome/shumway.manifest
@BINPATH@/metro/chrome/shumway/*
#endif
@BINPATH@/metro/chrome/pdfjs.manifest
@BINPATH@/metro/chrome/pdfjs/*
#endif
@BINPATH@/metro/components
@BINPATH@/metro/defaults
@BINPATH@/metro/modules

View File

@ -2,23 +2,20 @@
<html>
<head>
<title>Test for Content Security Policy Connections</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<iframe style="width:200px;height:200px;" id='cspframe'></iframe>
<iframe style="width:200px;height:200px;" id='cspframe2'></iframe>
<script class="testbody" type="text/javascript">
var path = "/tests/content/base/test/csp/";
// These are test results: -1 means it hasn't run,
// These are test results: -1 means it hasn't run,
// true/false is the pass/fail result.
window.tests = {
img_good: -1,
@ -55,37 +52,34 @@ window.tests = {
object_spec_compliant_bad: -1,
};
// 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);
SpecialPowers.addObserver(this, "http-on-modify-request", false);
SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
}
examiner.prototype = {
observe: function(subject, topic, data) {
// subject should be an nsURI, and should be either allowed or blocked.
if (!SpecialPowers.can_QI(subject))
return;
var testpat = new RegExp("testid=([a-z0-9_]+)");
//_good things better be allowed!
//_bad things better be stopped!
if (topic === "http-on-modify-request") {
//these things were allowed by CSP
var asciiSpec = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIHttpChannel"), "URI.asciiSpec");
if (!testpat.test(asciiSpec)) return;
var testid = testpat.exec(asciiSpec)[1];
// This is a special observer topic that is proxied from
// http-on-modify-request in the parent process to inform us when a URI is
// loaded
if (topic === "specialpowers-http-notify-request") {
var uri = data;
if (!testpat.test(uri)) return;
var testid = testpat.exec(uri)[1];
window.testResult(testid,
/_good/.test(testid),
asciiSpec + " allowed by csp");
uri + " allowed by csp");
}
if(topic === "csp-on-violate-policy") {
//these were blocked... record that they were blocked
if (topic === "csp-on-violate-policy") {
// these were blocked... record that they were blocked
var asciiSpec = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIURI"), "asciiSpec");
if (!testpat.test(asciiSpec)) return;
var testid = testpat.exec(asciiSpec)[1];
@ -95,11 +89,11 @@ examiner.prototype = {
}
},
// must eventually call this to remove the listener,
// must eventually call this to remove the listener,
// or mochitests might get borked.
remove: function() {
SpecialPowers.removeObserver(this, "csp-on-violate-policy");
SpecialPowers.removeObserver(this, "http-on-modify-request");
SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
}
}
@ -126,7 +120,13 @@ window.testResult = function(testname, result, msg) {
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv(
{'set':[["security.csp.speccompliant", true]]},
{'set':[["security.csp.speccompliant", true],
// This defaults to 0 ("preload none") on mobile (B2G/Android), which
// blocks loading the resource until the user interacts with a
// corresponding widget, which breaks the media_* tests. We set it
// back to the default used by desktop Firefox to get consistent
// behavior.
["media.preload.default", 2]]},
function() {
// save this for last so that our listeners are registered.
// ... this loads the testbed of good and bad requests.

View File

@ -26,22 +26,16 @@ window.tests = {
function examiner() {
SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
SpecialPowers.addObserver(this, "http-on-modify-request", false);
SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
}
examiner.prototype = {
observe: function(subject, topic, data) {
// subject should be an nsURI, and should be either allowed or blocked.
if (!SpecialPowers.can_QI(subject))
return;
var testpat = new RegExp("testid=([a-zA-Z]+)");
if (topic === "http-on-modify-request") {
//these things were allowed by CSP
var asciiSpec = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIHttpChannel"), "URI.asciiSpec");
if (!testpat.test(asciiSpec))
return;
var testid = testpat.exec(asciiSpec)[1];
if (topic === "specialpowers-http-notify-request") {
var uri = data;
if (!testpat.test(uri)) return;
var testid = testpat.exec(uri)[1];
window.testResult(testid,
/Loaded/.test(testid),
"resource loaded");
@ -70,7 +64,7 @@ examiner.prototype = {
// or mochitests might get borked.
remove: function() {
SpecialPowers.removeObserver(this, "csp-on-violate-policy");
SpecialPowers.removeObserver(this, "http-on-modify-request");
SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
}
}

View File

@ -19,24 +19,20 @@
// get sent out to the wire.
function examiner() {
SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
SpecialPowers.addObserver(this, "http-on-modify-request", false);
SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
}
examiner.prototype = {
completedTests: 0,
totalTests: 4,
observe: function(subject, topic, data) {
// subject should be an nsURI, and should be either allowed or blocked.
if (!SpecialPowers.can_QI(subject))
return;
var testpat = new RegExp("testid=([a-z0-9_]+)");
if (topic === "http-on-modify-request") {
//these things were allowed by CSP
var asciiSpec = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIHttpChannel"), "URI.asciiSpec");
if (!testpat.test(asciiSpec)) return;
var testid = testpat.exec(asciiSpec)[1];
if (topic === "specialpowers-http-notify-request") {
// these things were allowed by CSP
var uri = data;
if (!testpat.test(uri)) return;
var testid = testpat.exec(uri)[1];
if (testid === "img_bad") {
// img_bad should be *allowed* because the policy is report-only
ok(true, "Inline scripts should execute (because the policy is report-only)");
@ -69,7 +65,7 @@ examiner.prototype = {
// or mochitests might get borked.
remove: function() {
SpecialPowers.removeObserver(this, "csp-on-violate-policy");
SpecialPowers.removeObserver(this, "http-on-modify-request");
SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
}
}

View File

@ -33,29 +33,25 @@ window.tests = {
// get sent out to the wire.
function examiner() {
SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
SpecialPowers.addObserver(this, "http-on-modify-request", false);
SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
}
examiner.prototype = {
observe: function(subject, topic, data) {
// subject should be an nsURI, and should be either allowed or blocked.
if (!SpecialPowers.can_QI(subject))
return;
var testpat = new RegExp("testid=([a-z0-9_]+)");
//_good things better be allowed!
//_bad things better be stopped!
if (topic === "http-on-modify-request") {
if (topic === "specialpowers-http-notify-request") {
//these things were allowed by CSP
var asciiSpec = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIHttpChannel"), "URI.asciiSpec");
if (!testpat.test(asciiSpec)) return;
var testid = testpat.exec(asciiSpec)[1];
var uri = data;
if (!testpat.test(uri)) return;
var testid = testpat.exec(uri)[1];
window.testResult(testid,
/_good/.test(testid),
asciiSpec + " allowed by csp");
uri + " allowed by csp");
}
if(topic === "csp-on-violate-policy") {
@ -73,7 +69,7 @@ examiner.prototype = {
// or mochitests might get borked.
remove: function() {
SpecialPowers.removeObserver(this, "csp-on-violate-policy");
SpecialPowers.removeObserver(this, "http-on-modify-request");
SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
}
}

View File

@ -2,16 +2,13 @@
<html>
<head>
<title>Test for Content Security Policy "no eval" base restriction</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<iframe style="width:100%;height:300px;" id='cspframe'></iframe>
<iframe style="width:100%;height:300px;" id='cspframe2'></iframe>
<iframe style="width:100%;height:300px;" id='cspframe3'></iframe>
@ -23,7 +20,6 @@ var evalScriptsThatRan = 0;
var evalScriptsBlocked = 0;
var evalScriptsTotal = 24;
// called by scripts that run
var scriptRan = function(shouldrun, testname, data) {
evalScriptsThatRan++;

View File

@ -2,16 +2,13 @@
<html>
<head>
<title>Test for Content Security Policy "no eval" in crypto.getCRMFRequest()</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<iframe style="width:100%;height:300px;" id='cspframe'></iframe>
<iframe style="width:100%;height:300px;" id='cspframe2'></iframe>
<iframe style="width:100%;height:300px;" id='cspframe3'></iframe>
@ -24,7 +21,6 @@ var evalScriptsThatRan = 0;
var evalScriptsBlocked = 0;
var evalScriptsTotal = 4;
// called by scripts that run
var scriptRan = function(shouldrun, testname, data) {
evalScriptsThatRan++;
@ -39,7 +35,6 @@ var scriptBlocked = function(shouldrun, testname, data) {
checkTestResults();
}
// Check to see if all the tests have run
var checkTestResults = function() {
// if any test is incomplete, keep waiting

View File

@ -2,23 +2,20 @@
<html>
<head>
<title>Test for Content Security Policy Frame Ancestors directive</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<iframe style="width:100%;height:300px;" id='cspframe'></iframe>
<iframe style="width:100%;height:300px;" id='cspframe2'></iframe>
<script class="testbody" type="text/javascript">
var path = "/tests/content/base/test/csp/";
// These are test results: -1 means it hasn't run,
// These are test results: -1 means it hasn't run,
// true/false is the pass/fail result.
var framesThatShouldLoad = {
aa_allow: -1, /* innermost frame allows a */
@ -47,7 +44,7 @@ var framesThatShouldLoad = {
var expectedViolationsLeft = 14;
// 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);
@ -57,7 +54,7 @@ examiner.prototype = {
// subject should be an nsURI, and should be either allowed or blocked.
if (!SpecialPowers.can_QI(subject))
return;
if (topic === "csp-on-violate-policy") {
//these were blocked... record that they were blocked
var asciiSpec = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIURI"), "asciiSpec");
@ -65,7 +62,7 @@ examiner.prototype = {
}
},
// must eventually call this to remove the listener,
// must eventually call this to remove the listener,
// or mochitests might get borked.
remove: function() {
SpecialPowers.removeObserver(this, "csp-on-violate-policy");

View File

@ -24,18 +24,14 @@ var totalTests = 2;
// get sent out to the wire.
function examiner() {
SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
SpecialPowers.addObserver(this, "http-on-modify-request", false);
SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
}
examiner.prototype = {
observe: function(subject, topic, data) {
// subject should be an nsURI, and should be either allowed or blocked.
if(!SpecialPowers.can_QI(subject))
return;
if (topic === "http-on-modify-request") {
var asciiSpec = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIHttpChannel"), "URI.asciiSpec");
if (asciiSpec == prefixedHeaderImgURL || asciiSpec == unprefixedHeaderImgURL) {
is(asciiSpec, unprefixedHeaderImgURL, "Load was allowed - should be allowed by unprefixed header (blocked by prefixed)");
if (topic === "specialpowers-http-notify-request") {
var allowedUri = data;
if (allowedUri == prefixedHeaderImgURL || allowedUri == unprefixedHeaderImgURL) {
is(allowedUri, unprefixedHeaderImgURL, "Load was allowed - should be allowed by unprefixed header (blocked by prefixed)");
testRan();
}
}
@ -55,7 +51,7 @@ examiner.prototype = {
// or mochitests might get borked.
remove: function() {
SpecialPowers.removeObserver(this, "csp-on-violate-policy");
SpecialPowers.removeObserver(this, "http-on-modify-request");
SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
}
}

View File

@ -43,22 +43,16 @@ window.violation_reports = {
// get sent out to the wire. This also watches for violation reports to go out.
function examiner() {
SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
SpecialPowers.addObserver(this, "http-on-modify-request", false);
SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
}
examiner.prototype = {
observe: function(subject, topic, data) {
// subject should be an nsURI, and should be either allowed or blocked.
if(!SpecialPowers.can_QI(subject))
return;
var testpat = new RegExp("testid=([a-z0-9_]+)");
if (topic === "http-on-modify-request") {
var asciiSpec = SpecialPowers.getPrivilegedProps(
SpecialPowers.do_QueryInterface(subject, "nsIHttpChannel"),
"URI.asciiSpec");
if (!testpat.test(asciiSpec)) return;
var testid = testpat.exec(asciiSpec)[1];
if (topic === "specialpowers-http-notify-request") {
var uri = data;
if (!testpat.test(uri)) return;
var testid = testpat.exec(uri)[1];
// violation reports don't come through here, but the requested resources do
// if the test has already finished, move on. Some things throw multiple
@ -68,8 +62,8 @@ examiner.prototype = {
} catch(e) { return; }
// these are requests that were allowed by CSP
var testid = testpat.exec(asciiSpec)[1];
window.testResult(testid, 'allowed', asciiSpec + " allowed by csp");
var testid = testpat.exec(uri)[1];
window.testResult(testid, 'allowed', uri + " allowed by csp");
}
if(topic === "csp-on-violate-policy") {
@ -112,7 +106,7 @@ examiner.prototype = {
// or mochitests might get borked.
remove: function() {
SpecialPowers.removeObserver(this, "csp-on-violate-policy");
SpecialPowers.removeObserver(this, "http-on-modify-request");
SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
}
}
window.bug836922examiner = new examiner();

View File

@ -9,10 +9,7 @@
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<iframe style="width:200px;height:200px;" id='cspframe' sandbox="allow-same-origin"></iframe>
<iframe style="width:200px;height:200px;" id='cspframe2' sandbox></iframe>
<iframe style="width:200px;height:200px;" id='cspframe3' sandbox="allow-same-origin"></iframe>
@ -96,28 +93,24 @@ function ok_wrapper(result, desc) {
// get sent out to the wire.
function examiner() {
SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
SpecialPowers.addObserver(this, "http-on-modify-request", false);
SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
}
examiner.prototype = {
observe: function(subject, topic, data) {
// subject should be an nsURI, and should be either allowed or blocked.
if (!SpecialPowers.can_QI(subject))
return;
var testpat = new RegExp("testid=([a-z0-9_]+)");
//_good things better be allowed!
//_bad things better be stopped!
if (topic === "http-on-modify-request") {
if (topic === "specialpowers-http-notify-request") {
//these things were allowed by CSP
var asciiSpec = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIHttpChannel"), "URI.asciiSpec");
if (!testpat.test(asciiSpec)) return;
var testid = testpat.exec(asciiSpec)[1];
var uri = data;
if (!testpat.test(uri)) return;
var testid = testpat.exec(uri)[1];
window.testResult(testid,
/_good/.test(testid),
asciiSpec + " allowed by csp");
uri + " allowed by csp");
}
if(topic === "csp-on-violate-policy") {
@ -135,7 +128,7 @@ examiner.prototype = {
// or mochitests might get borked.
remove: function() {
SpecialPowers.removeObserver(this, "csp-on-violate-policy");
SpecialPowers.removeObserver(this, "http-on-modify-request");
SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
}
}

View File

@ -20,6 +20,7 @@ var path = "/tests/content/base/test/csp/";
// debugging
function log(s) {
return;
dump("**" + s + "\n");
var log = document.getElementById("log");
log.textContent = log.textContent+s+"\n";
}
@ -27,26 +28,22 @@ function log(s) {
// used to watch if requests are blocked by CSP or allowed through
function examiner() {
SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
SpecialPowers.addObserver(this, "http-on-modify-request", false);
SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
}
examiner.prototype = {
observe: function(subject, topic, data) {
// subject should be an nsURI, and should be either allowed or blocked.
if (!SpecialPowers.can_QI(subject))
return;
var testpat = new RegExp("testid=([a-z0-9-]+)");
var asciiSpec;
var testid;
if (topic === "http-on-modify-request") {
if (topic === "specialpowers-http-notify-request") {
// request was sent
asciiSpec = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIHttpChannel"), "URI.asciiSpec");
if (!testpat.test(asciiSpec)) return;
testid = testpat.exec(asciiSpec)[1];
var allowedUri = data;
if (!testpat.test(allowedUri)) return;
testid = testpat.exec(allowedUri)[1];
if (testExpectedResults[testid] == "completed") return;
log("allowed: "+asciiSpec);
window.testResult(testid, asciiSpec, true);
log("allowed: "+allowedUri);
window.testResult(testid, allowedUri, true);
}
else if (topic === "csp-on-violate-policy") {
@ -65,7 +62,7 @@ examiner.prototype = {
remove: function() {
SpecialPowers.removeObserver(this, "csp-on-violate-policy");
SpecialPowers.removeObserver(this, "http-on-modify-request");
SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
}
}
window.examiner = new examiner();
@ -132,7 +129,13 @@ var testResult = function(testName, url, result) {
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv(
{'set':[["security.csp.speccompliant", true]]},
{'set':[["security.csp.speccompliant", true],
// This defaults to 0 ("preload none") on mobile (B2G/Android), which
// blocks loading the resource until the user interacts with a
// corresponding widget, which breaks the media_* tests. We set it
// back to the default used by desktop Firefox to get consistent
// behavior.
["media.preload.default", 2]]},
function() {
// save this for last so that our listeners are registered.
// ... this loads the testbed of good and bad requests.

View File

@ -26,25 +26,21 @@ var expectedScriptInlineViolations = 1;
// This is used to watch the blocked data bounce off CSP
function examiner() {
SpecialPowers.addObserver(this, "http-on-modify-request", false);
SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
}
examiner.prototype = {
observe: function(subject, topic, data) {
if (!SpecialPowers.can_QI(subject))
return;
var testid_re = new RegExp("testid=([a-z0-9_]+)");
//_good things better be allowed!
//_bad things better be blocked!
if (topic === "http-on-modify-request") {
// these things were allowed by CSP
var allowed_uri = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIHttpChannel"), "URI.asciiSpec");
if (!testid_re.test(allowed_uri)) return;
var testid = testid_re.exec(allowed_uri)[1];
if (topic === "specialpowers-http-notify-request") {
var uri = data;
if (!testid_re.test(uri)) return;
var testid = testid_re.exec(uri)[1];
ok(/_good/.test(testid), "Allowed URI with testid " + testid);
ranTests(1);
}
@ -59,6 +55,7 @@ examiner.prototype = {
ranTests(1);
} catch (e) {
// if the subject is blocked inline, data will be a violation msg (defined at the top of contentSecurityPolicy.js)
//dump("** exception in csp-on-violate-policy: " + e + "\n");
var violation_msg = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsISupportsCString"), "data");
if (/Inline Script/.test(violation_msg)) {
if (/Inline Script had invalid nonce/.test(violation_msg))
@ -73,7 +70,7 @@ examiner.prototype = {
},
// must eventually call this to remove the listener, or mochitests might get borked.
remove: function() {
SpecialPowers.removeObserver(this, "http-on-modify-request");
SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
SpecialPowers.removeObserver(this, "csp-on-violate-policy");
}
}

View File

@ -5,7 +5,6 @@
dist_dest = $(DIST)/$(APP_NAME).app
PREF_JS_EXPORTS = $(srcdir)/mobile.js
DIST_FILES = recommended-addons.json
ifndef LIBXUL_SDK
ifneq (Android,$(OS_TARGET))

View File

@ -1,15 +0,0 @@
{
"addons": [{
"id": "fullscreen@mbrubeck.limpet.net",
"name": "Full Screen",
"version": "3.4",
"iconURL": "https://addons.cdn.mozilla.net/img/uploads/addon_icons/252/252573-32.png?modified=1354183977",
"learnmoreURL": "https://addons.mozilla.org/en-US/android/addon/full-screen-252573/?src=api"
}, {
"id": "cloudviewer@starkravingfinkle.org",
"name": "Cloud Viewer",
"version": "2.1",
"iconURL": "https://addons.cdn.mozilla.net/img/uploads/addon_icons/295/295895-32.png?modified=1353947644",
"learnmoreURL": "https://addons.mozilla.org/en-US/android/addon/cloud-viewer/?src=api"
}]
}

View File

@ -32,8 +32,8 @@ import java.util.List;
/**
* A page in about:home that displays a ListView of bookmarks.
*/
public class BookmarksPage extends HomeFragment {
public static final String LOGTAG = "GeckoBookmarksPage";
public class BookmarksPanel extends HomeFragment {
public static final String LOGTAG = "GeckoBookmarksPanel";
// Cursor loader ID for list of bookmarks.
private static final int LOADER_ID_BOOKMARKS_LIST = 0;
@ -61,7 +61,7 @@ public class BookmarksPage extends HomeFragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.home_bookmarks_page, container, false);
final View view = inflater.inflate(R.layout.home_bookmarks_panel, container, false);
mList = (BookmarksListView) view.findViewById(R.id.bookmarks_list);

View File

@ -19,17 +19,17 @@ import android.view.LayoutInflater;
import android.widget.ImageButton;
public class HistoryPage extends HomeFragment
implements IconTabWidget.OnTabChangedListener {
public class HistoryPanel extends HomeFragment
implements IconTabWidget.OnTabChangedListener {
// Logging tag name
private static final String LOGTAG = "GeckoHistoryPage";
private static final String LOGTAG = "GeckoHistoryPanel";
private IconTabWidget mTabWidget;
private int mSelectedTab;
private boolean initializeRecentPage;
private boolean initializeRecentPanel;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.home_history_page, container, false);
return inflater.inflate(R.layout.home_history_panel, container, false);
}
@Override
@ -49,11 +49,11 @@ public class HistoryPage extends HomeFragment
@Override
public void load() {
// Show most recent page as the initial page.
// Show most recent panel as the initial panel.
// Since we detach/attach on config change, this prevents from replacing current fragment.
if (!initializeRecentPage) {
showMostRecentPage();
initializeRecentPage = true;
if (!initializeRecentPanel) {
showMostRecentPanel();
initializeRecentPanel = true;
}
}
@ -64,9 +64,9 @@ public class HistoryPage extends HomeFragment
}
if (index == 0) {
showMostRecentPage();
showMostRecentPanel();
} else if (index == 1) {
showLastTabsPage();
showLastTabsPanel();
}
mTabWidget.setCurrentTab(index);
@ -86,23 +86,23 @@ public class HistoryPage extends HomeFragment
}
}
private void showSubPage(Fragment subPage) {
private void showSubPanel(Fragment subPanel) {
final Bundle args = new Bundle();
args.putBoolean(HomePager.CAN_LOAD_ARG, getCanLoadHint());
subPage.setArguments(args);
subPanel.setArguments(args);
getChildFragmentManager().beginTransaction()
.addToBackStack(null).replace(R.id.history_page_container, subPage)
.addToBackStack(null).replace(R.id.history_panel_container, subPanel)
.commitAllowingStateLoss();
}
private void showMostRecentPage() {
final MostRecentPage mostRecentPage = MostRecentPage.newInstance();
showSubPage(mostRecentPage);
private void showMostRecentPanel() {
final MostRecentPanel mostRecentPanel = MostRecentPanel.newInstance();
showSubPanel(mostRecentPanel);
}
private void showLastTabsPage() {
final LastTabsPage lastTabsPage = LastTabsPage.newInstance();
showSubPage(lastTabsPage);
private void showLastTabsPanel() {
final LastTabsPanel lastTabsPanel = LastTabsPanel.newInstance();
showSubPanel(lastTabsPanel);
}
}

View File

@ -5,8 +5,8 @@
package org.mozilla.gecko.home;
import org.mozilla.gecko.home.HomeConfig.PageEntry;
import org.mozilla.gecko.home.HomeConfig.PageType;
import org.mozilla.gecko.home.HomeConfig.PanelConfig;
import org.mozilla.gecko.home.HomeConfig.PanelType;
import org.mozilla.gecko.home.HomePager;
import android.content.Context;
@ -23,15 +23,15 @@ import java.util.List;
class HomeAdapter extends FragmentStatePagerAdapter {
private final Context mContext;
private final ArrayList<PageInfo> mPageInfos;
private final HashMap<String, Fragment> mPages;
private final ArrayList<PanelInfo> mPanelInfos;
private final HashMap<String, Fragment> mPanels;
private boolean mCanLoadHint;
private OnAddPageListener mAddPageListener;
private OnAddPanelListener mAddPanelListener;
interface OnAddPageListener {
public void onAddPage(String title);
interface OnAddPanelListener {
public void onAddPanel(String title);
}
public HomeAdapter(Context context, FragmentManager fm) {
@ -40,25 +40,25 @@ class HomeAdapter extends FragmentStatePagerAdapter {
mContext = context;
mCanLoadHint = HomeFragment.DEFAULT_CAN_LOAD_HINT;
mPageInfos = new ArrayList<PageInfo>();
mPages = new HashMap<String, Fragment>();
mPanelInfos = new ArrayList<PanelInfo>();
mPanels = new HashMap<String, Fragment>();
}
@Override
public int getCount() {
return mPageInfos.size();
return mPanelInfos.size();
}
@Override
public Fragment getItem(int position) {
PageInfo info = mPageInfos.get(position);
PanelInfo info = mPanelInfos.get(position);
return Fragment.instantiate(mContext, info.getClassName(), info.getArgs());
}
@Override
public CharSequence getPageTitle(int position) {
if (mPageInfos.size() > 0) {
PageInfo info = mPageInfos.get(position);
if (mPanelInfos.size() > 0) {
PanelInfo info = mPanelInfos.get(position);
return info.getTitle().toUpperCase();
}
@ -68,7 +68,7 @@ class HomeAdapter extends FragmentStatePagerAdapter {
@Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment) super.instantiateItem(container, position);
mPages.put(mPageInfos.get(position).getId(), fragment);
mPanels.put(mPanelInfos.get(position).getId(), fragment);
return fragment;
}
@ -76,17 +76,17 @@ class HomeAdapter extends FragmentStatePagerAdapter {
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
mPages.remove(mPageInfos.get(position).getId());
mPanels.remove(mPanelInfos.get(position).getId());
}
public void setOnAddPageListener(OnAddPageListener listener) {
mAddPageListener = listener;
public void setOnAddPanelListener(OnAddPanelListener listener) {
mAddPanelListener = listener;
}
public int getItemPosition(String pageId) {
for (int i = 0; i < mPageInfos.size(); i++) {
final String id = mPageInfos.get(i).getId();
if (id.equals(pageId)) {
public int getItemPosition(String panelId) {
for (int i = 0; i < mPanelInfos.size(); i++) {
final String id = mPanelInfos.get(i).getId();
if (id.equals(panelId)) {
return i;
}
}
@ -94,32 +94,32 @@ class HomeAdapter extends FragmentStatePagerAdapter {
return -1;
}
public String getPageIdAtPosition(int position) {
// getPageAtPosition() might be called before HomeAdapter
// has got its initial list of PageEntries. Just bail.
if (mPageInfos.isEmpty()) {
public String getPanelIdAtPosition(int position) {
// getPanelIdAtPosition() might be called before HomeAdapter
// has got its initial list of PanelConfigs. Just bail.
if (mPanelInfos.isEmpty()) {
return null;
}
return mPageInfos.get(position).getId();
return mPanelInfos.get(position).getId();
}
private void addPage(PageInfo info) {
mPageInfos.add(info);
private void addPanel(PanelInfo info) {
mPanelInfos.add(info);
if (mAddPageListener != null) {
mAddPageListener.onAddPage(info.getTitle());
if (mAddPanelListener != null) {
mAddPanelListener.onAddPanel(info.getTitle());
}
}
public void update(List<PageEntry> pageEntries) {
mPages.clear();
mPageInfos.clear();
public void update(List<PanelConfig> panelConfigs) {
mPanels.clear();
mPanelInfos.clear();
if (pageEntries != null) {
for (PageEntry pageEntry : pageEntries) {
final PageInfo info = new PageInfo(pageEntry);
addPage(info);
if (panelConfigs != null) {
for (PanelConfig panelConfig : panelConfigs) {
final PanelInfo info = new PanelInfo(panelConfig);
addPanel(info);
}
}
@ -132,34 +132,34 @@ class HomeAdapter extends FragmentStatePagerAdapter {
public void setCanLoadHint(boolean canLoadHint) {
// We cache the last hint value so that we can use it when
// creating new pages. See PageInfo.getArgs().
// creating new panels. See PanelInfo.getArgs().
mCanLoadHint = canLoadHint;
// Enable/disable loading on all existing pages
for (Fragment page : mPages.values()) {
final HomeFragment homePage = (HomeFragment) page;
homePage.setCanLoadHint(canLoadHint);
// Enable/disable loading on all existing panels
for (Fragment panelFragment : mPanels.values()) {
final HomeFragment panel = (HomeFragment) panelFragment;
panel.setCanLoadHint(canLoadHint);
}
}
private final class PageInfo {
private final PageEntry mPageEntry;
private final class PanelInfo {
private final PanelConfig mPanelConfig;
PageInfo(PageEntry pageEntry) {
mPageEntry = pageEntry;
PanelInfo(PanelConfig panelConfig) {
mPanelConfig = panelConfig;
}
public String getId() {
return mPageEntry.getId();
return mPanelConfig.getId();
}
public String getTitle() {
return mPageEntry.getTitle();
return mPanelConfig.getTitle();
}
public String getClassName() {
final PageType type = mPageEntry.getType();
return type.getPageClass().getName();
final PanelType type = mPanelConfig.getType();
return type.getPanelClass().getName();
}
public Bundle getArgs() {
@ -167,9 +167,9 @@ class HomeAdapter extends FragmentStatePagerAdapter {
args.putBoolean(HomePager.CAN_LOAD_ARG, mCanLoadHint);
// Only list pages need the page entry
if (mPageEntry.getType() == PageType.LIST) {
args.putParcelable(HomePager.PAGE_ENTRY_ARG, mPageEntry);
// Only ListPanel's need the PanelConfig argument
if (mPanelConfig.getType() == PanelType.LIST) {
args.putParcelable(HomePager.PANEL_CONFIG_ARG, mPanelConfig);
}
return args;

View File

@ -14,33 +14,33 @@ import java.util.EnumSet;
import java.util.List;
final class HomeConfig {
public static enum PageType implements Parcelable {
TOP_SITES("top_sites", TopSitesPage.class),
BOOKMARKS("bookmarks", BookmarksPage.class),
HISTORY("history", HistoryPage.class),
READING_LIST("reading_list", ReadingListPage.class),
LIST("list", ListPage.class);
public static enum PanelType implements Parcelable {
TOP_SITES("top_sites", TopSitesPanel.class),
BOOKMARKS("bookmarks", BookmarksPanel.class),
HISTORY("history", HistoryPanel.class),
READING_LIST("reading_list", ReadingListPanel.class),
LIST("list", ListPanel.class);
private final String mId;
private final Class<?> mPageClass;
private final Class<?> mPanelClass;
PageType(String id, Class<?> pageClass) {
PanelType(String id, Class<?> panelClass) {
mId = id;
mPageClass = pageClass;
mPanelClass = panelClass;
}
public static PageType fromId(String id) {
public static PanelType fromId(String id) {
if (id == null) {
throw new IllegalArgumentException("Could not convert null String to PageType");
throw new IllegalArgumentException("Could not convert null String to PanelType");
}
for (PageType page : PageType.values()) {
if (TextUtils.equals(page.mId, id.toLowerCase())) {
return page;
for (PanelType panelType : PanelType.values()) {
if (TextUtils.equals(panelType.mId, id.toLowerCase())) {
return panelType;
}
}
throw new IllegalArgumentException("Could not convert String id to PageType");
throw new IllegalArgumentException("Could not convert String id to PanelType");
}
@Override
@ -48,8 +48,8 @@ final class HomeConfig {
return mId;
}
public Class<?> getPageClass() {
return mPageClass;
public Class<?> getPanelClass() {
return mPanelClass;
}
@Override
@ -62,72 +62,72 @@ final class HomeConfig {
dest.writeInt(ordinal());
}
public static final Creator<PageType> CREATOR = new Creator<PageType>() {
public static final Creator<PanelType> CREATOR = new Creator<PanelType>() {
@Override
public PageType createFromParcel(final Parcel source) {
return PageType.values()[source.readInt()];
public PanelType createFromParcel(final Parcel source) {
return PanelType.values()[source.readInt()];
}
@Override
public PageType[] newArray(final int size) {
return new PageType[size];
public PanelType[] newArray(final int size) {
return new PanelType[size];
}
};
}
public static class PageEntry implements Parcelable {
private final PageType mType;
public static class PanelConfig implements Parcelable {
private final PanelType mType;
private final String mTitle;
private final String mId;
private final EnumSet<Flags> mFlags;
public enum Flags {
DEFAULT_PAGE
DEFAULT_PANEL
}
@SuppressWarnings("unchecked")
public PageEntry(Parcel in) {
mType = (PageType) in.readParcelable(getClass().getClassLoader());
public PanelConfig(Parcel in) {
mType = (PanelType) in.readParcelable(getClass().getClassLoader());
mTitle = in.readString();
mId = in.readString();
mFlags = (EnumSet<Flags>) in.readSerializable();
}
public PageEntry(PageType type, String title) {
public PanelConfig(PanelType type, String title) {
this(type, title, EnumSet.noneOf(Flags.class));
}
public PageEntry(PageType type, String title, EnumSet<Flags> flags) {
public PanelConfig(PanelType type, String title, EnumSet<Flags> flags) {
this(type, title, type.toString(), flags);
}
public PageEntry(PageType type, String title, String id) {
public PanelConfig(PanelType type, String title, String id) {
this(type, title, id, EnumSet.noneOf(Flags.class));
}
public PageEntry(PageType type, String title, String id, EnumSet<Flags> flags) {
public PanelConfig(PanelType type, String title, String id, EnumSet<Flags> flags) {
if (type == null) {
throw new IllegalArgumentException("Can't create PageEntry with null type");
throw new IllegalArgumentException("Can't create PanelConfig with null type");
}
mType = type;
if (title == null) {
throw new IllegalArgumentException("Can't create PageEntry with null title");
throw new IllegalArgumentException("Can't create PanelConfig with null title");
}
mTitle = title;
if (id == null) {
throw new IllegalArgumentException("Can't create PageEntry with null id");
throw new IllegalArgumentException("Can't create PanelConfig with null id");
}
mId = id;
if (flags == null) {
throw new IllegalArgumentException("Can't create PageEntry with null flags");
throw new IllegalArgumentException("Can't create PanelConfig with null flags");
}
mFlags = flags;
}
public PageType getType() {
public PanelType getType() {
return mType;
}
@ -140,7 +140,7 @@ final class HomeConfig {
}
public boolean isDefault() {
return mFlags.contains(Flags.DEFAULT_PAGE);
return mFlags.contains(Flags.DEFAULT_PANEL);
}
@Override
@ -156,15 +156,15 @@ final class HomeConfig {
dest.writeSerializable(mFlags);
}
public static final Creator<PageEntry> CREATOR = new Creator<PageEntry>() {
public static final Creator<PanelConfig> CREATOR = new Creator<PanelConfig>() {
@Override
public PageEntry createFromParcel(final Parcel in) {
return new PageEntry(in);
public PanelConfig createFromParcel(final Parcel in) {
return new PanelConfig(in);
}
@Override
public PageEntry[] newArray(final int size) {
return new PageEntry[size];
public PanelConfig[] newArray(final int size) {
return new PanelConfig[size];
}
};
}
@ -174,8 +174,8 @@ final class HomeConfig {
}
public interface HomeConfigBackend {
public List<PageEntry> load();
public void save(List<PageEntry> entries);
public List<PanelConfig> load();
public void save(List<PanelConfig> entries);
public void setOnChangeListener(OnChangeListener listener);
}
@ -185,11 +185,11 @@ final class HomeConfig {
mBackend = backend;
}
public List<PageEntry> load() {
public List<PanelConfig> load() {
return mBackend.load();
}
public void save(List<PageEntry> entries) {
public void save(List<PanelConfig> entries) {
mBackend.save(entries);
}

View File

@ -5,7 +5,7 @@
package org.mozilla.gecko.home;
import org.mozilla.gecko.home.HomeConfig.PageEntry;
import org.mozilla.gecko.home.HomeConfig.PanelConfig;
import org.mozilla.gecko.home.HomeConfig.OnChangeListener;
import android.content.Context;
@ -13,9 +13,9 @@ import android.support.v4.content.AsyncTaskLoader;
import java.util.List;
public class HomeConfigLoader extends AsyncTaskLoader<List<PageEntry>> {
public class HomeConfigLoader extends AsyncTaskLoader<List<PanelConfig>> {
private final HomeConfig mConfig;
private List<PageEntry> mPageEntries;
private List<PanelConfig> mPanelConfigs;
public HomeConfigLoader(Context context, HomeConfig homeConfig) {
super(context);
@ -23,32 +23,32 @@ public class HomeConfigLoader extends AsyncTaskLoader<List<PageEntry>> {
}
@Override
public List<PageEntry> loadInBackground() {
public List<PanelConfig> loadInBackground() {
return mConfig.load();
}
@Override
public void deliverResult(List<PageEntry> pageEntries) {
public void deliverResult(List<PanelConfig> panelConfigs) {
if (isReset()) {
mPageEntries = null;
mPanelConfigs = null;
return;
}
mPageEntries = pageEntries;
mPanelConfigs = panelConfigs;
mConfig.setOnChangeListener(new ForceLoadChangeListener());
if (isStarted()) {
super.deliverResult(pageEntries);
super.deliverResult(panelConfigs);
}
}
@Override
protected void onStartLoading() {
if (mPageEntries != null) {
deliverResult(mPageEntries);
if (mPanelConfigs != null) {
deliverResult(mPanelConfigs);
}
if (takeContentChanged() || mPageEntries == null) {
if (takeContentChanged() || mPanelConfigs == null) {
forceLoad();
}
}
@ -59,8 +59,8 @@ public class HomeConfigLoader extends AsyncTaskLoader<List<PageEntry>> {
}
@Override
public void onCanceled(List<PageEntry> pageEntries) {
mPageEntries = null;
public void onCanceled(List<PanelConfig> panelConfigs) {
mPanelConfigs = null;
}
@Override
@ -70,7 +70,7 @@ public class HomeConfigLoader extends AsyncTaskLoader<List<PageEntry>> {
// Ensure the loader is stopped.
onStopLoading();
mPageEntries = null;
mPanelConfigs = null;
mConfig.setOnChangeListener(null);
}

View File

@ -8,9 +8,8 @@ package org.mozilla.gecko.home;
import org.mozilla.gecko.R;
import org.mozilla.gecko.home.HomeConfig.HomeConfigBackend;
import org.mozilla.gecko.home.HomeConfig.OnChangeListener;
import org.mozilla.gecko.home.HomeConfig.PageEntry;
import org.mozilla.gecko.home.HomeConfig.PageType;
import org.mozilla.gecko.home.ListManager.ListInfo;
import org.mozilla.gecko.home.HomeConfig.PanelConfig;
import org.mozilla.gecko.home.HomeConfig.PanelType;
import org.mozilla.gecko.util.HardwareUtils;
import org.mozilla.gecko.util.ThreadUtils;
@ -54,129 +53,129 @@ class HomeConfigPrefsBackend implements HomeConfigBackend {
return PreferenceManager.getDefaultSharedPreferences(mContext);
}
private List<PageEntry> loadDefaultConfig() {
final ArrayList<PageEntry> pageEntries = new ArrayList<PageEntry>();
private List<PanelConfig> loadDefaultConfig() {
final ArrayList<PanelConfig> panelConfigs = new ArrayList<PanelConfig>();
pageEntries.add(new PageEntry(PageType.TOP_SITES,
panelConfigs.add(new PanelConfig(PanelType.TOP_SITES,
mContext.getString(R.string.home_top_sites_title),
EnumSet.of(PageEntry.Flags.DEFAULT_PAGE)));
EnumSet.of(PanelConfig.Flags.DEFAULT_PANEL)));
pageEntries.add(new PageEntry(PageType.BOOKMARKS,
panelConfigs.add(new PanelConfig(PanelType.BOOKMARKS,
mContext.getString(R.string.bookmarks_title)));
// We disable reader mode support on low memory devices. Hence the
// reading list page should not show up on such devices.
// reading list panel should not show up on such devices.
if (!HardwareUtils.isLowMemoryPlatform()) {
pageEntries.add(new PageEntry(PageType.READING_LIST,
panelConfigs.add(new PanelConfig(PanelType.READING_LIST,
mContext.getString(R.string.reading_list_title)));
}
final PageEntry historyEntry = new PageEntry(PageType.HISTORY,
final PanelConfig historyEntry = new PanelConfig(PanelType.HISTORY,
mContext.getString(R.string.home_history_title));
// On tablets, the history page is the last.
// On phones, the history page is the first one.
// On tablets, the history panel is the last.
// On phones, the history panel is the first one.
if (HardwareUtils.isTablet()) {
pageEntries.add(historyEntry);
panelConfigs.add(historyEntry);
} else {
pageEntries.add(0, historyEntry);
panelConfigs.add(0, historyEntry);
}
return pageEntries;
return panelConfigs;
}
private List<PageEntry> loadConfigFromString(String jsonString) {
final JSONArray jsonPageEntries;
private List<PanelConfig> loadConfigFromString(String jsonString) {
final JSONArray jsonPanelConfigs;
try {
jsonPageEntries = new JSONArray(jsonString);
jsonPanelConfigs = new JSONArray(jsonString);
} catch (JSONException e) {
Log.e(LOGTAG, "Error loading the list of home pages from JSON prefs", e);
Log.e(LOGTAG, "Error loading the list of home panels from JSON prefs", e);
// Fallback to default config
return loadDefaultConfig();
}
final ArrayList<PageEntry> pageEntries = new ArrayList<PageEntry>();
final ArrayList<PanelConfig> panelConfigs = new ArrayList<PanelConfig>();
final int count = jsonPageEntries.length();
final int count = jsonPanelConfigs.length();
for (int i = 0; i < count; i++) {
try {
final JSONObject jsonPageEntry = jsonPageEntries.getJSONObject(i);
final JSONObject jsonPanelConfig = jsonPanelConfigs.getJSONObject(i);
final PageEntry pageEntry = loadPageEntryFromJSON(jsonPageEntry);
pageEntries.add(pageEntry);
final PanelConfig panelConfig = loadPanelConfigFromJSON(jsonPanelConfig);
panelConfigs.add(panelConfig);
} catch (Exception e) {
Log.e(LOGTAG, "Exception loading page entry from JSON", e);
Log.e(LOGTAG, "Exception loading PanelConfig from JSON", e);
}
}
return pageEntries;
return panelConfigs;
}
private PageEntry loadPageEntryFromJSON(JSONObject jsonPageEntry)
private PanelConfig loadPanelConfigFromJSON(JSONObject jsonPanelConfig)
throws JSONException, IllegalArgumentException {
final PageType type = PageType.fromId(jsonPageEntry.getString(JSON_KEY_TYPE));
final String title = jsonPageEntry.getString(JSON_KEY_TITLE);
final String id = jsonPageEntry.getString(JSON_KEY_ID);
final PanelType type = PanelType.fromId(jsonPanelConfig.getString(JSON_KEY_TYPE));
final String title = jsonPanelConfig.getString(JSON_KEY_TITLE);
final String id = jsonPanelConfig.getString(JSON_KEY_ID);
final EnumSet<PageEntry.Flags> flags = EnumSet.noneOf(PageEntry.Flags.class);
final boolean isDefault = (jsonPageEntry.optInt(JSON_KEY_DEFAULT, -1) == IS_DEFAULT);
final EnumSet<PanelConfig.Flags> flags = EnumSet.noneOf(PanelConfig.Flags.class);
final boolean isDefault = (jsonPanelConfig.optInt(JSON_KEY_DEFAULT, -1) == IS_DEFAULT);
if (isDefault) {
flags.add(PageEntry.Flags.DEFAULT_PAGE);
flags.add(PanelConfig.Flags.DEFAULT_PANEL);
}
return new PageEntry(type, title, id, flags);
return new PanelConfig(type, title, id, flags);
}
@Override
public List<PageEntry> load() {
public List<PanelConfig> load() {
final SharedPreferences prefs = getSharedPreferences();
final String jsonString = prefs.getString(PREFS_KEY, null);
final List<PageEntry> pageEntries;
final List<PanelConfig> panelConfigs;
if (TextUtils.isEmpty(jsonString)) {
pageEntries = loadDefaultConfig();
panelConfigs = loadDefaultConfig();
} else {
pageEntries = loadConfigFromString(jsonString);
panelConfigs = loadConfigFromString(jsonString);
}
return Collections.unmodifiableList(pageEntries);
return Collections.unmodifiableList(panelConfigs);
}
private JSONObject convertPageEntryToJSON(PageEntry pageEntry) throws JSONException {
final JSONObject jsonPageEntry = new JSONObject();
private JSONObject convertPanelConfigToJSON(PanelConfig PanelConfig) throws JSONException {
final JSONObject jsonPanelConfig = new JSONObject();
jsonPageEntry.put(JSON_KEY_TYPE, pageEntry.getType().toString());
jsonPageEntry.put(JSON_KEY_TITLE, pageEntry.getTitle());
jsonPageEntry.put(JSON_KEY_ID, pageEntry.getId());
jsonPanelConfig.put(JSON_KEY_TYPE, PanelConfig.getType().toString());
jsonPanelConfig.put(JSON_KEY_TITLE, PanelConfig.getTitle());
jsonPanelConfig.put(JSON_KEY_ID, PanelConfig.getId());
if (pageEntry.isDefault()) {
jsonPageEntry.put(JSON_KEY_DEFAULT, IS_DEFAULT);
if (PanelConfig.isDefault()) {
jsonPanelConfig.put(JSON_KEY_DEFAULT, IS_DEFAULT);
}
return jsonPageEntry;
return jsonPanelConfig;
}
@Override
public void save(List<PageEntry> pageEntries) {
final JSONArray jsonPageEntries = new JSONArray();
public void save(List<PanelConfig> panelConfigs) {
final JSONArray jsonPanelConfigs = new JSONArray();
final int count = pageEntries.size();
final int count = panelConfigs.size();
for (int i = 0; i < count; i++) {
try {
final PageEntry pageEntry = pageEntries.get(i);
final PanelConfig PanelConfig = panelConfigs.get(i);
final JSONObject jsonPageEntry = convertPageEntryToJSON(pageEntry);
jsonPageEntries.put(jsonPageEntry);
final JSONObject jsonPanelConfig = convertPanelConfigToJSON(PanelConfig);
jsonPanelConfigs.put(jsonPanelConfig);
} catch (Exception e) {
Log.e(LOGTAG, "Exception loading page entry from JSON", e);
Log.e(LOGTAG, "Exception converting PanelConfig to JSON", e);
}
}
final SharedPreferences prefs = getSharedPreferences();
final SharedPreferences.Editor editor = prefs.edit();
final String jsonString = jsonPageEntries.toString();
final String jsonString = jsonPanelConfigs.toString();
editor.putString(PREFS_KEY, jsonString);
editor.commit();
}

View File

@ -8,9 +8,9 @@ package org.mozilla.gecko.home;
import org.mozilla.gecko.R;
import org.mozilla.gecko.animation.PropertyAnimator;
import org.mozilla.gecko.animation.ViewHelper;
import org.mozilla.gecko.home.HomeAdapter.OnAddPageListener;
import org.mozilla.gecko.home.HomeConfig.PageEntry;
import org.mozilla.gecko.home.HomeConfig.PageType;
import org.mozilla.gecko.home.HomeAdapter.OnAddPanelListener;
import org.mozilla.gecko.home.HomeConfig.PanelConfig;
import org.mozilla.gecko.home.HomeConfig.PanelType;
import org.mozilla.gecko.util.HardwareUtils;
import android.content.Context;
@ -40,12 +40,12 @@ public class HomePager extends ViewPager {
private Decor mDecor;
private View mTabStrip;
private final OnAddPageListener mAddPageListener;
private final OnAddPanelListener mAddPanelListener;
private final HomeConfig mConfig;
private ConfigLoaderCallbacks mConfigLoaderCallbacks;
private String mInitialPageId;
private String mInitialPanelId;
// Whether or not we need to restart the loader when we show the HomePager.
private boolean mRestartLoader;
@ -88,7 +88,7 @@ public class HomePager extends ViewPager {
}
static final String CAN_LOAD_ARG = "canLoad";
static final String PAGE_ENTRY_ARG = "pageEntry";
static final String PANEL_CONFIG_ARG = "panelConfig";
public HomePager(Context context) {
this(context, null);
@ -101,16 +101,16 @@ public class HomePager extends ViewPager {
mConfig = HomeConfig.getDefault(mContext);
mConfigLoaderCallbacks = new ConfigLoaderCallbacks();
mAddPageListener = new OnAddPageListener() {
mAddPanelListener = new OnAddPanelListener() {
@Override
public void onAddPage(String title) {
public void onAddPanel(String title) {
if (mDecor != null) {
mDecor.onAddPagerView(title);
}
}
};
// This is to keep all 4 pages in memory after they are
// This is to keep all 4 panels in memory after they are
// selected in the pager.
setOffscreenPageLimit(3);
@ -173,17 +173,17 @@ public class HomePager extends ViewPager {
private void redisplay(LoaderManager lm, FragmentManager fm) {
final HomeAdapter adapter = (HomeAdapter) getAdapter();
// If mInitialPageId is non-null, this means the HomePager hasn't
// If mInitialPanelId is non-null, this means the HomePager hasn't
// finished loading its config yet. Simply re-show() with the
// current target page.
final String currentPageId;
if (mInitialPageId != null) {
currentPageId = mInitialPageId;
// current target panel.
final String currentPanelId;
if (mInitialPanelId != null) {
currentPanelId = mInitialPanelId;
} else {
currentPageId = adapter.getPageIdAtPosition(getCurrentItem());
currentPanelId = adapter.getPanelIdAtPosition(getCurrentItem());
}
show(lm, fm, currentPageId, null);
show(lm, fm, currentPanelId, null);
}
/**
@ -191,25 +191,25 @@ public class HomePager extends ViewPager {
*
* @param fm FragmentManager for the adapter
*/
public void show(LoaderManager lm, FragmentManager fm, String pageId, PropertyAnimator animator) {
public void show(LoaderManager lm, FragmentManager fm, String panelId, PropertyAnimator animator) {
mLoaded = true;
mInitialPageId = pageId;
mInitialPanelId = panelId;
// Only animate on post-HC devices, when a non-null animator is given
final boolean shouldAnimate = (animator != null && Build.VERSION.SDK_INT >= 11);
final HomeAdapter adapter = new HomeAdapter(mContext, fm);
adapter.setOnAddPageListener(mAddPageListener);
adapter.setOnAddPanelListener(mAddPanelListener);
adapter.setCanLoadHint(!shouldAnimate);
setAdapter(adapter);
setVisibility(VISIBLE);
// Don't show the tabs strip until we have the
// list of pages in place.
// list of panels in place.
mTabStrip.setVisibility(View.INVISIBLE);
// Load list of pages from configuration. Restart the loader if necessary.
// Load list of panels from configuration. Restart the loader if necessary.
if (mRestartLoader) {
lm.restartLoader(LOADER_ID_CONFIG, null, mConfigLoaderCallbacks);
mRestartLoader = false;
@ -279,7 +279,7 @@ public class HomePager extends ViewPager {
return super.onInterceptTouchEvent(event);
}
private void updateUiFromPageEntries(List<PageEntry> pageEntries) {
private void updateUiFromPanelConfigs(List<PanelConfig> panelConfigs) {
// We only care about the adapter if HomePager is currently
// loaded, which means it's visible in the activity.
if (!mLoaded) {
@ -293,27 +293,27 @@ public class HomePager extends ViewPager {
final HomeAdapter adapter = (HomeAdapter) getAdapter();
// Disable loading until the final current item is defined
// after loading the page entries. This is to stop any temporary
// after loading the panel configs. This is to stop any temporary
// active item from loading.
boolean originalCanLoadHint = adapter.getCanLoadHint();
adapter.setCanLoadHint(false);
// Update the adapter with the new page entries
adapter.update(pageEntries);
// Update the adapter with the new panel configs
adapter.update(panelConfigs);
final int count = (pageEntries != null ? pageEntries.size() : 0);
final int count = (panelConfigs != null ? panelConfigs.size() : 0);
mTabStrip.setVisibility(count > 0 ? View.VISIBLE : View.INVISIBLE);
// Use the default page as defined in the HomePager's configuration
// if the initial page wasn't explicitly set by the show() caller.
if (mInitialPageId != null) {
// XXX: Handle the case where the desired page isn't currently in the adapter (bug 949178)
setCurrentItem(adapter.getItemPosition(mInitialPageId), false);
mInitialPageId = null;
// Use the default panel as defined in the HomePager's configuration
// if the initial panel wasn't explicitly set by the show() caller.
if (mInitialPanelId != null) {
// XXX: Handle the case where the desired panel isn't currently in the adapter (bug 949178)
setCurrentItem(adapter.getItemPosition(mInitialPanelId), false);
mInitialPanelId = null;
} else {
for (int i = 0; i < count; i++) {
final PageEntry pageEntry = pageEntries.get(i);
if (pageEntry.isDefault()) {
final PanelConfig panelConfig = panelConfigs.get(i);
if (panelConfig.isDefault()) {
setCurrentItem(i, false);
break;
}
@ -325,20 +325,20 @@ public class HomePager extends ViewPager {
adapter.setCanLoadHint(originalCanLoadHint);
}
private class ConfigLoaderCallbacks implements LoaderCallbacks<List<PageEntry>> {
private class ConfigLoaderCallbacks implements LoaderCallbacks<List<PanelConfig>> {
@Override
public Loader<List<PageEntry>> onCreateLoader(int id, Bundle args) {
public Loader<List<PanelConfig>> onCreateLoader(int id, Bundle args) {
return new HomeConfigLoader(mContext, mConfig);
}
@Override
public void onLoadFinished(Loader<List<PageEntry>> loader, List<PageEntry> pageEntries) {
updateUiFromPageEntries(pageEntries);
public void onLoadFinished(Loader<List<PanelConfig>> loader, List<PanelConfig> panelConfigs) {
updateUiFromPanelConfigs(panelConfigs);
}
@Override
public void onLoaderReset(Loader<List<PageEntry>> loader) {
updateUiFromPageEntries(null);
public void onLoaderReset(Loader<List<PanelConfig>> loader) {
updateUiFromPanelConfigs(null);
}
}
}

View File

@ -36,9 +36,9 @@ import android.widget.TextView;
/**
* Fragment that displays tabs from last session in a ListView.
*/
public class LastTabsPage extends HomeFragment {
public class LastTabsPanel extends HomeFragment {
// Logging tag name
private static final String LOGTAG = "GeckoLastTabsPage";
private static final String LOGTAG = "GeckoLastTabsPanel";
// Cursor loader ID for the session parser
private static final int LOADER_ID_LAST_TABS = 0;
@ -49,7 +49,7 @@ public class LastTabsPage extends HomeFragment {
// The view shown by the fragment.
private ListView mList;
// The title for this HomeFragment page.
// The title for this HomeFragment panel.
private TextView mTitle;
// The button view for restoring tabs from last session.
@ -64,11 +64,11 @@ public class LastTabsPage extends HomeFragment {
// On new tabs listener
private OnNewTabsListener mNewTabsListener;
public static LastTabsPage newInstance() {
return new LastTabsPage();
public static LastTabsPanel newInstance() {
return new LastTabsPanel();
}
public LastTabsPage() {
public LastTabsPanel() {
mNewTabsListener = null;
}
@ -93,7 +93,7 @@ public class LastTabsPage extends HomeFragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.home_last_tabs_page, container, false);
return inflater.inflate(R.layout.home_last_tabs_panel, container, false);
}
@Override
@ -169,7 +169,7 @@ public class LastTabsPage extends HomeFragment {
mRestoreButton.setVisibility(View.GONE);
if (mEmptyView == null) {
// Set empty page view. We delay this so that the empty view won't flash.
// Set empty panel view. We delay this so that the empty view won't flash.
final ViewStub emptyViewStub = (ViewStub) getView().findViewById(R.id.home_empty_view_stub);
mEmptyView = emptyViewStub.inflate();

View File

@ -9,7 +9,7 @@ import org.mozilla.gecko.R;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.BrowserContract.HomeListItems;
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
import org.mozilla.gecko.home.HomeConfig.PageEntry;
import org.mozilla.gecko.home.HomeConfig.PanelConfig;
import android.app.Activity;
import android.content.ContentResolver;
@ -32,14 +32,14 @@ import java.util.EnumSet;
/**
* Fragment that displays custom lists.
*/
public class ListPage extends HomeFragment {
private static final String LOGTAG = "GeckoListPage";
public class ListPanel extends HomeFragment {
private static final String LOGTAG = "GeckoListPanel";
// Cursor loader ID for the lists
private static final int LOADER_ID_LIST = 0;
// The page entry associated with this page
private PageEntry mPageEntry;
// The configuration associated with this panel
private PanelConfig mPanelConfig;
// Adapter for the list
private HomeListAdapter mAdapter;
@ -78,11 +78,11 @@ public class ListPage extends HomeFragment {
final Bundle args = getArguments();
if (args != null) {
mPageEntry = (PageEntry) args.getParcelable(HomePager.PAGE_ENTRY_ARG);
mPanelConfig = (PanelConfig) args.getParcelable(HomePager.PANEL_CONFIG_ARG);
}
if (mPageEntry == null) {
throw new IllegalStateException("Can't create a ListPage without a PageEntry");
if (mPanelConfig == null) {
throw new IllegalStateException("Can't create a ListPanel without a PanelConfig");
}
}
@ -189,7 +189,7 @@ public class ListPage extends HomeFragment {
private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new HomeListLoader(getActivity(), mPageEntry.getId());
return new HomeListLoader(getActivity(), mPanelConfig.getId());
}
@Override

View File

@ -35,9 +35,9 @@ import java.util.EnumSet;
/**
* Fragment that displays recent history in a ListView.
*/
public class MostRecentPage extends HomeFragment {
public class MostRecentPanel extends HomeFragment {
// Logging tag name
private static final String LOGTAG = "GeckoMostRecentPage";
private static final String LOGTAG = "GeckoMostRecentPanel";
// Cursor loader ID for history query
private static final int LOADER_ID_HISTORY = 0;
@ -57,11 +57,11 @@ public class MostRecentPage extends HomeFragment {
// On URL open listener
private OnUrlOpenListener mUrlOpenListener;
public static MostRecentPage newInstance() {
return new MostRecentPage();
public static MostRecentPanel newInstance() {
return new MostRecentPanel();
}
public MostRecentPage() {
public MostRecentPanel() {
mUrlOpenListener = null;
}
@ -85,7 +85,7 @@ public class MostRecentPage extends HomeFragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.home_most_recent_page, container, false);
return inflater.inflate(R.layout.home_most_recent_panel, container, false);
}
@Override
@ -155,7 +155,7 @@ public class MostRecentPage extends HomeFragment {
// Cursor is empty, so set the empty view if it hasn't been set already.
if (mEmptyView == null) {
// Set empty page view. We delay this so that the empty view won't flash.
// Set empty panel view. We delay this so that the empty view won't flash.
final ViewStub emptyViewStub = (ViewStub) getView().findViewById(R.id.home_empty_view_stub);
mEmptyView = emptyViewStub.inflate();

View File

@ -21,14 +21,14 @@ import android.util.Log;
import java.util.ArrayList;
import java.util.List;
public class ListManager implements GeckoEventListener {
private static final String LOGTAG = "GeckoListManager";
public class PanelManager implements GeckoEventListener {
private static final String LOGTAG = "GeckoPanelManager";
public class ListInfo {
public class PanelInfo {
public final String id;
public final String title;
public ListInfo(String id, String title) {
public PanelInfo(String id, String title) {
this.id = id;
this.title = title;
}
@ -36,20 +36,20 @@ public class ListManager implements GeckoEventListener {
private final Context mContext;
public ListManager(Context context) {
public PanelManager(Context context) {
mContext = context;
// Add a listener to handle any new lists that are added after the lists have been loaded.
GeckoAppShell.getEventDispatcher().registerEventListener("HomeLists:Added", this);
// Add a listener to handle any new panels that are added after the panels have been loaded.
GeckoAppShell.getEventDispatcher().registerEventListener("HomePanels:Added", this);
}
/**
* Reads list info from SharedPreferences. Don't call this on the main thread!
*
* @return List<ListInfo> A list of ListInfos for each registered list.
* @return List<PanelInfo> A list of PanelInfos for each registered list.
*/
public List<ListInfo> getListInfos() {
final ArrayList<ListInfo> listInfos = new ArrayList<ListInfo>();
public List<PanelInfo> getPanelInfos() {
final ArrayList<PanelInfo> panelInfos = new ArrayList<PanelInfo>();
// XXX: We need to use PreferenceManager right now because that's what SharedPreferences.jsm uses (see bug 940575)
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
@ -60,23 +60,23 @@ public class ListManager implements GeckoEventListener {
final JSONArray lists = new JSONArray(prefValue);
for (int i = 0; i < lists.length(); i++) {
final JSONObject list = lists.getJSONObject(i);
final ListInfo info = new ListInfo(list.getString("id"), list.getString("title"));
listInfos.add(info);
final PanelInfo info = new PanelInfo(list.getString("id"), list.getString("title"));
panelInfos.add(info);
}
} catch (JSONException e) {
Log.e(LOGTAG, "Exception getting list info", e);
}
}
return listInfos;
return panelInfos;
}
/**
* Listens for "HomeLists:Added"
* Listens for "HomePanels:Added"
*/
@Override
public void handleMessage(String event, JSONObject message) {
try {
final ListInfo info = new ListInfo(message.getString("id"), message.getString("title"));
final PanelInfo info = new PanelInfo(message.getString("id"), message.getString("title"));
// Do something to update the set of list pages.

View File

@ -111,7 +111,7 @@ class PinSiteDialog extends DialogFragment {
// If the user manually entered a search term or URL, wrap the value in
// a special URI until we can get a valid URL for this bookmark.
final String text = mSearch.getText().toString();
final String url = TopSitesPage.encodeUserEnteredUrl(text);
final String url = TopSitesPanel.encodeUserEnteredUrl(text);
mOnSiteSelectedListener.onSiteSelected(url, text);
dismiss();

View File

@ -37,7 +37,7 @@ import java.util.EnumSet;
/**
* Fragment that displays reading list contents in a ListView.
*/
public class ReadingListPage extends HomeFragment {
public class ReadingListPanel extends HomeFragment {
// Cursor loader ID for reading list
private static final int LOADER_ID_READING_LIST = 0;
@ -80,7 +80,7 @@ public class ReadingListPage extends HomeFragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.home_reading_list_page, container, false);
return inflater.inflate(R.layout.home_reading_list_panel, container, false);
}
@Override

View File

@ -193,7 +193,7 @@ public class TopSitesGridItemView extends RelativeLayout {
// The dirty state forces the state update to return true
// so that the adapter loads favicons once the thumbnails
// are loaded in TopSitesPage/TopSitesGridAdapter.
// are loaded in TopSitesPanel/TopSitesGridAdapter.
changed = (changed || mIsDirty);
mIsDirty = false;

View File

@ -106,7 +106,7 @@ public class TopSitesGridView extends GridView {
TopSitesGridItemView row = (TopSitesGridItemView) view;
// Decode "user-entered" URLs before loading them.
String url = TopSitesPage.decodeUserEnteredUrl(row.getUrl());
String url = TopSitesPanel.decodeUserEnteredUrl(row.getUrl());
// If the url is empty, the user can pin a site.
// If not, navigate to the page given by the url.

View File

@ -62,9 +62,9 @@ import java.util.Map;
/**
* Fragment that displays frecency search results in a ListView.
*/
public class TopSitesPage extends HomeFragment {
public class TopSitesPanel extends HomeFragment {
// Logging tag name
private static final String LOGTAG = "GeckoTopSitesPage";
private static final String LOGTAG = "GeckoTopSitesPanel";
// Cursor loader ID for the top sites
private static final int LOADER_ID_TOP_SITES = 0;
@ -114,11 +114,11 @@ 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;
public static TopSitesPage newInstance() {
return new TopSitesPage();
public static TopSitesPanel newInstance() {
return new TopSitesPanel();
}
public TopSitesPage() {
public TopSitesPanel() {
mUrlOpenListener = null;
}
@ -160,7 +160,7 @@ public class TopSitesPage extends HomeFragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.home_top_sites_page, container, false);
final View view = inflater.inflate(R.layout.home_top_sites_panel, container, false);
mList = (HomeListView) view.findViewById(R.id.list);
@ -212,7 +212,7 @@ public class TopSitesPage extends HomeFragment {
mList.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
TopSitesPage.this.handleListTouchEvent(event);
TopSitesPanel.this.handleListTouchEvent(event);
return false;
}
});

View File

@ -208,10 +208,10 @@ gbjar.sources += [
'home/BookmarkFolderView.java',
'home/BookmarksListAdapter.java',
'home/BookmarksListView.java',
'home/BookmarksPage.java',
'home/BookmarksPanel.java',
'home/BrowserSearch.java',
'home/FadedTextView.java',
'home/HistoryPage.java',
'home/HistoryPanel.java',
'home/HomeAdapter.java',
'home/HomeBanner.java',
'home/HomeConfig.java',
@ -221,13 +221,13 @@ gbjar.sources += [
'home/HomeListView.java',
'home/HomePager.java',
'home/HomePagerTabStrip.java',
'home/LastTabsPage.java',
'home/ListManager.java',
'home/ListPage.java',
'home/MostRecentPage.java',
'home/LastTabsPanel.java',
'home/ListPanel.java',
'home/MostRecentPanel.java',
'home/MultiTypeCursorAdapter.java',
'home/PanelManager.java',
'home/PinSiteDialog.java',
'home/ReadingListPage.java',
'home/ReadingListPanel.java',
'home/SearchEngine.java',
'home/SearchEngineRow.java',
'home/SearchLoader.java',
@ -236,7 +236,7 @@ gbjar.sources += [
'home/TabMenuStrip.java',
'home/TopSitesGridItemView.java',
'home/TopSitesGridView.java',
'home/TopSitesPage.java',
'home/TopSitesPanel.java',
'home/TopSitesThumbnailView.java',
'home/TwoLinePageRow.java',
'InputMethods.java',

View File

@ -16,7 +16,7 @@
android:layout="@layout/home_history_tabs_indicator"
gecko:display="text"/>
<FrameLayout android:id="@+id/history_page_container"
<FrameLayout android:id="@+id/history_panel_container"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1" />

View File

@ -16,7 +16,7 @@
android:layout="@layout/home_history_tabs_indicator"
gecko:display="text"/>
<FrameLayout android:id="@+id/history_page_container"
<FrameLayout android:id="@+id/history_panel_container"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1" />

View File

@ -11,7 +11,7 @@
android:layout_height="fill_parent"/>
<TextView android:id="@+id/title"
style="@style/Widget.Home.HistoryPageTitle"
style="@style/Widget.Home.HistoryPanelTitle"
android:visibility="gone"/>
<org.mozilla.gecko.home.HomeListView

View File

@ -8,7 +8,7 @@
android:layout_height="fill_parent"
android:orientation="vertical">
<FrameLayout android:id="@+id/history_page_container"
<FrameLayout android:id="@+id/history_panel_container"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1" />

View File

@ -53,7 +53,7 @@
<!--
The content of the banner should align with the Grid/List views
in BookmarksPage. BookmarksListView has a 120dp padding and
in BookmarksPanel. BookmarksListView has a 120dp padding and
the TwoLinePageRows have a 50dp padding. Hence HomeBanner should
have 170dp padding.
-->

View File

@ -91,7 +91,7 @@
<style name="Widget.ReadingListView" parent="Widget.BookmarksListView"/>
<style name="Widget.Home.HistoryPageTitle" parent="Widget.Home.HistoryTabIndicator">
<style name="Widget.Home.HistoryPanelTitle" parent="Widget.Home.HistoryTabIndicator">
<item name="android:layout_marginLeft">32dp</item>
<item name="android:layout_marginRight">32dp</item>
</style>

View File

@ -209,14 +209,14 @@
<item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">32dp</item>
<item name="android:textAppearance">@style/TextAppearance.Widget.Home.PageTitle</item>
<item name="android:background">@drawable/home_page_title_background</item>
<item name="android:background">@drawable/home_panel_title_background</item>
<item name="android:focusable">false</item>
<item name="android:gravity">center|left</item>
<item name="android:paddingLeft">10dip</item>
<item name="android:paddingRight">10dip</item>
</style>
<style name="Widget.Home.HistoryPageTitle" parent="Widget.Home.HistoryTabIndicator"/>
<style name="Widget.Home.HistoryPanelTitle" parent="Widget.Home.HistoryTabIndicator"/>
<!--
TextAppearance

View File

@ -24,8 +24,8 @@ import android.view.View;
public class AboutHomeComponent extends BaseComponent {
private static final String LOGTAG = AboutHomeComponent.class.getSimpleName();
// The different types of pages that can be present on about:home
public enum PageType {
// The different types of panels that can be present on about:home
public enum PanelType {
HISTORY,
TOP_SITES,
BOOKMARKS,
@ -62,7 +62,7 @@ public class AboutHomeComponent extends BaseComponent {
return (ViewPager) mSolo.getView(R.id.home_pager);
}
public AboutHomeComponent assertCurrentPage(final PageType expectedPage) {
public AboutHomeComponent assertCurrentPage(final PanelType expectedPage) {
assertVisible();
final int expectedPageIndex = getPageIndexForDevice(expectedPage.ordinal());
@ -132,10 +132,10 @@ public class AboutHomeComponent extends BaseComponent {
/**
* Gets the page index in the device specific Page enum for the given index in the
* PageType enum.
* PanelType enum.
*/
private int getPageIndexForDevice(final int pageIndex) {
final String pageName = PageType.values()[pageIndex].name();
final String pageName = PanelType.values()[pageIndex].name();
final Class devicePageEnum =
DeviceHelper.isTablet() ? TabletPage.class : PhonePage.class;
return Enum.valueOf(devicePageEnum, pageName).ordinal();

View File

@ -9,7 +9,7 @@ skip-if = processor == "x86"
# disabled on x86 only; bug 927476
skip-if = processor == "x86"
# [testBookmark] # see bug 915350
[testBookmarksPage]
[testBookmarksPanel]
[testBookmarkFolders]
# [testBookmarklets] # see bug 915350
# [testBookmarkKeyword] # see bug 915350

View File

@ -2,7 +2,7 @@ package org.mozilla.gecko.tests;
import static org.mozilla.gecko.tests.helpers.AssertionHelper.*;
import org.mozilla.gecko.tests.components.AboutHomeComponent.PageType;
import org.mozilla.gecko.tests.components.AboutHomeComponent.PanelType;
import org.mozilla.gecko.tests.helpers.*;
/**
@ -16,13 +16,13 @@ public class testAboutHomePageNavigation extends UITest {
GeckoHelper.blockForReady();
mAboutHome.assertVisible()
.assertCurrentPage(PageType.TOP_SITES);
.assertCurrentPage(PanelType.TOP_SITES);
mAboutHome.swipeToPageOnRight();
mAboutHome.assertCurrentPage(PageType.BOOKMARKS);
mAboutHome.assertCurrentPage(PanelType.BOOKMARKS);
mAboutHome.swipeToPageOnRight();
mAboutHome.assertCurrentPage(PageType.READING_LIST);
mAboutHome.assertCurrentPage(PanelType.READING_LIST);
// Ideally these helpers would just be their own tests. However, by keeping this within
// one method, we're saving test setUp and tearDown resources.
@ -35,46 +35,46 @@ public class testAboutHomePageNavigation extends UITest {
private void helperTestTablet() {
mAboutHome.swipeToPageOnRight();
mAboutHome.assertCurrentPage(PageType.HISTORY);
mAboutHome.assertCurrentPage(PanelType.HISTORY);
// Edge case.
mAboutHome.swipeToPageOnRight();
mAboutHome.assertCurrentPage(PageType.HISTORY);
mAboutHome.assertCurrentPage(PanelType.HISTORY);
mAboutHome.swipeToPageOnLeft();
mAboutHome.assertCurrentPage(PageType.READING_LIST);
mAboutHome.assertCurrentPage(PanelType.READING_LIST);
mAboutHome.swipeToPageOnLeft();
mAboutHome.assertCurrentPage(PageType.BOOKMARKS);
mAboutHome.assertCurrentPage(PanelType.BOOKMARKS);
mAboutHome.swipeToPageOnLeft();
mAboutHome.assertCurrentPage(PageType.TOP_SITES);
mAboutHome.assertCurrentPage(PanelType.TOP_SITES);
// Edge case.
mAboutHome.swipeToPageOnLeft();
mAboutHome.assertCurrentPage(PageType.TOP_SITES);
mAboutHome.assertCurrentPage(PanelType.TOP_SITES);
}
private void helperTestPhone() {
// Edge case.
mAboutHome.swipeToPageOnRight();
mAboutHome.assertCurrentPage(PageType.READING_LIST);
mAboutHome.assertCurrentPage(PanelType.READING_LIST);
mAboutHome.swipeToPageOnLeft();
mAboutHome.assertCurrentPage(PageType.BOOKMARKS);
mAboutHome.assertCurrentPage(PanelType.BOOKMARKS);
mAboutHome.swipeToPageOnLeft();
mAboutHome.assertCurrentPage(PageType.TOP_SITES);
mAboutHome.assertCurrentPage(PanelType.TOP_SITES);
mAboutHome.swipeToPageOnLeft();
mAboutHome.assertCurrentPage(PageType.HISTORY);
mAboutHome.assertCurrentPage(PanelType.HISTORY);
// Edge case.
mAboutHome.swipeToPageOnLeft();
mAboutHome.assertCurrentPage(PageType.HISTORY);
mAboutHome.assertCurrentPage(PanelType.HISTORY);
mAboutHome.swipeToPageOnRight();
mAboutHome.assertCurrentPage(PageType.TOP_SITES);
mAboutHome.assertCurrentPage(PanelType.TOP_SITES);
}
// TODO: bug 943706 - reimplement this old test code.

View File

@ -2,7 +2,7 @@ package org.mozilla.gecko.tests;
import static org.mozilla.gecko.tests.helpers.AssertionHelper.*;
import org.mozilla.gecko.tests.components.AboutHomeComponent.PageType;
import org.mozilla.gecko.tests.components.AboutHomeComponent.PanelType;
import org.mozilla.gecko.tests.helpers.*;
/**
@ -15,7 +15,7 @@ public class testAboutHomeVisibility extends UITest {
// Check initial state on about:home.
mToolbar.assertTitle(StringHelper.ABOUT_HOME_TITLE);
mAboutHome.assertVisible()
.assertCurrentPage(PageType.TOP_SITES);
.assertCurrentPage(PanelType.TOP_SITES);
// Go to blank 01.
NavigationHelper.enterAndLoadUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
@ -30,7 +30,7 @@ public class testAboutHomeVisibility extends UITest {
// Enter editing mode, where the about:home UI should be visible.
mToolbar.enterEditingMode();
mAboutHome.assertVisible()
.assertCurrentPage(PageType.TOP_SITES);
.assertCurrentPage(PanelType.TOP_SITES);
// Dismiss editing mode, where the about:home UI should be gone.
mToolbar.dismissEditingMode();
@ -40,7 +40,7 @@ public class testAboutHomeVisibility extends UITest {
NavigationHelper.enterAndLoadUrl(StringHelper.ABOUT_HOME_URL);
mToolbar.assertTitle(StringHelper.ABOUT_HOME_TITLE);
mAboutHome.assertVisible()
.assertCurrentPage(PageType.TOP_SITES);
.assertCurrentPage(PanelType.TOP_SITES);
// TODO: Type in a url and assert the go button is visible.
}

View File

@ -2,13 +2,13 @@ package org.mozilla.gecko.tests;
import org.mozilla.gecko.*;
public class testBookmarksPage extends AboutHomeTest {
public class testBookmarksPanel extends AboutHomeTest {
protected int getTestType() {
return TEST_MOCHITEST;
}
public void testBookmarksPage() {
public void testBookmarksPanel() {
final String BOOKMARK_URL = getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
// Add a mobile bookmark

View File

@ -125,23 +125,36 @@ var SelectionHandler = {
this._ignoreSelectionChanges = true;
// Check to see if the handles should be reversed.
let isStartHandle = JSON.parse(aData).handleType == this.HANDLE_TYPE_START;
let selectionReversed = this._updateCacheForSelection(isStartHandle);
if (selectionReversed) {
// Reverse the anchor and focus to correspond to the new start and end handles.
let selection = this._getSelection();
let anchorNode = selection.anchorNode;
let anchorOffset = selection.anchorOffset;
selection.collapse(selection.focusNode, selection.focusOffset);
selection.extend(anchorNode, anchorOffset);
try {
let selectionReversed = this._updateCacheForSelection(isStartHandle);
if (selectionReversed) {
// Reverse the anchor and focus to correspond to the new start and end handles.
let selection = this._getSelection();
let anchorNode = selection.anchorNode;
let anchorOffset = selection.anchorOffset;
selection.collapse(selection.focusNode, selection.focusOffset);
selection.extend(anchorNode, anchorOffset);
}
} catch (e) {
// User finished handle positioning with one end off the screen
this._closeSelection();
break;
}
// Act on selectionChange notifications after handle movement ends
this._ignoreSelectionChanges = false;
this._positionHandles();
} else if (this._activeType == this.TYPE_CURSOR) {
// Act on IMM composition notifications after caret movement ends
this._ignoreCompositionChanges = false;
this._positionHandles();
} else {
Cu.reportError("Ignored \"TextSelection:Position\" message during invalid selection status");
}
this._positionHandles();
break;
}
@ -743,8 +756,12 @@ var SelectionHandler = {
// Returns true if the selection has been reversed. Takes optional aIsStartHandle
// param to decide whether the selection has been reversed.
_updateCacheForSelection: function sh_updateCacheForSelection(aIsStartHandle) {
let selection = this._getSelection();
let rects = selection.getRangeAt(0).getClientRects();
let rects = this._getSelection().getRangeAt(0).getClientRects();
if (!rects[0]) {
// nsISelection object exists, but there's nothing actually selected
throw "Failed to update cache for invalid selection";
}
let start = { x: this._isRTL ? rects[0].right : rects[0].left, y: rects[0].bottom };
let end = { x: this._isRTL ? rects[rects.length - 1].left : rects[rects.length - 1].right, y: rects[rects.length - 1].bottom };

View File

@ -68,8 +68,6 @@ AddonUpdateService.prototype = {
}
});
});
RecommendedSearchResults.search();
}
};
@ -113,71 +111,5 @@ UpdateCheckListener.prototype = {
}
};
// -----------------------------------------------------------------------
// RecommendedSearchResults fetches add-on data and saves it to a cache
// -----------------------------------------------------------------------
var RecommendedSearchResults = {
_getFile: function() {
let dirService = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
let file = dirService.get("ProfD", Ci.nsILocalFile);
file.append("recommended-addons.json");
return file;
},
_writeFile: function (aFile, aData) {
if (!aData)
return;
// Asynchronously copy the data to the file.
let array = new TextEncoder().encode(aData);
OS.File.writeAtomic(aFile.path, array, { tmpPath: aFile.path + ".tmp" }).then(function onSuccess() {
Services.obs.notifyObservers(null, "recommended-addons-cache-updated", "");
});
},
searchSucceeded: function(aAddons, aAddonCount, aTotalResults) {
let self = this;
// Filter addons already installed
AddonManager.getAllAddons(function(aAllAddons) {
let addons = aAddons.filter(function(addon) {
for (let i = 0; i < aAllAddons.length; i++)
if (addon.id == aAllAddons[i].id)
return false;
return true;
});
let json = {
addons: []
};
addons.forEach(function(aAddon) {
json.addons.push({
id: aAddon.id,
name: aAddon.name,
version: aAddon.version,
learnmoreURL: aAddon.learnmoreURL,
iconURL: aAddon.iconURL
})
});
let file = self._getFile();
self._writeFile(file, JSON.stringify(json));
});
},
searchFailed: function searchFailed() { },
search: function() {
const kAddonsMaxDisplay = 2;
if (AddonRepository.isSearching)
AddonRepository.cancelSearch();
AddonRepository.retrieveRecommendedAddons(kAddonsMaxDisplay, RecommendedSearchResults);
}
}
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AddonUpdateService]);

View File

@ -87,7 +87,6 @@
@BINPATH@/res/drawable
@BINPATH@/res/drawable-hdpi
@BINPATH@/res/layout
@BINPATH@/recommended-addons.json
@BINPATH@/distribution/*
[browser]

View File

@ -137,7 +137,7 @@ let HomeBanner = {
}
};
function List(options) {
function Panel(options) {
if ("id" in options)
this.id = options.id;
@ -145,11 +145,12 @@ function List(options) {
this.title = options.title;
}
function HomeLists() {
function HomePanels() {
// XXX: Not renaming this because it is going away in bug 958192
this.PREF_KEY = "home_lists";
this._sharedPrefs = new SharedPreferences();
this._lists = {};
this._panels = {};
let prefValue = this._sharedPrefs.getCharPref(this.PREF_KEY);
if (!prefValue) {
@ -157,47 +158,47 @@ function HomeLists() {
}
JSON.parse(prefValue).forEach(data => {
let list = new List(data);
this._lists[list.id] = list;
let panel = new Panel(data);
this._panels[panel.id] = panel;
});
}
HomeLists.prototype = {
HomePanels.prototype = {
add: function(options) {
let list = new List(options);
if (!list.id || !list.title) {
throw "Can't create a home list without an id and title!";
let panel = new Panel(options);
if (!panel.id || !panel.title) {
throw "Can't create a home panel without an id and title!";
}
// Bail if the list already exists
if (list.id in this._lists) {
throw "List already exists: " + list.id;
// Bail if the panel already exists
if (panel.id in this._panels) {
throw "Panel already exists: " + panel.id;
}
this._lists[list.id] = list;
this._panels[panel.id] = panel;
this._updateSharedPref();
// Send a message to Java to update the home pager if it's currently showing
sendMessageToJava({
type: "HomeLists:Added",
id: list.id,
title: list.title
type: "HomePanels:Added",
id: panel.id,
title: panel.title
});
},
remove: function(id) {
delete this._lists[id];
delete this._panels[id];
this._updateSharedPref();
},
// Set a shared pref so that Java can know about this list before Gecko is running
// Set a shared pref so that Java can know about this panel before Gecko is running
_updateSharedPref: function() {
let lists = [];
for (let id in this._lists) {
let list = this._lists[id];
lists.push({ id: list.id, title: list.title});
let panels = [];
for (let id in this._panels) {
let panel = this._panels[id];
panels.push({ id: panel.id, title: panel.title});
}
this._sharedPrefs.setCharPref(this.PREF_KEY, JSON.stringify(lists));
this._sharedPrefs.setCharPref(this.PREF_KEY, JSON.stringify(panels));
}
};
@ -205,5 +206,5 @@ HomeLists.prototype = {
// Public API
this.Home = {
banner: HomeBanner,
lists: new HomeLists()
panels: new HomePanels()
}

View File

@ -205,18 +205,6 @@
"content/base/test/test_object.html":"needs plugin support",
"content/base/test/test_bug827160.html": "needs plugin support",
"content/base/test/csp/test_CSP_evalscript.html":"observer not working",
"content/base/test/csp/test_CSP_evalscript_getCRMFRequest.html":"observer not working",
"content/base/test/csp/test_CSP_frameancestors.html":"observer not working",
"content/base/test/csp/test_CSP.html":"observer not working",
"content/base/test/csp/test_bug836922_npolicies.html":"observer not working",
"content/base/test/csp/test_bug886164.html":"observer not working",
"content/base/test/csp/test_CSP_bug916446.html":"observer not working",
"content/base/test/csp/test_CSP_bug909029.html":"observer not working",
"content/base/test/csp/test_policyuri_regression_from_multipolicy.html":"observer not working",
"content/base/test/csp/test_nonce_source.html":"observer not working",
"content/base/test/csp/test_CSP_bug941404.html":"observer not working",
"content/base/test/test_CrossSiteXHR_origin.html":"https not working, bug 907770",
"content/base/test/test_plugin_freezing.html":"",
"content/base/test/test_bug466409.html":"",
@ -229,7 +217,6 @@
"content/base/test/test_bug717511.html":"",
"content/base/test/test_copypaste.xhtml":"bug 904183",
"content/base/test/test_copypaste.xul":"bug 904183",
"content/base/test/csp/test_csp_redirects.html":"",
"content/base/test/test_fileapi_slice.html":"",
"content/base/test/test_mixed_content_blocker.html":"",
"content/base/test/test_mixed_content_blocker_bug803225.html":"",
@ -255,7 +242,6 @@
"content/svg/content/test/test_text_selection.html":"Mouse selection not workin on b2g",
"content/svg/content/test/test_SVGAnimatedImageSMILDisabled.html":"",
"content/xml/document/test/test_bug392338.html":"",
"content/base/test/csp/test_bothCSPheaders.html":"",
"content/base/test/test_bug383430.html":"",
"content/base/test/test_bug422403-2.xhtml":"",
"content/base/test/test_bug424359-1.html":"",
@ -433,6 +419,8 @@
"layout/style/test/test_visited_reftests.html":"bug 870262, :visited support",
"Harness_sanity/test_sanityEventUtils.html": "bug 688052",
"Harness_sanity/test_sanitySimpletest.html": "bug 688052"
"Harness_sanity/test_sanitySimpletest.html": "bug 688052",
"content/base/test/csp/test_CSP_evalscript_getCRMFRequest.html":"no window.crypto support in multiprocess"
}
}

View File

@ -70,6 +70,13 @@ SpecialPowersObserver.prototype = new SpecialPowersObserverAPI();
}
break;
case "http-on-modify-request":
if (aSubject instanceof Ci.nsIChannel) {
let uri = aSubject.URI.spec;
this._sendAsyncMessage("specialpowers-http-notify-request", { uri: uri });
}
break;
case "xpcom-shutdown":
this.uninit();
break;
@ -99,6 +106,7 @@ SpecialPowersObserver.prototype = new SpecialPowersObserverAPI();
var obs = Services.obs;
obs.addObserver(this, "xpcom-shutdown", false);
obs.addObserver(this, "chrome-document-global-created", false);
obs.addObserver(this, "http-on-modify-request", false);
if (messageManager) {
this._messageManager = messageManager;
@ -110,6 +118,7 @@ SpecialPowersObserver.prototype = new SpecialPowersObserverAPI();
{
var obs = Services.obs;
obs.removeObserver(this, "chrome-document-global-created");
obs.removeObserver(this, "http-on-modify-request");
this._removeProcessCrashObservers();
};

View File

@ -973,7 +973,27 @@ SpecialPowersAPI.prototype = {
return this._sendSyncMessage("SPWebAppService", message);
},
_proxiedObservers: {
"specialpowers-http-notify-request": function(aMessage) {
let uri = aMessage.json.uri;
Services.obs.notifyObservers(null, "specialpowers-http-notify-request", uri);
},
},
_addObserverProxy: function(notification) {
if (notification in this._proxiedObservers) {
this._addMessageListener(notification, this._proxiedObservers[notification]);
}
},
_removeObserverProxy: function(notification) {
if (notification in this._proxiedObservers) {
this._removeMessageListener(notification, this._proxiedObservers[notification]);
}
},
addObserver: function(obs, notification, weak) {
this._addObserverProxy(notification);
if (typeof obs == 'object' && obs.observe.name != 'SpecialPowersCallbackWrapper')
obs.observe = wrapCallback(obs.observe);
var obsvc = Cc['@mozilla.org/observer-service;1']
@ -981,6 +1001,7 @@ SpecialPowersAPI.prototype = {
obsvc.addObserver(obs, notification, weak);
},
removeObserver: function(obs, notification) {
this._removeObserverProxy(notification);
var obsvc = Cc['@mozilla.org/observer-service;1']
.getService(Ci.nsIObserverService);
obsvc.removeObserver(obs, notification);

View File

@ -3351,6 +3351,126 @@
"n_values": 101,
"description": "Session restore: Number of times the tab state cache has been cleared during a session divided by number of total accesses during the session (percentage)"
},
"FX_SESSION_RESTORE_EXTRACTING_STATISTICS_DURATION_MS": {
"expires_in_version": "never",
"kind": "exponential",
"high": "3000",
"n_buckets": 10,
"extended_statistics_ok": true,
"description": "Session restore: Duration of the off main thread statistics extraction mechanism (ms)"
},
"FX_SESSION_RESTORE_TOTAL_OPEN_WINDOWS_SIZE_BYTES": {
"expires_in_version": "never",
"kind": "exponential",
"high": "50000000",
"n_buckets": 30,
"description": "Session restore: The subset of sessionrestore.js representing open windows (total size, in bytes)"
},
"FX_SESSION_RESTORE_TOTAL_CLOSED_WINDOWS_SIZE_BYTES": {
"expires_in_version": "never",
"kind": "exponential",
"high": "50000000",
"n_buckets": 30,
"description": "Session restore: The subset of sessionrestore.js representing closed windows (total size, in bytes)"
},
"FX_SESSION_RESTORE_TOTAL_CLOSED_TABS_IN_OPEN_WINDOWS_SIZE_BYTES": {
"expires_in_version": "never",
"kind": "exponential",
"high": "50000000",
"n_buckets": 30,
"description": "Sessionrestore: The subset of sesionstore.js representing closed tabs in open windows (total size, in bytes)"
},
"FX_SESSION_RESTORE_TOTAL_COOKIES_SIZE_BYTES": {
"expires_in_version": "never",
"kind": "exponential",
"high": "50000000",
"n_buckets": 30,
"description": "The subset of sessionstore.js dealing with cookies (total size, in bytes)"
},
"FX_SESSION_RESTORE_TOTAL_DOM_STORAGE_SIZE_BYTES": {
"expires_in_version": "never",
"kind": "exponential",
"high": "50000000",
"n_buckets": 30,
"description": "The subset of sessionstore.js dealing with DOM storage (total size, in bytes)"
},
"FX_SESSION_RESTORE_TOTAL_FORMDATA_SIZE_BYTES": {
"expires_in_version": "never",
"kind": "exponential",
"high": "50000000",
"n_buckets": 30,
"description": "The subset of sessionstore.js dealing with storing form data (total size, in bytes)"
},
"FX_SESSION_RESTORE_TOTAL_HISTORY_SIZE_BYTES": {
"expires_in_version": "never",
"kind": "exponential",
"high": "50000000",
"n_buckets": 30,
"description": "The subset of sessionstore.js dealing with storing history (total size, in bytes)"
},
"FX_SESSION_RESTORE_TOTAL_POSTDATA_SIZE_BYTES": {
"expires_in_version": "never",
"kind": "exponential",
"high": "50000000",
"n_buckets": 30,
"description": "The subset of sessionstore.js dealing with storing POST data (total size, in bytes)"
},
"FX_SESSION_RESTORE_INDIVIDUAL_OPEN_WINDOWS_SIZE_BYTES": {
"expires_in_version": "never",
"kind": "exponential",
"high": "5000000",
"n_buckets": 30,
"description": "Session restore: The subset of sessionrestore.js representing open windows (item size, in bytes)"
},
"FX_SESSION_RESTORE_INDIVIDUAL_CLOSED_WINDOWS_SIZE_BYTES": {
"expires_in_version": "never",
"kind": "exponential",
"high": "5000000",
"n_buckets": 30,
"description": "Session restore: The subset of sessionrestore.js representing closed windows (item size, in bytes)"
},
"FX_SESSION_RESTORE_INDIVIDUAL_CLOSED_TABS_IN_OPEN_WINDOWS_SIZE_BYTES": {
"expires_in_version": "never",
"kind": "exponential",
"high": "5000000",
"n_buckets": 30,
"description": "Sessionrestore: The subset of sesionstore.js representing closed tabs in open windows (item size, in bytes)"
},
"FX_SESSION_RESTORE_INDIVIDUAL_COOKIES_SIZE_BYTES": {
"expires_in_version": "never",
"kind": "exponential",
"high": "50000000",
"n_buckets": 30,
"description": "The subset of sessionstore.js dealing with cookies (item size, in bytes)"
},
"FX_SESSION_RESTORE_INDIVIDUAL_DOM_STORAGE_SIZE_BYTES": {
"expires_in_version": "never",
"kind": "exponential",
"high": "5000000",
"n_buckets": 30,
"description": "The subset of sessionstore.js dealing with DOM storage (item size, in bytes)"
},
"FX_SESSION_RESTORE_INDIVIDUAL_FORMDATA_SIZE_BYTES": {
"expires_in_version": "never",
"kind": "exponential",
"high": "5000000",
"n_buckets": 30,
"description": "The subset of sessionstore.js dealing with storing form data (item size, in bytes)"
},
"FX_SESSION_RESTORE_INDIVIDUAL_HISTORY_SIZE_BYTES": {
"expires_in_version": "never",
"kind": "exponential",
"high": "5000000",
"n_buckets": 30,
"description": "The subset of sessionstore.js dealing with storing history (item size, in bytes)"
},
"FX_SESSION_RESTORE_INDIVIDUAL_POSTDATA_SIZE_BYTES": {
"expires_in_version": "never",
"kind": "exponential",
"high": "5000000",
"n_buckets": 30,
"description": "The subset of sessionstore.js dealing with storing history POST data (item size, in bytes)"
},
"INNERWINDOWS_WITH_MUTATION_LISTENERS": {
"expires_in_version": "never",
"kind": "boolean",

View File

@ -337,7 +337,6 @@ DIST_FILES += \
chrome.manifest \
update.locale \
removed-files \
recommended-addons.json \
distribution \
$(NULL)