зеркало из https://github.com/mozilla/gecko-dev.git
Merge fx-team to m-c a=merge
This commit is contained in:
Коммит
58cb48da88
|
@ -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");
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче