Merge fx-team to mozilla-central

This commit is contained in:
Carsten "Tomcat" Book 2013-10-17 13:42:23 +02:00
Родитель ef3b3325fc 2973051823
Коммит 348406bdbc
64 изменённых файлов: 2567 добавлений и 1412 удалений

Просмотреть файл

@ -482,7 +482,7 @@
} }
// null parameter below specifies HTML response for search // 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"); BrowserSearch.recordSearchInHealthReport(this.currentEngine.name, "searchbar");
openUILinkIn(submission.uri.spec, aWhere, null, submission.postData); openUILinkIn(submission.uri.spec, aWhere, null, submission.postData);
]]></body> ]]></body>
@ -531,14 +531,14 @@
var engine = this.currentEngine; var engine = this.currentEngine;
var connector = var connector =
Services.io.QueryInterface(Components.interfaces.nsISpeculativeConnect); 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) let callbacks = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIWebNavigation) .getInterface(Components.interfaces.nsIWebNavigation)
.QueryInterface(Components.interfaces.nsILoadContext); .QueryInterface(Components.interfaces.nsILoadContext);
connector.speculativeConnect(searchURI, callbacks); connector.speculativeConnect(searchURI, callbacks);
if (engine.supportsResponseType(SUGGEST_TYPE)) { 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) if (suggestURI.prePath != searchURI.prePath)
connector.speculativeConnect(suggestURI, callbacks); connector.speculativeConnect(suggestURI, callbacks);
} }

Просмотреть файл

@ -5,6 +5,7 @@
ifdef ENABLE_TESTS ifdef ENABLE_TESTS
pp_mochitest_browser_files := \ pp_mochitest_browser_files := \
browser_google.js \ browser_google.js \
browser_google_behavior.js \
$(NULL) $(NULL)
pp_mochitest_browser_files_PATH := $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir) pp_mochitest_browser_files_PATH := $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
pp_mochitest_browser_files_FLAGS := -DMOZ_DISTRIBUTION_ID=$(MOZ_DISTRIBUTION_ID) 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'"); is(url, base + "&channel=rcs", "Check context menu search URL for 'foo'");
url = engine.getSubmission("foo", null, "keyword").uri.spec; url = engine.getSubmission("foo", null, "keyword").uri.spec;
is(url, base + "&channel=fflb", "Check keyword search URL for 'foo'"); 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. // Check search suggestion URL.
url = engine.getSubmission("foo", "application/x-suggestions+json").uri.spec; url = engine.getSubmission("foo", "application/x-suggestions+json").uri.spec;
@ -147,6 +151,11 @@ function test() {
"value": "fflb", "value": "fflb",
"purpose": "keyword", "purpose": "keyword",
}, },
{
"name": "channel",
"value": "sb",
"purpose": "searchbar",
},
{ {
"name": "channel", "name": "channel",
"value": "np", "value": "np",

Просмотреть файл

@ -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 #endif
<MozParam name="channel" condition="purpose" purpose="contextmenu" value="rcs"/> <MozParam name="channel" condition="purpose" purpose="contextmenu" value="rcs"/>
<MozParam name="channel" condition="purpose" purpose="keyword" value="fflb"/> <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="channel" condition="purpose" purpose="homepage" value="np"/>
<MozParam name="source" condition="purpose" purpose="homepage" value="hp"/> <MozParam name="source" condition="purpose" purpose="homepage" value="hp"/>
</Url> </Url>

Просмотреть файл

@ -9,7 +9,7 @@
* link parameter/model object expected to have a .url property, and optionally .title * link parameter/model object expected to have a .url property, and optionally .title
*/ */
function Site(aLink) { function Site(aLink) {
if(!aLink.url) { if (!aLink.url) {
throw Cr.NS_ERROR_INVALID_ARG; throw Cr.NS_ERROR_INVALID_ARG;
} }
this._link = aLink; this._link = aLink;
@ -64,7 +64,7 @@ Site.prototype = {
} }
} }
// is binding already applied? // is binding already applied?
if (aNode.refresh) { if ('refresh' in aNode) {
// just update it // just update it
aNode.refresh(); aNode.refresh();
} else { } else {

Просмотреть файл

@ -27,7 +27,7 @@
<field name="controller">null</field> <field name="controller">null</field>
<!-- collection of child items excluding empty tiles --> <!-- 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;"/> <property name="itemCount" readonly="true" onget="return this.items.length;"/>
<!-- nsIDOMXULMultiSelectControlElement (not fully implemented) --> <!-- nsIDOMXULMultiSelectControlElement (not fully implemented) -->
@ -96,7 +96,7 @@
<parameter name="aEvent"/> <parameter name="aEvent"/>
<body> <body>
<![CDATA[ <![CDATA[
if(!this.isBound) if (!this.isBound)
return; return;
if ("single" == this.getAttribute("seltype")) { if ("single" == this.getAttribute("seltype")) {
@ -116,7 +116,7 @@
<parameter name="aEvent"/> <parameter name="aEvent"/>
<body> <body>
<![CDATA[ <![CDATA[
if(!this.isBound || this.suppressOnSelect) if (!this.isBound || this.suppressOnSelect)
return; return;
// we'll republish this as a selectionchange event on the grid // we'll republish this as a selectionchange event on the grid
aEvent.stopPropagation(); aEvent.stopPropagation();
@ -175,7 +175,7 @@
<property name="selectedItems"> <property name="selectedItems">
<getter> <getter>
<![CDATA[ <![CDATA[
return this.querySelectorAll("richgriditem[selected]"); return this.querySelectorAll("richgriditem[value][selected]");
]]> ]]>
</getter> </getter>
</property> </property>
@ -204,28 +204,105 @@
<parameter name="aSkipArrange"/> <parameter name="aSkipArrange"/>
<body> <body>
<![CDATA[ <![CDATA[
let addition = this._createItemElement(aLabel, aValue); let item = this.nextSlot();
this.appendChild(addition); item.setAttribute("value", aValue);
item.setAttribute("label", aLabel);
if (!aSkipArrange) if (!aSkipArrange)
this.arrangeItems(); this.arrangeItems();
return addition; return item;
]]> ]]>
</body> </body>
</method> </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"> <method name="clearAll">
<parameter name="aSkipArrange"/> <parameter name="aSkipArrange"/>
<body> <body>
<![CDATA[ <![CDATA[
while (this.firstChild) { const ELEMENT_NODE_TYPE = Components.interfaces.nsIDOMNode.ELEMENT_NODE;
this.removeChild(this.firstChild); 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) if (!aSkipArrange)
this.arrangeItems(); this.arrangeItems();
]]> ]]>
</body> </body>
</method> </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"> <method name="insertItemAt">
<parameter name="anIndex"/> <parameter name="anIndex"/>
<parameter name="aLabel"/> <parameter name="aLabel"/>
@ -233,19 +310,30 @@
<parameter name="aSkipArrange"/> <parameter name="aSkipArrange"/>
<body> <body>
<![CDATA[ <![CDATA[
anIndex = Math.min(this.itemCount, anIndex);
let insertedItem;
let existing = this.getItemAtIndex(anIndex); let existing = this.getItemAtIndex(anIndex);
let addition = this._createItemElement(aLabel, aValue);
if (existing) { if (existing) {
this.insertBefore(addition, existing); // use an empty slot if we have one, otherwise insert it
} else { let childIndex = Array.indexOf(this.children, existing);
this.appendChild(addition); 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) if (!aSkipArrange)
this.arrangeItems(); this.arrangeItems();
return addition; return insertedItem;
]]> ]]>
</body> </body>
</method> </method>
<method name="removeItemAt"> <method name="removeItemAt">
<parameter name="anIndex"/> <parameter name="anIndex"/>
<parameter name="aSkipArrange"/> <parameter name="aSkipArrange"/>
@ -266,7 +354,13 @@
<![CDATA[ <![CDATA[
if (!aItem || Array.indexOf(this.items, aItem) < 0) if (!aItem || Array.indexOf(this.items, aItem) < 0)
return null; return null;
let removal = this.removeChild(aItem); let removal = this.removeChild(aItem);
// replace the slot if necessary
if (this.childElementCount < this.minSlots) {
this.nextSlot();
}
if (removal && !aSkipArrange) if (removal && !aSkipArrange)
this.arrangeItems(); this.arrangeItems();
@ -422,6 +516,7 @@
<field name="_scheduledArrangeItemsTimerId">null</field> <field name="_scheduledArrangeItemsTimerId">null</field>
<field name="_scheduledArrangeItemsTries">0</field> <field name="_scheduledArrangeItemsTries">0</field>
<field name="_maxArrangeItemsRetries">5</field> <field name="_maxArrangeItemsRetries">5</field>
<method name="_scheduleArrangeItems"> <method name="_scheduleArrangeItems">
<parameter name="aTime"/> <parameter name="aTime"/>
<body> <body>
@ -453,6 +548,7 @@
let itemDims = this._itemSize; let itemDims = this._itemSize;
let containerDims = this._containerSize; let containerDims = this._containerSize;
let slotsCount = this.childElementCount;
// reset the flags // reset the flags
if (this._scheduledArrangeItemsTimerId) { if (this._scheduledArrangeItemsTimerId) {
@ -468,25 +564,25 @@
if (this.hasAttribute("vertical")) { if (this.hasAttribute("vertical")) {
this._columnCount = Math.floor(containerDims.width / itemDims.width) || 1; 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 { } else {
// We favor overflowing horizontally, not vertically (rows then colums) // rows attribute is fixed number of rows
// rows attribute = max rows let maxRows = Math.floor(containerDims.height / itemDims.height);
let maxRowCount = Math.min(this.getAttribute("rows") || Infinity, Math.floor(containerDims.height / itemDims.height)); this._rowCount = this.getAttribute("rows") ?
this._rowCount = Math.min(this.itemCount, maxRowCount); // fit indicated rows when possible
Math.min(maxRows, this.getAttribute("rows")) :
// at least 1 row
Math.min(maxRows, slotsCount) || 1;
// columns attribute = min cols // columns attribute is min number of cols
this._columnCount = this.itemCount ? this._columnCount = Math.ceil(slotsCount / this._rowCount) || 1;
Math.max( if (this.getAttribute("columns")) {
// at least 1 column when there are items this._columnCount = Math.max(this._columnCount, this.getAttribute("columns"));
this.getAttribute("columns") || 1, }
Math.ceil(this.itemCount / this._rowCount)
) : this.getAttribute("columns") || 0;
} }
// width is typically auto, cap max columns by truncating items collection // width is typically auto, cap max columns by truncating items collection
// or, setting max-width style property with overflow hidden // 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) { if (this._columnCount) {
gridStyle.MozColumnCount = this._columnCount; gridStyle.MozColumnCount = this._columnCount;
} }
@ -521,6 +617,12 @@
<field name="_xslideHandler"/> <field name="_xslideHandler"/>
<constructor> <constructor>
<![CDATA[ <![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) if (this.controller && this.controller.gridBoundCallback != undefined)
this.controller.gridBoundCallback(); this.controller.gridBoundCallback();
@ -605,6 +707,7 @@
]]> ]]>
</body> </body>
</method> </method>
<method name="_isIndexInBounds"> <method name="_isIndexInBounds">
<parameter name="anIndex"/> <parameter name="anIndex"/>
<body> <body>
@ -626,7 +729,7 @@
if (aLabel) { if (aLabel) {
item.setAttribute("label", aLabel); item.setAttribute("label", aLabel);
} }
if(this.hasAttribute("tiletype")) { if (this.hasAttribute("tiletype")) {
item.setAttribute("tiletype", this.getAttribute("tiletype")); item.setAttribute("tiletype", this.getAttribute("tiletype"));
} }
return item; return item;
@ -704,6 +807,7 @@
aItem.setAttribute("bending", true); aItem.setAttribute("bending", true);
]]></body> ]]></body>
</method> </method>
<method name="unbendItem"> <method name="unbendItem">
<parameter name="aItem"/> <parameter name="aItem"/>
<body><![CDATA[ <body><![CDATA[
@ -749,7 +853,7 @@
let state = event.crossSlidingState; let state = event.crossSlidingState;
let thresholds = this._xslideHandler.thresholds; let thresholds = this._xslideHandler.thresholds;
let transformValue; let transformValue;
switch(state) { switch (state) {
case "cancelled": case "cancelled":
this.unbendItem(event.target); this.unbendItem(event.target);
event.target.removeAttribute('crosssliding'); event.target.removeAttribute('crosssliding');
@ -844,7 +948,7 @@
<body> <body>
<![CDATA[ <![CDATA[
// Prevent an exception in case binding is not done yet. // Prevent an exception in case binding is not done yet.
if(!this.isBound) if (!this.isBound)
return; return;
// Seed the binding properties from bound-node attribute values // Seed the binding properties from bound-node attribute values
@ -914,7 +1018,7 @@
<method name="refreshBackgroundImage"> <method name="refreshBackgroundImage">
<body><![CDATA[ <body><![CDATA[
if(!this.isBound) if (!this.isBound)
return; return;
if (this.backgroundImage) { if (this.backgroundImage) {
this._top.style.removeProperty("background-image"); this._top.style.removeProperty("background-image");
@ -927,7 +1031,7 @@
<property name="contextActions"> <property name="contextActions">
<getter> <getter>
<![CDATA[ <![CDATA[
if(!this._contextActions) { if (!this._contextActions) {
this._contextActions = new Set(); this._contextActions = new Set();
let actionSet = this._contextActions; let actionSet = this._contextActions;
let actions = this.getAttribute("data-contextactions"); let actions = this.getAttribute("data-contextactions");
@ -959,7 +1063,7 @@
// fires for right-click, long-click and (keyboard) contextmenu input // fires for right-click, long-click and (keyboard) contextmenu input
// toggle the selected state of tiles in a grid // toggle the selected state of tiles in a grid
let gridParent = this.control; let gridParent = this.control;
if(!this.isBound || !gridParent) if (!this.isBound || !gridParent)
return; return;
gridParent.handleItemContextMenu(this, event); gridParent.handleItemContextMenu(this, event);
]]> ]]>
@ -967,4 +1071,10 @@
</handlers> </handlers>
</binding> </binding>
<binding id="richgrid-empty-item">
<content>
<html:div anonid="anon-tile" class="tile-content"></html:div>
</content>
</binding>
</bindings> </bindings>

Просмотреть файл

@ -119,6 +119,9 @@ richgrid {
} }
richgriditem { 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"); -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) { _getItemForBookmarkId: function bv__getItemForBookmark(aBookmarkId) {
return this._set.querySelector("richgriditem[bookmarkId='" + aBookmarkId + "']"); return this._set.querySelector("richgriditem[anonid='" + aBookmarkId + "']");
}, },
_getBookmarkIdForItem: function bv__getBookmarkForItem(aItem) { _getBookmarkIdForItem: function bv__getBookmarkForItem(aItem) {
return +aItem.getAttribute("bookmarkId"); return +aItem.getAttribute("anonid");
}, },
_updateItemWithAttrs: function dv__updateItemWithAttrs(anItem, aAttrs) { _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.removeItemAt(this._set.itemCount - 1, true);
} }
this._set.arrangeItems(); this._set.arrangeItems();
this._set.removeAttribute("fade");
this._inBatch = false; this._inBatch = false;
rootNode.containerOpen = false; rootNode.containerOpen = false;
}, },
@ -154,7 +155,8 @@ BookmarksView.prototype = Util.extend(Object.create(View.prototype), {
}, },
clearBookmarks: function bv_clearBookmarks() { clearBookmarks: function bv_clearBookmarks() {
this._set.clearAll(); if ('clearAll' in this._set)
this._set.clearAll();
}, },
addBookmark: function bv_addBookmark(aBookmarkId, aPos) { 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 uri = this._bookmarkService.getBookmarkURI(aBookmarkId);
let title = this._bookmarkService.getItemTitle(aBookmarkId) || uri.spec; let title = this._bookmarkService.getItemTitle(aBookmarkId) || uri.spec;
let item = this._set.insertItemAt(aPos || index, title, uri.spec, this._inBatch); let item = this._set.insertItemAt(aPos || index, title, uri.spec, this._inBatch);
item.setAttribute("bookmarkId", aBookmarkId); item.setAttribute("anonid", aBookmarkId);
this._setContextActions(item); this._setContextActions(item);
this._updateFavicon(item, uri); this._updateFavicon(item, uri);
}, },
@ -198,6 +200,7 @@ BookmarksView.prototype = Util.extend(Object.create(View.prototype), {
let uri = this._bookmarkService.getBookmarkURI(aBookmarkId); let uri = this._bookmarkService.getBookmarkURI(aBookmarkId);
let title = this._bookmarkService.getItemTitle(aBookmarkId) || uri.spec; let title = this._bookmarkService.getItemTitle(aBookmarkId) || uri.spec;
item.setAttribute("anonid", aBookmarkId);
item.setAttribute("value", uri.spec); item.setAttribute("value", uri.spec);
item.setAttribute("label", title); item.setAttribute("label", title);

Просмотреть файл

@ -95,6 +95,7 @@ HistoryView.prototype = Util.extend(Object.create(View.prototype), {
rootNode.containerOpen = false; rootNode.containerOpen = false;
this._set.arrangeItems(); this._set.arrangeItems();
this._set.removeAttribute("fade");
if (this._inBatch > 0) if (this._inBatch > 0)
this._inBatch--; this._inBatch--;
}, },
@ -130,6 +131,9 @@ HistoryView.prototype = Util.extend(Object.create(View.prototype), {
let tileGroup = this._set; let tileGroup = this._set;
let selectedTiles = tileGroup.selectedItems; let selectedTiles = tileGroup.selectedItems;
// just arrange the grid once at the end of any action handling
this._inBatch = true;
switch (aActionName){ switch (aActionName){
case "delete": case "delete":
Array.forEach(selectedTiles, function(aNode) { Array.forEach(selectedTiles, function(aNode) {
@ -182,9 +186,11 @@ HistoryView.prototype = Util.extend(Object.create(View.prototype), {
break; break;
default: default:
this._inBatch = false;
return; return;
} }
this._inBatch = false;
// Send refresh event so all view are in sync. // Send refresh event so all view are in sync.
this._sendNeedsRefresh(); this._sendNeedsRefresh();
}, },
@ -254,7 +260,8 @@ HistoryView.prototype = Util.extend(Object.create(View.prototype), {
}, },
onClearHistory: function() { onClearHistory: function() {
this._set.clearAll(); if ('clearAll' in this._set)
this._set.clearAll();
}, },
onPageChanged: function(aURI, aWhat, aValue) { onPageChanged: function(aURI, aWhat, aValue) {
@ -264,7 +271,7 @@ HistoryView.prototype = Util.extend(Object.create(View.prototype), {
let currIcon = item.getAttribute("iconURI"); let currIcon = item.getAttribute("iconURI");
if (currIcon != aValue) { if (currIcon != aValue) {
item.setAttribute("iconURI", aValue); item.setAttribute("iconURI", aValue);
if("refresh" in item) if ("refresh" in item)
item.refresh(); item.refresh();
} }
} }

Просмотреть файл

@ -93,6 +93,7 @@ RemoteTabsView.prototype = Util.extend(Object.create(View.prototype), {
} }
this.setUIAccessVisible(show); this.setUIAccessVisible(show);
this._set.arrangeItems(); this._set.arrangeItems();
this._set.removeAttribute("fade");
}, },
destruct: function destruct() { destruct: function destruct() {

Просмотреть файл

@ -48,7 +48,17 @@
<html:div class="meta-section-title narrow-title" onclick="StartUI.onNarrowTitleClick('start-topsites')"> <html:div class="meta-section-title narrow-title" onclick="StartUI.onNarrowTitleClick('start-topsites')">
&narrowTopSitesHeader.label; &narrowTopSitesHeader.label;
</html:div> </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>
<vbox id="start-bookmarks" class="meta-section"> <vbox id="start-bookmarks" class="meta-section">
@ -56,7 +66,10 @@
<html:div class="meta-section-title narrow-title" onclick="StartUI.onNarrowTitleClick('start-bookmarks')"> <html:div class="meta-section-title narrow-title" onclick="StartUI.onNarrowTitleClick('start-bookmarks')">
&narrowBookmarksHeader.label; &narrowBookmarksHeader.label;
</html:div> </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>
<vbox id="start-history" class="meta-section"> <vbox id="start-history" class="meta-section">
@ -64,7 +77,11 @@
<html:div class="meta-section-title narrow-title" onclick="StartUI.onNarrowTitleClick('start-history')"> <html:div class="meta-section-title narrow-title" onclick="StartUI.onNarrowTitleClick('start-history')">
&narrowRecentHistoryHeader.label; &narrowRecentHistoryHeader.label;
</html:div> </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> </vbox>
#ifdef MOZ_SERVICES_SYNC #ifdef MOZ_SERVICES_SYNC
@ -73,7 +90,12 @@
<html:div id="snappedRemoteTabsLabel" class="meta-section-title narrow-title" onclick="StartUI.onNarrowTitleClick('start-remotetabs')"> <html:div id="snappedRemoteTabsLabel" class="meta-section-title narrow-title" onclick="StartUI.onNarrowTitleClick('start-remotetabs')">
&narrowRemoteTabsHeader.label; &narrowRemoteTabsHeader.label;
</html:div> </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> </vbox>
#endif #endif
</hbox> </hbox>

Просмотреть файл

@ -158,6 +158,9 @@ TopSitesView.prototype = Util.extend(Object.create(View.prototype), {
}, },
updateTile: function(aTileNode, aSite, aArrangeGrid) { 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)); this._updateFavicon(aTileNode, Util.makeURI(aSite.url));
Task.spawn(function() { Task.spawn(function() {
@ -192,14 +195,11 @@ TopSitesView.prototype = Util.extend(Object.create(View.prototype), {
tileset.clearAll(true); tileset.clearAll(true);
for (let site of sites) { for (let site of sites) {
// call to private _createItemElement is a temp measure let slot = tileset.nextSlot();
// we'll eventually just request the next slot this.updateTile(slot, site);
let item = tileset._createItemElement(site.title, site.url);
this.updateTile(item, site);
tileset.appendChild(item);
} }
tileset.arrangeItems(); tileset.arrangeItems();
tileset.removeAttribute("fade");
this.isUpdating = false; this.isUpdating = false;
}, },
@ -244,7 +244,7 @@ TopSitesView.prototype = Util.extend(Object.create(View.prototype), {
// nsIObservers // nsIObservers
observe: function (aSubject, aTopic, aState) { observe: function (aSubject, aTopic, aState) {
switch(aTopic) { switch (aTopic) {
case "Metro:RefreshTopsiteThumbnail": case "Metro:RefreshTopsiteThumbnail":
this.forceReloadOfThumbnail(aState); this.forceReloadOfThumbnail(aState);
break; break;
@ -269,7 +269,8 @@ TopSitesView.prototype = Util.extend(Object.create(View.prototype), {
}, },
onClearHistory: function() { onClearHistory: function() {
this._set.clearAll(); if ('clearAll' in this._set)
this._set.clearAll();
}, },
onPageChanged: function(aURI, aWhat, aValue) { onPageChanged: function(aURI, aWhat, aValue) {

Просмотреть файл

@ -67,7 +67,7 @@ gTests.push({
ok(!item, "Item not in grid"); ok(!item, "Item not in grid");
ok(!gStartView._pinHelper.isPinned(uriFromIndex(2)), "Item unpinned"); 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 // --------- unpin multiple items
@ -124,7 +124,7 @@ gTests.push({
item = gStartView._set.getItemsByUrl(uriFromIndex(2))[0]; item = gStartView._set.getItemsByUrl(uriFromIndex(2))[0];
ok(!item, "Item not in grid"); 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(!restoreButton.hidden, "Restore button is visible.");
ok(gStartView._set.itemCount === gStartView._limit, "Grid repopulated"); ok(gStartView._set.itemCount === gStartView._limit, "Grid repopulated");
@ -150,9 +150,13 @@ gTests.push({
ok(!deleteButton.hidden, "Delete button is visible."); ok(!deleteButton.hidden, "Delete button is visible.");
let promise = waitForCondition(() => !restoreButton.hidden); let promise = waitForCondition(() => !restoreButton.hidden);
let populateGridSpy = spyOnMethod(gStartView, "populateGrid");
EventUtils.synthesizeMouse(deleteButton, 10, 10, {}, window); EventUtils.synthesizeMouse(deleteButton, 10, 10, {}, window);
yield promise; yield promise;
is(populateGridSpy.callCount, 1, "populateGrid was called in response to the deleting a tile");
item = gStartView._set.getItemsByUrl(uriFromIndex(2))[0]; item = gStartView._set.getItemsByUrl(uriFromIndex(2))[0];
ok(!item, "Item not in grid"); ok(!item, "Item not in grid");
@ -163,11 +167,14 @@ gTests.push({
Elements.contextappbar.dismiss(); Elements.contextappbar.dismiss();
yield promise; 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]; item = gStartView._set.getItemsByUrl(uriFromIndex(2))[0];
ok(!item, "Item not in grid"); ok(!item, "Item not in grid");
ok(!HistoryTestHelper._nodes[uriFromIndex(2)], "Item RIP"); 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 // --------- delete multiple items and restore

Просмотреть файл

@ -17,6 +17,9 @@
<richgrid id="grid_layout" seltype="single" flex="1"> <richgrid id="grid_layout" seltype="single" flex="1">
</richgrid> </richgrid>
</vbox> </vbox>
<vbox>
<richgrid id="slots_grid" seltype="single" minSlots="6" flex="1"/>
</vbox>
<vbox style="height:600px"> <vbox style="height:600px">
<hbox> <hbox>
<richgrid id="clearGrid" seltype="single" flex="1" rows="2"> <richgrid id="clearGrid" seltype="single" flex="1" rows="2">
@ -26,7 +29,7 @@
</richgrid> </richgrid>
</hbox> </hbox>
<hbox> <hbox>
<richgrid id="emptyGrid" seltype="single" flex="1" rows="2"> <richgrid id="emptyGrid" seltype="single" flex="1" rows="2" minSlots="6">
</richgrid> </richgrid>
</hbox> </hbox>
<hbox> <hbox>

Просмотреть файл

@ -9,6 +9,14 @@ function test() {
}).then(runTests); }).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({ gTests.push({
desc: "richgrid binding is applied", desc: "richgrid binding is applied",
run: function() { run: function() {
@ -17,9 +25,9 @@ gTests.push({
let grid = doc.querySelector("#grid1"); let grid = doc.querySelector("#grid1");
ok(grid, "#grid1 is found"); ok(grid, "#grid1 is found");
is(typeof grid.clearSelection, "function", "#grid1 has the binding applied"); is(typeof grid.clearSelection, "function", "#grid1 has the binding applied");
is(grid.items.length, 2, "#grid1 has a 2 items"); is(grid.items.length, 2, "#grid1 has a 2 items");
is(grid.items[0].control, grid, "#grid1 item's control points back at #grid1'"); 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({ gTests.push({
desc: "empty grid", desc: "empty grid",
run: function() { run: function() {
// XXX grids have minSlots and may not be ever truly empty
let grid = doc.getElementById("emptyGrid"); let grid = doc.getElementById("emptyGrid");
grid.arrangeItems(); grid.arrangeItems();
yield waitForCondition(() => !grid.isArranging); 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"); ok(grid.isBound, "binding was applied");
is(grid.itemCount, 0, "empty grid has 0 items"); is(grid.itemCount, 0, "empty grid has 0 items");
is(grid.rowCount, 0, "empty grid has 0 rows"); // minSlots attr. creates unpopulated slots
is(grid.columnCount, 0, "empty grid has 0 cols"); 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; // remove rows attribute and allow space for the grid to find its own height
let cStyle = doc.defaultView.getComputedStyle(columnsNode); // for its number of slots
is(cStyle.getPropertyValue("-moz-column-count"), "auto", "empty grid has -moz-column-count: auto"); 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"); is(typeof grid.insertItemAt, "function", "insertItemAt is a function on the grid");
let arrangeStub = stubMethod(grid, "arrangeItems"); 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"); ok(insertedAt0 && insertedAt00, "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");
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(); arrangeStub.restore();
} }
}); });
@ -417,3 +443,172 @@ gTests.push({
doc.defaultView.removeEventListener("selectionchange", handler, false); 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; 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 /* Snapped-view variation
We use the compact, single-column grid treatment for <=320px */ We use the compact, single-column grid treatment for <=320px */

Просмотреть файл

@ -169,7 +169,7 @@ let AboutHome = {
window.BrowserSearch.recordSearchInHealthReport(data.engineName, "abouthome"); window.BrowserSearch.recordSearchInHealthReport(data.engineName, "abouthome");
#endif #endif
// Trigger a search through nsISearchEngine.getSubmission() // 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); window.loadURI(submission.uri.spec, null, submission.postData);
break; break;
} }

Просмотреть файл

@ -9,11 +9,7 @@ dir-tests := $(DEPTH)/$(mobile-tests)
ANDROID_APK_NAME := robocop-debug ANDROID_APK_NAME := robocop-debug
ANDROID_EXTRA_JARS += \ ANDROID_EXTRA_JARS += \
$(srcdir)/robotium-solo-4.2.jar \ $(srcdir)/robotium-solo-4.3.jar \
$(NULL)
ANDROID_RESFILES = \
res/values/strings.xml \
$(NULL) $(NULL)
ANDROID_ASSETS_DIR := $(TESTPATH)/assets ANDROID_ASSETS_DIR := $(TESTPATH)/assets

Просмотреть файл

@ -1,11 +1,11 @@
Robocop is a Mozilla project which uses Robotium to test Firefox on Android devices. 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: source can be found here:
http://code.google.com/p/robotium/ 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 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: from the original download found at:
http://code.google.com/p/robotium/ http://code.google.com/p/robotium/
Firefox for Android developers should read the documentation in Firefox for Android developers should read the documentation in

Просмотреть файл

@ -6,3 +6,6 @@
MODULE = 'robocop' MODULE = 'robocop'
ANDROID_RESFILES = [
'res/values/strings.xml',
]

Двоичные данные
build/mobile/robocop/robotium-solo-4.2.jar

Двоичный файл не отображается.

Двоичные данные
build/mobile/robocop/robotium-solo-4.3.jar Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -20,16 +20,6 @@ JAVAFILES = \
WifiConfiguration.java \ WifiConfiguration.java \
$(NULL) $(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 = \ ANDROID_EXTRA_JARS = \
$(srcdir)/network-libs/commons-net-2.0.jar \ $(srcdir)/network-libs/commons-net-2.0.jar \
$(srcdir)/network-libs/jmdns.jar \ $(srcdir)/network-libs/jmdns.jar \

Просмотреть файл

@ -11,14 +11,6 @@ JAVAFILES = \
FileCursor.java \ FileCursor.java \
$(NULL) $(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 include $(topsrcdir)/config/rules.mk
tools:: $(ANDROID_APK_NAME).apk tools:: $(ANDROID_APK_NAME).apk

Просмотреть файл

@ -6,3 +6,10 @@
MODULE = 'FenCP' 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 \ FileCursor.java \
$(NULL) $(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 include $(topsrcdir)/config/rules.mk
tools:: $(ANDROID_APK_NAME).apk tools:: $(ANDROID_APK_NAME).apk

Просмотреть файл

@ -6,3 +6,10 @@
MODULE = 'FfxCP' 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' 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 \ WatcherService.java \
$(NULL) $(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 include $(topsrcdir)/config/rules.mk
tools:: $(ANDROID_APK_NAME).apk tools:: $(ANDROID_APK_NAME).apk

Просмотреть файл

@ -6,3 +6,13 @@
MODULE = 'Watcher' 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 #{ ifndef INCLUDED_JAVA_BUILD_MK #{
ifdef ANDROID_RESFILES #{ ifdef ANDROID_RESFILES #{
ifndef IGNORE_ANDROID_RESFILES #{
res-dep := .deps-copy-java-res res-dep := .deps-copy-java-res
GENERATED_DIRS += res GENERATED_DIRS += res
@ -25,6 +26,7 @@ res-dep-preqs := \
$(res-dep): $(res-dep-preqs) $(res-dep): $(res-dep-preqs)
$(call copy_dir,$(srcdir)/res,$(CURDIR)/res) $(call copy_dir,$(srcdir)/res,$(CURDIR)/res)
@$(TOUCH) $@ @$(TOUCH) $@
endif #} IGNORE_ANDROID_RESFILES
endif #} 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 # present. If they are, this is a violation of the separation of
# responsibility between Makefile.in and mozbuild files. # responsibility between Makefile.in and mozbuild files.
_MOZBUILD_EXTERNAL_VARIABLES := \ _MOZBUILD_EXTERNAL_VARIABLES := \
ANDROID_GENERATED_RESFILES \
ANDROID_RESFILES \
CMMSRCS \ CMMSRCS \
CPP_UNIT_TESTS \ CPP_UNIT_TESTS \
DIRS \ DIRS \

Просмотреть файл

@ -8,6 +8,7 @@
ifndef INCLUDED_JAVA_BUILD_MK #{ ifndef INCLUDED_JAVA_BUILD_MK #{
ifdef ANDROID_RESFILES #{ ifdef ANDROID_RESFILES #{
ifndef IGNORE_ANDROID_RESFILES #{
res-dep := .deps-copy-java-res res-dep := .deps-copy-java-res
GENERATED_DIRS += res GENERATED_DIRS += res
@ -25,6 +26,7 @@ res-dep-preqs := \
$(res-dep): $(res-dep-preqs) $(res-dep): $(res-dep-preqs)
$(call copy_dir,$(srcdir)/res,$(CURDIR)/res) $(call copy_dir,$(srcdir)/res,$(CURDIR)/res)
@$(TOUCH) $@ @$(TOUCH) $@
endif #} IGNORE_ANDROID_RESFILES
endif #} 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 # present. If they are, this is a violation of the separation of
# responsibility between Makefile.in and mozbuild files. # responsibility between Makefile.in and mozbuild files.
_MOZBUILD_EXTERNAL_VARIABLES := \ _MOZBUILD_EXTERNAL_VARIABLES := \
ANDROID_GENERATED_RESFILES \
ANDROID_RESFILES \
CMMSRCS \ CMMSRCS \
CPP_UNIT_TESTS \ CPP_UNIT_TESTS \
DIRS \ DIRS \

Просмотреть файл

@ -505,7 +505,7 @@ abstract public class BrowserApp extends GeckoApp
registerEventListener("Updater:Launch"); registerEventListener("Updater:Launch");
registerEventListener("Reader:GoToReadingList"); registerEventListener("Reader:GoToReadingList");
Distribution.init(this, getPackageResourcePath()); Distribution.init(this);
JavaAddonManager.getInstance().init(getApplicationContext()); JavaAddonManager.getInstance().init(getApplicationContext());
mSharedPreferencesHelper = new SharedPreferencesHelper(getApplicationContext()); mSharedPreferencesHelper = new SharedPreferencesHelper(getApplicationContext());
mOrderedBroadcastHelper = new OrderedBroadcastHelper(getApplicationContext()); mOrderedBroadcastHelper = new OrderedBroadcastHelper(getApplicationContext());

Просмотреть файл

@ -1,11 +1,7 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- /* -*- 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 * 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, * 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/. * You can obtain one at http://mozilla.org/MPL/2.0/. */
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko; package org.mozilla.gecko;
@ -13,113 +9,224 @@ import org.mozilla.gecko.util.ThreadUtils;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.util.Log; import android.util.Log;
import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.Collections;
import java.util.Enumeration; 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.ZipEntry;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
public final class Distribution { public final class Distribution {
private static final String LOGTAG = "GeckoDistribution"; 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_UNKNOWN = 0;
private static final int STATE_NONE = 1; private static final int STATE_NONE = 1;
private static final int STATE_SET = 2; 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. // Read/write preferences and files on the background thread.
ThreadUtils.postToBackgroundThread(new Runnable() { ThreadUtils.postToBackgroundThread(new Runnable() {
@Override @Override
public void run() { public void run() {
// Bail if we've already initialized the distribution. Distribution dist = new Distribution(context, packagePath, prefsPath);
SharedPreferences settings = context.getSharedPreferences(GeckoApp.PREFS_NAME, Activity.MODE_PRIVATE); boolean distributionSet = dist.doInit();
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;
}
}
if (distributionSet) { if (distributionSet) {
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Distribution:Set", "")); 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. * Copies the /distribution folder out of the APK and into the app's data directory.
* Returns true if distribution files were found and copied. * 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); File applicationPackage = new File(packagePath);
ZipFile zip = new ZipFile(applicationPackage); ZipFile zip = new ZipFile(applicationPackage);
boolean distributionSet = false; boolean distributionSet = false;
Enumeration<? extends ZipEntry> zipEntries = zip.entries(); Enumeration<? extends ZipEntry> zipEntries = zip.entries();
byte[] buffer = new byte[1024];
while (zipEntries.hasMoreElements()) { while (zipEntries.hasMoreElements()) {
ZipEntry fileEntry = zipEntries.nextElement(); ZipEntry fileEntry = zipEntries.nextElement();
String name = fileEntry.getName(); String name = fileEntry.getName();
if (!name.startsWith("distribution/")) if (!name.startsWith("distribution/")) {
continue; continue;
}
distributionSet = true; distributionSet = true;
File dataDir = new File(context.getApplicationInfo().dataDir); File outFile = new File(getDataDir(), name);
File outFile = new File(dataDir, name);
File dir = outFile.getParentFile(); 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); InputStream fileStream = zip.getInputStream(fileEntry);
OutputStream outStream = new FileOutputStream(outFile); OutputStream outStream = new FileOutputStream(outFile);
int b; int count;
while ((b = fileStream.read()) != -1) while ((count = fileStream.read(buffer)) != -1) {
outStream.write(b); outStream.write(buffer, 0, count);
}
fileStream.close(); fileStream.close();
outStream.close(); outStream.close();
@ -132,77 +239,125 @@ public final class Distribution {
} }
/** /**
* Returns parsed contents of bookmarks.json. * After calling this method, either <code>distributionDir</code>
* This method should only be called from a background thread. * will be set, or there is no distribution in use.
*
* Only call after init.
*/ */
public static JSONArray getBookmarks(Context context) { private File ensureDistributionDir() {
SharedPreferences settings = context.getSharedPreferences(GeckoApp.PREFS_NAME, Activity.MODE_PRIVATE); if (this.distributionDir != null) {
String keyName = context.getPackageName() + ".distribution_state"; return this.distributionDir;
int state = settings.getInt(keyName, STATE_UNKNOWN); }
if (state == STATE_NONE) {
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; return null;
} }
ZipFile zip = null;
InputStream inputStream = null;
try { try {
if (state == STATE_UNKNOWN) { JSONObject all = new JSONObject(getFileContents(descFile));
// 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");
}
File file = new File(distDir, "bookmarks.json"); if (!all.has("Global")) {
inputStream = new FileInputStream(file); Log.e(LOGTAG, "Distribution preferences.json has no Global entry!");
return null;
} }
// Convert input stream to JSONArray return new DistributionDescriptor(all.getJSONObject("Global"));
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder stringBuilder = new StringBuilder(); } catch (IOException e) {
String s; Log.e(LOGTAG, "Error getting distribution descriptor file.", e);
while ((s = reader.readLine()) != null) { return null;
stringBuilder.append(s); } catch (JSONException e) {
} Log.e(LOGTAG, "Error parsing preferences.json", e);
return new JSONArray(stringBuilder.toString()); 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) { } catch (IOException e) {
Log.e(LOGTAG, "Error getting bookmarks", e); Log.e(LOGTAG, "Error getting bookmarks", e);
} catch (JSONException e) { } catch (JSONException e) {
Log.e(LOGTAG, "Error parsing bookmarks.json", 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; 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.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -1291,7 +1292,16 @@ abstract public class GeckoApp
final String profilePath = getProfile().getDir().getAbsolutePath(); final String profilePath = getProfile().getDir().getAbsolutePath();
final EventDispatcher dispatcher = GeckoAppShell.getEventDispatcher(); final EventDispatcher dispatcher = GeckoAppShell.getEventDispatcher();
Log.i(LOGTAG, "Creating BrowserHealthRecorder."); 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); previousSession);
} }
}); });
@ -1555,8 +1565,15 @@ abstract public class GeckoApp
GeckoPreferences.broadcastHealthReportUploadPref(context); GeckoPreferences.broadcastHealthReportUploadPref(context);
/* /*
XXXX see bug 635342 XXXX see Bug 635342.
We want to disable this code if possible. It is about 145ms in runtime 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); SharedPreferences settings = getPreferences(Activity.MODE_PRIVATE);
String localeCode = settings.getString(getPackageName() + ".locale", ""); String localeCode = settings.getString(getPackageName() + ".locale", "");
if (localeCode != null && localeCode.length() > 0) 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. Gecko resets the locale to en-US by calling this function with an empty string.
This affects GeckoPreferences activity in multi-locale builds. 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) //We're not using this, not need to save it (see bug 635342)
SharedPreferences settings = SharedPreferences settings =
getContext().getPreferences(Activity.MODE_PRIVATE); getContext().getPreferences(Activity.MODE_PRIVATE);

Просмотреть файл

@ -313,8 +313,13 @@ public final class GeckoProfile {
} }
public synchronized File getDir() { public synchronized File getDir() {
forceCreate();
return mDir;
}
public synchronized GeckoProfile forceCreate() {
if (mDir != null) { if (mDir != null) {
return mDir; return this;
} }
try { try {
@ -330,7 +335,7 @@ public final class GeckoProfile {
} catch (IOException ioe) { } catch (IOException ioe) {
Log.e(LOGTAG, "Error getting profile dir", ioe); Log.e(LOGTAG, "Error getting profile dir", ioe);
} }
return mDir; return this;
} }
public File getFile(String aFile) { public File getFile(String aFile) {

Просмотреть файл

@ -55,6 +55,7 @@ public class GeckoView extends LayerView
Clipboard.init(context); Clipboard.init(context);
HardwareUtils.init(context); HardwareUtils.init(context);
GeckoNetworkManager.getInstance().init(context);
GeckoLoader.loadMozGlue(); GeckoLoader.loadMozGlue();
BrowserDB.setEnableContentProviders(false); BrowserDB.setEnableContentProviders(false);
@ -75,7 +76,7 @@ public class GeckoView extends LayerView
ThreadUtils.setUiThread(Thread.currentThread(), new Handler()); ThreadUtils.setUiThread(Thread.currentThread(), new Handler());
initializeView(GeckoAppShell.getEventDispatcher()); initializeView(GeckoAppShell.getEventDispatcher());
GeckoProfile profile = GeckoProfile.get(context); GeckoProfile profile = GeckoProfile.get(context).forceCreate();
BrowserDB.initialize(profile.getName()); BrowserDB.initialize(profile.getName());
if (GeckoThread.checkAndSetLaunchState(GeckoThread.LaunchState.Launching, GeckoThread.LaunchState.Launched)) { 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 ICON_PATH_HDPI = $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/content/icon64.png
endif 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 JAVA_CLASSPATH = $(ANDROID_SDK)/android.jar
ifdef MOZ_CRASHREPORTER ifdef MOZ_CRASHREPORTER
FENNEC_JAVA_FILES += CrashReporter.java FENNEC_JAVA_FILES += CrashReporter.java
RES_DRAWABLE_MDPI += res/drawable-mdpi/crash_reporter.png
RES_LAYOUT += res/layout/crash_reporter.xml
endif 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 = \ ALL_JARS = \
jars/gecko-browser.jar \ jars/gecko-browser.jar \
jars/gecko-mozglue.jar \ jars/gecko-mozglue.jar \
@ -1280,6 +503,10 @@ endif
include $(topsrcdir)/config/makefiles/java-build.mk 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 include $(topsrcdir)/config/rules.mk
# Override the Java settings with some specific android settings # 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 $(NSINSTALL) -D res/drawable-xxhdpi
cp $(ICON_PATH_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) $(RM) -r $(@D)
$(NSINSTALL) -D $(@D) $(NSINSTALL) -D $(@D)
$(TOUCH) $@ $(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 $@" @echo "creating $@"
$(NSINSTALL) $(subst res/,$(srcdir)/resources/,$@) $(dir $@) $(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. # rebuild gecko.ap_ if any of them change.
MULTILOCALE_STRINGS_XML_FILES := $(wildcard res/values-*/strings.xml) MULTILOCALE_STRINGS_XML_FILES := $(wildcard res/values-*/strings.xml)
all_resources = \ 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) \ $(MULTILOCALE_STRINGS_XML_FILES) \
AndroidManifest.xml \ AndroidManifest.xml \
$(RESOURCES) \ $(subst resources/,res/,$(ANDROID_RESFILES)) \
$(ANDROID_GENERATED_RESFILES) \
$(NULL) $(NULL)
R.java: $(all_resources) R.java: $(all_resources)

Просмотреть файл

@ -40,6 +40,7 @@ SYNC_JAVA_FILES := \
background/db/Tab.java \ background/db/Tab.java \
background/healthreport/Environment.java \ background/healthreport/Environment.java \
background/healthreport/EnvironmentBuilder.java \ background/healthreport/EnvironmentBuilder.java \
background/healthreport/EnvironmentV1.java \
background/healthreport/HealthReportBroadcastReceiver.java \ background/healthreport/HealthReportBroadcastReceiver.java \
background/healthreport/HealthReportBroadcastService.java \ background/healthreport/HealthReportBroadcastService.java \
background/healthreport/HealthReportDatabases.java \ background/healthreport/HealthReportDatabases.java \
@ -301,53 +302,6 @@ SYNC_JAVA_FILES := \
sync/Utils.java \ sync/Utils.java \
$(NULL) $(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 := \ SYNC_THIRDPARTY_JAVA_FILES := \
httpclientandroidlib/androidextra/HttpClientAndroidLog.java \ httpclientandroidlib/androidextra/HttpClientAndroidLog.java \
httpclientandroidlib/annotation/GuardedBy.java \ httpclientandroidlib/annotation/GuardedBy.java \

Просмотреть файл

@ -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; 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. * 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 * 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. * registered an <code>Environment</code>, don't do so again; start from scratch.
* *
*/ */
public abstract class Environment { public abstract class Environment extends EnvironmentV1 {
private static final String LOG_TAG = "GeckoEnvironment"; // Version 2 adds osLocale, appLocale, acceptLangSet, and distribution.
public static final int CURRENT_VERSION = 2;
public static int VERSION = 1; public String osLocale; // The Android OS "Locale" value.
public String appLocale;
protected final Class<? extends EnvironmentAppender> appenderClass; public int acceptLangSet;
public String distribution; // ID + version. Typically empty.
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 Environment() { public Environment() {
this(Environment.HashAppender.class); this(Environment.HashAppender.class);
} }
public Environment(Class<? extends EnvironmentAppender> appenderClass) { public Environment(Class<? extends EnvironmentAppender> appenderClass) {
this.appenderClass = appenderClass; super(appenderClass);
version = CURRENT_VERSION;
} }
public JSONObject getNonIgnoredAddons() { @Override
if (addons == null) { protected void appendHash(EnvironmentAppender appender) {
return null; super.appendHash(appender);
}
JSONObject out = new JSONObject(); // v2.
@SuppressWarnings("unchecked") appender.append(osLocale);
Iterator<String> keys = addons.keys(); appender.append(appLocale);
while (keys.hasNext()) { appender.append(acceptLangSet);
try { appender.append(distribution);
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;
} }
/**
* 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 static interface ProfileInformationProvider {
public boolean isBlocklistEnabled(); public boolean isBlocklistEnabled();
public boolean isTelemetryEnabled(); public boolean isTelemetryEnabled();
public boolean isAcceptLangUserSet();
public long getProfileCreationTime(); public long getProfileCreationTime();
public String getDistributionString();
public String getOSLocale();
public String getAppLocale();
public JSONObject getAddonsJSON(); public JSONObject getAddonsJSON();
} }
@ -124,6 +130,12 @@ public class EnvironmentBuilder {
} }
e.addons = addons; e.addons = addons;
// v2 environment fields.
e.distribution = info.getDistributionString();
e.osLocale = info.getOSLocale();
e.appLocale = info.getAppLocale();
e.acceptLangSet = info.isAcceptLangUserSet() ? 1 : 0;
} }
/** /**

Просмотреть файл

@ -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[] { private static final String[] COLUMNS_ENVIRONMENT_DETAILS = new String[] {
"id", "hash", "id", "version", "hash",
"profileCreation", "cpuCount", "memoryMB", "profileCreation", "cpuCount", "memoryMB",
"isBlocklistEnabled", "isTelemetryEnabled", "extensionCount", "isBlocklistEnabled", "isTelemetryEnabled", "extensionCount",
@ -138,6 +138,8 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
"appVersion", "appBuildID", "platformVersion", "platformBuildID", "os", "appVersion", "appBuildID", "platformVersion", "platformBuildID", "os",
"xpcomabi", "updateChannel", "xpcomabi", "updateChannel",
"distribution", "osLocale", "appLocale", "acceptLangSet",
// Joined to the add-ons table. // Joined to the add-ons table.
"addonsBody" "addonsBody"
}; };
@ -188,7 +190,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
protected final HealthReportSQLiteOpenHelper helper; protected final HealthReportSQLiteOpenHelper helper;
public static class HealthReportSQLiteOpenHelper extends SQLiteOpenHelper { 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"; public static final String LOG_TAG = "HealthReportSQL";
/** /**
@ -252,7 +254,10 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
" UNIQUE (body) " + " 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, " + db.execSQL("CREATE TABLE environments (id INTEGER PRIMARY KEY AUTOINCREMENT, " +
" version INTEGER, " +
" hash TEXT, " + " hash TEXT, " +
" profileCreation INTEGER, " + " profileCreation INTEGER, " +
" cpuCount INTEGER, " + " cpuCount INTEGER, " +
@ -275,6 +280,12 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
" os TEXT, " + " os TEXT, " +
" xpcomabi TEXT, " + " xpcomabi TEXT, " +
" updateChannel TEXT, " + " updateChannel TEXT, " +
" distribution TEXT, " +
" osLocale TEXT, " +
" appLocale TEXT, " +
" acceptLangSet INTEGER, " +
" addonsID INTEGER, " + " addonsID INTEGER, " +
" FOREIGN KEY (addonsID) REFERENCES addons(id) ON DELETE RESTRICT, " + " FOREIGN KEY (addonsID) REFERENCES addons(id) ON DELETE RESTRICT, " +
" UNIQUE (hash) " + " UNIQUE (hash) " +
@ -357,6 +368,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
private void createAddonsEnvironmentsView(SQLiteDatabase db) { private void createAddonsEnvironmentsView(SQLiteDatabase db) {
db.execSQL("CREATE VIEW environments_with_addons AS " + db.execSQL("CREATE VIEW environments_with_addons AS " +
"SELECT e.id AS id, " + "SELECT e.id AS id, " +
" e.version AS version, " +
" e.hash AS hash, " + " e.hash AS hash, " +
" e.profileCreation AS profileCreation, " + " e.profileCreation AS profileCreation, " +
" e.cpuCount AS cpuCount, " + " e.cpuCount AS cpuCount, " +
@ -379,6 +391,10 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
" e.os AS os, " + " e.os AS os, " +
" e.xpcomabi AS xpcomabi, " + " e.xpcomabi AS xpcomabi, " +
" e.updateChannel AS updateChannel, " + " e.updateChannel AS updateChannel, " +
" e.distribution AS distribution, " +
" e.osLocale AS osLocale, " +
" e.appLocale AS appLocale, " +
" e.acceptLangSet AS acceptLangSet, " +
" addons.body AS addonsBody " + " addons.body AS addonsBody " +
"FROM environments AS e, addons " + "FROM environments AS e, addons " +
"WHERE e.addonsID = addons.id"); "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); 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 @Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion >= newVersion) { if (oldVersion >= newVersion) {
@ -432,6 +464,8 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
upgradeDatabaseFrom3To4(db); upgradeDatabaseFrom3To4(db);
case 4: case 4:
upgradeDatabaseFrom4to5(db); upgradeDatabaseFrom4to5(db);
case 5:
upgradeDatabaseFrom5to6(db);
} }
} catch (Exception e) { } catch (Exception e) {
Logger.error(LOG_TAG, "Failure in onUpgrade.", 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. // Otherwise, add data and hash to the DB.
ContentValues v = new ContentValues(); ContentValues v = new ContentValues();
v.put("version", version);
v.put("hash", h); v.put("hash", h);
v.put("profileCreation", profileCreation); v.put("profileCreation", profileCreation);
v.put("cpuCount", cpuCount); v.put("cpuCount", cpuCount);
@ -558,6 +593,10 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
v.put("os", os); v.put("os", os);
v.put("xpcomabi", xpcomabi); v.put("xpcomabi", xpcomabi);
v.put("updateChannel", updateChannel); 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(); final SQLiteDatabase db = storage.helper.getWritableDatabase();
@ -643,6 +682,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
} }
public void init(ContentValues v) { public void init(ContentValues v) {
version = v.containsKey("version") ? v.getAsInteger("version") : Environment.CURRENT_VERSION;
profileCreation = v.getAsInteger("profileCreation"); profileCreation = v.getAsInteger("profileCreation");
cpuCount = v.getAsInteger("cpuCount"); cpuCount = v.getAsInteger("cpuCount");
memoryMB = v.getAsInteger("memoryMB"); memoryMB = v.getAsInteger("memoryMB");
@ -667,6 +707,11 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
xpcomabi = v.getAsString("xpcomabi"); xpcomabi = v.getAsString("xpcomabi");
updateChannel = v.getAsString("updateChannel"); updateChannel = v.getAsString("updateChannel");
distribution = v.getAsString("distribution");
osLocale = v.getAsString("osLocale");
appLocale = v.getAsString("appLocale");
acceptLangSet = v.getAsInteger("acceptLangSet");
try { try {
setJSONForAddons(v.getAsString("addonsBody")); setJSONForAddons(v.getAsString("addonsBody"));
} catch (Exception e) { } catch (Exception e) {
@ -686,6 +731,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
public boolean init(Cursor cursor) { public boolean init(Cursor cursor) {
int i = 0; int i = 0;
this.id = cursor.getInt(i++); this.id = cursor.getInt(i++);
this.version = cursor.getInt(i++);
this.hash = cursor.getString(i++); this.hash = cursor.getString(i++);
profileCreation = cursor.getInt(i++); profileCreation = cursor.getInt(i++);
@ -712,6 +758,11 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
xpcomabi = cursor.getString(i++); xpcomabi = cursor.getString(i++);
updateChannel = cursor.getString(i++); updateChannel = cursor.getString(i++);
distribution = cursor.getString(i++);
osLocale = cursor.getString(i++);
appLocale = cursor.getString(i++);
acceptLangSet = cursor.getInt(i++);
try { try {
setJSONForAddons(cursor.getBlob(i++)); setJSONForAddons(cursor.getBlob(i++));
} catch (Exception e) { } catch (Exception e) {
@ -1339,6 +1390,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
} }
// Called internally only to ensure the same db instance is used. // Called internally only to ensure the same db instance is used.
@SuppressWarnings("static-method")
protected int deleteOrphanedEnv(final SQLiteDatabase db, final int curEnv) { protected int deleteOrphanedEnv(final SQLiteDatabase db, final int curEnv) {
final String whereClause = final String whereClause =
"id != ? AND " + "id != ? AND " +
@ -1353,6 +1405,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
} }
// Called internally only to ensure the same db instance is used. // Called internally only to ensure the same db instance is used.
@SuppressWarnings("static-method")
protected int deleteEventsBefore(final SQLiteDatabase db, final String dayString) { protected int deleteEventsBefore(final SQLiteDatabase db, final String dayString) {
final String whereClause = "date < ?"; final String whereClause = "date < ?";
final String[] whereArgs = new String[] {dayString}; 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. // Called internally only to ensure the same db instance is used.
@SuppressWarnings("static-method")
protected int deleteOrphanedAddons(final SQLiteDatabase db) { protected int deleteOrphanedAddons(final SQLiteDatabase db) {
final String whereClause = "id NOT IN (SELECT addonsID FROM environments)"; final String whereClause = "id NOT IN (SELECT addonsID FROM environments)";
return db.delete("addons", whereClause, null); return db.delete("addons", whereClause, null);

Просмотреть файл

@ -388,24 +388,117 @@ public class HealthReportGenerator {
return gecko; 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 { private static JSONObject getAppInfo(Environment e, Environment current) throws JSONException {
JSONObject appinfo = new JSONObject(); JSONObject appinfo = new JSONObject();
int changes = 0;
if (current == null || current.isBlocklistEnabled != e.isBlocklistEnabled) { Logger.debug(LOG_TAG, "Generating appinfo for v" + e.version + " env " + e.hash);
appinfo.put("isBlocklistEnabled", e.isBlocklistEnabled);
changes++; // 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); switch (e.version) {
changes++; 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; return null;
} }
appinfo.put("_v", 2);
return appinfo; 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 { private static JSONObject getAddonCounts(Environment e, Environment current) throws JSONException {
JSONObject counts = new JSONObject(); JSONObject counts = new JSONObject();
int changes = 0; int changes = 0;

Просмотреть файл

@ -10,6 +10,7 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.Locale;
import java.util.Scanner; import java.util.Scanner;
import org.json.JSONException; import org.json.JSONException;
@ -32,8 +33,9 @@ public class ProfileInformationCache implements ProfileInformationProvider {
* -: No version number; implicit v1. * -: No version number; implicit v1.
* 1: Add versioning (Bug 878670). * 1: Add versioning (Bug 878670).
* 2: Bump to regenerate add-on set after landing Bug 900694 (Bug 901622). * 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 initialized = false;
protected boolean needsWrite = false; protected boolean needsWrite = false;
@ -42,7 +44,29 @@ public class ProfileInformationCache implements ProfileInformationProvider {
private volatile boolean blocklistEnabled = true; private volatile boolean blocklistEnabled = true;
private volatile boolean telemetryEnabled = false; private volatile boolean telemetryEnabled = false;
private volatile boolean isAcceptLangUserSet = false;
private volatile long profileCreationTime = 0; 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; private volatile JSONObject addons = null;
@ -62,7 +86,11 @@ public class ProfileInformationCache implements ProfileInformationProvider {
object.put("version", FORMAT_VERSION); object.put("version", FORMAT_VERSION);
object.put("blocklist", blocklistEnabled); object.put("blocklist", blocklistEnabled);
object.put("telemetry", telemetryEnabled); object.put("telemetry", telemetryEnabled);
object.put("isAcceptLangUserSet", isAcceptLangUserSet);
object.put("profileCreated", profileCreationTime); object.put("profileCreated", profileCreationTime);
object.put("osLocale", osLocale);
object.put("appLocale", appLocale);
object.put("distribution", distribution);
object.put("addons", addons); object.put("addons", addons);
} catch (JSONException e) { } catch (JSONException e) {
// There isn't much we can do about this. // There isn't much we can do about this.
@ -86,8 +114,12 @@ public class ProfileInformationCache implements ProfileInformationProvider {
case FORMAT_VERSION: case FORMAT_VERSION:
blocklistEnabled = object.getBoolean("blocklist"); blocklistEnabled = object.getBoolean("blocklist");
telemetryEnabled = object.getBoolean("telemetry"); telemetryEnabled = object.getBoolean("telemetry");
isAcceptLangUserSet = object.getBoolean("isAcceptLangUserSet");
profileCreationTime = object.getLong("profileCreated"); profileCreationTime = object.getLong("profileCreated");
addons = object.getJSONObject("addons"); addons = object.getJSONObject("addons");
distribution = object.getString("distribution");
osLocale = object.getString("osLocale");
appLocale = object.getString("appLocale");
return true; return true;
default: default:
Logger.warn(LOG_TAG, "Unable to restore from version " + version + " PIC file: expecting " + FORMAT_VERSION); 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; 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 @Override
public long getProfileCreationTime() { public long getProfileCreationTime() {
ensureInitialized(); ensureInitialized();
@ -218,17 +262,83 @@ public class ProfileInformationCache implements ProfileInformationProvider {
needsWrite = true; 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 @Override
public JSONObject getAddonsJSON() { public JSONObject getAddonsJSON() {
ensureInitialized();
return addons; return addons;
} }
public void updateJSONForAddon(String id, String json) throws Exception { public void updateJSONForAddon(String id, String json) throws Exception {
addons.put(id, new JSONObject(json)); addons.put(id, new JSONObject(json));
needsWrite = true;
} }
public void removeAddon(String id) { 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 { try {
addons.put(id, json); addons.put(id, json);
needsWrite = true;
} catch (Exception e) { } catch (Exception e) {
// Why would this happen? // Why would this happen?
Logger.warn(LOG_TAG, "Unexpected failure updating JSON for add-on.", e); 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 { public void setJSONForAddons(String json) throws Exception {
addons = new JSONObject(json); addons = new JSONObject(json);
needsWrite = true;
} }
public void setJSONForAddons(JSONObject json) { public void setJSONForAddons(JSONObject json) {
addons = json; addons = json;
needsWrite = true;
} }
} }

Просмотреть файл

@ -13,11 +13,11 @@ import android.content.SharedPreferences;
import android.util.Log; import android.util.Log;
import org.mozilla.gecko.AppConstants; 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.GeckoApp;
import org.mozilla.gecko.GeckoAppShell; import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent; 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.EnvironmentBuilder;
import org.mozilla.gecko.background.healthreport.HealthReportDatabaseStorage; import org.mozilla.gecko.background.healthreport.HealthReportDatabaseStorage;
@ -38,6 +38,7 @@ import java.io.FileOutputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import java.util.Scanner; import java.util.Scanner;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@ -50,8 +51,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
* Keep an instance of this class around. * Keep an instance of this class around.
* *
* Tell it when an environment attribute has changed: call {@link * Tell it when an environment attribute has changed: call {@link
* #onBlocklistPrefChanged(boolean)} or {@link * #onAppLocaleChanged(String)} followed by {@link
* #onTelemetryPrefChanged(boolean)}, followed by {@link
* #onEnvironmentChanged()}. * #onEnvironmentChanged()}.
* *
* Use it to record events: {@link #recordSearch(String, String)}. * Use it to record events: {@link #recordSearch(String, String)}.
@ -60,8 +60,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
*/ */
public class BrowserHealthRecorder implements GeckoEventListener { public class BrowserHealthRecorder implements GeckoEventListener {
private static final String LOG_TAG = "GeckoHealthRec"; 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 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_CHANGE = "Addons:Change";
private static final String EVENT_ADDONS_UNINSTALLING = "Addons:Uninstalling"; private static final String EVENT_ADDONS_UNINSTALLING = "Addons:Uninstalling";
private static final String EVENT_PREF_CHANGE = "Pref:Change"; 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. * 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); Log.d(LOG_TAG, "Initializing. Dispatcher is " + dispatcher);
this.dispatcher = dispatcher; this.dispatcher = dispatcher;
this.previousSession = previousSession; this.previousSession = previousSession;
@ -263,9 +271,12 @@ public class BrowserHealthRecorder implements GeckoEventListener {
this.client = null; 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); this.profileCache = new ProfileInformationCache(profilePath);
try { try {
this.initialize(context, profilePath); this.initialize(context, profilePath, osLocale, appLocale);
} catch (Exception e) { } catch (Exception e) {
Log.e(LOG_TAG, "Exception initializing.", e); Log.e(LOG_TAG, "Exception initializing.", e);
} }
@ -299,7 +310,7 @@ public class BrowserHealthRecorder implements GeckoEventListener {
} }
private void unregisterEventListeners() { 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_CHANGE, this);
this.dispatcher.unregisterEventListener(EVENT_ADDONS_UNINSTALLING, this); this.dispatcher.unregisterEventListener(EVENT_ADDONS_UNINSTALLING, this);
this.dispatcher.unregisterEventListener(EVENT_PREF_CHANGE, this); this.dispatcher.unregisterEventListener(EVENT_PREF_CHANGE, this);
@ -307,14 +318,9 @@ public class BrowserHealthRecorder implements GeckoEventListener {
this.dispatcher.unregisterEventListener(EVENT_SEARCH, this); this.dispatcher.unregisterEventListener(EVENT_SEARCH, this);
} }
public void onBlocklistPrefChanged(boolean to) { public void onAppLocaleChanged(String to) {
this.profileCache.beginInitialization(); this.profileCache.beginInitialization();
this.profileCache.setBlocklistEnabled(to); this.profileCache.setAppLocale(to);
}
public void onTelemetryPrefChanged(boolean to) {
this.profileCache.beginInitialization();
this.profileCache.setTelemetryEnabled(to);
} }
public void onAddonChanged(String id, JSONObject json) { 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 * environment, such that a new environment should be computed and prepared
* for use in future events. * for use in future events.
* *
* Invoke this method after calls that mutate the environment, such as * Invoke this method after calls that mutate the environment.
* {@link #onBlocklistPrefChanged(boolean)}.
* *
* If this change resulted in a transition between two environments, {@link * If this change resulted in a transition between two environments, {@link
* #onEnvironmentTransition(int, int)} will be invoked on the background * #onEnvironmentTransition(int, int)} will be invoked on the background
@ -491,14 +496,36 @@ public class BrowserHealthRecorder implements GeckoEventListener {
return time; return time;
} }
private void handlePrefValue(final String pref, final boolean value) { private void onPrefMessage(final String pref, final JSONObject message) {
Log.d(LOG_TAG, "Incorporating environment: " + pref + " = " + value); Log.d(LOG_TAG, "Incorporating environment: " + pref);
if (AppConstants.TELEMETRY_PREF_NAME.equals(pref)) { if (PREF_ACCEPT_LANG.equals(pref)) {
profileCache.setTelemetryEnabled(value); // 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; 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; return;
} }
Log.w(LOG_TAG, "Unexpected pref: " + pref); Log.w(LOG_TAG, "Unexpected pref: " + pref);
@ -571,7 +598,9 @@ public class BrowserHealthRecorder implements GeckoEventListener {
* Add provider-specific initialization in this method. * Add provider-specific initialization in this method.
*/ */
private synchronized void initialize(final Context context, private synchronized void initialize(final Context context,
final String profilePath) final String profilePath,
final String osLocale,
final String appLocale)
throws java.io.IOException { throws java.io.IOException {
Log.d(LOG_TAG, "Initializing profile cache."); 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 we can restore state from last time, great.
if (this.profileCache.restoreUnlessInitialized()) { if (this.profileCache.restoreUnlessInitialized()) {
this.profileCache.updateLocales(osLocale, appLocale);
this.profileCache.completeInitialization();
Log.d(LOG_TAG, "Successfully restored state. Initializing storage."); Log.d(LOG_TAG, "Successfully restored state. Initializing storage.");
initializeStorage(); initializeStorage();
return; return;
@ -587,31 +619,24 @@ public class BrowserHealthRecorder implements GeckoEventListener {
// Otherwise, let's initialize it from scratch. // Otherwise, let's initialize it from scratch.
this.profileCache.beginInitialization(); this.profileCache.beginInitialization();
this.profileCache.setProfileCreationTime(getAndPersistProfileInitTime(context, profilePath)); this.profileCache.setProfileCreationTime(getAndPersistProfileInitTime(context, profilePath));
this.profileCache.setOSLocale(osLocale);
this.profileCache.setAppLocale(appLocale);
final BrowserHealthRecorder self = this; // Because the distribution lookup can take some time, do it at the end of
// our background startup work, along with the Gecko snapshot fetch.
PrefHandler handler = new PrefsHelper.PrefHandlerBase() { final GeckoEventListener self = this;
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override @Override
public void prefValue(String pref, boolean value) { public void run() {
handlePrefValue(pref, value); 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 @Override
public void handleMessage(String event, JSONObject message) { public void handleMessage(String event, JSONObject message) {
try { try {
if (EVENT_ADDONS_ALL.equals(event)) { if (EVENT_SNAPSHOT.equals(event)) {
Log.d(LOG_TAG, "Got all add-ons."); Log.d(LOG_TAG, "Got all add-ons and prefs.");
try { 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."); Log.i(LOG_TAG, "Persisting " + addons.length() + " add-ons.");
profileCache.setJSONForAddons(addons); 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(); profileCache.completeInitialization();
} catch (java.io.IOException e) { } catch (java.io.IOException e) {
Log.e(LOG_TAG, "Error completing profile cache initialization.", 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)) { if (EVENT_PREF_CHANGE.equals(event)) {
final String pref = message.getString("pref"); final String pref = message.getString("pref");
Log.d(LOG_TAG, "Pref changed: " + pref); Log.d(LOG_TAG, "Pref changed: " + pref);
handlePrefValue(pref, message.getBoolean("value")); this.onPrefMessage(pref, message);
this.onEnvironmentChanged(); this.onEnvironmentChanged();
return; return;
} }

Просмотреть файл

@ -5,3 +5,609 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
DIRS += ['locales'] 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_PACKAGE_NAME@.*;
import android.app.Activity; import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
@ -38,11 +39,50 @@ public class testDistribution extends ContentProviderTest {
return TEST_MOCHITEST; 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() { public void testDistribution() {
mActivity = getActivity(); mActivity = getActivity();
String mockPackagePath = getMockPackagePath(); 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 // Pre-clear distribution pref, run basic preferences and en-US localized preferences Tests
clearDistributionPref(); clearDistributionPref();
setTestLocale("en-US"); setTestLocale("en-US");
@ -64,11 +104,10 @@ public class testDistribution extends ContentProviderTest {
// Call Distribution.init with the mock package. // Call Distribution.init with the mock package.
ClassLoader classLoader = mActivity.getClassLoader(); ClassLoader classLoader = mActivity.getClassLoader();
Class distributionClass = classLoader.loadClass("org.mozilla.gecko.Distribution"); Class distributionClass = classLoader.loadClass("org.mozilla.gecko.Distribution");
Class contextClass = classLoader.loadClass("android.content.Context"); Method init = distributionClass.getMethod("init", Context.class, String.class, String.class);
Method init = distributionClass.getMethod("init", contextClass, String.class);
Actions.EventExpecter distributionSetExpecter = mActions.expectGeckoEvent("Distribution:Set:OK"); Actions.EventExpecter distributionSetExpecter = mActions.expectGeckoEvent("Distribution:Set:OK");
init.invoke(null, mActivity, aPackagePath); init.invoke(null, mActivity, aPackagePath, "prefs-" + System.currentTimeMillis());
distributionSetExpecter.blockForEvent(); distributionSetExpecter.blockForEvent();
distributionSetExpecter.unregisterListener(); distributionSetExpecter.unregisterListener();
} catch (Exception e) { } catch (Exception e) {
@ -268,6 +307,7 @@ public class testDistribution extends ContentProviderTest {
// Clears the distribution pref to return distribution state to STATE_UNKNOWN // Clears the distribution pref to return distribution state to STATE_UNKNOWN
private void clearDistributionPref() { private void clearDistributionPref() {
mAsserter.dumpLog("Clearing distribution pref.");
SharedPreferences settings = mActivity.getSharedPreferences("GeckoApp", Activity.MODE_PRIVATE); SharedPreferences settings = mActivity.getSharedPreferences("GeckoApp", Activity.MODE_PRIVATE);
String keyName = mActivity.getPackageName() + ".distribution_state"; String keyName = mActivity.getPackageName() + ".distribution_state";
settings.edit().remove(keyName).commit(); settings.edit().remove(keyName).commit();

Просмотреть файл

@ -5316,7 +5316,10 @@ var FormAssistant = {
* -- and reflect them back to Java. * -- and reflect them back to Java.
*/ */
let HealthReportStatusListener = { let HealthReportStatusListener = {
TELEMETRY_PREF: PREF_ACCEPT_LANG: "intl.accept_languages",
PREF_BLOCKLIST_ENABLED: "extensions.blocklist.enabled",
PREF_TELEMETRY_ENABLED:
#ifdef MOZ_TELEMETRY_REPORTING #ifdef MOZ_TELEMETRY_REPORTING
// Telemetry pref differs based on build. // Telemetry pref differs based on build.
#ifdef MOZ_TELEMETRY_ON_BY_DEFAULT #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); console.log("Failed to initialize add-on status listener. FHR cannot report add-on state. " + ex);
} }
Services.obs.addObserver(this, "Addons:FetchAll", false); console.log("Adding HealthReport:RequestSnapshot observer.");
Services.prefs.addObserver("extensions.blocklist.enabled", this, false); Services.obs.addObserver(this, "HealthReport:RequestSnapshot", false);
if (this.TELEMETRY_PREF) { Services.prefs.addObserver(this.PREF_ACCEPT_LANG, this, false);
Services.prefs.addObserver(this.TELEMETRY_PREF, 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 () { uninit: function () {
Services.obs.removeObserver(this, "Addons:FetchAll"); Services.obs.removeObserver(this, "HealthReport:RequestSnapshot");
Services.prefs.removeObserver("extensions.blocklist.enabled", this); Services.prefs.removeObserver(this.PREF_ACCEPT_LANG, this);
if (this.TELEMETRY_PREF) { Services.prefs.removeObserver(this.PREF_BLOCKLIST_ENABLED, this);
Services.prefs.removeObserver(this.TELEMETRY_PREF, this); if (this.PREF_TELEMETRY_ENABLED) {
Services.prefs.removeObserver(this.PREF_TELEMETRY_ENABLED, this);
} }
AddonManager.removeAddonListener(this); AddonManager.removeAddonListener(this);
@ -5354,11 +5360,30 @@ let HealthReportStatusListener = {
observe: function (aSubject, aTopic, aData) { observe: function (aSubject, aTopic, aData) {
switch (aTopic) { switch (aTopic) {
case "Addons:FetchAll": case "HealthReport:RequestSnapshot":
HealthReportStatusListener.sendAllAddonsToJava(); HealthReportStatusListener.sendSnapshotToJava();
break; break;
case "nsPref:changed": 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; break;
} }
}, },
@ -5440,9 +5465,9 @@ let HealthReportStatusListener = {
this.notifyJava(aAddon); this.notifyJava(aAddon);
}, },
sendAllAddonsToJava: function () { sendSnapshotToJava: function () {
AddonManager.getAllAddons(function (aAddons) { AddonManager.getAllAddons(function (aAddons) {
let json = {}; let jsonA = {};
if (aAddons) { if (aAddons) {
for (let i = 0; i < aAddons.length; ++i) { for (let i = 0; i < aAddons.length; ++i) {
let addon = aAddons[i]; let addon = aAddons[i];
@ -5451,14 +5476,43 @@ let HealthReportStatusListener = {
if (HealthReportStatusListener._shouldIgnore(addon)) { if (HealthReportStatusListener._shouldIgnore(addon)) {
addonJSON.ignore = true; addonJSON.ignore = true;
} }
json[addon.id] = addonJSON; jsonA[addon.id] = addonJSON;
} catch (e) { } catch (e) {
// Just skip this add-on. // 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/db/Tab.java
background/healthreport/Environment.java background/healthreport/Environment.java
background/healthreport/EnvironmentBuilder.java background/healthreport/EnvironmentBuilder.java
background/healthreport/EnvironmentV1.java
background/healthreport/HealthReportBroadcastReceiver.java background/healthreport/HealthReportBroadcastReceiver.java
background/healthreport/HealthReportBroadcastService.java background/healthreport/HealthReportBroadcastService.java
background/healthreport/HealthReportDatabases.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. # BACKGROUND_TESTS_{JAVA,RES}_FILES are defined in android-services-files.mk.
JAVAFILES := $(BACKGROUND_TESTS_JAVA_FILES) 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 # 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 # being linked against them. This is a best effort to avoid getting

Просмотреть файл

@ -100,11 +100,3 @@ BACKGROUND_TESTS_JAVA_FILES := \
src/testhelpers/WBORepository.java \ src/testhelpers/WBORepository.java \
$(NULL) $(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 # 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 # 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/. # 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; profileCreation = 1234;
cpuCount = 2; cpuCount = 2;
memoryMB = 512; memoryMB = 512;
@ -55,7 +55,7 @@ public class MockDatabaseEnvironment extends DatabaseEnvironment {
vendor = ""; vendor = "";
appName = ""; appName = "";
appID = ""; appID = "";
appVersion = version; this.appVersion = appVersion;
appBuildID = ""; appBuildID = "";
platformVersion = ""; platformVersion = "";
platformBuildID = ""; platformBuildID = "";
@ -63,6 +63,14 @@ public class MockDatabaseEnvironment extends DatabaseEnvironment {
xpcomabi = ""; xpcomabi = "";
updateChannel = ""; updateChannel = "";
// v2 fields.
distribution = "";
appLocale = "";
osLocale = "";
acceptLangSet = 0;
version = Environment.CURRENT_VERSION;
return this; 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.healthreport.HealthReportStorage.MeasurementFields;
import org.mozilla.gecko.background.helpers.FakeProfileTestCase; 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 { public class TestHealthReportGenerator extends FakeProfileTestCase {
@SuppressWarnings("static-method") @SuppressWarnings("static-method")
public void testOptObject() throws JSONException { public void testOptObject() throws JSONException {
@ -57,9 +61,14 @@ public class TestHealthReportGenerator extends FakeProfileTestCase {
assertFalse(bar.has("b")); 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" private static final String EXPECTED_MOCK_BASE_HASH = "000nullnullnullnullnullnullnull"
+ "nullnullnullnullnullnull00000"; + "nullnullnullnullnullnull00000";
// v2 fields.
private static final String EXPECTED_MOCK_BASE_HASH_SUFFIX = "null" + "null" + 0 + "null";
public void testHashing() throws JSONException { public void testHashing() throws JSONException {
MockHealthReportDatabaseStorage storage = new MockHealthReportDatabaseStorage(context, fakeProfileDirectory); MockHealthReportDatabaseStorage storage = new MockHealthReportDatabaseStorage(context, fakeProfileDirectory);
MockDatabaseEnvironment env = new MockDatabaseEnvironment(storage, MockDatabaseEnvironment.MockEnvironmentAppender.class); MockDatabaseEnvironment env = new MockDatabaseEnvironment(storage, MockDatabaseEnvironment.MockEnvironmentAppender.class);
@ -96,10 +105,10 @@ public class TestHealthReportGenerator extends FakeProfileTestCase {
"}"); "}");
env.addons.put("{addonA}", addonA1); 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); 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 { private void assertJSONDiff(JSONObject source, JSONObject diff) throws JSONException {
@ -406,4 +415,103 @@ public class TestHealthReportGenerator extends FakeProfileTestCase {
protected String getCacheSuffix() { protected String getCacheSuffix() {
return File.separator + "health-" + System.currentTimeMillis() + ".profile"; 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); Cursor envCursor = resolver.query(envURI, null, null, null, null);
try { try {
assertTrue(envCursor.moveToFirst()); assertTrue(envCursor.moveToFirst());
envHash = envCursor.getString(1); envHash = envCursor.getString(2); // id, version, hash, ...
} finally { } finally {
envCursor.close(); envCursor.close();
} }
@ -249,6 +249,13 @@ public class TestHealthReportProvider extends DBProviderTestCase<HealthReportPro
v.put("os", ""); v.put("os", "");
v.put("xpcomabi", ""); v.put("xpcomabi", "");
v.put("updateChannel", ""); v.put("updateChannel", "");
// v2.
v.put("distribution", "");
v.put("osLocale", "en_us");
v.put("appLocale", "en_us");
v.put("acceptLangSet", 0);
return v; return v;
} }
} }

Просмотреть файл

@ -139,6 +139,8 @@ class TreeMetadataEmitter(LoggingMixin):
passthru = VariablePassthru(sandbox) passthru = VariablePassthru(sandbox)
varmap = dict( varmap = dict(
# Makefile.in : moz.build # Makefile.in : moz.build
ANDROID_GENERATED_RESFILES='ANDROID_GENERATED_RESFILES',
ANDROID_RESFILES='ANDROID_RESFILES',
ASFILES='ASFILES', ASFILES='ASFILES',
CMMSRCS='CMMSRCS', CMMSRCS='CMMSRCS',
CPPSRCS='CPP_SOURCES', CPPSRCS='CPP_SOURCES',

Просмотреть файл

@ -43,6 +43,22 @@ from mozbuild.util import (
VARIABLES = { VARIABLES = {
# Variables controlling reading of other frontend files. # 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, [], 'ASFILES': (StrictOrderingOnAppendList, list, [],
"""Assembly file sources. """Assembly file sources.