Merge fx-team to central, a=merge

--HG--
extra : commitid : G8bubLAHlYY
This commit is contained in:
Wes Kocher 2016-01-07 16:35:32 -08:00
Родитель cd44b3ab48 67bd7924e0
Коммит 5b52b8c20a
140 изменённых файлов: 2271 добавлений и 2437 удалений

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

@ -1547,8 +1547,6 @@ pref("experiments.supported", true);
// Enable GMP support in the addon manager.
pref("media.gmp-provider.enabled", true);
pref("browser.apps.URL", "https://marketplace.firefox.com/discovery/");
#ifdef NIGHTLY_BUILD
pref("browser.polaris.enabled", false);
pref("privacy.trackingprotection.ui.enabled", false);

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

@ -3121,8 +3121,7 @@ function getDetailedCertErrorInfo(location, securityInfoAsString) {
if (!securityInfoAsString)
return "";
let details = [];
details.push(location);
let certErrorDetails = location;
const serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
.getService(Ci.nsISerializationHelper);
@ -3132,7 +3131,7 @@ function getDetailedCertErrorInfo(location, securityInfoAsString) {
let errors = Cc["@mozilla.org/nss_errors_service;1"]
.getService(Ci.nsINSSErrorsService);
let code = securityInfo.errorCode;
details.push(errors.getErrorMessage(errors.getXPCOMFromNSSError(code)));
certErrorDetails += "\r\n\r\n" + errors.getErrorMessage(errors.getXPCOMFromNSSError(code));
const sss = Cc["@mozilla.org/ssservice;1"]
.getService(Ci.nsISiteSecurityService);
@ -3143,8 +3142,15 @@ function getDetailedCertErrorInfo(location, securityInfoAsString) {
Ci.nsISocketProvider.NO_PERMANENT_STORAGE : 0;
let uri = Services.io.newURI(location, null, null);
details.push(sss.isSecureHost(sss.HEADER_HSTS, uri.host, flags));
details.push(sss.isSecureHost(sss.HEADER_HPKP, uri.host, flags));
let hasHSTS = sss.isSecureHost(sss.HEADER_HSTS, uri.host, flags);
let hasHPKP = sss.isSecureHost(sss.HEADER_HPKP, uri.host, flags);
certErrorDetails += "\r\n\r\n" +
gNavigatorBundle.getFormattedString("certErrorDetailsHSTS.label",
[hasHSTS]);
certErrorDetails += "\r\n" +
gNavigatorBundle.getFormattedString("certErrorDetailsKeyPinning.label",
[hasHPKP]);
let certChain = "";
if (securityInfo.failedCertChain) {
@ -3155,8 +3161,12 @@ function getDetailedCertErrorInfo(location, securityInfoAsString) {
certChain += getPEMString(cert);
}
}
details.push(certChain);
return gNavigatorBundle.getFormattedString("certErrorDetails.label", details, 5);
certErrorDetails += "\r\n\r\n" +
gNavigatorBundle.getString("certErrorDetailsCertChain.label") +
"\r\n\r\n" + certChain;
return certErrorDetails;
}
// TODO: can we pull getDERString and getPEMString in from pippki.js instead of
@ -6408,11 +6418,6 @@ function BrowserOpenAddonsMgr(aView) {
}
}
function BrowserOpenApps() {
let appsURL = Services.urlFormatter.formatURLPref("browser.apps.URL");
switchToTabHavingURI(appsURL, true)
}
function AddKeywordForSearchField() {
let mm = gBrowser.selectedBrowser.messageManager;

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

@ -1141,18 +1141,6 @@ const CustomizableWidgets = [
let win = aEvent.view;
win.MailIntegration.sendLinkForBrowser(win.gBrowser.selectedBrowser)
}
}, {
id: "web-apps-button",
label: "web-apps-button.label",
tooltiptext: "web-apps-button.tooltiptext",
onCommand: function(aEvent) {
let win = aEvent.target &&
aEvent.target.ownerDocument &&
aEvent.target.ownerDocument.defaultView;
if (win && typeof win.BrowserOpenApps == "function") {
win.BrowserOpenApps();
}
}
}];
if (Services.prefs.getBoolPref("privacy.panicButton.enabled")) {

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

@ -19,13 +19,13 @@
% override chrome://loop/skin/toolbar.png chrome://loop/skin/toolbar-XP.png os=WINNT osversion<6
% override chrome://loop/skin/toolbar.png chrome://loop/skin/toolbar-aero.png os=WINNT osversion=6
% override chrome://loop/skin/toolbar.png chrome://loop/skin/toolbar-aero.png os=WINNT osversion=6.1
% override chrome://loop/skin/toolbar.png chrome://loop/skin/toolbar-win8.png os=WINNT osversion=6.2
% override chrome://loop/skin/toolbar.png chrome://loop/skin/toolbar-win8.png os=WINNT osversion=6.3
% override chrome://loop/skin/toolbar.png chrome://loop/skin/toolbar-win10.png os=WINNT osversion=6.2
% override chrome://loop/skin/toolbar.png chrome://loop/skin/toolbar-win10.png os=WINNT osversion=6.3
% override chrome://loop/skin/toolbar@2x.png chrome://loop/skin/toolbar-XP@2x.png os=WINNT osversion<6
% override chrome://loop/skin/toolbar@2x.png chrome://loop/skin/toolbar-aero@2x.png os=WINNT osversion=6
% override chrome://loop/skin/toolbar@2x.png chrome://loop/skin/toolbar-aero@2x.png os=WINNT osversion=6.1
% override chrome://loop/skin/toolbar@2x.png chrome://loop/skin/toolbar-win8@2x.png os=WINNT osversion=6.2
% override chrome://loop/skin/toolbar@2x.png chrome://loop/skin/toolbar-win8@2x.png os=WINNT osversion=6.3
% override chrome://loop/skin/toolbar@2x.png chrome://loop/skin/toolbar-win10@2x.png os=WINNT osversion=6.2
% override chrome://loop/skin/toolbar@2x.png chrome://loop/skin/toolbar-win10@2x.png os=WINNT osversion=6.3
skin/ (skin/*)
content/modules/ (content/modules/*)
* content/preferences/prefs.js (content/preferences/prefs.js)

Двоичные данные
browser/extensions/loop/skin/linux/menuPanel.png

Двоичный файл не отображается.

До

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

После

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

Двоичный файл не отображается.

До

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

После

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

Двоичный файл не отображается.

До

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

После

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

Двоичный файл не отображается.

До

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

После

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

Двоичные данные
browser/extensions/loop/skin/linux/toolbar.png

Двоичный файл не отображается.

До

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

После

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

Двоичный файл не отображается.

До

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

После

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

Двоичный файл не отображается.

До

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

После

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

Двоичный файл не отображается.

До

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

После

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

Двоичные данные
browser/extensions/loop/skin/osx/menuPanel.png

Двоичный файл не отображается.

До

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

После

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

Двоичный файл не отображается.

До

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

После

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

Двоичный файл не отображается.

До

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

После

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

Двоичный файл не отображается.

До

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

После

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

Двоичный файл не отображается.

До

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

После

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

Двоичный файл не отображается.

До

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

После

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

Двоичные данные
browser/extensions/loop/skin/osx/toolbar.png

Двоичный файл не отображается.

До

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

После

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

Двоичные данные
browser/extensions/loop/skin/osx/toolbar@2x.png

Двоичный файл не отображается.

До

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

После

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

Двоичный файл не отображается.

До

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

После

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

Двоичный файл не отображается.

До

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

После

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

Двоичный файл не отображается.

До

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

После

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

Двоичный файл не отображается.

До

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

После

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

Двоичный файл не отображается.

До

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

После

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

Двоичный файл не отображается.

До

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

После

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

Двоичный файл не отображается.

До

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

После

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

Двоичный файл не отображается.

До

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

После

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

Двоичный файл не отображается.

До

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

После

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

Двоичный файл не отображается.

До

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

После

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

Двоичный файл не отображается.

До

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

После

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

Двоичный файл не отображается.

До

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

После

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

Двоичные данные
browser/extensions/loop/skin/windows/toolbar-win10.png Normal file

Двоичный файл не отображается.

После

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

Двоичные данные
browser/extensions/loop/skin/windows/toolbar-win10@2x.png Normal file

Двоичный файл не отображается.

После

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

Двоичный файл не отображается.

До

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

Двоичный файл не отображается.

До

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

Двоичные данные
browser/extensions/loop/skin/windows/toolbar.png

Двоичный файл не отображается.

До

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

После

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

Двоичный файл не отображается.

До

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

После

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

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

@ -799,13 +799,13 @@ unmuteTab.accesskey = M
weakCryptoOverriding.message = %S recommends that you don't enter your password, credit card and other personal information on this website.
revokeOverride.label = Don't Trust This Website
revokeOverride.accesskey = D
# LOCALIZATION NOTE (certErrorDetails.label): This is a text string that
# appears in the about:certerror page, so that the user can copy and send it to
# the server administrators for troubleshooting. %1$S is the visited URL, %2$S
# is the error message, %3$S is true or false, depending on whether the server
# supports HSTS, %4$S is true or false, depending on whether the server
# supports HPKP, %5$S is the certificate chain in PEM format.
certErrorDetails.label = %1$S\r\n\r\n%2$S\r\n\r\nHTTP Strict Transport Security: %3$S\r\nHTTP Public Key Pinning: %4$S\r\n\r\nCertificate chain:\r\n\r\n%5$S
# LOCALIZATION NOTE (certErrorDetails*.label): These are text strings that
# appear in the about:certerror page, so that the user can copy and send them to
# the server administrators for troubleshooting.
certErrorDetailsHSTS.label = HTTP Strict Transport Security: %S
certErrorDetailsKeyPinning.label = HTTP Public Key Pinning: %S
certErrorDetailsCertChain.label = Certificate chain:
# LOCALIZATION NOTE (tabgroups.migration.anonGroup):
# %S is the group number/ID

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

@ -115,9 +115,6 @@ social-share-button.tooltiptext = Share this page
panic-button.label = Forget
panic-button.tooltiptext = Forget about some browsing history
web-apps-button.label = Apps
web-apps-button.tooltiptext = Discover Apps
# LOCALIZATION NOTE(devtools-webide-button.label, devtools-webide-button.tooltiptext):
# widget is only visible after WebIDE has been started once (Tools > Web Developers > WebIDE)
# %S is the keyboard shortcut

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

@ -92,7 +92,6 @@ XPCOMUtils.defineLazyGetter(this, "PALETTE_ITEMS", function() {
"feed-button",
"email-link-button",
"sync-button",
"web-apps-button",
];
let panelPlacements = DEFAULT_AREA_PLACEMENTS["PanelUI-contents"];

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

@ -890,10 +890,6 @@ toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-ic
-moz-image-region: rect(18px, 702px, 36px, 684px);
}
#web-apps-button@toolbarButtonPressed@ {
-moz-image-region: rect(18px, 720px, 36px, 702px);
}
/**
* OSX has a unique set of icons when fullscreen is in the checked state.
*/
@ -1061,10 +1057,6 @@ toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-ic
-moz-image-region: rect(36px, 1404px, 72px, 1368px);
}
#web-apps-button@toolbarButtonPressed@ {
-moz-image-region: rect(36px, 1440px, 72px, 1404px);
}
#add-share-provider {
list-style-image: url(chrome://browser/skin/menuPanel-small@2x.png);
-moz-image-region: rect(0px, 192px, 32px, 160px);

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

@ -2,7 +2,7 @@
% Note that zoom-reset-button is a bit different since it doesn't use an image and thus has the image with display: none.
%define nestedButtons #zoom-out-button, #zoom-reset-button, #zoom-in-button, #cut-button, #copy-button, #paste-button
%define primaryToolbarButtons #back-button, #forward-button, #home-button, #print-button, #downloads-button, #bookmarks-menu-button, #new-tab-button, #new-window-button, #fullscreen-button, #sync-button, #feed-button, #social-share-button, #open-file-button, #find-button, #developer-button, #preferences-button, #privatebrowsing-button, #save-page-button, #add-ons-button, #history-panelmenu, #nav-bar-overflow-button, #PanelUI-menu-button, #characterencoding-button, #email-link-button, #sidebar-button, @nestedButtons@, #e10s-button, #panic-button, #web-apps-button, #webide-button
%define primaryToolbarButtons #back-button, #forward-button, #home-button, #print-button, #downloads-button, #bookmarks-menu-button, #new-tab-button, #new-window-button, #fullscreen-button, #sync-button, #feed-button, #social-share-button, #open-file-button, #find-button, #developer-button, #preferences-button, #privatebrowsing-button, #save-page-button, #add-ons-button, #history-panelmenu, #nav-bar-overflow-button, #PanelUI-menu-button, #characterencoding-button, #email-link-button, #sidebar-button, @nestedButtons@, #e10s-button, #panic-button, #webide-button
%ifdef XP_MACOSX
% Prior to 10.7 there wasn't a native fullscreen button so we use #restore-button to exit fullscreen

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

@ -158,11 +158,6 @@
-moz-image-region: rect(32px, 896px, 64px, 864px);
}
#web-apps-button[cui-areatype="menu-panel"],
toolbarpaletteitem[place="palette"] > #web-apps-button {
-moz-image-region: rect(0, 928px, 32px, 896px);
}
#webide-button[cui-areatype="menu-panel"],
toolbarpaletteitem[place="palette"] > #webide-button {
-moz-image-region: rect(0px, 960px, 32px, 928px);
@ -368,11 +363,6 @@
-moz-image-region: rect(64px, 1792px, 128px, 1728px);
}
#web-apps-button[cui-areatype="menu-panel"],
toolbarpaletteitem[place="palette"] > #web-apps-button {
-moz-image-region: rect(0, 1856px, 64px, 1792px);
}
toolbaritem[sdkstylewidget="true"] > toolbarbutton {
-moz-image-region: rect(0, 1664px, 64px, 1600px);
}

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

