Merge m-c to inbound, a=merge
--HG-- rename : testing/eslint-plugin-mozilla/lib/rules/.eslintrc => testing/eslint/eslint-plugin-mozilla/lib/rules/.eslintrc
4
.gitignore
vendored
@ -100,8 +100,8 @@ testing/mozharness/logs/
|
||||
testing/mozharness/.coverage
|
||||
testing/mozharness/nosetests.xml
|
||||
|
||||
# Ignore node_modules from eslint-plugin-mozilla
|
||||
testing/eslint-plugin-mozilla/node_modules/
|
||||
# Ignore node_modules
|
||||
testing/eslint/node_modules/
|
||||
|
||||
# Ignore talos virtualenv and tp5n files.
|
||||
# The tp5n set is supposed to be decompressed at
|
||||
|
@ -116,8 +116,8 @@ GPATH
|
||||
# Ignore tox generated dir
|
||||
.tox/
|
||||
|
||||
# Ignore node_modules from eslint-plugin-mozilla
|
||||
^testing/eslint-plugin-mozilla/node_modules/
|
||||
# Ignore node_modules
|
||||
^testing/eslint/node_modules/
|
||||
|
||||
# Ignore talos virtualenv and tp5n files.
|
||||
# The tp5n set is supposed to be decompressed at
|
||||
|
@ -246,6 +246,8 @@ var AboutReaderListener = {
|
||||
|
||||
_articlePromise: null,
|
||||
|
||||
_isLeavingReaderMode: false,
|
||||
|
||||
init: function() {
|
||||
addEventListener("AboutReaderContentLoaded", this, false, true);
|
||||
addEventListener("DOMContentLoaded", this, false);
|
||||
@ -263,6 +265,7 @@ var AboutReaderListener = {
|
||||
this._articlePromise = ReaderMode.parseDocument(content.document).catch(Cu.reportError);
|
||||
ReaderMode.enterReaderMode(docShell, content);
|
||||
} else {
|
||||
this._isLeavingReaderMode = true;
|
||||
ReaderMode.leaveReaderMode(docShell, content);
|
||||
}
|
||||
break;
|
||||
@ -301,7 +304,13 @@ var AboutReaderListener = {
|
||||
|
||||
case "pagehide":
|
||||
this.cancelPotentialPendingReadabilityCheck();
|
||||
sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: false });
|
||||
// this._isLeavingReaderMode is used here to keep the Reader Mode icon
|
||||
// visible in the location bar when transitioning from reader-mode page
|
||||
// back to the source page.
|
||||
sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: this._isLeavingReaderMode });
|
||||
if (this._isLeavingReaderMode) {
|
||||
this._isLeavingReaderMode = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case "pageshow":
|
||||
|
@ -505,6 +505,7 @@ tags = mcb
|
||||
[browser_readerMode.js]
|
||||
support-files =
|
||||
readerModeArticle.html
|
||||
readerModeArticleHiddenNodes.html
|
||||
[browser_readerMode_hidden_nodes.js]
|
||||
support-files =
|
||||
readerModeArticleHiddenNodes.html
|
||||
|
@ -101,3 +101,115 @@ add_task(function* test_getOriginalUrl() {
|
||||
is(ReaderMode.getOriginalUrl("about:reader?url=" + encodeURIComponent(badUrl)), badUrl, "Found original URL from encoded malformed URL");
|
||||
is(ReaderMode.getOriginalUrl("about:reader?url=" + badUrl), badUrl, "Found original URL from non-encoded malformed URL");
|
||||
});
|
||||
|
||||
add_task(function* test_reader_view_element_attribute_transform() {
|
||||
registerCleanupFunction(function() {
|
||||
while (gBrowser.tabs.length > 1) {
|
||||
gBrowser.removeCurrentTab();
|
||||
}
|
||||
});
|
||||
|
||||
function observeAttribute(element, attribute, triggerFn, checkFn) {
|
||||
let initValue = element.getAttribute(attribute);
|
||||
return new Promise(resolve => {
|
||||
let observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach( mu => {
|
||||
let muValue = element.getAttribute(attribute);
|
||||
if(element.getAttribute(attribute) !== mu.oldValue) {
|
||||
checkFn();
|
||||
resolve();
|
||||
observer.disconnect();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
observer.observe(element, {
|
||||
attributes: true,
|
||||
attributeOldValue: true,
|
||||
attributeFilter: [attribute]
|
||||
});
|
||||
|
||||
triggerFn();
|
||||
});
|
||||
};
|
||||
|
||||
let command = document.getElementById("View:ReaderView");
|
||||
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||
is(command.hidden, true, "Command element should have the hidden attribute");
|
||||
|
||||
info("Navigate a reader-able page");
|
||||
let waitForPageshow = BrowserTestUtils.waitForContentEvent(tab.linkedBrowser, "pageshow");
|
||||
yield observeAttribute(command, "hidden",
|
||||
() => {
|
||||
let url = TEST_PATH + "readerModeArticle.html";
|
||||
tab.linkedBrowser.loadURI(url);
|
||||
},
|
||||
() => {
|
||||
is(command.hidden, false, "Command's hidden attribute should be false on a reader-able page");
|
||||
}
|
||||
);
|
||||
yield waitForPageshow;
|
||||
|
||||
info("Navigate a non-reader-able page");
|
||||
waitForPageshow = BrowserTestUtils.waitForContentEvent(tab.linkedBrowser, "pageshow");
|
||||
yield observeAttribute(command, "hidden",
|
||||
() => {
|
||||
let url = TEST_PATH + "readerModeArticleHiddenNodes.html";
|
||||
tab.linkedBrowser.loadURI(url);
|
||||
},
|
||||
() => {
|
||||
is(command.hidden, true, "Command's hidden attribute should be true on a non-reader-able page");
|
||||
}
|
||||
);
|
||||
yield waitForPageshow;
|
||||
|
||||
info("Navigate a reader-able page");
|
||||
waitForPageshow = BrowserTestUtils.waitForContentEvent(tab.linkedBrowser, "pageshow");
|
||||
yield observeAttribute(command, "hidden",
|
||||
() => {
|
||||
let url = TEST_PATH + "readerModeArticle.html";
|
||||
tab.linkedBrowser.loadURI(url);
|
||||
},
|
||||
() => {
|
||||
is(command.hidden, false, "Command's hidden attribute should be false on a reader-able page");
|
||||
}
|
||||
);
|
||||
yield waitForPageshow;
|
||||
|
||||
info("Enter Reader Mode");
|
||||
waitForPageshow = BrowserTestUtils.waitForContentEvent(tab.linkedBrowser, "pageshow");
|
||||
yield observeAttribute(readerButton, "readeractive",
|
||||
() => {
|
||||
readerButton.click();
|
||||
},
|
||||
() => {
|
||||
is(readerButton.getAttribute("readeractive"), "true", "readerButton's readeractive attribute should be true when entering reader mode");
|
||||
}
|
||||
);
|
||||
yield waitForPageshow;
|
||||
|
||||
info("Exit Reader Mode");
|
||||
waitForPageshow = BrowserTestUtils.waitForContentEvent(tab.linkedBrowser, "pageshow");
|
||||
yield observeAttribute(readerButton, "readeractive",
|
||||
() => {
|
||||
readerButton.click();
|
||||
},
|
||||
() => {
|
||||
is(readerButton.getAttribute("readeractive"), "", "readerButton's readeractive attribute should be empty when reader mode is exited");
|
||||
}
|
||||
);
|
||||
yield waitForPageshow;
|
||||
|
||||
info("Navigate a non-reader-able page");
|
||||
waitForPageshow = BrowserTestUtils.waitForContentEvent(tab.linkedBrowser, "pageshow");
|
||||
yield observeAttribute(command, "hidden",
|
||||
() => {
|
||||
let url = TEST_PATH + "readerModeArticleHiddenNodes.html";
|
||||
tab.linkedBrowser.loadURI(url);
|
||||
},
|
||||
() => {
|
||||
is(command.hidden, true, "Command's hidden attribute should be true on a non-reader-able page");
|
||||
}
|
||||
);
|
||||
yield waitForPageshow;
|
||||
});
|
||||
|
@ -13,6 +13,9 @@ support-files =
|
||||
file_language_fr_en.html
|
||||
file_language_ja.html
|
||||
file_dummy.html
|
||||
searchSuggestionEngine.xml
|
||||
searchSuggestionEngine.sjs
|
||||
|
||||
|
||||
[browser_ext_browserAction_context.js]
|
||||
[browser_ext_browserAction_disabled.js]
|
||||
@ -65,6 +68,7 @@ support-files =
|
||||
[browser_ext_tabs_update_url.js]
|
||||
[browser_ext_topwindowid.js]
|
||||
[browser_ext_webNavigation_getFrames.js]
|
||||
[browser_ext_webNavigation_urlbar_transitions.js]
|
||||
[browser_ext_windows.js]
|
||||
[browser_ext_windows_create.js]
|
||||
tags = fullscreen
|
||||
|
@ -0,0 +1,261 @@
|
||||
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set sts=2 sw=2 et tw=80: */
|
||||
"use strict";
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
|
||||
"resource://gre/modules/PlacesUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
|
||||
"resource://testing-common/PlacesTestUtils.jsm");
|
||||
|
||||
const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
|
||||
const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
|
||||
|
||||
function* addBookmark(bookmark) {
|
||||
if (bookmark.keyword) {
|
||||
yield PlacesUtils.keywords.insert({
|
||||
keyword: bookmark.keyword,
|
||||
url: bookmark.url,
|
||||
});
|
||||
}
|
||||
|
||||
yield PlacesUtils.bookmarks.insert({
|
||||
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
|
||||
url: bookmark.url,
|
||||
title: bookmark.title,
|
||||
});
|
||||
|
||||
registerCleanupFunction(function* () {
|
||||
yield PlacesUtils.bookmarks.eraseEverything();
|
||||
});
|
||||
}
|
||||
|
||||
function addSearchEngine(basename) {
|
||||
return new Promise((resolve, reject) => {
|
||||
info("Waiting for engine to be added: " + basename);
|
||||
let url = getRootDirectory(gTestPath) + basename;
|
||||
Services.search.addEngine(url, null, "", false, {
|
||||
onSuccess: (engine) => {
|
||||
info(`Search engine added: ${basename}`);
|
||||
registerCleanupFunction(() => Services.search.removeEngine(engine));
|
||||
resolve(engine);
|
||||
},
|
||||
onError: (errCode) => {
|
||||
ok(false, `addEngine failed with error code ${errCode}`);
|
||||
reject();
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function* prepareSearchEngine() {
|
||||
let oldCurrentEngine = Services.search.currentEngine;
|
||||
Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
|
||||
let engine = yield addSearchEngine(TEST_ENGINE_BASENAME);
|
||||
Services.search.currentEngine = engine;
|
||||
|
||||
registerCleanupFunction(function* () {
|
||||
Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF);
|
||||
Services.search.currentEngine = oldCurrentEngine;
|
||||
|
||||
// Make sure the popup is closed for the next test.
|
||||
gURLBar.blur();
|
||||
gURLBar.popup.selectedIndex = -1;
|
||||
gURLBar.popup.hidePopup();
|
||||
ok(!gURLBar.popup.popupOpen, "popup should be closed");
|
||||
|
||||
// Clicking suggestions causes visits to search results pages, so clear that
|
||||
// history now.
|
||||
yield PlacesTestUtils.clearHistory();
|
||||
});
|
||||
}
|
||||
|
||||
add_task(function* test_webnavigation_urlbar_typed_transitions() {
|
||||
function backgroundScript() {
|
||||
browser.webNavigation.onCommitted.addListener((msg) => {
|
||||
browser.test.assertEq("http://example.com/?q=typed", msg.url,
|
||||
"Got the expected url");
|
||||
// assert from_address_bar transition qualifier
|
||||
browser.test.assertTrue(msg.transitionQualifiers &&
|
||||
msg.transitionQualifiers.includes("from_address_bar"),
|
||||
"Got the expected from_address_bar transitionQualifier");
|
||||
browser.test.assertEq("typed", msg.transitionType,
|
||||
"Got the expected transitionType");
|
||||
browser.test.notifyPass("webNavigation.from_address_bar.typed");
|
||||
});
|
||||
|
||||
browser.test.sendMessage("ready");
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background: backgroundScript,
|
||||
manifest: {
|
||||
permissions: ["webNavigation"],
|
||||
},
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
|
||||
yield extension.awaitMessage("ready");
|
||||
|
||||
gURLBar.focus();
|
||||
gURLBar.textValue = "http://example.com/?q=typed";
|
||||
|
||||
EventUtils.synthesizeKey("VK_RETURN", {altKey: true});
|
||||
|
||||
yield extension.awaitFinish("webNavigation.from_address_bar.typed");
|
||||
|
||||
yield extension.unload();
|
||||
info("extension unloaded");
|
||||
});
|
||||
|
||||
add_task(function* test_webnavigation_urlbar_bookmark_transitions() {
|
||||
function backgroundScript() {
|
||||
browser.webNavigation.onCommitted.addListener((msg) => {
|
||||
browser.test.assertEq("http://example.com/?q=bookmark", msg.url,
|
||||
"Got the expected url");
|
||||
|
||||
// assert from_address_bar transition qualifier
|
||||
browser.test.assertTrue(msg.transitionQualifiers &&
|
||||
msg.transitionQualifiers.includes("from_address_bar"),
|
||||
"Got the expected from_address_bar transitionQualifier");
|
||||
browser.test.assertEq("auto_bookmark", msg.transitionType,
|
||||
"Got the expected transitionType");
|
||||
browser.test.notifyPass("webNavigation.from_address_bar.auto_bookmark");
|
||||
});
|
||||
|
||||
browser.test.sendMessage("ready");
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background: backgroundScript,
|
||||
manifest: {
|
||||
permissions: ["webNavigation"],
|
||||
},
|
||||
});
|
||||
|
||||
yield addBookmark({
|
||||
title: "Bookmark To Click",
|
||||
url: "http://example.com/?q=bookmark",
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
|
||||
yield extension.awaitMessage("ready");
|
||||
|
||||
gURLBar.focus();
|
||||
gURLBar.value = "Bookmark To Click";
|
||||
gURLBar.controller.startSearch("Bookmark To Click");
|
||||
|
||||
let item;
|
||||
|
||||
yield BrowserTestUtils.waitForCondition(() => {
|
||||
item = gURLBar.popup.richlistbox.getItemAtIndex(1);
|
||||
return item;
|
||||
});
|
||||
|
||||
item.click();
|
||||
yield extension.awaitFinish("webNavigation.from_address_bar.auto_bookmark");
|
||||
|
||||
yield extension.unload();
|
||||
info("extension unloaded");
|
||||
});
|
||||
|
||||
add_task(function* test_webnavigation_urlbar_keyword_transition() {
|
||||
function backgroundScript() {
|
||||
browser.webNavigation.onCommitted.addListener((msg) => {
|
||||
browser.test.assertEq(`http://example.com/?q=search`, msg.url,
|
||||
"Got the expected url");
|
||||
|
||||
// assert from_address_bar transition qualifier
|
||||
browser.test.assertTrue(msg.transitionQualifiers &&
|
||||
msg.transitionQualifiers.includes("from_address_bar"),
|
||||
"Got the expected from_address_bar transitionQualifier");
|
||||
browser.test.assertEq("keyword", msg.transitionType,
|
||||
"Got the expected transitionType");
|
||||
browser.test.notifyPass("webNavigation.from_address_bar.keyword");
|
||||
});
|
||||
|
||||
browser.test.sendMessage("ready");
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background: backgroundScript,
|
||||
manifest: {
|
||||
permissions: ["webNavigation"],
|
||||
},
|
||||
});
|
||||
|
||||
yield addBookmark({
|
||||
title: "Test Keyword",
|
||||
url: "http://example.com/?q=%s",
|
||||
keyword: "testkw",
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
|
||||
yield extension.awaitMessage("ready");
|
||||
|
||||
gURLBar.focus();
|
||||
gURLBar.value = "testkw search";
|
||||
gURLBar.controller.startSearch("testkw search");
|
||||
|
||||
yield BrowserTestUtils.waitForCondition(() => {
|
||||
return gURLBar.popup.input.controller.matchCount;
|
||||
});
|
||||
|
||||
let item = gURLBar.popup.richlistbox.getItemAtIndex(0);
|
||||
item.click();
|
||||
|
||||
yield extension.awaitFinish("webNavigation.from_address_bar.keyword");
|
||||
|
||||
yield extension.unload();
|
||||
info("extension unloaded");
|
||||
});
|
||||
|
||||
add_task(function* test_webnavigation_urlbar_search_transitions() {
|
||||
function backgroundScript() {
|
||||
browser.webNavigation.onCommitted.addListener((msg) => {
|
||||
browser.test.assertEq("http://mochi.test:8888/", msg.url,
|
||||
"Got the expected url");
|
||||
|
||||
// assert from_address_bar transition qualifier
|
||||
browser.test.assertTrue(msg.transitionQualifiers &&
|
||||
msg.transitionQualifiers.includes("from_address_bar"),
|
||||
"Got the expected from_address_bar transitionQualifier");
|
||||
browser.test.assertEq("generated", msg.transitionType,
|
||||
"Got the expected 'generated' transitionType");
|
||||
browser.test.notifyPass("webNavigation.from_address_bar.generated");
|
||||
});
|
||||
|
||||
browser.test.sendMessage("ready");
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background: backgroundScript,
|
||||
manifest: {
|
||||
permissions: ["webNavigation"],
|
||||
},
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
|
||||
yield extension.awaitMessage("ready");
|
||||
|
||||
yield prepareSearchEngine();
|
||||
|
||||
gURLBar.focus();
|
||||
gURLBar.value = "foo";
|
||||
gURLBar.controller.startSearch("foo");
|
||||
|
||||
yield BrowserTestUtils.waitForCondition(() => {
|
||||
return gURLBar.popup.input.controller.matchCount;
|
||||
});
|
||||
|
||||
let item = gURLBar.popup.richlistbox.getItemAtIndex(0);
|
||||
item.click();
|
||||
|
||||
yield extension.awaitFinish("webNavigation.from_address_bar.generated");
|
||||
|
||||
yield extension.unload();
|
||||
info("extension unloaded");
|
||||
});
|
@ -0,0 +1,9 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function handleRequest(req, resp) {
|
||||
let suffixes = ["foo", "bar"];
|
||||
let data = [req.queryString, suffixes.map(s => req.queryString + s)];
|
||||
resp.setHeader("Content-Type", "application/json", false);
|
||||
resp.write(JSON.stringify(data));
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
- http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
|
||||
<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
|
||||
<ShortName>browser_searchSuggestionEngine searchSuggestionEngine.xml</ShortName>
|
||||
<Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/components/extensions/test/browser/searchSuggestionEngine.sjs?{searchTerms}"/>
|
||||
<Url type="text/html" method="GET" template="http://mochi.test:8888/" rel="searchform"/>
|
||||
</SearchPlugin>
|
@ -6,7 +6,6 @@
|
||||
|
||||
Cu.import("resource:///modules/experiments/Experiments.jsm");
|
||||
Cu.import("resource://gre/modules/TelemetryController.jsm", this);
|
||||
Cu.import("resource://gre/modules/TelemetrySession.jsm", this);
|
||||
|
||||
const FILE_MANIFEST = "experiments.manifest";
|
||||
const SEC_IN_ONE_DAY = 24 * 60 * 60;
|
||||
@ -53,8 +52,7 @@ add_task(function* test_setup() {
|
||||
createAppInfo();
|
||||
gProfileDir = do_get_profile();
|
||||
startAddonManagerOnly();
|
||||
yield TelemetryController.setup();
|
||||
yield TelemetrySession.setup();
|
||||
yield TelemetryController.testSetup();
|
||||
gPolicy = new Experiments.Policy();
|
||||
|
||||
patchPolicy(gPolicy, {
|
||||
@ -329,5 +327,5 @@ add_task(function* test_times() {
|
||||
});
|
||||
|
||||
add_task(function* test_shutdown() {
|
||||
yield TelemetrySession.shutdown(false);
|
||||
yield TelemetryController.testShutdown();
|
||||
});
|
||||
|
@ -400,7 +400,7 @@ pointerLock.title3=Would you like to allow the pointer to be hidden on this site
|
||||
pointerLock.autoLock.title3=This site will hide the pointer.
|
||||
|
||||
# Phishing/Malware Notification Bar.
|
||||
# LOCALIZATION NOTE (notAForgery, notAnAttack)
|
||||
# LOCALIZATION NOTE (notADeceptiveSite, notAnAttack)
|
||||
# The two button strings will never be shown at the same time, so
|
||||
# it's okay for them to have the same access key
|
||||
safebrowsing.getMeOutOfHereButton.label=Get me out of here!
|
||||
|
@ -22,20 +22,65 @@ function promiseAddonEvent(event) {
|
||||
});
|
||||
}
|
||||
|
||||
add_task(function* () {
|
||||
function getReloadButton(document, addonName) {
|
||||
const names = [...document.querySelectorAll("#addons .target-name")];
|
||||
const name = names.filter(element => element.textContent === addonName)[0];
|
||||
ok(name, `Found ${addonName} add-on in the list`);
|
||||
const targetElement = name.parentNode.parentNode;
|
||||
const reloadButton = targetElement.querySelector(".reload-button");
|
||||
info(`Found reload button for ${addonName}`);
|
||||
return reloadButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a web extension from scratch in a temporary location.
|
||||
* The object must be removed when you're finished working with it.
|
||||
*/
|
||||
class TempWebExt {
|
||||
constructor(addonId) {
|
||||
this.addonId = addonId;
|
||||
this.tmpDir = FileUtils.getDir("TmpD", ["browser_addons_reload"]);
|
||||
if (!this.tmpDir.exists()) {
|
||||
this.tmpDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
|
||||
}
|
||||
this.sourceDir = this.tmpDir.clone();
|
||||
this.sourceDir.append(this.addonId);
|
||||
if (!this.sourceDir.exists()) {
|
||||
this.sourceDir.create(Ci.nsIFile.DIRECTORY_TYPE,
|
||||
FileUtils.PERMS_DIRECTORY);
|
||||
}
|
||||
}
|
||||
|
||||
writeManifest(manifestData) {
|
||||
const manifest = this.sourceDir.clone();
|
||||
manifest.append("manifest.json");
|
||||
if (manifest.exists()) {
|
||||
manifest.remove(true);
|
||||
}
|
||||
const fos = Cc["@mozilla.org/network/file-output-stream;1"]
|
||||
.createInstance(Ci.nsIFileOutputStream);
|
||||
fos.init(manifest,
|
||||
FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE |
|
||||
FileUtils.MODE_TRUNCATE,
|
||||
FileUtils.PERMS_FILE, 0);
|
||||
|
||||
const manifestString = JSON.stringify(manifestData);
|
||||
fos.write(manifestString, manifestString.length);
|
||||
fos.close();
|
||||
}
|
||||
|
||||
remove() {
|
||||
return this.tmpDir.remove(true);
|
||||
}
|
||||
}
|
||||
|
||||
add_task(function* reloadButtonReloadsAddon() {
|
||||
const { tab, document } = yield openAboutDebugging("addons");
|
||||
yield waitForInitialAddonList(document);
|
||||
yield installAddon(document, "addons/unpacked/install.rdf",
|
||||
ADDON_NAME, ADDON_NAME);
|
||||
|
||||
// Retrieve the Reload button.
|
||||
const names = [...document.querySelectorAll("#addons .target-name")];
|
||||
const name = names.filter(element => element.textContent === ADDON_NAME)[0];
|
||||
ok(name, `Found ${ADDON_NAME} add-on in the list`);
|
||||
const targetElement = name.parentNode.parentNode;
|
||||
const reloadButton = targetElement.querySelector(".reload-button");
|
||||
ok(reloadButton, "Found its reload button");
|
||||
|
||||
const reloadButton = getReloadButton(document, ADDON_NAME);
|
||||
const onInstalled = promiseAddonEvent("onInstalled");
|
||||
|
||||
const onBootstrapInstallCalled = new Promise(done => {
|
||||
@ -63,3 +108,55 @@ add_task(function* () {
|
||||
|
||||
yield closeAboutDebugging(tab);
|
||||
});
|
||||
|
||||
add_task(function* reloadButtonRefreshesMetadata() {
|
||||
const { tab, document } = yield openAboutDebugging("addons");
|
||||
yield waitForInitialAddonList(document);
|
||||
|
||||
const manifestBase = {
|
||||
"manifest_version": 2,
|
||||
"name": "Temporary web extension",
|
||||
"version": "1.0",
|
||||
"applications": {
|
||||
"gecko": {
|
||||
"id": ADDON_ID
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const tempExt = new TempWebExt(ADDON_ID);
|
||||
tempExt.writeManifest(manifestBase);
|
||||
|
||||
const onAddonListUpdated = waitForMutation(getAddonList(document),
|
||||
{ childList: true });
|
||||
const onInstalled = promiseAddonEvent("onInstalled");
|
||||
yield AddonManager.installTemporaryAddon(tempExt.sourceDir);
|
||||
const [addon] = yield onInstalled;
|
||||
info(`addon installed: ${addon.id}`);
|
||||
yield onAddonListUpdated;
|
||||
|
||||
const newName = "Temporary web extension (updated)";
|
||||
tempExt.writeManifest(Object.assign({}, manifestBase, {name: newName}));
|
||||
|
||||
// Wait for the add-on list to be updated with the reloaded name.
|
||||
const onReInstall = promiseAddonEvent("onInstalled");
|
||||
const onAddonReloaded = waitForMutation(getAddonList(document),
|
||||
{ childList: true, subtree: true });
|
||||
const reloadButton = getReloadButton(document, manifestBase.name);
|
||||
reloadButton.click();
|
||||
|
||||
yield onAddonReloaded;
|
||||
const [reloadedAddon] = yield onReInstall;
|
||||
// Make sure the name was updated correctly.
|
||||
const allAddons = [...document.querySelectorAll("#addons .target-name")]
|
||||
.map(element => element.textContent);
|
||||
const nameWasUpdated = allAddons.some(name => name === newName);
|
||||
ok(nameWasUpdated, `New name appeared in reloaded add-ons: ${allAddons}`);
|
||||
|
||||
const onUninstalled = promiseAddonEvent("onUninstalled");
|
||||
reloadedAddon.uninstall();
|
||||
yield onUninstalled;
|
||||
|
||||
tempExt.remove();
|
||||
yield closeAboutDebugging(tab);
|
||||
});
|
||||
|
@ -172,7 +172,7 @@ body {
|
||||
.cm-s-mozilla .CodeMirror-dialog { /* General toolbar styling */
|
||||
color: var(--theme-body-color-alt);
|
||||
background-color: var(--theme-toolbar-background);
|
||||
border-color: hsla(210,8%,5%,.6);
|
||||
border-color: var(--theme-splitter-color);
|
||||
}
|
||||
|
||||
.theme-fg-contrast { /* To be used for text on theme-bg-contrast */
|
||||
|
@ -1,7 +1,7 @@
|
||||
<!-- 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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="#2E761A">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="#5FC749">
|
||||
<path d="M8.2 1.7C4.8 1.7 2 4.4 2 7.9s2.7 6.3 6.3 6.3c3.5 0 6.3-2.7 6.3-6.3-.1-3.4-2.9-6.2-6.4-6.2zm0 11.4C5.3 13.1 3 10.8 3 7.9s2.3-5.2 5.2-5.2 5.2 2.3 5.2 5.2c0 3-2.2 5.2-5.2 5.2z"/>
|
||||
<path d="M11.4 7.5L10 6.2c-.1-.1-.2-.1-.4-.1H5.7c-.1 0-.2.1-.2.2v3.2c0 .1.1.4.2.4h4c.1 0 .2-.1.2-.1l1.5-1.7c.2-.1.2-.4 0-.6z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 635 B After Width: | Height: | Size: 635 B |
@ -1,7 +1,7 @@
|
||||
<!-- 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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="#2E761A">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="#5FC749">
|
||||
<path opacity="0.2" d="M5.2 2.9l5.7.1v9.4H5.2V2.9z"/>
|
||||
<path d="M10.5 2.2h-5c-.4 0-.8.4-.8.9v9.2c0 .4.3.9.8.9h5c.4 0 .8-.4.8-.9V3.1s-.6-.9-.8-.9zm-.2 10H5.7v-9h4.6v9zM15.5 6.6c-.1 0-.3-.1-.4-.2l-1.5-1.7-.9.9c-.2.2-.5.2-.7 0-.2-.2-.2-.5 0-.7l1.3-1.3c.1-.1.2-.1.4-.1.1 0 .3.1.4.2l1.9 2c.2.2.2.5 0 .7-.3.2-.4.2-.5.2zM15.5 9.7c-.1 0-.3-.1-.4-.2l-1.5-1.7-.9.9c-.2.3-.5.3-.7.1-.2-.2-.2-.5 0-.7l1.3-1.2c.1-.1.2-.1.4-.1.1 0 .3.1.4.2L16 9c.2.2.2.5 0 .7h-.5zM15.5 12.7c-.1 0-.3-.1-.4-.2l-1.5-1.7-.9.9c-.2.2-.5.2-.7 0-.2-.2-.2-.5 0-.7l1.3-1.2c.1-.1.2-.1.4-.1.1 0 .3.1.4.2l1.9 2c.2.2.2.5 0 .7-.3.1-.4.1-.5.1zM.5 6.6c.1 0 .3-.1.4-.2l1.5-1.7.9.9c.2.2.5.2.7 0 .2-.2.2-.5 0-.7L2.7 3.7c-.1-.1-.2-.1-.3-.1-.2 0-.3 0-.4.1l-1.9 2c-.2.3-.1.6.1.8.1.1.2.1.3.1zM.5 9.7c.1 0 .3-.1.4-.2l1.5-1.7.9.9c.2.3.5.3.7.1.2-.2.2-.6 0-.8L2.7 6.8c-.1-.1-.2-.1-.3-.1-.2 0-.3 0-.4.1l-1.9 2c-.2.2-.2.5 0 .7.2.2.3.2.4.2zM.5 12.7c.1 0 .3-.1.4-.2l1.5-1.7.9.9c.2.3.5.3.7.1.2-.2.2-.5 0-.7L2.7 9.8c-.1-.1-.2-.1-.3-.1-.2 0-.3 0-.4.1l-1.9 2c-.2.2-.2.5 0 .7.2.2.3.2.4.2z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@ -1,7 +1,7 @@
|
||||
<!-- 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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="#2E761A">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="#5FC749">
|
||||
<path d="M6.9 7.8c-.3.1-.6.4-.6.7-.1.5.2 1 .7 1.1.3 0 .7-.1.9-.3l2.5-2.9-3.5 1.4z"/>
|
||||
<path opacity="0.5" d="M4.7 10.6c.7 1.1 1.9 1.9 3.3 1.9s2.6-.8 3.3-1.9H4.7z"/>
|
||||
<path d="M-12.7-2.5c-3.8 0-6.7 3-6.7 6.7s3 6.7 6.7 6.7c3.8 0 6.7-3 6.7-6.7s-2.9-6.7-6.7-6.7zM-12.8 9c-2.5 0-4.6-2.1-4.8-4.5v-.2h.6c.6 0 1-.4 1-1s-.4-1-1-1h-.4c.4-.9.8-1.4 1.5-1.9l.2.4c.3.5.8.7 1.3.4.5-.2.7-.7.4-1.2l-.2-.4c.4-.1.9-.2 1.4-.2.6 0 1.1.1 1.6.3l-.2.4c-.3.5-.1 1 .4 1.3.5.3 1 .1 1.3-.4l.2-.6c.6.6 1.2 1.5 1.4 1.8h-.4c-.6 0-1 .4-1 1s.4 1 1 1h.6v.2C-8.2 6.9-10.3 9-12.8 9zM-12.8 12.7c-3.4 0-6.2 2.7-6.2 6.2s2.7 6.3 6.3 6.3c3.5 0 6.3-2.7 6.3-6.3-.1-3.4-2.9-6.2-6.4-6.2zm0 11.4c-2.9 0-5.2-2.3-5.2-5.2 0-2.9 2.3-5.2 5.2-5.2s5.2 2.3 5.2 5.2c0 3-2.2 5.2-5.2 5.2z"/>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@ -859,10 +859,6 @@
|
||||
border-bottom-color: var(--theme-splitter-color);
|
||||
}
|
||||
|
||||
.theme-dark .devtools-tabbar {
|
||||
box-shadow: 0 -0.5px 0 var(--theme-splitter-color) inset;
|
||||
}
|
||||
|
||||
#toolbox-tabs {
|
||||
margin: 0;
|
||||
}
|
||||
@ -925,12 +921,8 @@
|
||||
background-color: var(--toolbar-tab-hover-active);
|
||||
}
|
||||
|
||||
.devtools-tab:not([selected])[highlighted] {
|
||||
box-shadow: 0 2px 0 var(--theme-highlight-green) inset;
|
||||
}
|
||||
|
||||
.theme-dark .devtools-tab:not([selected])[highlighted] {
|
||||
background-color: hsla(99,100%,14%,.2);
|
||||
background-color: hsla(99, 100%, 14%, .3);
|
||||
}
|
||||
|
||||
.theme-light .devtools-tab:not([selected])[highlighted] {
|
||||
|
@ -2325,6 +2325,11 @@ Object.defineProperty(BrowserAddonList.prototype, "onListChanged", {
|
||||
});
|
||||
|
||||
BrowserAddonList.prototype.onInstalled = function (addon) {
|
||||
if (this._actorByAddonId.get(addon.id)) {
|
||||
// When an add-on gets upgraded or reloaded, it will not be uninstalled
|
||||
// so this step is necessary to clear the cache.
|
||||
this._actorByAddonId.delete(addon.id);
|
||||
}
|
||||
this._onListChanged();
|
||||
};
|
||||
|
||||
|
@ -454,7 +454,9 @@ CheckHeapTracer::CheckHeapTracer(JSRuntime* rt)
|
||||
failures(0),
|
||||
parentIndex(-1)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
setCheckEdges(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -9,6 +9,7 @@ import android.Manifest;
|
||||
import android.app.DownloadManager;
|
||||
import android.os.Environment;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.WorkerThread;
|
||||
import org.json.JSONArray;
|
||||
import org.mozilla.gecko.adjust.AdjustHelperInterface;
|
||||
@ -78,6 +79,7 @@ import org.mozilla.gecko.tabs.TabHistoryController.OnShowTabHistory;
|
||||
import org.mozilla.gecko.tabs.TabHistoryFragment;
|
||||
import org.mozilla.gecko.tabs.TabHistoryPage;
|
||||
import org.mozilla.gecko.tabs.TabsPanel;
|
||||
import org.mozilla.gecko.telemetry.measurements.SearchCountMeasurements;
|
||||
import org.mozilla.gecko.telemetry.TelemetryDispatcher;
|
||||
import org.mozilla.gecko.telemetry.UploadTelemetryCorePingCallback;
|
||||
import org.mozilla.gecko.toolbar.AutocompleteHandler;
|
||||
@ -1954,6 +1956,8 @@ public class BrowserApp extends GeckoApp
|
||||
|
||||
} else if (event.equals("Search:Keyword")) {
|
||||
storeSearchQuery(message.getString("query"));
|
||||
recordSearch(GeckoSharedPrefs.forProfile(this), message.getString("identifier"),
|
||||
TelemetryContract.Method.ACTIONBAR);
|
||||
} else if (event.equals("LightweightTheme:Update")) {
|
||||
ThreadUtils.postToUiThread(new Runnable() {
|
||||
@Override
|
||||
@ -2378,6 +2382,7 @@ public class BrowserApp extends GeckoApp
|
||||
}
|
||||
|
||||
// Otherwise, check for a bookmark keyword.
|
||||
final SharedPreferences sharedPrefs = GeckoSharedPrefs.forProfile(this);
|
||||
final BrowserDB db = getProfile().getDB();
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
@ -2404,8 +2409,6 @@ public class BrowserApp extends GeckoApp
|
||||
return;
|
||||
}
|
||||
|
||||
recordSearch(null, "barkeyword");
|
||||
|
||||
// Otherwise, construct a search query from the bookmark keyword.
|
||||
// Replace lower case bookmark keywords with URLencoded search query or
|
||||
// replace upper case bookmark keywords with un-encoded search query.
|
||||
@ -2421,25 +2424,16 @@ public class BrowserApp extends GeckoApp
|
||||
}
|
||||
|
||||
/**
|
||||
* Record in Health Report that a search has occurred.
|
||||
* Records in telemetry that a search has occurred.
|
||||
*
|
||||
* @param engine
|
||||
* a search engine instance. Can be null.
|
||||
* @param where
|
||||
* where the search was initialized; one of the values in
|
||||
* {@link BrowserHealthRecorder#SEARCH_LOCATIONS}.
|
||||
* @param where where the search was started from
|
||||
*/
|
||||
private static void recordSearch(SearchEngine engine, String where) {
|
||||
//try {
|
||||
// String identifier = (engine == null) ? "other" : engine.getEngineIdentifier();
|
||||
// JSONObject message = new JSONObject();
|
||||
// message.put("type", BrowserHealthRecorder.EVENT_SEARCH);
|
||||
// message.put("location", where);
|
||||
// message.put("identifier", identifier);
|
||||
// EventDispatcher.getInstance().dispatchEvent(message, null);
|
||||
//} catch (Exception e) {
|
||||
// Log.e(LOGTAG, "Error recording search.", e);
|
||||
//}
|
||||
private static void recordSearch(@NonNull final SharedPreferences prefs, @NonNull final String engineIdentifier,
|
||||
@NonNull final TelemetryContract.Method where) {
|
||||
// We could include the engine identifier as an extra but we'll
|
||||
// just capture that with core ping telemetry (bug 1253319).
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.SEARCH, where);
|
||||
SearchCountMeasurements.incrementSearch(prefs, engineIdentifier, where.name());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2604,6 +2598,13 @@ public class BrowserApp extends GeckoApp
|
||||
}
|
||||
|
||||
private void showFirstrunPager() {
|
||||
if (Experiments.isInExperimentLocal(getContext(), Experiments.ONBOARDING3_A)) {
|
||||
Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, Experiments.ONBOARDING3_A);
|
||||
GeckoSharedPrefs.forProfile(getContext()).edit().putString(Experiments.PREF_ONBOARDING_VERSION, Experiments.ONBOARDING3_A).apply();
|
||||
Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, Experiments.ONBOARDING3_A);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mFirstrunAnimationContainer == null) {
|
||||
final ViewStub firstrunPagerStub = (ViewStub) findViewById(R.id.firstrun_pager_stub);
|
||||
mFirstrunAnimationContainer = (FirstrunAnimationContainer) firstrunPagerStub.inflate();
|
||||
@ -3888,14 +3889,14 @@ public class BrowserApp extends GeckoApp
|
||||
|
||||
// BrowserSearch.OnSearchListener
|
||||
@Override
|
||||
public void onSearch(SearchEngine engine, String text) {
|
||||
public void onSearch(SearchEngine engine, final String text, final TelemetryContract.Method method) {
|
||||
// Don't store searches that happen in private tabs. This assumes the user can only
|
||||
// perform a search inside the currently selected tab, which is true for searches
|
||||
// that come from SearchEngineRow.
|
||||
if (!Tabs.getInstance().getSelectedTab().isPrivate()) {
|
||||
storeSearchQuery(text);
|
||||
}
|
||||
recordSearch(engine, "barsuggest");
|
||||
recordSearch(GeckoSharedPrefs.forProfile(this), engine.getEngineIdentifier(), method);
|
||||
openUrlAndStopEditing(text, engine.name);
|
||||
}
|
||||
|
||||
|
@ -64,9 +64,8 @@ public class FirstrunAnimationContainer extends LinearLayout {
|
||||
animateHide();
|
||||
|
||||
// Stop all versions of firstrun A/B sessions.
|
||||
Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, Experiments.ONBOARDING2_A);
|
||||
Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, Experiments.ONBOARDING2_B);
|
||||
Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, Experiments.ONBOARDING2_C);
|
||||
Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, Experiments.ONBOARDING3_B);
|
||||
Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, Experiments.ONBOARDING3_C);
|
||||
}
|
||||
|
||||
private void animateHide() {
|
||||
|
@ -35,7 +35,6 @@ public class FirstrunPager extends ViewPager {
|
||||
private Context context;
|
||||
protected FirstrunPanel.PagerNavigation pagerNavigation;
|
||||
private Decor mDecor;
|
||||
private View mTabStrip;
|
||||
|
||||
public FirstrunPager(Context context) {
|
||||
this(context, null);
|
||||
@ -51,8 +50,6 @@ public class FirstrunPager extends ViewPager {
|
||||
if (child instanceof Decor) {
|
||||
((ViewPager.LayoutParams) params).isDecor = true;
|
||||
mDecor = (Decor) child;
|
||||
mTabStrip = child;
|
||||
|
||||
mDecor.setOnTitleClickListener(new TabMenuStrip.OnTitleClickListener() {
|
||||
@Override
|
||||
public void onTitleClicked(int index) {
|
||||
@ -71,9 +68,6 @@ public class FirstrunPager extends ViewPager {
|
||||
panels = FirstrunPagerConfig.getRestricted();
|
||||
} else {
|
||||
panels = FirstrunPagerConfig.getDefault(appContext);
|
||||
if (panels.size() == 1) {
|
||||
mTabStrip.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
setAdapter(new ViewPagerAdapter(fm, panels));
|
||||
|
@ -27,34 +27,28 @@ public class FirstrunPagerConfig {
|
||||
public static List<FirstrunPanelConfig> getDefault(Context context) {
|
||||
final List<FirstrunPanelConfig> panels = new LinkedList<>();
|
||||
|
||||
if (Experiments.isInExperimentLocal(context, Experiments.ONBOARDING2_A)) {
|
||||
panels.add(new FirstrunPanelConfig(WelcomePanel.class.getName(), WelcomePanel.TITLE_RES));
|
||||
Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, Experiments.ONBOARDING2_A);
|
||||
GeckoSharedPrefs.forProfile(context).edit().putString(Experiments.PREF_ONBOARDING_VERSION, Experiments.ONBOARDING2_A).apply();
|
||||
} else if (Experiments.isInExperimentLocal(context, Experiments.ONBOARDING2_B)) {
|
||||
panels.add(SimplePanelConfigs.urlbarPanelConfig);
|
||||
panels.add(SimplePanelConfigs.bookmarksPanelConfig);
|
||||
panels.add(SimplePanelConfigs.syncPanelConfig);
|
||||
panels.add(new FirstrunPanelConfig(SyncPanel.class.getName(), SyncPanel.TITLE_RES));
|
||||
Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, Experiments.ONBOARDING2_B);
|
||||
GeckoSharedPrefs.forProfile(context).edit().putString(Experiments.PREF_ONBOARDING_VERSION, Experiments.ONBOARDING2_B).apply();
|
||||
} else if (Experiments.isInExperimentLocal(context, Experiments.ONBOARDING2_C)) {
|
||||
if (Experiments.isInExperimentLocal(context, Experiments.ONBOARDING3_B)) {
|
||||
panels.add(SimplePanelConfigs.urlbarPanelConfig);
|
||||
panels.add(SimplePanelConfigs.bookmarksPanelConfig);
|
||||
panels.add(SimplePanelConfigs.dataPanelConfig);
|
||||
panels.add(SimplePanelConfigs.syncPanelConfig);
|
||||
panels.add(new FirstrunPanelConfig(SyncPanel.class.getName(), SyncPanel.TITLE_RES));
|
||||
Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, Experiments.ONBOARDING2_C);
|
||||
GeckoSharedPrefs.forProfile(context).edit().putString(Experiments.PREF_ONBOARDING_VERSION, Experiments.ONBOARDING2_C).apply();
|
||||
panels.add(SimplePanelConfigs.signInPanelConfig);
|
||||
Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, Experiments.ONBOARDING3_B);
|
||||
GeckoSharedPrefs.forProfile(context).edit().putString(Experiments.PREF_ONBOARDING_VERSION, Experiments.ONBOARDING3_B).apply();
|
||||
} else if (Experiments.isInExperimentLocal(context, Experiments.ONBOARDING3_C)) {
|
||||
panels.add(SimplePanelConfigs.tabqueuePanelConfig);
|
||||
panels.add(SimplePanelConfigs.notificationsPanelConfig);
|
||||
panels.add(SimplePanelConfigs.readerviewPanelConfig);
|
||||
panels.add(SimplePanelConfigs.accountPanelConfig);
|
||||
Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, Experiments.ONBOARDING3_C);
|
||||
GeckoSharedPrefs.forProfile(context).edit().putString(Experiments.PREF_ONBOARDING_VERSION, Experiments.ONBOARDING3_C).apply();
|
||||
} else {
|
||||
Log.d(LOGTAG, "Not in an experiment!");
|
||||
panels.add(new FirstrunPanelConfig(WelcomePanel.class.getName(), WelcomePanel.TITLE_RES));
|
||||
Log.e(LOGTAG, "Not in an experiment!");
|
||||
panels.add(SimplePanelConfigs.signInPanelConfig);
|
||||
}
|
||||
|
||||
return panels;
|
||||
}
|
||||
|
||||
|
||||
public static List<FirstrunPanelConfig> getRestricted() {
|
||||
final List<FirstrunPanelConfig> panels = new LinkedList<>();
|
||||
panels.add(new FirstrunPanelConfig(RestrictedWelcomePanel.class.getName(), RestrictedWelcomePanel.TITLE_RES));
|
||||
@ -100,10 +94,16 @@ public class FirstrunPagerConfig {
|
||||
}
|
||||
}
|
||||
|
||||
protected static class SimplePanelConfigs {
|
||||
private static class SimplePanelConfigs {
|
||||
public static final FirstrunPanelConfig urlbarPanelConfig = new FirstrunPanelConfig(FirstrunPanel.class.getName(), R.string.firstrun_panel_title_welcome, R.drawable.firstrun_urlbar, R.string.firstrun_urlbar_message, R.string.firstrun_urlbar_subtext);
|
||||
public static final FirstrunPanelConfig bookmarksPanelConfig = new FirstrunPanelConfig(FirstrunPanel.class.getName(), R.string.firstrun_bookmarks_title, R.drawable.firstrun_bookmarks, R.string.firstrun_bookmarks_message, R.string.firstrun_bookmarks_subtext);
|
||||
public static final FirstrunPanelConfig syncPanelConfig = new FirstrunPanelConfig(FirstrunPanel.class.getName(), R.string.firstrun_sync_title, R.drawable.firstrun_sync, R.string.firstrun_sync_message, R.string.firstrun_sync_subtext);
|
||||
public static final FirstrunPanelConfig dataPanelConfig = new FirstrunPanelConfig(DataPanel.class.getName(), R.string.firstrun_data_title, R.drawable.firstrun_data_off, R.string.firstrun_data_message, R.string.firstrun_data_subtext);
|
||||
public static final FirstrunPanelConfig syncPanelConfig = new FirstrunPanelConfig(FirstrunPanel.class.getName(), R.string.firstrun_sync_title, R.drawable.firstrun_sync, R.string.firstrun_sync_message, R.string.firstrun_sync_subtext);
|
||||
public static final FirstrunPanelConfig signInPanelConfig = new FirstrunPanelConfig(SyncPanel.class.getName(), R.string.pref_sync, R.drawable.firstrun_signin, R.string.firstrun_signin_message, R.string.firstrun_welcome_button_browser);
|
||||
|
||||
public static final FirstrunPanelConfig tabqueuePanelConfig = new FirstrunPanelConfig(TabQueuePanel.class.getName(), R.string.firstrun_tabqueue_title, R.drawable.firstrun_tabqueue_off, R.string.firstrun_tabqueue_message_off, R.string.firstrun_tabqueue_subtext_off);
|
||||
public static final FirstrunPanelConfig notificationsPanelConfig = new FirstrunPanelConfig(FirstrunPanel.class.getName(), R.string.firstrun_notifications_title, R.drawable.firstrun_notifications, R.string.firstrun_notifications_message, R.string.firstrun_notifications_subtext);
|
||||
public static final FirstrunPanelConfig readerviewPanelConfig = new FirstrunPanelConfig(FirstrunPanel.class.getName(), R.string.firstrun_readerview_title, R.drawable.firstrun_readerview, R.string.firstrun_readerview_message, R.string.firstrun_readerview_subtext);
|
||||
public static final FirstrunPanelConfig accountPanelConfig = new FirstrunPanelConfig(SyncPanel.class.getName(), R.string.firstrun_account_title, R.drawable.firstrun_account, R.string.firstrun_account_message, R.string.firstrun_button_notnow);
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ public class FirstrunPanel extends Fragment {
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance) {
|
||||
final ViewGroup root = (ViewGroup) inflater.inflate(R.layout.firstrun_basepanel_fragment, container, false);
|
||||
final ViewGroup root = (ViewGroup) inflater.inflate(R.layout.firstrun_basepanel_checkable_fragment, container, false);
|
||||
Bundle args = getArguments();
|
||||
if (args != null) {
|
||||
final int imageRes = args.getInt(FirstrunPagerConfig.KEY_IMAGE);
|
||||
|
@ -10,6 +10,8 @@ import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
@ -17,13 +19,20 @@ import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.activities.FxAccountWebFlowActivity;
|
||||
|
||||
public class SyncPanel extends FirstrunPanel {
|
||||
// XXX: To simplify localization, this uses the pref_sync string. If this is used in the final product, add a new string to Nightly.
|
||||
public static final int TITLE_RES = R.string.pref_sync;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance) {
|
||||
final ViewGroup root = (ViewGroup) inflater.inflate(R.layout.firstrun_sync_fragment, container, false);
|
||||
// TODO: Update id names.
|
||||
final Bundle args = getArguments();
|
||||
if (args != null) {
|
||||
final int imageRes = args.getInt(FirstrunPagerConfig.KEY_IMAGE);
|
||||
final int textRes = args.getInt(FirstrunPagerConfig.KEY_TEXT);
|
||||
final int subtextRes = args.getInt(FirstrunPagerConfig.KEY_SUBTEXT);
|
||||
|
||||
((ImageView) root.findViewById(R.id.firstrun_image)).setImageResource(imageRes);
|
||||
((TextView) root.findViewById(R.id.firstrun_text)).setText(textRes);
|
||||
((TextView) root.findViewById(R.id.welcome_browse)).setText(subtextRes);
|
||||
}
|
||||
|
||||
root.findViewById(R.id.welcome_account).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
@ -0,0 +1,92 @@
|
||||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
|
||||
* 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.firstrun;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.widget.SwitchCompat;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import org.mozilla.gecko.GeckoSharedPrefs;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
import org.mozilla.gecko.preferences.GeckoPreferences;
|
||||
import org.mozilla.gecko.tabqueue.TabQueueHelper;
|
||||
import org.mozilla.gecko.tabqueue.TabQueuePrompt;
|
||||
|
||||
public class TabQueuePanel extends FirstrunPanel {
|
||||
private static final int REQUEST_CODE_TAB_QUEUE = 1;
|
||||
private SwitchCompat toggleSwitch;
|
||||
private ImageView imageView;
|
||||
private TextView messageTextView;
|
||||
private TextView subtextTextView;
|
||||
private Context context;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, final ViewGroup container, Bundle savedInstance) {
|
||||
context = getContext();
|
||||
final View root = super.onCreateView(inflater, container, savedInstance);
|
||||
|
||||
imageView = (ImageView) root.findViewById(R.id.firstrun_image);
|
||||
messageTextView = (TextView) root.findViewById(R.id.firstrun_text);
|
||||
subtextTextView = (TextView) root.findViewById(R.id.firstrun_subtext);
|
||||
|
||||
toggleSwitch = (SwitchCompat) root.findViewById(R.id.firstrun_switch);
|
||||
toggleSwitch.setVisibility(View.VISIBLE);
|
||||
toggleSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.DIALOG, "firstrun_tabqueue-permissions");
|
||||
if (b && !TabQueueHelper.canDrawOverlays(context)) {
|
||||
Intent promptIntent = new Intent(context, TabQueuePrompt.class);
|
||||
startActivityForResult(promptIntent, REQUEST_CODE_TAB_QUEUE);
|
||||
return;
|
||||
}
|
||||
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "firstrun-tabqueue-" + b);
|
||||
|
||||
final SharedPreferences prefs = GeckoSharedPrefs.forApp(context);
|
||||
final SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putBoolean(GeckoPreferences.PREFS_TAB_QUEUE, b).apply();
|
||||
|
||||
// Set image, text, and typeface changes.
|
||||
imageView.setImageResource(b ? R.drawable.firstrun_tabqueue_on : R.drawable.firstrun_tabqueue_off);
|
||||
messageTextView.setText(b ? R.string.firstrun_tabqueue_message_on : R.string.firstrun_tabqueue_message_off);
|
||||
messageTextView.setTypeface(b ? Typeface.DEFAULT_BOLD : Typeface.DEFAULT);
|
||||
subtextTextView.setText(b ? R.string.firstrun_tabqueue_subtext_on : R.string.firstrun_tabqueue_subtext_off);
|
||||
subtextTextView.setTypeface(b ? Typeface.defaultFromStyle(Typeface.ITALIC) : Typeface.DEFAULT);
|
||||
subtextTextView.setTextColor(b ? ContextCompat.getColor(context, R.color.fennec_ui_orange) : ContextCompat.getColor(context, R.color.placeholder_grey));
|
||||
}
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_TAB_QUEUE:
|
||||
final boolean accepted = TabQueueHelper.processTabQueuePromptResponse(resultCode, context);
|
||||
if (accepted) {
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.DIALOG, "firstrun_tabqueue-permissions-yes");
|
||||
toggleSwitch.setChecked(true);
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "firstrun-tabqueue-true");
|
||||
}
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.DIALOG, "firstrun_tabqueue-permissions-" + (accepted ? "accepted" : "rejected"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
|
||||
* 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.firstrun;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.activities.FxAccountWebFlowActivity;
|
||||
|
||||
public class WelcomePanel extends FirstrunPanel {
|
||||
public static final int TITLE_RES = R.string.firstrun_panel_title_welcome;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance) {
|
||||
final ViewGroup root = (ViewGroup) inflater.inflate(R.layout.firstrun_welcome_fragment, container, false);
|
||||
root.findViewById(R.id.welcome_account).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "firstrun-sync");
|
||||
showBrowserHint = false;
|
||||
|
||||
final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_GET_STARTED);
|
||||
intent.putExtra(FxAccountWebFlowActivity.EXTRA_ENDPOINT, FxAccountConstants.ENDPOINT_FIRSTRUN);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||
startActivity(intent);
|
||||
|
||||
close();
|
||||
}
|
||||
});
|
||||
|
||||
root.findViewById(R.id.welcome_browse).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "firstrun-browser");
|
||||
close();
|
||||
}
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
@ -186,7 +186,7 @@ public class BrowserSearch extends HomeFragment
|
||||
private View mSuggestionsOptInPrompt;
|
||||
|
||||
public interface OnSearchListener {
|
||||
public void onSearch(SearchEngine engine, String text);
|
||||
void onSearch(SearchEngine engine, String text, TelemetryContract.Method method);
|
||||
}
|
||||
|
||||
public interface OnEditSuggestionListener {
|
||||
@ -729,10 +729,9 @@ public class BrowserSearch extends HomeFragment
|
||||
|
||||
@Override
|
||||
public void onSearchBarClickListener(final SearchEngine searchEngine) {
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.LIST_ITEM,
|
||||
"searchenginebar");
|
||||
|
||||
mSearchListener.onSearch(searchEngine, mSearchTerm);
|
||||
final TelemetryContract.Method method = TelemetryContract.Method.LIST_ITEM;
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, method, "searchenginebar");
|
||||
mSearchListener.onSearch(searchEngine, mSearchTerm, method);
|
||||
}
|
||||
|
||||
private void ensureSuggestClientIsSet(final String suggestTemplate) {
|
||||
|
@ -43,13 +43,13 @@ public final class HomeConfig {
|
||||
TOP_SITES("top_sites", TopSitesPanel.class),
|
||||
BOOKMARKS("bookmarks", BookmarksPanel.class),
|
||||
COMBINED_HISTORY("combined_history", CombinedHistoryPanel.class),
|
||||
READING_LIST("reading_list", ReadingListPanel.class),
|
||||
RECENT_TABS("recent_tabs", RecentTabsPanel.class),
|
||||
DYNAMIC("dynamic", DynamicPanel.class),
|
||||
// Deprecated panels that should no longer exist but are kept around for
|
||||
// migration code. Class references have been replaced with new version of the panel.
|
||||
DEPRECATED_REMOTE_TABS("remote_tabs", CombinedHistoryPanel.class),
|
||||
DEPRECATED_HISTORY("history", CombinedHistoryPanel.class);
|
||||
DEPRECATED_HISTORY("history", CombinedHistoryPanel.class),
|
||||
DEPRECATED_READING_LIST("reading_list", BookmarksPanel.class);
|
||||
|
||||
private final String mId;
|
||||
private final Class<?> mPanelClass;
|
||||
@ -1598,11 +1598,11 @@ public final class HomeConfig {
|
||||
// panels).
|
||||
private static final String TOP_SITES_PANEL_ID = "4becc86b-41eb-429a-a042-88fe8b5a094e";
|
||||
private static final String BOOKMARKS_PANEL_ID = "7f6d419a-cd6c-4e34-b26f-f68b1b551907";
|
||||
private static final String READING_LIST_PANEL_ID = "20f4549a-64ad-4c32-93e4-1dcef792733b";
|
||||
private static final String HISTORY_PANEL_ID = "f134bf20-11f7-4867-ab8b-e8e705d7fbe8";
|
||||
private static final String COMBINED_HISTORY_PANEL_ID = "4d716ce2-e063-486d-9e7c-b190d7b04dc6";
|
||||
private static final String RECENT_TABS_PANEL_ID = "5c2601a5-eedc-4477-b297-ce4cef52adf8";
|
||||
private static final String REMOTE_TABS_PANEL_ID = "72429afd-8d8b-43d8-9189-14b779c563d0";
|
||||
private static final String DEPRECATED_READING_LIST_PANEL_ID = "20f4549a-64ad-4c32-93e4-1dcef792733b";
|
||||
|
||||
private final HomeConfigBackend mBackend;
|
||||
|
||||
@ -1639,6 +1639,7 @@ public final class HomeConfig {
|
||||
return R.string.home_top_sites_title;
|
||||
|
||||
case BOOKMARKS:
|
||||
case DEPRECATED_READING_LIST:
|
||||
return R.string.bookmarks_title;
|
||||
|
||||
case DEPRECATED_HISTORY:
|
||||
@ -1646,9 +1647,6 @@ public final class HomeConfig {
|
||||
case COMBINED_HISTORY:
|
||||
return R.string.home_history_title;
|
||||
|
||||
case READING_LIST:
|
||||
return R.string.reading_list_title;
|
||||
|
||||
case RECENT_TABS:
|
||||
return R.string.recent_tabs_title;
|
||||
|
||||
@ -1674,8 +1672,8 @@ public final class HomeConfig {
|
||||
case DEPRECATED_REMOTE_TABS:
|
||||
return REMOTE_TABS_PANEL_ID;
|
||||
|
||||
case READING_LIST:
|
||||
return READING_LIST_PANEL_ID;
|
||||
case DEPRECATED_READING_LIST:
|
||||
return DEPRECATED_READING_LIST_PANEL_ID;
|
||||
|
||||
case RECENT_TABS:
|
||||
return RECENT_TABS_PANEL_ID;
|
||||
|
@ -35,7 +35,7 @@ public class HomeConfigPrefsBackend implements HomeConfigBackend {
|
||||
private static final String LOGTAG = "GeckoHomeConfigBackend";
|
||||
|
||||
// Increment this to trigger a migration.
|
||||
private static final int VERSION = 5;
|
||||
private static final int VERSION = 6;
|
||||
|
||||
// This key was originally used to store only an array of panel configs.
|
||||
public static final String PREFS_CONFIG_KEY_OLD = "home_panels";
|
||||
@ -76,7 +76,6 @@ public class HomeConfigPrefsBackend implements HomeConfigBackend {
|
||||
|
||||
|
||||
panelConfigs.add(createBuiltinPanelConfig(mContext, PanelType.RECENT_TABS));
|
||||
panelConfigs.add(createBuiltinPanelConfig(mContext, PanelType.READING_LIST));
|
||||
|
||||
return new State(panelConfigs, true);
|
||||
}
|
||||
@ -233,6 +232,48 @@ public class HomeConfigPrefsBackend implements HomeConfigBackend {
|
||||
jsonPanels.put(historyIndex, historyPanelConfig.toJSON());
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the reading list panel.
|
||||
* If the reading list panel used to be the default panel, we make bookmarks the new default.
|
||||
*/
|
||||
private static JSONArray removeReadingListPanel(Context context, JSONArray jsonPanels) throws JSONException {
|
||||
boolean wasDefault = false;
|
||||
int bookmarksIndex = -1;
|
||||
|
||||
// JSONArrary doesn't provide remove() for API < 19, therefore we need to manually copy all
|
||||
// the items we don't want deleted into a new array.
|
||||
final JSONArray newJSONPanels = new JSONArray();
|
||||
|
||||
for (int i = 0; i < jsonPanels.length(); i++) {
|
||||
final JSONObject panelJSON = jsonPanels.getJSONObject(i);
|
||||
final PanelConfig panelConfig = new PanelConfig(panelJSON);
|
||||
|
||||
if (panelConfig.getType() == PanelType.DEPRECATED_READING_LIST) {
|
||||
// If this panel was the default we'll need to assign a new default:
|
||||
wasDefault = panelConfig.isDefault();
|
||||
} else {
|
||||
if (panelConfig.getType() == PanelType.BOOKMARKS) {
|
||||
bookmarksIndex = newJSONPanels.length();
|
||||
}
|
||||
|
||||
newJSONPanels.put(panelJSON);
|
||||
}
|
||||
}
|
||||
|
||||
if (wasDefault) {
|
||||
// This will make the bookmarks panel visible if it was previously hidden - this is desired
|
||||
// since this will make the new equivalent of the reading list visible by default.
|
||||
final JSONObject bookmarksPanelConfig = createBuiltinPanelConfig(context, PanelType.BOOKMARKS, EnumSet.of(PanelConfig.Flags.DEFAULT_PANEL)).toJSON();
|
||||
if (bookmarksIndex != -1) {
|
||||
newJSONPanels.put(bookmarksIndex, bookmarksPanelConfig);
|
||||
} else {
|
||||
newJSONPanels.put(bookmarksPanelConfig);
|
||||
}
|
||||
}
|
||||
|
||||
return newJSONPanels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the reading list panel already exists.
|
||||
*
|
||||
@ -246,7 +287,7 @@ public class HomeConfigPrefsBackend implements HomeConfigBackend {
|
||||
try {
|
||||
final JSONObject jsonPanelConfig = jsonPanels.getJSONObject(i);
|
||||
final PanelConfig panelConfig = new PanelConfig(jsonPanelConfig);
|
||||
if (panelConfig.getType() == PanelType.READING_LIST) {
|
||||
if (panelConfig.getType() == PanelType.DEPRECATED_READING_LIST) {
|
||||
return true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@ -323,10 +364,10 @@ public class HomeConfigPrefsBackend implements HomeConfigBackend {
|
||||
// considered "low memory". Now, we expose the panel to all devices.
|
||||
// This migration should only occur for "low memory" devices.
|
||||
// Note: This will not agree with the default configuration, which
|
||||
// has DEPRECATED_REMOTE_TABS after READING_LIST on some devices.
|
||||
// has DEPRECATED_REMOTE_TABS after DEPRECATED_READING_LIST on some devices.
|
||||
if (!readingListPanelExists(jsonPanels)) {
|
||||
addBuiltinPanelConfig(context, jsonPanels,
|
||||
PanelType.READING_LIST, Position.BACK, Position.BACK);
|
||||
PanelType.DEPRECATED_READING_LIST, Position.BACK, Position.BACK);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -341,6 +382,10 @@ public class HomeConfigPrefsBackend implements HomeConfigBackend {
|
||||
// This is the fix for bug 1264136 where we lost track of the default panel during some migrations.
|
||||
ensureDefaultPanelForV5(context, jsonPanels);
|
||||
break;
|
||||
|
||||
case 6:
|
||||
jsonPanels = removeReadingListPanel(context, jsonPanels);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,72 +0,0 @@
|
||||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* 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.home;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.GeckoSharedPrefs;
|
||||
import org.mozilla.gecko.Locales;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.SnackbarHelper;
|
||||
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
/**
|
||||
* Fragment that used to display reading list contents in a ListView, and now directs
|
||||
* users to Bookmarks to view their former reading-list content.
|
||||
*/
|
||||
public class ReadingListPanel extends HomeFragment {
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance) {
|
||||
final ViewGroup root = (ViewGroup) inflater.inflate(R.layout.readinglistpanel_gone_fragment, container, false);
|
||||
|
||||
// We could update the ID names - however this panel is only intended to be live for one
|
||||
// release, hence there's little utility in optimising this code.
|
||||
root.findViewById(R.id.welcome_account).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
boolean bookmarksEnabled = GeckoSharedPrefs.forProfile(getContext()).getBoolean(HomeConfig.PREF_KEY_BOOKMARKS_PANEL_ENABLED, true);
|
||||
|
||||
if (bookmarksEnabled) {
|
||||
mUrlOpenListener.onUrlOpen("about:home?panel=" + HomeConfig.getIdForBuiltinPanelType(HomeConfig.PanelType.BOOKMARKS),
|
||||
EnumSet.noneOf(HomePager.OnUrlOpenListener.Flags.class));
|
||||
} else {
|
||||
SnackbarHelper.showSnackbar(getActivity(),
|
||||
getResources().getString(R.string.reading_list_migration_bookmarks_hidden),
|
||||
Snackbar.LENGTH_LONG);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
root.findViewById(R.id.welcome_browse).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
final String link = getString(R.string.migrated_reading_list_url,
|
||||
AppConstants.MOZ_APP_VERSION,
|
||||
AppConstants.OS_TARGET,
|
||||
Locales.getLanguageTag(Locale.getDefault()));
|
||||
|
||||
mUrlOpenListener.onUrlOpen(link,
|
||||
EnumSet.noneOf(HomePager.OnUrlOpenListener.Flags.class));
|
||||
}
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void load() {
|
||||
// Must be overriden, but we're not doing any loading hence no real implementation...
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@
|
||||
|
||||
package org.mozilla.gecko.home;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
import org.mozilla.gecko.R;
|
||||
|
||||
@ -63,6 +64,7 @@ public class SearchEngine {
|
||||
/**
|
||||
* @return a non-null string suitable for use by FHR.
|
||||
*/
|
||||
@NonNull
|
||||
public String getEngineIdentifier() {
|
||||
if (this.identifier != null) {
|
||||
return this.identifier;
|
||||
|
@ -116,7 +116,7 @@ class SearchEngineRow extends AnimatedHeightLayout {
|
||||
} else {
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.SUGGESTION, (String) v.getTag());
|
||||
}
|
||||
mSearchListener.onSearch(mSearchEngine, suggestion);
|
||||
mSearchListener.onSearch(mSearchEngine, suggestion, TelemetryContract.Method.SUGGESTION);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -241,7 +241,7 @@ class SearchEngineRow extends AnimatedHeightLayout {
|
||||
String searchTerm = getSuggestionTextFromView(mUserEnteredView);
|
||||
if (mSearchListener != null) {
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.SUGGESTION, "user");
|
||||
mSearchListener.onSearch(mSearchEngine, searchTerm);
|
||||
mSearchListener.onSearch(mSearchEngine, searchTerm, TelemetryContract.Method.SUGGESTION);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,8 @@ import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.GeckoSharedPrefs;
|
||||
import org.mozilla.gecko.distribution.DistributionStoreCallback;
|
||||
import org.mozilla.gecko.search.SearchEngineManager;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.telemetry.measurements.SearchCountMeasurements;
|
||||
import org.mozilla.gecko.telemetry.pingbuilders.TelemetryCorePingBuilder;
|
||||
import org.mozilla.gecko.util.StringUtils;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
@ -77,13 +79,22 @@ public class UploadTelemetryCorePingCallback implements SearchEngineManager.Sear
|
||||
.setDefaultSearchEngine(TelemetryCorePingBuilder.getEngineIdentifier(engine))
|
||||
.setProfileCreationDate(TelemetryCorePingBuilder.getProfileCreationDate(activity, profile))
|
||||
.setSequenceNumber(TelemetryCorePingBuilder.getAndIncrementSequenceNumber(sharedPrefs));
|
||||
final String distributionId = sharedPrefs.getString(DistributionStoreCallback.PREF_DISTRIBUTION_ID, null);
|
||||
if (distributionId != null) {
|
||||
pingBuilder.setOptDistributionID(distributionId);
|
||||
}
|
||||
maybeSetOptionalMeasurements(sharedPrefs, pingBuilder);
|
||||
|
||||
activity.getTelemetryDispatcher().queuePingForUpload(activity, pingBuilder);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void maybeSetOptionalMeasurements(final SharedPreferences sharedPrefs, final TelemetryCorePingBuilder pingBuilder) {
|
||||
final String distributionId = sharedPrefs.getString(DistributionStoreCallback.PREF_DISTRIBUTION_ID, null);
|
||||
if (distributionId != null) {
|
||||
pingBuilder.setOptDistributionID(distributionId);
|
||||
}
|
||||
|
||||
final ExtendedJSONObject searchCounts = SearchCountMeasurements.getAndZeroSearch(sharedPrefs);
|
||||
if (searchCounts.size() > 0) {
|
||||
pingBuilder.setOptSearchCounts(searchCounts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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.telemetry.measurements;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A place to store and retrieve the number of times a user has searched with a specific engine from a
|
||||
* specific location. This is designed for use as a telemetry core ping measurement.
|
||||
*
|
||||
* The implementation works by storing a preference for each engine-location pair and incrementing them
|
||||
* each time {@link #incrementSearch(SharedPreferences, String, String)} is called. In order to
|
||||
* retrieve the full set of keys later, we store all the available key names in another preference.
|
||||
*
|
||||
* When we retrieve the keys in {@link #getAndZeroSearch(SharedPreferences)} (using the set of keys
|
||||
* preference), the values saved to the preferences are returned and the preferences are removed
|
||||
* (i.e. zeroed) from Shared Preferences. The reason we remove the preferences (rather than actually
|
||||
* zeroing them) is to avoid bloating shared preferences if 1) the set of engines ever changes or
|
||||
* 2) we remove this feature.
|
||||
*
|
||||
* Since we increment a value on each successive search, which doesn't take up more space, we don't
|
||||
* have to worry about using excess disk space if the measurements are never zeroed (e.g. telemetry
|
||||
* upload is disabled). In the worst case, we overflow the integer and may return negative values.
|
||||
*
|
||||
* This class is thread-safe by locking access to its public methods. When this class was written, incrementing &
|
||||
* retrieval were called from multiple threads so rather than enforcing the callers keep their threads straight, it
|
||||
* was simpler to lock all access.
|
||||
*/
|
||||
public class SearchCountMeasurements {
|
||||
/** The set of "engine + where" keys we've stored; used for retrieving stored engines. */
|
||||
@VisibleForTesting static final String PREF_SEARCH_KEYSET = "measurements-search-count-keyset";
|
||||
private static final String PREF_SEARCH_PREFIX = "measurements-search-count-engine-"; // + "engine.where"
|
||||
|
||||
private SearchCountMeasurements() {}
|
||||
|
||||
public static synchronized void incrementSearch(@NonNull final SharedPreferences prefs,
|
||||
@NonNull final String engineIdentifier, @NonNull final String where) {
|
||||
final String engineWhereStr = engineIdentifier + "." + where;
|
||||
final String key = getEngineSearchCountKey(engineWhereStr);
|
||||
|
||||
final int count = prefs.getInt(key, 0);
|
||||
prefs.edit().putInt(key, count + 1).apply();
|
||||
|
||||
unionKeyToSearchKeyset(prefs, engineWhereStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key Engine of the form, "engine.where"
|
||||
*/
|
||||
private static void unionKeyToSearchKeyset(@NonNull final SharedPreferences prefs, @NonNull final String key) {
|
||||
final Set<String> keysFromPrefs = prefs.getStringSet(PREF_SEARCH_KEYSET, Collections.<String>emptySet());
|
||||
if (keysFromPrefs.contains(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// String set returned by shared prefs cannot be modified so we copy.
|
||||
final Set<String> keysToSave = new HashSet<>(keysFromPrefs);
|
||||
keysToSave.add(key);
|
||||
prefs.edit().putStringSet(PREF_SEARCH_KEYSET, keysToSave).apply();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and zeroes search counts.
|
||||
*
|
||||
* We return ExtendedJSONObject for now because that's the format needed by the core telemetry ping.
|
||||
*/
|
||||
public static synchronized ExtendedJSONObject getAndZeroSearch(@NonNull final SharedPreferences prefs) {
|
||||
final ExtendedJSONObject out = new ExtendedJSONObject();
|
||||
final SharedPreferences.Editor editor = prefs.edit();
|
||||
|
||||
final Set<String> keysFromPrefs = prefs.getStringSet(PREF_SEARCH_KEYSET, Collections.<String>emptySet());
|
||||
for (final String engineWhereStr : keysFromPrefs) {
|
||||
final String key = getEngineSearchCountKey(engineWhereStr);
|
||||
out.put(engineWhereStr, prefs.getInt(key, 0));
|
||||
editor.remove(key);
|
||||
}
|
||||
editor.remove(PREF_SEARCH_KEYSET)
|
||||
.apply();
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param engineWhereStr string of the form "engine.where"
|
||||
* @return the key for the engines' search counts in shared preferences
|
||||
*/
|
||||
@VisibleForTesting static String getEngineSearchCountKey(final String engineWhereStr) {
|
||||
return PREF_SEARCH_PREFIX + engineWhereStr;
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.Locales;
|
||||
import org.mozilla.gecko.search.SearchEngine;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.telemetry.TelemetryConstants;
|
||||
import org.mozilla.gecko.telemetry.TelemetryPing;
|
||||
import org.mozilla.gecko.util.DateUtil;
|
||||
@ -39,7 +40,7 @@ import java.util.concurrent.TimeUnit;
|
||||
public class TelemetryCorePingBuilder extends TelemetryPingBuilder {
|
||||
|
||||
private static final String NAME = "core";
|
||||
private static final int VERSION_VALUE = 5; // For version history, see toolkit/components/telemetry/docs/core-ping.rst
|
||||
private static final int VERSION_VALUE = 6; // For version history, see toolkit/components/telemetry/docs/core-ping.rst
|
||||
private static final String OS_VALUE = "Android";
|
||||
|
||||
private static final String ARCHITECTURE = "arch";
|
||||
@ -53,6 +54,7 @@ public class TelemetryCorePingBuilder extends TelemetryPingBuilder {
|
||||
private static final String OS_VERSION = "osversion";
|
||||
private static final String PING_CREATION_DATE = "created";
|
||||
private static final String PROFILE_CREATION_DATE = "profileDate";
|
||||
private static final String SEARCH_COUNTS = "searches";
|
||||
private static final String SEQ = "seq";
|
||||
private static final String TIMEZONE_OFFSET = "tz";
|
||||
private static final String VERSION_ATTR = "v";
|
||||
@ -133,6 +135,20 @@ public class TelemetryCorePingBuilder extends TelemetryPingBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param searchCounts non-empty JSON with {"engine.where": <int-count>}
|
||||
*/
|
||||
public TelemetryCorePingBuilder setOptSearchCounts(@NonNull final ExtendedJSONObject searchCounts) {
|
||||
if (searchCounts == null) {
|
||||
throw new IllegalStateException("Expected non-null search counts");
|
||||
} else if (searchCounts.size() == 0) {
|
||||
throw new IllegalStateException("Expected non-empty search counts");
|
||||
}
|
||||
|
||||
payload.put(SEARCH_COUNTS, searchCounts);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param date The profile creation date in days to the unix epoch (not millis!), or null if there is an error.
|
||||
*/
|
||||
|
@ -150,6 +150,12 @@ public class TelemetryJSONFilePingStore implements TelemetryPingStore {
|
||||
* @return the JSON object from the given file or null if there is an error.
|
||||
*/
|
||||
private JSONObject lockAndReadJSONFromFile(final File file) {
|
||||
// lockAndReadFileAndCloseStream doesn't handle file size of 0.
|
||||
if (file.length() == 0) {
|
||||
Log.w(LOGTAG, "Unexpected empty file: " + file.getName() + ". Ignoring");
|
||||
return null;
|
||||
}
|
||||
|
||||
final FileInputStream inputStream;
|
||||
try {
|
||||
inputStream = new FileInputStream(file);
|
||||
|
@ -37,9 +37,9 @@ public class Experiments {
|
||||
|
||||
// Onboarding: "Features and Story". These experiments are determined
|
||||
// on the client, they are not part of the server config.
|
||||
public static final String ONBOARDING2_A = "onboarding2-a"; // Control: Single (blue) welcome screen
|
||||
public static final String ONBOARDING2_B = "onboarding2-b"; // 4 static Feature slides
|
||||
public static final String ONBOARDING2_C = "onboarding2-c"; // 4 static + 1 clickable (Data saving) Feature slides
|
||||
public static final String ONBOARDING3_A = "onboarding3-a"; // Control: No first run
|
||||
public static final String ONBOARDING3_B = "onboarding3-b"; // 4 static Feature + 1 dynamic slides
|
||||
public static final String ONBOARDING3_C = "onboarding3-c"; // Differentiating features slides
|
||||
|
||||
// Synchronizing the catalog of downloadable content from Kinto
|
||||
public static final String DOWNLOAD_CONTENT_CATALOG_SYNC = "download-content-catalog-sync";
|
||||
@ -95,12 +95,12 @@ public class Experiments {
|
||||
* @return returns value for experiment or false if experiment does not exist.
|
||||
*/
|
||||
public static boolean isInExperimentLocal(Context context, String experiment) {
|
||||
if (SwitchBoard.isInBucket(context, 0, 33)) {
|
||||
return Experiments.ONBOARDING2_A.equals(experiment);
|
||||
} else if (SwitchBoard.isInBucket(context, 33, 66)) {
|
||||
return Experiments.ONBOARDING2_B.equals(experiment);
|
||||
} else if (SwitchBoard.isInBucket(context, 66, 100)) {
|
||||
return Experiments.ONBOARDING2_C.equals(experiment);
|
||||
if (SwitchBoard.isInBucket(context, 0, 20)) {
|
||||
return Experiments.ONBOARDING3_A.equals(experiment);
|
||||
} else if (SwitchBoard.isInBucket(context, 20, 60)) {
|
||||
return Experiments.ONBOARDING3_B.equals(experiment);
|
||||
} else if (SwitchBoard.isInBucket(context, 60, 100)) {
|
||||
return Experiments.ONBOARDING3_C.equals(experiment);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
@ -13,6 +13,10 @@ final class UnusedResourcesUtil {
|
||||
R.dimen.wrap_content,
|
||||
};
|
||||
|
||||
public static final int[] USED_IN_BRANDING = {
|
||||
R.drawable.large_icon
|
||||
};
|
||||
|
||||
public static final int[] USED_IN_COLOR_PALETTE = {
|
||||
R.color.private_browsing_purple, // This will be used eventually, then this item removed.
|
||||
};
|
||||
|
@ -3,8 +3,6 @@
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<!ENTITY firstrun_panel_title_welcome "Welcome">
|
||||
<!ENTITY onboard_start_message3 "Browse with &brandShortName;">
|
||||
<!ENTITY onboard_start_subtext3 "Make your mobile Web browsing experience truly your own.">
|
||||
|
||||
<!ENTITY firstrun_urlbar_message "Welcome to &brandShortName;">
|
||||
<!ENTITY firstrun_urlbar_subtext "Find things faster with helpful search suggestion shortcuts.">
|
||||
@ -20,20 +18,36 @@
|
||||
<!ENTITY firstrun_signin_message "Get connected, get started">
|
||||
<!ENTITY firstrun_signin_button "Sign in to Sync">
|
||||
<!ENTITY onboard_start_button_browser "Start Browsing">
|
||||
<!ENTITY firstrun_button_notnow "Not right now">
|
||||
<!ENTITY firstrun_button_next "Next">
|
||||
|
||||
<!ENTITY onboard_start_restricted1 "Stay safe and in control with this simplified version of &brandShortName;.">
|
||||
<!ENTITY firstrun_tabqueue_title "Links">
|
||||
<!-- Localization note (firstrun_tabqueue_message): 'Tab queue' is a feature that allows users to queue up or save links from outside of Firefox (without switching apps) - these links will be loaded in Firefox the next time Firefox is opened. -->
|
||||
<!ENTITY firstrun_tabqueue_message_off "Turn on Tab queue">
|
||||
<!ENTITY firstrun_tabqueue_subtext_off "Save links for later in &brandShortName; when tapping them in other apps.">
|
||||
|
||||
<!ENTITY reading_list_migration_title "Reading List connected">
|
||||
<!ENTITY reading_list_migration_subtext "Your Reading List items will now be added to your Bookmarks">
|
||||
<!ENTITY reading_list_migration_goto_bookmarks "Go to Bookmarks">
|
||||
<!ENTITY reading_list_migration_bookmarks_hidden "Your Bookmarks panel is hidden">
|
||||
<!ENTITY firstrun_tabqueue_message_on "Success!">
|
||||
<!ENTITY firstrun_tabqueue_subtext_on "You can always turn this off in &settings; under &pref_category_general;.">
|
||||
|
||||
<!ENTITY firstrun_notifications_title "Blogs">
|
||||
<!ENTITY firstrun_notifications_message "Stay informed">
|
||||
<!ENTITY firstrun_notifications_subtext "Get notified when blogs you have bookmarked post an update.">
|
||||
|
||||
<!ENTITY firstrun_readerview_title "Articles">
|
||||
<!-- Localization note (firstrun_readerview_message): This is a casual way of describing getting rid of unnecessary things, and is referring to simplifying websites so only the article text and images are visible, removing unnecessary headers or ads. -->
|
||||
<!ENTITY firstrun_readerview_message "Lose the clutter">
|
||||
<!ENTITY firstrun_readerview_subtext "Use Reader View to make articles nicer to read \u2014 even offline.">
|
||||
|
||||
<!-- Localization note (firstrun_devices_title): This is a casual way of addressing the user, somewhat referring to their online identity (which would include other devices, Firefox usage, accounts, etc). -->
|
||||
<!ENTITY firstrun_account_title "You">
|
||||
<!ENTITY firstrun_account_message "Have &brandShortName; on another device?">
|
||||
|
||||
<!ENTITY onboard_start_restricted1 "Stay safe and in control with this simplified version of &brandShortName;.">
|
||||
|
||||
<!-- Localization note: These are used as the titles of different pages on the home screen.
|
||||
They are automatically converted to all caps by the Android platform. -->
|
||||
<!ENTITY bookmarks_title "Bookmarks">
|
||||
<!ENTITY history_title "History">
|
||||
<!ENTITY reading_list_title "Reading List">
|
||||
<!ENTITY recent_tabs_title "Recent Tabs">
|
||||
|
||||
<!ENTITY switch_to_tab "Switch to tab">
|
||||
|
@ -313,7 +313,7 @@ gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
|
||||
'firstrun/FirstrunPanel.java',
|
||||
'firstrun/RestrictedWelcomePanel.java',
|
||||
'firstrun/SyncPanel.java',
|
||||
'firstrun/WelcomePanel.java',
|
||||
'firstrun/TabQueuePanel.java',
|
||||
'FormAssistPopup.java',
|
||||
'GeckoAccessibility.java',
|
||||
'GeckoActivity.java',
|
||||
@ -426,7 +426,6 @@ gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
|
||||
'home/PanelViewAdapter.java',
|
||||
'home/PanelViewItemHandler.java',
|
||||
'home/PinSiteDialog.java',
|
||||
'home/ReadingListPanel.java',
|
||||
'home/RecentTabsPanel.java',
|
||||
'home/RemoteTabsExpandableListState.java',
|
||||
'home/SearchEngine.java',
|
||||
@ -576,6 +575,7 @@ gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
|
||||
'tabs/TabsPanel.java',
|
||||
'tabs/TabsPanelThumbnailView.java',
|
||||
'Telemetry.java',
|
||||
'telemetry/measurements/SearchCountMeasurements.java',
|
||||
'telemetry/pingbuilders/TelemetryCorePingBuilder.java',
|
||||
'telemetry/pingbuilders/TelemetryPingBuilder.java',
|
||||
'telemetry/schedulers/TelemetryUploadAllPingsImmediatelyScheduler.java',
|
||||
|
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 86 KiB |
After Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 9.7 KiB |
After Width: | Height: | Size: 8.6 KiB |
After Width: | Height: | Size: 9.2 KiB |
Before Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 10 KiB |
@ -40,9 +40,12 @@
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/TextAppearance.FirstrunRegular.Body"/>
|
||||
|
||||
<View android:layout_weight="1"
|
||||
android:layout_height="0dp"
|
||||
android:layout_width="match_parent"/>
|
||||
<android.support.v7.widget.SwitchCompat
|
||||
android:id="@+id/firstrun_switch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:visibility="invisible"/>
|
||||
|
||||
<TextView android:id="@+id/firstrun_link"
|
||||
android:layout_width="wrap_content"
|
@ -18,21 +18,21 @@
|
||||
android:orientation="vertical">
|
||||
|
||||
|
||||
<ImageView android:layout_width="wrap_content"
|
||||
<ImageView android:id="@+id/firstrun_image"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/firstrun_background_height"
|
||||
android:layout_marginTop="40dp"
|
||||
android:layout_marginBottom="40dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:layout_gravity="center"
|
||||
android:adjustViewBounds="true"
|
||||
android:src="@drawable/firstrun_signin"/>
|
||||
android:adjustViewBounds="true"/>
|
||||
|
||||
<TextView android:layout_width="@dimen/firstrun_content_width"
|
||||
<TextView android:id="@+id/firstrun_text"
|
||||
android:layout_width="@dimen/firstrun_content_width"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:paddingBottom="40dp"
|
||||
android:textAppearance="@style/TextAppearance.FirstrunLight.Main"
|
||||
android:text="@string/firstrun_signin_message"/>
|
||||
android:textAppearance="@style/TextAppearance.FirstrunLight.Main"/>
|
||||
|
||||
<Button android:id="@+id/welcome_account"
|
||||
style="@style/Widget.Firstrun.Button"
|
||||
@ -49,7 +49,6 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="30dp"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/TextAppearance.FirstrunRegular.Link"
|
||||
android:text="@string/firstrun_welcome_button_browser"/>
|
||||
android:textAppearance="@style/TextAppearance.FirstrunRegular.Link"/>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
@ -1,66 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
|
||||
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:fillViewport="true">
|
||||
|
||||
<LinearLayout android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="248dp"
|
||||
android:background="@color/android:white"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/link_blue">
|
||||
|
||||
<ImageView android:layout_width="wrap_content"
|
||||
android:layout_height="248dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:layout_gravity="center"
|
||||
android:adjustViewBounds="true"
|
||||
android:src="@drawable/firstrun_background_coffee"/>
|
||||
|
||||
<ImageView android:layout_width="@dimen/firstrun_brand_size"
|
||||
android:layout_height="@dimen/firstrun_brand_size"
|
||||
android:src="@drawable/large_icon"
|
||||
android:layout_gravity="center"/>
|
||||
</FrameLayout>
|
||||
|
||||
<TextView android:layout_width="@dimen/firstrun_content_width"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:paddingTop="30dp"
|
||||
android:textAppearance="@style/TextAppearance.FirstrunLight.Main"
|
||||
android:text="@string/firstrun_welcome_message"/>
|
||||
|
||||
<TextView android:layout_width="@dimen/firstrun_content_width"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="20dp"
|
||||
android:paddingBottom="30dp"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/TextAppearance.FirstrunRegular.Body"
|
||||
android:text="@string/firstrun_welcome_subtext"/>
|
||||
|
||||
<Button android:id="@+id/welcome_account"
|
||||
style="@style/Widget.Firstrun.Button"
|
||||
android:background="@drawable/button_background_action_orange_round"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/firstrun_signin_button"/>
|
||||
|
||||
<TextView android:id="@+id/welcome_browse"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="20dp"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/TextAppearance.FirstrunRegular.Link"
|
||||
android:text="@string/firstrun_welcome_button_browser"/>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
@ -1,58 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
|
||||
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:fillViewport="true">
|
||||
|
||||
<LinearLayout android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="@dimen/firstrun_min_height"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="40dp"
|
||||
android:layout_marginBottom="40dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:layout_gravity="center"
|
||||
android:adjustViewBounds="true"
|
||||
android:src="@drawable/reading_list_migration"/>
|
||||
|
||||
<TextView android:layout_width="@dimen/firstrun_content_width"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/TextAppearance.FirstrunLight.Main"
|
||||
android:text="@string/reading_list_migration_title"/>
|
||||
|
||||
<TextView android:id="@+id/firstrun_subtext"
|
||||
android:layout_width="@dimen/firstrun_content_width"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="20dp"
|
||||
android:paddingBottom="30dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/reading_list_migration_subtext"
|
||||
android:textAppearance="@style/TextAppearance.FirstrunRegular.Body"
|
||||
android:singleLine="false"/>
|
||||
|
||||
<Button android:id="@+id/welcome_account"
|
||||
style="@style/Widget.Firstrun.Button"
|
||||
android:background="@drawable/button_background_action_orange_round"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/reading_list_migration_goto_bookmarks"/>
|
||||
|
||||
<TextView android:id="@+id/welcome_browse"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/TextAppearance.FirstrunRegular.Link"
|
||||
android:text="@string/pref_learn_more"/>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
@ -24,7 +24,6 @@
|
||||
<dimen name="browser_toolbar_site_security_padding_vertical">21dp</dimen>
|
||||
<dimen name="browser_toolbar_site_security_padding_horizontal">8dp</dimen>
|
||||
|
||||
<dimen name="firstrun_brand_size">72dp</dimen>
|
||||
<dimen name="firstrun_background_height">300dp</dimen>
|
||||
|
||||
<dimen name="tabs_panel_indicator_width">72dp</dimen>
|
||||
|
@ -61,7 +61,6 @@
|
||||
|
||||
<dimen name="firstrun_content_width">300dp</dimen>
|
||||
<dimen name="firstrun_min_height">180dp</dimen>
|
||||
<dimen name="firstrun_brand_size">48dp</dimen>
|
||||
<dimen name="firstrun_background_height">180dp</dimen>
|
||||
|
||||
<dimen name="overlay_prompt_content_width">260dp</dimen>
|
||||
|
@ -30,8 +30,6 @@
|
||||
#include ../services/strings.xml.in
|
||||
|
||||
<string name="firstrun_panel_title_welcome">&firstrun_panel_title_welcome;</string>
|
||||
<string name="firstrun_welcome_message">&onboard_start_message3;</string>
|
||||
<string name="firstrun_welcome_subtext">&onboard_start_subtext3;</string>
|
||||
|
||||
<string name="firstrun_urlbar_message">&firstrun_urlbar_message;</string>
|
||||
<string name="firstrun_urlbar_subtext">&firstrun_urlbar_subtext;</string>
|
||||
@ -47,18 +45,32 @@
|
||||
<string name="firstrun_signin_message">&firstrun_signin_message;</string>
|
||||
<string name="firstrun_signin_button">&firstrun_signin_button;</string>
|
||||
<string name="firstrun_welcome_button_browser">&onboard_start_button_browser;</string>
|
||||
<string name="firstrun_button_notnow">&firstrun_button_notnow;</string>
|
||||
<string name="firstrun_button_next">&firstrun_button_next;</string>
|
||||
|
||||
<string name="firstrun_tabqueue_title">&firstrun_tabqueue_title;</string>
|
||||
<string name="firstrun_tabqueue_message_off">&firstrun_tabqueue_message_off;</string>
|
||||
<string name="firstrun_tabqueue_subtext_off">&firstrun_tabqueue_subtext_off;</string>
|
||||
<string name="firstrun_tabqueue_message_on">&firstrun_tabqueue_message_on;</string>
|
||||
<string name="firstrun_tabqueue_subtext_on">&firstrun_tabqueue_subtext_on;</string>
|
||||
|
||||
<string name="firstrun_notifications_title">&firstrun_notifications_title;</string>
|
||||
<string name="firstrun_notifications_message">&firstrun_notifications_message;</string>
|
||||
<string name="firstrun_notifications_subtext">&firstrun_notifications_subtext;</string>
|
||||
|
||||
<string name="firstrun_readerview_title">&firstrun_readerview_title;</string>
|
||||
<string name="firstrun_readerview_message">&firstrun_readerview_message;</string>
|
||||
<string name="firstrun_readerview_subtext">&firstrun_readerview_subtext;</string>
|
||||
|
||||
<string name="firstrun_account_title">&firstrun_account_title;</string>
|
||||
<string name="firstrun_account_message">&firstrun_account_message;</string>
|
||||
|
||||
<string name="firstrun_welcome_restricted">&onboard_start_restricted1;</string>
|
||||
|
||||
<string name="bookmarks_title">&bookmarks_title;</string>
|
||||
<string name="history_title">&history_title;</string>
|
||||
<string name="reading_list_title">&reading_list_title;</string>
|
||||
<string name="recent_tabs_title">&recent_tabs_title;</string>
|
||||
|
||||
<!-- https://support.mozilla.org/1/mobile/%VERSION%/%OS%/%LOCALE%/reading-list -->
|
||||
<string name="migrated_reading_list_url">https://support.mozilla.org/1/mobile/&formatS1;/&formatS2;/&formatS3;/reading-list</string>
|
||||
|
||||
<string name="switch_to_tab">&switch_to_tab;</string>
|
||||
|
||||
<string name="crash_reporter_title">&crash_reporter_title;</string>
|
||||
@ -375,11 +387,6 @@
|
||||
<string name="site_settings_cancel">&site_settings_cancel;</string>
|
||||
<string name="site_settings_clear">&site_settings_clear;</string>
|
||||
|
||||
<string name="reading_list_migration_title">&reading_list_migration_title;</string>
|
||||
<string name="reading_list_migration_subtext">&reading_list_migration_subtext;</string>
|
||||
<string name="reading_list_migration_goto_bookmarks">&reading_list_migration_goto_bookmarks;</string>
|
||||
<string name="reading_list_migration_bookmarks_hidden">&reading_list_migration_bookmarks_hidden;</string>
|
||||
|
||||
<string name="page_action_dropmarker_description">&page_action_dropmarker_description;</string>
|
||||
|
||||
<string name="contextmenu_open_new_tab">&contextmenu_open_new_tab;</string>
|
||||
|
@ -2,10 +2,19 @@
|
||||
/* vim: set sts=2 sw=2 et tw=80: */
|
||||
"use strict";
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
|
||||
"resource://devtools/shared/event-emitter.js");
|
||||
|
||||
// Import the android PageActions module.
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PageActions",
|
||||
"resource://gre/modules/PageActions.jsm");
|
||||
|
||||
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
|
||||
|
||||
var {
|
||||
SingletonEventManager,
|
||||
} = ExtensionUtils;
|
||||
|
||||
// WeakMap[Extension -> PageAction]
|
||||
var pageActionMap = new WeakMap();
|
||||
|
||||
@ -18,7 +27,12 @@ function PageAction(options, extension) {
|
||||
title: options.default_title || extension.name,
|
||||
icon: DEFAULT_ICON,
|
||||
id: extension.id,
|
||||
clickCallback: () => {
|
||||
this.emit("click");
|
||||
},
|
||||
};
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
|
||||
PageAction.prototype = {
|
||||
@ -54,6 +68,16 @@ extensions.on("shutdown", (type, extension) => {
|
||||
extensions.registerSchemaAPI("pageAction", null, (extension, context) => {
|
||||
return {
|
||||
pageAction: {
|
||||
onClicked: new SingletonEventManager(context, "pageAction.onClicked", fire => {
|
||||
let listener = (event) => {
|
||||
fire();
|
||||
};
|
||||
pageActionMap.get(extension).on("click", listener);
|
||||
return () => {
|
||||
pageActionMap.get(extension).off("click", listener);
|
||||
};
|
||||
}).api(),
|
||||
|
||||
show(tabId) {
|
||||
pageActionMap.get(extension).show(tabId);
|
||||
},
|
||||
|
@ -212,7 +212,6 @@
|
||||
"events": [
|
||||
{
|
||||
"name": "onClicked",
|
||||
"unsupported": true,
|
||||
"type": "function",
|
||||
"description": "Fired when a page action icon is clicked. This event will not fire if the page action has a popup.",
|
||||
"parameters": [
|
||||
|
@ -3,5 +3,6 @@
|
||||
|
||||
"globals": {
|
||||
"isPageActionShown": true,
|
||||
"clickPageAction": true,
|
||||
},
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
"use strict";
|
||||
|
||||
/* exported isPageActionShown */
|
||||
/* exported isPageActionShown clickPageAction */
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
@ -9,3 +9,7 @@ Cu.import("resource://gre/modules/PageActions.jsm");
|
||||
function isPageActionShown(extensionId) {
|
||||
return PageActions.isShown(extensionId);
|
||||
}
|
||||
|
||||
function clickPageAction(extensionId) {
|
||||
PageActions.synthesizeClick(extensionId);
|
||||
}
|
||||
|
@ -23,7 +23,10 @@ function backgroundScript() {
|
||||
browser.pageAction.show(tabId);
|
||||
browser.test.sendMessage("page-action-shown");
|
||||
|
||||
browser.test.notifyPass("page-action");
|
||||
browser.pageAction.onClicked.addListener(tab => {
|
||||
// TODO: Make sure we get the correct tab once the tabs API is supported.
|
||||
browser.test.notifyPass("pageAction-clicked");
|
||||
});
|
||||
}
|
||||
|
||||
add_task(function* test_contentscript() {
|
||||
@ -42,7 +45,9 @@ add_task(function* test_contentscript() {
|
||||
|
||||
is(isPageActionShown(extension.id), true, "The PageAction should be shown");
|
||||
|
||||
yield extension.awaitFinish("page-action");
|
||||
clickPageAction(extension.id);
|
||||
|
||||
yield extension.awaitFinish("pageAction-clicked");
|
||||
yield extension.unload();
|
||||
|
||||
is(isPageActionShown(extension.id), false, "The PageAction should be removed after unload");
|
||||
|
@ -31,7 +31,6 @@ function resolveGeckoURI(aURI) {
|
||||
return aURI;
|
||||
}
|
||||
|
||||
|
||||
var PageActions = {
|
||||
_items: { },
|
||||
|
||||
@ -70,6 +69,13 @@ var PageActions = {
|
||||
return !!this._items[id];
|
||||
},
|
||||
|
||||
synthesizeClick: function(id) {
|
||||
let item = this._items[id];
|
||||
if (item && item.clickCallback) {
|
||||
item.clickCallback();
|
||||
}
|
||||
},
|
||||
|
||||
add: function(aOptions) {
|
||||
let id = aOptions.id || uuidgen.generateUUID().toString()
|
||||
|
||||
|
@ -0,0 +1,161 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.telemetry.measurements;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mozilla.gecko.background.testhelpers.TestRunner;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Tests for the class that stores search count measurements.
|
||||
*/
|
||||
@RunWith(TestRunner.class)
|
||||
public class TestSearchCountMeasurements {
|
||||
|
||||
private SharedPreferences sharedPrefs;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
sharedPrefs = RuntimeEnvironment.application.getSharedPreferences(
|
||||
TestSearchCountMeasurements.class.getSimpleName(), Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
private void assertNewValueInsertedNoIncrementedValues(final int expectedKeyCount) {
|
||||
assertEquals("Shared prefs key count has incremented", expectedKeyCount, sharedPrefs.getAll().size());
|
||||
assertTrue("Shared prefs still contains non-incremented initial value", sharedPrefs.getAll().containsValue(1));
|
||||
assertFalse("Shared prefs has not incremented any values", sharedPrefs.getAll().containsValue(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncrementSearchCanRecreateEngineAndWhere() throws Exception {
|
||||
final String expectedIdentifier = "google";
|
||||
final String expectedWhere = "suggestbar";
|
||||
|
||||
SearchCountMeasurements.incrementSearch(sharedPrefs, expectedIdentifier, expectedWhere);
|
||||
assertFalse("Shared prefs has some values", sharedPrefs.getAll().isEmpty());
|
||||
assertTrue("Shared prefs contains initial value", sharedPrefs.getAll().containsValue(1));
|
||||
|
||||
boolean foundEngine = false;
|
||||
for (final String key : sharedPrefs.getAll().keySet()) {
|
||||
// We could try to match the exact key, but that's more fragile.
|
||||
if (key.contains(expectedIdentifier) && key.contains(expectedWhere)) {
|
||||
foundEngine = true;
|
||||
}
|
||||
}
|
||||
assertTrue("SharedPrefs keyset contains enough info to recreate engine & where", foundEngine);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncrementSearchCalledMultipleTimesSameEngine() throws Exception {
|
||||
final String engineIdentifier = "whatever";
|
||||
final String where = "wherever";
|
||||
|
||||
SearchCountMeasurements.incrementSearch(sharedPrefs, engineIdentifier, where);
|
||||
assertFalse("Shared prefs has some values", sharedPrefs.getAll().isEmpty());
|
||||
assertTrue("Shared prefs contains initial value", sharedPrefs.getAll().containsValue(1));
|
||||
|
||||
// The initial key count storage saves metadata so we can't verify the number of keys is only 1. However,
|
||||
// we assume subsequent calls won't add additional metadata and use it to verify the key count.
|
||||
final int keyCountAfterFirst = sharedPrefs.getAll().size();
|
||||
for (int i = 2; i <= 3; ++i) {
|
||||
SearchCountMeasurements.incrementSearch(sharedPrefs, engineIdentifier, where);
|
||||
assertEquals("Shared prefs key count has not changed", keyCountAfterFirst, sharedPrefs.getAll().size());
|
||||
assertTrue("Shared prefs incremented", sharedPrefs.getAll().containsValue(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncrementSearchCalledMultipleTimesSameEngineDifferentWhere() throws Exception {
|
||||
final String engineIdenfitier = "whatever";
|
||||
|
||||
SearchCountMeasurements.incrementSearch(sharedPrefs, engineIdenfitier, "one place");
|
||||
assertFalse("Shared prefs has some values", sharedPrefs.getAll().isEmpty());
|
||||
assertTrue("Shared prefs contains initial value", sharedPrefs.getAll().containsValue(1));
|
||||
|
||||
// The initial key count storage saves metadata so we can't verify the number of keys is only 1. However,
|
||||
// we assume subsequent calls won't add additional metadata and use it to verify the key count.
|
||||
final int keyCountAfterFirst = sharedPrefs.getAll().size();
|
||||
for (int i = 1; i <= 2; ++i) {
|
||||
SearchCountMeasurements.incrementSearch(sharedPrefs, engineIdenfitier, "another place " + i);
|
||||
assertNewValueInsertedNoIncrementedValues(keyCountAfterFirst + i);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncrementSearchCalledMultipleTimesDifferentEngines() throws Exception {
|
||||
final String where = "wherever";
|
||||
|
||||
SearchCountMeasurements.incrementSearch(sharedPrefs, "steam engine", where);
|
||||
assertFalse("Shared prefs has some values", sharedPrefs.getAll().isEmpty());
|
||||
assertTrue("Shared prefs contains initial value", sharedPrefs.getAll().containsValue(1));
|
||||
|
||||
// The initial key count storage saves metadata so we can't verify the number of keys is only 1. However,
|
||||
// we assume subsequent calls won't add additional metadata and use it to verify the key count.
|
||||
final int keyCountAfterFirst = sharedPrefs.getAll().size();
|
||||
for (int i = 1; i <= 2; ++i) {
|
||||
SearchCountMeasurements.incrementSearch(sharedPrefs, "combustion engine" + i, where);
|
||||
assertNewValueInsertedNoIncrementedValues(keyCountAfterFirst + i);
|
||||
}
|
||||
}
|
||||
|
||||
@Test // assumes the format saved in SharedPrefs to store test data
|
||||
public void testGetAndZeroSearchDeletesPrefs() throws Exception {
|
||||
assertTrue("Shared prefs is empty", sharedPrefs.getAll().isEmpty());
|
||||
|
||||
final SharedPreferences.Editor editor = sharedPrefs.edit();
|
||||
final Set<String> engineKeys = new HashSet<>(Arrays.asList("whatever.yeah", "lol.what"));
|
||||
editor.putStringSet(SearchCountMeasurements.PREF_SEARCH_KEYSET, engineKeys);
|
||||
for (final String key : engineKeys) {
|
||||
editor.putInt(getEngineSearchCountKey(key), 1);
|
||||
}
|
||||
editor.apply();
|
||||
assertFalse("Shared prefs is not empty after test data inserted", sharedPrefs.getAll().isEmpty());
|
||||
|
||||
SearchCountMeasurements.getAndZeroSearch(sharedPrefs);
|
||||
assertTrue("Shared prefs is empty after zero", sharedPrefs.getAll().isEmpty());
|
||||
}
|
||||
|
||||
@Test // assumes the format saved in SharedPrefs to store test data
|
||||
public void testGetAndZeroSearchVerifyReturnedData() throws Exception {
|
||||
final HashMap<String, Integer> expected = new HashMap<>();
|
||||
expected.put("steamengine.here", 1337);
|
||||
expected.put("combustionengine.there", 10);
|
||||
|
||||
final SharedPreferences.Editor editor = sharedPrefs.edit();
|
||||
editor.putStringSet(SearchCountMeasurements.PREF_SEARCH_KEYSET, expected.keySet());
|
||||
for (final String key : expected.keySet()) {
|
||||
editor.putInt(SearchCountMeasurements.getEngineSearchCountKey(key), expected.get(key));
|
||||
}
|
||||
editor.apply();
|
||||
assertFalse("Shared prefs is not empty after test data inserted", sharedPrefs.getAll().isEmpty());
|
||||
|
||||
final ExtendedJSONObject actual = SearchCountMeasurements.getAndZeroSearch(sharedPrefs);
|
||||
assertEquals("Returned JSON contains number of items inserted", expected.size(), actual.size());
|
||||
for (final String key : expected.keySet()) {
|
||||
assertEquals("Returned JSON contains inserted value", expected.get(key), (Integer) actual.getIntegerSafely(key));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAndZeroSearchNoData() throws Exception {
|
||||
final ExtendedJSONObject actual = SearchCountMeasurements.getAndZeroSearch(sharedPrefs);
|
||||
assertEquals("Returned json is empty", 0, actual.size());
|
||||
}
|
||||
|
||||
private String getEngineSearchCountKey(final String engineWhereStr) {
|
||||
return SearchCountMeasurements.getEngineSearchCountKey(engineWhereStr);
|
||||
}
|
||||
}
|
@ -120,6 +120,14 @@ public class TestTelemetryJSONFilePingStore {
|
||||
}
|
||||
}
|
||||
|
||||
@Test // regression test: bug 1272817
|
||||
public void testGetAllPingsHandlesEmptyFiles() throws Exception {
|
||||
final int expectedPingCount = 3;
|
||||
writeTestPingsToStore(expectedPingCount, "whatever");
|
||||
assertTrue("Empty file is created", testStore.getPingFile(getDocID()).createNewFile());
|
||||
assertEquals("Returned pings only contains valid files", expectedPingCount, testStore.getAllPings().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMaybePrunePingsDoesNothingIfAtMax() throws Exception {
|
||||
final int pingCount = TelemetryJSONFilePingStore.MAX_PING_COUNT;
|
||||
|
@ -33,13 +33,11 @@ abstract class AboutHomeTest extends PixelTest {
|
||||
HISTORY,
|
||||
TOP_SITES,
|
||||
BOOKMARKS,
|
||||
READING_LIST
|
||||
};
|
||||
|
||||
private final ArrayList<String> aboutHomeTabs = new ArrayList<String>() {{
|
||||
add("TOP_SITES");
|
||||
add("BOOKMARKS");
|
||||
add("READING_LIST");
|
||||
}};
|
||||
|
||||
|
||||
|
@ -117,7 +117,6 @@ public class StringHelper {
|
||||
public final String HISTORY_LABEL;
|
||||
public final String TOP_SITES_LABEL;
|
||||
public final String BOOKMARKS_LABEL;
|
||||
public final String READING_LIST_LABEL;
|
||||
public final String TODAY_LABEL;
|
||||
|
||||
// Desktop default bookmarks folders
|
||||
@ -290,7 +289,6 @@ public class StringHelper {
|
||||
HISTORY_LABEL = res.getString(R.string.home_history_title);
|
||||
TOP_SITES_LABEL = res.getString(R.string.home_top_sites_title);
|
||||
BOOKMARKS_LABEL = res.getString(R.string.bookmarks_title);
|
||||
READING_LIST_LABEL = res.getString(R.string.reading_list_title);
|
||||
TODAY_LABEL = res.getString(R.string.history_today_section);
|
||||
|
||||
BOOKMARKS_UP_TO = res.getString(R.string.home_move_back_to_filter);
|
||||
|
@ -35,8 +35,7 @@ public class AboutHomeComponent extends BaseComponent {
|
||||
PanelType.TOP_SITES,
|
||||
PanelType.BOOKMARKS,
|
||||
PanelType.COMBINED_HISTORY,
|
||||
PanelType.RECENT_TABS,
|
||||
PanelType.READING_LIST
|
||||
PanelType.RECENT_TABS
|
||||
);
|
||||
|
||||
// The percentage of the panel to swipe between 0 and 1. This value was set through
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
package org.mozilla.gecko.tests;
|
||||
|
||||
import org.mozilla.gecko.home.HomeConfig;
|
||||
import org.mozilla.gecko.home.HomeConfig.PanelType;
|
||||
import org.mozilla.gecko.tests.helpers.DeviceHelper;
|
||||
import org.mozilla.gecko.tests.helpers.GeckoHelper;
|
||||
@ -27,9 +26,6 @@ public class testAboutHomePageNavigation extends UITest {
|
||||
mAboutHome.swipeToPanelOnRight();
|
||||
mAboutHome.assertCurrentPanel(PanelType.BOOKMARKS);
|
||||
|
||||
mAboutHome.swipeToPanelOnRight();
|
||||
mAboutHome.assertCurrentPanel(PanelType.READING_LIST);
|
||||
|
||||
// Ideally these helpers would just be their own tests. However, by keeping this within
|
||||
// one method, we're saving test setUp and tearDown resources.
|
||||
if (DeviceHelper.isTablet()) {
|
||||
@ -47,9 +43,6 @@ public class testAboutHomePageNavigation extends UITest {
|
||||
mAboutHome.swipeToPanelOnRight();
|
||||
mAboutHome.assertCurrentPanel(PanelType.COMBINED_HISTORY);
|
||||
|
||||
mAboutHome.swipeToPanelOnLeft();
|
||||
mAboutHome.assertCurrentPanel(PanelType.READING_LIST);
|
||||
|
||||
mAboutHome.swipeToPanelOnLeft();
|
||||
mAboutHome.assertCurrentPanel(PanelType.BOOKMARKS);
|
||||
|
||||
@ -63,9 +56,6 @@ public class testAboutHomePageNavigation extends UITest {
|
||||
|
||||
private void helperTestPhone() {
|
||||
// Edge case.
|
||||
mAboutHome.swipeToPanelOnRight();
|
||||
mAboutHome.assertCurrentPanel(PanelType.READING_LIST);
|
||||
|
||||
mAboutHome.swipeToPanelOnLeft();
|
||||
mAboutHome.assertCurrentPanel(PanelType.BOOKMARKS);
|
||||
|
||||
|
@ -34,7 +34,7 @@ public class testShareLink extends AboutHomeTest {
|
||||
blockForGeckoReady();
|
||||
|
||||
// FIXME: This is a temporary hack workaround for a permissions problem.
|
||||
openAboutHomeTab(AboutHomeTabs.READING_LIST);
|
||||
openAboutHomeTab(AboutHomeTabs.HISTORY);
|
||||
|
||||
inputAndLoadUrl(url);
|
||||
verifyUrlBarTitle(url); // Waiting for page title to ensure the page is loaded
|
||||
|
@ -6,6 +6,7 @@
|
||||
<ShortName>Amazon.com</ShortName>
|
||||
<InputEncoding>ISO-8859-1</InputEncoding>
|
||||
<Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAAAXNSR0IArs4c6QAACixJREFUeAHtXQ1MldcZfuRXlIr4A1MoYAf+xKBswlKmdTJ1Q7RZdc5kM7XWsXaGWk2WTDPc0qlzOmZqnHGZoxrntuiydB1UXZSRqS1h03WTVdDa+osov/I3qAicve93ufTC/eHc673f+ejOmxzu933n/c77nuc5/+fcC/CJjKDLXArlFFopCB38igFjytgyxoz1APkM3ZVQ0KCbgwFjzZgbwmxo8M0B3rGAM+YjgunPdyhspKDFXASeInPVTMABCvHm2tbW+hCI5eaHO4cnNCRKEGhjArhd0qIIgSBFdrXZPgQ0AYqLgiZAE6AYAcXmdQ3QBChGQLF5XQM0AYoRUGxe1wBNgGIEFJvXNUAToBgBxeZ1DdAEKEZAsfkQxfZ9Nh8SEoKwsDD09PTg4cOHPqej+sVhQcC4ceOwdOlSZGRkICUlxQhJSUkIDuYNPaCpqQm3bt3CzZs3UVlZiVOnTqG8vNwgRzXAMvYdN4otdT1//nxBYIpHjx4Jb6W2tlbk5+eLqKgoS+WJCBnsj9ODwQqm38fHx4uSkhJvMXep39zcLNauXWt6HlwA7c4HaxGwZMkS0dDQ4BLMx3m4b98+dwCofm4dAhYuXOhTcyNLzObNm1WD7cq+NQiYNm2a4OYikNLZ2SmSk5NdgaDymTUIOHnyZCCx70/70KFDKsF2sm2JYyk02sHZs2dlRmyoqanB4cOHcfnyZbS2tmLq1KlYs2YN0tLSpN5vbGxEbGyspYaoTqx40YP75d2jR4/2l1BPFzt37hQjR450shkUFCR27drl6dUBcenp6U5pmJ1nB3tqmyAGr76+fgBArm4KCgqGBI0mX65edXq2YsWKIdNyACigusoX41JTUzFhwgTKr3uhzhnbtm1zr9AXc+TIkSF1WIHmGVJ6ZigpJ+DatWtG+9/d3e02v8eOHUNbW5vbeHtERUWF/dLjJy9tWEWUrwV1dHRgwYIFxrpOXFwcEhMTMWXKFCPwek9oaCi2b98uhVd7e7uUHi/kWUUs4wmvat6+fdsI58+f9wkfT7XIMUHqdxxvlV5bxxM/wEC9rVQqsnpSiT2mkmVqgEw+uDnicf/MmTMxY8YMozOdPHkyOEyaNAkTJ06UScZSOpYnIDMzEzk5OcjOzjYmW1Zqv/3BpCUJCA8Px+rVq7Fp0ybwMPXTLJYjYN68eSgsLAQtzn2ace/Pm6U64S1btuDcuXP/N+AzC5apAVu3bpUe77PjPJLh/V9enKOlDNAmjjFnWL9+PUd7FCuNgtjRgK51yKS/aNEiwkRO6urqxMaNG0VMTIyT39OnT5dKZMeOHU7vyvgZCB3lNSAiIgIHDx6kvA0tpaWlWL58ubEM7UrbfkrCVZxVnyknYOXKlcayw1AAXbhwAcuWLQPtarlVHY4EKO+E161b5xZQxwg6YuIRfNYdjnMEpQSMHj0avBs2lNy5cwdnzpwZSm1YzoSVEsCTLJmFMdllZj41JyPR0dEyaqboKCUgISFBKpOe2n3HBPj4oozwsrdVRCkBtL8rhYNMieWZ8+LFi6XSmzNnDkaM4PMI6kUpAXTmUwoB2kQ3JlmelGnDvv+wric9juMtSVmyhkrLH/HKJiVZWVlSEydW2rBhg1s/aQlDOh274qVLlwQtb7tNk4A1K840Q04Zos140dvba8fE42dXV5egFVJBTUd/OrQPIPbu3evxPU+Re/bs6U/LRMAH21RHAGeaRjieMHKKq6qqErRJL06fPi3oixlO8d4+sMAZIbUEcNOiUmjkNLhEmn2vloDIyEhx7949v3Fw8eJFo4bIJLh7926zwXZlTy0B3AzRdqN0X+AJ2OLiYkGza8GkXr161a0qnZ4QtGztCgwVz9QTwCTk5uYKBsZX2b9/v6DFuH4A6UyRqK6udkqODngJCzQ7/X5S3q1BAPvB+wJXrlxxAs3Tg7KyMsHfJXOVDzo5MeDcKa0pidmzZ7vUdXyfjg2J1DiI59IgXsmCePXLEHn0mZMKkTDOv3jxdJAd8lqSY4DrDUBvr9evenyBj56sWrXKCPSNGfCCnaPQsBXUvIAPbxUVFeHEiROO0U7XPOs9cOCAsXOWl5dnfDop9T1IiQW+/xXga3TSfWKkOy3gj+8B3/iV+3hvYnwm4P3XbL9Anf8W8Na/vTHpnS4f3OXzPnzssKWlxTgjSjXCu0QktHetAL5HKxkhEmsDnTSBH/WKRKISKj4TkDge+BNtv37uSeDvN4D8PwN/rZKwaFGVl2lVvPVj4E4Tfe+4A2im0Eb3wUTIdPqZ7Re/CLz0jM35FtoTGrvJPxnxmQA2HxEGvLEG+GaGzZmKu8AvSoHf/QPo7PKPg1ZJZewo4MHrNm/OXQO+9HP/eCZR4dwbYpC/VQhsfpP6AmoVZtEq76+fB+7uBn72dSDJ87F/9wkrjmG/f5ADHH8JGN/XF0QTAXZ59yP71eN/PlYNcDSfReeo3ngBmEJNk12YlGI6sv8b+rcFpyuBdqrSVpXYMcCqdFttznzK5iX7n7ETeO828PzTlI8Xbc+X7gdO/sc/OfEbAezOKGqSfvIcQMM2BHHKDtLVA5z9AHibCHmbnL9e7xCp6DKaBljL0wj0LwBcgIIdfL7RCLz8W+AMFRyWQmpqvz0XuEnPU34IdFN+/CF+JcDu0NNUgn65Gkjz8E2gqvs2Mt75EPgnlbC7D+xvB+6TxvCYmwzMozD3swCN9Z0KSncv8HoJ8Fox0NHXj4XSb4LcK6DmiAh79bitn/OXlwEhgJ3jDSeu0j9+FphG4+uhpLaNiLhlq+4f1gFcAm80ADXNQA+B4o3EUHOSNJ5KaozNNo9iuFA86WErmFobFF0CflQEVFQPtEYTMmPEV9NCaW79hJiBWr7dBYwAuzs8jHshkzrqbGAqAeKtcDvc9F+gjgh60GHLvL1kcjMXFgJEhtM/QKDdzbER9I9ZougZlVhZ6aH0j18AfvoX4H0axbkSHm7TLNgY+ZRfd6Xh+7OAE2B3jWsEt7PfnQ9wieJqrVLutwJ/uAjsKwU+qnfvCfcT9wuAvN9TP/COez1fY0wjwNFBHnFwx/fsLOAZao/NIqORatKb/wKOUYn/Gw0IZJZRRoYCcxKBd6mvCoQoIcAxI1HUbHx1JpBNIZ0yOmOS3HKAYxrurhvagTJqMspo3M4Alt/w3+jFnU1vnysnYLDDXOJmxQOfT7B1oHFjgTjqPGOfAMYQWWOorQ+ndp/7ho8f0XIB/VxcIwFdT4E7bW5OOHCH/kHt4NStd285AmQg4o7d25GRTLoqdIYlASqACpRNKktaVCKgCVCJPtnWBGgCFCOg2LyuAZoAxQgoNq9rgCZAMQKKzesaoAlQjIBi87oGaAIUI6DYvK4BmgDFCCg2zzWAtru1KEKgjQmoVGRcmyXsmYBCjYQyBAzseVeshALtsupgIgaMOWNvCJ0d0yQQBmYVQgafMR8gzEYuhXIKdGzJNGfMyrRqO4wpY8sY95f8/wEKvBLprcz3zwAAAABJRU5ErkJggg==</Image>
|
||||
<Url type="application/x-suggestions+json" method="GET" template="https://completion.amazon.com/search/complete?q={searchTerms}&search-alias=aps&mkt=1"/>
|
||||
<Url type="text/html" method="GET" template="https://www.amazon.com/gp/aw/s">
|
||||
<Param name="k" value="{searchTerms}"/>
|
||||
<Param name="sourceid" value="Mozilla-search"/>
|
||||
|
@ -4,7 +4,9 @@
|
||||
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import __main__
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import mozpack.path as mozpath
|
||||
import os
|
||||
@ -24,24 +26,17 @@ from mach.decorators import (
|
||||
Command,
|
||||
)
|
||||
|
||||
ESLINT_VERSION = "2.9.0"
|
||||
ESLINT_PACKAGES = [
|
||||
"eslint@2.9.0",
|
||||
"eslint-plugin-html@1.4.0",
|
||||
"eslint-plugin-mozilla@0.0.3",
|
||||
"eslint-plugin-react@4.2.3"
|
||||
]
|
||||
|
||||
ESLINT_NOT_FOUND_MESSAGE = '''
|
||||
Could not find eslint! We looked at the --binary option, at the ESLINT
|
||||
environment variable, and then at your path. Install eslint and needed plugins
|
||||
with
|
||||
|
||||
mach eslint --setup
|
||||
|
||||
and try again.
|
||||
'''.strip()
|
||||
|
||||
ESLINT_OUTDATED_MESSAGE = '''
|
||||
eslint in your path is outdated.
|
||||
path: %(binary)s
|
||||
version: %(version)s
|
||||
Expected version: %(min_version)s
|
||||
Update eslint with
|
||||
environment variable, and then at your local node_modules path. Please Install
|
||||
eslint and needed plugins with:
|
||||
|
||||
mach eslint --setup
|
||||
|
||||
@ -216,6 +211,8 @@ class MachCommands(MachCommandBase):
|
||||
def eslint(self, setup, ext=None, binary=None, args=None):
|
||||
'''Run eslint.'''
|
||||
|
||||
module_path = self.get_eslint_module_path()
|
||||
|
||||
# eslint requires at least node 4.2.3
|
||||
nodePath = self.getNodeOrNpmPath("node", LooseVersion("4.2.3"))
|
||||
if not nodePath:
|
||||
@ -224,24 +221,35 @@ class MachCommands(MachCommandBase):
|
||||
if setup:
|
||||
return self.eslint_setup()
|
||||
|
||||
npmPath = self.getNodeOrNpmPath("npm")
|
||||
if not npmPath:
|
||||
return 1
|
||||
|
||||
if self.eslintModuleHasIssues():
|
||||
install = self._prompt_yn("\nContinuing will automatically fix "
|
||||
"these issues. Would you like to "
|
||||
"continue")
|
||||
if install:
|
||||
self.eslint_setup()
|
||||
else:
|
||||
return 1
|
||||
|
||||
# Valid binaries are:
|
||||
# - Any provided by the binary argument.
|
||||
# - Any pointed at by the ESLINT environmental variable.
|
||||
# - Those provided by mach eslint --setup.
|
||||
#
|
||||
# eslint --setup installs some mozilla specific plugins and installs
|
||||
# all node modules locally. This is the preferred method of
|
||||
# installation.
|
||||
|
||||
if not binary:
|
||||
binary = os.environ.get('ESLINT', None)
|
||||
|
||||
if not binary:
|
||||
try:
|
||||
binary = which.which('eslint')
|
||||
except which.WhichError:
|
||||
npmPath = self.getNodeOrNpmPath("npm")
|
||||
if npmPath:
|
||||
try:
|
||||
output = subprocess.check_output([npmPath, "bin", "-g"],
|
||||
stderr=subprocess.STDOUT)
|
||||
if output:
|
||||
base = output.split("\n")[0].strip()
|
||||
binary = os.path.join(base, "eslint")
|
||||
if not os.path.isfile(binary):
|
||||
binary = None
|
||||
except (subprocess.CalledProcessError, OSError):
|
||||
pass
|
||||
binary = os.path.join(module_path, "node_modules", ".bin", "eslint")
|
||||
if not os.path.isfile(binary):
|
||||
binary = None
|
||||
|
||||
if not binary:
|
||||
print(ESLINT_NOT_FOUND_MESSAGE)
|
||||
@ -250,13 +258,6 @@ class MachCommands(MachCommandBase):
|
||||
self.log(logging.INFO, 'eslint', {'binary': binary, 'args': args},
|
||||
'Running {binary}')
|
||||
|
||||
version_str = subprocess.check_output([binary, "--version"],
|
||||
stderr=subprocess.STDOUT)
|
||||
version = LooseVersion(version_str.lstrip('v'))
|
||||
if version < LooseVersion(ESLINT_VERSION):
|
||||
print (ESLINT_OUTDATED_MESSAGE % {"binary": binary, "version": version_str, "min_version": ESLINT_VERSION})
|
||||
return 1
|
||||
|
||||
args = args or ['.']
|
||||
|
||||
cmd_args = [binary,
|
||||
@ -285,44 +286,48 @@ class MachCommands(MachCommandBase):
|
||||
guide you through an interactive wizard helping you configure
|
||||
eslint for optimal use on Mozilla projects.
|
||||
"""
|
||||
orig_cwd = os.getcwd()
|
||||
sys.path.append(os.path.dirname(__file__))
|
||||
|
||||
module_path = self.get_eslint_module_path()
|
||||
|
||||
# npm sometimes fails to respect cwd when it is run using check_call so
|
||||
# we manually switch folders here instead.
|
||||
os.chdir(module_path)
|
||||
|
||||
npmPath = self.getNodeOrNpmPath("npm")
|
||||
if not npmPath:
|
||||
return 1
|
||||
|
||||
# Install eslint.
|
||||
# Note that that's the version currently compatible with the mozilla
|
||||
# eslint plugin.
|
||||
success = self.callProcess("eslint",
|
||||
[npmPath, "install", "eslint@%s" % ESLINT_VERSION, "-g"])
|
||||
if not success:
|
||||
return 1
|
||||
# Install eslint and necessary plugins.
|
||||
for pkg in ESLINT_PACKAGES:
|
||||
name, version = pkg.split("@")
|
||||
success = False
|
||||
|
||||
# Install eslint-plugin-mozilla.
|
||||
success = self.callProcess("eslint-plugin-mozilla",
|
||||
[npmPath, "link"],
|
||||
"testing/eslint-plugin-mozilla")
|
||||
if not success:
|
||||
return 1
|
||||
if self.node_package_installed(pkg, cwd=module_path):
|
||||
success = True
|
||||
else:
|
||||
if pkg.startswith("eslint-plugin-mozilla"):
|
||||
cmd = [npmPath, "install",
|
||||
os.path.join(module_path, "eslint-plugin-mozilla")]
|
||||
else:
|
||||
cmd = [npmPath, "install", pkg]
|
||||
|
||||
# Install eslint-plugin-html.
|
||||
success = self.callProcess("eslint-plugin-html",
|
||||
[npmPath, "install", "eslint-plugin-html@1.4.0", "-g"])
|
||||
if not success:
|
||||
return 1
|
||||
print("Installing %s v%s using \"%s\"..."
|
||||
% (name, version, " ".join(cmd)))
|
||||
success = self.callProcess(pkg, cmd)
|
||||
|
||||
# Install eslint-plugin-react.
|
||||
success = self.callProcess("eslint-plugin-react",
|
||||
[npmPath, "install", "eslint-plugin-react@4.2.3", "-g"])
|
||||
if not success:
|
||||
return 1
|
||||
if not success:
|
||||
return 1
|
||||
|
||||
eslint_path = os.path.join(module_path, "node_modules", ".bin", "eslint")
|
||||
|
||||
print("\nESLint and approved plugins installed successfully!")
|
||||
print("\nNOTE: Your local eslint binary is at %s\n" % eslint_path)
|
||||
|
||||
os.chdir(orig_cwd)
|
||||
|
||||
def callProcess(self, name, cmd, cwd=None):
|
||||
print("\nInstalling %s using \"%s\"..." % (name, " ".join(cmd)))
|
||||
|
||||
try:
|
||||
with open(os.devnull, "w") as fnull:
|
||||
subprocess.check_call(cmd, cwd=cwd, stdout=fnull)
|
||||
@ -336,6 +341,74 @@ class MachCommands(MachCommandBase):
|
||||
|
||||
return True
|
||||
|
||||
def eslintModuleHasIssues(self):
|
||||
print("Checking eslint and modules...")
|
||||
|
||||
has_issues = False
|
||||
npmPath = self.getNodeOrNpmPath("npm")
|
||||
module_path = self.get_eslint_module_path()
|
||||
|
||||
for pkg in ESLINT_PACKAGES:
|
||||
name, req_version = pkg.split("@")
|
||||
|
||||
try:
|
||||
with open(os.devnull, "w") as fnull:
|
||||
global_install = subprocess.check_output([npmPath, "ls", "--json", name, "-g"],
|
||||
stderr=fnull)
|
||||
info = json.loads(global_install)
|
||||
global_version = info["dependencies"][name]["version"]
|
||||
except subprocess.CalledProcessError:
|
||||
global_version = None
|
||||
|
||||
try:
|
||||
with open(os.devnull, "w") as fnull:
|
||||
local_install = subprocess.check_output([npmPath, "ls", "--json", name],
|
||||
cwd=module_path, stderr=fnull)
|
||||
info = json.loads(local_install)
|
||||
local_version = info["dependencies"][name]["version"]
|
||||
except subprocess.CalledProcessError:
|
||||
local_version = None
|
||||
|
||||
if global_version:
|
||||
if name == "eslint-plugin-mozilla":
|
||||
print("%s should never be installed globally. This global "
|
||||
"module will be removed." % name)
|
||||
has_issues = True
|
||||
else:
|
||||
print("%s is installed globally. This global module will "
|
||||
"be ignored. We recommend uninstalling it using "
|
||||
"sudo %s remove %s -g" % (name, npmPath, name))
|
||||
if local_version:
|
||||
if local_version != req_version:
|
||||
print("%s v%s is installed locally but is not the "
|
||||
"required version (v%s). This module will be "
|
||||
"reinstalled so that the versions match." %
|
||||
(name, local_version, req_version))
|
||||
has_issues = True
|
||||
else:
|
||||
print("%s v%s is not installed locally and only local modules "
|
||||
"are valid. This module will be installed locally."
|
||||
% (name, req_version))
|
||||
has_issues = True
|
||||
|
||||
return has_issues
|
||||
|
||||
def node_package_installed(self, package_name="", globalInstall=False, cwd=None):
|
||||
try:
|
||||
npmPath = self.getNodeOrNpmPath("npm")
|
||||
|
||||
cmd = [npmPath, "ls", "--parseable", package_name]
|
||||
|
||||
if globalInstall:
|
||||
cmd.append("-g")
|
||||
|
||||
with open(os.devnull, "w") as fnull:
|
||||
subprocess.check_call(cmd, stdout=fnull, stderr=fnull, cwd=cwd)
|
||||
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
def getPossibleNodePathsWin(self):
|
||||
"""
|
||||
Return possible nodejs paths on Windows.
|
||||
@ -399,3 +472,30 @@ class MachCommands(MachCommandBase):
|
||||
return True
|
||||
except (subprocess.CalledProcessError, OSError):
|
||||
return False
|
||||
|
||||
def get_project_root(self):
|
||||
fullpath = os.path.abspath(sys.modules['__main__'].__file__)
|
||||
return os.path.dirname(fullpath)
|
||||
|
||||
def get_eslint_module_path(self):
|
||||
return os.path.join(self.get_project_root(), "testing", "eslint")
|
||||
|
||||
def _prompt_yn(self, msg):
|
||||
if not sys.stdin.isatty():
|
||||
return False
|
||||
|
||||
print('%s? [Y/n]' % msg)
|
||||
|
||||
while True:
|
||||
choice = raw_input().lower().strip()
|
||||
|
||||
if not choice:
|
||||
return True
|
||||
|
||||
if choice in ('y', 'yes'):
|
||||
return True
|
||||
|
||||
if choice in ('n', 'no'):
|
||||
return False
|
||||
|
||||
print('Must reply with one of {yes, no, y, n}.')
|
||||
|
@ -165,6 +165,11 @@ TabStore.prototype = {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (current.url.length >= (MAX_UPLOAD_BYTES - 1000)) {
|
||||
this._log.trace("Skipping over-long URL.");
|
||||
continue;
|
||||
}
|
||||
|
||||
// The element at `index` is the current page. Previous URLs were
|
||||
// previously visited URLs; subsequent URLs are in the 'forward' stack,
|
||||
// which we can't represent in Sync, so we truncate here.
|
||||
|
@ -1,6 +1,7 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/engines/tabs.js");
|
||||
Cu.import("resource://services-sync/record.js");
|
||||
Cu.import("resource://services-sync/service.js");
|
||||
@ -23,11 +24,12 @@ add_test(function test_getOpenURLs() {
|
||||
_("Test getOpenURLs.");
|
||||
let [engine, store] = getMocks();
|
||||
|
||||
let urls = ["http://bar.com", "http://foo.com", "http://foobar.com"];
|
||||
function threeURLs() {
|
||||
let superLongURL = "http://" + (new Array(MAX_UPLOAD_BYTES).join("w")) + ".com/";
|
||||
let urls = ["http://bar.com", "http://foo.com", "http://foobar.com", superLongURL];
|
||||
function fourURLs() {
|
||||
return urls.pop();
|
||||
}
|
||||
store.getWindowEnumerator = mockGetWindowEnumerator.bind(this, threeURLs, 1, 3);
|
||||
store.getWindowEnumerator = mockGetWindowEnumerator.bind(this, fourURLs, 1, 4);
|
||||
|
||||
let matches;
|
||||
|
||||
@ -40,6 +42,10 @@ add_test(function test_getOpenURLs() {
|
||||
matches = openurlsset.has("http://barfoo.com");
|
||||
ok(!matches);
|
||||
|
||||
_(" test matching works (too long)");
|
||||
matches = openurlsset.has(superLongURL);
|
||||
ok(!matches);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
|
@ -6,9 +6,11 @@ WORKDIR /home/worker
|
||||
|
||||
# install necessary npm packages
|
||||
RUN npm install -g taskcluster-vcs@2.3.12
|
||||
RUN npm install -g eslint@2.9.0
|
||||
RUN npm install -g eslint-plugin-html@1.4.0
|
||||
RUN npm install -g eslint-plugin-react@4.2.3
|
||||
|
||||
# Install tooltool directly from github.
|
||||
RUN mkdir /build
|
||||
ADD https://raw.githubusercontent.com/mozilla/build-tooltool/master/tooltool.py /build/tooltool.py
|
||||
RUN chmod +rx /build/tooltool.py
|
||||
|
||||
# Set variable normally configured at login, by the shells parent process, these
|
||||
# are taken from GNU su manual
|
||||
|