merge fx-team to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2015-05-19 11:55:11 +02:00
commit b4eb63ff3e
34 changed files with 911 additions and 187 deletions

View File

@ -1455,6 +1455,13 @@ pref("devtools.performance.ui.enable-memory", false);
pref("devtools.performance.ui.enable-framerate", true);
pref("devtools.performance.ui.show-jit-optimizations", false);
// Enable experimental options in the UI only in Nightly
#if defined(NIGHTLY_BUILD)
pref("devtools.performance.ui.experimental", true);
#else
pref("devtools.performance.ui.experimental", false);
#endif
// The default cache UI setting
pref("devtools.cache.disabled", false);

View File

@ -427,6 +427,7 @@ skip-if = e10s # Bug 1100700 - test relies on unload event firing on closed tabs
[browser_urlHighlight.js]
[browser_urlbarAutoFillTrimURLs.js]
[browser_urlbarCopying.js]
[browser_urlbarDelete.js]
[browser_urlbarEnter.js]
[browser_urlbarEnterAfterMouseOver.js]
skip-if = os == "linux" || e10s # Bug 1073339 - Investigate autocomplete test unreliability on Linux/e10s

View File

@ -0,0 +1,53 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
add_task(function*() {
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.com/",
title: "test" });
registerCleanupFunction(function* () {
yield PlacesUtils.bookmarks.remove(bm);
});
// We do this test with both unifiedcomplete disabled and enabled.
let ucpref = Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete");
registerCleanupFunction(() => {
Services.prefs.setBoolPref("browser.urlbar.unifiedcomplete", ucpref);
});
Services.prefs.setBoolPref("browser.urlbar.unifiedcomplete", false);
yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, testDelete);
Services.prefs.setBoolPref("browser.urlbar.unifiedcomplete", true);
yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, testDelete);
});
function sendHome() {
// unclear why VK_HOME doesn't work on Mac, but it doesn't...
if (Services.appinfo.OS == "Darwin") {
EventUtils.synthesizeKey("VK_LEFT", { altKey: true });
} else {
EventUtils.synthesizeKey("VK_HOME", {});
}
}
function sendDelete() {
EventUtils.synthesizeKey("VK_DELETE", {});
}
function* testDelete() {
yield promiseAutocompleteResultPopup("exam");
// move to the start.
sendHome();
// delete the first few chars - each delete should operate on the input field.
sendDelete();
Assert.equal(gURLBar.inputField.value, "xam");
yield promisePopupShown(gURLBar.popup);
sendDelete();
Assert.equal(gURLBar.inputField.value, "am");
}

View File

@ -866,6 +866,23 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
]]></body>
</method>
<method name="handleDelete">
<body><![CDATA[
// When UnifiedComplete is enabled, we arrange for the popup to
// always have a "special" first item that's always selected. The
// autocomplete controller's handleDelete() implementation will
// remove the selected entry from the popup in that case.
// So when our first special item is selected, we call handleText
// instead so it acts as a delete on the text value instead of
// removing that item.
if (Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete") &&
this.popup.selectedIndex == 0) {
return this.mController.handleText();
}
return this.mController.handleDelete();
]]></body>
</method>
</implementation>
<handlers>

View File

@ -100,6 +100,7 @@ browser.jar:
content/browser/devtools/webaudioeditor/views/properties.js (webaudioeditor/views/properties.js)
content/browser/devtools/webaudioeditor/views/automation.js (webaudioeditor/views/automation.js)
content/browser/devtools/performance.xul (performance/performance.xul)
* content/browser/devtools/performance/system.js (performance/system.js)
content/browser/devtools/performance/performance-controller.js (performance/performance-controller.js)
content/browser/devtools/performance/performance-view.js (performance/performance-view.js)
content/browser/devtools/performance/views/overview.js (performance/views/overview.js)

View File