@ -167,10 +167,6 @@ toolbar[brighttext] #bookmarks-menu-button > .toolbarbutton-menubutton-dropmarke
transform: scaleX(-1);
}
#web-apps-button[cui-areatype="toolbar"] {
-moz-image-region: rect(0, 720px, 18px, 702px);
}
#webide-button[cui-areatype="toolbar"] {
-moz-image-region: rect(0, 738px, 18px, 720px);
}
@ -338,10 +334,6 @@ toolbar[brighttext] #bookmarks-menu-button > .toolbarbutton-menubutton-dropmarke
%endif
}
#web-apps-button[cui-areatype="toolbar"] {
-moz-image-region: rect(0, 1440px, 36px, 1404px);
}
#webide-button[cui-areatype="toolbar"] {
-moz-image-region: rect(0, 1476px, 36px, 1440px);
}

Двоичные данные
browser/themes/windows/loop/toolbar-win10.png Normal file

Двоичный файл не отображается.

После

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

Двоичные данные
browser/themes/windows/loop/toolbar-win10@2x.png Normal file

Двоичный файл не отображается.

После

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

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

@ -8,10 +8,8 @@
"use strict";
const {
AnimationsTimeline,
RateSelector
} = require("devtools/client/animationinspector/components");
const {AnimationsTimeline} = require("devtools/client/animationinspector/components/animation-timeline");
const {RateSelector} = require("devtools/client/animationinspector/components/rate-selector");
const {formatStopwatchTime} = require("devtools/client/animationinspector/utils");
var $ = (selector, target = document) => target.querySelector(selector);

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -0,0 +1,160 @@
"use strict";
const {Cu} = require("chrome");
const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
const {
createNode,
TimeScale
} = require("devtools/client/animationinspector/utils");
const {Keyframes} = require("devtools/client/animationinspector/components/keyframes");
/**
* UI component responsible for displaying detailed information for a given
* animation.
* This includes information about timing, easing, keyframes, animated
* properties.
*/
function AnimationDetails() {
EventEmitter.decorate(this);
this.onFrameSelected = this.onFrameSelected.bind(this);
this.keyframeComponents = [];
}
exports.AnimationDetails = AnimationDetails;
AnimationDetails.prototype = {
// These are part of frame objects but are not animated properties. This
// array is used to skip them.
NON_PROPERTIES: ["easing", "composite", "computedOffset", "offset"],
init: function(containerEl) {
this.containerEl = containerEl;
},
destroy: function() {
this.unrender();
this.containerEl = null;
},
unrender: function() {
for (let component of this.keyframeComponents) {
component.off("frame-selected", this.onFrameSelected);
component.destroy();
}
this.keyframeComponents = [];
while (this.containerEl.firstChild) {
this.containerEl.firstChild.remove();
}
},
/**
* Convert a list of frames into a list of tracks, one per animated property,
* each with a list of frames.
*/
getTracksFromFrames: function(frames) {
let tracks = {};
for (let frame of frames) {
for (let name in frame) {
if (this.NON_PROPERTIES.indexOf(name) != -1) {
continue;
}
if (!tracks[name]) {
tracks[name] = [];
}
tracks[name].push({
value: frame[name],
offset: frame.computedOffset
});
}
}
return tracks;
},
render: Task.async(function*(animation) {
this.unrender();
if (!animation) {
return;
}
this.animation = animation;
let frames = yield animation.getFrames();
// We might have been destroyed in the meantime, or the component might
// have been re-rendered.
if (!this.containerEl || this.animation !== animation) {
return;
}
// Useful for tests to know when the keyframes have been retrieved.
this.emit("keyframes-retrieved");
// Build an element for each animated property track.
this.tracks = this.getTracksFromFrames(frames);
for (let propertyName in this.tracks) {
let line = createNode({
parent: this.containerEl,
attributes: {"class": "property"}
});
createNode({
// text-overflow doesn't work in flex items, so we need a second level
// of container to actually have an ellipsis on the name.
// See bug 972664.
parent: createNode({
parent: line,
attributes: {"class": "name"},
}),
textContent: getCssPropertyName(propertyName)
});
// Add the keyframes diagram for this property.
let framesWrapperEl = createNode({
parent: line,
attributes: {"class": "track-container"}
});
let framesEl = createNode({
parent: framesWrapperEl,
attributes: {"class": "frames"}
});
// Scale the list of keyframes according to the current time scale.
let {x, w} = TimeScale.getAnimationDimensions(animation);
framesEl.style.left = `${x}%`;
framesEl.style.width = `${w}%`;
let keyframesComponent = new Keyframes();
keyframesComponent.init(framesEl);
keyframesComponent.render({
keyframes: this.tracks[propertyName],
propertyName: propertyName,
animation: animation
});
keyframesComponent.on("frame-selected", this.onFrameSelected);
this.keyframeComponents.push(keyframesComponent);
}
}),
onFrameSelected: function(e, args) {
// Relay the event up, it's needed in parents too.
this.emit(e, args);
}
};
/**
* Turn propertyName into property-name.
* @param {String} jsPropertyName A camelcased CSS property name. Typically
* something that comes out of computed styles. E.g. borderBottomColor
* @return {String} The corresponding CSS property name: border-bottom-color
*/
function getCssPropertyName(jsPropertyName) {
return jsPropertyName.replace(/[A-Z]/g, "-$&").toLowerCase();
}
exports.getCssPropertyName = getCssPropertyName;

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

@ -0,0 +1,320 @@
"use strict";
const {Cu} = require("chrome");
Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
const {
createNode,
TargetNodeHighlighter
} = require("devtools/client/animationinspector/utils");
const STRINGS_URI = "chrome://devtools/locale/animationinspector.properties";
const L10N = new ViewHelpers.L10N(STRINGS_URI);
/**
* UI component responsible for displaying a preview of the target dom node of
* a given animation.
* @param {InspectorPanel} inspector Requires a reference to the inspector-panel
* to highlight and select the node, as well as refresh it when there are
* mutations.
* @param {Object} options Supported properties are:
* - compact {Boolean} Defaults to false. If true, nodes will be previewed like
* tag#id.class instead of <tag id="id" class="class">
*/
function AnimationTargetNode(inspector, options = {}) {
this.inspector = inspector;
this.options = options;
this.onPreviewMouseOver = this.onPreviewMouseOver.bind(this);
this.onPreviewMouseOut = this.onPreviewMouseOut.bind(this);
this.onSelectNodeClick = this.onSelectNodeClick.bind(this);
this.onMarkupMutations = this.onMarkupMutations.bind(this);
this.onHighlightNodeClick = this.onHighlightNodeClick.bind(this);
this.onTargetHighlighterLocked = this.onTargetHighlighterLocked.bind(this);
EventEmitter.decorate(this);
}
exports.AnimationTargetNode = AnimationTargetNode;
AnimationTargetNode.prototype = {
init: function(containerEl) {
let document = containerEl.ownerDocument;
// Init the markup for displaying the target node.
this.el = createNode({
parent: containerEl,
attributes: {
"class": "animation-target"
}
});
// Icon to select the node in the inspector.
this.highlightNodeEl = createNode({
parent: this.el,
nodeType: "span",
attributes: {
"class": "node-highlighter",
"title": L10N.getStr("node.highlightNodeLabel")
}
});
// Wrapper used for mouseover/out event handling.
this.previewEl = createNode({
parent: this.el,
nodeType: "span",
attributes: {
"title": L10N.getStr("node.selectNodeLabel")
}
});
if (!this.options.compact) {
this.previewEl.appendChild(document.createTextNode("<"));
}
// Tag name.
this.tagNameEl = createNode({
parent: this.previewEl,
nodeType: "span",
attributes: {
"class": "tag-name theme-fg-color3"
}
});
// Id attribute container.
this.idEl = createNode({
parent: this.previewEl,
nodeType: "span"
});
if (!this.options.compact) {
createNode({
parent: this.idEl,
nodeType: "span",
attributes: {
"class": "attribute-name theme-fg-color2"
},
textContent: "id"
});
this.idEl.appendChild(document.createTextNode("=\""));
} else {
createNode({
parent: this.idEl,
nodeType: "span",
attributes: {
"class": "theme-fg-color2"
},
textContent: "#"
});
}
createNode({
parent: this.idEl,
nodeType: "span",
attributes: {
"class": "attribute-value theme-fg-color6"
}
});
if (!this.options.compact) {
this.idEl.appendChild(document.createTextNode("\""));
}
// Class attribute container.
this.classEl = createNode({
parent: this.previewEl,
nodeType: "span"
});
if (!this.options.compact) {
createNode({
parent: this.classEl,
nodeType: "span",
attributes: {
"class": "attribute-name theme-fg-color2"
},
textContent: "class"
});
this.classEl.appendChild(document.createTextNode("=\""));
} else {
createNode({
parent: this.classEl,
nodeType: "span",
attributes: {
"class": "theme-fg-color6"
},
textContent: "."
});
}
createNode({
parent: this.classEl,
nodeType: "span",
attributes: {
"class": "attribute-value theme-fg-color6"
}
});
if (!this.options.compact) {
this.classEl.appendChild(document.createTextNode("\""));
this.previewEl.appendChild(document.createTextNode(">"));
}
this.startListeners();
},
startListeners: function() {
// Init events for highlighting and selecting the node.
this.previewEl.addEventListener("mouseover", this.onPreviewMouseOver);
this.previewEl.addEventListener("mouseout", this.onPreviewMouseOut);
this.previewEl.addEventListener("click", this.onSelectNodeClick);
this.highlightNodeEl.addEventListener("click", this.onHighlightNodeClick);
// Start to listen for markupmutation events.
this.inspector.on("markupmutation", this.onMarkupMutations);
// Listen to the target node highlighter.
TargetNodeHighlighter.on("highlighted", this.onTargetHighlighterLocked);
},
stopListeners: function() {
TargetNodeHighlighter.off("highlighted", this.onTargetHighlighterLocked);
this.inspector.off("markupmutation", this.onMarkupMutations);
this.previewEl.removeEventListener("mouseover", this.onPreviewMouseOver);
this.previewEl.removeEventListener("mouseout", this.onPreviewMouseOut);
this.previewEl.removeEventListener("click", this.onSelectNodeClick);
this.highlightNodeEl.removeEventListener("click", this.onHighlightNodeClick);
},
destroy: function() {
TargetNodeHighlighter.unhighlight().catch(e => console.error(e));
this.stopListeners();
this.el.remove();
this.el = this.tagNameEl = this.idEl = this.classEl = null;
this.highlightNodeEl = this.previewEl = null;
this.nodeFront = this.inspector = this.playerFront = null;
},
get highlighterUtils() {
if (this.inspector && this.inspector.toolbox) {
return this.inspector.toolbox.highlighterUtils;
}
return null;
},
onPreviewMouseOver: function() {
if (!this.nodeFront || !this.highlighterUtils) {
return;
}
this.highlighterUtils.highlightNodeFront(this.nodeFront)
.catch(e => console.error(e));
},
onPreviewMouseOut: function() {
if (!this.nodeFront || !this.highlighterUtils) {
return;
}
this.highlighterUtils.unhighlight()
.catch(e => console.error(e));
},
onSelectNodeClick: function() {
if (!this.nodeFront) {
return;
}
this.inspector.selection.setNodeFront(this.nodeFront, "animationinspector");
},
onHighlightNodeClick: function(e) {
e.stopPropagation();
let classList = this.highlightNodeEl.classList;
let isHighlighted = classList.contains("selected");
if (isHighlighted) {
classList.remove("selected");
TargetNodeHighlighter.unhighlight().then(() => {
this.emit("target-highlighter-unlocked");
}, e => console.error(e));
} else {
classList.add("selected");
TargetNodeHighlighter.highlight(this).then(() => {
this.emit("target-highlighter-locked");
}, e => console.error(e));
}
},
onTargetHighlighterLocked: function(e, animationTargetNode) {
if (animationTargetNode !== this) {
this.highlightNodeEl.classList.remove("selected");
}
},
onMarkupMutations: function(e, mutations) {
if (!this.nodeFront || !this.playerFront) {
return;
}
for (let {target} of mutations) {
if (target === this.nodeFront) {
// Re-render with the same nodeFront to update the output.
this.render(this.playerFront);
break;
}
}
},
render: Task.async(function*(playerFront) {
this.playerFront = playerFront;
this.nodeFront = undefined;
try {
this.nodeFront = yield this.inspector.walker.getNodeFromActor(
playerFront.actorID, ["node"]);
} catch (e) {
if (!this.el) {
// The panel was destroyed in the meantime. Just log a warning.
console.warn("Cound't retrieve the animation target node, widget " +
"destroyed");
} else {
// This was an unexpected error, log it.
console.error(e);
}
return;
}
if (!this.nodeFront || !this.el) {
return;
}
let {tagName, attributes} = this.nodeFront;
this.tagNameEl.textContent = tagName.toLowerCase();
let idIndex = attributes.findIndex(({name}) => name === "id");
if (idIndex > -1 && attributes[idIndex].value) {
this.idEl.querySelector(".attribute-value").textContent =
attributes[idIndex].value;
this.idEl.style.display = "inline";
} else {
this.idEl.style.display = "none";
}
let classIndex = attributes.findIndex(({name}) => name === "class");
if (classIndex > -1 && attributes[classIndex].value) {
let value = attributes[classIndex].value;
if (this.options.compact) {
value = value.split(" ").join(".");
}
this.classEl.querySelector(".attribute-value").textContent = value;
this.classEl.style.display = "inline";
} else {
this.classEl.style.display = "none";
}
this.emit("target-retrieved");
})
};

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

