Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2016-03-02 16:09:27 +01:00
commit 044a3f21fc
150 changed files with 1899 additions and 1484 deletions

View File

@ -15,6 +15,7 @@ const DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC =
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
@ -322,27 +323,24 @@ DistributionCustomizer.prototype = {
if (!(globalPrefs["id"] && globalPrefs["version"] && globalPrefs["about"]))
return this._checkCustomizationComplete();
let defaults = this._prefSvc.getDefaultBranch(null);
let defaults = new Preferences({defaultBranch: true});
// Global really contains info we set as prefs. They're only
// separate because they are "special" (read: required)
defaults.setCharPref("distribution.id", this._ini.getString("Global", "id"));
defaults.setCharPref("distribution.version",
this._ini.getString("Global", "version"));
defaults.set("distribution.id", this._ini.getString("Global", "id"));
defaults.set("distribution.version", this._ini.getString("Global", "version"));
let partnerAbout = Cc["@mozilla.org/supports-string;1"].
createInstance(Ci.nsISupportsString);
let partnerAbout;
try {
if (globalPrefs["about." + this._locale]) {
partnerAbout.data = this._ini.getString("Global", "about." + this._locale);
partnerAbout = this._ini.getString("Global", "about." + this._locale);
} else if (globalPrefs["about." + this._language]) {
partnerAbout.data = this._ini.getString("Global", "about." + this._language);
partnerAbout = this._ini.getString("Global", "about." + this._language);
} else {
partnerAbout.data = this._ini.getString("Global", "about");
partnerAbout = this._ini.getString("Global", "about");
}
defaults.setComplexValue("distribution.about",
Ci.nsISupportsString, partnerAbout);
defaults.set("distribution.about", partnerAbout);
} catch (e) {
/* ignore bad prefs due to bug 895473 and move on */
Cu.reportError(e);
@ -352,28 +350,11 @@ DistributionCustomizer.prototype = {
for (let key of enumerate(this._ini.getKeys("Preferences"))) {
try {
let value = parseValue(this._ini.getString("Preferences", key));
switch (typeof value) {
case "boolean":
defaults.setBoolPref(key, value);
break;
case "number":
defaults.setIntPref(key, value);
break;
case "string":
defaults.setCharPref(key, value);
break;
case "undefined":
defaults.setCharPref(key, value);
break;
}
Preferences.set(key, value);
} catch (e) { /* ignore bad prefs and move on */ }
}
}
// We eval() the localizable prefs as well (even though they'll
// always get set as a string) to keep the INI format consistent:
// string prefs always need to be in quotes
let localizedStr = Cc["@mozilla.org/pref-localizedstring;1"].
createInstance(Ci.nsIPrefLocalizedString);
@ -385,7 +366,7 @@ DistributionCustomizer.prototype = {
let value = parseValue(this._ini.getString("LocalizablePreferences-" + this._locale, key));
if (value !== undefined) {
localizedStr.data = "data:text/plain," + key + "=" + value;
defaults.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr);
defaults._prefBranch.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr);
}
usedLocalizablePreferences.push(key);
} catch (e) { /* ignore bad prefs and move on */ }
@ -401,7 +382,7 @@ DistributionCustomizer.prototype = {
let value = parseValue(this._ini.getString("LocalizablePreferences-" + this._language, key));
if (value !== undefined) {
localizedStr.data = "data:text/plain," + key + "=" + value;
defaults.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr);
defaults._prefBranch.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr);
}
usedLocalizablePreferences.push(key);
} catch (e) { /* ignore bad prefs and move on */ }
@ -419,7 +400,7 @@ DistributionCustomizer.prototype = {
value = value.replace(/%LOCALE%/g, this._locale);
value = value.replace(/%LANGUAGE%/g, this._language);
localizedStr.data = "data:text/plain," + key + "=" + value;
defaults.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr);
defaults._prefBranch.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr);
}
} catch (e) { /* ignore bad prefs and move on */ }
}

View File