@ -209,7 +209,9 @@ exports.RecordingUtils.getProfileThreadFromAllocations = function(allocations) {
* The filtered timeline blueprint.
*/
exports.RecordingUtils.getFilteredBlueprint = function({ blueprint, hiddenMarkers }) {
let filteredBlueprint = Cu.cloneInto(blueprint, {});
// Clone functions here just to prevent an error, as the blueprint
// contains functions (even though we do not use them).
let filteredBlueprint = Cu.cloneInto(blueprint, {}, { cloneFunctions: true });
let maybeRemovedGroups = new Set();
let removedGroups = new Set();

View File

@ -191,6 +191,11 @@ let PerformanceController = {
this._onRecordingStateChange = this._onRecordingStateChange.bind(this);
this._onProfilerStatusUpdated = this._onProfilerStatusUpdated.bind(this);
// Store data regarding if e10s is enabled.
this._e10s = Services.appinfo.browserTabsRemoteAutostart;
this._setMultiprocessAttributes();
// All boolean prefs should be handled via the OptionsView in the
// ToolbarView, so that they may be accessible via the "gear" menu.
// Every other pref should be registered here.
@ -513,6 +518,44 @@ let PerformanceController = {
return true;
},
/**
* Returns an object with `supported` and `enabled` properties indicating
* whether or not the platform is capable of turning on e10s and whether or not
* it's already enabled, respectively.
*
* @return {object}
*/
getMultiprocessStatus: function () {
// If testing, set both supported and enabled to true so we
// have realtime rendering tests in non-e10s. This function is
// overridden wholesale in tests when we want to test multiprocess support
// specifically.
if (gDevTools.testing) {
return { supported: true, enabled: true };
}
let supported = SYSTEM.MULTIPROCESS_SUPPORTED;
// This is only checked on tool startup -- requires a restart if
// e10s subsequently enabled.
let enabled = this._e10s;
return { supported, enabled };
},
/**
* Called on init, sets an `e10s` attribute on the main view container with
* "disabled" if e10s is possible on the platform and just not on, or "unsupported"
* if e10s is not possible on the platform. If e10s is on, no attribute is set.
*/
_setMultiprocessAttributes: function () {
let { enabled, supported } = this.getMultiprocessStatus();
if (!enabled && supported) {
$("#performance-view").setAttribute("e10s", "disabled");
}
// Could be a chance where the directive goes away yet e10s is still on
else if (!enabled && !supported) {
$("#performance-view").setAttribute("e10s", "unsupported");
}
},
toString: () => "[object PerformanceController]"
};

View File

@ -30,6 +30,10 @@ let PerformanceView = {
recorded: [
{ deck: "#performance-view", pane: "#performance-view-content" },
{ deck: "#details-pane-container", pane: "#details-pane" }
],
loading: [
{ deck: "#performance-view", pane: "#performance-view-content" },
{ deck: "#details-pane-container", pane: "#loading-notice" }
]
},
@ -50,6 +54,7 @@ let PerformanceView = {
this._onRecordingStopped = this._onRecordingStopped.bind(this);
this._onRecordingStarted = this._onRecordingStarted.bind(this);
this._onProfilerStatusUpdated = this._onProfilerStatusUpdated.bind(this);
this._onRecordingWillStop = this._onRecordingWillStop.bind(this);
for (let button of $$(".record-button")) {
button.addEventListener("click", this._onRecordButtonClick);
@ -62,6 +67,7 @@ let PerformanceView = {
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
PerformanceController.on(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
PerformanceController.on(EVENTS.PROFILER_STATUS_UPDATED, this._onProfilerStatusUpdated);
PerformanceController.on(EVENTS.RECORDING_WILL_STOP, this._onRecordingWillStop);
this.setState("empty");
@ -87,6 +93,7 @@ let PerformanceView = {
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
PerformanceController.off(EVENTS.PROFILER_STATUS_UPDATED, this._onProfilerStatusUpdated);
PerformanceController.off(EVENTS.RECORDING_WILL_STOP, this._onRecordingWillStop);
yield ToolbarView.destroy();
yield RecordingsView.destroy();
@ -218,6 +225,17 @@ let PerformanceView = {
}
},
/**
* Fired when a recording is stopping, but not yet completed
*/
_onRecordingWillStop: function (_, recording) {
// Lock the details view while the recording is being loaded in the UI.
// Only do this if this is the current recording.
if (recording === PerformanceController.getCurrentRecording()) {
this.setState("loading");
}
},
/**
* Handler for clicking the clear button.
*/
@ -249,7 +267,8 @@ let PerformanceView = {
*/
_onImportButtonClick: function(e) {
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
fp.init(window, L10N.getStr("recordingsList.saveDialogTitle"), Ci.nsIFilePicker.modeOpen);
// TODO localize? in bug 1163763
fp.init(window, "Import recording…", Ci.nsIFilePicker.modeOpen);
fp.appendFilter(L10N.getStr("recordingsList.saveDialogJSONFilter"), "*.json");
fp.appendFilter(L10N.getStr("recordingsList.saveDialogAllFilter"), "*.*");

View File

@ -14,6 +14,7 @@
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script src="chrome://browser/content/devtools/theme-switching.js"/>
<script type="application/javascript" src="performance/system.js"/>
<script type="application/javascript" src="performance/performance-controller.js"/>
<script type="application/javascript" src="performance/performance-view.js"/>
<script type="application/javascript" src="performance/recording-model.js"/>
@ -37,12 +38,12 @@
data-pref="show-platform-data"
label="&profilerUI.showPlatformData;"
tooltiptext="&profilerUI.showPlatformData.tooltiptext;"/>
<!-- TODO Re-enable in bug 1163350 -->
<!-- <menuitem id="option-enable-memory"
<menuitem id="option-enable-memory"
class="experimental-option"
type="checkbox"
data-pref="enable-memory"
label="&profilerUI.enableMemory;"
tooltiptext="&profilerUI.enableMemory.tooltiptext;"/> -->
tooltiptext="&profilerUI.enableMemory.tooltiptext;"/>
<menuitem id="option-enable-framerate"
type="checkbox"
data-pref="enable-framerate"
@ -63,12 +64,12 @@
data-pref="flatten-tree-recursion"
label="&profilerUI.flattenTreeRecursion;"
tooltiptext="&profilerUI.flattenTreeRecursion.tooltiptext;"/>
<!-- TODO Re-enable in bug 1163351 -->
<!-- <menuitem id="option-show-jit-optimizations"
<menuitem id="option-show-jit-optimizations"
class="experimental-option"
type="checkbox"
data-pref="show-jit-optimizations"
label="&profilerUI.showJITOptimizations;"
tooltiptext="&profilerUI.showJITOptimizations.tooltiptext;"/> -->
tooltiptext="&profilerUI.showJITOptimizations.tooltiptext;"/>
</menupopup>
</popupset>
@ -96,8 +97,8 @@
<toolbar id="performance-toolbar" class="devtools-toolbar">
<hbox id="performance-toolbar-control-other" class="devtools-toolbarbutton-group">
<toolbarbutton id="filter-button"
popup="performance-filter-menupopup"
class="devtools-toolbarbutton"
popup="performance-filter-menupopup"
tooltiptext="&profilerUI.options.filter.tooltiptext;"/>
</hbox>
<hbox id="performance-toolbar-controls-detail-views" class="devtools-toolbarbutton-group">
@ -155,6 +156,13 @@
<hbox id="time-framerate"/>
</vbox>
<deck id="details-pane-container" flex="1">
<hbox id="loading-notice"
class="notice-container devtools-throbber"
align="center"
pack="center"
flex="1">
<label value="&profilerUI.loadingNotice;"/>
</hbox>
<hbox id="recording-notice"
class="notice-container"
align="center"
@ -166,6 +174,10 @@
<toolbarbutton class="devtools-toolbarbutton record-button"
label="&profilerUI.stopRecording;" />
</hbox>
<label class="realtime-disabled-message"
value="Realtime recording data disabled on non-multiprocess Firefox."/>
<label class="realtime-disabled-on-e10s-message"
value="Enable multiprocess Firefox in preferences for rendering recording data in realtime."/>
<label class="buffer-status-message"
tooltiptext="&profilerUI.bufferStatusTooltip;"/>
<label class="buffer-status-message-full"
@ -188,6 +200,10 @@
<label class="console-profile-command" />
<label value="&profilerUI.console.stopCommandEnd;" />
</hbox>
<label class="realtime-disabled-message"
value="Realtime recording data disabled on non-multiprocess Firefox."/>
<label class="realtime-disabled-on-e10s-message"
value="Enable multiprocess Firefox in preferences for rendering recording data in realtime."/>
<label class="buffer-status-message"
tooltiptext="&profilerUI.bufferStatusTooltip;"/>
<label class="buffer-status-message-full"

View File

@ -0,0 +1,16 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/**
* A dump file to attach preprocessing directives consumable to the controller
* without littering our code with directives.
*/
const SYSTEM = {};
// If e10s is possible on the platform.
#ifdef E10S_TESTING_ONLY
SYSTEM.MULTIPROCESS_SUPPORTED = true;
#endif

View File

@ -11,8 +11,10 @@ support-files =
# that need to be moved over to performance tool
[browser_perf-aaa-run-first-leaktest.js]
[browser_marker-utils.js]
[browser_markers-gc.js]
[browser_markers-parse-html.js]
[browser_markers-styles.js]
[browser_markers-timestamp.js]
[browser_perf-allocations-to-samples.js]
[browser_perf-categories-js-calltree.js]
@ -65,8 +67,10 @@ support-files =
[browser_perf-jit-model-01.js]
[browser_perf-jit-model-02.js]
[browser_perf-loading-01.js]
[browser_perf-loading-02.js]
[browser_perf-options-01.js]
[browser_perf-options-02.js]
[browser_perf-options-03.js]
[browser_perf-options-invert-call-tree-01.js]
[browser_perf-options-invert-call-tree-02.js]
[browser_perf-options-invert-flame-graph-01.js]
@ -85,6 +89,7 @@ support-files =
[browser_perf-overview-render-01.js]
[browser_perf-overview-render-02.js]
[browser_perf-overview-render-03.js]
[browser_perf-overview-render-04.js]
[browser_perf-overview-selection-01.js]
[browser_perf-overview-selection-02.js]
[browser_perf-overview-selection-03.js]
@ -100,6 +105,7 @@ support-files =
[browser_perf-recording-notices-02.js]
[browser_perf-recording-notices-03.js]
[browser_perf-recording-notices-04.js]
[browser_perf-recording-notices-05.js]
[browser_perf_recordings-io-01.js]
[browser_perf_recordings-io-02.js]
[browser_perf_recordings-io-03.js]

View File

@ -0,0 +1,37 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests the marker utils methods.
*/
function spawnTest () {
let { TIMELINE_BLUEPRINT } = devtools.require("devtools/shared/timeline/global");
let Utils = devtools.require("devtools/shared/timeline/marker-utils");
is(Utils.getMarkerLabel({ name: "DOMEvent" }), "DOM Event",
"getMarkerLabel() returns a simple label");
is(Utils.getMarkerLabel({ name: "Javascript", causeName: "js" }), "js",
"getMarkerLabel() returns a label defined via function");
ok(Utils.getMarkerFields({ name: "Paint" }).length === 0,
"getMarkerFields() returns an empty array when no fields defined");
let fields = Utils.getMarkerFields({ name: "ConsoleTime", causeName: "snowstorm" });
is(fields[0].label, "Timer Name:", "getMarkerFields() returns an array with proper label");
is(fields[0].value, "snowstorm", "getMarkerFields() returns an array with proper value");
fields = Utils.getMarkerFields({ name: "DOMEvent", type: "mouseclick" });
is(fields.length, 1, "getMarkerFields() ignores fields that are not found on marker");
is(fields[0].label, "Event Type:", "getMarkerFields() returns an array with proper label");
is(fields[0].value, "mouseclick", "getMarkerFields() returns an array with proper value");
fields = Utils.getMarkerFields({ name: "DOMEvent", eventPhase: Ci.nsIDOMEvent.AT_TARGET, type: "mouseclick" });
is(fields.length, 2, "getMarkerFields() returns multiple fields when they exist");
is(fields[0].label, "Event Type:", "getMarkerFields() returns an array with proper label (ordered)");
is(fields[0].value, "mouseclick", "getMarkerFields() returns an array with proper value (ordered)");
is(fields[1].label, "Phase:", "getMarkerFields() returns an array with proper label (ordered)");
is(fields[1].value, "Target", "getMarkerFields() uses the `formatter` function when available");
finish();
}

View File

@ -0,0 +1,38 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that we get a "Styles" marker with the correct meta.
*/
function* spawnTest () {
let { target, front } = yield initBackend(SIMPLE_URL);
let markers = [];
front.on("timeline-data", handler);
let model = yield front.startRecording({ withTicks: true });
yield waitUntil(() => {
return markers.some(({restyleHint}) => restyleHint != void 0);
});
front.off("timeline-data", handler);
yield front.stopRecording(model);
info(`Got ${markers.length} markers.`);
ok(markers.every(({name}) => name === "Styles"), "All markers found are Styles markers");
ok(markers.length, "found some restyle markers");
ok(markers.some(({restyleHint}) => restyleHint != void 0), "some markers have a restyleHint property");
yield removeTab(target.tab);
finish();
function handler (_, name, m) {
if (name === "markers") {
markers = markers.concat(m.filter(marker => marker.name === "Styles"));
}
}
}

View File

@ -0,0 +1,78 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that the details view is locked after recording has stopped and before
* the recording has finished loading.
* Also test that the details view isn't locked if the recording that is being
* stopped isn't the active one.
*/
let test = Task.async(function*() {
let { panel } = yield initPerformance(SIMPLE_URL);
let { PerformanceController, PerformanceView, RecordingsView,
EVENTS, $ } = panel.panelWin;
let detailsContainer = $("#details-pane-container");
let recordingNotice = $("#recording-notice");
let loadingNotice = $("#loading-notice");
let detailsPane = $("#details-pane");
info("Start to record");
yield startRecording(panel);
is(detailsContainer.selectedPanel, recordingNotice,
"The recording-notice is shown while recording");
info("Stop the recording and wait for the WILL_STOP and STOPPED events");
let clicked = PerformanceView.once(EVENTS.UI_STOP_RECORDING);
let willStop = PerformanceController.once(EVENTS.RECORDING_WILL_STOP);
let hasStopped = PerformanceController.once(EVENTS.RECORDING_STOPPED);
click(panel.panelWin, $("#main-record-button"));
yield clicked;
yield willStop;
is(detailsContainer.selectedPanel, loadingNotice,
"The loading-notice is shown while the record is stopping");
let stateChanged = once(PerformanceView, EVENTS.UI_STATE_CHANGED);
yield hasStopped;
yield stateChanged;
is(detailsContainer.selectedPanel, detailsPane,
"The details panel is shown after the record has stopped");
info("Start to record again");
yield startRecording(panel);
info("While the 2nd record is still going, switch to the first one");
let select = once(PerformanceController, EVENTS.RECORDING_SELECTED);
RecordingsView.selectedIndex = 0;
yield select;
info("Stop the 2nd recording and wait for the WILL_STOP and STOPPED events");
clicked = PerformanceView.once(EVENTS.UI_STOP_RECORDING);
willStop = PerformanceController.once(EVENTS.RECORDING_WILL_STOP);
hasStopped = PerformanceController.once(EVENTS.RECORDING_STOPPED);
click(panel.panelWin, $("#main-record-button"));
yield clicked;
yield willStop;
is(detailsContainer.selectedPanel, detailsPane,
"The details panel is still shown while the 2nd record is being stopped");
is(RecordingsView.selectedIndex, 0, "The first record is still selected");
stateChanged = once(PerformanceView, EVENTS.UI_STATE_CHANGED);
yield hasStopped;
yield stateChanged;
is(detailsContainer.selectedPanel, detailsPane,
"The details panel is still shown after the 2nd record has stopped");
is(RecordingsView.selectedIndex, 1, "The second record is now selected");
yield PerformanceController.clearRecordings();
yield teardown(panel);
finish();
});

View File

@ -0,0 +1,27 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that toggling meta option prefs change visibility of other options.
*/
Services.prefs.setBoolPref(EXPERIMENTAL_PREF, false);
function spawnTest () {
let { panel } = yield initPerformance(SIMPLE_URL);
let { $, EVENTS, PerformanceController } = panel.panelWin;
let $body = $(".theme-body");
let $menu = $("#performance-options-menupopup");
ok(!$body.classList.contains("experimental-enabled"), "body does not have `experimental-enabled` on start");
ok(!$menu.classList.contains("experimental-enabled"), "menu does not have `experimental-enabled` on start");
Services.prefs.setBoolPref(EXPERIMENTAL_PREF, true);
ok($body.classList.contains("experimental-enabled"), "body has `experimental-enabled` after toggle");
ok($menu.classList.contains("experimental-enabled"), "menu has `experimental-enabled` after toggle");
yield teardown(panel);
finish();
}

View File

@ -0,0 +1,52 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that the overview graphs do not render when realtime rendering is off
* due to lack of e10s.
*/
function spawnTest () {
let { panel } = yield initPerformance(SIMPLE_URL);
let { $, EVENTS, PerformanceController, OverviewView, RecordingsView } = panel.panelWin;
let updated = 0;
OverviewView.on(EVENTS.OVERVIEW_RENDERED, () => updated++);
OverviewView.OVERVIEW_UPDATE_INTERVAL = 1;
// Set realtime rendering off.
OverviewView.isRealtimeRenderingEnabled = () => false;
yield startRecording(panel, { waitForOverview: false, waitForStateChange: true });
is($("#overview-pane").hidden, true, "overview graphs hidden");
is(updated, 0, "Overview graphs have still not been updated");
yield waitUntil(() => PerformanceController.getCurrentRecording().getMarkers().length);
yield waitUntil(() => PerformanceController.getCurrentRecording().getTicks().length);
is(updated, 0, "Overview graphs have still not been updated");
yield stopRecording(panel);
let markers = OverviewView.graphs.get("timeline");
let framerate = OverviewView.graphs.get("framerate");
ok(markers.width > 0,
"The overview's markers graph has a width.");
ok(framerate.width > 0,
"The overview's framerate graph has a width.");
is(updated, 1, "Overview graphs rendered upon completion.");
is($("#overview-pane").hidden, false, "overview graphs no longer hidden");
yield startRecording(panel, { waitForOverview: false, waitForStateChange: true });
is($("#overview-pane").hidden, true, "overview graphs hidden again when starting new recording");
RecordingsView.selectedIndex = 0;
is($("#overview-pane").hidden, false, "overview graphs no longer hidden when switching back to complete recording.");
RecordingsView.selectedIndex = 1;
is($("#overview-pane").hidden, true, "overview graphs hidden again when going back to inprogress recording.");
yield stopRecording(panel);
is($("#overview-pane").hidden, false, "overview graphs no longer hidden when recording finishes");
yield teardown(panel);
finish();
}

View File

@ -0,0 +1,43 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that when a recording overlaps the circular buffer, that
* a class is assigned to the recording notices.
*/
function spawnTest () {
let { panel } = yield initPerformance(SIMPLE_URL, void 0, { TEST_MOCK_PROFILER_CHECK_TIMER: 10 });
let { EVENTS, $, PerformanceController, PerformanceView } = panel.panelWin;
let supported = false;
let enabled = false;
PerformanceController.getMultiprocessStatus = () => {
return { supported, enabled };
};
PerformanceController._setMultiprocessAttributes();
ok($("#performance-view").getAttribute("e10s"), "unsupported",
"when e10s is disabled and no option to turn on, container has [e10s=unsupported]");
supported = true;
enabled = false;
PerformanceController._setMultiprocessAttributes();
ok($("#performance-view").getAttribute("e10s"), "disabled",
"when e10s is disabled and but is supported, container has [e10s=disabled]");
supported = false;
enabled = true;
PerformanceController._setMultiprocessAttributes();
ok($("#performance-view").getAttribute("e10s"), "",
"when e10s is enabled, but not supported, this probably means we no longer have E10S_TESTING_ONLY, and we have no e10s attribute.");
supported = true;
enabled = true;
PerformanceController._setMultiprocessAttributes();
ok($("#performance-view").getAttribute("e10s"), "",
"when e10s is enabled and supported, there should be no e10s attribute.");
yield teardown(panel);
finish();
}

View File

@ -9,6 +9,7 @@ function spawnTest () {
let { target, panel } = yield initPerformance(SIMPLE_URL);
let { $, $$, EVENTS, PerformanceController, OverviewView } = panel.panelWin;
let { L10N, TIMELINE_BLUEPRINT } = devtools.require("devtools/shared/timeline/global");
let { getMarkerLabel } = devtools.require("devtools/shared/timeline/marker-utils");
yield startRecording(panel);
ok(true, "Recording has started.");
@ -38,20 +39,14 @@ function spawnTest () {
bar.click();
let m = markers[i];
let name = TIMELINE_BLUEPRINT[m.name].label;
is($("#waterfall-details .marker-details-type").getAttribute("value"), name,
is($("#waterfall-details .marker-details-type").getAttribute("value"), getMarkerLabel(m),
"sidebar title matches markers name");
let printedStartTime = $(".marker-details-start .marker-details-labelvalue").getAttribute("value");
let printedEndTime = $(".marker-details-end .marker-details-labelvalue").getAttribute("value");
let printedDuration= $(".marker-details-duration .marker-details-labelvalue").getAttribute("value");
let printedDuration = $(".marker-details-duration .marker-details-labelvalue").getAttribute("value");
let toMs = ms => L10N.getFormatStrWithNumbers("timeline.tick", ms);
// Values are rounded. We don't use a strict equality.
is(toMs(m.start), printedStartTime, "sidebar start time is valid");
is(toMs(m.end), printedEndTime, "sidebar end time is valid");
is(toMs(m.end - m.start), printedDuration, "sidebar duration is valid");
}
yield teardown(panel);

View File

@ -37,6 +37,7 @@ const INVERT_PREF = "devtools.performance.ui.invert-call-tree";
const INVERT_FLAME_PREF = "devtools.performance.ui.invert-flame-graph";
const FLATTEN_PREF = "devtools.performance.ui.flatten-tree-recursion";
const JIT_PREF = "devtools.performance.ui.show-jit-optimizations";
const EXPERIMENTAL_PREF = "devtools.performance.ui.experimental";
// All tests are asynchronous.
waitForExplicitFinish();
@ -56,6 +57,7 @@ let DEFAULT_PREFS = [
"devtools.performance.memory.max-log-length",
"devtools.performance.profiler.buffer-size",
"devtools.performance.profiler.sample-frequency-khz",
"devtools.performance.ui.experimental",
].reduce((prefs, pref) => {
prefs[pref] = Preferences.get(pref);
return prefs;

View File

@ -30,6 +30,12 @@ const GRAPH_REQUIREMENTS = {
*/
let OverviewView = {
/**
* How frequently we attempt to render the graphs. Overridden
* in tests.
*/
OVERVIEW_UPDATE_INTERVAL: OVERVIEW_UPDATE_INTERVAL,
/**
* Sets up the view with event binding.
*/
@ -46,6 +52,9 @@ let OverviewView = {
return;
}
// Store info on multiprocess support.
this._multiprocessData = PerformanceController.getMultiprocessStatus();
this._onRecordingWillStart = this._onRecordingWillStart.bind(this);
this._onRecordingStarted = this._onRecordingStarted.bind(this);
this._onRecordingWillStop = this._onRecordingWillStop.bind(this);
@ -173,6 +182,7 @@ let OverviewView = {
if (this.isDisabled()) {
return;
}
let recording = PerformanceController.getCurrentRecording();
yield this.graphs.render(recording.getAllData(), resolution);
@ -197,7 +207,7 @@ let OverviewView = {
// Check here to see if there's still a _timeoutId, incase
// `stop` was called before the _prepareNextTick call was executed.
if (this.isRendering()) {
this._timeoutId = setTimeout(this._onRecordingTick, OVERVIEW_UPDATE_INTERVAL);
this._timeoutId = setTimeout(this._onRecordingTick, this.OVERVIEW_UPDATE_INTERVAL);
}
},
@ -255,7 +265,7 @@ let OverviewView = {
* Start the polling for rendering the overview graph.
*/
_startPolling: function () {
this._timeoutId = setTimeout(this._onRecordingTick, OVERVIEW_UPDATE_INTERVAL);
this._timeoutId = setTimeout(this._onRecordingTick, this.OVERVIEW_UPDATE_INTERVAL);
},
/**
@ -340,6 +350,35 @@ let OverviewView = {
}
},
/**
* Fetch the multiprocess status and if e10s is not currently on, disable
* realtime rendering.
*
* @return {boolean}
*/
isRealtimeRenderingEnabled: function () {
return this._multiprocessData.enabled;
},
/**
* Show the graphs overview panel when a recording is finished
* when non-realtime graphs are enabled. Also set the graph visibility
* so the performance graphs know which graphs to render.
*
* @param {RecordingModel} recording
*/
_showGraphsPanel: function (recording) {
this._setGraphVisibilityFromRecordingFeatures(recording);
$("#overview-pane").hidden = false;
},
/**
* Hide the graphs container completely.
*/
_hideGraphsPanel: function () {
$("#overview-pane").hidden = true;
},
/**
* Called when `devtools.theme` changes.
*/
@ -361,14 +400,28 @@ let OverviewView = {
* @return {function}
*/
function OverviewViewOnStateChange (fn) {
return function _onRecordingStateChange () {
return function _onRecordingStateChange (eventName, recording) {
let currentRecording = PerformanceController.getCurrentRecording();
// All these methods require a recording to exist.
if (!currentRecording) {
// All these methods require a recording to exist selected and
// from the event name, since there is a delay between starting
// a recording and changing the selection.
if (!currentRecording || !recording) {
return;
}
// If realtime rendering is not enabed (e10s not on), then
// show the disabled message, or the full graphs if the recording is completed
if (!this.isRealtimeRenderingEnabled()) {
if (recording.isRecording()) {
this._hideGraphsPanel();
// Abort, as we do not want to change polling status.
return;
} else {
this._showGraphsPanel(recording);
}
}
if (this.isRendering() && !currentRecording.isRecording()) {
this._stopPolling();
} else if (currentRecording.isRecording() && !this.isRendering()) {

View File

@ -214,7 +214,8 @@ let RecordingsView = Heritage.extend(WidgetMethods, {
*/
_onSaveButtonClick: function (e) {
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
fp.init(window, L10N.getStr("recordingsList.saveDialogTitle"), Ci.nsIFilePicker.modeSave);
// TODO localize? in bug 1163763
fp.init(window, "Save recording…", Ci.nsIFilePicker.modeSave);
fp.appendFilter(L10N.getStr("recordingsList.saveDialogJSONFilter"), "*.json");
fp.appendFilter(L10N.getStr("recordingsList.saveDialogAllFilter"), "*.*");
fp.defaultString = "profile.json";

View File

@ -15,12 +15,18 @@ let ToolbarView = {
this._onFilterPopupHiding = this._onFilterPopupHiding.bind(this);
this._onHiddenMarkersChanged = this._onHiddenMarkersChanged.bind(this);
this._onPrefChanged = this._onPrefChanged.bind(this);
this._popup = $("#performance-options-menupopup");
this.optionsView = new OptionsView({
branchName: BRANCH_NAME,
menupopup: $("#performance-options-menupopup")
menupopup: this._popup
});
// Set the visibility of experimental UI options on load
// based off of `devtools.performance.ui.experimental` preference
let experimentalEnabled = PerformanceController.getOption("experimental");
this._toggleExperimentalUI(experimentalEnabled);
yield this.optionsView.initialize();
this.optionsView.on("pref-changed", this._onPrefChanged);
@ -36,6 +42,7 @@ let ToolbarView = {
destroy: function () {
$("#performance-filter-menupopup").removeEventListener("popupshowing", this._onFilterPopupShowing);
$("#performance-filter-menupopup").removeEventListener("popuphiding", this._onFilterPopupHiding);
this._popup = null
this.optionsView.off("pref-changed", this._onPrefChanged);
this.optionsView.destroy();
@ -77,6 +84,29 @@ let ToolbarView = {
}
},
/**
* Fired when `devtools.performance.ui.experimental` is changed, or
* during init. Toggles the visibility of experimental performance tool options
* in the UI options.
*
* Sets or removes "experimental-enabled" on the menu and main elements,
* hiding or showing all elements with class "experimental-option".
*
* TODO re-enable "#option-enable-memory" permanently once stable in bug 1163350
* TODO re-enable "#option-show-jit-optimizations" permanently once stable in bug 1163351
*
* @param {boolean} isEnabled
*/
_toggleExperimentalUI: function (isEnabled) {
if (isEnabled) {
$(".theme-body").classList.add("experimental-enabled");
this._popup.classList.add("experimental-enabled");
} else {
$(".theme-body").classList.remove("experimental-enabled");
this._popup.classList.remove("experimental-enabled");
}
},
/**
* Fired when the markers filter popup starts to show.
*/
@ -105,7 +135,12 @@ let ToolbarView = {
* Propogated by the PerformanceController.
*/
_onPrefChanged: function (_, prefName) {
let value = Services.prefs.getBoolPref(BRANCH_NAME + prefName);
let value = PerformanceController.getOption(prefName);
if (prefName === "experimental") {
this._toggleExperimentalUI(value);
}
this.emit(EVENTS.PREF_CHANGED, prefName, value);
},

View File

@ -41,6 +41,7 @@ EXTRA_JS_MODULES.devtools.shared.profiler += [
EXTRA_JS_MODULES.devtools.shared.timeline += [
'timeline/global.js',
'timeline/marker-details.js',
'timeline/marker-utils.js',
'timeline/markers-overview.js',
'timeline/waterfall.js',
]

View File

@ -16,19 +16,35 @@ const L10N = new ViewHelpers.L10N(STRINGS_URI);
/**
* A simple schema for mapping markers to the timeline UI. The keys correspond
* to marker names, while the values are objects with the following format:
*
* - group: the row index in the timeline overview graph; multiple markers
* can be added on the same row. @see <overview.js/buildGraphImage>
* - label: the label used in the waterfall to identify the marker
* - colorName: the name of the DevTools color used for this marker. If adding
* - label: the label used in the waterfall to identify the marker. Can be a
* string or just a function that accepts the marker and returns a string,
* if you want to use a dynamic property for the main label.
* - colorName: the label of the DevTools color used for this marker. If adding
* a new color, be sure to check that there's an entry for
* `.marker-details-bullet.{COLORNAME}` for the equivilent entry
* in ./browser/themes/shared/devtools/performance.inc.css
* https://developer.mozilla.org/en-US/docs/Tools/DevToolsColors
* - fields: An optional array of marker properties you wish to display in the
* marker details view. For example, a field of
* { property: "aCauseName", label: "Cause" }
* would render a field like `Cause: ${marker.aCauseName}`.
* Each `field` item may take the following properties:
*
* - property: The property that must exist on the marker to render, and
* the value of the property will be displayed.
* - label: The name of the property that should be displayed.
* - formatter: If a formatter is provided, instead of directly using the `property`
* property on the marker, the marker is passed into the formatter
* function to determine the display value.
*
* Whenever this is changed, browser_timeline_waterfall-styles.js *must* be
* updated as well.
*/
const TIMELINE_BLUEPRINT = {
/* Group 0 - Reflow and Rendering pipeline */
"Styles": {
group: 0,
colorName: "highlight-pink",
@ -44,15 +60,25 @@ const TIMELINE_BLUEPRINT = {
colorName: "highlight-green",
label: L10N.getStr("timeline.label.paint")
},
/* Group 1 - JS */
"DOMEvent": {
group: 1,
colorName: "highlight-lightorange",
label: L10N.getStr("timeline.label.domevent")
label: L10N.getStr("timeline.label.domevent"),
fields: [{
property: "type",
label: L10N.getStr("timeline.markerDetail.DOMEventType")
}, {
property: "eventPhase",
label: L10N.getStr("timeline.markerDetail.DOMEventPhase"),
formatter: getEventPhaseName
}]
},
"Javascript": {
group: 1,
colorName: "highlight-lightorange",
label: L10N.getStr("timeline.label.javascript2")
label: (marker) => marker.causeName,
},
"Parse HTML": {
group: 1,
@ -67,12 +93,22 @@ const TIMELINE_BLUEPRINT = {
"GarbageCollection": {
group: 1,
colorName: "highlight-red",
label: L10N.getStr("timeline.label.garbageCollection")
label: getGCLabel,
fields: [
{ property: "causeName", label: "Reason:" },
{ property: "nonincrementalReason", label: "Non-incremental Reason:" }
]
},
/* Group 2 - User Controlled */
"ConsoleTime": {
group: 2,
colorName: "highlight-bluegrey",
label: L10N.getStr("timeline.label.consoleTime")
label: L10N.getStr("timeline.label.consoleTime"),
fields: [{
property: "causeName",
label: L10N.getStr("timeline.markerDetail.consoleTimerName")
}]
},
"TimeStamp": {
group: 2,
@ -81,6 +117,30 @@ const TIMELINE_BLUEPRINT = {
},
};
/**
* A series of formatters used by the blueprint.
*/
function getEventPhaseName (marker) {
if (marker.eventPhase === Ci.nsIDOMEvent.AT_TARGET) {
return L10N.getStr("timeline.markerDetail.DOMEventTargetPhase");
} else if (marker.eventPhase === Ci.nsIDOMEvent.CAPTURING_PHASE) {
return L10N.getStr("timeline.markerDetail.DOMEventCapturingPhase");
} else if (marker.eventPhase === Ci.nsIDOMEvent.BUBBLING_PHASE) {
return L10N.getStr("timeline.markerDetail.DOMEventBubblingPhase");
}
}
function getGCLabel (marker) {
let label = L10N.getStr("timeline.label.garbageCollection");
// Only if a `nonincrementalReason` exists, do we want to label
// this as a non incremental GC event.
if ("nonincrementalReason" in marker) {
label = `${label} (Non-incremental)`;
}
return label;
}
// Exported symbols.
exports.L10N = L10N;
exports.TIMELINE_BLUEPRINT = TIMELINE_BLUEPRINT;

View File

@ -16,6 +16,8 @@ loader.lazyRequireGetter(this, "TIMELINE_BLUEPRINT",
"devtools/shared/timeline/global", true);
loader.lazyRequireGetter(this, "EventEmitter",
"devtools/toolkit/event-emitter");
loader.lazyRequireGetter(this, "MarkerUtils",
"devtools/shared/timeline/marker-utils");
/**
* A detailed view for one single marker.
@ -50,54 +52,6 @@ MarkerDetails.prototype = {
this._parent.innerHTML = "";
},
/**
* Builds the label representing marker's type.
*
* @param string type
* Could be "Paint", "Reflow", "Styles", ...
* See TIMELINE_BLUEPRINT in widgets/global.js
*/
buildMarkerTypeLabel: function(type) {
let blueprint = TIMELINE_BLUEPRINT[type];
let hbox = this._document.createElement("hbox");
hbox.setAttribute("align", "center");
let bullet = this._document.createElement("hbox");
bullet.className = `marker-details-bullet ${blueprint.colorName}`;
let label = this._document.createElement("label");
label.className = "marker-details-type";
label.setAttribute("value", blueprint.label);
hbox.appendChild(bullet);
hbox.appendChild(label);
return hbox;
},
/**
* Builds labels for name:value pairs. Like "Start: 100ms",
* "Duration: 200ms", ...
*
* @param string l10nName
* String identifier for label's name.
* @param string value
* Label's value.
*/
buildNameValueLabel: function(l10nName, value) {
let hbox = this._document.createElement("hbox");
let labelName = this._document.createElement("label");
let labelValue = this._document.createElement("label");
labelName.className = "plain marker-details-labelname";
labelValue.className = "plain marker-details-labelvalue";
labelName.setAttribute("value", L10N.getStr(l10nName));
labelValue.setAttribute("value", value);
hbox.appendChild(labelName);
hbox.appendChild(labelValue);
return hbox;
},
/**
* Populates view with marker's details.
*
@ -112,37 +66,13 @@ MarkerDetails.prototype = {
// UI for any marker
let title = this.buildMarkerTypeLabel(marker.name);
let toMs = ms => L10N.getFormatStrWithNumbers("timeline.tick", ms);
let start = this.buildNameValueLabel("timeline.markerDetail.start", toMs(marker.start));
let end = this.buildNameValueLabel("timeline.markerDetail.end", toMs(marker.end));
let duration = this.buildNameValueLabel("timeline.markerDetail.duration", toMs(marker.end - marker.start));
start.classList.add("marker-details-start");
end.classList.add("marker-details-end");
duration.classList.add("marker-details-duration");
let title = MarkerUtils.DOM.buildTitle(this._document, marker);
let duration = MarkerUtils.DOM.buildDuration(this._document, marker);
let fields = MarkerUtils.DOM.buildFields(this._document, marker);
this._parent.appendChild(title);
this._parent.appendChild(start);
this._parent.appendChild(end);
this._parent.appendChild(duration);
// UI for specific markers
switch (marker.name) {
case "ConsoleTime":
this.renderConsoleTimeMarker(this._parent, marker);
break;
case "DOMEvent":
this.renderDOMEventMarker(this._parent, marker);
break;
case "Javascript":
this.renderJavascriptMarker(this._parent, marker);
break;
default:
}
fields.forEach(field => this._parent.appendChild(field));
if (marker.stack) {
let property = "timeline.markerDetail.stack";
@ -242,66 +172,6 @@ MarkerDetails.prototype = {
}
}
},
/**
* Render details of a console marker (console.time).
*
* @param nsIDOMNode parent
* The parent node holding the view.
* @param object marker
* The marker to display.
*/
renderConsoleTimeMarker: function(parent, marker) {
if ("causeName" in marker) {
let timerName = this.buildNameValueLabel("timeline.markerDetail.consoleTimerName", marker.causeName);
this._parent.appendChild(timerName);
}
},
/**
* Render details of a DOM Event marker.
*
* @param nsIDOMNode parent
* The parent node holding the view.
* @param object marker
* The marker to display.
*/
renderDOMEventMarker: function(parent, marker) {
if ("type" in marker) {
let type = this.buildNameValueLabel("timeline.markerDetail.DOMEventType", marker.type);
this._parent.appendChild(type);
}
if ("eventPhase" in marker) {
let phaseL10NProp;
if (marker.eventPhase == Ci.nsIDOMEvent.AT_TARGET) {
phaseL10NProp = "timeline.markerDetail.DOMEventTargetPhase";
}
if (marker.eventPhase == Ci.nsIDOMEvent.CAPTURING_PHASE) {
phaseL10NProp = "timeline.markerDetail.DOMEventCapturingPhase";
}
if (marker.eventPhase == Ci.nsIDOMEvent.BUBBLING_PHASE) {
phaseL10NProp = "timeline.markerDetail.DOMEventBubblingPhase";
}
let phase = this.buildNameValueLabel("timeline.markerDetail.DOMEventPhase", L10N.getStr(phaseL10NProp));
this._parent.appendChild(phase);
}
},
/**
* Render details of a Javascript marker.
*
* @param nsIDOMNode parent
* The parent node holding the view.
* @param object marker
* The marker to display.
*/
renderJavascriptMarker: function(parent, marker) {
if ("causeName" in marker) {
let cause = this.buildNameValueLabel("timeline.markerDetail.causeName", marker.causeName);
this._parent.appendChild(cause);
}
},
};
exports.MarkerDetails = MarkerDetails;

View File

@ -0,0 +1,141 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/**
* This file contains utilities for creating elements for markers to be displayed,
* and parsing out the blueprint to generate correct values for markers.
*/
loader.lazyRequireGetter(this, "L10N",
"devtools/shared/timeline/global", true);
loader.lazyRequireGetter(this, "TIMELINE_BLUEPRINT",
"devtools/shared/timeline/global", true);
/**
* Returns the correct label to display for passed in marker, based
* off of the blueprints.
*
* @param {ProfileTimelineMarker} marker
* @return {string}
*/
function getMarkerLabel (marker) {
let blueprint = TIMELINE_BLUEPRINT[marker.name];
// Either use the label function in the blueprint, or use it directly
// as a string.
return typeof blueprint.label === "function" ? blueprint.label(marker) : blueprint.label;
}
exports.getMarkerLabel = getMarkerLabel;
/**
* Returns an array of objects with key/value pairs of what should be rendered
* in the marker details view.
*
* @param {ProfileTimelineMarker} marker
* @return {Array<object>}
*/
function getMarkerFields (marker) {
let blueprint = TIMELINE_BLUEPRINT[marker.name];
return (blueprint.fields || []).reduce((fields, field) => {
// Ensure this marker has this field present
if (field.property in marker) {
let label = field.label;
let value = marker[field.property];
// If a formatter function defined, use it to get the
// value we actually want to display.
if (typeof field.formatter === "function") {
value = field.formatter(marker);
}
fields.push({ label, value });
}
return fields;
}, []);
}
exports.getMarkerFields = getMarkerFields;
/**
* Utilites for creating elements for markers.
*/
const DOM = exports.DOM = {
/**
* Builds all the fields possible for the given marker. Returns an
* array of elements to be appended to a parent element.
*
* @param {Document} doc
* @param {ProfileTimelineMarker} marker
* @return {Array<Element>}
*/
buildFields: function (doc, marker) {
let blueprint = TIMELINE_BLUEPRINT[marker.name];
let fields = getMarkerFields(marker);
return fields.map(({ label, value }) => DOM.buildNameValueLabel(doc, label, value));
},
/**
* Builds the label representing marker's type.
*
* @param {Document} doc
* @param {ProfileTimelineMarker}
* @return {Element}
*/
buildTitle: function (doc, marker) {
let blueprint = TIMELINE_BLUEPRINT[marker.name];
let hbox = doc.createElement("hbox");
hbox.setAttribute("align", "center");
let bullet = doc.createElement("hbox");
bullet.className = `marker-details-bullet ${blueprint.colorName}`;
let title = getMarkerLabel(marker);
let label = doc.createElement("label");
label.className = "marker-details-type";
label.setAttribute("value", title);
hbox.appendChild(bullet);
hbox.appendChild(label);
return hbox;
},
/**
* Builds the duration element, like "Duration: 200ms".
*
* @param {Document} doc
* @param {ProfileTimelineMarker} marker
* @return {Element}
*/
buildDuration: function (doc, marker) {
let label = L10N.getStr("timeline.markerDetail.duration");
let value = L10N.getFormatStrWithNumbers("timeline.tick", marker.end - marker.start);
let el = DOM.buildNameValueLabel(doc, label, value);
el.classList.add("marker-details-duration");
return el;
},
/**
* Builds labels for name:value pairs. Like "Start: 100ms",
* "Duration: 200ms", ...
*
* @param {Document} doc
* @param string field
* String identifier for label's name.
* @param string value
* Label's value.
* @return {Element}
*/
buildNameValueLabel: function (doc, field, value) {
let hbox = doc.createElement("hbox");
let labelName = doc.createElement("label");
let labelValue = doc.createElement("label");
labelName.className = "plain marker-details-labelname";
labelValue.className = "plain marker-details-labelvalue";
labelName.setAttribute("value", field);
labelValue.setAttribute("value", value);
hbox.appendChild(labelName);
hbox.appendChild(labelValue);
return hbox;
},
};

View File

@ -19,6 +19,8 @@ loader.lazyImporter(this, "clearNamedTimeout",
"resource:///modules/devtools/ViewHelpers.jsm");
loader.lazyRequireGetter(this, "EventEmitter",
"devtools/toolkit/event-emitter");
loader.lazyRequireGetter(this, "MarkerUtils",
"devtools/shared/timeline/marker-utils");
const HTML_NS = "http://www.w3.org/1999/xhtml";
@ -441,14 +443,7 @@ Waterfall.prototype = {
name.setAttribute("flex", "1");
name.className = "plain waterfall-marker-name";
let label;
if (marker.causeName) {
label = this._l10n.getFormatStr("timeline.markerDetailFormat",
blueprint.label,
marker.causeName);
} else {
label = blueprint.label;
}
let label = MarkerUtils.getMarkerLabel(marker);
name.setAttribute("value", label);
name.setAttribute("tooltiptext", label);
sidebar.appendChild(name);

View File

@ -101,12 +101,32 @@
text-shadow: none;
}
#performance-view .realtime-message {
opacity: 0.5;
display: block;
}
#performance-view toolbarbutton.record-button[checked],
#performance-view toolbarbutton.record-button[checked] {
color: var(--theme-selection-color);
background: var(--theme-selection-background);
}
#performance-view .realtime-disabled-message,
#performance-view .realtime-disabled-on-e10s-message {
display: none;
}
#performance-view[e10s="disabled"] .realtime-disabled-on-e10s-message {
display: block;
opacity: 0.5;
}
#performance-view[e10s="unsupported"] .realtime-disabled-message {
display: block;
opacity: 0.5;
}
#details-pane-container .buffer-status-message,
#details-pane-container .buffer-status-message-full {
display: none;
@ -672,3 +692,35 @@
background-image: url(chrome://browser/skin/devtools/webconsole@2x.png);
}
}
/**
* Configurable Options
*
* Elements can be tagged with a class and visibility is controlled via a preference being
* applied or removed.
*/
/**
* devtools.performance.ui.experimental
*/
menuitem.experimental-option::before {
content: "";
background-image: url(chrome://browser/skin/devtools/webconsole.png);
background-repeat: no-repeat;
background-size: 48px 40px;
margin: 2px 5px 0 0;
width: 8px;
height: 8px;
max-height: 8px;
display: inline-block;
background-position: -16px -16px;
}
@media (min-resolution: 1.25dppx) {
menuitem.experimental-option::before {
background-image: url(chrome://browser/skin/devtools/webconsole@2x.png);
}
}
#performance-options-menupopup:not(.experimental-enabled) .experimental-option,
#performance-options-menupopup:not(.experimental-enabled) .experimental-option::before {
display: none;
}

View File

@ -25,4 +25,6 @@ dictionary ProfileTimelineMarker {
unsigned short eventPhase;
/* For Paint markers. */
sequence<ProfileTimelineLayerRect> rectangles;
/* For Style markers. */
DOMString restyleHint;
};

View File

@ -4186,7 +4186,6 @@ RestyleManager::StructsToLog()
}
#endif
#ifdef DEBUG
/* static */ nsCString
RestyleManager::RestyleHintToString(nsRestyleHint aHint)
{
@ -4219,6 +4218,7 @@ RestyleManager::RestyleHintToString(nsRestyleHint aHint)
return result;
}
#ifdef DEBUG
/* static */ nsCString
RestyleManager::ChangeHintToString(nsChangeHint aHint)
{

View File

@ -348,8 +348,9 @@ public:
mOverflowChangedTracker.Flush();
}
#ifdef DEBUG
static nsCString RestyleHintToString(nsRestyleHint aHint);
#ifdef DEBUG
static nsCString ChangeHintToString(nsChangeHint aHint);
#endif

View File

@ -96,6 +96,30 @@ struct RestyleCollector {
#endif
};
class RestyleTimelineMarker : public TimelineMarker
{
public:
RestyleTimelineMarker(nsDocShell* aDocShell,
TracingMetadata aMetaData,
nsRestyleHint aRestyleHint)
: TimelineMarker(aDocShell, "Styles", aMetaData)
{
if (aRestyleHint) {
mRestyleHint.AssignWithConversion(RestyleManager::RestyleHintToString(aRestyleHint));
}
}
virtual void AddDetails(mozilla::dom::ProfileTimelineMarker& aMarker) override
{
if (GetMetaData() == TRACING_INTERVAL_START) {
aMarker.mRestyleHint.Construct(mRestyleHint);
}
}
private:
nsAutoString mRestyleHint;
};
static PLDHashOperator
CollectRestyles(nsISupports* aElement,
nsAutoPtr<RestyleTracker::RestyleData>& aData,
@ -202,6 +226,13 @@ RestyleTracker::DoProcessRestyles()
PROFILER_LABEL("RestyleTracker", "ProcessRestyles",
js::ProfileEntry::Category::CSS);
bool isTimelineRecording = false;
nsDocShell* docShell =
static_cast<nsDocShell*>(mRestyleManager->PresContext()->GetDocShell());
if (docShell) {
docShell->GetRecordProfileTimelineMarkers(&isTimelineRecording);
}
// Create a ReframingStyleContexts struct on the stack and put it in our
// mReframingStyleContexts for almost all of the remaining scope of
// this function.
@ -311,6 +342,14 @@ RestyleTracker::DoProcessRestyles()
continue;
}
if (isTimelineRecording) {
mozilla::UniquePtr<TimelineMarker> marker =
MakeUnique<RestyleTimelineMarker>(docShell,
TRACING_INTERVAL_START,
data->mRestyleHint);
docShell->AddProfileTimelineMarker(Move(marker));
}
#if defined(MOZ_ENABLE_PROFILER_SPS) && !defined(MOZILLA_XPCOMRT_API)
Maybe<GeckoProfilerTracingRAII> profilerRAII;
if (profiler_feature_active("restyle")) {
@ -319,6 +358,14 @@ RestyleTracker::DoProcessRestyles()
#endif
ProcessOneRestyle(element, data->mRestyleHint, data->mChangeHint);
AddRestyleRootsIfAwaitingRestyle(data->mDescendants);
if (isTimelineRecording) {
mozilla::UniquePtr<TimelineMarker> marker =
MakeUnique<RestyleTimelineMarker>(docShell,
TRACING_INTERVAL_END,
data->mRestyleHint);
docShell->AddProfileTimelineMarker(Move(marker));
}
}
if (mHaveLaterSiblingRestyles) {
@ -359,9 +406,25 @@ RestyleTracker::DoProcessRestyles()
profilerRAII.emplace("Paint", "Styles", Move(currentRestyle->mBacktrace));
}
#endif
if (isTimelineRecording) {
mozilla::UniquePtr<TimelineMarker> marker =
MakeUnique<RestyleTimelineMarker>(docShell,
TRACING_INTERVAL_START,
currentRestyle->mRestyleHint);
docShell->AddProfileTimelineMarker(Move(marker));
}
ProcessOneRestyle(currentRestyle->mElement,
currentRestyle->mRestyleHint,
currentRestyle->mChangeHint);
if (isTimelineRecording) {
mozilla::UniquePtr<TimelineMarker> marker =
MakeUnique<RestyleTimelineMarker>(docShell,
TRACING_INTERVAL_END,
currentRestyle->mRestyleHint);
docShell->AddProfileTimelineMarker(Move(marker));
}
}
}
}

View File

@ -1656,11 +1656,6 @@ nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime)
if (!mStyleFlushObservers.Contains(shell))
continue;
nsRefPtr<nsDocShell> docShell = GetDocShell(shell->GetPresContext());
if (docShell) {
docShell->AddProfileTimelineMarker("Styles", TRACING_INTERVAL_START);
}
if (!tracingStyleFlush) {
tracingStyleFlush = true;
profiler_tracing("Paint", "Styles", mStyleCause, TRACING_INTERVAL_START);
@ -1679,10 +1674,6 @@ nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime)
presContext->NotifyFontFaceSetOnRefresh();
}
NS_RELEASE(shell);
if (docShell) {
docShell->AddProfileTimelineMarker("Styles", TRACING_INTERVAL_END);
}
}
if (tracingStyleFlush) {

View File

@ -485,7 +485,7 @@
case KeyEvent.DOM_VK_BACK_SPACE:
if (aEvent.shiftKey)
#endif
cancel = this.mController.handleDelete();
cancel = this.handleDelete();
break;
case KeyEvent.DOM_VK_DOWN:
case KeyEvent.DOM_VK_UP:
@ -514,6 +514,12 @@
]]></body>
</method>
<method name="handleDelete">
<body><![CDATA[
return this.mController.handleDelete();
]]></body>
</method>
<!-- ::::::::::::: miscellaneous ::::::::::::: -->
<method name="initSearchNames">