@ -0,0 +1,157 @@
"use strict";
const {Cu} = require("chrome");
Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
const {
createNode,
TimeScale
} = require("devtools/client/animationinspector/utils");
const STRINGS_URI = "chrome://devtools/locale/animationinspector.properties";
const L10N = new ViewHelpers.L10N(STRINGS_URI);
/**
* UI component responsible for displaying a single animation timeline, which
* basically looks like a rectangle that shows the delay and iterations.
*/
function AnimationTimeBlock() {
EventEmitter.decorate(this);
this.onClick = this.onClick.bind(this);
}
exports.AnimationTimeBlock = AnimationTimeBlock;
AnimationTimeBlock.prototype = {
init: function(containerEl) {
this.containerEl = containerEl;
this.containerEl.addEventListener("click", this.onClick);
},
destroy: function() {
this.containerEl.removeEventListener("click", this.onClick);
this.unrender();
this.containerEl = null;
this.animation = null;
},
unrender: function() {
while (this.containerEl.firstChild) {
this.containerEl.firstChild.remove();
}
},
render: function(animation) {
this.unrender();
this.animation = animation;
let {state} = this.animation;
// Create a container element to hold the delay and iterations.
// It is positioned according to its delay (divided by the playbackrate),
// and its width is according to its duration (divided by the playbackrate).
let {x, iterationW, delayX, delayW, negativeDelayW} =
TimeScale.getAnimationDimensions(animation);
let iterations = createNode({
parent: this.containerEl,
attributes: {
"class": state.type + " iterations" +
(state.iterationCount ? "" : " infinite"),
// Individual iterations are represented by setting the size of the
// repeating linear-gradient.
"style": `left:${x}%;
width:${iterationW}%;
background-size:${100 / (state.iterationCount || 1)}% 100%;`
}
});
// The animation name is displayed over the iterations.
// Note that in case of negative delay, we push the name towards the right
// so the delay can be shown.
createNode({
parent: iterations,
attributes: {
"class": "name",
"title": this.getTooltipText(state),
// Make space for the negative delay with a margin-left.
"style": `margin-left:${negativeDelayW}%`
},
textContent: state.name
});
// Delay.
if (state.delay) {
// Negative delays need to start at 0.
createNode({
parent: iterations,
attributes: {
"class": "delay" + (state.delay < 0 ? " negative" : ""),
"style": `left:-${delayX}%;
width:${delayW}%;`
}
});
}
},
getTooltipText: function(state) {
let getTime = time => L10N.getFormatStr("player.timeLabel",
L10N.numberWithDecimals(time / 1000, 2));
let text = "";
// Adding the name.
text += getFormattedAnimationTitle({state});
text += "\n";
// Adding the delay.
text += L10N.getStr("player.animationDelayLabel") + " ";
text += getTime(state.delay);
text += "\n";
// Adding the duration.
text += L10N.getStr("player.animationDurationLabel") + " ";
text += getTime(state.duration);
text += "\n";
// Adding the iteration count (the infinite symbol, or an integer).
if (state.iterationCount !== 1) {
text += L10N.getStr("player.animationIterationCountLabel") + " ";
text += state.iterationCount ||
L10N.getStr("player.infiniteIterationCountText");
text += "\n";
}
// Adding the playback rate if it's different than 1.
if (state.playbackRate !== 1) {
text += L10N.getStr("player.animationRateLabel") + " ";
text += state.playbackRate;
text += "\n";
}
// Adding a note that the animation is running on the compositor thread if
// needed.
if (state.isRunningOnCompositor) {
text += L10N.getStr("player.runningOnCompositorTooltip");
}
return text;
},
onClick: function(e) {
e.stopPropagation();
this.emit("selected", this.animation);
}
};
/**
* Get a formatted title for this animation. This will be either:
* "some-name", "some-name : CSS Transition", or "some-name : CSS Animation",
* depending if the server provides the type, and what type it is.
* @param {AnimationPlayerFront} animation
*/
function getFormattedAnimationTitle({state}) {
// Older servers don't send the type.
return state.type
? L10N.getFormatStr("timeline." + state.type + ".nameLabel", state.name)
: state.name;
}

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

@ -0,0 +1,433 @@
"use strict";
const {
createNode,
drawGraphElementBackground,
findOptimalTimeInterval,
TimeScale
} = require("devtools/client/animationinspector/utils");
const {AnimationDetails} = require("devtools/client/animationinspector/components/animation-details");
const {AnimationTargetNode} = require("devtools/client/animationinspector/components/animation-target-node");
const {AnimationTimeBlock} = require("devtools/client/animationinspector/components/animation-time-block");
// The minimum spacing between 2 time graduation headers in the timeline (px).
const TIME_GRADUATION_MIN_SPACING = 40;
// When the container window is resized, the timeline background gets refreshed,
// but only after a timer, and the timer is reset if the window is continuously
// resized.
const TIMELINE_BACKGROUND_RESIZE_DEBOUNCE_TIMER = 50;
/**
* UI component responsible for displaying a timeline for animations.
* The timeline is essentially a graph with time along the x axis and animations
* along the y axis.
* The time is represented with a graduation header at the top and a current
* time play head.
* Animations are organized by lines, with a left margin containing the preview
* of the target DOM element the animation applies to.
* The current time play head can be moved by clicking/dragging in the header.
* when this happens, the component emits "current-data-changed" events with the
* new time and state of the timeline.
*
* @param {InspectorPanel} inspector.
*/
function AnimationsTimeline(inspector) {
this.animations = [];
this.targetNodes = [];
this.timeBlocks = [];
this.details = [];
this.inspector = inspector;
this.onAnimationStateChanged = this.onAnimationStateChanged.bind(this);
this.onScrubberMouseDown = this.onScrubberMouseDown.bind(this);
this.onScrubberMouseUp = this.onScrubberMouseUp.bind(this);
this.onScrubberMouseOut = this.onScrubberMouseOut.bind(this);
this.onScrubberMouseMove = this.onScrubberMouseMove.bind(this);
this.onAnimationSelected = this.onAnimationSelected.bind(this);
this.onWindowResize = this.onWindowResize.bind(this);
this.onFrameSelected = this.onFrameSelected.bind(this);
EventEmitter.decorate(this);
}
exports.AnimationsTimeline = AnimationsTimeline;
AnimationsTimeline.prototype = {
init: function(containerEl) {
this.win = containerEl.ownerDocument.defaultView;
this.rootWrapperEl = createNode({
parent: containerEl,
attributes: {
"class": "animation-timeline"
}
});
let scrubberContainer = createNode({
parent: this.rootWrapperEl,
attributes: {"class": "scrubber-wrapper track-container"}
});
this.scrubberEl = createNode({
parent: scrubberContainer,
attributes: {
"class": "scrubber"
}
});
this.scrubberHandleEl = createNode({
parent: this.scrubberEl,
attributes: {
"class": "scrubber-handle"
}
});
this.scrubberHandleEl.addEventListener("mousedown", this.onScrubberMouseDown);
this.timeHeaderEl = createNode({
parent: this.rootWrapperEl,
attributes: {
"class": "time-header track-container"
}
});
this.timeHeaderEl.addEventListener("mousedown", this.onScrubberMouseDown);
this.animationsEl = createNode({
parent: this.rootWrapperEl,
nodeType: "ul",
attributes: {
"class": "animations"
}
});
this.win.addEventListener("resize", this.onWindowResize);
},
destroy: function() {
this.stopAnimatingScrubber();
this.unrender();
this.win.removeEventListener("resize", this.onWindowResize);
this.timeHeaderEl.removeEventListener("mousedown",
this.onScrubberMouseDown);
this.scrubberHandleEl.removeEventListener("mousedown",
this.onScrubberMouseDown);
this.rootWrapperEl.remove();
this.animations = [];
this.rootWrapperEl = null;
this.timeHeaderEl = null;
this.animationsEl = null;
this.scrubberEl = null;
this.scrubberHandleEl = null;
this.win = null;
this.inspector = null;
},
/**
* Destroy sub-components that have been created and stored on this instance.
* @param {String} name An array of components will be expected in this[name]
* @param {Array} handlers An option list of event handlers information that
* should be used to remove these handlers.
*/
destroySubComponents: function(name, handlers = []) {
for (let component of this[name]) {
for (let {event, fn} of handlers) {
component.off(event, fn);
}
component.destroy();
}
this[name] = [];
},
unrender: function() {
for (let animation of this.animations) {
animation.off("changed", this.onAnimationStateChanged);
}
TimeScale.reset();
this.destroySubComponents("targetNodes");
this.destroySubComponents("timeBlocks");
this.destroySubComponents("details", [{
event: "frame-selected",
fn: this.onFrameSelected
}]);
this.animationsEl.innerHTML = "";
},
onWindowResize: function() {
if (this.windowResizeTimer) {
this.win.clearTimeout(this.windowResizeTimer);
}
this.windowResizeTimer = this.win.setTimeout(() => {
this.drawHeaderAndBackground();
}, TIMELINE_BACKGROUND_RESIZE_DEBOUNCE_TIMER);
},
onAnimationSelected: function(e, animation) {
let index = this.animations.indexOf(animation);
if (index === -1) {
return;
}
let el = this.rootWrapperEl;
let animationEl = el.querySelectorAll(".animation")[index];
let propsEl = el.querySelectorAll(".animated-properties")[index];
// Toggle the selected state on this animation.
animationEl.classList.toggle("selected");
propsEl.classList.toggle("selected");
// Render the details component for this animation if it was shown.
if (animationEl.classList.contains("selected")) {
this.details[index].render(animation);
this.emit("animation-selected", animation);
} else {
this.emit("animation-unselected", animation);
}
},
/**
* When a frame gets selected, move the scrubber to the corresponding position
*/
onFrameSelected: function(e, {x}) {
this.moveScrubberTo(x, true);
},
onScrubberMouseDown: function(e) {
this.moveScrubberTo(e.pageX);
this.win.addEventListener("mouseup", this.onScrubberMouseUp);
this.win.addEventListener("mouseout", this.onScrubberMouseOut);
this.win.addEventListener("mousemove", this.onScrubberMouseMove);
// Prevent text selection while dragging.
e.preventDefault();
},
onScrubberMouseUp: function() {
this.cancelTimeHeaderDragging();
},
onScrubberMouseOut: function(e) {
// Check that mouseout happened on the window itself, and if yes, cancel
// the dragging.
if (!this.win.document.contains(e.relatedTarget)) {
this.cancelTimeHeaderDragging();
}
},
cancelTimeHeaderDragging: function() {
this.win.removeEventListener("mouseup", this.onScrubberMouseUp);
this.win.removeEventListener("mouseout", this.onScrubberMouseOut);
this.win.removeEventListener("mousemove", this.onScrubberMouseMove);
},
onScrubberMouseMove: function(e) {
this.moveScrubberTo(e.pageX);
},
moveScrubberTo: function(pageX, noOffset) {
this.stopAnimatingScrubber();
// The offset needs to be in % and relative to the timeline's area (so we
// subtract the scrubber's left offset, which is equal to the sidebar's
// width).
let offset = pageX;
if (!noOffset) {
offset -= this.timeHeaderEl.offsetLeft;
}
offset = offset * 100 / this.timeHeaderEl.offsetWidth;
if (offset < 0) {
offset = 0;
}
this.scrubberEl.style.left = offset + "%";
let time = TimeScale.distanceToRelativeTime(offset);
this.emit("timeline-data-changed", {
isPaused: true,
isMoving: false,
isUserDrag: true,
time: time
});
},
render: function(animations, documentCurrentTime) {
this.unrender();
this.animations = animations;
if (!this.animations.length) {
return;
}
// Loop first to set the time scale for all current animations.
for (let {state} of animations) {
TimeScale.addAnimation(state);
}
this.drawHeaderAndBackground();
for (let animation of this.animations) {
animation.on("changed", this.onAnimationStateChanged);
// Each line contains the target animated node and the animation time
// block.
let animationEl = createNode({
parent: this.animationsEl,
nodeType: "li",
attributes: {
"class": "animation" + (animation.state.isRunningOnCompositor
? " fast-track"
: "")
}
});
// Right below the line is a hidden-by-default line for displaying the
// inline keyframes.
let detailsEl = createNode({
parent: this.animationsEl,
nodeType: "li",
attributes: {
"class": "animated-properties"
}
});
let details = new AnimationDetails();
details.init(detailsEl);
details.on("frame-selected", this.onFrameSelected);
this.details.push(details);
// Left sidebar for the animated node.
let animatedNodeEl = createNode({
parent: animationEl,
attributes: {
"class": "target"
}
});
// Draw the animated node target.
let targetNode = new AnimationTargetNode(this.inspector, {compact: true});
targetNode.init(animatedNodeEl);
targetNode.render(animation);
this.targetNodes.push(targetNode);
// Right-hand part contains the timeline itself (called time-block here).
let timeBlockEl = createNode({
parent: animationEl,
attributes: {
"class": "time-block track-container"
}
});
// Draw the animation time block.
let timeBlock = new AnimationTimeBlock();
timeBlock.init(timeBlockEl);
timeBlock.render(animation);
this.timeBlocks.push(timeBlock);
timeBlock.on("selected", this.onAnimationSelected);
}
// Use the document's current time to position the scrubber (if the server
// doesn't provide it, hide the scrubber entirely).
// Note that because the currentTime was sent via the protocol, some time
// may have gone by since then, and so the scrubber might be a bit late.
if (!documentCurrentTime) {
this.scrubberEl.style.display = "none";
} else {
this.scrubberEl.style.display = "block";
this.startAnimatingScrubber(this.wasRewound()
? TimeScale.minStartTime
: documentCurrentTime);
}
},
isAtLeastOneAnimationPlaying: function() {
return this.animations.some(({state}) => state.playState === "running");
},
wasRewound: function() {
return !this.isAtLeastOneAnimationPlaying() &&
this.animations.every(({state}) => state.currentTime === 0);
},
hasInfiniteAnimations: function() {
return this.animations.some(({state}) => !state.iterationCount);
},
startAnimatingScrubber: function(time) {
let x = TimeScale.startTimeToDistance(time);
this.scrubberEl.style.left = x + "%";
// Only stop the scrubber if it's out of bounds or all animations have been
// paused, but not if at least an animation is infinite.
let isOutOfBounds = time < TimeScale.minStartTime ||
time > TimeScale.maxEndTime;
let isAllPaused = !this.isAtLeastOneAnimationPlaying();
let hasInfinite = this.hasInfiniteAnimations();
if (isAllPaused || (isOutOfBounds && !hasInfinite)) {
this.stopAnimatingScrubber();
this.emit("timeline-data-changed", {
isPaused: !this.isAtLeastOneAnimationPlaying(),
isMoving: false,
isUserDrag: false,
time: TimeScale.distanceToRelativeTime(x)
});
return;
}
this.emit("timeline-data-changed", {
isPaused: false,
isMoving: true,
isUserDrag: false,
time: TimeScale.distanceToRelativeTime(x)
});
let now = this.win.performance.now();
this.rafID = this.win.requestAnimationFrame(() => {
if (!this.rafID) {
// In case the scrubber was stopped in the meantime.
return;
}
this.startAnimatingScrubber(time + this.win.performance.now() - now);
});
},
stopAnimatingScrubber: function() {
if (this.rafID) {
this.win.cancelAnimationFrame(this.rafID);
this.rafID = null;
}
},
onAnimationStateChanged: function() {
// For now, simply re-render the component. The animation front's state has
// already been updated.
this.render(this.animations);
},
drawHeaderAndBackground: function() {
let width = this.timeHeaderEl.offsetWidth;
let scale = width / (TimeScale.maxEndTime - TimeScale.minStartTime);
drawGraphElementBackground(this.win.document, "time-graduations",
width, scale);
// And the time graduation header.
this.timeHeaderEl.innerHTML = "";
let interval = findOptimalTimeInterval(scale, TIME_GRADUATION_MIN_SPACING);
for (let i = 0; i < width; i += interval) {
let pos = 100 * i / width;
createNode({
parent: this.timeHeaderEl,
nodeType: "span",
attributes: {
"class": "time-tick",
"style": `left:${pos}%`
},
textContent: TimeScale.formatTime(TimeScale.distanceToRelativeTime(pos))
});
}
}
};

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

