This commit is contained in:
Wes Kocher 2014-12-12 17:18:42 -08:00
Родитель e4ad7391ee 3c36af6c35
Коммит 58cb48da88
52 изменённых файлов: 811 добавлений и 311 удалений

Просмотреть файл

@ -5,21 +5,58 @@
const { Cc, Ci, Cu } = require("chrome");
const { SimulatorProcess } = require("./simulator-process");
const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
const { Simulator } = Cu.import("resource://gre/modules/devtools/Simulator.jsm");
const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
const { Simulator } = Cu.import("resource://gre/modules/devtools/Simulator.jsm");
const { SimulatorProcess } = require("./simulator-process");
const Runtime = require("sdk/system/runtime");
const URL = require("sdk/url");
const ROOT_URI = require("addon").uri;
const PROFILE_URL = ROOT_URI + "profile/";
const BIN_URL = ROOT_URI + "b2g/";
let process;
function launch({ port }) {
// Close already opened simulation
function launch(options) {
// Close already opened simulation.
if (process) {
return close().then(launch.bind(null, { port: port }));
return close().then(launch.bind(null, options));
}
process = SimulatorProcess();
process.remoteDebuggerPort = port;
// Compute B2G runtime path.
let path;
try {
let pref = "extensions." + require("addon").id + ".customRuntime";
path = Services.prefs.getComplexValue(pref, Ci.nsIFile);
} catch(e) {}
if (!path) {
let executables = {
WINNT: "b2g-bin.exe",
Darwin: "B2G.app/Contents/MacOS/b2g-bin",
Linux: "b2g-bin",
};
path = URL.toFilename(BIN_URL);
path += Runtime.OS == "WINNT" ? "\\" : "/";
path += executables[Runtime.OS];
}
options.runtimePath = path;
console.log("simulator path:", options.runtimePath);
// Compute Gaia profile path.
if (!options.profilePath) {
let gaiaProfile;
try {
let pref = "extensions." + require("addon").id + ".gaiaProfile";
gaiaProfile = Services.prefs.getComplexValue(pref, Ci.nsIFile).path;
} catch(e) {}
options.profilePath = gaiaProfile || URL.toFilename(PROFILE_URL);
}
process = new SimulatorProcess(options);
process.run();
return promise.resolve();

Просмотреть файл

@ -9,18 +9,12 @@ const { Cc, Ci, Cu, ChromeWorker } = require("chrome");
Cu.import("resource://gre/modules/Services.jsm");
const { EventTarget } = require("sdk/event/target");
const { emit, off } = require("sdk/event/core");
const { Class } = require("sdk/core/heritage");
const Environment = require("sdk/system/environment").env;
const Runtime = require("sdk/system/runtime");
const URL = require("sdk/url");
const Subprocess = require("sdk/system/child_process/subprocess");
const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
const { EventEmitter } = Cu.import("resource://gre/modules/devtools/event-emitter.js", {});
const ROOT_URI = require("addon").uri;
const PROFILE_URL = ROOT_URI + "profile/";
const BIN_URL = ROOT_URI + "b2g/";
// Log subprocess error and debug messages to the console. This logs messages
// for all consumers of the API. We trim the messages because they sometimes
@ -33,14 +27,15 @@ Subprocess.registerDebugHandler(
function(s) console.debug("subprocess: " + s.trim())
);
exports.SimulatorProcess = Class({
extends: EventTarget,
initialize: function initialize(options) {
EventTarget.prototype.initialize.call(this, options);
function SimulatorProcess(options) {
this.options = options;
this.on("stdout", function onStdout(data) console.log(data.trim()));
this.on("stderr", function onStderr(data) console.error(data.trim()));
},
EventEmitter.decorate(this);
this.on("stdout", data => { console.log(data.trim()) });
this.on("stderr", data => { console.error(data.trim()) });
}
SimulatorProcess.prototype = {
// check if b2g is running
get isRunning() !!this.process,
@ -77,7 +72,7 @@ exports.SimulatorProcess = Class({
let environment;
if (Runtime.OS == "Linux") {
environment = ["TMPDIR=" + Services.dirsvc.get("TmpD",Ci.nsIFile).path];
environment = ["TMPDIR=" + Services.dirsvc.get("TmpD", Ci.nsIFile).path];
if ("DISPLAY" in Environment) {
environment.push("DISPLAY=" + Environment.DISPLAY);
}
@ -90,21 +85,21 @@ exports.SimulatorProcess = Class({
environment: environment,
// emit stdout event
stdout: (function(data) {
emit(this, "stdout", data);
}).bind(this),
stdout: data => {
this.emit("stdout", data);
},
// emit stderr event
stderr: (function(data) {
emit(this, "stderr", data);
}).bind(this),
stderr: data => {
this.emit("stderr", data);
},
// on b2g instance exit, reset tracked process, remoteDebuggerPort and
// on b2g instance exit, reset tracked process, remote debugger port and
// shuttingDown flag, then finally emit an exit event
done: (function(result) {
console.log(this.b2gFilename + " terminated with " + result.exitCode);
console.log("B2G terminated with " + result.exitCode);
this.process = null;
emit(this, "exit", result.exitCode);
this.emit("exit", result.exitCode);
}).bind(this)
});
},
@ -119,7 +114,7 @@ exports.SimulatorProcess = Class({
});
if (!this.shuttingDown) {
this.shuttingDown = true;
emit(this, "kill", null);
this.emit("kill", null);
this.process.kill();
}
return deferred.promise;
@ -128,42 +123,14 @@ exports.SimulatorProcess = Class({
}
},
// compute current b2g filename
get b2gFilename() {
return this._executable ? this._executableFilename : "B2G";
},
// compute current b2g file handle
get b2gExecutable() {
if (this._executable) {
return this._executable;
}
let customRuntime;
try {
let pref = "extensions." + require("addon").id + ".customRuntime";
customRuntime = Services.prefs.getComplexValue(pref, Ci.nsIFile);
} catch(e) {}
if (customRuntime) {
this._executable = customRuntime;
this._executableFilename = "Custom runtime";
return this._executable;
}
let bin = URL.toFilename(BIN_URL);
let executables = {
WINNT: "b2g-bin.exe",
Darwin: "B2G.app/Contents/MacOS/b2g-bin",
Linux: "b2g-bin",
};
let path = bin;
path += Runtime.OS == "WINNT" ? "\\" : "/";
path += executables[Runtime.OS];
console.log("simulator path: " + path);
let executable = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
executable.initWithPath(path);
executable.initWithPath(this.options.runtimePath);
if (!executable.exists()) {
// B2G binaries not found
@ -171,7 +138,6 @@ exports.SimulatorProcess = Class({
}
this._executable = executable;
this._executableFilename = "b2g-bin";
return executable;
},
@ -180,23 +146,18 @@ exports.SimulatorProcess = Class({
get b2gArguments() {
let args = [];
let gaiaProfile;
try {
let pref = "extensions." + require("addon").id + ".gaiaProfile";
gaiaProfile = Services.prefs.getComplexValue(pref, Ci.nsIFile).path;
} catch(e) {}
let profile = gaiaProfile || URL.toFilename(PROFILE_URL);
let profile = this.options.profilePath;
args.push("-profile", profile);
console.log("profile", profile);
// NOTE: push dbgport option on the b2g-desktop commandline
args.push("-start-debugger-server", "" + this.remoteDebuggerPort);
args.push("-start-debugger-server", "" + this.options.port);
// Ignore eventual zombie instances of b2g that are left over
args.push("-no-remote");
return args;
},
});
};
exports.SimulatorProcess = SimulatorProcess;

Просмотреть файл

@ -7,7 +7,7 @@ var gSafeBrowsing = {
setReportPhishingMenu: function() {
// A phishing page will have a specific about:blocked content documentURI
var uri = getBrowser().currentURI;
var uri = gBrowser.currentURI;
var isPhishingPage = uri && uri.spec.startsWith("about:blocked?e=phishingBlocked");
// Show/hide the appropriate menu item.

Просмотреть файл

@ -1058,7 +1058,7 @@
const kXULNS =
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
let addEngines = getBrowser().mCurrentBrowser.engines;
let addEngines = gBrowser.mCurrentBrowser.engines;
if (addEngines && addEngines.length > 0) {
const kBundleURI = "chrome://browser/locale/search.properties";
let bundle = Services.strings.createBundle(kBundleURI);

Просмотреть файл

@ -452,7 +452,7 @@ WebContentConverterRegistrar.prototype = {
}
};
var browserElement = this._getBrowserForContentWindow(browserWindow, aContentWindow);
var notificationBox = browserWindow.getBrowser().getNotificationBox(browserElement);
var notificationBox = browserWindow.gBrowser.getNotificationBox(browserElement);
notificationBox.appendNotification(message,
notificationValue,
notificationIcon,
@ -481,7 +481,7 @@ WebContentConverterRegistrar.prototype = {
var browserWindow = this._getBrowserWindowForContentWindow(aContentWindow);
var browserElement = this._getBrowserForContentWindow(browserWindow, aContentWindow);
var notificationBox = browserWindow.getBrowser().getNotificationBox(browserElement);
var notificationBox = browserWindow.gBrowser.getNotificationBox(browserElement);
this._appendFeedReaderNotification(uri, aTitle, notificationBox);
}
else
@ -516,7 +516,7 @@ WebContentConverterRegistrar.prototype = {
function WCCR__getBrowserForContentWindow(aBrowserWindow, aContentWindow) {
// This depends on pseudo APIs of browser.js and tabbrowser.xml
aContentWindow = aContentWindow.top;
var browsers = aBrowserWindow.getBrowser().browsers;
var browsers = aBrowserWindow.gBrowser.browsers;
for (var i = 0; i < browsers.length; ++i) {
if (browsers[i].contentWindow == aContentWindow)
return browsers[i];

Просмотреть файл

@ -135,7 +135,7 @@ let LoopRoomsInternal = {
get participantsCount() {
let count = 0;
for (let room of this.rooms.values()) {
if (!("participants" in room)) {
if (room.deleted || !("participants" in room)) {
continue;
}
count += room.participants.length;

Просмотреть файл

@ -1073,11 +1073,13 @@ this.MozLoopService = {
// The Loop toolbar button should change icon when the room participant count
// changes from 0 to something.
const onRoomsChange = () => {
MozLoopServiceInternal.notifyStatusChanged();
const onRoomsChange = (e) => {
// Pass the event name as notification reason for better logging.
MozLoopServiceInternal.notifyStatusChanged("room-" + e);
};
LoopRooms.on("add", onRoomsChange);
LoopRooms.on("update", onRoomsChange);
LoopRooms.on("delete", onRoomsChange);
LoopRooms.on("joined", (e, room, participant) => {
// Don't alert if we're in the doNotDisturb mode, or the participant
// is the owner - the content code deals with the rest of the sounds.

Просмотреть файл

@ -319,7 +319,8 @@ loop.panel = (function(_, mozL10n) {
onClick: this.handleClickAccountEntry,
icon: "account",
displayed: this._isSignedIn()}),
SettingsDropdownEntry({label: mozL10n.get("tour_label"),
SettingsDropdownEntry({icon: "tour",
label: mozL10n.get("tour_label"),
onClick: this.openGettingStartedTour}),
SettingsDropdownEntry({label: this._isSignedIn() ?
mozL10n.get("settings_menu_item_signout") :
@ -695,7 +696,7 @@ loop.panel = (function(_, mozL10n) {
* Room list.
*/
var RoomList = React.createClass({displayName: 'RoomList',
mixins: [Backbone.Events],
mixins: [Backbone.Events, sharedMixins.WindowCloseMixin],
propTypes: {
store: React.PropTypes.instanceOf(loop.store.RoomStore).isRequired,
@ -737,6 +738,8 @@ loop.panel = (function(_, mozL10n) {
},
handleCreateButtonClick: function() {
this.closeWindow();
this.props.dispatcher.dispatch(new sharedActions.CreateRoom({
nameTemplate: mozL10n.get("rooms_default_room_name_template"),
roomOwner: this.props.userDisplayName

Просмотреть файл

@ -319,7 +319,8 @@ loop.panel = (function(_, mozL10n) {
onClick={this.handleClickAccountEntry}
icon="account"
displayed={this._isSignedIn()} />
<SettingsDropdownEntry label={mozL10n.get("tour_label")}
<SettingsDropdownEntry icon="tour"
label={mozL10n.get("tour_label")}
onClick={this.openGettingStartedTour} />
<SettingsDropdownEntry label={this._isSignedIn() ?
mozL10n.get("settings_menu_item_signout") :
@ -695,7 +696,7 @@ loop.panel = (function(_, mozL10n) {
* Room list.
*/
var RoomList = React.createClass({
mixins: [Backbone.Events],
mixins: [Backbone.Events, sharedMixins.WindowCloseMixin],
propTypes: {
store: React.PropTypes.instanceOf(loop.store.RoomStore).isRequired,
@ -737,6 +738,8 @@ loop.panel = (function(_, mozL10n) {
},
handleCreateButtonClick: function() {
this.closeWindow();
this.props.dispatcher.dispatch(new sharedActions.CreateRoom({
nameTemplate: mozL10n.get("rooms_default_room_name_template"),
roomOwner: this.props.userDisplayName

Просмотреть файл

@ -512,6 +512,36 @@
background-position: center;
}
/*
* Ensure that the publisher (i.e. local) video is never cropped, so that it's
* not possible for someone to be presented with a picture that displays
* (for example) a person from the neck up, even though the camera is capturing
* and transmitting a picture of that person from the waist up.
*
* The !importants are necessary to override the SDK attempts to avoid
* letterboxing entirely.
*
* If we could easily use test video streams with the SDK (eg if initPublisher
* supported something like a "testMediaToStreamURI" parameter that it would
* use to source the stream rather than the output of gUM, it wouldn't be too
* hard to generate a video with a 1 pixel border at the edges that one could
* at least visually see wasn't being cropped.
*
* Another less ugly possibility would be to work with Ted Mielczarek to use
* the fake camera drivers he has for Linux.
*/
.room-conversation .OT_publisher .OT_video-container {
height: 100% !important;
width: 100% !important;
top: 0 !important;
left: 0 !important;
background-color: transparent; /* avoid visually obvious letterboxing */
}
.room-conversation .OT_publisher .OT_video-container video {
background-color: transparent; /* avoid visually obvious letterboxing */
}
.fx-embedded .media.nested {
min-height: 200px;
}

Просмотреть файл

@ -680,6 +680,10 @@ body[dir=rtl] .generate-url-spinner {
background: transparent url(../img/svg/glyph-settings-16x16.svg) no-repeat center center;
}
.settings-menu .icon-tour {
background: transparent url("../img/icons-16x16.svg#tour") no-repeat center center;
}
.settings-menu .icon-account {
background: transparent url(../img/svg/glyph-account-16x16.svg) no-repeat center center;
}

Просмотреть файл

@ -122,6 +122,10 @@ use[id$="-red"] {
<polygon fill="#FFFFFF" points="2.08,11.52 2.08,4 8,4 8,2.24 0.32,2.24 0.32,13.28 8,13.28 8,11.52"/>
<polygon fill="#FFFFFF" points="15.66816,7.77344 9.6,2.27456 9.6,5.6 3.68,5.6 3.68,9.92 9.6,9.92 9.6,13.27232"/>
</g>
<path id="tour-shape" fill="#5A5A5A" d="M8,0C4.831,0,2.262,2.674,2.262,5.972c0,1.393,1.023,3.398,2.206,5.249l0.571,0.866C6.504,14.245,8,16,8,16
s1.496-1.755,2.961-3.912l0.571-0.866c1.182-1.852,2.206-3.856,2.206-5.249C13.738,2.674,11.169,0,8,0z M8,7.645
c-0.603,0-1.146-0.262-1.534-0.681C6.098,6.566,5.87,6.025,5.87,5.428c0-1.224,0.954-2.217,2.13-2.217s2.13,0.992,2.13,2.217
C10.13,6.653,9.177,7.645,8,7.645z"/>
</defs>
<use id="audio" xlink:href="#audio-shape"/>
<use id="audio-hover" xlink:href="#audio-shape"/>
@ -158,4 +162,5 @@ use[id$="-red"] {
<use id="video" xlink:href="#video-shape"/>
<use id="video-hover" xlink:href="#video-shape"/>
<use id="video-active" xlink:href="#video-shape"/>
<use id="tour" xlink:href="#tour-shape"/>
</svg>

До

Ширина:  |  Высота:  |  Размер: 11 KiB

После

Ширина:  |  Высота:  |  Размер: 11 KiB

Просмотреть файл

@ -897,7 +897,7 @@ describe("loop.panel", function() {
});
describe("loop.panel.RoomList", function() {
var roomStore, dispatcher, fakeEmail;
var roomStore, dispatcher, fakeEmail, dispatch;
beforeEach(function() {
fakeEmail = "fakeEmail@example.com";
@ -911,6 +911,7 @@ describe("loop.panel", function() {
rooms: [],
error: undefined
});
dispatch = sandbox.stub(dispatcher, "dispatch");
});
function createTestComponent() {
@ -922,8 +923,6 @@ describe("loop.panel", function() {
}
it("should dispatch a GetAllRooms action on mount", function() {
var dispatch = sandbox.stub(dispatcher, "dispatch");
createTestComponent();
sinon.assert.calledOnce(dispatch);
@ -934,7 +933,6 @@ describe("loop.panel", function() {
"conversation button",
function() {
navigator.mozLoop.userProfile = {email: fakeEmail};
var dispatch = sandbox.stub(dispatcher, "dispatch");
var view = createTestComponent();
TestUtils.Simulate.click(view.getDOMNode().querySelector("button"));
@ -945,9 +943,17 @@ describe("loop.panel", function() {
}));
});
it("should close the panel when 'Start a Conversation' is clicked",
function() {
var view = createTestComponent();
TestUtils.Simulate.click(view.getDOMNode().querySelector("button"));
sinon.assert.calledOnce(fakeWindow.close);
});
it("should disable the create button when a creation operation is ongoing",
function() {
var dispatch = sandbox.stub(dispatcher, "dispatch");
roomStore.setStoreState({pendingCreation: true});
var view = createTestComponent();
@ -958,7 +964,6 @@ describe("loop.panel", function() {
it("should disable the create button when a list retrieval operation is pending",
function() {
var dispatch = sandbox.stub(dispatcher, "dispatch");
roomStore.setStoreState({pendingInitialRetrieval: true});
var view = createTestComponent();

Просмотреть файл

@ -79,6 +79,15 @@ var gAdvancedPane = {
gAdvancedPane.showCertificates);
setEventListener("viewSecurityDevicesButton", "command",
gAdvancedPane.showSecurityDevices);
#ifdef MOZ_WIDGET_GTK
// GTK tabbox' allow the scroll wheel to change the selected tab,
// but we don't want this behavior for the in-content preferences.
let tabsElement = document.getElementById("tabsElement");
tabsElement.addEventListener("DOMMouseScroll", event => {
event.stopPropagation();
}, true);
#endif
},
/**

Просмотреть файл

@ -349,7 +349,7 @@
popup.removeChild(items[i]);
}
var addengines = getBrowser().mCurrentBrowser.engines;
var addengines = gBrowser.mCurrentBrowser.engines;
if (addengines && addengines.length > 0) {
const kXULNS =
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

Просмотреть файл

@ -42,7 +42,7 @@ add_task(function* () {
yield inspector.once("inspector-updated");
info("Inspector updated, performing checks.");
yield assertNodeSelectedAndPanelsUpdated("#deleteChildren", "ul#deleteChildren");
yield assertNodeSelectedAndPanelsUpdated("#selectedAfterDelete", "li#selectedAfterDelete");
}
function* testAutomaticallyDeleteSelectedNode() {

Просмотреть файл

@ -7,6 +7,7 @@
<body>
<ul id="deleteChildren">
<li id="deleteManually">Delete me via the inspector</li>
<li id="selectedAfterDelete">This node is selected after manual delete</li>
<li id="deleteAutomatically">Delete me via javascript</li>
</ul>
<iframe id="deleteIframe" src="data:text/html,%3C!DOCTYPE%20html%3E%3Chtml%20lang%3D%22en%22%3E%3Cbody%3E%3Cp%20id%3D%22deleteInIframe%22%3EDelete my container iframe%3C%2Fp%3E%3C%2Fbody%3E%3C%2Fhtml%3E"></iframe>

Просмотреть файл

@ -426,9 +426,11 @@ MarkupView.prototype = {
}
break;
case Ci.nsIDOMKeyEvent.DOM_VK_DELETE:
case Ci.nsIDOMKeyEvent.DOM_VK_BACK_SPACE:
this.deleteNode(this._selectedContainer.node);
break;
case Ci.nsIDOMKeyEvent.DOM_VK_BACK_SPACE:
this.deleteNode(this._selectedContainer.node, true);
break;
case Ci.nsIDOMKeyEvent.DOM_VK_HOME:
let rootContainer = this.getContainer(this._rootNode);
this.navigate(rootContainer.children.firstChild.container);
@ -508,8 +510,12 @@ MarkupView.prototype = {
/**
* Delete a node from the DOM.
* This is an undoable action.
*
* @param {NodeFront} aNode The node to remove.
* @param {boolean} moveBackward If set to true, focus the previous sibling,
* otherwise the next one.
*/
deleteNode: function(aNode) {
deleteNode: function(aNode, moveBackward) {
if (aNode.isDocumentElement ||
aNode.nodeType == Ci.nsIDOMNode.DOCUMENT_TYPE_NODE ||
aNode.isAnonymous) {
@ -524,8 +530,15 @@ MarkupView.prototype = {
let nextSibling = null;
this.undo.do(() => {
this.walker.removeNode(aNode).then(siblings => {
let focusNode = siblings.previousSibling || parent;
nextSibling = siblings.nextSibling;
let focusNode = moveBackward ? siblings.previousSibling : nextSibling;
// If we can't move as the user wants, we move to the other direction.
// If there is no sibling elements anymore, move to the parent node.
if (!focusNode) {
focusNode = nextSibling || siblings.previousSibling || parent;
}
if (container.selected) {
this.navigate(this.getContainer(focusNode));
}

Просмотреть файл

@ -10,13 +10,13 @@
const TEST_URL = "data:text/html,<div id='parent'><div id='first'></div><div id='second'></div><div id='third'></div></div>";
function* checkDeleteAndSelection(inspector, nodeSelector, focusedNodeSelector) {
function* checkDeleteAndSelection(inspector, key, nodeSelector, focusedNodeSelector) {
yield selectNode(nodeSelector, inspector);
yield clickContainer(nodeSelector, inspector);
info("Deleting the element \"" + nodeSelector + "\" with the keyboard");
info(`Deleting the element "${nodeSelector}" using the ${key} key`);
let mutated = inspector.once("markupmutation");
EventUtils.sendKey("delete", inspector.panelWin);
EventUtils.sendKey(key, inspector.panelWin);
yield Promise.all([mutated, inspector.once("inspector-updated")]);
@ -36,9 +36,24 @@ let test = asyncTest(function*() {
info("Selecting the test node by clicking on it to make sure it receives focus");
yield checkDeleteAndSelection(inspector, "#first", "#parent");
yield checkDeleteAndSelection(inspector, "#second", "#first");
yield checkDeleteAndSelection(inspector, "#third", "#second");
yield checkDeleteAndSelection(inspector, "delete", "#first", "#second");
yield checkDeleteAndSelection(inspector, "delete", "#second", "#third");
yield checkDeleteAndSelection(inspector, "delete", "#third", "#second");
yield checkDeleteAndSelection(inspector, "back_space", "#first", "#second");
yield checkDeleteAndSelection(inspector, "back_space", "#second", "#first");
yield checkDeleteAndSelection(inspector, "back_space", "#third", "#second");
// Removing the siblings of #first.
let mutated = inspector.once("markupmutation");
for (let node of content.document.querySelectorAll("#second, #third")) {
node.remove();
}
yield mutated;
// Testing with an only child.
info("testing with an only child");
yield checkDeleteAndSelection(inspector, "delete", "#first", "#parent");
yield checkDeleteAndSelection(inspector, "back_space", "#first", "#parent");
yield inspector.once("inspector-updated");
});

Просмотреть файл

@ -55,6 +55,10 @@ const EVENTS = {
// Emitted by the CallTreeView when a call tree has been rendered
CALL_TREE_RENDERED: "Performance:UI:CallTreeRendered",
// When a source is shown in the JavaScript Debugger at a specific location.
SOURCE_SHOWN_IN_JS_DEBUGGER: "Performance:UI:SourceShownInJsDebugger",
SOURCE_NOT_FOUND_IN_JS_DEBUGGER: "Performance:UI:SourceNotFoundInJsDebugger",
// Emitted by the WaterfallView when it has been rendered
WATERFALL_RENDERED: "Performance:UI:WaterfallRendered"
};

Просмотреть файл

@ -35,6 +35,8 @@ support-files =
[browser_perf-overview-selection.js]
[browser_perf-details.js]
[browser_perf-jump-to-debugger-01.js]
[browser_perf-jump-to-debugger-02.js]
[browser_perf-details-calltree-render-01.js]
[browser_perf-details-calltree-render-02.js]
[browser_perf-details-waterfall-render-01.js]

Просмотреть файл

@ -0,0 +1,27 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if the performance tool can jump to the debugger.
*/
function spawnTest () {
let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL);
let { viewSourceInDebugger } = panel.panelWin;
yield viewSourceInDebugger(SIMPLE_URL, 14);
let debuggerPanel = toolbox.getPanel("jsdebugger");
ok(debuggerPanel, "The debugger panel was opened.");
let { DebuggerView } = debuggerPanel.panelWin;
let Sources = DebuggerView.Sources;
is(Sources.selectedValue, getSourceActor(Sources, SIMPLE_URL),
"The correct source is shown in the debugger.");
is(DebuggerView.editor.getCursor().line + 1, 14,
"The correct line is highlighted in the debugger's source editor.");
yield teardown(panel);
finish();
}

Просмотреть файл

@ -0,0 +1,41 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if the performance tool can jump to the debugger, when the source was already
* loaded in that tool.
*/
function spawnTest() {
let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL, "jsdebugger");
let debuggerWin = panel.panelWin;
let debuggerEvents = debuggerWin.EVENTS;
let { DebuggerView } = debuggerWin;
let Sources = DebuggerView.Sources;
yield debuggerWin.once(debuggerEvents.SOURCE_SHOWN);
ok("A source was shown in the debugger.");
is(Sources.selectedValue, getSourceActor(Sources, SIMPLE_URL),
"The correct source is initially shown in the debugger.");
is(DebuggerView.editor.getCursor().line, 0,
"The correct line is initially highlighted in the debugger's source editor.");
yield toolbox.selectTool("performance");
let perfPanel = toolbox.getCurrentPanel();
let perfWin = perfPanel.panelWin;
let { viewSourceInDebugger } = perfWin;
yield viewSourceInDebugger(SIMPLE_URL, 14);
panel = toolbox.getPanel("jsdebugger");
ok(panel, "The debugger panel was reselected.");
is(DebuggerView.Sources.selectedValue, getSourceActor(Sources, SIMPLE_URL),
"The correct source is still shown in the debugger.");
is(DebuggerView.editor.getCursor().line + 1, 14,
"The correct line is now highlighted in the debugger's source editor.");
yield teardown(perfPanel);
finish();
}

Просмотреть файл

@ -158,7 +158,7 @@ function initBackend(aUrl) {
});
}
function initPerformance(aUrl) {
function initPerformance(aUrl, selectedTool="performance") {
info("Initializing a performance pane.");
return Task.spawn(function*() {
@ -168,7 +168,7 @@ function initPerformance(aUrl) {
yield target.makeRemote();
Services.prefs.setBoolPref("devtools.performance_dev.enabled", true);
let toolbox = yield gDevTools.showToolbox(target, "performance");
let toolbox = yield gDevTools.showToolbox(target, selectedTool);
let panel = toolbox.getCurrentPanel();
return { target, panel, toolbox };
});
@ -317,3 +317,8 @@ function dropSelection(graph) {
graph.dropSelection();
graph.emit("mouseup");
}
function getSourceActor(aSources, aURL) {
let item = aSources.getItemForAttachment(a => a.source.url === aURL);
return item && item.value;
}

Просмотреть файл

@ -14,6 +14,7 @@ let CallTreeView = {
this.el = $(".call-tree");
this._graphEl = $(".call-tree-cells-container");
this._onRangeChange = this._onRangeChange.bind(this);
this._onLink = this._onLink.bind(this);
this._stop = this._stop.bind(this);
OverviewView.on(EVENTS.OVERVIEW_RANGE_SELECTED, this._onRangeChange);
@ -58,6 +59,13 @@ let CallTreeView = {
this.render(this._profilerData, beginAt, endAt);
},
_onLink: function (_, treeItem) {
let { url, line } = treeItem.frame.getInfo();
viewSourceInDebugger(url, line).then(
() => this.emit(EVENTS.SOURCE_SHOWN_IN_JS_DEBUGGER),
() => this.emit(EVENTS.SOURCE_NOT_FOUND_IN_JS_DEBUGGER));
},
/**
* Called when the recording is stopped and prepares data to
* populate the call tree.
@ -85,6 +93,9 @@ let CallTreeView = {
inverted: options.inverted
});
// Bind events
root.on("link", this._onLink);
// Clear out other graphs
this._graphEl.innerHTML = "";
root.attachTo(this._graphEl);
@ -98,3 +109,30 @@ let CallTreeView = {
* Convenient way of emitting events from the view.
*/
EventEmitter.decorate(CallTreeView);
/**
* Opens/selects the debugger in this toolbox and jumps to the specified
* file name and line number.
* @param string url
* @param number line
*/
let viewSourceInDebugger = Task.async(function *(url, line) {
// If the Debugger was already open, switch to it and try to show the
// source immediately. Otherwise, initialize it and wait for the sources
// to be added first.
let debuggerAlreadyOpen = gToolbox.getPanel("jsdebugger");
let { panelWin: dbg } = yield gToolbox.selectTool("jsdebugger");
if (!debuggerAlreadyOpen) {
yield new Promise((resolve) => dbg.once(dbg.EVENTS.SOURCES_ADDED, () => resolve(dbg)));
}
let { DebuggerView } = dbg;
let item = DebuggerView.Sources.getItemForAttachment(a => a.source.url === url);
if (item) {
return DebuggerView.setEditorLocation(item.attachment.source.actor, line, { noDebug: true });
}
return Promise.reject();
});

Просмотреть файл

@ -217,7 +217,7 @@ let CommandUtils = {
},
get window() {
return this.chromeWindow.getBrowser().selectedTab.linkedBrowser.contentWindow;
return this.chromeWindow.gBrowser.selectedTab.linkedBrowser.contentWindow;
},
get document() {
@ -296,7 +296,7 @@ DeveloperToolbar.prototype.NOTIFICATIONS = NOTIFICATIONS;
*/
Object.defineProperty(DeveloperToolbar.prototype, "target", {
get: function() {
return TargetFactory.forTab(this._chromeWindow.getBrowser().selectedTab);
return TargetFactory.forTab(this._chromeWindow.gBrowser.selectedTab);
},
enumerable: true
});
@ -410,7 +410,7 @@ DeveloperToolbar.prototype.show = function(focus) {
return gcli.load().then(() => {
this.display = gcli.createDisplay({
contentDocument: this._chromeWindow.getBrowser().contentDocument,
contentDocument: this._chromeWindow.gBrowser.contentDocument,
chromeDocument: this._doc,
chromeWindow: this._chromeWindow,
hintElement: this.tooltipPanel.hintElement,
@ -433,7 +433,7 @@ DeveloperToolbar.prototype.show = function(focus) {
this.tooltipPanel);
this.display.onOutput.add(this.outputPanel._outputChanged, this.outputPanel);
let tabbrowser = this._chromeWindow.getBrowser();
let tabbrowser = this._chromeWindow.gBrowser;
tabbrowser.tabContainer.addEventListener("TabSelect", this, false);
tabbrowser.tabContainer.addEventListener("TabClose", this, false);
tabbrowser.addEventListener("load", this, true);
@ -500,7 +500,7 @@ DeveloperToolbar.prototype.hide = function() {
* @private
*/
DeveloperToolbar.prototype._devtoolsUnloaded = function() {
let tabbrowser = this._chromeWindow.getBrowser();
let tabbrowser = this._chromeWindow.gBrowser;
Array.prototype.forEach.call(tabbrowser.tabs, this._stopErrorsCount, this);
};
@ -509,7 +509,7 @@ DeveloperToolbar.prototype._devtoolsUnloaded = function() {
* @private
*/
DeveloperToolbar.prototype._devtoolsLoaded = function() {
let tabbrowser = this._chromeWindow.getBrowser();
let tabbrowser = this._chromeWindow.gBrowser;
this._initErrorsCount(tabbrowser.selectedTab);
};
@ -575,7 +575,7 @@ DeveloperToolbar.prototype.destroy = function() {
return; // Already destroyed
}
let tabbrowser = this._chromeWindow.getBrowser();
let tabbrowser = this._chromeWindow.gBrowser;
tabbrowser.tabContainer.removeEventListener("TabSelect", this, false);
tabbrowser.tabContainer.removeEventListener("TabClose", this, false);
tabbrowser.removeEventListener("load", this, true);
@ -624,7 +624,7 @@ DeveloperToolbar.prototype.handleEvent = function(ev) {
if (ev.type == "TabSelect" || ev.type == "load") {
if (this.visible) {
this.display.reattach({
contentDocument: this._chromeWindow.getBrowser().contentDocument
contentDocument: this._chromeWindow.gBrowser.contentDocument
});
if (ev.type == "TabSelect") {
@ -676,7 +676,7 @@ DeveloperToolbar.prototype._onPageBeforeUnload = function(ev) {
return;
}
let tabs = this._chromeWindow.getBrowser().tabs;
let tabs = this._chromeWindow.gBrowser.tabs;
Array.prototype.some.call(tabs, function(tab) {
if (tab.linkedBrowser.contentWindow === window) {
let tabId = tab.linkedPanel;
@ -701,7 +701,7 @@ DeveloperToolbar.prototype._onPageBeforeUnload = function(ev) {
* selected tab, then the button is not updated.
*/
DeveloperToolbar.prototype._updateErrorsCount = function(changedTabId) {
let tabId = this._chromeWindow.getBrowser().selectedTab.linkedPanel;
let tabId = this._chromeWindow.gBrowser.selectedTab.linkedPanel;
if (changedTabId && tabId != changedTabId) {
return;
}

Просмотреть файл

@ -415,7 +415,7 @@ Tooltip.prototype = {
setNamedTimeout(this.uid, this._showDelay, () => {
this.isValidHoverTarget(event.target).then(target => {
this.show(target);
}).catch((reason) => {
}, reason => {
if (reason === false) {
// isValidHoverTarget rejects with false if the tooltip should
// not be shown. This can be safely ignored.

Просмотреть файл

@ -58,6 +58,60 @@ const TESTS = [
expectedPage: 2,
message: "navigated to 2nd page using thumbnail view"
},
{
action: {
selector: "#viewer",
event: "keydown",
keyCode: 36
},
expectedPage: 1,
message: "navigated to 1st page using 'home' key"
},
{
action: {
selector: "#viewer",
event: "keydown",
keyCode: 34
},
expectedPage: 2,
message: "navigated to 2nd page using 'Page Down' key"
},
{
action: {
selector: "#viewer",
event: "keydown",
keyCode: 33
},
expectedPage: 1,
message: "navigated to 1st page using 'Page Up' key"
},
{
action: {
selector: "#viewer",
event: "keydown",
keyCode: 39
},
expectedPage: 2,
message: "navigated to 2nd page using 'right' key"
},
{
action: {
selector: "#viewer",
event: "keydown",
keyCode: 37
},
expectedPage: 1,
message: "navigated to 1st page using 'left' key"
},
{
action: {
selector: "#viewer",
event: "keydown",
keyCode: 35
},
expectedPage: 5,
message: "navigated to last page using 'home' key"
},
{
action: {
selector: ".outlineItem:nth-child(1) a",
@ -130,7 +184,14 @@ function runTests(document, window, finish) {
// Wait for outline items, the start the navigation actions
waitForOutlineItems(document).then(function () {
runNextTest(document, window, finish);
// The key navigation has to happen in page-fit, otherwise it won't scroll
// trough a complete page
setZoomToPageFit(document).then(function () {
runNextTest(document, window, finish);
}, function () {
ok(false, "Current scale has been ste to 'page-fit'");
finish();
});
}, function () {
ok(false, "Outline items have ben found");
finish();
@ -170,7 +231,17 @@ function runNextTest(document, window, endCallback) {
el.value = test.action.value;
// Dispatch the event for changing the page
el.dispatchEvent(new Event(test.action.event));
if (test.action.event == "keydown") {
var ev = document.createEvent("KeyboardEvent");
ev.initKeyEvent("keydown", true, true, null, false, false, false, false,
test.action.keyCode, 0);
el.dispatchEvent(ev);
}
else {
var ev = new Event(test.action.event);
}
el.dispatchEvent(ev);
// When the promise gets resolved we call the next test if there are any left
// or else we call the final callback which will end the test
@ -207,3 +278,27 @@ function waitForOutlineItems(document) {
return deferred.promise;
}
/**
* The key navigation has to happen in page-fit, otherwise it won't scroll
* trough a complete page
*
* @param document
* @returns {deferred.promise|*}
*/
function setZoomToPageFit(document) {
var deferred = Promise.defer();
document.addEventListener("pagerendered", function onZoom(e) {
document.removeEventListener("pagerendered", onZoom), false;
document.querySelector("#viewer").click();
deferred.resolve();
}, false);
var select = document.querySelector("select#scaleSelect");
select.selectedIndex = 2;
select.dispatchEvent(new Event("change"));
return deferred.promise;
}

Просмотреть файл

@ -94,7 +94,7 @@ Window.prototype = {
},
get _tabbrowser() {
return this._window.getBrowser();
return this._window.gBrowser;
},
/*

Просмотреть файл

@ -153,7 +153,7 @@
- panel. -->
<!ENTITY options.profiler.label "JavaScript Profiler">
<!-- LOCALICATION NOTE (options.commonprefs): This is the label for the heading
<!-- LOCALIZATION NOTE (options.commonprefs): This is the label for the heading
of all preferences that affect both the Web Console and the Network
Monitor -->
<!ENTITY options.commonPrefs.label "Common Preferences">

Просмотреть файл

@ -14,4 +14,13 @@
<!ENTITY chooseWhichOneToDisplay.label "The search bar lets you search alternate engines directly. Choose which ones to display.">
<!ENTITY engineNameColumn.label "Search Engine">
<!ENTITY engineKeywordColumn.label "Keyword">
<!ENTITY restoreDefaultSearchEngines.label "Restore Default Search Engines">
<!ENTITY restoreDefaultSearchEngines.accesskey "d">
<!ENTITY removeEngine.label "Remove">
<!ENTITY removeEngine.accesskey "r">
<!ENTITY addMoreSearchEngines.label "Add more search engines…">

Просмотреть файл

@ -141,86 +141,8 @@ treecol {
-moz-margin-start: 0;
}
#handlersView {
-moz-appearance: none;
-moz-margin-start: 0;
font-size: 1.25rem;
line-height: 22px;
color: #333333;
border: 1px solid #C1C1C1;
border-radius: 2px;
background-color: #FBFBFB;
overflow-y: auto;
height: 500px;
}
#handlersView > listheader {
-moz-appearance: none;
border: 0;
padding: 0;
}
#typeColumn,
#actionColumn {
-moz-appearance: none;
line-height: 20px;
color: #333333;
height: 36px;
padding: 0 10px;
background-color: #FBFBFB;
border: 1px solid #C1C1C1;
-moz-border-top-colors: none;
-moz-border-right-colors: none;
-moz-border-bottom-colors: none;
-moz-border-left-colors: none;
}
#typeColumn:-moz-locale-dir(ltr),
#actionColumn:-moz-locale-dir(rtl) {
border-top-left-radius: 2px;
}
#typeColumn:-moz-locale-dir(rtl),
#actionColumn:-moz-locale-dir(ltr) {
border-top-right-radius: 2px;
}
#typeColumn:hover,
#actionColumn:hover {
border-color: #0095DD;
}
#typeColumn:hover:active,
#actionColumn:hover:active {
padding: 0 10px;
}
#typeColumn > .treecol-sortdirection[sortDirection=ascending],
#actionColumn > .treecol-sortdirection[sortDirection=ascending],
#typeColumn > .treecol-sortdirection[sortDirection=descending],
#actionColumn > .treecol-sortdirection[sortDirection=descending] {
-moz-appearance: none;
list-style-image: url("chrome://global/skin/in-content/sorter.png");
}
#typeColumn > .treecol-sortdirection[sortDirection=descending],
#actionColumn > .treecol-sortdirection[sortDirection=descending] {
transform: scaleY(-1);
}
@media (min-resolution: 2dppx) {
#typeColumn > .treecol-sortdirection[sortDirection=ascending],
#actionColumn > .treecol-sortdirection[sortDirection=ascending],
#typeColumn > .treecol-sortdirection[sortDirection=descending],
#actionColumn > .treecol-sortdirection[sortDirection=descending] {
width: 12px;
height: 8px;
list-style-image: url("chrome://global/skin/in-content/sorter@2x.png");
}
}
#handlersView > richlistitem {
min-height: 40px !important;
min-height: 36px !important;
}
.typeIcon {
@ -234,8 +156,7 @@ treecol {
}
.actionsMenu {
height: 40px;
max-height: 40px;
min-height: 36px;
}
.actionsMenu > menupopup > menuitem {

Просмотреть файл

@ -2898,6 +2898,11 @@ nsDocShell::PopProfileTimelineMarkers(JSContext* aCx,
const char* startMarkerName = startPayload->GetName();
bool hasSeenPaintedLayer = false;
bool isPaint = strcmp(startMarkerName, "Paint") == 0;
// If we are processing a Paint marker, we append information from
// all the embedded Layer markers to this array.
mozilla::dom::Sequence<mozilla::dom::ProfileTimelineLayerRect> layerRectangles;
if (startPayload->GetMetaData() == TRACING_INTERVAL_START) {
bool hasSeenEnd = false;
@ -2915,14 +2920,14 @@ nsDocShell::PopProfileTimelineMarkers(JSContext* aCx,
const char* endMarkerName = endPayload->GetName();
// Look for Layer markers to stream out paint markers.
if (strcmp(endMarkerName, "Layer") == 0) {
if (isPaint && strcmp(endMarkerName, "Layer") == 0) {
hasSeenPaintedLayer = true;
endPayload->AddLayerRectangles(layerRectangles);
}
if (!startPayload->Equals(endPayload)) {
continue;
}
bool isPaint = strcmp(startMarkerName, "Paint") == 0;
// Pair start and end markers.
if (endPayload->GetMetaData() == TRACING_INTERVAL_START) {
@ -2938,7 +2943,11 @@ nsDocShell::PopProfileTimelineMarkers(JSContext* aCx,
marker.mName = NS_ConvertUTF8toUTF16(startPayload->GetName());
marker.mStart = startPayload->GetTime();
marker.mEnd = endPayload->GetTime();
startPayload->AddDetails(marker);
if (isPaint) {
marker.mRectangles.Construct(layerRectangles);
} else {
startPayload->AddDetails(marker);
}
profileTimelineMarkers.AppendElement(marker);
}

Просмотреть файл

@ -305,6 +305,11 @@ public:
{
}
virtual void AddLayerRectangles(mozilla::dom::Sequence<mozilla::dom::ProfileTimelineLayerRect>&)
{
MOZ_ASSERT_UNREACHABLE("can only be called on layer markers");
}
const char* GetName() const
{
return mName;

Просмотреть файл

@ -7,6 +7,7 @@
// restyles, reflows and paints occur
let URL = '<!DOCTYPE html><style>' +
'body {margin:0; padding: 0;} ' +
'div {width:100px;height:100px;background:red;} ' +
'.resize-change-color {width:50px;height:50px;background:blue;} ' +
'.change-color {width:50px;height:50px;background:yellow;} ' +
@ -21,8 +22,15 @@ let TESTS = [{
check: function(markers) {
ok(markers.length > 0, "markers were returned");
console.log(markers);
info(JSON.stringify(markers.filter(m => m.name == "Paint")));
ok(markers.some(m => m.name == "Reflow"), "markers includes Reflow");
ok(markers.some(m => m.name == "Paint"), "markers includes Paint");
for (let marker of markers.filter(m => m.name == "Paint")) {
// This change should generate at least one rectangle.
ok(marker.rectangles.length >= 1, "marker has one rectangle");
// One of the rectangles should contain the div.
ok(marker.rectangles.some(r => rectangleContains(r, 0, 0, 100, 100)));
}
ok(markers.some(m => m.name == "Styles"), "markers includes Restyle");
}
}, {
@ -34,6 +42,12 @@ let TESTS = [{
ok(markers.length > 0, "markers were returned");
ok(!markers.some(m => m.name == "Reflow"), "markers doesn't include Reflow");
ok(markers.some(m => m.name == "Paint"), "markers includes Paint");
for (let marker of markers.filter(m => m.name == "Paint")) {
// This change should generate at least one rectangle.
ok(marker.rectangles.length >= 1, "marker has one rectangle");
// One of the rectangles should contain the div.
ok(marker.rectangles.some(r => rectangleContains(r, 0, 0, 50, 50)));
}
ok(markers.some(m => m.name == "Styles"), "markers includes Restyle");
}
}, {
@ -145,3 +159,8 @@ function waitForMarkers(docshell) {
}, 200);
});
}
function rectangleContains(rect, x, y, width, height) {
return rect.x <= x && rect.y <= y && rect.width >= width &&
rect.height >= height;
}

Просмотреть файл

@ -4,6 +4,13 @@
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
dictionary ProfileTimelineLayerRect {
long x = 0;
long y = 0;
long width = 0;
long height = 0;
};
dictionary ProfileTimelineMarker {
DOMString name = "";
DOMHighResTimeStamp start = 0;
@ -13,4 +20,6 @@ dictionary ProfileTimelineMarker {
/* For DOMEvent markers. */
DOMString type;
unsigned short eventPhase;
/* For Paint markers. */
sequence<ProfileTimelineLayerRect> rectangles;
};

Просмотреть файл

@ -26,6 +26,7 @@
#include "mozilla/LookAndFeel.h"
#include "nsDocShell.h"
#include "nsImageFrame.h"
#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
#include "GeckoProfiler.h"
#include "mozilla/gfx/Tools.h"
@ -4419,6 +4420,36 @@ static void DrawForcedBackgroundColor(DrawTarget& aDrawTarget,
}
}
class LayerTimelineMarker : public nsDocShell::TimelineMarker
{
public:
LayerTimelineMarker(nsDocShell* aDocShell, const nsIntRegion& aRegion)
: nsDocShell::TimelineMarker(aDocShell, "Layer", TRACING_EVENT)
, mRegion(aRegion)
{
}
~LayerTimelineMarker()
{
}
virtual void AddLayerRectangles(mozilla::dom::Sequence<mozilla::dom::ProfileTimelineLayerRect>& aRectangles)
{
nsIntRegionRectIterator it(mRegion);
while (const nsIntRect* iterRect = it.Next()) {
mozilla::dom::ProfileTimelineLayerRect rect;
rect.mX = iterRect->X();
rect.mY = iterRect->Y();
rect.mWidth = iterRect->Width();
rect.mHeight = iterRect->Height();
aRectangles.AppendElement(rect);
}
}
private:
nsIntRegion mRegion;
};
/*
* A note on residual transforms:
*
@ -4568,7 +4599,13 @@ FrameLayerBuilder::DrawPaintedLayer(PaintedLayer* aLayer,
if (presContext && presContext->GetDocShell() && isActiveLayerManager) {
nsDocShell* docShell = static_cast<nsDocShell*>(presContext->GetDocShell());
docShell->AddProfileTimelineMarker("Layer", TRACING_EVENT);
bool isRecording;
docShell->GetRecordProfileTimelineMarkers(&isRecording);
if (isRecording) {
mozilla::UniquePtr<nsDocShell::TimelineMarker> marker =
MakeUnique<LayerTimelineMarker>(docShell, aRegionToDraw);
docShell->AddProfileTimelineMarker(marker);
}
}
if (!aRegionToInvalidate.IsEmpty()) {

Просмотреть файл

@ -235,6 +235,13 @@ public class AppConstants {
false;
//#endif
public static final boolean NIGHTLY_BUILD =
//#ifdef NIGHTLY_BUILD
true;
//#else
false;
//#endif
public static final boolean DEBUG_BUILD =
//#ifdef MOZ_DEBUG
true;

Просмотреть файл

@ -172,6 +172,8 @@
<!ENTITY pref_cookies_not_accept_foreign "Enabled, excluding 3rd party">
<!ENTITY pref_cookies_disabled "Disabled">
<!ENTITY pref_tracking_protection_title "Tracking protection">
<!ENTITY pref_tracking_protection_summary "&brandShortName; will prevent sites from tracking you">
<!ENTITY pref_donottrack_title "Do not track">
<!ENTITY pref_donottrack_summary "&brandShortName; will tell sites that you do not want to be tracked">

Просмотреть файл

@ -123,6 +123,8 @@ OnSharedPreferenceChangeListener
private static final String PREFS_DISPLAY_REFLOW_ON_ZOOM = "browser.zoom.reflowOnZoom";
private static final String PREFS_DISPLAY_TITLEBAR_MODE = "browser.chrome.titlebarMode";
private static final String PREFS_SYNC = NON_PREF_PREFIX + "sync";
private static final String PREFS_TRACKING_PROTECTION = "privacy.trackingprotection.enabled";
private static final String PREFS_TRACKING_PROTECTION_LEARN_MORE = NON_PREF_PREFIX + "trackingprotection.learn_more";
private static final String ACTION_STUMBLER_UPLOAD_PREF = AppConstants.ANDROID_PACKAGE_NAME + ".STUMBLER_PREF";
@ -682,6 +684,13 @@ OnSharedPreferenceChangeListener
preferences.removePreference(pref);
i--;
continue;
} else if (!AppConstants.NIGHTLY_BUILD &&
(PREFS_TRACKING_PROTECTION.equals(key) ||
PREFS_TRACKING_PROTECTION_LEARN_MORE.equals(key))) {
// Remove UI for tracking protection preference on non-Nightly builds.
preferences.removePreference(pref);
i--;
continue;
} else if (!AppConstants.MOZ_TELEMETRY_REPORTING &&
PREFS_TELEMETRY_ENABLED.equals(key)) {
preferences.removePreference(pref);

Просмотреть файл

@ -8,12 +8,28 @@
android:title="@string/pref_category_privacy_short"
android:enabled="false">
<CheckBoxPreference android:key="privacy.trackingprotection.enabled"
android:title="@string/pref_tracking_protection_title"
android:summary="@string/pref_tracking_protection_summary"
android:persistent="false" />
<org.mozilla.gecko.preferences.AlignRightLinkPreference
android:key="android.not_a_preference.trackingprotection.learn_more"
android:title="@string/pref_learn_more"
android:persistent="false"
url="https://support.mozilla.org/kb/firefox-android-tracking-protection" />
<CheckBoxPreference android:key="privacy.donottrackheader.enabled"
android:title="@string/pref_donottrack_title"
android:summary="@string/pref_donottrack_summary"
android:defaultValue="false"
android:persistent="false" />
<org.mozilla.gecko.preferences.AlignRightLinkPreference
android:key="android.not_a_preference.donottrackheader.learn_more"
android:title="@string/pref_learn_more"
android:persistent="false"
url="https://www.mozilla.org/firefox/dnt/" />
<ListPreference android:key="network.cookie.cookieBehavior"
android:title="@string/pref_cookies_menu"
android:entries="@array/pref_cookies_entries"
@ -22,12 +38,10 @@
<CheckBoxPreference android:key="signon.rememberSignons"
android:title="@string/pref_remember_signons"
android:defaultValue="true"
android:persistent="false" />
<CheckBoxPreference android:key="privacy.masterpassword.enabled"
android:title="@string/pref_use_master_password"
android:defaultValue="false"
android:persistent="false" />
<!-- keys prefixed with "android.not_a_preference." are not synced with Gecko -->

Просмотреть файл

@ -182,6 +182,8 @@
<string name="pref_cookies_not_accept_foreign">&pref_cookies_not_accept_foreign;</string>
<string name="pref_cookies_disabled">&pref_cookies_disabled;</string>
<string name="pref_tracking_protection_title">&pref_tracking_protection_title;</string>
<string name="pref_tracking_protection_summary">&pref_tracking_protection_summary;</string>
<string name="pref_donottrack_title">&pref_donottrack_title;</string>
<string name="pref_donottrack_summary">&pref_donottrack_summary;</string>

Просмотреть файл

@ -55,6 +55,9 @@ public abstract class BaseRobocopTest extends ActivityInstrumentationTestCase2<A
protected Assert mAsserter;
protected String mLogFile;
protected String mBaseHostnameUrl;
protected String mBaseIpUrl;
protected Map<String, String> mConfig;
protected String mRootPath;
@ -112,6 +115,9 @@ public abstract class BaseRobocopTest extends ActivityInstrumentationTestCase2<A
}
mAsserter.setLogFile(mLogFile);
mAsserter.setTestName(getClass().getName());
mBaseHostnameUrl = mConfig.get("host").replaceAll("(/$)", "");
mBaseIpUrl = mConfig.get("rawhost").replaceAll("(/$)", "");
}
/**

Просмотреть файл

@ -77,8 +77,6 @@ abstract class BaseTest extends BaseRobocopTest {
protected Solo mSolo;
protected Driver mDriver;
protected Actions mActions;
protected String mBaseUrl;
protected String mRawBaseUrl;
protected String mProfile;
public Device mDevice;
protected DatabaseHelper mDatabaseHelper;
@ -113,8 +111,6 @@ abstract class BaseTest extends BaseRobocopTest {
super.setUp();
// Create the intent to be used with all the important arguments.
mBaseUrl = mConfig.get("host").replaceAll("(/$)", "");
mRawBaseUrl = mConfig.get("rawhost").replaceAll("(/$)", "");
Intent i = new Intent(Intent.ACTION_MAIN);
mProfile = mConfig.get("profile");
i.putExtra("args", "-no-remote -profile " + mProfile);
@ -311,11 +307,11 @@ abstract class BaseTest extends BaseRobocopTest {
}
protected final String getAbsoluteUrl(String url) {
return mBaseUrl + "/" + url.replaceAll("(^/)", "");
return mBaseHostnameUrl + "/" + url.replaceAll("(^/)", "");
}
protected final String getAbsoluteRawUrl(String url) {
return mRawBaseUrl + "/" + url.replaceAll("(^/)", "");
return mBaseIpUrl + "/" + url.replaceAll("(^/)", "");
}
/*

Просмотреть файл

@ -177,7 +177,8 @@ public class StringHelper {
public static final String SHOW_PAGE_ADDRESS_LABEL = "Show page address";
// Privacy
public static final String TRACKING_LABEL = "Do not track";
public static final String TRACKING_PROTECTION_LABEL = "Tracking protection";
public static final String DNT_LABEL = "Do not track";
public static final String COOKIES_LABEL = "Cookies";
public static final String REMEMBER_PASSWORDS_LABEL = "Remember passwords";
public static final String MASTER_PASSWORD_LABEL = "Use master password";

Просмотреть файл

@ -45,11 +45,6 @@ abstract class UITest extends BaseRobocopTest
private Driver mDriver;
private Actions mActions;
// Base to build hostname URLs
private String mBaseHostnameUrl;
// Base to build IP URLs
private String mBaseIpUrl;
protected AboutHomeComponent mAboutHome;
protected AppMenuComponent mAppMenu;
protected GeckoViewComponent mGeckoView;
@ -68,9 +63,6 @@ abstract class UITest extends BaseRobocopTest
mDriver = new FennecNativeDriver(activity, mSolo, mRootPath);
mActions = new FennecNativeActions(activity, mSolo, getInstrumentation(), mAsserter);
mBaseHostnameUrl = mConfig.get("host").replaceAll("(/$)", "");
mBaseIpUrl = mConfig.get("rawhost").replaceAll("(/$)", "");
// Helpers depend on components so initialize them first.
initComponents();
initHelpers();

Просмотреть файл

@ -60,7 +60,8 @@ public class testSettingsMenuItems extends PixelTest {
// Privacy menu items.
String[] PATH_PRIVACY = { StringHelper.PRIVACY_SECTION_LABEL };
String[][] OPTIONS_PRIVACY = {
{ StringHelper.TRACKING_LABEL },
{ StringHelper.TRACKING_PROTECTION_LABEL },
{ StringHelper.DNT_LABEL },
{ StringHelper.COOKIES_LABEL, "Enabled", "Enabled, excluding 3rd party", "Disabled" },
{ StringHelper.REMEMBER_PASSWORDS_LABEL },
{ StringHelper.MASTER_PASSWORD_LABEL },

Просмотреть файл

@ -39,7 +39,7 @@ public class SiteIdentityPopup extends ArrowPopup {
"https://support.mozilla.org/kb/how-does-insecure-content-affect-safety-android";
private static final String TRACKING_CONTENT_SUPPORT_URL =
"https://support.mozilla.org/kb/how-does-insecure-content-affect-safety-android";
"https://support.mozilla.org/kb/firefox-android-tracking-protection";
private SiteIdentity mSiteIdentity;

Просмотреть файл

@ -456,83 +456,15 @@ let Bookmarks = Object.freeze({
*/
eraseEverything: Task.async(function* () {
let db = yield DBConnPromised;
yield db.executeTransaction(function* () {
let rows = yield db.executeCached(
`WITH RECURSIVE
descendants(did) AS (
SELECT b.id FROM moz_bookmarks b
JOIN moz_bookmarks p ON b.parent = p.id
WHERE p.guid IN ( :toolbarGuid, :menuGuid, :unfiledGuid )
UNION ALL
SELECT id FROM moz_bookmarks
JOIN descendants ON parent = did
)
SELECT b.id AS _id, b.parent AS _parentId, b.position AS 'index',
b.type, url, b.guid, p.guid AS parentGuid, b.dateAdded,
b.lastModified, b.title, p.parent AS _grandParentId,
NULL AS _childCount, NULL AS keyword
FROM moz_bookmarks b
JOIN moz_bookmarks p ON p.id = b.parent
LEFT JOIN moz_places h ON b.fk = h.id
WHERE b.id IN descendants
`, { menuGuid: this.menuGuid, toolbarGuid: this.toolbarGuid,
unfiledGuid: this.unfiledGuid });
let items = rowsToItemsArray(rows);
yield db.executeCached(
`WITH RECURSIVE
descendants(did) AS (
SELECT b.id FROM moz_bookmarks b
JOIN moz_bookmarks p ON b.parent = p.id
WHERE p.guid IN ( :toolbarGuid, :menuGuid, :unfiledGuid )
UNION ALL
SELECT id FROM moz_bookmarks
JOIN descendants ON parent = did
)
DELETE FROM moz_bookmarks WHERE id IN descendants
`, { menuGuid: this.menuGuid, toolbarGuid: this.toolbarGuid,
unfiledGuid: this.unfiledGuid });
// Clenup orphans.
yield removeOrphanAnnotations(db);
yield removeOrphanKeywords(db);
// TODO (Bug 1087576): this may leave orphan tags behind.
// Update roots' lastModified.
yield db.executeCached(
`UPDATE moz_bookmarks SET lastModified = :time
WHERE id IN (SELECT id FROM moz_bookmarks
WHERE guid IN ( :rootGuid, :toolbarGuid, :menuGuid, :unfiledGuid ))
`, { time: toPRTime(new Date()), rootGuid: this.rootGuid,
menuGuid: this.menuGuid, toolbarGuid: this.toolbarGuid,
unfiledGuid: this.unfiledGuid });
let urls = [for (item of items) if (item.url) item.url];
updateFrecency(db, urls).then(null, Cu.reportError);
// Send onItemRemoved notifications to listeners.
// TODO (Bug 1087580): this should send a single clear bookmarks
// notification rather than notifying for each bookmark.
// Notify listeners in reverse order to serve children before parents.
let observers = PlacesUtils.bookmarks.getObservers();
for (let item of items.reverse()) {
let uri = item.hasOwnProperty("url") ? toURI(item.url) : null;
notify(observers, "onItemRemoved", [ item._id, item._parentId,
item.index, item.type, uri,
item.guid, item.parentGuid ]);
let isUntagging = item._grandParentId == PlacesUtils.tagsFolderId;
if (isUntagging) {
for (let entry of (yield fetchBookmarksByURL(item))) {
notify(observers, "onItemChanged", [ entry._id, "tags", false, "",
toPRTime(entry.lastModified),
entry.type, entry._parentId,
entry.guid, entry.parentGuid ]);
}
}
const folderGuids = [this.toolbarGuid, this.menuGuid, this.unfiledGuid];
yield removeFoldersContents(db, folderGuids);
const time = toPRTime(new Date());
for (let folderGuid of folderGuids) {
yield db.executeCached(
`UPDATE moz_bookmarks SET lastModified = :time
WHERE id IN (SELECT id FROM moz_bookmarks WHERE guid = :folderGuid )
`, { folderGuid, time });
}
}.bind(this));
}),
@ -1026,6 +958,10 @@ function* removeBookmark(item) {
let isUntagging = item._grandParentId == PlacesUtils.tagsFolderId;
yield db.executeTransaction(function* transaction() {
// If it's a folder, remove its contents first.
if (item.type == Bookmarks.TYPE_FOLDER)
yield removeFoldersContents(db, [item.guid]);
// Remove annotations first. If it's a tag, we can avoid paying that cost.
if (!isUntagging) {
// We don't go through the annotations service for this cause otherwise
@ -1414,3 +1350,83 @@ let setAncestorsLastModified = Task.async(function* (db, folderGuid, time) {
`, { guid: folderGuid, type: Bookmarks.TYPE_FOLDER,
time: toPRTime(time) });
});
/**
* Remove all descendants of one or more bookmark folders.
*
* @param db
* the Sqlite.jsm connection handle.
* @param folderGuids
* array of folder guids.
*/
let removeFoldersContents =
Task.async(function* (db, folderGuids) {
let itemsRemoved = [];
for (let folderGuid of folderGuids) {
let rows = yield db.executeCached(
`WITH RECURSIVE
descendants(did) AS (
SELECT b.id FROM moz_bookmarks b
JOIN moz_bookmarks p ON b.parent = p.id
WHERE p.guid = :folderGuid
UNION ALL
SELECT id FROM moz_bookmarks
JOIN descendants ON parent = did
)
SELECT b.id AS _id, b.parent AS _parentId, b.position AS 'index',
b.type, url, b.guid, p.guid AS parentGuid, b.dateAdded,
b.lastModified, b.title, p.parent AS _grandParentId,
NULL AS _childCount, NULL AS keyword
FROM moz_bookmarks b
JOIN moz_bookmarks p ON p.id = b.parent
LEFT JOIN moz_places h ON b.fk = h.id
WHERE b.id IN descendants`, { folderGuid });
itemsRemoved = itemsRemoved.concat(rowsToItemsArray(rows));
yield db.executeCached(
`WITH RECURSIVE
descendants(did) AS (
SELECT b.id FROM moz_bookmarks b
JOIN moz_bookmarks p ON b.parent = p.id
WHERE p.guid = :folderGuid
UNION ALL
SELECT id FROM moz_bookmarks
JOIN descendants ON parent = did
)
DELETE FROM moz_bookmarks WHERE id IN descendants`, { folderGuid });
}
// Cleanup orphans.
yield removeOrphanAnnotations(db);
yield removeOrphanKeywords(db);
// TODO (Bug 1087576): this may leave orphan tags behind.
let urls = [for (item of itemsRemoved) if (item.url) item.url];
updateFrecency(db, urls).then(null, Cu.reportError);
// Send onItemRemoved notifications to listeners.
// TODO (Bug 1087580): for the case of eraseEverything, this should send a
// single clear bookmarks notification rather than notifying for each
// bookmark.
// Notify listeners in reverse order to serve children before parents.
let observers = PlacesUtils.bookmarks.getObservers();
for (let item of itemsRemoved.reverse()) {
let uri = item.hasOwnProperty("url") ? toURI(item.url) : null;
notify(observers, "onItemRemoved", [ item._id, item._parentId,
item.index, item.type, uri,
item.guid, item.parentGuid ]);
let isUntagging = item._grandParentId == PlacesUtils.tagsFolderId;
if (isUntagging) {
for (let entry of (yield fetchBookmarksByURL(item))) {
notify(observers, "onItemChanged", [ entry._id, "tags", false, "",
toPRTime(entry.lastModified),
entry.type, entry._parentId,
entry.guid, entry.parentGuid ]);
}
}
}
});

Просмотреть файл

@ -315,6 +315,46 @@ add_task(function* remove_bookmark_tag_notification() {
]);
});
add_task(function* remove_folder_notification() {
let folder1 = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
let folder1Id = yield PlacesUtils.promiseItemId(folder1.guid);
let folder1ParentId = yield PlacesUtils.promiseItemId(folder1.parentGuid);
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: folder1.guid,
url: new URL("http://example.com/") });
let bmItemId = yield PlacesUtils.promiseItemId(bm.guid);
let folder2 = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: folder1.guid });
let folder2Id = yield PlacesUtils.promiseItemId(folder2.guid);
let bm2 = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: folder2.guid,
url: new URL("http://example.com/") });
let bm2ItemId = yield PlacesUtils.promiseItemId(bm2.guid);
let observer = expectNotifications();
yield PlacesUtils.bookmarks.remove(folder1.guid);
observer.check([ { name: "onItemRemoved",
arguments: [ bm2ItemId, folder2Id, bm2.index, bm2.type,
bm2.url, bm2.guid, bm2.parentGuid ] },
{ name: "onItemRemoved",
arguments: [ folder2Id, folder1Id, folder2.index,
folder2.type, null, folder2.guid,
folder2.parentGuid ] },
{ name: "onItemRemoved",
arguments: [ bmItemId, folder1Id, bm.index, bm.type,
bm.url, bm.guid, bm.parentGuid ] },
{ name: "onItemRemoved",
arguments: [ folder1Id, folder1ParentId, folder1.index,
folder1.type, null, folder1.guid,
folder1.parentGuid ] }
]);
});
add_task(function* eraseEverything_notification() {
// Let's start from a clean situation.
yield PlacesUtils.bookmarks.eraseEverything();
@ -348,19 +388,9 @@ add_task(function* eraseEverything_notification() {
let menuBmParentId = yield PlacesUtils.promiseItemId(menuBm.parentGuid);
let observer = expectNotifications();
let removed = yield PlacesUtils.bookmarks.eraseEverything();
yield PlacesUtils.bookmarks.eraseEverything();
observer.check([ { name: "onItemRemoved",
arguments: [ menuBmId, menuBmParentId,
menuBm.index, menuBm.type,
menuBm.url, menuBm.guid,
menuBm.parentGuid ] },
{ name: "onItemRemoved",
arguments: [ toolbarBmId, toolbarBmParentId,
toolbarBm.index, toolbarBm.type,
toolbarBm.url, toolbarBm.guid,
toolbarBm.parentGuid ] },
{ name: "onItemRemoved",
arguments: [ folder2Id, folder2ParentId, folder2.index,
folder2.type, null, folder2.guid,
folder2.parentGuid ] },
@ -370,7 +400,17 @@ add_task(function* eraseEverything_notification() {
{ name: "onItemRemoved",
arguments: [ folder1Id, folder1ParentId, folder1.index,
folder1.type, null, folder1.guid,
folder1.parentGuid ] }
folder1.parentGuid ] },
{ name: "onItemRemoved",
arguments: [ menuBmId, menuBmParentId,
menuBm.index, menuBm.type,
menuBm.url, menuBm.guid,
menuBm.parentGuid ] },
{ name: "onItemRemoved",
arguments: [ toolbarBmId, toolbarBmParentId,
toolbarBm.index, toolbarBm.type,
toolbarBm.url, toolbarBm.guid,
toolbarBm.parentGuid ] }
]);
});

Просмотреть файл

@ -147,6 +147,20 @@ add_task(function* remove_folder() {
Assert.ok(!("keyword" in bm2));
});
add_task(function* test_nested_contents_removed() {
let folder1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "a folder" });
let folder2 = yield PlacesUtils.bookmarks.insert({ parentGuid: folder1.guid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "a folder" });
let sep = yield PlacesUtils.bookmarks.insert({ parentGuid: folder2.guid,
type: PlacesUtils.bookmarks.TYPE_SEPARATOR });
yield PlacesUtils.bookmarks.remove(folder1);
Assert.strictEqual((yield PlacesUtils.bookmarks.fetch(folder1.guid)), null);
Assert.strictEqual((yield PlacesUtils.bookmarks.fetch(folder2.guid)), null);
Assert.strictEqual((yield PlacesUtils.bookmarks.fetch(sep.guid)), null);
});
add_task(function* remove_folder_empty_title() {
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,

Просмотреть файл

@ -166,6 +166,22 @@ xul|menulist[disabled="true"] {
opacity: 0.5;
}
*|button.primary {
background-color: #0095dd;
border-color: transparent;
color: #fff;
}
html|button.primary:enabled:hover,
xul|button.primary:not([disabled="true"]):hover {
background-color: #008acb;
}
html|button.primary:enabled:hover:active,
xul|button.primary:not([disabled="true"]):hover:active {
background-color: #006b9d;
}
xul|colorpicker[type="button"] {
padding: 6px;
width: 50px;
@ -561,3 +577,78 @@ xul|textbox + xul|button,
xul|filefield + xul|button {
-moz-border-start: none;
}
/* List boxes */
xul|richlistbox,
xul|listbox {
-moz-appearance: none;
border: 1px solid #c1c1c1;
}
xul|treechildren::-moz-tree-row,
xul|listbox xul|listitem {
padding: 5px;
margin: 0;
border: none;
background-image: none;
}
xul|treechildren::-moz-tree-row(selected),
xul|listbox xul|listitem[selected="true"] {
background-color: #f1f1f1;
}
/* Trees */
xul|tree {
-moz-appearance: none;
font-size: 1em;
border: 1px solid #c1c1c1;
}
xul|listheader,
xul|treecols {
-moz-appearance: none;
border: none;
border-bottom: 1px solid #c1c1c1;
padding: 0;
}
xul|treecol:not([hideheader="true"]),
xul|treecolpicker {
-moz-appearance: none;
border: none;
background-color: #ebebeb;
color: #808080;
padding: 5px 10px;
}
xul|treecol:not([hideheader="true"]):hover,
xul|treecolpicker:hover {
background-color: #dadada;
color: #333;
}
xul|treecol:not([hideheader="true"]):not(:first-child),
xul|treecolpicker {
-moz-border-start-width: 1px;
-moz-border-start-style: solid;
border-image: linear-gradient(transparent 0%, transparent 20%, #c1c1c1 20%, #c1c1c1 80%, transparent 80%, transparent 100%) 1 1;
}
xul|treecol:not([hideheader="true"]) > xul|*.treecol-sortdirection[sortDirection] {
list-style-image: url("chrome://global/skin/in-content/sorter.png");
width: 12px;
height: 8px;
}
xul|treecol:not([hideheader="true"]) > xul|*.treecol-sortdirection[sortDirection="descending"] {
transform: scaleY(-1);
}
@media (min-resolution: 2dppx) {
xul|treecol:not([hideheader="true"]) > xul|*.treecol-sortdirection[sortDirection] {
list-style-image: url("chrome://global/skin/in-content/sorter@2x.png");
}
}