Merge fx-team to m-c. a=merge

This commit is contained in:
Ryan VanderMeulen 2015-07-31 15:03:32 -04:00
commit c83013fa4f
44 changed files with 568 additions and 103 deletions

View File

@ -239,10 +239,13 @@ let gFxAccounts = {
// fxAccountsEnabled is false because migration has not yet finished. In
// that case, hide the button. We'll get another notification with a null
// state once migration is complete.
this.panelUIFooter.hidden = true;
this.panelUIFooter.removeAttribute("fxastatus");
return;
}
this.panelUIFooter.hidden = false;
// Make sure the button is disabled in customization mode.
if (this._inCustomizationMode) {
this.panelUIStatus.setAttribute("disabled", "true");

View File

@ -1,6 +1,10 @@
/* 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/. */
html {
font-size: 10px;
font-family: menu;
}
body {
background: none;
@ -177,7 +181,7 @@ body {
display: block;
width: 100%;
margin-top: 10px;
font-size: 10px;
font-size: 1rem;
color: #777;
}
@ -211,13 +215,20 @@ body {
font-weight: bold;
color: #999;
padding: .5rem 0;
height: 3rem;
line-height: 3rem;
}
.new-room-view > .context-checkbox-checked {
background-color: #dbf7ff;
}
.new-room-view > .context {
margin: .5rem 0 0;
background-color: #dbf7ff;
margin: .5rem 0 .5rem;
border-radius: 3px 3px 0 0;
padding: .5rem;
padding: .5rem 1rem ;
margin-left: -1rem;
margin-right: -1rem;
}
.new-room-view > .context > .context-enabled {
@ -230,11 +241,13 @@ body {
}
.new-room-view > .context > .checkbox-wrapper {
height: 2rem;
margin-bottom: .5em;
line-height: 2rem;
}
.new-room-view > .context > .checkbox-wrapper > .checkbox {
border-color: #0096dd;
border-color: #d8d8d8;
background-color: #fff;
}
@ -244,16 +257,17 @@ body {
.new-room-view > .context > .checkbox-wrapper > label {
color: #333;
font-weight: 700;
font-size: 1.1rem;
}
.new-room-view > .btn {
height: 3rem;
display: block;
font-size: 1rem;
margin: 0 auto .5rem;
font-size: 1.2rem;
margin: 0 auto 1rem;
width: 100%;
padding: .5rem 1rem;
border-radius: 0 0 3px 3px;
border-radius: 4px;
}
/* Remove when bug 1142671 is backed out. */
@ -277,14 +291,14 @@ body {
}
.room-list > .room-entry {
padding: .5rem 1rem;
padding: .2rem 1rem;
/* Always show the default pointer, even over the text part of the entry. */
cursor: default;
}
.room-list > .room-entry > h2 {
display: inline-block;
font-size: .85rem;
font-size: 1rem;
color: #777;
/* See .room-entry-context-item for the margin/size reductions. */
width: calc(100% - 1rem - 16px);
@ -309,11 +323,7 @@ body {
}
.room-list > .room-entry:hover {
background: #f1f1f1;
}
.room-list > .room-entry:not(:last-child) {
border-bottom: 1px solid #ccc;
background: #dbf7ff;
}
.room-list > .room-entry > p {
@ -424,7 +434,7 @@ body {
border: 1px solid #c1c1c1;
border-radius: 2px;
min-height: 26px;
font-size: 12px;
font-size: 1.2rem;
}
.button > .button-caption {
@ -619,7 +629,7 @@ html[dir="rtl"] .generate-url-spinner {
}
.terms-service > a {
color: #00caee;
color: #00a9dc;
}
/* DnD menu */

View File

@ -774,6 +774,7 @@ loop.panel = (function(_, mozL10n) {
var contextClasses = React.addons.classSet({
context: true,
"context-checkbox-checked": this.state.checked,
hide: !hostname ||
!this.props.mozLoop.getLoopPref("contextInConversations.enabled")
});

View File

@ -774,6 +774,7 @@ loop.panel = (function(_, mozL10n) {
var contextClasses = React.addons.classSet({
context: true,
"context-checkbox-checked": this.state.checked,
hide: !hostname ||
!this.props.mozLoop.getLoopPref("contextInConversations.enabled")
});

View File

@ -100,19 +100,19 @@ p {
}
.btn-info {
background-color: #0096dd;
border: 1px solid #0095dd;
background-color: #00a9dc;
border: 1px solid #00a9dc;
color: #fff;
}
.btn-info:hover {
background-color: #008acb;
border: 1px solid #008acb;
background-color: #5cccee;
border: 1px solid #5cccee;
}
.btn-info:active {
background-color: #006b9d;
border: 1px solid #006b9d;
background-color: #5cccee;
border: 1px solid #5cccee;
}
.btn-accept,
@ -468,8 +468,8 @@ html[dir="rtl"] .dropdown-menu {
.checkbox {
float: left;
width: 1em;
height: 1em;
width: 2rem;
height: 2rem;
-moz-margin-end: .5em;
margin-top: .1em;
border: 1px solid #999;
@ -521,7 +521,7 @@ html[dir="rtl"] .context-content {
.context-wrapper {
border: 1px solid #5cccee;
border-radius: 3px;
border-radius: 4px;
background: #fff;
padding: .8em;
/* Use the flex row mode to position the elements next to each other. */
@ -530,6 +530,10 @@ html[dir="rtl"] .context-content {
line-height: 1.1em;
}
.context-wrapper:hover {
background-color: #dbf7ff;
}
.context-wrapper > .context-preview {
float: left;
/* 16px is standard height/width for a favicon */
@ -556,7 +560,7 @@ html[dir="rtl"] .context-wrapper > .context-preview {
.context-wrapper > .context-description > .context-url {
display: block;
color: #59A1D7;
color: #00a9dc;
font-weight: 700;
clear: both;
}

View File

@ -200,8 +200,8 @@
pack="end"
role="dialog"
aria-labelledby="dialogTitle">
<caption class="titlebar" flex="1" align="center">
<label id="dialogTitle" class="title" flex="1"></label>
<caption flex="1" align="center">
<label id="dialogTitle" flex="1"></label>
<button id="dialogClose"
class="close-icon"
aria-label="&preferencesCloseButton.label;"/>

View File

@ -329,14 +329,11 @@ ToolSidebar.prototype = {
}),
/**
* Show or hide a specific tab and tabpanel.
* Show or hide a specific tab.
* @param {Boolean} isVisible True to show the tab/tabpanel, False to hide it.
* @param {String} id The ID of the tab to be hidden.
* @param {String} tabPanelId Optionally pass the ID for the tabPanel if it
* can't be retrieved using the tab ID. This is useful when tabs and tabpanels
* existed before the widget was created.
*/
toggleTab: function(isVisible, id, tabPanelId) {
toggleTab: function(isVisible, id) {
// Toggle the tab.
let tab = this.getTab(id);
if (!tab) {
@ -348,16 +345,6 @@ ToolSidebar.prototype = {
if (this._allTabsBtn) {
this._allTabsBtn.querySelector("#sidebar-alltabs-item-" + id).hidden = !isVisible;
}
// Toggle the corresponding tabPanel, if one can be found either with the id
// or the provided tabPanelId.
let tabPanel = this.getTabPanel(id);
if (!tabPanel && tabPanelId) {
tabPanel = this.getTabPanel(tabPanelId);
}
if (tabPanel) {
tabPanel.hidden = !isVisible;
}
},
/**

View File

@ -2475,14 +2475,14 @@ NetworkDetailsView.prototype = {
let isHtml = RequestsMenuView.prototype.isHtml({ attachment: aData });
// Show the "Preview" tabpanel only for plain HTML responses.
this.sidebar.toggleTab(isHtml, "preview-tab", "preview-tabpanel");
this.sidebar.toggleTab(isHtml, "preview-tab");
// Show the "Security" tab only for requests that
// 1) are https (state != insecure)
// 2) come from a target that provides security information.
let hasSecurityInfo = aData.securityState &&
aData.securityState !== "insecure";
this.sidebar.toggleTab(hasSecurityInfo, "security-tab", "security-tabpanel");
this.sidebar.toggleTab(hasSecurityInfo, "security-tab");
// Switch to the "Headers" tabpanel if the "Preview" previously selected
// and this is not an HTML response or "Security" was selected but this

View File

@ -22,8 +22,8 @@ function test() {
"The first tab in the details pane should be selected.");
is($("#preview-tab").hidden, true,
"The preview tab should be hidden for non html responses.");
is($("#preview-tabpanel").hidden, true,
"The preview tabpanel should be hidden for non html responses.");
is($("#preview-tabpanel").hidden, false,
"The preview tabpanel is not hidden for non html responses.");
RequestsMenu.selectedIndex = 4;
NetMonitorView.toggleDetailsPane({ visible: true, animated: false }, 6);
@ -32,8 +32,6 @@ function test() {
"The sixth tab in the details pane should be selected.");
is($("#preview-tab").hidden, false,
"The preview tab should be visible now.");
is($("#preview-tabpanel").hidden, false,
"The preview tabpanel should be visible now.");
waitFor(aMonitor.panelWin, EVENTS.RESPONSE_HTML_PREVIEW_DISPLAYED).then(() => {
let iframe = $("#response-preview");
@ -50,8 +48,8 @@ function test() {
"The first tab in the details pane should be selected again.");
is($("#preview-tab").hidden, true,
"The preview tab should be hidden again for non html responses.");
is($("#preview-tabpanel").hidden, true,
"The preview tabpanel should be hidden again for non html responses.");
is($("#preview-tabpanel").hidden, false,
"The preview tabpanel is not hidden again for non html responses.");
teardown(aMonitor).then(finish);
});

View File

@ -45,6 +45,7 @@ add_task(function* () {
waitForNetworkEvents(monitor, 1);
let tab = $("#security-tab");
let tabpanel = $("#security-tabpanel");
info("Performing a request to " + testcase.uri);
debuggee.performRequests(1, testcase.uri);
@ -61,6 +62,8 @@ add_task(function* () {
"Security tab is " +
(testcase.visibleOnNewEvent ? "visible" : "hidden") +
" after new request was added to the menu.");
is(tabpanel.hidden, false,
"#security-tabpanel is visible after new request was added to the menu.");
info("Waiting for security information to arrive.");
yield onSecurityInfo;
@ -71,6 +74,8 @@ add_task(function* () {
"Security tab is " +
(testcase.visibleOnSecurityInfo ? "visible" : "hidden") +
" after security information arrived.");
is(tabpanel.hidden, false,
"#security-tabpanel is visible after security information arrived.");
info("Waiting for request to complete.");
yield onComplete;
@ -78,6 +83,8 @@ add_task(function* () {
"Security tab is " +
(testcase.visibleOnceComplete ? "visible" : "hidden") +
" after request has been completed.");
is(tabpanel.hidden, false,
"#security-tabpanel is visible after request is complete.");
info("Clearing requests.");
RequestsMenu.clear();

View File

@ -185,6 +185,7 @@ browser.jar:
skin/classic/browser/feeds/subscribe.css (feeds/subscribe.css)
skin/classic/browser/feeds/subscribe-ui.css (feeds/subscribe-ui.css)
* skin/classic/browser/newtab/newTab.css (newtab/newTab.css)
skin/classic/browser/newtab/close.png (../shared/newtab/close.png)
skin/classic/browser/newtab/controls.svg (../shared/newtab/controls.svg)
skin/classic/browser/newtab/whimsycorn.png (../shared/newtab/whimsycorn.png)
skin/classic/browser/panic-panel/header.png (../shared/panic-panel/header.png)

View File

@ -242,6 +242,7 @@ browser.jar:
skin/classic/browser/feeds/feedIcon.png (feeds/feedIcon.png)
skin/classic/browser/feeds/feedIcon16.png (feeds/feedIcon16.png)
* skin/classic/browser/newtab/newTab.css (newtab/newTab.css)
skin/classic/browser/newtab/close.png (../shared/newtab/close.png)
skin/classic/browser/newtab/controls.svg (../shared/newtab/controls.svg)
skin/classic/browser/newtab/whimsycorn.png (../shared/newtab/whimsycorn.png)
skin/classic/browser/setDesktopBackground.css

View File

@ -520,7 +520,7 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
}
#PanelUI-footer-inner,
#PanelUI-footer-fxa {
#PanelUI-footer-fxa:not([hidden]) {
display: flex;
border-top: 1px solid hsla(210,4%,10%,.14);
}

View File

@ -49,7 +49,7 @@
}
#notification-popup-box:not([hidden]) + #identity-box {
-moz-padding-start: 10px;
padding-inline-start: 10px;
border-radius: 0;
}
@ -61,12 +61,8 @@
transition: padding-left, padding-right;
}
@conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
padding-left: calc(var(--backbutton-urlbar-overlap) + 4px);
}
@conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
padding-right: calc(var(--backbutton-urlbar-overlap) + 4px);
@conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box {
padding-inline-start: calc(var(--backbutton-urlbar-overlap) + 4px);
}
@conditionalForwardWithUrlbar@:hover:not([switchingtabs]) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box {
@ -74,14 +70,9 @@
transition-delay: 100s;
}
@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box {
/* when not hovered anymore, trigger a new non-delayed transition to react to the forward button hiding */
padding-left: calc(var(--backbutton-urlbar-overlap) + 4.01px);
}
@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
/* when not hovered anymore, trigger a new non-delayed transition to react to the forward button hiding */
padding-right: calc(var(--backbutton-urlbar-overlap) + 4.01px);
padding-inline-start: calc(var(--backbutton-urlbar-overlap) + 4.01px);
}
/* TRACKING PROTECTION ICON */

View File

@ -333,9 +333,10 @@ description > html|a {
background-color: transparent !important;
border: none;
box-shadow: none;
height: 18px;
padding: 0;
min-width: 18px;
height: auto;
min-height: 16px;
min-width: 0;
}
#dialogBox > .groupbox-body {

Binary file not shown.

After

Width:  |  Height:  |  Size: 931 B

View File

@ -55,16 +55,16 @@
width: 16px;
float: right;
right: 0;
background-image: -moz-image-rect(url(chrome://global/skin/icons/close.png), 0, 16, 16, 0);
background-image: -moz-image-rect(url(chrome://browser/skin/newtab/close.png), 0, 16, 16, 0);
background-color: transparent;
}
#newtab-undo-close-button:hover {
background-image: -moz-image-rect(url(chrome://global/skin/icons/close.png), 0, 32, 16, 16);
background-image: -moz-image-rect(url(chrome://browser/skin/newtab/close.png), 0, 32, 16, 16);
}
#newtab-undo-close-button:active {
background-image: -moz-image-rect(url(chrome://global/skin/icons/close.png), 0, 48, 16, 32);
#newtab-undo-close-button:hover:active {
background-image: -moz-image-rect(url(chrome://browser/skin/newtab/close.png), 0, 48, 16, 32);
}
/* CUSTOMIZE */

View File

@ -248,6 +248,7 @@ browser.jar:
skin/classic/browser/feeds/subscribe.css (feeds/subscribe.css)
skin/classic/browser/feeds/subscribe-ui.css (feeds/subscribe-ui.css)
* skin/classic/browser/newtab/newTab.css (newtab/newTab.css)
skin/classic/browser/newtab/close.png (../shared/newtab/close.png)
skin/classic/browser/newtab/controls.svg (../shared/newtab/controls.svg)
skin/classic/browser/newtab/whimsycorn.png (../shared/newtab/whimsycorn.png)
skin/classic/browser/panic-panel/header.png (../shared/panic-panel/header.png)

View File

@ -415,7 +415,9 @@ pref("font.size.inflation.minTwips", 0);
pref("browser.ui.zoom.force-user-scalable", false);
pref("ui.zoomedview.disabled", false);
pref("ui.zoomedview.limitReadableSize", 8); // value in layer pixels
pref("ui.zoomedview.limitReadableSize", 8); // value in layer pixels
pref("ui.zoomedview.defaultZoomFactor", 2);
pref("ui.zoomedview.simplified", true); // Do not display all the zoomed view controls
pref("ui.touch.radius.enabled", false);
pref("ui.touch.radius.leftmm", 3);

View File

@ -10,6 +10,7 @@ import org.mozilla.gecko.gfx.LayerView;
import org.mozilla.gecko.gfx.PanZoomController;
import org.mozilla.gecko.gfx.PointUtils;
import org.mozilla.gecko.mozglue.DirectBufferAllocator;
import org.mozilla.gecko.PrefsHelper;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.ThreadUtils;
@ -51,7 +52,7 @@ public class ZoomedView extends FrameLayout implements LayerView.OnMetricsChange
LayerView.ZoomedViewListener, GeckoEventListener {
private static final String LOGTAG = "Gecko" + ZoomedView.class.getSimpleName();
private static final float[] ZOOM_FACTORS_LIST = {2.0f, 3.0f, 1.5f};
private static final float[] ZOOM_FACTORS_LIST = {2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 1.5f};
private static final int W_CAPTURED_VIEW_IN_PERCENT = 50;
private static final int H_CAPTURED_VIEW_IN_PERCENT = 50;
private static final int MINIMUM_DELAY_BETWEEN_TWO_RENDER_CALLS_NS = 1000000;
@ -62,6 +63,11 @@ public class ZoomedView extends FrameLayout implements LayerView.OnMetricsChange
private float zoomFactor;
private int currentZoomFactorIndex;
private boolean isSimplifiedUI;
private int defaultZoomFactor;
private int prefDefaultZoomFactorObserverId;
private int prefSimplifiedUIObserverId;
private ImageView zoomedImageView;
private LayerView layerView;
private int viewWidth;
@ -213,10 +219,11 @@ public class ZoomedView extends FrameLayout implements LayerView.OnMetricsChange
public ZoomedView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
isSimplifiedUI = true;
getPrefs();
currentZoomFactorIndex = 0;
returnValue = new PointF();
animationStart = new PointF();
currentZoomFactorIndex = 0;
zoomFactor = ZOOM_FACTORS_LIST[currentZoomFactorIndex];
requestRenderRunnable = new Runnable() {
@Override
public void run() {
@ -230,6 +237,8 @@ public class ZoomedView extends FrameLayout implements LayerView.OnMetricsChange
}
void destroy() {
PrefsHelper.removeObserver(prefDefaultZoomFactorObserverId);
PrefsHelper.removeObserver(prefSimplifiedUIObserverId);
ThreadUtils.removeCallbacksFromUiThread(requestRenderRunnable);
EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
"Gesture:clusteredLinksClicked", "Window:Resize", "Content:LocationChange",
@ -246,7 +255,7 @@ public class ZoomedView extends FrameLayout implements LayerView.OnMetricsChange
changeZoomFactorButton = (TextView) findViewById(R.id.change_zoom_factor);
zoomedImageView = (ImageView) findViewById(R.id.zoomed_image_view);
setTextInZoomFactorButton(ZOOM_FACTORS_LIST[0]);
setTextInZoomFactorButton(zoomFactor);
toolbarHeight = getResources().getDimensionPixelSize(R.dimen.zoomed_view_toolbar_height);
containterSize = getResources().getDimensionPixelSize(R.dimen.drawable_dropshadow_size);
@ -262,9 +271,17 @@ public class ZoomedView extends FrameLayout implements LayerView.OnMetricsChange
}
});
changeZoomFactorButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
changeZoomFactor();
changeZoomFactorButton.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
if (event.getX() >= (changeZoomFactorButton.getLeft() + changeZoomFactorButton.getWidth() / 2)) {
changeZoomFactor(true);
} else {
changeZoomFactor(false);
}
}
return true;
}
});
@ -274,7 +291,7 @@ public class ZoomedView extends FrameLayout implements LayerView.OnMetricsChange
private void removeListeners() {
closeButton.setOnClickListener(null);
changeZoomFactorButton.setOnClickListener(null);
changeZoomFactorButton.setOnTouchListener(null);
setOnTouchListener(null);
}
@ -479,7 +496,47 @@ public class ZoomedView extends FrameLayout implements LayerView.OnMetricsChange
return (GeckoAppShell.getScreenDepth() == 24) ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
}
private void startZoomDisplay(LayerView aLayerView, final int leftFromGecko, final int topFromGecko) {
private void getPrefs() {
prefSimplifiedUIObserverId = PrefsHelper.getPref("ui.zoomedview.simplified", new PrefsHelper.PrefHandlerBase() {
@Override
public void prefValue(String pref, boolean simplified) {
isSimplifiedUI = simplified;
if (simplified) {
changeZoomFactorButton.setVisibility(View.INVISIBLE);
zoomFactor = (float) defaultZoomFactor;
} else {
zoomFactor = ZOOM_FACTORS_LIST[currentZoomFactorIndex];
setTextInZoomFactorButton(zoomFactor);
changeZoomFactorButton.setVisibility(View.VISIBLE);
}
}
@Override
public boolean isObserver() {
return true;
}
});
prefDefaultZoomFactorObserverId = PrefsHelper.getPref("ui.zoomedview.defaultZoomFactor", new PrefsHelper.PrefHandlerBase() {
@Override
public void prefValue(String pref, int defaultZoomFactorFromSettings) {
defaultZoomFactor = defaultZoomFactorFromSettings;
if (isSimplifiedUI) {
zoomFactor = (float) defaultZoomFactor;
} else {
zoomFactor = ZOOM_FACTORS_LIST[currentZoomFactorIndex];
setTextInZoomFactorButton(zoomFactor);
}
}
@Override
public boolean isObserver() {
return true;
}
});
}
private void startZoomDisplay(LayerView aLayerView, final int leftFromGecko, final int topFromGecko) {
if (layerView == null) {
layerView = aLayerView;
layerView.addZoomedViewListener(this);
@ -514,11 +571,15 @@ public class ZoomedView extends FrameLayout implements LayerView.OnMetricsChange
}
}
private void changeZoomFactor() {
if (currentZoomFactorIndex < ZOOM_FACTORS_LIST.length - 1) {
private void changeZoomFactor(boolean zoomIn) {
if (zoomIn && currentZoomFactorIndex < ZOOM_FACTORS_LIST.length - 1) {
currentZoomFactorIndex++;
} else {
} else if (zoomIn && currentZoomFactorIndex >= ZOOM_FACTORS_LIST.length - 1) {
currentZoomFactorIndex = 0;
} else if (!zoomIn && currentZoomFactorIndex > 0) {
currentZoomFactorIndex--;
} else {
currentZoomFactorIndex = ZOOM_FACTORS_LIST.length - 1;
}
zoomFactor = ZOOM_FACTORS_LIST[currentZoomFactorIndex];
@ -529,7 +590,7 @@ public class ZoomedView extends FrameLayout implements LayerView.OnMetricsChange
private void setTextInZoomFactorButton(float zoom) {
final String percentageValue = Integer.toString((int) (100*zoom));
changeZoomFactorButton.setText(getResources().getString(R.string.percent, percentageValue));
changeZoomFactorButton.setText("- " + getResources().getString(R.string.percent, percentageValue) + " +");
}
@Override

View File

@ -10,11 +10,13 @@ import java.util.EnumSet;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.R;
import org.mozilla.gecko.RestrictedProfiles;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.db.BrowserContract.Combined;
@ -22,6 +24,7 @@ import org.mozilla.gecko.db.BrowserContract.History;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
import org.mozilla.gecko.restrictions.Restriction;
import android.app.AlertDialog;
import android.content.ContentResolver;
@ -39,6 +42,7 @@ import android.text.style.StyleSpan;
import android.text.style.UnderlineSpan;
import android.util.Log;
import android.util.SparseArray;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -161,6 +165,15 @@ public class HistoryPanel extends HomeFragment {
});
}
@Override
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, view, menuInfo);
if (!RestrictedProfiles.isAllowed(getActivity(), Restriction.DISALLOW_CLEAR_HISTORY)) {
menu.findItem(R.id.home_remove).setVisible(false);
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
@ -206,7 +219,9 @@ public class HistoryPanel extends HomeFragment {
private void updateUiFromCursor(Cursor c) {
if (c != null && c.getCount() > 0) {
mClearHistoryButton.setVisibility(View.VISIBLE);
if (RestrictedProfiles.isAllowed(getActivity(), Restriction.DISALLOW_CLEAR_HISTORY)) {
mClearHistoryButton.setVisibility(View.VISIBLE);
}
return;
}

View File

@ -701,3 +701,9 @@ just addresses the organization to follow, e.g. "This site is run by " -->
<!ENTITY restriction_disallow_private_browsing_description "Disallow private browsing mode.">
<!ENTITY restriction_disallow_location_services_title "Disallow location services">
<!ENTITY restriction_disallow_location_services_description "Disallow sharing of location data to improve geolocation service.">
<!ENTITY restriction_disallow_display_settings_title "Disallow display settings">
<!ENTITY restriction_disallow_display_settings_description "Disallow changing of advanced display settings.">
<!ENTITY restriction_disallow_clear_history_title "Disallow clearing history">
<!ENTITY restriction_disallow_clear_history_description "Disallow clearing of browser history.">
<!ENTITY restriction_disallow_master_password_title "Disallow master password">
<!ENTITY restriction_disallow_master_password_description "Disallow setting a master password for logins.">

View File

@ -135,6 +135,7 @@ OnSharedPreferenceChangeListener
private static final String PREFS_CUSTOMIZE_HOME = NON_PREF_PREFIX + "customize_home";
private static final String PREFS_TRACKING_PROTECTION_PRIVATE_BROWSING = "privacy.trackingprotection.pbmode.enabled";
private static final String PREFS_TRACKING_PROTECTION_LEARN_MORE = NON_PREF_PREFIX + "trackingprotection.learn_more";
private static final String PREFS_CATEGORY_PRIVATE_DATA = NON_PREF_PREFIX + "category_private_data";
private static final String ACTION_STUMBLER_UPLOAD_PREF = AppConstants.ANDROID_PACKAGE_NAME + ".STUMBLER_PREF";
@ -491,6 +492,8 @@ OnSharedPreferenceChangeListener
iterator.remove();
} else if (header.id == R.id.pref_header_devtools && !RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_DEVELOPER_TOOLS)) {
iterator.remove();
} else if (header.id == R.id.pref_header_display && !RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_DISPLAY_SETTINGS)) {
iterator.remove();
}
}
}
@ -715,6 +718,13 @@ OnSharedPreferenceChangeListener
continue;
}
}
if (PREFS_CATEGORY_PRIVATE_DATA.equals(key)) {
if (!RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_CLEAR_HISTORY)) {
preferences.removePreference(pref);
i--;
continue;
}
}
setupPreferences((PreferenceGroup) pref, prefs);
} else {
@ -862,6 +872,12 @@ OnSharedPreferenceChangeListener
i--;
continue;
}
} else if (PREFS_MP_ENABLED.equals(key)) {
if (!RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_MASTER_PASSWORD)) {
preferences.removePreference(pref);
i--;
continue;
}
}
// Some Preference UI elements are not actually preferences,

View File

@ -20,6 +20,10 @@
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:background="@drawable/toolbar_grey_round">
<!--
Zoom factor button is invisible by default. Set ui.zoomedview.simplified to false
in order to display the button in the zoomed view tool bar
-->
<TextView
android:id="@+id/change_zoom_factor"
android:layout_width="wrap_content"
@ -28,7 +32,8 @@
android:padding="12dip"
android:layout_alignLeft="@+id/zoomed_image_view"
android:textSize="16sp"
android:textColor="@color/text_and_tabs_tray_grey"/>
android:textColor="@color/text_and_tabs_tray_grey"
android:visibility="invisible"/>
<ImageView
android:id="@+id/dialog_close"
android:scaleType="center"

View File

@ -16,7 +16,8 @@
</header>
<header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
android:title="@string/pref_header_display">
android:title="@string/pref_header_display"
android:id="@+id/pref_header_display">
<extra android:name="resource"
android:value="preferences_display"/>
</header>

View File

@ -13,4 +13,7 @@
<header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
android:id="@+id/pref_header_devtools">
</header>
<header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
android:id="@+id/pref_header_display">
</header>
</preference-headers>

View File

@ -53,7 +53,7 @@
</PreferenceCategory>
<!-- keys prefixed with "android.not_a_preference." are not synced with Gecko -->
<PreferenceCategory android:title="@string/pref_clear_private_data_category">
<PreferenceCategory android:key="android.not_a_preference.category_private_data" android:title="@string/pref_clear_private_data_category">
<org.mozilla.gecko.preferences.PrivateDataPreference
android:key="android.not_a_preference.privacy.clear"

View File

@ -24,11 +24,15 @@ public class RestrictedProfileConfiguration implements RestrictionConfiguration
Restriction.DISALLOW_DEVELOPER_TOOLS,
Restriction.DISALLOW_CUSTOMIZE_HOME,
Restriction.DISALLOW_PRIVATE_BROWSING,
Restriction.DISALLOW_LOCATION_SERVICE
Restriction.DISALLOW_LOCATION_SERVICE,
Restriction.DISALLOW_DISPLAY_SETTINGS,
Restriction.DISALLOW_CLEAR_HISTORY,
Restriction.DISALLOW_MASTER_PASSWORD
);
private static final String ABOUT_ADDONS = "about:addons";
private static final String ABOUT_PRIVATE_BROWSING = "about:privatebrowsing";
private static final String ABOUT_CONFIG = "about:config";
private Context context;
@ -59,6 +63,11 @@ public class RestrictedProfileConfiguration implements RestrictionConfiguration
return false;
}
if (url.toLowerCase().startsWith(ABOUT_CONFIG)) {
// Always block access to about:config to prevent circumventing restrictions (Bug 1189233)
return false;
}
return true;
}

View File

@ -88,6 +88,24 @@ public enum Restriction {
17, "no_location_service",
R.string.restriction_disallow_location_services_title,
R.string.restriction_disallow_location_services_description
),
DISALLOW_DISPLAY_SETTINGS(
18, "no_display_settings",
R.string.restriction_disallow_display_settings_title,
R.string.restriction_disallow_display_settings_description
),
DISALLOW_CLEAR_HISTORY(
19, "no_clear_history",
R.string.restriction_disallow_clear_history_title,
R.string.restriction_disallow_clear_history_description
),
DISALLOW_MASTER_PASSWORD(
20, "no_master_password",
R.string.restriction_disallow_master_password_title,
R.string.restriction_disallow_master_password_description
);
public final int id;

View File

@ -557,6 +557,12 @@
<string name="restriction_disallow_private_browsing_description">&restriction_disallow_private_browsing_description;</string>
<string name="restriction_disallow_location_services_title">&restriction_disallow_location_services_title;</string>
<string name="restriction_disallow_location_services_description">&restriction_disallow_location_services_description;</string>
<string name="restriction_disallow_display_settings_title">&restriction_disallow_display_settings_title;</string>
<string name="restriction_disallow_display_settings_description">&restriction_disallow_display_settings_description;</string>
<string name="restriction_disallow_clear_history_title">&restriction_disallow_clear_history_title;</string>
<string name="restriction_disallow_clear_history_description">&restriction_disallow_clear_history_description;</string>
<string name="restriction_disallow_master_password_title">&restriction_disallow_master_password_title;</string>
<string name="restriction_disallow_master_password_description">&restriction_disallow_master_password_description;</string>
<!-- Miscellaneous -->
<string name="ellipsis">&ellipsis;</string>

View File

@ -17,6 +17,8 @@ import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.animation.PropertyAnimator;
import org.mozilla.gecko.animation.PropertyAnimator.PropertyAnimationListener;
import org.mozilla.gecko.preferences.GeckoPreferences;
@ -227,6 +229,7 @@ public class ToolbarEditLayout extends ThemedLinearLayout {
}
private void launchVoiceRecognizer() {
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.ACTIONBAR, "voice_input_launch");
final Intent intent = InputOptionsUtils.createVoiceRecognizerIntent(getResources().getString(R.string.voicesearch_prompt));
Activity activity = GeckoAppShell.getGeckoInterface().getActivity();
@ -237,6 +240,7 @@ public class ToolbarEditLayout extends ThemedLinearLayout {
return;
}
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.ACTIONBAR, "voice_input_success");
// We have RESULT_OK, not RESULT_NO_MATCH so it should be safe to assume that
// we have at least one match. We only need one: this will be
// used for showing the user search engines with this search term in it.
@ -262,6 +266,7 @@ public class ToolbarEditLayout extends ThemedLinearLayout {
}
private void launchQRCodeReader() {
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.ACTIONBAR, "qrcode_input_launch");
final Intent intent = InputOptionsUtils.createQRCodeReaderIntent();
Activity activity = GeckoAppShell.getGeckoInterface().getActivity();
@ -271,6 +276,7 @@ public class ToolbarEditLayout extends ThemedLinearLayout {
if (resultCode == Activity.RESULT_OK) {
String text = intent.getStringExtra("SCAN_RESULT");
if (!StringUtils.isSearchQuery(text, false)) {
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.ACTIONBAR, "qrcode_input_success");
mEditText.setText(text);
mEditText.selectAll();

View File

@ -138,6 +138,7 @@ skip-if = android_version == "10" || android_version == "18"
[testReadingListCache.java]
[testResourceSubstitutions.java]
[testRestrictedProfiles.java]
[testRestrictions.java]
[testSessionFormData.java]
[testSharedPreferences.java]
[testSimpleDiscovery.java]

View File

@ -0,0 +1,24 @@
/* 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.tests;
import static org.mozilla.gecko.tests.helpers.AssertionHelper.fAssertTrue;
import org.mozilla.gecko.RestrictedProfiles;
import org.mozilla.gecko.restrictions.Restriction;
import org.mozilla.gecko.tests.helpers.GeckoHelper;
public class testRestrictions extends UITest {
public void testRestrictions() {
GeckoHelper.blockForReady();
// No restrictions should be enforced when using a normal profile
for (Restriction restriction : Restriction.values()) {
fAssertTrue(String.format("Restriction %s is not enforced", restriction.name),
RestrictedProfiles.isAllowed(getActivity(), restriction)
);
}
}
}

View File

@ -11,7 +11,7 @@ interface nsIFile;
interface nsIInterfaceRequestor;
interface nsIArray;
[scriptable, uuid(4ee714a7-e9a8-43ed-a061-60155b63e290)]
[scriptable, uuid(30ff7af7-ae52-4bd6-88c0-4a8ce4f37bdc)]
interface nsIParentalControlsService : nsISupports
{
/**
@ -32,6 +32,9 @@ interface nsIParentalControlsService : nsISupports
const short REPORT_SITE_ISSUE = 13; // Hide "Report Site Issue" menu entry
const short PRIVATE_BROWSING = 16; // Disallow usage of private browsing
const short LOCATION_SERVICE = 17; // Sharing of location data to location service
const short DISPLAY_SETTINGS = 18; // Website display settings
const short CLEAR_HISTORY = 19; // Clear browsing history
const short MASTER_PASSWORD = 20; // Setting master password for logins
/**
* @returns true if the current user account has parental controls

View File

@ -259,6 +259,43 @@ this.LoginHelper = {
return newLogin;
},
/**
* Removes duplicates from a list of logins.
*
* @param {nsILoginInfo[]} logins
* A list of logins we want to deduplicate.
*
* @param {string[] = ["username", "password"]} uniqueKeys
* A list of login attributes to use as unique keys for the deduplication.
*
* @returns {nsILoginInfo[]} list of unique logins.
*/
dedupeLogins(logins, uniqueKeys = ["username", "password"]) {
const KEY_DELIMITER = ":";
// Generate a unique key string from a login.
function getKey(login, uniqueKeys) {
return uniqueKeys.reduce((prev, key) => prev + KEY_DELIMITER + login[key], "");
}
// We use a Map to easily lookup logins by their unique keys.
let loginsByKeys = new Map();
for (let login of logins) {
let key = getKey(login, uniqueKeys);
// If we find a more recently used login for the same key, replace the existing one.
if (loginsByKeys.has(key)) {
let loginDate = login.QueryInterface(Ci.nsILoginMetaInfo).timeLastUsed;
let storedLoginDate = loginsByKeys.get(key).QueryInterface(Ci.nsILoginMetaInfo).timeLastUsed;
if (loginDate < storedLoginDate) {
continue;
}
}
loginsByKeys.set(key, login);
}
// Return the map values in the form of an array.
return [...loginsByKeys.values()];
},
/**
* Open the password manager window.
*

View File

@ -17,6 +17,7 @@ const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/LoginRecipes.jsm");
Cu.import("resource://gre/modules/LoginHelper.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadPaths",
"resource://gre/modules/DownloadPaths.jsm");

View File

@ -52,6 +52,29 @@ function checkLoginInvalid(aLoginInfo, aExpectedError)
Services.logins.removeLogin(testLogin);
}
/**
* Verifies that two objects are not the same instance
* but have equal attributes.
*
* @param {Object} objectA
* An object to compare.
*
* @param {Object} objectB
* Another object to compare.
*
* @param {string[]} attributes
* Attributes to compare.
*
* @return true if all passed attributes are equal for both objects, false otherwise.
*/
function compareAttributes(objectA, objectB, attributes) {
// If it's the same object, we want to return false.
if (objectA == objectB) {
return false;
}
return attributes.every(attr => objectA[attr] == objectB[attr]);
}
////////////////////////////////////////////////////////////////////////////////
//// Tests
@ -295,3 +318,69 @@ add_task(function test_modifyLogin_nsIProperyBag()
LoginTestUtils.clearData();
});
/**
* Tests the login deduplication function.
*/
add_task(function test_deduplicate_logins() {
// Different key attributes combinations and the amount of unique
// results expected for the TestData login list.
let keyCombinations = [
{
keyset: ["username", "password"],
results: 13,
},
{
keyset: ["hostname", "username"],
results: 17,
},
{
keyset: ["hostname", "username", "password"],
results: 18,
},
{
keyset: ["hostname", "username", "password", "formSubmitURL"],
results: 22,
},
];
let logins = TestData.loginList();
for (let testCase of keyCombinations) {
// Deduplicate the logins using the current testcase keyset.
let deduped = LoginHelper.dedupeLogins(logins, testCase.keyset);
Assert.equal(deduped.length, testCase.results, "Correct amount of results.");
// Checks that every login after deduping is unique.
Assert.ok(deduped.every(loginA =>
deduped.every(loginB => !compareAttributes(loginA, loginB, testCase.keyset))
), "Every login is unique.");
}
});
/**
* Ensure that the login deduplication function keeps the most recent login.
*/
add_task(function test_deduplicate_keeps_most_recent() {
// Logins to deduplicate.
let logins = [
TestData.formLogin({timeLastUsed: Date.UTC(2004, 11, 4, 0, 0, 0)}),
TestData.formLogin({formSubmitURL: "http://example.com", timeLastUsed: Date.UTC(2015, 11, 4, 0, 0, 0)}),
];
// Deduplicate the logins.
let deduped = LoginHelper.dedupeLogins(logins);
Assert.equal(deduped.length, 1, "Deduplicated the logins array.");
// Verify that the remaining login have the most recent date.
let loginTimeLastUsed = deduped[0].QueryInterface(Ci.nsILoginMetaInfo).timeLastUsed;
Assert.equal(loginTimeLastUsed, Date.UTC(2015, 11, 4, 0, 0, 0), "Most recent login was kept.");
// Deduplicate the reverse logins array.
deduped = LoginHelper.dedupeLogins(logins.reverse());
Assert.equal(deduped.length, 1, "Deduplicated the reversed logins array.");
// Verify that the remaining login have the most recent date.
loginTimeLastUsed = deduped[0].QueryInterface(Ci.nsILoginMetaInfo).timeLastUsed;
Assert.equal(loginTimeLastUsed, Date.UTC(2015, 11, 4, 0, 0, 0), "Most recent login was kept.");
});

View File

@ -4655,6 +4655,18 @@
"n_buckets": 20,
"description": "Time (ms) it takes for evicting over-quota pings"
},
"TELEMETRY_PENDING_LOAD_FAILURE_READ": {
"alert_emails": ["telemetry-client-dev@mozilla.com"],
"expires_in_version": "never",
"kind": "count",
"description": "Number of pending Telemetry pings that failed to load from the disk"
},
"TELEMETRY_PENDING_LOAD_FAILURE_PARSE": {
"alert_emails": ["telemetry-client-dev@mozilla.com"],
"expires_in_version": "never",
"kind": "count",
"description": "Number of pending Telemetry pings that failed to parse once loaded from the disk"
},
"TELEMETRY_PENDING_PINGS_SIZE_MB": {
"alert_emails": ["telemetry-client-dev@mozilla.com"],
"expires_in_version": "never",
@ -4745,6 +4757,12 @@
"keyed": true,
"description": "Count of individual invalid ping types that were submitted to Telemetry."
},
"TELEMETRY_PING_EVICTED_FOR_SERVER_ERRORS": {
"alert_emails": ["telemetry-client-dev@mozilla.com"],
"expires_in_version": "never",
"kind": "count",
"description": "Number of Telemetry ping files evicted due to server errors (4XX HTTP code received)"
},
"TELEMETRY_TEST_FLAG": {
"expires_in_version": "never",
"kind": "flag",

View File

@ -901,6 +901,7 @@ let TelemetrySendImpl = {
// 4XX means that something with the request was broken.
this._log.error("_doPing - error submitting to " + url + ", status: " + status
+ " - ping request broken?");
Telemetry.getHistogramById("TELEMETRY_PING_EVICTED_FOR_SERVER_ERRORS").add();
// TODO: we should handle this better, but for now we should avoid resubmitting
// broken requests by pretending success.
success = true;

View File

@ -68,6 +68,34 @@ const PENDING_PINGS_SIZE_PROBE_SPECIAL_VALUE = 17;
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
/**
* This is thrown by |TelemetryStorage.loadPingFile| when reading the ping
* from the disk fails.
*/
function PingReadError(message="Error reading the ping file") {
Error.call(this, message);
let error = new Error();
this.name = "PingReadError";
this.message = message;
this.stack = error.stack;
}
PingReadError.prototype = Object.create(Error.prototype);
PingReadError.prototype.constructor = PingReadError;
/**
* This is thrown by |TelemetryStorage.loadPingFile| when parsing the ping JSON
* content fails.
*/
function PingParseError(message="Error parsing ping content") {
Error.call(this, message);
let error = new Error();
this.name = "PingParseError";
this.message = message;
this.stack = error.stack;
}
PingParseError.prototype = Object.create(Error.prototype);
PingParseError.prototype.constructor = PingParseError;
/**
* This is a policy object used to override behavior for testing.
*/
@ -1171,7 +1199,17 @@ let TelemetryStorageImpl = {
return Promise.reject(new Error("TelemetryStorage.loadPendingPing - no ping with id " + id));
}
return this.loadPingFile(info.path, false);
return this.loadPingFile(info.path, false).catch(e => {
// If we failed to load the ping, check what happened and update the histogram.
// Then propagate the rejection.
if (e instanceof PingReadError) {
Telemetry.getHistogramById("TELEMETRY_PENDING_LOAD_FAILURE_READ").add();
} else if (e instanceof PingParseError) {
Telemetry.getHistogramById("TELEMETRY_PENDING_LOAD_FAILURE_PARSE").add();
}
return Promise.reject(e);
});
},
removePendingPing: function(id) {
@ -1319,21 +1357,35 @@ let TelemetryStorageImpl = {
* @param {Boolean} [aCompressed=false] If |true|, expects the file to be compressed using lz4.
* @return {Promise<Object>} A promise resolved with the ping content or rejected if the
* ping contains invalid data.
* @throws {PingReadError} There was an error while reading the ping file from the disk.
* @throws {PingParseError} There was an error while parsing the JSON content of the ping file.
*/
loadPingFile: Task.async(function* (aFilePath, aCompressed = false) {
let options = {};
if (aCompressed) {
options.compression = "lz4";
}
let array = yield OS.File.read(aFilePath, options);
let array;
try {
array = yield OS.File.read(aFilePath, options);
} catch(e) {
throw new PingReadError(e.message);
};
let decoder = new TextDecoder();
let string = decoder.decode(array);
let ping = JSON.parse(string);
// The ping's payload used to be stringified JSON. Deal with that.
if (typeof(ping.payload) == "string") {
ping.payload = JSON.parse(ping.payload);
let ping;
try {
ping = JSON.parse(string);
// The ping's payload used to be stringified JSON. Deal with that.
if (typeof(ping.payload) == "string") {
ping.payload = JSON.parse(ping.payload);
}
} catch (e) {
throw new PingParseError(e.message);
}
return ping;
}),

View File

@ -14,6 +14,8 @@ Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/Preferences.jsm", this);
Cu.import("resource://gre/modules/osfile.jsm", this);
const Telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry);
const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabled";
const PREF_TELEMETRY_SERVER = "toolkit.telemetry.server";
@ -236,6 +238,42 @@ add_task(function* test_backoffTimeout() {
Assert.equal(TelemetrySend.pendingPingCount, 0, "Should have no pending pings left");
});
add_task(function* test_evictedOnServerErrors() {
const TEST_TYPE = "test-evicted";
yield TelemetrySend.reset();
// Write a custom ping handler which will return 403. This will trigger ping eviction
// on client side.
PingServer.registerPingHandler((req, res) => {
res.setStatusLine(null, 403, "Forbidden");
res.processAsync();
res.finish();
});
// Clear the histogram and submit a ping.
Telemetry.getHistogramById("TELEMETRY_PING_EVICTED_FOR_SERVER_ERRORS").clear();
let pingId = yield TelemetryController.submitExternalPing(TEST_TYPE, {});
yield TelemetrySend.testWaitOnOutgoingPings();
let h = Telemetry.getHistogramById("TELEMETRY_PING_EVICTED_FOR_SERVER_ERRORS").snapshot();
Assert.equal(h.sum, 1, "Telemetry must report a ping evicted due to server errors");
// The ping should not be persisted.
yield Assert.rejects(TelemetryStorage.loadPendingPing(pingId), "The ping must not be persisted.");
// Reset the ping handler and submit a new ping.
PingServer.resetPingHandler();
pingId = yield TelemetryController.submitExternalPing(TEST_TYPE, {});
let ping = yield PingServer.promiseNextPings(1);
Assert.equal(ping[0].id, pingId, "The correct ping must be received");
// We should not have updated the error histogram.
h = Telemetry.getHistogramById("TELEMETRY_PING_EVICTED_FOR_SERVER_ERRORS").snapshot();
Assert.equal(h.sum, 1, "Telemetry must report a ping evicted due to server errors");
});
// Test that the current, non-persisted pending pings are properly saved on shutdown.
add_task(function* test_persistCurrentPingsOnShutdown() {
const TEST_TYPE = "test-persistCurrentPingsOnShutdown";

View File

@ -273,6 +273,54 @@ add_task(function* test_overdue_old_format() {
yield clearPendingPings();
});
add_task(function* test_corrupted_pending_pings() {
const TEST_TYPE = "test_corrupted";
Telemetry.getHistogramById("TELEMETRY_PENDING_LOAD_FAILURE_READ").clear();
Telemetry.getHistogramById("TELEMETRY_PENDING_LOAD_FAILURE_PARSE").clear();
// Save a pending ping and get its id.
let pendingPingId = yield TelemetryController.addPendingPing(TEST_TYPE, {}, {});
// Try to load it: there should be no error.
yield TelemetryStorage.loadPendingPing(pendingPingId);
let h = Telemetry.getHistogramById("TELEMETRY_PENDING_LOAD_FAILURE_READ").snapshot();
Assert.equal(h.sum, 0, "Telemetry must not report a pending ping load failure");
h = Telemetry.getHistogramById("TELEMETRY_PENDING_LOAD_FAILURE_PARSE").snapshot();
Assert.equal(h.sum, 0, "Telemetry must not report a pending ping parse failure");
// Delete it from the disk, so that its id will be kept in the cache but it will
// fail loading the file.
yield OS.File.remove(getSavePathForPingId(pendingPingId));
// Try to load a pending ping which isn't there anymore.
yield Assert.rejects(TelemetryStorage.loadPendingPing(pendingPingId),
"Telemetry must fail loading a ping which isn't there");
h = Telemetry.getHistogramById("TELEMETRY_PENDING_LOAD_FAILURE_READ").snapshot();
Assert.equal(h.sum, 1, "Telemetry must report a pending ping load failure");
h = Telemetry.getHistogramById("TELEMETRY_PENDING_LOAD_FAILURE_PARSE").snapshot();
Assert.equal(h.sum, 0, "Telemetry must not report a pending ping parse failure");
// Save a new ping, so that it gets in the pending pings cache.
pendingPingId = yield TelemetryController.addPendingPing(TEST_TYPE, {}, {});
// Overwrite it with a corrupted JSON file and then try to load it.
const INVALID_JSON = "{ invalid,JSON { {1}";
yield OS.File.writeAtomic(getSavePathForPingId(pendingPingId), INVALID_JSON, { encoding: "utf-8" });
// Try to load the ping with the corrupted JSON content.
yield Assert.rejects(TelemetryStorage.loadPendingPing(pendingPingId),
"Telemetry must fail loading a corrupted ping");
h = Telemetry.getHistogramById("TELEMETRY_PENDING_LOAD_FAILURE_READ").snapshot();
Assert.equal(h.sum, 1, "Telemetry must report a pending ping load failure");
h = Telemetry.getHistogramById("TELEMETRY_PENDING_LOAD_FAILURE_PARSE").snapshot();
Assert.equal(h.sum, 1, "Telemetry must report a pending ping parse failure");
yield clearPendingPings();
});
/**
* Create some recent and overdue pings and verify that they get sent.
*/

View File

@ -18,7 +18,7 @@ const promise = require("promise");
const PromiseDebugging = require("PromiseDebugging");
const xpcInspector = require("xpcInspector");
const ScriptStore = require("./utils/ScriptStore");
const {DevToolsWorker} = require("devtools/toolkit/shared/worker.js");
const { DevToolsWorker } = require("devtools/toolkit/shared/worker.js");
const { defer, resolve, reject, all } = require("devtools/toolkit/deprecated-sync-thenables");

View File

@ -181,7 +181,6 @@ xul|menulist {
-moz-appearance: none;
height: 30px;
color: var(--in-content-text-color);
line-height: 20px;
border: 1px solid var(--in-content-box-border-color);
-moz-border-top-colors: none !important;
-moz-border-right-colors: none !important;