@ -0,0 +1,70 @@
"use strict";
const {Cu} = require("chrome");
Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
const {createNode} = require("devtools/client/animationinspector/utils");
/**
* UI component responsible for displaying a list of keyframes.
*/
function Keyframes() {
EventEmitter.decorate(this);
this.onClick = this.onClick.bind(this);
}
exports.Keyframes = Keyframes;
Keyframes.prototype = {
init: function(containerEl) {
this.containerEl = containerEl;
this.keyframesEl = createNode({
parent: this.containerEl,
attributes: {"class": "keyframes"}
});
this.containerEl.addEventListener("click", this.onClick);
},
destroy: function() {
this.containerEl.removeEventListener("click", this.onClick);
this.keyframesEl.remove();
this.containerEl = this.keyframesEl = this.animation = null;
},
render: function({keyframes, propertyName, animation}) {
this.keyframes = keyframes;
this.propertyName = propertyName;
this.animation = animation;
this.keyframesEl.classList.add(animation.state.type);
for (let frame of this.keyframes) {
createNode({
parent: this.keyframesEl,
attributes: {
"class": "frame",
"style": `left:${frame.offset * 100}%;`,
"data-offset": frame.offset,
"data-property": propertyName,
"title": frame.value
}
});
}
},
onClick: function(e) {
// If the click happened on a frame, tell our parent about it.
if (!e.target.classList.contains("frame")) {
return;
}
e.stopPropagation();
this.emit("frame-selected", {
animation: this.animation,
propertyName: this.propertyName,
offset: parseFloat(e.target.dataset.offset),
value: e.target.getAttribute("title"),
x: e.target.offsetLeft + e.target.closest(".frames").offsetLeft
});
}
};

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

@ -0,0 +1,12 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'animation-details.js',
'animation-target-node.js',
'animation-time-block.js',
'animation-timeline.js',
'keyframes.js',
'rate-selector.js'
)

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

@ -0,0 +1,95 @@
"use strict";
const {Cu} = require("chrome");
Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
const {createNode} = require("devtools/client/animationinspector/utils");
const STRINGS_URI = "chrome://devtools/locale/animationinspector.properties";
const L10N = new ViewHelpers.L10N(STRINGS_URI);
// List of playback rate presets displayed in the timeline toolbar.
const PLAYBACK_RATES = [.1, .25, .5, 1, 2, 5, 10];
/**
* UI component responsible for displaying a playback rate selector UI.
* The rendering logic is such that a predefined list of rates is generated.
* If *all* animations passed to render share the same rate, then that rate is
* selected in the <select> element, otherwise, the empty value is selected.
* If the rate that all animations share isn't part of the list of predefined
* rates, than that rate is added to the list.
*/
function RateSelector() {
this.onRateChanged = this.onRateChanged.bind(this);
EventEmitter.decorate(this);
}
exports.RateSelector = RateSelector;
RateSelector.prototype = {
init: function(containerEl) {
this.selectEl = createNode({
parent: containerEl,
nodeType: "select",
attributes: {"class": "devtools-button"}
});
this.selectEl.addEventListener("change", this.onRateChanged);
},
destroy: function() {
this.selectEl.removeEventListener("change", this.onRateChanged);
this.selectEl.remove();
this.selectEl = null;
},
getAnimationsRates: function(animations) {
return sortedUnique(animations.map(a => a.state.playbackRate));
},
getAllRates: function(animations) {
let animationsRates = this.getAnimationsRates(animations);
if (animationsRates.length > 1) {
return PLAYBACK_RATES;
}
return sortedUnique(PLAYBACK_RATES.concat(animationsRates));
},
render: function(animations) {
let allRates = this.getAnimationsRates(animations);
let hasOneRate = allRates.length === 1;
this.selectEl.innerHTML = "";
if (!hasOneRate) {
// When the animations displayed have mixed playback rates, we can't
// select any of the predefined ones, instead, insert an empty rate.
createNode({
parent: this.selectEl,
nodeType: "option",
attributes: {value: "", selector: "true"},
textContent: "-"
});
}
for (let rate of this.getAllRates(animations)) {
let option = createNode({
parent: this.selectEl,
nodeType: "option",
attributes: {value: rate},
textContent: L10N.getFormatStr("player.playbackRateLabel", rate)
});
// If there's only one rate and this is the option for it, select it.
if (hasOneRate && rate === allRates[0]) {
option.setAttribute("selected", "true");
}
}
},
onRateChanged: function() {
let rate = parseFloat(this.selectEl.value);
if (!isNaN(rate)) {
this.emit("rate-changed", rate);
}
}
};
let sortedUnique = arr => [...new Set(arr)].sort((a, b) => a > b);

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

@ -7,7 +7,10 @@
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
DIRS += [
'components'
]
DevToolsModules(
'components.js',
'utils.js',
)

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

@ -8,9 +8,11 @@ requestLongerTimeout(2);
// Check that the timeline shows correct time graduations in the header.
const {findOptimalTimeInterval} = require("devtools/client/animationinspector/utils");
const {TimeScale} = require("devtools/client/animationinspector/components");
// Should be kept in sync with TIME_GRADUATION_MIN_SPACING in components.js
const {
findOptimalTimeInterval,
TimeScale
} = require("devtools/client/animationinspector/utils");
// Should be kept in sync with TIME_GRADUATION_MIN_SPACING in animation-timeline.js
const TIME_GRADUATION_MIN_SPACING = 40;
add_task(function*() {

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

@ -7,7 +7,7 @@
var Cu = Components.utils;
const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
const {getCssPropertyName} = require("devtools/client/animationinspector/components");
const {getCssPropertyName} = require("devtools/client/animationinspector/components/animation-details");
const TEST_DATA = [{
jsName: "alllowercase",

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

@ -7,8 +7,7 @@
var Cu = Components.utils;
const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
const {TimeScale} = require("devtools/client/animationinspector/components");
const {TimeScale} = require("devtools/client/animationinspector/utils");
const TEST_ANIMATIONS = [{
desc: "Testing a few standard animations",
animations: [{

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

@ -7,11 +7,14 @@
"use strict";
const {Cu} = require("chrome");
Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
var {loader} = Cu.import("resource://devtools/shared/Loader.jsm");
loader.lazyRequireGetter(this, "EventEmitter",
"devtools/shared/event-emitter");
const STRINGS_URI = "chrome://devtools/locale/animationinspector.properties";
const L10N = new ViewHelpers.L10N(STRINGS_URI);
// How many times, maximum, can we loop before we find the optimal time
// interval in the timeline graph.
const OPTIMAL_TIME_INTERVAL_MAX_ITERS = 100;
@ -27,6 +30,8 @@ const TIME_INTERVAL_OPACITY_MIN = 32;
// byte
const TIME_INTERVAL_OPACITY_ADD = 32;
const MILLIS_TIME_FORMAT_MAX_DURATION = 4000;
/**
* DOM node creation helper function.
* @param {Object} Options to customize the node to be created.
@ -208,3 +213,139 @@ function formatStopwatchTime(time) {
}
exports.formatStopwatchTime = formatStopwatchTime;
/**
* The TimeScale helper object is used to know which size should something be
* displayed with in the animation panel, depending on the animations that are
* currently displayed.
* If there are 5 animations displayed, and the first one starts at 10000ms and
* the last one ends at 20000ms, then this helper can be used to convert any
* time in this range to a distance in pixels.
*
* For the helper to know how to convert, it needs to know all the animations.
* Whenever a new animation is added to the panel, addAnimation(state) should be
* called. reset() can be called to start over.
*/
var TimeScale = {
minStartTime: Infinity,
maxEndTime: 0,
/**
* Add a new animation to time scale.
* @param {Object} state A PlayerFront.state object.
*/
addAnimation: function(state) {
let {previousStartTime, delay, duration,
iterationCount, playbackRate} = state;
// Negative-delayed animations have their startTimes set such that we would
// be displaying the delay outside the time window if we didn't take it into
// account here.
let relevantDelay = delay < 0 ? delay / playbackRate : 0;
previousStartTime = previousStartTime || 0;
this.minStartTime = Math.min(this.minStartTime,
previousStartTime + relevantDelay);
let length = (delay / playbackRate) +
((duration / playbackRate) *
(!iterationCount ? 1 : iterationCount));
let endTime = previousStartTime + length;
this.maxEndTime = Math.max(this.maxEndTime, endTime);
},
/**
* Reset the current time scale.
*/
reset: function() {
this.minStartTime = Infinity;
this.maxEndTime = 0;
},
/**
* Convert a startTime to a distance in %, in the current time scale.
* @param {Number} time
* @return {Number}
*/
startTimeToDistance: function(time) {
time -= this.minStartTime;
return this.durationToDistance(time);
},
/**
* Convert a duration to a distance in %, in the current time scale.
* @param {Number} time
* @return {Number}
*/
durationToDistance: function(duration) {
return duration * 100 / this.getDuration();
},
/**
* Convert a distance in % to a time, in the current time scale.
* @param {Number} distance
* @return {Number}
*/
distanceToTime: function(distance) {
return this.minStartTime + (this.getDuration() * distance / 100);
},
/**
* Convert a distance in % to a time, in the current time scale.
* The time will be relative to the current minimum start time.
* @param {Number} distance
* @return {Number}
*/
distanceToRelativeTime: function(distance) {
let time = this.distanceToTime(distance);
return time - this.minStartTime;
},
/**
* Depending on the time scale, format the given time as milliseconds or
* seconds.
* @param {Number} time
* @return {String} The formatted time string.
*/
formatTime: function(time) {
// Format in milliseconds if the total duration is short enough.
if (this.getDuration() <= MILLIS_TIME_FORMAT_MAX_DURATION) {
return L10N.getFormatStr("timeline.timeGraduationLabel", time.toFixed(0));
}
// Otherwise format in seconds.
return L10N.getFormatStr("player.timeLabel", (time / 1000).toFixed(1));
},
getDuration: function() {
return this.maxEndTime - this.minStartTime;
},
/**
* Given an animation, get the various dimensions (in %) useful to draw the
* animation in the timeline.
*/
getAnimationDimensions: function({state}) {
let start = state.previousStartTime || 0;
let duration = state.duration;
let rate = state.playbackRate;
let count = state.iterationCount;
let delay = state.delay || 0;
// The start position.
let x = this.startTimeToDistance(start + (delay / rate));
// The width for a single iteration.
let w = this.durationToDistance(duration / rate);
// The width for all iterations.
let iterationW = w * (count || 1);
// The start position of the delay.
let delayX = this.durationToDistance((delay < 0 ? 0 : delay) / rate);
// The width of the delay.
let delayW = this.durationToDistance(Math.abs(delay) / rate);
// The width of the delay if it is positive, 0 otherwise.
let negativeDelayW = delay < 0 ? delayW : 0;
return {x, w, iterationW, delayX, delayW, negativeDelayW};
}
};
exports.TimeScale = TimeScale;

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

@ -79,15 +79,13 @@ function testSetBreakpointBlankLine() {
let sourceForm = getSourceForm(gSources, COFFEE_URL);
let source = gDebugger.gThreadClient.source(sourceForm);
source.setBreakpoint({ line: 7 }, aResponse => {
source.setBreakpoint({ line: 8 }, aResponse => {
ok(!aResponse.error,
"Should be able to set a breakpoint in a coffee source file on a blank line.");
ok(aResponse.actualLocation,
"Because 7 is empty, we should have an actualLocation.");
is(aResponse.actualLocation.source.url, COFFEE_URL,
"actualLocation.actor should be source mapped to the coffee file.");
is(aResponse.actualLocation.line, 8,
"actualLocation.line should be source mapped back to 8.");
"Should be able to set a breakpoint in a coffee source file on a blank line.");
ok(!aResponse.isPending,
"Should not be a pending breakpoint.");
ok(!aResponse.actualLocation,
"Should not be a moved breakpoint.");
deferred.resolve();
});

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

@ -42,6 +42,8 @@ support-files =
[browser_rules_add-rule_04.js]
[browser_rules_add-rule_pseudo_class.js]
[browser_rules_authored.js]
[browser_rules_authored_color.js]
[browser_rules_authored_override.js]
[browser_rules_colorpicker-and-image-tooltip_01.js]
[browser_rules_colorpicker-and-image-tooltip_02.js]
[browser_rules_colorpicker-appears-on-swatch-click.js]

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

@ -6,12 +6,6 @@
// Test for as-authored styles.
add_task(function*() {
yield basicTest();
yield overrideTest();
yield colorEditingTest();
});
function* createTestContent(style) {
let content = `<style type="text/css">
${style}
@ -24,7 +18,7 @@ function* createTestContent(style) {
return view;
}
function* basicTest() {
add_task(function* () {
let view = yield createTestContent("#testid {" +
// Invalid property.
" something: random;" +
@ -52,79 +46,4 @@ function* basicTest() {
is(prop.overridden, expected[i].overridden,
"test overridden for prop " + i);
}
}
function* overrideTest() {
let gradientText1 = "(orange, blue);";
let gradientText2 = "(pink, teal);";
let view =
yield createTestContent("#testid {" +
" background-image: linear-gradient" +
gradientText1 +
" background-image: -ms-linear-gradient" +
gradientText2 +
" background-image: linear-gradient" +
gradientText2 +
"} ");
let elementStyle = view._elementStyle;
let rule = elementStyle.rules[1];
// Initially the last property should be active.
for (let i = 0; i < 3; ++i) {
let prop = rule.textProps[i];
is(prop.name, "background-image", "check the property name");
is(prop.overridden, i !== 2, "check overridden for " + i);
}
rule.textProps[2].setEnabled(false);
yield rule._applyingModifications;
// Now the first property should be active.
for (let i = 0; i < 3; ++i) {
let prop = rule.textProps[i];
is(prop.overridden || !prop.enabled, i !== 0,
"post-change check overridden for " + i);
}
}
function* colorEditingTest() {
let colors = [
{name: "hex", text: "#f0c", result: "#0f0"},
{name: "rgb", text: "rgb(0,128,250)", result: "rgb(0, 255, 0)"},
// Test case preservation.
{name: "hex", text: "#F0C", result: "#0F0"},
];
Services.prefs.setCharPref("devtools.defaultColorUnit", "authored");
for (let color of colors) {
let view = yield createTestContent("#testid {" +
" color: " + color.text + ";" +
"} ");
let cPicker = view.tooltips.colorPicker;
let swatch = getRuleViewProperty(view, "#testid", "color").valueSpan
.querySelector(".ruleview-colorswatch");
let onShown = cPicker.tooltip.once("shown");
swatch.click();
yield onShown;
let testNode = yield getNode("#testid");
yield simulateColorPickerChange(view, cPicker, [0, 255, 0, 1], {
element: testNode,
name: "color",
value: "rgb(0, 255, 0)"
});
let spectrum = yield cPicker.spectrum;
let onHidden = cPicker.tooltip.once("hidden");
EventUtils.sendKey("RETURN", spectrum.element.ownerDocument.defaultView);
yield onHidden;
is(getRuleViewPropertyValue(view, "#testid", "color"), color.result,
"changing the color preserved the unit for " + color.name);
}
}
});

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

@ -0,0 +1,59 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test for as-authored styles.
function* createTestContent(style) {
let content = `<style type="text/css">
${style}
</style>
<div id="testid" class="testclass">Styled Node</div>`;
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(content));
let {inspector, view} = yield openRuleView();
yield selectNode("#testid", inspector);
return view;
}
add_task(function* () {
let colors = [
{name: "hex", text: "#f0c", result: "#0f0"},
{name: "rgb", text: "rgb(0,128,250)", result: "rgb(0, 255, 0)"},
// Test case preservation.
{name: "hex", text: "#F0C", result: "#0F0"},
];
Services.prefs.setCharPref("devtools.defaultColorUnit", "authored");
for (let color of colors) {
let view = yield createTestContent("#testid {" +
" color: " + color.text + ";" +
"} ");
let cPicker = view.tooltips.colorPicker;
let swatch = getRuleViewProperty(view, "#testid", "color").valueSpan
.querySelector(".ruleview-colorswatch");
let onShown = cPicker.tooltip.once("shown");
swatch.click();
yield onShown;
let testNode = yield getNode("#testid");
yield simulateColorPickerChange(view, cPicker, [0, 255, 0, 1], {
element: testNode,
name: "color",
value: "rgb(0, 255, 0)"
});
let spectrum = yield cPicker.spectrum;
let onHidden = cPicker.tooltip.once("hidden");
EventUtils.sendKey("RETURN", spectrum.element.ownerDocument.defaultView);
yield onHidden;
is(getRuleViewPropertyValue(view, "#testid", "color"), color.result,
"changing the color preserved the unit for " + color.name);
}
});

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

@ -0,0 +1,54 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test for as-authored styles.
function* createTestContent(style) {
let content = `<style type="text/css">
${style}
</style>
<div id="testid" class="testclass">Styled Node</div>`;
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(content));
let {inspector, view} = yield openRuleView();
yield selectNode("#testid", inspector);
return view;
}
add_task(function* () {
let gradientText1 = "(orange, blue);";
let gradientText2 = "(pink, teal);";
let view =
yield createTestContent("#testid {" +
" background-image: linear-gradient" +
gradientText1 +
" background-image: -ms-linear-gradient" +
gradientText2 +
" background-image: linear-gradient" +
gradientText2 +
"} ");
let elementStyle = view._elementStyle;
let rule = elementStyle.rules[1];
// Initially the last property should be active.
for (let i = 0; i < 3; ++i) {
let prop = rule.textProps[i];
is(prop.name, "background-image", "check the property name");
is(prop.overridden, i !== 2, "check overridden for " + i);
}
rule.textProps[2].setEnabled(false);
yield rule._applyingModifications;
// Now the first property should be active.
for (let i = 0; i < 3; ++i) {
let prop = rule.textProps[i];
is(prop.overridden || !prop.enabled, i !== 0,
"post-change check overridden for " + i);
}
});

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

@ -841,14 +841,13 @@ MarkupView.prototype = {
// we're not viewing.
continue;
}
if (type === "attributes" || type === "characterData") {
if (type === "attributes" || type === "characterData"
|| type === "events" || type === "pseudoClassLock") {
container.update();
} else if (type === "childList" || type === "nativeAnonymousChildList") {
container.childrenDirty = true;
// Update the children to take care of changes in the markup view DOM.
this._updateChildren(container, {flash: true});
} else if (type === "pseudoClassLock") {
container.update();
}
}
@ -2547,7 +2546,6 @@ function ElementEditor(aContainer, aNode) {
let tagName = this.node.nodeName.toLowerCase();
this.tag.textContent = tagName;
this.closeTag.textContent = tagName;
this.eventNode.style.display = this.node.hasEventListeners ? "inline-block" : "none";
this.update();
this.initialized = true;
@ -2643,6 +2641,10 @@ ElementEditor.prototype = {
}
}
// Update the event bubble display
this.eventNode.style.display = this.node.hasEventListeners ?
"inline-block" : "none";
this.updateTextEditor();
},

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

@ -152,6 +152,42 @@ const TEST_DATA = [
}
]
},
// #noevents tests check that dynamically added events are properly displayed
// in the markupview
{
selector: "#noevents",
expected: []
},
{
selector: "#noevents",
beforeTest: function* (inspector, testActor) {
let nodeMutated = inspector.once("markupmutation");
yield testActor.eval("window.wrappedJSObject.addNoeventsClickHandler();");
yield nodeMutated;
},
expected: [
{
type: "click",
filename: TEST_URL + ":106",
attributes: [
"Bubbling",
"DOM2"
],
handler: 'function noeventsClickHandler(event) {\n' +
' alert("noevents has an event listener");\n' +
'}'
}
]
},
{
selector: "#noevents",
beforeTest: function* (inspector, testActor) {
let nodeMutated = inspector.once("markupmutation");
yield testActor.eval("window.wrappedJSObject.removeNoeventsClickHandler();");
yield nodeMutated;
},
expected: []
},
];
add_task(runEventPopupTests);

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

@ -102,6 +102,20 @@
alert("boundHandleEvent clicked");
}
};
function noeventsClickHandler(event) {
alert("noevents has an event listener");
};
function addNoeventsClickHandler() {
let noevents = document.getElementById("noevents");
noevents.addEventListener("click", noeventsClickHandler);
};
function removeNoeventsClickHandler() {
let noevents = document.getElementById("noevents");
noevents.removeEventListener("click", noeventsClickHandler);
};
</script>
</head>
<body onload="init();">

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

@ -7,12 +7,12 @@
* TEST_DATA array.
*/
function* runEventPopupTests() {
let {inspector} = yield addTab(TEST_URL).then(openInspector);
let {inspector, testActor} = yield addTab(TEST_URL).then(openInspector);
yield inspector.markup.expandAll();
for (let {selector, expected} of TEST_DATA) {
yield checkEventsForNode(selector, expected, inspector);
for (let test of TEST_DATA) {
yield checkEventsForNode(test, inspector, testActor);
}
// Wait for promises to avoid leaks when running this as a single test.
@ -25,12 +25,36 @@ function* runEventPopupTests() {
* Generator function that takes a selector and expected results and returns
* the event info.
*
* @param {String} selector
* Selector pointing at the node to be inspected
* @param {Object} test
* A test object should contain the following properties:
* - selector {String} a css selector targeting the node to edit
* - expected {Array} array of expected event objects
* - type {String} event type
* - filename {String} filename:line where the evt handler is defined
* - attributes {Array} array of event attributes ({String})
* - handler {String} string representation of the handler
* - beforeTest {Function} (optional) a function to execute on the page
* before running the test
* @param {InspectorPanel} inspector The instance of InspectorPanel currently
* opened
* @param {TestActorFront} testActor
*/
function* checkEventsForNode(selector, expected, inspector) {
function* checkEventsForNode(test, inspector, testActor) {
let {selector, expected, beforeTest} = test;
let container = yield getContainerForSelector(selector, inspector);
if (typeof beforeTest === "function") {
yield beforeTest(inspector, testActor);
}
let evHolder = container.elt.querySelector(".markupview-events");
if (expected.length === 0) {
// if no event is expected, simply check that the event bubble is hidden
is(evHolder.style.display, "none", "event bubble should be hidden");
return;
}
let tooltip = inspector.markup.tooltip;
yield selectNode(selector, inspector);

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

@ -44,11 +44,14 @@ function* spawnTest() {
// Expand the root and test the child items...
let receivedExpandEvent = treeRoot.once("expand");
let receivedInitialFocusEvent = treeRoot.once("focus");
EventUtils.sendMouseEvent({ type: "mousedown" }, treeRoot.target.querySelector(".arrow"));
let eventItem = yield receivedExpandEvent;
is(eventItem, treeRoot,
"The 'expand' event target is correct.");
yield receivedInitialFocusEvent;
is(document.commandDispatcher.focusedElement, treeRoot.target,
"The root node is now focused.");

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

@ -863,6 +863,8 @@ var NodeFront = protocol.FrontClass(NodeActor, {
this._form.incompleteValue = change.incompleteValue;
} else if (change.type === "pseudoClassLock") {
this._form.pseudoClassLocks = change.pseudoClassLocks;
} else if (change.type === "events") {
this._form.hasEventListeners = change.hasEventListeners;
}
},
@ -1348,6 +1350,32 @@ var WalkerActor = protocol.ActorClass({
this.layoutChangeObserver.on("reflows", this._onReflows);
this._onResize = this._onResize.bind(this);
this.layoutChangeObserver.on("resize", this._onResize);
this._onEventListenerChange = this._onEventListenerChange.bind(this);
eventListenerService.addListenerChangeListener(this._onEventListenerChange);
},
/**
* Callback for eventListenerService.addListenerChangeListener
* @param nsISimpleEnumerator changesEnum
* enumerator of nsIEventListenerChange
*/
_onEventListenerChange: function(changesEnum) {
let changes = changesEnum.enumerate();
while (changes.hasMoreElements()) {
let current = changes.getNext().QueryInterface(Ci.nsIEventListenerChange);
let target = current.target;
if (this._refMap.has(target)) {
let actor = this._refMap.get(target);
let mutation = {
type: "events",
target: actor.actorID,
hasEventListeners: actor._hasEventListeners
};
this.queueMutation(mutation);
}
}
},
// Returns the JSON representation of this object over the wire.
@ -1414,6 +1442,9 @@ var WalkerActor = protocol.ActorClass({
this.layoutChangeObserver = null;
releaseLayoutChangesObserver(this.tabActor);
eventListenerService.removeListenerChangeListener(
this._onEventListenerChange);
this.onMutations = null;
this.tabActor = null;

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

@ -2724,245 +2724,93 @@ SourceActor.prototype = {
* @returns A Promise that resolves to the given BreakpointActor.
*/
_setBreakpoint: function (actor) {
let { originalLocation } = actor;
let { originalSourceActor, originalLine, originalColumn } = originalLocation;
const { originalLocation } = actor;
const { originalLine, originalSourceActor } = originalLocation;
return this._setBreakpointAtOriginalLocation(actor, originalLocation)
.then((actualLocation) => {
if (actualLocation) {
return actualLocation;
}
// There were no scripts that matched the given location, so we need to
// perform breakpoint sliding. We try to slide the breakpoint by column
// first, and if that fails, by line instead.
if (!this.isSourceMapped) {
if (originalColumn !== undefined) {
// To perform breakpoint sliding for column breakpoints, we need to
// build a map from column numbers to a list of entry points for each
// column, implemented as a sparse array. An entry point is a (script,
// offsets) pair, and represents all offsets in that script that are
// entry points for the corresponding column.
let columnToEntryPointsMap = [];
// Iterate over all scripts that correspond to this source actor and
// line number.
let scripts = this.scripts.getScriptsBySourceActor(this, originalLine);
for (let script of scripts) {
let columnToOffsetMap = script.getAllColumnOffsets()
.filter(({ lineNumber }) => {
return lineNumber === originalLine;
})
// Iterate over each column, and add their list of offsets to the
// map from column numbers to entry points by forming a (script,
// offsets) pair, where script is the current script, and offsets is
// the list of offsets for the current column.
for (let { columnNumber: column, offset } of columnToOffsetMap) {
let entryPoints = columnToEntryPointsMap[column];
if (!entryPoints) {
// We dont have a list of entry points for the current column
// number yet, so create it and add it to the map.
entryPoints = [];
columnToEntryPointsMap[column] = entryPoints;
}
entryPoints.push({ script, offsets: [offset] });
}
}
// Now that we have a map from column numbers to a list of entry points
// for each column, we can use it to perform breakpoint sliding. Start
// at the original column of the breakpoint actor, and keep
// incrementing it by one, until either we find a line that has at
// least one entry point, or we go past the last column in the map.
//
// Note that by computing the entire map up front, and implementing it
// as a sparse array, we can easily tell when we went past the last
// column in the map.
let actualColumn = originalColumn + 1;
while (actualColumn < columnToEntryPointsMap.length) {
let entryPoints = columnToEntryPointsMap[actualColumn];
if (entryPoints) {
setBreakpointAtEntryPoints(actor, entryPoints);
return new OriginalLocation(
originalSourceActor,
originalLine,
actualColumn
);
}
++actualColumn;
}
return originalLocation;
} else {
// To perform breakpoint sliding for line breakpoints, we need to
// build a map from line numbers to a list of entry points for each
// line, implemented as a sparse array. An entry point is a (script,
// offsets) pair, and represents all offsets in that script that are
// entry points for the corresponding line.
let lineToEntryPointsMap = [];
// Iterate over all scripts that correspond to this source actor.
let scripts = this.scripts.getScriptsBySourceActor(this);
for (let script of scripts) {
// Get all offsets for each line in the current script. This returns
// a map from line numbers fo a list of offsets for each line,
// implemented as a sparse array.
let lineToOffsetsMap = script.getAllOffsets();
// Iterate over each line, and add their list of offsets to the map
// from line numbers to entry points by forming a (script, offsets)
// pair, where script is the current script, and offsets is the list
// of offsets for the current line.
for (let line = 0; line < lineToOffsetsMap.length; ++line) {
let offsets = lineToOffsetsMap[line];
if (offsets) {
let entryPoints = lineToEntryPointsMap[line];
if (!entryPoints) {
// We dont have a list of entry points for the current line
// number yet, so create it and add it to the map.
entryPoints = [];
lineToEntryPointsMap[line] = entryPoints;
}
entryPoints.push({ script, offsets });
}
}
}
// Now that we have a map from line numbers to a list of entry points
// for each line, we can use it to perform breakpoint sliding. Start
// at the original line of the breakpoint actor, and keep incrementing
// it by one, until either we find a line that has at least one entry
// point, or we go past the last line in the map.
//
// Note that by computing the entire map up front, and implementing it
// as a sparse array, we can easily tell when we went past the last
// line in the map.
let actualLine = originalLine + 1;
while (actualLine < lineToEntryPointsMap.length) {
let entryPoints = lineToEntryPointsMap[actualLine];
if (entryPoints) {
setBreakpointAtEntryPoints(actor, entryPoints);
break;
}
++actualLine;
}
if (actualLine >= lineToEntryPointsMap.length) {
// We went past the last line in the map, so breakpoint sliding
// failed. Keep the BreakpointActor in the BreakpointActorMap as a
// pending breakpoint, so we can try again whenever a new script is
// introduced.
return originalLocation;
}
return new OriginalLocation(
originalSourceActor,
actualLine
);
}
} else {
let slideByColumn = (actualColumn) => {
return this.sources.getAllGeneratedLocations(new OriginalLocation(
this,
originalLine,
actualColumn
)).then((generatedLocations) => {
// Because getAllGeneratedLocations will always return the list of
// generated locations for the closest column that is greater than
// the one we are searching for if no exact match can be found, if
// the list of generated locations is empty, we've reached the end
// of the original line, and sliding continues by line.
if (generatedLocations.length === 0) {
return slideByLine(originalLine + 1);
}
// If at least one script has an offset that matches one of the
// generated locations in the list, then breakpoint sliding
// succeeded.
if (this._setBreakpointAtAllGeneratedLocations(actor, generatedLocations)) {
return this.threadActor.sources.getOriginalLocation(generatedLocations[0]);
}
// Try the next column in the original source.
return slideByColumn(actualColumn + 1);
});
};
let slideByLine = (actualLine) => {
return this.sources.getAllGeneratedLocations(new OriginalLocation(
this,
actualLine
)).then((generatedLocations) => {
// Because getAllGeneratedLocations will always return the list of
// generated locations for the closest line that is greater than
// the one we are searching for if no exact match can be found, if
// the list of generated locations is empty, we've reached the end
// of the original source, and breakpoint sliding failed.
if (generatedLocations.length === 0) {
return originalLocation;
}
// If at least one script has an offset that matches one of the
// generated locations in the list, then breakpoint sliding
// succeeded.
if (this._setBreakpointAtAllGeneratedLocations(actor, generatedLocations)) {
return this.threadActor.sources.getOriginalLocation(generatedLocations[0]);
}
// Try the next line in the original source.
return slideByLine(actualLine + 1);
});
};
if (originalColumn !== undefined) {
return slideByColumn(originalColumn + 1);
} else {
return slideByLine(originalLine + 1);
}
}
}).then((actualLocation) => {
// If the actual location on which the BreakpointActor ended up being
// set differs from the original line that was requested, both the
// BreakpointActor and the BreakpointActorMap need to be updated
// accordingly.
if (!actualLocation.equals(originalLocation)) {
let existingActor = this.breakpointActorMap.getActor(actualLocation);
if (existingActor) {
actor.onDelete();
this.breakpointActorMap.deleteActor(originalLocation);
actor = existingActor;
} else {
this.breakpointActorMap.deleteActor(originalLocation);
actor.originalLocation = actualLocation;
this.breakpointActorMap.setActor(actualLocation, actor);
}
}
return actor;
});
},
_setBreakpointAtOriginalLocation: function (actor, originalLocation) {
if (!this.isSourceMapped) {
if (!this._setBreakpointAtGeneratedLocation(
actor,
GeneratedLocation.fromOriginalLocation(originalLocation)
)) {
return promise.resolve(null);
}
const scripts = this.scripts.getScriptsBySourceActorAndLine(
this,
originalLine
);
return promise.resolve(originalLocation);
} else {
return this.sources.getAllGeneratedLocations(originalLocation)
.then((generatedLocations) => {
if (!this._setBreakpointAtAllGeneratedLocations(
actor,
generatedLocations
)) {
return null;
// Never do breakpoint sliding for column breakpoints.
// Additionally, never do breakpoint sliding if no scripts
// exist on this line.
//
// Sliding can go horribly wrong if we always try to find the
// next line with valid entry points in the entire file.
// Scripts may be completely GCed and we never knew they
// existed, so we end up sliding through whole functions to
// the user's bewilderment.
//
// We can slide reliably if any scripts exist, however, due
// to how scripts are kept alive. A parent Debugger.Script
// keeps all of its children alive, so as long as we have a
// valid script, we can slide through it and know we won't
// slide through any of its child scripts. Additionally, if a
// script gets GCed, that means that all parents scripts are
// GCed as well, and no scripts will exist on those lines
// anymore. We will never slide through a GCed script.
if (originalLocation.originalColumn || scripts.length === 0) {
return promise.resolve(actor);
}
return this.threadActor.sources.getOriginalLocation(generatedLocations[0]);
// Find the script that spans the largest amount of code to
// determine the bounds for sliding.
const largestScript = scripts.reduce((largestScript, script) => {
if (script.lineCount > largestScript.lineCount) {
return script;
}
return largestScript;
});
const maxLine = largestScript.startLine + largestScript.lineCount - 1;
let actualLine = originalLine;
for (; actualLine <= maxLine; actualLine++) {
const loc = new GeneratedLocation(this, actualLine);
if (this._setBreakpointAtGeneratedLocation(actor, loc)) {
break;
}
}
// The above loop should never complete. We only did breakpoint sliding
// because we found scripts on the line we started from,
// which means there must be valid entry points somewhere
// within those scripts.
assert(
actualLine <= maxLine,
"Could not find any entry points to set a breakpoint on, " +
"even though I was told a script existed on the line I started " +
"the search with."
);
// Update the actor to use the new location (reusing a
// previous breakpoint if it already exists on that line).
const actualLocation = new OriginalLocation(originalSourceActor, actualLine);
const existingActor = this.breakpointActorMap.getActor(actualLocation);
this.breakpointActorMap.deleteActor(originalLocation);
if (existingActor) {
actor.onDelete();
actor = existingActor;
} else {
actor.originalLocation = actualLocation;
this.breakpointActorMap.setActor(actualLocation, actor);
}
}
return promise.resolve(actor);
} else {
return this.sources.getAllGeneratedLocations(originalLocation).then((generatedLocations) => {
this._setBreakpointAtAllGeneratedLocations(
actor,
generatedLocations
);
return actor;
});
}
},

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

@ -632,8 +632,8 @@ TabSources.prototype = {
originalColumn
} = originalLocation;
let source = originalSourceActor.source ||
originalSourceActor.generatedSource;
let source = (originalSourceActor.source ||
originalSourceActor.generatedSource);
return this.fetchSourceMap(source).then((map) => {
if (map) {

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

@ -69,6 +69,7 @@ skip-if = buildapp == 'mulet'
[test_inspector-hide.html]
[test_inspector-insert.html]
[test_inspector-mutations-attr.html]
[test_inspector-mutations-events.html]
[test_inspector-mutations-childlist.html]
[test_inspector-mutations-frameload.html]
[test_inspector-mutations-value.html]

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

@ -0,0 +1,184 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1157469
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1157469</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
<script type="application/javascript;version=1.8">
window.onload = function() {
const Cu = Components.utils;
Cu.import("resource://gre/modules/devtools/Loader.jsm");
const {InspectorFront} =
devtools.require("devtools/server/actors/inspector");
SimpleTest.waitForExplicitFinish();
let inspectee = null;
let inspector = null;
let walker = null;
let eventListener1 = function () {};
let eventListener2 = function () {};
let eventNode1;
let eventNode2;
let eventFront1;
let eventFront2;
addAsyncTest(function* setup() {
info ("Setting up inspector and walker actors.");
let url = document.getElementById("inspectorContent").href;
yield new Promise(resolve => {
attachURL(url, function(err, client, tab, doc) {
inspectee = doc;
inspector = InspectorFront(client, tab);
resolve();
});
});
walker = yield inspector.getWalker();
ok(walker, "getWalker() should return an actor.");
runNextTest();
});
addAsyncTest(function* setupEventTest() {
eventNode1 = inspectee.querySelector("#a")
eventNode2 = inspectee.querySelector("#b")
eventFront1 = yield walker.querySelector(walker.rootNode, "#a");
eventFront2 = yield walker.querySelector(walker.rootNode, "#b");
runNextTest();
});
addAsyncTest(function* testChangeEventListenerOnSingleNode() {
checkNodesHaveNoEventListener();
info("add event listener on a single node");
eventNode1.addEventListener("click", eventListener1);
let mutations = yield waitForMutations();
is(mutations.length, 1, "one mutation expected");
is(mutations[0].target, eventFront1, "mutation targets eventFront1");
is(mutations[0].type, "events", "mutation type is events");
is(mutations[0].hasEventListeners, true, "mutation target should have event listeners");
is(eventFront1.hasEventListeners, true, "eventFront1 should have event listeners");
info("remove event listener on a single node");
eventNode1.removeEventListener("click", eventListener1);
mutations = yield waitForMutations();
is(mutations.length, 1, "one mutation expected");
is(mutations[0].target, eventFront1, "mutation targets eventFront1");
is(mutations[0].type, "events", "mutation type is events");
is(mutations[0].hasEventListeners, false, "mutation target should have no event listeners");
is(eventFront1.hasEventListeners, false, "eventFront1 should have no event listeners");
info("perform several event listener changes on a single node")
eventNode1.addEventListener("click", eventListener1);
eventNode1.addEventListener("click", eventListener2);
eventNode1.removeEventListener("click", eventListener1);
eventNode1.removeEventListener("click", eventListener2);
mutations = yield waitForMutations();
is(mutations.length, 1, "one mutation expected");
is(mutations[0].target, eventFront1, "mutation targets eventFront1");
is(mutations[0].type, "events", "mutation type is events");
is(mutations[0].hasEventListeners, false, "no event listener expected on mutation target");
is(eventFront1.hasEventListeners, false, "no event listener expected on node");
runNextTest();
});
addAsyncTest(function* testChangeEventsOnSeveralNodes() {
checkNodesHaveNoEventListener();
info("add event listeners on both nodes");
eventNode1.addEventListener("click", eventListener1);
eventNode2.addEventListener("click", eventListener2);
let mutations = yield waitForMutations();
is(mutations.length, 2, "two mutations expected, one for each modified node");
// first mutation
is(mutations[0].target, eventFront1, "first mutation targets eventFront1");
is(mutations[0].type, "events", "mutation type is events");
is(mutations[0].hasEventListeners, true, "mutation target should have event listeners");
is(eventFront1.hasEventListeners, true, "eventFront1 should have event listeners");
// second mutation
is(mutations[1].target, eventFront2, "second mutation targets eventFront2");
is(mutations[1].type, "events", "mutation type is events");
is(mutations[1].hasEventListeners, true, "mutation target should have event listeners");
is(eventFront2.hasEventListeners, true, "eventFront1 should have event listeners");
info("remove event listeners on both nodes");
eventNode1.removeEventListener("click", eventListener1);
eventNode2.removeEventListener("click", eventListener2);
mutations = yield waitForMutations();
is(mutations.length, 2, "one mutation registered for event listener change");
// first mutation
is(mutations[0].target, eventFront1, "first mutation targets eventFront1");
is(mutations[0].type, "events", "mutation type is events");
is(mutations[0].hasEventListeners, false, "mutation target should have no event listeners");
is(eventFront1.hasEventListeners, false, "eventFront2 should have no event listeners");
// second mutation
is(mutations[1].target, eventFront2, "second mutation targets eventFront2");
is(mutations[1].type, "events", "mutation type is events");
is(mutations[1].hasEventListeners, false, "mutation target should have no event listeners");
is(eventFront2.hasEventListeners, false, "eventFront2 should have no event listeners");
runNextTest();
});
addAsyncTest(function* testRemoveMissingEvent() {
checkNodesHaveNoEventListener();
info("try to remove an event listener not previously added");
eventNode1.removeEventListener("click", eventListener1);
info("set any attribute on the node to trigger a mutation")
eventNode1.setAttribute("data-attr", "somevalue");
let mutations = yield waitForMutations();
is(mutations.length, 1, "expect only one mutation");
isnot(mutations.type, "events", "mutation type should not be events");
runNextTest();
});
function checkNodesHaveNoEventListener() {
is(eventFront1.hasEventListeners, false, "eventFront1 hasEventListeners should be false");
is(eventFront2.hasEventListeners, false, "eventFront2 hasEventListeners should be false");
};
function waitForMutations() {
return new Promise(resolve => {
walker.once("mutations", mutations => {
resolve(mutations);
});
});
}
runNextTest();
}
</script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1157469">Mozilla Bug 1157469</a>
<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

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

@ -65,9 +65,11 @@ function test_simple_breakpoint()
});
});
Components.utils.evalInSandbox("var line0 = Error().lineNumber;\n" +
"debugger;\n" + // line0 + 1
"var a = 1;\n" + // line0 + 2
"var b = 2;\n", // line0 + 3
gDebuggee);
Cu.evalInSandbox(
"var line0 = Error().lineNumber;\n" +
"debugger;\n" + // line0 + 1
"var a = 1;\n" + // line0 + 2
"var b = 2;\n", // line0 + 3
gDebuggee
);
}

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

@ -57,8 +57,11 @@ function test_breakpoint_running()
});
});
gDebuggee.eval("var line0 = Error().lineNumber;\n" +
"debugger;\n" +
"var a = 1;\n" + // line0 + 2
"var b = 2;\n"); // line0 + 3
Cu.evalInSandbox(
"var line0 = Error().lineNumber;\n" +
"debugger;\n" +
"var a = 1;\n" + // line0 + 2
"var b = 2;\n", // line0 + 3
gDebuggee
);
}

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

@ -2,7 +2,9 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Check that setting a breakpoint in a line without code will skip forward.
* Check that setting a breakpoint on a line without code will skip
* forward when we know the script isn't GCed (the debugger is connected,
* so it's kept alive).
*/
var gDebuggee;
@ -65,14 +67,18 @@ function test_skip_breakpoint()
});
});
// Continue until the breakpoint is hit.
gThreadClient.resume();
});
});
gDebuggee.eval("var line0 = Error().lineNumber;\n" +
"debugger;\n" + // line0 + 1
"var a = 1;\n" + // line0 + 2
"// A comment.\n" + // line0 + 3
"var b = 2;"); // line0 + 4
// Use `evalInSandbox` to make the debugger treat it as normal
// globally-scoped code, where breakpoint sliding rules apply.
Cu.evalInSandbox(
"var line0 = Error().lineNumber;\n" +
"debugger;\n" + // line0 + 1
"var a = 1;\n" + // line0 + 2
"// A comment.\n" + // line0 + 3
"var b = 2;", // line0 + 4
gDebuggee
);
}

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

@ -67,11 +67,14 @@ function test_child_breakpoint()
});
gDebuggee.eval("var line0 = Error().lineNumber;\n" +
"function foo() {\n" + // line0 + 1
" this.a = 1;\n" + // line0 + 2
" this.b = 2;\n" + // line0 + 3
"}\n" + // line0 + 4
"debugger;\n" + // line0 + 5
"foo();\n"); // line0 + 6
Cu.evalInSandbox(
"var line0 = Error().lineNumber;\n" +
"function foo() {\n" + // line0 + 1
" this.a = 1;\n" + // line0 + 2
" this.b = 2;\n" + // line0 + 3
"}\n" + // line0 + 4
"debugger;\n" + // line0 + 5
"foo();\n", // line0 + 6
gDebuggee
);
}

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

@ -2,7 +2,7 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Check that setting a breakpoint in a line without code in a child scrip
* Check that setting a breakpoint in a line without code in a child script
* will skip forward.
*/
@ -68,12 +68,15 @@ function test_child_skip_breakpoint()
});
});
gDebuggee.eval("var line0 = Error().lineNumber;\n" +
"function foo() {\n" + // line0 + 1
" this.a = 1;\n" + // line0 + 2
" // A comment.\n" + // line0 + 3
" this.b = 2;\n" + // line0 + 4
"}\n" + // line0 + 5
"debugger;\n" + // line0 + 6
"foo();\n"); // line0 + 7
Cu.evalInSandbox(
"var line0 = Error().lineNumber;\n" +
"function foo() {\n" + // line0 + 1
" this.a = 1;\n" + // line0 + 2
" // A comment.\n" + // line0 + 3
" this.b = 2;\n" + // line0 + 4
"}\n" + // line0 + 5
"debugger;\n" + // line0 + 6
"foo();\n", // line0 + 7
gDebuggee
);
}

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

@ -69,18 +69,21 @@ function test_nested_breakpoint()
});
gDebuggee.eval("var line0 = Error().lineNumber;\n" +
"function foo() {\n" + // line0 + 1
" function bar() {\n" + // line0 + 2
" function baz() {\n" + // line0 + 3
" this.a = 1;\n" + // line0 + 4
" // A comment.\n" + // line0 + 5
" this.b = 2;\n" + // line0 + 6
" }\n" + // line0 + 7
" baz();\n" + // line0 + 8
" }\n" + // line0 + 9
" bar();\n" + // line0 + 10
"}\n" + // line0 + 11
"debugger;\n" + // line0 + 12
"foo();\n"); // line0 + 13
Cu.evalInSandbox(
"var line0 = Error().lineNumber;\n" +
"function foo() {\n" + // line0 + 1
" function bar() {\n" + // line0 + 2
" function baz() {\n" + // line0 + 3
" this.a = 1;\n" + // line0 + 4
" // A comment.\n" + // line0 + 5
" this.b = 2;\n" + // line0 + 6
" }\n" + // line0 + 7
" baz();\n" + // line0 + 8
" }\n" + // line0 + 9
" bar();\n" + // line0 + 10
"}\n" + // line0 + 11
"debugger;\n" + // line0 + 12
"foo();\n", // line0 + 13
gDebuggee
)
}

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

@ -68,15 +68,18 @@ function test_second_child_skip_breakpoint()
});
});
gDebuggee.eval("var line0 = Error().lineNumber;\n" +
"function foo() {\n" + // line0 + 1
" bar();\n" + // line0 + 2
"}\n" + // line0 + 3
"function bar() {\n" + // line0 + 4
" this.a = 1;\n" + // line0 + 5
" // A comment.\n" + // line0 + 6
" this.b = 2;\n" + // line0 + 7
"}\n" + // line0 + 8
"debugger;\n" + // line0 + 9
"foo();\n"); // line0 + 10
Cu.evalInSandbox(
"var line0 = Error().lineNumber;\n" +
"function foo() {\n" + // line0 + 1
" bar();\n" + // line0 + 2
"}\n" + // line0 + 3
"function bar() {\n" + // line0 + 4
" this.a = 1;\n" + // line0 + 5
" // A comment.\n" + // line0 + 6
" this.b = 2;\n" + // line0 + 7
"}\n" + // line0 + 8
"debugger;\n" + // line0 + 9
"foo();\n", // line0 + 10
gDebuggee
)
}

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

@ -48,8 +48,7 @@ function test_child_skip_breakpoint()
let location = { line: gDebuggee.line0 + 3 };
source.setBreakpoint(location, function (aResponse, bpClient) {
// Check that the breakpoint has properly skipped forward one
// line.
// Check that the breakpoint has properly skipped forward one line.
do_check_eq(aResponse.actualLocation.source.actor, source.actor);
do_check_eq(aResponse.actualLocation.line, location.line + 1);
@ -78,13 +77,20 @@ function test_child_skip_breakpoint()
}
});
gDebuggee.eval("var line0 = Error().lineNumber;\n" +
"function foo() {\n" + // line0 + 1
" this.a = 1;\n" + // line0 + 2
" // A comment.\n" + // line0 + 3
" this.b = 2;\n" + // line0 + 3
"}\n"); // line0 + 4
gDebuggee.eval("var line1 = Error().lineNumber;\n" +
"debugger;\n" + // line1 + 1
"foo();\n"); // line1 + 2
Cu.evalInSandbox("var line0 = Error().lineNumber;\n" +
"function foo() {\n" + // line0 + 1
" this.a = 1;\n" + // line0 + 2
" // A comment.\n" + // line0 + 3
" this.b = 2;\n" + // line0 + 4
"}\n", // line0 + 5
gDebuggee,
"1.7",
"script1.js");
Cu.evalInSandbox("var line1 = Error().lineNumber;\n" +
"debugger;\n" + // line1 + 1
"foo();\n", // line1 + 2
gDebuggee,
"1.7",
"script2.js");
}

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

@ -71,15 +71,16 @@ function test_remove_breakpoint()
});
gDebuggee.eval("var line0 = Error().lineNumber;\n" +
"function foo(stop) {\n" + // line0 + 1
" this.a = 1;\n" + // line0 + 2
" if (stop) return;\n" + // line0 + 3
" delete this.a;\n" + // line0 + 4
" foo(true);\n" + // line0 + 5
"}\n" + // line0 + 6
"debugger;\n" + // line1 + 7
"foo();\n"); // line1 + 8
Cu.evalInSandbox("var line0 = Error().lineNumber;\n" +
"function foo(stop) {\n" + // line0 + 1
" this.a = 1;\n" + // line0 + 2
" if (stop) return;\n" + // line0 + 3
" delete this.a;\n" + // line0 + 4
" foo(true);\n" + // line0 + 5
"}\n" + // line0 + 6
"debugger;\n" + // line1 + 7
"foo();\n", // line1 + 8
gDebuggee);
if (!done) {
do_check_true(false);
}

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

@ -79,10 +79,11 @@ function test_child_breakpoint()
});
gDebuggee.eval("var line0 = Error().lineNumber;\n" +
"debugger;\n" + // line0 + 1
"var a, i = 0;\n" + // line0 + 2
"for (i = 1; i <= 2; i++) {\n" + // line0 + 3
" a = i;\n" + // line0 + 4
"}\n"); // line0 + 5
Cu.evalInSandbox("var line0 = Error().lineNumber;\n" +
"debugger;\n" + // line0 + 1
"var a, i = 0;\n" + // line0 + 2
"for (i = 1; i <= 2; i++) {\n" + // line0 + 3
" a = i;\n" + // line0 + 4
"}\n", // line0 + 5
gDebuggee);
}

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

@ -80,8 +80,9 @@ function test_child_breakpoint()
});
gDebuggee.eval("var line0 = Error().lineNumber;\n" +
"debugger;\n" + // line0 + 1
"var a = { b: 1, f: function() { return 2; } };\n" + // line0+2
"var res = a.f();\n"); // line0 + 3
Cu.evalInSandbox("var line0 = Error().lineNumber;\n" +
"debugger;\n" + // line0 + 1
"var a = { b: 1, f: function() { return 2; } };\n" + // line0+2
"var res = a.f();\n", // line0 + 3
gDebuggee);
}

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

@ -55,14 +55,15 @@ function test_child_skip_breakpoint()
});
gDebuggee.eval("var line0 = Error().lineNumber;\n" +
"function foo() {\n" + // line0 + 1
" this.a = 1;\n" + // line0 + 2
" // A comment.\n" + // line0 + 3
" this.b = 2;\n" + // line0 + 4
"}\n" + // line0 + 5
"debugger;\n" + // line0 + 6
"foo();\n"); // line0 + 7
Cu.evalInSandbox("var line0 = Error().lineNumber;\n" +
"function foo() {\n" + // line0 + 1
" this.a = 1;\n" + // line0 + 2
" // A comment.\n" + // line0 + 3
" this.b = 2;\n" + // line0 + 4
"}\n" + // line0 + 5
"debugger;\n" + // line0 + 6
"foo();\n", // line0 + 7
gDebuggee);
}
// Set many breakpoints at the same location.

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

@ -104,12 +104,13 @@ function test_simple_breakpoint()
});
});
gDebuggee.eval("var line0 = Error().lineNumber;\n" +
"function foo() {\n" + // line0 + 1
" this.a = 1;\n" + // line0 + 2 <-- Breakpoint is set here.
"}\n" + // line0 + 3
"debugger;\n" + // line0 + 4
"foo();\n" + // line0 + 5
"debugger;\n" + // line0 + 6
"var b = 2;\n"); // line0 + 7
Cu.evalInSandbox("var line0 = Error().lineNumber;\n" +
"function foo() {\n" + // line0 + 1
" this.a = 1;\n" + // line0 + 2 <-- Breakpoint is set here.
"}\n" + // line0 + 3
"debugger;\n" + // line0 + 4
"foo();\n" + // line0 + 5
"debugger;\n" + // line0 + 6
"var b = 2;\n", // line0 + 7
gDebuggee);
}

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

@ -102,12 +102,13 @@ function test_simple_breakpoint()
});
});
gDebuggee.eval("var line0 = Error().lineNumber;\n" +
"function foo() {\n" + // line0 + 1
" this.a = 1;\n" + // line0 + 2 <-- Breakpoint is set here.
"}\n" + // line0 + 3
"debugger;\n" + // line0 + 4
"foo();\n" + // line0 + 5
"debugger;\n" + // line0 + 6
"var b = 2;\n"); // line0 + 7
Cu.evalInSandbox("var line0 = Error().lineNumber;\n" +
"function foo() {\n" + // line0 + 1
" this.a = 1;\n" + // line0 + 2 <-- Breakpoint is set here.
"}\n" + // line0 + 3
"debugger;\n" + // line0 + 4
"foo();\n" + // line0 + 5
"debugger;\n" + // line0 + 6
"var b = 2;\n", // line0 + 7
gDebuggee);
}

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

@ -1,57 +0,0 @@
"use strict";
var SOURCE_URL = getFileUrl("setBreakpoint-on-column-with-no-offsets-in-gcd-script.js");
function run_test() {
return Task.spawn(function* () {
do_test_pending();
let global = testGlobal("test");
loadSubScript(SOURCE_URL, global);
Cu.forceGC();
DebuggerServer.registerModule("xpcshell-test/testactors");
DebuggerServer.init(() => true);
DebuggerServer.addTestGlobal(global);
let client = new DebuggerClient(DebuggerServer.connectPipe());
yield connect(client);
let tab = yield findTab(client, "test");
let [, tabClient] = yield attachTab(client, tab);
let [, threadClient] = yield attachThread(tabClient);
yield resume(threadClient);
let source = yield findSource(threadClient, SOURCE_URL);
let sourceClient = threadClient.source(source);
let location = { line: 6, column: 17 };
let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location);
do_check_true(packet.isPending);
do_check_false("actualLocation" in packet);
executeSoon(function () {
reload(tabClient).then(function () {
loadSubScript(SOURCE_URL, global);
});
});
packet = yield waitForPaused(threadClient);
do_check_eq(packet.type, "paused");
let why = packet.why;
do_check_eq(why.type, "breakpoint");
do_check_eq(why.actors.length, 1);
do_check_eq(why.actors[0], breakpointClient.actor);
let frame = packet.frame;
let where = frame.where;
do_check_eq(where.source.actor, source.actor);
do_check_eq(where.line, location.line);
do_check_eq(where.column, location.column + 7);
let variables = frame.environment.bindings.variables;
do_check_eq(variables.a.value, 1);
do_check_eq(variables.b.value.type, "undefined");
do_check_eq(variables.c.value.type, "undefined");
yield resume(threadClient);
yield close(client);
do_test_finished();
});
}

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

@ -1,55 +0,0 @@
"use strict";
var SOURCE_URL = getFileUrl("setBreakpoint-on-column-with-no-offsets-at-end-of-script.js");
function run_test() {
return Task.spawn(function* () {
do_test_pending();
DebuggerServer.registerModule("xpcshell-test/testactors");
DebuggerServer.init(() => true);
let global = createTestGlobal("test");
DebuggerServer.addTestGlobal(global);
let client = new DebuggerClient(DebuggerServer.connectPipe());
yield connect(client);
let { tabs } = yield listTabs(client);
let tab = findTab(tabs, "test");
let [, tabClient] = yield attachTab(client, tab);
let [, threadClient] = yield attachThread(tabClient);
yield resume(threadClient);
let promise = waitForNewSource(threadClient, SOURCE_URL);
loadSubScript(SOURCE_URL, global);
let { source } = yield promise;
let sourceClient = threadClient.source(source);
let location = { line: 4, column: 38 };
let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location);
do_check_false(packet.isPending);
do_check_true("actualLocation" in packet);
let actualLocation = packet.actualLocation;
do_check_eq(actualLocation.line, 4);
do_check_eq(actualLocation.column, 41);
packet = yield executeOnNextTickAndWaitForPause(function () {
Cu.evalInSandbox("f()", global);
}, client);
do_check_eq(packet.type, "paused");
let why = packet.why;
do_check_eq(why.type, "breakpoint");
do_check_eq(why.actors.length, 1);
do_check_eq(why.actors[0], breakpointClient.actor);
let where = packet.frame.where;
do_check_eq(where.source.actor, source.actor);
do_check_eq(where.line, actualLocation.line);
do_check_eq(where.column, actualLocation.column);
yield resume(threadClient);
yield close(client);
do_test_finished();
});
}

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

@ -1,57 +0,0 @@
"use strict";
var SOURCE_URL = getFileUrl("setBreakpoint-on-column-with-no-offsets-in-gcd-script.js");
function run_test() {
return Task.spawn(function* () {
do_test_pending();
let global = testGlobal("test");
loadSubScript(SOURCE_URL, global);
Cu.forceGC(); Cu.forceGC(); Cu.forceGC();
DebuggerServer.registerModule("xpcshell-test/testactors");
DebuggerServer.init(() => true);
DebuggerServer.addTestGlobal(global);
let client = new DebuggerClient(DebuggerServer.connectPipe());
yield connect(client);
let { tabs } = yield listTabs(client);
let tab = findTab(tabs, "test");
let [, tabClient] = yield attachTab(client, tab);
let [, threadClient] = yield attachThread(tabClient);
yield resume(threadClient);
let { sources } = yield getSources(threadClient);
let source = findSource(sources, SOURCE_URL);
let sourceClient = threadClient.source(source);
let location = { line: 6, column: 17 };
let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location);
do_check_true(packet.isPending);
do_check_false("actualLocation" in packet);
packet = yield executeOnNextTickAndWaitForPause(function () {
reload(tabClient).then(function () {
loadSubScript(SOURCE_URL, global);
});
}, client);
do_check_eq(packet.type, "paused");
let why = packet.why;
do_check_eq(why.type, "breakpoint");
do_check_eq(why.actors.length, 1);
do_check_eq(why.actors[0], breakpointClient.actor);
let frame = packet.frame;
let where = frame.where;
do_check_eq(where.source.actor, source.actor);
do_check_eq(where.line, location.line);
do_check_eq(where.column, 28);
let variables = frame.environment.bindings.variables;
do_check_eq(variables.a.value, 1);
do_check_eq(variables.c.value.type, "undefined");
yield resume(threadClient);
yield close(client);
do_test_finished();
});
}

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

@ -1,59 +0,0 @@
"use strict";
var SOURCE_URL = getFileUrl("setBreakpoint-on-column-with-no-offsets.js");
function run_test() {
return Task.spawn(function* () {
do_test_pending();
DebuggerServer.registerModule("xpcshell-test/testactors");
DebuggerServer.init(() => true);
let global = createTestGlobal("test");
DebuggerServer.addTestGlobal(global);
let client = new DebuggerClient(DebuggerServer.connectPipe());
yield connect(client);
let { tabs } = yield listTabs(client);
let tab = findTab(tabs, "test");
let [, tabClient] = yield attachTab(client, tab);
let [, threadClient] = yield attachThread(tabClient);
yield resume(threadClient);
let promise = waitForNewSource(threadClient, SOURCE_URL);
loadSubScript(SOURCE_URL, global);
let { source } = yield promise;
let sourceClient = threadClient.source(source);
let location = { line: 4, column: 17 };
let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location);
do_check_false(packet.isPending);
do_check_true("actualLocation" in packet);
let actualLocation = packet.actualLocation;
do_check_eq(actualLocation.line, 4);
do_check_eq(actualLocation.column, 28);
packet = yield executeOnNextTickAndWaitForPause(function () {
Cu.evalInSandbox("f()", global);
}, client);
do_check_eq(packet.type, "paused");
let why = packet.why;
do_check_eq(why.type, "breakpoint");
do_check_eq(why.actors.length, 1);
do_check_eq(why.actors[0], breakpointClient.actor);
let frame = packet.frame;
let where = frame.where;
do_check_eq(where.source.actor, source.actor);
do_check_eq(where.line, actualLocation.line);
do_check_eq(where.column, actualLocation.column);
let variables = frame.environment.bindings.variables;
do_check_eq(variables.a.value, 1);
do_check_eq(variables.c.value.type, "undefined");
yield resume(threadClient);
yield close(client);
do_test_finished();
});
}

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

@ -27,58 +27,46 @@ function run_test()
function testBreakpointMapping(aName, aCallback)
{
// Pause so we can set a breakpoint.
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
do_check_true(!aPacket.error);
do_check_eq(aPacket.why.type, "debuggerStatement");
Task.spawn(function*() {
let response = yield waitForPause(gThreadClient);
do_check_eq(response.why.type, "debuggerStatement");
getSource(gThreadClient, "http://example.com/www/js/" + aName + ".js").then(source => {
source.setBreakpoint({
// Setting the breakpoint on an empty line so that it is pushed down one
// line and we can check the source mapped actualLocation later.
line: 3
}, function (aResponse) {
do_check_true(!aResponse.error);
// Actual location should come back source mapped still so that
// breakpoints are displayed in the UI correctly, etc.
do_check_eq(aResponse.actualLocation.line, 4);
do_check_eq(aResponse.actualLocation.source.url,
"http://example.com/www/js/" + aName + ".js");
// The eval will cause us to resume, then we get an unsolicited pause
// because of our breakpoint, we resume again to finish the eval, and
// finally receive our last pause which has the result of the client
// evaluation.
gThreadClient.eval(null, aName + "()", function (aResponse) {
do_check_true(!aResponse.error, "Shouldn't be an error resuming to eval");
do_check_eq(aResponse.type, "resumed");
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
do_check_eq(aPacket.why.type, "breakpoint");
// Assert that we paused because of the breakpoint at the correct
// location in the code by testing that the value of `ret` is still
// undefined.
do_check_eq(aPacket.frame.environment.bindings.variables.ret.value.type,
"undefined");
gThreadClient.resume(function (aResponse) {
do_check_true(!aResponse.error);
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
do_check_eq(aPacket.why.type, "clientEvaluated");
do_check_eq(aPacket.why.frameFinished.return, aName);
gThreadClient.resume(function (aResponse) {
do_check_true(!aResponse.error);
aCallback();
});
});
});
});
});
});
const source = yield getSource(gThreadClient, "http://example.com/www/js/" + aName + ".js");
response = yield setBreakpoint(source, {
// Setting the breakpoint on an empty line so that it is pushed down one
// line and we can check the source mapped actualLocation later.
line: 3
});
// Should not slide breakpoints for sourcemapped sources
do_check_true(!response.actualLocation);
yield setBreakpoint(source, { line: 4 });
// The eval will cause us to resume, then we get an unsolicited pause
// because of our breakpoint, we resume again to finish the eval, and
// finally receive our last pause which has the result of the client
// evaluation.
response = yield rdpRequest(gThreadClient, gThreadClient.eval, null, aName + "()");
do_check_eq(response.type, "resumed");
response = yield waitForPause(gThreadClient);
do_check_eq(response.why.type, "breakpoint");
// Assert that we paused because of the breakpoint at the correct
// location in the code by testing that the value of `ret` is still
// undefined.
do_check_eq(response.frame.environment.bindings.variables.ret.value.type,
"undefined");
response = yield resume(gThreadClient);
response = yield waitForPause(gThreadClient);
do_check_eq(response.why.type, "clientEvaluated");
do_check_eq(response.why.frameFinished.return, aName);
response = yield resume(gThreadClient);
aCallback();
});
gDebuggee.eval("(" + function () {

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

@ -256,10 +256,7 @@ skip-if = os != 'linux' || debug || asan
reason = bug 1014071
[test_setBreakpoint-on-column.js]
[test_setBreakpoint-on-column-in-gcd-script.js]
[test_setBreakpoint-on-column-with-no-offsets.js]
[test_setBreakpoint-on-column-with-no-offsets-at-end-of-line.js]
[test_setBreakpoint-on-column-with-no-offsets-at-end-of-script.js]
[test_setBreakpoint-on-column-with-no-offsets-in-gcd-script.js]
[test_setBreakpoint-on-line.js]
[test_setBreakpoint-on-line-in-gcd-script.js]
[test_setBreakpoint-on-line-with-multiple-offsets.js]

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

@ -28,7 +28,6 @@ Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-common/rest.js");
Cu.import("resource://services-common/utils.js");
/**
* Represents the result of a Bagheera request.
*/
@ -185,8 +184,7 @@ BagheeraClient.prototype = Object.freeze({
let h = Services.telemetry.getHistogramById(options.telemetryCompressed);
h.add(data.length);
} catch (ex) {
this._log.warn("Unable to record telemetry for compressed payload size: " +
CommonUtils.exceptionStr(ex));
this._log.warn("Unable to record telemetry for compressed payload size", ex);
}
}
@ -251,8 +249,7 @@ BagheeraClient.prototype = Object.freeze({
result.request = request;
if (error) {
this._log.info("Transport failure on request: " +
CommonUtils.exceptionStr(error));
this._log.info("Transport failure on request", error);
result.transportSuccess = false;
deferred.resolve(result);
return;

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

@ -28,7 +28,6 @@ this.EXPORTED_SYMBOLS = ["HawkClient"];
var {interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://services-common/utils.js");
Cu.import("resource://services-crypto/utils.js");
Cu.import("resource://services-common/hawkrequest.js");
Cu.import("resource://services-common/observers.js");
@ -271,8 +270,7 @@ this.HawkClient.prototype = {
// gets the same one.
_onComplete.call(this, error);
} catch (ex) {
log.error("Unhandled exception processing response:" +
CommonUtils.exceptionStr(ex));
log.error("Unhandled exception processing response", ex);
deferred.reject(ex);
}
}

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

@ -168,7 +168,7 @@ this.Intl.prototype = {
this._accepted = Services.prefs.getComplexValue(
"intl.accept_languages", Ci.nsIPrefLocalizedString).data;
} catch (err) {
this._log.error("Error reading intl.accept_languages pref: " + CommonUtils.exceptionStr(err));
this._log.error("Error reading intl.accept_languages pref", err);
}
},

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

@ -12,7 +12,6 @@ Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-common/utils.js");
Cu.import("resource://testing-common/httpd.js");
/**
* This is an implementation of the Bagheera server.
*
@ -159,8 +158,7 @@ BagheeraServer.prototype = {
if (ex instanceof HttpError) {
this._log.info("HttpError thrown: " + ex.code + " " + ex.description);
} else {
this._log.warn("Exception processing request: " +
CommonUtils.exceptionStr(ex));
this._log.warn("Exception processing request", ex);
}
throw ex;

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше