Merge m-c to inbound, a=merge

--HG--
rename : testing/eslint-plugin-mozilla/lib/rules/.eslintrc => testing/eslint/eslint-plugin-mozilla/lib/rules/.eslintrc
This commit is contained in:
Wes Kocher 2016-05-17 14:17:19 -07:00
commit 3ac80a6ae8
140 changed files with 2662 additions and 830 deletions

4
.gitignore vendored
View File

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

View File

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

View File

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

View File

@ -505,6 +505,7 @@ tags = mcb
[browser_readerMode.js]
support-files =
readerModeArticle.html
readerModeArticleHiddenNodes.html
[browser_readerMode_hidden_nodes.js]
support-files =
readerModeArticleHiddenNodes.html

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -454,7 +454,9 @@ CheckHeapTracer::CheckHeapTracer(JSRuntime* rt)
failures(0),
parentIndex(-1)
{
#ifdef DEBUG
setCheckEdges(false);
#endif
}
bool

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,5 +3,6 @@
"globals": {
"isPageActionShown": true,
"clickPageAction": true,
},
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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}&amp;search-alias=aps&amp;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"/>

View File

@ -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}.')

View File

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

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More