merge fx-team to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2015-10-08 11:55:31 +02:00
commit d3d7a667f0
74 changed files with 1090 additions and 738 deletions

View File

@ -229,20 +229,14 @@ var StarUI = {
gEditItemOverlay.initPanel({ node: aNode
, hiddenRows: ["description", "location",
"loadInSidebar", "keyword"] });
"loadInSidebar", "keyword"]
, focusedElement: "preferred" });
}),
panelShown:
function SU_panelShown(aEvent) {
if (aEvent.target == this.panel) {
if (!this._element("editBookmarkPanelContent").hidden) {
let fieldToFocus = "editBMPanel_" +
gPrefService.getCharPref("browser.bookmarks.editDialog.firstEditField");
var elt = this._element(fieldToFocus);
elt.focus();
elt.select();
}
else {
if (this._element("editBookmarkPanelContent").hidden) {
// Note this isn't actually used anymore, we should remove this
// once we decide not to bring back the page bookmarked notification
this.panel.focus();

View File

@ -255,6 +255,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
XPCOMUtils.defineLazyModuleGetter(this, "ReaderParent",
"resource:///modules/ReaderParent.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerParent",
"resource://gre/modules/LoginManagerParent.jsm");
var gInitialPages = [
"about:blank",
"about:newtab",
@ -1192,6 +1195,10 @@ var gBrowserInit = {
}
}, false, true);
gBrowser.addEventListener("InsecureLoginFormsStateChange", function() {
gIdentityHandler.refreshForInsecureLoginForms();
});
let uriToLoad = this._getUriToLoad();
if (uriToLoad && uriToLoad != "about:blank") {
if (uriToLoad instanceof Ci.nsISupportsArray) {
@ -7006,10 +7013,7 @@ var gIdentityHandler = {
}
// Then, update the user interface with the available data.
if (this._identityBox) {
this.refreshIdentityBlock();
}
this.refreshIdentityBlock();
// NOTE: We do NOT update the identity popup (the control center) when
// we receive a new security state. If the user opened the popup and looks
@ -7017,6 +7021,20 @@ var gIdentityHandler = {
// contents.
},
/**
* This is called asynchronously when requested by the Logins module, after
* the insecure login forms state for the page has been updated.
*/
refreshForInsecureLoginForms() {
// Check this._uri because we don't want to refresh the user interface if
// this is called before the first page load in the window for any reason.
if (!this._uri) {
Cu.reportError("Unexpected early call to refreshForInsecureLoginForms.");
return;
}
this.refreshIdentityBlock();
},
/**
* Attempt to provide proper IDN treatment for host names
*/
@ -7053,6 +7071,10 @@ var gIdentityHandler = {
* Updates the identity block user interface with the data from this object.
*/
refreshIdentityBlock() {
if (!this._identityBox) {
return;
}
let icon_label = "";
let tooltip = "";
let icon_country_label = "";
@ -7121,6 +7143,11 @@ var gIdentityHandler = {
this._identityBox.classList.add("weakCipher");
}
}
if (LoginManagerParent.hasInsecureLoginForms(gBrowser.selectedBrowser)) {
// Insecure login forms can only be present on "unknown identity"
// pages, either already insecure or with mixed active content loaded.
this._identityBox.classList.add("insecureLoginForms");
}
tooltip = gNavigatorBundle.getString("identity.unknown.tooltip");
}
@ -7158,6 +7185,12 @@ var gIdentityHandler = {
connection = "secure";
}
// Determine if there are insecure login forms.
let loginforms = "secure";
if (LoginManagerParent.hasInsecureLoginForms(gBrowser.selectedBrowser)) {
loginforms = "insecure";
}
// Determine the mixed content state.
let mixedcontent = [];
if (this._isMixedPassiveContentLoaded) {
@ -7195,6 +7228,7 @@ var gIdentityHandler = {
for (let id of elementIDs) {
let element = document.getElementById(id);
updateAttribute(element, "connection", connection);
updateAttribute(element, "loginforms", loginforms);
updateAttribute(element, "ciphers", ciphers);
updateAttribute(element, "mixedcontent", mixedcontent);
updateAttribute(element, "isbroken", this._isBroken);

View File

@ -13,6 +13,6 @@ add_task(function* test_settingsOpen() {
Services.obs.notifyObservers(principal, "notifications-open-settings", null);
let tab = yield tabPromise;
ok(tab, "The notification settings tab opened");
BrowserTestUtils.removeTab(tab);
yield BrowserTestUtils.removeTab(tab);
});
});

View File

@ -267,7 +267,7 @@ tags = mcb
tags = mcb
[browser_bug906190.js]
tags = mcb
skip-if = buildapp == "mulet" || e10s # Bug 1093642 - test manipulates content and relies on content focus
skip-if = buildapp == "mulet" || e10s || os == "linux" # Bug 1093642 - test manipulates content and relies on content focus, Bug 1212520 - Re-enable on Linux
[browser_mixedContentFromOnunload.js]
tags = mcb
[browser_mixedContentFramesOnHttp.js]
@ -321,6 +321,7 @@ skip-if = e10s # Bug 863514 - no gesture support.
[browser_homeDrop.js]
skip-if = buildapp == 'mulet'
[browser_identity_UI.js]
[browser_insecureLoginForms.js]
[browser_keywordBookmarklets.js]
skip-if = e10s # Bug 1102025 - different principals for the bookmarklet only in e10s mode (unclear if test or 'real' issue)
[browser_keywordSearch.js]

View File

@ -0,0 +1,92 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Load directly from the browser-chrome support files of login tests.
const testUrlPath =
"://example.com/browser/toolkit/components/passwordmgr/test/browser/";
/**
* Waits for the given number of occurrences of InsecureLoginFormsStateChange
* on the given browser element.
*/
function waitForInsecureLoginFormsStateChange(browser, count) {
return BrowserTestUtils.waitForEvent(browser, "InsecureLoginFormsStateChange",
false, () => --count == 0);
}
/**
* Checks the insecure login forms logic for the identity block.
*/
add_task(function* test_simple() {
for (let scheme of ["http", "https"]) {
let tab = gBrowser.addTab(scheme + testUrlPath + "form_basic.html");
let browser = tab.linkedBrowser;
yield Promise.all([
BrowserTestUtils.switchTab(gBrowser, tab),
BrowserTestUtils.browserLoaded(browser),
// One event is triggered by pageshow and one by DOMFormHasPassword.
waitForInsecureLoginFormsStateChange(browser, 2),
]);
let { gIdentityHandler } = gBrowser.ownerGlobal;
gIdentityHandler._identityBox.click();
document.getElementById("identity-popup-security-expander").click();
if (scheme == "http") {
let identityBoxImage = gBrowser.ownerGlobal
.getComputedStyle(document.getElementById("page-proxy-favicon"), "")
.getPropertyValue("list-style-image");
let securityViewBG = gBrowser.ownerGlobal
.getComputedStyle(document.getElementById("identity-popup-securityView"), "")
.getPropertyValue("background-image");
let securityContentBG = gBrowser.ownerGlobal
.getComputedStyle(document.getElementById("identity-popup-security-content"), "")
.getPropertyValue("background-image");
is(identityBoxImage,
"url(\"chrome://browser/skin/identity-mixed-active-loaded.svg\")",
"Using expected icon image in the identity block");
is(securityViewBG,
"url(\"chrome://browser/skin/controlcenter/mcb-disabled.svg\")",
"Using expected icon image in the Control Center main view");
is(securityContentBG,
"url(\"chrome://browser/skin/controlcenter/mcb-disabled.svg\")",
"Using expected icon image in the Control Center subview");
}
// Messages should be visible when the scheme is HTTP, and invisible when
// the scheme is HTTPS.
is(Array.every(document.querySelectorAll("[when-loginforms=insecure]"),
element => !is_hidden(element)),
scheme == "http",
"The relevant messages should visible or hidden.");
gIdentityHandler._identityPopup.hidden = true;
gBrowser.removeTab(tab);
}
});
/**
* Checks that the insecure login forms logic does not regress mixed content
* blocking messages when mixed active content is loaded.
*/
add_task(function* test_mixedcontent() {
yield new Promise(resolve => SpecialPowers.pushPrefEnv({
"set": [["security.mixed_content.block_active_content", false]],
}, resolve));
// Load the page with the subframe in a new tab.
let tab = gBrowser.addTab("https" + testUrlPath + "insecure_test.html");
let browser = tab.linkedBrowser;
yield Promise.all([
BrowserTestUtils.switchTab(gBrowser, tab),
BrowserTestUtils.browserLoaded(browser),
// Two events are triggered by pageshow and one by DOMFormHasPassword.
waitForInsecureLoginFormsStateChange(browser, 3),
]);
assertMixedContentBlockingState(browser, { activeLoaded: true,
activeBlocked: false,
passiveLoaded: false });
gBrowser.removeTab(tab);
});

View File

@ -888,6 +888,13 @@ function assertMixedContentBlockingState(tabbrowser, states = {}) {
}
}
if (activeLoaded || activeBlocked || passiveLoaded) {
doc.getElementById("identity-popup-security-expander").click();
is(Array.filter(doc.querySelectorAll("[observes=identity-popup-mcb-learn-more]"),
element => !is_hidden(element)).length, 1,
"The 'Learn more' link should be visible once.");
}
gIdentityHandler._identityPopup.hidden = true;
}

View File

@ -41,6 +41,7 @@
<description when-mixedcontent="active-loaded">&identity.activeLoaded;</description>
<description class="identity-popup-warning-yellow"
when-ciphers="weak">&identity.weakEncryption;</description>
<description when-loginforms="insecure">&identity.insecureLoginForms;</description>
</vbox>
</vbox>
<button id="identity-popup-security-expander"
@ -116,7 +117,11 @@
when-connection="secure secure-ev"/>
<!-- Connection is Not Secure -->
<description when-connection="not-secure">&identity.description.insecure;</description>
<description when-connection="not-secure"
and-when-loginforms="secure">&identity.description.insecure;</description>
<!-- Insecure login forms -->
<description when-loginforms="insecure">&identity.description.insecureLoginForms;</description>
<!-- Weak Cipher -->
<description when-ciphers="weak">&identity.description.weakCipher;</description>
@ -138,8 +143,14 @@
class="identity-popup-warning-yellow">&identity.description.passiveLoaded3; <label observes="identity-popup-mcb-learn-more"/></description>
<!-- Active Mixed Content Blocking Disabled -->
<description when-mixedcontent="active-loaded">&identity.description.activeLoaded;</description>
<description when-mixedcontent="active-loaded">&identity.description.activeLoaded2; <label observes="identity-popup-mcb-learn-more"/></description>
<description when-mixedcontent="active-loaded"
and-when-loginforms="secure">&identity.description.activeLoaded;</description>
<description when-mixedcontent="active-loaded"
and-when-loginforms="secure">&identity.description.activeLoaded2; <label observes="identity-popup-mcb-learn-more"/></description>
<!-- Show only the first message when there are insecure login forms,
and make sure the Learn More link is included. -->
<description when-mixedcontent="active-loaded"
and-when-loginforms="insecure">&identity.description.activeLoaded; <label observes="identity-popup-mcb-learn-more"/></description>
<!-- Buttons to enable/disable mixed content blocking. -->
<button when-mixedcontent="active-blocked"

View File

@ -15,10 +15,10 @@ button::-moz-focus-inner {
z-index: 1020; /* required to have it superimposed to the video element */
border: 0;
left: 1.2rem;
right: .7rem;
height: 2.6rem;
right: 1.2rem;
height: 2.4rem;
position: absolute;
bottom: 1.5rem;
bottom: 1.2rem;
}
html[dir="rtl"] .conversation-toolbar {
@ -48,11 +48,11 @@ html[dir="rtl"] .conversation-toolbar > li {
.conversation-toolbar .btn {
background-position: center;
background-size: 28px;
background-size: 24px;
background-repeat: no-repeat;
background-color: transparent;
height: 28px;
width: 33px;
height: 24px;
width: 24px;
}
.conversation-toolbar-media-btn-group-box {
@ -855,8 +855,8 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
}
.standalone-room-wrapper > .media-layout {
/* 50px is the header, 3em is the footer. */
height: calc(100% - 50px - 3em);
/* 50px is the header, 10px is the bottom margin. */
height: calc(100% - 50px - 10px);
margin: 0 10px;
}
@ -977,8 +977,8 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
@media screen and (max-width:640px) {
.standalone-room-wrapper > .media-layout {
/* 50px is height of header, 25px is height of footer. */
height: calc(100% - 50px - 25px);
/* 50px is height of header, 10px is bottom margin. */
height: calc(100% - 50px - 10px);
}
.media-layout > .media-wrapper {
@ -1427,8 +1427,8 @@ html[dir="rtl"] .text-chat-entry.received .text-chat-arrow {
left: 0;
}
.standalone .room-conversation-wrapper .video-layout-wrapper {
/* 50px: header's height; 25px: footer's height */
height: calc(100% - 50px - 25px);
/* 50px: header's height; 10px: bottom margin */
height: calc(100% - 50px - 10px);
}
.standalone .room-conversation .video_wrapper.remote_wrapper {
width: 100%;

View File

@ -25,7 +25,8 @@ npm_install:
# assets
.PHONY: dist
dist:
cp -pr content dist
mkdir -p dist
cp -pR content dist
NODE_ENV="production" $(NODE_LOCAL_BIN)/webpack \
-p -v --display-errors
sed 's#webappEntryPoint.js#js/standalone.js#' \

View File

@ -57,74 +57,27 @@ body,
z-index: 1000;
}
/* Footer */
/* Mozilla Logo */
.footer-logo {
width: 100px;
margin: 0 auto;
height: 30px;
.focus-stream > .standalone-moz-logo {
width: 50px;
height: 13px;
background-size: contain;
background-image: url("../img/mozilla-logo.svg#logo-white");
background-repeat: no-repeat;
}
.rooms-footer {
background: #000;
margin: 0 10px;
text-align: left;
height: 3em;
position: relative;
}
html[dir="rtl"] .rooms-footer {
text-align: right;
}
.rooms-footer p {
/* Right-margin offset to account for .footer-logo plus 20px. */
/* Zero other margins due to 1em margin from reset.css. */
margin: 0 120px 0 0;
/* Vertically align in the middle. */
position: absolute;
top: 50%;
transform: translate(0, -50%);
bottom: 1.2rem;
right: 1.2rem;
left: auto;
}
html[dir="rtl"] .rooms-footer p {
margin: 0 0 0 120px;
}
.rooms-footer a {
color: #555;
}
.rooms-footer .footer-logo {
/* Vertically-align in the middle. */
position: absolute;
top: 50%;
transform: translate(0, -50%);
/* Align to the right. */
right: 0;
}
html[dir="rtl"] .rooms-footer .footer-logo {
left: 0;
html[dir="rtl"] .focus-stream > .standalone-moz-logo {
left: 1.2rem;
right: auto;
}
@media screen and (max-width:640px) {
.rooms-footer {
font-size: 80%;
height: 25px;
text-align: center;
}
.rooms-footer p {
margin: 0;
width: 100%;
}
.rooms-footer .footer-logo {
.focus-stream > .standalone-moz-logo {
display: none;
}
}

View File

@ -416,20 +416,6 @@ loop.standaloneRoomViews = (function(mozL10n) {
}
});
var StandaloneRoomFooter = React.createClass({displayName: "StandaloneRoomFooter",
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
},
render: function() {
return (
React.createElement("footer", {className: "rooms-footer"},
React.createElement("div", {className: "footer-logo"})
)
);
}
});
var StandaloneRoomView = React.createClass({displayName: "StandaloneRoomView",
mixins: [
Backbone.Events,
@ -678,14 +664,26 @@ loop.standaloneRoomViews = (function(mozL10n) {
publishStream: this.publishStream,
show: true,
video: {enabled: !this.state.videoMuted,
visible: this._roomIsActive()}})
),
React.createElement(StandaloneRoomFooter, {dispatcher: this.props.dispatcher})
visible: this._roomIsActive()}}),
React.createElement(StandaloneMozLogo, {dispatcher: this.props.dispatcher})
)
)
);
}
});
var StandaloneMozLogo = React.createClass({displayName: "StandaloneMozLogo",
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
},
render: function() {
return (
React.createElement("div", {className: "standalone-moz-logo"})
);
}
});
var StandaloneRoomControllerView = React.createClass({displayName: "StandaloneRoomControllerView",
mixins: [
loop.store.StoreMixin("activeRoomStore")
@ -726,7 +724,6 @@ loop.standaloneRoomViews = (function(mozL10n) {
StandaloneHandleUserAgentView: StandaloneHandleUserAgentView,
StandaloneRoomControllerView: StandaloneRoomControllerView,
StandaloneRoomFailureView: StandaloneRoomFailureView,
StandaloneRoomFooter: StandaloneRoomFooter,
StandaloneRoomHeader: StandaloneRoomHeader,
StandaloneRoomInfoArea: StandaloneRoomInfoArea,
StandaloneRoomView: StandaloneRoomView,

View File

@ -416,20 +416,6 @@ loop.standaloneRoomViews = (function(mozL10n) {
}
});
var StandaloneRoomFooter = React.createClass({
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
},
render: function() {
return (
<footer className="rooms-footer">
<div className="footer-logo" />
</footer>
);
}
});
var StandaloneRoomView = React.createClass({
mixins: [
Backbone.Events,
@ -679,13 +665,25 @@ loop.standaloneRoomViews = (function(mozL10n) {
show={true}
video={{enabled: !this.state.videoMuted,
visible: this._roomIsActive()}} />
<StandaloneMozLogo dispatcher={this.props.dispatcher}/>
</sharedViews.MediaLayoutView>
<StandaloneRoomFooter dispatcher={this.props.dispatcher} />
</div>
);
}
});
var StandaloneMozLogo = React.createClass({
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
},
render: function() {
return (
<div className="standalone-moz-logo" />
);
}
});
var StandaloneRoomControllerView = React.createClass({
mixins: [
loop.store.StoreMixin("activeRoomStore")
@ -726,7 +724,6 @@ loop.standaloneRoomViews = (function(mozL10n) {
StandaloneHandleUserAgentView: StandaloneHandleUserAgentView,
StandaloneRoomControllerView: StandaloneRoomControllerView,
StandaloneRoomFailureView: StandaloneRoomFailureView,
StandaloneRoomFooter: StandaloneRoomFooter,
StandaloneRoomHeader: StandaloneRoomHeader,
StandaloneRoomInfoArea: StandaloneRoomInfoArea,
StandaloneRoomView: StandaloneRoomView,

View File

@ -315,7 +315,8 @@ var BookmarkPropertiesPanel = {
switch (this._action) {
case ACTION_EDIT:
gEditItemOverlay.initPanel({ node: this._node
, hiddenRows: this._hiddenRows });
, hiddenRows: this._hiddenRows
, focusedElement: "first" });
acceptButton.disabled = gEditItemOverlay.readOnly;
break;
case ACTION_ADD:
@ -323,7 +324,8 @@ var BookmarkPropertiesPanel = {
// Edit the new item
gEditItemOverlay.initPanel({ node: this._node
, hiddenRows: this._hiddenRows
, postData: this._postData });
, postData: this._postData
, focusedElement: "first" });
// Empty location field if the uri is about:blank, this way inserting a new
// url will be easier for the user, Accept button will be automatically

View File

@ -56,12 +56,13 @@ var gEditItemOverlay = {
PlacesUIUtils.isContentsReadOnly(parent);
}
}
let focusedElement = aInitInfo.focusedElement;
return this._paneInfo = { itemId, itemGuid, isItem,
isURI, uri, title,
isBookmark, isFolderShortcut, isParentReadOnly,
bulkTagging, uris,
visibleRows, postData, isTag };
visibleRows, postData, isTag, focusedElement };
},
get initialized() {
@ -181,7 +182,7 @@ var gEditItemOverlay = {
let { itemId, itemGuid, isItem,
isURI, uri, title,
isBookmark, bulkTagging, uris,
visibleRows } = this._setPaneInfo(aInfo);
visibleRows, focusedElement } = this._setPaneInfo(aInfo);
let showOrCollapse =
(rowId, isAppropriateForInput, nameInHiddenRows = null) => {
@ -252,6 +253,24 @@ var gEditItemOverlay = {
window.addEventListener("unload", this, false);
this._observersAdded = true;
}
// The focusedElement possible values are:
// * preferred: focus the field that the user touched first the last
// time the pane was shown (either namePicker or tagsField)
// * first: focus the first non collapsed textbox
// Note: since all controls are collapsed by default, we don't get the
// default XUL dialog behavior, that selects the first control, so we set
// the focus explicitly.
let elt;
if (focusedElement === "preferred") {
elt = this._element(gPrefService.getCharPref("browser.bookmarks.editDialog.firstEditField"));
} else if (focusedElement === "first") {
elt = document.querySelector("textbox:not([collapsed=true])");
}
if (elt) {
elt.focus();
elt.select();
}
},
/**

View File

@ -322,6 +322,15 @@ var withBookmarksDialog = Task.async(function* (autoCancel, openFn, taskFn) {
yield waitForCondition(() => dialogWin.gEditItemOverlay.initialized,
"EditItemOverlay should be initialized");
// Check the first textbox is focused.
let doc = dialogWin.document;
let elt = doc.querySelector("textbox:not([collapsed=true])");
if (elt) {
info("waiting for focus on the first textfield");
yield waitForCondition(() => doc.activeElement == elt.inputField,
"The first non collapsed textbox should have been focused");
}
info("withBookmarksDialog: executing the task");
try {
yield taskFn(dialogWin);
@ -331,7 +340,7 @@ var withBookmarksDialog = Task.async(function* (autoCancel, openFn, taskFn) {
ok(false, "The test should have closed the dialog!");
}
info("withBookmarksDialog: canceling the dialog");
dialogWin.document.documentElement.cancelDialog();
doc.documentElement.cancelDialog();
}
}
});

View File

@ -765,7 +765,7 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
#forward-button > .toolbarbutton-icon {
background-clip: padding-box;
padding-left: 9px;
padding-left: calc(var(--backbutton-urlbar-overlap) + 4px);
padding-right: 3px;
border: 1px solid #9a9a9a;
border-left-style: none;
@ -1206,7 +1206,7 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
-moz-appearance: none;
list-style-image: url("chrome://browser/skin/reload-stop-go.png");
padding: 0 9px;
margin-inline-start: 2px;
margin-inline-start: 5px;
border-inline-start: 1px solid var(--urlbar-separator-color);
border-image: linear-gradient(transparent 15%,
var(--urlbar-separator-color) 15%,

View File

@ -8,7 +8,6 @@ browser.jar:
#include ../shared/jar.inc.mn
skin/classic/browser/sanitizeDialog.css
skin/classic/browser/aboutSessionRestore-window-icon.png
skin/classic/browser/aboutCertError.css
skin/classic/browser/aboutCertError_sectionCollapsed.png
skin/classic/browser/aboutCertError_sectionCollapsed-rtl.png
skin/classic/browser/aboutCertError_sectionExpanded.png

View File

@ -1,72 +0,0 @@
/* 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 {
background: -moz-Dialog;
}
body {
margin: 0;
padding: 0 1em;
color: -moz-FieldText;
font: message-box;
}
h1 {
margin: 0 0 .6em 0;
border-bottom: 1px solid ThreeDLightShadow;
font-size: 160%;
}
h2 {
font-size: 130%;
}
#errorPageContainer {
position: relative;
min-width: 13em;
max-width: 52em;
margin: 4em auto;
border: 1px solid #FFBD09; /* pale yellow extracted from yellow passport icon */
border-radius: 10px;
padding: 3em;
-moz-padding-start: 30px;
background: url("chrome://global/skin/icons/sslWarning.png") left 0 no-repeat -moz-Field;
background-origin: content-box;
}
#errorPageContainer:-moz-dir(rtl) {
background-position: right 0;
}
#errorTitle {
-moz-margin-start: 80px;
}
#errorLongContent {
-moz-margin-start: 80px;
}
.expander > button {
-moz-padding-start: 20px;
-moz-margin-start: -20px;
background: url("chrome://browser/skin/aboutCertError_sectionExpanded.png") left center no-repeat;
border: none;
font: inherit;
color: inherit;
cursor: pointer;
}
.expander > button:-moz-dir(rtl) {
background-position: right center;
}
.expander[collapsed] > button {
background-image: url("chrome://browser/skin/aboutCertError_sectionCollapsed.png");
}
.expander[collapsed] > button:-moz-dir(rtl) {
background-image: url("chrome://browser/skin/aboutCertError_sectionCollapsed-rtl.png");
}

View File

@ -1898,8 +1898,7 @@ richlistitem[type~="action"][actiontype="switchtab"][selected="true"] > .ac-url-
margin: 0;
list-style-image: url("chrome://browser/skin/reload-stop-go.png");
padding: 0 9px;
margin-inline-start: 2px;
border-inline-end-style: none;
margin-inline-start: 5px;
border-inline-start: 1px solid var(--urlbar-separator-color);
border-image: linear-gradient(transparent 15%,
var(--urlbar-separator-color) 15%,

View File

@ -7,7 +7,6 @@ browser.jar:
#include ../shared/jar.inc.mn
skin/classic/browser/sanitizeDialog.css
skin/classic/browser/aboutSessionRestore-window-icon.png
skin/classic/browser/aboutCertError.css
skin/classic/browser/aboutCertError_sectionCollapsed.png
skin/classic/browser/aboutCertError_sectionCollapsed-rtl.png
skin/classic/browser/aboutCertError_sectionExpanded.png

View File

@ -5,7 +5,7 @@
%endif
/* Hide all conditional elements by default. */
:-moz-any([when-connection],[when-mixedcontent],[when-ciphers]) {
:-moz-any([when-connection],[when-mixedcontent],[when-ciphers],[when-loginforms]) {
display: none;
}
@ -15,6 +15,8 @@
#identity-popup[connection=secure] [when-connection~=secure],
#identity-popup[connection=chrome] [when-connection~=chrome],
#identity-popup[connection=file] [when-connection~=file],
/* Show insecure login forms messages when needed. */
#identity-popup[loginforms=insecure] [when-loginforms=insecure],
/* Show weak cipher messages when needed. */
#identity-popup[ciphers=weak] [when-ciphers~=weak],
/* Show mixed content warnings when needed */
@ -28,6 +30,14 @@
display: inherit;
}
/* Hide redundant messages based on insecure login forms presence. */
#identity-popup[loginforms=secure] [and-when-loginforms=insecure] {
display: none;
}
#identity-popup[loginforms=insecure] [and-when-loginforms=secure] {
display: none;
}
/* Hide 'not secure' message in subview when weak cipher or mixed content messages are shown. */
#identity-popup-securityView-body:-moz-any([mixedcontent],[ciphers]) > description[when-connection=not-secure],
/* Hide 'passive-loaded (only)' message when there is mixed passive content loaded and active blocked. */
@ -219,6 +229,8 @@
background-image: url(chrome://browser/skin/controlcenter/conn-degraded.svg);
}
#identity-popup[loginforms=insecure] #identity-popup-securityView,
#identity-popup[loginforms=insecure] #identity-popup-security-content,
#identity-popup[mixedcontent~=active-loaded][isbroken] #identity-popup-securityView,
#identity-popup[mixedcontent~=active-loaded][isbroken] #identity-popup-security-content {
background-image: url(chrome://browser/skin/controlcenter/mcb-disabled.svg);

View File

@ -123,6 +123,7 @@
list-style-image: url(chrome://browser/skin/identity-secure.svg);
}
.insecureLoginForms > #identity-icons > #page-proxy-favicon[pageproxystate="valid"],
.mixedActiveContent > #identity-icons > #page-proxy-favicon[pageproxystate="valid"] {
list-style-image: url(chrome://browser/skin/identity-mixed-active-loaded.svg);
}

View File

@ -7,6 +7,7 @@
# be specified once. As a result, the source file paths are relative
# to the location of the actual manifest.
skin/classic/browser/aboutCertError.css (../shared/aboutCertError.css)
skin/classic/browser/aboutNetError.css (../shared/aboutNetError.css)
skin/classic/browser/aboutNetError_info.svg (../shared/aboutNetError_info.svg)
skin/classic/browser/aboutNetError_alert.svg (../shared/aboutNetError_alert.svg)

View File

@ -99,7 +99,7 @@
}
@conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #notification-popup-box {
padding-left: 7px;
padding-left: calc(var(--backbutton-urlbar-overlap) + 3px);
}
/* This changes the direction of the main notification box on the url bar. */

View File

@ -1,72 +0,0 @@
/* 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 {
background: -moz-Dialog;
}
body {
margin: 0;
padding: 0 1em;
color: -moz-FieldText;
font: message-box;
}
h1 {
margin: 0 0 .6em 0;
border-bottom: 1px solid ThreeDLightShadow;
font-size: 160%;
}
h2 {
font-size: 130%;
}
#errorPageContainer {
position: relative;
min-width: 13em;
max-width: 52em;
margin: 4em auto;
border: 1px solid #FFBD09; /* pale yellow extracted from yellow passport icon */
border-radius: 10px;
padding: 3em;
-moz-padding-start: 30px;
background: url("chrome://global/skin/icons/sslWarning.png") left 0 no-repeat -moz-Field;
background-origin: content-box;
}
#errorPageContainer:-moz-dir(rtl) {
background-position: right 0;
}
#errorTitle {
-moz-margin-start: 80px;
}
#errorLongContent {
-moz-margin-start: 80px;
}
.expander > button {
-moz-padding-start: 20px;
-moz-margin-start: -20px;
background: url("chrome://browser/skin/aboutCertError_sectionExpanded.png") left center no-repeat;
border: none;
font: inherit;
color: inherit;
cursor: pointer;
}
.expander > button:-moz-dir(rtl) {
background-position: right center;
}
.expander[collapsed] > button {
background-image: url("chrome://browser/skin/aboutCertError_sectionCollapsed.png");
}
.expander[collapsed] > button:-moz-dir(rtl) {
background-image: url("chrome://browser/skin/aboutCertError_sectionCollapsed-rtl.png");
}