@ -33,20 +33,13 @@ function BrowserAction(options, extension) {
this.tabManager = TabManager.for(extension);
let title = extension.localize(options.default_title || "");
let popup = extension.localize(options.default_popup || "");
if (popup) {
popup = extension.baseURI.resolve(popup);
}
this.defaults = {
enabled: true,
title: title || extension.name,
title: options.default_title || extension.name,
badgeText: "",
badgeBackgroundColor: null,
icon: IconDetails.normalize({path: options.default_icon}, extension,
null, true),
popup: popup,
icon: IconDetails.normalize({path: options.default_icon}, extension),
popup: options.default_popup || "",
};
this.tabContext = new TabContext(tab => Object.create(this.defaults),

View File

@ -19,18 +19,11 @@ function PageAction(options, extension) {
this.tabManager = TabManager.for(extension);
let title = extension.localize(options.default_title || "");
let popup = extension.localize(options.default_popup || "");
if (popup) {
popup = extension.baseURI.resolve(popup);
}
this.defaults = {
show: false,
title: title || extension.name,
icon: IconDetails.normalize({path: options.default_icon}, extension,
null, true),
popup: popup && extension.baseURI.resolve(popup),
title: options.default_title || extension.name,
icon: IconDetails.normalize({path: options.default_icon}, extension),
popup: options.default_popup || "",
};
this.tabContext = new TabContext(tab => Object.create(this.defaults),

View File

@ -656,6 +656,19 @@ extensions.registerSchemaAPI("tabs", null, (extension, context) => {
message, recipient);
},
detectLanguage: function(tabId) {
let tab = tabId !== null ? TabManager.getTab(tabId) : TabManager.activeTab;
if (!tab) {
return Promise.reject({message: `Invalid tab ID: ${tabId}`});
}
let browser = tab.linkedBrowser;
let recipient = {innerWindowID: browser.innerWindowID};
return context.sendMessage(browser.messageManager, "Extension:DetectLanguage",
{}, recipient);
},
_execute: function(tabId, details, kind, method) {
let tab = tabId !== null ? TabManager.getTab(tabId) : TabManager.activeTab;
let mm = tab.linkedBrowser.messageManager;

View File

@ -36,7 +36,7 @@ global.IconDetails = {
//
// If no context is specified, instead of throwing an error, this
// function simply logs a warning message.
normalize(details, extension, context = null, localize = false) {
normalize(details, extension, context = null) {
let result = {};
try {
@ -73,12 +73,7 @@ global.IconDetails = {
throw new Error(`Invalid icon size ${size}, must be an integer`);
}
let url = path[size];
if (localize) {
url = extension.localize(url);
}
url = baseURI.resolve(path[size]);
let url = baseURI.resolve(path[size]);
// The Chrome documentation specifies these parameters as
// relative paths. We currently accept absolute URLs as well,

View File

@ -12,9 +12,21 @@
"browser_action": {
"type": "object",
"properties": {
"default_title": { "type": "string", "optional": true },
"default_icon": { "$ref": "IconPath", "optional": true },
"default_popup": { "type": "string", "format": "relativeUrl", "optional": true }
"default_title": {
"type": "string",
"optional": true,
"preprocess": "localize"
},
"default_icon": {
"$ref": "IconPath",
"optional": true
},
"default_popup": {
"type": "string",
"format": "relativeUrl",
"optional": true,
"preprocess": "localize"
}
},
"optional": true
}

View File

@ -12,9 +12,21 @@
"page_action": {
"type": "object",
"properties": {
"default_title": { "type": "string", "optional": true },
"default_icon": { "$ref": "IconPath", "optional": true },
"default_popup": { "type": "string", "format": "relativeUrl", "optional": true }
"default_title": {
"type": "string",
"optional": true,
"preprocess": "localize"
},
"default_icon": {
"$ref": "IconPath",
"optional": true
},
"default_popup": {
"type": "string",
"format": "relativeUrl",
"optional": true,
"preprocess": "localize"
}
},
"optional": true
}

View File

@ -15,6 +15,7 @@
// Test harness globals
"ExtensionTestUtils": false,
"TestUtils": false,
"clickBrowserAction": true,
"clickPageAction": true,

View File

@ -9,6 +9,8 @@ support-files =
file_popup_api_injection_b.html
file_iframe_document.html
file_iframe_document.sjs
file_language_fr_en.html
file_language_ja.html
[browser_ext_simple.js]
[browser_ext_currentWindow.js]
@ -28,6 +30,7 @@ support-files =
[browser_ext_runtime_setUninstallURL.js]
[browser_ext_tabs_audio.js]
[browser_ext_tabs_captureVisibleTab.js]
[browser_ext_tabs_detectLanguage.js]
[browser_ext_tabs_events.js]
[browser_ext_tabs_executeScript.js]
[browser_ext_tabs_executeScript_good.js]
@ -50,4 +53,5 @@ support-files =
[browser_ext_windows_update.js]
[browser_ext_contentscript_connect.js]
[browser_ext_tab_runtimeConnect.js]
[browser_ext_topwindowid.js]
[browser_ext_webNavigation_getFrames.js]

View File

@ -82,6 +82,8 @@ function* runTests(options) {
let extension = ExtensionTestUtils.loadExtension({
manifest: options.manifest,
files: options.files || {},
background: `(${background})(${options.getTests})`,
});
@ -140,12 +142,29 @@ add_task(function* testTabSwitchContext() {
manifest: {
"browser_action": {
"default_icon": "default.png",
"default_popup": "default.html",
"default_title": "Default Title",
"default_popup": "__MSG_popup__",
"default_title": "Default __MSG_title__",
},
"default_locale": "en",
"permissions": ["tabs"],
},
"files": {
"_locales/en/messages.json": {
"popup": {
"message": "default.html",
"description": "Popup",
},
"title": {
"message": "Title",
"description": "Title",
},
},
},
getTests(tabs, expectDefaults) {
let details = [
{"icon": browser.runtime.getURL("default.png"),

View File

@ -82,6 +82,8 @@ function* runTests(options) {
let extension = ExtensionTestUtils.loadExtension({
manifest: options.manifest,
files: options.files || {},
background: `(${background})(${options.getTests})`,
});
@ -154,13 +156,29 @@ add_task(function* testTabSwitchContext() {
"page_action": {
"default_icon": "default.png",
"default_popup": "default.html",
"default_title": "Default Title \u263a",
"default_popup": "__MSG_popup__",
"default_title": "Default __MSG_title__ \u263a",
},
"default_locale": "en",
"permissions": ["tabs"],
},
"files": {
"_locales/en/messages.json": {
"popup": {
"message": "default.html",
"description": "Popup",
},
"title": {
"message": "Title",
"description": "Title",
},
},
},
getTests(tabs) {
let details = [
{"icon": browser.runtime.getURL("default.png"),

View File

@ -0,0 +1,57 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
add_task(function* testDetectLanguage() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"permissions": ["tabs"],
},
background() {
const BASE_PATH = "browser/browser/components/extensions/test/browser";
function loadTab(url) {
let tabId;
let awaitUpdated = new Promise(resolve => {
browser.tabs.onUpdated.addListener(function onUpdated(changedTabId, changed, tab) {
if (changedTabId === tabId && changed.url) {
browser.tabs.onUpdated.removeListener(onUpdated);
resolve(tab);
}
});
});
return browser.tabs.create({url}).then(tab => {
tabId = tab.id;
return awaitUpdated;
});
}
loadTab(`http://example.co.jp/${BASE_PATH}/file_language_ja.html`).then(tab => {
return browser.tabs.detectLanguage(tab.id).then(lang => {
browser.test.assertEq("ja", lang, "Japanese document should be detected as Japanese");
return browser.tabs.remove(tab.id);
});
}).then(() => {
return loadTab(`http://example.co.jp/${BASE_PATH}/file_language_fr_en.html`);
}).then(tab => {
return browser.tabs.detectLanguage(tab.id).then(lang => {
browser.test.assertEq("fr", lang, "French/English document should be detected as primarily French");
return browser.tabs.remove(tab.id);
});
}).then(() => {
browser.test.notifyPass("detectLanguage");
}).catch(e => {
browser.test.fail(`Error: ${e} :: ${e.stack}`);
browser.test.notifyFail("detectLanguage");
});
},
});
yield extension.startup();
yield extension.awaitFinish("detectLanguage");
yield extension.unload();
});

View File

@ -2,6 +2,95 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
add_task(function* tabsSendMessageReply() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"permissions": ["tabs"],
"content_scripts": [{
"matches": ["http://example.com/"],
"js": ["content-script.js"],
"run_at": "document_start",
}],
},
background: function() {
let promiseResponse = new Promise(resolve => {
browser.runtime.onMessage.addListener((msg, sender, respond) => {
if (msg == "content-script-ready") {
let tabId = sender.tab.id;
browser.tabs.sendMessage(tabId, "respond-never", response => {
browser.test.fail("Got unexpected response callback");
browser.test.notifyFail("sendMessage");
});
Promise.all([
promiseResponse,
browser.tabs.sendMessage(tabId, "respond-now"),
new Promise(resolve => browser.tabs.sendMessage(tabId, "respond-soon", resolve)),
browser.tabs.sendMessage(tabId, "respond-promise"),
browser.tabs.sendMessage(tabId, "respond-never"),
browser.tabs.sendMessage(tabId, "respond-error").catch(error => Promise.resolve({error})),
browser.tabs.sendMessage(tabId, "throw-error").catch(error => Promise.resolve({error})),
]).then(([response, respondNow, respondSoon, respondPromise, respondNever, respondError, throwError]) => {
browser.test.assertEq("expected-response", response, "Content script got the expected response");
browser.test.assertEq("respond-now", respondNow, "Got the expected immediate response");
browser.test.assertEq("respond-soon", respondSoon, "Got the expected delayed response");
browser.test.assertEq("respond-promise", respondPromise, "Got the expected promise response");
browser.test.assertEq(undefined, respondNever, "Got the expected no-response resolution");
browser.test.assertEq("respond-error", respondError.error.message, "Got the expected error response");
browser.test.assertEq("throw-error", throwError.error.message, "Got the expected thrown error response");
return browser.tabs.remove(tabId);
}).then(() => {
browser.test.notifyPass("sendMessage");
});
return Promise.resolve("expected-response");
} else if (msg[0] == "got-response") {
resolve(msg[1]);
}
});
});
browser.tabs.create({url: "http://example.com/"});
},
files: {
"content-script.js": function() {
browser.runtime.onMessage.addListener((msg, sender, respond) => {
if (msg == "respond-now") {
respond(msg);
} else if (msg == "respond-soon") {
setTimeout(() => { respond(msg); }, 0);
return true;
} else if (msg == "respond-promise") {
return Promise.resolve(msg);
} else if (msg == "respond-never") {
return;
} else if (msg == "respond-error") {
return Promise.reject(new Error(msg));
} else if (msg == "throw-error") {
throw new Error(msg);
}
});
browser.runtime.sendMessage("content-script-ready").then(response => {
browser.runtime.sendMessage(["got-response", response]);
});
},
},
});
yield extension.startup();
yield extension.awaitFinish("sendMessage");
yield extension.unload();
});
add_task(function* tabsSendMessageNoExceptionOnNonExistentTab() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {

View File

@ -0,0 +1,23 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
add_task(function* test_topwindowid_cleanup() {
let {Frames} = Cu.import("resource://gre/modules/ExtensionManagement.jsm", {});
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
let {outerWindowID, messageManager} = tab.linkedBrowser;
ok(Frames.topWindowIds.has(outerWindowID), "Outer window ID is registered");
let awaitDisconnect = TestUtils.topicObserved("message-manager-disconnect",
subject => subject === messageManager);
yield BrowserTestUtils.removeTab(tab);
yield awaitDisconnect;
ok(!Frames.topWindowIds.has(outerWindowID), "Outer window ID is no longer registered");
});

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
France is the largest country in Western Europe and the third-largest in Europe as a whole.
A accès aux chiens et aux frontaux qui lui ont été il peut consulter et modifier ses collections et exporter
Cet article concerne le pays européen aujourdhui appelé République française. Pour dautres usages du nom France,
Pour une aide rapide et effective, veuiller trouver votre aide dans le menu ci-dessus.
Motoring events began soon after the construction of the first successful gasoline-fueled automobiles. The quick brown fox jumps over the lazy dog.
</body>
</html>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
このペ ジでは アカウントに指定された予算の履歴を一覧にしています それぞれの項目には 予算額と特定期間のステ タスが表示されます 現在または今後の予算を設定するには
</body>
</html>

View File

@ -5,6 +5,7 @@
id=disttest
version=1.0
about=Test distribution file
about.en-US=Tèƨƭ δïƨƭřïβúƭïôñ ƒïℓè
[Preferences]
distribution.test.string="Test String"

View File

@ -62,6 +62,10 @@ add_task(function* () {
let glue = Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsIObserver)
glue.observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_DISTRIBUTION_CUSTOMIZATION);
Assert.equal(Services.prefs.getCharPref("distribution.id"), "disttest");
Assert.equal(Services.prefs.getCharPref("distribution.version"), "1.0");
Assert.equal(Services.prefs.getComplexValue("distribution.about", Ci.nsISupportsString).data, "Tèƨƭ δïƨƭřïβúƭïôñ ƒïℓè");
Assert.equal(Services.prefs.getCharPref("distribution.test.string"), "Test String");
Assert.equal(Services.prefs.getCharPref("distribution.test.string.noquotes"), "Test String");
Assert.equal(Services.prefs.getIntPref("distribution.test.int"), 777);

View File

@ -24,6 +24,7 @@ var helpers = {};
var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
var { TargetFactory } = require("devtools/client/framework/target");
var Services = require("Services");
var assert = { ok: ok, is: is, log: info };
var util = require('gcli/util/util');

View File

@ -100,13 +100,24 @@ function _removeOrDisableBreakpoint(location, isDisabled) {
}
const bpClient = getBreakpointClient(bp.actor);
return dispatch({
const action = {
type: constants.REMOVE_BREAKPOINT,
breakpoint: bp,
disabled: isDisabled,
disabled: isDisabled
};
// If the breakpoint is already disabled, we don't need to remove
// it from the server. We just need to dispatch an action
// simulating a successful server request to remove it, and it
// will be removed completely from the state.
if(!bp.disabled) {
return dispatch(Object.assign({}, action, {
[PROMISE]: bpClient.remove()
});
}));
}
else {
return dispatch(Object.assign({}, action, { status: "done" }));
}
}
}

View File

@ -5,6 +5,7 @@
const constants = require('../constants');
const promise = require('promise');
const Services = require('Services');
const { dumpn } = require("devtools/shared/DevToolsUtils");
const { PROMISE, HISTOGRAM_ID } = require('devtools/client/shared/redux/middleware/promise');
const { getSource, getSourceText } = require('../queries');

View File

@ -1,6 +1,7 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* import-globals-from ../../debugger-controller.js */
"use strict";
const utils = require('../utils');

View File

@ -4,6 +4,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* globals document, window */
/* import-globals-from ./debugger-controller.js */
"use strict";
// Maps known URLs to friendly source group names and put them at the

View File

@ -9,6 +9,7 @@
const { Ci, Cu } = require("chrome");
const promise = require("promise");
const EventEmitter = require("devtools/shared/event-emitter");
const Services = require("Services");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true);

View File

@ -6,8 +6,8 @@
"use strict";
/* eslint no-unused-vars: [2, {"vars": "local"}] */
/* import-globals-from ./shared-head.js */
// Currently this file expects "promise" to be imported into scope.
/* globals promise */
// Common utility functions for working with Redux stores. The file is meant
// to be safe to load in both mochitest and xpcshell environments.

View File

@ -10,6 +10,7 @@ const {Cu} = require("chrome");
const {setTimeout, clearTimeout} =
Cu.import("resource://gre/modules/Timer.jsm", {});
const {gDevTools} = require("devtools/client/framework/devtools");
const Services = require("Services");
const DEFAULT_PREVIEW_TEXT = "Abc";
const PREVIEW_UPDATE_DELAY = 150;

View File

@ -28,6 +28,7 @@ const {editableField, InplaceEditor} =
require("devtools/client/shared/inplace-editor");
const {HTMLEditor} = require("devtools/client/inspector/markup/html-editor");
const promise = require("promise");
const Services = require("Services");
const {Tooltip} = require("devtools/client/shared/widgets/Tooltip");
const EventEmitter = require("devtools/shared/event-emitter");
const Heritage = require("sdk/core/heritage");

View File

@ -26,6 +26,7 @@ const {
SELECTOR_PSEUDO_CLASS
} = require("devtools/client/shared/css-parsing-utils");
const promise = require("promise");
const Services = require("Services");
const EventEmitter = require("devtools/shared/event-emitter");
XPCOMUtils.defineLazyGetter(this, "_strings", function() {

View File

@ -4,6 +4,8 @@
"use strict";
/* globals Services, sendAsyncMessage, addMessageListener */
// XXX Some helper API could go to testing/mochitest/tests/SimpleTest/AsyncContentUtils.js
// (or at least to share test API in devtools)

View File

@ -7,6 +7,7 @@
"use strict";
const {Cu, Cc, Ci} = require("chrome");
const Services = require("Services");
const {getMostRecentBrowserWindow} = require("sdk/window/utils");
const OPEN_FLAGS = {

View File

@ -48,14 +48,6 @@ aggregate.mb=%S MB
# it has a creation time to display.
snapshot-title.loading=Processing…
# LOCALIZATION NOTE (checkbox.invertTree): The label describing the boolean
# checkbox whether or not to invert the tree.
checkbox.invertTree=Invert tree
# LOCALIZATION NOTE (checkbox.invertTree): The tooltip for the label describing
# the boolean checkbox whether or not to invert the tree.
checkbox.invertTree.tooltip=When uninverted, the tree is shown top-down, giving an overview of memory consumption. When inverted, the tree is shown bottom-up, highlighting the heaviest memory consumers.
# LOCALIZATION NOTE (checkbox.recordAllocationStacks): The label describing the
# boolean checkbox whether or not to record allocation stacks.
checkbox.recordAllocationStacks=Record allocation stacks
@ -65,29 +57,33 @@ checkbox.recordAllocationStacks=Record allocation stacks
# stacks.
checkbox.recordAllocationStacks.tooltip=Toggle the recording of allocation stacks. Subsequent heap snapshots will be able to label and group objects created when allocation stack recording is active by their allocation stack. Recording allocation stacks has a performance overhead.
# LOCALIZATION NOTE (toolbar.breakdownBy): The label describing the select menu
# options of the breakdown options.
toolbar.breakdownBy=Group by:
# LOCALIZATION NOTE (toolbar.displayBy): The label describing the select menu
# options of the display options.
toolbar.displayBy=Group by:
# LOCALIZATION NOTE (toolbar.breakdownBy): The tooltip for the label describing
# the select menu options of the breakdown options.
toolbar.breakdownBy.tooltip=Change how objects are grouped
# LOCALIZATION NOTE (toolbar.displayBy.tooltip): The tooltip for the label
# describing the select menu options of the display options.
toolbar.displayBy.tooltip=Change how objects are grouped
# LOCALIZATION NOTE (breakdowns.coarseType.tooltip): The tooltip for the "coarse
# type" breakdown option.
breakdowns.coarseType.tooltip=Group items into broad categories
# LOCALIZATION NOTE (censusDisplays.coarseType.tooltip): The tooltip for the
# "coarse type" display option.
censusDisplays.coarseType.tooltip=Group items by their type
# LOCALIZATION NOTE (breakdowns.allocationStack.tooltip): The tooltip for the
# "allocation stack" breakdown option.
breakdowns.allocationStack.tooltip=Group items by the JavaScript stack recorded when the object was allocated
# LOCALIZATION NOTE (censusDisplays.allocationStack.tooltip): The tooltip for
# the "allocation stack" display option.
censusDisplays.allocationStack.tooltip=Group items by the JavaScript stack recorded when the object was allocated
# LOCALIZATION NOTE (breakdowns.objectClass.tooltip): The tooltip for the
# "object class" breakdown option.
breakdowns.objectClass.tooltip=Group items by their JavaScript Object [[class]] name
# LOCALIZATION NOTE (censusDisplays.invertedAllocationStack.tooltip): The
# tooltip for the "inverted allocation stack" display option.
censusDisplays.invertedAllocationStack.tooltip=Group items by the inverted JavaScript call stack recorded when the object was created
# LOCALIZATION NOTE (breakdowns.internalType.tooltip): The tooltip for the
# "internal type" breakdown option.
breakdowns.internalType.tooltip=Group items by their internal C++ type
# LOCALIZATION NOTE (censusDisplays.objectClass.tooltip): The tooltip for the
# "object class" display option.
censusDisplays.objectClass.tooltip=Group items by their JavaScript Object [[class]] name
# LOCALIZATION NOTE (censusDisplays.internalType.tooltip): The tooltip for the
# "internal type" display option.
censusDisplays.internalType.tooltip=Group items by their internal C++ type
# LOCALIZATION NOTE (toolbar.labelBy): The label describing the select menu
# options of the label options.
@ -97,17 +93,17 @@ toolbar.labelBy=Label by:
# select menu options of the label options.
toolbar.labelBy.tooltip=Change how objects are labeled
# LOCALIZATION NOTE (dominatorTreeBreakdowns.coarseType.tooltip): The tooltip for the "coarse
# type" dominator tree breakdown option.
dominatorTreeBreakdowns.coarseType.tooltip=Label objects by the broad categories they fit in
# LOCALIZATION NOTE (dominatorTreeDisplays.coarseType.tooltip): The tooltip for
# the "coarse type" dominator tree display option.
dominatorTreeDisplays.coarseType.tooltip=Label objects by the broad categories they fit in
# LOCALIZATION NOTE (dominatorTreeBreakdowns.allocationStack.tooltip): The
# tooltip for the "allocation stack" dominator tree breakdown option.
dominatorTreeBreakdowns.allocationStack.tooltip=Label objects by the JavaScript stack recorded when it was allocated
# LOCALIZATION NOTE (dominatorTreeDisplays.allocationStack.tooltip): The
# tooltip for the "allocation stack" dominator tree display option.
dominatorTreeDisplays.allocationStack.tooltip=Label objects by the JavaScript stack recorded when it was allocated
# LOCALIZATION NOTE (dominatorTreeBreakdowns.internalType.tooltip): The
# tooltip for the "internal type" dominator tree breakdown option.
dominatorTreeBreakdowns.internalType.tooltip=Label objects by their internal C++ type name
# LOCALIZATION NOTE (dominatorTreeDisplays.internalType.tooltip): The
# tooltip for the "internal type" dominator tree display option.
dominatorTreeDisplays.internalType.tooltip=Label objects by their internal C++ type name
# LOCALIZATION NOTE (toolbar.view): The label for the view selector in the
# toolbar.
@ -301,8 +297,8 @@ snapshot.state.saving-census=Saving census…
snapshot.state.error=Error
# LOCALIZATION NOTE (heapview.noAllocationStacks): The message displayed to
# users when selecting a breakdown by "allocation stack" but no allocation
# stacks were recorded in the heap snapshot.
# users when selecting a display by "allocation stack" but no allocation stacks
# were recorded in the heap snapshot.
heapview.noAllocationStacks=No allocation stacks found. Record allocation stacks before taking a heap snapshot.
# LOCALIZATION NOTE (heapview.field.retainedSize): The name of the column in the

View File

@ -18,6 +18,9 @@ responsiveUI.rotate2=Rotate
# LOCALIZATION NOTE (responsiveUI.screenshot): tooltip of the screenshot button.
responsiveUI.screenshot=Screenshot
# LOCALIZATION NOTE (responsiveUI.userAgentPlaceholder): placeholder for the user agent input.
responsiveUI.userAgentPlaceholder=Custom User Agent
# LOCALIZATION NOTE (responsiveUI.screenshotGeneratedFilename): The auto generated filename.
# The first argument (%1$S) is the date string in yyyy-mm-dd format and the second
# argument (%2$S) is the time string in HH.MM.SS format.

View File

@ -98,11 +98,6 @@ stacktrace.anonymousFunction=<anonymous>
# %S is the "Async Cause" of the frame.
stacktrace.asyncStack=(Async: %S)
# LOCALIZATION NOTE (unknownLocation): this string is used to
# display messages with sources that have an unknown location, eg. from
# console.trace() calls.
unknownLocation=<unknown>
# LOCALIZATION NOTE (timerStarted): this string is used to display the result
# of the console.time() call. Parameters: %S is the name of the timer.
timerStarted=%S: timer started

View File

@ -1,35 +0,0 @@
/* 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";
const { assert } = require("devtools/shared/DevToolsUtils");
const { breakdownEquals, createSnapshot } = require("../utils");
const { actions, snapshotState: states } = require("../constants");
const { refresh } = require("./refresh");
const setBreakdownAndRefresh = exports.setBreakdownAndRefresh = function (heapWorker, breakdown) {
return function *(dispatch, getState) {
// Clears out all stored census data and sets the breakdown.
dispatch(setBreakdown(breakdown));
yield dispatch(refresh(heapWorker));
};
};
/**
* Clears out all census data in the snapshots and sets
* a new breakdown.
*
* @param {Breakdown} breakdown
*/
const setBreakdown = exports.setBreakdown = function (breakdown) {
assert(typeof breakdown === "object"
&& breakdown
&& breakdown.by,
`Breakdowns must be an object with a \`by\` property, attempted to set: ${uneval(breakdown)}`);
return {
type: actions.SET_BREAKDOWN,
breakdown,
};
};

View File

@ -0,0 +1,34 @@
/* 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";
const { assert } = require("devtools/shared/DevToolsUtils");
const { actions } = require("../constants");
const { refresh } = require("./refresh");
exports.setCensusDisplayAndRefresh = function(heapWorker, display) {
return function*(dispatch, getState) {
dispatch(setCensusDisplay(display));
yield dispatch(refresh(heapWorker));
};
};
/**
* Clears out all cached census data in the snapshots and sets new display data
* for censuses.
*
* @param {censusDisplayModel} display
*/
const setCensusDisplay = exports.setCensusDisplay = function(display) {
assert(typeof display === "object"
&& display
&& display.breakdown
&& display.breakdown.by,
`Breakdowns must be an object with a \`by\` property, attempted to set: ${uneval(display)}`);
return {
type: actions.SET_CENSUS_DISPLAY,
display,
};
};

View File

@ -7,7 +7,6 @@ const { assert, reportException } = require("devtools/shared/DevToolsUtils");
const { actions, diffingState, viewState } = require("../constants");
const telemetry = require("../telemetry");
const {
breakdownEquals,
getSnapshot,
censusIsUpToDate,
snapshotIsDiffable
@ -51,11 +50,10 @@ const takeCensusDiff = exports.takeCensusDiff = function (heapWorker, first, sec
`Second snapshot must be in a diffable state, found ${second.state}`);
let report, parentMap;
let inverted = getState().inverted;
let breakdown = getState().breakdown;
let display = getState().censusDisplay;
let filter = getState().filter;
if (censusIsUpToDate(inverted, filter, breakdown, getState().diffing.census)) {
if (censusIsUpToDate(filter, display, getState().diffing.census)) {
return;
}
@ -70,26 +68,27 @@ const takeCensusDiff = exports.takeCensusDiff = function (heapWorker, first, sec
return;
}
inverted = getState().inverted;
breakdown = getState().breakdown;
display = getState().censusDisplay;
filter = getState().filter;
dispatch({
type: actions.TAKE_CENSUS_DIFF_START,
first,
second,
inverted,
filter,
breakdown,
display,
});
let opts = inverted ? { asInvertedTreeNode: true } : { asTreeNode: true };
let opts = display.inverted
? { asInvertedTreeNode: true }
: { asTreeNode: true };
opts.filter = filter || null;
try {
({ delta: report, parentMap } = yield heapWorker.takeCensusDiff(first.path,
({ delta: report, parentMap } = yield heapWorker.takeCensusDiff(
first.path,
second.path,
{ breakdown },
{ breakdown: display.breakdown },
opts));
} catch (error) {
reportException("actions/diffing/takeCensusDiff", error);
@ -97,9 +96,8 @@ const takeCensusDiff = exports.takeCensusDiff = function (heapWorker, first, sec
return;
}
}
while (inverted !== getState().inverted
|| filter !== getState().filter
|| !breakdownEquals(breakdown, getState().breakdown));
while (filter !== getState().filter
|| display !== getState().censusDisplay);
dispatch({
type: actions.TAKE_CENSUS_DIFF_END,
@ -107,19 +105,18 @@ const takeCensusDiff = exports.takeCensusDiff = function (heapWorker, first, sec
second,
report,
parentMap,
inverted,
filter,
breakdown,
display,
});
telemetry.countDiff({ inverted, filter, breakdown });
telemetry.countDiff({ filter, display });
};
};
/**
* Ensure that the current diffing data is up to date with the currently
* selected breakdown, filter, inversion, etc. If the state is not up-to-date,
* then a recompute is triggered.
* selected display, filter, etc. If the state is not up-to-date, then a
* recompute is triggered.
*
* @param {HeapAnalysesClient} heapWorker
*/

View File

@ -0,0 +1,35 @@
/* 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";
const { assert } = require("devtools/shared/DevToolsUtils");
const { actions } = require("../constants");
const { refresh } = require("./refresh");
exports.setDominatorTreeDisplayAndRefresh = function(heapWorker, display) {
return function*(dispatch, getState) {
// Clears out all stored census data and sets the display.
dispatch(setDominatorTreeDisplay(display));
yield dispatch(refresh(heapWorker));
};
};
/**
* Clears out all census data in the snapshots and sets
* a new display.
*
* @param {dominatorTreeDisplayModel} display
*/
const setDominatorTreeDisplay = exports.setDominatorTreeDisplay = function (display) {
assert(typeof display === "object"
&& display
&& display.breakdown
&& display.breakdown.by,
`Breakdowns must be an object with a \`by\` property, attempted to set: ${uneval(display)}`);
return {
type: actions.SET_DOMINATOR_TREE_DISPLAY,
display,
};
};

View File

@ -1,37 +0,0 @@
/* 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";
const { assert } = require("devtools/shared/DevToolsUtils");
const { breakdownEquals, createSnapshot } = require("../utils");
const { actions, snapshotState: states } = require("../constants");
const { refresh } = require("./refresh");
const setDominatorTreeBreakdownAndRefresh =
exports.setDominatorTreeBreakdownAndRefresh =
function (heapWorker, breakdown) {
return function *(dispatch, getState) {
// Clears out all stored census data and sets the breakdown.
dispatch(setDominatorTreeBreakdown(breakdown));
yield dispatch(refresh(heapWorker));
};
};
/**
* Clears out all census data in the snapshots and sets
* a new breakdown.
*
* @param {Breakdown} breakdown
*/
const setDominatorTreeBreakdown = exports.setDominatorTreeBreakdown = function (breakdown) {
assert(typeof breakdown === "object"
&& breakdown
&& breakdown.by,
`Breakdowns must be an object with a \`by\` property, attempted to set: ${uneval(breakdown)}`);
return {
type: actions.SET_DOMINATOR_TREE_BREAKDOWN,
breakdown,
};
};

View File

@ -1,18 +0,0 @@
/* 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";
const { actions } = require("../constants");
const { refresh } = require("./refresh");
const toggleInverted = exports.toggleInverted = function () {
return { type: actions.TOGGLE_INVERTED };
};
exports.toggleInvertedAndRefresh = function (heapWorker) {
return function* (dispatch, getState) {
dispatch(toggleInverted());
yield dispatch(refresh(heapWorker));
};
};

View File

@ -5,11 +5,10 @@
DevToolsModules(
'allocations.js',
'breakdown.js',
'census-display.js',
'diffing.js',
'dominatorTreeBreakdown.js',
'dominator-tree-display.js',
'filter.js',
'inverted.js',
'io.js',
'refresh.js',
'sizes.js',

View File

@ -7,7 +7,6 @@ const { assert, reportException } = require("devtools/shared/DevToolsUtils");
const {
censusIsUpToDate,
getSnapshot,
breakdownEquals,
createSnapshot,
dominatorTreeIsComputed,
} = require("../utils");
@ -44,7 +43,7 @@ const takeSnapshotAndCensus = exports.takeSnapshotAndCensus = function (front, h
/**
* Selects a snapshot and if the snapshot's census is using a different
* breakdown, take a new census.
* display, take a new census.
*
* @param {HeapAnalysesClient} heapWorker
* @param {snapshotId} id
@ -137,37 +136,37 @@ const takeCensus = exports.takeCensus = function (heapWorker, id) {
`Can only take census of snapshots in READ or SAVED_CENSUS state, found ${snapshot.state}`);
let report, parentMap;
let inverted = getState().inverted;
let breakdown = getState().breakdown;
let display = getState().censusDisplay;
let filter = getState().filter;
// If breakdown, filter and inversion haven't changed, don't do anything.
if (censusIsUpToDate(inverted, filter, breakdown, snapshot.census)) {
// If display, filter and inversion haven't changed, don't do anything.
if (censusIsUpToDate(filter, display, snapshot.census)) {
return;
}
// Keep taking a census if the breakdown changes during. Recheck
// that the breakdown used for the census is the same as
// the state's breakdown.
// Keep taking a census if the display changes while our request is in
// flight. Recheck that the display used for the census is the same as the
// state's display.
do {
inverted = getState().inverted;
breakdown = getState().breakdown;
display = getState().censusDisplay;
filter = getState().filter;
dispatch({
type: actions.TAKE_CENSUS_START,
id,
inverted,
filter,
breakdown
display
});
let opts = inverted ? { asInvertedTreeNode: true } : { asTreeNode: true };
let opts = display.inverted
? { asInvertedTreeNode: true }
: { asTreeNode: true };
opts.filter = filter || null;
try {
({ report, parentMap } = yield heapWorker.takeCensus(snapshot.path,
{ breakdown },
({ report, parentMap } = yield heapWorker.takeCensus(
snapshot.path,
{ breakdown: display.breakdown },
opts));
} catch (error) {
reportException("takeCensus", error);
@ -175,27 +174,25 @@ const takeCensus = exports.takeCensus = function (heapWorker, id) {
return;
}
}
while (inverted !== getState().inverted ||
filter !== getState().filter ||
!breakdownEquals(breakdown, getState().breakdown));
while (filter !== getState().filter ||
display !== getState().censusDisplay);
dispatch({
type: actions.TAKE_CENSUS_END,
id,
breakdown,
inverted,
display,
filter,
report,
parentMap
});
telemetry.countCensus({ inverted, filter, breakdown });
telemetry.countCensus({ filter, display });
};
};
/**
* Refresh the selected snapshot's census data, if need be (for example,
* breakdown configuration changed).
* display configuration changed).
*
* @param {HeapAnalysesClient} heapWorker
*/
@ -261,18 +258,19 @@ const fetchDominatorTree = exports.fetchDominatorTree = function (heapWorker, id
assert(dominatorTreeIsComputed(snapshot),
"Should have dominator tree model and it should be computed");
let breakdown;
let display;
let root;
do {
breakdown = getState().dominatorTreeBreakdown;
assert(breakdown, "Should have a breakdown to describe nodes with.");
display = getState().dominatorTreeDisplay;
assert(display && display.breakdown,
`Should have a breakdown to describe nodes with, got: ${uneval(display)}`);
dispatch({ type: actions.FETCH_DOMINATOR_TREE_START, id, breakdown });
dispatch({ type: actions.FETCH_DOMINATOR_TREE_START, id, display });
try {
root = yield heapWorker.getDominatorTree({
dominatorTreeId: snapshot.dominatorTree.dominatorTreeId,
breakdown,
breakdown: display.breakdown,
});
} catch (error) {
reportException("actions/snapshot/fetchDominatorTree", error);
@ -280,10 +278,10 @@ const fetchDominatorTree = exports.fetchDominatorTree = function (heapWorker, id
return null;
}
}
while (!breakdownEquals(breakdown, getState().dominatorTreeBreakdown));
while (display !== getState().dominatorTreeDisplay);
dispatch({ type: actions.FETCH_DOMINATOR_TREE_END, id, root });
telemetry.countDominatorTree({ breakdown });
telemetry.countDominatorTree({ display });
return root;
};
};
@ -305,18 +303,18 @@ const fetchImmediatelyDominated = exports.fetchImmediatelyDominated = function (
"Cannot fetch immediately dominated nodes in a dominator tree unless " +
" the dominator tree has already been computed");
let breakdown;
let display;
let response;
do {
breakdown = getState().dominatorTreeBreakdown;
assert(breakdown, "Should have a breakdown to describe nodes with.");
display = getState().dominatorTreeDisplay;
assert(display, "Should have a display to describe nodes with.");
dispatch({ type: actions.FETCH_IMMEDIATELY_DOMINATED_START, id });
try {
response = yield heapWorker.getImmediatelyDominated({
dominatorTreeId: snapshot.dominatorTree.dominatorTreeId,
breakdown,
breakdown: display.breakdown,
nodeId: lazyChildren.parentNodeId(),
startIndex: lazyChildren.siblingIndex(),
});
@ -326,7 +324,7 @@ const fetchImmediatelyDominated = exports.fetchImmediatelyDominated = function (
return null;
}
}
while (!breakdownEquals(breakdown, getState().dominatorTreeBreakdown));
while (display !== getState().dominatorTreeDisplay);
dispatch({
type: actions.FETCH_IMMEDIATELY_DOMINATED_END,

View File

@ -6,10 +6,14 @@ const { assert } = require("devtools/shared/DevToolsUtils");
const { appinfo } = require("Services");
const { DOM: dom, createClass, createFactory, PropTypes } = require("devtools/client/shared/vendor/react");
const { connect } = require("devtools/client/shared/vendor/react-redux");
const { breakdowns, diffingState, viewState } = require("./constants");
const { censusDisplays, dominatorTreeDisplays, diffingState, viewState } = require("./constants");
const { toggleRecordingAllocationStacks } = require("./actions/allocations");
const { setBreakdownAndRefresh } = require("./actions/breakdown");
const { setDominatorTreeBreakdownAndRefresh } = require("./actions/dominatorTreeBreakdown");
const { setCensusDisplayAndRefresh } = require("./actions/census-display");
const { setDominatorTreeDisplayAndRefresh } = require("./actions/dominator-tree-display");
const {
getCustomCensusDisplays,
getCustomDominatorTreeDisplays,
} = require("devtools/client/memory/utils");
const {
selectSnapshotForDiffingAndRefresh,
toggleDiffing,
@ -17,7 +21,6 @@ const {
collapseDiffingCensusNode,
focusDiffingCensusNode,
} = require("./actions/diffing");
const { toggleInvertedAndRefresh } = require("./actions/inverted");
const { setFilterStringAndRefresh } = require("./actions/filter");
const { pickFileAndExportSnapshot, pickFileAndImportSnapshotAndCensus } = require("./actions/io");
const {
@ -34,12 +37,6 @@ const {
} = require("./actions/snapshot");
const { changeViewAndRefresh } = require("./actions/view");
const { resizeShortestPaths } = require("./actions/sizes");
const {
breakdownNameToSpec,
getBreakdownDisplayData,
dominatorTreeBreakdownNameToSpec,
getDominatorTreeBreakdownDisplayData,
} = require("./utils");
const Toolbar = createFactory(require("./components/toolbar"));
const List = createFactory(require("./components/list"));
const SnapshotListItem = createFactory(require("./components/snapshot-list-item"));
@ -87,12 +84,14 @@ const MemoryApp = createClass({
let isOSX = appinfo.OS == "Darwin";
let isAccelKey = (isOSX && e.metaKey) || (!isOSX && e.ctrlKey);
// On ACCEL+UP, select previous snapshot.
if (isAccelKey && e.key === "ArrowUp") {
let previousIndex = Math.max(0, selectedIndex - 1);
let previousSnapshotId = snapshots[previousIndex].id;
dispatch(selectSnapshotAndRefresh(heapWorker, previousSnapshotId));
}
// On ACCEL+DOWN, select next snapshot.
if (isAccelKey && e.key === "ArrowDown") {
let nextIndex = Math.min(snapshots.length - 1, selectedIndex + 1);
@ -101,15 +100,40 @@ const MemoryApp = createClass({
}
},
_getCensusDisplays() {
const customDisplays = getCustomCensusDisplays();
const custom = Object.keys(customDisplays).reduce((arr, key) => {
arr.push(customDisplays[key]);
return arr;
}, []);
return [
censusDisplays.coarseType,
censusDisplays.allocationStack,
censusDisplays.invertedAllocationStack,
].concat(custom);
},
_getDominatorTreeDisplays() {
const customDisplays = getCustomDominatorTreeDisplays();
const custom = Object.keys(customDisplays).reduce((arr, key) => {
arr.push(customDisplays[key]);
return arr;
}, []);
return [
dominatorTreeDisplays.coarseType,
dominatorTreeDisplays.allocationStack,
].concat(custom);
},
render() {
let {
dispatch,
snapshots,
front,
heapWorker,
breakdown,
allocations,
inverted,
toolbox,
filter,
diffing,
@ -131,30 +155,24 @@ const MemoryApp = createClass({
Toolbar({
snapshots,
breakdowns: getBreakdownDisplayData(),
censusDisplays: this._getCensusDisplays(),
onCensusDisplayChange: newDisplay =>
dispatch(setCensusDisplayAndRefresh(heapWorker, newDisplay)),
onImportClick: () => dispatch(pickFileAndImportSnapshotAndCensus(heapWorker)),
onClearSnapshotsClick: () => dispatch(clearSnapshots(heapWorker)),
onTakeSnapshotClick: () => dispatch(takeSnapshotAndCensus(front, heapWorker)),
onBreakdownChange: breakdown =>
dispatch(setBreakdownAndRefresh(heapWorker, breakdownNameToSpec(breakdown))),
onToggleRecordAllocationStacks: () =>
dispatch(toggleRecordingAllocationStacks(front)),
allocations,
inverted,
onToggleInverted: () =>
dispatch(toggleInvertedAndRefresh(heapWorker)),
filterString: filter,
setFilterString: filterString =>
dispatch(setFilterStringAndRefresh(filterString, heapWorker)),
diffing,
onToggleDiffing: () => dispatch(toggleDiffing()),
view,
dominatorTreeBreakdowns: getDominatorTreeBreakdownDisplayData(),
onDominatorTreeBreakdownChange: breakdown => {
const spec = dominatorTreeBreakdownNameToSpec(breakdown);
assert(spec, "Should have a breakdown spec");
dispatch(setDominatorTreeBreakdownAndRefresh(heapWorker, spec));
},
dominatorTreeDisplays: this._getDominatorTreeDisplays(),
onDominatorTreeDisplayChange: newDisplay =>
dispatch(setDominatorTreeDisplayAndRefresh(heapWorker, newDisplay)),
onViewChange: v => dispatch(changeViewAndRefresh(v, heapWorker)),
}),

View File

@ -66,7 +66,7 @@ const Census = module.exports = createClass({
getPercentBytes,
getPercentCount,
showSign: !!diffing,
inverted: census.inverted,
inverted: census.display.inverted,
}),
getRoots: () => report.children || [],
getKey: node => node.id,

View File

@ -258,7 +258,7 @@ const Heap = module.exports = createClass({
_renderCensus(state, census, diffing, onViewSourceInDebugger) {
const contents = [];
if (census.breakdown.by === "allocationStack"
if (census.display.breakdown.by === "allocationStack"
&& census.report
&& census.report.children
&& census.report.children.length === 1

View File

@ -12,11 +12,6 @@ const { isSavedFrame } = require("devtools/shared/DevToolsUtils");
const { getSourceNames } = require("devtools/client/shared/source-utils");
const { L10N } = require("../utils");
const { ViewHelpers } = require("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
const COMPONENTS_STRINGS_URI = "chrome://devtools/locale/components.properties";
const componentsL10N = new ViewHelpers.L10N(COMPONENTS_STRINGS_URI);
const UNKNOWN_SOURCE_STRING = componentsL10N.getStr("frame.unknownSource");
const GRAPH_DEFAULTS = {
translate: [20, 20],
scale: 1
@ -33,7 +28,7 @@ function stringifyLabel(label, id) {
const piece = label[i];
if (isSavedFrame(piece)) {
const { short } = getSourceNames(piece.source, UNKNOWN_SOURCE_STRING);
const { short } = getSourceNames(piece.source);
sanitized[i] = `${piece.functionDisplayName} @ ${short}:${piece.line}:${piece.column}`;
} else if (piece === NO_STACK) {
sanitized[i] = L10N.getStr("tree-item.nostack");

View File

@ -1,6 +1,7 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { assert } = require("devtools/shared/DevToolsUtils");
const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
@ -8,32 +9,30 @@ const { L10N } = require("../utils");
const models = require("../models");
const { viewState } = require("../constants");
const Toolbar = module.exports = createClass({
module.exports = createClass({
displayName: "Toolbar",
propTypes: {
breakdowns: PropTypes.arrayOf(PropTypes.shape({
censusDisplays: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string.isRequired,
displayName: PropTypes.string.isRequired,
})).isRequired,
onTakeSnapshotClick: PropTypes.func.isRequired,
onImportClick: PropTypes.func.isRequired,
onClearSnapshotsClick: PropTypes.func.isRequired,
onBreakdownChange: PropTypes.func.isRequired,
onCensusDisplayChange: PropTypes.func.isRequired,
onToggleRecordAllocationStacks: PropTypes.func.isRequired,
allocations: models.allocations,
onToggleInverted: PropTypes.func.isRequired,
inverted: PropTypes.bool.isRequired,
filterString: PropTypes.string,
setFilterString: PropTypes.func.isRequired,
diffing: models.diffingModel,
onToggleDiffing: PropTypes.func.isRequired,
view: PropTypes.string.isRequired,
onViewChange: PropTypes.func.isRequired,
dominatorTreeBreakdowns: PropTypes.arrayOf(PropTypes.shape({
dominatorTreeDisplays: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string.isRequired,
displayName: PropTypes.string.isRequired,
})).isRequired,
onDominatorTreeBreakdownChange: PropTypes.func.isRequired,
onDominatorTreeDisplayChange: PropTypes.func.isRequired,
snapshots: PropTypes.arrayOf(models.snapshot).isRequired,
},
@ -42,14 +41,12 @@ const Toolbar = module.exports = createClass({
onTakeSnapshotClick,
onImportClick,
onClearSnapshotsClick,
onBreakdownChange,
breakdowns,
dominatorTreeBreakdowns,
onDominatorTreeBreakdownChange,
onCensusDisplayChange,
censusDisplays,
dominatorTreeDisplays,
onDominatorTreeDisplayChange,
onToggleRecordAllocationStacks,
allocations,
onToggleInverted,
inverted,
filterString,
setFilterString,
snapshots,
@ -68,20 +65,24 @@ const Toolbar = module.exports = createClass({
dom.label(
{
className: "breakdown-by",
title: L10N.getStr("toolbar.breakdownBy.tooltip"),
className: "display-by",
title: L10N.getStr("toolbar.displayBy.tooltip"),
},
L10N.getStr("toolbar.breakdownBy"),
L10N.getStr("toolbar.displayBy"),
dom.select(
{
id: "select-breakdown",
className: "select-breakdown",
onChange: e => onBreakdownChange(e.target.value),
id: "select-display",
className: "select-display",
onChange: e => {
const newDisplay =
censusDisplays.find(b => b.displayName === e.target.value);
onCensusDisplayChange(newDisplay);
},
breakdowns.map(({ name, tooltip, displayName }) => dom.option(
},
censusDisplays.map(({ tooltip, displayName }) => dom.option(
{
key: name,
value: name,
key: `display-${displayName}`,
value: displayName,
title: tooltip,
},
displayName
@ -89,19 +90,6 @@ const Toolbar = module.exports = createClass({
)
),
dom.label(
{
title: L10N.getStr("checkbox.invertTree.tooltip")
},
dom.input({
id: "invert-tree-checkbox",
type: "checkbox",
checked: inverted,
onChange: onToggleInverted,
}),
L10N.getStr("checkbox.invertTree")
),
dom.div({ id: "toolbar-spacer", className: "spacer" }),
dom.input({
@ -111,7 +99,7 @@ const Toolbar = module.exports = createClass({
placeholder: L10N.getStr("filter.placeholder"),
title: L10N.getStr("filter.tooltip"),
onChange: event => setFilterString(event.target.value),
value: !!filterString ? filterString : undefined,
value: filterString || undefined,
})
);
} else {
@ -130,13 +118,17 @@ const Toolbar = module.exports = createClass({
L10N.getStr("toolbar.labelBy"),
dom.select(
{
id: "select-dominator-tree-breakdown",
onChange: e => onDominatorTreeBreakdownChange(e.target.value),
id: "select-dominator-tree-display",
onChange: e => {
const newDisplay =
dominatorTreeDisplays.find(b => b.displayName === e.target.value);
onDominatorTreeDisplayChange(newDisplay);
},
dominatorTreeBreakdowns.map(({ name, tooltip, displayName }) => dom.option(
},
dominatorTreeDisplays.map(({ tooltip, displayName }) => dom.option(
{
key: name,
value: name,
key: `dominator-tree-display-${displayName}`,
value: displayName,
title: tooltip,
},
displayName

View File

@ -65,11 +65,11 @@ actions.TAKE_CENSUS_DIFF_START = "take-census-diff-start";
actions.TAKE_CENSUS_DIFF_END = "take-census-diff-end";
actions.DIFFING_ERROR = "diffing-error";
// Fired to set a new breakdown.
actions.SET_BREAKDOWN = "set-breakdown";
// Fired to set a new census display.
actions.SET_CENSUS_DISPLAY = "set-census-display";
// Fired to change the breakdown that controls the dominator tree labels.
actions.SET_DOMINATOR_TREE_BREAKDOWN = "set-dominator-tree-breakdown";
// Fired to change the display that controls the dominator tree labels.
actions.SET_DOMINATOR_TREE_DISPLAY = "set-dominator-tree-display";
// Fired when changing between census or dominators view.
actions.CHANGE_VIEW = "change-view";
@ -103,23 +103,24 @@ actions.COLLAPSE_DOMINATOR_TREE_NODE = "collapse-dominator-tree-node";
actions.RESIZE_SHORTEST_PATHS = "resize-shortest-paths";
/*** Breakdowns ***************************************************************/
/*** Census Displays ***************************************************************/
const COUNT = { by: "count", count: true, bytes: true };
const INTERNAL_TYPE = { by: "internalType", then: COUNT };
const ALLOCATION_STACK = { by: "allocationStack", then: COUNT, noStack: COUNT };
const OBJECT_CLASS = { by: "objectClass", then: COUNT, other: COUNT };
const COUNT = Object.freeze({ by: "count", count: true, bytes: true });
const INTERNAL_TYPE = Object.freeze({ by: "internalType", then: COUNT });
const ALLOCATION_STACK = Object.freeze({ by: "allocationStack", then: COUNT, noStack: COUNT });
const OBJECT_CLASS = Object.freeze({ by: "objectClass", then: COUNT, other: COUNT });
const breakdowns = exports.breakdowns = {
coarseType: {
exports.censusDisplays = Object.freeze({
coarseType: Object.freeze({
displayName: "Type",
get tooltip() {
// Importing down here is necessary because of the circular dependency
// this introduces with `./utils.js`.
const { L10N } = require("./utils");
return L10N.getStr("breakdowns.coarseType.tooltip");
return L10N.getStr("censusDisplays.coarseType.tooltip");
},
breakdown: {
inverted: true,
breakdown: Object.freeze({
by: "coarseType",
objects: OBJECT_CLASS,
strings: COUNT,
@ -129,57 +130,68 @@ const breakdowns = exports.breakdowns = {
noFilename: INTERNAL_TYPE
},
other: INTERNAL_TYPE,
}
},
})
}),
allocationStack: {
allocationStack: Object.freeze({
displayName: "Call Stack",
get tooltip() {
const { L10N } = require("./utils");
return L10N.getStr("breakdowns.allocationStack.tooltip");
return L10N.getStr("censusDisplays.allocationStack.tooltip");
},
inverted: false,
breakdown: ALLOCATION_STACK,
},
};
}),
const DOMINATOR_TREE_LABEL_COARSE_TYPE = {
invertedAllocationStack: Object.freeze({
displayName: "Inverted Call Stack",
get tooltip() {
const { L10N } = require("./utils");
return L10N.getStr("censusDisplays.invertedAllocationStack.tooltip");
},
inverted: true,
breakdown: ALLOCATION_STACK,
}),
});
const DOMINATOR_TREE_LABEL_COARSE_TYPE = Object.freeze({
by: "coarseType",
objects: OBJECT_CLASS,
scripts: {
scripts: Object.freeze({
by: "internalType",
then: {
then: Object.freeze({
by: "filename",
then: COUNT,
noFilename: COUNT,
},
},
}),
}),
strings: INTERNAL_TYPE,
other: INTERNAL_TYPE,
};
});
const dominatorTreeBreakdowns = exports.dominatorTreeBreakdowns = {
coarseType: {
exports.dominatorTreeDisplays = Object.freeze({
coarseType: Object.freeze({
displayName: "Type",
get tooltip() {
const { L10N } = require("./utils");
return L10N.getStr("dominatorTreeBreakdowns.coarseType.tooltip");
return L10N.getStr("dominatorTreeDisplays.coarseType.tooltip");
},
breakdown: DOMINATOR_TREE_LABEL_COARSE_TYPE
},
}),
allocationStack: {
allocationStack: Object.freeze({
displayName: "Call Stack",
get tooltip() {
const { L10N } = require("./utils");
return L10N.getStr("dominatorTreeBreakdowns.allocationStack.tooltip");
return L10N.getStr("dominatorTreeDisplays.allocationStack.tooltip");
},
breakdown: {
breakdown: Object.freeze({
by: "allocationStack",
then: DOMINATOR_TREE_LABEL_COARSE_TYPE,
noStack: DOMINATOR_TREE_LABEL_COARSE_TYPE,
},
},
};
}),
}),
});
/*** View States **************************************************************/

View File

@ -45,12 +45,32 @@ function catchAndIgnore(fn) {
}
/**
* The breakdown object DSL describing how we want
* the census data to be.
* The data describing the census report's shape, and its associated metadata.
*
* @see `js/src/doc/Debugger/Debugger.Memory.md`
*/
let breakdownModel = exports.breakdown = PropTypes.shape({
const censusDisplayModel = exports.censusDisplay = PropTypes.shape({
displayName: PropTypes.string.isRequired,
tooltip: PropTypes.string.isRequired,
inverted: PropTypes.bool.isRequired,
breakdown: PropTypes.shape({
by: PropTypes.string.isRequired,
})
});
/**
* How we want to label nodes in the dominator tree, and associated
* metadata. The notable difference from `censusDisplayModel` is the lack of
* an `inverted` property.
*
* @see `js/src/doc/Debugger/Debugger.Memory.md`
*/
const dominatorTreeDisplayModel = exports.dominatorTreeDisplay = PropTypes.shape({
displayName: PropTypes.string.isRequired,
tooltip: PropTypes.string.isRequired,
breakdown: PropTypes.shape({
by: PropTypes.string.isRequired,
})
});
let censusModel = exports.censusModel = PropTypes.shape({
@ -58,10 +78,8 @@ let censusModel = exports.censusModel = PropTypes.shape({
report: PropTypes.object,
// The parent map for the report.
parentMap: PropTypes.object,
// The breakdown used to generate the current census
breakdown: breakdownModel,
// Whether the currently cached report tree is inverted or not.
inverted: PropTypes.bool,
// The display data used to generate the current census.
display: censusDisplayModel,
// If present, the currently cached report's filter string used for pruning
// the tree items.
filter: PropTypes.string,
@ -99,9 +117,9 @@ let dominatorTreeModel = exports.dominatorTreeModel = PropTypes.shape({
PropTypes.object,
]),
// The breakdown used to generate descriptive labels of nodes in this
// dominator tree.
breakdown: breakdownModel,
// The display used to generate descriptive labels of nodes in this dominator
// tree.
display: dominatorTreeDisplayModel,
// The number of active requests to incrementally fetch subtrees. This should
// only be non-zero when the state is INCREMENTAL_FETCHING.
@ -197,7 +215,8 @@ let snapshotModel = exports.snapshot = PropTypes.shape({
if (shouldHavePath.includes(current) && !snapshot.path) {
throw new Error(`Snapshots in state ${current} must have a snapshot path.`);
}
if (shouldHaveCensus.includes(current) && (!snapshot.census || !snapshot.census.breakdown)) {
if (shouldHaveCensus.includes(current) &&
(!snapshot.census || !snapshot.census.display || !snapshot.census.display.breakdown)) {
throw new Error(`Snapshots in state ${current} must have a census and breakdown.`);
}
if (shouldHaveCreationTime.includes(current) && !snapshot.creationTime) {
@ -269,22 +288,16 @@ let appModel = exports.app = {
// {HeapAnalysesClient} Used to interface with snapshots
heapWorker: PropTypes.instanceOf(HeapAnalysesClient),
// The breakdown object DSL describing how we want
// the census data to be.
// @see `js/src/doc/Debugger/Debugger.Memory.md`
breakdown: breakdownModel.isRequired,
// The display data describing how we want the census data to be.
censusDisplay: censusDisplayModel.isRequired,
// The breakdown object DSL describing how we want
// the dominator tree labels to be computed.
// @see `js/src/doc/Debugger/Debugger.Memory.md`
dominatorTreeBreakdown: breakdownModel.isRequired,
// The display data describing how we want the dominator tree labels to be
// computed.
dominatorTreeDisplay: dominatorTreeDisplayModel.isRequired,
// List of reference to all snapshots taken
snapshots: PropTypes.arrayOf(snapshotModel).isRequired,
// True iff we want the tree displayed inverted.
inverted: PropTypes.bool.isRequired,
// If present, a filter string for pruning the tree items.
filter: PropTypes.string,

View File

@ -4,12 +4,11 @@
"use strict";
exports.allocations = require("./reducers/allocations");
exports.breakdown = require("./reducers/breakdown");
exports.censusDisplay = require("./reducers/census-display");
exports.diffing = require("./reducers/diffing");
exports.dominatorTreeBreakdown = require("./reducers/dominatorTreeBreakdown");
exports.dominatorTreeDisplay = require("./reducers/dominator-tree-display");
exports.errors = require("./reducers/errors");
exports.filter = require("./reducers/filter");
exports.inverted = require("./reducers/inverted");
exports.sizes = require("./reducers/sizes");
exports.snapshots = require("./reducers/snapshots");
exports.view = require("./reducers/view");

View File

@ -1,17 +1,18 @@
/* 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";
const { actions, breakdowns } = require("../constants");
const DEFAULT_BREAKDOWN = breakdowns.coarseType.breakdown;
const { actions, censusDisplays } = require("../constants");
const DEFAULT_CENSUS_DISPLAY = censusDisplays.coarseType;
let handlers = Object.create(null);
handlers[actions.SET_BREAKDOWN] = function (_, action) {
return Object.assign({}, action.breakdown);
handlers[actions.SET_CENSUS_DISPLAY] = function(_, { display }) {
return display;
};
module.exports = function (state=DEFAULT_BREAKDOWN, action) {
module.exports = function(state = DEFAULT_CENSUS_DISPLAY, action) {
let handle = handlers[action.type];
if (handle) {
return handle(state, action);

View File

@ -65,7 +65,7 @@ handlers[actions.TAKE_CENSUS_DIFF_START] = function (diffing, action) {
report: null,
inverted: action.inverted,
filter: action.filter,
breakdown: action.breakdown,
display: action.display,
}
});
};
@ -85,7 +85,7 @@ handlers[actions.TAKE_CENSUS_DIFF_END] = function (diffing, action) {
expanded: new Set(),
inverted: action.inverted,
filter: action.filter,
breakdown: action.breakdown,
display: action.display,
}
});
};

View File

@ -4,16 +4,16 @@
"use strict";
const { actions, dominatorTreeBreakdowns } = require("../constants");
const DEFAULT_BREAKDOWN = dominatorTreeBreakdowns.coarseType.breakdown;
const { actions, dominatorTreeDisplays } = require("../constants");
const DEFAULT_DOMINATOR_TREE_DISPLAY = dominatorTreeDisplays.coarseType;
const handlers = Object.create(null);
handlers[actions.SET_DOMINATOR_TREE_BREAKDOWN] = function (_, { breakdown }) {
return breakdown;
handlers[actions.SET_DOMINATOR_TREE_DISPLAY] = function (_, { display }) {
return display;
};
module.exports = function (state = DEFAULT_BREAKDOWN, action) {
module.exports = function (state = DEFAULT_DOMINATOR_TREE_DISPLAY, action) {
const handler = handlers[action.type];
return handler ? handler(state, action) : state;
};

View File

@ -1,14 +0,0 @@
/* 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";
const { actions } = require("../constants");
module.exports = function (inverted = false, action) {
if (action.type === actions.TOGGLE_INVERTED) {
return !inverted;
} else {
return inverted;
}
};

View File

@ -5,12 +5,11 @@
DevToolsModules(
'allocations.js',
'breakdown.js',
'census-display.js',
'diffing.js',
'dominatorTreeBreakdown.js',
'dominator-tree-display.js',
'errors.js',
'filter.js',
'inverted.js',
'sizes.js',
'snapshots.js',
'view.js',

View File

@ -52,11 +52,10 @@ handlers[actions.READ_SNAPSHOT_END] = function (snapshots, { id, creationTime })
});
};
handlers[actions.TAKE_CENSUS_START] = function (snapshots, { id, breakdown, inverted, filter }) {
handlers[actions.TAKE_CENSUS_START] = function (snapshots, { id, display, filter }) {
const census = {
report: null,
breakdown,
inverted,
display,
filter,
};
@ -70,15 +69,13 @@ handlers[actions.TAKE_CENSUS_START] = function (snapshots, { id, breakdown, inve
handlers[actions.TAKE_CENSUS_END] = function (snapshots, { id,
report,
parentMap,
breakdown,
inverted,
display,
filter }) {
const census = {
report,
parentMap,
expanded: new Set(),
breakdown,
inverted,
display,
filter,
};
@ -194,7 +191,7 @@ handlers[actions.COMPUTE_DOMINATOR_TREE_END] = function (snapshots, { id, domina
});
};
handlers[actions.FETCH_DOMINATOR_TREE_START] = function (snapshots, { id, breakdown }) {
handlers[actions.FETCH_DOMINATOR_TREE_START] = function (snapshots, { id, display }) {
return snapshots.map(snapshot => {
if (snapshot.id !== id) {
return snapshot;
@ -208,7 +205,7 @@ handlers[actions.FETCH_DOMINATOR_TREE_START] = function (snapshots, { id, breakd
const dominatorTree = immutableUpdate(snapshot.dominatorTree, {
state: dominatorTreeState.FETCHING,
root: undefined,
breakdown,
display,
});
return immutableUpdate(snapshot, { dominatorTree });
});

View File

@ -10,7 +10,7 @@
const { telemetry } = require("Services");
const { makeInfallible, immutableUpdate } = require("devtools/shared/DevToolsUtils");
const { dominatorTreeBreakdowns, breakdowns } = require("./constants");
const { dominatorTreeDisplays, censusDisplays } = require("./constants");
exports.countTakeSnapshot = makeInfallible(function () {
const histogram = telemetry.getHistogramById("DEVTOOLS_MEMORY_TAKE_SNAPSHOT_COUNT");
@ -29,26 +29,22 @@ exports.countExportSnapshot = makeInfallible(function () {
const COARSE_TYPE = "Coarse Type";
const ALLOCATION_STACK = "Allocation Stack";
const OBJECT_CLASS = "Object Class";
const INTERNAL_TYPE = "Internal Type";
const INVERTED_ALLOCATION_STACK = "Inverted Allocation Stack";
const CUSTOM = "Custom";
/**
* @param {Boolean} inverted
* True if the census was inverted, false otherwise.
*
* @param {String|null} filter
* The filter string used, if any.
*
* @param {Boolean} diffing
* True if the census was a diffing census, false otherwise.
*
* @param {Object} breakdown
* The breakdown used with the census.
* @param {censusDisplayModel} display
* The display used with the census.
*/
exports.countCensus = makeInfallible(function ({ inverted, filter, diffing, breakdown }) {
exports.countCensus = makeInfallible(function ({ filter, diffing, display }) {
let histogram = telemetry.getHistogramById("DEVTOOLS_MEMORY_INVERTED_CENSUS");
histogram.add(!!inverted);
histogram.add(!!display.inverted);
histogram = telemetry.getHistogramById("DEVTOOLS_MEMORY_FILTER_CENSUS");
histogram.add(!!filter);
@ -57,10 +53,12 @@ exports.countCensus = makeInfallible(function ({ inverted, filter, diffing, brea
histogram.add(!!diffing);
histogram = telemetry.getKeyedHistogramById("DEVTOOLS_MEMORY_BREAKDOWN_CENSUS_COUNT");
if (breakdown === breakdowns.coarseType.breakdown) {
if (display === censusDisplays.coarseType) {
histogram.add(COARSE_TYPE);
} else if (breakdown === breakdowns.allocationStack.breakdown) {
} else if (display === censusDisplays.allocationStack) {
histogram.add(ALLOCATION_STACK);
} else if (display === censusDisplays.invertedAllocationStack) {
histogram.add(INVERTED_ALLOCATION_STACK);
} else {
histogram.add(CUSTOM);
}
@ -75,17 +73,17 @@ exports.countDiff = makeInfallible(function (opts) {
}, "devtools/client/memory/telemetry#countDiff");
/**
* @param {Object} breakdown
* The breakdown used to label nodes in the dominator tree.
* @param {Object} display
* The display used to label nodes in the dominator tree.
*/
exports.countDominatorTree = makeInfallible(function ({ breakdown }) {
exports.countDominatorTree = makeInfallible(function ({ display }) {
let histogram = telemetry.getHistogramById("DEVTOOLS_MEMORY_DOMINATOR_TREE_COUNT");
histogram.add(1);
histogram = telemetry.getKeyedHistogramById("DEVTOOLS_MEMORY_BREAKDOWN_DOMINATOR_TREE_COUNT");
if (breakdown === dominatorTreeBreakdowns.coarseType.breakdown) {
if (display === dominatorTreeDisplays.coarseType) {
histogram.add(COARSE_TYPE);
} else if (breakdown === dominatorTreeBreakdowns.allocationStack.breakdown) {
} else if (display === dominatorTreeDisplays.allocationStack) {
histogram.add(ALLOCATION_STACK);
} else {
histogram.add(CUSTOM);

View File

@ -6,9 +6,9 @@ support-files =
doc_big_tree.html
doc_steady_allocation.html
[browser_memory_allocationStackBreakdown_01.js]
[browser_memory_allocationStackDisplay_01.js]
skip-if = debug # bug 1219554
[browser_memory_breakdowns_01.js]
[browser_memory_displays_01.js]
[browser_memory_clear_snapshots.js]
[browser_memory_diff_01.js]
[browser_memory_dominator_trees_01.js]

View File

@ -1,16 +1,14 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Sanity test that we can show allocation stack breakdowns in the tree.
// Sanity test that we can show allocation stack displays in the tree.
"use strict";
const { waitForTime } = require("devtools/shared/DevToolsUtils");
const { breakdowns } = require("devtools/client/memory/constants");
const { toggleRecordingAllocationStacks } = require("devtools/client/memory/actions/allocations");
const { takeSnapshotAndCensus } = require("devtools/client/memory/actions/snapshot");
const breakdownActions = require("devtools/client/memory/actions/breakdown");
const { toggleInvertedAndRefresh } = require("devtools/client/memory/actions/inverted");
const censusDisplayActions = require("devtools/client/memory/actions/census-display");
const TEST_URL = "http://example.com/browser/devtools/client/memory/test/browser/doc_steady_allocation.html";
@ -20,13 +18,8 @@ this.test = makeMemoryTest(TEST_URL, function* ({ tab, panel }) {
const { getState, dispatch } = panel.panelWin.gStore;
const doc = panel.panelWin.document;
yield dispatch(toggleInvertedAndRefresh(heapWorker));
ok(getState().inverted, true);
ok(doc.getElementById("invert-tree-checkbox").checked,
"invert-tree-checkbox should be checked");
dispatch(breakdownActions.setBreakdown(breakdowns.allocationStack.breakdown));
is(getState().breakdown.by, "allocationStack");
dispatch(censusDisplayActions.setCensusDisplay(censusDisplays.invertedAllocationStack));
is(getState().censusDisplay.breakdown.by, "allocationStack");
yield dispatch(toggleRecordingAllocationStacks(front));
ok(getState().allocations.recording);

View File

@ -1,15 +1,19 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* Tests that the heap tree renders rows based on the breakdown
* Tests that the heap tree renders rows based on the display
*/
const TEST_URL = "http://example.com/browser/devtools/client/memory/test/browser/doc_steady_allocation.html";
this.test = makeMemoryTest(TEST_URL, function* ({ tab, panel }) {
const { gStore, document } = panel.panelWin;
const $$ = document.querySelectorAll.bind(document);
function $$(selector) {
return [...document.querySelectorAll(selector)];
}
yield takeSnapshot(panel.panelWin);
@ -17,14 +21,15 @@ this.test = makeMemoryTest(TEST_URL, function* ({ tab, panel }) {
state.snapshots[0].state === states.SAVED_CENSUS);
info("Check coarse type heap view");
["objects", "other", "scripts", "strings"].forEach(findNameCell);
["Function", "js::Shape", "Object", "strings"].forEach(findNameCell);
yield setBreakdown(panel.panelWin, "allocationStack");
yield setCensusDisplay(panel.panelWin, censusDisplays.allocationStack);
info("Check allocation stack heap view");
[L10N.getStr("tree-item.nostack")].forEach(findNameCell);
function findNameCell(name) {
let el = Array.prototype.find.call($$(".tree .heap-tree-item-name span"), el => el.textContent === name);
const el = $$(".tree .heap-tree-item-name span")
.find(e => e.textContent === name);
ok(el, `Found heap tree item cell for ${name}.`);
}
});

View File

@ -8,14 +8,11 @@
const {
dominatorTreeState,
snapshotState,
viewState,
} = require("devtools/client/memory/constants");
const {
expandDominatorTreeNode,
takeSnapshotAndCensus,
} = require("devtools/client/memory/actions/snapshot");
const { toggleInverted } = require("devtools/client/memory/actions/inverted");
const { changeView } = require("devtools/client/memory/actions/view");
const TEST_URL = "http://example.com/browser/devtools/client/memory/test/browser/doc_big_tree.html";
@ -24,8 +21,6 @@ this.test = makeMemoryTest(TEST_URL, function* ({ tab, panel }) {
// Taking snapshots and computing dominator trees is slow :-/
requestLongerTimeout(4);
const heapWorker = panel.panelWin.gHeapAnalysesClient;
const front = panel.panelWin.gFront;
const store = panel.panelWin.gStore;
const { getState, dispatch } = store;
const doc = panel.panelWin.document;

View File

@ -1,7 +1,7 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Sanity test that we can show allocation stack breakdowns in the tree.
// Sanity test that we can show allocation stack displays in the tree.
"use strict";
@ -21,11 +21,6 @@ this.test = makeMemoryTest(TEST_URL, function* ({ tab, panel }) {
const { getState, dispatch } = store;
const doc = panel.panelWin.document;
ok(!getState().inverted, "not inverted by default");
const invertCheckbox = doc.getElementById("invert-tree-checkbox");
EventUtils.synthesizeMouseAtCenter(invertCheckbox, {}, panel.panelWin);
yield waitUntilState(store, state => state.inverted === true);
const takeSnapshotButton = doc.getElementById("take-snapshot");
EventUtils.synthesizeMouseAtCenter(takeSnapshotButton, {}, panel.panelWin);

View File

@ -39,7 +39,7 @@ this.test = makeMemoryTest(TEST_URL, function* ({ tab, panel }) {
const { getState, dispatch } = store;
const doc = panel.panelWin.document;
is(getState().breakdown.by, "coarseType");
is(getState().censusDisplay.breakdown.by, "coarseType");
yield dispatch(takeSnapshotAndCensus(front, heapWorker));
let census = getState().snapshots[0].census;
let root1 = census.report.children[0];

View File

@ -1,13 +1,12 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Sanity test that we can show allocation stack breakdowns in the tree.
// Sanity test that we can show allocation stack displays in the tree.
"use strict";
const { breakdowns } = require("devtools/client/memory/constants");
const { takeSnapshotAndCensus } = require("devtools/client/memory/actions/snapshot");
const breakdownActions = require("devtools/client/memory/actions/breakdown");
const censusDisplayActions = require("devtools/client/memory/actions/census-display");
const TEST_URL = "http://example.com/browser/devtools/client/memory/test/browser/doc_steady_allocation.html";
@ -21,10 +20,11 @@ this.test = makeMemoryTest(TEST_URL, function* ({ tab, panel }) {
"Should not be recording allocagtions");
yield dispatch(takeSnapshotAndCensus(front, heapWorker));
yield dispatch(breakdownActions.setBreakdownAndRefresh(heapWorker,
breakdowns.allocationStack.breakdown));
yield dispatch(censusDisplayActions.setCensusDisplayAndRefresh(
heapWorker,
censusDisplays.allocationStack));
is(getState().breakdown.by, "allocationStack",
is(getState().censusDisplay.breakdown.by, "allocationStack",
"Should be using allocation stack breakdown");
ok(!getState().allocations.recording,

View File

@ -16,7 +16,6 @@ this.test = makeMemoryTest(TEST_URL, function* ({ tab, panel }) {
const { getState, dispatch } = panel.panelWin.gStore;
const doc = panel.panelWin.document;
is(getState().breakdown.by, "coarseType");
yield dispatch(takeSnapshotAndCensus(front, heapWorker));
is(getState().allocations.recording, false);
@ -25,23 +24,10 @@ this.test = makeMemoryTest(TEST_URL, function* ({ tab, panel }) {
is(getState().allocations.recording, true);
const nameElems = [...doc.querySelectorAll(".heap-tree-item-field.heap-tree-item-name")];
is(nameElems.length, 4, "Should get 4 items, one for each coarse type");
for (let el of nameElems) {
dumpn(`Found ${el.textContent.trim()}`);
}
ok(nameElems.some(e => e.textContent.indexOf("objects") >= 0),
"One for coarse type 'objects'");
ok(nameElems.some(e => e.textContent.indexOf("scripts") >= 0),
"One for coarse type 'scripts'");
ok(nameElems.some(e => e.textContent.indexOf("strings") >= 0),
"One for coarse type 'strings'");
ok(nameElems.some(e => e.textContent.indexOf("other") >= 0),
"One for coarse type 'other'");
for (let e of nameElems) {
is(e.style.marginLeft, "0px",
is(el.style.marginLeft, "0px",
"None of the elements should be an indented/expanded child");
}
});

View File

@ -5,9 +5,7 @@
"use strict";
const { breakdowns } = require("devtools/client/memory/constants");
const { takeSnapshotAndCensus } = require("devtools/client/memory/actions/snapshot");
const breakdownActions = require("devtools/client/memory/actions/breakdown");
const TEST_URL = "http://example.com/browser/devtools/client/memory/test/browser/doc_steady_allocation.html";
@ -28,7 +26,7 @@ this.test = makeMemoryTest(TEST_URL, function* ({ tab, panel }) {
const doc = panel.panelWin.document;
yield dispatch(takeSnapshotAndCensus(front, heapWorker));
is(getState().breakdown.by, "coarseType",
is(getState().censusDisplay.breakdown.by, "coarseType",
"Should be using coarse type breakdown");
const bytesCells = [...doc.querySelectorAll(".heap-tree-item-bytes")];

View File

@ -13,8 +13,8 @@ Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/devtools/client/framework/test/shared-redux-head.js",
this);
var { snapshotState: states } = require("devtools/client/memory/constants");
var { breakdownEquals, breakdownNameToSpec, L10N } = require("devtools/client/memory/utils");
var { censusDisplays, snapshotState: states } = require("devtools/client/memory/constants");
var { L10N } = require("devtools/client/memory/utils");
Services.prefs.setBoolPref("devtools.memory.enabled", true);
@ -117,22 +117,22 @@ function clearSnapshots (window) {
}
/**
* Sets breakdown and waits for currently selected breakdown to use it
* and be completed the census.
* Sets the current requested display and waits for the selected snapshot to use
* it and complete the new census that entails.
*/
function setBreakdown (window, type) {
info(`Setting breakdown to ${type}...`);
function setCensusDisplay(window, display) {
info(`Setting census display to ${display}...`);
let { gStore, gHeapAnalysesClient } = window;
// XXX: Should handle this via clicking the DOM, but React doesn't
// fire the onChange event, so just change it in the store.
// window.document.querySelector(`.select-breakdown`).value = type;
gStore.dispatch(require("devtools/client/memory/actions/breakdown")
.setBreakdownAndRefresh(gHeapAnalysesClient, breakdownNameToSpec(type)));
// window.document.querySelector(`.select-display`).value = type;
gStore.dispatch(require("devtools/client/memory/actions/census-display")
.setCensusDisplayAndRefresh(gHeapAnalysesClient, display));
return waitUntilState(window.gStore, () => {
let selected = window.gStore.getState().snapshots.find(s => s.selected);
return selected.state === states.SAVED_CENSUS &&
breakdownEquals(breakdownNameToSpec(type), selected.census.breakdown);
selected.census.display === display;
});
}

View File

@ -14,4 +14,3 @@ support-files =
[test_ShortestPaths_01.html]
[test_ShortestPaths_02.html]
[test_Toolbar_01.html]
[test_Toolbar_02.html]

View File

@ -17,17 +17,15 @@ var { immutableUpdate } = DevToolsUtils;
var constants = require("devtools/client/memory/constants");
var {
breakdowns,
censusDisplays,
diffingState,
dominatorTreeBreakdowns,
dominatorTreeDisplays,
dominatorTreeState,
snapshotState,
viewState
} = constants;
const {
getBreakdownDisplayData,
getDominatorTreeBreakdownDisplayData,
L10N,
} = require("devtools/client/memory/utils");
@ -122,7 +120,7 @@ var TEST_DOMINATOR_TREE = Object.freeze({
expanded: new Set(),
focused: null,
error: null,
breakdown: dominatorTreeBreakdowns.coarseType.breakdown,
display: dominatorTreeDisplays.coarseType,
activeFetchRequestCount: null,
state: dominatorTreeState.LOADED,
});
@ -173,6 +171,10 @@ var TEST_HEAP_PROPS = Object.freeze({
strings: Object.freeze({ count: 2, bytes: 200 }),
other: Object.freeze({ count: 1, bytes: 100 }),
}),
display: Object.freeze({
displayName: "Test Display",
tooltip: "Test display tooltup",
inverted: false,
breakdown: Object.freeze({
by: "coarseType",
objects: Object.freeze({ by: "count", count: true, bytes: true }),
@ -180,6 +182,7 @@ var TEST_HEAP_PROPS = Object.freeze({
strings: Object.freeze({ by: "count", count: true, bytes: true }),
other: Object.freeze({ by: "count", count: true, bytes: true }),
}),
}),
inverted: false,
filter: null,
expanded: new Set(),
@ -196,10 +199,14 @@ var TEST_HEAP_PROPS = Object.freeze({
});
var TEST_TOOLBAR_PROPS = Object.freeze({
breakdowns: getBreakdownDisplayData(),
censusDisplays: [
censusDisplays.coarseType,
censusDisplays.allocationStack,
censusDisplays.invertedAllocationStack,
],
onTakeSnapshotClick: noop,
onImportClick: noop,
onBreakdownChange: noop,
onCensusDisplayChange: noop,
onToggleRecordAllocationStacks: noop,
allocations: models.allocations,
onToggleInverted: noop,
@ -210,8 +217,11 @@ var TEST_TOOLBAR_PROPS = Object.freeze({
onToggleDiffing: noop,
view: viewState.CENSUS,
onViewChange: noop,
dominatorTreeBreakdowns: getDominatorTreeBreakdownDisplayData(),
onDominatorTreeBreakdownChange: noop,
dominatorTreeDisplays: [
dominatorTreeDisplays.coarseType,
dominatorTreeDisplays.allocationStack,
],
onDominatorTreeDisplayChange: noop,
snapshots: [],
});

View File

@ -41,7 +41,7 @@ Test that we show the "hey you're not recording allocation stacks" message at th
}
]
},
breakdown: breakdowns.allocationStack.breakdown,
display: censusDisplays.allocationStack,
}),
}),
})), container);
@ -82,7 +82,7 @@ Test that we show the "hey you're not recording allocation stacks" message at th
}
]
},
breakdown: breakdowns.allocationStack.breakdown,
display: censusDisplays.allocationStack,
}),
}),
})), container);
@ -102,7 +102,7 @@ Test that we show the "hey you're not recording allocation stacks" message at th
parent: undefined,
children: undefined
},
breakdown: breakdowns.allocationStack.breakdown,
display: censusDisplays.allocationStack,
}),
}),
})), container);

View File

@ -1,47 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
Test that the Toolbar component shows the tree inversion checkbox only at the appropriate times.
-->
<head>
<meta charset="utf-8">
<title>Tree component test</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">
</head>
<body>
<div id="container"></div>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
try {
const container = document.getElementById("container");
// Census and dominator tree views.
for (let view of [viewState.CENSUS, viewState.DIFFING]) {
yield renderComponent(Toolbar(immutableUpdate(TEST_TOOLBAR_PROPS, {
view,
})), container);
ok(container.querySelector("#invert-tree-checkbox"),
`The invert checkbox is shown in view = ${view}`);
}
yield renderComponent(Toolbar(immutableUpdate(TEST_TOOLBAR_PROPS, {
view: viewState.DOMINATOR_TREE,
})), container);
ok(!container.querySelector("#invert-tree-checkbox"),
"The invert checkbox is NOT shown in the DOMINATOR_TREE view");
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
});
</script>
</pre>
</body>
</html>

View File

@ -73,21 +73,6 @@ function waitUntilSnapshotState (store, expected) {
return waitUntilState(store, predicate);
}
function isBreakdownType (report, type) {
// Little sanity check, all reports should have at least a children array.
if (!report || !Array.isArray(report.children)) {
return false;
}
switch (type) {
case "coarseType":
return report.children.find(c => c.name === "objects");
case "allocationStack":
return report.children.find(c => c.name === "noStack");
default:
throw new Error(`isBreakdownType does not yet support ${type}`);
}
}
function *createTempFile () {
let file = FileUtils.getFile("TmpD", ["tmp.fxsnapshot"]);
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);

View File

@ -1,9 +1,10 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that changing filter state properly refreshes the selected census.
let { breakdowns, snapshotState: states } = require("devtools/client/memory/constants");
let { snapshotState: states } = require("devtools/client/memory/constants");
let { setFilterStringAndRefresh } = require("devtools/client/memory/actions/filter");
let { takeSnapshotAndCensus, selectSnapshotAndRefresh } = require("devtools/client/memory/actions/snapshot");

View File

@ -1,5 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* Tests the task creator `importSnapshotAndCensus()` for the whole flow of
@ -7,7 +8,6 @@
*/
let { actions, snapshotState: states } = require("devtools/client/memory/constants");
let { breakdownEquals } = require("devtools/client/memory/utils");
let { exportSnapshot, importSnapshotAndCensus } = require("devtools/client/memory/actions/io");
let { takeSnapshotAndCensus } = require("devtools/client/memory/actions/snapshot");
@ -61,8 +61,8 @@ add_task(function *() {
let snapshot1 = getState().snapshots[0];
let snapshot2 = getState().snapshots[1];
ok(breakdownEquals(snapshot1.breakdown, snapshot2.breakdown),
"imported snapshot has correct breakdown");
equal(snapshot1.display, snapshot2.display,
"imported snapshot has correct display");
// Clone the census data so we can destructively remove the ID/parents to compare
// equal census data

View File

@ -1,98 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests the task creator `setBreakdownAndRefreshAndRefresh()` for breakdown changing.
* We test this rather than `setBreakdownAndRefresh` directly, as we use the refresh action
* in the app itself composed from `setBreakdownAndRefresh`
*/
let { breakdowns, snapshotState: states } = require("devtools/client/memory/constants");
let { breakdownEquals } = require("devtools/client/memory/utils");
let { setBreakdownAndRefresh } = require("devtools/client/memory/actions/breakdown");
let { takeSnapshotAndCensus, selectSnapshotAndRefresh } = require("devtools/client/memory/actions/snapshot");
function run_test() {
run_next_test();
}
add_task(function *() {
let front = new StubbedMemoryFront();
let heapWorker = new HeapAnalysesClient();
yield front.attach();
let store = Store();
let { getState, dispatch } = store;
// Test default breakdown with no snapshots
equal(getState().breakdown.by, "coarseType", "default coarseType breakdown selected at start.");
dispatch(setBreakdownAndRefresh(heapWorker, breakdowns.allocationStack.breakdown));
equal(getState().breakdown.by, "allocationStack", "breakdown changed with no snapshots");
// Test invalid breakdowns
ok(getState().errors.length === 0, "No error actions in the queue.");
dispatch(setBreakdownAndRefresh(heapWorker, {}));
yield waitUntilState(store, () => getState().errors.length === 1);
ok(true, "Emits an error action when passing in an invalid breakdown object");
equal(getState().breakdown.by, "allocationStack",
"current breakdown unchanged when passing invalid breakdown");
// Test new snapshots
dispatch(takeSnapshotAndCensus(front, heapWorker));
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS]);
ok(isBreakdownType(getState().snapshots[0].census.report, "allocationStack"),
"New snapshots use the current, non-default breakdown");
// Updates when changing breakdown during `SAVING`
dispatch(takeSnapshotAndCensus(front, heapWorker));
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS, states.SAVING]);
dispatch(setBreakdownAndRefresh(heapWorker, breakdowns.coarseType.breakdown));
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS, states.SAVED_CENSUS]);
ok(isBreakdownType(getState().snapshots[1].census.report, "coarseType"),
"Breakdown can be changed while saving snapshots, uses updated breakdown in census");
// Updates when changing breakdown during `SAVING_CENSUS`
dispatch(takeSnapshotAndCensus(front, heapWorker));
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS, states.SAVED_CENSUS, states.SAVING_CENSUS]);
dispatch(setBreakdownAndRefresh(heapWorker, breakdowns.allocationStack.breakdown));
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS, states.SAVED_CENSUS, states.SAVED_CENSUS]);
ok(breakdownEquals(getState().snapshots[2].census.breakdown, breakdowns.allocationStack.breakdown),
"Breakdown can be changed while saving census, stores updated breakdown in snapshot");
ok(isBreakdownType(getState().snapshots[2].census.report, "allocationStack"),
"Breakdown can be changed while saving census, uses updated breakdown in census");
// Updates census on currently selected snapshot when changing breakdown
ok(getState().snapshots[2].selected, "Third snapshot currently selected");
dispatch(setBreakdownAndRefresh(heapWorker, breakdowns.coarseType.breakdown));
yield waitUntilState(store, () => isBreakdownType(getState().snapshots[2].census.report, "coarseType"));
ok(isBreakdownType(getState().snapshots[2].census.report, "coarseType"),
"Snapshot census updated when changing breakdowns after already generating one census");
dispatch(setBreakdownAndRefresh(heapWorker, breakdowns.allocationStack.breakdown));
yield waitUntilState(store, () => isBreakdownType(getState().snapshots[2].census.report, "allocationStack"));
// Does not update unselected censuses
ok(!getState().snapshots[1].selected, "Second snapshot unselected currently");
ok(breakdownEquals(getState().snapshots[1].census.breakdown, breakdowns.coarseType.breakdown),
"Second snapshot using `coarseType` breakdown still and not yet updated to correct breakdown");
ok(isBreakdownType(getState().snapshots[1].census.report, "coarseType"),
"Second snapshot using `coarseType` still for census and not yet updated to correct breakdown");
// Updates to current breakdown when switching to stale snapshot
dispatch(selectSnapshotAndRefresh(heapWorker, getState().snapshots[1].id));
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS, states.SAVING_CENSUS, states.SAVED_CENSUS]);
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS, states.SAVED_CENSUS, states.SAVED_CENSUS]);
ok(getState().snapshots[1].selected, "Second snapshot selected currently");
ok(breakdownEquals(getState().snapshots[1].census.breakdown, breakdowns.allocationStack.breakdown),
"Second snapshot using `allocationStack` breakdown and updated to correct breakdown");
ok(isBreakdownType(getState().snapshots[1].census.report, "allocationStack"),
"Second snapshot using `allocationStack` for census and updated to correct breakdown");
heapWorker.destroy();
yield front.detach();
});

View File

@ -1,45 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests the action creator `setBreakdown()` for breakdown changing.
* Does not test refreshing the census information, check `setBreakdownAndRefresh` action
* for that.
*/
let { breakdowns, snapshotState: states } = require("devtools/client/memory/constants");
let { setBreakdown } = require("devtools/client/memory/actions/breakdown");
let { takeSnapshotAndCensus } = require("devtools/client/memory/actions/snapshot");
function run_test() {
run_next_test();
}
add_task(function *() {
let front = new StubbedMemoryFront();
let heapWorker = new HeapAnalysesClient();
yield front.attach();
let store = Store();
let { getState, dispatch } = store;
// Test default breakdown with no snapshots
equal(getState().breakdown.by, "coarseType", "default coarseType breakdown selected at start.");
dispatch(setBreakdown(breakdowns.allocationStack.breakdown));
equal(getState().breakdown.by, "allocationStack", "breakdown changed with no snapshots");
// Test invalid breakdowns
try {
dispatch(setBreakdown({}));
ok(false, "Throws when passing in an invalid breakdown object");
} catch (e) {
ok(true, "Throws when passing in an invalid breakdown object");
}
equal(getState().breakdown.by, "allocationStack",
"current breakdown unchanged when passing invalid breakdown");
// Test new snapshots
dispatch(takeSnapshotAndCensus(front, heapWorker));
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS]);
ok(isBreakdownType(getState().snapshots[0].census.report, "allocationStack"),
"New snapshots use the current, non-default breakdown");
});

View File

@ -0,0 +1,93 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* Tests the task creator `setCensusDisplayAndRefreshAndRefresh()` for display
* changing. We test this rather than `setCensusDisplayAndRefresh` directly, as
* we use the refresh action in the app itself composed from
* `setCensusDisplayAndRefresh`.
*/
let { censusDisplays, snapshotState: states } = require("devtools/client/memory/constants");
let { setCensusDisplayAndRefresh } = require("devtools/client/memory/actions/census-display");
let { takeSnapshotAndCensus, selectSnapshotAndRefresh } = require("devtools/client/memory/actions/snapshot");
function run_test() {
run_next_test();
}
add_task(function *() {
let front = new StubbedMemoryFront();
let heapWorker = new HeapAnalysesClient();
yield front.attach();
let store = Store();
let { getState, dispatch } = store;
// Test default display with no snapshots
equal(getState().censusDisplay.breakdown.by, "coarseType",
"default coarseType display selected at start.");
dispatch(setCensusDisplayAndRefresh(heapWorker,
censusDisplays.allocationStack));
equal(getState().censusDisplay.breakdown.by, "allocationStack",
"display changed with no snapshots");
// Test invalid displays
ok(getState().errors.length === 0, "No error actions in the queue.");
dispatch(setCensusDisplayAndRefresh(heapWorker, {}));
yield waitUntilState(store, () => getState().errors.length === 1);
ok(true, "Emits an error action when passing in an invalid display object");
equal(getState().censusDisplay.breakdown.by, "allocationStack",
"current display unchanged when passing invalid display");
// Test new snapshots
dispatch(takeSnapshotAndCensus(front, heapWorker));
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS]);
// Updates when changing display during `SAVING`
dispatch(takeSnapshotAndCensus(front, heapWorker));
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS, states.SAVING]);
dispatch(setCensusDisplayAndRefresh(heapWorker, censusDisplays.coarseType));
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS, states.SAVED_CENSUS]);
// Updates when changing display during `SAVING_CENSUS`
dispatch(takeSnapshotAndCensus(front, heapWorker));
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS, states.SAVED_CENSUS, states.SAVING_CENSUS]);
dispatch(setCensusDisplayAndRefresh(heapWorker, censusDisplays.allocationStack));
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS, states.SAVED_CENSUS, states.SAVED_CENSUS]);
equal(getState().snapshots[2].census.display, censusDisplays.allocationStack,
"Display can be changed while saving census, stores updated display in snapshot");
// Updates census on currently selected snapshot when changing display
ok(getState().snapshots[2].selected, "Third snapshot currently selected");
dispatch(setCensusDisplayAndRefresh(heapWorker, censusDisplays.coarseType));
yield waitUntilState(store, state => state.snapshots[2].state === states.SAVED_CENSUS);
equal(getState().snapshots[2].census.display, censusDisplays.coarseType,
"Snapshot census updated when changing displays after already generating one census");
dispatch(setCensusDisplayAndRefresh(heapWorker, censusDisplays.allocationStack));
yield waitUntilState(store, state => state.snapshots[2].state === states.SAVED_CENSUS);
equal(getState().snapshots[2].census.display, censusDisplays.allocationStack,
"Snapshot census updated when changing displays after already generating one census");
// Does not update unselected censuses
ok(!getState().snapshots[1].selected, "Second snapshot unselected currently");
equal(getState().snapshots[1].census.display, censusDisplays.coarseType,
"Second snapshot using `coarseType` display still and not yet updated to correct display");
// Updates to current display when switching to stale snapshot
dispatch(selectSnapshotAndRefresh(heapWorker, getState().snapshots[1].id));
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS, states.SAVING_CENSUS, states.SAVED_CENSUS]);
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS, states.SAVED_CENSUS, states.SAVED_CENSUS]);
ok(getState().snapshots[1].selected, "Second snapshot selected currently");
equal(getState().snapshots[1].census.display, censusDisplays.allocationStack,
"Second snapshot using `allocationStack` display and updated to correct display");
heapWorker.destroy();
yield front.detach();
});

View File

@ -1,16 +1,25 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* Tests the task creator `setBreakdownAndRefreshAndRefresh()` for custom
* breakdowns.
* Tests the task creator `setCensusDisplayAndRefreshAndRefresh()` for custom
* displays.
*/
let { snapshotState: states } = require("devtools/client/memory/constants");
let { breakdownEquals } = require("devtools/client/memory/utils");
let { setBreakdownAndRefresh } = require("devtools/client/memory/actions/breakdown");
let { setCensusDisplayAndRefresh } = require("devtools/client/memory/actions/census-display");
let { takeSnapshotAndCensus } = require("devtools/client/memory/actions/snapshot");
let custom = { by: "internalType", then: { by: "count", bytes: true, count: false }};
let CUSTOM = {
displayName: "Custom",
tooltip: "Custom tooltip",
inverted: false,
breakdown: {
by: "internalType",
then: { by: "count", bytes: true, count: false }
}
};
function run_test() {
run_next_test();
@ -23,20 +32,20 @@ add_task(function *() {
let store = Store();
let { getState, dispatch } = store;
dispatch(setBreakdownAndRefresh(heapWorker, custom));
ok(breakdownEquals(getState().breakdown, custom),
"Custom breakdown stored in breakdown state.");
dispatch(setCensusDisplayAndRefresh(heapWorker, CUSTOM));
equal(getState().censusDisplay, CUSTOM,
"CUSTOM display stored in display state.");
dispatch(takeSnapshotAndCensus(front, heapWorker));
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS]);
ok(breakdownEquals(getState().snapshots[0].census.breakdown, custom),
"New snapshot stored custom breakdown when done taking census");
equal(getState().snapshots[0].census.display, CUSTOM,
"New snapshot stored CUSTOM display when done taking census");
ok(getState().snapshots[0].census.report.children.length, "Census has some children");
// Ensure we don't have `count` in any results
ok(getState().snapshots[0].census.report.children.every(c => !c.count),
"Census used custom breakdown without counts");
"Census used CUSTOM display without counts");
// Ensure we do have `bytes` in the results
ok(getState().snapshots[0].census.report.children.every(c => typeof c.bytes === "number"),
"Census used custom breakdown with bytes");
"Census used CUSTOM display with bytes");
});

View File

@ -0,0 +1,49 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* Tests the action creator `setCensusDisplay()` for display changing. Does not
* test refreshing the census information, check `setCensusDisplayAndRefresh`
* action for that.
*/
let { censusDisplays, snapshotState: states } = require("devtools/client/memory/constants");
let { setCensusDisplay } = require("devtools/client/memory/actions/census-display");
let { takeSnapshotAndCensus } = require("devtools/client/memory/actions/snapshot");
function run_test() {
run_next_test();
}
add_task(function*() {
let front = new StubbedMemoryFront();
let heapWorker = new HeapAnalysesClient();
yield front.attach();
let store = Store();
let { getState, dispatch } = store;
// Test default display with no snapshots
equal(getState().censusDisplay.breakdown.by, "coarseType",
"default coarseType display selected at start.");
dispatch(setCensusDisplay(censusDisplays.allocationStack));
equal(getState().censusDisplay.breakdown.by, "allocationStack",
"display changed with no snapshots");
// Test invalid displays
try {
dispatch(setCensusDisplay({}));
ok(false, "Throws when passing in an invalid display object");
} catch (e) {
ok(true, "Throws when passing in an invalid display object");
}
equal(getState().censusDisplay.breakdown.by, "allocationStack",
"current display unchanged when passing invalid display");
// Test new snapshots
dispatch(takeSnapshotAndCensus(front, heapWorker));
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS]);
equal(getState().snapshots[0].census.display, censusDisplays.allocationStack,
"New snapshots use the current, non-default display");
});

View File

@ -5,9 +5,7 @@
* Tests the async reducer responding to the action `takeCensus(heapWorker, snapshot)`
*/
var { snapshotState: states, breakdowns } = require("devtools/client/memory/constants");
var { breakdownEquals } = require("devtools/client/memory/utils");
var { ERROR_TYPE } = require("devtools/client/shared/redux/middleware/task");
var { snapshotState: states, censusDisplays } = require("devtools/client/memory/constants");
var actions = require("devtools/client/memory/actions/snapshot");
function run_test() {
@ -45,8 +43,6 @@ add_task(function *() {
snapshot = store.getState().snapshots[0];
ok(snapshot.census, "Snapshot has census after saved census");
ok(snapshot.census.report.children.length, "Census is in tree node form");
ok(isBreakdownType(snapshot.census.report, "coarseType"),
"Census is in tree node form with the default breakdown");
ok(breakdownEquals(snapshot.census.breakdown, breakdowns.coarseType.breakdown),
"Snapshot stored correct breakdown used for the census");
equal(snapshot.census.display, censusDisplays.coarseType,
"Snapshot stored correct display used for the census");
});

View File

@ -1,11 +1,20 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that changing inverted state properly refreshes the selected census.
// Test that changing displays with different inverted state properly
// refreshes the selected census.
let { breakdowns, snapshotState: states } = require("devtools/client/memory/constants");
let { toggleInvertedAndRefresh } = require("devtools/client/memory/actions/inverted");
let { takeSnapshotAndCensus, selectSnapshotAndRefresh } = require("devtools/client/memory/actions/snapshot");
const {
censusDisplays,
snapshotState: states,
} = require("devtools/client/memory/constants");
const {
setCensusDisplayAndRefresh
} = require("devtools/client/memory/actions/census-display");
const {
takeSnapshotAndCensus,
selectSnapshotAndRefresh,
} = require("devtools/client/memory/actions/snapshot");
function run_test() {
run_next_test();
@ -18,7 +27,9 @@ add_task(function *() {
let store = Store();
let { getState, dispatch } = store;
equal(getState().inverted, false, "not inverted by default");
// Select a non-inverted display.
dispatch(setCensusDisplayAndRefresh(heapWorker, censusDisplays.allocationStack));
equal(getState().censusDisplay.inverted, false, "not inverted by default");
dispatch(takeSnapshotAndCensus(front, heapWorker));
dispatch(takeSnapshotAndCensus(front, heapWorker));
@ -29,21 +40,23 @@ add_task(function *() {
states.SAVED_CENSUS]);
ok(true, "saved 3 snapshots and took a census of each of them");
dispatch(toggleInvertedAndRefresh(heapWorker));
// Select an inverted display.
dispatch(setCensusDisplayAndRefresh(heapWorker, censusDisplays.invertedAllocationStack));
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS,
states.SAVED_CENSUS,
states.SAVING_CENSUS]);
ok(true, "toggling inverted should recompute the selected snapshot's census");
equal(getState().inverted, true, "now inverted");
equal(getState().censusDisplay.inverted, true, "now inverted");
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS,
states.SAVED_CENSUS,
states.SAVED_CENSUS]);
equal(getState().snapshots[0].census.inverted, false);
equal(getState().snapshots[1].census.inverted, false);
equal(getState().snapshots[2].census.inverted, true);
equal(getState().snapshots[0].census.display.inverted, false);
equal(getState().snapshots[1].census.display.inverted, false);
equal(getState().snapshots[2].census.display.inverted, true);
dispatch(selectSnapshotAndRefresh(heapWorker, getState().snapshots[1].id));
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS,
@ -55,9 +68,9 @@ add_task(function *() {
states.SAVED_CENSUS,
states.SAVED_CENSUS]);
equal(getState().snapshots[0].census.inverted, false);
equal(getState().snapshots[1].census.inverted, true);
equal(getState().snapshots[2].census.inverted, true);
equal(getState().snapshots[0].census.display.inverted, false);
equal(getState().snapshots[1].census.display.inverted, true);
equal(getState().snapshots[2].census.display.inverted, true);
heapWorker.destroy();
yield front.detach();

View File

@ -1,12 +1,13 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that changing inverted state in the middle of taking a snapshot results
// in an inverted census.
let { snapshotState: states } = require("devtools/client/memory/constants");
let { toggleInverted, toggleInvertedAndRefresh } = require("devtools/client/memory/actions/inverted");
let { takeSnapshotAndCensus } = require("devtools/client/memory/actions/snapshot");
const { censusDisplays, snapshotState: states } = require("devtools/client/memory/constants");
const { takeSnapshotAndCensus } = require("devtools/client/memory/actions/snapshot");
const { setCensusDisplayAndRefresh } = require("devtools/client/memory/actions/census-display");
function run_test() {
run_next_test();
@ -19,26 +20,29 @@ add_task(function *() {
let store = Store();
let { getState, dispatch } = store;
dispatch(setCensusDisplayAndRefresh(heapWorker, censusDisplays.allocationStack));
equal(getState().censusDisplay.inverted, false);
dispatch(takeSnapshotAndCensus(front, heapWorker));
yield waitUntilSnapshotState(store, [states.SAVING]);
dispatch(toggleInverted());
dispatch(setCensusDisplayAndRefresh(heapWorker, censusDisplays.invertedAllocationStack));
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS]);
ok(getState().inverted,
ok(getState().censusDisplay.inverted,
"should want inverted trees");
ok(getState().snapshots[0].census.inverted,
ok(getState().snapshots[0].census.display.inverted,
"snapshot-we-were-in-the-middle-of-saving's census should be inverted");
dispatch(toggleInvertedAndRefresh(heapWorker));
dispatch(setCensusDisplayAndRefresh(heapWorker, censusDisplays.allocationStack));
yield waitUntilSnapshotState(store, [states.SAVING_CENSUS]);
ok(true, "toggling inverted retriggers census");
ok(!getState().inverted, "no long inverted");
ok(!getState().censusDisplay.inverted, "no longer inverted");
dispatch(toggleInverted());
dispatch(setCensusDisplayAndRefresh(heapWorker, censusDisplays.invertedAllocationStack));
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS]);
ok(getState().inverted, "inverted again");
ok(getState().snapshots[0].census.inverted,
ok(getState().censusDisplay.inverted, "inverted again");
ok(getState().snapshots[0].census.display.inverted,
"census-we-were-in-the-middle-of-recomputing should be inverted again");
heapWorker.destroy();

View File

@ -1,29 +1,28 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test toggling the top level inversion state of the tree.
let { toggleInverted } = require("devtools/client/memory/actions/inverted");
const { censusDisplays } = require("devtools/client/memory/constants");
const { setCensusDisplay } = require("devtools/client/memory/actions/census-display");
function run_test() {
run_next_test();
}
add_task(function*() {
let front = new StubbedMemoryFront();
let heapWorker = new HeapAnalysesClient();
yield front.attach();
let store = Store();
const { getState, dispatch } = store;
equal(getState().inverted, false, "not inverted by default");
dispatch(setCensusDisplay(censusDisplays.allocationStack));
equal(getState().censusDisplay.inverted, false,
"not inverted initially");
dispatch(toggleInverted());
equal(getState().inverted, true, "now inverted after toggling");
dispatch(setCensusDisplay(censusDisplays.invertedAllocationStack));
equal(getState().censusDisplay.inverted, true, "now inverted after toggling");
dispatch(toggleInverted());
equal(getState().inverted, false, "not inverted again after toggling again");
heapWorker.destroy();
yield front.detach();
dispatch(setCensusDisplay(censusDisplays.allocationStack));
equal(getState().censusDisplay.inverted, false,
"not inverted again after toggling again");
});

View File

@ -1,10 +1,9 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* Tests the action creator `setBreakdown()` for breakdown changing.
* Does not test refreshing the census information, check `setBreakdownAndRefresh` action
* for that.
* Test toggling the recording of allocation stacks.
*/
let { toggleRecordingAllocationStacks } = require("devtools/client/memory/actions/allocations");
@ -15,7 +14,6 @@ function run_test() {
add_task(function *() {
let front = new StubbedMemoryFront();
let heapWorker = new HeapAnalysesClient();
yield front.attach();
let store = Store();
const { getState, dispatch } = store;
@ -39,4 +37,6 @@ add_task(function *() {
equal(getState().allocations.recording, false, "now we are not recording");
ok(front.recordingAllocations, "front is not recording anymore");
yield front.detach();
});

View File

@ -15,7 +15,6 @@ const {
takeSnapshot,
readSnapshot
} = require("devtools/client/memory/actions/snapshot");
const { breakdownEquals } = require("devtools/client/memory/utils");
function run_test() {
run_next_test();
@ -63,11 +62,12 @@ add_task(function *() {
ok(true, "And then the diff should complete.");
ok(getState().diffing.census, "And we should have a census.");
ok(getState().diffing.census.report, "And that census should have a report.");
ok(breakdownEquals(getState().diffing.census.breakdown, getState().breakdown),
"And that census should have the correct breakdown");
equal(getState().diffing.census.display, getState().censusDisplay,
"And that census should have the correct display");
equal(getState().diffing.census.filter, getState().filter,
"And that census should have the correct filter");
equal(getState().diffing.census.inverted, getState().inverted,
equal(getState().diffing.census.display.inverted,
getState().censusDisplay.inverted,
"And that census should have the correct inversion");
heapWorker.destroy();

View File

@ -6,11 +6,11 @@
const {
diffingState,
snapshotState,
breakdowns,
censusDisplays,
} = require("devtools/client/memory/constants");
const {
setBreakdownAndRefresh,
} = require("devtools/client/memory/actions/breakdown");
setCensusDisplayAndRefresh,
} = require("devtools/client/memory/actions/census-display");
const {
toggleDiffing,
selectSnapshotForDiffingAndRefresh,
@ -18,14 +18,10 @@ const {
const {
setFilterStringAndRefresh,
} = require("devtools/client/memory/actions/filter");
const {
toggleInvertedAndRefresh,
} = require("devtools/client/memory/actions/inverted");
const {
takeSnapshot,
readSnapshot,
} = require("devtools/client/memory/actions/snapshot");
const { breakdownEquals } = require("devtools/client/memory/utils");
function run_test() {
run_next_test();
@ -38,6 +34,11 @@ add_task(function *() {
let store = Store();
const { getState, dispatch } = store;
yield dispatch(setCensusDisplayAndRefresh(heapWorker,
censusDisplays.allocationStack));
equal(getState().censusDisplay.inverted, false,
"not inverted at start");
equal(getState().diffing, null, "not diffing by default");
const s1 = yield dispatch(takeSnapshot(front, heapWorker));
@ -61,17 +62,19 @@ add_task(function *() {
const shouldTriggerRecompute = [
{
name: "toggling inversion",
func: () => dispatch(toggleInvertedAndRefresh(heapWorker))
func: () => dispatch(setCensusDisplayAndRefresh(
heapWorker,
censusDisplays.invertedAllocationStack))
},
{
name: "filtering",
func: () => dispatch(setFilterStringAndRefresh("scr", heapWorker))
},
{
name: "changing breakdowns",
name: "changing displays",
func: () =>
dispatch(setBreakdownAndRefresh(heapWorker,
breakdowns.allocationStack.breakdown))
dispatch(setCensusDisplayAndRefresh(heapWorker,
censusDisplays.coarseType))
}
];
@ -91,12 +94,13 @@ add_task(function *() {
ok(getState().diffing.census, "And we should have a census.");
ok(getState().diffing.census.report,
"And that census should have a report.");
ok(breakdownEquals(getState().diffing.census.breakdown,
getState().breakdown),
"And that census should have the correct breakdown");
equal(getState().diffing.census.display,
getState().censusDisplay,
"And that census should have the correct display");
equal(getState().diffing.census.filter, getState().filter,
"And that census should have the correct filter");
equal(getState().diffing.census.inverted, getState().inverted,
equal(getState().diffing.census.display.inverted,
getState().censusDisplay.inverted,
"And that census should have the correct inversion");
}

View File

@ -1,18 +1,18 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that we can change the breakdown with which we describe a dominator tree
// Test that we can change the display with which we describe a dominator tree
// and that the dominator tree is re-fetched.
const {
snapshotState: states,
dominatorTreeState,
viewState,
dominatorTreeBreakdowns,
dominatorTreeDisplays,
} = require("devtools/client/memory/constants");
const {
setDominatorTreeBreakdownAndRefresh
} = require("devtools/client/memory/actions/dominatorTreeBreakdown");
setDominatorTreeDisplayAndRefresh
} = require("devtools/client/memory/actions/dominator-tree-display");
const {
changeView,
} = require("devtools/client/memory/actions/view");
@ -46,34 +46,34 @@ add_task(function *() {
state.snapshots[0].dominatorTree &&
state.snapshots[0].dominatorTree.state === dominatorTreeState.LOADED);
ok(getState().dominatorTreeBreakdown,
"We have a default breakdown for describing nodes in a dominator tree");
equal(getState().dominatorTreeBreakdown,
dominatorTreeBreakdowns.coarseType.breakdown,
ok(getState().dominatorTreeDisplay,
"We have a default display for describing nodes in a dominator tree");
equal(getState().dominatorTreeDisplay,
dominatorTreeDisplays.coarseType,
"and the default is coarse type");
equal(getState().dominatorTreeBreakdown,
getState().snapshots[0].dominatorTree.breakdown,
"and the newly computed dominator tree has that breakdown");
equal(getState().dominatorTreeDisplay,
getState().snapshots[0].dominatorTree.display,
"and the newly computed dominator tree has that display");
// Switch to the allocationStack breakdown.
dispatch(setDominatorTreeBreakdownAndRefresh(
// Switch to the allocationStack display.
dispatch(setDominatorTreeDisplayAndRefresh(
heapWorker,
dominatorTreeBreakdowns.allocationStack.breakdown));
dominatorTreeDisplays.allocationStack));
yield waitUntilState(store, state =>
state.snapshots[0].dominatorTree.state === dominatorTreeState.FETCHING);
ok(true,
"switching breakdown types caused the dominator tree to be fetched " +
"switching display types caused the dominator tree to be fetched " +
"again.");
yield waitUntilState(store, state =>
state.snapshots[0].dominatorTree.state === dominatorTreeState.LOADED);
equal(getState().snapshots[0].dominatorTree.breakdown,
dominatorTreeBreakdowns.allocationStack.breakdown,
"The new dominator tree's breakdown is allocationStack");
equal(getState().dominatorTreeBreakdown,
dominatorTreeBreakdowns.allocationStack.breakdown,
"as is our requested dominator tree breakdown");
equal(getState().snapshots[0].dominatorTree.display,
dominatorTreeDisplays.allocationStack,
"The new dominator tree's display is allocationStack");
equal(getState().dominatorTreeDisplay,
dominatorTreeDisplays.allocationStack,
"as is our requested dominator tree display");
heapWorker.destroy();
yield front.detach();

View File

@ -1,18 +1,18 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that we can change the breakdown with which we describe a dominator tree
// Test that we can change the display with which we describe a dominator tree
// while the dominator tree is in the middle of being fetched.
const {
snapshotState: states,
dominatorTreeState,
viewState,
dominatorTreeBreakdowns,
dominatorTreeDisplays,
} = require("devtools/client/memory/constants");
const {
setDominatorTreeBreakdownAndRefresh
} = require("devtools/client/memory/actions/dominatorTreeBreakdown");
setDominatorTreeDisplayAndRefresh
} = require("devtools/client/memory/actions/dominator-tree-display");
const {
changeView,
} = require("devtools/client/memory/actions/view");
@ -46,31 +46,31 @@ add_task(function *() {
state.snapshots[0].dominatorTree &&
state.snapshots[0].dominatorTree.state === dominatorTreeState.FETCHING);
ok(getState().dominatorTreeBreakdown,
"We have a default breakdown for describing nodes in a dominator tree");
equal(getState().dominatorTreeBreakdown,
dominatorTreeBreakdowns.coarseType.breakdown,
ok(getState().dominatorTreeDisplay,
"We have a default display for describing nodes in a dominator tree");
equal(getState().dominatorTreeDisplay,
dominatorTreeDisplays.coarseType,
"and the default is coarse type");
equal(getState().dominatorTreeBreakdown,
getState().snapshots[0].dominatorTree.breakdown,
"and the newly computed dominator tree has that breakdown");
equal(getState().dominatorTreeDisplay,
getState().snapshots[0].dominatorTree.display,
"and the newly computed dominator tree has that display");
// Switch to the allocationStack breakdown while we are still fetching the
// Switch to the allocationStack display while we are still fetching the
// dominator tree.
dispatch(setDominatorTreeBreakdownAndRefresh(
dispatch(setDominatorTreeDisplayAndRefresh(
heapWorker,
dominatorTreeBreakdowns.allocationStack.breakdown));
dominatorTreeDisplays.allocationStack));
// Wait for the dominator tree to finish being fetched.
yield waitUntilState(store, state =>
state.snapshots[0].dominatorTree.state === dominatorTreeState.LOADED);
equal(getState().snapshots[0].dominatorTree.breakdown,
dominatorTreeBreakdowns.allocationStack.breakdown,
"The new dominator tree's breakdown is allocationStack");
equal(getState().dominatorTreeBreakdown,
dominatorTreeBreakdowns.allocationStack.breakdown,
"as is our requested dominator tree breakdown");
equal(getState().snapshots[0].dominatorTree.display,
dominatorTreeDisplays.allocationStack,
"The new dominator tree's display is allocationStack");
equal(getState().dominatorTreeDisplay,
dominatorTreeDisplays.allocationStack,
"as is our requested dominator tree display");
heapWorker.destroy();
yield front.detach();

View File

@ -1,15 +1,16 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* Tests that we use the correct snapshot aggregate value
* in `utils.getSnapshotTotals(snapshot)`
*/
let { breakdowns, snapshotState: states } = require("devtools/client/memory/constants");
let { getSnapshotTotals, breakdownEquals } = require("devtools/client/memory/utils");
let { toggleInvertedAndRefresh } = require("devtools/client/memory/actions/inverted");
let { takeSnapshotAndCensus } = require("devtools/client/memory/actions/snapshot");
const { censusDisplays, snapshotState: states } = require("devtools/client/memory/constants");
const { getSnapshotTotals } = require("devtools/client/memory/utils");
const { takeSnapshotAndCensus } = require("devtools/client/memory/actions/snapshot");
const { setCensusDisplayAndRefresh } = require("devtools/client/memory/actions/census-display");
function run_test() {
run_next_test();
@ -22,12 +23,13 @@ add_task(function *() {
let store = Store();
let { getState, dispatch } = store;
yield dispatch(setCensusDisplayAndRefresh(heapWorker,
censusDisplays.allocationStack));
dispatch(takeSnapshotAndCensus(front, heapWorker));
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS]);
ok(!getState().snapshots[0].census.inverted, "Snapshot is not inverted");
ok(isBreakdownType(getState().snapshots[0].census.report, "coarseType"),
"Snapshot using `coarseType` breakdown");
ok(!getState().snapshots[0].census.display.inverted, "Snapshot is not inverted");
let census = getState().snapshots[0].census;
let result = aggregate(census.report);
@ -41,14 +43,17 @@ add_task(function *() {
equal(totalBytes, result.bytes, "getSnapshotTotals reuslted in correct bytes");
equal(totalCount, result.count, "getSnapshotTotals reuslted in correct count");
dispatch(toggleInvertedAndRefresh(heapWorker));
dispatch(setCensusDisplayAndRefresh(heapWorker,
censusDisplays.invertedAllocationStack));
yield waitUntilSnapshotState(store, [states.SAVING_CENSUS]);
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS]);
ok(getState().snapshots[0].census.inverted, "Snapshot is inverted");
ok(getState().snapshots[0].census.display.inverted, "Snapshot is inverted");
result = getSnapshotTotals(getState().snapshots[0].census);
equal(totalBytes, result.bytes, "getSnapshotTotals reuslted in correct bytes when inverted");
equal(totalCount, result.count, "getSnapshotTotals reuslted in correct count when inverted");
equal(totalBytes, result.bytes,
"getSnapshotTotals reuslted in correct bytes when inverted");
equal(totalCount, result.count,
"getSnapshotTotals reuslted in correct count when inverted");
});
function aggregate (report) {

View File

@ -1,5 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* Tests the task creator `takeSnapshotAndCensus()` for the whole flow of
@ -8,7 +9,7 @@
*/
let utils = require("devtools/client/memory/utils");
let { snapshotState: states, breakdowns } = require("devtools/client/memory/constants");
let { snapshotState: states, censusDisplays } = require("devtools/client/memory/constants");
let { Preferences } = require("resource://gre/modules/Preferences.jsm");
function run_test() {
@ -16,39 +17,16 @@ function run_test() {
}
add_task(function *() {
ok(utils.breakdownEquals(breakdowns.allocationStack.breakdown, {
by: "allocationStack",
then: { by: "count", count: true, bytes: true },
noStack: { by: "count", count: true, bytes: true },
}), "utils.breakdownEquals() passes with preset"),
ok(!utils.breakdownEquals(breakdowns.allocationStack.breakdown, {
by: "allocationStack",
then: { by: "count", count: false, bytes: true },
noStack: { by: "count", count: true, bytes: true },
}), "utils.breakdownEquals() fails when deep properties do not match");
ok(!utils.breakdownEquals(breakdowns.allocationStack.breakdown, {
by: "allocationStack",
then: { by: "count", bytes: true },
noStack: { by: "count", count: true, bytes: true },
}), "utils.breakdownEquals() fails when deep properties are missing.");
let s1 = utils.createSnapshot({});
let s2 = utils.createSnapshot({});
equal(s1.state, states.SAVING, "utils.createSnapshot() creates snapshot in saving state");
ok(s1.id !== s2.id, "utils.createSnapshot() creates snapshot with unique ids");
ok(utils.breakdownEquals(utils.breakdownNameToSpec("coarseType"), breakdowns.coarseType.breakdown),
"utils.breakdownNameToSpec() works for presets");
ok(utils.breakdownEquals(utils.breakdownNameToSpec("coarseType"), breakdowns.coarseType.breakdown),
"utils.breakdownNameToSpec() works for presets");
let custom = { by: "internalType", then: { by: "count", bytes: true }};
Preferences.set("devtools.memory.custom-breakdowns", JSON.stringify({ "My Breakdown": custom }));
Preferences.set("devtools.memory.custom-census-displays", JSON.stringify({ "My Display": custom }));
ok(utils.breakdownEquals(utils.getCustomBreakdowns()["My Breakdown"], custom),
"utils.getCustomBreakdowns() returns custom breakdowns");
equal(utils.getCustomCensusDisplays()["My Display"].by, custom.by,
"utils.getCustomCensusDisplays() returns custom displays");
ok(true, "test formatNumber util functions");
equal(utils.formatNumber(12), "12", "formatNumber returns 12 for 12");

View File

@ -21,9 +21,9 @@ skip-if = toolkit == 'android' || toolkit == 'gonk'
[test_action-filter-03.js]
[test_action-import-snapshot-and-census.js]
[test_action-select-snapshot.js]
[test_action-set-breakdown.js]
[test_action-set-breakdown-and-refresh-01.js]
[test_action-set-breakdown-and-refresh-02.js]
[test_action-set-display.js]
[test_action-set-display-and-refresh-01.js]
[test_action-set-display-and-refresh-02.js]
[test_action-take-census.js]
[test_action-take-snapshot.js]
[test_action-take-snapshot-and-census.js]

View File

@ -11,20 +11,20 @@ const L10N = exports.L10N = new ViewHelpers.L10N(STRINGS_URI);
const { OS } = require("resource://gre/modules/osfile.jsm");
const { assert } = require("devtools/shared/DevToolsUtils");
const { Preferences } = require("resource://gre/modules/Preferences.jsm");
const CUSTOM_BREAKDOWN_PREF = "devtools.memory.custom-breakdowns";
const CUSTOM_DOMINATOR_TREE_BREAKDOWN_PREF = "devtools.memory.custom-dominator-tree-breakdowns";
const CUSTOM_CENSUS_DISPLAY_PREF = "devtools.memory.custom-census-displays";
const CUSTOM_DOMINATOR_TREE_DISPLAY_PREF = "devtools.memory.custom-dominator-tree-displays";
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const {
snapshotState: states,
diffingState,
breakdowns,
dominatorTreeBreakdowns,
censusDisplays,
dominatorTreeDisplays,
dominatorTreeState
} = require("./constants");
/**
* Takes a snapshot object and returns the
* localized form of its timestamp to be used as a title.
* Takes a snapshot object and returns the localized form of its timestamp to be
* used as a title.
*
* @param {Snapshot} snapshot
* @return {String}
@ -48,160 +48,35 @@ exports.getSnapshotTitle = function (snapshot) {
});
};
/**
* Returns an array of objects with the unique key `name`
* and `displayName` for each breakdown.
*
* @return {Object{name, displayName}}
*/
exports.getBreakdownDisplayData = function () {
return exports.getBreakdownNames().map(({ name, tooltip }) => {
// If it's a preset use the display name value
let preset = breakdowns[name];
let displayName = name;
if (preset && preset.displayName) {
displayName = preset.displayName;
}
return { name, tooltip, displayName };
});
};
/**
* Returns an array of the unique names and tooltips for each breakdown in
* presets and custom pref.
*
* @return {Array<Object>}
*/
exports.getBreakdownNames = function () {
let custom = exports.getCustomBreakdowns();
return Object.keys(Object.assign({}, breakdowns, custom))
.map(key => {
return breakdowns[key]
? { name: key, tooltip: breakdowns[key].tooltip }
: { name: key };
});
};
/**
* Returns custom breakdowns defined in `devtools.memory.custom-breakdowns` pref.
*
* @return {Object}
*/
exports.getCustomBreakdowns = function () {
let customBreakdowns = Object.create(null);
function getCustomDisplaysHelper(pref) {
let customDisplays = Object.create(null);
try {
customBreakdowns = JSON.parse(Preferences.get(CUSTOM_BREAKDOWN_PREF)) || Object.create(null);
customDisplays = JSON.parse(Preferences.get(pref)) || Object.create(null);
} catch (e) {
DevToolsUtils.reportException(
`String stored in "${CUSTOM_BREAKDOWN_PREF}" pref cannot be parsed by \`JSON.parse()\`.`);
`String stored in "${pref}" pref cannot be parsed by \`JSON.parse()\`.`);
}
return customBreakdowns;
return Object.freeze(customDisplays);
}
/**
* Converts a breakdown preset name, like "allocationStack", and returns the
* spec for the breakdown. Also checks properties of keys in the `devtools.memory.custom-breakdowns`
* pref. If not found, returns an empty object.
*
* @param {String} name
* @return {Object}
*/
exports.breakdownNameToSpec = function (name) {
let customBreakdowns = exports.getCustomBreakdowns();
// If breakdown is already a breakdown, use it
if (typeof name === "object") {
return name;
}
// If it's in our custom breakdowns, use it
else if (name in customBreakdowns) {
return customBreakdowns[name];
}
// If breakdown name is in our presets, use that
else if (name in breakdowns) {
return breakdowns[name].breakdown;
}
return Object.create(null);
};
/**
* Returns an array of objects with the unique key `name` and `displayName` for
* each breakdown for dominator trees.
*
* @return {Array<Object>}
*/
exports.getDominatorTreeBreakdownDisplayData = function () {
return exports.getDominatorTreeBreakdownNames().map(({ name, tooltip }) => {
// If it's a preset use the display name value
let preset = dominatorTreeBreakdowns[name];
let displayName = name;
if (preset && preset.displayName) {
displayName = preset.displayName;
}
return { name, tooltip, displayName };
});
};
/**
* Returns an array of the unique names for each breakdown in
* presets and custom pref.
*
* @return {Array<Breakdown>}
*/
exports.getDominatorTreeBreakdownNames = function () {
let custom = exports.getCustomDominatorTreeBreakdowns();
return Object.keys(Object.assign({}, dominatorTreeBreakdowns, custom))
.map(key => {
return dominatorTreeBreakdowns[key]
? { name: key, tooltip: dominatorTreeBreakdowns[key].tooltip }
: { name: key };
});
};
/**
* Returns custom breakdowns defined in `devtools.memory.custom-dominator-tree-breakdowns` pref.
* Returns custom displays defined in `devtools.memory.custom-census-displays`
* pref.
*
* @return {Object}
*/
exports.getCustomDominatorTreeBreakdowns = function () {
let customBreakdowns = Object.create(null);
try {
customBreakdowns = JSON.parse(Preferences.get(CUSTOM_DOMINATOR_TREE_BREAKDOWN_PREF)) || Object.create(null);
} catch (e) {
DevToolsUtils.reportException(
`String stored in "${CUSTOM_BREAKDOWN_PREF}" pref cannot be parsed by \`JSON.parse()\`.`);
}
return customBreakdowns;
exports.getCustomCensusDisplays = function () {
return getCustomDisplaysHelper(CUSTOM_CENSUS_DISPLAY_PREF);
};
/**
* Converts a dominator tree breakdown preset name, like "allocationStack", and
* returns the spec for the breakdown. Also checks properties of keys in the
* `devtools.memory.custom-breakdowns` pref. If not found, returns an empty
* object.
* Returns custom displays defined in
* `devtools.memory.custom-dominator-tree-displays` pref.
*
* @param {String} name
* @return {Object}
*/
exports.dominatorTreeBreakdownNameToSpec = function (name) {
let customBreakdowns = exports.getCustomDominatorTreeBreakdowns();
// If breakdown is already a breakdown, use it.
if (typeof name === "object") {
return name;
}
// If it's in our custom breakdowns, use it.
if (name in customBreakdowns) {
return customBreakdowns[name];
}
// If breakdown name is in our presets, use that.
if (name in dominatorTreeBreakdowns) {
return dominatorTreeBreakdowns[name].breakdown;
}
return Object.create(null);
exports.getCustomDominatorTreeDisplays = function () {
return getCustomDisplaysHelper(CUSTOM_DOMINATOR_TREE_DISPLAY_PREF);
};
/**
@ -389,61 +264,19 @@ exports.createSnapshot = function createSnapshot(state) {
};
/**
* Takes two objects and compares them deeply, returning
* a boolean indicating if they're equal or not. Used for breakdown
* comparison.
* Return true if the census is up to date with regards to the current filtering
* and requested display, false otherwise.
*
* @param {Any} obj1
* @param {Any} obj2
* @return {Boolean}
*/
const breakdownEquals = exports.breakdownEquals = function (obj1, obj2) {
let type1 = typeof obj1;
let type2 = typeof obj2;
// Quick checks
if (type1 !== type2 || (Array.isArray(obj1) !== Array.isArray(obj2))) {
return false;
}
if (obj1 === obj2) {
return true;
}
if (Array.isArray(obj1)) {
if (obj1.length !== obj2.length) { return false; }
return obj1.every((_, i) => exports.breakdownEquals(obj[1], obj2[i]));
}
else if (type1 === "object") {
let k1 = Object.keys(obj1);
let k2 = Object.keys(obj2);
if (k1.length !== k2.length) {
return false;
}
return k1.every(k => exports.breakdownEquals(obj1[k], obj2[k]));
}
return false;
};
/**
* Return true if the census is up to date with regards to the current
* inversion/filtering/breakdown, false otherwise.
*
* @param {Boolean} inverted
* @param {String} filter
* @param {Object} breakdown
* @param {censusDisplayModel} display
* @param {censusModel} census
*
* @returns {Boolean}
*/
exports.censusIsUpToDate = function (inverted, filter, breakdown, census) {
exports.censusIsUpToDate = function (filter, display, census) {
return census
&& inverted === census.inverted
&& filter === census.filter
&& breakdownEquals(breakdown, census.breakdown);
&& display === census.display;
};
/**

View File

@ -4,6 +4,7 @@
"use strict";
const { Cu, Ci } = require("chrome");
const Services = require("Services");
loader.lazyRequireGetter(this, "HarAutomation", "devtools/client/netmonitor/har/har-automation", true);

View File

@ -13,7 +13,7 @@ const { Cu, Ci } = require("chrome");
const Services = require("Services");
const { L10N } = require("devtools/client/performance/modules/global");
const { TIMELINE_BLUEPRINT } = require("devtools/client/performance/modules/markers");
const WebConsoleUtils = require("devtools/shared/webconsole/utils");
const { getSourceNames } = require("devtools/client/shared/source-utils");
const SHOW_TRIGGER_FOR_GC_TYPES_PREF = "devtools.performance.ui.show-triggers-for-gc-types";
/**
@ -259,7 +259,7 @@ const DOM = {
let urlNode = doc.createElement("label");
urlNode.className = "filename";
urlNode.setAttribute("value", WebConsoleUtils.Utils.abbreviateSourceURL(url));
urlNode.setAttribute("value", getSourceNames(url).short);
let lineNode = doc.createElement("label");
lineNode.className = "line-number";
lineNode.setAttribute("value", `:${line}`);

View File

@ -108,8 +108,8 @@ pref("devtools.debugger.ui.variables-searchbox-visible", false);
// Enable the Memory tools
pref("devtools.memory.enabled", false);
pref("devtools.memory.custom-breakdowns", "{}");
pref("devtools.memory.custom-dominator-tree-breakdowns", "{}");
pref("devtools.memory.custom-census-displays", "{}");
pref("devtools.memory.custom-dominator-tree-displays", "{}");
// Enable the Performance tools
pref("devtools.performance.enabled", true);

View File

@ -5,7 +5,7 @@
"use strict";
/* global content, docShell, addEventListener, addMessageListener,
removeEventListener, removeMessageListener, sendAsyncMessage */
removeEventListener, removeMessageListener, sendAsyncMessage, Services */
var global = this;

View File

@ -19,6 +19,10 @@ var EventEmitter = require("devtools/shared/event-emitter");
var {ViewHelpers} = require("devtools/client/shared/widgets/ViewHelpers.jsm");
loader.lazyImporter(this, "SystemAppProxy",
"resource://gre/modules/SystemAppProxy.jsm");
loader.lazyRequireGetter(this, "DebuggerClient",
"devtools/shared/client/main", true);
loader.lazyRequireGetter(this, "DebuggerServer",
"devtools/server/main", true);
this.EXPORTED_SYMBOLS = ["ResponsiveUIManager"];
@ -175,6 +179,7 @@ function ResponsiveUI(aWindow, aTab)
this.bound_startResizing = this.startResizing.bind(this);
this.bound_stopResizing = this.stopResizing.bind(this);
this.bound_onDrag = this.onDrag.bind(this);
this.bound_changeUA = this.changeUA.bind(this);
this.bound_onContentResize = this.onContentResize.bind(this);
this.mm.addMessageListener("ResponsiveMode:OnContentResize",
@ -201,7 +206,6 @@ ResponsiveUI.prototype = {
init: Task.async(function*() {
debug("INIT BEGINS");
let ready = this.waitForMessage("ResponsiveMode:ChildScriptReady");
this.mm.loadFrameScript("resource://devtools/client/responsivedesign/responsivedesign-child.js", true);
yield ready;
@ -241,6 +245,9 @@ ResponsiveUI.prototype = {
this.touchEnableBefore = false;
this.touchEventSimulator = new TouchEventSimulator(this.browser);
yield this.connectToServer();
this.userAgentInput.hidden = false;
// Hook to display promotional Developer Edition doorhanger.
// Only displayed once.
showDoorhanger({
@ -254,6 +261,21 @@ ResponsiveUI.prototype = {
ResponsiveUIManager.emit("on", { tab: this.tab });
}),
connectToServer: Task.async(function*() {
if (!DebuggerServer.initialized) {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
}
this.client = new DebuggerClient(DebuggerServer.connectPipe());
yield this.client.connect();
let {tab} = yield this.client.getTab();
let [response, tabClient] = yield this.client.attachTab(tab.actor);
this.tabClient = tabClient;
if (!tabClient) {
Cu.reportError("Responsive Mode: failed to attach tab");
}
}),
loadPresets: function() {
// Try to load presets from prefs
let presets = defaultPresets;
@ -359,7 +381,14 @@ ResponsiveUI.prototype = {
if (this.touchEventSimulator) {
this.touchEventSimulator.stop();
}
yield new Promise((resolve, reject) => {
this.client.close(resolve);
this.client = this.tabClient = null;
});
this._telemetry.toolClosed("responsive");
let stopped = this.waitForMessage("ResponsiveMode:Stop:Done");
this.tab.linkedBrowser.messageManager.sendAsyncMessage("ResponsiveMode:Stop");
yield stopped;
@ -510,6 +539,14 @@ ResponsiveUI.prototype = {
this.toolbar.appendChild(this.screenshotbutton);
this.userAgentInput = this.chromeDoc.createElement("textbox");
this.userAgentInput.className = "devtools-responsiveui-textinput";
this.userAgentInput.setAttribute("placeholder",
this.strings.GetStringFromName("responsiveUI.userAgentPlaceholder"));
this.userAgentInput.addEventListener("blur", this.bound_changeUA, true);
this.userAgentInput.hidden = true;
this.toolbar.appendChild(this.userAgentInput);
// Resizers
let resizerTooltip = this.strings.GetStringFromName("responsiveUI.resizerTooltip");
this.resizer = this.chromeDoc.createElement("box");
@ -905,6 +942,39 @@ ResponsiveUI.prototype = {
}
}),
waitForReload() {
let navigatedDeferred = promise.defer();
let onNavigated = (_, { state }) => {
if (state != "stop") {
return;
}
this.client.removeListener("tabNavigated", onNavigated);
navigatedDeferred.resolve();
};
this.client.addListener("tabNavigated", onNavigated);
return navigatedDeferred.promise;
},
/**
* Change the user agent string
*/
changeUA: Task.async(function*() {
let value = this.userAgentInput.value;
if (value) {
this.userAgentInput.setAttribute("attention", "true");
} else {
this.userAgentInput.removeAttribute("attention");
}
// Changing the UA triggers an automatic reload. Ensure we wait for this to
// complete before emitting the changed event, so that tests wait for the
// reload.
let reloaded = this.waitForReload();
yield this.tabClient.reconfigure({customUserAgent: value});
yield reloaded;
ResponsiveUIManager.emit("userAgentChanged", { tab: this.tab });
}),
/**
* Get the current width and height.
*/

View File

@ -7,8 +7,11 @@ support-files =
[browser_responsive_cmd.js]
[browser_responsivecomputedview.js]
skip-if = e10s && debug && os == 'win' # Bug 1252201 - Docshell leak on win 7 debug e10s
[browser_responsiveruleview.js]
skip-if = e10s && debug && os == 'win' # Bug 1252201 - Docshell leak on win 7 debug e10s
[browser_responsiveui.js]
[browser_responsiveui_touch.js]
[browser_responsiveuiaddcustompreset.js]
[browser_responsive_devicewidth.js]
[browser_responsiveui_customuseragent.js]

View File

@ -0,0 +1,56 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const TEST_URI = "data:text/html, Custom User Agent test";
const DEFAULT_UA = Cc["@mozilla.org/network/protocol;1?name=http"]
.getService(Ci.nsIHttpProtocolHandler)
.userAgent;
const CHROME_UA = "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36" +
" (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36";
add_task(function*() {
yield addTab(TEST_URI);
let {rdm, manager} = yield openRDM();
yield testUserAgent(DEFAULT_UA);
info("Setting UA to " + CHROME_UA);
yield setUserAgent(CHROME_UA, rdm, manager);
yield testUserAgent(CHROME_UA);
info("Resetting UA");
yield setUserAgent("", rdm, manager);
yield testUserAgent(DEFAULT_UA);
info("Setting UA to " + CHROME_UA);
yield setUserAgent(CHROME_UA, rdm, manager);
yield testUserAgent(CHROME_UA);
info("Closing responsive mode");
yield closeRDM(rdm);
yield testUserAgent(DEFAULT_UA);
});
function* setUserAgent(ua, rdm, manager) {
let input = rdm.userAgentInput;
input.focus();
input.value = ua;
let onUAChanged = once(manager, "userAgentChanged");
input.blur();
yield onUAChanged;
if (ua !== "") {
ok(input.hasAttribute("attention"), "UA input should be highlighted");
} else {
ok(!input.hasAttribute("attention"), "UA input shouldn't be highlighted");
}
}
function* testUserAgent(value) {
let ua = yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function*() {
return content.navigator.userAgent;
});
is(ua, value, `UA should be set to ${value}`);
}

View File

@ -2,13 +2,10 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
const { Cu } = require("chrome");
Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
const STRINGS_URI = "chrome://devtools/locale/components.properties";
const L10N = new ViewHelpers.L10N(STRINGS_URI);
const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
const { getSourceNames } = require("devtools/client/shared/source-utils");
const UNKNOWN_SOURCE_STRING = L10N.getStr("frame.unknownSource");
const { L10N } = require("resource://devtools/client/shared/widgets/ViewHelpers.jsm").ViewHelpers;
const l10n = new L10N("chrome://devtools/locale/components.properties");
const Frame = module.exports = createClass({
displayName: "Frame",
@ -39,7 +36,7 @@ const Frame = module.exports = createClass({
render() {
let { onClick, frame, showFunctionName, showHost } = this.props;
const { short, long, host } = getSourceNames(frame.source, UNKNOWN_SOURCE_STRING);
const { short, long, host } = getSourceNames(frame.source);
let tooltip = `${long}:${frame.line}`;
if (frame.column) {
@ -51,7 +48,7 @@ const Frame = module.exports = createClass({
sourceString += `:${frame.column}`;
}
let onClickTooltipString = L10N.getFormatStr("frame.viewsourceindebugger", sourceString);
let onClickTooltipString = l10n.getFormatStr("frame.viewsourceindebugger", sourceString);
let fields = [
dom.a({

View File

@ -4,10 +4,16 @@
"use strict";
const { URL } = require("sdk/url");
const { Cu } = require("chrome");
Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
const STRINGS_URI = "chrome://devtools/locale/components.properties";
const L10N = new ViewHelpers.L10N(STRINGS_URI);
const UNKNOWN_SOURCE_STRING = L10N.getStr("frame.unknownSource");
// Character codes used in various parsing helper functions.
const CHAR_CODE_A = "a".charCodeAt(0);
const CHAR_CODE_C = "c".charCodeAt(0);
const CHAR_CODE_D = "d".charCodeAt(0);
const CHAR_CODE_E = "e".charCodeAt(0);
const CHAR_CODE_F = "f".charCodeAt(0);
const CHAR_CODE_H = "h".charCodeAt(0);
@ -26,6 +32,8 @@ const CHAR_CODE_SLASH = "/".charCodeAt(0);
// The cache used in the `nsIURL` function.
const gURLStore = new Map();
// The cache used in the `getSourceNames` function.
const gSourceNamesStore = new Map();
/**
* Takes a string and returns an object containing all the properties
@ -81,17 +89,38 @@ function parseURL(location) {
*
* @param {String} source
* The source to parse. Can be a URI or names like "(eval)" or "self-hosted".
* @param {String} unknownSourceString
* The string to use if no valid source name can be generated.
* @return {Object}
* An object with the following properties:
* - {String} short: A short name for the source.
* - {String} long: The full, long name for the source.
* - "http://page.com/test.js#go?q=query" -> "test.js"
* - {String} long: The full, long name for the source, with hash/query stripped.
* - "http://page.com/test.js#go?q=query" -> "http://page.com/test.js"
* - {String?} host: If available, the host name for the source.
* - "http://page.com/test.js#go?q=query" -> "page.com"
*/
function getSourceNames (source, unknownSourceString) {
function getSourceNames (source) {
let data = gSourceNamesStore.get(source);
if (data) {
return data;
}
let short, long, host;
const sourceStr = source ? String(source) : "";
// If `data:...` uri
if (isDataScheme(sourceStr)) {
let commaIndex = sourceStr.indexOf(",");
if (commaIndex > -1) {
// The `short` name for a data URI becomes `data:` followed by the actual
// encoded content, omitting the MIME type, and charset.
let short = `data:${sourceStr.substring(commaIndex + 1)}`.slice(0, 100);
let result = { short, long: sourceStr };
gSourceNamesStore.set(source, result);
return result;
}
}
const parsedUrl = parseURL(sourceStr);
if (!parsedUrl) {
@ -99,19 +128,35 @@ function getSourceNames (source, unknownSourceString) {
long = sourceStr;
short = sourceStr.slice(0, 100);
} else {
short = parsedUrl.fileName;
long = parsedUrl.href;
host = parsedUrl.host;
long = parsedUrl.href;
if (parsedUrl.hash) {
long = long.replace(parsedUrl.hash, "");
}
if (parsedUrl.search) {
long = long.replace(parsedUrl.search, "");
}
short = parsedUrl.fileName;
// If `short` is just a slash, and we actually have a path,
// strip the slash and parse again to get a more useful short name.
// e.g. "http://foo.com/bar/" -> "bar", rather than "/"
if (short === "/" && parsedUrl.pathname !== "/") {
short = parseURL(long.replace(/\/$/, "")).fileName;
}
}
if (!short) {
if (!long) {
long = unknownSourceString;
long = UNKNOWN_SOURCE_STRING;
}
short = long.slice(0, 100);
}
return { short, long, host };
let result = { short, long, host };
gSourceNamesStore.set(source, result);
return result;
}
// For the functions below, we assume that we will never access the location
@ -126,6 +171,14 @@ function isColonSlashSlash(location, i=0) {
location.charCodeAt(++i) === CHAR_CODE_SLASH;
}
function isDataScheme(location, i=0) {
return location.charCodeAt(i) === CHAR_CODE_D &&
location.charCodeAt(++i) === CHAR_CODE_A &&
location.charCodeAt(++i) === CHAR_CODE_T &&
location.charCodeAt(++i) === CHAR_CODE_A &&
location.charCodeAt(++i) === CHAR_CODE_COLON;
}
function isContentScheme(location, i=0) {
let firstChar = location.charCodeAt(i);
@ -208,3 +261,4 @@ exports.parseURL = parseURL;
exports.getSourceNames = getSourceNames;
exports.isChromeScheme = isChromeScheme;
exports.isContentScheme = isContentScheme;
exports.isDataScheme = isDataScheme;

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