merge fx-team to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2015-10-29 11:47:58 +01:00
commit fff6a458cd
187 changed files with 1059 additions and 737 deletions

View File

@ -126,6 +126,15 @@ function URL(url, base) {
}
}
let fileName = "/";
try {
fileName = uri.QueryInterface(Ci.nsIURL).fileName;
} catch (e) {
if (e.result != Cr.NS_NOINTERFACE) {
throw e;
}
}
let uriData = [uri.path, uri.path.length, {}, {}, {}, {}, {}, {}];
URLParser.parsePath.apply(URLParser, uriData);
let [{ value: filepathPos }, { value: filepathLen },
@ -137,6 +146,7 @@ function URL(url, base) {
let search = uri.path.substr(queryPos, queryLen);
search = search ? "?" + search : "";
this.__defineGetter__("fileName", () => fileName);
this.__defineGetter__("scheme", () => uri.scheme);
this.__defineGetter__("userPass", () => userPass);
this.__defineGetter__("host", () => host);

View File

@ -20,8 +20,6 @@ const { decode } = require('sdk/base64');
const httpd = require('./lib/httpd');
const port = 8099;
const defaultLocation = '{\'scheme\':\'about\',\'userPass\':null,\'host\':null,\'hostname\':null,\'port\':null,\'path\':\'addons\',\'pathname\':\'addons\',\'hash\':\'\',\'href\':\'about:addons\',\'origin\':\'about:\',\'protocol\':\'about:\',\'search\':\'\'}'.replace(/'/g, '"');
exports.testResolve = function(assert) {
assert.equal(URL('bar', 'http://www.foo.com/').toString(),
'http://www.foo.com/bar');
@ -65,6 +63,7 @@ exports.testParseHttp = function(assert) {
assert.equal(info.href, aUrl);
assert.equal(info.hash, '#myhash');
assert.equal(info.search, '?locale=en-US&otherArg=%20x%20');
assert.equal(info.fileName, 'bar');
};
exports.testParseHttpSearchAndHash = function (assert) {
@ -109,6 +108,7 @@ exports.testParseChrome = function(assert) {
assert.equal(info.port, null);
assert.equal(info.userPass, null);
assert.equal(info.path, '/content/blah');
assert.equal(info.fileName, 'blah');
};
exports.testParseAbout = function(assert) {
@ -127,6 +127,7 @@ exports.testParseFTP = function(assert) {
assert.equal(info.port, null);
assert.equal(info.userPass, null);
assert.equal(info.path, '/foo');
assert.equal(info.fileName, 'foo');
};
exports.testParseFTPWithUserPass = function(assert) {
@ -216,7 +217,7 @@ exports.testStringInterface = function(assert) {
// make sure the standard URL properties are enumerable and not the String interface bits
assert.equal(Object.keys(a),
'scheme,userPass,host,hostname,port,path,pathname,hash,href,origin,protocol,search',
'fileName,scheme,userPass,host,hostname,port,path,pathname,hash,href,origin,protocol,search',
'enumerable key list check for URL.');
assert.equal(
JSON.stringify(a),
@ -392,6 +393,20 @@ exports.testLocalURLwithInvalidURL = function(assert) {
});
}
exports.testFileName = function(assert) {
let urls = [
['https://foo/bar.js', 'bar.js'],
['app://myfxosapp/file.js', 'file.js'],
['http://localhost:8888/file.js', 'file.js'],
['http://foo/bar.js#hash', 'bar.js'],
['http://foo/bar.js?q=go&query=yeah', 'bar.js'],
['chrome://browser/content/content.js', 'content.js'],
['resource://gre/foo.js', 'foo.js'],
];
urls.forEach(([url, fileName]) => assert.equal(URL(url).fileName, fileName, 'file names are equal'));
};
function validURIs() {
return [
'http://foo.com/blah_blah',

View File

@ -186,12 +186,11 @@ body {
}
.rooms > h1 {
font-weight: bold;
color: #666;
font-size: 1rem;
padding: .5rem 0;
height: 3rem;
line-height: 3rem;
font-size: 1.1rem;
margin: 0 15px;
}
@ -205,7 +204,7 @@ body {
border-radius: 5px;
font-size: 1.2rem;
font-weight: bold;
margin: 1rem;
margin: 1.5rem;
padding: 1rem;
}
@ -252,8 +251,8 @@ body {
line-height: 2.4rem;
color: #000;
/* See .room-entry-context-item for the margin/size reductions.
* An extra 40px to make space for the call button and chevron. */
width: calc(100% - 1rem - 56px);
* An extra 16px to make space for the edit button. */
width: calc(100% - 1rem - 32px);
}
.room-list > .room-entry.room-active:not(.room-opened) > h2 {
@ -331,27 +330,9 @@ body {
vertical-align: middle;
}
.room-list > .room-entry > h2:before {
content: "";
display: inline-block;
background-image: url("../shared/img/icons-14x14.svg#hello");
background-repeat: no-repeat;
background-size: cover;
width: 13px;
height: 13px;
-moz-margin-end: 1rem;
margin-bottom: -3px;
}
.room-list > .room-entry.room-active > h2:before {
background-image: url("../shared/img/icons-14x14.svg#hello-active");
}
/* Room entry context button (call button + chevron) */
/* Room entry context button (edit button) */
.room-entry-context-actions {
display: none;
border-radius: 30px;
background: #56b397;
vertical-align: top;
}
@ -359,41 +340,17 @@ body {
display: inline-block;
}
.room-entry:hover .room-entry-context-actions:hover {
background: #50e3c2;
}
/* Room entry call button */
.room-entry-call-btn {
border-top-left-radius: 30px;
border-bottom-left-radius: 30px;
background-color: transparent;
background-image: url("../shared/img/icons-14x14.svg#video-white");
background-position: right center;
}
html[dir="rtl"] .room-entry-call-btn {
background-position: left center;
}
/* Room entry context menu */
.room-entry-context-menu-chevron {
display: inline-block;
border-top-right-radius: 30px;
border-bottom-right-radius: 30px;
background-image: url("../shared/img/icons-10x10.svg#dropdown-white");
/* Room entry edit button */
.room-entry-context-edit-btn {
background-image: url("../shared/img/icons-10x10.svg#edit-darkgrey");
background-position: center;
cursor: pointer;
}
/* Common styles for chevron and call button. */
.room-entry-context-menu-chevron,
.room-entry-call-btn {
width: 30px;
height: 24px;
background-size: 12px;
background-repeat: no-repeat;
background-size: 12px;
cursor: pointer;
display: inline-block;
height: 24px;
vertical-align: middle;
width: 16px;
}
html[dir="rtl"] .room-entry-context-actions > .dropdown-menu {
@ -410,16 +367,11 @@ html[dir="rtl"] .room-entry-context-actions > .dropdown-menu {
/* Keep ".room-list > .room-entry > h2" in sync with these. */
.room-entry-context-item {
display: inline-block;
-moz-margin-end: 1rem;
vertical-align: middle;
-moz-margin-start: 1rem;
height: 16px;
}
.room-entry:not(.room-opened):hover .room-entry-context-item {
display: none;
}
.room-entry-context-item > a > img {
.room-entry-context-item > img {
height: 16px;
width: 16px;
}

View File

@ -340,23 +340,39 @@ loop.panel = (function(_, mozL10n) {
handleClick: function(event) {
event.stopPropagation();
event.preventDefault();
this.props.mozLoop.openURL(event.currentTarget.href);
this.closeWindow();
if (event.currentTarget.href) {
this.props.mozLoop.openURL(event.currentTarget.href);
this.closeWindow();
}
},
render: function() {
var roomUrl = this.props.roomUrls && this.props.roomUrls[0];
if (!roomUrl) {
return null;
}
_renderDefaultIcon: function() {
return (
React.createElement("div", {className: "room-entry-context-item"},
React.createElement("a", {href: roomUrl.location, onClick: this.handleClick, title: roomUrl.description},
React.createElement("img", {src: "loop/shared/img/icons-16x16.svg#globe"})
)
);
},
_renderIcon: function(roomUrl) {
return (
React.createElement("div", {className: "room-entry-context-item"},
React.createElement("a", {href: roomUrl.location,
onClick: this.handleClick,
title: roomUrl.description},
React.createElement("img", {src: roomUrl.thumbnail || "loop/shared/img/icons-16x16.svg#globe"})
)
)
);
},
render: function() {
var roomUrl = this.props.roomUrls && this.props.roomUrls[0];
if (roomUrl && roomUrl.location) {
return this._renderIcon(roomUrl);
} else {
return this._renderDefaultIcon();
}
}
});
@ -398,7 +414,7 @@ loop.panel = (function(_, mozL10n) {
this.closeWindow();
},
handleContextChevronClick: function(e) {
handleClick: function(e) {
e.preventDefault();
e.stopPropagation();
@ -435,23 +451,19 @@ loop.panel = (function(_, mozL10n) {
onClick: this.props.isOpenedRoom ? null : this.handleClickEntry,
onMouseLeave: this.props.isOpenedRoom ? null : this._handleMouseOut,
ref: "roomEntry"},
React.createElement("h2", null,
roomTitle
),
React.createElement(RoomEntryContextItem, {
mozLoop: this.props.mozLoop,
roomUrls: this.props.room.decryptedContext.urls}),
React.createElement("h2", null, roomTitle),
this.props.isOpenedRoom ? null :
React.createElement(RoomEntryContextButtons, {
dispatcher: this.props.dispatcher,
eventPosY: this.state.eventPosY,
handleClickEntry: this.handleClickEntry,
handleContextChevronClick: this.handleContextChevronClick,
handleClick: this.handleClick,
ref: "contextActions",
room: this.props.room,
showMenu: this.state.showMenu,
toggleDropdownMenu: this.toggleDropdownMenu})
)
);
}
@ -459,16 +471,14 @@ loop.panel = (function(_, mozL10n) {
/**
* Buttons corresponding to each conversation entry.
* This component renders the video icon call button and chevron button for
* displaying contextual dropdown menu for conversation entries.
* It also holds the dropdown menu.
* This component renders the edit button for displaying contextual dropdown
* menu for conversation entries. It also holds the dropdown menu.
*/
var RoomEntryContextButtons = React.createClass({displayName: "RoomEntryContextButtons",
propTypes: {
dispatcher: React.PropTypes.object.isRequired,
eventPosY: React.PropTypes.number.isRequired,
handleClickEntry: React.PropTypes.func.isRequired,
handleContextChevronClick: React.PropTypes.func.isRequired,
handleClick: React.PropTypes.func.isRequired,
room: React.PropTypes.object.isRequired,
showMenu: React.PropTypes.bool.isRequired,
toggleDropdownMenu: React.PropTypes.func.isRequired
@ -514,13 +524,9 @@ loop.panel = (function(_, mozL10n) {
render: function() {
return (
React.createElement("div", {className: "room-entry-context-actions"},
React.createElement("button", {
className: "btn room-entry-call-btn",
onClick: this.props.handleClickEntry,
ref: "callButton"}),
React.createElement("div", {
className: "room-entry-context-menu-chevron dropdown-menu-button",
onClick: this.props.handleContextChevronClick,
className: "room-entry-context-edit-btn dropdown-menu-button",
onClick: this.props.handleClick,
ref: "menu-button"}),
this.props.showMenu ?
React.createElement(ConversationDropdown, {
@ -563,7 +569,7 @@ loop.panel = (function(_, mozL10n) {
// Get the parent element and make sure the menu does not overlow its
// container.
var listNode = loop.shared.utils.findParentNode(this.getDOMNode(),
".rooms");
"rooms");
var listNodeRect = listNode.getBoundingClientRect();
// Click offset to not display the menu right next to the area clicked.

View File

@ -340,23 +340,39 @@ loop.panel = (function(_, mozL10n) {
handleClick: function(event) {
event.stopPropagation();
event.preventDefault();
this.props.mozLoop.openURL(event.currentTarget.href);
this.closeWindow();
if (event.currentTarget.href) {
this.props.mozLoop.openURL(event.currentTarget.href);
this.closeWindow();
}
},
render: function() {
var roomUrl = this.props.roomUrls && this.props.roomUrls[0];
if (!roomUrl) {
return null;
}
_renderDefaultIcon: function() {
return (
<div className="room-entry-context-item">
<a href={roomUrl.location} onClick={this.handleClick} title={roomUrl.description}>
<img src="loop/shared/img/icons-16x16.svg#globe" />
</div>
);
},
_renderIcon: function(roomUrl) {
return (
<div className="room-entry-context-item">
<a href={roomUrl.location}
onClick={this.handleClick}
title={roomUrl.description}>
<img src={roomUrl.thumbnail || "loop/shared/img/icons-16x16.svg#globe"} />
</a>
</div>
);
},
render: function() {
var roomUrl = this.props.roomUrls && this.props.roomUrls[0];
if (roomUrl && roomUrl.location) {
return this._renderIcon(roomUrl);
} else {
return this._renderDefaultIcon();
}
}
});
@ -398,7 +414,7 @@ loop.panel = (function(_, mozL10n) {
this.closeWindow();
},
handleContextChevronClick: function(e) {
handleClick: function(e) {
e.preventDefault();
e.stopPropagation();
@ -435,23 +451,19 @@ loop.panel = (function(_, mozL10n) {
onClick={this.props.isOpenedRoom ? null : this.handleClickEntry}
onMouseLeave={this.props.isOpenedRoom ? null : this._handleMouseOut}
ref="roomEntry">
<h2>
{roomTitle}
</h2>
<RoomEntryContextItem
mozLoop={this.props.mozLoop}
roomUrls={this.props.room.decryptedContext.urls} />
<h2>{roomTitle}</h2>
{this.props.isOpenedRoom ? null :
<RoomEntryContextButtons
dispatcher={this.props.dispatcher}
eventPosY={this.state.eventPosY}
handleClickEntry={this.handleClickEntry}
handleContextChevronClick={this.handleContextChevronClick}
handleClick={this.handleClick}
ref="contextActions"
room={this.props.room}
showMenu={this.state.showMenu}
toggleDropdownMenu={this.toggleDropdownMenu} />
}
toggleDropdownMenu={this.toggleDropdownMenu} />}
</div>
);
}
@ -459,16 +471,14 @@ loop.panel = (function(_, mozL10n) {
/**
* Buttons corresponding to each conversation entry.
* This component renders the video icon call button and chevron button for
* displaying contextual dropdown menu for conversation entries.
* It also holds the dropdown menu.
* This component renders the edit button for displaying contextual dropdown
* menu for conversation entries. It also holds the dropdown menu.
*/
var RoomEntryContextButtons = React.createClass({
propTypes: {
dispatcher: React.PropTypes.object.isRequired,
eventPosY: React.PropTypes.number.isRequired,
handleClickEntry: React.PropTypes.func.isRequired,
handleContextChevronClick: React.PropTypes.func.isRequired,
handleClick: React.PropTypes.func.isRequired,
room: React.PropTypes.object.isRequired,
showMenu: React.PropTypes.bool.isRequired,
toggleDropdownMenu: React.PropTypes.func.isRequired
@ -514,13 +524,9 @@ loop.panel = (function(_, mozL10n) {
render: function() {
return (
<div className="room-entry-context-actions">
<button
className="btn room-entry-call-btn"
onClick={this.props.handleClickEntry}
ref="callButton" />
<div
className="room-entry-context-menu-chevron dropdown-menu-button"
onClick={this.props.handleContextChevronClick}
className="room-entry-context-edit-btn dropdown-menu-button"
onClick={this.props.handleClick}
ref="menu-button" />
{this.props.showMenu ?
<ConversationDropdown
@ -563,7 +569,7 @@ loop.panel = (function(_, mozL10n) {
// Get the parent element and make sure the menu does not overlow its
// container.
var listNode = loop.shared.utils.findParentNode(this.getDOMNode(),
".rooms");
"rooms");
var listNodeRect = listNode.getBoundingClientRect();
// Click offset to not display the menu right next to the area clicked.

View File

@ -22,6 +22,9 @@
use[id$="-disabled"] {
fill: rgba(255,255,255,0.4);
}
use[id$="-darkgrey"] {
fill: #565656;
}
</style>
<defs>
<polygon id="close-shape" points="10,1.717 8.336,0.049 5.024,3.369 1.663,0 0,1.668 3.36,5.037 0.098,8.307 1.762,9.975 5.025,6.705 8.311,10 9.975,8.332 6.688,5.037"/>
@ -43,6 +46,7 @@
<use id="edit-active" xlink:href="#edit-shape"/>
<use id="edit-disabled" xlink:href="#edit-shape"/>
<use id="edit-white" xlink:href="#edit-shape"/>
<use id="edit-darkgrey" xlink:href="#edit-shape"/>
<use id="expand" xlink:href="#expand-shape"/>
<use id="expand-active" xlink:href="#expand-shape"/>
<use id="expand-disabled" xlink:href="#expand-shape"/>

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -593,7 +593,7 @@ describe("loop.panel", function() {
React.createElement(loop.panel.RoomEntry, props));
}
describe("handleContextChevronClick", function() {
describe("handleClick", function() {
var view;
beforeEach(function() {
@ -613,15 +613,15 @@ describe("loop.panel", function() {
expect(view.refs.contextActions.state.showMenu).to.eql(false);
});
it("should set eventPosY when handleContextChevronClick is called", function() {
view.handleContextChevronClick(fakeEvent);
it("should set eventPosY when handleClick is called", function() {
view.handleClick(fakeEvent);
expect(view.state.eventPosY).to.eql(fakeEvent.pageY);
});
it("toggle state.showMenu when handleContextChevronClick is called", function() {
it("toggle state.showMenu when handleClick is called", function() {
var prevState = view.state.showMenu;
view.handleContextChevronClick(fakeEvent);
view.handleClick(fakeEvent);
expect(view.state.showMenu).to.eql(!prevState);
});
@ -702,10 +702,10 @@ describe("loop.panel", function() {
});
}
it("should not display a context indicator if the room doesn't have any", function() {
it("should display a default context indicator if the room doesn't have any", function() {
roomEntry = mountEntryForContext();
expect(roomEntry.getDOMNode().querySelector(".room-entry-context-item")).eql(null);
expect(roomEntry.getDOMNode().querySelector(".room-entry-context-item")).not.eql(null);
});
it("should a context indicator if the room specifies context", function() {
@ -1129,11 +1129,10 @@ describe("loop.panel", function() {
var props = _.extend({
dispatcher: dispatcher,
eventPosY: 0,
handleClickEntry: sandbox.stub(),
showMenu: false,
room: roomData,
toggleDropdownMenu: sandbox.stub(),
handleContextChevronClick: sandbox.stub()
handleClick: sandbox.stub()
}, extraProps);
return TestUtils.renderIntoDocument(
React.createElement(loop.panel.RoomEntryContextButtons, props));
@ -1192,11 +1191,5 @@ describe("loop.panel", function() {
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.DeleteRoom({ roomToken: roomData.roomToken }));
});
it("should trigger handleClickEntry when button is clicked", function() {
TestUtils.Simulate.click(view.refs.callButton.getDOMNode());
sinon.assert.calledOnce(view.props.handleClickEntry);
});
});
});

View File

@ -2,8 +2,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/.
mobile-tests := mobile/android/tests/browser/robocop
TESTPATH := $(topsrcdir)/$(mobile-tests)
TESTPATH := $(topsrcdir)/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests
ANDROID_EXTRA_JARS += \
$(srcdir)/robotium-solo-4.3.1.jar \
@ -29,7 +28,7 @@ _JAVA_HARNESS := \
StructuredLogger.java \
$(NULL)
java-harness := $(addprefix $(srcdir)/,$(_JAVA_HARNESS))
java-harness := $(addprefix $(topsrcdir)/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/,$(_JAVA_HARNESS))
java-tests := \
$(wildcard $(TESTPATH)/*.java) \
$(wildcard $(TESTPATH)/components/*.java) \

View File

@ -278,21 +278,17 @@ Tools.performance = {
Tools.memory = {
id: "memory",
ordinal: 8,
icon: "chrome://devtools/skin/themes/images/tool-styleeditor.svg",
icon: "chrome://devtools/skin/themes/images/tool-memory.svg",
invertIconForLightTheme: true,
highlightedicon: "chrome://devtools/skin/themes/images/tool-memory-active.svg",
url: "chrome://devtools/content/memory/memory.xhtml",
visibilityswitch: "devtools.memory.enabled",
label: "Memory",
panelLabel: "Memory Panel",
tooltip: "Memory (keyboardshortcut)",
hiddenInOptions: true,
isTargetSupported: function (target) {
// TODO 1201907
// Once Fx44 lands, we should add a root trait `heapSnapshots`
// to indicate that the memory actor can handle this.
// Shouldn't make this change until Fx44, however.
return true; // target.getTrait("heapSnapshots");
return target.getTrait("heapSnapshots");
},
build: function (frame, target) {

View File

@ -302,6 +302,8 @@ devtools.jar:
skin/themes/images/tool-network.svg (themes/images/tool-network.svg)
skin/themes/images/tool-scratchpad.svg (themes/images/tool-scratchpad.svg)
skin/themes/images/tool-webaudio.svg (themes/images/tool-webaudio.svg)
skin/themes/images/tool-memory.svg (themes/images/tool-memory.svg)
skin/themes/images/tool-memory-active.svg (themes/images/tool-memory-active.svg)
skin/themes/images/close.png (themes/images/close.png)
skin/themes/images/close@2x.png (themes/images/close@2x.png)
skin/themes/images/vview-delete.png (themes/images/vview-delete.png)

View File

@ -4,6 +4,7 @@
const { DOM: dom, createClass, createFactory, PropTypes } = require("devtools/client/shared/vendor/react");
const { connect } = require("devtools/client/shared/vendor/react-redux");
const { breakdowns } = require("./constants");
const { toggleRecordingAllocationStacks } = require("./actions/allocations");
const { setBreakdownAndRefresh } = require("./actions/breakdown");
const { toggleInvertedAndRefresh } = require("./actions/inverted");
@ -20,15 +21,24 @@ const App = createClass({
propTypes: appModel,
getDefaultProps() {
return {
breakdown: breakdowns.coarseType.breakdown,
inverted: false,
};
},
childContextTypes: {
front: PropTypes.any,
heapWorker: PropTypes.any,
toolbox: PropTypes.any,
},
getChildContext() {
return {
front: this.props.front,
heapWorker: this.props.heapWorker,
toolbox: this.props.toolbox,
}
},
@ -40,13 +50,14 @@ const App = createClass({
heapWorker,
breakdown,
allocations,
inverted
inverted,
toolbox,
} = this.props;
let selectedSnapshot = snapshots.find(s => s.selected);
return (
dom.div({ id: "memory-tool" }, [
dom.div({ id: "memory-tool" },
Toolbar({
breakdowns: getBreakdownDisplayData(),
@ -61,7 +72,7 @@ const App = createClass({
dispatch(toggleInvertedAndRefresh(heapWorker))
}),
dom.div({ id: "memory-tool-container" }, [
dom.div({ id: "memory-tool-container" },
List({
itemComponent: SnapshotListItem,
items: snapshots,
@ -71,9 +82,10 @@ const App = createClass({
HeapView({
snapshot: selectedSnapshot,
onSnapshotClick: () => dispatch(takeSnapshotAndCensus(front, heapWorker)),
}),
])
])
toolbox
})
)
)
);
},
});

View File

@ -0,0 +1,40 @@
/* 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/. */
const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
const { URL } = require("sdk/url");
const Frame = module.exports = createClass({
displayName: "frame-view",
propTypes: {
frame: PropTypes.object.isRequired,
toolbox: PropTypes.object.isRequired,
},
render() {
let { toolbox, frame } = this.props;
let url = new URL(frame.source);
let spec = url.toString();
let func = frame.functionDisplayFrame || "";
let tooltip = `${func} (${spec}:${frame.line}:${frame.column})`;
let onClick = () => toolbox.viewSourceInDebugger(spec, frame.line);
let fields = [
dom.span({ className: "frame-link-function-display-name" }, func),
dom.a({ className: "frame-link-filename", onClick }, url.fileName),
dom.span({ className: "frame-link-colon" }, ":"),
dom.span({ className: "frame-link-line" }, frame.line),
dom.span({ className: "frame-link-colon" }, ":"),
dom.span({ className: "frame-link-column" }, frame.column)
];
if (url.scheme === "http" || url.scheme === "https" || url.scheme === "ftp") {
fields.push(dom.span({ className: "frame-link-host" }, url.host));
}
return dom.span({ className: "frame-link", title: tooltip }, ...fields);
}
});

View File

@ -37,13 +37,13 @@ function createParentMap (node, aggregator=Object.create(null)) {
* @param {CensusTreeNode} census
* @return {Object}
*/
function createTreeProperties (census) {
function createTreeProperties (census, toolbox) {
let map = createParentMap(census);
return {
getParent: node => map(node.id),
getParent: node => map[node.id],
getChildren: node => node.children || [],
renderItem: (item, depth, focused, arrow) => new TreeItem({ item, depth, focused, arrow }),
renderItem: (item, depth, focused, arrow) => new TreeItem({ toolbox, item, depth, focused, arrow }),
getRoots: () => census.children,
getKey: node => node.id,
itemHeight: HEAP_TREE_ROW_HEIGHT,
@ -68,7 +68,7 @@ const Heap = module.exports = createClass({
},
render() {
let { snapshot, onSnapshotClick } = this.props;
let { snapshot, onSnapshotClick, toolbox } = this.props;
let census = snapshot ? snapshot.census : null;
let state = snapshot ? snapshot.state : "initial";
let statusText = snapshot ? getSnapshotStatusTextFull(snapshot) : "";
@ -76,14 +76,14 @@ const Heap = module.exports = createClass({
switch (state) {
case "initial":
content = dom.button({
content = [dom.button({
className: "devtools-toolbarbutton take-snapshot",
onClick: onSnapshotClick,
// Want to use the [standalone] tag to leverage our styles,
// but React hates that evidently
"data-standalone": true,
"data-text-only": true,
}, TAKE_SNAPSHOT_TEXT)
}, TAKE_SNAPSHOT_TEXT)];
break;
case states.ERROR:
content = [
@ -96,7 +96,7 @@ const Heap = module.exports = createClass({
case states.READING:
case states.READ:
case states.SAVING_CENSUS:
content = dom.span({ className: "snapshot-status devtools-throbber" }, statusText)
content = [dom.span({ className: "snapshot-status devtools-throbber" }, statusText)];
break;
case states.SAVED_CENSUS:
content = [
@ -107,11 +107,11 @@ const Heap = module.exports = createClass({
dom.span({ className: "heap-tree-item-total-count" }, "Total Count"),
dom.span({ className: "heap-tree-item-name" }, "Name")
),
Tree(createTreeProperties(snapshot.census))
Tree(createTreeProperties(snapshot.census, toolbox))
];
break;
}
let pane = dom.div({ className: "heap-view-panel", "data-state": state }, content);
let pane = dom.div({ className: "heap-view-panel", "data-state": state }, ...content);
return (
dom.div({ id: "heap-view", "data-state": state }, pane)

View File

@ -22,9 +22,9 @@ const List = module.exports = createClass({
let { items, onClick, itemComponent: Item } = this.props;
return (
dom.ul({ className: "list" }, items.map((item, index) => {
dom.ul({ className: "list" }, ...items.map((item, index) => {
return Item({
item, index, onClick: () => onClick(item),
key: index, item, index, onClick: () => onClick(item),
});
}))
);

View File

@ -4,6 +4,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'frame.js',
'heap.js',
'list.js',
'snapshot-list-item.js',

View File

@ -2,7 +2,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/. */
const { DOM, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
const models = require("../models");
@ -33,29 +33,29 @@ const Toolbar = module.exports = createClass({
} = this.props;
return (
DOM.div({ className: "devtools-toolbar" }, [
DOM.button({ className: `take-snapshot devtools-button`, onClick: onTakeSnapshotClick }),
dom.div({ className: "devtools-toolbar" },
dom.button({ className: `take-snapshot devtools-button`, onClick: onTakeSnapshotClick }),
DOM.label({},
dom.label({},
"Breakdown by ",
DOM.select({
dom.select({
className: `select-breakdown`,
onChange: e => onBreakdownChange(e.target.value),
}, breakdowns.map(({ name, displayName }) => DOM.option({ value: name }, displayName)))
}, ...breakdowns.map(({ name, displayName }) => dom.option({ key: name, value: name }, displayName)))
),
DOM.label({}, [
DOM.input({
dom.label({},
dom.input({
type: "checkbox",
checked: inverted,
onChange: onToggleInverted,
}),
// TODO bug 1214799
"Invert tree"
]),
),
DOM.label({}, [
DOM.input({
dom.label({},
dom.input({
type: "checkbox",
checked: allocations.recording,
disabled: allocations.togglingInProgress,
@ -63,8 +63,8 @@ const Toolbar = module.exports = createClass({
}),
// TODO bug 1214799
"Record allocation stacks"
])
])
)
)
);
}
});

View File

@ -3,7 +3,8 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
const { isSavedFrame } = require("devtools/shared/DevToolsUtils");
const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
const { DOM: dom, createClass, createFactory, PropTypes } = require("devtools/client/shared/vendor/react");
const FrameView = createFactory(require("./frame"));
const INDENT = 10;
const MAX_SOURCE_LENGTH = 200;
@ -17,36 +18,23 @@ const TreeItem = module.exports = createClass({
displayName: "tree-item",
render() {
let { item, depth, arrow, focused } = this.props;
let { item, depth, arrow, focused, toolbox } = this.props;
return dom.div({ className: `heap-tree-item ${focused ? "focused" :""}` },
dom.span({ className: "heap-tree-item-bytes" }, item.bytes),
dom.span({ className: "heap-tree-item-count" }, item.count),
dom.span({ className: "heap-tree-item-total-bytes" }, item.totalBytes),
dom.span({ className: "heap-tree-item-total-count" }, item.totalCount),
dom.span({ className: "heap-tree-item-name", style: { marginLeft: depth * INDENT }},
dom.span({ className: "heap-tree-item-field heap-tree-item-bytes" }, item.bytes),
dom.span({ className: "heap-tree-item-field heap-tree-item-count" }, item.count),
dom.span({ className: "heap-tree-item-field heap-tree-item-total-bytes" }, item.totalBytes),
dom.span({ className: "heap-tree-item-field heap-tree-item-total-count" }, item.totalCount),
dom.span({ className: "heap-tree-item-field heap-tree-item-name", style: { marginLeft: depth * INDENT }},
arrow,
this.toLabel(item.name)
this.toLabel(item.name, toolbox)
)
);
},
toLabel(name) {
toLabel(name, toolbox) {
return isSavedFrame(name)
? this.savedFrameToLabel(name)
? FrameView({ frame: name, toolbox })
: String(name);
},
savedFrameToLabel(frame) {
return [
dom.span({ className: "heap-tree-item-function-display-name" },
frame.functionDisplayFrame || ""),
dom.span({ className: "heap-tree-item-at" }, "@"),
dom.span({ className: "heap-tree-item-source" }, frame.source.slice(0, MAX_SOURCE_LENGTH)),
dom.span({ className: "heap-tree-item-colon" }, ":"),
dom.span({ className: "heap-tree-item-line" }, frame.line),
dom.span({ className: "heap-tree-item-colon" }, ":"),
dom.span({ className: "heap-tree-item-column" }, frame.column)
];
}
});

View File

@ -45,7 +45,7 @@ const ArrowExpander = createFactory(createClass({
const TreeNode = createFactory(createClass({
componentDidUpdate() {
if (this.props.focused) {
this.refs.button.getDOMNode().focus();
this.refs.button.focus();
}
},
@ -244,7 +244,7 @@ const Tree = module.exports = createClass({
*/
_updateHeight() {
this.setState({
height: this.refs.tree.getDOMNode().clientHeight
height: this.refs.tree.clientHeight
});
},
@ -361,8 +361,8 @@ const Tree = module.exports = createClass({
*/
_onScroll(e) {
this.setState({
scroll: Math.max(this.refs.tree.getDOMNode().scrollTop, 0),
height: this.refs.tree.getDOMNode().clientHeight
scroll: Math.max(this.refs.tree.scrollTop, 0),
height: this.refs.tree.clientHeight
});
},

View File

@ -29,6 +29,9 @@ actions.SELECT_SNAPSHOT = "select-snapshot";
// Fired to toggle tree inversion on or off.
actions.TOGGLE_INVERTED = "toggle-inverted";
// Fired to set a new breakdown.
actions.SET_BREAKDOWN = "set-breakdown";
// Fired when there is an error processing a snapshot or taking a census.
actions.SNAPSHOT_ERROR = "snapshot-error";

View File

@ -9,26 +9,55 @@ const BrowserLoaderModule = {};
Cu.import("resource://devtools/client/shared/browser-loader.js", BrowserLoaderModule);
const { require } = BrowserLoaderModule.BrowserLoader("resource://devtools/client/memory/", this);
const { Task } = require("resource://gre/modules/Task.jsm");
const { createFactory, createElement, render } = require("devtools/client/shared/vendor/react");
const { createFactory, createElement, render, unmountComponentAtNode } = require("devtools/client/shared/vendor/react");
const { Provider } = require("devtools/client/shared/vendor/react-redux");
const App = createFactory(require("devtools/client/memory/app"));
const Store = require("devtools/client/memory/store");
const { assert } = require("devtools/shared/DevToolsUtils");
/**
* The current target, toolbox, MemoryFront, and HeapAnalysesClient, set by this tool's host.
*/
var gToolbox, gTarget, gFront, gHeapAnalysesClient;
function initialize () {
return Task.spawn(function*() {
let root = document.querySelector("#app");
let store = Store();
let app = createElement(App, { front: gFront, heapWorker: gHeapAnalysesClient });
let provider = createElement(Provider, { store }, app);
render(provider, root);
});
}
/**
* Variables set by `initialize()`
*/
var gStore, gRoot, gApp, gProvider, unsubscribe, isHighlighted;
function destroy () {
return Task.spawn(function*(){});
var initialize = Task.async(function*() {
gRoot = document.querySelector("#app");
gStore = Store();
gApp = createElement(App, { toolbox: gToolbox, front: gFront, heapWorker: gHeapAnalysesClient });
gProvider = createElement(Provider, { store: gStore }, gApp);
render(gProvider, gRoot);
unsubscribe = gStore.subscribe(onStateChange);
});
var destroy = Task.async(function*() {
const ok = unmountComponentAtNode(gRoot);
assert(ok, "Should successfully unmount the memory tool's top level React component");
unsubscribe();
gStore, gRoot, gApp, gProvider, unsubscribe, isHighlighted = null;
});
/**
* Fired on any state change, currently only handles toggling
* the highlighting of the tool when recording allocations.
*/
function onStateChange () {
let isRecording = gStore.getState().allocations.recording;
if (isRecording === isHighlighted) {
return;
}
if (isRecording) {
gToolbox.highlightTool("memory");
} else {
gToolbox.unhighlightTool("memory");
}
isHighlighted = isRecording;
}

View File

@ -19,6 +19,7 @@ let breakdownModel = exports.breakdown = PropTypes.shape({
/**
* Snapshot model.
*/
let stateKeys = Object.keys(states).map(state => states[state]);
let snapshotModel = exports.snapshot = PropTypes.shape({
// Unique ID for a snapshot
id: PropTypes.number.isRequired,
@ -37,19 +38,18 @@ let snapshotModel = exports.snapshot = PropTypes.shape({
error: PropTypes.object,
// State the snapshot is in
// @see ./constants.js
state: function (props, propName) {
let stateNames = Object.keys(states);
let current = props.state;
state: function (snapshot, propName) {
let current = snapshot.state;
let shouldHavePath = [states.SAVED, states.READ, states.SAVING_CENSUS, states.SAVED_CENSUS];
let shouldHaveCensus = [states.SAVED_CENSUS];
if (!stateNames.includes(current)) {
throw new Error(`Snapshot state must be one of ${stateNames}.`);
if (!stateKeys.includes(current)) {
throw new Error(`Snapshot state must be one of ${stateKeys}.`);
}
if (shouldHavePath.includes(current) && !path) {
if (shouldHavePath.includes(current) && !snapshot.path) {
throw new Error(`Snapshots in state ${current} must have a snapshot path.`);
}
if (shouldHaveCensus.includes(current) && (!props.census || !props.breakdown)) {
if (shouldHaveCensus.includes(current) && (!snapshot.census || !snapshot.breakdown)) {
throw new Error(`Snapshots in state ${current} must have a census and breakdown.`);
}
},

View File

@ -3,5 +3,11 @@ tags = devtools
subsuite = devtools
support-files =
head.js
doc_steady_allocation.html
[browser_memory_allocationStackBreakdown_01.js]
[browser_memory-breakdowns-01.js]
skip-if = debug # bug 1219554
[browser_memory-simple-01.js]
skip-if = debug # bug 1219554
[browser_memory_transferHeapSnapshot_e10s_01.js]

View File

@ -0,0 +1,33 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that the heap tree renders rows based on the breakdown
*/
const TEST_URL = "http://example.com/browser/devtools/client/memory/test/browser/doc_steady_allocation.html";
this.test = makeMemoryTest(TEST_URL, function* ({ tab, panel }) {
const { gStore, document } = panel.panelWin;
const $$ = document.querySelectorAll.bind(document);
yield takeSnapshot(panel.panelWin);
yield waitUntilSnapshotState(gStore, [states.SAVED_CENSUS]);
info("Check coarse type heap view");
["objects", "other", "scripts", "strings"].forEach(findNameCell);
yield setBreakdown(panel.panelWin, "objectClass");
info("Check object class heap view");
["Function", "Object"].forEach(findNameCell);
yield setBreakdown(panel.panelWin, "internalType");
info("Check internal type heap view");
["JSObject"].forEach(findNameCell);
function findNameCell (name) {
let el = Array.prototype.find.call($$(".tree .heap-tree-item-name span"), el => el.textContent === name);
ok(el, `Found heap tree item cell for ${name}.`);
}
});

View File

@ -0,0 +1,35 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests taking snapshots and default states.
*/
const TEST_URL = "http://example.com/browser/devtools/client/memory/test/browser/doc_steady_allocation.html";
this.test = makeMemoryTest(TEST_URL, function* ({ tab, panel }) {
const { gStore, document } = panel.panelWin;
const { getState, dispatch } = gStore;
let snapshotEls = document.querySelectorAll("#memory-tool-container .list li");
is(getState().snapshots.length, 0, "Starts with no snapshots in store");
is(snapshotEls.length, 0, "No snapshots rendered");
yield takeSnapshot(panel.panelWin);
snapshotEls = document.querySelectorAll("#memory-tool-container .list li");
is(getState().snapshots.length, 1, "One snapshot was created in store");
is(snapshotEls.length, 1, "One snapshot was rendered");
ok(snapshotEls[0].classList.contains("selected"), "Only snapshot has `selected` class");
yield takeSnapshot(panel.panelWin);
snapshotEls = document.querySelectorAll("#memory-tool-container .list li");
is(getState().snapshots.length, 2, "Two snapshots created in store");
is(snapshotEls.length, 2, "Two snapshots rendered");
ok(!snapshotEls[0].classList.contains("selected"), "First snapshot no longer has `selected` class");
ok(snapshotEls[1].classList.contains("selected"), "Second snapshot has `selected` class");
yield waitUntilSnapshotState(gStore, [states.SAVED_CENSUS, states.SAVED_CENSUS]);
ok(document.querySelector(".heap-tree-item-name"),
"Should have rendered some tree items");
});

View File

@ -0,0 +1,39 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Sanity test that we can show allocation stack breakdowns in the tree.
"use strict";
const { waitForTime } = require("devtools/shared/DevToolsUtils");
const { breakdowns } = require("devtools/client/memory/constants");
const { toggleRecordingAllocationStacks } = require("devtools/client/memory/actions/allocations");
const { takeSnapshotAndCensus } = require("devtools/client/memory/actions/snapshot");
const breakdownActions = require("devtools/client/memory/actions/breakdown");
const { toggleInverted } = require("devtools/client/memory/actions/inverted");
const TEST_URL = "http://example.com/browser/devtools/client/memory/test/browser/doc_steady_allocation.html";
this.test = makeMemoryTest(TEST_URL, function* ({ tab, panel }) {
const heapWorker = panel.panelWin.gHeapAnalysesClient;
const front = panel.panelWin.gFront;
const { getState, dispatch } = panel.panelWin.gStore;
dispatch(toggleInverted());
ok(getState().inverted, true);
dispatch(breakdownActions.setBreakdown(breakdowns.allocationStack.breakdown));
is(getState().breakdown.by, "allocationStack");
yield dispatch(toggleRecordingAllocationStacks(front));
ok(getState().allocations.recording);
// Let some allocations build up.
yield waitForTime(500);
yield dispatch(takeSnapshotAndCensus(front, heapWorker));
const doc = panel.panelWin.document;
ok(doc.querySelector(".frame-link-function-display-name"),
"Should have rendered some allocation stack tree items");
});

View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<body>
<script>
var objects = window.objects = [];
var allocate = this.allocate = function allocate() {
for (var i = 0; i < 100; i++)
objects.push({});
setTimeout(allocate, 10);
}
allocate();
</script>
</body>
</html>

View File

@ -8,6 +8,9 @@ Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js",
this);
var { snapshotState: states } = require("devtools/client/memory/constants");
var { breakdownEquals, breakdownNameToSpec } = require("devtools/client/memory/utils");
Services.prefs.setBoolPref("devtools.memory.enabled", true);
/**
@ -63,3 +66,60 @@ function makeMemoryTest(url, generator) {
finish();
});
}
function waitUntilState (store, predicate) {
let deferred = promise.defer();
let unsubscribe = store.subscribe(check);
function check () {
if (predicate(store.getState())) {
unsubscribe();
deferred.resolve()
}
}
// Fire the check immediately incase the action has already occurred
check();
return deferred.promise;
}
function waitUntilSnapshotState (store, expected) {
let predicate = () => {
let snapshots = store.getState().snapshots;
info(snapshots.map(x => x.state));
return snapshots.length === expected.length &&
expected.every((state, i) => state === "*" || snapshots[i].state === state);
};
info(`Waiting for snapshots to be of state: ${expected}`);
return waitUntilState(store, predicate);
}
function takeSnapshot (window) {
let { gStore, document } = window;
let snapshotCount = gStore.getState().snapshots.length;
info(`Taking snapshot...`);
document.querySelector(".devtools-toolbar .take-snapshot").click();
return waitUntilState(gStore, () => gStore.getState().snapshots.length === snapshotCount + 1);
}
/**
* Sets breakdown and waits for currently selected breakdown to use it
* and be completed the census.
*/
function setBreakdown (window, type) {
info(`Setting breakdown to ${type}...`);
let { gStore, gHeapAnalysesClient } = window;
// XXX: Should handle this via clicking the DOM, but React doesn't
// fire the onChange event, so just change it in the store.
// window.document.querySelector(`.select-breakdown`).value = type;
gStore.dispatch(require("devtools/client/memory/actions/breakdown")
.setBreakdownAndRefresh(gHeapAnalysesClient, breakdownNameToSpec(type)));
return waitUntilState(window.gStore, () => {
let selected = window.gStore.getState().snapshots.find(s => s.selected);
return selected.state === states.SAVED_CENSUS &&
breakdownEquals(breakdownNameToSpec(type), selected.breakdown);
});
}

View File

@ -0,0 +1,7 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="#71c054">
<path opacity="0.2" d="M5.8 3.9l4.9.1v8.3H5.8z"/>
<path d="M12.8 8L14 9.2c.1.1.3.2.5.2s.4-.1.5-.2c.3-.3.3-.8 0-1.1l-1.6-1.6c-.2-.2-.5-.3-.8-.2l-.6.4V5.3l.8-.3L14 6.2c.1.1.3.2.5.2s.4-.1.5-.2c.3-.3.3-.8 0-1.1l-1.6-1.6c-.2-.2-.5-.3-.8-.2l-.6.4V2.3c0-.2-.1-.3-.3-.3H4.3c-.2 0-.3.1-.3.3v1.5l-.8-.4c-.3-.1-.6-.1-.9.1L.8 5.1c-.3.3-.3.8 0 1.1.1.1.3.2.5.2s.4-.1.5-.2L3 5l1 .4v1.4l-.8-.4c-.3-.1-.6-.1-.9.1L.8 8.1c-.3.3-.3.8 0 1.1.1.1.3.2.5.2s.4-.1.5-.2L3 8l1 .4v1.4l-.8-.4c-.3-.1-.6-.1-.9.1L.7 11.1c-.3.3-.3.8 0 1.1.1.1.3.2.5.2s.4-.1.5-.2L3 11l1 .4v2.4c0 .1.1.2.3.2h7.5c.1 0 .3-.1.3-.3v-2.4l.8-.3 1.2 1.2c.1.1.3.2.5.2s.4-.1.5-.2c.3-.3.3-.8 0-1.1l-1.6-1.6c-.2-.2-.5-.3-.8-.2l-.7.4V8.3l.8-.3zM10 12H6V4h4v8z"/>
</svg>

After

Width:  |  Height:  |  Size: 1021 B

View File

@ -0,0 +1,7 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="whitesmoke">
<path opacity="0.2" d="M5.8 3.9l4.9.1v8.3H5.8z"/>
<path d="M12.8 8L14 9.2c.1.1.3.2.5.2s.4-.1.5-.2c.3-.3.3-.8 0-1.1l-1.6-1.6c-.2-.2-.5-.3-.8-.2l-.6.4V5.3l.8-.3L14 6.2c.1.1.3.2.5.2s.4-.1.5-.2c.3-.3.3-.8 0-1.1l-1.6-1.6c-.2-.2-.5-.3-.8-.2l-.6.4V2.3c0-.2-.1-.3-.3-.3H4.3c-.2 0-.3.1-.3.3v1.5l-.8-.4c-.3-.1-.6-.1-.9.1L.8 5.1c-.3.3-.3.8 0 1.1.1.1.3.2.5.2s.4-.1.5-.2L3 5l1 .4v1.4l-.8-.4c-.3-.1-.6-.1-.9.1L.8 8.1c-.3.3-.3.8 0 1.1.1.1.3.2.5.2s.4-.1.5-.2L3 8l1 .4v1.4l-.8-.4c-.3-.1-.6-.1-.9.1L.7 11.1c-.3.3-.3.8 0 1.1.1.1.3.2.5.2s.4-.1.5-.2L3 11l1 .4v2.4c0 .1.1.2.3.2h7.5c.1 0 .3-.1.3-.3v-2.4l.8-.3 1.2 1.2c.1.1.3.2.5.2s.4-.1.5-.2c.3-.3.3-.8 0-1.1l-1.6-1.6c-.2-.2-.5-.3-.8-.2l-.7.4V8.3l.8-.3zM10 12H6V4h4v8z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -122,7 +122,6 @@ html, .theme-body, #app, #memory-tool, #memory-tool-container {
border-bottom: 1px solid rgba(128,128,128,0.15);
padding: 8px;
cursor: pointer;
color: var(--theme-selection-color);
}
.list > li.selected {
@ -219,7 +218,7 @@ html, .theme-body, #app, #memory-tool, #memory-tool-container {
height: var(--heap-tree-row-height);
}
.heap-tree-item span, .header span {
.heap-tree-item-field, .header span {
float: left;
}
@ -268,7 +267,7 @@ html, .theme-body, #app, #memory-tool, #memory-tool-container {
}
.heap-tree-item-name {
padding-left: 10px;
padding-left: 5px;
}
.error::before {
@ -287,3 +286,34 @@ html, .theme-body, #app, #memory-tool, #memory-tool-container {
.theme-light .error::before {
background-image: url(chrome://devtools/skin/themes/images/webconsole.svg#light-icons);
}
/**
* Frame View components
*/
.focused .frame-link-filename,
.focused .frame-link-column,
.focused .frame-link-line,
.focused .frame-link-host,
.focused .frame-link-colon {
color: var(--theme-selection-color);
}
.frame-link-filename {
color: var(--theme-highlight-blue);
cursor: pointer;
}
.frame-link-filename:hover {
text-decoration: underline;
}
.frame-link-column, .frame-link-line, .frame-link-colon {
color: var(--theme-highlight-orange);
}
.frame-link-host {
font-size: 90%;
margin-left: 5px;
color: var(--theme-content-color2);
}

View File

@ -173,6 +173,9 @@ RootActor.prototype = {
// Whether or not `getProfile()` supports specifying a `startTime`
// and `endTime` to filter out samples. Fx40+
profilerDataFilterable: true,
// Whether or not the MemoryActor's heap snapshot abilities are
// fully equipped to handle heap snapshots for the memory tool. Fx44+
heapSnapshots: true,
},
/**

View File

@ -55,9 +55,11 @@ using JS::ubi::AtomOrTwoByteChars;
NS_IMPL_CYCLE_COLLECTION_CLASS(HeapSnapshot)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(HeapSnapshot)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(HeapSnapshot)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

View File

@ -5,7 +5,6 @@
package org.mozilla.gecko;
import android.util.Log;
import org.json.JSONObject;
import android.text.TextUtils;
@ -14,6 +13,7 @@ public class SiteIdentity {
private final String LOGTAG = "GeckoSiteIdentity";
private SecurityMode mSecurityMode;
private boolean mSecure;
private boolean mLoginInsecure;
private MixedMode mMixedModeActive;
private MixedMode mMixedModeDisplay;
private TrackingMode mTrackingMode;
@ -134,6 +134,7 @@ public class SiteIdentity {
mSupplemental = null;
mVerifier = null;
mSecure = false;
mLoginInsecure = false;
}
public void reset() {
@ -220,6 +221,14 @@ public class SiteIdentity {
return mSecure;
}
public void setLoginInsecure(boolean isInsecure) {
mLoginInsecure = isInsecure;
}
public boolean loginInsecure() {
return mLoginInsecure;
}
public MixedMode getMixedModeActive() {
return mMixedModeActive;
}

View File

@ -36,7 +36,6 @@ import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import org.mozilla.gecko.widget.SiteLogins;
public class Tab {
@ -508,6 +507,10 @@ public class Tab {
mSiteIdentity.update(identityData);
}
public void setLoginInsecure(boolean isInsecure) {
mSiteIdentity.setLoginInsecure(isInsecure);
}
public void setSiteLogins(SiteLogins siteLogins) {
mSiteLogins = siteLogins;
}

View File

@ -99,6 +99,7 @@ public class Tabs implements GeckoEventListener {
"Tab:Close",
"Tab:Select",
"Content:LocationChange",
"Content:LoginInsecure",
"Content:SecurityChange",
"Content:StateChange",
"Content:LoadError",
@ -510,6 +511,9 @@ public class Tabs implements GeckoEventListener {
} else if (event.equals("Content:SecurityChange")) {
tab.updateIdentityData(message.getJSONObject("identity"));
notifyListeners(tab, TabEvents.SECURITY_CHANGE);
} else if (event.equals("Content:LoginInsecure")) {
tab.setLoginInsecure(true);
notifyListeners(tab, TabEvents.SECURITY_CHANGE);
} else if (event.equals("Content:StateChange")) {
int state = message.getInt("state");
if ((state & GeckoAppShell.WPL_STATE_IS_NETWORK) != 0) {

View File

@ -589,6 +589,7 @@ with that structure, consider a translation which ignores the preceding domain a
just addresses the organization to follow, e.g. "This site is run by " -->
<!ENTITY identity_connection_secure "Secure Connection">
<!ENTITY identity_connection_insecure "Insecure connection">
<!ENTITY identity_login_insecure "This page is not secure and your login could be vulnerable.">
<!-- Mixed content notifications in site identity popup -->
<!ENTITY mixed_content_blocked_all1 "&brandShortName; has blocked insecure content on this page.">

View File

@ -494,6 +494,7 @@
<!-- Site identity popup -->
<string name="identity_connection_secure">&identity_connection_secure;</string>
<string name="identity_connection_insecure">&identity_connection_insecure;</string>
<string name="identity_login_insecure">&identity_login_insecure;</string>
<string name="mixed_content_blocked_all">&mixed_content_blocked_all1;</string>
<string name="mixed_content_blocked_some">&mixed_content_blocked_some1;</string>

View File

@ -36,6 +36,7 @@ public class TabReceivedService extends IntentService {
public TabReceivedService() {
super(LOGTAG);
setIntentRedelivery(true);
}
@Override
@ -74,6 +75,10 @@ public class TabReceivedService extends IntentService {
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
notificationManager.notify(notificationId, builder.build());
// Save the ID last so if the Service is killed and the Intent is redelivered,
// the ID is unlikely to have been updated and we would re-use the the old one.
// This would prevent two identical notifications from appearing if the
// notification was shown during the previous Intent processing attempt.
prefs.edit().putInt(PREF_NOTIFICATION_ID, notificationId).apply();
}

View File

@ -48,6 +48,8 @@ import org.mozilla.gecko.widget.SiteLogins;
/**
* SiteIdentityPopup is a singleton class that displays site identity data in
* an arrow panel popup hanging from the lock icon in the browser toolbar.
*
* A site identity icon may be displayed in the url, and is set in <code>ToolbarDisplayLayout</code>.
*/
public class SiteIdentityPopup extends AnchoredPopup implements GeckoEventListener {
@ -309,7 +311,14 @@ public class SiteIdentityPopup extends AnchoredPopup implements GeckoEventListen
*/
private void updateConnectionState(final SiteIdentity siteIdentity) {
if (!siteIdentity.isSecure()) {
if (siteIdentity.getMixedModeActive() == MixedMode.MIXED_CONTENT_LOADED) {
if (siteIdentity.loginInsecure()) {
// Login detected on an insecure page.
mIcon.setImageResource(R.drawable.lock_disabled);
clearSecurityStateIcon();
mMixedContentActivity.setVisibility(View.VISIBLE);
mMixedContentActivity.setText(R.string.identity_login_insecure);
} else if (siteIdentity.getMixedModeActive() == MixedMode.MIXED_CONTENT_LOADED) {
// Active Mixed Content loaded because user has disabled blocking.
mIcon.setImageResource(R.drawable.lock_disabled);
clearSecurityStateIcon();

View File

@ -484,16 +484,19 @@ public class ToolbarDisplayLayout extends ThemedLinearLayout
final MixedMode activeMixedMode;
final MixedMode displayMixedMode;
final TrackingMode trackingMode;
final boolean loginInsecure;
if (siteIdentity == null) {
securityMode = SecurityMode.UNKNOWN;
activeMixedMode = MixedMode.UNKNOWN;
displayMixedMode = MixedMode.UNKNOWN;
trackingMode = TrackingMode.UNKNOWN;
loginInsecure = false;
} else {
securityMode = siteIdentity.getSecurityMode();
activeMixedMode = siteIdentity.getMixedModeActive();
displayMixedMode = siteIdentity.getMixedModeDisplay();
trackingMode = siteIdentity.getTrackingMode();
loginInsecure = siteIdentity.loginInsecure();
}
// This is a bit tricky, but we have one icon and three potential indicators.
@ -501,7 +504,9 @@ public class ToolbarDisplayLayout extends ThemedLinearLayout
int imageLevel = securityMode.ordinal();
// Check to see if any protection was overridden first
if (trackingMode == TrackingMode.TRACKING_CONTENT_LOADED) {
if (loginInsecure) {
imageLevel = LEVEL_LOCK_DISABLED;
} else if (trackingMode == TrackingMode.TRACKING_CONTENT_LOADED) {
imageLevel = LEVEL_SHIELD_DISABLED;
} else if (trackingMode == TrackingMode.TRACKING_CONTENT_BLOCKED) {
imageLevel = LEVEL_SHIELD_ENABLED;

View File

@ -32,7 +32,7 @@
<h1 class="showNormal">&privatebrowsingpage.title.normal1;</h1>
<div class="contentSection">
<p class="showPrivate">&privatebrowsingpage.description.private4;</p>
<p class="showPrivate">&privatebrowsingpage.description.trackingProtection;<br /><br />&privatebrowsingpage.description.privateDetails;</p>
<p class="showNormal">&privatebrowsingpage.description.normal2;</p>
<p class="showPrivate"><a href="https://support.mozilla.org/kb/private-browsing-firefox-android">&privatebrowsingpage.link.private;</a></p>

View File

@ -4763,6 +4763,7 @@ var BrowserEventHandler = {
InitLater(() => BrowserApp.deck.addEventListener("click", InputWidgetHelper, true));
InitLater(() => BrowserApp.deck.addEventListener("click", SelectHelper, true));
InitLater(() => BrowserApp.deck.addEventListener("InsecureLoginFormsStateChange", IdentityHandler.sendLoginInsecure, true));
// ReaderViews support backPress listeners.
Messaging.addListener(() => {
@ -6617,6 +6618,18 @@ var IdentityHandler = {
return this.TRACKING_MODE_UNKNOWN;
},
sendLoginInsecure: function sendLoginInsecure() {
let loginInsecure = LoginManagerParent.hasInsecureLoginForms(BrowserApp.selectedBrowser);
if (loginInsecure) {
let message = {
type: "Content:LoginInsecure",
tabID: BrowserApp.selectedTab.id
};
Messaging.sendRequest(message);
}
},
shieldHistogramAdd: function(browser, value) {
if (PrivateBrowsingUtils.isBrowserPrivate(browser)) {
return;

View File

@ -1,7 +1,6 @@
/* 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/. */
* 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/. */
const Cc = Components.classes;
const Ci = Components.interfaces;
@ -12,7 +11,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
/* Constants for password prompt telemetry.
* Mirrored in nsLoginManagerPrompter.js */
* Mirrored in nsLoginManagerPrompter.js */
const PROMPT_DISPLAYED = 0;
const PROMPT_ADD = 1;
@ -34,429 +33,407 @@ function LoginManagerPrompter() {
}
LoginManagerPrompter.prototype = {
classID : Components.ID("97d12931-abe2-11df-94e2-0800200c9a66"),
QueryInterface : XPCOMUtils.generateQI([Ci.nsILoginManagerPrompter]),
classID : Components.ID("97d12931-abe2-11df-94e2-0800200c9a66"),
QueryInterface : XPCOMUtils.generateQI([Ci.nsILoginManagerPrompter]),
_factory : null,
_window : null,
_debug : false, // mirrors signon.debug
_factory : null,
_window : null,
_debug : false, // mirrors signon.debug
__pwmgr : null, // Password Manager service
get _pwmgr() {
if (!this.__pwmgr)
this.__pwmgr = Cc["@mozilla.org/login-manager;1"].
getService(Ci.nsILoginManager);
return this.__pwmgr;
},
__pwmgr : null, // Password Manager service
get _pwmgr() {
if (!this.__pwmgr)
this.__pwmgr = Cc["@mozilla.org/login-manager;1"].
getService(Ci.nsILoginManager);
return this.__pwmgr;
},
__promptService : null, // Prompt service for user interaction
get _promptService() {
if (!this.__promptService)
this.__promptService = Cc["@mozilla.org/embedcomp/prompt-service;1"].
getService(Ci.nsIPromptService2);
return this.__promptService;
},
__promptService : null, // Prompt service for user interaction
get _promptService() {
if (!this.__promptService)
this.__promptService =
Cc["@mozilla.org/embedcomp/prompt-service;1"].
getService(Ci.nsIPromptService2);
return this.__promptService;
},
__strBundle : null, // String bundle for L10N
get _strBundle() {
if (!this.__strBundle) {
var bunService = Cc["@mozilla.org/intl/stringbundle;1"].
getService(Ci.nsIStringBundleService);
this.__strBundle = {
pwmgr : bunService.createBundle(
"chrome://passwordmgr/locale/passwordmgr.properties"),
brand : bunService.createBundle("chrome://branding/locale/brand.properties")
};
__strBundle : null, // String bundle for L10N
get _strBundle() {
if (!this.__strBundle) {
var bunService = Cc["@mozilla.org/intl/stringbundle;1"].
getService(Ci.nsIStringBundleService);
this.__strBundle = {
pwmgr : bunService.createBundle("chrome://passwordmgr/locale/passwordmgr.properties"),
brand : bunService.createBundle("chrome://branding/locale/brand.properties")
};
if (!this.__strBundle)
throw "String bundle for Login Manager not present!";
if (!this.__strBundle)
throw "String bundle for Login Manager not present!";
}
return this.__strBundle;
},
__ellipsis : null,
get _ellipsis() {
if (!this.__ellipsis) {
this.__ellipsis = "\u2026";
try {
this.__ellipsis = Services.prefs.getComplexValue(
"intl.ellipsis", Ci.nsIPrefLocalizedString).data;
} catch (e) { }
}
return this.__ellipsis;
},
/*
* log
*
* Internal function for logging debug messages to the Error Console window.
*/
log : function (message) {
if (!this._debug)
return;
dump("Pwmgr Prompter: " + message + "\n");
Services.console.logStringMessage("Pwmgr Prompter: " + message);
},
/* ---------- nsILoginManagerPrompter prompts ---------- */
/*
* init
*
*/
init : function (aWindow, aFactory) {
this._window = aWindow;
this._factory = aFactory || null;
var prefBranch = Services.prefs.getBranch("signon.");
this._debug = prefBranch.getBoolPref("debug");
this.log("===== initialized =====");
},
setE10sData : function (aBrowser, aOpener) {
throw new Error("This should be filled in when Android is multiprocess");
},
/*
* promptToSavePassword
*
*/
promptToSavePassword : function (aLogin) {
this._showSaveLoginNotification(aLogin);
Services.telemetry.getHistogramById("PWMGR_PROMPT_REMEMBER_ACTION").add(PROMPT_DISPLAYED);
},
/*
* _showLoginNotification
*
* Displays a notification doorhanger.
* @param aBody
* String message to be displayed in the doorhanger
* @param aButtons
* Buttons to display with the doorhanger
* @param aUsername
* Username string used in creating a doorhanger action
* @param aPassword
* Password string used in creating a doorhanger action
*/
_showLoginNotification : function (aBody, aButtons, aUsername, aPassword) {
let notifyWin = this._window.top;
let chromeWin = this._getChromeWindow(notifyWin).wrappedJSObject;
let browser = chromeWin.BrowserApp.getBrowserForWindow(notifyWin);
let tabID = chromeWin.BrowserApp.getTabForBrowser(browser).id;
let actionText = {
text: aUsername,
type: "EDIT",
bundle: { username: aUsername,
password: aPassword }
};
// The page we're going to hasn't loaded yet, so we want to persist
// across the first location change.
// Sites like Gmail perform a funky redirect dance before you end up
// at the post-authentication page. I don't see a good way to
// heuristically determine when to ignore such location changes, so
// we'll try ignoring location changes based on a time interval.
let options = {
persistWhileVisible: true,
timeout: Date.now() + 10000,
actionText: actionText
}
var nativeWindow = this._getNativeWindow();
if (nativeWindow)
nativeWindow.doorhanger.show(aBody, "password", aButtons, tabID, options, "LOGIN");
},
/*
* _showSaveLoginNotification
*
* Displays a notification doorhanger (rather than a popup), to allow the user to
* save the specified login. This allows the user to see the results of
* their login, and only save a login which they know worked.
*
*/
_showSaveLoginNotification : function (aLogin) {
let brandShortName = this._strBundle.brand.GetStringFromName("brandShortName");
let notificationText = this._getLocalizedString("saveLogin", [brandShortName]);
let username = aLogin.username ? this._sanitizeUsername(aLogin.username) : "";
// The callbacks in |buttons| have a closure to access the variables
// in scope here; set one to |this._pwmgr| so we can get back to pwmgr
// without a getService() call.
var pwmgr = this._pwmgr;
let promptHistogram = Services.telemetry.getHistogramById("PWMGR_PROMPT_REMEMBER_ACTION");
var buttons = [
{
label: this._getLocalizedString("neverButton"),
callback: function() {
promptHistogram.add(PROMPT_NEVER);
pwmgr.setLoginSavingEnabled(aLogin.hostname, false);
}
},
{
label: this._getLocalizedString("rememberButton"),
callback: function(checked, response) {
if (response) {
aLogin.username = response["username"] || aLogin.username;
aLogin.password = response["password"] || aLogin.password;
}
pwmgr.addLogin(aLogin);
promptHistogram.add(PROMPT_ADD);
},
positive: true
}
];
return this.__strBundle;
},
this._showLoginNotification(notificationText, buttons, aLogin.username, aLogin.password);
},
/*
* promptToChangePassword
*
* Called when we think we detect a password change for an existing
* login, when the form being submitted contains multiple password
* fields.
*
*/
promptToChangePassword : function (aOldLogin, aNewLogin) {
this._showChangeLoginNotification(aOldLogin, aNewLogin.password);
Services.telemetry.getHistogramById("PWMGR_PROMPT_UPDATE_ACTION").add(PROMPT_DISPLAYED);
},
__ellipsis : null,
get _ellipsis() {
if (!this.__ellipsis) {
this.__ellipsis = "\u2026";
try {
this.__ellipsis = Services.prefs.getComplexValue(
"intl.ellipsis", Ci.nsIPrefLocalizedString).data;
} catch (e) { }
/*
* _showChangeLoginNotification
*
* Shows the Change Password notification doorhanger.
*
*/
_showChangeLoginNotification : function (aOldLogin, aNewPassword) {
var notificationText;
if (aOldLogin.username) {
let displayUser = this._sanitizeUsername(aOldLogin.username);
notificationText = this._getLocalizedString("updatePassword", [displayUser]);
} else {
notificationText = this._getLocalizedString("updatePasswordNoUser");
}
// The callbacks in |buttons| have a closure to access the variables
// in scope here; set one to |this._pwmgr| so we can get back to pwmgr
// without a getService() call.
var self = this;
let promptHistogram = Services.telemetry.getHistogramById("PWMGR_PROMPT_UPDATE_ACTION");
var buttons = [
{
label: this._getLocalizedString("dontUpdateButton"),
callback: function() {
promptHistogram.add(PROMPT_NOTNOW);
// do nothing
}
return this.__ellipsis;
},
},
{
label: this._getLocalizedString("updateButton"),
callback: function(checked, response) {
let password = response ? response["password"] : aNewPassword;
self._updateLogin(aOldLogin, password);
promptHistogram.add(PROMPT_UPDATE);
},
positive: true
}
];
/*
* log
*
* Internal function for logging debug messages to the Error Console window.
*/
log : function (message) {
if (!this._debug)
return;
this._showLoginNotification(notificationText, buttons, aOldLogin.username, aNewPassword);
},
dump("Pwmgr Prompter: " + message + "\n");
Services.console.logStringMessage("Pwmgr Prompter: " + message);
},
/*
* promptToChangePasswordWithUsernames
*
* Called when we detect a password change in a form submission, but we
* don't know which existing login (username) it's for. Asks the user
* to select a username and confirm the password change.
*
* Note: The caller doesn't know the username for aNewLogin, so this
* function fills in .username and .usernameField with the values
* from the login selected by the user.
*
* Note; XPCOM stupidity: |count| is just |logins.length|.
*/
promptToChangePasswordWithUsernames : function (logins, count, aNewLogin) {
const buttonFlags = Ci.nsIPrompt.STD_YES_NO_BUTTONS;
var usernames = logins.map(l => l.username);
var dialogText = this._getLocalizedString("userSelectText");
var dialogTitle = this._getLocalizedString("passwordChangeTitle");
var selectedIndex = { value: null };
/* ---------- nsILoginManagerPrompter prompts ---------- */
// If user selects ok, outparam.value is set to the index
// of the selected username.
var ok = this._promptService.select(null,
dialogTitle, dialogText,
usernames.length, usernames,
selectedIndex);
if (ok) {
// Now that we know which login to use, modify its password.
var selectedLogin = logins[selectedIndex.value];
this.log("Updating password for user " + selectedLogin.username);
this._updateLogin(selectedLogin, aNewLogin.password);
}
},
/* ---------- Internal Methods ---------- */
/*
* _updateLogin
*/
_updateLogin : function (login, newPassword) {
var now = Date.now();
var propBag = Cc["@mozilla.org/hash-property-bag;1"].
createInstance(Ci.nsIWritablePropertyBag);
if (newPassword) {
propBag.setProperty("password", newPassword);
// Explicitly set the password change time here (even though it would
// be changed automatically), to ensure that it's exactly the same
// value as timeLastUsed.
propBag.setProperty("timePasswordChanged", now);
}
propBag.setProperty("timeLastUsed", now);
propBag.setProperty("timesUsedIncrement", 1);
this._pwmgr.modifyLogin(login, propBag);
},
/*
* _getChromeWindow
*
* Given a content DOM window, returns the chrome window it's in.
*/
_getChromeWindow: function (aWindow) {
var chromeWin = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell)
.chromeEventHandler.ownerDocument.defaultView;
return chromeWin;
},
/*
* init
*
*/
init : function (aWindow, aFactory) {
this._window = aWindow;
this._factory = aFactory || null;
/*
* _getNativeWindow
*
* Returns the NativeWindow to this prompter, or null if there isn't
* a NativeWindow available (w/ error sent to logcat).
*/
_getNativeWindow : function () {
let nativeWindow = null;
try {
let notifyWin = this._window.top;
let chromeWin = this._getChromeWindow(notifyWin).wrappedJSObject;
if (chromeWin.NativeWindow) {
nativeWindow = chromeWin.NativeWindow;
} else {
Cu.reportError("NativeWindow not available on window");
}
var prefBranch = Services.prefs.getBranch("signon.");
this._debug = prefBranch.getBoolPref("debug");
this.log("===== initialized =====");
},
} catch (e) {
// If any errors happen, just assume no native window helper.
Cu.reportError("No NativeWindow available: " + e);
}
return nativeWindow;
},
setE10sData : function (aBrowser, aOpener) {
throw new Error("This should be filled in when Android is multiprocess");
},
/*
* _getLocalizedString
*
* Can be called as:
* _getLocalizedString("key1");
* _getLocalizedString("key2", ["arg1"]);
* _getLocalizedString("key3", ["arg1", "arg2"]);
* (etc)
*
* Returns the localized string for the specified key,
* formatted if required.
*
*/
_getLocalizedString : function (key, formatArgs) {
if (formatArgs)
return this._strBundle.pwmgr.formatStringFromName(
key, formatArgs, formatArgs.length);
else
return this._strBundle.pwmgr.GetStringFromName(key);
},
/*
* promptToSavePassword
*
*/
promptToSavePassword : function (aLogin) {
this._showSaveLoginNotification(aLogin);
Services.telemetry.getHistogramById("PWMGR_PROMPT_REMEMBER_ACTION").add(PROMPT_DISPLAYED);
},
/*
* _sanitizeUsername
*
* Sanitizes the specified username, by stripping quotes and truncating if
* it's too long. This helps prevent an evil site from messing with the
* "save password?" prompt too much.
*/
_sanitizeUsername : function (username) {
if (username.length > 30) {
username = username.substring(0, 30);
username += this._ellipsis;
}
return username.replace(/['"]/g, "");
},
/*
* _getFormattedHostname
*
* The aURI parameter may either be a string uri, or an nsIURI instance.
*
* Returns the hostname to use in a nsILoginInfo object (for example,
* "http://example.com").
*/
_getFormattedHostname : function (aURI) {
var uri;
if (aURI instanceof Ci.nsIURI) {
uri = aURI;
} else {
uri = Services.io.newURI(aURI, null, null);
}
var scheme = uri.scheme;
/*
* _showLoginNotification
*
* Displays a notification doorhanger.
* @param aBody
* String message to be displayed in the doorhanger
* @param aButtons
* Buttons to display with the doorhanger
* @param aUsername
* Username string used in creating a doorhanger action
* @param aPassword
* Password string used in creating a doorhanger action
*/
_showLoginNotification : function (aBody, aButtons, aUsername, aPassword) {
let notifyWin = this._window.top;
let chromeWin = this._getChromeWindow(notifyWin).wrappedJSObject;
let browser = chromeWin.BrowserApp.getBrowserForWindow(notifyWin);
let tabID = chromeWin.BrowserApp.getTabForBrowser(browser).id;
var hostname = scheme + "://" + uri.host;
let actionText = {
text: aUsername,
type: "EDIT",
bundle: { username: aUsername,
password: aPassword }
};
// If the URI explicitly specified a port, only include it when
// it's not the default. (We never want "http://foo.com:80")
let port = uri.port;
if (port != -1) {
var handler = Services.io.getProtocolHandler(scheme);
if (port != handler.defaultPort)
hostname += ":" + port;
}
// The page we're going to hasn't loaded yet, so we want to persist
// across the first location change.
// Sites like Gmail perform a funky redirect dance before you end up
// at the post-authentication page. I don't see a good way to
// heuristically determine when to ignore such location changes, so
// we'll try ignoring location changes based on a time interval.
let options = {
persistWhileVisible: true,
timeout: Date.now() + 10000,
actionText: actionText
}
var nativeWindow = this._getNativeWindow();
if (nativeWindow)
nativeWindow.doorhanger.show(aBody, "password", aButtons, tabID, options, "LOGIN");
},
/*
* _showSaveLoginNotification
*
* Displays a notification doorhanger (rather than a popup), to allow the user to
* save the specified login. This allows the user to see the results of
* their login, and only save a login which they know worked.
*
*/
_showSaveLoginNotification : function (aLogin) {
let brandShortName = this._strBundle.brand.GetStringFromName("brandShortName");
let notificationText = this._getLocalizedString("saveLogin", [brandShortName]);
let username = aLogin.username ? this._sanitizeUsername(aLogin.username) : "";
// The callbacks in |buttons| have a closure to access the variables
// in scope here; set one to |this._pwmgr| so we can get back to pwmgr
// without a getService() call.
var pwmgr = this._pwmgr;
let promptHistogram = Services.telemetry.getHistogramById("PWMGR_PROMPT_REMEMBER_ACTION");
var buttons = [
{
label: this._getLocalizedString("neverButton"),
callback: function() {
promptHistogram.add(PROMPT_NEVER);
pwmgr.setLoginSavingEnabled(aLogin.hostname, false);
}
},
{
label: this._getLocalizedString("rememberButton"),
callback: function(checked, response) {
if (response) {
aLogin.username = response["username"] || aLogin.username;
aLogin.password = response["password"] || aLogin.password;
}
pwmgr.addLogin(aLogin);
promptHistogram.add(PROMPT_ADD);
},
positive: true
}
];
this._showLoginNotification(notificationText, buttons, aLogin.username, aLogin.password);
},
/*
* promptToChangePassword
*
* Called when we think we detect a password change for an existing
* login, when the form being submitted contains multiple password
* fields.
*
*/
promptToChangePassword : function (aOldLogin, aNewLogin) {
this._showChangeLoginNotification(aOldLogin, aNewLogin.password);
Services.telemetry.getHistogramById("PWMGR_PROMPT_UPDATE_ACTION").add(PROMPT_DISPLAYED);
},
/*
* _showChangeLoginNotification
*
* Shows the Change Password notification doorhanger.
*
*/
_showChangeLoginNotification : function (aOldLogin, aNewPassword) {
var notificationText;
if (aOldLogin.username) {
let displayUser = this._sanitizeUsername(aOldLogin.username);
notificationText = this._getLocalizedString("updatePassword", [displayUser]);
} else {
notificationText = this._getLocalizedString("updatePasswordNoUser");
}
// The callbacks in |buttons| have a closure to access the variables
// in scope here; set one to |this._pwmgr| so we can get back to pwmgr
// without a getService() call.
var self = this;
let promptHistogram = Services.telemetry.getHistogramById("PWMGR_PROMPT_UPDATE_ACTION");
var buttons = [
{
label: this._getLocalizedString("dontUpdateButton"),
callback: function() {
promptHistogram.add(PROMPT_NOTNOW);
// do nothing
}
},
{
label: this._getLocalizedString("updateButton"),
callback: function(checked, response) {
let password = response ? response["password"] : aNewPassword;
self._updateLogin(aOldLogin, password);
promptHistogram.add(PROMPT_UPDATE);
},
positive: true
}
];
this._showLoginNotification(notificationText, buttons, aOldLogin.username, aNewPassword);
},
/*
* promptToChangePasswordWithUsernames
*
* Called when we detect a password change in a form submission, but we
* don't know which existing login (username) it's for. Asks the user
* to select a username and confirm the password change.
*
* Note: The caller doesn't know the username for aNewLogin, so this
* function fills in .username and .usernameField with the values
* from the login selected by the user.
*
* Note; XPCOM stupidity: |count| is just |logins.length|.
*/
promptToChangePasswordWithUsernames : function (logins, count, aNewLogin) {
const buttonFlags = Ci.nsIPrompt.STD_YES_NO_BUTTONS;
var usernames = logins.map(l => l.username);
var dialogText = this._getLocalizedString("userSelectText");
var dialogTitle = this._getLocalizedString("passwordChangeTitle");
var selectedIndex = { value: null };
// If user selects ok, outparam.value is set to the index
// of the selected username.
var ok = this._promptService.select(null,
dialogTitle, dialogText,
usernames.length, usernames,
selectedIndex);
if (ok) {
// Now that we know which login to use, modify its password.
var selectedLogin = logins[selectedIndex.value];
this.log("Updating password for user " + selectedLogin.username);
this._updateLogin(selectedLogin, aNewLogin.password);
}
},
/* ---------- Internal Methods ---------- */
/*
* _updateLogin
*/
_updateLogin : function (login, newPassword) {
var now = Date.now();
var propBag = Cc["@mozilla.org/hash-property-bag;1"].
createInstance(Ci.nsIWritablePropertyBag);
if (newPassword) {
propBag.setProperty("password", newPassword);
// Explicitly set the password change time here (even though it would
// be changed automatically), to ensure that it's exactly the same
// value as timeLastUsed.
propBag.setProperty("timePasswordChanged", now);
}
propBag.setProperty("timeLastUsed", now);
propBag.setProperty("timesUsedIncrement", 1);
this._pwmgr.modifyLogin(login, propBag);
},
/*
* _getChromeWindow
*
* Given a content DOM window, returns the chrome window it's in.
*/
_getChromeWindow: function (aWindow) {
var chromeWin = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell)
.chromeEventHandler.ownerDocument.defaultView;
return chromeWin;
},
/*
* _getNativeWindow
*
* Returns the NativeWindow to this prompter, or null if there isn't
* a NativeWindow available (w/ error sent to logcat).
*/
_getNativeWindow : function () {
let nativeWindow = null;
try {
let notifyWin = this._window.top;
let chromeWin = this._getChromeWindow(notifyWin).wrappedJSObject;
if (chromeWin.NativeWindow) {
nativeWindow = chromeWin.NativeWindow;
} else {
Cu.reportError("NativeWindow not available on window");
}
} catch (e) {
// If any errors happen, just assume no native window helper.
Cu.reportError("No NativeWindow available: " + e);
}
return nativeWindow;
},
/*
* _getLocalizedString
*
* Can be called as:
* _getLocalizedString("key1");
* _getLocalizedString("key2", ["arg1"]);
* _getLocalizedString("key3", ["arg1", "arg2"]);
* (etc)
*
* Returns the localized string for the specified key,
* formatted if required.
*
*/
_getLocalizedString : function (key, formatArgs) {
if (formatArgs)
return this._strBundle.pwmgr.formatStringFromName(
key, formatArgs, formatArgs.length);
else
return this._strBundle.pwmgr.GetStringFromName(key);
},
/*
* _sanitizeUsername
*
* Sanitizes the specified username, by stripping quotes and truncating if
* it's too long. This helps prevent an evil site from messing with the
* "save password?" prompt too much.
*/
_sanitizeUsername : function (username) {
if (username.length > 30) {
username = username.substring(0, 30);
username += this._ellipsis;
}
return username.replace(/['"]/g, "");
},
/*
* _getFormattedHostname
*
* The aURI parameter may either be a string uri, or an nsIURI instance.
*
* Returns the hostname to use in a nsILoginInfo object (for example,
* "http://example.com").
*/
_getFormattedHostname : function (aURI) {
var uri;
if (aURI instanceof Ci.nsIURI) {
uri = aURI;
} else {
uri = Services.io.newURI(aURI, null, null);
}
var scheme = uri.scheme;
var hostname = scheme + "://" + uri.host;
// If the URI explicitly specified a port, only include it when
// it's not the default. (We never want "http://foo.com:80")
let port = uri.port;
if (port != -1) {
var handler = Services.io.getProtocolHandler(scheme);
if (port != handler.defaultPort)
hostname += ":" + port;
}
return hostname;
},
return hostname;
},
}; // end of LoginManagerPrompter implementation
var component = [LoginManagerPrompter];
this.NSGetFactory = XPCOMUtils.generateNSGetFactory(component);

View File

@ -36,7 +36,6 @@ android {
androidTest {
manifest.srcFile "${topobjdir}/build/mobile/robocop/AndroidManifest.xml"
java {
srcDir "src/robocop_harness"
srcDir "src/robocop"
srcDir "src/background"
srcDir "src/browser"

View File

@ -13,7 +13,9 @@
term. -->
<!ENTITY privatebrowsingpage.title.normal1 "You are not in Private Browsing">
<!ENTITY privatebrowsingpage.description.private4 "&brandShortName; will prevent you from being tracked and won't remember any history, but downloaded files and new bookmarks will still be saved to your device.">
<!ENTITY privatebrowsingpage.description.trackingProtection "&brandShortName; blocks parts of the pages that may track your browsing activity.">
<!ENTITY privatebrowsingpage.description.privateDetails "We won't remember any history, but downloaded files and new bookmarks will still be saved to your device.">
<!-- Localization note (privatebrowsingpage.description.normal2): "Private
Browsing is capitalized in English to be consistent with our existing uses
of the term. -->

View File

@ -115,8 +115,7 @@ class MachCommands(MachCommandBase):
srcdir('app/src/androidTest/res', 'build/mobile/robocop/res')
srcdir('app/src/androidTest/assets', 'mobile/android/tests/browser/robocop/assets')
# Test code.
srcdir('app/src/robocop_harness/org/mozilla/gecko', 'build/mobile/robocop')
srcdir('app/src/robocop/org/mozilla/gecko/tests', 'mobile/android/tests/browser/robocop')
srcdir('app/src/robocop', 'mobile/android/tests/browser/robocop/src')
srcdir('app/src/background', 'mobile/android/tests/background/junit3/src')
srcdir('app/src/browser', 'mobile/android/tests/browser/junit3/src')
srcdir('app/src/javaaddons', 'mobile/android/tests/javaaddons/src')

View File

@ -1,179 +1,179 @@
[DEFAULT]
subsuite = robocop
[testGeckoProfile.java]
# [test_bug720538.java] # disabled on fig - bug 897072
[testAboutPage.java]
[src/org/mozilla/gecko/tests/testGeckoProfile.java]
# [src/org/mozilla/gecko/tests/test_bug720538.java] # disabled on fig - bug 897072
[src/org/mozilla/gecko/tests/testAboutPage.java]
# disabled on Android 2.3; bug 975187
skip-if = android_version == "10"
[testAddonManager.java]
[src/org/mozilla/gecko/tests/testAddonManager.java]
# disabled on 2.3: bug 941624, bug 1063509, bug 1073374, bug 1087221, bug 1088023, bug 1088027, bug 1090206; on 4.3, bug 1144918
skip-if = android_version == "10" || android_version == "18"
[testAddSearchEngine.java]
[src/org/mozilla/gecko/tests/testAddSearchEngine.java]
# disabled on Android 2.3, bug 979552; on 4.3, bug 1120759
skip-if = android_version == "10" || android_version == "18"
[testAdobeFlash.java]
[src/org/mozilla/gecko/tests/testAdobeFlash.java]
# disabled on 4.3, bug 1146420
skip-if = android_version == "18"
[testANRReporter.java]
[testAwesomebar.java]
[testAxisLocking.java]
[src/org/mozilla/gecko/tests/testANRReporter.java]
[src/org/mozilla/gecko/tests/testAwesomebar.java]
[src/org/mozilla/gecko/tests/testAxisLocking.java]
# disabled on 4.3, bug 1144874
skip-if = android_version == "18"
# [testBookmark.java] # see bug 915350
[testBookmarksPanel.java]
# [src/org/mozilla/gecko/tests/testBookmark.java] # see bug 915350
[src/org/mozilla/gecko/tests/testBookmarksPanel.java]
# disabled on 2.3, bug 979615; on 4.3, bug 987930
skip-if = android_version == "10" || android_version == "18"
[testBookmarkFolders.java]
[src/org/mozilla/gecko/tests/testBookmarkFolders.java]
# disabled on Android 2.3, bug 979552; on 4.3, bug 1144921
skip-if = android_version == "10" || android_version == "18"
# [testBookmarklets.java] # see bug 915350
# [testBookmarkKeyword.java] # see bug 915350
[testBrowserProvider.java]
[testBrowserSearchVisibility.java]
[testClearPrivateData.java]
# [src/org/mozilla/gecko/tests/testBookmarklets.java] # see bug 915350
# [src/org/mozilla/gecko/tests/testBookmarkKeyword.java] # see bug 915350
[src/org/mozilla/gecko/tests/testBrowserProvider.java]
[src/org/mozilla/gecko/tests/testBrowserSearchVisibility.java]
[src/org/mozilla/gecko/tests/testClearPrivateData.java]
# disabled on 2.3, bug 948591; on 4.3, bug 1000643
skip-if = android_version == "10" || android_version == "18"
[testDBUtils.java]
[testDistribution.java]
[testDoorHanger.java]
[src/org/mozilla/gecko/tests/testDBUtils.java]
[src/org/mozilla/gecko/tests/testDistribution.java]
[src/org/mozilla/gecko/tests/testDoorHanger.java]
# disabled on 2.3, bug 1085609; on 4.3, bug 1144924
skip-if = android_version == "10" || android_version == "18"
[testFilterOpenTab.java]
# [testFindInPage.java] # bug 1128287
[testFlingCorrectness.java]
[src/org/mozilla/gecko/tests/testFilterOpenTab.java]
# [src/org/mozilla/gecko/tests/testFindInPage.java] # bug 1128287
[src/org/mozilla/gecko/tests/testFlingCorrectness.java]
# disabled on 4.3, bug 1144874
skip-if = android_version == "18"
[testFormHistory.java]
[testGetUserMedia.java]
[src/org/mozilla/gecko/tests/testFormHistory.java]
[src/org/mozilla/gecko/tests/testGetUserMedia.java]
# failures across the board, bug 1092202 & bug 1144926
skip-if = true
# [testHistory.java] # see bug 915350
[testHomeBanner.java]
[testImportFromAndroid.java]
# [src/org/mozilla/gecko/tests/testHistory.java] # see bug 915350
[src/org/mozilla/gecko/tests/testHomeBanner.java]
[src/org/mozilla/gecko/tests/testImportFromAndroid.java]
# disabled on 2.3 and 4.3 bug 979552
skip-if = android_version == "10" || android_version == "18"
[testInputUrlBar.java]
[src/org/mozilla/gecko/tests/testInputUrlBar.java]
# disabled on 2.3 bug 1165511
skip-if = android_version == "10"
[testJarReader.java]
[testLinkContextMenu.java]
[src/org/mozilla/gecko/tests/testJarReader.java]
[src/org/mozilla/gecko/tests/testLinkContextMenu.java]
# disabled on 4.3, bug 1083666
skip-if = android_version == "18"
# [testHomeListsProvider.java] # see bug 952310
[testLoad.java]
[testMailToContextMenu.java]
# [testMasterPassword.java] disabled for being finicky, see bug 1033013
# [src/org/mozilla/gecko/tests/testHomeListsProvider.java] # see bug 952310
[src/org/mozilla/gecko/tests/testLoad.java]
[src/org/mozilla/gecko/tests/testMailToContextMenu.java]
# [src/org/mozilla/gecko/tests/testMasterPassword.java] disabled for being finicky, see bug 1033013
# disabled on 2.3; bug 979603
# disabled on 4.0; bug 1006242
# skip-if = android_version == "10" || android_version == "15"
[testNewTab.java]
[src/org/mozilla/gecko/tests/testNewTab.java]
# disabled on 4.3, bug 1145851
skip-if = android_version == "18"
[testPanCorrectness.java]
[src/org/mozilla/gecko/tests/testPanCorrectness.java]
# disabled on 4.3, bug 1144874
skip-if = android_version == "18"
# [testPasswordEncrypt.java] # see bug 824067
[testPasswordProvider.java]
# [testPermissions.java] # see bug 757475
[testPictureLinkContextMenu.java]
# [src/org/mozilla/gecko/tests/testPasswordEncrypt.java] # see bug 824067
[src/org/mozilla/gecko/tests/testPasswordProvider.java]
# [src/org/mozilla/gecko/tests/testPermissions.java] # see bug 757475
[src/org/mozilla/gecko/tests/testPictureLinkContextMenu.java]
# disabled on 4.3, bug 1031496
skip-if = android_version == "18"
[testPrefsObserver.java]
[testPrivateBrowsing.java]
[testPromptGridInput.java]
[src/org/mozilla/gecko/tests/testPrefsObserver.java]
[src/org/mozilla/gecko/tests/testPrivateBrowsing.java]
[src/org/mozilla/gecko/tests/testPromptGridInput.java]
# bug 1001657 for 2.3 and 4.3
skip-if = android_version == "10" || android_version == "18"
[testReadingListProvider.java]
[testSearchHistoryProvider.java]
[testSearchSuggestions.java]
[src/org/mozilla/gecko/tests/testReadingListProvider.java]
[src/org/mozilla/gecko/tests/testSearchHistoryProvider.java]
[src/org/mozilla/gecko/tests/testSearchSuggestions.java]
# disabled on 2.3, bug 907768; on 4.3, bug 1145867
skip-if = android_version == "10" || android_version == "18"
[testSessionOOMSave.java]
[src/org/mozilla/gecko/tests/testSessionOOMSave.java]
# disabled on 2.3, bug 945395; on 4.3, bug 1144888
skip-if = android_version == "10" || android_version == "18"
[testSessionOOMRestore.java]
[src/org/mozilla/gecko/tests/testSessionOOMRestore.java]
# disabled on Android 2.3, bug 979600; on 4.3, bug 1145879
skip-if = android_version == "10" || android_version == "18"
[testSettingsMenuItems.java]
[src/org/mozilla/gecko/tests/testSettingsMenuItems.java]
# disabled on Android 2.3, bug 979552; on 4.3, bug 1144898
skip-if = android_version == "10" || android_version == "18"
# [testShareLink.java] # see bug 915897
[testSystemPages.java]
# [src/org/mozilla/gecko/tests/testShareLink.java] # see bug 915897
[src/org/mozilla/gecko/tests/testSystemPages.java]
# disabled on 2.3, bug 979603; on 4.3, bug 1142811
skip-if = android_version == "10" || android_version == "18"
# [testThumbnails.java] # see bug 813107
[testTitleBar.java]
# [src/org/mozilla/gecko/tests/testThumbnails.java] # see bug 813107
[src/org/mozilla/gecko/tests/testTitleBar.java]
# disabled on Android 2.3, bug 979552; on 4.3, bug 1145881
skip-if = android_version == "10" || android_version == "18"
# [testVkbOverlap.java] # see bug 907274
# [src/org/mozilla/gecko/tests/testVkbOverlap.java] # see bug 907274
# Using JavascriptTest
# (If your test can be written entirely in Javascript, consider writing
# it as a chrome test instead. See mobile/android/tests/browser/chrome.)
[testBrowserDiscovery.java]
[src/org/mozilla/gecko/tests/testBrowserDiscovery.java]
# disabled on 4.3, bug 1158384
skip-if = android_version == "18"
[testFilePicker.java]
[testHistoryService.java]
[src/org/mozilla/gecko/tests/testFilePicker.java]
[src/org/mozilla/gecko/tests/testHistoryService.java]
# disabled on 4.3, bug 1116036
skip-if = android_version == "18"
# [testMozPay.java] # see bug 945675
[testOrderedBroadcast.java]
[testOSLocale.java]
# [src/org/mozilla/gecko/tests/testMozPay.java] # see bug 945675
[src/org/mozilla/gecko/tests/testOrderedBroadcast.java]
[src/org/mozilla/gecko/tests/testOSLocale.java]
# disabled on 2.3 and 4.3: Bug 1124494
skip-if = android_version == "10" || android_version == "18"
[testReadingListCache.java]
[testRestrictions.java]
[testSnackbarAPI.java]
[testTrackingProtection.java]
[src/org/mozilla/gecko/tests/testReadingListCache.java]
[src/org/mozilla/gecko/tests/testRestrictions.java]
[src/org/mozilla/gecko/tests/testSnackbarAPI.java]
[src/org/mozilla/gecko/tests/testTrackingProtection.java]
# disabled on 4.3, bug 1158363
skip-if = android_version == "18"
[testUITelemetry.java]
[testVideoControls.java]
[src/org/mozilla/gecko/tests/testUITelemetry.java]
[src/org/mozilla/gecko/tests/testVideoControls.java]
# disabled on Android 2.3 due to video playback issues, bug 1088038; on 4.3, bug 1098532
skip-if = android_version == "10" || android_version == "18"
# Used for Talos, please don't use in mochitest
#[testCheck2.java]
#[testCheck3.java] # and the autophone version
#[src/org/mozilla/gecko/tests/testCheck2.java]
#[src/org/mozilla/gecko/tests/testCheck3.java] # and the autophone version
# Using UITest
#[testAboutHomePageNavigation.java] # see bug 947550, bug 979038 and bug 977952
[testAboutHomeVisibility.java]
#[src/org/mozilla/gecko/tests/testAboutHomePageNavigation.java] # see bug 947550, bug 979038 and bug 977952
[src/org/mozilla/gecko/tests/testAboutHomeVisibility.java]
# disabled on Android 2.3; bug 946656
skip-if = android_version == "10"
[testAppMenuPathways.java]
[src/org/mozilla/gecko/tests/testAppMenuPathways.java]
# disabled on 4.3, bug 1158005
skip-if = android_version == "18"
[testBackButtonInEditMode.java]
[testEventDispatcher.java]
[testGeckoRequest.java]
[testInputConnection.java]
[src/org/mozilla/gecko/tests/testBackButtonInEditMode.java]
[src/org/mozilla/gecko/tests/testEventDispatcher.java]
[src/org/mozilla/gecko/tests/testGeckoRequest.java]
[src/org/mozilla/gecko/tests/testInputConnection.java]
# disabled on Android 2.3, 4.3; bug 1025968
skip-if = android_version == "10" || android_version == "18"
[testJavascriptBridge.java]
[testNativeCrypto.java]
[testReaderModeTitle.java]
[testSessionHistory.java]
[src/org/mozilla/gecko/tests/testJavascriptBridge.java]
[src/org/mozilla/gecko/tests/testNativeCrypto.java]
[src/org/mozilla/gecko/tests/testReaderModeTitle.java]
[src/org/mozilla/gecko/tests/testSessionHistory.java]
# disabled on Android 4.3, bug 1144879
skip-if = android_version == "18"
[testStateWhileLoading.java]
[src/org/mozilla/gecko/tests/testStateWhileLoading.java]
[testSelectionCarets.java]
[src/org/mozilla/gecko/tests/testSelectionCarets.java]
# testSelectionHandler disabled on Android 2.3 by trailing skip-if, due to bug 980074
# also disabled on Android 4.3, bug 1144882
[testSelectionHandler.java]
[src/org/mozilla/gecko/tests/testSelectionHandler.java]
skip-if = android_version == "10" || android_version == "18"
# testInputSelections disabled on Android 2.3 by trailing skip-if, due to bug 980074
[testInputSelections.java]
[src/org/mozilla/gecko/tests/testInputSelections.java]
skip-if = android_version == "10"
# testTextareaSelections disabled on Android 2.3 by trailing skip-if, due to bug 980074
[testTextareaSelections.java]
[src/org/mozilla/gecko/tests/testTextareaSelections.java]
skip-if = android_version == "10"
# testStumblerSetting disabled on Android 4.3, bug 1145846
[testStumblerSetting.java]
[src/org/mozilla/gecko/tests/testStumblerSetting.java]
skip-if = android_version == "10" || android_version == "18"

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