mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-28 12:45:27 +00:00
Merge fx-team to m-c. a=merge
This commit is contained in:
commit
c83013fa4f
@ -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");
|
||||
|
@ -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 */
|
||||
|
@ -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")
|
||||
});
|
||||
|
@ -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")
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;"/>
|
||||
|
@ -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;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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 {
|
||||
|
BIN
browser/themes/shared/newtab/close.png
Normal file
BIN
browser/themes/shared/newtab/close.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 931 B |
@ -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 */
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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.">
|
||||
|
@ -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,
|
||||
|
@ -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"
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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]
|
||||
|
24
mobile/android/tests/browser/robocop/testRestrictions.java
Normal file
24
mobile/android/tests/browser/robocop/testRestrictions.java
Normal 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)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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");
|
||||
|
@ -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.");
|
||||
});
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}),
|
||||
|
||||
|
@ -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";
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user