mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-03-03 23:30:46 +00:00
Merge mozilla-central to mozilla-inbound
This commit is contained in:
commit
390a0af4b0
@ -1,4 +1,4 @@
|
||||
{
|
||||
"revision": "86e06b1db110e34eb66826d3b1bdee3a5d57b3a7",
|
||||
"revision": "563d1aa93586165246ab2ab9d40566a598f56387",
|
||||
"repo_path": "/integration/gaia-central"
|
||||
}
|
||||
|
@ -482,7 +482,7 @@
|
||||
}
|
||||
|
||||
// null parameter below specifies HTML response for search
|
||||
var submission = this.currentEngine.getSubmission(aData);
|
||||
var submission = this.currentEngine.getSubmission(aData, null, "searchbar");
|
||||
BrowserSearch.recordSearchInHealthReport(this.currentEngine.name, "searchbar");
|
||||
openUILinkIn(submission.uri.spec, aWhere, null, submission.postData);
|
||||
]]></body>
|
||||
@ -531,14 +531,14 @@
|
||||
var engine = this.currentEngine;
|
||||
var connector =
|
||||
Services.io.QueryInterface(Components.interfaces.nsISpeculativeConnect);
|
||||
var searchURI = engine.getSubmission("dummy").uri;
|
||||
var searchURI = engine.getSubmission("dummy", null, "searchbar").uri;
|
||||
let callbacks = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
||||
.getInterface(Components.interfaces.nsIWebNavigation)
|
||||
.QueryInterface(Components.interfaces.nsILoadContext);
|
||||
connector.speculativeConnect(searchURI, callbacks);
|
||||
|
||||
if (engine.supportsResponseType(SUGGEST_TYPE)) {
|
||||
var suggestURI = engine.getSubmission("dummy", SUGGEST_TYPE).uri;
|
||||
var suggestURI = engine.getSubmission("dummy", SUGGEST_TYPE, "searchbar").uri;
|
||||
if (suggestURI.prePath != searchURI.prePath)
|
||||
connector.speculativeConnect(suggestURI, callbacks);
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
ifdef ENABLE_TESTS
|
||||
pp_mochitest_browser_files := \
|
||||
browser_google.js \
|
||||
browser_google_behavior.js \
|
||||
$(NULL)
|
||||
pp_mochitest_browser_files_PATH := $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
|
||||
pp_mochitest_browser_files_FLAGS := -DMOZ_DISTRIBUTION_ID=$(MOZ_DISTRIBUTION_ID)
|
||||
|
@ -80,6 +80,10 @@ function test() {
|
||||
is(url, base + "&channel=rcs", "Check context menu search URL for 'foo'");
|
||||
url = engine.getSubmission("foo", null, "keyword").uri.spec;
|
||||
is(url, base + "&channel=fflb", "Check keyword search URL for 'foo'");
|
||||
url = engine.getSubmission("foo", null, "searchbar").uri.spec;
|
||||
is(url, base + "&channel=sb", "Check search bar search URL for 'foo'");
|
||||
url = engine.getSubmission("foo", null, "homepage").uri.spec;
|
||||
is(url, base + "&channel=np&source=hp", "Check homepage search URL for 'foo'");
|
||||
|
||||
// Check search suggestion URL.
|
||||
url = engine.getSubmission("foo", "application/x-suggestions+json").uri.spec;
|
||||
@ -147,6 +151,11 @@ function test() {
|
||||
"value": "fflb",
|
||||
"purpose": "keyword",
|
||||
},
|
||||
{
|
||||
"name": "channel",
|
||||
"value": "sb",
|
||||
"purpose": "searchbar",
|
||||
},
|
||||
{
|
||||
"name": "channel",
|
||||
"value": "np",
|
||||
|
187
browser/components/search/test/browser_google_behavior.js
Normal file
187
browser/components/search/test/browser_google_behavior.js
Normal file
@ -0,0 +1,187 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/*
|
||||
* Test Google search plugin URLs
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const BROWSER_SEARCH_PREF = "browser.search.";
|
||||
|
||||
const MOZ_PARAM_LOCALE = /\{moz:locale\}/g;
|
||||
const MOZ_PARAM_DIST_ID = /\{moz:distributionID\}/g;
|
||||
const MOZ_PARAM_OFFICIAL = /\{moz:official\}/g;
|
||||
|
||||
// Custom search parameters
|
||||
#ifdef MOZ_OFFICIAL_BRANDING
|
||||
const MOZ_OFFICIAL = "official";
|
||||
#else
|
||||
const MOZ_OFFICIAL = "unofficial";
|
||||
#endif
|
||||
|
||||
#if MOZ_UPDATE_CHANNEL == beta
|
||||
const GOOGLE_CLIENT = "firefox-beta";
|
||||
#elif MOZ_UPDATE_CHANNEL == aurora
|
||||
const GOOGLE_CLIENT = "firefox-aurora";
|
||||
#elif MOZ_UPDATE_CHANNEL == nightly
|
||||
const GOOGLE_CLIENT = "firefox-nightly";
|
||||
#else
|
||||
const GOOGLE_CLIENT = "firefox-a";
|
||||
#endif
|
||||
|
||||
#expand const MOZ_DISTRIBUTION_ID = __MOZ_DISTRIBUTION_ID__;
|
||||
|
||||
function getLocale() {
|
||||
const localePref = "general.useragent.locale";
|
||||
return getLocalizedPref(localePref, Services.prefs.getCharPref(localePref));
|
||||
}
|
||||
|
||||
function getLocalizedPref(aPrefName, aDefault) {
|
||||
try {
|
||||
return Services.prefs.getComplexValue(aPrefName, Ci.nsIPrefLocalizedString).data;
|
||||
} catch (ex) {
|
||||
return aDefault;
|
||||
}
|
||||
|
||||
return aDefault;
|
||||
}
|
||||
|
||||
function test() {
|
||||
let engine = Services.search.getEngineByName("Google");
|
||||
ok(engine, "Google is installed");
|
||||
|
||||
is(Services.search.defaultEngine, engine, "Check that Google is the default search engine");
|
||||
|
||||
let distributionID;
|
||||
try {
|
||||
distributionID = Services.prefs.getCharPref(BROWSER_SEARCH_PREF + "distributionID");
|
||||
} catch (ex) {
|
||||
distributionID = MOZ_DISTRIBUTION_ID;
|
||||
}
|
||||
|
||||
let base = "https://www.google.com/search?q=foo&ie=utf-8&oe=utf-8&aq=t&rls={moz:distributionID}:{moz:locale}:{moz:official}&client=" + GOOGLE_CLIENT;
|
||||
base = base.replace(MOZ_PARAM_LOCALE, getLocale());
|
||||
base = base.replace(MOZ_PARAM_DIST_ID, distributionID);
|
||||
base = base.replace(MOZ_PARAM_OFFICIAL, MOZ_OFFICIAL);
|
||||
|
||||
let url;
|
||||
|
||||
// Test search URLs (including purposes).
|
||||
url = engine.getSubmission("foo").uri.spec;
|
||||
is(url, base, "Check search URL for 'foo'");
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
var gCurrTest;
|
||||
var gTests = [
|
||||
{
|
||||
name: "context menu search",
|
||||
searchURL: base + "&channel=rcs",
|
||||
run: function () {
|
||||
// Simulate a contextmenu search
|
||||
// FIXME: This is a bit "low-level"...
|
||||
BrowserSearch.loadSearch("foo", false, "contextmenu");
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "keyword search",
|
||||
searchURL: base + "&channel=fflb",
|
||||
run: function () {
|
||||
gURLBar.value = "? foo";
|
||||
gURLBar.focus();
|
||||
EventUtils.synthesizeKey("VK_RETURN", {});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "search bar search",
|
||||
searchURL: base + "&channel=sb",
|
||||
run: function () {
|
||||
let sb = BrowserSearch.searchBar;
|
||||
sb.focus();
|
||||
sb.value = "foo";
|
||||
registerCleanupFunction(function () {
|
||||
sb.value = "";
|
||||
});
|
||||
EventUtils.synthesizeKey("VK_RETURN", {});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "home page search",
|
||||
searchURL: base + "&channel=np&source=hp",
|
||||
run: function () {
|
||||
// load about:home, but remove the listener first so it doesn't
|
||||
// get in the way
|
||||
gBrowser.removeProgressListener(listener);
|
||||
gBrowser.loadURI("about:home");
|
||||
info("Waiting for about:home load");
|
||||
tab.linkedBrowser.addEventListener("load", function load(event) {
|
||||
if (event.originalTarget != tab.linkedBrowser.contentDocument ||
|
||||
event.target.location.href == "about:blank") {
|
||||
info("skipping spurious load event");
|
||||
return;
|
||||
}
|
||||
tab.linkedBrowser.removeEventListener("load", load, true);
|
||||
|
||||
// Observe page setup
|
||||
let doc = gBrowser.contentDocument;
|
||||
let mutationObserver = new MutationObserver(function (mutations) {
|
||||
for (let mutation of mutations) {
|
||||
if (mutation.attributeName == "searchEngineName") {
|
||||
// Re-add the listener, and perform a search
|
||||
gBrowser.addProgressListener(listener);
|
||||
doc.getElementById("searchText").value = "foo";
|
||||
doc.getElementById("searchSubmit").click();
|
||||
}
|
||||
}
|
||||
});
|
||||
mutationObserver.observe(doc.documentElement, { attributes: true });
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
function nextTest() {
|
||||
if (gTests.length) {
|
||||
gCurrTest = gTests.shift();
|
||||
info("Running : " + gCurrTest.name);
|
||||
executeSoon(gCurrTest.run);
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
let tab = gBrowser.selectedTab = gBrowser.addTab();
|
||||
|
||||
let listener = {
|
||||
onStateChange: function onStateChange(webProgress, req, flags, status) {
|
||||
info("onStateChange");
|
||||
// Only care about top-level document starts
|
||||
let docStart = Ci.nsIWebProgressListener.STATE_IS_DOCUMENT |
|
||||
Ci.nsIWebProgressListener.STATE_START;
|
||||
if (!(flags & docStart) || !webProgress.isTopLevel)
|
||||
return;
|
||||
|
||||
info("received document start");
|
||||
|
||||
ok(req instanceof Ci.nsIChannel, "req is a channel");
|
||||
is(req.originalURI.spec, gCurrTest.searchURL, "search URL was loaded");
|
||||
info("Actual URI: " + req.URI.spec);
|
||||
|
||||
req.cancel(Components.results.NS_ERROR_FAILURE);
|
||||
|
||||
executeSoon(nextTest);
|
||||
}
|
||||
}
|
||||
|
||||
registerCleanupFunction(function () {
|
||||
gBrowser.removeProgressListener(listener);
|
||||
gBrowser.removeTab(tab);
|
||||
});
|
||||
|
||||
tab.linkedBrowser.addEventListener("load", function load() {
|
||||
tab.linkedBrowser.removeEventListener("load", load, true);
|
||||
gBrowser.addProgressListener(listener);
|
||||
nextTest();
|
||||
}, true);
|
||||
}
|
@ -25,6 +25,7 @@
|
||||
#endif
|
||||
<MozParam name="channel" condition="purpose" purpose="contextmenu" value="rcs"/>
|
||||
<MozParam name="channel" condition="purpose" purpose="keyword" value="fflb"/>
|
||||
<MozParam name="channel" condition="purpose" purpose="searchbar" value="sb"/>
|
||||
<MozParam name="channel" condition="purpose" purpose="homepage" value="np"/>
|
||||
<MozParam name="source" condition="purpose" purpose="homepage" value="hp"/>
|
||||
</Url>
|
||||
|
@ -9,7 +9,7 @@
|
||||
* link parameter/model object expected to have a .url property, and optionally .title
|
||||
*/
|
||||
function Site(aLink) {
|
||||
if(!aLink.url) {
|
||||
if (!aLink.url) {
|
||||
throw Cr.NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
this._link = aLink;
|
||||
@ -64,7 +64,7 @@ Site.prototype = {
|
||||
}
|
||||
}
|
||||
// is binding already applied?
|
||||
if (aNode.refresh) {
|
||||
if ('refresh' in aNode) {
|
||||
// just update it
|
||||
aNode.refresh();
|
||||
} else {
|
||||
|
@ -27,7 +27,7 @@
|
||||
<field name="controller">null</field>
|
||||
|
||||
<!-- collection of child items excluding empty tiles -->
|
||||
<property name="items" readonly="true" onget="return this.querySelectorAll('richgriditem');"/>
|
||||
<property name="items" readonly="true" onget="return this.querySelectorAll('richgriditem[value]');"/>
|
||||
<property name="itemCount" readonly="true" onget="return this.items.length;"/>
|
||||
|
||||
<!-- nsIDOMXULMultiSelectControlElement (not fully implemented) -->
|
||||
@ -96,7 +96,7 @@
|
||||
<parameter name="aEvent"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if(!this.isBound)
|
||||
if (!this.isBound)
|
||||
return;
|
||||
|
||||
if ("single" == this.getAttribute("seltype")) {
|
||||
@ -116,7 +116,7 @@
|
||||
<parameter name="aEvent"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if(!this.isBound || this.suppressOnSelect)
|
||||
if (!this.isBound || this.suppressOnSelect)
|
||||
return;
|
||||
// we'll republish this as a selectionchange event on the grid
|
||||
aEvent.stopPropagation();
|
||||
@ -175,7 +175,7 @@
|
||||
<property name="selectedItems">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
return this.querySelectorAll("richgriditem[selected]");
|
||||
return this.querySelectorAll("richgriditem[value][selected]");
|
||||
]]>
|
||||
</getter>
|
||||
</property>
|
||||
@ -204,28 +204,105 @@
|
||||
<parameter name="aSkipArrange"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
let addition = this._createItemElement(aLabel, aValue);
|
||||
this.appendChild(addition);
|
||||
let item = this.nextSlot();
|
||||
item.setAttribute("value", aValue);
|
||||
item.setAttribute("label", aLabel);
|
||||
|
||||
if (!aSkipArrange)
|
||||
this.arrangeItems();
|
||||
return addition;
|
||||
return item;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="_slotValues">
|
||||
<body><![CDATA[
|
||||
return Array.map(this.children, (cnode) => cnode.getAttribute("value"));
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<property name="minSlots" readonly="true"
|
||||
onget="return this.getAttribute('minSlots') || 3;"/>
|
||||
|
||||
<method name="clearAll">
|
||||
<parameter name="aSkipArrange"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
while (this.firstChild) {
|
||||
this.removeChild(this.firstChild);
|
||||
const ELEMENT_NODE_TYPE = Components.interfaces.nsIDOMNode.ELEMENT_NODE;
|
||||
let slotCount = this.minSlots;
|
||||
let childIndex = 0;
|
||||
let child = this.firstChild;
|
||||
while (child) {
|
||||
// remove excess elements and non-element nodes
|
||||
if (child.nodeType !== ELEMENT_NODE_TYPE || childIndex+1 > slotCount) {
|
||||
let orphanNode = child;
|
||||
child = orphanNode.nextSibling;
|
||||
this.removeChild(orphanNode);
|
||||
continue;
|
||||
}
|
||||
if (child.hasAttribute("value")) {
|
||||
this._releaseSlot(child);
|
||||
}
|
||||
child = child.nextSibling;
|
||||
childIndex++;
|
||||
}
|
||||
// create our quota of item slots
|
||||
for (let count = this.childElementCount; count < slotCount; count++) {
|
||||
this.appendChild( this._createItemElement() );
|
||||
}
|
||||
|
||||
if (!aSkipArrange)
|
||||
this.arrangeItems();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="_slotAt">
|
||||
<parameter name="anIndex"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
// backfill with new slots as necessary
|
||||
let count = Math.max(1+anIndex, this.minSlots) - this.childElementCount;
|
||||
for (; count > 0; count--) {
|
||||
this.appendChild( this._createItemElement() );
|
||||
}
|
||||
return this.children[anIndex];
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="nextSlot">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!this.itemCount) {
|
||||
return this._slotAt(0);
|
||||
}
|
||||
let lastItem = this.items[this.itemCount-1];
|
||||
let nextIndex = 1 + Array.indexOf(this.children, lastItem);
|
||||
return this._slotAt(nextIndex);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="_releaseSlot">
|
||||
<parameter name="anItem"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
// Flush out data and state attributes so we can recycle this slot/element
|
||||
let exclude = { value: 1, tiletype: 1 };
|
||||
let attrNames = [attr.name for (attr of anItem.attributes)];
|
||||
for (let attrName of attrNames) {
|
||||
if (!(attrName in exclude))
|
||||
anItem.removeAttribute(attrName);
|
||||
}
|
||||
// clear out inline styles
|
||||
anItem.removeAttribute("style");
|
||||
// finally clear the value, which should apply the richgrid-empty-item binding
|
||||
anItem.removeAttribute("value");
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="insertItemAt">
|
||||
<parameter name="anIndex"/>
|
||||
<parameter name="aLabel"/>
|
||||
@ -233,19 +310,30 @@
|
||||
<parameter name="aSkipArrange"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
anIndex = Math.min(this.itemCount, anIndex);
|
||||
let insertedItem;
|
||||
let existing = this.getItemAtIndex(anIndex);
|
||||
let addition = this._createItemElement(aLabel, aValue);
|
||||
if (existing) {
|
||||
this.insertBefore(addition, existing);
|
||||
} else {
|
||||
this.appendChild(addition);
|
||||
// use an empty slot if we have one, otherwise insert it
|
||||
let childIndex = Array.indexOf(this.children, existing);
|
||||
if (childIndex > 0 && !this.children[childIndex-1].hasAttribute("value")) {
|
||||
insertedItem = this.children[childIndex-1];
|
||||
} else {
|
||||
insertedItem = this.insertBefore(this._createItemElement(),existing);
|
||||
}
|
||||
}
|
||||
if (!insertedItem) {
|
||||
insertedItem = this._slotAt(anIndex);
|
||||
}
|
||||
insertedItem.setAttribute("value", aValue);
|
||||
insertedItem.setAttribute("label", aLabel);
|
||||
if (!aSkipArrange)
|
||||
this.arrangeItems();
|
||||
return addition;
|
||||
return insertedItem;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="removeItemAt">
|
||||
<parameter name="anIndex"/>
|
||||
<parameter name="aSkipArrange"/>
|
||||
@ -266,7 +354,13 @@
|
||||
<![CDATA[
|
||||
if (!aItem || Array.indexOf(this.items, aItem) < 0)
|
||||
return null;
|
||||
|
||||
let removal = this.removeChild(aItem);
|
||||
// replace the slot if necessary
|
||||
if (this.childElementCount < this.minSlots) {
|
||||
this.nextSlot();
|
||||
}
|
||||
|
||||
if (removal && !aSkipArrange)
|
||||
this.arrangeItems();
|
||||
|
||||
@ -422,6 +516,7 @@
|
||||
<field name="_scheduledArrangeItemsTimerId">null</field>
|
||||
<field name="_scheduledArrangeItemsTries">0</field>
|
||||
<field name="_maxArrangeItemsRetries">5</field>
|
||||
|
||||
<method name="_scheduleArrangeItems">
|
||||
<parameter name="aTime"/>
|
||||
<body>
|
||||
@ -453,6 +548,7 @@
|
||||
|
||||
let itemDims = this._itemSize;
|
||||
let containerDims = this._containerSize;
|
||||
let slotsCount = this.childElementCount;
|
||||
|
||||
// reset the flags
|
||||
if (this._scheduledArrangeItemsTimerId) {
|
||||
@ -468,25 +564,25 @@
|
||||
|
||||
if (this.hasAttribute("vertical")) {
|
||||
this._columnCount = Math.floor(containerDims.width / itemDims.width) || 1;
|
||||
this._rowCount = Math.floor(this.itemCount / this._columnCount);
|
||||
this._rowCount = Math.floor(slotsCount / this._columnCount);
|
||||
} else {
|
||||
// We favor overflowing horizontally, not vertically (rows then colums)
|
||||
// rows attribute = max rows
|
||||
let maxRowCount = Math.min(this.getAttribute("rows") || Infinity, Math.floor(containerDims.height / itemDims.height));
|
||||
this._rowCount = Math.min(this.itemCount, maxRowCount);
|
||||
// rows attribute is fixed number of rows
|
||||
let maxRows = Math.floor(containerDims.height / itemDims.height);
|
||||
this._rowCount = this.getAttribute("rows") ?
|
||||
// fit indicated rows when possible
|
||||
Math.min(maxRows, this.getAttribute("rows")) :
|
||||
// at least 1 row
|
||||
Math.min(maxRows, slotsCount) || 1;
|
||||
|
||||
// columns attribute = min cols
|
||||
this._columnCount = this.itemCount ?
|
||||
Math.max(
|
||||
// at least 1 column when there are items
|
||||
this.getAttribute("columns") || 1,
|
||||
Math.ceil(this.itemCount / this._rowCount)
|
||||
) : this.getAttribute("columns") || 0;
|
||||
// columns attribute is min number of cols
|
||||
this._columnCount = Math.ceil(slotsCount / this._rowCount) || 1;
|
||||
if (this.getAttribute("columns")) {
|
||||
this._columnCount = Math.max(this._columnCount, this.getAttribute("columns"));
|
||||
}
|
||||
}
|
||||
|
||||
// width is typically auto, cap max columns by truncating items collection
|
||||
// or, setting max-width style property with overflow hidden
|
||||
// '0' is an invalid value, just leave the property unset when 0 columns
|
||||
if (this._columnCount) {
|
||||
gridStyle.MozColumnCount = this._columnCount;
|
||||
}
|
||||
@ -521,6 +617,12 @@
|
||||
<field name="_xslideHandler"/>
|
||||
<constructor>
|
||||
<![CDATA[
|
||||
// create our quota of item slots
|
||||
for (let count = this.childElementCount, slotCount = this.minSlots;
|
||||
count < slotCount; count++) {
|
||||
this.appendChild( this._createItemElement() );
|
||||
}
|
||||
|
||||
if (this.controller && this.controller.gridBoundCallback != undefined)
|
||||
this.controller.gridBoundCallback();
|
||||
|
||||
@ -605,6 +707,7 @@
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="_isIndexInBounds">
|
||||
<parameter name="anIndex"/>
|
||||
<body>
|
||||
@ -626,7 +729,7 @@
|
||||
if (aLabel) {
|
||||
item.setAttribute("label", aLabel);
|
||||
}
|
||||
if(this.hasAttribute("tiletype")) {
|
||||
if (this.hasAttribute("tiletype")) {
|
||||
item.setAttribute("tiletype", this.getAttribute("tiletype"));
|
||||
}
|
||||
return item;
|
||||
@ -704,6 +807,7 @@
|
||||
aItem.setAttribute("bending", true);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="unbendItem">
|
||||
<parameter name="aItem"/>
|
||||
<body><![CDATA[
|
||||
@ -749,7 +853,7 @@
|
||||
let state = event.crossSlidingState;
|
||||
let thresholds = this._xslideHandler.thresholds;
|
||||
let transformValue;
|
||||
switch(state) {
|
||||
switch (state) {
|
||||
case "cancelled":
|
||||
this.unbendItem(event.target);
|
||||
event.target.removeAttribute('crosssliding');
|
||||
@ -844,7 +948,7 @@
|
||||
<body>
|
||||
<![CDATA[
|
||||
// Prevent an exception in case binding is not done yet.
|
||||
if(!this.isBound)
|
||||
if (!this.isBound)
|
||||
return;
|
||||
|
||||
// Seed the binding properties from bound-node attribute values
|
||||
@ -914,7 +1018,7 @@
|
||||
|
||||
<method name="refreshBackgroundImage">
|
||||
<body><![CDATA[
|
||||
if(!this.isBound)
|
||||
if (!this.isBound)
|
||||
return;
|
||||
if (this.backgroundImage) {
|
||||
this._top.style.removeProperty("background-image");
|
||||
@ -927,7 +1031,7 @@
|
||||
<property name="contextActions">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
if(!this._contextActions) {
|
||||
if (!this._contextActions) {
|
||||
this._contextActions = new Set();
|
||||
let actionSet = this._contextActions;
|
||||
let actions = this.getAttribute("data-contextactions");
|
||||
@ -959,7 +1063,7 @@
|
||||
// fires for right-click, long-click and (keyboard) contextmenu input
|
||||
// toggle the selected state of tiles in a grid
|
||||
let gridParent = this.control;
|
||||
if(!this.isBound || !gridParent)
|
||||
if (!this.isBound || !gridParent)
|
||||
return;
|
||||
gridParent.handleItemContextMenu(this, event);
|
||||
]]>
|
||||
@ -967,4 +1071,10 @@
|
||||
</handlers>
|
||||
</binding>
|
||||
|
||||
<binding id="richgrid-empty-item">
|
||||
<content>
|
||||
<html:div anonid="anon-tile" class="tile-content"></html:div>
|
||||
</content>
|
||||
</binding>
|
||||
|
||||
</bindings>
|
||||
|
@ -119,6 +119,9 @@ richgrid {
|
||||
}
|
||||
|
||||
richgriditem {
|
||||
-moz-binding: url("chrome://browser/content/bindings/grid.xml#richgrid-empty-item");
|
||||
}
|
||||
richgriditem[value] {
|
||||
-moz-binding: url("chrome://browser/content/bindings/grid.xml#richgrid-item");
|
||||
}
|
||||
|
||||
|
@ -73,11 +73,11 @@ BookmarksView.prototype = Util.extend(Object.create(View.prototype), {
|
||||
},
|
||||
|
||||
_getItemForBookmarkId: function bv__getItemForBookmark(aBookmarkId) {
|
||||
return this._set.querySelector("richgriditem[bookmarkId='" + aBookmarkId + "']");
|
||||
return this._set.querySelector("richgriditem[anonid='" + aBookmarkId + "']");
|
||||
},
|
||||
|
||||
_getBookmarkIdForItem: function bv__getBookmarkForItem(aItem) {
|
||||
return +aItem.getAttribute("bookmarkId");
|
||||
return +aItem.getAttribute("anonid");
|
||||
},
|
||||
|
||||
_updateItemWithAttrs: function dv__updateItemWithAttrs(anItem, aAttrs) {
|
||||
@ -142,6 +142,7 @@ BookmarksView.prototype = Util.extend(Object.create(View.prototype), {
|
||||
this._set.removeItemAt(this._set.itemCount - 1, true);
|
||||
}
|
||||
this._set.arrangeItems();
|
||||
this._set.removeAttribute("fade");
|
||||
this._inBatch = false;
|
||||
rootNode.containerOpen = false;
|
||||
},
|
||||
@ -154,7 +155,8 @@ BookmarksView.prototype = Util.extend(Object.create(View.prototype), {
|
||||
},
|
||||
|
||||
clearBookmarks: function bv_clearBookmarks() {
|
||||
this._set.clearAll();
|
||||
if ('clearAll' in this._set)
|
||||
this._set.clearAll();
|
||||
},
|
||||
|
||||
addBookmark: function bv_addBookmark(aBookmarkId, aPos) {
|
||||
@ -162,7 +164,7 @@ BookmarksView.prototype = Util.extend(Object.create(View.prototype), {
|
||||
let uri = this._bookmarkService.getBookmarkURI(aBookmarkId);
|
||||
let title = this._bookmarkService.getItemTitle(aBookmarkId) || uri.spec;
|
||||
let item = this._set.insertItemAt(aPos || index, title, uri.spec, this._inBatch);
|
||||
item.setAttribute("bookmarkId", aBookmarkId);
|
||||
item.setAttribute("anonid", aBookmarkId);
|
||||
this._setContextActions(item);
|
||||
this._updateFavicon(item, uri);
|
||||
},
|
||||
@ -198,6 +200,7 @@ BookmarksView.prototype = Util.extend(Object.create(View.prototype), {
|
||||
let uri = this._bookmarkService.getBookmarkURI(aBookmarkId);
|
||||
let title = this._bookmarkService.getItemTitle(aBookmarkId) || uri.spec;
|
||||
|
||||
item.setAttribute("anonid", aBookmarkId);
|
||||
item.setAttribute("value", uri.spec);
|
||||
item.setAttribute("label", title);
|
||||
|
||||
|
@ -95,6 +95,7 @@ HistoryView.prototype = Util.extend(Object.create(View.prototype), {
|
||||
|
||||
rootNode.containerOpen = false;
|
||||
this._set.arrangeItems();
|
||||
this._set.removeAttribute("fade");
|
||||
if (this._inBatch > 0)
|
||||
this._inBatch--;
|
||||
},
|
||||
@ -130,6 +131,9 @@ HistoryView.prototype = Util.extend(Object.create(View.prototype), {
|
||||
let tileGroup = this._set;
|
||||
let selectedTiles = tileGroup.selectedItems;
|
||||
|
||||
// just arrange the grid once at the end of any action handling
|
||||
this._inBatch = true;
|
||||
|
||||
switch (aActionName){
|
||||
case "delete":
|
||||
Array.forEach(selectedTiles, function(aNode) {
|
||||
@ -182,9 +186,11 @@ HistoryView.prototype = Util.extend(Object.create(View.prototype), {
|
||||
break;
|
||||
|
||||
default:
|
||||
this._inBatch = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this._inBatch = false;
|
||||
// Send refresh event so all view are in sync.
|
||||
this._sendNeedsRefresh();
|
||||
},
|
||||
@ -254,7 +260,8 @@ HistoryView.prototype = Util.extend(Object.create(View.prototype), {
|
||||
},
|
||||
|
||||
onClearHistory: function() {
|
||||
this._set.clearAll();
|
||||
if ('clearAll' in this._set)
|
||||
this._set.clearAll();
|
||||
},
|
||||
|
||||
onPageChanged: function(aURI, aWhat, aValue) {
|
||||
@ -264,7 +271,7 @@ HistoryView.prototype = Util.extend(Object.create(View.prototype), {
|
||||
let currIcon = item.getAttribute("iconURI");
|
||||
if (currIcon != aValue) {
|
||||
item.setAttribute("iconURI", aValue);
|
||||
if("refresh" in item)
|
||||
if ("refresh" in item)
|
||||
item.refresh();
|
||||
}
|
||||
}
|
||||
|
@ -93,6 +93,7 @@ RemoteTabsView.prototype = Util.extend(Object.create(View.prototype), {
|
||||
}
|
||||
this.setUIAccessVisible(show);
|
||||
this._set.arrangeItems();
|
||||
this._set.removeAttribute("fade");
|
||||
},
|
||||
|
||||
destruct: function destruct() {
|
||||
|
@ -48,7 +48,17 @@
|
||||
<html:div class="meta-section-title narrow-title" onclick="StartUI.onNarrowTitleClick('start-topsites')">
|
||||
&narrowTopSitesHeader.label;
|
||||
</html:div>
|
||||
<richgrid id="start-topsites-grid" observes="bcast_windowState" set-name="topSites" rows="3" columns="3" tiletype="thumbnail" seltype="multiple" flex="1"/>
|
||||
<richgrid id="start-topsites-grid" observes="bcast_windowState" set-name="topSites" rows="3" columns="3" tiletype="thumbnail" seltype="multiple" minSlots="8" fade="true" flex="1">
|
||||
<richgriditem/>
|
||||
<richgriditem/>
|
||||
<richgriditem/>
|
||||
<richgriditem/>
|
||||
<richgriditem/>
|
||||
<richgriditem/>
|
||||
<richgriditem/>
|
||||
<richgriditem/>
|
||||
<richgriditem/>
|
||||
</richgrid>
|
||||
</vbox>
|
||||
|
||||
<vbox id="start-bookmarks" class="meta-section">
|
||||
@ -56,7 +66,10 @@
|
||||
<html:div class="meta-section-title narrow-title" onclick="StartUI.onNarrowTitleClick('start-bookmarks')">
|
||||
&narrowBookmarksHeader.label;
|
||||
</html:div>
|
||||
<richgrid id="start-bookmarks-grid" observes="bcast_windowState" set-name="bookmarks" seltype="multiple" flex="1"/>
|
||||
<richgrid id="start-bookmarks-grid" observes="bcast_windowState" set-name="bookmarks" seltype="multiple" fade="true" flex="1" minSlots="2">
|
||||
<richgriditem/>
|
||||
<richgriditem/>
|
||||
</richgrid>
|
||||
</vbox>
|
||||
|
||||
<vbox id="start-history" class="meta-section">
|
||||
@ -64,7 +77,11 @@
|
||||
<html:div class="meta-section-title narrow-title" onclick="StartUI.onNarrowTitleClick('start-history')">
|
||||
&narrowRecentHistoryHeader.label;
|
||||
</html:div>
|
||||
<richgrid id="start-history-grid" observes="bcast_windowState" set-name="recentHistory" seltype="multiple" flex="1"/>
|
||||
<richgrid id="start-history-grid" observes="bcast_windowState" set-name="recentHistory" seltype="multiple" fade="true" flex="1">
|
||||
<richgriditem/>
|
||||
<richgriditem/>
|
||||
<richgriditem/>
|
||||
</richgrid>
|
||||
</vbox>
|
||||
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
@ -73,7 +90,12 @@
|
||||
<html:div id="snappedRemoteTabsLabel" class="meta-section-title narrow-title" onclick="StartUI.onNarrowTitleClick('start-remotetabs')">
|
||||
&narrowRemoteTabsHeader.label;
|
||||
</html:div>
|
||||
<richgrid id="start-remotetabs-grid" observes="bcast_windowState" set-name="remoteTabs" seltype="multiple" flex="1"/>
|
||||
<richgrid id="start-remotetabs-grid" observes="bcast_windowState" set-name="remoteTabs" seltype="multiple" fade="true" flex="1">
|
||||
<richgriditem/>
|
||||
<richgriditem/>
|
||||
<richgriditem/>
|
||||
</richgrid>
|
||||
|
||||
</vbox>
|
||||
#endif
|
||||
</hbox>
|
||||
|
@ -158,6 +158,9 @@ TopSitesView.prototype = Util.extend(Object.create(View.prototype), {
|
||||
},
|
||||
|
||||
updateTile: function(aTileNode, aSite, aArrangeGrid) {
|
||||
if (!(aSite && aSite.url)) {
|
||||
throw new Error("Invalid Site object passed to TopSitesView updateTile");
|
||||
}
|
||||
this._updateFavicon(aTileNode, Util.makeURI(aSite.url));
|
||||
|
||||
Task.spawn(function() {
|
||||
@ -192,14 +195,11 @@ TopSitesView.prototype = Util.extend(Object.create(View.prototype), {
|
||||
tileset.clearAll(true);
|
||||
|
||||
for (let site of sites) {
|
||||
// call to private _createItemElement is a temp measure
|
||||
// we'll eventually just request the next slot
|
||||
let item = tileset._createItemElement(site.title, site.url);
|
||||
|
||||
this.updateTile(item, site);
|
||||
tileset.appendChild(item);
|
||||
let slot = tileset.nextSlot();
|
||||
this.updateTile(slot, site);
|
||||
}
|
||||
tileset.arrangeItems();
|
||||
tileset.removeAttribute("fade");
|
||||
this.isUpdating = false;
|
||||
},
|
||||
|
||||
@ -244,7 +244,7 @@ TopSitesView.prototype = Util.extend(Object.create(View.prototype), {
|
||||
|
||||
// nsIObservers
|
||||
observe: function (aSubject, aTopic, aState) {
|
||||
switch(aTopic) {
|
||||
switch (aTopic) {
|
||||
case "Metro:RefreshTopsiteThumbnail":
|
||||
this.forceReloadOfThumbnail(aState);
|
||||
break;
|
||||
@ -269,7 +269,8 @@ TopSitesView.prototype = Util.extend(Object.create(View.prototype), {
|
||||
},
|
||||
|
||||
onClearHistory: function() {
|
||||
this._set.clearAll();
|
||||
if ('clearAll' in this._set)
|
||||
this._set.clearAll();
|
||||
},
|
||||
|
||||
onPageChanged: function(aURI, aWhat, aValue) {
|
||||
|
@ -67,7 +67,7 @@ gTests.push({
|
||||
|
||||
ok(!item, "Item not in grid");
|
||||
ok(!gStartView._pinHelper.isPinned(uriFromIndex(2)), "Item unpinned");
|
||||
ok(gStartView._set.itemCount === gStartView._limit, "Grid repopulated");
|
||||
is(gStartView._set.itemCount, gStartView._limit, "Grid repopulated");
|
||||
|
||||
// --------- unpin multiple items
|
||||
|
||||
@ -124,7 +124,7 @@ gTests.push({
|
||||
item = gStartView._set.getItemsByUrl(uriFromIndex(2))[0];
|
||||
|
||||
ok(!item, "Item not in grid");
|
||||
ok(HistoryTestHelper._nodes[uriFromIndex(2)], "Item not deleted yet");
|
||||
ok(HistoryTestHelper._nodes[uriFromIndex(2)], "Item not actually deleted yet");
|
||||
ok(!restoreButton.hidden, "Restore button is visible.");
|
||||
ok(gStartView._set.itemCount === gStartView._limit, "Grid repopulated");
|
||||
|
||||
@ -150,9 +150,13 @@ gTests.push({
|
||||
ok(!deleteButton.hidden, "Delete button is visible.");
|
||||
|
||||
let promise = waitForCondition(() => !restoreButton.hidden);
|
||||
let populateGridSpy = spyOnMethod(gStartView, "populateGrid");
|
||||
|
||||
EventUtils.synthesizeMouse(deleteButton, 10, 10, {}, window);
|
||||
yield promise;
|
||||
|
||||
is(populateGridSpy.callCount, 1, "populateGrid was called in response to the deleting a tile");
|
||||
|
||||
item = gStartView._set.getItemsByUrl(uriFromIndex(2))[0];
|
||||
|
||||
ok(!item, "Item not in grid");
|
||||
@ -163,11 +167,14 @@ gTests.push({
|
||||
Elements.contextappbar.dismiss();
|
||||
yield promise;
|
||||
|
||||
is(populateGridSpy.callCount, 1, "populateGrid not called when a removed item is actually deleted");
|
||||
populateGridSpy.restore();
|
||||
|
||||
item = gStartView._set.getItemsByUrl(uriFromIndex(2))[0];
|
||||
|
||||
ok(!item, "Item not in grid");
|
||||
ok(!HistoryTestHelper._nodes[uriFromIndex(2)], "Item RIP");
|
||||
ok(gStartView._set.itemCount === gStartView._limit, "Grid repopulated");
|
||||
is(gStartView._set.itemCount, gStartView._limit, "Grid repopulated");
|
||||
|
||||
// --------- delete multiple items and restore
|
||||
|
||||
|
@ -17,6 +17,9 @@
|
||||
<richgrid id="grid_layout" seltype="single" flex="1">
|
||||
</richgrid>
|
||||
</vbox>
|
||||
<vbox>
|
||||
<richgrid id="slots_grid" seltype="single" minSlots="6" flex="1"/>
|
||||
</vbox>
|
||||
<vbox style="height:600px">
|
||||
<hbox>
|
||||
<richgrid id="clearGrid" seltype="single" flex="1" rows="2">
|
||||
@ -26,7 +29,7 @@
|
||||
</richgrid>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<richgrid id="emptyGrid" seltype="single" flex="1" rows="2">
|
||||
<richgrid id="emptyGrid" seltype="single" flex="1" rows="2" minSlots="6">
|
||||
</richgrid>
|
||||
</hbox>
|
||||
<hbox>
|
||||
|
@ -9,6 +9,14 @@ function test() {
|
||||
}).then(runTests);
|
||||
}
|
||||
|
||||
function _checkIfBoundByRichGrid_Item(expected, node, idx) {
|
||||
let binding = node.ownerDocument.defaultView.getComputedStyle(node).MozBinding;
|
||||
let result = ('url("chrome://browser/content/bindings/grid.xml#richgrid-item")' == binding);
|
||||
return (result == expected);
|
||||
}
|
||||
let isBoundByRichGrid_Item = _checkIfBoundByRichGrid_Item.bind(this, true);
|
||||
let isNotBoundByRichGrid_Item = _checkIfBoundByRichGrid_Item.bind(this, false);
|
||||
|
||||
gTests.push({
|
||||
desc: "richgrid binding is applied",
|
||||
run: function() {
|
||||
@ -17,9 +25,9 @@ gTests.push({
|
||||
let grid = doc.querySelector("#grid1");
|
||||
ok(grid, "#grid1 is found");
|
||||
is(typeof grid.clearSelection, "function", "#grid1 has the binding applied");
|
||||
|
||||
is(grid.items.length, 2, "#grid1 has a 2 items");
|
||||
is(grid.items[0].control, grid, "#grid1 item's control points back at #grid1'");
|
||||
ok(Array.every(grid.items, isBoundByRichGrid_Item), "All items are bound by richgrid-item");
|
||||
}
|
||||
});
|
||||
|
||||
@ -125,19 +133,28 @@ gTests.push({
|
||||
gTests.push({
|
||||
desc: "empty grid",
|
||||
run: function() {
|
||||
// XXX grids have minSlots and may not be ever truly empty
|
||||
|
||||
let grid = doc.getElementById("emptyGrid");
|
||||
grid.arrangeItems();
|
||||
yield waitForCondition(() => !grid.isArranging);
|
||||
|
||||
// grid has rows=2 but 0 items
|
||||
// grid has 2 rows, 6 slots, 0 items
|
||||
ok(grid.isBound, "binding was applied");
|
||||
is(grid.itemCount, 0, "empty grid has 0 items");
|
||||
is(grid.rowCount, 0, "empty grid has 0 rows");
|
||||
is(grid.columnCount, 0, "empty grid has 0 cols");
|
||||
// minSlots attr. creates unpopulated slots
|
||||
is(grid.rowCount, grid.getAttribute("rows"), "empty grid with rows-attribute has that number of rows");
|
||||
is(grid.columnCount, 3, "empty grid has expected number of columns");
|
||||
|
||||
let columnsNode = grid._grid;
|
||||
let cStyle = doc.defaultView.getComputedStyle(columnsNode);
|
||||
is(cStyle.getPropertyValue("-moz-column-count"), "auto", "empty grid has -moz-column-count: auto");
|
||||
// remove rows attribute and allow space for the grid to find its own height
|
||||
// for its number of slots
|
||||
grid.removeAttribute("rows");
|
||||
grid.parentNode.style.height = 20+(grid.tileHeight*grid.minSlots)+"px";
|
||||
|
||||
grid.arrangeItems();
|
||||
yield waitForCondition(() => !grid.isArranging);
|
||||
is(grid.rowCount, grid.minSlots, "empty grid has this.minSlots rows");
|
||||
is(grid.columnCount, 1, "empty grid has 1 column");
|
||||
}
|
||||
});
|
||||
|
||||
@ -211,16 +228,25 @@ gTests.push({
|
||||
is(typeof grid.insertItemAt, "function", "insertItemAt is a function on the grid");
|
||||
|
||||
let arrangeStub = stubMethod(grid, "arrangeItems");
|
||||
let insertedItem = grid.insertItemAt(1, "inserted item", "http://example.com/inserted");
|
||||
let insertedAt0 = grid.insertItemAt(0, "inserted item 0", "http://example.com/inserted0");
|
||||
let insertedAt00 = grid.insertItemAt(0, "inserted item 00", "http://example.com/inserted00");
|
||||
|
||||
ok(insertedItem, "insertItemAt gives back an item");
|
||||
is(grid.items[1], insertedItem, "item is inserted at the correct index");
|
||||
is(insertedItem.getAttribute("label"), "inserted item", "insertItemAt creates item with the correct label");
|
||||
is(insertedItem.getAttribute("value"), "http://example.com/inserted", "insertItemAt creates item with the correct url value");
|
||||
is(grid.items[2].getAttribute("id"), "grid3_item2", "following item ends up at the correct index");
|
||||
is(grid.itemCount, 3, "itemCount is incremented when we insertItemAt");
|
||||
ok(insertedAt0 && insertedAt00, "insertItemAt gives back an item");
|
||||
|
||||
is(arrangeStub.callCount, 1, "arrangeItems is called when we insertItemAt");
|
||||
is(insertedAt0.getAttribute("label"), "inserted item 0", "insertItemAt creates item with the correct label");
|
||||
is(insertedAt0.getAttribute("value"), "http://example.com/inserted0", "insertItemAt creates item with the correct url value");
|
||||
|
||||
is(grid.items[0], insertedAt00, "item is inserted at the correct index");
|
||||
is(grid.children[0], insertedAt00, "first item occupies the first slot");
|
||||
is(grid.items[1], insertedAt0, "item is inserted at the correct index");
|
||||
is(grid.children[1], insertedAt0, "next item occupies the next slot");
|
||||
|
||||
is(grid.items[2].getAttribute("label"), "First item", "Old first item is now at index 2");
|
||||
is(grid.items[3].getAttribute("label"), "2nd item", "Old 2nd item is now at index 3");
|
||||
|
||||
is(grid.itemCount, 4, "itemCount is incremented when we insertItemAt");
|
||||
|
||||
is(arrangeStub.callCount, 2, "arrangeItems is called when we insertItemAt");
|
||||
arrangeStub.restore();
|
||||
}
|
||||
});
|
||||
@ -417,3 +443,172 @@ gTests.push({
|
||||
doc.defaultView.removeEventListener("selectionchange", handler, false);
|
||||
}
|
||||
});
|
||||
|
||||
function gridSlotsSetup() {
|
||||
let grid = this.grid = doc.createElement("richgrid");
|
||||
grid.setAttribute("minSlots", 6);
|
||||
doc.documentElement.appendChild(grid);
|
||||
is(grid.ownerDocument, doc, "created grid in the expected document");
|
||||
}
|
||||
function gridSlotsTearDown() {
|
||||
this.grid && this.grid.parentNode.removeChild(this.grid);
|
||||
}
|
||||
|
||||
gTests.push({
|
||||
desc: "richgrid slots init",
|
||||
setUp: gridSlotsSetup,
|
||||
run: function() {
|
||||
let grid = this.grid;
|
||||
// grid is initially populated with empty slots matching the minSlots attribute
|
||||
is(grid.children.length, 6, "minSlots slots are created");
|
||||
is(grid.itemCount, 0, "slots do not count towards itemCount");
|
||||
ok(Array.every(grid.children, (node) => node.nodeName == 'richgriditem'), "slots have nodeName richgriditem");
|
||||
ok(Array.every(grid.children, isNotBoundByRichGrid_Item), "slots aren't bound by the richgrid-item binding");
|
||||
},
|
||||
tearDown: gridSlotsTearDown
|
||||
});
|
||||
|
||||
gTests.push({
|
||||
desc: "richgrid using slots for items",
|
||||
setUp: gridSlotsSetup, // creates grid with minSlots = num. slots = 6
|
||||
run: function() {
|
||||
let grid = this.grid;
|
||||
let numSlots = grid.getAttribute("minSlots");
|
||||
is(grid.children.length, numSlots);
|
||||
// adding items occupies those slots
|
||||
for (let idx of [0,1,2,3,4,5,6]) {
|
||||
let slot = grid.children[idx];
|
||||
let item = grid.appendItem("item "+idx, "about:mozilla");
|
||||
if (idx < numSlots) {
|
||||
is(grid.children.length, numSlots);
|
||||
is(slot, item, "The same node is reused when an item is assigned to a slot");
|
||||
} else {
|
||||
is(typeof slot, 'undefined');
|
||||
ok(item);
|
||||
is(grid.children.length, grid.itemCount);
|
||||
}
|
||||
}
|
||||
},
|
||||
tearDown: gridSlotsTearDown
|
||||
});
|
||||
|
||||
gTests.push({
|
||||
desc: "richgrid assign and release slots",
|
||||
setUp: function(){
|
||||
info("assign and release slots setUp");
|
||||
this.grid = doc.getElementById("slots_grid");
|
||||
this.grid.scrollIntoView();
|
||||
let rect = this.grid.getBoundingClientRect();
|
||||
info("slots grid at top: " + rect.top + ", window.pageYOffset: " + doc.defaultView.pageYOffset);
|
||||
},
|
||||
run: function() {
|
||||
let grid = this.grid;
|
||||
// start with 5 of 6 slots occupied
|
||||
for (let idx of [0,1,2,3,4]) {
|
||||
let item = grid.appendItem("item "+idx, "about:mozilla");
|
||||
item.setAttribute("id", "test_item_"+idx);
|
||||
}
|
||||
is(grid.itemCount, 5);
|
||||
is(grid.children.length, 6); // see setup, where we init with 6 slots
|
||||
let firstItem = grid.items[0];
|
||||
|
||||
ok(firstItem.ownerDocument, "item has ownerDocument");
|
||||
is(doc, firstItem.ownerDocument, "item's ownerDocument is the document we expect");
|
||||
|
||||
is(firstItem, grid.children[0], "Item and assigned slot are one and the same");
|
||||
is(firstItem.control, grid, "Item is bound and its .control points back at the grid");
|
||||
|
||||
// before releasing, the grid should be nofified of clicks on that slot
|
||||
let testWindow = grid.ownerDocument.defaultView;
|
||||
|
||||
let rect = firstItem.getBoundingClientRect();
|
||||
{
|
||||
let handleStub = stubMethod(grid, 'handleItemClick');
|
||||
// send click to item and wait for next tick;
|
||||
sendElementTap(testWindow, firstItem);
|
||||
yield waitForMs(0);
|
||||
|
||||
is(handleStub.callCount, 1, "handleItemClick was called when we clicked an item");
|
||||
handleStub.restore();
|
||||
}
|
||||
// _releaseSlot is semi-private, we don't expect consumers of the binding to call it
|
||||
// but want to be sure it does what we expect
|
||||
grid._releaseSlot(firstItem);
|
||||
|
||||
is(grid.itemCount, 4, "Releasing a slot gives us one less item");
|
||||
is(firstItem, grid.children[0],"Released slot is still the same node we started with");
|
||||
|
||||
// after releasing, the grid should NOT be nofified of clicks
|
||||
{
|
||||
let handleStub = stubMethod(grid, 'handleItemClick');
|
||||
// send click to item and wait for next tick;
|
||||
sendElementTap(testWindow, firstItem);
|
||||
yield waitForMs(0);
|
||||
|
||||
is(handleStub.callCount, 0, "handleItemClick was NOT called when we clicked a released slot");
|
||||
handleStub.restore();
|
||||
}
|
||||
|
||||
ok(!firstItem.mozMatchesSelector("richgriditem[value]"), "Released slot doesn't match binding selector");
|
||||
ok(isNotBoundByRichGrid_Item(firstItem), "Released slot is no longer bound");
|
||||
|
||||
waitForCondition(() => isNotBoundByRichGrid_Item(firstItem));
|
||||
ok(true, "Slot eventually gets unbound");
|
||||
is(firstItem, grid.children[0], "Released slot is still at expected index in children collection");
|
||||
|
||||
let firstSlot = grid.children[0];
|
||||
firstItem = grid.insertItemAt(0, "New item 0", "about:blank");
|
||||
ok(firstItem == grid.items[0], "insertItemAt 0 creates item at expected index");
|
||||
ok(firstItem == firstSlot, "insertItemAt occupies the released slot with the new item");
|
||||
is(grid.itemCount, 5);
|
||||
is(grid.children.length, 6);
|
||||
is(firstItem.control, grid,"Item is bound and its .control points back at the grid");
|
||||
|
||||
let nextSlotIndex = grid.itemCount;
|
||||
let lastItem = grid.insertItemAt(9, "New item 9", "about:blank");
|
||||
// Check we don't create sparse collection of items
|
||||
is(lastItem, grid.children[nextSlotIndex], "Item is appended at the next index when an out of bounds index is provided");
|
||||
is(grid.children.length, 6);
|
||||
is(grid.itemCount, 6);
|
||||
|
||||
grid.appendItem("one more", "about:blank");
|
||||
is(grid.children.length, 7);
|
||||
is(grid.itemCount, 7);
|
||||
|
||||
// clearAll results in slots being emptied
|
||||
grid.clearAll();
|
||||
is(grid.children.length, 6, "Extra slots are trimmed when we clearAll");
|
||||
ok(!Array.some(grid.children, (node) => node.hasAttribute("value")), "All slots have no value attribute after clearAll")
|
||||
},
|
||||
tearDown: gridSlotsTearDown
|
||||
});
|
||||
|
||||
gTests.push({
|
||||
desc: "richgrid slot management",
|
||||
setUp: gridSlotsSetup,
|
||||
run: function() {
|
||||
let grid = this.grid;
|
||||
// populate grid with some items
|
||||
let numSlots = grid.getAttribute("minSlots");
|
||||
for (let idx of [0,1,2,3,4,5]) {
|
||||
let item = grid.appendItem("item "+idx, "about:mozilla");
|
||||
}
|
||||
|
||||
is(grid.itemCount, 6, "Grid setup with 6 items");
|
||||
is(grid.children.length, 6, "Full grid has the expected number of slots");
|
||||
|
||||
// removing an item creates a replacement slot *on the end of the stack*
|
||||
let item = grid.removeItemAt(0);
|
||||
is(item.getAttribute("label"), "item 0", "removeItemAt gives back the populated node");
|
||||
is(grid.children.length, 6);
|
||||
is(grid.itemCount, 5);
|
||||
is(grid.items[0].getAttribute("label"), "item 1", "removeItemAt removes the node so the nextSibling takes its place");
|
||||
ok(grid.children[5] && !grid.children[5].hasAttribute("value"), "empty slot is added at the end of the existing children");
|
||||
|
||||
let item1 = grid.removeItem(grid.items[0]);
|
||||
is(grid.children.length, 6);
|
||||
is(grid.itemCount, 4);
|
||||
is(grid.items[0].getAttribute("label"), "item 2", "removeItem removes the node so the nextSibling takes its place");
|
||||
},
|
||||
tearDown: gridSlotsTearDown
|
||||
});
|
||||
|
@ -275,6 +275,26 @@ richgriditem[bending] > .tile-content {
|
||||
transform-origin: center center;
|
||||
}
|
||||
|
||||
/* Empty/unused tiles */
|
||||
richgriditem:not([value]) {
|
||||
visibility: hidden;
|
||||
}
|
||||
richgriditem[tiletype="thumbnail"]:not([value]) {
|
||||
visibility: visible;
|
||||
}
|
||||
richgriditem:not([value]) > .tile-content {
|
||||
padding: 10px 14px;
|
||||
}
|
||||
richgriditem[tiletype="thumbnail"]:not([value]) > .tile-content {
|
||||
box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.05);
|
||||
background-image: url("chrome://browser/skin/images/firefox-watermark.png");
|
||||
background-origin: content-box;
|
||||
background-repeat: no-repeat;
|
||||
background-color: rgba(255,255,255, 0.2);
|
||||
background-position: center center;
|
||||
background-size: @grid_row_height@;
|
||||
}
|
||||
|
||||
/* Snapped-view variation
|
||||
We use the compact, single-column grid treatment for <=320px */
|
||||
|
||||
|
@ -169,7 +169,7 @@ let AboutHome = {
|
||||
window.BrowserSearch.recordSearchInHealthReport(data.engineName, "abouthome");
|
||||
#endif
|
||||
// Trigger a search through nsISearchEngine.getSubmission()
|
||||
let submission = Services.search.currentEngine.getSubmission(data.searchTerms);
|
||||
let submission = Services.search.currentEngine.getSubmission(data.searchTerms, null, "homepage");
|
||||
window.loadURI(submission.uri.spec, null, submission.postData);
|
||||
break;
|
||||
}
|
||||
|
@ -9,11 +9,7 @@ dir-tests := $(DEPTH)/$(mobile-tests)
|
||||
ANDROID_APK_NAME := robocop-debug
|
||||
|
||||
ANDROID_EXTRA_JARS += \
|
||||
$(srcdir)/robotium-solo-4.2.jar \
|
||||
$(NULL)
|
||||
|
||||
ANDROID_RESFILES = \
|
||||
res/values/strings.xml \
|
||||
$(srcdir)/robotium-solo-4.3.jar \
|
||||
$(NULL)
|
||||
|
||||
ANDROID_ASSETS_DIR := $(TESTPATH)/assets
|
||||
|
@ -1,11 +1,11 @@
|
||||
Robocop is a Mozilla project which uses Robotium to test Firefox on Android devices.
|
||||
|
||||
Robotium is an open source tool licensed under the Apache 2.0 license and the original
|
||||
Robotium is an open source tool licensed under the Apache 2.0 license and the original
|
||||
source can be found here:
|
||||
http://code.google.com/p/robotium/
|
||||
|
||||
We are including robotium-solo-4.2.jar as a binary and are not modifying it in any way
|
||||
from the original download found at:
|
||||
We are including robotium-solo-4.3.jar as a binary and are not modifying it in any way
|
||||
from the original download found at:
|
||||
http://code.google.com/p/robotium/
|
||||
|
||||
Firefox for Android developers should read the documentation in
|
||||
|
@ -6,3 +6,6 @@
|
||||
|
||||
MODULE = 'robocop'
|
||||
|
||||
ANDROID_RESFILES = [
|
||||
'res/values/strings.xml',
|
||||
]
|
||||
|
Binary file not shown.
BIN
build/mobile/robocop/robotium-solo-4.3.jar
Normal file
BIN
build/mobile/robocop/robotium-solo-4.3.jar
Normal file
Binary file not shown.
@ -20,16 +20,6 @@ JAVAFILES = \
|
||||
WifiConfiguration.java \
|
||||
$(NULL)
|
||||
|
||||
ANDROID_RESFILES = \
|
||||
res/drawable/icon.png \
|
||||
res/drawable/ateamlogo.png \
|
||||
res/drawable/ic_stat_first.png \
|
||||
res/drawable/ic_stat_neterror.png \
|
||||
res/drawable/ic_stat_warning.png \
|
||||
res/layout/main.xml \
|
||||
res/values/strings.xml \
|
||||
$(NULL)
|
||||
|
||||
ANDROID_EXTRA_JARS = \
|
||||
$(srcdir)/network-libs/commons-net-2.0.jar \
|
||||
$(srcdir)/network-libs/jmdns.jar \
|
||||
|
@ -11,14 +11,6 @@ JAVAFILES = \
|
||||
FileCursor.java \
|
||||
$(NULL)
|
||||
|
||||
ANDROID_RESFILES = \
|
||||
res/drawable-hdpi/icon.png \
|
||||
res/drawable-ldpi/icon.png \
|
||||
res/drawable-mdpi/icon.png \
|
||||
res/layout/main.xml \
|
||||
res/values/strings.xml \
|
||||
$(NULL)
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
tools:: $(ANDROID_APK_NAME).apk
|
||||
|
@ -6,3 +6,10 @@
|
||||
|
||||
MODULE = 'FenCP'
|
||||
|
||||
ANDROID_RESFILES = [
|
||||
'res/drawable-hdpi/icon.png',
|
||||
'res/drawable-ldpi/icon.png',
|
||||
'res/drawable-mdpi/icon.png',
|
||||
'res/layout/main.xml',
|
||||
'res/values/strings.xml',
|
||||
]
|
||||
|
@ -11,14 +11,6 @@ JAVAFILES = \
|
||||
FileCursor.java \
|
||||
$(NULL)
|
||||
|
||||
ANDROID_RESFILES = \
|
||||
res/drawable-hdpi/icon.png \
|
||||
res/drawable-ldpi/icon.png \
|
||||
res/drawable-mdpi/icon.png \
|
||||
res/layout/main.xml \
|
||||
res/values/strings.xml \
|
||||
$(NULL)
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
tools:: $(ANDROID_APK_NAME).apk
|
||||
|
@ -6,3 +6,10 @@
|
||||
|
||||
MODULE = 'FfxCP'
|
||||
|
||||
ANDROID_RESFILES = [
|
||||
'res/drawable-hdpi/icon.png',
|
||||
'res/drawable-ldpi/icon.png',
|
||||
'res/drawable-mdpi/icon.png',
|
||||
'res/layout/main.xml',
|
||||
'res/values/strings.xml',
|
||||
]
|
||||
|
@ -6,3 +6,12 @@
|
||||
|
||||
MODULE = 'sutAgentAndroid'
|
||||
|
||||
ANDROID_RESFILES = [
|
||||
'res/drawable/ateamlogo.png',
|
||||
'res/drawable/ic_stat_first.png',
|
||||
'res/drawable/ic_stat_neterror.png',
|
||||
'res/drawable/ic_stat_warning.png',
|
||||
'res/drawable/icon.png',
|
||||
'res/layout/main.xml',
|
||||
'res/values/strings.xml',
|
||||
]
|
||||
|
@ -12,17 +12,6 @@ JAVAFILES = \
|
||||
WatcherService.java \
|
||||
$(NULL)
|
||||
|
||||
ANDROID_RESFILES = \
|
||||
res/drawable-hdpi/icon.png \
|
||||
res/drawable-hdpi/ateamlogo.png \
|
||||
res/drawable-ldpi/icon.png \
|
||||
res/drawable-ldpi/ateamlogo.png \
|
||||
res/drawable-mdpi/icon.png \
|
||||
res/drawable-mdpi/ateamlogo.png \
|
||||
res/layout/main.xml \
|
||||
res/values/strings.xml \
|
||||
$(NULL)
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
tools:: $(ANDROID_APK_NAME).apk
|
||||
|
@ -6,3 +6,13 @@
|
||||
|
||||
MODULE = 'Watcher'
|
||||
|
||||
ANDROID_RESFILES = [
|
||||
'res/drawable-hdpi/ateamlogo.png',
|
||||
'res/drawable-hdpi/icon.png',
|
||||
'res/drawable-ldpi/ateamlogo.png',
|
||||
'res/drawable-ldpi/icon.png',
|
||||
'res/drawable-mdpi/ateamlogo.png',
|
||||
'res/drawable-mdpi/icon.png',
|
||||
'res/layout/main.xml',
|
||||
'res/values/strings.xml',
|
||||
]
|
||||
|
@ -8,6 +8,7 @@
|
||||
ifndef INCLUDED_JAVA_BUILD_MK #{
|
||||
|
||||
ifdef ANDROID_RESFILES #{
|
||||
ifndef IGNORE_ANDROID_RESFILES #{
|
||||
res-dep := .deps-copy-java-res
|
||||
|
||||
GENERATED_DIRS += res
|
||||
@ -25,6 +26,7 @@ res-dep-preqs := \
|
||||
$(res-dep): $(res-dep-preqs)
|
||||
$(call copy_dir,$(srcdir)/res,$(CURDIR)/res)
|
||||
@$(TOUCH) $@
|
||||
endif #} IGNORE_ANDROID_RESFILES
|
||||
endif #} ANDROID_RESFILES
|
||||
|
||||
|
||||
|
@ -21,6 +21,8 @@ INCLUDED_RULES_MK = 1
|
||||
# present. If they are, this is a violation of the separation of
|
||||
# responsibility between Makefile.in and mozbuild files.
|
||||
_MOZBUILD_EXTERNAL_VARIABLES := \
|
||||
ANDROID_GENERATED_RESFILES \
|
||||
ANDROID_RESFILES \
|
||||
CMMSRCS \
|
||||
CPP_UNIT_TESTS \
|
||||
DIRS \
|
||||
|
@ -4,8 +4,6 @@
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
interface nsIDOMMozNetworkStatsInterface;
|
||||
|
||||
[scriptable, builtinclass, uuid(3b16fe17-5583-483a-b486-b64a3243221c)]
|
||||
interface nsIDOMMozNetworkStatsData : nsISupports
|
||||
{
|
||||
@ -14,7 +12,7 @@ interface nsIDOMMozNetworkStatsData : nsISupports
|
||||
readonly attribute jsval date; // Date.
|
||||
};
|
||||
|
||||
[scriptable, builtinclass, uuid(b6fc4b14-628d-4c99-bf4e-e4ed56916cbe)]
|
||||
[scriptable, builtinclass, uuid(6613ea55-b99c-44f9-91bf-d07da10b9b74)]
|
||||
interface nsIDOMMozNetworkStats : nsISupports
|
||||
{
|
||||
/**
|
||||
@ -24,12 +22,13 @@ interface nsIDOMMozNetworkStats : nsISupports
|
||||
readonly attribute DOMString manifestURL;
|
||||
|
||||
/**
|
||||
* Network the returned data belongs to.
|
||||
* Can be 'mobile', 'wifi' or null.
|
||||
* If null, stats for both mobile and wifi are returned.
|
||||
*/
|
||||
readonly attribute nsIDOMMozNetworkStatsInterface network;
|
||||
readonly attribute DOMString connectionType;
|
||||
|
||||
/**
|
||||
* Stats for a network.
|
||||
* Stats for connectionType
|
||||
*/
|
||||
readonly attribute jsval data; // array of NetworkStatsData.
|
||||
// one element per day.
|
||||
|
@ -6,65 +6,57 @@
|
||||
|
||||
interface nsIDOMDOMRequest;
|
||||
|
||||
/**
|
||||
* Represents a data interface for which the manager is recording statistics.
|
||||
*/
|
||||
[scriptable, uuid(f540615b-d803-43ff-8200-2a9d145a5645)]
|
||||
interface nsIDOMMozNetworkStatsInterface : nsISupports
|
||||
dictionary NetworkStatsOptions
|
||||
{
|
||||
readonly attribute long type;
|
||||
|
||||
/**
|
||||
* Id value is '0' for wifi or the iccid for mobile (SIM).
|
||||
* Connection type used to filter which network stats will be returned:
|
||||
* 'mobile', 'wifi' or null.
|
||||
* If null, stats for both mobile and wifi are returned.
|
||||
*
|
||||
* Manifest URL used to retrieve network stats per app.
|
||||
* If null, system stats (regardless of the app) are returned.
|
||||
*/
|
||||
readonly attribute DOMString id;
|
||||
DOMString connectionType;
|
||||
DOMString manifestURL;
|
||||
jsval start; // date
|
||||
jsval end; // date
|
||||
};
|
||||
|
||||
[scriptable, uuid(5fbdcae6-a2cd-47b3-929f-83ac75bd4881)]
|
||||
[scriptable, uuid(87529a6c-aef6-11e1-a595-4f034275cfa6)]
|
||||
interface nsIDOMMozNetworkStatsManager : nsISupports
|
||||
{
|
||||
/**
|
||||
* Constants for known interface types.
|
||||
*/
|
||||
const long WIFI = 0;
|
||||
const long MOBILE = 1;
|
||||
|
||||
/**
|
||||
* Find samples between two dates start and end, both included.
|
||||
* Query network statistics.
|
||||
*
|
||||
* If manifestURL is provided, per-app usage is retrieved,
|
||||
* otherwise the target will be system usage.
|
||||
* If options.connectionType is not provided, return statistics for all known
|
||||
* network interfaces.
|
||||
*
|
||||
* If success, the request result will be an nsIDOMMozNetworkStats object.
|
||||
* If options.manifestURL is not provided, return statistics regardless of the app.
|
||||
*
|
||||
* If successful, the request result will be an nsIDOMMozNetworkStats object.
|
||||
*
|
||||
* If network stats are not available for some dates, then rxBytes &
|
||||
* txBytes are undefined for those dates.
|
||||
*/
|
||||
nsIDOMDOMRequest getSamples(in nsIDOMMozNetworkStatsInterface network,
|
||||
in jsval start,
|
||||
in jsval end,
|
||||
[optional] in DOMString manifestURL);
|
||||
nsIDOMDOMRequest getNetworkStats(in jsval options);
|
||||
|
||||
/**
|
||||
* Remove all stats related with the provided network from DB.
|
||||
* Return available connection types.
|
||||
*/
|
||||
nsIDOMDOMRequest clearStats(in nsIDOMMozNetworkStatsInterface network);
|
||||
readonly attribute jsval connectionTypes; // array of DOMStrings.
|
||||
|
||||
/**
|
||||
* Remove all stats in the database.
|
||||
* Clear all stats from DB.
|
||||
*/
|
||||
nsIDOMDOMRequest clearAllStats();
|
||||
nsIDOMDOMRequest clearAllData();
|
||||
|
||||
/**
|
||||
* Return currently available networks.
|
||||
* Time in seconds between samples stored in database.
|
||||
*/
|
||||
readonly attribute jsval availableNetworks; // array of nsIDOMMozNetworkStatsInterface.
|
||||
readonly attribute long sampleRate;
|
||||
|
||||
/**
|
||||
* Minimum time in milliseconds between samples stored in the database.
|
||||
* Maximum number of samples stored in the database per connection type.
|
||||
*/
|
||||
readonly attribute long sampleRate;
|
||||
|
||||
/**
|
||||
* Time in milliseconds recorded by the API until present time. All samples
|
||||
* older than maxStorageAge from now are deleted.
|
||||
*/
|
||||
readonly attribute long long maxStorageAge;
|
||||
readonly attribute long maxStorageSamples;
|
||||
};
|
||||
|
@ -4,8 +4,6 @@
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
interface nsINetworkInterface;
|
||||
|
||||
[scriptable, function, uuid(5f821529-1d80-4ab5-a933-4e1b3585b6bc)]
|
||||
interface nsINetworkStatsServiceProxyCallback : nsISupports
|
||||
{
|
||||
@ -16,20 +14,20 @@ interface nsINetworkStatsServiceProxyCallback : nsISupports
|
||||
void notify(in boolean aResult, in jsval aMessage);
|
||||
};
|
||||
|
||||
[scriptable, uuid(facef032-3fd9-4509-a396-83d94c1a11ae)]
|
||||
[scriptable, uuid(8fbd115d-f590-474c-96dc-e2b6803ca975)]
|
||||
interface nsINetworkStatsServiceProxy : nsISupports
|
||||
{
|
||||
/*
|
||||
* An interface used to record per-app traffic data.
|
||||
* @param aAppId app id
|
||||
* @param aNetworkInterface network
|
||||
* @param aConnectionType network connection type (0 for wifi, 1 for mobile)
|
||||
* @param aTimeStamp time stamp
|
||||
* @param aRxBytes received data amount
|
||||
* @param aTxBytes transmitted data amount
|
||||
* @param aCallback an optional callback
|
||||
*/
|
||||
void saveAppStats(in unsigned long aAppId,
|
||||
in nsINetworkInterface aNetwork,
|
||||
in long aConnectionType,
|
||||
in unsigned long long aTimeStamp,
|
||||
in unsigned long long aRxBytes,
|
||||
in unsigned long long aTxBytes,
|
||||
|
@ -16,7 +16,8 @@ Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
|
||||
|
||||
const DB_NAME = "net_stats";
|
||||
const DB_VERSION = 2;
|
||||
const STORE_NAME = "net_stats";
|
||||
const STORE_NAME = "net_stats"; // Deprecated. Use "net_stats_v2" instead.
|
||||
const STORE_NAME_V2 = "net_stats_v2";
|
||||
|
||||
// Constant defining the maximum values allowed per interface. If more, older
|
||||
// will be erased.
|
||||
@ -25,11 +26,12 @@ const VALUES_MAX_LENGTH = 6 * 30;
|
||||
// Constant defining the rate of the samples. Daily.
|
||||
const SAMPLE_RATE = 1000 * 60 * 60 * 24;
|
||||
|
||||
this.NetworkStatsDB = function NetworkStatsDB() {
|
||||
this.NetworkStatsDB = function NetworkStatsDB(aConnectionTypes) {
|
||||
if (DEBUG) {
|
||||
debug("Constructor");
|
||||
}
|
||||
this.initDBHelper(DB_NAME, DB_VERSION, [STORE_NAME]);
|
||||
this._connectionTypes = aConnectionTypes;
|
||||
this.initDBHelper(DB_NAME, DB_VERSION, [STORE_NAME_V2]);
|
||||
}
|
||||
|
||||
NetworkStatsDB.prototype = {
|
||||
@ -42,7 +44,7 @@ NetworkStatsDB.prototype = {
|
||||
function errorCb(error) {
|
||||
txnCb(error, null);
|
||||
}
|
||||
return this.newTxn(txn_type, STORE_NAME, callback, successCb, errorCb);
|
||||
return this.newTxn(txn_type, STORE_NAME_V2, callback, successCb, errorCb);
|
||||
},
|
||||
|
||||
upgradeSchema: function upgradeSchema(aTransaction, aDb, aOldVersion, aNewVersion) {
|
||||
@ -67,57 +69,69 @@ NetworkStatsDB.prototype = {
|
||||
if (DEBUG) {
|
||||
debug("Created object stores and indexes");
|
||||
}
|
||||
|
||||
// There could be a time delay between the point when the network
|
||||
// interface comes up and the point when the database is initialized.
|
||||
// In this short interval some traffic data are generated but are not
|
||||
// registered by the first sample. The initialization of the database
|
||||
// should make up the missing sample.
|
||||
let stats = [];
|
||||
for (let connection in this._connectionTypes) {
|
||||
let connectionType = this._connectionTypes[connection].name;
|
||||
let timestamp = this.normalizeDate(new Date());
|
||||
stats.push({ connectionType: connectionType,
|
||||
timestamp: timestamp,
|
||||
rxBytes: 0,
|
||||
txBytes: 0,
|
||||
rxTotalBytes: 0,
|
||||
txTotalBytes: 0 });
|
||||
}
|
||||
this._saveStats(aTransaction, objectStore, stats);
|
||||
if (DEBUG) {
|
||||
debug("Database initialized");
|
||||
}
|
||||
} else if (currVersion == 1) {
|
||||
// In order to support per-app traffic data storage, the original
|
||||
// objectStore needs to be replaced by a new objectStore with new
|
||||
// key path ("appId") and new index ("appId").
|
||||
// Also, since now networks are identified by their
|
||||
// [networkId, networkType] not just by their connectionType,
|
||||
// to modify the keyPath is mandatory to delete the object store
|
||||
// and create it again. Old data is going to be deleted because the
|
||||
// networkId for each sample can not be set.
|
||||
db.deleteObjectStore(STORE_NAME);
|
||||
let newObjectStore;
|
||||
newObjectStore = db.createObjectStore(STORE_NAME_V2, { keyPath: ["appId", "connectionType", "timestamp"] });
|
||||
newObjectStore.createIndex("appId", "appId", { unique: false });
|
||||
newObjectStore.createIndex("connectionType", "connectionType", { unique: false });
|
||||
newObjectStore.createIndex("timestamp", "timestamp", { unique: false });
|
||||
newObjectStore.createIndex("rxBytes", "rxBytes", { unique: false });
|
||||
newObjectStore.createIndex("txBytes", "txBytes", { unique: false });
|
||||
newObjectStore.createIndex("rxTotalBytes", "rxTotalBytes", { unique: false });
|
||||
newObjectStore.createIndex("txTotalBytes", "txTotalBytes", { unique: false });
|
||||
if (DEBUG) {
|
||||
debug("Created new object stores and indexes");
|
||||
}
|
||||
|
||||
objectStore = db.createObjectStore(STORE_NAME, { keyPath: ["appId", "network", "timestamp"] });
|
||||
objectStore.createIndex("appId", "appId", { unique: false });
|
||||
objectStore.createIndex("network", "network", { unique: false });
|
||||
objectStore.createIndex("networkType", "networkType", { unique: false });
|
||||
objectStore.createIndex("timestamp", "timestamp", { unique: false });
|
||||
objectStore.createIndex("rxBytes", "rxBytes", { unique: false });
|
||||
objectStore.createIndex("txBytes", "txBytes", { unique: false });
|
||||
objectStore.createIndex("rxTotalBytes", "rxTotalBytes", { unique: false });
|
||||
objectStore.createIndex("txTotalBytes", "txTotalBytes", { unique: false });
|
||||
// Copy the data from the original objectStore to the new objectStore.
|
||||
objectStore = aTransaction.objectStore(STORE_NAME);
|
||||
objectStore.openCursor().onsuccess = function(event) {
|
||||
let cursor = event.target.result;
|
||||
if (!cursor) {
|
||||
// Delete the original object store.
|
||||
db.deleteObjectStore(STORE_NAME);
|
||||
return;
|
||||
}
|
||||
|
||||
debug("Created object stores and indexes for version 2");
|
||||
let oldStats = cursor.value;
|
||||
let newStats = { appId: 0,
|
||||
connectionType: oldStats.connectionType,
|
||||
timestamp: oldStats.timestamp,
|
||||
rxBytes: oldStats.rxBytes,
|
||||
txBytes: oldStats.txBytes,
|
||||
rxTotalBytes: oldStats.rxTotalBytes,
|
||||
txTotalBytes: oldStats.txTotalBytes };
|
||||
this._saveStats(aTransaction, newObjectStore, newStats);
|
||||
cursor.continue();
|
||||
}.bind(this);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
importData: function importData(aStats) {
|
||||
let stats = { appId: aStats.appId,
|
||||
network: [aStats.networkId, aStats.networkType],
|
||||
timestamp: aStats.timestamp,
|
||||
rxBytes: aStats.rxBytes,
|
||||
txBytes: aStats.txBytes,
|
||||
rxTotalBytes: aStats.rxTotalBytes,
|
||||
txTotalBytes: aStats.txTotalBytes };
|
||||
|
||||
return stats;
|
||||
},
|
||||
|
||||
exportData: function exportData(aStats) {
|
||||
let stats = { appId: aStats.appId,
|
||||
networkId: aStats.network[0],
|
||||
networkType: aStats.network[1],
|
||||
timestamp: aStats.timestamp,
|
||||
rxBytes: aStats.rxBytes,
|
||||
txBytes: aStats.txBytes,
|
||||
rxTotalBytes: aStats.rxTotalBytes,
|
||||
txTotalBytes: aStats.txTotalBytes };
|
||||
|
||||
return stats;
|
||||
},
|
||||
|
||||
normalizeDate: function normalizeDate(aDate) {
|
||||
// Convert to UTC according to timezone and
|
||||
// filter timestamp to get SAMPLE_RATE precission
|
||||
@ -126,42 +140,29 @@ NetworkStatsDB.prototype = {
|
||||
return timestamp;
|
||||
},
|
||||
|
||||
saveStats: function saveStats(aStats, aResultCb) {
|
||||
let timestamp = this.normalizeDate(aStats.date);
|
||||
saveStats: function saveStats(stats, aResultCb) {
|
||||
let timestamp = this.normalizeDate(stats.date);
|
||||
|
||||
let stats = { appId: aStats.appId,
|
||||
networkId: aStats.networkId,
|
||||
networkType: aStats.networkType,
|
||||
timestamp: timestamp,
|
||||
rxBytes: (aStats.appId == 0) ? 0 : aStats.rxBytes,
|
||||
txBytes: (aStats.appId == 0) ? 0 : aStats.txBytes,
|
||||
rxTotalBytes: (aStats.appId == 0) ? aStats.rxBytes : 0,
|
||||
txTotalBytes: (aStats.appId == 0) ? aStats.txBytes : 0 };
|
||||
stats = { appId: stats.appId,
|
||||
connectionType: stats.connectionType,
|
||||
timestamp: timestamp,
|
||||
rxBytes: (stats.appId == 0) ? 0 : stats.rxBytes,
|
||||
txBytes: (stats.appId == 0) ? 0 : stats.txBytes,
|
||||
rxTotalBytes: (stats.appId == 0) ? stats.rxBytes : 0,
|
||||
txTotalBytes: (stats.appId == 0) ? stats.txBytes : 0 };
|
||||
|
||||
stats = this.importData(stats);
|
||||
|
||||
this.dbNewTxn("readwrite", function(aTxn, aStore) {
|
||||
this.dbNewTxn("readwrite", function(txn, store) {
|
||||
if (DEBUG) {
|
||||
debug("Filtered time: " + new Date(timestamp));
|
||||
debug("New stats: " + JSON.stringify(stats));
|
||||
}
|
||||
|
||||
let request = aStore.index("network").openCursor(stats.network, "prev");
|
||||
let request = store.index("connectionType").openCursor(stats.connectionType, "prev");
|
||||
request.onsuccess = function onsuccess(event) {
|
||||
let cursor = event.target.result;
|
||||
if (!cursor) {
|
||||
// Empty, so save first element.
|
||||
|
||||
// There could be a time delay between the point when the network
|
||||
// interface comes up and the point when the database is initialized.
|
||||
// In this short interval some traffic data are generated but are not
|
||||
// registered by the first sample.
|
||||
if (stats.appId == 0) {
|
||||
stats.rxBytes = stats.rxTotalBytes;
|
||||
stats.txBytes = stats.txTotalBytes;
|
||||
}
|
||||
|
||||
this._saveStats(aTxn, aStore, stats);
|
||||
this._saveStats(txn, store, stats);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -176,10 +177,10 @@ NetworkStatsDB.prototype = {
|
||||
}
|
||||
|
||||
// Remove stats previous to now - VALUE_MAX_LENGTH
|
||||
this._removeOldStats(aTxn, aStore, stats.appId, stats.network, stats.timestamp);
|
||||
this._removeOldStats(txn, store, stats.appId, stats.connectionType, stats.timestamp);
|
||||
|
||||
// Process stats before save
|
||||
this._processSamplesDiff(aTxn, aStore, cursor, stats);
|
||||
this._processSamplesDiff(txn, store, cursor, stats);
|
||||
}.bind(this);
|
||||
}.bind(this), aResultCb);
|
||||
},
|
||||
@ -188,21 +189,20 @@ NetworkStatsDB.prototype = {
|
||||
* This function check that stats are saved in the database following the sample rate.
|
||||
* In this way is easier to find elements when stats are requested.
|
||||
*/
|
||||
_processSamplesDiff: function _processSamplesDiff(aTxn, aStore, aLastSampleCursor, aNewSample) {
|
||||
let lastSample = aLastSampleCursor.value;
|
||||
_processSamplesDiff: function _processSamplesDiff(txn, store, lastSampleCursor, newSample) {
|
||||
let lastSample = lastSampleCursor.value;
|
||||
|
||||
// Get difference between last and new sample.
|
||||
let diff = (aNewSample.timestamp - lastSample.timestamp) / SAMPLE_RATE;
|
||||
let diff = (newSample.timestamp - lastSample.timestamp) / SAMPLE_RATE;
|
||||
if (diff % 1) {
|
||||
// diff is decimal, so some error happened because samples are stored as a multiple
|
||||
// of SAMPLE_RATE
|
||||
aTxn.abort();
|
||||
txn.abort();
|
||||
throw new Error("Error processing samples");
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
debug("New: " + aNewSample.timestamp + " - Last: " +
|
||||
lastSample.timestamp + " - diff: " + diff);
|
||||
debug("New: " + newSample.timestamp + " - Last: " + lastSample.timestamp + " - diff: " + diff);
|
||||
}
|
||||
|
||||
// If the incoming data is obtained from netd (|newSample.appId| is 0),
|
||||
@ -210,15 +210,15 @@ NetworkStatsDB.prototype = {
|
||||
// |txTotalBytes|/|rxTotalBytes| and the last |txTotalBytes|/|rxTotalBytes|.
|
||||
// Else, the incoming data is per-app data (|newSample.appId| is not 0),
|
||||
// the |txBytes|/|rxBytes| is directly the new |txBytes|/|rxBytes|.
|
||||
if (aNewSample.appId == 0) {
|
||||
let rxDiff = aNewSample.rxTotalBytes - lastSample.rxTotalBytes;
|
||||
let txDiff = aNewSample.txTotalBytes - lastSample.txTotalBytes;
|
||||
if (newSample.appId == 0) {
|
||||
let rxDiff = newSample.rxTotalBytes - lastSample.rxTotalBytes;
|
||||
let txDiff = newSample.txTotalBytes - lastSample.txTotalBytes;
|
||||
if (rxDiff < 0 || txDiff < 0) {
|
||||
rxDiff = aNewSample.rxTotalBytes;
|
||||
txDiff = aNewSample.txTotalBytes;
|
||||
rxDiff = newSample.rxTotalBytes;
|
||||
txDiff = newSample.txTotalBytes;
|
||||
}
|
||||
aNewSample.rxBytes = rxDiff;
|
||||
aNewSample.txBytes = txDiff;
|
||||
newSample.rxBytes = rxDiff;
|
||||
newSample.txBytes = txDiff;
|
||||
}
|
||||
|
||||
if (diff == 1) {
|
||||
@ -227,12 +227,11 @@ NetworkStatsDB.prototype = {
|
||||
// If the incoming data is per-data data, new |rxTotalBytes|/|txTotalBytes|
|
||||
// needs to be obtained by adding new |rxBytes|/|txBytes| to last
|
||||
// |rxTotalBytes|/|txTotalBytes|.
|
||||
if (aNewSample.appId != 0) {
|
||||
aNewSample.rxTotalBytes = aNewSample.rxBytes + lastSample.rxTotalBytes;
|
||||
aNewSample.txTotalBytes = aNewSample.txBytes + lastSample.txTotalBytes;
|
||||
if (newSample.appId != 0) {
|
||||
newSample.rxTotalBytes = newSample.rxBytes + lastSample.rxTotalBytes;
|
||||
newSample.txTotalBytes = newSample.txBytes + lastSample.txTotalBytes;
|
||||
}
|
||||
|
||||
this._saveStats(aTxn, aStore, aNewSample);
|
||||
this._saveStats(txn, store, newSample);
|
||||
return;
|
||||
}
|
||||
if (diff > 1) {
|
||||
@ -245,20 +244,19 @@ NetworkStatsDB.prototype = {
|
||||
|
||||
let data = [];
|
||||
for (let i = diff - 2; i >= 0; i--) {
|
||||
let time = aNewSample.timestamp - SAMPLE_RATE * (i + 1);
|
||||
let sample = { appId: aNewSample.appId,
|
||||
network: aNewSample.network,
|
||||
timestamp: time,
|
||||
rxBytes: 0,
|
||||
txBytes: 0,
|
||||
rxTotalBytes: lastSample.rxTotalBytes,
|
||||
txTotalBytes: lastSample.txTotalBytes };
|
||||
|
||||
let time = newSample.timestamp - SAMPLE_RATE * (i + 1);
|
||||
let sample = {appId: newSample.appId,
|
||||
connectionType: newSample.connectionType,
|
||||
timestamp: time,
|
||||
rxBytes: 0,
|
||||
txBytes: 0,
|
||||
rxTotalBytes: lastSample.rxTotalBytes,
|
||||
txTotalBytes: lastSample.txTotalBytes};
|
||||
data.push(sample);
|
||||
}
|
||||
|
||||
data.push(aNewSample);
|
||||
this._saveStats(aTxn, aStore, data);
|
||||
data.push(newSample);
|
||||
this._saveStats(txn, store, data);
|
||||
return;
|
||||
}
|
||||
if (diff == 0 || diff < 0) {
|
||||
@ -268,163 +266,91 @@ NetworkStatsDB.prototype = {
|
||||
|
||||
// If diff < 0, clock or timezone changed back. Place data in the last sample.
|
||||
|
||||
lastSample.rxBytes += aNewSample.rxBytes;
|
||||
lastSample.txBytes += aNewSample.txBytes;
|
||||
lastSample.rxBytes += newSample.rxBytes;
|
||||
lastSample.txBytes += newSample.txBytes;
|
||||
|
||||
// If incoming data is obtained from netd, last |rxTotalBytes|/|txTotalBytes|
|
||||
// needs to get updated by replacing the new |rxTotalBytes|/|txTotalBytes|.
|
||||
if (aNewSample.appId == 0) {
|
||||
lastSample.rxTotalBytes = aNewSample.rxTotalBytes;
|
||||
lastSample.txTotalBytes = aNewSample.txTotalBytes;
|
||||
if (newSample.appId == 0) {
|
||||
lastSample.rxTotalBytes = newSample.rxTotalBytes;
|
||||
lastSample.txTotalBytes = newSample.txTotalBytes;
|
||||
} else {
|
||||
// Else, the incoming data is per-app data, old |rxTotalBytes|/
|
||||
// |txTotalBytes| needs to get updated by adding the new
|
||||
// |rxBytes|/|txBytes| to last |rxTotalBytes|/|txTotalBytes|.
|
||||
lastSample.rxTotalBytes += aNewSample.rxBytes;
|
||||
lastSample.txTotalBytes += aNewSample.txBytes;
|
||||
lastSample.rxTotalBytes += newSample.rxBytes;
|
||||
lastSample.txTotalBytes += newSample.txBytes;
|
||||
}
|
||||
if (DEBUG) {
|
||||
debug("Update: " + JSON.stringify(lastSample));
|
||||
}
|
||||
let req = aLastSampleCursor.update(lastSample);
|
||||
let req = lastSampleCursor.update(lastSample);
|
||||
}
|
||||
},
|
||||
|
||||
_saveStats: function _saveStats(aTxn, aStore, aNetworkStats) {
|
||||
_saveStats: function _saveStats(txn, store, networkStats) {
|
||||
if (DEBUG) {
|
||||
debug("_saveStats: " + JSON.stringify(aNetworkStats));
|
||||
debug("_saveStats: " + JSON.stringify(networkStats));
|
||||
}
|
||||
|
||||
if (Array.isArray(aNetworkStats)) {
|
||||
let len = aNetworkStats.length - 1;
|
||||
if (Array.isArray(networkStats)) {
|
||||
let len = networkStats.length - 1;
|
||||
for (let i = 0; i <= len; i++) {
|
||||
aStore.put(aNetworkStats[i]);
|
||||
store.put(networkStats[i]);
|
||||
}
|
||||
} else {
|
||||
aStore.put(aNetworkStats);
|
||||
store.put(networkStats);
|
||||
}
|
||||
},
|
||||
|
||||
_removeOldStats: function _removeOldStats(aTxn, aStore, aAppId, aNetwork, aDate) {
|
||||
_removeOldStats: function _removeOldStats(txn, store, appId, connType, date) {
|
||||
// Callback function to remove old items when new ones are added.
|
||||
let filterDate = aDate - (SAMPLE_RATE * VALUES_MAX_LENGTH - 1);
|
||||
let lowerFilter = [aAppId, aNetwork, 0];
|
||||
let upperFilter = [aAppId, aNetwork, filterDate];
|
||||
let filterDate = date - (SAMPLE_RATE * VALUES_MAX_LENGTH - 1);
|
||||
let lowerFilter = [appId, connType, 0];
|
||||
let upperFilter = [appId, connType, filterDate];
|
||||
let range = IDBKeyRange.bound(lowerFilter, upperFilter, false, false);
|
||||
let lastSample = null;
|
||||
let self = this;
|
||||
|
||||
aStore.openCursor(range).onsuccess = function(event) {
|
||||
store.openCursor(range).onsuccess = function(event) {
|
||||
var cursor = event.target.result;
|
||||
if (cursor) {
|
||||
lastSample = cursor.value;
|
||||
cursor.delete();
|
||||
cursor.continue();
|
||||
return;
|
||||
}
|
||||
|
||||
// If all samples for a network are removed, an empty sample
|
||||
// has to be saved to keep the totalBytes in order to compute
|
||||
// future samples because system counters are not set to 0.
|
||||
// Thus, if there are no samples left, the last sample removed
|
||||
// will be saved again after setting its bytes to 0.
|
||||
let request = aStore.index("network").openCursor(aNetwork);
|
||||
request.onsuccess = function onsuccess(event) {
|
||||
let cursor = event.target.result;
|
||||
if (!cursor && lastSample != null) {
|
||||
let timestamp = new Date();
|
||||
timestamp = self.normalizeDate(timestamp);
|
||||
lastSample.timestamp = timestamp;
|
||||
lastSample.rxBytes = 0;
|
||||
lastSample.txBytes = 0;
|
||||
self._saveStats(aTxn, aStore, lastSample);
|
||||
}
|
||||
};
|
||||
};
|
||||
}.bind(this);
|
||||
},
|
||||
|
||||
clearInterfaceStats: function clearInterfaceStats(aNetwork, aResultCb) {
|
||||
let network = [aNetwork.id, aNetwork.type];
|
||||
let self = this;
|
||||
|
||||
// Clear and save an empty sample to keep sync with system counters
|
||||
this.dbNewTxn("readwrite", function(aTxn, aStore) {
|
||||
let sample = null;
|
||||
let request = aStore.index("network").openCursor(network, "prev");
|
||||
request.onsuccess = function onsuccess(event) {
|
||||
let cursor = event.target.result;
|
||||
if (cursor) {
|
||||
if (!sample) {
|
||||
sample = cursor.value;
|
||||
}
|
||||
|
||||
cursor.delete();
|
||||
cursor.continue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (sample) {
|
||||
let timestamp = new Date();
|
||||
timestamp = self.normalizeDate(timestamp);
|
||||
sample.timestamp = timestamp;
|
||||
sample.appId = 0;
|
||||
sample.rxBytes = 0;
|
||||
sample.txBytes = 0;
|
||||
|
||||
self._saveStats(aTxn, aStore, sample);
|
||||
}
|
||||
};
|
||||
clear: function clear(aResultCb) {
|
||||
this.dbNewTxn("readwrite", function(txn, store) {
|
||||
if (DEBUG) {
|
||||
debug("Going to clear all!");
|
||||
}
|
||||
store.clear();
|
||||
}, aResultCb);
|
||||
},
|
||||
|
||||
clearStats: function clearStats(aNetworks, aResultCb) {
|
||||
let index = 0;
|
||||
let stats = [];
|
||||
let self = this;
|
||||
|
||||
let callback = function(aError, aResult) {
|
||||
index++;
|
||||
|
||||
if (!aError && index < aNetworks.length) {
|
||||
self.clearInterfaceStats(aNetworks[index], callback);
|
||||
return;
|
||||
}
|
||||
|
||||
aResultCb(aError, aResult);
|
||||
};
|
||||
|
||||
if (!aNetworks[index]) {
|
||||
aResultCb(null, true);
|
||||
return;
|
||||
}
|
||||
this.clearInterfaceStats(aNetworks[index], callback);
|
||||
},
|
||||
|
||||
find: function find(aResultCb, aNetwork, aStart, aEnd, aAppId, aManifestURL) {
|
||||
find: function find(aResultCb, aOptions) {
|
||||
let offset = (new Date()).getTimezoneOffset() * 60 * 1000;
|
||||
let start = this.normalizeDate(aStart);
|
||||
let end = this.normalizeDate(aEnd);
|
||||
let start = this.normalizeDate(aOptions.start);
|
||||
let end = this.normalizeDate(aOptions.end);
|
||||
|
||||
if (DEBUG) {
|
||||
debug("Find samples for appId: " + aAppId + " network " +
|
||||
JSON.stringify(aNetwork) + " from " + start + " until " + end);
|
||||
debug("Find: appId: " + aOptions.appId + " connectionType:" +
|
||||
aOptions.connectionType + " start: " + start + " end: " + end);
|
||||
debug("Start time: " + new Date(start));
|
||||
debug("End time: " + new Date(end));
|
||||
}
|
||||
|
||||
this.dbNewTxn("readonly", function(aTxn, aStore) {
|
||||
let network = [aNetwork.id, aNetwork.type];
|
||||
let lowerFilter = [aAppId, network, start];
|
||||
let upperFilter = [aAppId, network, end];
|
||||
this.dbNewTxn("readonly", function(txn, store) {
|
||||
let lowerFilter = [aOptions.appId, aOptions.connectionType, start];
|
||||
let upperFilter = [aOptions.appId, aOptions.connectionType, end];
|
||||
let range = IDBKeyRange.bound(lowerFilter, upperFilter, false, false);
|
||||
|
||||
let data = [];
|
||||
|
||||
if (!aTxn.result) {
|
||||
aTxn.result = {};
|
||||
if (!txn.result) {
|
||||
txn.result = {};
|
||||
}
|
||||
|
||||
let request = aStore.openCursor(range).onsuccess = function(event) {
|
||||
let request = store.openCursor(range).onsuccess = function(event) {
|
||||
var cursor = event.target.result;
|
||||
if (cursor){
|
||||
data.push({ rxBytes: cursor.value.rxBytes,
|
||||
@ -438,11 +364,66 @@ NetworkStatsDB.prototype = {
|
||||
// now - VALUES_MAX_LENGTH, fill with empty samples.
|
||||
this.fillResultSamples(start + offset, end + offset, data);
|
||||
|
||||
aTxn.result.manifestURL = aManifestURL;
|
||||
aTxn.result.network = aNetwork;
|
||||
aTxn.result.start = aStart;
|
||||
aTxn.result.end = aEnd;
|
||||
aTxn.result.data = data;
|
||||
txn.result.manifestURL = aOptions.manifestURL;
|
||||
txn.result.connectionType = aOptions.connectionType;
|
||||
txn.result.start = aOptions.start;
|
||||
txn.result.end = aOptions.end;
|
||||
txn.result.data = data;
|
||||
}.bind(this);
|
||||
}.bind(this), aResultCb);
|
||||
},
|
||||
|
||||
findAll: function findAll(aResultCb, aOptions) {
|
||||
let offset = (new Date()).getTimezoneOffset() * 60 * 1000;
|
||||
let start = this.normalizeDate(aOptions.start);
|
||||
let end = this.normalizeDate(aOptions.end);
|
||||
|
||||
if (DEBUG) {
|
||||
debug("FindAll: appId: " + aOptions.appId +
|
||||
" start: " + start + " end: " + end + "\n");
|
||||
}
|
||||
|
||||
let self = this;
|
||||
this.dbNewTxn("readonly", function(txn, store) {
|
||||
let lowerFilter = start;
|
||||
let upperFilter = end;
|
||||
let range = IDBKeyRange.bound(lowerFilter, upperFilter, false, false);
|
||||
|
||||
let data = [];
|
||||
|
||||
if (!txn.result) {
|
||||
txn.result = {};
|
||||
}
|
||||
|
||||
let request = store.index("timestamp").openCursor(range).onsuccess = function(event) {
|
||||
var cursor = event.target.result;
|
||||
if (cursor) {
|
||||
if (cursor.value.appId != aOptions.appId) {
|
||||
cursor.continue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.length > 0 &&
|
||||
data[data.length - 1].date.getTime() == cursor.value.timestamp + offset) {
|
||||
// Time is the same, so add values.
|
||||
data[data.length - 1].rxBytes += cursor.value.rxBytes;
|
||||
data[data.length - 1].txBytes += cursor.value.txBytes;
|
||||
} else {
|
||||
data.push({ rxBytes: cursor.value.rxBytes,
|
||||
txBytes: cursor.value.txBytes,
|
||||
date: new Date(cursor.value.timestamp + offset) });
|
||||
}
|
||||
cursor.continue();
|
||||
return;
|
||||
}
|
||||
|
||||
this.fillResultSamples(start + offset, end + offset, data);
|
||||
|
||||
txn.result.manifestURL = aOptions.manifestURL;
|
||||
txn.result.connectionType = aOptions.connectionType;
|
||||
txn.result.start = aOptions.start;
|
||||
txn.result.end = aOptions.end;
|
||||
txn.result.data = data;
|
||||
}.bind(this);
|
||||
}.bind(this), aResultCb);
|
||||
},
|
||||
@ -480,9 +461,9 @@ NetworkStatsDB.prototype = {
|
||||
},
|
||||
|
||||
logAllRecords: function logAllRecords(aResultCb) {
|
||||
this.dbNewTxn("readonly", function(aTxn, aStore) {
|
||||
aStore.mozGetAll().onsuccess = function onsuccess(event) {
|
||||
aTxn.result = event.target.result;
|
||||
this.dbNewTxn("readonly", function(txn, store) {
|
||||
store.mozGetAll().onsuccess = function onsuccess(event) {
|
||||
txn.result = event.target.result;
|
||||
};
|
||||
}, aResultCb);
|
||||
},
|
||||
|
@ -55,38 +55,9 @@ NetworkStatsData.prototype = {
|
||||
QueryInterface : XPCOMUtils.generateQI([nsIDOMMozNetworkStatsData])
|
||||
};
|
||||
|
||||
// NetworkStatsInterface
|
||||
const NETWORKSTATSINTERFACE_CONTRACTID = "@mozilla.org/networkstatsinterface;1";
|
||||
const NETWORKSTATSINTERFACE_CID = Components.ID("{f540615b-d803-43ff-8200-2a9d145a5645}");
|
||||
const nsIDOMMozNetworkStatsInterface = Components.interfaces.nsIDOMMozNetworkStatsInterface;
|
||||
|
||||
function NetworkStatsInterface(aNetwork) {
|
||||
if (DEBUG) {
|
||||
debug("NetworkStatsInterface Constructor");
|
||||
}
|
||||
this.type = aNetwork.type;
|
||||
this.id = aNetwork.id;
|
||||
}
|
||||
|
||||
NetworkStatsInterface.prototype = {
|
||||
__exposedProps__: {
|
||||
id: 'r',
|
||||
type: 'r',
|
||||
},
|
||||
|
||||
classID : NETWORKSTATSINTERFACE_CID,
|
||||
classInfo : XPCOMUtils.generateCI({classID: NETWORKSTATSINTERFACE_CID,
|
||||
contractID: NETWORKSTATSINTERFACE_CONTRACTID,
|
||||
classDescription: "NetworkStatsInterface",
|
||||
interfaces: [nsIDOMMozNetworkStatsInterface],
|
||||
flags: nsIClassInfo.DOM_OBJECT}),
|
||||
|
||||
QueryInterface : XPCOMUtils.generateQI([nsIDOMMozNetworkStatsInterface])
|
||||
}
|
||||
|
||||
// NetworkStats
|
||||
const NETWORKSTATS_CONTRACTID = "@mozilla.org/networkstats;1";
|
||||
const NETWORKSTATS_CID = Components.ID("{b6fc4b14-628d-4c99-bf4e-e4ed56916cbe}");
|
||||
const NETWORKSTATS_CID = Components.ID("{6613ea55-b99c-44f9-91bf-d07da10b9b74}");
|
||||
const nsIDOMMozNetworkStats = Components.interfaces.nsIDOMMozNetworkStats;
|
||||
|
||||
function NetworkStats(aWindow, aStats) {
|
||||
@ -94,7 +65,7 @@ function NetworkStats(aWindow, aStats) {
|
||||
debug("NetworkStats Constructor");
|
||||
}
|
||||
this.manifestURL = aStats.manifestURL || null;
|
||||
this.network = new NetworkStatsInterface(aStats.network);
|
||||
this.connectionType = aStats.connectionType || null;
|
||||
this.start = aStats.start || null;
|
||||
this.end = aStats.end || null;
|
||||
|
||||
@ -107,7 +78,7 @@ function NetworkStats(aWindow, aStats) {
|
||||
NetworkStats.prototype = {
|
||||
__exposedProps__: {
|
||||
manifestURL: 'r',
|
||||
network: 'r',
|
||||
connectionType: 'r',
|
||||
start: 'r',
|
||||
end: 'r',
|
||||
data: 'r',
|
||||
@ -121,14 +92,13 @@ NetworkStats.prototype = {
|
||||
flags: nsIClassInfo.DOM_OBJECT}),
|
||||
|
||||
QueryInterface : XPCOMUtils.generateQI([nsIDOMMozNetworkStats,
|
||||
nsIDOMMozNetworkStatsData,
|
||||
nsIDOMMozNetworkStatsInterface])
|
||||
nsIDOMMozNetworkStatsData])
|
||||
}
|
||||
|
||||
// NetworkStatsManager
|
||||
|
||||
const NETWORKSTATSMANAGER_CONTRACTID = "@mozilla.org/networkStatsManager;1";
|
||||
const NETWORKSTATSMANAGER_CID = Components.ID("{5fbdcae6-a2cd-47b3-929f-83ac75bd4881}");
|
||||
const NETWORKSTATSMANAGER_CID = Components.ID("{87529a6c-aef6-11e1-a595-4f034275cfa6}");
|
||||
const nsIDOMMozNetworkStatsManager = Components.interfaces.nsIDOMMozNetworkStatsManager;
|
||||
|
||||
function NetworkStatsManager() {
|
||||
@ -146,64 +116,42 @@ NetworkStatsManager.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
getSamples: function getSamples(aNetwork, aStart, aEnd, aManifestURL) {
|
||||
getNetworkStats: function getNetworkStats(aOptions) {
|
||||
this.checkPrivileges();
|
||||
|
||||
if (aStart.constructor.name !== "Date" ||
|
||||
aEnd.constructor.name !== "Date" ||
|
||||
aStart > aEnd) {
|
||||
if (!aOptions.start || !aOptions.end ||
|
||||
aOptions.start > aOptions.end) {
|
||||
throw Components.results.NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
let request = this.createRequest();
|
||||
cpmm.sendAsyncMessage("NetworkStats:Get",
|
||||
{ network: aNetwork,
|
||||
start: aStart,
|
||||
end: aEnd,
|
||||
manifestURL: aManifestURL,
|
||||
id: this.getRequestId(request) });
|
||||
{data: aOptions, id: this.getRequestId(request)});
|
||||
return request;
|
||||
},
|
||||
|
||||
clearStats: function clearStats(aNetwork) {
|
||||
clearAllData: function clearAllData() {
|
||||
this.checkPrivileges();
|
||||
|
||||
let request = this.createRequest();
|
||||
cpmm.sendAsyncMessage("NetworkStats:Clear",
|
||||
{ network: aNetwork,
|
||||
id: this.getRequestId(request) });
|
||||
return request;
|
||||
},
|
||||
|
||||
clearAllStats: function clearAllStats() {
|
||||
this.checkPrivileges();
|
||||
|
||||
let request = this.createRequest();
|
||||
cpmm.sendAsyncMessage("NetworkStats:ClearAll",
|
||||
{id: this.getRequestId(request)});
|
||||
return request;
|
||||
},
|
||||
|
||||
get availableNetworks() {
|
||||
get connectionTypes() {
|
||||
this.checkPrivileges();
|
||||
|
||||
let result = ObjectWrapper.wrap(cpmm.sendSyncMessage("NetworkStats:Networks")[0], this._window);
|
||||
let networks = this.data = Cu.createArrayIn(this._window);
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
networks.push(new NetworkStatsInterface(result[i]));
|
||||
}
|
||||
|
||||
return networks;
|
||||
return ObjectWrapper.wrap(cpmm.sendSyncMessage("NetworkStats:Types")[0], this._window);
|
||||
},
|
||||
|
||||
get sampleRate() {
|
||||
this.checkPrivileges();
|
||||
return cpmm.sendSyncMessage("NetworkStats:SampleRate")[0];
|
||||
return cpmm.sendSyncMessage("NetworkStats:SampleRate")[0] / 1000;
|
||||
},
|
||||
|
||||
get maxStorageAge() {
|
||||
get maxStorageSamples() {
|
||||
this.checkPrivileges();
|
||||
return cpmm.sendSyncMessage("NetworkStats:MaxStorageAge")[0];
|
||||
return cpmm.sendSyncMessage("NetworkStats:MaxStorageSamples")[0];
|
||||
},
|
||||
|
||||
receiveMessage: function(aMessage) {
|
||||
@ -235,7 +183,6 @@ NetworkStatsManager.prototype = {
|
||||
break;
|
||||
|
||||
case "NetworkStats:Clear:Return":
|
||||
case "NetworkStats:ClearAll:Return":
|
||||
if (msg.error) {
|
||||
Services.DOMRequest.fireError(req, msg.error);
|
||||
return;
|
||||
@ -275,8 +222,7 @@ NetworkStatsManager.prototype = {
|
||||
}
|
||||
|
||||
this.initDOMRequestHelper(aWindow, ["NetworkStats:Get:Return",
|
||||
"NetworkStats:Clear:Return",
|
||||
"NetworkStats:ClearAll:Return"]);
|
||||
"NetworkStats:Clear:Return"]);
|
||||
},
|
||||
|
||||
// Called from DOMRequestIpcHelper
|
||||
@ -299,6 +245,5 @@ NetworkStatsManager.prototype = {
|
||||
}
|
||||
|
||||
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NetworkStatsData,
|
||||
NetworkStatsInterface,
|
||||
NetworkStats,
|
||||
NetworkStatsManager]);
|
||||
|
@ -1,12 +1,9 @@
|
||||
component {3b16fe17-5583-483a-b486-b64a3243221c} NetworkStatsManager.js
|
||||
contract @mozilla.org/networkStatsdata;1 {3b16fe17-5583-483a-b486-b64a3243221c}
|
||||
|
||||
component {b6fc4b14-628d-4c99-bf4e-e4ed56916cbe} NetworkStatsManager.js
|
||||
contract @mozilla.org/networkStats;1 {b6fc4b14-628d-4c99-bf4e-e4ed56916cbe}
|
||||
component {6613ea55-b99c-44f9-91bf-d07da10b9b74} NetworkStatsManager.js
|
||||
contract @mozilla.org/networkStats;1 {6613ea55-b99c-44f9-91bf-d07da10b9b74}
|
||||
|
||||
component {f540615b-d803-43ff-8200-2a9d145a5645} NetworkStatsManager.js
|
||||
contract @mozilla.org/networkstatsinterface;1 {f540615b-d803-43ff-8200-2a9d145a5645}
|
||||
|
||||
component {5fbdcae6-a2cd-47b3-929f-83ac75bd4881} NetworkStatsManager.js
|
||||
contract @mozilla.org/networkStatsManager;1 {5fbdcae6-a2cd-47b3-929f-83ac75bd4881}
|
||||
component {87529a6c-aef6-11e1-a595-4f034275cfa6} NetworkStatsManager.js
|
||||
contract @mozilla.org/networkStatsManager;1 {87529a6c-aef6-11e1-a595-4f034275cfa6}
|
||||
category JavaScript-navigator-property mozNetworkStats @mozilla.org/networkStatsManager;1
|
||||
|
@ -5,11 +5,7 @@
|
||||
"use strict";
|
||||
|
||||
const DEBUG = false;
|
||||
function debug(s) {
|
||||
if (DEBUG) {
|
||||
dump("-*- NetworkStatsService: " + s + "\n");
|
||||
}
|
||||
}
|
||||
function debug(s) { dump("-*- NetworkStatsService: " + s + "\n"); }
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
@ -26,6 +22,7 @@ const TOPIC_INTERFACE_REGISTERED = "network-interface-registered";
|
||||
const TOPIC_INTERFACE_UNREGISTERED = "network-interface-unregistered";
|
||||
const NET_TYPE_WIFI = Ci.nsINetworkInterface.NETWORK_TYPE_WIFI;
|
||||
const NET_TYPE_MOBILE = Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE;
|
||||
const NET_TYPE_UNKNOWN = Ci.nsINetworkInterface.NETWORK_TYPE_UNKNOWN;
|
||||
|
||||
// The maximum traffic amount can be saved in the |cachedAppStats|.
|
||||
const MAX_CACHED_TRAFFIC = 500 * 1000 * 1000; // 500 MB
|
||||
@ -42,19 +39,11 @@ XPCOMUtils.defineLazyServiceGetter(this, "appsService",
|
||||
"@mozilla.org/AppsService;1",
|
||||
"nsIAppsService");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
|
||||
"@mozilla.org/settingsService;1",
|
||||
"nsISettingsService");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "gRadioInterface", function () {
|
||||
let ril = Cc["@mozilla.org/ril;1"].getService(Ci["nsIRadioInterfaceLayer"]);
|
||||
// TODO: Bug 923382 - B2G Multi-SIM: support multiple SIM cards for network metering.
|
||||
return ril.getRadioInterface(0);
|
||||
});
|
||||
|
||||
this.NetworkStatsService = {
|
||||
init: function() {
|
||||
debug("Service started");
|
||||
if (DEBUG) {
|
||||
debug("Service started");
|
||||
}
|
||||
|
||||
Services.obs.addObserver(this, "xpcom-shutdown", false);
|
||||
Services.obs.addObserver(this, TOPIC_INTERFACE_REGISTERED, false);
|
||||
@ -63,41 +52,24 @@ this.NetworkStatsService = {
|
||||
|
||||
this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
|
||||
// Object to store network interfaces, each network interface is composed
|
||||
// by a network object (network type and network Id) and a interfaceName
|
||||
// that contains the name of the physical interface (wlan0, rmnet0, etc.).
|
||||
// The network type can be 0 for wifi or 1 for mobile. On the other hand,
|
||||
// the network id is '0' for wifi or the iccid for mobile (SIM).
|
||||
// Each networkInterface is placed in the _networks object by the index of
|
||||
// 'networkId + networkType'.
|
||||
//
|
||||
// _networks object allows to map available network interfaces at low level
|
||||
// (wlan0, rmnet0, etc.) to a network. It's not mandatory to have a
|
||||
// networkInterface per network but can't exist a networkInterface not
|
||||
// being mapped to a network.
|
||||
this._connectionTypes = Object.create(null);
|
||||
this._connectionTypes[NET_TYPE_WIFI] = { name: "wifi",
|
||||
network: Object.create(null) };
|
||||
this._connectionTypes[NET_TYPE_MOBILE] = { name: "mobile",
|
||||
network: Object.create(null) };
|
||||
|
||||
this._networks = Object.create(null);
|
||||
|
||||
// There is no way to know a priori if wifi connection is available,
|
||||
// just when the wifi driver is loaded, but it is unloaded when
|
||||
// wifi is switched off. So wifi connection is hardcoded
|
||||
let netId = this.getNetworkId('0', NET_TYPE_WIFI);
|
||||
this._networks[netId] = { network: { id: '0',
|
||||
type: NET_TYPE_WIFI },
|
||||
interfaceName: null };
|
||||
|
||||
this.messages = ["NetworkStats:Get",
|
||||
"NetworkStats:Clear",
|
||||
"NetworkStats:ClearAll",
|
||||
"NetworkStats:Networks",
|
||||
"NetworkStats:Types",
|
||||
"NetworkStats:SampleRate",
|
||||
"NetworkStats:MaxStorageAge"];
|
||||
"NetworkStats:MaxStorageSamples"];
|
||||
|
||||
this.messages.forEach(function(aMsgName) {
|
||||
ppmm.addMessageListener(aMsgName, this);
|
||||
this.messages.forEach(function(msgName) {
|
||||
ppmm.addMessageListener(msgName, this);
|
||||
}, this);
|
||||
|
||||
this._db = new NetworkStatsDB();
|
||||
this._db = new NetworkStatsDB(this._connectionTypes);
|
||||
|
||||
// Stats for all interfaces are updated periodically
|
||||
this.timer.initWithCallback(this, this._db.sampleRate,
|
||||
@ -116,56 +88,58 @@ this.NetworkStatsService = {
|
||||
return;
|
||||
}
|
||||
|
||||
debug("receiveMessage " + aMessage.name);
|
||||
|
||||
if (DEBUG) {
|
||||
debug("receiveMessage " + aMessage.name);
|
||||
}
|
||||
let mm = aMessage.target;
|
||||
let msg = aMessage.json;
|
||||
|
||||
switch (aMessage.name) {
|
||||
case "NetworkStats:Get":
|
||||
this.getSamples(mm, msg);
|
||||
this.getStats(mm, msg);
|
||||
break;
|
||||
case "NetworkStats:Clear":
|
||||
this.clearInterfaceStats(mm, msg);
|
||||
break;
|
||||
case "NetworkStats:ClearAll":
|
||||
this.clearDB(mm, msg);
|
||||
break;
|
||||
case "NetworkStats:Networks":
|
||||
return this.availableNetworks();
|
||||
case "NetworkStats:Types":
|
||||
// This message is sync.
|
||||
let types = [];
|
||||
for (let i in this._connectionTypes) {
|
||||
types.push(this._connectionTypes[i].name);
|
||||
}
|
||||
return types;
|
||||
case "NetworkStats:SampleRate":
|
||||
// This message is sync.
|
||||
return this._db.sampleRate;
|
||||
case "NetworkStats:MaxStorageAge":
|
||||
case "NetworkStats:MaxStorageSamples":
|
||||
// This message is sync.
|
||||
return this._db.maxStorageSamples * this._db.sampleRate;
|
||||
return this._db.maxStorageSamples;
|
||||
}
|
||||
},
|
||||
|
||||
observe: function observe(aSubject, aTopic, aData) {
|
||||
switch (aTopic) {
|
||||
observe: function observe(subject, topic, data) {
|
||||
switch (topic) {
|
||||
case TOPIC_INTERFACE_REGISTERED:
|
||||
case TOPIC_INTERFACE_UNREGISTERED:
|
||||
|
||||
// If new interface is registered (notified from NetworkManager),
|
||||
// the stats are updated for the new interface without waiting to
|
||||
// complete the updating period.
|
||||
|
||||
let network = aSubject.QueryInterface(Ci.nsINetworkInterface);
|
||||
debug("Network " + network.name + " of type " + network.type + " status change");
|
||||
|
||||
let netId = this.convertNetworkInterface(network);
|
||||
if (!netId) {
|
||||
break;
|
||||
// complete the updating period
|
||||
let network = subject.QueryInterface(Ci.nsINetworkInterface);
|
||||
if (DEBUG) {
|
||||
debug("Network " + network.name + " of type " + network.type + " status change");
|
||||
}
|
||||
if (this._connectionTypes[network.type]) {
|
||||
this._connectionTypes[network.type].network = network;
|
||||
this.updateStats(network.type);
|
||||
}
|
||||
|
||||
this.updateStats(netId);
|
||||
break;
|
||||
case "xpcom-shutdown":
|
||||
debug("Service shutdown");
|
||||
if (DEBUG) {
|
||||
debug("Service shutdown");
|
||||
}
|
||||
|
||||
this.messages.forEach(function(aMsgName) {
|
||||
ppmm.removeMessageListener(aMsgName, this);
|
||||
this.messages.forEach(function(msgName) {
|
||||
ppmm.removeMessageListener(msgName, this);
|
||||
}, this);
|
||||
|
||||
Services.obs.removeObserver(this, "xpcom-shutdown");
|
||||
@ -186,53 +160,10 @@ this.NetworkStatsService = {
|
||||
* nsITimerCallback
|
||||
* Timer triggers the update of all stats
|
||||
*/
|
||||
notify: function(aTimer) {
|
||||
notify: function(timer) {
|
||||
this.updateAllStats();
|
||||
},
|
||||
|
||||
/*
|
||||
* nsINetworkStatsService
|
||||
*/
|
||||
|
||||
convertNetworkInterface: function(aNetwork) {
|
||||
if (aNetwork.type != NET_TYPE_MOBILE &&
|
||||
aNetwork.type != NET_TYPE_WIFI) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let id = '0';
|
||||
if (aNetwork.type == NET_TYPE_MOBILE) {
|
||||
// Bug 904542 will provide the serviceId to map the iccId with the
|
||||
// nsINetworkInterface of the NetworkManager. Now, lets assume that
|
||||
// network is mapped with the current iccId of the single SIM.
|
||||
id = gRadioInterface.rilContext.iccInfo.iccid;
|
||||
}
|
||||
|
||||
let netId = this.getNetworkId(id, aNetwork.type);
|
||||
|
||||
if (!this._networks[netId]) {
|
||||
this._networks[netId] = Object.create(null);
|
||||
this._networks[netId].network = { id: id,
|
||||
type: aNetwork.type };
|
||||
}
|
||||
|
||||
this._networks[netId].interfaceName = aNetwork.name;
|
||||
return netId;
|
||||
},
|
||||
|
||||
getNetworkId: function getNetworkId(aIccId, aNetworkType) {
|
||||
return aIccId + '' + aNetworkType;
|
||||
},
|
||||
|
||||
availableNetworks: function availableNetworks() {
|
||||
let result = [];
|
||||
for (let netId in this._networks) {
|
||||
result.push(this._networks[netId].network);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/*
|
||||
* Function called from manager to get stats from database.
|
||||
* In order to return updated stats, first is performed a call to
|
||||
@ -241,71 +172,69 @@ this.NetworkStatsService = {
|
||||
* Then, depending on the request (stats per appId or total stats)
|
||||
* it retrieve them from database and return to the manager.
|
||||
*/
|
||||
getSamples: function getSamples(mm, msg) {
|
||||
let self = this;
|
||||
let network = msg.network;
|
||||
let netId = this.getNetworkId(network.id, network.type);
|
||||
getStats: function getStats(mm, msg) {
|
||||
this.updateAllStats(function onStatsUpdated(aResult, aMessage) {
|
||||
|
||||
if (!this._networks[netId]) {
|
||||
mm.sendAsyncMessage("NetworkStats:Get:Return",
|
||||
{ id: msg.id, error: "Invalid connectionType", result: null });
|
||||
return;
|
||||
}
|
||||
let data = msg.data;
|
||||
|
||||
let appId = 0;
|
||||
let manifestURL = msg.manifestURL;
|
||||
if (manifestURL) {
|
||||
appId = appsService.getAppLocalIdByManifestURL(manifestURL);
|
||||
let options = { appId: 0,
|
||||
connectionType: data.connectionType,
|
||||
start: data.start,
|
||||
end: data.end };
|
||||
|
||||
if (!appId) {
|
||||
mm.sendAsyncMessage("NetworkStats:Get:Return",
|
||||
{ id: msg.id, error: "Invalid manifestURL", result: null });
|
||||
let manifestURL = data.manifestURL;
|
||||
if (manifestURL) {
|
||||
let appId = appsService.getAppLocalIdByManifestURL(manifestURL);
|
||||
if (DEBUG) {
|
||||
debug("get appId: " + appId + " from manifestURL: " + manifestURL);
|
||||
}
|
||||
|
||||
if (!appId) {
|
||||
mm.sendAsyncMessage("NetworkStats:Get:Return",
|
||||
{ id: msg.id, error: "Invalid manifestURL", result: null });
|
||||
return;
|
||||
}
|
||||
|
||||
options.appId = appId;
|
||||
options.manifestURL = manifestURL;
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
debug("getStats for options: " + JSON.stringify(options));
|
||||
}
|
||||
|
||||
if (!options.connectionType || options.connectionType.length == 0) {
|
||||
this._db.findAll(function onStatsFound(error, result) {
|
||||
mm.sendAsyncMessage("NetworkStats:Get:Return",
|
||||
{ id: msg.id, error: error, result: result });
|
||||
}, options);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let start = new Date(msg.start);
|
||||
let end = new Date(msg.end);
|
||||
for (let i in this._connectionTypes) {
|
||||
if (this._connectionTypes[i].name == options.connectionType) {
|
||||
this._db.find(function onStatsFound(error, result) {
|
||||
mm.sendAsyncMessage("NetworkStats:Get:Return",
|
||||
{ id: msg.id, error: error, result: result });
|
||||
}, options);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.updateStats(netId, function onStatsUpdated(aResult, aMessage) {
|
||||
debug("getstats for network " + network.id + " of type " + network.type);
|
||||
debug("appId: " + appId + " from manifestURL: " + manifestURL);
|
||||
mm.sendAsyncMessage("NetworkStats:Get:Return",
|
||||
{ id: msg.id, error: "Invalid connectionType", result: null });
|
||||
|
||||
self._db.find(function onStatsFound(aError, aResult) {
|
||||
mm.sendAsyncMessage("NetworkStats:Get:Return",
|
||||
{ id: msg.id, error: aError, result: aResult });
|
||||
}, network, start, end, appId, manifestURL);
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
clearInterfaceStats: function clearInterfaceStats(mm, msg) {
|
||||
let network = msg.network;
|
||||
let netId = this.getNetworkId(network.id, network.type);
|
||||
|
||||
debug("clear stats for network " + network.id + " of type " + network.type);
|
||||
|
||||
if (!this._networks[netId]) {
|
||||
mm.sendAsyncMessage("NetworkStats:Clear:Return",
|
||||
{ id: msg.id, error: "Invalid networkType", result: null });
|
||||
return;
|
||||
}
|
||||
|
||||
this._db.clearInterfaceStats(network, function onDBCleared(aError, aResult) {
|
||||
mm.sendAsyncMessage("NetworkStats:Clear:Return",
|
||||
{ id: msg.id, error: aError, result: aResult });
|
||||
});
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
clearDB: function clearDB(mm, msg) {
|
||||
let networks = this.availableNetworks();
|
||||
this._db.clearStats(networks, function onDBCleared(aError, aResult) {
|
||||
mm.sendAsyncMessage("NetworkStats:ClearAll:Return",
|
||||
{ id: msg.id, error: aError, result: aResult });
|
||||
this._db.clear(function onDBCleared(error, result) {
|
||||
mm.sendAsyncMessage("NetworkStats:Clear:Return",
|
||||
{ id: msg.id, error: error, result: result });
|
||||
});
|
||||
},
|
||||
|
||||
updateAllStats: function updateAllStats(aCallback) {
|
||||
updateAllStats: function updateAllStats(callback) {
|
||||
// Update |cachedAppStats|.
|
||||
this.updateCachedAppStats();
|
||||
|
||||
@ -318,19 +247,18 @@ this.NetworkStatsService = {
|
||||
// the connection type is already in the queue it is not appended again,
|
||||
// else it is pushed in 'elements' array, which later will be pushed to
|
||||
// the queue array.
|
||||
for (let netId in this._networks) {
|
||||
lastElement = { netId: netId,
|
||||
queueIndex: this.updateQueueIndex(netId)};
|
||||
|
||||
for (let i in this._connectionTypes) {
|
||||
lastElement = { type: i,
|
||||
queueIndex: this.updateQueueIndex(i)};
|
||||
if (lastElement.queueIndex == -1) {
|
||||
elements.push({netId: lastElement.netId, callbacks: []});
|
||||
elements.push({type: lastElement.type, callbacks: []});
|
||||
}
|
||||
}
|
||||
|
||||
if (elements.length > 0) {
|
||||
// If length of elements is greater than 0, callback is set to
|
||||
// the last element.
|
||||
elements[elements.length - 1].callbacks.push(aCallback);
|
||||
elements[elements.length - 1].callbacks.push(callback);
|
||||
this.updateQueue = this.updateQueue.concat(elements);
|
||||
} else {
|
||||
// Else, it means that all connection types are already in the queue to
|
||||
@ -338,14 +266,16 @@ this.NetworkStatsService = {
|
||||
// the element in the main queue with the index of the last 'lastElement'.
|
||||
// But before is checked that element is still in the queue because it can
|
||||
// be processed while generating 'elements' array.
|
||||
let element = this.updateQueue[lastElement.queueIndex];
|
||||
if (aCallback &&
|
||||
(!element || element.netId != lastElement.netId)) {
|
||||
aCallback();
|
||||
|
||||
if (!this.updateQueue[lastElement.queueIndex] ||
|
||||
this.updateQueue[lastElement.queueIndex].type != lastElement.queueIndex) {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateQueue[lastElement.queueIndex].callbacks.push(aCallback);
|
||||
this.updateQueue[lastElement.queueIndex].callbacks.push(callback);
|
||||
}
|
||||
|
||||
// Call the function that process the elements of the queue.
|
||||
@ -356,14 +286,14 @@ this.NetworkStatsService = {
|
||||
}
|
||||
},
|
||||
|
||||
updateStats: function updateStats(aNetId, aCallback) {
|
||||
// Check if the connection is in the main queue, push a new element
|
||||
updateStats: function updateStats(connectionType, callback) {
|
||||
// Check if the connection type is in the main queue, push a new element
|
||||
// if it is not being processed or add a callback if it is.
|
||||
let index = this.updateQueueIndex(aNetId);
|
||||
let index = this.updateQueueIndex(connectionType);
|
||||
if (index == -1) {
|
||||
this.updateQueue.push({netId: aNetId, callbacks: [aCallback]});
|
||||
this.updateQueue.push({type: connectionType, callbacks: [callback]});
|
||||
} else {
|
||||
this.updateQueue[index].callbacks.push(aCallback);
|
||||
this.updateQueue[index].callbacks.push(callback);
|
||||
}
|
||||
|
||||
// Call the function that process the elements of the queue.
|
||||
@ -371,11 +301,16 @@ this.NetworkStatsService = {
|
||||
},
|
||||
|
||||
/*
|
||||
* Find if a connection is in the main queue array and return its
|
||||
* Find if a connection type is in the main queue array and return its
|
||||
* index, if it is not in the array return -1.
|
||||
*/
|
||||
updateQueueIndex: function updateQueueIndex(aNetId) {
|
||||
return this.updateQueue.map(function(e) { return e.netId; }).indexOf(aNetId);
|
||||
updateQueueIndex: function updateQueueIndex(type) {
|
||||
for (let i in this.updateQueue) {
|
||||
if (this.updateQueue[i].type == type) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
},
|
||||
|
||||
/*
|
||||
@ -412,64 +347,64 @@ this.NetworkStatsService = {
|
||||
}
|
||||
|
||||
// Call the update function for the next element.
|
||||
this.update(this.updateQueue[0].netId, this.processQueue.bind(this));
|
||||
this.update(this.updateQueue[0].type, this.processQueue.bind(this));
|
||||
},
|
||||
|
||||
update: function update(aNetId, aCallback) {
|
||||
update: function update(connectionType, callback) {
|
||||
// Check if connection type is valid.
|
||||
if (!this._networks[aNetId]) {
|
||||
if (aCallback) {
|
||||
aCallback(false, "Invalid network " + aNetId);
|
||||
if (!this._connectionTypes[connectionType]) {
|
||||
if (callback) {
|
||||
callback(false, "Invalid network type " + connectionType);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let interfaceName = this._networks[aNetId].interfaceName;
|
||||
debug("Update stats for " + interfaceName);
|
||||
if (DEBUG) {
|
||||
debug("Update stats for " + this._connectionTypes[connectionType].name);
|
||||
}
|
||||
|
||||
// Request stats to NetworkManager, which will get stats from netd, passing
|
||||
// 'networkStatsAvailable' as a callback.
|
||||
if (interfaceName) {
|
||||
networkManager.getNetworkInterfaceStats(interfaceName,
|
||||
this.networkStatsAvailable.bind(this, aCallback, aNetId));
|
||||
let networkName = this._connectionTypes[connectionType].network.name;
|
||||
if (networkName) {
|
||||
networkManager.getNetworkInterfaceStats(networkName,
|
||||
this.networkStatsAvailable.bind(this, callback, connectionType));
|
||||
return;
|
||||
}
|
||||
|
||||
if (aCallback) {
|
||||
aCallback(true, "ok");
|
||||
if (callback) {
|
||||
callback(true, "ok");
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Callback of request stats. Store stats in database.
|
||||
*/
|
||||
networkStatsAvailable: function networkStatsAvailable(aCallback, aNetId,
|
||||
aResult, aRxBytes,
|
||||
aTxBytes, aDate) {
|
||||
if (!aResult) {
|
||||
if (aCallback) {
|
||||
aCallback(false, "Netd IPC error");
|
||||
networkStatsAvailable: function networkStatsAvailable(callback, connType, result, rxBytes, txBytes, date) {
|
||||
if (!result) {
|
||||
if (callback) {
|
||||
callback(false, "Netd IPC error");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let stats = { appId: 0,
|
||||
networkId: this._networks[aNetId].network.id,
|
||||
networkType: this._networks[aNetId].network.type,
|
||||
date: aDate,
|
||||
rxBytes: aTxBytes,
|
||||
txBytes: aRxBytes };
|
||||
let stats = { appId: 0,
|
||||
connectionType: this._connectionTypes[connType].name,
|
||||
date: date,
|
||||
rxBytes: rxBytes,
|
||||
txBytes: txBytes };
|
||||
|
||||
debug("Update stats for: " + JSON.stringify(stats));
|
||||
|
||||
this._db.saveStats(stats, function onSavedStats(aError, aResult) {
|
||||
if (aCallback) {
|
||||
if (aError) {
|
||||
aCallback(false, aError);
|
||||
if (DEBUG) {
|
||||
debug("Update stats for " + stats.connectionType + ": rx=" + stats.rxBytes +
|
||||
" tx=" + stats.txBytes + " timestamp=" + stats.date);
|
||||
}
|
||||
this._db.saveStats(stats, function onSavedStats(error, result) {
|
||||
if (callback) {
|
||||
if (error) {
|
||||
callback(false, error);
|
||||
return;
|
||||
}
|
||||
|
||||
aCallback(true, "OK");
|
||||
callback(true, "OK");
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -477,34 +412,26 @@ this.NetworkStatsService = {
|
||||
/*
|
||||
* Function responsible for receiving per-app stats.
|
||||
*/
|
||||
saveAppStats: function saveAppStats(aAppId, aNetwork, aTimeStamp, aRxBytes, aTxBytes, aCallback) {
|
||||
let netId = this.convertNetworkInterface(aNetwork);
|
||||
if (!netId) {
|
||||
if (aCallback) {
|
||||
aCallback.notify(false, "Invalid network type");
|
||||
}
|
||||
return;
|
||||
saveAppStats: function saveAppStats(aAppId, aConnectionType, aTimeStamp, aRxBytes, aTxBytes, aCallback) {
|
||||
if (DEBUG) {
|
||||
debug("saveAppStats: " + aAppId + " " + aConnectionType + " " +
|
||||
aTimeStamp + " " + aRxBytes + " " + aTxBytes);
|
||||
}
|
||||
|
||||
debug("saveAppStats: " + aAppId + " " + netId + " " +
|
||||
aTimeStamp + " " + aRxBytes + " " + aTxBytes);
|
||||
|
||||
// Check if |aAppId| and |aConnectionType| are valid.
|
||||
if (!aAppId || !this._networks[netId]) {
|
||||
debug("Invalid appId or network interface");
|
||||
if (!aAppId || aConnectionType == NET_TYPE_UNKNOWN) {
|
||||
return;
|
||||
}
|
||||
|
||||
let stats = { appId: aAppId,
|
||||
networkId: this._networks[netId].network.id,
|
||||
networkType: this._networks[netId].network.type,
|
||||
connectionType: this._connectionTypes[aConnectionType].name,
|
||||
date: new Date(aTimeStamp),
|
||||
rxBytes: aRxBytes,
|
||||
txBytes: aTxBytes };
|
||||
|
||||
// Generate an unique key from |appId| and |connectionType|,
|
||||
// which is used to retrieve data in |cachedAppStats|.
|
||||
let key = stats.appId + "" + netId;
|
||||
let key = stats.appId + stats.connectionType;
|
||||
|
||||
// |cachedAppStats| only keeps the data with the same date.
|
||||
// If the incoming date is different from |cachedAppStatsDate|,
|
||||
@ -551,15 +478,19 @@ this.NetworkStatsService = {
|
||||
appStats.txBytes > MAX_CACHED_TRAFFIC) {
|
||||
this._db.saveStats(appStats,
|
||||
function (error, result) {
|
||||
debug("Application stats inserted in indexedDB");
|
||||
if (DEBUG) {
|
||||
debug("Application stats inserted in indexedDB");
|
||||
}
|
||||
}
|
||||
);
|
||||
delete this.cachedAppStats[key];
|
||||
}
|
||||
},
|
||||
|
||||
updateCachedAppStats: function updateCachedAppStats(aCallback) {
|
||||
debug("updateCachedAppStats: " + this.cachedAppStatsDate);
|
||||
updateCachedAppStats: function updateCachedAppStats(callback) {
|
||||
if (DEBUG) {
|
||||
debug("updateCachedAppStats: " + this.cachedAppStatsDate);
|
||||
}
|
||||
|
||||
let stats = Object.keys(this.cachedAppStats);
|
||||
if (stats.length == 0) {
|
||||
@ -578,16 +509,16 @@ this.NetworkStatsService = {
|
||||
if (index == stats.length - 1) {
|
||||
this.cachedAppStats = Object.create(null);
|
||||
|
||||
if (!aCallback) {
|
||||
if (!callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
aCallback(false, error);
|
||||
callback(false, error);
|
||||
return;
|
||||
}
|
||||
|
||||
aCallback(true, "ok");
|
||||
callback(true, "ok");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -603,17 +534,17 @@ this.NetworkStatsService = {
|
||||
},
|
||||
|
||||
logAllRecords: function logAllRecords() {
|
||||
this._db.logAllRecords(function onResult(aError, aResult) {
|
||||
if (aError) {
|
||||
debug("Error: " + aError);
|
||||
this._db.logAllRecords(function onResult(error, result) {
|
||||
if (error) {
|
||||
debug("Error: " + error);
|
||||
return;
|
||||
}
|
||||
|
||||
debug("===== LOG =====");
|
||||
debug("There are " + aResult.length + " items");
|
||||
debug(JSON.stringify(aResult));
|
||||
debug("There are " + result.length + " items");
|
||||
debug(JSON.stringify(result));
|
||||
});
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
NetworkStatsService.init();
|
||||
|
@ -29,15 +29,15 @@ NetworkStatsServiceProxy.prototype = {
|
||||
* Function called in the protocol layer (HTTP, FTP, WebSocket ...etc)
|
||||
* to pass the per-app stats to NetworkStatsService.
|
||||
*/
|
||||
saveAppStats: function saveAppStats(aAppId, aNetwork, aTimeStamp,
|
||||
saveAppStats: function saveAppStats(aAppId, aConnectionType, aTimeStamp,
|
||||
aRxBytes, aTxBytes, aCallback) {
|
||||
if (DEBUG) {
|
||||
debug("saveAppStats: " + aAppId + " connectionType " + aNetwork.type +
|
||||
" " + aTimeStamp + " " + aRxBytes + " " + aTxBytes);
|
||||
debug("saveAppStats: " + aAppId + " " + aConnectionType + " " +
|
||||
aTimeStamp + " " + aRxBytes + " " + aTxBytes);
|
||||
}
|
||||
|
||||
NetworkStatsService.saveAppStats(aAppId, aNetwork, aTimeStamp,
|
||||
aRxBytes, aTxBytes, aCallback);
|
||||
NetworkStatsService.saveAppStats(aAppId, aConnectionType, aTimeStamp,
|
||||
aRxBytes, aTxBytes, aCallback);
|
||||
},
|
||||
|
||||
classID : NETWORKSTATSSERVICEPROXY_CID,
|
||||
|
@ -167,7 +167,7 @@ TCPSocket.prototype = {
|
||||
_txBytes: 0,
|
||||
_rxBytes: 0,
|
||||
_appId: Ci.nsIScriptSecurityManager.NO_APP_ID,
|
||||
_activeNetwork: null,
|
||||
_connectionType: Ci.nsINetworkInterface.NETWORK_TYPE_UNKNOWN,
|
||||
#endif
|
||||
|
||||
// Public accessors.
|
||||
@ -347,7 +347,7 @@ TCPSocket.prototype = {
|
||||
LOG("Error: Ci.nsINetworkStatsServiceProxy service is not available.");
|
||||
return;
|
||||
}
|
||||
nssProxy.saveAppStats(this._appId, this._activeNetwork, Date.now(),
|
||||
nssProxy.saveAppStats(this._appId, this._connectionType, Date.now(),
|
||||
this._rxBytes, this._txBytes);
|
||||
|
||||
// Reset the counters once the statistics is saved to NetworkStatsServiceProxy.
|
||||
@ -530,12 +530,12 @@ TCPSocket.prototype = {
|
||||
that._initStream(that._binaryType);
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
// Set _activeNetwork, which is only required for network statistics.
|
||||
// Set _connectionType, which is only required for network statistics.
|
||||
// Note that nsINetworkManager, as well as nsINetworkStatsServiceProxy, is
|
||||
// Gonk-specific.
|
||||
let networkManager = Cc["@mozilla.org/network/manager;1"].getService(Ci.nsINetworkManager);
|
||||
if (networkManager) {
|
||||
that._activeNetwork = networkManager.active;
|
||||
if (networkManager && networkManager.active) {
|
||||
that._connectionType = networkManager.active.type;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -12,34 +12,45 @@
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
// Test for NetworkStats
|
||||
function checkInterface(aInterface) {
|
||||
ok(!(aInterface in window), aInterface + " should be prefixed");
|
||||
ok(("Moz" + aInterface) in window, aInterface + " should be prefixed");
|
||||
}
|
||||
|
||||
function test() {
|
||||
// Test interfaces
|
||||
checkInterface("NetworkStatsManager");
|
||||
checkInterface("NetworkStats");
|
||||
checkInterface("NetworkStatsData");
|
||||
|
||||
ok('mozNetworkStats' in navigator, "navigator.mozMozNetworkStats should exist");
|
||||
ok(navigator.mozNetworkStats, "navigator.mozNetworkStats returns an object");
|
||||
|
||||
netStats = navigator.mozNetworkStats;
|
||||
|
||||
// Test IDL attributes
|
||||
ok('availableNetworks' in netStats,
|
||||
"availableNetworks should be a NetworkStats attribute");
|
||||
ok(Array.isArray(netStats.availableNetworks) && netStats.availableNetworks.length > 0,
|
||||
"availableNetworks is an array not empty.");
|
||||
ok('connectionTypes' in netStats,
|
||||
"connectionTypes should be a NetworkStats attribute");
|
||||
ok(Array.isArray(netStats.connectionTypes) && netStats.connectionTypes.length > 0,
|
||||
"connectionTypes is an array not empty.");
|
||||
|
||||
ok('sampleRate' in netStats,
|
||||
"sampleRate should be a NetworkStats attribute");
|
||||
ok(netStats.sampleRate > 0,
|
||||
"sampleRate is greater than 0.");
|
||||
|
||||
ok('maxStorageAge' in netStats,
|
||||
"maxStorageAge should be a NetworkStats attribute");
|
||||
ok(netStats.maxStorageAge > 0,
|
||||
"maxStorageAge is greater than 0.");
|
||||
ok('maxStorageSamples' in netStats,
|
||||
"maxStorageSamples should be a NetworkStats attribute");
|
||||
ok(netStats.maxStorageSamples > 0,
|
||||
"maxStorageSamples is greater than 0.");
|
||||
|
||||
// Test IDL methods
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
function checkDataDates(data, start, end, sampleRate) {
|
||||
function checkDataDates(data, start, end, sampleRate){
|
||||
var offset = (new Date()).getTimezoneOffset() * 60 * 1000;
|
||||
start = Math.floor((start.getTime() - offset) / sampleRate) * sampleRate + offset;
|
||||
end = Math.floor((end.getTime() - offset) / sampleRate) * sampleRate + offset;
|
||||
@ -60,114 +71,84 @@ function checkDataDates(data, start, end, sampleRate) {
|
||||
ok(success, "data result has correct dates");
|
||||
}
|
||||
|
||||
function compareNetworks(networkA, networkB) {
|
||||
return (networkA.id == networkB.id &&
|
||||
networkA.type == networkB.type);
|
||||
}
|
||||
|
||||
var req;
|
||||
var index = -1;
|
||||
var netStats = null;
|
||||
|
||||
var steps = [
|
||||
function () {
|
||||
// Test clearAllStats
|
||||
req = netStats.clearAllStats();
|
||||
// Test clearAlldata
|
||||
req = netStats.clearAllData();
|
||||
req.onsuccess = function () {
|
||||
ok(true, "clearAllStats deleted the database");
|
||||
ok(true, "clearAllData deleted the database");
|
||||
next();
|
||||
};
|
||||
req.onerror = function () {
|
||||
ok(false, "clearAllStats deleted the database");
|
||||
ok(false, "clearAllData deleted the database");
|
||||
}
|
||||
},
|
||||
function () {
|
||||
// Check if getSamples launch exception when start is greather than end
|
||||
// Check if getNetworkStats launch exception when start is greather than end
|
||||
|
||||
// Prepare get params
|
||||
var network = netStats.availableNetworks[0];
|
||||
var type = netStats.connectionTypes[0];
|
||||
// Get dates
|
||||
var endDate = new Date();
|
||||
var startDate = new Date(endDate.getTime() + 1000);
|
||||
|
||||
try {
|
||||
netStats.getSamples(network, startDate, endDate);
|
||||
netStats.getNetworkStats({start: startDate, end: endDate});
|
||||
} catch(ex) {
|
||||
ok(true, "getSamples launch exception when start is greater than end");
|
||||
ok(true, "getNetworkStats launch exception when start is greater than end");
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
ok(false, "getSamples launch exception when start is greater than end");
|
||||
ok(false, "getNetworkStats launch exceptionwhen start is greater than end");
|
||||
next();
|
||||
return;
|
||||
},
|
||||
function () {
|
||||
// Test if call getSamples with network of type different than
|
||||
// nsIDOMMozNetworkStatsInterface launch an exception
|
||||
// Test if call getNetworkStats with undefined start param launch an exception
|
||||
|
||||
// Prepare get params
|
||||
var network = "wifi";
|
||||
var endDate = new Date();
|
||||
var startDate = new Date(endDate.getTime() - 1000);
|
||||
var type = netStats.connectionTypes[0];
|
||||
setTimeout(function() {
|
||||
try {
|
||||
netStats.getNetworkStats({end: new Date()});
|
||||
} catch(ex) {
|
||||
ok(true, "getNetworkStats launch exception when start param does not exist");
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
netStats.getSamples(network, new Date(), new Date());
|
||||
} catch(ex) {
|
||||
ok(true, "getSamples launch exception if network is not " +
|
||||
"a nsIDOMMozNetworkStatsInterface");
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
ok(false, "getSamples launch exception if network is not " +
|
||||
"a nsIDOMMozNetworkStatsInterface");
|
||||
ok(false, "getNetworkStats launch exception when start param does not exist");
|
||||
}, 1000);
|
||||
},
|
||||
function () {
|
||||
// Test if call getSamples with start parameter type different than Date launch an exception
|
||||
// Test if call getNetworkStats with undefined end param launch an exception
|
||||
|
||||
// Prepare get params
|
||||
var network = netStats.availableNetworks[0];
|
||||
var endDate = new Date();
|
||||
var startDate = new Date(endDate.getTime() - 1000);
|
||||
startDate = startDate.toString();
|
||||
var type = netStats.connectionTypes[0];
|
||||
setTimeout(function() {
|
||||
try {
|
||||
netStats.getNetworkStats({start: new Date()});
|
||||
} catch(ex) {
|
||||
ok(true, "getNetworkStats launch exception when end param does not exist");
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
netStats.getSamples(network, startDate, endDate);
|
||||
} catch(ex) {
|
||||
ok(true, "getSamples launch exception when start param is not a Date");
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
ok(false, "getSamples launch exception when start param is not a Date");
|
||||
ok(false, "getNetworkStats launch exception when end param does not exist");
|
||||
}, 1000);
|
||||
},
|
||||
function () {
|
||||
// Test if call getSamples with end parameter type different than Date launch an exception
|
||||
|
||||
ok(true, "Get system stats for a connectionType and dates adapted to samplerate");
|
||||
// Prepare get params
|
||||
var network = netStats.availableNetworks[0];
|
||||
var endDate = new Date();
|
||||
var startDate = new Date(endDate.getTime() - 1000);
|
||||
endDate = startDate.toString();
|
||||
|
||||
try {
|
||||
netStats.getSamples(network, startDate, endDate);
|
||||
} catch(ex) {
|
||||
ok(true, "getSamples launch exception when end param is not a Date");
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
ok(false, "getSamples launch exception when end param is not a Date");
|
||||
},
|
||||
function () {
|
||||
ok(true, "Get stats for a network and dates adapted to samplerate");
|
||||
// Prepare get params
|
||||
var network = netStats.availableNetworks[0];
|
||||
var type = netStats.connectionTypes[0];
|
||||
var diff = 2;
|
||||
// Get samplerate in millis
|
||||
var sampleRate = netStats.sampleRate;
|
||||
var sampleRate = netStats.sampleRate * 1000;
|
||||
// Get date with samplerate's precision
|
||||
var offset = new Date().getTimezoneOffset() * 60 * 1000;
|
||||
var endDate = new Date(Math.floor((new Date().getTime() - offset) / sampleRate)
|
||||
@ -178,11 +159,11 @@ var steps = [
|
||||
var samples = (endDate.getTime() - startDate.getTime()) / sampleRate + 1;
|
||||
|
||||
// Launch request
|
||||
req = netStats.getSamples(network, startDate, endDate);
|
||||
req = netStats.getNetworkStats({start: startDate, end: endDate, connectionType: type});
|
||||
req.onsuccess = function () {
|
||||
ok(true, "Get system stats request ok");
|
||||
ok(req.result.manifestURL == null, "manifestURL should be null");
|
||||
ok(compareNetworks(req.result.network, network), "networks should be equals");
|
||||
ok(req.result.connectionType == type, "connectionTypes should be equals");
|
||||
ok(req.result.start.getTime() == startDate.getTime(), "starts should be equals");
|
||||
ok(req.result.end.getTime() == endDate.getTime(), "ends should be equals");
|
||||
var data = req.result.data;
|
||||
@ -192,16 +173,123 @@ var steps = [
|
||||
next();
|
||||
};
|
||||
req.onerror = function () {
|
||||
ok(false, "Get stats failure!");
|
||||
ok(false, "Get system stats for a connectionType failure!");
|
||||
}
|
||||
},
|
||||
function () {
|
||||
ok(true, "Get system stats for a network and dates not adapted to samplerate");
|
||||
ok(true, "Get system stats for all connectionTypes and dates adapted to samplerate");
|
||||
// Prepare get params
|
||||
var network = netStats.availableNetworks[0];
|
||||
var diff = 2;
|
||||
// Get samplerate in millis
|
||||
var sampleRate = netStats.sampleRate;
|
||||
var sampleRate = netStats.sampleRate * 1000;
|
||||
// Get date with samplerate's precision
|
||||
var offset = new Date().getTimezoneOffset() * 60 * 1000;
|
||||
var endDate = new Date(Math.floor((new Date().getTime() - offset) / sampleRate)
|
||||
* sampleRate + offset);
|
||||
var startDate = new Date(endDate.getTime() - (sampleRate * diff));
|
||||
// Calculate the number of samples that should be returned based on the
|
||||
// the samplerate and including final and initial samples.
|
||||
var samples = (endDate.getTime() - startDate.getTime()) / sampleRate + 1;
|
||||
|
||||
// Launch request
|
||||
req = netStats.getNetworkStats({start: startDate, end: endDate});
|
||||
req.onsuccess = function () {
|
||||
ok(true, "Get stats request ok");
|
||||
ok(req.result.manifestURL == null, "manifestURL should be null");
|
||||
ok(req.result.connectionType == null, "connectionTypes should be null");
|
||||
ok(req.result.start.getTime() == startDate.getTime(), "starts should be equals");
|
||||
ok(req.result.end.getTime() == endDate.getTime(), "ends should be equals");
|
||||
var data = req.result.data;
|
||||
ok(Array.isArray(data) && data.length == samples,
|
||||
"data is an array of length " + samples);
|
||||
checkDataDates(data, startDate, endDate, sampleRate);
|
||||
next();
|
||||
};
|
||||
req.onerror = function () {
|
||||
ok(false, "Get system stats for all connectionTypes failure!");
|
||||
}
|
||||
},
|
||||
function () {
|
||||
ok(true, "Get app stats for a connectionType and dates adapted to samplerate");
|
||||
// Prepare get params
|
||||
var url = 'app://browser.gaiamobile.org/manifest.webapp';
|
||||
var type = netStats.connectionTypes[0];
|
||||
var diff = 2;
|
||||
// Get samplerate in millis
|
||||
var sampleRate = netStats.sampleRate * 1000;
|
||||
// Get date with samplerate's precision
|
||||
var offset = new Date().getTimezoneOffset() * 60 * 1000;
|
||||
var endDate = new Date(Math.floor((new Date().getTime() - offset) / sampleRate)
|
||||
* sampleRate + offset);
|
||||
var startDate = new Date(endDate.getTime() - (sampleRate * diff));
|
||||
// Calculate the number of samples that should be returned based on the
|
||||
// the samplerate and including final and initial samples.
|
||||
var samples = (endDate.getTime() - startDate.getTime()) / sampleRate + 1;
|
||||
|
||||
// Launch request
|
||||
req = netStats.getNetworkStats({start: startDate,
|
||||
end: endDate,
|
||||
connectionType: type,
|
||||
manifestURL: url});
|
||||
req.onsuccess = function () {
|
||||
ok(true, "Get app stats request ok");
|
||||
ok(req.result.manifestURL == url, "manifestURL should be equals");
|
||||
ok(req.result.connectionType == type, "connectionTypes should be equals");
|
||||
ok(req.result.start.getTime() == startDate.getTime(), "starts should be equals");
|
||||
ok(req.result.end.getTime() == endDate.getTime(), "ends should be equals");
|
||||
var data = req.result.data;
|
||||
ok(Array.isArray(data) && data.length == samples,
|
||||
"data is an array of length " + samples);
|
||||
checkDataDates(data, startDate, endDate, sampleRate);
|
||||
next();
|
||||
};
|
||||
req.onerror = function () {
|
||||
ok(false, "Get app stats for a connectionType failure!");
|
||||
}
|
||||
},
|
||||
function () {
|
||||
ok(true, "Get app stats for all connectionTypes and dates adapted to samplerate");
|
||||
// Prepare get params
|
||||
var url = 'app://browser.gaiamobile.org/manifest.webapp';
|
||||
var diff = 2;
|
||||
// Get samplerate in millis
|
||||
var sampleRate = netStats.sampleRate * 1000;
|
||||
// Get date with samplerate's precision
|
||||
var offset = new Date().getTimezoneOffset() * 60 * 1000;
|
||||
var endDate = new Date(Math.floor((new Date().getTime() - offset) / sampleRate)
|
||||
* sampleRate + offset);
|
||||
var startDate = new Date(endDate.getTime() - (sampleRate * diff));
|
||||
// Calculate the number of samples that should be returned based on the
|
||||
// the samplerate and including final and initial samples.
|
||||
var samples = (endDate.getTime() - startDate.getTime()) / sampleRate + 1;
|
||||
|
||||
// Launch request
|
||||
req = netStats.getNetworkStats({start: startDate,
|
||||
end: endDate,
|
||||
manifestURL: url});
|
||||
req.onsuccess = function () {
|
||||
ok(true, "Get app stats request ok");
|
||||
ok(req.result.manifestURL == url, "manifestURL should be equals");
|
||||
ok(req.result.connectionType == null, "connectionTypes should be null");
|
||||
ok(req.result.start.getTime() == startDate.getTime(), "starts should be equals");
|
||||
ok(req.result.end.getTime() == endDate.getTime(), "ends should be equals");
|
||||
var data = req.result.data;
|
||||
ok(Array.isArray(data) && data.length == samples,
|
||||
"data is an array of length " + samples);
|
||||
checkDataDates(data, startDate, endDate, sampleRate);
|
||||
next();
|
||||
};
|
||||
req.onerror = function () {
|
||||
ok(false, "Get app stats for all connectionTypes failure!");
|
||||
}
|
||||
},
|
||||
function () {
|
||||
ok(true, "Get system stats for a connectionType and dates not adapted to samplerate");
|
||||
// Prepare get params
|
||||
var type = netStats.connectionTypes[0];
|
||||
var diff = 2;
|
||||
// Get samplerate in millis
|
||||
var sampleRate = netStats.sampleRate * 1000;
|
||||
var endDate = new Date();
|
||||
var startDate = new Date(endDate.getTime() - (sampleRate * diff));
|
||||
// Calculate the number of samples that should be returned based on the
|
||||
@ -211,11 +299,11 @@ var steps = [
|
||||
Math.floor(startDate.getTime() / (sampleRate)) * sampleRate) / sampleRate + 1;
|
||||
|
||||
// Launch request
|
||||
req = netStats.getSamples(network, startDate, endDate);
|
||||
req = netStats.getNetworkStats({start: startDate, end: endDate, connectionType: type});
|
||||
req.onsuccess = function () {
|
||||
ok(true, "Get stats request ok");
|
||||
ok(true, "Get system stats request ok");
|
||||
ok(req.result.manifestURL == null, "manifestURL should be null");
|
||||
ok(compareNetworks(req.result.network, network), "networks should be equals");
|
||||
ok(req.result.connectionType == type, "connectionTypes should be equals");
|
||||
ok(req.result.start.getTime() == startDate.getTime(), "starts should be equals");
|
||||
ok(req.result.end.getTime() == endDate.getTime(), "ends should be equals");
|
||||
var data = req.result.data;
|
||||
@ -225,20 +313,40 @@ var steps = [
|
||||
next();
|
||||
};
|
||||
req.onerror = function () {
|
||||
ok(false, "Get stats failure!");
|
||||
ok(false, "Get system stats for a connectionType failure!");
|
||||
}
|
||||
},
|
||||
function () {
|
||||
// Test clearStats
|
||||
var network = netStats.availableNetworks[0];
|
||||
ok(true, "Get system stats for all connectionTypes and dates not adapted to samplerate");
|
||||
// Prepare get params
|
||||
var diff = 2;
|
||||
// Get samplerate in millis
|
||||
var sampleRate = netStats.sampleRate * 1000;
|
||||
// Get date with samplerate's precision
|
||||
var endDate = new Date();
|
||||
var startDate = new Date(endDate.getTime() - (sampleRate * diff));
|
||||
// Calculate the number of samples that should be returned based on the
|
||||
// the samplerate, including final and initial samples and taking into
|
||||
// account that these will be filtered according to precision.
|
||||
var samples = (Math.floor(endDate.getTime() / (sampleRate)) * sampleRate -
|
||||
Math.floor(startDate.getTime() / (sampleRate)) * sampleRate) / sampleRate + 1;
|
||||
|
||||
req = netStats.clearStats(network);
|
||||
// Launch request
|
||||
req = netStats.getNetworkStats({start: startDate, end: endDate});
|
||||
req.onsuccess = function () {
|
||||
ok(true, "clearStats deleted the database");
|
||||
ok(true, "Get stats request ok");
|
||||
ok(req.result.manifestURL == null, "manifestURL should be null");
|
||||
ok(req.result.connectionType == null, "connectionTypes should be null");
|
||||
ok(req.result.start.getTime() == startDate.getTime(), "starts should be equals");
|
||||
ok(req.result.end.getTime() == endDate.getTime(), "ends should be equals");
|
||||
var data = req.result.data;
|
||||
ok(Array.isArray(data) && data.length == samples,
|
||||
"data is an array of length " + samples);
|
||||
checkDataDates(data, startDate, endDate, sampleRate);
|
||||
next();
|
||||
};
|
||||
req.onerror = function () {
|
||||
ok(false, "clearStats deleted the database");
|
||||
ok(false, "Get system stats for all connectionType failure!");
|
||||
}
|
||||
},
|
||||
function () {
|
||||
|
@ -12,7 +12,7 @@
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
// Test to ensure NetworkStats is enabled but mozNetworkStats.availableNetworks
|
||||
// Test to ensure NetworkStats is enabled but mozNetworkStats.connectionTypes
|
||||
// does not work in content.
|
||||
|
||||
SpecialPowers.setBoolPref("dom.mozNetworkStats.enabled", true);
|
||||
@ -22,12 +22,12 @@ ok('mozNetworkStats' in navigator, "navigator.mozNetworkStats should be accessib
|
||||
|
||||
var error;
|
||||
try {
|
||||
navigator.mozNetworkStats.availableNetworks;
|
||||
ok(false, "Accessing navigator.mozNetworkStats.availableNetworks should have thrown!");
|
||||
navigator.mozNetworkStats.connectionTypes;
|
||||
ok(false, "Accessing navigator.mozNetworkStats.connectionTypes should have thrown!");
|
||||
} catch (ex) {
|
||||
error = ex;
|
||||
}
|
||||
ok(error, "Got an exception accessing navigator.mozNetworkStats.availableNetworks");
|
||||
ok(error, "Got an exception accessing navigator.mozNetworkStats.connectionTypes");
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
|
@ -5,7 +5,7 @@ const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/NetworkStatsDB.jsm");
|
||||
|
||||
const netStatsDb = new NetworkStatsDB();
|
||||
const netStatsDb = new NetworkStatsDB(this);
|
||||
|
||||
function filterTimestamp(date) {
|
||||
var sampleRate = netStatsDb.sampleRate;
|
||||
@ -13,15 +13,6 @@ function filterTimestamp(date) {
|
||||
return Math.floor((date.getTime() - offset) / sampleRate) * sampleRate;
|
||||
}
|
||||
|
||||
function getNetworks() {
|
||||
return [{ id: '0', type: Ci.nsIDOMMozNetworkStatsManager.WIFI },
|
||||
{ id: '1234', type: Ci.nsIDOMMozNetworkStatsManager.MOBILE }];
|
||||
}
|
||||
|
||||
function compareNetworks(networkA, networkB) {
|
||||
return (networkA[0] == networkB[0] && networkA[1] == networkB[1]);
|
||||
}
|
||||
|
||||
add_test(function test_sampleRate() {
|
||||
var sampleRate = netStatsDb.sampleRate;
|
||||
do_check_true(sampleRate > 0);
|
||||
@ -98,31 +89,20 @@ add_test(function test_fillResultSamples_noEmptyData() {
|
||||
});
|
||||
|
||||
add_test(function test_clear() {
|
||||
var networks = getNetworks();
|
||||
netStatsDb.clearStats(networks, function (error, result) {
|
||||
do_check_eq(error, null);
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_clear_interface() {
|
||||
var networks = getNetworks();
|
||||
netStatsDb.clearInterfaceStats(networks[0], function (error, result) {
|
||||
netStatsDb.clear(function (error, result) {
|
||||
do_check_eq(error, null);
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_internalSaveStats_singleSample() {
|
||||
var networks = getNetworks();
|
||||
|
||||
var stats = { appId: 0,
|
||||
network: [networks[0].id, networks[0].type],
|
||||
timestamp: Date.now(),
|
||||
rxBytes: 0,
|
||||
txBytes: 0,
|
||||
rxTotalBytes: 1234,
|
||||
txTotalBytes: 1234 };
|
||||
var stats = {appId: 0,
|
||||
connectionType: "wifi",
|
||||
timestamp: Date.now(),
|
||||
rxBytes: 0,
|
||||
txBytes: 0,
|
||||
rxTotalBytes: 1234,
|
||||
txTotalBytes: 1234};
|
||||
|
||||
netStatsDb.dbNewTxn("readwrite", function(txn, store) {
|
||||
netStatsDb._saveStats(txn, store, stats);
|
||||
@ -133,7 +113,7 @@ add_test(function test_internalSaveStats_singleSample() {
|
||||
do_check_eq(error, null);
|
||||
do_check_eq(result.length, 1);
|
||||
do_check_eq(result[0].appId, stats.appId);
|
||||
do_check_true(compareNetworks(result[0].network, stats.network));
|
||||
do_check_eq(result[0].connectionType, stats.connectionType);
|
||||
do_check_eq(result[0].timestamp, stats.timestamp);
|
||||
do_check_eq(result[0].rxBytes, stats.rxBytes);
|
||||
do_check_eq(result[0].txBytes, stats.txBytes);
|
||||
@ -145,23 +125,19 @@ add_test(function test_internalSaveStats_singleSample() {
|
||||
});
|
||||
|
||||
add_test(function test_internalSaveStats_arraySamples() {
|
||||
var networks = getNetworks();
|
||||
|
||||
netStatsDb.clearStats(networks, function (error, result) {
|
||||
netStatsDb.clear(function (error, result) {
|
||||
do_check_eq(error, null);
|
||||
|
||||
var network = [networks[0].id, networks[0].type];
|
||||
|
||||
var samples = 2;
|
||||
var stats = [];
|
||||
for (var i = 0; i < samples; i++) {
|
||||
stats.push({ appId: 0,
|
||||
network: network,
|
||||
timestamp: Date.now() + (10 * i),
|
||||
rxBytes: 0,
|
||||
txBytes: 0,
|
||||
rxTotalBytes: 1234,
|
||||
txTotalBytes: 1234 });
|
||||
stats.push({appId: 0,
|
||||
connectionType: "wifi",
|
||||
timestamp: Date.now() + (10 * i),
|
||||
rxBytes: 0,
|
||||
txBytes: 0,
|
||||
rxTotalBytes: 1234,
|
||||
txTotalBytes: 1234});
|
||||
}
|
||||
|
||||
netStatsDb.dbNewTxn("readwrite", function(txn, store) {
|
||||
@ -171,16 +147,12 @@ add_test(function test_internalSaveStats_arraySamples() {
|
||||
|
||||
netStatsDb.logAllRecords(function(error, result) {
|
||||
do_check_eq(error, null);
|
||||
|
||||
// Result has one sample more than samples because clear inserts
|
||||
// an empty sample to keep totalBytes synchronized with netd counters
|
||||
result.shift();
|
||||
do_check_eq(result.length, samples);
|
||||
|
||||
var success = true;
|
||||
for (var i = 1; i < samples; i++) {
|
||||
for (var i = 0; i < samples; i++) {
|
||||
if (result[i].appId != stats[i].appId ||
|
||||
!compareNetworks(result[i].network, stats[i].network) ||
|
||||
result[i].connectionType != stats[i].connectionType ||
|
||||
result[i].timestamp != stats[i].timestamp ||
|
||||
result[i].rxBytes != stats[i].rxBytes ||
|
||||
result[i].txBytes != stats[i].txBytes ||
|
||||
@ -198,31 +170,28 @@ add_test(function test_internalSaveStats_arraySamples() {
|
||||
});
|
||||
|
||||
add_test(function test_internalRemoveOldStats() {
|
||||
var networks = getNetworks();
|
||||
|
||||
netStatsDb.clearStats(networks, function (error, result) {
|
||||
netStatsDb.clear(function (error, result) {
|
||||
do_check_eq(error, null);
|
||||
|
||||
var network = [networks[0].id, networks[0].type];
|
||||
var samples = 10;
|
||||
var stats = [];
|
||||
for (var i = 0; i < samples - 1; i++) {
|
||||
stats.push({ appId: 0,
|
||||
network: network, timestamp: Date.now() + (10 * i),
|
||||
rxBytes: 0, txBytes: 0,
|
||||
rxTotalBytes: 1234, txTotalBytes: 1234 });
|
||||
stats.push({appId: 0,
|
||||
connectionType: "wifi", timestamp: Date.now() + (10 * i),
|
||||
rxBytes: 0, txBytes: 0,
|
||||
rxTotalBytes: 1234, txTotalBytes: 1234});
|
||||
}
|
||||
|
||||
stats.push({ appId: 0,
|
||||
network: network, timestamp: Date.now() + (10 * samples),
|
||||
rxBytes: 0, txBytes: 0,
|
||||
rxTotalBytes: 1234, txTotalBytes: 1234 });
|
||||
stats.push({appId: 0,
|
||||
connectionType: "wifi", timestamp: Date.now() + (10 * samples),
|
||||
rxBytes: 0, txBytes: 0,
|
||||
rxTotalBytes: 1234, txTotalBytes: 1234});
|
||||
|
||||
netStatsDb.dbNewTxn("readwrite", function(txn, store) {
|
||||
netStatsDb._saveStats(txn, store, stats);
|
||||
var date = stats[stats.length - 1].timestamp
|
||||
var date = stats[stats.length -1].timestamp
|
||||
+ (netStatsDb.sampleRate * netStatsDb.maxStorageSamples - 1) - 1;
|
||||
netStatsDb._removeOldStats(txn, store, 0, network, date);
|
||||
netStatsDb._removeOldStats(txn, store, 0, "wifi", date);
|
||||
}, function(error, result) {
|
||||
do_check_eq(error, null);
|
||||
|
||||
@ -236,14 +205,14 @@ add_test(function test_internalRemoveOldStats() {
|
||||
});
|
||||
});
|
||||
|
||||
function processSamplesDiff(networks, lastStat, newStat, callback) {
|
||||
netStatsDb.clearStats(networks, function (error, result){
|
||||
function processSamplesDiff(lastStat, newStat, callback) {
|
||||
netStatsDb.clear(function (error, result){
|
||||
do_check_eq(error, null);
|
||||
netStatsDb.dbNewTxn("readwrite", function(txn, store) {
|
||||
netStatsDb._saveStats(txn, store, lastStat);
|
||||
}, function(error, result) {
|
||||
netStatsDb.dbNewTxn("readwrite", function(txn, store) {
|
||||
let request = store.index("network").openCursor(newStat.network, "prev");
|
||||
let request = store.index("connectionType").openCursor(newStat.connectionType, "prev");
|
||||
request.onsuccess = function onsuccess(event) {
|
||||
let cursor = event.target.result;
|
||||
do_check_neq(cursor, null);
|
||||
@ -261,26 +230,22 @@ function processSamplesDiff(networks, lastStat, newStat, callback) {
|
||||
}
|
||||
|
||||
add_test(function test_processSamplesDiffSameSample() {
|
||||
var networks = getNetworks();
|
||||
var network = [networks[0].id, networks[0].type];
|
||||
|
||||
var sampleRate = netStatsDb.sampleRate;
|
||||
var date = filterTimestamp(new Date());
|
||||
var lastStat = {appId: 0,
|
||||
connectionType: "wifi", timestamp: date,
|
||||
rxBytes: 0, txBytes: 0,
|
||||
rxTotalBytes: 1234, txTotalBytes: 1234};
|
||||
|
||||
var lastStat = { appId: 0,
|
||||
network: network, timestamp: date,
|
||||
rxBytes: 0, txBytes: 0,
|
||||
rxTotalBytes: 1234, txTotalBytes: 1234 };
|
||||
var newStat = {appId: 0,
|
||||
connectionType: "wifi", timestamp: date,
|
||||
rxBytes: 0, txBytes: 0,
|
||||
rxTotalBytes: 2234, txTotalBytes: 2234};
|
||||
|
||||
var newStat = { appId: 0,
|
||||
network: network, timestamp: date,
|
||||
rxBytes: 0, txBytes: 0,
|
||||
rxTotalBytes: 2234, txTotalBytes: 2234 };
|
||||
|
||||
processSamplesDiff(networks, lastStat, newStat, function(result) {
|
||||
processSamplesDiff(lastStat, newStat, function(result) {
|
||||
do_check_eq(result.length, 1);
|
||||
do_check_eq(result[0].appId, newStat.appId);
|
||||
do_check_true(compareNetworks(result[0].network, newStat.network));
|
||||
do_check_eq(result[0].connectionType, newStat.connectionType);
|
||||
do_check_eq(result[0].timestamp, newStat.timestamp);
|
||||
do_check_eq(result[0].rxBytes, newStat.rxTotalBytes - lastStat.rxTotalBytes);
|
||||
do_check_eq(result[0].txBytes, newStat.txTotalBytes - lastStat.txTotalBytes);
|
||||
@ -291,26 +256,22 @@ add_test(function test_processSamplesDiffSameSample() {
|
||||
});
|
||||
|
||||
add_test(function test_processSamplesDiffNextSample() {
|
||||
var networks = getNetworks();
|
||||
var network = [networks[0].id, networks[0].type];
|
||||
|
||||
var sampleRate = netStatsDb.sampleRate;
|
||||
var date = filterTimestamp(new Date());
|
||||
var lastStat = {appId: 0,
|
||||
connectionType: "wifi", timestamp: date,
|
||||
rxBytes: 0, txBytes: 0,
|
||||
rxTotalBytes: 1234, txTotalBytes: 1234};
|
||||
|
||||
var lastStat = { appId: 0,
|
||||
network: network, timestamp: date,
|
||||
rxBytes: 0, txBytes: 0,
|
||||
rxTotalBytes: 1234, txTotalBytes: 1234 };
|
||||
var newStat = {appId: 0,
|
||||
connectionType: "wifi", timestamp: date + sampleRate,
|
||||
rxBytes: 0, txBytes: 0,
|
||||
rxTotalBytes: 500, txTotalBytes: 500};
|
||||
|
||||
var newStat = { appId: 0,
|
||||
network: network, timestamp: date + sampleRate,
|
||||
rxBytes: 0, txBytes: 0,
|
||||
rxTotalBytes: 500, txTotalBytes: 500 };
|
||||
|
||||
processSamplesDiff(networks, lastStat, newStat, function(result) {
|
||||
processSamplesDiff(lastStat, newStat, function(result) {
|
||||
do_check_eq(result.length, 2);
|
||||
do_check_eq(result[1].appId, newStat.appId);
|
||||
do_check_true(compareNetworks(result[1].network, newStat.network));
|
||||
do_check_eq(result[1].connectionType, newStat.connectionType);
|
||||
do_check_eq(result[1].timestamp, newStat.timestamp);
|
||||
do_check_eq(result[1].rxBytes, newStat.rxTotalBytes);
|
||||
do_check_eq(result[1].txBytes, newStat.txTotalBytes);
|
||||
@ -321,25 +282,23 @@ add_test(function test_processSamplesDiffNextSample() {
|
||||
});
|
||||
|
||||
add_test(function test_processSamplesDiffSamplesLost() {
|
||||
var networks = getNetworks();
|
||||
var network = [networks[0].id, networks[0].type];
|
||||
var samples = 5;
|
||||
var sampleRate = netStatsDb.sampleRate;
|
||||
var date = filterTimestamp(new Date());
|
||||
var lastStat = { appId: 0,
|
||||
network: network, timestamp: date,
|
||||
rxBytes: 0, txBytes: 0,
|
||||
rxTotalBytes: 1234, txTotalBytes: 1234 };
|
||||
var lastStat = {appId: 0,
|
||||
connectionType: "wifi", timestamp: date,
|
||||
rxBytes: 0, txBytes: 0,
|
||||
rxTotalBytes: 1234, txTotalBytes: 1234};
|
||||
|
||||
var newStat = { appId: 0,
|
||||
network: network, timestamp: date + (sampleRate * samples),
|
||||
rxBytes: 0, txBytes: 0,
|
||||
rxTotalBytes: 2234, txTotalBytes: 2234 };
|
||||
var newStat = {appId: 0,
|
||||
connectionType: "wifi", timestamp: date + (sampleRate * samples),
|
||||
rxBytes: 0, txBytes: 0,
|
||||
rxTotalBytes: 2234, txTotalBytes: 2234};
|
||||
|
||||
processSamplesDiff(networks, lastStat, newStat, function(result) {
|
||||
processSamplesDiff(lastStat, newStat, function(result) {
|
||||
do_check_eq(result.length, samples + 1);
|
||||
do_check_eq(result[0].appId, newStat.appId);
|
||||
do_check_true(compareNetworks(result[samples].network, newStat.network));
|
||||
do_check_eq(result[samples].connectionType, newStat.connectionType);
|
||||
do_check_eq(result[samples].timestamp, newStat.timestamp);
|
||||
do_check_eq(result[samples].rxBytes, newStat.rxTotalBytes - lastStat.rxTotalBytes);
|
||||
do_check_eq(result[samples].txBytes, newStat.txTotalBytes - lastStat.txTotalBytes);
|
||||
@ -350,17 +309,13 @@ add_test(function test_processSamplesDiffSamplesLost() {
|
||||
});
|
||||
|
||||
add_test(function test_saveStats() {
|
||||
var networks = getNetworks();
|
||||
var network = [networks[0].id, networks[0].type];
|
||||
var stats = {appId: 0,
|
||||
connectionType: "wifi",
|
||||
date: new Date(),
|
||||
rxBytes: 2234,
|
||||
txBytes: 2234};
|
||||
|
||||
var stats = { appId: 0,
|
||||
networkId: networks[0].id,
|
||||
networkType: networks[0].type,
|
||||
date: new Date(),
|
||||
rxBytes: 2234,
|
||||
txBytes: 2234};
|
||||
|
||||
netStatsDb.clearStats(networks, function (error, result) {
|
||||
netStatsDb.clear(function (error, result) {
|
||||
do_check_eq(error, null);
|
||||
netStatsDb.saveStats(stats, function(error, result) {
|
||||
do_check_eq(error, null);
|
||||
@ -368,7 +323,7 @@ add_test(function test_saveStats() {
|
||||
do_check_eq(error, null);
|
||||
do_check_eq(result.length, 1);
|
||||
do_check_eq(result[0].appId, stats.appId);
|
||||
do_check_true(compareNetworks(result[0].network, network));
|
||||
do_check_eq(result[0].connectionType, stats.connectionType);
|
||||
let timestamp = filterTimestamp(stats.date);
|
||||
do_check_eq(result[0].timestamp, timestamp);
|
||||
do_check_eq(result[0].rxBytes, 0);
|
||||
@ -382,44 +337,35 @@ add_test(function test_saveStats() {
|
||||
});
|
||||
|
||||
add_test(function test_saveAppStats() {
|
||||
var networks = getNetworks();
|
||||
var network = [networks[0].id, networks[0].type];
|
||||
var stats = {appId: 1,
|
||||
connectionType: "wifi",
|
||||
date: new Date(),
|
||||
rxBytes: 2234,
|
||||
txBytes: 2234};
|
||||
|
||||
var stats = { appId: 1,
|
||||
networkId: networks[0].id,
|
||||
networkType: networks[0].type,
|
||||
date: new Date(),
|
||||
rxBytes: 2234,
|
||||
txBytes: 2234};
|
||||
|
||||
netStatsDb.clearStats(networks, function (error, result) {
|
||||
netStatsDb.clear(function (error, result) {
|
||||
do_check_eq(error, null);
|
||||
netStatsDb.saveStats(stats, function(error, result) {
|
||||
do_check_eq(error, null);
|
||||
netStatsDb.logAllRecords(function(error, result) {
|
||||
do_check_eq(error, null);
|
||||
// The clear function clears all records of the datbase but
|
||||
// inserts a new element for each [appId, connectionId, connectionType]
|
||||
// record to keep the track of rxTotalBytes / txTotalBytes.
|
||||
// So at this point, we have two records, one for the appId 0 used in
|
||||
// past tests and the new one for appId 1
|
||||
do_check_eq(result.length, 2);
|
||||
do_check_eq(result[1].appId, stats.appId);
|
||||
do_check_true(compareNetworks(result[1].network, network));
|
||||
do_check_eq(result.length, 1);
|
||||
do_check_eq(result[0].appId, stats.appId);
|
||||
do_check_eq(result[0].connectionType, stats.connectionType);
|
||||
let timestamp = filterTimestamp(stats.date);
|
||||
do_check_eq(result[1].timestamp, timestamp);
|
||||
do_check_eq(result[1].rxBytes, stats.rxBytes);
|
||||
do_check_eq(result[1].txBytes, stats.txBytes);
|
||||
do_check_eq(result[1].rxTotalBytes, 0);
|
||||
do_check_eq(result[1].txTotalBytes, 0);
|
||||
do_check_eq(result[0].timestamp, timestamp);
|
||||
do_check_eq(result[0].rxBytes, stats.rxBytes);
|
||||
do_check_eq(result[0].txBytes, stats.txBytes);
|
||||
do_check_eq(result[0].rxTotalBytes, 0);
|
||||
do_check_eq(result[0].txTotalBytes, 0);
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function prepareFind(network, stats, callback) {
|
||||
netStatsDb.clearStats(network, function (error, result) {
|
||||
function prepareFind(stats, callback) {
|
||||
netStatsDb.clear(function (error, result) {
|
||||
do_check_eq(error, null);
|
||||
netStatsDb.dbNewTxn("readwrite", function(txn, store) {
|
||||
netStatsDb._saveStats(txn, store, stats);
|
||||
@ -430,11 +376,6 @@ function prepareFind(network, stats, callback) {
|
||||
}
|
||||
|
||||
add_test(function test_find () {
|
||||
var networks = getNetworks();
|
||||
var networkWifi = [networks[0].id, networks[0].type];
|
||||
var networkMobile = [networks[1].id, networks[1].type]; // Fake mobile interface
|
||||
var appId = 0;
|
||||
|
||||
var samples = 5;
|
||||
var sampleRate = netStatsDb.sampleRate;
|
||||
var start = Date.now();
|
||||
@ -443,39 +384,46 @@ add_test(function test_find () {
|
||||
start = new Date(start - sampleRate);
|
||||
var stats = [];
|
||||
for (var i = 0; i < samples; i++) {
|
||||
stats.push({ appId: appId,
|
||||
network: networkWifi, timestamp: saveDate + (sampleRate * i),
|
||||
rxBytes: 0, txBytes: 10,
|
||||
rxTotalBytes: 0, txTotalBytes: 0 });
|
||||
stats.push({appId: 0,
|
||||
connectionType: "wifi", timestamp: saveDate + (sampleRate * i),
|
||||
rxBytes: 0, txBytes: 10,
|
||||
rxTotalBytes: 0, txTotalBytes: 0});
|
||||
|
||||
stats.push({ appId: appId,
|
||||
network: networkMobile, timestamp: saveDate + (sampleRate * i),
|
||||
rxBytes: 0, txBytes: 10,
|
||||
rxTotalBytes: 0, txTotalBytes: 0 });
|
||||
stats.push({appId: 0,
|
||||
connectionType: "mobile", timestamp: saveDate + (sampleRate * i),
|
||||
rxBytes: 0, txBytes: 10,
|
||||
rxTotalBytes: 0, txTotalBytes: 0});
|
||||
}
|
||||
|
||||
prepareFind(networks[0], stats, function(error, result) {
|
||||
prepareFind(stats, function(error, result) {
|
||||
do_check_eq(error, null);
|
||||
netStatsDb.find(function (error, result) {
|
||||
do_check_eq(error, null);
|
||||
do_check_eq(result.network.id, networks[0].id);
|
||||
do_check_eq(result.network.type, networks[0].type);
|
||||
do_check_eq(result.connectionType, "wifi");
|
||||
do_check_eq(result.start.getTime(), start.getTime());
|
||||
do_check_eq(result.end.getTime(), end.getTime());
|
||||
do_check_eq(result.data.length, samples + 1);
|
||||
do_check_eq(result.data[0].rxBytes, null);
|
||||
do_check_eq(result.data[1].rxBytes, 0);
|
||||
do_check_eq(result.data[samples].rxBytes, 0);
|
||||
run_next_test();
|
||||
}, networks[0], start, end, appId);
|
||||
|
||||
netStatsDb.findAll(function (error, result) {
|
||||
do_check_eq(error, null);
|
||||
do_check_eq(result.connectionType, null);
|
||||
do_check_eq(result.start.getTime(), start.getTime());
|
||||
do_check_eq(result.end.getTime(), end.getTime());
|
||||
do_check_eq(result.data.length, samples + 1);
|
||||
do_check_eq(result.data[0].rxBytes, null);
|
||||
do_check_eq(result.data[1].rxBytes, 0);
|
||||
do_check_eq(result.data[1].txBytes, 20);
|
||||
do_check_eq(result.data[samples].rxBytes, 0);
|
||||
run_next_test();
|
||||
}, {appId: 0, start: start, end: end});
|
||||
}, {start: start, end: end, connectionType: "wifi", appId: 0});
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_findAppStats () {
|
||||
var networks = getNetworks();
|
||||
var networkWifi = [networks[0].id, networks[0].type];
|
||||
var networkMobile = [networks[1].id, networks[1].type]; // Fake mobile interface
|
||||
|
||||
var samples = 5;
|
||||
var sampleRate = netStatsDb.sampleRate;
|
||||
var start = Date.now();
|
||||
@ -484,63 +432,69 @@ add_test(function test_findAppStats () {
|
||||
start = new Date(start - sampleRate);
|
||||
var stats = [];
|
||||
for (var i = 0; i < samples; i++) {
|
||||
stats.push({ appId: 1,
|
||||
network: networkWifi, timestamp: saveDate + (sampleRate * i),
|
||||
rxBytes: 0, txBytes: 10,
|
||||
rxTotalBytes: 0, txTotalBytes: 0 });
|
||||
stats.push({appId: 1,
|
||||
connectionType: "wifi", timestamp: saveDate + (sampleRate * i),
|
||||
rxBytes: 0, txBytes: 10,
|
||||
rxTotalBytes: 0, txTotalBytes: 0});
|
||||
|
||||
stats.push({ appId: 1,
|
||||
network: networkMobile, timestamp: saveDate + (sampleRate * i),
|
||||
rxBytes: 0, txBytes: 10,
|
||||
rxTotalBytes: 0, txTotalBytes: 0 });
|
||||
stats.push({appId: 1,
|
||||
connectionType: "mobile", timestamp: saveDate + (sampleRate * i),
|
||||
rxBytes: 0, txBytes: 10,
|
||||
rxTotalBytes: 0, txTotalBytes: 0});
|
||||
}
|
||||
|
||||
prepareFind(networks[0], stats, function(error, result) {
|
||||
prepareFind(stats, function(error, result) {
|
||||
do_check_eq(error, null);
|
||||
netStatsDb.find(function (error, result) {
|
||||
do_check_eq(error, null);
|
||||
do_check_eq(result.network.id, networks[0].id);
|
||||
do_check_eq(result.network.type, networks[0].type);
|
||||
do_check_eq(result.connectionType, "wifi");
|
||||
do_check_eq(result.start.getTime(), start.getTime());
|
||||
do_check_eq(result.end.getTime(), end.getTime());
|
||||
do_check_eq(result.data.length, samples + 1);
|
||||
do_check_eq(result.data[0].rxBytes, null);
|
||||
do_check_eq(result.data[1].rxBytes, 0);
|
||||
do_check_eq(result.data[samples].rxBytes, 0);
|
||||
run_next_test();
|
||||
}, networks[0], start, end, 1);
|
||||
|
||||
netStatsDb.findAll(function (error, result) {
|
||||
do_check_eq(error, null);
|
||||
do_check_eq(result.connectionType, null);
|
||||
do_check_eq(result.start.getTime(), start.getTime());
|
||||
do_check_eq(result.end.getTime(), end.getTime());
|
||||
do_check_eq(result.data.length, samples + 1);
|
||||
do_check_eq(result.data[0].rxBytes, null);
|
||||
do_check_eq(result.data[1].rxBytes, 0);
|
||||
do_check_eq(result.data[1].txBytes, 20);
|
||||
do_check_eq(result.data[samples].rxBytes, 0);
|
||||
run_next_test();
|
||||
}, {start: start, end: end, appId: 1});
|
||||
}, {start: start, end: end, connectionType: "wifi", appId: 1});
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_saveMultipleAppStats () {
|
||||
var networks = getNetworks();
|
||||
var networkWifi = networks[0];
|
||||
var networkMobile = networks[1]; // Fake mobile interface
|
||||
|
||||
var saveDate = filterTimestamp(new Date());
|
||||
var cached = Object.create(null);
|
||||
|
||||
cached['1wifi'] = { appId: 1, date: new Date(),
|
||||
networkId: networkWifi.id, networkType: networkWifi.type,
|
||||
rxBytes: 0, txBytes: 10 };
|
||||
cached['1wifi'] = {appId: 1,
|
||||
connectionType: "wifi", date: new Date(),
|
||||
rxBytes: 0, txBytes: 10};
|
||||
|
||||
cached['1mobile'] = { appId: 1, date: new Date(),
|
||||
networkId: networkMobile.id, networkType: networkMobile.type,
|
||||
rxBytes: 0, txBytes: 10 };
|
||||
cached['1mobile'] = {appId: 1,
|
||||
connectionType: "mobile", date: new Date(),
|
||||
rxBytes: 0, txBytes: 10};
|
||||
|
||||
cached['2wifi'] = { appId: 2, date: new Date(),
|
||||
networkId: networkWifi.id, networkType: networkWifi.type,
|
||||
rxBytes: 0, txBytes: 10 };
|
||||
cached['2wifi'] = {appId: 2,
|
||||
connectionType: "wifi", date: new Date(),
|
||||
rxBytes: 0, txBytes: 10};
|
||||
|
||||
cached['2mobile'] = { appId: 2, date: new Date(),
|
||||
networkId: networkMobile.id, networkType: networkMobile.type,
|
||||
rxBytes: 0, txBytes: 10 };
|
||||
cached['2mobile'] = {appId: 2,
|
||||
connectionType: "mobile", date: new Date(),
|
||||
rxBytes: 0, txBytes: 10};
|
||||
|
||||
let keys = Object.keys(cached);
|
||||
let index = 0;
|
||||
|
||||
networks.push(networkMobile);
|
||||
netStatsDb.clearStats(networks, function (error, result) {
|
||||
netStatsDb.clear(function (error, result) {
|
||||
do_check_eq(error, null);
|
||||
netStatsDb.saveStats(cached[keys[index]],
|
||||
function callback(error, result) {
|
||||
@ -548,17 +502,10 @@ add_test(function test_saveMultipleAppStats () {
|
||||
|
||||
if (index == keys.length - 1) {
|
||||
netStatsDb.logAllRecords(function(error, result) {
|
||||
// Again, result has two samples more than expected samples because
|
||||
// clear inserts one empty sample for each network to keep totalBytes
|
||||
// synchronized with netd counters. so the first two samples have to
|
||||
// be discarted.
|
||||
result.shift();
|
||||
result.shift();
|
||||
|
||||
do_check_eq(error, null);
|
||||
do_check_eq(result.length, 4);
|
||||
do_check_eq(result[0].appId, 1);
|
||||
do_check_true(compareNetworks(result[0].network,[networkWifi.id, networkWifi.type]));
|
||||
do_check_eq(result[0].connectionType, 'mobile');
|
||||
do_check_eq(result[0].rxBytes, 0);
|
||||
do_check_eq(result[0].txBytes, 10);
|
||||
run_next_test();
|
||||
|
@ -4,56 +4,48 @@
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
add_test(function test_clearDB() {
|
||||
var networks = NetworkStatsService.availableNetworks();
|
||||
NetworkStatsService._db.clearStats(networks, function onDBCleared(error, result) {
|
||||
NetworkStatsService._db.clear(function onDBCleared(error, result) {
|
||||
do_check_eq(result, null);
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
function getNetworkId() {
|
||||
var network = (NetworkStatsService.availableNetworks())[0];
|
||||
return NetworkStatsService.getNetworkId(network.id, network.type);
|
||||
}
|
||||
|
||||
add_test(function test_networkStatsAvailable_ok() {
|
||||
var netId = getNetworkId();
|
||||
NetworkStatsService.networkStatsAvailable(function (success, msg) {
|
||||
do_check_eq(success, true);
|
||||
run_next_test();
|
||||
}, netId, true, 1234, 4321, new Date());
|
||||
}, Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, true, 1234, 4321, new Date());
|
||||
});
|
||||
|
||||
add_test(function test_networkStatsAvailable_failure() {
|
||||
var netId = getNetworkId();
|
||||
NetworkStatsService.networkStatsAvailable(function (success, msg) {
|
||||
do_check_eq(success, false);
|
||||
run_next_test();
|
||||
}, netId, false, 1234, 4321, new Date());
|
||||
}, Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, false, 1234, 4321, new Date());
|
||||
});
|
||||
|
||||
add_test(function test_update_invalidNetwork() {
|
||||
add_test(function test_update_invalidConnection() {
|
||||
NetworkStatsService.update(-1, function (success, msg) {
|
||||
do_check_eq(success, false);
|
||||
do_check_eq(msg, "Invalid network -1");
|
||||
do_check_eq(msg, "Invalid network type -1");
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_update() {
|
||||
var netId = getNetworkId();
|
||||
NetworkStatsService.update(netId, function (success, msg) {
|
||||
NetworkStatsService.update(Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, function (success, msg) {
|
||||
do_check_eq(success, true);
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_updateQueueIndex() {
|
||||
NetworkStatsService.updateQueue = [{netId: 0, callbacks: null},
|
||||
{netId: 1, callbacks: null},
|
||||
{netId: 2, callbacks: null},
|
||||
{netId: 3, callbacks: null},
|
||||
{netId: 4, callbacks: null}];
|
||||
NetworkStatsService.updateQueue = [{type: 0, callbacks: null},
|
||||
{type: 1, callbacks: null},
|
||||
{type: 2, callbacks: null},
|
||||
{type: 3, callbacks: null},
|
||||
{type: 4, callbacks: null}];
|
||||
var index = NetworkStatsService.updateQueueIndex(3);
|
||||
do_check_eq(index, 3);
|
||||
index = NetworkStatsService.updateQueueIndex(10);
|
||||
@ -71,8 +63,7 @@ add_test(function test_updateAllStats() {
|
||||
});
|
||||
|
||||
add_test(function test_updateStats_ok() {
|
||||
var netId = getNetworkId();
|
||||
NetworkStatsService.updateStats(netId, function(success, msg){
|
||||
NetworkStatsService.updateStats(Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, function(success, msg){
|
||||
do_check_eq(success, true);
|
||||
run_next_test();
|
||||
});
|
||||
@ -86,20 +77,15 @@ add_test(function test_updateStats_failure() {
|
||||
});
|
||||
|
||||
add_test(function test_queue() {
|
||||
// Fill networks with fake network interfaces
|
||||
// Fill connections with fake network interfaces (wlan0 and rmnet0)
|
||||
// to enable netd async requests
|
||||
var network = {id: "1234", type: Ci.nsIDOMMozNetworkStatsManager.MOBILE};
|
||||
var netId1 = NetworkStatsService.getNetworkId(network.id, network.type);
|
||||
NetworkStatsService._networks[netId1] = { network: network,
|
||||
interfaceName: "net1" };
|
||||
NetworkStatsService._connectionTypes[Ci.nsINetworkInterface.NETWORK_TYPE_WIFI]
|
||||
.network.name = 'wlan0';
|
||||
NetworkStatsService._connectionTypes[Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE]
|
||||
.network.name = 'rmnet0';
|
||||
|
||||
network = {id: "5678", type: Ci.nsIDOMMozNetworkStatsManager.MOBILE};
|
||||
var netId2 = NetworkStatsService.getNetworkId(network.id, network.type);
|
||||
NetworkStatsService._networks[netId2] = { network: network,
|
||||
interfaceName: "net2" };
|
||||
|
||||
NetworkStatsService.updateStats(netId1);
|
||||
NetworkStatsService.updateStats(netId2);
|
||||
NetworkStatsService.updateStats(Ci.nsINetworkInterface.NETWORK_TYPE_WIFI);
|
||||
NetworkStatsService.updateStats(Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE);
|
||||
do_check_eq(NetworkStatsService.updateQueue.length, 2);
|
||||
do_check_eq(NetworkStatsService.updateQueue[0].callbacks.length, 1);
|
||||
|
||||
@ -107,8 +93,8 @@ add_test(function test_queue() {
|
||||
return;
|
||||
};
|
||||
|
||||
NetworkStatsService.updateStats(netId1, callback);
|
||||
NetworkStatsService.updateStats(netId2, callback);
|
||||
NetworkStatsService.updateStats(Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, callback);
|
||||
NetworkStatsService.updateStats(Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE, callback);
|
||||
|
||||
do_check_eq(NetworkStatsService.updateQueue.length, 2);
|
||||
do_check_eq(NetworkStatsService.updateQueue[0].callbacks.length, 2);
|
||||
|
@ -9,66 +9,33 @@ XPCOMUtils.defineLazyServiceGetter(this, "nssProxy",
|
||||
"@mozilla.org/networkstatsServiceProxy;1",
|
||||
"nsINetworkStatsServiceProxy");
|
||||
|
||||
function mokConvertNetworkInterface() {
|
||||
NetworkStatsService.convertNetworkInterface = function(aNetwork) {
|
||||
if (aNetwork.type != Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE &&
|
||||
aNetwork.type != Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let id = '0';
|
||||
if (aNetwork.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE) {
|
||||
id = '1234'
|
||||
}
|
||||
|
||||
let netId = this.getNetworkId(id, aNetwork.type);
|
||||
|
||||
if (!this._networks[netId]) {
|
||||
this._networks[netId] = Object.create(null);
|
||||
this._networks[netId].network = { id: id,
|
||||
type: aNetwork.type };
|
||||
}
|
||||
|
||||
return netId;
|
||||
};
|
||||
}
|
||||
|
||||
add_test(function test_saveAppStats() {
|
||||
var cachedAppStats = NetworkStatsService.cachedAppStats;
|
||||
var timestamp = NetworkStatsService.cachedAppStatsDate.getTime();
|
||||
var samples = 5;
|
||||
|
||||
// Create to fake nsINetworkInterfaces. As nsINetworkInterface can not
|
||||
// be instantiated, these two vars will emulate it by filling the properties
|
||||
// that will be used.
|
||||
var wifi = {type: Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, id: "0"};
|
||||
var mobile = {type: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE, id: "1234"};
|
||||
|
||||
// Insert fake mobile network interface in NetworkStatsService
|
||||
var mobileNetId = NetworkStatsService.getNetworkId(mobile.id, mobile.type);
|
||||
|
||||
do_check_eq(Object.keys(cachedAppStats).length, 0);
|
||||
|
||||
for (var i = 0; i < samples; i++) {
|
||||
nssProxy.saveAppStats(1, wifi, timestamp, 10, 20);
|
||||
nssProxy.saveAppStats(1, Ci.nsINetworkInterface.NETWORK_TYPE_WIFI,
|
||||
timestamp, 10, 20);
|
||||
|
||||
nssProxy.saveAppStats(1, mobile, timestamp, 10, 20);
|
||||
nssProxy.saveAppStats(1, Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE,
|
||||
timestamp, 10, 20);
|
||||
}
|
||||
|
||||
var key1 = 1 + NetworkStatsService.getNetworkId(wifi.id, wifi.type);
|
||||
var key2 = 1 + mobileNetId;
|
||||
var key1 = 1 + 'wifi';
|
||||
var key2 = 1 + 'mobile';
|
||||
|
||||
do_check_eq(Object.keys(cachedAppStats).length, 2);
|
||||
do_check_eq(cachedAppStats[key1].appId, 1);
|
||||
do_check_eq(cachedAppStats[key1].networkId, wifi.id);
|
||||
do_check_eq(cachedAppStats[key1].networkType, wifi.type);
|
||||
do_check_eq(cachedAppStats[key1].connectionType, 'wifi');
|
||||
do_check_eq(new Date(cachedAppStats[key1].date).getTime() / 1000,
|
||||
Math.floor(timestamp / 1000));
|
||||
do_check_eq(cachedAppStats[key1].rxBytes, 50);
|
||||
do_check_eq(cachedAppStats[key1].txBytes, 100);
|
||||
do_check_eq(cachedAppStats[key2].appId, 1);
|
||||
do_check_eq(cachedAppStats[key2].networkId, mobile.id);
|
||||
do_check_eq(cachedAppStats[key2].networkType, mobile.type);
|
||||
do_check_eq(cachedAppStats[key2].connectionType, 'mobile');
|
||||
do_check_eq(new Date(cachedAppStats[key2].date).getTime() / 1000,
|
||||
Math.floor(timestamp / 1000));
|
||||
do_check_eq(cachedAppStats[key2].rxBytes, 50);
|
||||
@ -80,11 +47,7 @@ add_test(function test_saveAppStats() {
|
||||
add_test(function test_saveAppStatsWithDifferentDates() {
|
||||
var today = NetworkStatsService.cachedAppStatsDate;
|
||||
var tomorrow = new Date(today.getTime() + (24 * 60 * 60 * 1000));
|
||||
|
||||
var wifi = {type: Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, id: "0"};
|
||||
var mobile = {type: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE, id: "1234"};
|
||||
|
||||
var key = 1 + NetworkStatsService.getNetworkId(wifi.id, wifi.type);
|
||||
var key = 1 + 'wifi';
|
||||
|
||||
NetworkStatsService.updateCachedAppStats(
|
||||
function (success, msg) {
|
||||
@ -92,20 +55,21 @@ add_test(function test_saveAppStatsWithDifferentDates() {
|
||||
|
||||
do_check_eq(Object.keys(NetworkStatsService.cachedAppStats).length, 0);
|
||||
|
||||
nssProxy.saveAppStats(1, wifi, today.getTime(), 10, 20);
|
||||
nssProxy.saveAppStats(1, Ci.nsINetworkInterface.NETWORK_TYPE_WIFI,
|
||||
today.getTime(), 10, 20);
|
||||
|
||||
nssProxy.saveAppStats(1, mobile, today.getTime(), 10, 20);
|
||||
nssProxy.saveAppStats(1, Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE,
|
||||
today.getTime(), 10, 20);
|
||||
|
||||
var saveAppStatsCb = {
|
||||
notify: function notify(success, message) {
|
||||
do_check_eq(success, true);
|
||||
|
||||
var cachedAppStats = NetworkStatsService.cachedAppStats;
|
||||
var key = 2 + NetworkStatsService.getNetworkId(mobile.id, mobile.type);
|
||||
var key = 2 + 'mobile';
|
||||
do_check_eq(Object.keys(cachedAppStats).length, 1);
|
||||
do_check_eq(cachedAppStats[key].appId, 2);
|
||||
do_check_eq(cachedAppStats[key].networkId, mobile.id);
|
||||
do_check_eq(cachedAppStats[key].networkType, mobile.type);
|
||||
do_check_eq(cachedAppStats[key].connectionType, 'mobile');
|
||||
do_check_eq(new Date(cachedAppStats[key].date).getTime() / 1000,
|
||||
Math.floor(tomorrow.getTime() / 1000));
|
||||
do_check_eq(cachedAppStats[key].rxBytes, 30);
|
||||
@ -115,7 +79,8 @@ add_test(function test_saveAppStatsWithDifferentDates() {
|
||||
}
|
||||
};
|
||||
|
||||
nssProxy.saveAppStats(2, mobile, tomorrow.getTime(), 30, 40, saveAppStatsCb);
|
||||
nssProxy.saveAppStats(2, Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE,
|
||||
tomorrow.getTime(), 30, 40, saveAppStatsCb);
|
||||
}
|
||||
);
|
||||
});
|
||||
@ -123,7 +88,6 @@ add_test(function test_saveAppStatsWithDifferentDates() {
|
||||
add_test(function test_saveAppStatsWithMaxCachedTraffic() {
|
||||
var timestamp = NetworkStatsService.cachedAppStatsDate.getTime();
|
||||
var maxtraffic = NetworkStatsService.maxCachedTraffic;
|
||||
var wifi = {type: Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, id: "0"};
|
||||
|
||||
NetworkStatsService.updateCachedAppStats(
|
||||
function (success, msg) {
|
||||
@ -132,11 +96,13 @@ add_test(function test_saveAppStatsWithMaxCachedTraffic() {
|
||||
var cachedAppStats = NetworkStatsService.cachedAppStats;
|
||||
do_check_eq(Object.keys(cachedAppStats).length, 0);
|
||||
|
||||
nssProxy.saveAppStats(1, wifi, timestamp, 10, 20);
|
||||
nssProxy.saveAppStats(1, Ci.nsINetworkInterface.NETWORK_TYPE_WIFI,
|
||||
timestamp, 10, 20);
|
||||
|
||||
do_check_eq(Object.keys(cachedAppStats).length, 1);
|
||||
|
||||
nssProxy.saveAppStats(1, wifi, timestamp, maxtraffic, 20);
|
||||
nssProxy.saveAppStats(1, Ci.nsINetworkInterface.NETWORK_TYPE_WIFI,
|
||||
timestamp, maxtraffic, 20);
|
||||
|
||||
do_check_eq(Object.keys(cachedAppStats).length, 0);
|
||||
|
||||
@ -149,9 +115,5 @@ function run_test() {
|
||||
|
||||
Cu.import("resource://gre/modules/NetworkStatsService.jsm");
|
||||
|
||||
// Function convertNetworkInterface of NetworkStatsService causes errors when dealing
|
||||
// with RIL to get the iccid, so overwrite it.
|
||||
mokConvertNetworkInterface();
|
||||
|
||||
run_next_test();
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
ifndef INCLUDED_JAVA_BUILD_MK #{
|
||||
|
||||
ifdef ANDROID_RESFILES #{
|
||||
ifndef IGNORE_ANDROID_RESFILES #{
|
||||
res-dep := .deps-copy-java-res
|
||||
|
||||
GENERATED_DIRS += res
|
||||
@ -25,6 +26,7 @@ res-dep-preqs := \
|
||||
$(res-dep): $(res-dep-preqs)
|
||||
$(call copy_dir,$(srcdir)/res,$(CURDIR)/res)
|
||||
@$(TOUCH) $@
|
||||
endif #} IGNORE_ANDROID_RESFILES
|
||||
endif #} ANDROID_RESFILES
|
||||
|
||||
|
||||
|
@ -21,6 +21,8 @@ INCLUDED_RULES_MK = 1
|
||||
# present. If they are, this is a violation of the separation of
|
||||
# responsibility between Makefile.in and mozbuild files.
|
||||
_MOZBUILD_EXTERNAL_VARIABLES := \
|
||||
ANDROID_GENERATED_RESFILES \
|
||||
ANDROID_RESFILES \
|
||||
CMMSRCS \
|
||||
CPP_UNIT_TESTS \
|
||||
DIRS \
|
||||
|
@ -505,7 +505,7 @@ abstract public class BrowserApp extends GeckoApp
|
||||
registerEventListener("Updater:Launch");
|
||||
registerEventListener("Reader:GoToReadingList");
|
||||
|
||||
Distribution.init(this, getPackageResourcePath());
|
||||
Distribution.init(this);
|
||||
JavaAddonManager.getInstance().init(getApplicationContext());
|
||||
mSharedPreferencesHelper = new SharedPreferencesHelper(getApplicationContext());
|
||||
mOrderedBroadcastHelper = new OrderedBroadcastHelper(getApplicationContext());
|
||||
|
@ -1,11 +1,7 @@
|
||||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* ***** BEGIN LICENSE BLOCK *****
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko;
|
||||
|
||||
@ -13,113 +9,224 @@ import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
public final class Distribution {
|
||||
private static final String LOGTAG = "GeckoDistribution";
|
||||
|
||||
private static final String DEFAULT_PREFS = GeckoApp.PREFS_NAME;
|
||||
|
||||
private static final int STATE_UNKNOWN = 0;
|
||||
private static final int STATE_NONE = 1;
|
||||
private static final int STATE_SET = 2;
|
||||
|
||||
public static class DistributionDescriptor {
|
||||
public final boolean valid;
|
||||
public final String id;
|
||||
public final String version; // Example uses a float, but that's a crazy idea.
|
||||
|
||||
// Default UI-visible description of the distribution.
|
||||
public final String about;
|
||||
|
||||
// Each distribution file can include multiple localized versions of
|
||||
// the 'about' string. These are represented as, e.g., "about.en-US"
|
||||
// keys in the Global object.
|
||||
// Here we map locale to description.
|
||||
public final Map<String, String> localizedAbout;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public DistributionDescriptor(JSONObject obj) {
|
||||
this.id = obj.optString("id");
|
||||
this.version = obj.optString("version");
|
||||
this.about = obj.optString("about");
|
||||
Map<String, String> loc = new HashMap<String, String>();
|
||||
try {
|
||||
Iterator<String> keys = obj.keys();
|
||||
while (keys.hasNext()) {
|
||||
String key = keys.next();
|
||||
if (key.startsWith("about.")) {
|
||||
String locale = key.substring(6);
|
||||
if (!obj.isNull(locale)) {
|
||||
loc.put(locale, obj.getString(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (JSONException ex) {
|
||||
Log.w(LOGTAG, "Unable to completely process distribution JSON.", ex);
|
||||
}
|
||||
|
||||
this.localizedAbout = Collections.unmodifiableMap(loc);
|
||||
this.valid = (null != this.id) &&
|
||||
(null != this.version) &&
|
||||
(null != this.about);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes distribution if it hasn't already been initalized.
|
||||
* Initializes distribution if it hasn't already been initalized. Sends
|
||||
* messages to Gecko as appropriate.
|
||||
*
|
||||
* @param packagePath specifies where to look for the distribution directory.
|
||||
* @param packagePath where to look for the distribution directory.
|
||||
*/
|
||||
public static void init(final Context context, final String packagePath) {
|
||||
public static void init(final Context context, final String packagePath, final String prefsPath) {
|
||||
// Read/write preferences and files on the background thread.
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Bail if we've already initialized the distribution.
|
||||
SharedPreferences settings = context.getSharedPreferences(GeckoApp.PREFS_NAME, Activity.MODE_PRIVATE);
|
||||
String keyName = context.getPackageName() + ".distribution_state";
|
||||
int state = settings.getInt(keyName, STATE_UNKNOWN);
|
||||
if (state == STATE_NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Send a message to Gecko if we've set a distribution.
|
||||
if (state == STATE_SET) {
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Distribution:Set", ""));
|
||||
return;
|
||||
}
|
||||
|
||||
boolean distributionSet = false;
|
||||
try {
|
||||
// First, try copying distribution files out of the APK.
|
||||
distributionSet = copyFiles(context, packagePath);
|
||||
} catch (IOException e) {
|
||||
Log.e(LOGTAG, "Error copying distribution files", e);
|
||||
}
|
||||
|
||||
if (!distributionSet) {
|
||||
// If there aren't any distribution files in the APK, look in the /system directory.
|
||||
File distDir = new File("/system/" + context.getPackageName() + "/distribution");
|
||||
if (distDir.exists()) {
|
||||
distributionSet = true;
|
||||
}
|
||||
}
|
||||
|
||||
Distribution dist = new Distribution(context, packagePath, prefsPath);
|
||||
boolean distributionSet = dist.doInit();
|
||||
if (distributionSet) {
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Distribution:Set", ""));
|
||||
settings.edit().putInt(keyName, STATE_SET).commit();
|
||||
} else {
|
||||
settings.edit().putInt(keyName, STATE_NONE).commit();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Use <code>Context.getPackageResourcePath</code> to find an implicit
|
||||
* package path.
|
||||
*/
|
||||
public static void init(final Context context) {
|
||||
Distribution.init(context, context.getPackageResourcePath(), DEFAULT_PREFS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns parsed contents of bookmarks.json.
|
||||
* This method should only be called from a background thread.
|
||||
*/
|
||||
public static JSONArray getBookmarks(final Context context) {
|
||||
Distribution dist = new Distribution(context);
|
||||
return dist.getBookmarks();
|
||||
}
|
||||
|
||||
private final Context context;
|
||||
private final String packagePath;
|
||||
private final String prefsBranch;
|
||||
|
||||
private int state = STATE_UNKNOWN;
|
||||
private File distributionDir = null;
|
||||
|
||||
/**
|
||||
* @param packagePath where to look for the distribution directory.
|
||||
*/
|
||||
public Distribution(final Context context, final String packagePath, final String prefsBranch) {
|
||||
this.context = context;
|
||||
this.packagePath = packagePath;
|
||||
this.prefsBranch = prefsBranch;
|
||||
}
|
||||
|
||||
public Distribution(final Context context) {
|
||||
this(context, context.getPackageResourcePath(), DEFAULT_PREFS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't call from the main thread.
|
||||
*
|
||||
* @return true if we've set a distribution.
|
||||
*/
|
||||
private boolean doInit() {
|
||||
// Bail if we've already tried to initialize the distribution, and
|
||||
// there wasn't one.
|
||||
SharedPreferences settings = context.getSharedPreferences(prefsBranch, Activity.MODE_PRIVATE);
|
||||
String keyName = context.getPackageName() + ".distribution_state";
|
||||
this.state = settings.getInt(keyName, STATE_UNKNOWN);
|
||||
if (this.state == STATE_NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We've done the work once; don't do it again.
|
||||
if (this.state == STATE_SET) {
|
||||
// Note that we don't compute the distribution directory.
|
||||
// Call `ensureDistributionDir` if you need it.
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean distributionSet = false;
|
||||
try {
|
||||
// First, try copying distribution files out of the APK.
|
||||
distributionSet = copyFiles();
|
||||
if (distributionSet) {
|
||||
// We always copy to the data dir, and we only copy files from
|
||||
// a 'distribution' subdirectory. Track our dist dir now that
|
||||
// we know it.
|
||||
this.distributionDir = new File(getDataDir(), "distribution/");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(LOGTAG, "Error copying distribution files", e);
|
||||
}
|
||||
|
||||
if (!distributionSet) {
|
||||
// If there aren't any distribution files in the APK, look in the /system directory.
|
||||
File distDir = getSystemDistributionDir();
|
||||
if (distDir.exists()) {
|
||||
distributionSet = true;
|
||||
this.distributionDir = distDir;
|
||||
}
|
||||
}
|
||||
|
||||
this.state = distributionSet ? STATE_SET : STATE_NONE;
|
||||
settings.edit().putInt(keyName, this.state).commit();
|
||||
return distributionSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the /distribution folder out of the APK and into the app's data directory.
|
||||
* Returns true if distribution files were found and copied.
|
||||
*/
|
||||
private static boolean copyFiles(Context context, String packagePath) throws IOException {
|
||||
private boolean copyFiles() throws IOException {
|
||||
File applicationPackage = new File(packagePath);
|
||||
ZipFile zip = new ZipFile(applicationPackage);
|
||||
|
||||
boolean distributionSet = false;
|
||||
Enumeration<? extends ZipEntry> zipEntries = zip.entries();
|
||||
|
||||
byte[] buffer = new byte[1024];
|
||||
while (zipEntries.hasMoreElements()) {
|
||||
ZipEntry fileEntry = zipEntries.nextElement();
|
||||
String name = fileEntry.getName();
|
||||
|
||||
if (!name.startsWith("distribution/"))
|
||||
if (!name.startsWith("distribution/")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
distributionSet = true;
|
||||
|
||||
File dataDir = new File(context.getApplicationInfo().dataDir);
|
||||
File outFile = new File(dataDir, name);
|
||||
|
||||
File outFile = new File(getDataDir(), name);
|
||||
File dir = outFile.getParentFile();
|
||||
if (!dir.exists())
|
||||
dir.mkdirs();
|
||||
|
||||
if (!dir.exists()) {
|
||||
if (!dir.mkdirs()) {
|
||||
Log.e(LOGTAG, "Unable to create directories: " + dir.getAbsolutePath());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
InputStream fileStream = zip.getInputStream(fileEntry);
|
||||
OutputStream outStream = new FileOutputStream(outFile);
|
||||
|
||||
int b;
|
||||
while ((b = fileStream.read()) != -1)
|
||||
outStream.write(b);
|
||||
int count;
|
||||
while ((count = fileStream.read(buffer)) != -1) {
|
||||
outStream.write(buffer, 0, count);
|
||||
}
|
||||
|
||||
fileStream.close();
|
||||
outStream.close();
|
||||
@ -132,77 +239,125 @@ public final class Distribution {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns parsed contents of bookmarks.json.
|
||||
* This method should only be called from a background thread.
|
||||
* After calling this method, either <code>distributionDir</code>
|
||||
* will be set, or there is no distribution in use.
|
||||
*
|
||||
* Only call after init.
|
||||
*/
|
||||
public static JSONArray getBookmarks(Context context) {
|
||||
SharedPreferences settings = context.getSharedPreferences(GeckoApp.PREFS_NAME, Activity.MODE_PRIVATE);
|
||||
String keyName = context.getPackageName() + ".distribution_state";
|
||||
int state = settings.getInt(keyName, STATE_UNKNOWN);
|
||||
if (state == STATE_NONE) {
|
||||
private File ensureDistributionDir() {
|
||||
if (this.distributionDir != null) {
|
||||
return this.distributionDir;
|
||||
}
|
||||
|
||||
if (this.state != STATE_SET) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// After init, we know that either we've copied a distribution out of
|
||||
// the APK, or it exists in /system/.
|
||||
// Look in each location in turn.
|
||||
// (This could be optimized by caching the path in shared prefs.)
|
||||
File copied = new File(getDataDir(), "distribution/");
|
||||
if (copied.exists()) {
|
||||
return this.distributionDir = copied;
|
||||
}
|
||||
File system = getSystemDistributionDir();
|
||||
if (system.exists()) {
|
||||
return this.distributionDir = system;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to grab a file in the distribution directory.
|
||||
*
|
||||
* Returns null if there is no distribution directory or the file
|
||||
* doesn't exist. Ensures init first.
|
||||
*/
|
||||
private File getDistributionFile(String name) {
|
||||
Log.i(LOGTAG, "Getting file from distribution.");
|
||||
if (this.state == STATE_UNKNOWN) {
|
||||
if (!this.doInit()) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
File dist = ensureDistributionDir();
|
||||
if (dist == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
File descFile = new File(dist, name);
|
||||
if (!descFile.exists()) {
|
||||
Log.e(LOGTAG, "Distribution directory exists, but no file named " + name);
|
||||
return null;
|
||||
}
|
||||
|
||||
return descFile;
|
||||
}
|
||||
|
||||
public DistributionDescriptor getDescriptor() {
|
||||
File descFile = getDistributionFile("preferences.json");
|
||||
if (descFile == null) {
|
||||
// Logging and existence checks are handled in getDistributionFile.
|
||||
return null;
|
||||
}
|
||||
|
||||
ZipFile zip = null;
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
if (state == STATE_UNKNOWN) {
|
||||
// If the distribution hasn't been set yet, first look for bookmarks.json in the APK.
|
||||
File applicationPackage = new File(context.getPackageResourcePath());
|
||||
zip = new ZipFile(applicationPackage);
|
||||
ZipEntry zipEntry = zip.getEntry("distribution/bookmarks.json");
|
||||
if (zipEntry != null) {
|
||||
inputStream = zip.getInputStream(zipEntry);
|
||||
} else {
|
||||
// If there's no bookmarks.json in the APK, but there is a preferences.json,
|
||||
// don't create any distribution bookmarks.
|
||||
zipEntry = zip.getEntry("distribution/preferences.json");
|
||||
if (zipEntry != null) {
|
||||
return null;
|
||||
}
|
||||
// Otherwise, look for bookmarks.json in the /system directory.
|
||||
File systemFile = new File("/system/" + context.getPackageName() + "/distribution/bookmarks.json");
|
||||
if (!systemFile.exists()) {
|
||||
return null;
|
||||
}
|
||||
inputStream = new FileInputStream(systemFile);
|
||||
}
|
||||
} else {
|
||||
// Otherwise, first look for the distribution in the data directory.
|
||||
File distDir = new File(context.getApplicationInfo().dataDir, "distribution");
|
||||
if (!distDir.exists()) {
|
||||
// If that doesn't exist, then we must be using a distribution from the system directory.
|
||||
distDir = new File("/system/" + context.getPackageName() + "/distribution");
|
||||
}
|
||||
JSONObject all = new JSONObject(getFileContents(descFile));
|
||||
|
||||
File file = new File(distDir, "bookmarks.json");
|
||||
inputStream = new FileInputStream(file);
|
||||
if (!all.has("Global")) {
|
||||
Log.e(LOGTAG, "Distribution preferences.json has no Global entry!");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Convert input stream to JSONArray
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
String s;
|
||||
while ((s = reader.readLine()) != null) {
|
||||
stringBuilder.append(s);
|
||||
}
|
||||
return new JSONArray(stringBuilder.toString());
|
||||
return new DistributionDescriptor(all.getJSONObject("Global"));
|
||||
|
||||
} catch (IOException e) {
|
||||
Log.e(LOGTAG, "Error getting distribution descriptor file.", e);
|
||||
return null;
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error parsing preferences.json", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public JSONArray getBookmarks() {
|
||||
File bookmarks = getDistributionFile("bookmarks.json");
|
||||
if (bookmarks == null) {
|
||||
// Logging and existence checks are handled in getDistributionFile.
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return new JSONArray(getFileContents(bookmarks));
|
||||
} catch (IOException e) {
|
||||
Log.e(LOGTAG, "Error getting bookmarks", e);
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error parsing bookmarks.json", e);
|
||||
} finally {
|
||||
try {
|
||||
if (zip != null) {
|
||||
zip.close();
|
||||
}
|
||||
if (inputStream != null) {
|
||||
inputStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(LOGTAG, "Error closing streams", e);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Shortcut to slurp a file without messing around with streams.
|
||||
private String getFileContents(File file) throws IOException {
|
||||
Scanner scanner = null;
|
||||
try {
|
||||
scanner = new Scanner(file, "UTF-8");
|
||||
return scanner.useDelimiter("\\A").next();
|
||||
} finally {
|
||||
if (scanner != null) {
|
||||
scanner.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getDataDir() {
|
||||
return context.getApplicationInfo().dataDir;
|
||||
}
|
||||
|
||||
private File getSystemDistributionDir() {
|
||||
return new File("/system/" + context.getPackageName() + "/distribution");
|
||||
}
|
||||
}
|
||||
|
@ -117,6 +117,7 @@ import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
@ -1291,7 +1292,16 @@ abstract public class GeckoApp
|
||||
final String profilePath = getProfile().getDir().getAbsolutePath();
|
||||
final EventDispatcher dispatcher = GeckoAppShell.getEventDispatcher();
|
||||
Log.i(LOGTAG, "Creating BrowserHealthRecorder.");
|
||||
mHealthRecorder = new BrowserHealthRecorder(GeckoApp.this, profilePath, dispatcher,
|
||||
final String osLocale = Locale.getDefault().toString();
|
||||
Log.d(LOGTAG, "Locale is " + osLocale);
|
||||
|
||||
// Replace the duplicate `osLocale` argument when we support switchable
|
||||
// application locales.
|
||||
mHealthRecorder = new BrowserHealthRecorder(GeckoApp.this,
|
||||
profilePath,
|
||||
dispatcher,
|
||||
osLocale,
|
||||
osLocale, // Placeholder.
|
||||
previousSession);
|
||||
}
|
||||
});
|
||||
@ -1555,8 +1565,15 @@ abstract public class GeckoApp
|
||||
GeckoPreferences.broadcastHealthReportUploadPref(context);
|
||||
|
||||
/*
|
||||
XXXX see bug 635342
|
||||
We want to disable this code if possible. It is about 145ms in runtime
|
||||
XXXX see Bug 635342.
|
||||
We want to disable this code if possible. It is about 145ms in runtime.
|
||||
|
||||
If this code ever becomes live again, you'll need to chain the
|
||||
new locale into BrowserHealthRecorder correctly. See
|
||||
GeckoAppShell.setSelectedLocale.
|
||||
We pass the OS locale into the BHR constructor: we need to grab
|
||||
that *before* we modify the current locale!
|
||||
|
||||
SharedPreferences settings = getPreferences(Activity.MODE_PRIVATE);
|
||||
String localeCode = settings.getString(getPackageName() + ".locale", "");
|
||||
if (localeCode != null && localeCode.length() > 0)
|
||||
|
@ -1525,6 +1525,12 @@ public class GeckoAppShell
|
||||
Gecko resets the locale to en-US by calling this function with an empty string.
|
||||
This affects GeckoPreferences activity in multi-locale builds.
|
||||
|
||||
N.B., if this code ever becomes live again, you need to hook it up to locale
|
||||
recording in BrowserHealthRecorder: we track the current app and OS locales
|
||||
as part of the recorded environment.
|
||||
|
||||
See similar note in GeckoApp.java for the startup path.
|
||||
|
||||
//We're not using this, not need to save it (see bug 635342)
|
||||
SharedPreferences settings =
|
||||
getContext().getPreferences(Activity.MODE_PRIVATE);
|
||||
|
@ -313,8 +313,13 @@ public final class GeckoProfile {
|
||||
}
|
||||
|
||||
public synchronized File getDir() {
|
||||
forceCreate();
|
||||
return mDir;
|
||||
}
|
||||
|
||||
public synchronized GeckoProfile forceCreate() {
|
||||
if (mDir != null) {
|
||||
return mDir;
|
||||
return this;
|
||||
}
|
||||
|
||||
try {
|
||||
@ -330,7 +335,7 @@ public final class GeckoProfile {
|
||||
} catch (IOException ioe) {
|
||||
Log.e(LOGTAG, "Error getting profile dir", ioe);
|
||||
}
|
||||
return mDir;
|
||||
return this;
|
||||
}
|
||||
|
||||
public File getFile(String aFile) {
|
||||
|
@ -55,6 +55,7 @@ public class GeckoView extends LayerView
|
||||
|
||||
Clipboard.init(context);
|
||||
HardwareUtils.init(context);
|
||||
GeckoNetworkManager.getInstance().init(context);
|
||||
|
||||
GeckoLoader.loadMozGlue();
|
||||
BrowserDB.setEnableContentProviders(false);
|
||||
@ -75,7 +76,7 @@ public class GeckoView extends LayerView
|
||||
ThreadUtils.setUiThread(Thread.currentThread(), new Handler());
|
||||
initializeView(GeckoAppShell.getEventDispatcher());
|
||||
|
||||
GeckoProfile profile = GeckoProfile.get(context);
|
||||
GeckoProfile profile = GeckoProfile.get(context).forceCreate();
|
||||
BrowserDB.initialize(profile.getName());
|
||||
|
||||
if (GeckoThread.checkAndSetLaunchState(GeckoThread.LaunchState.Launching, GeckoThread.LaunchState.Launched)) {
|
||||
|
@ -416,789 +416,12 @@ ICON_PATH = $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/content/icon48.png
|
||||
ICON_PATH_HDPI = $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/content/icon64.png
|
||||
endif
|
||||
|
||||
RES_LAYOUT = \
|
||||
$(SYNC_RES_LAYOUT) \
|
||||
res/layout/arrow_popup.xml \
|
||||
res/layout/autocomplete_list.xml \
|
||||
res/layout/autocomplete_list_item.xml \
|
||||
res/layout/bookmark_edit.xml \
|
||||
res/layout/bookmark_folder_row.xml \
|
||||
res/layout/bookmark_item_row.xml \
|
||||
res/layout/browser_search.xml \
|
||||
res/layout/browser_toolbar.xml \
|
||||
res/layout/datetime_picker.xml \
|
||||
res/layout/doorhanger.xml \
|
||||
res/layout/doorhanger_button.xml \
|
||||
res/layout/find_in_page_content.xml \
|
||||
res/layout/font_size_preference.xml \
|
||||
res/layout/gecko_app.xml \
|
||||
res/layout/home_bookmarks_page.xml \
|
||||
res/layout/home_empty_page.xml \
|
||||
res/layout/home_empty_reading_page.xml \
|
||||
res/layout/home_item_row.xml \
|
||||
res/layout/home_header_row.xml \
|
||||
res/layout/home_history_page.xml \
|
||||
res/layout/home_history_tabs_indicator.xml \
|
||||
res/layout/home_last_tabs_page.xml \
|
||||
res/layout/home_history_list.xml \
|
||||
res/layout/home_most_recent_page.xml \
|
||||
res/layout/home_pager.xml \
|
||||
res/layout/home_reading_list_page.xml \
|
||||
res/layout/home_search_item_row.xml \
|
||||
res/layout/home_banner.xml \
|
||||
res/layout/home_suggestion_prompt.xml \
|
||||
res/layout/home_top_sites_page.xml \
|
||||
res/layout/icon_grid.xml \
|
||||
res/layout/icon_grid_item.xml \
|
||||
res/layout/web_app.xml \
|
||||
res/layout/launch_app_list.xml \
|
||||
res/layout/launch_app_listitem.xml \
|
||||
res/layout/menu_action_bar.xml \
|
||||
res/layout/menu_item_action_view.xml \
|
||||
res/layout/menu_popup.xml \
|
||||
res/layout/notification_icon_text.xml \
|
||||
res/layout/notification_progress.xml \
|
||||
res/layout/notification_progress_text.xml \
|
||||
res/layout/pin_site_dialog.xml \
|
||||
res/layout/preference_rightalign_icon.xml \
|
||||
res/layout/preference_search_engine.xml \
|
||||
res/layout/preference_search_tip.xml \
|
||||
res/layout/site_setting_item.xml \
|
||||
res/layout/site_setting_title.xml \
|
||||
res/layout/shared_ui_components.xml \
|
||||
res/layout/site_identity.xml \
|
||||
res/layout/remote_tabs_child.xml \
|
||||
res/layout/remote_tabs_group.xml \
|
||||
res/layout/search_engine_row.xml \
|
||||
res/layout/tab_menu_strip.xml \
|
||||
res/layout/tabs_panel.xml \
|
||||
res/layout/tabs_counter.xml \
|
||||
res/layout/tabs_panel_header.xml \
|
||||
res/layout/tabs_panel_indicator.xml \
|
||||
res/layout/tabs_item_cell.xml \
|
||||
res/layout/tabs_item_row.xml \
|
||||
res/layout/text_selection_handles.xml \
|
||||
res/layout/top_sites_grid_item_view.xml \
|
||||
res/layout/two_line_page_row.xml \
|
||||
res/layout/list_item_header.xml \
|
||||
res/layout/select_dialog_list.xml \
|
||||
res/layout/select_dialog_multichoice.xml \
|
||||
res/layout/select_dialog_singlechoice.xml \
|
||||
res/layout/simple_dropdown_item_1line.xml \
|
||||
res/layout/suggestion_item.xml \
|
||||
res/layout/validation_message.xml \
|
||||
res/layout/videoplayer.xml \
|
||||
$(NULL)
|
||||
|
||||
RES_LAYOUT_LARGE_V11 = \
|
||||
res/layout-large-v11/browser_toolbar.xml \
|
||||
res/layout-large-v11/home_pager.xml \
|
||||
$(NULL)
|
||||
|
||||
RES_LAYOUT_LARGE_LAND_V11 = \
|
||||
res/layout-large-land-v11/home_history_page.xml \
|
||||
res/layout-large-land-v11/home_history_tabs_indicator.xml \
|
||||
res/layout-large-land-v11/home_history_list.xml \
|
||||
res/layout-large-land-v11/tabs_panel.xml \
|
||||
res/layout-large-land-v11/tabs_panel_header.xml \
|
||||
res/layout-large-land-v11/tabs_panel_footer.xml \
|
||||
$(NULL)
|
||||
|
||||
RES_LAYOUT_XLARGE_V11 = \
|
||||
res/layout-xlarge-v11/font_size_preference.xml \
|
||||
res/layout-xlarge-v11/home_history_page.xml \
|
||||
res/layout-xlarge-v11/home_history_tabs_indicator.xml \
|
||||
res/layout-xlarge-v11/home_history_list.xml \
|
||||
res/layout-xlarge-v11/remote_tabs_child.xml \
|
||||
res/layout-xlarge-v11/remote_tabs_group.xml \
|
||||
$(NULL)
|
||||
|
||||
RES_VALUES = \
|
||||
$(SYNC_RES_VALUES) \
|
||||
res/values/attrs.xml \
|
||||
res/values/arrays.xml \
|
||||
res/values/colors.xml \
|
||||
res/values/dimens.xml \
|
||||
res/values/integers.xml \
|
||||
res/values/layout.xml \
|
||||
res/values/styles.xml \
|
||||
res/values/themes.xml \
|
||||
$(NULL)
|
||||
|
||||
RES_VALUES_LAND = \
|
||||
res/values-land/integers.xml \
|
||||
res/values-land/layout.xml \
|
||||
res/values-land/styles.xml \
|
||||
$(NULL)
|
||||
|
||||
RES_VALUES_V11 = \
|
||||
$(SYNC_RES_VALUES_V11) \
|
||||
res/values-v11/colors.xml \
|
||||
res/values-v11/dimens.xml \
|
||||
res/values-v11/styles.xml \
|
||||
res/values-v11/themes.xml \
|
||||
$(NULL)
|
||||
|
||||
RES_VALUES_LARGE_V11 = \
|
||||
$(SYNC_RES_VALUES_LARGE_V11) \
|
||||
res/values-large-v11/dimens.xml \
|
||||
res/values-large-v11/layout.xml \
|
||||
res/values-large-v11/styles.xml \
|
||||
res/values-large-v11/themes.xml \
|
||||
$(NULL)
|
||||
|
||||
RES_VALUES_LARGE_LAND_V11 = \
|
||||
res/values-large-land-v11/dimens.xml \
|
||||
res/values-large-land-v11/styles.xml \
|
||||
$(NULL)
|
||||
|
||||
RES_VALUES_XLARGE_V11 = \
|
||||
res/values-xlarge-v11/dimens.xml \
|
||||
res/values-xlarge-v11/integers.xml \
|
||||
res/values-xlarge-v11/styles.xml \
|
||||
$(NULL)
|
||||
|
||||
RES_VALUES_XLARGE_LAND_V11 = \
|
||||
res/values-xlarge-land-v11/dimens.xml \
|
||||
res/values-xlarge-land-v11/styles.xml \
|
||||
$(NULL)
|
||||
|
||||
RES_VALUES_V14 = \
|
||||
res/values-v14/styles.xml \
|
||||
$(NULL)
|
||||
|
||||
RES_VALUES_V16 = \
|
||||
res/values-v16/styles.xml \
|
||||
$(NULL)
|
||||
|
||||
RES_XML = \
|
||||
res/xml/preferences.xml \
|
||||
res/xml/preferences_customize.xml \
|
||||
res/xml/preferences_display.xml \
|
||||
res/xml/preferences_search.xml \
|
||||
res/xml/preferences_privacy.xml \
|
||||
res/xml/preferences_vendor.xml \
|
||||
res/xml/preferences_devtools.xml \
|
||||
res/xml/searchable.xml \
|
||||
$(SYNC_RES_XML) \
|
||||
$(NULL)
|
||||
|
||||
RES_XML_V11 = \
|
||||
res/xml-v11/preferences_customize.xml \
|
||||
res/xml-v11/preference_headers.xml \
|
||||
res/xml-v11/preferences_customize_tablet.xml \
|
||||
res/xml-v11/preferences.xml \
|
||||
$(NULL)
|
||||
|
||||
RES_ANIM = \
|
||||
res/anim/popup_show.xml \
|
||||
res/anim/popup_hide.xml \
|
||||
res/anim/grow_fade_in.xml \
|
||||
res/anim/grow_fade_in_center.xml \
|
||||
res/anim/progress_spinner.xml \
|
||||
res/anim/shrink_fade_out.xml \
|
||||
$(NULL)
|
||||
|
||||
RES_DRAWABLE_MDPI = \
|
||||
$(SYNC_RES_DRAWABLE_MDPI) \
|
||||
res/drawable-mdpi/blank.png \
|
||||
res/drawable-mdpi/favicon.png \
|
||||
res/drawable-mdpi/folder.png \
|
||||
res/drawable-mdpi/abouthome_thumbnail.png \
|
||||
res/drawable-mdpi/alert_addon.png \
|
||||
res/drawable-mdpi/alert_app.png \
|
||||
res/drawable-mdpi/alert_download.png \
|
||||
res/drawable-mdpi/alert_camera.png \
|
||||
res/drawable-mdpi/alert_mic.png \
|
||||
res/drawable-mdpi/alert_mic_camera.png \
|
||||
res/drawable-mdpi/arrow_popup_bg.9.png \
|
||||
res/drawable-mdpi/autocomplete_list_bg.9.png \
|
||||
res/drawable-mdpi/bookmark_folder_closed.png \
|
||||
res/drawable-mdpi/bookmark_folder_opened.png \
|
||||
res/drawable-mdpi/desktop_notification.png \
|
||||
res/drawable-mdpi/grid_icon_bg_activated.9.png \
|
||||
res/drawable-mdpi/grid_icon_bg_focused.9.png \
|
||||
res/drawable-mdpi/home_tab_menu_strip.9.png \
|
||||
res/drawable-mdpi/ic_menu_addons_filler.png \
|
||||
res/drawable-mdpi/ic_menu_bookmark_add.png \
|
||||
res/drawable-mdpi/ic_menu_bookmark_remove.png \
|
||||
res/drawable-mdpi/ic_menu_character_encoding.png \
|
||||
res/drawable-mdpi/close.png \
|
||||
res/drawable-mdpi/ic_menu_forward.png \
|
||||
res/drawable-mdpi/ic_menu_guest.png \
|
||||
res/drawable-mdpi/ic_menu_new_private_tab.png \
|
||||
res/drawable-mdpi/ic_menu_new_tab.png \
|
||||
res/drawable-mdpi/ic_menu_reload.png \
|
||||
res/drawable-mdpi/ic_status_logo.png \
|
||||
res/drawable-mdpi/ic_url_bar_go.png \
|
||||
res/drawable-mdpi/ic_url_bar_reader.png \
|
||||
res/drawable-mdpi/ic_url_bar_search.png \
|
||||
res/drawable-mdpi/ic_url_bar_star.png \
|
||||
res/drawable-mdpi/ic_url_bar_tab.png \
|
||||
res/drawable-mdpi/icon_bookmarks_empty.png \
|
||||
res/drawable-mdpi/icon_last_tabs.png \
|
||||
res/drawable-mdpi/icon_last_tabs_empty.png \
|
||||
res/drawable-mdpi/icon_most_recent.png \
|
||||
res/drawable-mdpi/icon_most_recent_empty.png \
|
||||
res/drawable-mdpi/icon_most_visited.png \
|
||||
res/drawable-mdpi/icon_openinapp.png \
|
||||
res/drawable-mdpi/icon_pageaction.png \
|
||||
res/drawable-mdpi/icon_reading_list_empty.png \
|
||||
res/drawable-mdpi/progress_spinner.png \
|
||||
res/drawable-mdpi/play.png \
|
||||
res/drawable-mdpi/pause.png \
|
||||
res/drawable-mdpi/tab_indicator_divider.9.png \
|
||||
res/drawable-mdpi/tab_indicator_selected.9.png \
|
||||
res/drawable-mdpi/tab_indicator_selected_focused.9.png \
|
||||
res/drawable-mdpi/spinner_default.9.png \
|
||||
res/drawable-mdpi/spinner_focused.9.png \
|
||||
res/drawable-mdpi/spinner_pressed.9.png \
|
||||
res/drawable-mdpi/tab_new.png \
|
||||
res/drawable-mdpi/tab_new_pb.png \
|
||||
res/drawable-mdpi/tab_close.png \
|
||||
res/drawable-mdpi/tab_thumbnail_default.png \
|
||||
res/drawable-mdpi/tab_thumbnail_shadow.png \
|
||||
res/drawable-mdpi/tabs_count.png \
|
||||
res/drawable-mdpi/tabs_count_foreground.png \
|
||||
res/drawable-mdpi/url_bar_entry_default.9.png \
|
||||
res/drawable-mdpi/url_bar_entry_default_pb.9.png \
|
||||
res/drawable-mdpi/url_bar_entry_pressed.9.png \
|
||||
res/drawable-mdpi/url_bar_entry_pressed_pb.9.png \
|
||||
res/drawable-mdpi/tip_addsearch.png \
|
||||
res/drawable-mdpi/toast.9.png \
|
||||
res/drawable-mdpi/toast_button_focused.9.png \
|
||||
res/drawable-mdpi/toast_button_pressed.9.png \
|
||||
res/drawable-mdpi/toast_divider.9.png \
|
||||
res/drawable-mdpi/find_close.png \
|
||||
res/drawable-mdpi/find_next.png \
|
||||
res/drawable-mdpi/find_prev.png \
|
||||
res/drawable-mdpi/larry.png \
|
||||
res/drawable-mdpi/lock_identified.png \
|
||||
res/drawable-mdpi/lock_verified.png \
|
||||
res/drawable-mdpi/menu.png \
|
||||
res/drawable-mdpi/menu_pb.png \
|
||||
res/drawable-mdpi/menu_panel_bg.9.png \
|
||||
res/drawable-mdpi/menu_popup_bg.9.png \
|
||||
res/drawable-mdpi/menu_popup_arrow_bottom.png \
|
||||
res/drawable-mdpi/menu_popup_arrow_top.png \
|
||||
res/drawable-mdpi/menu_item_check.png \
|
||||
res/drawable-mdpi/menu_item_more.png \
|
||||
res/drawable-mdpi/menu_item_uncheck.png \
|
||||
res/drawable-mdpi/pin.png \
|
||||
res/drawable-mdpi/shield.png \
|
||||
res/drawable-mdpi/shield_doorhanger.png \
|
||||
res/drawable-mdpi/tabs_normal.png \
|
||||
res/drawable-mdpi/tabs_private.png \
|
||||
res/drawable-mdpi/tabs_synced.png \
|
||||
res/drawable-mdpi/top_site_add.png \
|
||||
res/drawable-mdpi/urlbar_stop.png \
|
||||
res/drawable-mdpi/reader.png \
|
||||
res/drawable-mdpi/reader_cropped.png \
|
||||
res/drawable-mdpi/reader_active.png \
|
||||
res/drawable-mdpi/reading_list.png \
|
||||
res/drawable-mdpi/validation_arrow.png \
|
||||
res/drawable-mdpi/validation_arrow_inverted.png \
|
||||
res/drawable-mdpi/validation_bg.9.png \
|
||||
res/drawable-mdpi/bookmarkdefaults_favicon_support.png \
|
||||
res/drawable-mdpi/bookmarkdefaults_favicon_addons.png \
|
||||
res/drawable-mdpi/handle_end.png \
|
||||
res/drawable-mdpi/handle_middle.png \
|
||||
res/drawable-mdpi/handle_start.png \
|
||||
res/drawable-mdpi/scrollbar.png \
|
||||
res/drawable-mdpi/shadow.png \
|
||||
res/drawable-mdpi/start.png \
|
||||
res/drawable-mdpi/marketplace.png \
|
||||
res/drawable-mdpi/history_tabs_indicator_selected.9.png \
|
||||
res/drawable-mdpi/warning.png \
|
||||
res/drawable-mdpi/warning_doorhanger.png \
|
||||
$(NULL)
|
||||
|
||||
RES_DRAWABLE_LDPI = \
|
||||
$(SYNC_RES_DRAWABLE_LDPI) \
|
||||
$(NULL)
|
||||
|
||||
RES_DRAWABLE_HDPI = \
|
||||
$(SYNC_RES_DRAWABLE_HDPI) \
|
||||
res/drawable-hdpi/blank.png \
|
||||
res/drawable-hdpi/favicon.png \
|
||||
res/drawable-hdpi/folder.png \
|
||||
res/drawable-hdpi/home_bg.png \
|
||||
res/drawable-hdpi/home_star.png \
|
||||
res/drawable-hdpi/grid_icon_bg_activated.9.png \
|
||||
res/drawable-hdpi/grid_icon_bg_focused.9.png \
|
||||
res/drawable-hdpi/abouthome_thumbnail.png \
|
||||
res/drawable-hdpi/alert_addon.png \
|
||||
res/drawable-hdpi/alert_app.png \
|
||||
res/drawable-hdpi/alert_download.png \
|
||||
res/drawable-hdpi/bookmark_folder_closed.png \
|
||||
res/drawable-hdpi/bookmark_folder_opened.png \
|
||||
res/drawable-hdpi/alert_camera.png \
|
||||
res/drawable-hdpi/alert_mic.png \
|
||||
res/drawable-hdpi/alert_mic_camera.png \
|
||||
res/drawable-hdpi/arrow_popup_bg.9.png \
|
||||
res/drawable-hdpi/home_tab_menu_strip.9.png \
|
||||
res/drawable-hdpi/ic_menu_addons_filler.png \
|
||||
res/drawable-hdpi/ic_menu_bookmark_add.png \
|
||||
res/drawable-hdpi/ic_menu_bookmark_remove.png \
|
||||
res/drawable-hdpi/ic_menu_character_encoding.png \
|
||||
res/drawable-hdpi/close.png \
|
||||
res/drawable-hdpi/ic_menu_forward.png \
|
||||
res/drawable-hdpi/ic_menu_guest.png \
|
||||
res/drawable-hdpi/ic_menu_new_private_tab.png \
|
||||
res/drawable-hdpi/ic_menu_new_tab.png \
|
||||
res/drawable-hdpi/ic_menu_reload.png \
|
||||
res/drawable-hdpi/ic_status_logo.png \
|
||||
res/drawable-hdpi/ic_url_bar_go.png \
|
||||
res/drawable-hdpi/ic_url_bar_reader.png \
|
||||
res/drawable-hdpi/ic_url_bar_search.png \
|
||||
res/drawable-hdpi/ic_url_bar_star.png \
|
||||
res/drawable-hdpi/ic_url_bar_tab.png \
|
||||
res/drawable-hdpi/icon_bookmarks_empty.png \
|
||||
res/drawable-hdpi/icon_last_tabs.png \
|
||||
res/drawable-hdpi/icon_last_tabs_empty.png \
|
||||
res/drawable-hdpi/icon_most_recent.png \
|
||||
res/drawable-hdpi/icon_most_recent_empty.png \
|
||||
res/drawable-hdpi/icon_most_visited.png \
|
||||
res/drawable-hdpi/icon_openinapp.png \
|
||||
res/drawable-hdpi/icon_pageaction.png \
|
||||
res/drawable-hdpi/icon_reading_list_empty.png \
|
||||
res/drawable-hdpi/tab_indicator_divider.9.png \
|
||||
res/drawable-hdpi/tab_indicator_selected.9.png \
|
||||
res/drawable-hdpi/tab_indicator_selected_focused.9.png \
|
||||
res/drawable-hdpi/spinner_default.9.png \
|
||||
res/drawable-hdpi/spinner_focused.9.png \
|
||||
res/drawable-hdpi/spinner_pressed.9.png \
|
||||
res/drawable-hdpi/tab_new.png \
|
||||
res/drawable-hdpi/tab_new_pb.png \
|
||||
res/drawable-hdpi/tab_close.png \
|
||||
res/drawable-hdpi/tab_thumbnail_default.png \
|
||||
res/drawable-hdpi/tab_thumbnail_shadow.png \
|
||||
res/drawable-hdpi/tabs_count.png \
|
||||
res/drawable-hdpi/tabs_count_foreground.png \
|
||||
res/drawable-hdpi/url_bar_entry_default.9.png \
|
||||
res/drawable-hdpi/url_bar_entry_default_pb.9.png \
|
||||
res/drawable-hdpi/url_bar_entry_pressed.9.png \
|
||||
res/drawable-hdpi/url_bar_entry_pressed_pb.9.png \
|
||||
res/drawable-hdpi/tip_addsearch.png \
|
||||
res/drawable-hdpi/find_close.png \
|
||||
res/drawable-hdpi/find_next.png \
|
||||
res/drawable-hdpi/find_prev.png \
|
||||
res/drawable-hdpi/larry.png \
|
||||
res/drawable-hdpi/lock_identified.png \
|
||||
res/drawable-hdpi/lock_verified.png \
|
||||
res/drawable-hdpi/menu.png \
|
||||
res/drawable-hdpi/menu_pb.png \
|
||||
res/drawable-hdpi/menu_panel_bg.9.png \
|
||||
res/drawable-hdpi/menu_popup_bg.9.png \
|
||||
res/drawable-hdpi/menu_popup_arrow_bottom.png \
|
||||
res/drawable-hdpi/menu_popup_arrow_top.png \
|
||||
res/drawable-hdpi/menu_item_check.png \
|
||||
res/drawable-hdpi/menu_item_more.png \
|
||||
res/drawable-hdpi/menu_item_uncheck.png \
|
||||
res/drawable-hdpi/pin.png \
|
||||
res/drawable-hdpi/play.png \
|
||||
res/drawable-hdpi/pause.png \
|
||||
res/drawable-hdpi/shield.png \
|
||||
res/drawable-hdpi/shield_doorhanger.png \
|
||||
res/drawable-hdpi/tabs_normal.png \
|
||||
res/drawable-hdpi/tabs_private.png \
|
||||
res/drawable-hdpi/tabs_synced.png \
|
||||
res/drawable-hdpi/top_site_add.png \
|
||||
res/drawable-hdpi/urlbar_stop.png \
|
||||
res/drawable-hdpi/reader.png \
|
||||
res/drawable-hdpi/reader_cropped.png \
|
||||
res/drawable-hdpi/reader_active.png \
|
||||
res/drawable-hdpi/reading_list.png \
|
||||
res/drawable-hdpi/validation_arrow.png \
|
||||
res/drawable-hdpi/validation_arrow_inverted.png \
|
||||
res/drawable-hdpi/validation_bg.9.png \
|
||||
res/drawable-hdpi/handle_end.png \
|
||||
res/drawable-hdpi/handle_middle.png \
|
||||
res/drawable-hdpi/handle_start.png \
|
||||
res/drawable-hdpi/history_tabs_indicator_selected.9.png \
|
||||
res/drawable-hdpi/warning.png \
|
||||
res/drawable-hdpi/warning_doorhanger.png \
|
||||
$(NULL)
|
||||
|
||||
RES_DRAWABLE_XHDPI = \
|
||||
res/drawable-xhdpi/blank.png \
|
||||
res/drawable-xhdpi/favicon.png \
|
||||
res/drawable-xhdpi/folder.png \
|
||||
res/drawable-xhdpi/abouthome_thumbnail.png \
|
||||
res/drawable-xhdpi/url_bar_entry_default.9.png \
|
||||
res/drawable-xhdpi/url_bar_entry_default_pb.9.png \
|
||||
res/drawable-xhdpi/url_bar_entry_pressed.9.png \
|
||||
res/drawable-xhdpi/url_bar_entry_pressed_pb.9.png \
|
||||
res/drawable-xhdpi/alert_addon.png \
|
||||
res/drawable-xhdpi/alert_app.png \
|
||||
res/drawable-xhdpi/alert_download.png \
|
||||
res/drawable-xhdpi/bookmark_folder_closed.png \
|
||||
res/drawable-xhdpi/bookmark_folder_opened.png \
|
||||
res/drawable-xhdpi/alert_camera.png \
|
||||
res/drawable-xhdpi/alert_mic.png \
|
||||
res/drawable-xhdpi/alert_mic_camera.png \
|
||||
res/drawable-xhdpi/arrow_popup_bg.9.png \
|
||||
res/drawable-xhdpi/home_tab_menu_strip.9.png \
|
||||
res/drawable-xhdpi/grid_icon_bg_activated.9.png \
|
||||
res/drawable-xhdpi/grid_icon_bg_focused.9.png \
|
||||
res/drawable-xhdpi/ic_menu_addons_filler.png \
|
||||
res/drawable-xhdpi/ic_menu_bookmark_add.png \
|
||||
res/drawable-xhdpi/ic_menu_bookmark_remove.png \
|
||||
res/drawable-xhdpi/close.png \
|
||||
res/drawable-xhdpi/ic_menu_character_encoding.png \
|
||||
res/drawable-xhdpi/ic_menu_forward.png \
|
||||
res/drawable-xhdpi/ic_menu_guest.png \
|
||||
res/drawable-xhdpi/ic_menu_new_private_tab.png \
|
||||
res/drawable-xhdpi/ic_menu_new_tab.png \
|
||||
res/drawable-xhdpi/ic_menu_reload.png \
|
||||
res/drawable-xhdpi/ic_status_logo.png \
|
||||
res/drawable-xhdpi/ic_url_bar_go.png \
|
||||
res/drawable-xhdpi/ic_url_bar_reader.png \
|
||||
res/drawable-xhdpi/ic_url_bar_search.png \
|
||||
res/drawable-xhdpi/ic_url_bar_star.png \
|
||||
res/drawable-xhdpi/ic_url_bar_tab.png \
|
||||
res/drawable-xhdpi/icon_bookmarks_empty.png \
|
||||
res/drawable-xhdpi/icon_last_tabs.png \
|
||||
res/drawable-xhdpi/icon_last_tabs_empty.png \
|
||||
res/drawable-xhdpi/icon_most_recent.png \
|
||||
res/drawable-xhdpi/icon_most_recent_empty.png \
|
||||
res/drawable-xhdpi/icon_most_visited.png \
|
||||
res/drawable-xhdpi/icon_openinapp.png \
|
||||
res/drawable-xhdpi/icon_pageaction.png \
|
||||
res/drawable-xhdpi/icon_reading_list_empty.png \
|
||||
res/drawable-xhdpi/spinner_default.9.png \
|
||||
res/drawable-xhdpi/spinner_focused.9.png \
|
||||
res/drawable-xhdpi/spinner_pressed.9.png \
|
||||
res/drawable-xhdpi/tab_new.png \
|
||||
res/drawable-xhdpi/tab_new_pb.png \
|
||||
res/drawable-xhdpi/tab_close.png \
|
||||
res/drawable-xhdpi/tab_thumbnail_default.png \
|
||||
res/drawable-xhdpi/tab_thumbnail_shadow.png \
|
||||
res/drawable-xhdpi/tabs_count.png \
|
||||
res/drawable-xhdpi/tabs_count_foreground.png \
|
||||
res/drawable-xhdpi/tip_addsearch.png \
|
||||
res/drawable-xhdpi/find_close.png \
|
||||
res/drawable-xhdpi/find_next.png \
|
||||
res/drawable-xhdpi/find_prev.png \
|
||||
res/drawable-xhdpi/top_site_add.png \
|
||||
res/drawable-xhdpi/urlbar_stop.png \
|
||||
res/drawable-xhdpi/reader.png \
|
||||
res/drawable-xhdpi/reader_cropped.png \
|
||||
res/drawable-xhdpi/reader_active.png \
|
||||
res/drawable-xhdpi/reading_list.png \
|
||||
res/drawable-xhdpi/larry.png \
|
||||
res/drawable-xhdpi/lock_identified.png \
|
||||
res/drawable-xhdpi/lock_verified.png \
|
||||
res/drawable-xhdpi/menu.png \
|
||||
res/drawable-xhdpi/menu_pb.png \
|
||||
res/drawable-xhdpi/menu_panel_bg.9.png \
|
||||
res/drawable-xhdpi/menu_popup_bg.9.png \
|
||||
res/drawable-xhdpi/menu_popup_arrow_bottom.png \
|
||||
res/drawable-xhdpi/menu_popup_arrow_top.png \
|
||||
res/drawable-xhdpi/menu_item_check.png \
|
||||
res/drawable-xhdpi/menu_item_more.png \
|
||||
res/drawable-xhdpi/menu_item_uncheck.png \
|
||||
res/drawable-xhdpi/pin.png \
|
||||
res/drawable-xhdpi/play.png \
|
||||
res/drawable-xhdpi/pause.png \
|
||||
res/drawable-xhdpi/shield.png \
|
||||
res/drawable-xhdpi/shield_doorhanger.png \
|
||||
res/drawable-xhdpi/tab_indicator_divider.9.png \
|
||||
res/drawable-xhdpi/tab_indicator_selected.9.png \
|
||||
res/drawable-xhdpi/tab_indicator_selected_focused.9.png \
|
||||
res/drawable-xhdpi/tabs_normal.png \
|
||||
res/drawable-xhdpi/tabs_private.png \
|
||||
res/drawable-xhdpi/tabs_synced.png \
|
||||
res/drawable-xhdpi/validation_arrow.png \
|
||||
res/drawable-xhdpi/validation_arrow_inverted.png \
|
||||
res/drawable-xhdpi/validation_bg.9.png \
|
||||
res/drawable-xhdpi/handle_end.png \
|
||||
res/drawable-xhdpi/handle_middle.png \
|
||||
res/drawable-xhdpi/handle_start.png \
|
||||
res/drawable-xhdpi/history_tabs_indicator_selected.9.png \
|
||||
res/drawable-xhdpi/warning.png \
|
||||
res/drawable-xhdpi/warning_doorhanger.png \
|
||||
$(NULL)
|
||||
|
||||
RES_DRAWABLE_MDPI_V11 = \
|
||||
res/drawable-mdpi-v11/alert_addon.png \
|
||||
res/drawable-mdpi-v11/alert_app.png \
|
||||
res/drawable-mdpi-v11/alert_download.png \
|
||||
res/drawable-mdpi-v11/alert_camera.png \
|
||||
res/drawable-mdpi-v11/alert_mic.png \
|
||||
res/drawable-mdpi-v11/alert_mic_camera.png \
|
||||
res/drawable-mdpi-v11/firefox_settings_alert.png \
|
||||
res/drawable-mdpi-v11/ic_menu_addons.png \
|
||||
res/drawable-mdpi-v11/ic_menu_apps.png \
|
||||
res/drawable-mdpi-v11/ic_menu_back.png \
|
||||
res/drawable-mdpi-v11/ic_menu_bookmark_add.png \
|
||||
res/drawable-mdpi-v11/ic_menu_bookmark_remove.png \
|
||||
res/drawable-mdpi-v11/ic_menu_desktop_mode_off.png \
|
||||
res/drawable-mdpi-v11/ic_menu_desktop_mode_on.png \
|
||||
res/drawable-mdpi-v11/ic_menu_downloads.png \
|
||||
res/drawable-mdpi-v11/ic_menu_find_in_page.png \
|
||||
res/drawable-mdpi-v11/ic_menu_forward.png \
|
||||
res/drawable-mdpi-v11/ic_menu_new_private_tab.png \
|
||||
res/drawable-mdpi-v11/ic_menu_new_tab.png \
|
||||
res/drawable-mdpi-v11/ic_menu_reload.png \
|
||||
res/drawable-mdpi-v11/ic_menu_save_as_pdf.png \
|
||||
res/drawable-mdpi-v11/ic_menu_settings.png \
|
||||
res/drawable-mdpi-v11/ic_menu_share.png \
|
||||
res/drawable-mdpi-v11/ic_menu_tools.png \
|
||||
res/drawable-mdpi-v11/ic_menu_quit.png \
|
||||
res/drawable-mdpi-v11/ic_status_logo.png \
|
||||
$(NULL)
|
||||
|
||||
RES_DRAWABLE_HDPI_V11 = \
|
||||
res/drawable-hdpi-v11/alert_addon.png \
|
||||
res/drawable-hdpi-v11/alert_app.png \
|
||||
res/drawable-hdpi-v11/alert_download.png \
|
||||
res/drawable-hdpi-v11/alert_camera.png \
|
||||
res/drawable-hdpi-v11/alert_mic.png \
|
||||
res/drawable-hdpi-v11/alert_mic_camera.png \
|
||||
res/drawable-hdpi-v11/firefox_settings_alert.png \
|
||||
res/drawable-hdpi-v11/ic_menu_addons.png \
|
||||
res/drawable-hdpi-v11/ic_menu_apps.png \
|
||||
res/drawable-hdpi-v11/ic_menu_back.png \
|
||||
res/drawable-hdpi-v11/ic_menu_bookmark_add.png \
|
||||
res/drawable-hdpi-v11/ic_menu_bookmark_remove.png \
|
||||
res/drawable-hdpi-v11/ic_menu_desktop_mode_off.png \
|
||||
res/drawable-hdpi-v11/ic_menu_desktop_mode_on.png \
|
||||
res/drawable-hdpi-v11/ic_menu_downloads.png \
|
||||
res/drawable-hdpi-v11/ic_menu_find_in_page.png \
|
||||
res/drawable-hdpi-v11/ic_menu_forward.png \
|
||||
res/drawable-hdpi-v11/ic_menu_new_private_tab.png \
|
||||
res/drawable-hdpi-v11/ic_menu_new_tab.png \
|
||||
res/drawable-hdpi-v11/ic_menu_reload.png \
|
||||
res/drawable-hdpi-v11/ic_menu_save_as_pdf.png \
|
||||
res/drawable-hdpi-v11/ic_menu_settings.png \
|
||||
res/drawable-hdpi-v11/ic_menu_share.png \
|
||||
res/drawable-hdpi-v11/ic_menu_tools.png \
|
||||
res/drawable-hdpi-v11/ic_menu_quit.png \
|
||||
res/drawable-hdpi-v11/ic_status_logo.png \
|
||||
$(NULL)
|
||||
|
||||
RES_DRAWABLE_XHDPI_V11 = \
|
||||
res/drawable-xhdpi-v11/alert_addon.png \
|
||||
res/drawable-xhdpi-v11/alert_app.png \
|
||||
res/drawable-xhdpi-v11/alert_download.png \
|
||||
res/drawable-xhdpi-v11/alert_camera.png \
|
||||
res/drawable-xhdpi-v11/alert_mic.png \
|
||||
res/drawable-xhdpi-v11/alert_mic_camera.png \
|
||||
res/drawable-xhdpi-v11/firefox_settings_alert.png \
|
||||
res/drawable-xhdpi-v11/ic_menu_addons.png \
|
||||
res/drawable-xhdpi-v11/ic_menu_apps.png \
|
||||
res/drawable-xhdpi-v11/ic_menu_back.png \
|
||||
res/drawable-xhdpi-v11/ic_menu_bookmark_add.png \
|
||||
res/drawable-xhdpi-v11/ic_menu_bookmark_remove.png \
|
||||
res/drawable-xhdpi-v11/ic_menu_desktop_mode_off.png \
|
||||
res/drawable-xhdpi-v11/ic_menu_desktop_mode_on.png \
|
||||
res/drawable-xhdpi-v11/ic_menu_downloads.png \
|
||||
res/drawable-xhdpi-v11/ic_menu_find_in_page.png \
|
||||
res/drawable-xhdpi-v11/ic_menu_forward.png \
|
||||
res/drawable-xhdpi-v11/ic_menu_new_private_tab.png \
|
||||
res/drawable-xhdpi-v11/ic_menu_new_tab.png \
|
||||
res/drawable-xhdpi-v11/ic_menu_reload.png \
|
||||
res/drawable-xhdpi-v11/ic_menu_save_as_pdf.png \
|
||||
res/drawable-xhdpi-v11/ic_menu_settings.png \
|
||||
res/drawable-xhdpi-v11/ic_menu_share.png \
|
||||
res/drawable-xhdpi-v11/ic_menu_tools.png \
|
||||
res/drawable-xhdpi-v11/ic_menu_quit.png \
|
||||
res/drawable-xhdpi-v11/ic_status_logo.png \
|
||||
$(NULL)
|
||||
|
||||
RES_DRAWABLE_LARGE_LAND_V11 = \
|
||||
res/drawable-large-land-v11/home_history_tabs_indicator.xml \
|
||||
$(NULL)
|
||||
|
||||
RES_DRAWABLE_LARGE_MDPI_V11 = \
|
||||
res/drawable-large-mdpi-v11/arrow_popup_bg.9.png \
|
||||
res/drawable-large-mdpi-v11/ic_menu_reload.png \
|
||||
res/drawable-large-mdpi-v11/ic_menu_forward.png \
|
||||
res/drawable-large-mdpi-v11/menu.png \
|
||||
$(NULL)
|
||||
|
||||
RES_DRAWABLE_LARGE_HDPI_V11 = \
|
||||
res/drawable-large-hdpi-v11/arrow_popup_bg.9.png \
|
||||
res/drawable-large-hdpi-v11/ic_menu_reload.png \
|
||||
res/drawable-large-hdpi-v11/ic_menu_forward.png \
|
||||
res/drawable-large-hdpi-v11/menu.png \
|
||||
$(NULL)
|
||||
|
||||
RES_DRAWABLE_LARGE_XHDPI_V11 = \
|
||||
res/drawable-large-xhdpi-v11/arrow_popup_bg.9.png \
|
||||
res/drawable-large-xhdpi-v11/ic_menu_reload.png \
|
||||
res/drawable-large-xhdpi-v11/ic_menu_forward.png \
|
||||
res/drawable-large-xhdpi-v11/menu.png \
|
||||
$(NULL)
|
||||
|
||||
RES_DRAWABLE_XLARGE_V11 = \
|
||||
res/drawable-xlarge-v11/home_history_tabs_indicator.xml \
|
||||
$(NULL)
|
||||
|
||||
RES_DRAWABLE_XLARGE_MDPI_V11 = \
|
||||
res/drawable-xlarge-mdpi-v11/ic_menu_bookmark_add.png \
|
||||
res/drawable-xlarge-mdpi-v11/ic_menu_bookmark_remove.png \
|
||||
$(NULL)
|
||||
|
||||
RES_DRAWABLE_XLARGE_HDPI_V11 = \
|
||||
res/drawable-xlarge-hdpi-v11/ic_menu_bookmark_add.png \
|
||||
res/drawable-xlarge-hdpi-v11/ic_menu_bookmark_remove.png \
|
||||
$(NULL)
|
||||
|
||||
RES_DRAWABLE_XLARGE_XHDPI_V11 = \
|
||||
res/drawable-xlarge-xhdpi-v11/ic_menu_bookmark_add.png \
|
||||
res/drawable-xlarge-xhdpi-v11/ic_menu_bookmark_remove.png \
|
||||
$(NULL)
|
||||
|
||||
RES_COLOR = \
|
||||
res/color/primary_text.xml \
|
||||
res/color/primary_text_inverse.xml \
|
||||
res/color/secondary_text.xml \
|
||||
res/color/secondary_text_inverse.xml \
|
||||
res/color/select_item_multichoice.xml \
|
||||
res/color/tertiary_text.xml \
|
||||
res/color/tertiary_text_inverse.xml \
|
||||
res/color/top_sites_grid_item_title.xml \
|
||||
res/color/url_bar_title.xml \
|
||||
res/color/url_bar_title_hint.xml \
|
||||
$(NULL)
|
||||
|
||||
RES_MENU = \
|
||||
res/menu/browser_app_menu.xml \
|
||||
res/menu/gecko_app_menu.xml \
|
||||
res/menu/home_contextmenu.xml \
|
||||
res/menu/titlebar_contextmenu.xml \
|
||||
res/menu/top_sites_contextmenu.xml \
|
||||
res/menu-large-v11/browser_app_menu.xml \
|
||||
res/menu-v11/browser_app_menu.xml \
|
||||
res/menu-xlarge-v11/browser_app_menu.xml \
|
||||
$(NULL)
|
||||
|
||||
JAVA_CLASSPATH = $(ANDROID_SDK)/android.jar
|
||||
|
||||
ifdef MOZ_CRASHREPORTER
|
||||
FENNEC_JAVA_FILES += CrashReporter.java
|
||||
RES_DRAWABLE_MDPI += res/drawable-mdpi/crash_reporter.png
|
||||
RES_LAYOUT += res/layout/crash_reporter.xml
|
||||
endif
|
||||
|
||||
RES_DRAWABLE += \
|
||||
$(SYNC_RES_DRAWABLE) \
|
||||
res/drawable/action_bar_button.xml \
|
||||
res/drawable/action_bar_button_inverse.xml \
|
||||
res/drawable/top_sites_thumbnail_bg.xml \
|
||||
res/drawable/url_bar_bg.xml \
|
||||
res/drawable/url_bar_entry.xml \
|
||||
res/drawable/url_bar_nav_button.xml \
|
||||
res/drawable/icon_grid_item_bg.xml \
|
||||
res/drawable/url_bar_right_edge.xml \
|
||||
res/drawable/bookmark_folder.xml \
|
||||
res/drawable/divider_horizontal.xml \
|
||||
res/drawable/divider_vertical.xml \
|
||||
res/drawable/favicon_bg.xml \
|
||||
res/drawable/handle_end_level.xml \
|
||||
res/drawable/handle_start_level.xml \
|
||||
res/drawable/home_history_tabs_indicator.xml \
|
||||
res/drawable/home_page_title_background.xml \
|
||||
res/drawable/home_banner.xml \
|
||||
res/drawable/ic_menu_back.xml \
|
||||
res/drawable/ic_menu_desktop_mode_off.xml \
|
||||
res/drawable/ic_menu_desktop_mode_on.xml \
|
||||
res/drawable/ic_menu_quit.xml \
|
||||
res/drawable/menu_item_state.xml \
|
||||
res/drawable/menu_level.xml \
|
||||
res/drawable/remote_tabs_child_divider.xml \
|
||||
res/drawable/shaped_button.xml \
|
||||
res/drawable/site_security_level.xml \
|
||||
res/drawable/spinner.xml \
|
||||
res/drawable/suggestion_selector.xml \
|
||||
res/drawable/tab_new_level.xml \
|
||||
res/drawable/tab_row.xml \
|
||||
res/drawable/tab_thumbnail.xml \
|
||||
res/drawable/tabs_panel_indicator.xml \
|
||||
res/drawable/textbox_bg.xml \
|
||||
res/drawable/toast_button.xml \
|
||||
res/drawable/webapp_titlebar_bg.xml \
|
||||
$(NULL)
|
||||
|
||||
RESOURCES = \
|
||||
$(RES_ANIM) \
|
||||
$(RES_COLOR) \
|
||||
$(RES_DRAWABLE) \
|
||||
$(RES_DRAWABLE_HDPI) \
|
||||
$(RES_DRAWABLE_HDPI_V11) \
|
||||
$(RES_DRAWABLE_LARGE_LAND_V11) \
|
||||
$(RES_DRAWABLE_LARGE_HDPI_V11) \
|
||||
$(RES_DRAWABLE_LARGE_MDPI_V11) \
|
||||
$(RES_DRAWABLE_LARGE_XHDPI_V11) \
|
||||
$(RES_DRAWABLE_LDPI) \
|
||||
$(RES_DRAWABLE_MDPI) \
|
||||
$(RES_DRAWABLE_MDPI_V11) \
|
||||
$(RES_DRAWABLE_XHDPI) \
|
||||
$(RES_DRAWABLE_XHDPI_V11) \
|
||||
$(RES_DRAWABLE_XLARGE_V11) \
|
||||
$(RES_DRAWABLE_XLARGE_HDPI_V11) \
|
||||
$(RES_DRAWABLE_XLARGE_MDPI_V11) \
|
||||
$(RES_DRAWABLE_XLARGE_XHDPI_V11) \
|
||||
$(RES_LAYOUT) \
|
||||
$(RES_LAYOUT_LARGE_LAND_V11) \
|
||||
$(RES_LAYOUT_LARGE_V11) \
|
||||
$(RES_LAYOUT_XLARGE_LAND_V11) \
|
||||
$(RES_LAYOUT_XLARGE_V11) \
|
||||
$(RES_MENU) \
|
||||
$(RES_VALUES) \
|
||||
$(RES_VALUES_LAND) \
|
||||
$(RES_VALUES_LAND_V14) \
|
||||
$(RES_VALUES_LARGE_LAND_V11) \
|
||||
$(RES_VALUES_LARGE_V11) \
|
||||
$(RES_VALUES_V11) \
|
||||
$(RES_VALUES_V14) \
|
||||
$(RES_VALUES_V16) \
|
||||
$(RES_VALUES_XLARGE_LAND_V11) \
|
||||
$(RES_VALUES_XLARGE_V11) \
|
||||
$(RES_XML) \
|
||||
$(RES_XML_V11) \
|
||||
$(NULL)
|
||||
|
||||
RES_DIRS= \
|
||||
res/layout \
|
||||
res/layout-large-v11 \
|
||||
res/layout-large-land-v11 \
|
||||
res/layout-xlarge-v11 \
|
||||
res/values \
|
||||
res/values-v11 \
|
||||
res/values-large-v11 \
|
||||
res/values-xlarge-land-v11 \
|
||||
res/values-xlarge-v11 \
|
||||
res/values-v14 \
|
||||
res/values-v16 \
|
||||
res/xml \
|
||||
res/xml-v11 \
|
||||
res/anim \
|
||||
res/drawable-ldpi \
|
||||
res/drawable-mdpi \
|
||||
res/drawable-hdpi \
|
||||
res/drawable-xhdpi \
|
||||
res/drawable \
|
||||
res/drawable-mdpi-v11 \
|
||||
res/drawable-hdpi-v11 \
|
||||
res/drawable-xhdpi-v11 \
|
||||
res/drawable-large-land-v11 \
|
||||
res/drawable-large-mdpi-v11 \
|
||||
res/drawable-large-hdpi-v11 \
|
||||
res/drawable-large-xhdpi-v11 \
|
||||
res/drawable-xlarge-v11 \
|
||||
res/drawable-xlarge-mdpi-v11 \
|
||||
res/drawable-xlarge-hdpi-v11 \
|
||||
res/drawable-xlarge-xhdpi-v11 \
|
||||
res/color \
|
||||
res/menu \
|
||||
res/menu-v11 \
|
||||
res/menu-large-v11 \
|
||||
res/menu-xlarge-v11 \
|
||||
$(NULL)
|
||||
|
||||
ALL_JARS = \
|
||||
jars/gecko-browser.jar \
|
||||
jars/gecko-mozglue.jar \
|
||||
@ -1280,6 +503,10 @@ endif
|
||||
|
||||
include $(topsrcdir)/config/makefiles/java-build.mk
|
||||
|
||||
# We process ANDROID_RESFILES specially for now; the following flag
|
||||
# disables the default processing.
|
||||
IGNORE_ANDROID_RESFILES=1
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
# Override the Java settings with some specific android settings
|
||||
@ -1360,12 +587,14 @@ res/drawable-xxhdpi/icon.png: $(ICON_PATH_XXHDPI)
|
||||
$(NSINSTALL) -D res/drawable-xxhdpi
|
||||
cp $(ICON_PATH_XXHDPI) $@
|
||||
|
||||
$(call mkdir_deps,$(RES_DIRS)): $(subst res/,$(srcdir)/resources/,$(RESOURCES)) Makefile
|
||||
ANDROID_RESDIRS := $(subst resources/,res/,$(sort $(dir $(ANDROID_RESFILES))))
|
||||
|
||||
$(call mkdir_deps,$(ANDROID_RESDIRS)): $(ANDROID_RESFILES) Makefile
|
||||
$(RM) -r $(@D)
|
||||
$(NSINSTALL) -D $(@D)
|
||||
$(TOUCH) $@
|
||||
|
||||
$(RESOURCES): $(call mkdir_deps,$(RES_DIRS)) $(subst res/,$(srcdir)/resources/,$(RESOURCES))
|
||||
$(subst resources/,res/,$(ANDROID_RESFILES)): $(call mkdir_deps,$(ANDROID_RESDIRS)) $(ANDROID_RESFILES)
|
||||
@echo "creating $@"
|
||||
$(NSINSTALL) $(subst res/,$(srcdir)/resources/,$@) $(dir $@)
|
||||
|
||||
@ -1376,14 +605,10 @@ res/values/strings.xml: $(call mkdir_deps,res/values)
|
||||
# rebuild gecko.ap_ if any of them change.
|
||||
MULTILOCALE_STRINGS_XML_FILES := $(wildcard res/values-*/strings.xml)
|
||||
all_resources = \
|
||||
res/drawable-mdpi/icon.png \
|
||||
res/drawable-hdpi/icon.png \
|
||||
res/drawable-xhdpi/icon.png \
|
||||
res/drawable-xxhdpi/icon.png \
|
||||
res/values/strings.xml \
|
||||
$(MULTILOCALE_STRINGS_XML_FILES) \
|
||||
AndroidManifest.xml \
|
||||
$(RESOURCES) \
|
||||
$(subst resources/,res/,$(ANDROID_RESFILES)) \
|
||||
$(ANDROID_GENERATED_RESFILES) \
|
||||
$(NULL)
|
||||
|
||||
R.java: $(all_resources)
|
||||
|
@ -40,6 +40,7 @@ SYNC_JAVA_FILES := \
|
||||
background/db/Tab.java \
|
||||
background/healthreport/Environment.java \
|
||||
background/healthreport/EnvironmentBuilder.java \
|
||||
background/healthreport/EnvironmentV1.java \
|
||||
background/healthreport/HealthReportBroadcastReceiver.java \
|
||||
background/healthreport/HealthReportBroadcastService.java \
|
||||
background/healthreport/HealthReportDatabases.java \
|
||||
@ -301,53 +302,6 @@ SYNC_JAVA_FILES := \
|
||||
sync/Utils.java \
|
||||
$(NULL)
|
||||
|
||||
SYNC_RES_DRAWABLE := \
|
||||
res/drawable/pin_background.xml \
|
||||
$(NULL)
|
||||
|
||||
SYNC_RES_DRAWABLE_LDPI := \
|
||||
$(NULL)
|
||||
|
||||
SYNC_RES_DRAWABLE_MDPI := \
|
||||
res/drawable-mdpi/desktop.png \
|
||||
res/drawable-mdpi/mobile.png \
|
||||
$(NULL)
|
||||
|
||||
SYNC_RES_DRAWABLE_HDPI := \
|
||||
$(NULL)
|
||||
|
||||
SYNC_RES_LAYOUT := \
|
||||
res/layout/sync_account.xml \
|
||||
res/layout/sync_list_item.xml \
|
||||
res/layout/sync_redirect_to_setup.xml \
|
||||
res/layout/sync_send_tab.xml \
|
||||
res/layout/sync_setup.xml \
|
||||
res/layout/sync_setup_failure.xml \
|
||||
res/layout/sync_setup_jpake_waiting.xml \
|
||||
res/layout/sync_setup_nointernet.xml \
|
||||
res/layout/sync_setup_pair.xml \
|
||||
res/layout/sync_setup_success.xml \
|
||||
res/layout/sync_setup_webview.xml \
|
||||
$(NULL)
|
||||
|
||||
SYNC_RES_VALUES := \
|
||||
res/values/sync_styles.xml \
|
||||
$(NULL)
|
||||
|
||||
SYNC_RES_VALUES_V11 := \
|
||||
res/values-v11/sync_styles.xml \
|
||||
$(NULL)
|
||||
|
||||
SYNC_RES_VALUES_LARGE_V11 := \
|
||||
res/values-large-v11/sync_styles.xml \
|
||||
$(NULL)
|
||||
|
||||
SYNC_RES_XML := \
|
||||
res/xml/sync_authenticator.xml \
|
||||
res/xml/sync_syncadapter.xml \
|
||||
res/xml/sync_options.xml \
|
||||
$(NULL)
|
||||
|
||||
SYNC_THIRDPARTY_JAVA_FILES := \
|
||||
httpclientandroidlib/androidextra/HttpClientAndroidLog.java \
|
||||
httpclientandroidlib/annotation/GuardedBy.java \
|
||||
|
28
mobile/android/base/android-services.mozbuild
Normal file
28
mobile/android/base/android-services.mozbuild
Normal file
@ -0,0 +1,28 @@
|
||||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
ANDROID_RESFILES += [
|
||||
'resources/drawable-mdpi/desktop.png',
|
||||
'resources/drawable-mdpi/mobile.png',
|
||||
'resources/drawable/pin_background.xml',
|
||||
'resources/layout/sync_account.xml',
|
||||
'resources/layout/sync_list_item.xml',
|
||||
'resources/layout/sync_redirect_to_setup.xml',
|
||||
'resources/layout/sync_send_tab.xml',
|
||||
'resources/layout/sync_setup.xml',
|
||||
'resources/layout/sync_setup_failure.xml',
|
||||
'resources/layout/sync_setup_jpake_waiting.xml',
|
||||
'resources/layout/sync_setup_nointernet.xml',
|
||||
'resources/layout/sync_setup_pair.xml',
|
||||
'resources/layout/sync_setup_success.xml',
|
||||
'resources/layout/sync_setup_webview.xml',
|
||||
'resources/values-large-v11/sync_styles.xml',
|
||||
'resources/values-v11/sync_styles.xml',
|
||||
'resources/values/sync_styles.xml',
|
||||
'resources/xml/sync_authenticator.xml',
|
||||
'resources/xml/sync_options.xml',
|
||||
'resources/xml/sync_syncadapter.xml',
|
||||
]
|
@ -4,17 +4,6 @@
|
||||
|
||||
package org.mozilla.gecko.background.healthreport;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Iterator;
|
||||
import java.util.SortedSet;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.mozilla.apache.commons.codec.binary.Base64;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
|
||||
/**
|
||||
* This captures all of the details that define an 'environment' for FHR's purposes.
|
||||
* Whenever this format changes, it'll be changing with a build ID, so no migration
|
||||
@ -29,246 +18,32 @@ import org.mozilla.gecko.background.common.log.Logger;
|
||||
* registered an <code>Environment</code>, don't do so again; start from scratch.
|
||||
*
|
||||
*/
|
||||
public abstract class Environment {
|
||||
private static final String LOG_TAG = "GeckoEnvironment";
|
||||
public abstract class Environment extends EnvironmentV1 {
|
||||
// Version 2 adds osLocale, appLocale, acceptLangSet, and distribution.
|
||||
public static final int CURRENT_VERSION = 2;
|
||||
|
||||
public static int VERSION = 1;
|
||||
|
||||
protected final Class<? extends EnvironmentAppender> appenderClass;
|
||||
|
||||
protected volatile String hash = null;
|
||||
protected volatile int id = -1;
|
||||
|
||||
// org.mozilla.profile.age.
|
||||
public int profileCreation;
|
||||
|
||||
// org.mozilla.sysinfo.sysinfo.
|
||||
public int cpuCount;
|
||||
public int memoryMB;
|
||||
public String architecture;
|
||||
public String sysName;
|
||||
public String sysVersion; // Kernel.
|
||||
|
||||
// geckoAppInfo. Not sure if we can/should provide this on Android.
|
||||
public String vendor;
|
||||
public String appName;
|
||||
public String appID;
|
||||
public String appVersion;
|
||||
public String appBuildID;
|
||||
public String platformVersion;
|
||||
public String platformBuildID;
|
||||
public String os;
|
||||
public String xpcomabi;
|
||||
public String updateChannel;
|
||||
|
||||
// appInfo.
|
||||
public int isBlocklistEnabled;
|
||||
public int isTelemetryEnabled;
|
||||
// public int isDefaultBrowser; // This is meaningless on Android.
|
||||
|
||||
// org.mozilla.addons.active.
|
||||
public JSONObject addons = null;
|
||||
|
||||
// org.mozilla.addons.counts.
|
||||
public int extensionCount;
|
||||
public int pluginCount;
|
||||
public int themeCount;
|
||||
public String osLocale; // The Android OS "Locale" value.
|
||||
public String appLocale;
|
||||
public int acceptLangSet;
|
||||
public String distribution; // ID + version. Typically empty.
|
||||
|
||||
public Environment() {
|
||||
this(Environment.HashAppender.class);
|
||||
}
|
||||
|
||||
public Environment(Class<? extends EnvironmentAppender> appenderClass) {
|
||||
this.appenderClass = appenderClass;
|
||||
super(appenderClass);
|
||||
version = CURRENT_VERSION;
|
||||
}
|
||||
|
||||
public JSONObject getNonIgnoredAddons() {
|
||||
if (addons == null) {
|
||||
return null;
|
||||
}
|
||||
JSONObject out = new JSONObject();
|
||||
@SuppressWarnings("unchecked")
|
||||
Iterator<String> keys = addons.keys();
|
||||
while (keys.hasNext()) {
|
||||
try {
|
||||
final String key = keys.next();
|
||||
final Object obj = addons.get(key);
|
||||
if (obj != null && obj instanceof JSONObject && ((JSONObject) obj).optBoolean("ignore", false)) {
|
||||
continue;
|
||||
}
|
||||
out.put(key, obj);
|
||||
} catch (JSONException ex) {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
return out;
|
||||
@Override
|
||||
protected void appendHash(EnvironmentAppender appender) {
|
||||
super.appendHash(appender);
|
||||
|
||||
// v2.
|
||||
appender.append(osLocale);
|
||||
appender.append(appLocale);
|
||||
appender.append(acceptLangSet);
|
||||
appender.append(distribution);
|
||||
}
|
||||
|
||||
/**
|
||||
* We break out this interface in order to allow for testing -- pass in your
|
||||
* own appender that just records strings, for example.
|
||||
*/
|
||||
public static abstract class EnvironmentAppender {
|
||||
public abstract void append(String s);
|
||||
public abstract void append(int v);
|
||||
}
|
||||
|
||||
public static class HashAppender extends EnvironmentAppender {
|
||||
final MessageDigest hasher;
|
||||
|
||||
public HashAppender() throws NoSuchAlgorithmException {
|
||||
// Note to the security minded reader: we deliberately use SHA-1 here, not
|
||||
// a stronger hash. These identifiers don't strictly need a cryptographic
|
||||
// hash function, because there is negligible value in attacking the hash.
|
||||
// We use SHA-1 because it's *shorter* -- the exact same reason that Git
|
||||
// chose SHA-1.
|
||||
hasher = MessageDigest.getInstance("SHA-1");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void append(String s) {
|
||||
try {
|
||||
hasher.update(((s == null) ? "null" : s).getBytes("UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// This can never occur. Thanks, Java.
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void append(int profileCreation) {
|
||||
append(Integer.toString(profileCreation, 10));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// We *could* use ASCII85… but the savings would be negated by the
|
||||
// inclusion of JSON-unsafe characters like double-quote.
|
||||
return new Base64(-1, null, false).encodeAsString(hasher.digest());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the stable hash of the configured environment.
|
||||
*
|
||||
* @return the hash in base34, or null if there was a problem.
|
||||
*/
|
||||
public String getHash() {
|
||||
// It's never unset, so we only care about partial reads. volatile is enough.
|
||||
if (hash != null) {
|
||||
return hash;
|
||||
}
|
||||
|
||||
EnvironmentAppender appender;
|
||||
try {
|
||||
appender = appenderClass.newInstance();
|
||||
} catch (InstantiationException ex) {
|
||||
// Should never happen, but...
|
||||
Logger.warn(LOG_TAG, "Could not compute hash.", ex);
|
||||
return null;
|
||||
} catch (IllegalAccessException ex) {
|
||||
// Should never happen, but...
|
||||
Logger.warn(LOG_TAG, "Could not compute hash.", ex);
|
||||
return null;
|
||||
}
|
||||
|
||||
appender.append(profileCreation);
|
||||
appender.append(cpuCount);
|
||||
appender.append(memoryMB);
|
||||
appender.append(architecture);
|
||||
appender.append(sysName);
|
||||
appender.append(sysVersion);
|
||||
appender.append(vendor);
|
||||
appender.append(appName);
|
||||
appender.append(appID);
|
||||
appender.append(appVersion);
|
||||
appender.append(appBuildID);
|
||||
appender.append(platformVersion);
|
||||
appender.append(platformBuildID);
|
||||
appender.append(os);
|
||||
appender.append(xpcomabi);
|
||||
appender.append(updateChannel);
|
||||
appender.append(isBlocklistEnabled);
|
||||
appender.append(isTelemetryEnabled);
|
||||
appender.append(extensionCount);
|
||||
appender.append(pluginCount);
|
||||
appender.append(themeCount);
|
||||
|
||||
// We need sorted values.
|
||||
if (addons != null) {
|
||||
appendSortedAddons(getNonIgnoredAddons(), appender);
|
||||
}
|
||||
|
||||
return hash = appender.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a collection of add-on descriptors, appending a consistent string
|
||||
* to the provided builder.
|
||||
*/
|
||||
public static void appendSortedAddons(JSONObject addons,
|
||||
final EnvironmentAppender builder) {
|
||||
final SortedSet<String> keys = HealthReportUtils.sortedKeySet(addons);
|
||||
|
||||
// For each add-on, produce a consistent, sorted mapping of its descriptor.
|
||||
for (String key : keys) {
|
||||
try {
|
||||
JSONObject addon = addons.getJSONObject(key);
|
||||
|
||||
// Now produce the output for this add-on.
|
||||
builder.append(key);
|
||||
builder.append("={");
|
||||
|
||||
for (String addonKey : HealthReportUtils.sortedKeySet(addon)) {
|
||||
builder.append(addonKey);
|
||||
builder.append("==");
|
||||
try {
|
||||
builder.append(addon.get(addonKey).toString());
|
||||
} catch (JSONException e) {
|
||||
builder.append("_e_");
|
||||
}
|
||||
}
|
||||
|
||||
builder.append("}");
|
||||
} catch (Exception e) {
|
||||
// Muffle.
|
||||
Logger.warn(LOG_TAG, "Invalid add-on for ID " + key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setJSONForAddons(byte[] json) throws Exception {
|
||||
setJSONForAddons(new String(json, "UTF-8"));
|
||||
}
|
||||
|
||||
public void setJSONForAddons(String json) throws Exception {
|
||||
if (json == null || "null".equals(json)) {
|
||||
addons = null;
|
||||
return;
|
||||
}
|
||||
addons = new JSONObject(json);
|
||||
}
|
||||
|
||||
public void setJSONForAddons(JSONObject json) {
|
||||
addons = json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Includes ignored add-ons.
|
||||
*/
|
||||
public String getNormalizedAddonsJSON() {
|
||||
// We trust that our input will already be normalized. If that assumption
|
||||
// is invalidated, then we'll be sorry.
|
||||
return (addons == null) ? "null" : addons.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the {@link Environment} has been registered with its
|
||||
* storage layer, and can be used to annotate events.
|
||||
*
|
||||
* It's safe to call this method more than once, and each time you'll
|
||||
* get the same ID.
|
||||
*
|
||||
* @return the integer ID to use in subsequent DB insertions.
|
||||
*/
|
||||
public abstract int register();
|
||||
}
|
||||
|
@ -58,7 +58,13 @@ public class EnvironmentBuilder {
|
||||
public static interface ProfileInformationProvider {
|
||||
public boolean isBlocklistEnabled();
|
||||
public boolean isTelemetryEnabled();
|
||||
public boolean isAcceptLangUserSet();
|
||||
public long getProfileCreationTime();
|
||||
|
||||
public String getDistributionString();
|
||||
public String getOSLocale();
|
||||
public String getAppLocale();
|
||||
|
||||
public JSONObject getAddonsJSON();
|
||||
}
|
||||
|
||||
@ -124,6 +130,12 @@ public class EnvironmentBuilder {
|
||||
}
|
||||
|
||||
e.addons = addons;
|
||||
|
||||
// v2 environment fields.
|
||||
e.distribution = info.getDistributionString();
|
||||
e.osLocale = info.getOSLocale();
|
||||
e.appLocale = info.getAppLocale();
|
||||
e.acceptLangSet = info.isAcceptLangUserSet() ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
267
mobile/android/base/background/healthreport/EnvironmentV1.java
Normal file
267
mobile/android/base/background/healthreport/EnvironmentV1.java
Normal file
@ -0,0 +1,267 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.background.healthreport;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Iterator;
|
||||
import java.util.SortedSet;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.mozilla.apache.commons.codec.binary.Base64;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
|
||||
public abstract class EnvironmentV1 {
|
||||
private static final String LOG_TAG = "GeckoEnvironment";
|
||||
private static final int VERSION = 1;
|
||||
|
||||
protected final Class<? extends EnvironmentAppender> appenderClass;
|
||||
|
||||
protected volatile String hash = null;
|
||||
protected volatile int id = -1;
|
||||
|
||||
public int version = VERSION;
|
||||
|
||||
// org.mozilla.profile.age.
|
||||
public int profileCreation;
|
||||
|
||||
// org.mozilla.sysinfo.sysinfo.
|
||||
public int cpuCount;
|
||||
public int memoryMB;
|
||||
public String architecture;
|
||||
public String sysName;
|
||||
public String sysVersion; // Kernel.
|
||||
|
||||
// geckoAppInfo.
|
||||
public String vendor;
|
||||
public String appName;
|
||||
public String appID;
|
||||
public String appVersion;
|
||||
public String appBuildID;
|
||||
public String platformVersion;
|
||||
public String platformBuildID;
|
||||
public String os;
|
||||
public String xpcomabi;
|
||||
public String updateChannel;
|
||||
|
||||
// appinfo.
|
||||
public int isBlocklistEnabled;
|
||||
public int isTelemetryEnabled;
|
||||
|
||||
// org.mozilla.addons.active.
|
||||
public JSONObject addons = null;
|
||||
|
||||
// org.mozilla.addons.counts.
|
||||
public int extensionCount;
|
||||
public int pluginCount;
|
||||
public int themeCount;
|
||||
|
||||
/**
|
||||
* We break out this interface in order to allow for testing -- pass in your
|
||||
* own appender that just records strings, for example.
|
||||
*/
|
||||
public static abstract class EnvironmentAppender {
|
||||
public abstract void append(String s);
|
||||
public abstract void append(int v);
|
||||
}
|
||||
|
||||
public static class HashAppender extends EnvironmentAppender {
|
||||
final MessageDigest hasher;
|
||||
|
||||
public HashAppender() throws NoSuchAlgorithmException {
|
||||
// Note to the security-minded reader: we deliberately use SHA-1 here, not
|
||||
// a stronger hash. These identifiers don't strictly need a cryptographic
|
||||
// hash function, because there is negligible value in attacking the hash.
|
||||
// We use SHA-1 because it's *shorter* -- the exact same reason that Git
|
||||
// chose SHA-1.
|
||||
hasher = MessageDigest.getInstance("SHA-1");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void append(String s) {
|
||||
try {
|
||||
hasher.update(((s == null) ? "null" : s).getBytes("UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// This can never occur. Thanks, Java.
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void append(int profileCreation) {
|
||||
append(Integer.toString(profileCreation, 10));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// We *could* use ASCII85… but the savings would be negated by the
|
||||
// inclusion of JSON-unsafe characters like double-quote.
|
||||
return new Base64(-1, null, false).encodeAsString(hasher.digest());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the {@link Environment} has been registered with its
|
||||
* storage layer, and can be used to annotate events.
|
||||
*
|
||||
* It's safe to call this method more than once, and each time you'll
|
||||
* get the same ID.
|
||||
*
|
||||
* @return the integer ID to use in subsequent DB insertions.
|
||||
*/
|
||||
public abstract int register();
|
||||
|
||||
protected EnvironmentAppender getAppender() {
|
||||
EnvironmentAppender appender = null;
|
||||
try {
|
||||
appender = appenderClass.newInstance();
|
||||
} catch (InstantiationException ex) {
|
||||
// Should never happen, but...
|
||||
Logger.warn(LOG_TAG, "Could not compute hash.", ex);
|
||||
} catch (IllegalAccessException ex) {
|
||||
// Should never happen, but...
|
||||
Logger.warn(LOG_TAG, "Could not compute hash.", ex);
|
||||
}
|
||||
return appender;
|
||||
}
|
||||
|
||||
protected void appendHash(EnvironmentAppender appender) {
|
||||
appender.append(profileCreation);
|
||||
appender.append(cpuCount);
|
||||
appender.append(memoryMB);
|
||||
appender.append(architecture);
|
||||
appender.append(sysName);
|
||||
appender.append(sysVersion);
|
||||
appender.append(vendor);
|
||||
appender.append(appName);
|
||||
appender.append(appID);
|
||||
appender.append(appVersion);
|
||||
appender.append(appBuildID);
|
||||
appender.append(platformVersion);
|
||||
appender.append(platformBuildID);
|
||||
appender.append(os);
|
||||
appender.append(xpcomabi);
|
||||
appender.append(updateChannel);
|
||||
appender.append(isBlocklistEnabled);
|
||||
appender.append(isTelemetryEnabled);
|
||||
appender.append(extensionCount);
|
||||
appender.append(pluginCount);
|
||||
appender.append(themeCount);
|
||||
|
||||
// We need sorted values.
|
||||
if (addons != null) {
|
||||
appendSortedAddons(getNonIgnoredAddons(), appender);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the stable hash of the configured environment.
|
||||
*
|
||||
* @return the hash in base34, or null if there was a problem.
|
||||
*/
|
||||
public String getHash() {
|
||||
// It's never unset, so we only care about partial reads. volatile is enough.
|
||||
if (hash != null) {
|
||||
return hash;
|
||||
}
|
||||
|
||||
EnvironmentAppender appender = getAppender();
|
||||
if (appender == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
appendHash(appender);
|
||||
return hash = appender.toString();
|
||||
}
|
||||
|
||||
public EnvironmentV1(Class<? extends EnvironmentAppender> appenderClass) {
|
||||
super();
|
||||
this.appenderClass = appenderClass;
|
||||
}
|
||||
|
||||
public JSONObject getNonIgnoredAddons() {
|
||||
if (addons == null) {
|
||||
return null;
|
||||
}
|
||||
JSONObject out = new JSONObject();
|
||||
@SuppressWarnings("unchecked")
|
||||
Iterator<String> keys = addons.keys();
|
||||
while (keys.hasNext()) {
|
||||
try {
|
||||
final String key = keys.next();
|
||||
final Object obj = addons.get(key);
|
||||
if (obj != null &&
|
||||
obj instanceof JSONObject &&
|
||||
((JSONObject) obj).optBoolean("ignore", false)) {
|
||||
continue;
|
||||
}
|
||||
out.put(key, obj);
|
||||
} catch (JSONException ex) {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a collection of add-on descriptors, appending a consistent string
|
||||
* to the provided builder.
|
||||
*/
|
||||
public static void appendSortedAddons(JSONObject addons, final EnvironmentAppender builder) {
|
||||
final SortedSet<String> keys = HealthReportUtils.sortedKeySet(addons);
|
||||
|
||||
// For each add-on, produce a consistent, sorted mapping of its descriptor.
|
||||
for (String key : keys) {
|
||||
try {
|
||||
JSONObject addon = addons.getJSONObject(key);
|
||||
|
||||
// Now produce the output for this add-on.
|
||||
builder.append(key);
|
||||
builder.append("={");
|
||||
|
||||
for (String addonKey : HealthReportUtils.sortedKeySet(addon)) {
|
||||
builder.append(addonKey);
|
||||
builder.append("==");
|
||||
try {
|
||||
builder.append(addon.get(addonKey).toString());
|
||||
} catch (JSONException e) {
|
||||
builder.append("_e_");
|
||||
}
|
||||
}
|
||||
|
||||
builder.append("}");
|
||||
} catch (Exception e) {
|
||||
// Muffle.
|
||||
Logger.warn(LOG_TAG, "Invalid add-on for ID " + key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setJSONForAddons(byte[] json) throws Exception {
|
||||
setJSONForAddons(new String(json, "UTF-8"));
|
||||
}
|
||||
|
||||
public void setJSONForAddons(String json) throws Exception {
|
||||
if (json == null || "null".equals(json)) {
|
||||
addons = null;
|
||||
return;
|
||||
}
|
||||
addons = new JSONObject(json);
|
||||
}
|
||||
|
||||
public void setJSONForAddons(JSONObject json) {
|
||||
addons = json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Includes ignored add-ons.
|
||||
*/
|
||||
public String getNormalizedAddonsJSON() {
|
||||
// We trust that our input will already be normalized. If that assumption
|
||||
// is invalidated, then we'll be sorry.
|
||||
return (addons == null) ? "null" : addons.toString();
|
||||
}
|
||||
}
|
@ -128,7 +128,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||
};
|
||||
|
||||
private static final String[] COLUMNS_ENVIRONMENT_DETAILS = new String[] {
|
||||
"id", "hash",
|
||||
"id", "version", "hash",
|
||||
"profileCreation", "cpuCount", "memoryMB",
|
||||
|
||||
"isBlocklistEnabled", "isTelemetryEnabled", "extensionCount",
|
||||
@ -138,6 +138,8 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||
"appVersion", "appBuildID", "platformVersion", "platformBuildID", "os",
|
||||
"xpcomabi", "updateChannel",
|
||||
|
||||
"distribution", "osLocale", "appLocale", "acceptLangSet",
|
||||
|
||||
// Joined to the add-ons table.
|
||||
"addonsBody"
|
||||
};
|
||||
@ -188,7 +190,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||
protected final HealthReportSQLiteOpenHelper helper;
|
||||
|
||||
public static class HealthReportSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
public static final int CURRENT_VERSION = 5;
|
||||
public static final int CURRENT_VERSION = 6;
|
||||
public static final String LOG_TAG = "HealthReportSQL";
|
||||
|
||||
/**
|
||||
@ -252,7 +254,10 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||
" UNIQUE (body) " +
|
||||
")");
|
||||
|
||||
// N.B., hash collisions can occur across versions. In that case, the system
|
||||
// is likely to persist the original environment version.
|
||||
db.execSQL("CREATE TABLE environments (id INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
||||
" version INTEGER, " +
|
||||
" hash TEXT, " +
|
||||
" profileCreation INTEGER, " +
|
||||
" cpuCount INTEGER, " +
|
||||
@ -275,6 +280,12 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||
" os TEXT, " +
|
||||
" xpcomabi TEXT, " +
|
||||
" updateChannel TEXT, " +
|
||||
|
||||
" distribution TEXT, " +
|
||||
" osLocale TEXT, " +
|
||||
" appLocale TEXT, " +
|
||||
" acceptLangSet INTEGER, " +
|
||||
|
||||
" addonsID INTEGER, " +
|
||||
" FOREIGN KEY (addonsID) REFERENCES addons(id) ON DELETE RESTRICT, " +
|
||||
" UNIQUE (hash) " +
|
||||
@ -357,6 +368,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||
private void createAddonsEnvironmentsView(SQLiteDatabase db) {
|
||||
db.execSQL("CREATE VIEW environments_with_addons AS " +
|
||||
"SELECT e.id AS id, " +
|
||||
" e.version AS version, " +
|
||||
" e.hash AS hash, " +
|
||||
" e.profileCreation AS profileCreation, " +
|
||||
" e.cpuCount AS cpuCount, " +
|
||||
@ -379,6 +391,10 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||
" e.os AS os, " +
|
||||
" e.xpcomabi AS xpcomabi, " +
|
||||
" e.updateChannel AS updateChannel, " +
|
||||
" e.distribution AS distribution, " +
|
||||
" e.osLocale AS osLocale, " +
|
||||
" e.appLocale AS appLocale, " +
|
||||
" e.acceptLangSet AS acceptLangSet, " +
|
||||
" addons.body AS addonsBody " +
|
||||
"FROM environments AS e, addons " +
|
||||
"WHERE e.addonsID = addons.id");
|
||||
@ -417,6 +433,22 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||
db.delete(EVENTS_TEXTUAL, "field NOT IN (SELECT id FROM fields)", null);
|
||||
}
|
||||
|
||||
private void upgradeDatabaseFrom5to6(SQLiteDatabase db) {
|
||||
db.execSQL("DROP VIEW environments_with_addons");
|
||||
|
||||
// Add version to environment (default to 1).
|
||||
db.execSQL("ALTER TABLE environments ADD COLUMN version INTEGER DEFAULT 1");
|
||||
|
||||
// Add fields to environment (default to empty string).
|
||||
db.execSQL("ALTER TABLE environments ADD COLUMN distribution TEXT DEFAULT ''");
|
||||
db.execSQL("ALTER TABLE environments ADD COLUMN osLocale TEXT DEFAULT ''");
|
||||
db.execSQL("ALTER TABLE environments ADD COLUMN appLocale TEXT DEFAULT ''");
|
||||
db.execSQL("ALTER TABLE environments ADD COLUMN acceptLangSet INTEGER DEFAULT 0");
|
||||
|
||||
// Recreate view.
|
||||
createAddonsEnvironmentsView(db);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
if (oldVersion >= newVersion) {
|
||||
@ -432,6 +464,8 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||
upgradeDatabaseFrom3To4(db);
|
||||
case 4:
|
||||
upgradeDatabaseFrom4to5(db);
|
||||
case 5:
|
||||
upgradeDatabaseFrom5to6(db);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logger.error(LOG_TAG, "Failure in onUpgrade.", e);
|
||||
@ -536,6 +570,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||
|
||||
// Otherwise, add data and hash to the DB.
|
||||
ContentValues v = new ContentValues();
|
||||
v.put("version", version);
|
||||
v.put("hash", h);
|
||||
v.put("profileCreation", profileCreation);
|
||||
v.put("cpuCount", cpuCount);
|
||||
@ -558,6 +593,10 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||
v.put("os", os);
|
||||
v.put("xpcomabi", xpcomabi);
|
||||
v.put("updateChannel", updateChannel);
|
||||
v.put("distribution", distribution);
|
||||
v.put("osLocale", osLocale);
|
||||
v.put("appLocale", appLocale);
|
||||
v.put("acceptLangSet", acceptLangSet);
|
||||
|
||||
final SQLiteDatabase db = storage.helper.getWritableDatabase();
|
||||
|
||||
@ -643,6 +682,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||
}
|
||||
|
||||
public void init(ContentValues v) {
|
||||
version = v.containsKey("version") ? v.getAsInteger("version") : Environment.CURRENT_VERSION;
|
||||
profileCreation = v.getAsInteger("profileCreation");
|
||||
cpuCount = v.getAsInteger("cpuCount");
|
||||
memoryMB = v.getAsInteger("memoryMB");
|
||||
@ -667,6 +707,11 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||
xpcomabi = v.getAsString("xpcomabi");
|
||||
updateChannel = v.getAsString("updateChannel");
|
||||
|
||||
distribution = v.getAsString("distribution");
|
||||
osLocale = v.getAsString("osLocale");
|
||||
appLocale = v.getAsString("appLocale");
|
||||
acceptLangSet = v.getAsInteger("acceptLangSet");
|
||||
|
||||
try {
|
||||
setJSONForAddons(v.getAsString("addonsBody"));
|
||||
} catch (Exception e) {
|
||||
@ -686,6 +731,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||
public boolean init(Cursor cursor) {
|
||||
int i = 0;
|
||||
this.id = cursor.getInt(i++);
|
||||
this.version = cursor.getInt(i++);
|
||||
this.hash = cursor.getString(i++);
|
||||
|
||||
profileCreation = cursor.getInt(i++);
|
||||
@ -712,6 +758,11 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||
xpcomabi = cursor.getString(i++);
|
||||
updateChannel = cursor.getString(i++);
|
||||
|
||||
distribution = cursor.getString(i++);
|
||||
osLocale = cursor.getString(i++);
|
||||
appLocale = cursor.getString(i++);
|
||||
acceptLangSet = cursor.getInt(i++);
|
||||
|
||||
try {
|
||||
setJSONForAddons(cursor.getBlob(i++));
|
||||
} catch (Exception e) {
|
||||
@ -1339,6 +1390,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||
}
|
||||
|
||||
// Called internally only to ensure the same db instance is used.
|
||||
@SuppressWarnings("static-method")
|
||||
protected int deleteOrphanedEnv(final SQLiteDatabase db, final int curEnv) {
|
||||
final String whereClause =
|
||||
"id != ? AND " +
|
||||
@ -1353,6 +1405,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||
}
|
||||
|
||||
// Called internally only to ensure the same db instance is used.
|
||||
@SuppressWarnings("static-method")
|
||||
protected int deleteEventsBefore(final SQLiteDatabase db, final String dayString) {
|
||||
final String whereClause = "date < ?";
|
||||
final String[] whereArgs = new String[] {dayString};
|
||||
@ -1377,6 +1430,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||
}
|
||||
|
||||
// Called internally only to ensure the same db instance is used.
|
||||
@SuppressWarnings("static-method")
|
||||
protected int deleteOrphanedAddons(final SQLiteDatabase db) {
|
||||
final String whereClause = "id NOT IN (SELECT addonsID FROM environments)";
|
||||
return db.delete("addons", whereClause, null);
|
||||
|
@ -388,24 +388,117 @@ public class HealthReportGenerator {
|
||||
return gecko;
|
||||
}
|
||||
|
||||
// Null-safe string comparison.
|
||||
private static boolean stringsDiffer(final String a, final String b) {
|
||||
if (a == null) {
|
||||
return b != null;
|
||||
}
|
||||
return !a.equals(b);
|
||||
}
|
||||
|
||||
private static JSONObject getAppInfo(Environment e, Environment current) throws JSONException {
|
||||
JSONObject appinfo = new JSONObject();
|
||||
int changes = 0;
|
||||
if (current == null || current.isBlocklistEnabled != e.isBlocklistEnabled) {
|
||||
appinfo.put("isBlocklistEnabled", e.isBlocklistEnabled);
|
||||
changes++;
|
||||
|
||||
Logger.debug(LOG_TAG, "Generating appinfo for v" + e.version + " env " + e.hash);
|
||||
|
||||
// Is the environment in question newer than the diff target, or is
|
||||
// there no diff target?
|
||||
final boolean outdated = current == null ||
|
||||
e.version > current.version;
|
||||
|
||||
// Is the environment in question a different version (lower or higher),
|
||||
// or is there no diff target?
|
||||
final boolean differ = outdated || current.version > e.version;
|
||||
|
||||
// Always produce an output object if there's a version mismatch or this
|
||||
// isn't a diff. Otherwise, track as we go if there's any difference.
|
||||
boolean changed = differ;
|
||||
|
||||
switch (e.version) {
|
||||
// There's a straightforward correspondence between environment versions
|
||||
// and appinfo versions.
|
||||
case 2:
|
||||
appinfo.put("_v", 3);
|
||||
break;
|
||||
case 1:
|
||||
appinfo.put("_v", 2);
|
||||
break;
|
||||
default:
|
||||
Logger.warn(LOG_TAG, "Unknown environment version: " + e.version);
|
||||
return appinfo;
|
||||
}
|
||||
if (current == null || current.isTelemetryEnabled != e.isTelemetryEnabled) {
|
||||
appinfo.put("isTelemetryEnabled", e.isTelemetryEnabled);
|
||||
changes++;
|
||||
|
||||
switch (e.version) {
|
||||
case 2:
|
||||
if (populateAppInfoV2(appinfo, e, current, outdated)) {
|
||||
changed = true;
|
||||
}
|
||||
// Fall through.
|
||||
|
||||
case 1:
|
||||
// There is no older version than v1, so don't check outdated.
|
||||
if (populateAppInfoV1(e, current, appinfo)) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (current != null && changes == 0) {
|
||||
|
||||
if (!changed) {
|
||||
return null;
|
||||
}
|
||||
appinfo.put("_v", 2);
|
||||
|
||||
return appinfo;
|
||||
}
|
||||
|
||||
private static boolean populateAppInfoV1(Environment e,
|
||||
Environment current,
|
||||
JSONObject appinfo)
|
||||
throws JSONException {
|
||||
boolean changes = false;
|
||||
if (current == null || current.isBlocklistEnabled != e.isBlocklistEnabled) {
|
||||
appinfo.put("isBlocklistEnabled", e.isBlocklistEnabled);
|
||||
changes = true;
|
||||
}
|
||||
|
||||
if (current == null || current.isTelemetryEnabled != e.isTelemetryEnabled) {
|
||||
appinfo.put("isTelemetryEnabled", e.isTelemetryEnabled);
|
||||
changes = true;
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
private static boolean populateAppInfoV2(JSONObject appinfo,
|
||||
Environment e,
|
||||
Environment current,
|
||||
final boolean outdated)
|
||||
throws JSONException {
|
||||
boolean changes = false;
|
||||
if (outdated ||
|
||||
stringsDiffer(current.osLocale, e.osLocale)) {
|
||||
appinfo.put("osLocale", e.osLocale);
|
||||
changes = true;
|
||||
}
|
||||
|
||||
if (outdated ||
|
||||
stringsDiffer(current.appLocale, e.appLocale)) {
|
||||
appinfo.put("appLocale", e.appLocale);
|
||||
changes = true;
|
||||
}
|
||||
|
||||
if (outdated ||
|
||||
stringsDiffer(current.distribution, e.distribution)) {
|
||||
appinfo.put("distribution", e.distribution);
|
||||
changes = true;
|
||||
}
|
||||
|
||||
if (outdated ||
|
||||
current.acceptLangSet != e.acceptLangSet) {
|
||||
appinfo.put("acceptLangIsUserSet", e.acceptLangSet);
|
||||
changes = true;
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
|
||||
private static JSONObject getAddonCounts(Environment e, Environment current) throws JSONException {
|
||||
JSONObject counts = new JSONObject();
|
||||
int changes = 0;
|
||||
|
@ -10,6 +10,7 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Locale;
|
||||
import java.util.Scanner;
|
||||
|
||||
import org.json.JSONException;
|
||||
@ -32,8 +33,9 @@ public class ProfileInformationCache implements ProfileInformationProvider {
|
||||
* -: No version number; implicit v1.
|
||||
* 1: Add versioning (Bug 878670).
|
||||
* 2: Bump to regenerate add-on set after landing Bug 900694 (Bug 901622).
|
||||
* 3: Add distribution, osLocale, appLocale.
|
||||
*/
|
||||
public static final int FORMAT_VERSION = 2;
|
||||
public static final int FORMAT_VERSION = 3;
|
||||
|
||||
protected boolean initialized = false;
|
||||
protected boolean needsWrite = false;
|
||||
@ -42,7 +44,29 @@ public class ProfileInformationCache implements ProfileInformationProvider {
|
||||
|
||||
private volatile boolean blocklistEnabled = true;
|
||||
private volatile boolean telemetryEnabled = false;
|
||||
private volatile boolean isAcceptLangUserSet = false;
|
||||
|
||||
private volatile long profileCreationTime = 0;
|
||||
private volatile String distribution = "";
|
||||
|
||||
// There are really four kinds of locale in play:
|
||||
//
|
||||
// * The OS
|
||||
// * The Android environment of the app (setDefault)
|
||||
// * The Gecko locale
|
||||
// * The requested content locale (Accept-Language).
|
||||
//
|
||||
// We track only the first two, assuming that the Gecko locale will typically
|
||||
// be the same as the app locale.
|
||||
//
|
||||
// The app locale is fetched from the PIC because it can be modified at
|
||||
// runtime -- it won't necessarily be what Locale.getDefaultLocale() returns
|
||||
// in a fresh non-browser profile.
|
||||
//
|
||||
// We also track the OS locale here for the same reason -- we need to store
|
||||
// the default (OS) value before the locale-switching code takes effect!
|
||||
private volatile String osLocale = "";
|
||||
private volatile String appLocale = "";
|
||||
|
||||
private volatile JSONObject addons = null;
|
||||
|
||||
@ -62,7 +86,11 @@ public class ProfileInformationCache implements ProfileInformationProvider {
|
||||
object.put("version", FORMAT_VERSION);
|
||||
object.put("blocklist", blocklistEnabled);
|
||||
object.put("telemetry", telemetryEnabled);
|
||||
object.put("isAcceptLangUserSet", isAcceptLangUserSet);
|
||||
object.put("profileCreated", profileCreationTime);
|
||||
object.put("osLocale", osLocale);
|
||||
object.put("appLocale", appLocale);
|
||||
object.put("distribution", distribution);
|
||||
object.put("addons", addons);
|
||||
} catch (JSONException e) {
|
||||
// There isn't much we can do about this.
|
||||
@ -86,8 +114,12 @@ public class ProfileInformationCache implements ProfileInformationProvider {
|
||||
case FORMAT_VERSION:
|
||||
blocklistEnabled = object.getBoolean("blocklist");
|
||||
telemetryEnabled = object.getBoolean("telemetry");
|
||||
isAcceptLangUserSet = object.getBoolean("isAcceptLangUserSet");
|
||||
profileCreationTime = object.getLong("profileCreated");
|
||||
addons = object.getJSONObject("addons");
|
||||
distribution = object.getString("distribution");
|
||||
osLocale = object.getString("osLocale");
|
||||
appLocale = object.getString("appLocale");
|
||||
return true;
|
||||
default:
|
||||
Logger.warn(LOG_TAG, "Unable to restore from version " + version + " PIC file: expecting " + FORMAT_VERSION);
|
||||
@ -206,6 +238,18 @@ public class ProfileInformationCache implements ProfileInformationProvider {
|
||||
needsWrite = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAcceptLangUserSet() {
|
||||
ensureInitialized();
|
||||
return isAcceptLangUserSet;
|
||||
}
|
||||
|
||||
public void setAcceptLangUserSet(boolean value) {
|
||||
Logger.debug(LOG_TAG, "Setting accept-lang as user-set: " + value);
|
||||
isAcceptLangUserSet = value;
|
||||
needsWrite = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getProfileCreationTime() {
|
||||
ensureInitialized();
|
||||
@ -218,17 +262,83 @@ public class ProfileInformationCache implements ProfileInformationProvider {
|
||||
needsWrite = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDistributionString() {
|
||||
ensureInitialized();
|
||||
return distribution;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that your arguments are non-null.
|
||||
*/
|
||||
public void setDistributionString(String distributionID, String distributionVersion) {
|
||||
Logger.debug(LOG_TAG, "Setting distribution: " + distributionID + ", " + distributionVersion);
|
||||
distribution = distributionID + ":" + distributionVersion;
|
||||
needsWrite = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAppLocale() {
|
||||
ensureInitialized();
|
||||
return appLocale;
|
||||
}
|
||||
|
||||
public void setAppLocale(String value) {
|
||||
if (value.equalsIgnoreCase(appLocale)) {
|
||||
return;
|
||||
}
|
||||
Logger.debug(LOG_TAG, "Setting app locale: " + value);
|
||||
appLocale = value.toLowerCase(Locale.US);
|
||||
needsWrite = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOSLocale() {
|
||||
ensureInitialized();
|
||||
return osLocale;
|
||||
}
|
||||
|
||||
public void setOSLocale(String value) {
|
||||
if (value.equalsIgnoreCase(osLocale)) {
|
||||
return;
|
||||
}
|
||||
Logger.debug(LOG_TAG, "Setting OS locale: " + value);
|
||||
osLocale = value.toLowerCase(Locale.US);
|
||||
needsWrite = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the PIC, if necessary, to match the current locale environment.
|
||||
*
|
||||
* @return true if the PIC needed to be updated.
|
||||
*/
|
||||
public boolean updateLocales(String osLocale, String appLocale) {
|
||||
if (this.osLocale.equalsIgnoreCase(osLocale) &&
|
||||
(appLocale == null || this.appLocale.equalsIgnoreCase(appLocale))) {
|
||||
return false;
|
||||
}
|
||||
this.setOSLocale(osLocale);
|
||||
if (appLocale != null) {
|
||||
this.setAppLocale(appLocale);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject getAddonsJSON() {
|
||||
ensureInitialized();
|
||||
return addons;
|
||||
}
|
||||
|
||||
public void updateJSONForAddon(String id, String json) throws Exception {
|
||||
addons.put(id, new JSONObject(json));
|
||||
needsWrite = true;
|
||||
}
|
||||
|
||||
public void removeAddon(String id) {
|
||||
addons.remove(id);
|
||||
if (null != addons.remove(id)) {
|
||||
needsWrite = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -240,6 +350,7 @@ public class ProfileInformationCache implements ProfileInformationProvider {
|
||||
}
|
||||
try {
|
||||
addons.put(id, json);
|
||||
needsWrite = true;
|
||||
} catch (Exception e) {
|
||||
// Why would this happen?
|
||||
Logger.warn(LOG_TAG, "Unexpected failure updating JSON for add-on.", e);
|
||||
@ -253,9 +364,11 @@ public class ProfileInformationCache implements ProfileInformationProvider {
|
||||
*/
|
||||
public void setJSONForAddons(String json) throws Exception {
|
||||
addons = new JSONObject(json);
|
||||
needsWrite = true;
|
||||
}
|
||||
|
||||
public void setJSONForAddons(JSONObject json) {
|
||||
addons = json;
|
||||
needsWrite = true;
|
||||
}
|
||||
}
|
||||
|
@ -13,11 +13,11 @@ import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.Distribution;
|
||||
import org.mozilla.gecko.Distribution.DistributionDescriptor;
|
||||
import org.mozilla.gecko.GeckoApp;
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.GeckoEvent;
|
||||
import org.mozilla.gecko.PrefsHelper;
|
||||
import org.mozilla.gecko.PrefsHelper.PrefHandler;
|
||||
|
||||
import org.mozilla.gecko.background.healthreport.EnvironmentBuilder;
|
||||
import org.mozilla.gecko.background.healthreport.HealthReportDatabaseStorage;
|
||||
@ -38,6 +38,7 @@ import java.io.FileOutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.Scanner;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@ -50,8 +51,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
* Keep an instance of this class around.
|
||||
*
|
||||
* Tell it when an environment attribute has changed: call {@link
|
||||
* #onBlocklistPrefChanged(boolean)} or {@link
|
||||
* #onTelemetryPrefChanged(boolean)}, followed by {@link
|
||||
* #onAppLocaleChanged(String)} followed by {@link
|
||||
* #onEnvironmentChanged()}.
|
||||
*
|
||||
* Use it to record events: {@link #recordSearch(String, String)}.
|
||||
@ -60,8 +60,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
*/
|
||||
public class BrowserHealthRecorder implements GeckoEventListener {
|
||||
private static final String LOG_TAG = "GeckoHealthRec";
|
||||
private static final String PREF_ACCEPT_LANG = "intl.accept_languages";
|
||||
private static final String PREF_BLOCKLIST_ENABLED = "extensions.blocklist.enabled";
|
||||
private static final String EVENT_ADDONS_ALL = "Addons:All";
|
||||
private static final String EVENT_SNAPSHOT = "HealthReport:Snapshot";
|
||||
private static final String EVENT_ADDONS_CHANGE = "Addons:Change";
|
||||
private static final String EVENT_ADDONS_UNINSTALLING = "Addons:Uninstalling";
|
||||
private static final String EVENT_PREF_CHANGE = "Pref:Change";
|
||||
@ -242,8 +243,15 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
||||
|
||||
/**
|
||||
* This constructor does IO. Run it on a background thread.
|
||||
*
|
||||
* appLocale can be null, which indicates that it will be provided later.
|
||||
*/
|
||||
public BrowserHealthRecorder(final Context context, final String profilePath, final EventDispatcher dispatcher, SessionInformation previousSession) {
|
||||
public BrowserHealthRecorder(final Context context,
|
||||
final String profilePath,
|
||||
final EventDispatcher dispatcher,
|
||||
final String osLocale,
|
||||
final String appLocale,
|
||||
SessionInformation previousSession) {
|
||||
Log.d(LOG_TAG, "Initializing. Dispatcher is " + dispatcher);
|
||||
this.dispatcher = dispatcher;
|
||||
this.previousSession = previousSession;
|
||||
@ -263,9 +271,12 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
||||
this.client = null;
|
||||
}
|
||||
|
||||
// Note that the PIC is not necessarily fully initialized at this point:
|
||||
// we haven't set the app locale. This must be done before an environment
|
||||
// is recorded.
|
||||
this.profileCache = new ProfileInformationCache(profilePath);
|
||||
try {
|
||||
this.initialize(context, profilePath);
|
||||
this.initialize(context, profilePath, osLocale, appLocale);
|
||||
} catch (Exception e) {
|
||||
Log.e(LOG_TAG, "Exception initializing.", e);
|
||||
}
|
||||
@ -299,7 +310,7 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
||||
}
|
||||
|
||||
private void unregisterEventListeners() {
|
||||
this.dispatcher.unregisterEventListener(EVENT_ADDONS_ALL, this);
|
||||
this.dispatcher.unregisterEventListener(EVENT_SNAPSHOT, this);
|
||||
this.dispatcher.unregisterEventListener(EVENT_ADDONS_CHANGE, this);
|
||||
this.dispatcher.unregisterEventListener(EVENT_ADDONS_UNINSTALLING, this);
|
||||
this.dispatcher.unregisterEventListener(EVENT_PREF_CHANGE, this);
|
||||
@ -307,14 +318,9 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
||||
this.dispatcher.unregisterEventListener(EVENT_SEARCH, this);
|
||||
}
|
||||
|
||||
public void onBlocklistPrefChanged(boolean to) {
|
||||
public void onAppLocaleChanged(String to) {
|
||||
this.profileCache.beginInitialization();
|
||||
this.profileCache.setBlocklistEnabled(to);
|
||||
}
|
||||
|
||||
public void onTelemetryPrefChanged(boolean to) {
|
||||
this.profileCache.beginInitialization();
|
||||
this.profileCache.setTelemetryEnabled(to);
|
||||
this.profileCache.setAppLocale(to);
|
||||
}
|
||||
|
||||
public void onAddonChanged(String id, JSONObject json) {
|
||||
@ -340,8 +346,7 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
||||
* environment, such that a new environment should be computed and prepared
|
||||
* for use in future events.
|
||||
*
|
||||
* Invoke this method after calls that mutate the environment, such as
|
||||
* {@link #onBlocklistPrefChanged(boolean)}.
|
||||
* Invoke this method after calls that mutate the environment.
|
||||
*
|
||||
* If this change resulted in a transition between two environments, {@link
|
||||
* #onEnvironmentTransition(int, int)} will be invoked on the background
|
||||
@ -491,14 +496,36 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
||||
return time;
|
||||
}
|
||||
|
||||
private void handlePrefValue(final String pref, final boolean value) {
|
||||
Log.d(LOG_TAG, "Incorporating environment: " + pref + " = " + value);
|
||||
if (AppConstants.TELEMETRY_PREF_NAME.equals(pref)) {
|
||||
profileCache.setTelemetryEnabled(value);
|
||||
private void onPrefMessage(final String pref, final JSONObject message) {
|
||||
Log.d(LOG_TAG, "Incorporating environment: " + pref);
|
||||
if (PREF_ACCEPT_LANG.equals(pref)) {
|
||||
// We only record whether this is user-set.
|
||||
try {
|
||||
this.profileCache.beginInitialization();
|
||||
this.profileCache.setAcceptLangUserSet(message.getBoolean("isUserSet"));
|
||||
} catch (JSONException ex) {
|
||||
Log.w(LOG_TAG, "Unexpected JSONException fetching isUserSet for " + pref);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (PREF_BLOCKLIST_ENABLED.equals(pref)) {
|
||||
profileCache.setBlocklistEnabled(value);
|
||||
|
||||
// (We only handle boolean prefs right now.)
|
||||
try {
|
||||
boolean value = message.getBoolean("value");
|
||||
|
||||
if (AppConstants.TELEMETRY_PREF_NAME.equals(pref)) {
|
||||
this.profileCache.beginInitialization();
|
||||
this.profileCache.setTelemetryEnabled(value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (PREF_BLOCKLIST_ENABLED.equals(pref)) {
|
||||
this.profileCache.beginInitialization();
|
||||
this.profileCache.setBlocklistEnabled(value);
|
||||
return;
|
||||
}
|
||||
} catch (JSONException ex) {
|
||||
Log.w(LOG_TAG, "Unexpected JSONException fetching boolean value for " + pref);
|
||||
return;
|
||||
}
|
||||
Log.w(LOG_TAG, "Unexpected pref: " + pref);
|
||||
@ -571,7 +598,9 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
||||
* Add provider-specific initialization in this method.
|
||||
*/
|
||||
private synchronized void initialize(final Context context,
|
||||
final String profilePath)
|
||||
final String profilePath,
|
||||
final String osLocale,
|
||||
final String appLocale)
|
||||
throws java.io.IOException {
|
||||
|
||||
Log.d(LOG_TAG, "Initializing profile cache.");
|
||||
@ -579,6 +608,9 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
||||
|
||||
// If we can restore state from last time, great.
|
||||
if (this.profileCache.restoreUnlessInitialized()) {
|
||||
this.profileCache.updateLocales(osLocale, appLocale);
|
||||
this.profileCache.completeInitialization();
|
||||
|
||||
Log.d(LOG_TAG, "Successfully restored state. Initializing storage.");
|
||||
initializeStorage();
|
||||
return;
|
||||
@ -587,31 +619,24 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
||||
// Otherwise, let's initialize it from scratch.
|
||||
this.profileCache.beginInitialization();
|
||||
this.profileCache.setProfileCreationTime(getAndPersistProfileInitTime(context, profilePath));
|
||||
this.profileCache.setOSLocale(osLocale);
|
||||
this.profileCache.setAppLocale(appLocale);
|
||||
|
||||
final BrowserHealthRecorder self = this;
|
||||
|
||||
PrefHandler handler = new PrefsHelper.PrefHandlerBase() {
|
||||
// Because the distribution lookup can take some time, do it at the end of
|
||||
// our background startup work, along with the Gecko snapshot fetch.
|
||||
final GeckoEventListener self = this;
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void prefValue(String pref, boolean value) {
|
||||
handlePrefValue(pref, value);
|
||||
public void run() {
|
||||
final DistributionDescriptor desc = new Distribution(context).getDescriptor();
|
||||
if (desc != null && desc.valid) {
|
||||
profileCache.setDistributionString(desc.id, desc.version);
|
||||
}
|
||||
Log.d(LOG_TAG, "Requesting all add-ons and FHR prefs from Gecko.");
|
||||
dispatcher.registerEventListener(EVENT_SNAPSHOT, self);
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HealthReport:RequestSnapshot", null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
Log.d(LOG_TAG, "Requesting all add-ons from Gecko.");
|
||||
dispatcher.registerEventListener(EVENT_ADDONS_ALL, self);
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Addons:FetchAll", null));
|
||||
// Wait for the broadcast event which completes our initialization.
|
||||
}
|
||||
};
|
||||
|
||||
// Oh, singletons.
|
||||
PrefsHelper.getPrefs(new String[] {
|
||||
AppConstants.TELEMETRY_PREF_NAME,
|
||||
PREF_BLOCKLIST_ENABLED
|
||||
},
|
||||
handler);
|
||||
Log.d(LOG_TAG, "Requested prefs.");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -638,12 +663,22 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
||||
@Override
|
||||
public void handleMessage(String event, JSONObject message) {
|
||||
try {
|
||||
if (EVENT_ADDONS_ALL.equals(event)) {
|
||||
Log.d(LOG_TAG, "Got all add-ons.");
|
||||
if (EVENT_SNAPSHOT.equals(event)) {
|
||||
Log.d(LOG_TAG, "Got all add-ons and prefs.");
|
||||
try {
|
||||
JSONObject addons = message.getJSONObject("json");
|
||||
JSONObject json = message.getJSONObject("json");
|
||||
JSONObject addons = json.getJSONObject("addons");
|
||||
Log.i(LOG_TAG, "Persisting " + addons.length() + " add-ons.");
|
||||
profileCache.setJSONForAddons(addons);
|
||||
|
||||
JSONObject prefs = json.getJSONObject("prefs");
|
||||
Log.i(LOG_TAG, "Persisting prefs.");
|
||||
Iterator<?> keys = prefs.keys();
|
||||
while (keys.hasNext()) {
|
||||
String pref = (String) keys.next();
|
||||
this.onPrefMessage(pref, prefs.getJSONObject(pref));
|
||||
}
|
||||
|
||||
profileCache.completeInitialization();
|
||||
} catch (java.io.IOException e) {
|
||||
Log.e(LOG_TAG, "Error completing profile cache initialization.", e);
|
||||
@ -675,7 +710,7 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
||||
if (EVENT_PREF_CHANGE.equals(event)) {
|
||||
final String pref = message.getString("pref");
|
||||
Log.d(LOG_TAG, "Pref changed: " + pref);
|
||||
handlePrefValue(pref, message.getBoolean("value"));
|
||||
this.onPrefMessage(pref, message);
|
||||
this.onEnvironmentChanged();
|
||||
return;
|
||||
}
|
||||
|
@ -5,3 +5,609 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DIRS += ['locales']
|
||||
|
||||
include('android-services.mozbuild')
|
||||
|
||||
ANDROID_GENERATED_RESFILES += [
|
||||
'res/drawable-hdpi/icon.png',
|
||||
'res/drawable-mdpi/icon.png',
|
||||
'res/drawable-xhdpi/icon.png',
|
||||
'res/drawable-xxhdpi/icon.png',
|
||||
'res/values/strings.xml',
|
||||
]
|
||||
|
||||
ANDROID_RESFILES += [
|
||||
'resources/anim/grow_fade_in.xml',
|
||||
'resources/anim/grow_fade_in_center.xml',
|
||||
'resources/anim/popup_hide.xml',
|
||||
'resources/anim/popup_show.xml',
|
||||
'resources/anim/progress_spinner.xml',
|
||||
'resources/anim/shrink_fade_out.xml',
|
||||
'resources/color/primary_text.xml',
|
||||
'resources/color/primary_text_inverse.xml',
|
||||
'resources/color/secondary_text.xml',
|
||||
'resources/color/secondary_text_inverse.xml',
|
||||
'resources/color/select_item_multichoice.xml',
|
||||
'resources/color/tertiary_text.xml',
|
||||
'resources/color/tertiary_text_inverse.xml',
|
||||
'resources/color/top_sites_grid_item_title.xml',
|
||||
'resources/color/url_bar_title.xml',
|
||||
'resources/color/url_bar_title_hint.xml',
|
||||
'resources/drawable-hdpi-v11/alert_addon.png',
|
||||
'resources/drawable-hdpi-v11/alert_app.png',
|
||||
'resources/drawable-hdpi-v11/alert_camera.png',
|
||||
'resources/drawable-hdpi-v11/alert_download.png',
|
||||
'resources/drawable-hdpi-v11/alert_mic.png',
|
||||
'resources/drawable-hdpi-v11/alert_mic_camera.png',
|
||||
'resources/drawable-hdpi-v11/firefox_settings_alert.png',
|
||||
'resources/drawable-hdpi-v11/ic_menu_addons.png',
|
||||
'resources/drawable-hdpi-v11/ic_menu_apps.png',
|
||||
'resources/drawable-hdpi-v11/ic_menu_back.png',
|
||||
'resources/drawable-hdpi-v11/ic_menu_bookmark_add.png',
|
||||
'resources/drawable-hdpi-v11/ic_menu_bookmark_remove.png',
|
||||
'resources/drawable-hdpi-v11/ic_menu_desktop_mode_off.png',
|
||||
'resources/drawable-hdpi-v11/ic_menu_desktop_mode_on.png',
|
||||
'resources/drawable-hdpi-v11/ic_menu_downloads.png',
|
||||
'resources/drawable-hdpi-v11/ic_menu_find_in_page.png',
|
||||
'resources/drawable-hdpi-v11/ic_menu_forward.png',
|
||||
'resources/drawable-hdpi-v11/ic_menu_new_private_tab.png',
|
||||
'resources/drawable-hdpi-v11/ic_menu_new_tab.png',
|
||||
'resources/drawable-hdpi-v11/ic_menu_quit.png',
|
||||
'resources/drawable-hdpi-v11/ic_menu_reload.png',
|
||||
'resources/drawable-hdpi-v11/ic_menu_save_as_pdf.png',
|
||||
'resources/drawable-hdpi-v11/ic_menu_settings.png',
|
||||
'resources/drawable-hdpi-v11/ic_menu_share.png',
|
||||
'resources/drawable-hdpi-v11/ic_menu_tools.png',
|
||||
'resources/drawable-hdpi-v11/ic_status_logo.png',
|
||||
'resources/drawable-hdpi/abouthome_thumbnail.png',
|
||||
'resources/drawable-hdpi/alert_addon.png',
|
||||
'resources/drawable-hdpi/alert_app.png',
|
||||
'resources/drawable-hdpi/alert_camera.png',
|
||||
'resources/drawable-hdpi/alert_download.png',
|
||||
'resources/drawable-hdpi/alert_mic.png',
|
||||
'resources/drawable-hdpi/alert_mic_camera.png',
|
||||
'resources/drawable-hdpi/arrow_popup_bg.9.png',
|
||||
'resources/drawable-hdpi/blank.png',
|
||||
'resources/drawable-hdpi/bookmark_folder_closed.png',
|
||||
'resources/drawable-hdpi/bookmark_folder_opened.png',
|
||||
'resources/drawable-hdpi/close.png',
|
||||
'resources/drawable-hdpi/favicon.png',
|
||||
'resources/drawable-hdpi/find_close.png',
|
||||
'resources/drawable-hdpi/find_next.png',
|
||||
'resources/drawable-hdpi/find_prev.png',
|
||||
'resources/drawable-hdpi/folder.png',
|
||||
'resources/drawable-hdpi/grid_icon_bg_activated.9.png',
|
||||
'resources/drawable-hdpi/grid_icon_bg_focused.9.png',
|
||||
'resources/drawable-hdpi/handle_end.png',
|
||||
'resources/drawable-hdpi/handle_middle.png',
|
||||
'resources/drawable-hdpi/handle_start.png',
|
||||
'resources/drawable-hdpi/history_tabs_indicator_selected.9.png',
|
||||
'resources/drawable-hdpi/home_bg.png',
|
||||
'resources/drawable-hdpi/home_star.png',
|
||||
'resources/drawable-hdpi/home_tab_menu_strip.9.png',
|
||||
'resources/drawable-hdpi/ic_menu_addons_filler.png',
|
||||
'resources/drawable-hdpi/ic_menu_bookmark_add.png',
|
||||
'resources/drawable-hdpi/ic_menu_bookmark_remove.png',
|
||||
'resources/drawable-hdpi/ic_menu_character_encoding.png',
|
||||
'resources/drawable-hdpi/ic_menu_forward.png',
|
||||
'resources/drawable-hdpi/ic_menu_guest.png',
|
||||
'resources/drawable-hdpi/ic_menu_new_private_tab.png',
|
||||
'resources/drawable-hdpi/ic_menu_new_tab.png',
|
||||
'resources/drawable-hdpi/ic_menu_reload.png',
|
||||
'resources/drawable-hdpi/ic_status_logo.png',
|
||||
'resources/drawable-hdpi/ic_url_bar_go.png',
|
||||
'resources/drawable-hdpi/ic_url_bar_reader.png',
|
||||
'resources/drawable-hdpi/ic_url_bar_search.png',
|
||||
'resources/drawable-hdpi/ic_url_bar_star.png',
|
||||
'resources/drawable-hdpi/ic_url_bar_tab.png',
|
||||
'resources/drawable-hdpi/icon_bookmarks_empty.png',
|
||||
'resources/drawable-hdpi/icon_last_tabs.png',
|
||||
'resources/drawable-hdpi/icon_last_tabs_empty.png',
|
||||
'resources/drawable-hdpi/icon_most_recent.png',
|
||||
'resources/drawable-hdpi/icon_most_recent_empty.png',
|
||||
'resources/drawable-hdpi/icon_most_visited.png',
|
||||
'resources/drawable-hdpi/icon_openinapp.png',
|
||||
'resources/drawable-hdpi/icon_pageaction.png',
|
||||
'resources/drawable-hdpi/icon_reading_list_empty.png',
|
||||
'resources/drawable-hdpi/larry.png',
|
||||
'resources/drawable-hdpi/lock_identified.png',
|
||||
'resources/drawable-hdpi/lock_verified.png',
|
||||
'resources/drawable-hdpi/menu.png',
|
||||
'resources/drawable-hdpi/menu_item_check.png',
|
||||
'resources/drawable-hdpi/menu_item_more.png',
|
||||
'resources/drawable-hdpi/menu_item_uncheck.png',
|
||||
'resources/drawable-hdpi/menu_panel_bg.9.png',
|
||||
'resources/drawable-hdpi/menu_pb.png',
|
||||
'resources/drawable-hdpi/menu_popup_arrow_bottom.png',
|
||||
'resources/drawable-hdpi/menu_popup_arrow_top.png',
|
||||
'resources/drawable-hdpi/menu_popup_bg.9.png',
|
||||
'resources/drawable-hdpi/pause.png',
|
||||
'resources/drawable-hdpi/pin.png',
|
||||
'resources/drawable-hdpi/play.png',
|
||||
'resources/drawable-hdpi/reader.png',
|
||||
'resources/drawable-hdpi/reader_active.png',
|
||||
'resources/drawable-hdpi/reader_cropped.png',
|
||||
'resources/drawable-hdpi/reading_list.png',
|
||||
'resources/drawable-hdpi/shield.png',
|
||||
'resources/drawable-hdpi/shield_doorhanger.png',
|
||||
'resources/drawable-hdpi/spinner_default.9.png',
|
||||
'resources/drawable-hdpi/spinner_focused.9.png',
|
||||
'resources/drawable-hdpi/spinner_pressed.9.png',
|
||||
'resources/drawable-hdpi/tab_close.png',
|
||||
'resources/drawable-hdpi/tab_indicator_divider.9.png',
|
||||
'resources/drawable-hdpi/tab_indicator_selected.9.png',
|
||||
'resources/drawable-hdpi/tab_indicator_selected_focused.9.png',
|
||||
'resources/drawable-hdpi/tab_new.png',
|
||||
'resources/drawable-hdpi/tab_new_pb.png',
|
||||
'resources/drawable-hdpi/tab_thumbnail_default.png',
|
||||
'resources/drawable-hdpi/tab_thumbnail_shadow.png',
|
||||
'resources/drawable-hdpi/tabs_count.png',
|
||||
'resources/drawable-hdpi/tabs_count_foreground.png',
|
||||
'resources/drawable-hdpi/tabs_normal.png',
|
||||
'resources/drawable-hdpi/tabs_private.png',
|
||||
'resources/drawable-hdpi/tabs_synced.png',
|
||||
'resources/drawable-hdpi/tip_addsearch.png',
|
||||
'resources/drawable-hdpi/top_site_add.png',
|
||||
'resources/drawable-hdpi/url_bar_entry_default.9.png',
|
||||
'resources/drawable-hdpi/url_bar_entry_default_pb.9.png',
|
||||
'resources/drawable-hdpi/url_bar_entry_pressed.9.png',
|
||||
'resources/drawable-hdpi/url_bar_entry_pressed_pb.9.png',
|
||||
'resources/drawable-hdpi/urlbar_stop.png',
|
||||
'resources/drawable-hdpi/validation_arrow.png',
|
||||
'resources/drawable-hdpi/validation_arrow_inverted.png',
|
||||
'resources/drawable-hdpi/validation_bg.9.png',
|
||||
'resources/drawable-hdpi/warning.png',
|
||||
'resources/drawable-hdpi/warning_doorhanger.png',
|
||||
'resources/drawable-large-hdpi-v11/arrow_popup_bg.9.png',
|
||||
'resources/drawable-large-hdpi-v11/ic_menu_forward.png',
|
||||
'resources/drawable-large-hdpi-v11/ic_menu_reload.png',
|
||||
'resources/drawable-large-hdpi-v11/menu.png',
|
||||
'resources/drawable-large-land-v11/home_history_tabs_indicator.xml',
|
||||
'resources/drawable-large-mdpi-v11/arrow_popup_bg.9.png',
|
||||
'resources/drawable-large-mdpi-v11/ic_menu_forward.png',
|
||||
'resources/drawable-large-mdpi-v11/ic_menu_reload.png',
|
||||
'resources/drawable-large-mdpi-v11/menu.png',
|
||||
'resources/drawable-large-xhdpi-v11/arrow_popup_bg.9.png',
|
||||
'resources/drawable-large-xhdpi-v11/ic_menu_forward.png',
|
||||
'resources/drawable-large-xhdpi-v11/ic_menu_reload.png',
|
||||
'resources/drawable-large-xhdpi-v11/menu.png',
|
||||
'resources/drawable-mdpi-v11/alert_addon.png',
|
||||
'resources/drawable-mdpi-v11/alert_app.png',
|
||||
'resources/drawable-mdpi-v11/alert_camera.png',
|
||||
'resources/drawable-mdpi-v11/alert_download.png',
|
||||
'resources/drawable-mdpi-v11/alert_mic.png',
|
||||
'resources/drawable-mdpi-v11/alert_mic_camera.png',
|
||||
'resources/drawable-mdpi-v11/firefox_settings_alert.png',
|
||||
'resources/drawable-mdpi-v11/ic_menu_addons.png',
|
||||
'resources/drawable-mdpi-v11/ic_menu_apps.png',
|
||||
'resources/drawable-mdpi-v11/ic_menu_back.png',
|
||||
'resources/drawable-mdpi-v11/ic_menu_bookmark_add.png',
|
||||
'resources/drawable-mdpi-v11/ic_menu_bookmark_remove.png',
|
||||
'resources/drawable-mdpi-v11/ic_menu_desktop_mode_off.png',
|
||||
'resources/drawable-mdpi-v11/ic_menu_desktop_mode_on.png',
|
||||
'resources/drawable-mdpi-v11/ic_menu_downloads.png',
|
||||
'resources/drawable-mdpi-v11/ic_menu_find_in_page.png',
|
||||
'resources/drawable-mdpi-v11/ic_menu_forward.png',
|
||||
'resources/drawable-mdpi-v11/ic_menu_new_private_tab.png',
|
||||
'resources/drawable-mdpi-v11/ic_menu_new_tab.png',
|
||||
'resources/drawable-mdpi-v11/ic_menu_quit.png',
|
||||
'resources/drawable-mdpi-v11/ic_menu_reload.png',
|
||||
'resources/drawable-mdpi-v11/ic_menu_save_as_pdf.png',
|
||||
'resources/drawable-mdpi-v11/ic_menu_settings.png',
|
||||
'resources/drawable-mdpi-v11/ic_menu_share.png',
|
||||
'resources/drawable-mdpi-v11/ic_menu_tools.png',
|
||||
'resources/drawable-mdpi-v11/ic_status_logo.png',
|
||||
'resources/drawable-mdpi/abouthome_thumbnail.png',
|
||||
'resources/drawable-mdpi/alert_addon.png',
|
||||
'resources/drawable-mdpi/alert_app.png',
|
||||
'resources/drawable-mdpi/alert_camera.png',
|
||||
'resources/drawable-mdpi/alert_download.png',
|
||||
'resources/drawable-mdpi/alert_mic.png',
|
||||
'resources/drawable-mdpi/alert_mic_camera.png',
|
||||
'resources/drawable-mdpi/arrow_popup_bg.9.png',
|
||||
'resources/drawable-mdpi/autocomplete_list_bg.9.png',
|
||||
'resources/drawable-mdpi/blank.png',
|
||||
'resources/drawable-mdpi/bookmark_folder_closed.png',
|
||||
'resources/drawable-mdpi/bookmark_folder_opened.png',
|
||||
'resources/drawable-mdpi/bookmarkdefaults_favicon_addons.png',
|
||||
'resources/drawable-mdpi/bookmarkdefaults_favicon_support.png',
|
||||
'resources/drawable-mdpi/close.png',
|
||||
'resources/drawable-mdpi/desktop_notification.png',
|
||||
'resources/drawable-mdpi/favicon.png',
|
||||
'resources/drawable-mdpi/find_close.png',
|
||||
'resources/drawable-mdpi/find_next.png',
|
||||
'resources/drawable-mdpi/find_prev.png',
|
||||
'resources/drawable-mdpi/folder.png',
|
||||
'resources/drawable-mdpi/grid_icon_bg_activated.9.png',
|
||||
'resources/drawable-mdpi/grid_icon_bg_focused.9.png',
|
||||
'resources/drawable-mdpi/handle_end.png',
|
||||
'resources/drawable-mdpi/handle_middle.png',
|
||||
'resources/drawable-mdpi/handle_start.png',
|
||||
'resources/drawable-mdpi/history_tabs_indicator_selected.9.png',
|
||||
'resources/drawable-mdpi/home_tab_menu_strip.9.png',
|
||||
'resources/drawable-mdpi/ic_menu_addons_filler.png',
|
||||
'resources/drawable-mdpi/ic_menu_bookmark_add.png',
|
||||
'resources/drawable-mdpi/ic_menu_bookmark_remove.png',
|
||||
'resources/drawable-mdpi/ic_menu_character_encoding.png',
|
||||
'resources/drawable-mdpi/ic_menu_forward.png',
|
||||
'resources/drawable-mdpi/ic_menu_guest.png',
|
||||
'resources/drawable-mdpi/ic_menu_new_private_tab.png',
|
||||
'resources/drawable-mdpi/ic_menu_new_tab.png',
|
||||
'resources/drawable-mdpi/ic_menu_reload.png',
|
||||
'resources/drawable-mdpi/ic_status_logo.png',
|
||||
'resources/drawable-mdpi/ic_url_bar_go.png',
|
||||
'resources/drawable-mdpi/ic_url_bar_reader.png',
|
||||
'resources/drawable-mdpi/ic_url_bar_search.png',
|
||||
'resources/drawable-mdpi/ic_url_bar_star.png',
|
||||
'resources/drawable-mdpi/ic_url_bar_tab.png',
|
||||
'resources/drawable-mdpi/icon_bookmarks_empty.png',
|
||||
'resources/drawable-mdpi/icon_last_tabs.png',
|
||||
'resources/drawable-mdpi/icon_last_tabs_empty.png',
|
||||
'resources/drawable-mdpi/icon_most_recent.png',
|
||||
'resources/drawable-mdpi/icon_most_recent_empty.png',
|
||||
'resources/drawable-mdpi/icon_most_visited.png',
|
||||
'resources/drawable-mdpi/icon_openinapp.png',
|
||||
'resources/drawable-mdpi/icon_pageaction.png',
|
||||
'resources/drawable-mdpi/icon_reading_list_empty.png',
|
||||
'resources/drawable-mdpi/larry.png',
|
||||
'resources/drawable-mdpi/lock_identified.png',
|
||||
'resources/drawable-mdpi/lock_verified.png',
|
||||
'resources/drawable-mdpi/marketplace.png',
|
||||
'resources/drawable-mdpi/menu.png',
|
||||
'resources/drawable-mdpi/menu_item_check.png',
|
||||
'resources/drawable-mdpi/menu_item_more.png',
|
||||
'resources/drawable-mdpi/menu_item_uncheck.png',
|
||||
'resources/drawable-mdpi/menu_panel_bg.9.png',
|
||||
'resources/drawable-mdpi/menu_pb.png',
|
||||
'resources/drawable-mdpi/menu_popup_arrow_bottom.png',
|
||||
'resources/drawable-mdpi/menu_popup_arrow_top.png',
|
||||
'resources/drawable-mdpi/menu_popup_bg.9.png',
|
||||
'resources/drawable-mdpi/pause.png',
|
||||
'resources/drawable-mdpi/pin.png',
|
||||
'resources/drawable-mdpi/play.png',
|
||||
'resources/drawable-mdpi/progress_spinner.png',
|
||||
'resources/drawable-mdpi/reader.png',
|
||||
'resources/drawable-mdpi/reader_active.png',
|
||||
'resources/drawable-mdpi/reader_cropped.png',
|
||||
'resources/drawable-mdpi/reading_list.png',
|
||||
'resources/drawable-mdpi/scrollbar.png',
|
||||
'resources/drawable-mdpi/shadow.png',
|
||||
'resources/drawable-mdpi/shield.png',
|
||||
'resources/drawable-mdpi/shield_doorhanger.png',
|
||||
'resources/drawable-mdpi/spinner_default.9.png',
|
||||
'resources/drawable-mdpi/spinner_focused.9.png',
|
||||
'resources/drawable-mdpi/spinner_pressed.9.png',
|
||||
'resources/drawable-mdpi/start.png',
|
||||
'resources/drawable-mdpi/tab_close.png',
|
||||
'resources/drawable-mdpi/tab_indicator_divider.9.png',
|
||||
'resources/drawable-mdpi/tab_indicator_selected.9.png',
|
||||
'resources/drawable-mdpi/tab_indicator_selected_focused.9.png',
|
||||
'resources/drawable-mdpi/tab_new.png',
|
||||
'resources/drawable-mdpi/tab_new_pb.png',
|
||||
'resources/drawable-mdpi/tab_thumbnail_default.png',
|
||||
'resources/drawable-mdpi/tab_thumbnail_shadow.png',
|
||||
'resources/drawable-mdpi/tabs_count.png',
|
||||
'resources/drawable-mdpi/tabs_count_foreground.png',
|
||||
'resources/drawable-mdpi/tabs_normal.png',
|
||||
'resources/drawable-mdpi/tabs_private.png',
|
||||
'resources/drawable-mdpi/tabs_synced.png',
|
||||
'resources/drawable-mdpi/tip_addsearch.png',
|
||||
'resources/drawable-mdpi/toast.9.png',
|
||||
'resources/drawable-mdpi/toast_button_focused.9.png',
|
||||
'resources/drawable-mdpi/toast_button_pressed.9.png',
|
||||
'resources/drawable-mdpi/toast_divider.9.png',
|
||||
'resources/drawable-mdpi/top_site_add.png',
|
||||
'resources/drawable-mdpi/url_bar_entry_default.9.png',
|
||||
'resources/drawable-mdpi/url_bar_entry_default_pb.9.png',
|
||||
'resources/drawable-mdpi/url_bar_entry_pressed.9.png',
|
||||
'resources/drawable-mdpi/url_bar_entry_pressed_pb.9.png',
|
||||
'resources/drawable-mdpi/urlbar_stop.png',
|
||||
'resources/drawable-mdpi/validation_arrow.png',
|
||||
'resources/drawable-mdpi/validation_arrow_inverted.png',
|
||||
'resources/drawable-mdpi/validation_bg.9.png',
|
||||
'resources/drawable-mdpi/warning.png',
|
||||
'resources/drawable-mdpi/warning_doorhanger.png',
|
||||
'resources/drawable-xhdpi-v11/alert_addon.png',
|
||||
'resources/drawable-xhdpi-v11/alert_app.png',
|
||||
'resources/drawable-xhdpi-v11/alert_camera.png',
|
||||
'resources/drawable-xhdpi-v11/alert_download.png',
|
||||
'resources/drawable-xhdpi-v11/alert_mic.png',
|
||||
'resources/drawable-xhdpi-v11/alert_mic_camera.png',
|
||||
'resources/drawable-xhdpi-v11/firefox_settings_alert.png',
|
||||
'resources/drawable-xhdpi-v11/ic_menu_addons.png',
|
||||
'resources/drawable-xhdpi-v11/ic_menu_apps.png',
|
||||
'resources/drawable-xhdpi-v11/ic_menu_back.png',
|
||||
'resources/drawable-xhdpi-v11/ic_menu_bookmark_add.png',
|
||||
'resources/drawable-xhdpi-v11/ic_menu_bookmark_remove.png',
|
||||
'resources/drawable-xhdpi-v11/ic_menu_desktop_mode_off.png',
|
||||
'resources/drawable-xhdpi-v11/ic_menu_desktop_mode_on.png',
|
||||
'resources/drawable-xhdpi-v11/ic_menu_downloads.png',
|
||||
'resources/drawable-xhdpi-v11/ic_menu_find_in_page.png',
|
||||
'resources/drawable-xhdpi-v11/ic_menu_forward.png',
|
||||
'resources/drawable-xhdpi-v11/ic_menu_new_private_tab.png',
|
||||
'resources/drawable-xhdpi-v11/ic_menu_new_tab.png',
|
||||
'resources/drawable-xhdpi-v11/ic_menu_quit.png',
|
||||
'resources/drawable-xhdpi-v11/ic_menu_reload.png',
|
||||
'resources/drawable-xhdpi-v11/ic_menu_save_as_pdf.png',
|
||||
'resources/drawable-xhdpi-v11/ic_menu_settings.png',
|
||||
'resources/drawable-xhdpi-v11/ic_menu_share.png',
|
||||
'resources/drawable-xhdpi-v11/ic_menu_tools.png',
|
||||
'resources/drawable-xhdpi-v11/ic_status_logo.png',
|
||||
'resources/drawable-xhdpi/abouthome_thumbnail.png',
|
||||
'resources/drawable-xhdpi/alert_addon.png',
|
||||
'resources/drawable-xhdpi/alert_app.png',
|
||||
'resources/drawable-xhdpi/alert_camera.png',
|
||||
'resources/drawable-xhdpi/alert_download.png',
|
||||
'resources/drawable-xhdpi/alert_mic.png',
|
||||
'resources/drawable-xhdpi/alert_mic_camera.png',
|
||||
'resources/drawable-xhdpi/arrow_popup_bg.9.png',
|
||||
'resources/drawable-xhdpi/blank.png',
|
||||
'resources/drawable-xhdpi/bookmark_folder_closed.png',
|
||||
'resources/drawable-xhdpi/bookmark_folder_opened.png',
|
||||
'resources/drawable-xhdpi/close.png',
|
||||
'resources/drawable-xhdpi/favicon.png',
|
||||
'resources/drawable-xhdpi/find_close.png',
|
||||
'resources/drawable-xhdpi/find_next.png',
|
||||
'resources/drawable-xhdpi/find_prev.png',
|
||||
'resources/drawable-xhdpi/folder.png',
|
||||
'resources/drawable-xhdpi/grid_icon_bg_activated.9.png',
|
||||
'resources/drawable-xhdpi/grid_icon_bg_focused.9.png',
|
||||
'resources/drawable-xhdpi/handle_end.png',
|
||||
'resources/drawable-xhdpi/handle_middle.png',
|
||||
'resources/drawable-xhdpi/handle_start.png',
|
||||
'resources/drawable-xhdpi/history_tabs_indicator_selected.9.png',
|
||||
'resources/drawable-xhdpi/home_tab_menu_strip.9.png',
|
||||
'resources/drawable-xhdpi/ic_menu_addons_filler.png',
|
||||
'resources/drawable-xhdpi/ic_menu_bookmark_add.png',
|
||||
'resources/drawable-xhdpi/ic_menu_bookmark_remove.png',
|
||||
'resources/drawable-xhdpi/ic_menu_character_encoding.png',
|
||||
'resources/drawable-xhdpi/ic_menu_forward.png',
|
||||
'resources/drawable-xhdpi/ic_menu_guest.png',
|
||||
'resources/drawable-xhdpi/ic_menu_new_private_tab.png',
|
||||
'resources/drawable-xhdpi/ic_menu_new_tab.png',
|
||||
'resources/drawable-xhdpi/ic_menu_reload.png',
|
||||
'resources/drawable-xhdpi/ic_status_logo.png',
|
||||
'resources/drawable-xhdpi/ic_url_bar_go.png',
|
||||
'resources/drawable-xhdpi/ic_url_bar_reader.png',
|
||||
'resources/drawable-xhdpi/ic_url_bar_search.png',
|
||||
'resources/drawable-xhdpi/ic_url_bar_star.png',
|
||||
'resources/drawable-xhdpi/ic_url_bar_tab.png',
|
||||
'resources/drawable-xhdpi/icon_bookmarks_empty.png',
|
||||
'resources/drawable-xhdpi/icon_last_tabs.png',
|
||||
'resources/drawable-xhdpi/icon_last_tabs_empty.png',
|
||||
'resources/drawable-xhdpi/icon_most_recent.png',
|
||||
'resources/drawable-xhdpi/icon_most_recent_empty.png',
|
||||
'resources/drawable-xhdpi/icon_most_visited.png',
|
||||
'resources/drawable-xhdpi/icon_openinapp.png',
|
||||
'resources/drawable-xhdpi/icon_pageaction.png',
|
||||
'resources/drawable-xhdpi/icon_reading_list_empty.png',
|
||||
'resources/drawable-xhdpi/larry.png',
|
||||
'resources/drawable-xhdpi/lock_identified.png',
|
||||
'resources/drawable-xhdpi/lock_verified.png',
|
||||
'resources/drawable-xhdpi/menu.png',
|
||||
'resources/drawable-xhdpi/menu_item_check.png',
|
||||
'resources/drawable-xhdpi/menu_item_more.png',
|
||||
'resources/drawable-xhdpi/menu_item_uncheck.png',
|
||||
'resources/drawable-xhdpi/menu_panel_bg.9.png',
|
||||
'resources/drawable-xhdpi/menu_pb.png',
|
||||
'resources/drawable-xhdpi/menu_popup_arrow_bottom.png',
|
||||
'resources/drawable-xhdpi/menu_popup_arrow_top.png',
|
||||
'resources/drawable-xhdpi/menu_popup_bg.9.png',
|
||||
'resources/drawable-xhdpi/pause.png',
|
||||
'resources/drawable-xhdpi/pin.png',
|
||||
'resources/drawable-xhdpi/play.png',
|
||||
'resources/drawable-xhdpi/reader.png',
|
||||
'resources/drawable-xhdpi/reader_active.png',
|
||||
'resources/drawable-xhdpi/reader_cropped.png',
|
||||
'resources/drawable-xhdpi/reading_list.png',
|
||||
'resources/drawable-xhdpi/shield.png',
|
||||
'resources/drawable-xhdpi/shield_doorhanger.png',
|
||||
'resources/drawable-xhdpi/spinner_default.9.png',
|
||||
'resources/drawable-xhdpi/spinner_focused.9.png',
|
||||
'resources/drawable-xhdpi/spinner_pressed.9.png',
|
||||
'resources/drawable-xhdpi/tab_close.png',
|
||||
'resources/drawable-xhdpi/tab_indicator_divider.9.png',
|
||||
'resources/drawable-xhdpi/tab_indicator_selected.9.png',
|
||||
'resources/drawable-xhdpi/tab_indicator_selected_focused.9.png',
|
||||
'resources/drawable-xhdpi/tab_new.png',
|
||||
'resources/drawable-xhdpi/tab_new_pb.png',
|
||||
'resources/drawable-xhdpi/tab_thumbnail_default.png',
|
||||
'resources/drawable-xhdpi/tab_thumbnail_shadow.png',
|
||||
'resources/drawable-xhdpi/tabs_count.png',
|
||||
'resources/drawable-xhdpi/tabs_count_foreground.png',
|
||||
'resources/drawable-xhdpi/tabs_normal.png',
|
||||
'resources/drawable-xhdpi/tabs_private.png',
|
||||
'resources/drawable-xhdpi/tabs_synced.png',
|
||||
'resources/drawable-xhdpi/tip_addsearch.png',
|
||||
'resources/drawable-xhdpi/top_site_add.png',
|
||||
'resources/drawable-xhdpi/url_bar_entry_default.9.png',
|
||||
'resources/drawable-xhdpi/url_bar_entry_default_pb.9.png',
|
||||
'resources/drawable-xhdpi/url_bar_entry_pressed.9.png',
|
||||
'resources/drawable-xhdpi/url_bar_entry_pressed_pb.9.png',
|
||||
'resources/drawable-xhdpi/urlbar_stop.png',
|
||||
'resources/drawable-xhdpi/validation_arrow.png',
|
||||
'resources/drawable-xhdpi/validation_arrow_inverted.png',
|
||||
'resources/drawable-xhdpi/validation_bg.9.png',
|
||||
'resources/drawable-xhdpi/warning.png',
|
||||
'resources/drawable-xhdpi/warning_doorhanger.png',
|
||||
'resources/drawable-xlarge-hdpi-v11/ic_menu_bookmark_add.png',
|
||||
'resources/drawable-xlarge-hdpi-v11/ic_menu_bookmark_remove.png',
|
||||
'resources/drawable-xlarge-mdpi-v11/ic_menu_bookmark_add.png',
|
||||
'resources/drawable-xlarge-mdpi-v11/ic_menu_bookmark_remove.png',
|
||||
'resources/drawable-xlarge-v11/home_history_tabs_indicator.xml',
|
||||
'resources/drawable-xlarge-xhdpi-v11/ic_menu_bookmark_add.png',
|
||||
'resources/drawable-xlarge-xhdpi-v11/ic_menu_bookmark_remove.png',
|
||||
'resources/drawable/action_bar_button.xml',
|
||||
'resources/drawable/action_bar_button_inverse.xml',
|
||||
'resources/drawable/bookmark_folder.xml',
|
||||
'resources/drawable/divider_horizontal.xml',
|
||||
'resources/drawable/divider_vertical.xml',
|
||||
'resources/drawable/favicon_bg.xml',
|
||||
'resources/drawable/handle_end_level.xml',
|
||||
'resources/drawable/handle_start_level.xml',
|
||||
'resources/drawable/home_banner.xml',
|
||||
'resources/drawable/home_history_tabs_indicator.xml',
|
||||
'resources/drawable/home_page_title_background.xml',
|
||||
'resources/drawable/ic_menu_back.xml',
|
||||
'resources/drawable/ic_menu_desktop_mode_off.xml',
|
||||
'resources/drawable/ic_menu_desktop_mode_on.xml',
|
||||
'resources/drawable/ic_menu_quit.xml',
|
||||
'resources/drawable/icon_grid_item_bg.xml',
|
||||
'resources/drawable/menu_item_state.xml',
|
||||
'resources/drawable/menu_level.xml',
|
||||
'resources/drawable/remote_tabs_child_divider.xml',
|
||||
'resources/drawable/shaped_button.xml',
|
||||
'resources/drawable/site_security_level.xml',
|
||||
'resources/drawable/spinner.xml',
|
||||
'resources/drawable/suggestion_selector.xml',
|
||||
'resources/drawable/tab_new_level.xml',
|
||||
'resources/drawable/tab_row.xml',
|
||||
'resources/drawable/tab_thumbnail.xml',
|
||||
'resources/drawable/tabs_panel_indicator.xml',
|
||||
'resources/drawable/textbox_bg.xml',
|
||||
'resources/drawable/toast_button.xml',
|
||||
'resources/drawable/top_sites_thumbnail_bg.xml',
|
||||
'resources/drawable/url_bar_bg.xml',
|
||||
'resources/drawable/url_bar_entry.xml',
|
||||
'resources/drawable/url_bar_nav_button.xml',
|
||||
'resources/drawable/url_bar_right_edge.xml',
|
||||
'resources/drawable/webapp_titlebar_bg.xml',
|
||||
'resources/layout-large-land-v11/home_history_list.xml',
|
||||
'resources/layout-large-land-v11/home_history_page.xml',
|
||||
'resources/layout-large-land-v11/home_history_tabs_indicator.xml',
|
||||
'resources/layout-large-land-v11/tabs_panel.xml',
|
||||
'resources/layout-large-land-v11/tabs_panel_footer.xml',
|
||||
'resources/layout-large-land-v11/tabs_panel_header.xml',
|
||||
'resources/layout-large-v11/browser_toolbar.xml',
|
||||
'resources/layout-large-v11/home_pager.xml',
|
||||
'resources/layout-xlarge-v11/font_size_preference.xml',
|
||||
'resources/layout-xlarge-v11/home_history_list.xml',
|
||||
'resources/layout-xlarge-v11/home_history_page.xml',
|
||||
'resources/layout-xlarge-v11/home_history_tabs_indicator.xml',
|
||||
'resources/layout-xlarge-v11/remote_tabs_child.xml',
|
||||
'resources/layout-xlarge-v11/remote_tabs_group.xml',
|
||||
'resources/layout/arrow_popup.xml',
|
||||
'resources/layout/autocomplete_list.xml',
|
||||
'resources/layout/autocomplete_list_item.xml',
|
||||
'resources/layout/bookmark_edit.xml',
|
||||
'resources/layout/bookmark_folder_row.xml',
|
||||
'resources/layout/bookmark_item_row.xml',
|
||||
'resources/layout/browser_search.xml',
|
||||
'resources/layout/browser_toolbar.xml',
|
||||
'resources/layout/datetime_picker.xml',
|
||||
'resources/layout/doorhanger.xml',
|
||||
'resources/layout/doorhanger_button.xml',
|
||||
'resources/layout/find_in_page_content.xml',
|
||||
'resources/layout/font_size_preference.xml',
|
||||
'resources/layout/gecko_app.xml',
|
||||
'resources/layout/home_banner.xml',
|
||||
'resources/layout/home_bookmarks_page.xml',
|
||||
'resources/layout/home_empty_page.xml',
|
||||
'resources/layout/home_empty_reading_page.xml',
|
||||
'resources/layout/home_header_row.xml',
|
||||
'resources/layout/home_history_list.xml',
|
||||
'resources/layout/home_history_page.xml',
|
||||
'resources/layout/home_history_tabs_indicator.xml',
|
||||
'resources/layout/home_item_row.xml',
|
||||
'resources/layout/home_last_tabs_page.xml',
|
||||
'resources/layout/home_most_recent_page.xml',
|
||||
'resources/layout/home_pager.xml',
|
||||
'resources/layout/home_reading_list_page.xml',
|
||||
'resources/layout/home_search_item_row.xml',
|
||||
'resources/layout/home_suggestion_prompt.xml',
|
||||
'resources/layout/home_top_sites_page.xml',
|
||||
'resources/layout/icon_grid.xml',
|
||||
'resources/layout/icon_grid_item.xml',
|
||||
'resources/layout/launch_app_list.xml',
|
||||
'resources/layout/launch_app_listitem.xml',
|
||||
'resources/layout/list_item_header.xml',
|
||||
'resources/layout/menu_action_bar.xml',
|
||||
'resources/layout/menu_item_action_view.xml',
|
||||
'resources/layout/menu_popup.xml',
|
||||
'resources/layout/notification_icon_text.xml',
|
||||
'resources/layout/notification_progress.xml',
|
||||
'resources/layout/notification_progress_text.xml',
|
||||
'resources/layout/pin_site_dialog.xml',
|
||||
'resources/layout/preference_rightalign_icon.xml',
|
||||
'resources/layout/preference_search_engine.xml',
|
||||
'resources/layout/preference_search_tip.xml',
|
||||
'resources/layout/remote_tabs_child.xml',
|
||||
'resources/layout/remote_tabs_group.xml',
|
||||
'resources/layout/search_engine_row.xml',
|
||||
'resources/layout/select_dialog_list.xml',
|
||||
'resources/layout/select_dialog_multichoice.xml',
|
||||
'resources/layout/select_dialog_singlechoice.xml',
|
||||
'resources/layout/shared_ui_components.xml',
|
||||
'resources/layout/simple_dropdown_item_1line.xml',
|
||||
'resources/layout/site_identity.xml',
|
||||
'resources/layout/site_setting_item.xml',
|
||||
'resources/layout/site_setting_title.xml',
|
||||
'resources/layout/suggestion_item.xml',
|
||||
'resources/layout/tab_menu_strip.xml',
|
||||
'resources/layout/tabs_counter.xml',
|
||||
'resources/layout/tabs_item_cell.xml',
|
||||
'resources/layout/tabs_item_row.xml',
|
||||
'resources/layout/tabs_panel.xml',
|
||||
'resources/layout/tabs_panel_header.xml',
|
||||
'resources/layout/tabs_panel_indicator.xml',
|
||||
'resources/layout/text_selection_handles.xml',
|
||||
'resources/layout/top_sites_grid_item_view.xml',
|
||||
'resources/layout/two_line_page_row.xml',
|
||||
'resources/layout/validation_message.xml',
|
||||
'resources/layout/videoplayer.xml',
|
||||
'resources/layout/web_app.xml',
|
||||
'resources/menu-large-v11/browser_app_menu.xml',
|
||||
'resources/menu-v11/browser_app_menu.xml',
|
||||
'resources/menu-xlarge-v11/browser_app_menu.xml',
|
||||
'resources/menu/browser_app_menu.xml',
|
||||
'resources/menu/gecko_app_menu.xml',
|
||||
'resources/menu/home_contextmenu.xml',
|
||||
'resources/menu/titlebar_contextmenu.xml',
|
||||
'resources/menu/top_sites_contextmenu.xml',
|
||||
'resources/values-land/integers.xml',
|
||||
'resources/values-land/layout.xml',
|
||||
'resources/values-land/styles.xml',
|
||||
'resources/values-large-land-v11/dimens.xml',
|
||||
'resources/values-large-land-v11/styles.xml',
|
||||
'resources/values-large-v11/dimens.xml',
|
||||
'resources/values-large-v11/layout.xml',
|
||||
'resources/values-large-v11/styles.xml',
|
||||
'resources/values-large-v11/themes.xml',
|
||||
'resources/values-v11/colors.xml',
|
||||
'resources/values-v11/dimens.xml',
|
||||
'resources/values-v11/styles.xml',
|
||||
'resources/values-v11/themes.xml',
|
||||
'resources/values-v14/styles.xml',
|
||||
'resources/values-v16/styles.xml',
|
||||
'resources/values-xlarge-land-v11/dimens.xml',
|
||||
'resources/values-xlarge-land-v11/styles.xml',
|
||||
'resources/values-xlarge-v11/dimens.xml',
|
||||
'resources/values-xlarge-v11/integers.xml',
|
||||
'resources/values-xlarge-v11/styles.xml',
|
||||
'resources/values/arrays.xml',
|
||||
'resources/values/attrs.xml',
|
||||
'resources/values/colors.xml',
|
||||
'resources/values/dimens.xml',
|
||||
'resources/values/integers.xml',
|
||||
'resources/values/layout.xml',
|
||||
'resources/values/styles.xml',
|
||||
'resources/values/themes.xml',
|
||||
'resources/xml-v11/preference_headers.xml',
|
||||
'resources/xml-v11/preferences.xml',
|
||||
'resources/xml-v11/preferences_customize.xml',
|
||||
'resources/xml-v11/preferences_customize_tablet.xml',
|
||||
'resources/xml/preferences.xml',
|
||||
'resources/xml/preferences_customize.xml',
|
||||
'resources/xml/preferences_devtools.xml',
|
||||
'resources/xml/preferences_display.xml',
|
||||
'resources/xml/preferences_privacy.xml',
|
||||
'resources/xml/preferences_search.xml',
|
||||
'resources/xml/preferences_vendor.xml',
|
||||
'resources/xml/searchable.xml',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_CRASHREPORTER']:
|
||||
ANDROID_RESFILES += [
|
||||
'resources/drawable-mdpi/crash_reporter.png',
|
||||
'resources/layout/crash_reporter.xml',
|
||||
]
|
||||
|
@ -4,6 +4,7 @@ package @ANDROID_PACKAGE_NAME@.tests;
|
||||
import @ANDROID_PACKAGE_NAME@.*;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
@ -38,11 +39,50 @@ public class testDistribution extends ContentProviderTest {
|
||||
return TEST_MOCHITEST;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a hack.
|
||||
*
|
||||
* Startup results in us writing prefs -- we fetch the Distribution, which
|
||||
* caches its state. Our tests try to wipe those prefs, but apparently
|
||||
* sometimes race with startup, which leads to us not getting one of our
|
||||
* expected messages. The test fails.
|
||||
*
|
||||
* This hack waits for any existing background tasks -- such as the one that
|
||||
* writes prefs -- to finish before we begin the test.
|
||||
*/
|
||||
private void waitForBackgroundHappiness() {
|
||||
try {
|
||||
ClassLoader classLoader = mActivity.getClassLoader();
|
||||
Class threadUtilsClass = classLoader.loadClass("org.mozilla.gecko.util.ThreadUtils");
|
||||
Method postToBackgroundThread = threadUtilsClass.getMethod("postToBackgroundThread", Runnable.class);
|
||||
final Object signal = new Object();
|
||||
final Runnable done = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (signal) {
|
||||
signal.notify();
|
||||
}
|
||||
}
|
||||
};
|
||||
synchronized (signal) {
|
||||
postToBackgroundThread.invoke(null, done);
|
||||
signal.wait();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
mAsserter.ok(false, "Exception waiting on background thread.", e.toString());
|
||||
}
|
||||
mAsserter.dumpLog("Background task completed. Proceeding.");
|
||||
}
|
||||
|
||||
public void testDistribution() {
|
||||
mActivity = getActivity();
|
||||
|
||||
String mockPackagePath = getMockPackagePath();
|
||||
|
||||
// Wait for any startup-related background distribution shenanigans to
|
||||
// finish. This reduces the chance of us racing with startup pref writes.
|
||||
waitForBackgroundHappiness();
|
||||
|
||||
// Pre-clear distribution pref, run basic preferences and en-US localized preferences Tests
|
||||
clearDistributionPref();
|
||||
setTestLocale("en-US");
|
||||
@ -64,11 +104,10 @@ public class testDistribution extends ContentProviderTest {
|
||||
// Call Distribution.init with the mock package.
|
||||
ClassLoader classLoader = mActivity.getClassLoader();
|
||||
Class distributionClass = classLoader.loadClass("org.mozilla.gecko.Distribution");
|
||||
Class contextClass = classLoader.loadClass("android.content.Context");
|
||||
Method init = distributionClass.getMethod("init", contextClass, String.class);
|
||||
Method init = distributionClass.getMethod("init", Context.class, String.class, String.class);
|
||||
|
||||
Actions.EventExpecter distributionSetExpecter = mActions.expectGeckoEvent("Distribution:Set:OK");
|
||||
init.invoke(null, mActivity, aPackagePath);
|
||||
init.invoke(null, mActivity, aPackagePath, "prefs-" + System.currentTimeMillis());
|
||||
distributionSetExpecter.blockForEvent();
|
||||
distributionSetExpecter.unregisterListener();
|
||||
} catch (Exception e) {
|
||||
@ -268,6 +307,7 @@ public class testDistribution extends ContentProviderTest {
|
||||
|
||||
// Clears the distribution pref to return distribution state to STATE_UNKNOWN
|
||||
private void clearDistributionPref() {
|
||||
mAsserter.dumpLog("Clearing distribution pref.");
|
||||
SharedPreferences settings = mActivity.getSharedPreferences("GeckoApp", Activity.MODE_PRIVATE);
|
||||
String keyName = mActivity.getPackageName() + ".distribution_state";
|
||||
settings.edit().remove(keyName).commit();
|
||||
|
@ -5316,7 +5316,10 @@ var FormAssistant = {
|
||||
* -- and reflect them back to Java.
|
||||
*/
|
||||
let HealthReportStatusListener = {
|
||||
TELEMETRY_PREF:
|
||||
PREF_ACCEPT_LANG: "intl.accept_languages",
|
||||
PREF_BLOCKLIST_ENABLED: "extensions.blocklist.enabled",
|
||||
|
||||
PREF_TELEMETRY_ENABLED:
|
||||
#ifdef MOZ_TELEMETRY_REPORTING
|
||||
// Telemetry pref differs based on build.
|
||||
#ifdef MOZ_TELEMETRY_ON_BY_DEFAULT
|
||||
@ -5335,18 +5338,21 @@ let HealthReportStatusListener = {
|
||||
console.log("Failed to initialize add-on status listener. FHR cannot report add-on state. " + ex);
|
||||
}
|
||||
|
||||
Services.obs.addObserver(this, "Addons:FetchAll", false);
|
||||
Services.prefs.addObserver("extensions.blocklist.enabled", this, false);
|
||||
if (this.TELEMETRY_PREF) {
|
||||
Services.prefs.addObserver(this.TELEMETRY_PREF, this, false);
|
||||
console.log("Adding HealthReport:RequestSnapshot observer.");
|
||||
Services.obs.addObserver(this, "HealthReport:RequestSnapshot", false);
|
||||
Services.prefs.addObserver(this.PREF_ACCEPT_LANG, this, false);
|
||||
Services.prefs.addObserver(this.PREF_BLOCKLIST_ENABLED, this, false);
|
||||
if (this.PREF_TELEMETRY_ENABLED) {
|
||||
Services.prefs.addObserver(this.PREF_TELEMETRY_ENABLED, this, false);
|
||||
}
|
||||
},
|
||||
|
||||
uninit: function () {
|
||||
Services.obs.removeObserver(this, "Addons:FetchAll");
|
||||
Services.prefs.removeObserver("extensions.blocklist.enabled", this);
|
||||
if (this.TELEMETRY_PREF) {
|
||||
Services.prefs.removeObserver(this.TELEMETRY_PREF, this);
|
||||
Services.obs.removeObserver(this, "HealthReport:RequestSnapshot");
|
||||
Services.prefs.removeObserver(this.PREF_ACCEPT_LANG, this);
|
||||
Services.prefs.removeObserver(this.PREF_BLOCKLIST_ENABLED, this);
|
||||
if (this.PREF_TELEMETRY_ENABLED) {
|
||||
Services.prefs.removeObserver(this.PREF_TELEMETRY_ENABLED, this);
|
||||
}
|
||||
|
||||
AddonManager.removeAddonListener(this);
|
||||
@ -5354,11 +5360,30 @@ let HealthReportStatusListener = {
|
||||
|
||||
observe: function (aSubject, aTopic, aData) {
|
||||
switch (aTopic) {
|
||||
case "Addons:FetchAll":
|
||||
HealthReportStatusListener.sendAllAddonsToJava();
|
||||
case "HealthReport:RequestSnapshot":
|
||||
HealthReportStatusListener.sendSnapshotToJava();
|
||||
break;
|
||||
case "nsPref:changed":
|
||||
sendMessageToJava({ type: "Pref:Change", pref: aData, value: Services.prefs.getBoolPref(aData) });
|
||||
let response = {
|
||||
type: "Pref:Change",
|
||||
pref: aData,
|
||||
isUserSet: Services.prefs.prefHasUserValue(aData),
|
||||
};
|
||||
|
||||
switch (aData) {
|
||||
case this.PREF_ACCEPT_LANG:
|
||||
response.value = Services.prefs.getCharPref(aData);
|
||||
break;
|
||||
case this.PREF_TELEMETRY_ENABLED:
|
||||
case this.PREF_BLOCKLIST_ENABLED:
|
||||
response.value = Services.prefs.getBoolPref(aData);
|
||||
break;
|
||||
default:
|
||||
console.log("Unexpected pref in HealthReportStatusListener: " + aData);
|
||||
return;
|
||||
}
|
||||
|
||||
sendMessageToJava(response);
|
||||
break;
|
||||
}
|
||||
},
|
||||
@ -5440,9 +5465,9 @@ let HealthReportStatusListener = {
|
||||
this.notifyJava(aAddon);
|
||||
},
|
||||
|
||||
sendAllAddonsToJava: function () {
|
||||
sendSnapshotToJava: function () {
|
||||
AddonManager.getAllAddons(function (aAddons) {
|
||||
let json = {};
|
||||
let jsonA = {};
|
||||
if (aAddons) {
|
||||
for (let i = 0; i < aAddons.length; ++i) {
|
||||
let addon = aAddons[i];
|
||||
@ -5451,14 +5476,43 @@ let HealthReportStatusListener = {
|
||||
if (HealthReportStatusListener._shouldIgnore(addon)) {
|
||||
addonJSON.ignore = true;
|
||||
}
|
||||
json[addon.id] = addonJSON;
|
||||
jsonA[addon.id] = addonJSON;
|
||||
} catch (e) {
|
||||
// Just skip this add-on.
|
||||
}
|
||||
}
|
||||
}
|
||||
sendMessageToJava({ type: "Addons:All", json: json });
|
||||
});
|
||||
|
||||
// Now add prefs.
|
||||
let jsonP = {};
|
||||
for (let pref of [this.PREF_BLOCKLIST_ENABLED, this.PREF_TELEMETRY_ENABLED]) {
|
||||
if (!pref) {
|
||||
// This will be the case for PREF_TELEMETRY_ENABLED in developer builds.
|
||||
continue;
|
||||
}
|
||||
jsonP[pref] = {
|
||||
pref: pref,
|
||||
value: Services.prefs.getBoolPref(pref),
|
||||
isUserSet: Services.prefs.prefHasUserValue(pref),
|
||||
};
|
||||
}
|
||||
for (let pref of [this.PREF_ACCEPT_LANG]) {
|
||||
jsonP[pref] = {
|
||||
pref: pref,
|
||||
value: Services.prefs.getCharPref(pref),
|
||||
isUserSet: Services.prefs.prefHasUserValue(pref),
|
||||
};
|
||||
}
|
||||
|
||||
console.log("Sending snapshot message.");
|
||||
sendMessageToJava({
|
||||
type: "HealthReport:Snapshot",
|
||||
json: {
|
||||
addons: jsonA,
|
||||
prefs: jsonP,
|
||||
},
|
||||
});
|
||||
}.bind(this));
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -27,6 +27,7 @@ background/db/CursorDumper.java
|
||||
background/db/Tab.java
|
||||
background/healthreport/Environment.java
|
||||
background/healthreport/EnvironmentBuilder.java
|
||||
background/healthreport/EnvironmentV1.java
|
||||
background/healthreport/HealthReportBroadcastReceiver.java
|
||||
background/healthreport/HealthReportBroadcastService.java
|
||||
background/healthreport/HealthReportDatabases.java
|
||||
|
@ -21,7 +21,6 @@ include $(srcdir)/android-services-files.mk
|
||||
|
||||
# BACKGROUND_TESTS_{JAVA,RES}_FILES are defined in android-services-files.mk.
|
||||
JAVAFILES := $(BACKGROUND_TESTS_JAVA_FILES)
|
||||
ANDROID_RESFILES := $(BACKGROUND_TESTS_RES_FILES)
|
||||
|
||||
# The test APK needs to know the contents of the target APK while not
|
||||
# being linked against them. This is a best effort to avoid getting
|
||||
|
@ -100,11 +100,3 @@ BACKGROUND_TESTS_JAVA_FILES := \
|
||||
src/testhelpers/WBORepository.java \
|
||||
$(NULL)
|
||||
|
||||
BACKGROUND_TESTS_RES_FILES := \
|
||||
res/drawable-hdpi/icon.png \
|
||||
res/drawable-ldpi/icon.png \
|
||||
res/drawable-mdpi/icon.png \
|
||||
res/layout/main.xml \
|
||||
res/values/strings.xml \
|
||||
$(NULL)
|
||||
|
||||
|
@ -0,0 +1,7 @@
|
||||
ANDROID_RESFILES += [
|
||||
'res/drawable-hdpi/icon.png',
|
||||
'res/drawable-ldpi/icon.png',
|
||||
'res/drawable-mdpi/icon.png',
|
||||
'res/layout/main.xml',
|
||||
'res/values/strings.xml',
|
||||
]
|
@ -3,3 +3,5 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
include('android-services.mozbuild')
|
||||
|
@ -38,7 +38,7 @@ public class MockDatabaseEnvironment extends DatabaseEnvironment {
|
||||
}
|
||||
}
|
||||
|
||||
public MockDatabaseEnvironment mockInit(String version) {
|
||||
public MockDatabaseEnvironment mockInit(String appVersion) {
|
||||
profileCreation = 1234;
|
||||
cpuCount = 2;
|
||||
memoryMB = 512;
|
||||
@ -55,7 +55,7 @@ public class MockDatabaseEnvironment extends DatabaseEnvironment {
|
||||
vendor = "";
|
||||
appName = "";
|
||||
appID = "";
|
||||
appVersion = version;
|
||||
this.appVersion = appVersion;
|
||||
appBuildID = "";
|
||||
platformVersion = "";
|
||||
platformBuildID = "";
|
||||
@ -63,6 +63,14 @@ public class MockDatabaseEnvironment extends DatabaseEnvironment {
|
||||
xpcomabi = "";
|
||||
updateChannel = "";
|
||||
|
||||
// v2 fields.
|
||||
distribution = "";
|
||||
appLocale = "";
|
||||
osLocale = "";
|
||||
acceptLangSet = 0;
|
||||
|
||||
version = Environment.CURRENT_VERSION;
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,10 @@ import org.mozilla.gecko.background.healthreport.HealthReportStorage.Field;
|
||||
import org.mozilla.gecko.background.healthreport.HealthReportStorage.MeasurementFields;
|
||||
import org.mozilla.gecko.background.helpers.FakeProfileTestCase;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.util.SparseArray;
|
||||
|
||||
public class TestHealthReportGenerator extends FakeProfileTestCase {
|
||||
@SuppressWarnings("static-method")
|
||||
public void testOptObject() throws JSONException {
|
||||
@ -57,9 +61,14 @@ public class TestHealthReportGenerator extends FakeProfileTestCase {
|
||||
assertFalse(bar.has("b"));
|
||||
}
|
||||
|
||||
// We don't initialize the env in testHashing, so these are just the default
|
||||
// values for the Java types, in order.
|
||||
private static final String EXPECTED_MOCK_BASE_HASH = "000nullnullnullnullnullnullnull"
|
||||
+ "nullnullnullnullnullnull00000";
|
||||
|
||||
// v2 fields.
|
||||
private static final String EXPECTED_MOCK_BASE_HASH_SUFFIX = "null" + "null" + 0 + "null";
|
||||
|
||||
public void testHashing() throws JSONException {
|
||||
MockHealthReportDatabaseStorage storage = new MockHealthReportDatabaseStorage(context, fakeProfileDirectory);
|
||||
MockDatabaseEnvironment env = new MockDatabaseEnvironment(storage, MockDatabaseEnvironment.MockEnvironmentAppender.class);
|
||||
@ -96,10 +105,10 @@ public class TestHealthReportGenerator extends FakeProfileTestCase {
|
||||
"}");
|
||||
env.addons.put("{addonA}", addonA1);
|
||||
|
||||
assertEquals(EXPECTED_MOCK_BASE_HASH + addonAHash, env.getHash());
|
||||
assertEquals(EXPECTED_MOCK_BASE_HASH + addonAHash + EXPECTED_MOCK_BASE_HASH_SUFFIX, env.getHash());
|
||||
|
||||
env.addons.put("{addonA}", addonA1rev);
|
||||
assertEquals(EXPECTED_MOCK_BASE_HASH + addonAHash, env.getHash());
|
||||
assertEquals(EXPECTED_MOCK_BASE_HASH + addonAHash + EXPECTED_MOCK_BASE_HASH_SUFFIX, env.getHash());
|
||||
}
|
||||
|
||||
private void assertJSONDiff(JSONObject source, JSONObject diff) throws JSONException {
|
||||
@ -406,4 +415,103 @@ public class TestHealthReportGenerator extends FakeProfileTestCase {
|
||||
protected String getCacheSuffix() {
|
||||
return File.separator + "health-" + System.currentTimeMillis() + ".profile";
|
||||
}
|
||||
|
||||
|
||||
public void testEnvironmentDiffing() throws JSONException {
|
||||
// Manually insert a v1 environment.
|
||||
final MockHealthReportDatabaseStorage storage = new MockHealthReportDatabaseStorage(context, fakeProfileDirectory);
|
||||
final SQLiteDatabase db = storage.getDB();
|
||||
storage.deleteEverything();
|
||||
final MockDatabaseEnvironment v1env = storage.getEnvironment();
|
||||
v1env.mockInit("27.0a1");
|
||||
v1env.version = 1;
|
||||
v1env.appLocale = "";
|
||||
v1env.osLocale = "";
|
||||
v1env.distribution = "";
|
||||
v1env.acceptLangSet = 0;
|
||||
final int v1ID = v1env.register();
|
||||
|
||||
// Verify.
|
||||
final String[] cols = new String[] {
|
||||
"id", "version", "hash",
|
||||
"osLocale", "acceptLangSet", "appLocale", "distribution"
|
||||
};
|
||||
|
||||
final Cursor c1 = db.query("environments", cols, "id = " + v1ID, null, null, null, null);
|
||||
String v1envHash;
|
||||
try {
|
||||
assertTrue(c1.moveToFirst());
|
||||
assertEquals(1, c1.getCount());
|
||||
|
||||
assertEquals(v1ID, c1.getInt(0));
|
||||
assertEquals(1, c1.getInt(1));
|
||||
|
||||
v1envHash = c1.getString(2);
|
||||
assertNotNull(v1envHash);
|
||||
assertEquals("", c1.getString(3));
|
||||
assertEquals(0, c1.getInt(4));
|
||||
assertEquals("", c1.getString(5));
|
||||
assertEquals("", c1.getString(6));
|
||||
} finally {
|
||||
c1.close();
|
||||
}
|
||||
|
||||
// Insert a v2 environment.
|
||||
final MockDatabaseEnvironment v2env = storage.getEnvironment();
|
||||
v2env.mockInit("27.0a1");
|
||||
v2env.appLocale = v2env.osLocale = "en_us";
|
||||
v2env.acceptLangSet = 1;
|
||||
|
||||
final int v2ID = v2env.register();
|
||||
assertFalse(v1ID == v2ID);
|
||||
final Cursor c2 = db.query("environments", cols, "id = " + v2ID, null, null, null, null);
|
||||
String v2envHash;
|
||||
try {
|
||||
assertTrue(c2.moveToFirst());
|
||||
assertEquals(1, c2.getCount());
|
||||
|
||||
assertEquals(v2ID, c2.getInt(0));
|
||||
assertEquals(2, c2.getInt(1));
|
||||
|
||||
v2envHash = c2.getString(2);
|
||||
assertNotNull(v2envHash);
|
||||
assertEquals("en_us", c2.getString(3));
|
||||
assertEquals(1, c2.getInt(4));
|
||||
assertEquals("en_us", c2.getString(5));
|
||||
assertEquals("", c2.getString(6));
|
||||
} finally {
|
||||
c2.close();
|
||||
}
|
||||
|
||||
assertFalse(v1envHash.equals(v2envHash));
|
||||
|
||||
// Now let's diff based on DB contents.
|
||||
SparseArray<Environment> envs = storage.getEnvironmentRecordsByID();
|
||||
|
||||
JSONObject oldEnv = HealthReportGenerator.jsonify(envs.get(v1ID), null).getJSONObject("org.mozilla.appInfo.appinfo");
|
||||
JSONObject newEnv = HealthReportGenerator.jsonify(envs.get(v2ID), null).getJSONObject("org.mozilla.appInfo.appinfo");
|
||||
|
||||
// Generate the new env as if the old were the current. This should rarely happen in practice.
|
||||
// Fields supported by the new env but not the old will appear, even if the 'default' for the
|
||||
// old implementation is equal to the new env's value.
|
||||
JSONObject newVsOld = HealthReportGenerator.jsonify(envs.get(v2ID), envs.get(v1ID)).getJSONObject("org.mozilla.appInfo.appinfo");
|
||||
|
||||
// Generate the old env as if the new were the current. This is normal. Fields not supported by the old
|
||||
// environment version should not appear in the output.
|
||||
JSONObject oldVsNew = HealthReportGenerator.jsonify(envs.get(v1ID), envs.get(v2ID)).getJSONObject("org.mozilla.appInfo.appinfo");
|
||||
assertEquals(2, oldEnv.getInt("_v"));
|
||||
assertEquals(3, newEnv.getInt("_v"));
|
||||
assertEquals(2, oldVsNew.getInt("_v"));
|
||||
assertEquals(3, newVsOld.getInt("_v"));
|
||||
|
||||
assertFalse(oldVsNew.has("osLocale"));
|
||||
assertFalse(oldVsNew.has("appLocale"));
|
||||
assertFalse(oldVsNew.has("distribution"));
|
||||
assertFalse(oldVsNew.has("acceptLangIsUserSet"));
|
||||
|
||||
assertTrue(newVsOld.has("osLocale"));
|
||||
assertTrue(newVsOld.has("appLocale"));
|
||||
assertTrue(newVsOld.has("distribution"));
|
||||
assertTrue(newVsOld.has("acceptLangIsUserSet"));
|
||||
}
|
||||
}
|
||||
|
@ -146,7 +146,7 @@ public class TestHealthReportProvider extends DBProviderTestCase<HealthReportPro
|
||||
Cursor envCursor = resolver.query(envURI, null, null, null, null);
|
||||
try {
|
||||
assertTrue(envCursor.moveToFirst());
|
||||
envHash = envCursor.getString(1);
|
||||
envHash = envCursor.getString(2); // id, version, hash, ...
|
||||
} finally {
|
||||
envCursor.close();
|
||||
}
|
||||
@ -249,6 +249,13 @@ public class TestHealthReportProvider extends DBProviderTestCase<HealthReportPro
|
||||
v.put("os", "");
|
||||
v.put("xpcomabi", "");
|
||||
v.put("updateChannel", "");
|
||||
|
||||
// v2.
|
||||
v.put("distribution", "");
|
||||
v.put("osLocale", "en_us");
|
||||
v.put("appLocale", "en_us");
|
||||
v.put("acceptLangSet", 0);
|
||||
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
@ -53,6 +53,7 @@
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
#include "nsINetworkManager.h"
|
||||
#include "nsINetworkStatsServiceProxy.h"
|
||||
#endif
|
||||
|
||||
@ -966,6 +967,7 @@ WebSocketChannel::WebSocketChannel() :
|
||||
mCountRecv(0),
|
||||
mCountSent(0),
|
||||
mAppId(0),
|
||||
mConnectionType(NETWORK_NO_TYPE),
|
||||
mIsInBrowser(false)
|
||||
{
|
||||
NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
|
||||
@ -1080,9 +1082,9 @@ WebSocketChannel::BeginOpen()
|
||||
NS_GetAppInfo(localChannel, &mAppId, &mIsInBrowser);
|
||||
}
|
||||
|
||||
// obtain active network
|
||||
// obtain active connection type
|
||||
if (mAppId != NECKO_NO_APP_ID) {
|
||||
GetActiveNetwork();
|
||||
GetConnectionType(&mConnectionType);
|
||||
}
|
||||
|
||||
rv = localChannel->AsyncOpen(this, mHttpChannel);
|
||||
@ -3272,7 +3274,7 @@ WebSocketChannel::OnDataAvailable(nsIRequest *aRequest,
|
||||
}
|
||||
|
||||
nsresult
|
||||
WebSocketChannel::GetActiveNetwork()
|
||||
WebSocketChannel::GetConnectionType(int32_t *type)
|
||||
{
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
@ -3281,11 +3283,15 @@ WebSocketChannel::GetActiveNetwork()
|
||||
nsCOMPtr<nsINetworkManager> networkManager = do_GetService("@mozilla.org/network/manager;1", &result);
|
||||
|
||||
if (NS_FAILED(result) || !networkManager) {
|
||||
mActiveNetwork = nullptr;
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
*type = NETWORK_NO_TYPE;
|
||||
}
|
||||
|
||||
result = networkManager->GetActive(getter_AddRefs(mActiveNetwork));
|
||||
nsCOMPtr<nsINetworkInterface> networkInterface;
|
||||
result = networkManager->GetActive(getter_AddRefs(networkInterface));
|
||||
|
||||
if (networkInterface) {
|
||||
result = networkInterface->GetType(type);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
#else
|
||||
@ -3297,8 +3303,9 @@ nsresult
|
||||
WebSocketChannel::SaveNetworkStats(bool enforce)
|
||||
{
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
// Check if the active network and app id are valid.
|
||||
if(!mActiveNetwork || mAppId == NECKO_NO_APP_ID) {
|
||||
// Check if the connection type and app id are valid.
|
||||
if(mConnectionType == NETWORK_NO_TYPE ||
|
||||
mAppId == NECKO_NO_APP_ID) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -3322,7 +3329,7 @@ WebSocketChannel::SaveNetworkStats(bool enforce)
|
||||
return rv;
|
||||
}
|
||||
|
||||
mNetworkStatsServiceProxy->SaveAppStats(mAppId, mActiveNetwork, PR_Now() / 1000,
|
||||
mNetworkStatsServiceProxy->SaveAppStats(mAppId, mConnectionType, PR_Now() / 1000,
|
||||
mCountRecv, mCountSent, nullptr);
|
||||
|
||||
// Reset the counters after saving.
|
||||
|
@ -18,10 +18,6 @@
|
||||
#include "nsIHttpChannelInternal.h"
|
||||
#include "BaseWebSocketChannel.h"
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
#include "nsINetworkManager.h"
|
||||
#endif
|
||||
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsString.h"
|
||||
#include "nsDeque.h"
|
||||
@ -258,17 +254,16 @@ private:
|
||||
// These members are used for network per-app metering (bug 855949)
|
||||
// Currently, they are only available on gonk.
|
||||
public:
|
||||
const static int32_t NETWORK_NO_TYPE = -1; // default conntection type
|
||||
const static uint64_t NETWORK_STATS_THRESHOLD = 65536;
|
||||
|
||||
private:
|
||||
uint64_t mCountRecv;
|
||||
uint64_t mCountSent;
|
||||
uint32_t mAppId;
|
||||
int32_t mConnectionType;
|
||||
bool mIsInBrowser;
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
nsCOMPtr<nsINetworkInterface> mActiveNetwork;
|
||||
#endif
|
||||
nsresult GetActiveNetwork();
|
||||
nsresult GetConnectionType(int32_t *);
|
||||
nsresult SaveNetworkStats(bool);
|
||||
void CountRecvBytes(uint64_t recvBytes)
|
||||
{
|
||||
|
@ -139,6 +139,8 @@ class TreeMetadataEmitter(LoggingMixin):
|
||||
passthru = VariablePassthru(sandbox)
|
||||
varmap = dict(
|
||||
# Makefile.in : moz.build
|
||||
ANDROID_GENERATED_RESFILES='ANDROID_GENERATED_RESFILES',
|
||||
ANDROID_RESFILES='ANDROID_RESFILES',
|
||||
ASFILES='ASFILES',
|
||||
CMMSRCS='CMMSRCS',
|
||||
CPPSRCS='CPP_SOURCES',
|
||||
|
@ -43,6 +43,22 @@ from mozbuild.util import (
|
||||
|
||||
VARIABLES = {
|
||||
# Variables controlling reading of other frontend files.
|
||||
'ANDROID_GENERATED_RESFILES': (StrictOrderingOnAppendList, list, [],
|
||||
"""Android resource files generated as part of the build.
|
||||
|
||||
This variable contains a list of files that are expected to be
|
||||
generated (often by preprocessing) into a 'res' directory as
|
||||
part of the build process, and subsequently merged into an APK
|
||||
file.
|
||||
""", 'export'),
|
||||
|
||||
'ANDROID_RESFILES': (StrictOrderingOnAppendList, list, [],
|
||||
"""Android resource files.
|
||||
|
||||
This variable contains a list of files to package into a 'res'
|
||||
directory and merge into an APK file.
|
||||
""", 'export'),
|
||||
|
||||
'ASFILES': (StrictOrderingOnAppendList, list, [],
|
||||
"""Assembly file sources.
|
||||
|
||||
|
@ -0,0 +1,6 @@
|
||||
MARIONETTE_TIMEOUT = 60000;
|
||||
MARIONETTE_CONTEXT = "chrome";
|
||||
ok(true);
|
||||
(function () {
|
||||
finish();
|
||||
})();
|
@ -93,4 +93,5 @@ b2g = false
|
||||
[test_implicit_waits.py]
|
||||
[test_date_time_value.py]
|
||||
[test_getactiveframe_oop.py]
|
||||
[test_submit.py]
|
||||
[test_submit.py]
|
||||
[test_chrome_async_finish.js]
|
||||
|
@ -844,7 +844,7 @@ MarionetteServerConnection.prototype = {
|
||||
aRequest.newSandbox = true;
|
||||
}
|
||||
if (this.context == "chrome") {
|
||||
if (aRequest.async) {
|
||||
if (aRequest.parameters.async) {
|
||||
this.executeWithCallback(aRequest, aRequest.parameters.async);
|
||||
}
|
||||
else {
|
||||
|
Loading…
x
Reference in New Issue
Block a user