View File

@ -12,25 +12,23 @@
%include windowsShared.inc
%filter substitution
%define toolbarShadowColor hsla(209,67%,12%,0.35)
%define navbarTextboxCustomBorder border-color: rgba(0,0,0,.32);
%define forwardTransitionLength 150ms
%define conditionalForwardWithUrlbar window:not([chromehidden~="toolbar"]) #urlbar-wrapper
%define conditionalForwardWithUrlbarWidth 30
:root {
--space-above-tabbar: 15px;
--backbutton-urlbar-overlap: 5px;
--backbutton-urlbar-overlap: 6px;
--toolbarbutton-vertical-inner-padding: 2px;
--toolbarbutton-vertical-outer-padding: 8px;
--toolbarbutton-hover-background: rgba(0,0,0,.1);
--toolbarbutton-hover-bordercolor: rgba(0,0,0,.1);
--toolbarbutton-hover-bordercolor: rgba(0,0,0,.2);
--toolbarbutton-hover-boxshadow: none;
--toolbarbutton-active-background: rgba(0,0,0,.15);
--toolbarbutton-active-bordercolor: rgba(0,0,0,.15);
--toolbarbutton-active-bordercolor: rgba(0,0,0,.3);
--toolbarbutton-active-boxshadow: 0 0 0 1px rgba(0,0,0,.15) inset;
--toolbarbutton-checkedhover-backgroundcolor: rgba(0,0,0,.1);
@ -51,18 +49,6 @@
--urlbar-separator-color: hsla(0,0%,16%,.2);
}
#nav-bar[brighttext] {
--toolbarbutton-hover-background: rgba(255,255,255,.15);
--toolbarbutton-hover-bordercolor: rgba(255,255,255,.15);
--toolbarbutton-hover-boxshadow: none;
--toolbarbutton-active-background: rgba(255,255,255,.3);
--toolbarbutton-active-bordercolor: rgba(255,255,255,.3);
--toolbarbutton-active-boxshadow: 0 0 0 1px rgba(255,255,255,.3) inset;
--toolbarbutton-checkedhover-backgroundcolor: rgba(255,255,255,.2);
}
#menubar-items {
-moz-box-orient: vertical; /* for flex hack */
}
@ -727,6 +713,8 @@ toolbar[brighttext] .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
@conditionalForwardWithUrlbar@ > .toolbarbutton-1:-moz-any([disabled],:not([open]):not([disabled]):not(:active)) > .toolbarbutton-icon {
padding: var(--toolbarbutton-vertical-inner-padding) 6px;
background-origin: padding-box !important;
background-clip: padding-box !important;
border: 1px solid transparent;
border-radius: 1px;
transition-property: background-color, border-color, box-shadow;
@ -856,10 +844,6 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
opacity: .3;
}
@conditionalForwardWithUrlbar@ > .toolbarbutton-1:-moz-any([disabled],:not([open]):not([disabled]):not(:active)) > .toolbarbutton-icon {
border-color: hsla(210,4%,10%,.1);
}
.findbar-button:not(:-moz-any([checked="true"],[disabled="true"])):hover > .toolbarbutton-text,
#nav-bar .toolbarbutton-1:not([disabled=true]) > .toolbarbutton-menubutton-button[open] + .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
#nav-bar .toolbarbutton-1:not([disabled=true]):-moz-any(:hover,[open]) > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
@ -919,6 +903,15 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
/* unified back/forward button */
:-moz-any(#back-button, #forward-button) > .toolbarbutton-icon {
border-color: var(--urlbar-border-color-hover) !important;
}
:-moz-any(#back-button, #forward-button):not(:hover):not(:active):not([open=true]) > .toolbarbutton-icon,
:-moz-any(#back-button, #forward-button)[disabled=true] > .toolbarbutton-icon {
background-color: rgba(255,255,255,.15) !important;
}
#forward-button {
-moz-box-align: stretch; /* let the button shape grow vertically with the location bar */
padding: 0 !important;
@ -929,12 +922,13 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
}
#forward-button > .toolbarbutton-icon {
background-clip: padding-box !important;
border-left-style: none !important;
border-radius: 0 !important;
padding-left: 9px !important;
padding-left: calc(var(--backbutton-urlbar-overlap) + 3px) !important;
padding-right: 3px !important;
width: 31px !important; /* horizontal padding + border + actual icon width */
% icon width + border + horizontal padding without --backbutton-urlbar-overlap
%define forwardButtonWidth 25
width: calc(@forwardButtonWidth@px + var(--backbutton-urlbar-overlap)) !important;
}
@conditionalForwardWithUrlbar@:not([switchingtabs]) > #forward-button {
@ -942,7 +936,7 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
}
@conditionalForwardWithUrlbar@ > #forward-button[disabled] {
margin-left: -@conditionalForwardWithUrlbarWidth@px;
margin-left: calc(-@forwardButtonWidth@px - var(--backbutton-urlbar-overlap));
}
@conditionalForwardWithUrlbar@:hover:not([switchingtabs]) > #forward-button[disabled] {
@ -952,7 +946,7 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] {
/* when not hovered anymore, trigger a new transition to hide the forward button immediately */
margin-left: -@conditionalForwardWithUrlbarWidth@.01px;
margin-left: calc(-@forwardButtonWidth@.01px - var(--backbutton-urlbar-overlap));
}
#back-button {
@ -963,8 +957,6 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
position: relative !important;
z-index: 1 !important;
border-radius: 0 10000px 10000px 0 !important;
--toolbarbutton-hover-boxshadow: 0 1px 0 0 hsla(210,4%,10%,.25),
0 0 0 1px hsla(210,4%,10%,.25);
}
#back-button:-moz-locale-dir(rtl) {
@ -977,64 +969,8 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
#back-button > .toolbarbutton-icon {
border-radius: 10000px !important;
background-clip: padding-box !important;
background-color: hsla(210,25%,98%,.08) !important;
padding: 6px !important;
border-style: none !important;
width: 30px !important; /* horizontal padding + border + actual icon width */
box-shadow: 0 1px 0 0 hsla(210,4%,10%,.25),
0 0 0 1px hsla(210,4%,10%,.25);
transition-property: background-color, box-shadow !important;
transition-duration: 250ms !important;
}
#back-button:not([disabled="true"]):not([open="true"]):not(:active):hover > .toolbarbutton-icon {
background-color: hsla(210,4%,10%,.08) !important;
}
#back-button:not([disabled="true"]):hover:active > .toolbarbutton-icon,
#back-button[open="true"] > .toolbarbutton-icon {
background-color: hsla(210,4%,10%,.12) !important;
box-shadow: 0 1px 0 0 hsla(210,4%,10%,.25),
0 0 0 1px hsla(210,4%,10%,.25),
0 1px 0 0 hsla(210,80%,20%,.1) inset !important;
}
@media (-moz-os-version: windows-xp),
(-moz-os-version: windows-vista),
(-moz-os-version: windows-win7) {
#back-button > .toolbarbutton-icon {
background-image: linear-gradient(hsla(0,0%,100%,.6), hsla(0,0%,100%,.1)) !important;
box-shadow: 0 1px 0 hsla(0,0%,100%,.3) inset,
0 0 0 1px hsla(0,0%,100%,.3) inset,
0 0 0 1px hsla(210,54%,20%,.25),
0 1px 0 hsla(210,54%,20%,.35) !important;
}
#back-button:not([disabled="true"]):not([open="true"]):not(:active):hover > .toolbarbutton-icon {
background-color: hsla(210,48%,96%,.75) !important;
box-shadow: 0 1px 0 hsla(0,0%,100%,.3) inset,
0 0 0 1px hsla(0,0%,100%,.3) inset,
0 0 0 1px hsla(210,54%,20%,.3),
0 1px 0 hsla(210,54%,20%,.4),
0 0 4px hsla(210,54%,20%,.2) !important;
}
#back-button:not([disabled="true"]):hover:active > .toolbarbutton-icon,
#back-button[open="true"] > .toolbarbutton-icon {
background-color: hsla(210,54%,20%,.15) !important;
box-shadow: 0 1px 1px hsla(210,54%,20%,.1) inset,
0 0 1px hsla(210,54%,20%,.2) inset,
0 0 0 1px hsla(210,54%,20%,.4),
0 1px 0 hsla(210,54%,20%,.2) !important;
transition: none;
}
#main-window:not([customizing]) #back-button[disabled] > .toolbarbutton-icon {
box-shadow: 0 0 0 1px hsla(210,54%,20%,.55),
0 1px 0 hsla(210,54%,20%,.65) !important;
transition: none;
}
width: 32px !important; /* icon width + horizontal padding + border */
}
#back-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
@ -1173,13 +1109,46 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
/* ::::: Location Bar ::::: */
#nav-bar {
--urlbar-border-color: ThreeDShadow;
--urlbar-border-color-hover: var(--urlbar-border-color);
}
#nav-bar:-moz-lwtheme {
--urlbar-border-color: rgba(0,0,0,.32);
}
@media (-moz-windows-default-theme) {
@media (-moz-os-version: windows-vista),
(-moz-os-version: windows-win7),
(-moz-os-version: windows-win8) {
#nav-bar:not(:-moz-lwtheme) {
--urlbar-border-color: hsla(210,54%,20%,.25) hsla(210,54%,20%,.27) hsla(210,54%,20%,.3);
--urlbar-border-color-hover: hsla(210,54%,20%,.35) hsla(210,54%,20%,.37) hsla(210,54%,20%,.4);
}
}
@media (-moz-os-version: windows-win10) {
#nav-bar:not(:-moz-lwtheme) {
--urlbar-border-color: hsl(0,0%,90%);
--urlbar-border-color-hover: hsl(0,0%,80%);
}
}
}
#urlbar,
.searchbar-textbox {
-moz-appearance: none;
margin: 0 3px;
padding: 0;
background-clip: padding-box;
border: 1px solid ThreeDShadow;
border: 1px solid;
border-color: var(--urlbar-border-color);
}
#urlbar:hover,
.searchbar-textbox:hover {
border-color: var(--urlbar-border-color-hover);
}
/* overlap the urlbar's border */
@ -1190,7 +1159,6 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
@media (-moz-windows-default-theme) {
#urlbar,
.searchbar-textbox {
@navbarTextboxCustomBorder@
border-radius: 1px;
}
@ -1199,31 +1167,19 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
(-moz-os-version: windows-win8) {
#urlbar:not(:-moz-lwtheme),
.searchbar-textbox:not(:-moz-lwtheme) {
border-color: hsla(210,54%,20%,.25) hsla(210,54%,20%,.27) hsla(210,54%,20%,.3);
box-shadow: 0 1px 0 hsla(0,0%,0%,.01) inset,
0 1px 0 hsla(0,0%,100%,.1);
}
#urlbar:not(:-moz-lwtheme):hover,
.searchbar-textbox:not(:-moz-lwtheme):hover {
border-color: hsla(210,54%,20%,.35) hsla(210,54%,20%,.37) hsla(210,54%,20%,.4);
}
}
@media (-moz-os-version: windows-win10) {
#urlbar:not(:-moz-lwtheme),
.searchbar-textbox:not(:-moz-lwtheme) {
border-color: hsl(0,0%,90%);
padding: 1px;
transition-property: border-color, box-shadow;
transition-duration: .1s;
}
#urlbar:not(:-moz-lwtheme):hover,
.searchbar-textbox:not(:-moz-lwtheme):hover {
border-color: hsl(0,0%,80%);
}
#urlbar:not(:-moz-lwtheme)[focused],
.searchbar-textbox:not(:-moz-lwtheme)[focused] {
box-shadow: 0 0 0 1px Highlight inset;
@ -1260,7 +1216,6 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
#urlbar:-moz-lwtheme,
.searchbar-textbox:-moz-lwtheme {
background-color: rgba(255,255,255,.8);
@navbarTextboxCustomBorder@
color: black;
}
@ -1301,7 +1256,7 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
}
:root {
--backbutton-urlbar-overlap: 8px;
--backbutton-urlbar-overlap: 9px;
}
}
@ -1609,7 +1564,7 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
border-style: none;
list-style-image: url("chrome://browser/skin/reload-stop-go.png");
padding: 0 9px;
margin-inline-start: 2px;
margin-inline-start: 5px;
border-inline-start: 1px solid var(--urlbar-separator-color);
border-image: linear-gradient(transparent 15%,
var(--urlbar-separator-color) 15%,

View File

@ -7,7 +7,6 @@ browser.jar:
#include ../shared/jar.inc.mn
skin/classic/browser/sanitizeDialog.css
skin/classic/browser/aboutSessionRestore-window-icon.png
skin/classic/browser/aboutCertError.css
skin/classic/browser/aboutCertError_sectionCollapsed.png
skin/classic/browser/aboutCertError_sectionCollapsed-rtl.png
skin/classic/browser/aboutCertError_sectionExpanded.png

View File

@ -164,6 +164,7 @@ devtools.jar:
* skin/themes/common.css (themes/common.css)
* skin/themes/dark-theme.css (themes/dark-theme.css)
* skin/themes/light-theme.css (themes/light-theme.css)
skin/themes/variables.css (themes/variables.css)
skin/themes/images/add.svg (themes/images/add.svg)
skin/themes/images/filters.svg (themes/images/filters.svg)
skin/themes/images/filter-swatch.svg (themes/images/filter-swatch.svg)

View File

@ -23,6 +23,11 @@ add_task(function*() {
parentContainer.elt.scrollIntoView(true);
info("Testing putting an element back in it's original place");
yield dragElementToOriginalLocation("#firstChild", inspector);
is(parent.children[0].id, "firstChild", "#firstChild is still the first child of #test");
is(parent.children[1].id, "middleChild", "#middleChild is still the second child of #test");
info("Testing switching elements inside their parent");
yield moveElementDown("#firstChild", "#middleChild", inspector);
@ -95,6 +100,27 @@ function* dragContainer(selector, targetOffset, inspector) {
return updated;
};
function* dragElementToOriginalLocation(selector, inspector) {
let el = yield getContainerForSelector(selector, inspector);
let height = el.tagLine.getBoundingClientRect().height;
info("Picking up and putting back down " + selector);
function onMutation() {
ok(false, "Mutation received from dragging a node back to its location");
}
inspector.on("markupmutation", onMutation);
yield dragContainer(selector, {x: 0, y: 0}, inspector);
// Wait a bit to make sure the event never fires.
// This doesn't need to catch *all* cases, since the mutation
// will cause failure later in the test when it checks element ordering.
yield new Promise(resolve => {
setTimeout(resolve, 500);
});
inspector.off("markupmutation", onMutation);
}
function* moveElementDown(selector, next, inspector) {
let onMutated = inspector.once("markupmutation");
let uiUpdate = inspector.once("inspector-updated");
@ -106,6 +132,7 @@ function* moveElementDown(selector, next, inspector) {
yield dragContainer(selector, {x: 0, y: Math.round(height) + 2}, inspector);
yield onMutated;
let mutations = yield onMutated;
is(mutations.length, 2, "2 mutations");
yield uiUpdate;
};

View File

@ -14,7 +14,6 @@
"use strict";
const {cssTokenizer} = require("devtools/client/sourceeditor/css-tokenizer");
const {Cc, Ci, Cu} = require("chrome");
Cu.importGlobalProperties(["CSS"]);
const promise = require("promise");
@ -43,6 +42,85 @@ const BLANK_LINE_RX = /^[ \t]*(?:\r\n|\n|\r|\f|$)/;
// bypass the property name validity heuristic.
const COMMENT_PARSING_HEURISTIC_BYPASS_CHAR = "!";
/**
* A generator function that lexes a CSS source string, yielding the
* CSS tokens. Comment tokens are dropped.
*
* @param {String} CSS source string
* @yield {CSSToken} The next CSSToken that is lexed
* @see CSSToken for details about the returned tokens
*/
function* cssTokenizer(string) {
let lexer = DOMUtils.getCSSLexer(string);
while (true) {
let token = lexer.nextToken();
if (!token) {
break;
}
// None of the existing consumers want comments.
if (token.tokenType !== "comment") {
yield token;
}
}
}
/**
* Pass |string| to the CSS lexer and return an array of all the
* returned tokens. Comment tokens are not included. In addition to
* the usual information, each token will have starting and ending
* line and column information attached. Specifically, each token
* has an additional "loc" attribute. This attribute is an object
* of the form {line: L, column: C}. Lines and columns are both zero
* based.
*
* It's best not to add new uses of this function. In general it is
* simpler and better to use the CSSToken offsets, rather than line
* and column. Also, this function lexes the entire input string at
* once, rather than lazily yielding a token stream. Use
* |cssTokenizer| or |DOMUtils.getCSSLexer| instead.
*
* @param{String} string The input string.
* @return {Array} An array of tokens (@see CSSToken) that have
* line and column information.
*/
function cssTokenizerWithLineColumn(string) {
let lexer = DOMUtils.getCSSLexer(string);
let result = [];
let prevToken = undefined;
while (true) {
let token = lexer.nextToken();
let lineNumber = lexer.lineNumber;
let columnNumber = lexer.columnNumber;
if (prevToken) {
prevToken.loc.end = {
line: lineNumber,
column: columnNumber
};
}
if (!token) {
break;
}
if (token.tokenType === "comment") {
// We've already dealt with the previous token's location.
prevToken = undefined;
} else {
let startLoc = {
line: lineNumber,
column: columnNumber
};
token.loc = {start: startLoc};
result.push(token);
prevToken = token;
}
}
return result;
}
/**
* Escape a comment body. Find the comment start and end strings in a
* string and inserts backslashes so that the resulting text can
@ -992,6 +1070,8 @@ function parseSingleValue(value) {
};
}
exports.cssTokenizer = cssTokenizer;
exports.cssTokenizerWithLineColumn = cssTokenizerWithLineColumn;
exports.escapeCSSComment = escapeCSSComment;
// unescapeCSSComment is exported for testing.
exports._unescapeCSSComment = unescapeCSSComment;

View File

@ -17,6 +17,7 @@ DevToolsModules(
'AppCacheUtils.jsm',
'autocomplete-popup.js',
'browser-loader.js',
'css-parsing-utils.js',
'Curl.jsm',
'DeveloperToolbar.jsm',
'devices.js',

View File

@ -8,7 +8,7 @@
var Cu = Components.utils;
Cu.import("resource://gre/modules/devtools/Loader.jsm");
const {escapeCSSComment, _unescapeCSSComment} =
devtools.require("devtools/client/styleinspector/css-parsing-utils");
devtools.require("devtools/client/shared/css-parsing-utils");
const TEST_DATA = [
{

View File

@ -6,9 +6,9 @@
"use strict";
var Cu = Components.utils;
const {require} = Cu.import("resource://gre/modules/devtools/shared/Loader.jsm", {});
const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
const {parseDeclarations, _parseCommentDeclarations} =
require("devtools/client/styleinspector/css-parsing-utils");
require("devtools/client/shared/css-parsing-utils");
const TEST_DATA = [
// Simple test

View File

@ -6,13 +6,13 @@
"use strict";
var Cu = Components.utils;
const {require} = Cu.import("resource://gre/modules/devtools/shared/Loader.jsm", {});
const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
const {
parsePseudoClassesAndAttributes,
SELECTOR_ATTRIBUTE,
SELECTOR_ELEMENT,
SELECTOR_PSEUDO_CLASS
} = require("devtools/client/styleinspector/css-parsing-utils");
} = require("devtools/client/shared/css-parsing-utils");
const TEST_DATA = [
// Test that a null input throws an exception

View File

@ -6,8 +6,8 @@
"use strict";
var Cu = Components.utils;
const {require} = Cu.import("resource://gre/modules/devtools/shared/Loader.jsm", {});
const {parseSingleValue} = require("devtools/client/styleinspector/css-parsing-utils");
const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
const {parseSingleValue} = require("devtools/client/shared/css-parsing-utils");
const TEST_DATA = [
{input: null, throws: true},

View File

@ -8,7 +8,7 @@
var Cu = Components.utils;
Cu.import("resource://gre/modules/devtools/Loader.jsm");
const {parseDeclarations, RuleRewriter} =
devtools.require("devtools/client/styleinspector/css-parsing-utils");
devtools.require("devtools/client/shared/css-parsing-utils");
const TEST_DATA = [
{

View File

@ -10,6 +10,11 @@ skip-if = toolkit == 'android' || toolkit == 'gonk'
[test_attribute-parsing-02.js]
[test_bezierCanvas.js]
[test_cubicBezier.js]
[test_escapeCSSComment.js]
[test_parseDeclarations.js]
[test_parsePseudoClassesAndAttributes.js]
[test_parseSingleValue.js]
[test_rewriteDeclarations.js]
[test_undoStack.js]
[test_VariablesView_filtering-without-controller.js]
[test_VariablesView_getString_promise.js]

View File

@ -14,12 +14,13 @@ const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
loader.lazyRequireGetter(this, "Services");
loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/client/framework/gDevTools.jsm");
const themeURIs = {
light: "chrome://devtools/skin/themes/light-theme.css",
dark: "chrome://devtools/skin/themes/dark-theme.css"
const VARIABLES_URI = "chrome://devtools/skin/themes/variables.css";
const THEME_SELECTOR_STRINGS = {
light: ":root.theme-light {",
dark: ":root.theme-dark {"
}
const cachedThemes = {};
let variableFileContents;
/**
* Returns a string of the file found at URI
@ -37,19 +38,29 @@ function readURI (uri) {
}
/**
* Takes a theme name and either returns it from the cache,
* or fetches the theme CSS file and caches it.
* Takes a theme name and returns the contents of its variable rule block.
* The first time this runs fetches the variables CSS file and caches it.
*/
function getThemeFile (name) {
// Use the cached theme, or generate it
let themeFile = cachedThemes[name] || readURI(themeURIs[name]).match(/--theme-.*: .*;/g).join("\n");
// Cache if not already cached
if (!cachedThemes[name]) {
cachedThemes[name] = themeFile;
if (!variableFileContents) {
variableFileContents = readURI(VARIABLES_URI);
}
return themeFile;
// If there's no theme expected for this name, use `light` as default.
let selector = THEME_SELECTOR_STRINGS[name] ||
THEME_SELECTOR_STRINGS["light"];
// This is a pretty naive way to find the contents between:
// selector {
// name: val;
// }
// There is test coverage for this feature (browser_theme.js)
// so if an } is introduced in the variables file it will catch that.
let theme = variableFileContents;
theme = theme.substring(theme.indexOf(selector));
theme = theme.substring(0, theme.indexOf("}"));
return theme;
}
/**
@ -66,17 +77,11 @@ const getTheme = exports.getTheme = () => Services.prefs.getCharPref("devtools.t
*/
const getColor = exports.getColor = (type, theme) => {
let themeName = theme || getTheme();
// If there's no theme URIs for this theme, use `light` as default.
if (!themeURIs[themeName]) {
themeName = "light";
}
let themeFile = getThemeFile(themeName);
let match;
let match = themeFile.match(new RegExp("--theme-" + type + ": (.*);"));
// Return the appropriate variable in the theme, or otherwise, null.
return (match = themeFile.match(new RegExp("--theme-" + type + ": (.*);"))) ? match[1] : null;
return match ? match[1] : null;
};
/**

View File

@ -14,7 +14,7 @@ const { Cu } = require("chrome");
const { ViewHelpers } = Cu.import("resource:///modules/devtools/client/shared/widgets/ViewHelpers.jsm", {});
const STRINGS_URI = "chrome://browser/locale/devtools/filterwidget.properties";
const L10N = new ViewHelpers.L10N(STRINGS_URI);
const {cssTokenizer} = require("devtools/client/sourceeditor/css-tokenizer");
const {cssTokenizer} = require("devtools/client/shared/css-parsing-utils");
loader.lazyGetter(this, "asyncStorage",
() => require("devtools/shared/shared/async-storage"));

View File

@ -4,10 +4,10 @@
const { Cc, Ci, Cu } = require('chrome');
const {cssTokenizer, cssTokenizerWithLineColumn} =
require("devtools/client/sourceeditor/css-tokenizer");
require("devtools/client/shared/css-parsing-utils");
/**
* Here is what this file (+ ./css-tokenizer.js) do.
* Here is what this file (+ css-parsing-utils.js) do.
*
* The main objective here is to provide as much suggestions to the user editing
* a stylesheet in Style Editor. The possible things that can be suggested are:
@ -22,7 +22,7 @@ const {cssTokenizer, cssTokenizerWithLineColumn} =
* edited by the user, figure out what token or word is being written and last
* but the most difficult, what is being edited.
*
* The file 'css-tokenizer' helps in converting the CSS into meaningful tokens,
* The file 'css-parsing-utils' helps to convert the CSS into meaningful tokens,
* each having a certain type associated with it. These tokens help us to figure
* out the currently edited word and to write a CSS state machine to figure out
* what the user is currently editing. By that, I mean, whether he is editing a

View File

@ -1,95 +0,0 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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/. */
"use strict";
const {Cc, Ci} = require("chrome");
loader.lazyGetter(this, "DOMUtils", () => {
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
});
/**
* A generator function that lexes a CSS source string, yielding the
* CSS tokens. Comment tokens are dropped.
*
* @param {String} CSS source string
* @yield {CSSToken} The next CSSToken that is lexed
* @see CSSToken for details about the returned tokens
*/
function* cssTokenizer(string) {
let lexer = DOMUtils.getCSSLexer(string);
while (true) {
let token = lexer.nextToken();
if (!token) {
break;
}
// None of the existing consumers want comments.
if (token.tokenType !== "comment") {
yield token;
}
}
}
exports.cssTokenizer = cssTokenizer;
/**
* Pass |string| to the CSS lexer and return an array of all the
* returned tokens. Comment tokens are not included. In addition to
* the usual information, each token will have starting and ending
* line and column information attached. Specifically, each token
* has an additional "loc" attribute. This attribute is an object
* of the form {line: L, column: C}. Lines and columns are both zero
* based.
*
* It's best not to add new uses of this function. In general it is
* simpler and better to use the CSSToken offsets, rather than line
* and column. Also, this function lexes the entire input string at
* once, rather than lazily yielding a token stream. Use
* |cssTokenizer| or |DOMUtils.getCSSLexer| instead.
*
* @param{String} string The input string.
* @return {Array} An array of tokens (@see CSSToken) that have
* line and column information.
*/
function cssTokenizerWithLineColumn(string) {
let lexer = DOMUtils.getCSSLexer(string);
let result = [];
let prevToken = undefined;
while (true) {
let token = lexer.nextToken();
let lineNumber = lexer.lineNumber;
let columnNumber = lexer.columnNumber;
if (prevToken) {
prevToken.loc.end = {
line: lineNumber,
column: columnNumber
};
}
if (!token) {
break;
}
if (token.tokenType === "comment") {
// We've already dealt with the previous token's location.
prevToken = undefined;
} else {
let startLoc = {
line: lineNumber,
column: columnNumber
};
token.loc = {start: startLoc};
result.push(token);
prevToken = token;
}
}
return result;
}
exports.cssTokenizerWithLineColumn = cssTokenizerWithLineColumn;

View File

@ -7,7 +7,6 @@
DevToolsModules(
'autocomplete.js',
'css-autocompleter.js',
'css-tokenizer.js',
'debugger.js',
'editor.js'
)

View File

@ -5,11 +5,9 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
DevToolsModules(
'computed-view.js',
'css-parsing-utils.js',
'rule-view.js',
'style-inspector-menu.js',
'style-inspector-overlays.js',

View File

@ -33,7 +33,7 @@ const {
SELECTOR_ATTRIBUTE,
SELECTOR_ELEMENT,
SELECTOR_PSEUDO_CLASS
} = require("devtools/client/styleinspector/css-parsing-utils");
} = require("devtools/client/shared/css-parsing-utils");
loader.lazyRequireGetter(this, "overlays",
"devtools/client/styleinspector/style-inspector-overlays");
loader.lazyRequireGetter(this, "EventEmitter",

View File

@ -1,4 +0,0 @@
{
// Extend from the common devtools xpcshell eslintrc config.
"extends": "../../../../.eslintrc.xpcshell"
}

View File

@ -1,12 +0,0 @@
[DEFAULT]
tags = devtools
head =
tail =
firefox-appdir = browser
skip-if = toolkit == 'android' || toolkit == 'gonk'
[test_escapeCSSComment.js]
[test_parseDeclarations.js]
[test_parsePseudoClassesAndAttributes.js]
[test_parseSingleValue.js]
[test_rewriteDeclarations.js]

View File

@ -10,7 +10,7 @@ const {Cc, Ci, Cu} = require("chrome");
const {setTimeout, clearTimeout} =
Cu.import("resource://gre/modules/Timer.jsm", {});
const {parseDeclarations} =
require("devtools/client/styleinspector/css-parsing-utils");
require("devtools/client/shared/css-parsing-utils");
const promise = require("promise");
loader.lazyServiceGetter(this, "domUtils",

View File

@ -3,47 +3,7 @@
* 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/. */
/* Colors are taken from:
* https://developer.mozilla.org/en-US/docs/Tools/DevToolsColors.
* Changes should be kept in sync with commandline.css and commandline.inc.css.
*/
:root {
--theme-body-background: #14171a;
--theme-sidebar-background: #181d20;
--theme-contrast-background: #b28025;
--theme-tab-toolbar-background: #252c33;
--theme-toolbar-background: #343c45;
--theme-selection-background: #1d4f73;
--theme-selection-background-semitransparent: rgba(29, 79, 115, .5);
--theme-selection-color: #f5f7fa;
--theme-splitter-color: black;
--theme-comment: #757873;
--theme-body-color: #8fa1b2;
--theme-body-color-alt: #b6babf;
--theme-content-color1: #a9bacb;
--theme-content-color2: #8fa1b2;
--theme-content-color3: #5f7387;
--theme-highlight-green: #70bf53;
--theme-highlight-blue: #46afe3;
--theme-highlight-bluegrey: #5e88b0;
--theme-highlight-purple: #6b7abb;
--theme-highlight-lightorange: #d99b28;
--theme-highlight-orange: #d96629;
--theme-highlight-red: #eb5368;
--theme-highlight-pink: #df80ff;
/* Colors used in Graphs, like performance tools. Mostly similar to some "highlight-*" colors. */
--theme-graphs-green: #70bf53;
--theme-graphs-blue: #46afe3;
--theme-graphs-bluegrey: #5e88b0;
--theme-graphs-purple: #df80ff;
--theme-graphs-yellow: #d99b28;
--theme-graphs-red: #eb5368;
--theme-graphs-grey: #757873;
}
@import url(variables.css);
.theme-body {
background: var(--theme-body-background);

View File

@ -3,47 +3,7 @@
* 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/. */
/* Colors are taken from:
* https://developer.mozilla.org/en-US/docs/Tools/DevToolsColors.
* Changes should be kept in sync with commandline.css and commandline.inc.css.
*/
:root {
--theme-body-background: #fcfcfc;
--theme-sidebar-background: #f7f7f7;
--theme-contrast-background: #e6b064;
--theme-tab-toolbar-background: #ebeced;
--theme-toolbar-background: #f0f1f2;
--theme-selection-background: #4c9ed9;
--theme-selection-background-semitransparent: rgba(76, 158, 217, .23);
--theme-selection-color: #f5f7fa;
--theme-splitter-color: #aaaaaa;
--theme-comment: #757873;
--theme-body-color: #18191a;
--theme-body-color-alt: #585959;
--theme-content-color1: #292e33;
--theme-content-color2: #8fa1b2;
--theme-content-color3: #667380;
--theme-highlight-green: #2cbb0f;
--theme-highlight-blue: #0088cc;
--theme-highlight-bluegrey: #0072ab;
--theme-highlight-purple: #5b5fff;
--theme-highlight-lightorange: #d97e00;
--theme-highlight-orange: #f13c00;
--theme-highlight-red: #ed2655;
--theme-highlight-pink: #b82ee5;
/* Colors used in Graphs, like performance tools. Similar colors to Chrome's timeline. */
--theme-graphs-green: #85d175;
--theme-graphs-blue: #83b7f6;
--theme-graphs-bluegrey: #0072ab;
--theme-graphs-purple: #b693eb;
--theme-graphs-yellow: #efc052;
--theme-graphs-red: #e57180;
--theme-graphs-grey: #cccccc;
}
@import url(variables.css);
.theme-body {
background: var(--theme-body-background);

View File

@ -0,0 +1,91 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* 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/. */
/* Variable declarations for light and dark devtools themes.
* Colors are taken from:
* https://developer.mozilla.org/en-US/docs/Tools/DevToolsColors.
* Changes should be kept in sync with commandline.css and commandline.inc.css.
*/
/* IMPORTANT NOTE:
* This file is parsed in js (see client/shared/theme.js)
* so the formatting should be consistent (i.e. no '}' inside a rule).
*/
:root.theme-light {
--theme-body-background: #fcfcfc;
--theme-sidebar-background: #f7f7f7;
--theme-contrast-background: #e6b064;
--theme-tab-toolbar-background: #ebeced;
--theme-toolbar-background: #f0f1f2;
--theme-selection-background: #4c9ed9;
--theme-selection-background-semitransparent: rgba(76, 158, 217, .23);
--theme-selection-color: #f5f7fa;
--theme-splitter-color: #aaaaaa;
--theme-comment: #757873;
--theme-body-color: #18191a;
--theme-body-color-alt: #585959;
--theme-content-color1: #292e33;
--theme-content-color2: #8fa1b2;
--theme-content-color3: #667380;
--theme-highlight-green: #2cbb0f;
--theme-highlight-blue: #0088cc;
--theme-highlight-bluegrey: #0072ab;
--theme-highlight-purple: #5b5fff;
--theme-highlight-lightorange: #d97e00;
--theme-highlight-orange: #f13c00;
--theme-highlight-red: #ed2655;
--theme-highlight-pink: #b82ee5;
/* Colors used in Graphs, like performance tools. Similar colors to Chrome's timeline. */
--theme-graphs-green: #85d175;
--theme-graphs-blue: #83b7f6;
--theme-graphs-bluegrey: #0072ab;
--theme-graphs-purple: #b693eb;
--theme-graphs-yellow: #efc052;
--theme-graphs-red: #e57180;
--theme-graphs-grey: #cccccc;
}
:root.theme-dark {
--theme-body-background: #14171a;
--theme-sidebar-background: #181d20;
--theme-contrast-background: #b28025;
--theme-tab-toolbar-background: #252c33;
--theme-toolbar-background: #343c45;
--theme-selection-background: #1d4f73;
--theme-selection-background-semitransparent: rgba(29, 79, 115, .5);
--theme-selection-color: #f5f7fa;
--theme-splitter-color: black;
--theme-comment: #757873;
--theme-body-color: #8fa1b2;
--theme-body-color-alt: #b6babf;
--theme-content-color1: #a9bacb;
--theme-content-color2: #8fa1b2;
--theme-content-color3: #5f7387;
--theme-highlight-green: #70bf53;
--theme-highlight-blue: #46afe3;
--theme-highlight-bluegrey: #5e88b0;
--theme-highlight-purple: #6b7abb;
--theme-highlight-lightorange: #d99b28;
--theme-highlight-orange: #d96629;
--theme-highlight-red: #eb5368;
--theme-highlight-pink: #df80ff;
/* Colors used in Graphs, like performance tools. Mostly similar to some "highlight-*" colors. */
--theme-graphs-green: #70bf53;
--theme-graphs-blue: #46afe3;
--theme-graphs-bluegrey: #5e88b0;
--theme-graphs-purple: #df80ff;
--theme-graphs-yellow: #d99b28;
--theme-graphs-red: #eb5368;
--theme-graphs-grey: #757873;
}

View File

@ -2662,7 +2662,23 @@ var WalkerActor = protocol.ActorClass({
return null;
}
parent.rawNode.insertBefore(node.rawNode, sibling ? sibling.rawNode : null);
let rawNode = node.rawNode;
let rawParent = parent.rawNode;
let rawSibling = sibling ? sibling.rawNode : null;
// Don't bother inserting a node if the document position isn't going
// to change. This prevents needless iframes reloading and mutations.
if (rawNode.parentNode === rawParent) {
let currentNextSibling = this.nextSibling(node);
currentNextSibling = currentNextSibling ? currentNextSibling.rawNode :
null;
if (rawNode === rawSibling || currentNextSibling === rawSibling) {
return;
}
}
rawParent.insertBefore(rawNode, rawSibling);
}, {
request: {
node: Arg(0, "domnode"),

View File

@ -26,7 +26,7 @@ loader.lazyGetter(this, "DOMUtils", () => {
});
loader.lazyGetter(this, "RuleRewriter", () => {
return require("devtools/client/styleinspector/css-parsing-utils").RuleRewriter;
return require("devtools/client/shared/css-parsing-utils").RuleRewriter;
});
// The PageStyle actor flattens the DOM CSS objects a little bit, merging

View File

@ -39,36 +39,63 @@ addTest(function setup() {
});
});
addTest(function testRearrange() {
let longlist = null;
let nodeA = null;
let nextNode = null;
addAsyncTest(function* testRearrange() {
let longlist = yield gWalker.querySelector(gWalker.rootNode, "#longlist");
let children = yield gWalker.children(longlist);
let nodeA = children.nodes[0];
is(nodeA.id, "a", "Got the expected node.");
promiseDone(gWalker.querySelector(gWalker.rootNode, "#longlist").then(listFront => {
longlist = listFront;
}).then(() => {
return gWalker.children(longlist);
}).then(response => {
nodeA = response.nodes[0];
is(nodeA.id, "a", "Got the expected node.");
// Move nodeA to the end of the list.
return gWalker.insertBefore(nodeA, longlist, null);
}).then(() => {
ok(!gInspectee.querySelector("#a").nextSibling, "a should now be at the end of the list.");
return gWalker.children(longlist);
}).then(response => {
is(nodeA, response.nodes[response.nodes.length - 1], "a should now be the last returned child.");
// Now move it to the middle of the list.
nextNode = response.nodes[13];
return gWalker.insertBefore(nodeA, longlist, nextNode);
}).then(response => {
let sibling = new inspector._documentWalker(gInspectee.querySelector("#a"), window).nextSibling();
is(sibling, nextNode.rawNode(), "Node should match the expected next node.");
return gWalker.children(longlist);
}).then(response => {
is(nodeA, response.nodes[13], "a should be where we expect it.");
is(nextNode, response.nodes[14], "next node should be where we expect it.");
}).then(runNextTest));
// Move nodeA to the end of the list.
yield gWalker.insertBefore(nodeA, longlist, null);
ok(!gInspectee.querySelector("#a").nextSibling, "a should now be at the end of the list.");
children = yield gWalker.children(longlist);
is(nodeA, children.nodes[children.nodes.length - 1], "a should now be the last returned child.");
// Now move it to the middle of the list.
let nextNode = children.nodes[13];
yield gWalker.insertBefore(nodeA, longlist, nextNode);
let sibling =
new inspector._documentWalker(gInspectee.querySelector("#a"), window).nextSibling();
is(sibling, nextNode.rawNode(), "Node should match the expected next node.");
children = yield gWalker.children(longlist);
is(nodeA, children.nodes[13], "a should be where we expect it.");
is(nextNode, children.nodes[14], "next node should be where we expect it.");
runNextTest();
});
addAsyncTest(function* testInsertInvalidInput() {
let longlist = yield gWalker.querySelector(gWalker.rootNode, "#longlist");
let children = yield gWalker.children(longlist);
let nodeA = children.nodes[0];
let nextSibling = children.nodes[1];
// Now move it to the original location and make sure no mutation happens.
let hasMutated = false;
let observer = new gInspectee.defaultView.MutationObserver(() => {
hasMutated = true;
});
observer.observe(longlist.rawNode(), {
childList: true,
});
yield gWalker.insertBefore(nodeA, longlist, nodeA);
ok(!hasMutated, "hasn't mutated");
hasMutated = false;
yield gWalker.insertBefore(nodeA, longlist, nextSibling);
ok(!hasMutated, "still hasn't mutated after inserting before nextSibling");
hasMutated = false;
yield gWalker.insertBefore(nodeA, longlist);
ok(hasMutated, "has mutated after inserting with null sibling");
hasMutated = false;
yield gWalker.insertBefore(nodeA, longlist);
ok(!hasMutated, "hasn't mutated after inserting with null sibling again");
observer.disconnect();
runNextTest();
});
addTest(function cleanup() {
@ -77,7 +104,6 @@ addTest(function cleanup() {
runNextTest();
});
</script>
</head>
<body>

View File

@ -33,6 +33,7 @@ import org.mozilla.gecko.util.ActivityUtils;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.FileUtils;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.GeckoRequest;
import org.mozilla.gecko.util.HardwareUtils;
import org.mozilla.gecko.util.NativeEventListener;
import org.mozilla.gecko.util.NativeJSObject;
@ -2344,31 +2345,57 @@ public abstract class GeckoApp
return;
}
Tabs tabs = Tabs.getInstance();
Tab tab = tabs.getSelectedTab();
final Tabs tabs = Tabs.getInstance();
final Tab tab = tabs.getSelectedTab();
if (tab == null) {
moveTaskToBack(true);
return;
}
if (tab.doBack())
return;
// Give Gecko a chance to handle the back press first, then fallback to the Java UI.
GeckoAppShell.sendRequestToGecko(new GeckoRequest("Browser:OnBackPressed", null) {
@Override
public void onResponse(NativeJSObject nativeJSObject) {
if (!nativeJSObject.getBoolean("handled")) {
// Default behavior is Gecko didn't prevent.
onDefault();
}
}
if (tab.isExternal()) {
moveTaskToBack(true);
tabs.closeTab(tab);
return;
}
@Override
public void onError(NativeJSObject error) {
// Default behavior is Gecko didn't prevent, via failure.
onDefault();
}
int parentId = tab.getParentId();
Tab parent = tabs.getTab(parentId);
if (parent != null) {
// The back button should always return to the parent (not a sibling).
tabs.closeTab(tab, parent);
return;
}
// Return from Gecko thread, then back-press through the Java UI.
private void onDefault() {
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
if (tab.doBack()) {
return;
}
moveTaskToBack(true);
if (tab.isExternal()) {
moveTaskToBack(true);
tabs.closeTab(tab);
return;
}
final int parentId = tab.getParentId();
final Tab parent = tabs.getTab(parentId);
if (parent != null) {
// The back button should always return to the parent (not a sibling).
tabs.closeTab(tab, parent);
return;
}
moveTaskToBack(true);
}
});
}
});
}
@Override

View File

@ -14,7 +14,6 @@ import java.util.Locale;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.annotation.RobocopTarget;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.GeckoAppShell;
@ -535,14 +534,11 @@ public class BrowserSearch extends HomeFragment
}
getLoaderManager().restartLoader(LOADER_ID_SUGGESTION, null, mSearchEngineSuggestionLoaderCallbacks);
// Start search history suggestions query only in nightly. Bug 1201325
if (AppConstants.NIGHTLY_BUILD) {
// Saved suggestions
if (mSearchHistorySuggestionLoaderCallback == null) {
mSearchHistorySuggestionLoaderCallback = new SearchHistorySuggestionLoaderCallbacks();
}
getLoaderManager().restartLoader(LOADER_ID_SAVED_SUGGESTION, null, mSearchHistorySuggestionLoaderCallback);
// Saved suggestions
if (mSearchHistorySuggestionLoaderCallback == null) {
mSearchHistorySuggestionLoaderCallback = new SearchHistorySuggestionLoaderCallbacks();
}
getLoaderManager().restartLoader(LOADER_ID_SAVED_SUGGESTION, null, mSearchHistorySuggestionLoaderCallback);
}
private void setSuggestions(ArrayList<String> suggestions) {

View File

@ -5,7 +5,6 @@
package org.mozilla.gecko.home;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Telemetry;
@ -75,9 +74,6 @@ class SearchEngineRow extends AnimatedHeightLayout {
private int mMaxSavedSuggestions;
private int mMaxSearchSuggestions;
// Remove this default limit value in Bug 1201325
private static final int SUGGESTIONS_MAX = 4;
public SearchEngineRow(Context context) {
this(context, null);
}
@ -269,14 +265,10 @@ class SearchEngineRow extends AnimatedHeightLayout {
* @return the global count of how many suggestions have been bound/shown in the search engine row
*/
private int updateFromSearchEngine(boolean animate, int recycledSuggestionCount, int savedSuggestionCount) {
// Remove this default limit value in Bug 1201325
int maxSuggestions = SUGGESTIONS_MAX;
if (AppConstants.NIGHTLY_BUILD) {
maxSuggestions = mMaxSearchSuggestions;
// If there are less than max saved searches on phones, fill the space with more search engine suggestions
if (!HardwareUtils.isTablet() && savedSuggestionCount < mMaxSavedSuggestions) {
maxSuggestions += mMaxSavedSuggestions - savedSuggestionCount;
}
int maxSuggestions = mMaxSearchSuggestions;
// If there are less than max saved searches on phones, fill the space with more search engine suggestions
if (!HardwareUtils.isTablet() && savedSuggestionCount < mMaxSavedSuggestions) {
maxSuggestions += mMaxSavedSuggestions - savedSuggestionCount;
}
int suggestionCounter = 0;
@ -320,13 +312,6 @@ class SearchEngineRow extends AnimatedHeightLayout {
// Set the initial content description.
setDescriptionOnSuggestion(mUserEnteredTextView, mUserEnteredTextView.getText().toString());
if (!AppConstants.NIGHTLY_BUILD) {
if (searchSuggestionsEnabled) {
updateFromSearchEngine(animate, mSuggestionView.getChildCount(), 0);
}
return;
}
final int recycledSuggestionCount = mSuggestionView.getChildCount();
final SharedPreferences prefs = GeckoSharedPrefs.forApp(getContext());
final boolean savedSearchesEnabled = prefs.getBoolean(GeckoPreferences.PREFS_HISTORY_SAVED_SEARCH, true);

View File

@ -767,13 +767,6 @@ OnSharedPreferenceChangeListener
i--;
continue;
}
} else if (PREFS_HISTORY_SAVED_SEARCH.equals(key)) {
// Remove settings UI if not on Nightly
if (!AppConstants.NIGHTLY_BUILD) {
preferences.removePreference(pref);
i--;
continue;
}
} else if (PREFS_TRACKING_PROTECTION.equals(key)) {
// Remove UI for global TP pref in non-Nightly builds.
if (!AppConstants.NIGHTLY_BUILD) {

View File

@ -20,6 +20,39 @@ var Reader = {
return this._hasUsedToolbar = Services.prefs.getBoolPref("reader.has_used_toolbar");
},
/**
* BackPressListener (listeners / ReaderView Ids).
*/
_backPressListeners: [],
_backPressViewIds: [],
/**
* Set a backPressListener for this tabId / ReaderView Id pair.
*/
_addBackPressListener: function(tabId, viewId, listener) {
this._backPressListeners[tabId] = listener;
this._backPressViewIds[viewId] = tabId;
},
/**
* Remove a backPressListener for this ReaderView Id.
*/
_removeBackPressListener: function(viewId) {
let tabId = this._backPressViewIds[viewId];
if (tabId != undefined) {
this._backPressListeners[tabId] = null;
delete this._backPressViewIds[viewId];
}
},
/**
* If the requested tab has a backPress listener, return its results, else false.
*/
onBackPress: function(tabId) {
let listener = this._backPressListeners[tabId];
return { handled: (listener ? listener() : false) };
},
observe: function Reader_observe(aMessage, aTopic, aData) {
switch (aTopic) {
case "Reader:FetchContent": {
@ -66,6 +99,29 @@ var Reader = {
});
break;
// On DropdownClosed in ReaderView, we cleanup / clear existing BackPressListener.
case "Reader:DropdownClosed": {
this._removeBackPressListener(message.data);
break;
}
// On DropdownOpened in ReaderView, we add BackPressListener to handle a subsequent BACK request.
case "Reader:DropdownOpened": {
let tabId = BrowserApp.selectedTab.id;
this._addBackPressListener(tabId, message.data, () => {
// User hit BACK key while ReaderView has the banner font-dropdown opened.
// Close it and return prevent-default.
if (message.target.messageManager) {
message.target.messageManager.sendAsyncMessage("Reader:CloseDropdown");
return true;
}
// We can assume ReaderView banner's font-dropdown doesn't need to be closed.
return false;
});
break;
}
case "Reader:FaviconRequest": {
Messaging.sendRequestForResult({
type: "Reader:FaviconRequest",

View File

@ -191,17 +191,19 @@ lazilyLoadedObserverScripts.forEach(function (aScript) {
// Lazily-loaded browser scripts that use message listeners.
[
["Reader", [
"Reader:AddToList",
"Reader:ArticleGet",
"Reader:FaviconRequest",
"Reader:ListStatusRequest",
"Reader:RemoveFromList",
"Reader:Share",
"Reader:ToolbarHidden",
"Reader:SystemUIVisibility",
"Reader:UpdateReaderButton",
"Reader:SetIntPref",
"Reader:SetCharPref",
["Reader:AddToList", false],
["Reader:ArticleGet", false],
["Reader:DropdownClosed", true], // 'true' allows us to survive mid-air cycle-collection.
["Reader:DropdownOpened", false],
["Reader:FaviconRequest", false],
["Reader:ListStatusRequest", false],
["Reader:RemoveFromList", false],
["Reader:Share", false],
["Reader:ToolbarHidden", false],
["Reader:SystemUIVisibility", false],
["Reader:UpdateReaderButton", false],
["Reader:SetIntPref", false],
["Reader:SetCharPref", false],
], "chrome://browser/content/Reader.js"],
].forEach(aScript => {
let [name, messages, script] = aScript;
@ -214,11 +216,21 @@ lazilyLoadedObserverScripts.forEach(function (aScript) {
let mm = window.getGroupMessageManager("browsers");
let listener = (message) => {
mm.removeMessageListener(message.name, listener);
mm.addMessageListener(message.name, window[name]);
let listenAfterClose = false;
for (let [name, laClose] of messages) {
if (message.name === name) {
listenAfterClose = laClose;
break;
}
}
mm.addMessageListener(message.name, window[name], listenAfterClose);
window[name].receiveMessage(message);
};
messages.forEach((message) => {
mm.addMessageListener(message, listener);
let [name, listenAfterClose] = message;
mm.addMessageListener(name, listener, listenAfterClose);
});
});
@ -4737,6 +4749,11 @@ var BrowserEventHandler = {
InitLater(() => BrowserApp.deck.addEventListener("click", InputWidgetHelper, true));
InitLater(() => BrowserApp.deck.addEventListener("click", SelectHelper, true));
// ReaderViews support backPress listeners.
Messaging.addListener(() => {
return Reader.onBackPress(BrowserApp.selectedTab.id);
}, "Browser:OnBackPressed");
},
handleEvent: function(aEvent) {

View File

@ -5,6 +5,11 @@
$inherits:
from: tasks/branches/base_jobs.yml
# Flags specific to this branch
flags:
post-build:
- simulator
builds:
android-api-11:
platforms:
@ -34,3 +39,9 @@ builds:
task: tasks/builds/b2g_flame_kk_ota_opt.yml
debug:
task: tasks/builds/b2g_flame_kk_ota_debug.yml
post-build:
simulator:
allowed_build_tasks:
- tasks/builds/mulet_linux.yml
task: tasks/post-builds/mulet_simulator.yml

View File

@ -9,7 +9,6 @@ $inherits:
flags:
post-build:
- upload-symbols
- simulator
builds:
linux64_gecko:
@ -225,10 +224,6 @@ post-build:
- tasks/builds/dbg_linux64.yml
- tasks/builds/android_api_11.yml
task: tasks/post-builds/upload_symbols.yml
simulator:
allowed_build_tasks:
- tasks/builds/mulet_linux.yml
task: tasks/post-builds/mulet_simulator.yml
tests:
cppunit:

View File

@ -75,7 +75,7 @@ this.InsecurePasswordUtils = {
* both places. Look at
* https://bugzilla.mozilla.org/show_bug.cgi?id=899099 for more info.
*/
_checkIfURIisSecure : function(uri) {
checkIfURIisSecure : function(uri) {
let isSafe = false;
let netutil = Cc["@mozilla.org/network/util;1"].getService(Ci.nsINetUtil);
let ph = Ci.nsIProtocolHandler;
@ -109,7 +109,7 @@ this.InsecurePasswordUtils = {
// We are at the top, nothing to check here
return false;
}
if (!this._checkIfURIisSecure(uri)) {
if (!this.checkIfURIisSecure(uri)) {
// We are insecure
return true;
}
@ -127,7 +127,7 @@ this.InsecurePasswordUtils = {
checkForInsecurePasswords : function (aForm) {
var domDoc = aForm.ownerDocument;
let pageURI = domDoc.defaultView.top.document.documentURIObject;
let isSafePage = this._checkIfURIisSecure(pageURI);
let isSafePage = this.checkIfURIisSecure(pageURI);
if (!isSafePage) {
this._sendWebConsoleMessage("InsecurePasswordsPresentOnPage", domDoc);

View File

@ -15,12 +15,14 @@ Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask", "resource://gre/modules/DeferredTask.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginRecipesContent",
"resource://gre/modules/LoginRecipes.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
"resource://gre/modules/DeferredTask.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils",
"resource://gre/modules/InsecurePasswordUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
"resource://gre/modules/LoginHelper.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginRecipesContent",
"resource://gre/modules/LoginRecipes.jsm");
XPCOMUtils.defineLazyGetter(this, "log", () => {
let logger = LoginHelper.createLogger("LoginManagerContent");
@ -414,6 +416,18 @@ var LoginManagerContent = {
return null;
};
// Returns true if this window or any subframes have insecure login forms.
let hasInsecureLoginForms = (thisWindow, parentIsInsecure) => {
let doc = thisWindow.document;
let isInsecure =
parentIsInsecure ||
!InsecurePasswordUtils.checkIfURIisSecure(doc.documentURIObject);
let hasLoginForm = !!this.stateForDocument(doc).loginForm;
return (hasLoginForm && isInsecure) ||
Array.some(thisWindow.frames,
frame => hasInsecureLoginForms(frame, isInsecure));
};
// Store the actual form to use on the state for the top-level document.
let topState = this.stateForDocument(topWindow.document);
topState.loginFormForFill = getFirstLoginForm(topWindow);
@ -423,6 +437,7 @@ var LoginManagerContent = {
messageManager.sendAsyncMessage("RemoteLogins:updateLoginFormPresence", {
loginFormOrigin,
loginFormPresent: !!topState.loginFormForFill,
hasInsecureLoginForms: hasInsecureLoginForms(topWindow, false),
});
},

View File

@ -569,12 +569,23 @@ var LoginManagerParent = {
return loginFormState;
},
/**
* Returns true if the page currently loaded in the given browser element has
* insecure login forms. This state may be updated asynchronously, in which
* case a custom event named InsecureLoginFormsStateChange will be dispatched
* on the browser element.
*/
hasInsecureLoginForms(browser) {
return !!this.stateForBrowser(browser).hasInsecureLoginForms;
},
/**
* Called to indicate whether a login form on the currently loaded page is
* present or not. This is one of the factors used to control the visibility
* of the password fill doorhanger.
*/
updateLoginFormPresence(browser, { loginFormOrigin, loginFormPresent }) {
updateLoginFormPresence(browser, { loginFormOrigin, loginFormPresent,
hasInsecureLoginForms }) {
const ANCHOR_DELAY_MS = 200;
let state = this.stateForBrowser(browser);
@ -583,8 +594,13 @@ var LoginManagerParent = {
// processed in order, this will always be the latest version to use.
state.loginFormOrigin = loginFormOrigin;
state.loginFormPresent = loginFormPresent;
state.hasInsecureLoginForms = hasInsecureLoginForms;
// Apply the data to the currently displayed icon later.
// Report the insecure login form state immediately.
browser.dispatchEvent(new browser.ownerDocument.defaultView
.CustomEvent("InsecureLoginFormsStateChange"));
// Apply the data to the currently displayed login fill icon later.
if (!state.anchorDeferredTask) {
state.anchorDeferredTask = new DeferredTask(
() => this.updateLoginAnchor(browser),

View File

@ -2,11 +2,14 @@
support-files =
authenticate.sjs
form_basic.html
insecure_test.html
insecure_test_subframe.html
multiple_forms.html
[browser_DOMFormHasPassword.js]
[browser_DOMInputPasswordAdded.js]
[browser_filldoorhanger.js]
[browser_hasInsecureLoginForms.js]
[browser_notifications.js]
skip-if = true # Intermittent failures: Bug 1182296, bug 1148771
[browser_passwordmgr_editing.js]

View File

@ -0,0 +1,93 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://gre/modules/LoginManagerParent.jsm", this);
const testUrlPath =
"://example.com/browser/toolkit/components/passwordmgr/test/browser/";
/**
* Waits for the given number of occurrences of InsecureLoginFormsStateChange
* on the given browser element.
*/
function waitForInsecureLoginFormsStateChange(browser, count) {
return BrowserTestUtils.waitForEvent(browser, "InsecureLoginFormsStateChange",
false, () => --count == 0);
}
/**
* Checks that hasInsecureLoginForms is true for a simple HTTP page and false
* for a simple HTTPS page.
*/
add_task(function* test_simple() {
for (let scheme of ["http", "https"]) {
let tab = gBrowser.addTab(scheme + testUrlPath + "form_basic.html");
let browser = tab.linkedBrowser;
yield Promise.all([
BrowserTestUtils.switchTab(gBrowser, tab),
BrowserTestUtils.browserLoaded(browser),
// One event is triggered by pageshow and one by DOMFormHasPassword.
waitForInsecureLoginFormsStateChange(browser, 2),
]);
Assert.equal(LoginManagerParent.hasInsecureLoginForms(browser),
scheme == "http");
gBrowser.removeTab(tab);
}
});
/**
* Checks that hasInsecureLoginForms is true if a password field is present in
* an HTTP page loaded as a subframe of a top-level HTTPS page, when mixed
* active content blocking is disabled.
*
* When the subframe is navigated to an HTTPS page, hasInsecureLoginForms should
* be set to false.
*
* Moving back in history should set hasInsecureLoginForms to true again.
*/
add_task(function* test_subframe_navigation() {
yield new Promise(resolve => SpecialPowers.pushPrefEnv({
"set": [["security.mixed_content.block_active_content", false]],
}, resolve));
// Load the page with the subframe in a new tab.
let tab = gBrowser.addTab("https" + testUrlPath + "insecure_test.html");
let browser = tab.linkedBrowser;
yield Promise.all([
BrowserTestUtils.switchTab(gBrowser, tab),
BrowserTestUtils.browserLoaded(browser),
// Two events are triggered by pageshow and one by DOMFormHasPassword.
waitForInsecureLoginFormsStateChange(browser, 3),
]);
Assert.ok(LoginManagerParent.hasInsecureLoginForms(browser));
// Navigate the subframe to a secure page.
let promiseSubframeReady = Promise.all([
BrowserTestUtils.browserLoaded(browser, true),
// One event is triggered by pageshow and one by DOMFormHasPassword.
waitForInsecureLoginFormsStateChange(browser, 2),
]);
yield ContentTask.spawn(browser, null, function* () {
content.document.getElementById("test-iframe")
.contentDocument.getElementById("test-link").click();
});
yield promiseSubframeReady;
Assert.ok(!LoginManagerParent.hasInsecureLoginForms(browser));
// Navigate back to the insecure page. We only have to wait for the
// InsecureLoginFormsStateChange event that is triggered by pageshow.
let promise = waitForInsecureLoginFormsStateChange(browser, 1);
yield ContentTask.spawn(browser, null, function* () {
content.document.getElementById("test-iframe")
.contentWindow.history.back();
});
yield promise;
Assert.ok(LoginManagerParent.hasInsecureLoginForms(browser));
gBrowser.removeTab(tab);
});

View File

@ -0,0 +1,9 @@
<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!-- This frame is initially loaded over HTTP. -->
<iframe id="test-iframe"
src="http://example.org/browser/toolkit/components/passwordmgr/test/browser/insecure_test_subframe.html"/>
</body></html>

View File

@ -0,0 +1,13 @@
<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<form>
<input name="password" type="password">
</form>
<!-- Link to reload this page over HTTPS. -->
<a id="test-link"
href="https://example.org/browser/toolkit/components/passwordmgr/test/browser/insecure_test_subframe.html">HTTPS</a>
</body></html>

View File

@ -38,6 +38,7 @@ var AboutReader = function(mm, win, articlePromise) {
this._mm.addMessageListener("Reader:Removed", this);
this._mm.addMessageListener("Sidebar:VisibilityChange", this);
this._mm.addMessageListener("ReadingList:VisibilityStatus", this);
this._mm.addMessageListener("Reader:CloseDropdown", this);
this._docRef = Cu.getWeakReference(doc);
this._winRef = Cu.getWeakReference(win);
@ -178,6 +179,15 @@ AboutReader.prototype = {
return this._toolbarVertical = Services.prefs.getBoolPref("reader.toolbar.vertical");
},
// Provides unique view Id.
get viewId() {
let _viewId = Cc["@mozilla.org/uuid-generator;1"].
getService(Ci.nsIUUIDGenerator).generateUUID().toString();
Object.defineProperty(this, "viewId", { value: _viewId });
return _viewId;
},
receiveMessage: function (message) {
switch (message.name) {
case "Reader:Added": {
@ -190,6 +200,14 @@ AboutReader.prototype = {
}
break;
}
// Triggered by Android user pressing BACK while the banner font-dropdown is open.
case "Reader:CloseDropdown": {
// Just close it.
this._closeDropdown();
break;
}
case "Reader:Removed": {
if (message.data.url == this._article.url) {
if (this._isReadingListItem != 0) {
@ -246,10 +264,14 @@ AboutReader.prototype = {
break;
case "unload":
// Close the Banners Font-dropdown, cleanup Android BackPressListener.
this._closeDropdown();
this._mm.removeMessageListener("Reader:Added", this);
this._mm.removeMessageListener("Reader:Removed", this);
this._mm.removeMessageListener("Sidebar:VisibilityChange", this);
this._mm.removeMessageListener("ReadingList:VisibilityStatus", this);
this._mm.removeMessageListener("Reader:CloseDropdown", this);
this._windowUnloaded = true;
break;
}
@ -584,8 +606,7 @@ AboutReader.prototype = {
},
_setToolbarVisibility: function(visible) {
let dropdown = this._doc.getElementById("style-dropdown");
dropdown.classList.remove("open");
this._closeDropdown();
if (this._getToolbarVisibility() === visible) {
return;
@ -943,13 +964,41 @@ AboutReader.prototype = {
event.stopPropagation();
if (dropdown.classList.contains("open")) {
dropdown.classList.remove("open");
this._closeDropdown();
} else {
dropdown.classList.add("open");
this._openDropdown();
if (this._isToolbarVertical) {
updatePopupPosition();
}
}
}, true);
},
/*
* If the ReaderView banner font-dropdown is closed, open it.
*/
_openDropdown: function() {
let dropdown = this._doc.getElementById("style-dropdown");
if (dropdown.classList.contains("open")) {
return;
}
// Trigger BackPressListener initialization in Android.
dropdown.classList.add("open");
this._mm.sendAsyncMessage("Reader:DropdownOpened", this.viewId);
},
/*
* If the ReaderView banner font-dropdown is opened, close it.
*/
_closeDropdown: function() {
let dropdown = this._doc.getElementById("style-dropdown");
if (!dropdown.classList.contains("open")) {
return;
}
// Trigger BackPressListener cleanup in Android.
dropdown.classList.remove("open");
this._mm.sendAsyncMessage("Reader:DropdownClosed", this.viewId);
},
};

View File

@ -38,6 +38,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
const CHANGE_THROTTLE_INTERVAL_MS = 5 * 60 * 1000;
// The maximum length of a string (e.g. description) in the addons section.
const MAX_ADDON_STRING_LENGTH = 100;
/**
* This is a policy object used to override behavior for testing.
*/
@ -249,6 +252,21 @@ function getGfxField(aPropertyName, aDefault) {
return aDefault;
}
/**
* Returns a substring of the input string.
*
* @param {String} aString The input string.
* @param {Integer} aMaxLength The maximum length of the returned substring. If this is
* greater than the length of the input string, we return the whole input string.
* @return {String} The substring or null if the input string is null.
*/
function limitStringToLength(aString, aMaxLength) {
if (aString === null) {
return null;
}
return aString.substring(0, aMaxLength);
}
/**
* Get the information about a graphic adapter.
*
@ -500,11 +518,11 @@ EnvironmentAddonBuilder.prototype = {
activeAddons[addon.id] = {
blocklisted: (addon.blocklistState !== Ci.nsIBlocklistService.STATE_NOT_BLOCKED),
description: addon.description,
name: addon.name,
description: limitStringToLength(addon.description, MAX_ADDON_STRING_LENGTH),
name: limitStringToLength(addon.name, MAX_ADDON_STRING_LENGTH),
userDisabled: addon.userDisabled,
appDisabled: addon.appDisabled,
version: addon.version,
version: limitStringToLength(addon.version, MAX_ADDON_STRING_LENGTH),
scope: addon.scope,
type: addon.type,
foreignInstall: addon.foreignInstall,
@ -540,11 +558,11 @@ EnvironmentAddonBuilder.prototype = {
activeTheme = {
id: theme.id,
blocklisted: (theme.blocklistState !== Ci.nsIBlocklistService.STATE_NOT_BLOCKED),
description: theme.description,
name: theme.name,
description: limitStringToLength(theme.description, MAX_ADDON_STRING_LENGTH),
name: limitStringToLength(theme.name, MAX_ADDON_STRING_LENGTH),
userDisabled: theme.userDisabled,
appDisabled: theme.appDisabled,
version: theme.version,
version: limitStringToLength(theme.version, MAX_ADDON_STRING_LENGTH),
scope: theme.scope,
foreignInstall: theme.foreignInstall,
hasBinaryComponents: theme.hasBinaryComponents,
@ -575,9 +593,9 @@ EnvironmentAddonBuilder.prototype = {
let updateDate = new Date(Math.max(0, tag.lastModifiedTime));
activePlugins.push({
name: tag.name,
version: tag.version,
description: tag.description,
name: limitStringToLength(tag.name, MAX_ADDON_STRING_LENGTH),
version: limitStringToLength(tag.version, MAX_ADDON_STRING_LENGTH),
description: limitStringToLength(tag.description, MAX_ADDON_STRING_LENGTH),
blocklisted: tag.blocklisted,
disabled: tag.disabled,
clicktoplay: tag.clicktoplay,

View File

@ -299,3 +299,8 @@ The following is a partial list of collected preferences.
- ``browser.urlbar.unifiedcomplete``: True if the urlbar's UnifiedComplete back-end is enabled.
- ``browser.urlbar.userMadeSearchSuggestionsChoice``: True if the user has clicked Yes or No in the urlbar's opt-in notification. Defaults to false.
activeAddons
~~~~~~~~~~~~
Starting from Firefox 44, the length of the following string fields: ``name``, ``description`` and ``version`` is limited to 100 characters. The same limitation applies to the same fields in ``theme`` and ``activePlugins``.

View File

@ -0,0 +1,24 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>tel-longfields-xpi@tests.mozilla.org</em:id>
<em:version>This is a really long addon version, that will get limited to 100 characters. We're much longer, we're at about 116.</em:version>
<em:targetApplication>
<Description>
<em:id>toolkit@mozilla.org</em:id>
<em:minVersion>0</em:minVersion>
<em:maxVersion>*</em:maxVersion>
</Description>
</em:targetApplication>
<!-- Front End MetaData -->
<em:name>This is a really long addon name, that will get limited to 100 characters. We're much longer, we're at about 219. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus nullam sodales. Yeah, Latin placeholder.</em:name>
<em:description>This is a really long addon description, that will get limited to 100 characters. We're much longer, we're at about 200. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus nullam sodales.</em:description>
<em:bootstrap>true</em:bootstrap>
</Description>
</RDF>

View File

@ -1017,6 +1017,37 @@ add_task(function* test_signedAddon() {
}
});
add_task(function* test_addonsFieldsLimit() {
const ADDON_INSTALL_URL = gDataRoot + "long-fields.xpi";
const ADDON_ID = "tel-longfields-xpi@tests.mozilla.org";
// Set the clock in the future so our changes don't get throttled.
gNow = fakeNow(futureDate(gNow, 10 * MILLISECONDS_PER_MINUTE));
// Install the addon and wait for the TelemetryEnvironment to pick it up.
let deferred = PromiseUtils.defer();
TelemetryEnvironment.registerChangeListener("test_longFieldsAddon", deferred.resolve);
yield AddonTestUtils.installXPIFromURL(ADDON_INSTALL_URL);
yield deferred.promise;
TelemetryEnvironment.unregisterChangeListener("test_longFieldsAddon");
let data = TelemetryEnvironment.currentEnvironment;
checkEnvironmentData(data);
// Check that the addon is available and that the string fields are limited.
Assert.ok(ADDON_ID in data.addons.activeAddons, "Add-on should be in the environment.");
let targetAddon = data.addons.activeAddons[ADDON_ID];
// TelemetryEnvironment limits the length of string fields for activeAddons to 100 chars,
// to mitigate misbehaving addons.
Assert.lessOrEqual(targetAddon.version.length, 100,
"The version string must have been limited");
Assert.lessOrEqual(targetAddon.name.length, 100,
"The name string must have been limited");
Assert.lessOrEqual(targetAddon.description.length, 100,
"The description string must have been limited");
});
add_task(function* test_changeThrottling() {
const PREF_TEST = "toolkit.telemetry.test.pref1";
const PREFS_TO_WATCH = new Map([