зеркало из https://github.com/mozilla/gecko-dev.git
merge fx-team to mozilla-central a=merge
This commit is contained in:
Коммит
20095687cf
|
@ -2345,7 +2345,12 @@ function URLBarSetURI(aURI) {
|
|||
checkEmptyPageOrigin(gBrowser.selectedBrowser, uri)) {
|
||||
value = "";
|
||||
} else {
|
||||
value = losslessDecodeURI(uri);
|
||||
// We should deal with losslessDecodeURI throwing for exotic URIs
|
||||
try {
|
||||
value = losslessDecodeURI(uri);
|
||||
} catch (ex) {
|
||||
value = "about:blank";
|
||||
}
|
||||
}
|
||||
|
||||
valid = !isBlankPageURL(uri.spec);
|
||||
|
|
|
@ -8,5 +8,6 @@ support-files =
|
|||
[browser_notification_open_settings.js]
|
||||
[browser_notification_remove_permission.js]
|
||||
[browser_notification_permission_migration.js]
|
||||
[browser_notification_replace.js]
|
||||
[browser_notification_tab_switching.js]
|
||||
skip-if = buildapp == 'mulet'
|
||||
|
|
|
@ -28,7 +28,7 @@ add_task(function* test_notificationClose() {
|
|||
let alertTitleLabel = alertWindow.document.getElementById("alertTitleLabel");
|
||||
is(alertTitleLabel.value, "Test title", "Title text of notification should be present");
|
||||
let alertTextLabel = alertWindow.document.getElementById("alertTextLabel");
|
||||
is(alertTextLabel.textContent, "Test body", "Body text of notification should be present");
|
||||
is(alertTextLabel.textContent, "Test body 2", "Body text of notification should be present");
|
||||
|
||||
let alertCloseButton = alertWindow.document.querySelector(".alertCloseButton");
|
||||
is(alertCloseButton.localName, "toolbarbutton", "close button found");
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
"use strict";
|
||||
|
||||
let notificationURL = "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html";
|
||||
|
||||
add_task(function* test_notificationReplace() {
|
||||
let pm = Services.perms;
|
||||
pm.add(makeURI(notificationURL), "desktop-notification", pm.ALLOW_ACTION);
|
||||
|
||||
yield BrowserTestUtils.withNewTab({
|
||||
gBrowser,
|
||||
url: notificationURL
|
||||
}, function* dummyTabTask(aBrowser) {
|
||||
yield ContentTask.spawn(aBrowser, {}, function* () {
|
||||
let win = content.window.wrappedJSObject;
|
||||
let notification = win.showNotification1();
|
||||
let promiseCloseEvent = ContentTaskUtils.waitForEvent(notification, "close");
|
||||
|
||||
let showEvent = yield ContentTaskUtils.waitForEvent(notification, "show");
|
||||
is(showEvent.target.body, "Test body 1", "Showed tagged notification");
|
||||
|
||||
let newNotification = win.showNotification2();
|
||||
let newShowEvent = yield ContentTaskUtils.waitForEvent(newNotification, "show");
|
||||
is(newShowEvent.target.body, "Test body 2", "Showed new notification with same tag");
|
||||
|
||||
let closeEvent = yield promiseCloseEvent;
|
||||
is(closeEvent.target.body, "Test body 1", "Closed previous tagged notification");
|
||||
|
||||
let promiseNewCloseEvent = ContentTaskUtils.waitForEvent(newNotification, "close");
|
||||
newNotification.close();
|
||||
let newCloseEvent = yield promiseNewCloseEvent;
|
||||
is(newCloseEvent.target.body, "Test body 2", "Closed new notification");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function* cleanup() {
|
||||
Services.perms.remove(makeURI(notificationURL), "desktop-notification");
|
||||
});
|
|
@ -8,7 +8,7 @@ function showNotification1() {
|
|||
var options = {
|
||||
dir: undefined,
|
||||
lang: undefined,
|
||||
body: "Test body",
|
||||
body: "Test body 1",
|
||||
tag: "Test tag",
|
||||
icon: undefined,
|
||||
};
|
||||
|
@ -23,7 +23,7 @@ function showNotification2() {
|
|||
var options = {
|
||||
dir: undefined,
|
||||
lang: undefined,
|
||||
body: "Test body",
|
||||
body: "Test body 2",
|
||||
tag: "Test tag",
|
||||
icon: undefined,
|
||||
};
|
||||
|
|
|
@ -60,6 +60,9 @@ function test() {
|
|||
testVal("<user:pass@sub1.sub2.sub3.>mozilla.org");
|
||||
testVal("<user:pass@>mozilla.org");
|
||||
|
||||
testVal("<https://>mozilla.org< >");
|
||||
testVal("mozilla.org< >");
|
||||
|
||||
testVal("<https://>mozilla.org</file.ext>");
|
||||
testVal("<https://>mozilla.org</sub/file.ext>");
|
||||
testVal("<https://>mozilla.org</sub/file.ext?foo>");
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
[browser_moz_action_link.js]
|
||||
[browser_urlbar_blanking.js]
|
||||
support-files =
|
||||
file_blank_but_not_blank.html
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
"use strict";
|
||||
|
||||
const kURIs = [
|
||||
"moz-action:foo,",
|
||||
"moz-action:foo",
|
||||
];
|
||||
|
||||
add_task(function*() {
|
||||
for (let uri of kURIs) {
|
||||
let dataURI = `data:text/html,<a id=a href="${uri}" target=_blank>Link</a>`;
|
||||
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, dataURI);
|
||||
|
||||
let tabSwitchPromise = BrowserTestUtils.switchTab(gBrowser, function() {});
|
||||
yield ContentTask.spawn(tab.linkedBrowser, null, function*() {
|
||||
content.document.getElementById("a").click();
|
||||
});
|
||||
yield tabSwitchPromise;
|
||||
isnot(gBrowser.selectedTab, tab, "Switched to new tab!");
|
||||
is(gURLBar.value, "about:blank", "URL bar should be displaying about:blank");
|
||||
let newTab = gBrowser.selectedTab;
|
||||
yield BrowserTestUtils.switchTab(gBrowser, tab);
|
||||
yield BrowserTestUtils.switchTab(gBrowser, newTab);
|
||||
is(gBrowser.selectedTab, newTab, "Switched to new tab again!");
|
||||
is(gURLBar.value, "about:blank", "URL bar should be displaying about:blank after tab switch");
|
||||
// Finally, check that directly setting it produces the right results, too:
|
||||
URLBarSetURI(makeURI(uri));
|
||||
is(gURLBar.value, "about:blank", "URL bar should still be displaying about:blank");
|
||||
yield BrowserTestUtils.removeTab(newTab);
|
||||
yield BrowserTestUtils.removeTab(tab);
|
||||
}
|
||||
});
|
|
@ -261,7 +261,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|||
trimmedLength = "http://".length;
|
||||
}
|
||||
|
||||
let matchedURL = value.match(/^((?:[a-z]+:\/\/)(?:[^\/#?]+@)?)(.+?)(?::\d+)?(?:[\/#?]|$)/);
|
||||
let matchedURL = value.match(/^((?:[a-z]+:\/\/)(?:[^\/#?]+@)?)(\S+?)(?::\d+)?\s*(?:[\/#?]|$)/);
|
||||
if (!matchedURL)
|
||||
return;
|
||||
|
||||
|
@ -839,12 +839,13 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|||
<method name="_parseActionUrl">
|
||||
<parameter name="aUrl"/>
|
||||
<body><![CDATA[
|
||||
if (!aUrl.startsWith("moz-action:"))
|
||||
const MOZ_ACTION_REGEX = /^moz-action:([^,]+),(.*)$/;
|
||||
if (!MOZ_ACTION_REGEX.test(aUrl))
|
||||
return null;
|
||||
|
||||
// URL is in the format moz-action:ACTION,PARAMS
|
||||
// Where PARAMS is a JSON encoded object.
|
||||
let [, type, params] = aUrl.match(/^moz-action:([^,]+),(.*)$/);
|
||||
let [, type, params] = aUrl.match(MOZ_ACTION_REGEX);
|
||||
|
||||
let action = {
|
||||
type: type,
|
||||
|
|
|
@ -234,8 +234,21 @@ if (typeof Mozilla == 'undefined') {
|
|||
});
|
||||
};
|
||||
|
||||
Mozilla.UITour.showFirefoxAccounts = function() {
|
||||
_sendEvent('showFirefoxAccounts');
|
||||
/**
|
||||
* Request the browser open the Firefox Accounts page.
|
||||
*
|
||||
* @param {Object} extraURLCampaignParams - An object containing additional
|
||||
* paramaters for the URL opened by the browser for reasons of promotional
|
||||
* campaign tracking. Each attribute of the object must have a name that
|
||||
* begins with "utm_" and a value that is a string. Both the name and value
|
||||
* must contain only alphanumeric characters, dashes or underscores (meaning
|
||||
* that you are limited to values that don't need encoding, as any such
|
||||
* characters in the name or value will be rejected.)
|
||||
*/
|
||||
Mozilla.UITour.showFirefoxAccounts = function(extraURLCampaignParams) {
|
||||
_sendEvent('showFirefoxAccounts', {
|
||||
extraURLCampaignParams: JSON.stringify(extraURLCampaignParams),
|
||||
});
|
||||
};
|
||||
|
||||
Mozilla.UITour.resetFirefox = function() {
|
||||
|
|
|
@ -610,8 +610,15 @@ this.UITour = {
|
|||
case "showFirefoxAccounts": {
|
||||
// 'signup' is the only action that makes sense currently, so we don't
|
||||
// accept arbitrary actions just to be safe...
|
||||
let p = new URLSearchParams("action=signup&entrypoint=uitour");
|
||||
// Call our helper to validate extraURLCampaignParams and populate URLSearchParams
|
||||
if (!this._populateCampaignParams(p, data.extraURLCampaignParams)) {
|
||||
log.warn("showFirefoxAccounts: invalid campaign args specified");
|
||||
return false;
|
||||
}
|
||||
|
||||
// We want to replace the current tab.
|
||||
browser.loadURI("about:accounts?action=signup&entrypoint=uitour");
|
||||
browser.loadURI("about:accounts?" + p.toString());
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -805,6 +812,52 @@ this.UITour = {
|
|||
}
|
||||
},
|
||||
|
||||
// Given a string that is a JSONified represenation of an object with
|
||||
// additional utm_* URL params that should be appended, validate and append
|
||||
// them to the passed URLSearchParams object. Returns true if the params
|
||||
// were validated and appended, and false if the request should be ignored.
|
||||
_populateCampaignParams: function(urlSearchParams, extraURLCampaignParams) {
|
||||
// We are extra paranoid about what params we allow to be appended.
|
||||
if (typeof extraURLCampaignParams == "undefined") {
|
||||
// no params, so it's all good.
|
||||
return true;
|
||||
}
|
||||
if (typeof extraURLCampaignParams != "string") {
|
||||
log.warn("_populateCampaignParams: extraURLCampaignParams is not a string");
|
||||
return false;
|
||||
}
|
||||
let campaignParams;
|
||||
try {
|
||||
if (extraURLCampaignParams) {
|
||||
campaignParams = JSON.parse(extraURLCampaignParams);
|
||||
if (typeof campaignParams != "object") {
|
||||
log.warn("_populateCampaignParams: extraURLCampaignParams is not a stringified object");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (ex) {
|
||||
log.warn("_populateCampaignParams: extraURLCampaignParams is not a JSON object");
|
||||
return false;
|
||||
}
|
||||
if (campaignParams) {
|
||||
// The regex that both the name and value of each param must match.
|
||||
let reSimpleString = /^[-_a-zA-Z0-9]*$/;
|
||||
for (let name in campaignParams) {
|
||||
let value = campaignParams[name];
|
||||
if (typeof name != "string" || typeof value != "string" ||
|
||||
!name.startsWith("utm_") ||
|
||||
value.length == 0 ||
|
||||
!reSimpleString.test(name) ||
|
||||
!reSimpleString.test(value)) {
|
||||
log.warn("_populateCampaignParams: invalid campaign param specified");
|
||||
return false;
|
||||
}
|
||||
urlSearchParams.append(name, value);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
setTelemetryBucket: function(aPageID) {
|
||||
let bucket = BUCKET_NAME + BrowserUITelemetry.BUCKET_SEPARATOR + aPageID;
|
||||
BrowserUITelemetry.setBucket(bucket);
|
||||
|
|
|
@ -22,8 +22,50 @@ add_UITour_task(function* test_checkSyncSetup_enabled() {
|
|||
});
|
||||
|
||||
// The showFirefoxAccounts API is sync related, so we test that here too...
|
||||
add_UITour_task(function* test_firefoxAccounts() {
|
||||
add_UITour_task(function* test_firefoxAccountsNoParams() {
|
||||
yield gContentAPI.showFirefoxAccounts();
|
||||
yield BrowserTestUtils.browserLoaded(gTestTab.linkedBrowser, false,
|
||||
"about:accounts?action=signup&entrypoint=uitour");
|
||||
});
|
||||
|
||||
add_UITour_task(function* test_firefoxAccountsValidParams() {
|
||||
yield gContentAPI.showFirefoxAccounts({ utm_foo: "foo", utm_bar: "bar" });
|
||||
yield BrowserTestUtils.browserLoaded(gTestTab.linkedBrowser, false,
|
||||
"about:accounts?action=signup&entrypoint=uitour&utm_foo=foo&utm_bar=bar");
|
||||
});
|
||||
|
||||
// A helper to check the request was ignored due to invalid params.
|
||||
function* checkAboutAccountsNotLoaded() {
|
||||
try {
|
||||
yield waitForConditionPromise(() => {
|
||||
return gBrowser.selectedBrowser.currentURI.spec.startsWith("about:accounts");
|
||||
}, "Check if about:accounts opened");
|
||||
ok(false, "No about:accounts tab should have opened");
|
||||
} catch (ex) {
|
||||
ok(true, "No about:accounts tab opened");
|
||||
}
|
||||
}
|
||||
|
||||
add_UITour_task(function* test_firefoxAccountsNonObject() {
|
||||
// non-string should be rejected.
|
||||
yield gContentAPI.showFirefoxAccounts(99);
|
||||
yield checkAboutAccountsNotLoaded();
|
||||
});
|
||||
|
||||
add_UITour_task(function* test_firefoxAccountsNonUtmPrefix() {
|
||||
// Any non "utm_" name should should be rejected.
|
||||
yield gContentAPI.showFirefoxAccounts({ utm_foo: "foo", bar: "bar" });
|
||||
yield checkAboutAccountsNotLoaded();
|
||||
});
|
||||
|
||||
add_UITour_task(function* test_firefoxAccountsNonAlphaName() {
|
||||
// Any "utm_" name which includes non-alpha chars should be rejected.
|
||||
yield gContentAPI.showFirefoxAccounts({ utm_foo: "foo", "utm_bar=": "bar" });
|
||||
yield checkAboutAccountsNotLoaded();
|
||||
});
|
||||
|
||||
add_UITour_task(function* test_firefoxAccountsNonAlphaValue() {
|
||||
// Any non-alpha value should be rejected.
|
||||
yield gContentAPI.showFirefoxAccounts({ utm_foo: "foo&" });
|
||||
yield checkAboutAccountsNotLoaded();
|
||||
});
|
||||
|
|
|
@ -43,13 +43,6 @@ button {
|
|||
max-width: 800px;
|
||||
}
|
||||
|
||||
/* Prefs */
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
/* Targets */
|
||||
|
||||
.targets {
|
||||
|
@ -88,3 +81,8 @@ label {
|
|||
.addons-options {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.addons-debugging-label {
|
||||
display: inline-block;
|
||||
margin: 0 5px 5px 0;
|
||||
}
|
|
@ -17,6 +17,9 @@ loader.lazyImporter(this, "AddonManager",
|
|||
const Strings = Services.strings.createBundle(
|
||||
"chrome://devtools/locale/aboutdebugging.properties");
|
||||
|
||||
const MORE_INFO_URL = "https://developer.mozilla.org/docs/Tools" +
|
||||
"/about:debugging#Enabling_add-on_debugging";
|
||||
|
||||
exports.AddonsControls = React.createClass({
|
||||
displayName: "AddonsControls",
|
||||
|
||||
|
@ -33,9 +36,14 @@ exports.AddonsControls = React.createClass({
|
|||
onChange: this.onEnableAddonDebuggingChange,
|
||||
}),
|
||||
React.createElement("label", {
|
||||
className: "addons-debugging-label",
|
||||
htmlFor: "enable-addon-debugging",
|
||||
title: Strings.GetStringFromName("addonDebugging.tooltip")
|
||||
}, Strings.GetStringFromName("addonDebugging.label"))
|
||||
}, Strings.GetStringFromName("addonDebugging.label")),
|
||||
"(",
|
||||
React.createElement("a", { href: MORE_INFO_URL, target: "_blank" },
|
||||
Strings.GetStringFromName("addonDebugging.moreInfo")),
|
||||
")"
|
||||
),
|
||||
React.createElement("button", {
|
||||
id: "load-addon-from-file",
|
||||
|
@ -47,6 +55,7 @@ exports.AddonsControls = React.createClass({
|
|||
onEnableAddonDebuggingChange(event) {
|
||||
let enabled = event.target.checked;
|
||||
Services.prefs.setBoolPref("devtools.chrome.enabled", enabled);
|
||||
Services.prefs.setBoolPref("devtools.debugger.remote-enabled", enabled);
|
||||
},
|
||||
|
||||
loadAddonFromFile(event) {
|
||||
|
|
|
@ -23,6 +23,9 @@ const ExtensionIcon = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
|
|||
const Strings = Services.strings.createBundle(
|
||||
"chrome://devtools/locale/aboutdebugging.properties");
|
||||
|
||||
const CHROME_ENABLED_PREF = "devtools.chrome.enabled";
|
||||
const REMOTE_ENABLED_PREF = "devtools.debugger.remote-enabled";
|
||||
|
||||
exports.AddonsTab = React.createClass({
|
||||
displayName: "AddonsTab",
|
||||
|
||||
|
@ -35,15 +38,21 @@ exports.AddonsTab = React.createClass({
|
|||
|
||||
componentDidMount() {
|
||||
AddonManager.addAddonListener(this);
|
||||
Services.prefs.addObserver("devtools.chrome.enabled",
|
||||
|
||||
Services.prefs.addObserver(CHROME_ENABLED_PREF,
|
||||
this.updateDebugStatus, false);
|
||||
Services.prefs.addObserver(REMOTE_ENABLED_PREF,
|
||||
this.updateDebugStatus, false);
|
||||
|
||||
this.updateDebugStatus();
|
||||
this.updateAddonsList();
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
AddonManager.removeAddonListener(this);
|
||||
Services.prefs.removeObserver("devtools.chrome.enabled",
|
||||
Services.prefs.removeObserver(CHROME_ENABLED_PREF,
|
||||
this.updateDebugStatus);
|
||||
Services.prefs.removeObserver(REMOTE_ENABLED_PREF,
|
||||
this.updateDebugStatus);
|
||||
},
|
||||
|
||||
|
@ -68,9 +77,11 @@ exports.AddonsTab = React.createClass({
|
|||
},
|
||||
|
||||
updateDebugStatus() {
|
||||
this.setState({
|
||||
debugDisabled: !Services.prefs.getBoolPref("devtools.chrome.enabled")
|
||||
});
|
||||
let debugDisabled =
|
||||
!Services.prefs.getBoolPref(CHROME_ENABLED_PREF) ||
|
||||
!Services.prefs.getBoolPref(REMOTE_ENABLED_PREF);
|
||||
|
||||
this.setState({ debugDisabled });
|
||||
},
|
||||
|
||||
updateAddonsList() {
|
||||
|
|
|
@ -8,6 +8,7 @@ support-files =
|
|||
service-workers/empty-sw.html
|
||||
service-workers/empty-sw.js
|
||||
|
||||
[browser_addons_debugging_initial_state.js]
|
||||
[browser_addons_install.js]
|
||||
[browser_addons_toggle_debug.js]
|
||||
[browser_service_workers.js]
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
// Test that addons debugging controls are properly enabled/disabled depending
|
||||
// on the values of the relevant preferences:
|
||||
// - devtools.chrome.enabled
|
||||
// - devtools.debugger.remote-enabled
|
||||
|
||||
const ADDON_ID = "test-devtools@mozilla.org";
|
||||
|
||||
const TEST_DATA = [
|
||||
{
|
||||
chromeEnabled: false,
|
||||
debuggerRemoteEnable: false,
|
||||
expected: false,
|
||||
}, {
|
||||
chromeEnabled: false,
|
||||
debuggerRemoteEnable: true,
|
||||
expected: false,
|
||||
}, {
|
||||
chromeEnabled: true,
|
||||
debuggerRemoteEnable: false,
|
||||
expected: false,
|
||||
}, {
|
||||
chromeEnabled: true,
|
||||
debuggerRemoteEnable: true,
|
||||
expected: true,
|
||||
}
|
||||
];
|
||||
|
||||
add_task(function* () {
|
||||
for (let testData of TEST_DATA) {
|
||||
yield testCheckboxState(testData);
|
||||
}
|
||||
});
|
||||
|
||||
function* testCheckboxState(testData) {
|
||||
info("Set preferences as defined by the current test data.");
|
||||
yield new Promise(resolve => {
|
||||
let options = {"set": [
|
||||
["devtools.chrome.enabled", testData.chromeEnabled],
|
||||
["devtools.debugger.remote-enabled", testData.debuggerRemoteEnable],
|
||||
]};
|
||||
SpecialPowers.pushPrefEnv(options, resolve);
|
||||
});
|
||||
|
||||
let { tab, document } = yield openAboutDebugging("addons");
|
||||
|
||||
info("Install a test addon.");
|
||||
yield installAddon(document, "addons/unpacked/install.rdf", "test-devtools");
|
||||
|
||||
info("Test checkbox checked state.");
|
||||
let addonDebugCheckbox = document.querySelector("#enable-addon-debugging");
|
||||
is(addonDebugCheckbox.checked, testData.expected,
|
||||
"Addons debugging checkbox should be in expected state.");
|
||||
|
||||
info("Test debug buttons disabled state.");
|
||||
let debugButtons = [...document.querySelectorAll("#addons .debug-button")];
|
||||
ok(debugButtons.every(b => b.disabled != testData.expected),
|
||||
"Debug buttons should be in the expected state");
|
||||
|
||||
info("Uninstall test addon installed earlier.");
|
||||
yield uninstallAddon(ADDON_ID);
|
||||
|
||||
yield closeAboutDebugging(tab);
|
||||
}
|
|
@ -13,6 +13,7 @@ add_task(function* () {
|
|||
yield new Promise(resolve => {
|
||||
let options = {"set": [
|
||||
["devtools.chrome.enabled", false],
|
||||
["devtools.debugger.remote-enabled", false],
|
||||
]};
|
||||
SpecialPowers.pushPrefEnv(options, resolve);
|
||||
});
|
||||
|
|
|
@ -9,6 +9,11 @@ Services.scriptloader.loadSubScript(
|
|||
"chrome://mochitests/content/browser/devtools/client/inspector/test/head.js",
|
||||
this);
|
||||
|
||||
Services.prefs.setBoolPref("devtools.fontinspector.enabled", true);
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref("devtools.fontinspector.enabled");
|
||||
});
|
||||
|
||||
/**
|
||||
* Adds a new tab with the given URL, opens the inspector and selects the
|
||||
* font-inspector tab.
|
||||
|
|
|
@ -216,6 +216,7 @@ devtools.jar:
|
|||
skin/styleeditor.css (themes/styleeditor.css)
|
||||
skin/webaudioeditor.css (themes/webaudioeditor.css)
|
||||
skin/components-frame.css (themes/components-frame.css)
|
||||
skin/components-h-split-box.css (themes/components-h-split-box.css)
|
||||
skin/jit-optimizations.css (themes/jit-optimizations.css)
|
||||
skin/images/magnifying-glass.png (themes/images/magnifying-glass.png)
|
||||
skin/images/magnifying-glass@2x.png (themes/images/magnifying-glass@2x.png)
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
define(function(require, exports, module) {
|
||||
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("./reps/rep-utils");
|
||||
const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
|
||||
const { Headers } = createFactories(require("./headers"));
|
||||
const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
define(function(require, exports, module) {
|
||||
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("./reps/rep-utils");
|
||||
const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
|
||||
const { TreeView } = createFactories(require("./reps/tree-view"));
|
||||
const { SearchBox } = createFactories(require("./search-box"));
|
||||
const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
define(function(require, exports, module) {
|
||||
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("./reps/rep-utils");
|
||||
const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
|
||||
const { JsonPanel } = createFactories(require("./json-panel"));
|
||||
const { TextPanel } = createFactories(require("./text-panel"));
|
||||
const { HeadersPanel } = createFactories(require("./headers-panel"));
|
||||
|
|
|
@ -1,189 +0,0 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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";
|
||||
|
||||
define(function(require, exports, module) {
|
||||
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("./rep-utils");
|
||||
const { Rep } = createFactories(require("./rep"));
|
||||
const { ObjectBox } = createFactories(require("./object-box"));
|
||||
const { Caption } = createFactories(require("./caption"));
|
||||
|
||||
// Shortcuts
|
||||
const DOM = React.DOM;
|
||||
|
||||
/**
|
||||
* Renders an array. The array is enclosed by left and right bracket
|
||||
* and the max number of rendered items depends on the current mode.
|
||||
*/
|
||||
var ArrayRep = React.createClass({
|
||||
displayName: "ArrayRep",
|
||||
|
||||
render: function() {
|
||||
var mode = this.props.mode || "short";
|
||||
var object = this.props.object;
|
||||
var hasTwisty = this.hasSpecialProperties(object);
|
||||
|
||||
var items;
|
||||
|
||||
if (mode == "tiny") {
|
||||
items = DOM.span({className: "length"}, object.length);
|
||||
} else {
|
||||
var max = (mode == "short") ? 3 : 300;
|
||||
items = this.arrayIterator(object, max);
|
||||
}
|
||||
|
||||
return (
|
||||
ObjectBox({className: "array", onClick: this.onToggleProperties},
|
||||
DOM.a({className: "objectLink", onclick: this.onClickBracket},
|
||||
DOM.span({className: "arrayLeftBracket", role: "presentation"}, "[")
|
||||
),
|
||||
items,
|
||||
DOM.a({className: "objectLink", onclick: this.onClickBracket},
|
||||
DOM.span({className: "arrayRightBracket", role: "presentation"}, "]")
|
||||
),
|
||||
DOM.span({className: "arrayProperties", role: "group"})
|
||||
)
|
||||
)
|
||||
},
|
||||
|
||||
getTitle: function(object, context) {
|
||||
return "[" + object.length + "]";
|
||||
},
|
||||
|
||||
arrayIterator: function(array, max) {
|
||||
var items = [];
|
||||
|
||||
for (var i=0; i<array.length && i<=max; i++) {
|
||||
try {
|
||||
var delim = (i == array.length-1 ? "" : ", ");
|
||||
var value = array[i];
|
||||
|
||||
if (value === array) {
|
||||
items.push(Reference({
|
||||
key: i,
|
||||
object: value,
|
||||
delim: delim
|
||||
}));
|
||||
} else {
|
||||
items.push(ItemRep({
|
||||
key: i,
|
||||
object: value,
|
||||
delim: delim
|
||||
}));
|
||||
}
|
||||
} catch (exc) {
|
||||
items.push(ItemRep({object: exc, delim: delim, key: i}));
|
||||
}
|
||||
}
|
||||
|
||||
if (array.length > max + 1) {
|
||||
items.pop();
|
||||
items.push(Caption({
|
||||
key: "more",
|
||||
object: Locale.$STR("jsonViewer.reps.more"),
|
||||
}));
|
||||
}
|
||||
|
||||
return items;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if the passed object is an array with additional (custom)
|
||||
* properties, otherwise returns false. Custom properties should be
|
||||
* displayed in extra expandable section.
|
||||
*
|
||||
* Example array with a custom property.
|
||||
* let arr = [0, 1];
|
||||
* arr.myProp = "Hello";
|
||||
*
|
||||
* @param {Array} array The array object.
|
||||
*/
|
||||
hasSpecialProperties: function(array) {
|
||||
function isInteger(x) {
|
||||
var y = parseInt(x, 10);
|
||||
if (isNaN(y)) {
|
||||
return false;
|
||||
}
|
||||
return x === y.toString();
|
||||
}
|
||||
|
||||
var n = 0;
|
||||
var props = Object.getOwnPropertyNames(array);
|
||||
for (var i=0; i<props.length; i++) {
|
||||
var p = props[i];
|
||||
|
||||
// Valid indexes are skipped
|
||||
if (isInteger(p)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore standard 'length' property, anything else is custom.
|
||||
if (p != "length") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
// Event Handlers
|
||||
|
||||
onToggleProperties: function(event) {
|
||||
},
|
||||
|
||||
onClickBracket: function(event) {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Renders array item. Individual values are separated by a comma.
|
||||
*/
|
||||
var ItemRep = React.createFactory(React.createClass({
|
||||
displayName: "ItemRep",
|
||||
|
||||
render: function(){
|
||||
var object = this.props.object;
|
||||
var delim = this.props.delim;
|
||||
return (
|
||||
DOM.span({},
|
||||
Rep({object: object}),
|
||||
delim
|
||||
)
|
||||
)
|
||||
}
|
||||
}));
|
||||
|
||||
/**
|
||||
* Renders cycle references in an array.
|
||||
*/
|
||||
var Reference = React.createFactory(React.createClass({
|
||||
displayName: "Reference",
|
||||
|
||||
render: function(){
|
||||
var tooltip = Locale.$STR("jsonView.reps.reference");
|
||||
return (
|
||||
span({title: tooltip},
|
||||
"[...]")
|
||||
)
|
||||
}
|
||||
}));
|
||||
|
||||
function supportsObject(object, type) {
|
||||
return Array.isArray(object) ||
|
||||
Object.prototype.toString.call(object) === "[object Arguments]";
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
exports.ArrayRep = {
|
||||
rep: ArrayRep,
|
||||
supportsObject: supportsObject
|
||||
};
|
||||
|
||||
});
|
|
@ -1,31 +0,0 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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";
|
||||
|
||||
define(function(require, exports, module) {
|
||||
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const DOM = React.DOM;
|
||||
|
||||
/**
|
||||
* Renders a caption. This template is used by other components
|
||||
* that needs to distinguish between a simple text/value and a label.
|
||||
*/
|
||||
const Caption = React.createClass({
|
||||
displayName: "Caption",
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
DOM.span({"className": "caption"}, this.props.object)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// Exports from this module
|
||||
exports.Caption = Caption;
|
||||
});
|
|
@ -5,18 +5,7 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
'array.js',
|
||||
'caption.js',
|
||||
'null.js',
|
||||
'number.js',
|
||||
'object-box.js',
|
||||
'object-link.js',
|
||||
'object.js',
|
||||
'rep-utils.js',
|
||||
'rep.js',
|
||||
'string.js',
|
||||
'tabs.js',
|
||||
'toolbar.js',
|
||||
'tree-view.js',
|
||||
'undefined.js',
|
||||
)
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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";
|
||||
|
||||
define(function(require, exports, module) {
|
||||
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("./rep-utils");
|
||||
const { ObjectBox } = createFactories(require("./object-box"));
|
||||
|
||||
/**
|
||||
* Renders null value
|
||||
*/
|
||||
const Null = React.createClass({
|
||||
displayName: "NullRep",
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
ObjectBox({className: "null"},
|
||||
"null"
|
||||
)
|
||||
)
|
||||
},
|
||||
});
|
||||
|
||||
function supportsObject(object, type) {
|
||||
if (object && object.type && object.type == "null") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (object == null);
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
|
||||
exports.Null = {
|
||||
rep: Null,
|
||||
supportsObject: supportsObject
|
||||
};
|
||||
|
||||
});
|
|
@ -1,47 +0,0 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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";
|
||||
|
||||
define(function(require, exports, module) {
|
||||
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("./rep-utils");
|
||||
const { ObjectBox } = createFactories(require("./object-box"));
|
||||
|
||||
/**
|
||||
* Renders a number
|
||||
*/
|
||||
const Number = React.createClass({
|
||||
displayName: "Number",
|
||||
|
||||
render: function() {
|
||||
var value = this.props.object;
|
||||
return (
|
||||
ObjectBox({className: "number"},
|
||||
this.stringify(value)
|
||||
)
|
||||
)
|
||||
},
|
||||
|
||||
stringify: function(object) {
|
||||
return (Object.is(object, -0) ? "-0" : String(object));
|
||||
},
|
||||
});
|
||||
|
||||
function supportsObject(object, type) {
|
||||
return type == "boolean" || type == "number";
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
|
||||
exports.Number = {
|
||||
rep: Number,
|
||||
supportsObject: supportsObject
|
||||
};
|
||||
|
||||
});
|
|
@ -1,35 +0,0 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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";
|
||||
|
||||
define(function(require, exports, module) {
|
||||
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const DOM = React.DOM;
|
||||
|
||||
/**
|
||||
* Renders a box for given object.
|
||||
*/
|
||||
const ObjectBox = React.createClass({
|
||||
displayName: "ObjectBox",
|
||||
|
||||
render: function() {
|
||||
var className = this.props.className;
|
||||
var boxClassName = className ? " objectBox-" + className : "";
|
||||
|
||||
return (
|
||||
DOM.span({className: "objectBox" + boxClassName, role: "presentation"},
|
||||
this.props.children
|
||||
)
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
// Exports from this module
|
||||
exports.ObjectBox = ObjectBox;
|
||||
});
|
|
@ -1,36 +0,0 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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";
|
||||
|
||||
define(function(require, exports, module) {
|
||||
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const DOM = React.DOM;
|
||||
|
||||
/**
|
||||
* Renders a link for given object.
|
||||
*/
|
||||
const ObjectLink = React.createClass({
|
||||
displayName: "ObjectLink",
|
||||
|
||||
render: function() {
|
||||
var className = this.props.className;
|
||||
var objectClassName = className ? " objectLink-" + className : "";
|
||||
var linkClassName = "objectLink" + objectClassName + " a11yFocus";
|
||||
|
||||
return (
|
||||
DOM.a({className: linkClassName, _repObject: this.props.object},
|
||||
this.props.children
|
||||
)
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
// Exports from this module
|
||||
exports.ObjectLink = ObjectLink;
|
||||
});
|
|
@ -1,178 +0,0 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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";
|
||||
|
||||
define(function(require, exports, module) {
|
||||
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("./rep-utils");
|
||||
const { ObjectBox } = createFactories(require("./object-box"));
|
||||
const { Caption } = createFactories(require("./caption"));
|
||||
|
||||
// Shortcuts
|
||||
const DOM = React.DOM;
|
||||
|
||||
/**
|
||||
* Renders an object. An object is represented by a list of its
|
||||
* properties enclosed in curly brackets.
|
||||
*/
|
||||
const Obj = React.createClass({
|
||||
displayName: "Obj",
|
||||
|
||||
render: function() {
|
||||
var object = this.props.object;
|
||||
var props = this.shortPropIterator(object);
|
||||
|
||||
return (
|
||||
ObjectBox({className: "object"},
|
||||
DOM.span({className: "objectTitle"}, this.getTitle(object)),
|
||||
DOM.span({className: "objectLeftBrace", role: "presentation"}, "{"),
|
||||
props,
|
||||
DOM.span({className: "objectRightBrace"}, "}")
|
||||
)
|
||||
)
|
||||
},
|
||||
|
||||
getTitle: function() {
|
||||
return ""; // Could also be "Object";
|
||||
},
|
||||
|
||||
longPropIterator: function (object) {
|
||||
try {
|
||||
return this.propIterator(object, 100);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
},
|
||||
|
||||
shortPropIterator: function (object) {
|
||||
try {
|
||||
return this.propIterator(object, /*could be a pref*/ 3);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
},
|
||||
|
||||
propIterator: function(object, max) {
|
||||
function isInterestingProp(t, value) {
|
||||
return (t == "boolean" || t == "number" || (t == "string" && value) ||
|
||||
(t == "object" && value && value.toString));
|
||||
}
|
||||
|
||||
// Work around https://bugzilla.mozilla.org/show_bug.cgi?id=945377
|
||||
if (Object.prototype.toString.call(object) === "[object Generator]") {
|
||||
object = Object.getPrototypeOf(object);
|
||||
}
|
||||
|
||||
// Object members with non-empty values are preferred since it gives the
|
||||
// user a better overview of the object.
|
||||
var props = [];
|
||||
this.getProps(props, object, max, isInterestingProp);
|
||||
|
||||
if (props.length <= max) {
|
||||
// There are not enough props yet (or at least, not enough props to
|
||||
// be able to know whether we should print "more..." or not).
|
||||
// Let's display also empty members and functions.
|
||||
this.getProps(props, object, max, function(t, value) {
|
||||
return !isInterestingProp(t, value);
|
||||
});
|
||||
}
|
||||
|
||||
if (props.length > max) {
|
||||
props.pop();
|
||||
props.push(Caption({
|
||||
key: "more",
|
||||
object: Locale.$STR("jsonViewer.reps.more"),
|
||||
}));
|
||||
}
|
||||
else if (props.length > 0) {
|
||||
// Remove the last comma.
|
||||
props[props.length-1] = React.cloneElement(
|
||||
props[props.length-1], { delim: "" });
|
||||
}
|
||||
|
||||
return props;
|
||||
},
|
||||
|
||||
getProps: function (props, object, max, filter) {
|
||||
max = max || 3;
|
||||
if (!object) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var len = 0;
|
||||
var mode = this.props.mode;
|
||||
|
||||
try {
|
||||
for (var name in object) {
|
||||
if (props.length > max) {
|
||||
return;
|
||||
}
|
||||
|
||||
var value;
|
||||
try {
|
||||
value = object[name];
|
||||
}
|
||||
catch (exc) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var t = typeof(value);
|
||||
if (filter(t, value)) {
|
||||
props.push(PropRep({
|
||||
key: name,
|
||||
mode: "short",
|
||||
name: name,
|
||||
object: value,
|
||||
equal: ": ",
|
||||
delim: ", ",
|
||||
mode: mode,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (exc) {
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Renders object property, name-value pair.
|
||||
*/
|
||||
var PropRep = React.createFactory(React.createClass({
|
||||
displayName: "PropRep",
|
||||
|
||||
render: function(){
|
||||
var { Rep } = createFactories(require("./rep"));
|
||||
var object = this.props.object;
|
||||
var mode = this.props.mode;
|
||||
return (
|
||||
DOM.span({},
|
||||
DOM.span({"className": "nodeName"}, this.props.name),
|
||||
DOM.span({"className": "objectEqual", role: "presentation"}, this.props.equal),
|
||||
Rep({object: object, mode: mode}),
|
||||
DOM.span({"className": "objectComma", role: "presentation"}, this.props.delim)
|
||||
)
|
||||
);
|
||||
}
|
||||
}));
|
||||
|
||||
function supportsObject(object, type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
|
||||
exports.Obj = {
|
||||
rep: Obj,
|
||||
supportsObject: supportsObject
|
||||
};
|
||||
|
||||
});
|
|
@ -1,29 +0,0 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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";
|
||||
|
||||
define(function(require, exports, module) {
|
||||
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
|
||||
/**
|
||||
* Create React factories for given arguments.
|
||||
* Example:
|
||||
* const { Rep } = createFactories(require("./rep"));
|
||||
*/
|
||||
function createFactories(args) {
|
||||
var result = {};
|
||||
for (var p in args) {
|
||||
result[p] = React.createFactory(args[p]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
exports.createFactories = createFactories;
|
||||
});
|
|
@ -1,87 +0,0 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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";
|
||||
|
||||
define(function(require, exports, module) {
|
||||
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
|
||||
// Load all existing rep templates
|
||||
const { Undefined } = require("./undefined");
|
||||
const { Null } = require("./null");
|
||||
const { StringRep } = require("./string");
|
||||
const { Number } = require("./number");
|
||||
const { ArrayRep } = require("./array");
|
||||
const { Obj } = require("./object");
|
||||
|
||||
// List of all registered template.
|
||||
// XXX there should be a way for extensions to register a new
|
||||
// or modify an existing rep.
|
||||
var reps = [Undefined, Null, StringRep, Number, ArrayRep, Obj];
|
||||
var defaultRep;
|
||||
|
||||
/**
|
||||
* Generic rep that is using for rendering native JS types or an object.
|
||||
* The right template used for rendering is picked automatically according
|
||||
* to the current value type. The value must be passed is as 'object'
|
||||
* property.
|
||||
*/
|
||||
const Rep = React.createClass({
|
||||
displayName: "Rep",
|
||||
|
||||
render: function() {
|
||||
var rep = getRep(this.props.object);
|
||||
return rep(this.props);
|
||||
},
|
||||
});
|
||||
|
||||
// Helpers
|
||||
|
||||
/**
|
||||
* Return a rep object that is responsible for rendering given
|
||||
* object.
|
||||
*
|
||||
* @param object {Object} Object to be rendered in the UI. This
|
||||
* can be generic JS object as well as a grip (handle to a remote
|
||||
* debuggee object).
|
||||
*/
|
||||
function getRep(object) {
|
||||
var type = typeof(object);
|
||||
if (type == "object" && object instanceof String) {
|
||||
type = "string";
|
||||
}
|
||||
|
||||
if (isGrip(object)) {
|
||||
type = object.class;
|
||||
}
|
||||
|
||||
for (var i=0; i<reps.length; i++) {
|
||||
var rep = reps[i];
|
||||
try {
|
||||
// supportsObject could return weight (not only true/false
|
||||
// but a number), which would allow to priorities templates and
|
||||
// support better extensibility.
|
||||
if (rep.supportsObject(object, type)) {
|
||||
return React.createFactory(rep.rep);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error("reps.getRep; EXCEPTION ", err, err);
|
||||
}
|
||||
}
|
||||
|
||||
return React.createFactory(defaultRep.rep);
|
||||
}
|
||||
|
||||
function isGrip(object) {
|
||||
return object && object.actor;
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
exports.Rep = Rep;
|
||||
});
|
|
@ -1,102 +0,0 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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";
|
||||
|
||||
define(function(require, exports, module) {
|
||||
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("./rep-utils");
|
||||
const { ObjectBox } = createFactories(require("./object-box"));
|
||||
|
||||
/**
|
||||
* Renders a string. String value is enclosed within quotes.
|
||||
*/
|
||||
const StringRep = React.createClass({
|
||||
displayName: "StringRep",
|
||||
|
||||
render: function() {
|
||||
var text = this.props.object;
|
||||
var member = this.props.member;
|
||||
if (member && member.open) {
|
||||
return (
|
||||
ObjectBox({className: "string"},
|
||||
"\"" + text + "\""
|
||||
)
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
ObjectBox({className: "string"},
|
||||
"\"" + cropMultipleLines(text) + "\""
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Helpers
|
||||
|
||||
function escapeNewLines(value) {
|
||||
return value.replace(/\r/gm, "\\r").replace(/\n/gm, "\\n");
|
||||
};
|
||||
|
||||
function cropMultipleLines(text, limit) {
|
||||
return escapeNewLines(cropString(text, limit));
|
||||
};
|
||||
|
||||
function cropString(text, limit, alternativeText) {
|
||||
if (!alternativeText) {
|
||||
alternativeText = "...";
|
||||
}
|
||||
|
||||
// Make sure it's a string.
|
||||
text = text + "";
|
||||
|
||||
// Use default limit if necessary.
|
||||
if (!limit) {
|
||||
limit = 50;
|
||||
}
|
||||
|
||||
// Crop the string only if a limit is actually specified.
|
||||
if (limit <= 0) {
|
||||
return text;
|
||||
}
|
||||
|
||||
// Set the limit at least to the length of the alternative text
|
||||
// plus one character of the original text.
|
||||
if (limit <= alternativeText.length) {
|
||||
limit = alternativeText.length + 1;
|
||||
}
|
||||
|
||||
var halfLimit = (limit - alternativeText.length) / 2;
|
||||
|
||||
if (text.length > limit) {
|
||||
return text.substr(0, Math.ceil(halfLimit)) + alternativeText +
|
||||
text.substr(text.length - Math.floor(halfLimit));
|
||||
}
|
||||
|
||||
return text;
|
||||
};
|
||||
|
||||
function isCropped(value) {
|
||||
var cropLength = 50;
|
||||
return typeof(value) == "string" && value.length > cropLength;
|
||||
}
|
||||
|
||||
function supportsObject(object, type) {
|
||||
return (type == "string");
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
|
||||
exports.StringRep = {
|
||||
rep: StringRep,
|
||||
supportsObject: supportsObject,
|
||||
isCropped: isCropped
|
||||
};
|
||||
|
||||
});
|
|
@ -8,9 +8,9 @@ define(function(require, exports, module) {
|
|||
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("./rep-utils");
|
||||
const { Rep } = createFactories(require("./rep"));
|
||||
const { StringRep } = require("./string");
|
||||
const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
|
||||
const { Rep } = createFactories(require("devtools/client/shared/components/reps/rep"));
|
||||
const { StringRep } = require("devtools/client/shared/components/reps/string");
|
||||
const DOM = React.DOM;
|
||||
|
||||
var uid = 0;
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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";
|
||||
|
||||
define(function(require, exports, module) {
|
||||
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("./rep-utils");
|
||||
const { ObjectBox } = createFactories(require("./object-box"));
|
||||
|
||||
/**
|
||||
* Renders undefined value
|
||||
*/
|
||||
const Undefined = React.createClass({
|
||||
displayName: "UndefinedRep",
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
ObjectBox({className: "undefined"},
|
||||
"undefined"
|
||||
)
|
||||
)
|
||||
},
|
||||
});
|
||||
|
||||
function supportsObject(object, type) {
|
||||
if (object && object.type && object.type == "undefined") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (type == "undefined");
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
|
||||
exports.Undefined = {
|
||||
rep: Undefined,
|
||||
supportsObject: supportsObject
|
||||
};
|
||||
|
||||
});
|
|
@ -7,7 +7,7 @@
|
|||
define(function(require, exports, module) {
|
||||
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("./reps/rep-utils");
|
||||
const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
|
||||
const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
|
||||
const DOM = React.DOM;
|
||||
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
* 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 "resource://devtools/client/shared/components/reps/reps.css";
|
||||
|
||||
@import "general.css";
|
||||
@import "reps.css";
|
||||
@import "dom-tree.css";
|
||||
@import "search-box.css";
|
||||
@import "tabs.css";
|
||||
|
|
|
@ -14,7 +14,6 @@ DevToolsModules(
|
|||
'json-panel.css',
|
||||
'main.css',
|
||||
'read-only-prop.svg',
|
||||
'reps.css',
|
||||
'search-box.css',
|
||||
'search.svg',
|
||||
'tabs.css',
|
||||
|
|
|
@ -8,7 +8,7 @@ define(function(require, exports, module) {
|
|||
|
||||
// ReactJS
|
||||
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
|
||||
const { createFactories } = require("./components/reps/rep-utils");
|
||||
const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
|
||||
const { MainTabbedArea } = createFactories(require("./components/main-tabbed-area"));
|
||||
|
||||
const json = document.getElementById("json");
|
||||
|
|
|
@ -7,6 +7,7 @@ debug = Debug
|
|||
addons = Add-ons
|
||||
addonDebugging.label = Enable add-on debugging
|
||||
addonDebugging.tooltip = Turning this on will allow you to debug add-ons and various other parts of the browser chrome
|
||||
addonDebugging.moreInfo = more info
|
||||
loadTemporaryAddon = Load Temporary Add-on
|
||||
extensions = Extensions
|
||||
selectAddonFromFile = Select Add-on Directory or XPI File
|
||||
|
|
|
@ -368,3 +368,11 @@ heapview.field.name=Name
|
|||
# LOCALIZATION NOTE (heapview.field.name.tooltip): The tooltip for the column
|
||||
# header in the heap view for name.
|
||||
heapview.field.name.tooltip=The name of this group
|
||||
|
||||
# LOCALIZATION NOTE (shortest-paths.header): The header label for the shortest
|
||||
# paths pane.
|
||||
shortest-paths.header=Retaining Paths from GC Roots
|
||||
|
||||
# LOCALIZATION NOTE (shortest-paths.select-node): The message displayed in the
|
||||
# shortest paths pane when a node is not yet selected.
|
||||
shortest-paths.select-node=Select a node to view its retaining paths
|
||||
|
|
|
@ -12,6 +12,7 @@ DevToolsModules(
|
|||
'inverted.js',
|
||||
'io.js',
|
||||
'refresh.js',
|
||||
'sizes.js',
|
||||
'snapshot.js',
|
||||
'view.js',
|
||||
)
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
/* 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");
|
||||
|
||||
exports.resizeShortestPaths = function (newSize) {
|
||||
return {
|
||||
type: actions.RESIZE_SHORTEST_PATHS,
|
||||
size: newSize,
|
||||
};
|
||||
};
|
|
@ -33,6 +33,7 @@ const {
|
|||
focusDominatorTreeNode,
|
||||
} = require("./actions/snapshot");
|
||||
const { changeViewAndRefresh } = require("./actions/view");
|
||||
const { resizeShortestPaths } = require("./actions/sizes");
|
||||
const {
|
||||
breakdownNameToSpec,
|
||||
getBreakdownDisplayData,
|
||||
|
@ -113,7 +114,7 @@ const MemoryApp = createClass({
|
|||
filter,
|
||||
diffing,
|
||||
view,
|
||||
dominatorTreeBreakdown
|
||||
sizes,
|
||||
} = this.props;
|
||||
|
||||
const selectedSnapshot = snapshots.find(s => s.selected);
|
||||
|
@ -237,6 +238,10 @@ const MemoryApp = createClass({
|
|||
"...and that snapshot should have a dominator tree");
|
||||
dispatch(focusDominatorTreeNode(selectedSnapshot.id, node));
|
||||
},
|
||||
onShortestPathsResize: newSize => {
|
||||
dispatch(resizeShortestPaths(newSize));
|
||||
},
|
||||
sizes,
|
||||
view,
|
||||
})
|
||||
)
|
||||
|
|
|
@ -25,11 +25,11 @@ const CensusTreeItem = module.exports = createClass({
|
|||
depth,
|
||||
arrow,
|
||||
focused,
|
||||
toolbox,
|
||||
getPercentBytes,
|
||||
getPercentCount,
|
||||
showSign,
|
||||
onViewSourceInDebugger,
|
||||
inverted,
|
||||
} = this.props;
|
||||
|
||||
const bytes = formatNumber(item.bytes, showSign);
|
||||
|
@ -44,7 +44,14 @@ const CensusTreeItem = module.exports = createClass({
|
|||
const totalCount = formatNumber(item.totalCount, showSign);
|
||||
const percentTotalCount = formatPercent(getPercentCount(item.totalCount), showSign);
|
||||
|
||||
return dom.div({ className: `heap-tree-item ${focused ? "focused" :""}` },
|
||||
let pointer;
|
||||
if (inverted && depth > 0) {
|
||||
pointer = dom.span({ className: "children-pointer" }, "↖");
|
||||
} else if (!inverted && item.children && item.children.length) {
|
||||
pointer = dom.span({ className: "children-pointer" }, "↘");
|
||||
}
|
||||
|
||||
return dom.div({ className: `heap-tree-item ${focused ? "focused" : ""}` },
|
||||
dom.span({ className: "heap-tree-item-field heap-tree-item-bytes" },
|
||||
dom.span({ className: "heap-tree-number" }, bytes),
|
||||
dom.span({ className: "heap-tree-percent" }, percentBytes)),
|
||||
|
@ -60,6 +67,7 @@ const CensusTreeItem = module.exports = createClass({
|
|||
dom.span({ className: "heap-tree-item-field heap-tree-item-name",
|
||||
style: { marginLeft: depth * TREE_ROW_HEIGHT }},
|
||||
arrow,
|
||||
pointer,
|
||||
this.toLabel(item.name, onViewSourceInDebugger)
|
||||
)
|
||||
);
|
||||
|
|
|
@ -66,6 +66,7 @@ const Census = module.exports = createClass({
|
|||
getPercentBytes,
|
||||
getPercentCount,
|
||||
showSign: !!diffing,
|
||||
inverted: census.inverted,
|
||||
}),
|
||||
getRoots: () => report.children || [],
|
||||
getKey: node => node.id,
|
||||
|
|
|
@ -8,6 +8,8 @@ const Census = createFactory(require("./census"));
|
|||
const CensusHeader = createFactory(require("./census-header"));
|
||||
const DominatorTree = createFactory(require("./dominator-tree"));
|
||||
const DominatorTreeHeader = createFactory(require("./dominator-tree-header"));
|
||||
const HSplitBox = createFactory(require("devtools/client/shared/components/h-split-box"));
|
||||
const ShortestPaths = createFactory(require("./shortest-paths"));
|
||||
const { getStatusTextFull, L10N } = require("../utils");
|
||||
const { snapshotState: states, diffingState, viewState, dominatorTreeState } = require("../constants");
|
||||
const { snapshot: snapshotModel, diffingModel } = require("../models");
|
||||
|
@ -145,10 +147,12 @@ const Heap = module.exports = createClass({
|
|||
onDominatorTreeCollapse: PropTypes.func.isRequired,
|
||||
onCensusFocus: PropTypes.func.isRequired,
|
||||
onDominatorTreeFocus: PropTypes.func.isRequired,
|
||||
onShortestPathsResize: PropTypes.func.isRequired,
|
||||
snapshot: snapshotModel,
|
||||
onViewSourceInDebugger: PropTypes.func.isRequired,
|
||||
diffing: diffingModel,
|
||||
view: PropTypes.string.isRequired,
|
||||
sizes: PropTypes.object.isRequired,
|
||||
},
|
||||
|
||||
render() {
|
||||
|
@ -277,8 +281,13 @@ const Heap = module.exports = createClass({
|
|||
},
|
||||
|
||||
_renderDominatorTree(state, onViewSourceInDebugger, dominatorTree, onLoadMoreSiblings) {
|
||||
return this._renderHeapView(
|
||||
state,
|
||||
const tree = dom.div(
|
||||
{
|
||||
className: "vbox",
|
||||
style: {
|
||||
overflowY: "auto"
|
||||
}
|
||||
},
|
||||
DominatorTreeHeader(),
|
||||
DominatorTree({
|
||||
onViewSourceInDebugger,
|
||||
|
@ -289,5 +298,21 @@ const Heap = module.exports = createClass({
|
|||
onFocus: this.props.onDominatorTreeFocus,
|
||||
})
|
||||
);
|
||||
|
||||
const shortestPaths = ShortestPaths({
|
||||
graph: dominatorTree.focused
|
||||
? dominatorTree.focused.shortestPaths
|
||||
: null
|
||||
});
|
||||
|
||||
return this._renderHeapView(
|
||||
state,
|
||||
HSplitBox({
|
||||
start: tree,
|
||||
end: shortestPaths,
|
||||
startWidth: this.props.sizes.shortestPathsSize,
|
||||
onResize: this.props.onShortestPathsResize,
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -12,6 +12,7 @@ DevToolsModules(
|
|||
'dominator-tree.js',
|
||||
'heap.js',
|
||||
'list.js',
|
||||
'shortest-paths.js',
|
||||
'snapshot-list-item.js',
|
||||
'toolbar.js',
|
||||
)
|
||||
|
|
|
@ -0,0 +1,189 @@
|
|||
/* 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 {
|
||||
DOM: dom,
|
||||
createClass,
|
||||
PropTypes,
|
||||
} = require("devtools/client/shared/vendor/react");
|
||||
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
|
||||
};
|
||||
|
||||
const NO_STACK = "noStack";
|
||||
const NO_FILENAME = "noFilename";
|
||||
const ROOT_LIST = "JS::ubi::RootList";
|
||||
|
||||
function stringifyLabel(label, id) {
|
||||
const sanitized = [];
|
||||
|
||||
for (let i = 0, length = label.length; i < length; i++) {
|
||||
const piece = label[i];
|
||||
|
||||
if (isSavedFrame(piece)) {
|
||||
const { short } = getSourceNames(piece.source, UNKNOWN_SOURCE_STRING);
|
||||
sanitized[i] = `${piece.functionDisplayName} @ ${short}:${piece.line}:${piece.column}`;
|
||||
} else if (piece === NO_STACK) {
|
||||
sanitized[i] = L10N.getStr("tree-item.nostack");
|
||||
} else if (piece === NO_FILENAME) {
|
||||
sanitized[i] = L10N.getStr("tree-item.nofilename");
|
||||
} else if (piece === ROOT_LIST) {
|
||||
// Don't use the usual labeling machinery for root lists: replace it
|
||||
// with the "GC Roots" string.
|
||||
sanitized.splice(0, label.length);
|
||||
sanitized.push(L10N.getStr("tree-item.rootlist"));
|
||||
break;
|
||||
} else {
|
||||
sanitized[i] = "" + piece;
|
||||
}
|
||||
}
|
||||
|
||||
return `${sanitized.join(" › ")} @ 0x${id.toString(16)}`;
|
||||
}
|
||||
|
||||
module.exports = createClass({
|
||||
displayName: "ShortestPaths",
|
||||
|
||||
propTypes: {
|
||||
graph: PropTypes.shape({
|
||||
nodes: PropTypes.arrayOf(PropTypes.object),
|
||||
edges: PropTypes.arrayOf(PropTypes.object),
|
||||
}),
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return { zoom: null };
|
||||
},
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return this.props.graph != nextProps.graph;
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.graph) {
|
||||
this._renderGraph(this.refs.container, this.props.graph);
|
||||
}
|
||||
},
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.props.graph) {
|
||||
this._renderGraph(this.refs.container, this.props.graph);
|
||||
}
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.state.zoom) {
|
||||
this.state.zoom.on("zoom", null);
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
let contents;
|
||||
if (this.props.graph) {
|
||||
// Let the componentDidMount or componentDidUpdate method draw the graph
|
||||
// with DagreD3. We just provide the container for the graph here.
|
||||
contents = dom.div({
|
||||
ref: "container",
|
||||
style: {
|
||||
flex: 1,
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
}
|
||||
});
|
||||
} else {
|
||||
contents = dom.div(
|
||||
{
|
||||
id: "shortest-paths-select-node-msg"
|
||||
},
|
||||
L10N.getStr("shortest-paths.select-node")
|
||||
);
|
||||
}
|
||||
|
||||
return dom.div(
|
||||
{
|
||||
id: "shortest-paths",
|
||||
className: "vbox",
|
||||
},
|
||||
dom.label(
|
||||
{
|
||||
id: "shortest-paths-header",
|
||||
className: "header",
|
||||
},
|
||||
L10N.getStr("shortest-paths.header")
|
||||
),
|
||||
contents
|
||||
);
|
||||
},
|
||||
|
||||
_renderGraph(container, { nodes, edges }) {
|
||||
if (!container.firstChild) {
|
||||
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
svg.setAttribute("id", "graph-svg");
|
||||
svg.setAttribute("xlink", "http://www.w3.org/1999/xlink");
|
||||
svg.style.width = "100%";
|
||||
svg.style.height = "100%";
|
||||
|
||||
const target = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
||||
target.setAttribute("id", "graph-target");
|
||||
target.style.width = "100%";
|
||||
target.style.height = "100%";
|
||||
|
||||
svg.appendChild(target);
|
||||
container.appendChild(svg);
|
||||
}
|
||||
|
||||
const graph = new dagreD3.Digraph();
|
||||
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
graph.addNode(nodes[i].id, {
|
||||
id: nodes[i].id,
|
||||
label: stringifyLabel(nodes[i].label, nodes[i].id),
|
||||
});
|
||||
}
|
||||
|
||||
for (let i = 0; i < edges.length; i++) {
|
||||
graph.addEdge(null, edges[i].from, edges[i].to, {
|
||||
label: edges[i].name
|
||||
});
|
||||
}
|
||||
|
||||
const renderer = new dagreD3.Renderer();
|
||||
renderer.drawNodes();
|
||||
renderer.drawEdgePaths();
|
||||
|
||||
const svg = d3.select("#graph-svg");
|
||||
const target = d3.select("#graph-target");
|
||||
|
||||
let zoom = this.state.zoom;
|
||||
if (!zoom) {
|
||||
zoom = d3.behavior.zoom().on("zoom", function() {
|
||||
target.attr(
|
||||
"transform",
|
||||
`translate(${d3.event.translate}) scale(${d3.event.scale})`
|
||||
);
|
||||
});
|
||||
svg.call(zoom);
|
||||
this.setState({ zoom });
|
||||
}
|
||||
|
||||
const { translate, scale } = GRAPH_DEFAULTS;
|
||||
zoom.scale(scale);
|
||||
zoom.translate(translate);
|
||||
target.attr("transform", `translate(${translate}) scale(${scale})`);
|
||||
|
||||
const layout = dagreD3.layout();
|
||||
renderer.layout(layout).run(graph, target);
|
||||
},
|
||||
});
|
|
@ -101,6 +101,8 @@ actions.FETCH_IMMEDIATELY_DOMINATED_END = "fetch-immediately-dominated-end";
|
|||
actions.EXPAND_DOMINATOR_TREE_NODE = "expand-dominator-tree-node";
|
||||
actions.COLLAPSE_DOMINATOR_TREE_NODE = "collapse-dominator-tree-node";
|
||||
|
||||
actions.RESIZE_SHORTEST_PATHS = "resize-shortest-paths";
|
||||
|
||||
/*** Breakdowns ***************************************************************/
|
||||
|
||||
const COUNT = { by: "count", count: true, bytes: true };
|
||||
|
|
|
@ -14,14 +14,29 @@
|
|||
<link rel="stylesheet" href="chrome://devtools/skin/widgets.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/skin/memory.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/skin/components-frame.css" type="text/css"/>
|
||||
|
||||
<script type="application/javascript;version=1.8"
|
||||
src="chrome://devtools/content/shared/theme-switching.js"/>
|
||||
<script type="application/javascript;version=1.8"
|
||||
src="initializer.js"></script>
|
||||
<link rel="stylesheet" href="chrome://devtools/skin/components-h-split-box.css" type="text/css"/>
|
||||
</head>
|
||||
<body class="theme-body">
|
||||
<div id="app">
|
||||
</div>
|
||||
<div id="app"></div>
|
||||
|
||||
<script type="application/javascript;version=1.8"
|
||||
src="chrome://devtools/content/shared/theme-switching.js"
|
||||
defer="true">
|
||||
</script>
|
||||
|
||||
<script type="application/javascript;version=1.8"
|
||||
src="initializer.js"
|
||||
defer="true">
|
||||
</script>
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://devtools/content/shared/vendor/d3.js"
|
||||
defer="true">
|
||||
</script>
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://devtools/content/shared/vendor/dagre-d3.js"
|
||||
defer="true">
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -10,5 +10,6 @@ exports.dominatorTreeBreakdown = require("./reducers/dominatorTreeBreakdown");
|
|||
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");
|
||||
|
|
|
@ -11,6 +11,7 @@ DevToolsModules(
|
|||
'errors.js',
|
||||
'filter.js',
|
||||
'inverted.js',
|
||||
'sizes.js',
|
||||
'snapshots.js',
|
||||
'view.js',
|
||||
)
|
||||
|
|
|
@ -0,0 +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 } = require("../constants");
|
||||
const { immutableUpdate } = require("devtools/shared/DevToolsUtils");
|
||||
|
||||
const handlers = Object.create(null);
|
||||
|
||||
handlers[actions.RESIZE_SHORTEST_PATHS] = function (sizes, { size }) {
|
||||
return immutableUpdate(sizes, { shortestPathsSize: size });
|
||||
};
|
||||
|
||||
module.exports = function (sizes = { shortestPathsSize: .5 }, action) {
|
||||
const handler = handlers[action.type];
|
||||
return handler ? handler(sizes, action) : sizes;
|
||||
};
|
|
@ -26,10 +26,19 @@ this.test = makeMemoryTest(TEST_URL, function* ({ tab, panel }) {
|
|||
|
||||
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");
|
||||
ok(nameElems.some(e => e.textContent.trim() === "objects"), "One for coarse type 'objects'");
|
||||
ok(nameElems.some(e => e.textContent.trim() === "scripts"), "One for coarse type 'scripts'");
|
||||
ok(nameElems.some(e => e.textContent.trim() === "strings"), "One for coarse type 'strings'");
|
||||
ok(nameElems.some(e => e.textContent.trim() === "other"), "One for coarse type 'other'");
|
||||
|
||||
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",
|
||||
|
|
|
@ -72,6 +72,10 @@ function makeMemoryTest(url, generator) {
|
|||
});
|
||||
}
|
||||
|
||||
function dumpn(msg) {
|
||||
dump(`MEMORY-TEST: ${msg}\n`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise that will resolve when the provided store matches
|
||||
* the expected array. expectedStates is an array of dominatorTree states.
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
support-files =
|
||||
head.js
|
||||
|
||||
[test_CensusTreeItem_01.html]
|
||||
[test_DominatorTree_01.html]
|
||||
[test_DominatorTree_02.html]
|
||||
[test_DominatorTree_03.html]
|
||||
|
@ -10,5 +11,7 @@ support-files =
|
|||
[test_Heap_02.html]
|
||||
[test_Heap_03.html]
|
||||
[test_Heap_04.html]
|
||||
[test_ShortestPaths_01.html]
|
||||
[test_ShortestPaths_02.html]
|
||||
[test_Toolbar_01.html]
|
||||
[test_Toolbar_02.html]
|
||||
|
|
|
@ -28,6 +28,7 @@ var {
|
|||
const {
|
||||
getBreakdownDisplayData,
|
||||
getDominatorTreeBreakdownDisplayData,
|
||||
L10N,
|
||||
} = require("devtools/client/memory/utils");
|
||||
|
||||
var models = require("devtools/client/memory/models");
|
||||
|
@ -35,8 +36,10 @@ var models = require("devtools/client/memory/models");
|
|||
var React = require("devtools/client/shared/vendor/react");
|
||||
var ReactDOM = require("devtools/client/shared/vendor/react-dom");
|
||||
var Heap = React.createFactory(require("devtools/client/memory/components/heap"));
|
||||
var CensusTreeItem = React.createFactory(require("devtools/client/memory/components/census-tree-item"));
|
||||
var DominatorTreeComponent = React.createFactory(require("devtools/client/memory/components/dominator-tree"));
|
||||
var DominatorTreeItem = React.createFactory(require("devtools/client/memory/components/dominator-tree-item"));
|
||||
var ShortestPaths = React.createFactory(require("devtools/client/memory/components/shortest-paths"));
|
||||
var Toolbar = React.createFactory(require("devtools/client/memory/components/toolbar"));
|
||||
|
||||
// All tests are asynchronous.
|
||||
|
@ -44,6 +47,33 @@ SimpleTest.waitForExplicitFinish();
|
|||
|
||||
var noop = () => {};
|
||||
|
||||
var TEST_CENSUS_TREE_ITEM_PROPS = Object.freeze({
|
||||
item: Object.freeze({
|
||||
bytes: 10,
|
||||
count: 1,
|
||||
totalBytes: 10,
|
||||
totalCount: 1,
|
||||
name: "foo",
|
||||
children: [
|
||||
Object.freeze({
|
||||
bytes: 10,
|
||||
count: 1,
|
||||
totalBytes: 10,
|
||||
totalCount: 1,
|
||||
name: "bar",
|
||||
})
|
||||
]
|
||||
}),
|
||||
depth: 0,
|
||||
arrow: ">",
|
||||
focused: true,
|
||||
getPercentBytes: () => 50,
|
||||
getPercentCount: () => 50,
|
||||
showSign: false,
|
||||
onViewSourceInDebugger: noop,
|
||||
inverted: false,
|
||||
});
|
||||
|
||||
// Counter for mock DominatorTreeNode ids.
|
||||
var TEST_NODE_ID_COUNTER = 0;
|
||||
|
||||
|
@ -105,6 +135,21 @@ var TEST_DOMINATOR_TREE_PROPS = Object.freeze({
|
|||
onCollapse: noop,
|
||||
});
|
||||
|
||||
var TEST_SHORTEST_PATHS_PROPS = Object.freeze({
|
||||
graph: Object.freeze({
|
||||
nodes: [
|
||||
{ id: 1, label: ["other", "SomeType"] },
|
||||
{ id: 2, label: ["other", "SomeType"] },
|
||||
{ id: 3, label: ["other", "SomeType"] },
|
||||
],
|
||||
edges: [
|
||||
{ from: 1, to: 2, name: "1->2" },
|
||||
{ from: 1, to: 3, name: "1->3" },
|
||||
{ from: 2, to: 3, name: "2->3" },
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
var TEST_HEAP_PROPS = Object.freeze({
|
||||
onSnapshotClick: noop,
|
||||
onLoadMoreSiblings: noop,
|
||||
|
@ -146,6 +191,8 @@ var TEST_HEAP_PROPS = Object.freeze({
|
|||
creationTime: 0,
|
||||
state: snapshotState.SAVED_CENSUS,
|
||||
}),
|
||||
sizes: Object.freeze({ shortestPathsSize: .5 }),
|
||||
onShortestPathsResize: noop,
|
||||
});
|
||||
|
||||
var TEST_TOOLBAR_PROPS = Object.freeze({
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Test that children pointers show up at the correct 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>
|
||||
<!-- Give the container height so that the whole tree is rendered. -->
|
||||
<div id="container" style="height: 900px;"></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");
|
||||
|
||||
yield renderComponent(CensusTreeItem(immutableUpdate(TEST_CENSUS_TREE_ITEM_PROPS, {
|
||||
inverted: true,
|
||||
depth: 0,
|
||||
})), container);
|
||||
|
||||
ok(!container.querySelector(".children-pointer"),
|
||||
"Don't show children pointer for roots when we are inverted");
|
||||
|
||||
yield renderComponent(CensusTreeItem(immutableUpdate(TEST_CENSUS_TREE_ITEM_PROPS, {
|
||||
inverted: true,
|
||||
depth: 1,
|
||||
})), container);
|
||||
|
||||
ok(container.querySelector(".children-pointer"),
|
||||
"Do show children pointer for non-roots when we are inverted");
|
||||
|
||||
yield renderComponent(CensusTreeItem(immutableUpdate(TEST_CENSUS_TREE_ITEM_PROPS, {
|
||||
inverted: false,
|
||||
item: immutableUpdate(TEST_CENSUS_TREE_ITEM_PROPS.item, { children: undefined }),
|
||||
})), container);
|
||||
|
||||
ok(!container.querySelector(".children-pointer"),
|
||||
"Don't show children pointer when non-inverted and no children");
|
||||
|
||||
yield renderComponent(CensusTreeItem(immutableUpdate(TEST_CENSUS_TREE_ITEM_PROPS, {
|
||||
inverted: false,
|
||||
depth: 0,
|
||||
item: immutableUpdate(TEST_CENSUS_TREE_ITEM_PROPS.item, { children: [{}] }),
|
||||
})), container);
|
||||
|
||||
ok(container.querySelector(".children-pointer"),
|
||||
"Do show children pointer when non-inverted and have children");
|
||||
|
||||
} catch(e) {
|
||||
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
|
||||
} finally {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,112 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Test that the ShortestPaths component properly renders a graph of the merged shortest paths.
|
||||
-->
|
||||
<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">
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://devtools/content/shared/vendor/d3.js">
|
||||
</script>
|
||||
<script type="application/javascript"
|
||||
src="chrome://devtools/content/shared/vendor/dagre-d3.js">
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Give the container height so that the whole tree is rendered. -->
|
||||
<div id="container" style="height: 900px;"></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");
|
||||
|
||||
yield renderComponent(ShortestPaths(TEST_SHORTEST_PATHS_PROPS), container);
|
||||
|
||||
let found1 = false;
|
||||
let found2 = false;
|
||||
let found3 = false;
|
||||
|
||||
let found1to2 = false;
|
||||
let found1to3 = false;
|
||||
let found2to3 = false;
|
||||
|
||||
const tspans = [...container.querySelectorAll("tspan")];
|
||||
for (let el of tspans) {
|
||||
const text = el.textContent.trim();
|
||||
dumpn("tspan's text = " + text);
|
||||
|
||||
switch (text) {
|
||||
// Nodes
|
||||
|
||||
case "other › SomeType @ 0x1": {
|
||||
ok(!found1, "Should only find node 1 once");
|
||||
found1 = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case "other › SomeType @ 0x2": {
|
||||
ok(!found2, "Should only find node 2 once");
|
||||
found2 = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case "other › SomeType @ 0x3": {
|
||||
ok(!found3, "Should only find node 3 once");
|
||||
found3 = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Edges
|
||||
|
||||
case "1->2": {
|
||||
ok(!found1to2, "Should only find edge 1->2 once");
|
||||
found1to2 = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case "1->3": {
|
||||
ok(!found1to3, "Should only find edge 1->3 once");
|
||||
found1to3 = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case "2->3": {
|
||||
ok(!found2to3, "Should only find edge 2->3 once");
|
||||
found2to3 = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Unexpected
|
||||
|
||||
default: {
|
||||
ok(false, `Unexpected tspan: ${text}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ok(found1, "Should have rendered node 1");
|
||||
ok(found2, "Should have rendered node 2");
|
||||
ok(found3, "Should have rendered node 3");
|
||||
|
||||
ok(found1to2, "Should have rendered edge 1->2");
|
||||
ok(found1to3, "Should have rendered edge 1->3");
|
||||
ok(found2to3, "Should have rendered edge 2->3");
|
||||
|
||||
} catch(e) {
|
||||
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
|
||||
} finally {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,45 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Test that the ShortestPaths component renders a suggestion to select a node when there is no graph.
|
||||
-->
|
||||
<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">
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://devtools/content/shared/vendor/d3.js">
|
||||
</script>
|
||||
<script type="application/javascript"
|
||||
src="chrome://devtools/content/shared/vendor/dagre-d3.js">
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Give the container height so that the whole tree is rendered. -->
|
||||
<div id="container" style="height: 900px;"></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");
|
||||
|
||||
yield renderComponent(ShortestPaths(immutableUpdate(TEST_SHORTEST_PATHS_PROPS,
|
||||
{ graph: null })),
|
||||
container);
|
||||
|
||||
ok(container.textContent.indexOf(L10N.getStr("shortest-paths.select-node")) !== -1,
|
||||
"The node selection prompt is displayed");
|
||||
} catch(e) {
|
||||
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
|
||||
} finally {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -212,8 +212,17 @@ const DOM = {
|
|||
container.className = "marker-details-stack";
|
||||
container.appendChild(labelName);
|
||||
|
||||
// Workaround for profiles that have looping stack traces. See
|
||||
// bug 1246555.
|
||||
let wasAsyncParent = false;
|
||||
let seen = new Set();
|
||||
|
||||
while (frameIndex > 0) {
|
||||
if (seen.has(frameIndex)) {
|
||||
break;
|
||||
}
|
||||
seen.add(frameIndex);
|
||||
|
||||
let frame = frames[frameIndex];
|
||||
let url = frame.source;
|
||||
let displayName = frame.functionDisplayName;
|
||||
|
|
|
@ -58,9 +58,21 @@ function* spawnTest() {
|
|||
return m.start;
|
||||
}, 0);
|
||||
|
||||
// Override the timestamp marker's stack with our own recursive stack, which
|
||||
// can happen for unknown reasons (bug 1246555); we should not cause a crash
|
||||
// when attempting to render a recursive stack trace
|
||||
let timestampMarker = markers.find(m => m.name === "ConsoleTime");
|
||||
ok(typeof timestampMarker.stack === "number", "ConsoleTime marker has a stack before overwriting.");
|
||||
let frames = PerformanceController.getCurrentRecording().getFrames();
|
||||
let frameIndex = timestampMarker.stack = frames.length;
|
||||
frames.push({ line: 1, column: 1, source: "file.js", functionDisplayName: "test", parent: frameIndex + 1});
|
||||
frames.push({ line: 1, column: 1, source: "file.js", functionDisplayName: "test", parent: frameIndex + 2 });
|
||||
frames.push({ line: 1, column: 1, source: "file.js", functionDisplayName: "test", parent: frameIndex });
|
||||
|
||||
const tests = {
|
||||
ConsoleTime: function (marker) {
|
||||
info("Got `ConsoleTime` marker with data: " + JSON.stringify(marker));
|
||||
ok(marker.stack === frameIndex, "Should have the ConsoleTime marker with recursive stack");
|
||||
shouldHaveStack($, "startStack", marker);
|
||||
shouldHaveStack($, "endStack", marker);
|
||||
shouldHaveLabel($, "Timer Name:", "!!!", marker);
|
||||
|
|
|
@ -325,7 +325,7 @@ pref("devtools.editor.enableCodeFolding", true);
|
|||
pref("devtools.editor.autocomplete", true);
|
||||
|
||||
// Enable the Font Inspector
|
||||
pref("devtools.fontinspector.enabled", true);
|
||||
pref("devtools.fontinspector.enabled", false);
|
||||
|
||||
// Pref to store the browser version at the time of a telemetry ping for an
|
||||
// opened developer tool. This allows us to ping telemetry just once per browser
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"globals": {
|
||||
"define": true,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
/* 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/. */
|
||||
|
||||
// A box with a start and a end pane, separated by a dragable splitter that
|
||||
// allows the user to resize the relative widths of the panes.
|
||||
//
|
||||
// +-----------------------+---------------------+
|
||||
// | | |
|
||||
// | | |
|
||||
// | S |
|
||||
// | Start Pane p End Pane |
|
||||
// | l |
|
||||
// | i |
|
||||
// | t |
|
||||
// | t |
|
||||
// | e |
|
||||
// | r |
|
||||
// | | |
|
||||
// | | |
|
||||
// +-----------------------+---------------------+
|
||||
|
||||
const {
|
||||
DOM: dom,
|
||||
createClass,
|
||||
PropTypes,
|
||||
} = require("devtools/client/shared/vendor/react");
|
||||
const { assert } = require("devtools/shared/DevToolsUtils");
|
||||
|
||||
module.exports = createClass({
|
||||
displayName: "HSplitBox",
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
startWidth: 0.5,
|
||||
minStartWidth: "20px",
|
||||
minEndWidth: "20px",
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
mouseDown: false
|
||||
};
|
||||
},
|
||||
|
||||
propTypes: {
|
||||
// The contents of the start pane.
|
||||
start: PropTypes.any.isRequired,
|
||||
|
||||
// The contents of the end pane.
|
||||
end: PropTypes.any.isRequired,
|
||||
|
||||
// The relative width of the start pane, expressed as a number between 0 and
|
||||
// 1. The relative width of the end pane is 1 - startWidth. For example,
|
||||
// with startWidth = .5, both panes are of equal width; with startWidth =
|
||||
// .25, the start panel will take up 1/4 width and the end panel will take
|
||||
// up 3/4 width.
|
||||
startWidth: PropTypes.number,
|
||||
|
||||
// A minimum css width value for the start and end panes.
|
||||
minStartWidth: PropTypes.any,
|
||||
minEndWidth: PropTypes.any,
|
||||
|
||||
// A callback fired when the user drags the splitter to resize the relative
|
||||
// pane widths. The function is passed the startWidth value that would put
|
||||
// the splitter underneath the users mouse.
|
||||
onResize: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
_onMouseDown(event) {
|
||||
this.setState({ mouseDown: true });
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
_onMouseUp(event) {
|
||||
this.setState({ mouseDown: false });
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
_onMouseMove(event) {
|
||||
if (!this.state.mouseDown) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rect = this.refs.box.getBoundingClientRect();
|
||||
const { left, right } = rect;
|
||||
const width = right - left;
|
||||
const relative = event.clientX - left;
|
||||
this.props.onResize(relative / width);
|
||||
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
document.defaultView.top.addEventListener("mouseup", this._onMouseUp, false);
|
||||
document.defaultView.top.addEventListener("mousemove", this._onMouseMove, false);
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
document.defaultView.top.removeEventListener("mouseup", this._onMouseUp, false);
|
||||
document.defaultView.top.removeEventListener("mousemove", this._onMouseMove, false);
|
||||
},
|
||||
|
||||
render() {
|
||||
const { start, end, startWidth, minStartWidth, minEndWidth } = this.props;
|
||||
assert(0 <= startWidth && startWidth <= 1,
|
||||
"0 <= this.props.startWidth <= 1");
|
||||
|
||||
return dom.div(
|
||||
{
|
||||
className: "h-split-box",
|
||||
ref: "box",
|
||||
},
|
||||
|
||||
dom.div(
|
||||
{
|
||||
className: "h-split-box-pane",
|
||||
style: { flex: startWidth, minWidth: minStartWidth },
|
||||
},
|
||||
start
|
||||
),
|
||||
|
||||
dom.div({
|
||||
className: "h-split-box-splitter",
|
||||
onMouseDown: this._onMouseDown,
|
||||
}),
|
||||
|
||||
dom.div(
|
||||
{
|
||||
className: "h-split-box-pane",
|
||||
style: { flex: 1 - startWidth, minWidth: minEndWidth },
|
||||
},
|
||||
end
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
|
@ -4,8 +4,13 @@
|
|||
# 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/.
|
||||
|
||||
DIRS += [
|
||||
'reps',
|
||||
]
|
||||
|
||||
DevToolsModules(
|
||||
'frame.js',
|
||||
'h-split-box.js',
|
||||
'tree.js',
|
||||
)
|
||||
|
||||
|
|
|
@ -0,0 +1,208 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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";
|
||||
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function(require, exports, module) {
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("./rep-utils");
|
||||
const { Rep } = createFactories(require("./rep"));
|
||||
const { ObjectBox } = createFactories(require("./object-box"));
|
||||
const { Caption } = createFactories(require("./caption"));
|
||||
|
||||
// Shortcuts
|
||||
const DOM = React.DOM;
|
||||
|
||||
/**
|
||||
* Renders an array. The array is enclosed by left and right bracket
|
||||
* and the max number of rendered items depends on the current mode.
|
||||
*/
|
||||
let ArrayRep = React.createClass({
|
||||
displayName: "ArrayRep",
|
||||
|
||||
render: function() {
|
||||
let mode = this.props.mode || "short";
|
||||
let object = this.props.object;
|
||||
let items;
|
||||
|
||||
if (mode == "tiny") {
|
||||
items = DOM.span({className: "length"}, object.length);
|
||||
} else {
|
||||
let max = (mode == "short") ? 3 : 300;
|
||||
items = this.arrayIterator(object, max);
|
||||
}
|
||||
|
||||
return (
|
||||
ObjectBox({
|
||||
className: "array",
|
||||
onClick: this.onToggleProperties},
|
||||
DOM.a({
|
||||
className: "objectLink",
|
||||
onclick: this.onClickBracket},
|
||||
DOM.span({
|
||||
className: "arrayLeftBracket",
|
||||
role: "presentation"},
|
||||
"["
|
||||
)
|
||||
),
|
||||
items,
|
||||
DOM.a({
|
||||
className: "objectLink",
|
||||
onclick: this.onClickBracket},
|
||||
DOM.span({
|
||||
className: "arrayRightBracket",
|
||||
role: "presentation"},
|
||||
"]"
|
||||
)
|
||||
),
|
||||
DOM.span({
|
||||
className: "arrayProperties",
|
||||
role: "group"}
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
getTitle: function(object, context) {
|
||||
return "[" + object.length + "]";
|
||||
},
|
||||
|
||||
arrayIterator: function(array, max) {
|
||||
let items = [];
|
||||
let delim;
|
||||
|
||||
for (let i = 0; i < array.length && i <= max; i++) {
|
||||
try {
|
||||
let value = array[i];
|
||||
|
||||
delim = (i == array.length - 1 ? "" : ", ");
|
||||
|
||||
if (value === array) {
|
||||
items.push(Reference({
|
||||
key: i,
|
||||
object: value,
|
||||
delim: delim
|
||||
}));
|
||||
} else {
|
||||
items.push(ItemRep({
|
||||
key: i,
|
||||
object: value,
|
||||
delim: delim
|
||||
}));
|
||||
}
|
||||
} catch (exc) {
|
||||
items.push(ItemRep({
|
||||
object: exc,
|
||||
delim: delim,
|
||||
key: i
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
if (array.length > max + 1) {
|
||||
items.pop();
|
||||
items.push(Caption({
|
||||
key: "more",
|
||||
object: "more...",
|
||||
}));
|
||||
}
|
||||
|
||||
return items;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if the passed object is an array with additional (custom)
|
||||
* properties, otherwise returns false. Custom properties should be
|
||||
* displayed in extra expandable section.
|
||||
*
|
||||
* Example array with a custom property.
|
||||
* let arr = [0, 1];
|
||||
* arr.myProp = "Hello";
|
||||
*
|
||||
* @param {Array} array The array object.
|
||||
*/
|
||||
hasSpecialProperties: function(array) {
|
||||
function isInteger(x) {
|
||||
let y = parseInt(x, 10);
|
||||
if (isNaN(y)) {
|
||||
return false;
|
||||
}
|
||||
return x === y.toString();
|
||||
}
|
||||
|
||||
let props = Object.getOwnPropertyNames(array);
|
||||
for (let i = 0; i < props.length; i++) {
|
||||
let p = props[i];
|
||||
|
||||
// Valid indexes are skipped
|
||||
if (isInteger(p)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore standard 'length' property, anything else is custom.
|
||||
if (p != "length") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
// Event Handlers
|
||||
|
||||
onToggleProperties: function(event) {
|
||||
},
|
||||
|
||||
onClickBracket: function(event) {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Renders array item. Individual values are separated by a comma.
|
||||
*/
|
||||
let ItemRep = React.createFactory(React.createClass({
|
||||
displayName: "ItemRep",
|
||||
|
||||
render: function() {
|
||||
let object = this.props.object;
|
||||
let delim = this.props.delim;
|
||||
return (
|
||||
DOM.span({},
|
||||
Rep({object: object}),
|
||||
delim
|
||||
)
|
||||
);
|
||||
}
|
||||
}));
|
||||
|
||||
/**
|
||||
* Renders cycle references in an array.
|
||||
*/
|
||||
let Reference = React.createFactory(React.createClass({
|
||||
displayName: "Reference",
|
||||
|
||||
render: function() {
|
||||
let tooltip = "Circular reference";
|
||||
return (
|
||||
DOM.span({title: tooltip},
|
||||
"[...]")
|
||||
);
|
||||
}
|
||||
}));
|
||||
|
||||
function supportsObject(object, type) {
|
||||
return Array.isArray(object) ||
|
||||
Object.prototype.toString.call(object) === "[object Arguments]";
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
exports.ArrayRep = {
|
||||
rep: ArrayRep,
|
||||
supportsObject: supportsObject
|
||||
};
|
||||
});
|
|
@ -0,0 +1,31 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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";
|
||||
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function(require, exports, module) {
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const DOM = React.DOM;
|
||||
|
||||
/**
|
||||
* Renders a caption. This template is used by other components
|
||||
* that needs to distinguish between a simple text/value and a label.
|
||||
*/
|
||||
const Caption = React.createClass({
|
||||
displayName: "Caption",
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
DOM.span({"className": "caption"}, this.props.object)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// Exports from this module
|
||||
exports.Caption = Caption;
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
'array.js',
|
||||
'caption.js',
|
||||
'null.js',
|
||||
'number.js',
|
||||
'object-box.js',
|
||||
'object-link.js',
|
||||
'object.js',
|
||||
'rep-utils.js',
|
||||
'rep.js',
|
||||
'reps.css',
|
||||
'string.js',
|
||||
'undefined.js',
|
||||
)
|
|
@ -0,0 +1,45 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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";
|
||||
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function(require, exports, module) {
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("./rep-utils");
|
||||
const { ObjectBox } = createFactories(require("./object-box"));
|
||||
|
||||
/**
|
||||
* Renders null value
|
||||
*/
|
||||
const Null = React.createClass({
|
||||
displayName: "NullRep",
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
ObjectBox({className: "null"},
|
||||
"null"
|
||||
)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
function supportsObject(object, type) {
|
||||
if (object && object.type && object.type == "null") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (object == null);
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
|
||||
exports.Null = {
|
||||
rep: Null,
|
||||
supportsObject: supportsObject
|
||||
};
|
||||
});
|
|
@ -0,0 +1,46 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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";
|
||||
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function(require, exports, module) {
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("./rep-utils");
|
||||
const { ObjectBox } = createFactories(require("./object-box"));
|
||||
|
||||
/**
|
||||
* Renders a number
|
||||
*/
|
||||
const Number = React.createClass({
|
||||
displayName: "Number",
|
||||
|
||||
render: function() {
|
||||
let value = this.props.object;
|
||||
return (
|
||||
ObjectBox({className: "number"},
|
||||
this.stringify(value)
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
stringify: function(object) {
|
||||
return (Object.is(object, -0) ? "-0" : String(object));
|
||||
},
|
||||
});
|
||||
|
||||
function supportsObject(object, type) {
|
||||
return type == "boolean" || type == "number";
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
|
||||
exports.Number = {
|
||||
rep: Number,
|
||||
supportsObject: supportsObject
|
||||
};
|
||||
});
|
|
@ -0,0 +1,35 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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";
|
||||
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function(require, exports, module) {
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const DOM = React.DOM;
|
||||
|
||||
/**
|
||||
* Renders a box for given object.
|
||||
*/
|
||||
const ObjectBox = React.createClass({
|
||||
displayName: "ObjectBox",
|
||||
|
||||
render: function() {
|
||||
let className = this.props.className;
|
||||
let boxClassName = className ? " objectBox-" + className : "";
|
||||
|
||||
return (
|
||||
DOM.span({className: "objectBox" + boxClassName, role: "presentation"},
|
||||
this.props.children
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Exports from this module
|
||||
exports.ObjectBox = ObjectBox;
|
||||
});
|
|
@ -0,0 +1,36 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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";
|
||||
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function(require, exports, module) {
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const DOM = React.DOM;
|
||||
|
||||
/**
|
||||
* Renders a link for given object.
|
||||
*/
|
||||
const ObjectLink = React.createClass({
|
||||
displayName: "ObjectLink",
|
||||
|
||||
render: function() {
|
||||
let className = this.props.className;
|
||||
let objectClassName = className ? " objectLink-" + className : "";
|
||||
let linkClassName = "objectLink" + objectClassName + " a11yFocus";
|
||||
|
||||
return (
|
||||
DOM.a({className: linkClassName, _repObject: this.props.object},
|
||||
this.props.children
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Exports from this module
|
||||
exports.ObjectLink = ObjectLink;
|
||||
});
|
|
@ -0,0 +1,190 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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";
|
||||
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function(require, exports, module) {
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("./rep-utils");
|
||||
const { ObjectBox } = createFactories(require("./object-box"));
|
||||
const { Caption } = createFactories(require("./caption"));
|
||||
|
||||
// Shortcuts
|
||||
const DOM = React.DOM;
|
||||
|
||||
/**
|
||||
* Renders an object. An object is represented by a list of its
|
||||
* properties enclosed in curly brackets.
|
||||
*/
|
||||
const Obj = React.createClass({
|
||||
displayName: "Obj",
|
||||
|
||||
render: function() {
|
||||
let object = this.props.object;
|
||||
let props = this.shortPropIterator(object);
|
||||
|
||||
return (
|
||||
ObjectBox({className: "object"},
|
||||
DOM.span({className: "objectTitle"}, this.getTitle(object)),
|
||||
DOM.span({className: "objectLeftBrace", role: "presentation"}, "{"),
|
||||
props,
|
||||
DOM.span({className: "objectRightBrace"}, "}")
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
getTitle: function() {
|
||||
return "";
|
||||
},
|
||||
|
||||
longPropIterator: function(object) {
|
||||
try {
|
||||
return this.propIterator(object, 100);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
shortPropIterator: function(object) {
|
||||
try {
|
||||
return this.propIterator(object, 3);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
propIterator: function(object, max) {
|
||||
function isInterestingProp(t, value) {
|
||||
return (t == "boolean" || t == "number" || (t == "string" && value) ||
|
||||
(t == "object" && value && value.toString));
|
||||
}
|
||||
|
||||
// Work around https://bugzilla.mozilla.org/show_bug.cgi?id=945377
|
||||
if (Object.prototype.toString.call(object) === "[object Generator]") {
|
||||
object = Object.getPrototypeOf(object);
|
||||
}
|
||||
|
||||
// Object members with non-empty values are preferred since it gives the
|
||||
// user a better overview of the object.
|
||||
let props = [];
|
||||
this.getProps(props, object, max, isInterestingProp);
|
||||
|
||||
if (props.length <= max) {
|
||||
// There are not enough props yet (or at least, not enough props to
|
||||
// be able to know whether we should print "more..." or not).
|
||||
// Let's display also empty members and functions.
|
||||
this.getProps(props, object, max, function(t, value) {
|
||||
return !isInterestingProp(t, value);
|
||||
});
|
||||
}
|
||||
|
||||
if (props.length > max) {
|
||||
props.pop();
|
||||
props.push(Caption({
|
||||
key: "more",
|
||||
object: "more...",
|
||||
}));
|
||||
} else if (props.length > 0) {
|
||||
// Remove the last comma.
|
||||
props[props.length - 1] = React.cloneElement(
|
||||
props[props.length - 1], { delim: "" });
|
||||
}
|
||||
|
||||
return props;
|
||||
},
|
||||
|
||||
getProps: function(props, object, max, filter) {
|
||||
max = max || 3;
|
||||
if (!object) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let mode = this.props.mode;
|
||||
|
||||
try {
|
||||
for (let name in object) {
|
||||
if (props.length > max) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let value;
|
||||
try {
|
||||
value = object[name];
|
||||
} catch (exc) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let t = typeof value;
|
||||
if (filter(t, value)) {
|
||||
props.push(PropRep({
|
||||
key: name,
|
||||
mode: mode,
|
||||
name: name,
|
||||
object: value,
|
||||
equal: ": ",
|
||||
delim: ", ",
|
||||
}));
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Renders object property, name-value pair.
|
||||
*/
|
||||
let PropRep = React.createFactory(React.createClass({
|
||||
displayName: "PropRep",
|
||||
|
||||
render: function() {
|
||||
let { Rep } = createFactories(require("./rep"));
|
||||
let object = this.props.object;
|
||||
let mode = this.props.mode;
|
||||
|
||||
return (
|
||||
DOM.span({},
|
||||
DOM.span({
|
||||
"className": "nodeName"},
|
||||
this.props.name
|
||||
),
|
||||
DOM.span({
|
||||
"className": "objectEqual",
|
||||
role: "presentation"},
|
||||
this.props.equal
|
||||
),
|
||||
Rep({
|
||||
object: object,
|
||||
mode: mode
|
||||
}),
|
||||
DOM.span({
|
||||
"className": "objectComma",
|
||||
role: "presentation"},
|
||||
this.props.delim
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}));
|
||||
|
||||
function supportsObject(object, type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
|
||||
exports.Obj = {
|
||||
rep: Obj,
|
||||
supportsObject: supportsObject
|
||||
};
|
||||
});
|
|
@ -0,0 +1,29 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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";
|
||||
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function(require, exports, module) {
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
|
||||
/**
|
||||
* Create React factories for given arguments.
|
||||
* Example:
|
||||
* const { Rep } = createFactories(require("./rep"));
|
||||
*/
|
||||
function createFactories(args) {
|
||||
let result = {};
|
||||
for (let p in args) {
|
||||
result[p] = React.createFactory(args[p]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
exports.createFactories = createFactories;
|
||||
});
|
|
@ -0,0 +1,86 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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";
|
||||
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function(require, exports, module) {
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
|
||||
// Load all existing rep templates
|
||||
const { Undefined } = require("./undefined");
|
||||
const { Null } = require("./null");
|
||||
const { StringRep } = require("./string");
|
||||
const { Number } = require("./number");
|
||||
const { ArrayRep } = require("./array");
|
||||
const { Obj } = require("./object");
|
||||
|
||||
// List of all registered template.
|
||||
// XXX there should be a way for extensions to register a new
|
||||
// or modify an existing rep.
|
||||
let reps = [Undefined, Null, StringRep, Number, ArrayRep, Obj];
|
||||
let defaultRep;
|
||||
|
||||
/**
|
||||
* Generic rep that is using for rendering native JS types or an object.
|
||||
* The right template used for rendering is picked automatically according
|
||||
* to the current value type. The value must be passed is as 'object'
|
||||
* property.
|
||||
*/
|
||||
const Rep = React.createClass({
|
||||
displayName: "Rep",
|
||||
|
||||
render: function() {
|
||||
let rep = getRep(this.props.object);
|
||||
return rep(this.props);
|
||||
},
|
||||
});
|
||||
|
||||
// Helpers
|
||||
|
||||
/**
|
||||
* Return a rep object that is responsible for rendering given
|
||||
* object.
|
||||
*
|
||||
* @param object {Object} Object to be rendered in the UI. This
|
||||
* can be generic JS object as well as a grip (handle to a remote
|
||||
* debuggee object).
|
||||
*/
|
||||
function getRep(object) {
|
||||
let type = typeof object;
|
||||
if (type == "object" && object instanceof String) {
|
||||
type = "string";
|
||||
}
|
||||
|
||||
if (isGrip(object)) {
|
||||
type = object.class;
|
||||
}
|
||||
|
||||
for (let i = 0; i < reps.length; i++) {
|
||||
let rep = reps[i];
|
||||
try {
|
||||
// supportsObject could return weight (not only true/false
|
||||
// but a number), which would allow to priorities templates and
|
||||
// support better extensibility.
|
||||
if (rep.supportsObject(object, type)) {
|
||||
return React.createFactory(rep.rep);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("reps.getRep; EXCEPTION ", err, err);
|
||||
}
|
||||
}
|
||||
|
||||
return React.createFactory(defaultRep.rep);
|
||||
}
|
||||
|
||||
function isGrip(object) {
|
||||
return object && object.actor;
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
exports.Rep = Rep;
|
||||
});
|
|
@ -0,0 +1,101 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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";
|
||||
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function(require, exports, module) {
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("./rep-utils");
|
||||
const { ObjectBox } = createFactories(require("./object-box"));
|
||||
|
||||
/**
|
||||
* Renders a string. String value is enclosed within quotes.
|
||||
*/
|
||||
const StringRep = React.createClass({
|
||||
displayName: "StringRep",
|
||||
|
||||
render: function() {
|
||||
let text = this.props.object;
|
||||
let member = this.props.member;
|
||||
if (member && member.open) {
|
||||
return (
|
||||
ObjectBox({className: "string"},
|
||||
"\"" + text + "\""
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
ObjectBox({className: "string"},
|
||||
"\"" + cropMultipleLines(text) + "\""
|
||||
)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// Helpers
|
||||
|
||||
function escapeNewLines(value) {
|
||||
return value.replace(/\r/gm, "\\r").replace(/\n/gm, "\\n");
|
||||
}
|
||||
|
||||
function cropMultipleLines(text, limit) {
|
||||
return escapeNewLines(cropString(text, limit));
|
||||
}
|
||||
|
||||
function cropString(text, limit, alternativeText) {
|
||||
if (!alternativeText) {
|
||||
alternativeText = "...";
|
||||
}
|
||||
|
||||
// Make sure it's a string.
|
||||
text = text + "";
|
||||
|
||||
// Use default limit if necessary.
|
||||
if (!limit) {
|
||||
limit = 50;
|
||||
}
|
||||
|
||||
// Crop the string only if a limit is actually specified.
|
||||
if (limit <= 0) {
|
||||
return text;
|
||||
}
|
||||
|
||||
// Set the limit at least to the length of the alternative text
|
||||
// plus one character of the original text.
|
||||
if (limit <= alternativeText.length) {
|
||||
limit = alternativeText.length + 1;
|
||||
}
|
||||
|
||||
let halfLimit = (limit - alternativeText.length) / 2;
|
||||
|
||||
if (text.length > limit) {
|
||||
return text.substr(0, Math.ceil(halfLimit)) + alternativeText +
|
||||
text.substr(text.length - Math.floor(halfLimit));
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
function isCropped(value) {
|
||||
let cropLength = 50;
|
||||
return typeof value == "string" && value.length > cropLength;
|
||||
}
|
||||
|
||||
function supportsObject(object, type) {
|
||||
return (type == "string");
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
|
||||
exports.StringRep = {
|
||||
rep: StringRep,
|
||||
supportsObject: supportsObject,
|
||||
isCropped: isCropped
|
||||
};
|
||||
});
|
|
@ -0,0 +1,45 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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";
|
||||
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function(require, exports, module) {
|
||||
// Dependencies
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("./rep-utils");
|
||||
const { ObjectBox } = createFactories(require("./object-box"));
|
||||
|
||||
/**
|
||||
* Renders undefined value
|
||||
*/
|
||||
const Undefined = React.createClass({
|
||||
displayName: "UndefinedRep",
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
ObjectBox({className: "undefined"},
|
||||
"undefined"
|
||||
)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
function supportsObject(object, type) {
|
||||
if (object && object.type && object.type == "undefined") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (type == "undefined");
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
|
||||
exports.Undefined = {
|
||||
rep: Undefined,
|
||||
supportsObject: supportsObject
|
||||
};
|
||||
});
|
|
@ -2,6 +2,7 @@
|
|||
support-files =
|
||||
head.js
|
||||
|
||||
[test_HSplitBox_01.html]
|
||||
[test_frame_01.html]
|
||||
[test_frame_02.html]
|
||||
[test_tree_01.html]
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Basic tests for the HSplitBox component.
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Tree component test</title>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript "src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/skin/components-h-split-box.css" type="text/css"/>
|
||||
<style>
|
||||
html {
|
||||
--theme-splitter-color: black;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script src="head.js" type="application/javascript;version=1.8"></script>
|
||||
<script type="application/javascript;version=1.8">
|
||||
const FUDGE_FACTOR = .1;
|
||||
function aboutEq(a, b) {
|
||||
dumpn(`Checking ${a} ~= ${b}`);
|
||||
return Math.abs(a - b) < FUDGE_FACTOR;
|
||||
}
|
||||
|
||||
window.onload = Task.async(function* () {
|
||||
try {
|
||||
const React = browserRequire("devtools/client/shared/vendor/react");
|
||||
const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
|
||||
|
||||
let HSplitBox = React.createFactory(browserRequire("devtools/client/shared/components/h-split-box"));
|
||||
ok(HSplitBox, "Should get HSplitBox");
|
||||
|
||||
const newSizes = [];
|
||||
const box = ReactDOM.render(HSplitBox({
|
||||
start: "hello!",
|
||||
end: "world!",
|
||||
startWidth: .5,
|
||||
onResize(newSize) {
|
||||
newSizes.push(newSize);
|
||||
},
|
||||
}), window.document.body);
|
||||
|
||||
// Test that we properly rendered our two panes.
|
||||
|
||||
let panes = document.querySelectorAll(".h-split-box-pane");
|
||||
is(panes.length, 2, "Should get two panes");
|
||||
is(panes[0].style.flexGrow, "0.5", "Each pane should have .5 width");
|
||||
is(panes[1].style.flexGrow, "0.5", "Each pane should have .5 width");
|
||||
is(panes[0].textContent.trim(), "hello!", "First pane should be hello");
|
||||
is(panes[1].textContent.trim(), "world!", "Second pane should be world");
|
||||
|
||||
// Now change the left width and assert that the changes are reflected.
|
||||
|
||||
yield setProps(box, { startWidth: .25 });
|
||||
panes = document.querySelectorAll(".h-split-box-pane");
|
||||
is(panes.length, 2, "Should still have two panes");
|
||||
is(panes[0].style.flexGrow, "0.25", "First pane's width should be .25");
|
||||
is(panes[1].style.flexGrow, "0.75", "Second pane's width should be .75");
|
||||
|
||||
// Mouse moves without having grabbed the splitter should have no effect.
|
||||
|
||||
let container = document.querySelector(".h-split-box");
|
||||
ok(container, "Should get our container .h-split-box");
|
||||
|
||||
const { left, top, width } = container.getBoundingClientRect();
|
||||
const middle = left + width / 2;
|
||||
const oneQuarter = left + width / 4;
|
||||
const threeQuarters = left + 3 * width / 4;
|
||||
|
||||
synthesizeMouse(container, middle, top, { type: "mousemove" }, window);
|
||||
is(newSizes.length, 0, "Mouse moves without dragging the splitter should have no effect");
|
||||
|
||||
// Send a mouse down on the splitter, and then move the mouse a couple
|
||||
// times. Now we should get resizes.
|
||||
|
||||
const splitter = document.querySelector(".h-split-box-splitter");
|
||||
ok(splitter, "Should get our splitter");
|
||||
|
||||
synthesizeMouseAtCenter(splitter, { button: 1, type: "mousedown" }, window);
|
||||
|
||||
function mouseMove(clientX) {
|
||||
const event = new MouseEvent("mousemove", { clientX });
|
||||
document.defaultView.top.dispatchEvent(event);
|
||||
}
|
||||
|
||||
mouseMove(middle);
|
||||
is(newSizes.length, 1, "Should get 1 resize");
|
||||
ok(aboutEq(newSizes[0], .5), "New size should be ~.5");
|
||||
|
||||
mouseMove(left);
|
||||
is(newSizes.length, 2, "Should get 2 resizes");
|
||||
ok(aboutEq(newSizes[1], 0), "New size should be ~0");
|
||||
|
||||
mouseMove(oneQuarter);
|
||||
is(newSizes.length, 3, "Sould get 3 resizes");
|
||||
ok(aboutEq(newSizes[2], .25), "New size should be ~.25");
|
||||
|
||||
mouseMove(threeQuarters);
|
||||
is(newSizes.length, 4, "Should get 4 resizes");
|
||||
ok(aboutEq(newSizes[3], .75), "New size should be ~.75");
|
||||
|
||||
synthesizeMouseAtCenter(splitter, { button: 1, type: "mouseup" }, window);
|
||||
|
||||
// Now that we have let go of the splitter, mouse moves should not result in resizes.
|
||||
|
||||
synthesizeMouse(container, middle, top, { type: "mousemove" }, window);
|
||||
is(newSizes.length, 4, "Should still have 4 resizes");
|
||||
|
||||
} catch(e) {
|
||||
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
|
||||
} finally {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,31 @@
|
|||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* HSplitBox Component
|
||||
* Styles for React component at `devtools/client/shared/components/h-split-box.js`
|
||||
*/
|
||||
|
||||
.h-split-box,
|
||||
.h-split-box-pane {
|
||||
overflow: auto;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.h-split-box {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.h-split-box-splitter {
|
||||
-moz-border-end: 1px solid var(--theme-splitter-color);
|
||||
cursor: ew-resize;
|
||||
width: 3px;
|
||||
-moz-margin-start: -3px;
|
||||
}
|
|
@ -224,6 +224,26 @@ html, body, #app, #memory-tool {
|
|||
* Main panel
|
||||
*/
|
||||
|
||||
.vbox {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.vbox > * {
|
||||
flex: 1;
|
||||
|
||||
/**
|
||||
* By default, flex items have min-width: auto;
|
||||
* (https://drafts.csswg.org/css-flexbox/#min-size-auto)
|
||||
*/
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
#heap-view {
|
||||
/**
|
||||
* Flex: contains a .heap-view-panel which needs to fill out all the
|
||||
|
@ -267,7 +287,8 @@ html, body, #app, #memory-tool {
|
|||
}
|
||||
|
||||
#heap-view > .heap-view-panel > .snapshot-status,
|
||||
#heap-view > .heap-view-panel > .take-snapshot {
|
||||
#heap-view > .heap-view-panel > .take-snapshot,
|
||||
#shortest-paths-select-node-msg {
|
||||
margin: auto;
|
||||
margin-top: 65px;
|
||||
font-size: 120%;
|
||||
|
@ -297,20 +318,37 @@ html, body, #app, #memory-tool {
|
|||
color: var(--theme-body-color);
|
||||
background-color: var(--theme-tab-toolbar-background);
|
||||
border-bottom: 1px solid var(--cell-border-color);
|
||||
flex: 0;
|
||||
}
|
||||
|
||||
.header > span,
|
||||
#shortest-paths-header {
|
||||
text-overflow: ellipsis;
|
||||
line-height: var(--heap-tree-header-height);
|
||||
justify-content: center;
|
||||
justify-self: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.header > span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: var(--heap-tree-header-height);
|
||||
justify-content: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.header > .heap-tree-item-name {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
#shortest-paths {
|
||||
background-color: var(--theme-body-background);
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#shortest-paths-select-node-msg {
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
/**
|
||||
* Heap tree view body
|
||||
*/
|
||||
|
@ -329,6 +367,10 @@ html, body, #app, #memory-tool {
|
|||
line-height: var(--heap-tree-row-height);
|
||||
}
|
||||
|
||||
.children-pointer {
|
||||
padding-inline-end: 5px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Heap tree view columns
|
||||
*/
|
||||
|
@ -484,3 +526,50 @@ html, body, #app, #memory-tool {
|
|||
text-align: center;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dagre-D3 graphs
|
||||
*/
|
||||
|
||||
.edgePath path {
|
||||
stroke-width: 1px;
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.theme-dark .edgePath path {
|
||||
stroke: var(--theme-body-color-alt);
|
||||
}
|
||||
.theme-light .edgePath path {
|
||||
stroke: var(--theme-splitter-color);
|
||||
}
|
||||
|
||||
g.edgeLabel rect {
|
||||
fill: var(--theme-body-background);
|
||||
}
|
||||
g.edgeLabel tspan {
|
||||
fill: var(--theme-body-color-alt);
|
||||
}
|
||||
|
||||
.nodes rect {
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.nodes rect {
|
||||
stroke: var(--theme-tab-toolbar-background);
|
||||
}
|
||||
.theme-light rect {
|
||||
fill: var(--theme-tab-toolbar-background);
|
||||
}
|
||||
.theme-dark rect {
|
||||
fill: var(--theme-toolbar-background);
|
||||
}
|
||||
|
||||
text {
|
||||
font-weight: 300;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serf;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
text {
|
||||
fill: var(--theme-body-color-alt);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Test that when we take a census and get a bucket list of ids that matched the
|
||||
// given category, that the returned ids are all in the snapshot and their
|
||||
// reported category.
|
||||
|
||||
function run_test() {
|
||||
const g = newGlobal();
|
||||
const dbg = new Debugger(g);
|
||||
|
||||
const path = saveNewHeapSnapshot({ debugger: dbg });
|
||||
const snapshot = readHeapSnapshot(path);
|
||||
|
||||
const bucket = { by: "bucket" };
|
||||
const count = { by: "count", count: true, bytes: false };
|
||||
const objectClassCount = { by: "objectClass", then: count, other: count };
|
||||
|
||||
const byClassName = snapshot.takeCensus({
|
||||
breakdown: {
|
||||
by: "objectClass",
|
||||
then: bucket,
|
||||
other: bucket,
|
||||
}
|
||||
});
|
||||
|
||||
const byClassNameCount = snapshot.takeCensus({
|
||||
breakdown: objectClassCount
|
||||
});
|
||||
|
||||
const keys = new Set(Object.keys(byClassName));
|
||||
equal(keys.size, Object.keys(byClassNameCount).length,
|
||||
"Should have the same number of keys.");
|
||||
for (let k of Object.keys(byClassNameCount)) {
|
||||
ok(keys.has(k), "Should not have any unexpected class names");
|
||||
}
|
||||
|
||||
for (let key of Object.keys(byClassName)) {
|
||||
equal(byClassNameCount[key].count, byClassName[key].length,
|
||||
"Length of the bucket and count should be equal");
|
||||
|
||||
for (let id of byClassName[key]) {
|
||||
const desc = snapshot.describeNode(objectClassCount, id);
|
||||
equal(desc[key].count, 1,
|
||||
"Describing the bucketed node confirms that it belongs to the category");
|
||||
}
|
||||
}
|
||||
|
||||
do_test_finished();
|
||||
}
|
|
@ -81,6 +81,7 @@ support-files =
|
|||
[test_HeapSnapshot_takeCensus_09.js]
|
||||
[test_HeapSnapshot_takeCensus_10.js]
|
||||
[test_HeapSnapshot_takeCensus_11.js]
|
||||
[test_HeapSnapshot_takeCensus_12.js]
|
||||
[test_ReadHeapSnapshot.js]
|
||||
[test_ReadHeapSnapshot_with_allocations.js]
|
||||
skip-if = os == 'linux' # Bug 1176173
|
||||
|
|
|
@ -324,6 +324,22 @@ Function Properties of the `Debugger.Memory.prototype` Object
|
|||
types. When the census cannot find the byte size for a given type, it
|
||||
returns zero.
|
||||
|
||||
<code>{ by: "bucket" }</code>
|
||||
: Do not do any filtering or categorizing. Instead, accumulate a bucket of
|
||||
each node's ID for every node that matches. The resulting report is an
|
||||
array of the IDs.
|
||||
|
||||
For example, to find the ID of all nodes whose internal object
|
||||
`[[class]]` property is named "RegExp", you could use the following code:
|
||||
|
||||
const report = dbg.memory.takeCensus({
|
||||
breakdown: {
|
||||
by: "objectClass",
|
||||
then: { by: "bucket" }
|
||||
}
|
||||
});
|
||||
doStuffWithRegExpIDs(report.RegExp);
|
||||
|
||||
<code>{ by: "allocationStack", then:<i>breakdown</i>, noStack:<i>noStackBreakdown</i> }</code>
|
||||
: Group items by the full JavaScript stack trace at which they were
|
||||
allocated.
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
// Sanity test that we can accumulate matching individuals in a bucket.
|
||||
|
||||
var g = newGlobal();
|
||||
var dbg = new Debugger(g);
|
||||
|
||||
var bucket = { by: "bucket" };
|
||||
var count = { by: "count", count: true, bytes: false };
|
||||
|
||||
var all = dbg.memory.takeCensus({ breakdown: bucket });
|
||||
var allCount = dbg.memory.takeCensus({ breakdown: count }).count;
|
||||
|
||||
var coarse = dbg.memory.takeCensus({
|
||||
breakdown: {
|
||||
by: "coarseType",
|
||||
objects: bucket,
|
||||
strings: bucket,
|
||||
scripts: bucket,
|
||||
other: bucket
|
||||
}
|
||||
});
|
||||
var coarseCount = dbg.memory.takeCensus({
|
||||
breakdown: {
|
||||
by: "coarseType",
|
||||
objects: count,
|
||||
strings: count,
|
||||
scripts: count,
|
||||
other: count
|
||||
}
|
||||
});
|
||||
|
||||
assertEq(all.length > 0, true);
|
||||
assertEq(all.length, allCount);
|
||||
|
||||
assertEq(coarse.objects.length > 0, true);
|
||||
assertEq(coarseCount.objects.count, coarse.objects.length);
|
||||
|
||||
assertEq(coarse.strings.length > 0, true);
|
||||
assertEq(coarseCount.strings.count, coarse.strings.length);
|
||||
|
||||
assertEq(coarse.scripts.length > 0, true);
|
||||
assertEq(coarseCount.scripts.count, coarse.scripts.length);
|
||||
|
||||
assertEq(coarse.other.length > 0, true);
|
||||
assertEq(coarseCount.other.count, coarse.other.length);
|
||||
|
||||
assertEq(all.length >= coarse.objects.length, true);
|
||||
assertEq(all.length >= coarse.strings.length, true);
|
||||
assertEq(all.length >= coarse.scripts.length, true);
|
||||
assertEq(all.length >= coarse.other.length, true);
|
||||
|
||||
function assertIsIdentifier(id) {
|
||||
assertEq(id, Math.floor(id));
|
||||
assertEq(id > 0, true);
|
||||
assertEq(id <= Math.pow(2, 48), true);
|
||||
}
|
||||
|
||||
all.forEach(assertIsIdentifier);
|
||||
coarse.objects.forEach(assertIsIdentifier);
|
||||
coarse.strings.forEach(assertIsIdentifier);
|
||||
coarse.scripts.forEach(assertIsIdentifier);
|
||||
coarse.other.forEach(assertIsIdentifier);
|
|
@ -122,6 +122,60 @@ SimpleCount::report(JSContext* cx, CountBase& countBase, MutableHandleValue repo
|
|||
}
|
||||
|
||||
|
||||
// A count type that collects all matching nodes in a bucket.
|
||||
class BucketCount : public CountType {
|
||||
|
||||
struct Count : CountBase {
|
||||
mozilla::Vector<JS::ubi::Node::Id> ids_;
|
||||
|
||||
explicit Count(BucketCount& count)
|
||||
: CountBase(count),
|
||||
ids_()
|
||||
{ }
|
||||
};
|
||||
|
||||
public:
|
||||
explicit BucketCount()
|
||||
: CountType()
|
||||
{ }
|
||||
|
||||
void destructCount(CountBase& countBase) override {
|
||||
Count& count = static_cast<Count&>(countBase);
|
||||
count.~Count();
|
||||
}
|
||||
|
||||
CountBasePtr makeCount() override { return CountBasePtr(js_new<Count>(*this)); }
|
||||
void traceCount(CountBase& countBase, JSTracer* trc) final { }
|
||||
bool count(CountBase& countBase, mozilla::MallocSizeOf mallocSizeOf, const Node& node) override;
|
||||
bool report(JSContext* cx, CountBase& countBase, MutableHandleValue report) override;
|
||||
};
|
||||
|
||||
bool
|
||||
BucketCount::count(CountBase& countBase, mozilla::MallocSizeOf mallocSizeOf, const Node& node)
|
||||
{
|
||||
Count& count = static_cast<Count&>(countBase);
|
||||
return count.ids_.append(node.identifier());
|
||||
}
|
||||
|
||||
bool
|
||||
BucketCount::report(JSContext* cx, CountBase& countBase, MutableHandleValue report)
|
||||
{
|
||||
Count& count = static_cast<Count&>(countBase);
|
||||
|
||||
size_t length = count.ids_.length();
|
||||
RootedArrayObject arr(cx, NewDenseFullyAllocatedArray(cx, length));
|
||||
if (!arr)
|
||||
return false;
|
||||
arr->ensureDenseInitializedLength(cx, 0, length);
|
||||
|
||||
for (size_t i = 0; i < length; i++)
|
||||
arr->setDenseElement(i, NumberValue(count.ids_[i]));
|
||||
|
||||
report.setObject(*arr);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// A type that categorizes nodes by their JavaScript type -- 'objects',
|
||||
// 'strings', 'scripts', and 'other' -- and then passes the nodes to child
|
||||
// types.
|
||||
|
@ -977,6 +1031,9 @@ ParseBreakdown(JSContext* cx, HandleValue breakdownValue)
|
|||
return simple;
|
||||
}
|
||||
|
||||
if (StringEqualsAscii(by, "bucket"))
|
||||
return CountTypePtr(js_new<BucketCount>());
|
||||
|
||||
if (StringEqualsAscii(by, "objectClass")) {
|
||||
CountTypePtr thenType(ParseChildBreakdown(cx, breakdown, cx->names().then));
|
||||
if (!thenType)
|
||||
|
|
|
@ -26,6 +26,7 @@ public final class PrefsHelper {
|
|||
// Map pref name to ArrayList for multiple observers or PrefHandler for single observer.
|
||||
private static final SimpleArrayMap<String, Object> OBSERVERS = new SimpleArrayMap<>();
|
||||
private static final HashSet<String> INT_TO_STRING_PREFS = new HashSet<>(8);
|
||||
private static final HashSet<String> INT_TO_BOOL_PREFS = new HashSet<>(2);
|
||||
|
||||
static {
|
||||
INT_TO_STRING_PREFS.add("browser.chrome.titlebarMode");
|
||||
|
@ -33,6 +34,7 @@ public final class PrefsHelper {
|
|||
INT_TO_STRING_PREFS.add("font.size.inflation.minTwips");
|
||||
INT_TO_STRING_PREFS.add("home.sync.updateMode");
|
||||
INT_TO_STRING_PREFS.add("browser.image_blocking");
|
||||
INT_TO_BOOL_PREFS.add("browser.display.use_document_fonts");
|
||||
}
|
||||
|
||||
@WrapForJNI
|
||||
|
@ -89,6 +91,9 @@ public final class PrefsHelper {
|
|||
// actual types so we can store them.
|
||||
type = PREF_INT;
|
||||
intVal = Integer.parseInt(String.valueOf(value));
|
||||
} else if (INT_TO_BOOL_PREFS.contains(pref)) {
|
||||
type = PREF_INT;
|
||||
intVal = (Boolean) value ? 1 : 0;
|
||||
} else if (value instanceof Boolean) {
|
||||
type = PREF_BOOL;
|
||||
boolVal = (Boolean) value;
|
||||
|
@ -214,6 +219,9 @@ public final class PrefsHelper {
|
|||
if (INT_TO_STRING_PREFS.contains(pref)) {
|
||||
type = PREF_STRING;
|
||||
strVal = String.valueOf(intVal);
|
||||
} else if (INT_TO_BOOL_PREFS.contains(pref)) {
|
||||
type = PREF_BOOL;
|
||||
boolVal = intVal == 1;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
|
|
|
@ -8,8 +8,7 @@ package org.mozilla.gecko.firstrun;
|
|||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import com.keepsafe.switchboard.SwitchBoard;
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.GeckoSharedPrefs;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
|
@ -28,22 +27,25 @@ public class FirstrunPagerConfig {
|
|||
public static List<FirstrunPanelConfig> getDefault(Context context) {
|
||||
final List<FirstrunPanelConfig> panels = new LinkedList<>();
|
||||
|
||||
if (isInExperimentLocal(context, Experiments.ONBOARDING2_A)) {
|
||||
if (Experiments.isInExperimentLocal(context, Experiments.ONBOARDING2_A)) {
|
||||
panels.add(new FirstrunPanelConfig(WelcomePanel.class.getName(), WelcomePanel.TITLE_RES));
|
||||
Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, Experiments.ONBOARDING2_A);
|
||||
} else if (isInExperimentLocal(context, Experiments.ONBOARDING2_B)) {
|
||||
GeckoSharedPrefs.forProfile(context).edit().putString(Experiments.PREF_ONBOARDING_VERSION, Experiments.ONBOARDING2_A).apply();
|
||||
} else if (Experiments.isInExperimentLocal(context, Experiments.ONBOARDING2_B)) {
|
||||
panels.add(SimplePanelConfigs.urlbarPanelConfig);
|
||||
panels.add(SimplePanelConfigs.bookmarksPanelConfig);
|
||||
panels.add(SimplePanelConfigs.syncPanelConfig);
|
||||
panels.add(new FirstrunPanelConfig(SyncPanel.class.getName(), SyncPanel.TITLE_RES));
|
||||
Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, Experiments.ONBOARDING2_B);
|
||||
} else if (isInExperimentLocal(context, Experiments.ONBOARDING2_C)) {
|
||||
GeckoSharedPrefs.forProfile(context).edit().putString(Experiments.PREF_ONBOARDING_VERSION, Experiments.ONBOARDING2_B).apply();
|
||||
} else if (Experiments.isInExperimentLocal(context, Experiments.ONBOARDING2_C)) {
|
||||
panels.add(SimplePanelConfigs.urlbarPanelConfig);
|
||||
panels.add(SimplePanelConfigs.bookmarksPanelConfig);
|
||||
panels.add(SimplePanelConfigs.dataPanelConfig);
|
||||
panels.add(SimplePanelConfigs.syncPanelConfig);
|
||||
panels.add(new FirstrunPanelConfig(SyncPanel.class.getName(), SyncPanel.TITLE_RES));
|
||||
Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, Experiments.ONBOARDING2_C);
|
||||
GeckoSharedPrefs.forProfile(context).edit().putString(Experiments.PREF_ONBOARDING_VERSION, Experiments.ONBOARDING2_C).apply();
|
||||
} else {
|
||||
Log.d(LOGTAG, "Not in an experiment!");
|
||||
panels.add(new FirstrunPanelConfig(WelcomePanel.class.getName(), WelcomePanel.TITLE_RES));
|
||||
|
@ -52,22 +54,6 @@ public class FirstrunPagerConfig {
|
|||
return panels;
|
||||
}
|
||||
|
||||
/*
|
||||
* Wrapper method for using local bucketing rather than server-side.
|
||||
* This needs to match the server-side bucketing used on mozilla-switchboard.herokuapp.com.
|
||||
*/
|
||||
private static boolean isInExperimentLocal(Context context, String name) {
|
||||
if (AppConstants.MOZ_SWITCHBOARD) {
|
||||
if (SwitchBoard.isInBucket(context, 0, 33)) {
|
||||
return Experiments.ONBOARDING2_A.equals(name);
|
||||
} else if (SwitchBoard.isInBucket(context, 33, 66)) {
|
||||
return Experiments.ONBOARDING2_B.equals(name);
|
||||
} else if (SwitchBoard.isInBucket(context, 66, 100)) {
|
||||
return Experiments.ONBOARDING2_C.equals(name);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static List<FirstrunPanelConfig> getRestricted() {
|
||||
final List<FirstrunPanelConfig> panels = new LinkedList<>();
|
||||
|
|
|
@ -8,12 +8,11 @@ package org.mozilla.gecko.telemetry;
|
|||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
|
||||
import com.keepsafe.switchboard.SwitchBoard;
|
||||
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.Locales;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.telemetry.TelemetryConstants.CorePing;
|
||||
import org.mozilla.gecko.util.Experiments;
|
||||
import org.mozilla.gecko.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -87,9 +86,7 @@ public class TelemetryPingGenerator {
|
|||
ping.put(CorePing.LOCALE, Locales.getLanguageTag(Locale.getDefault()));
|
||||
ping.put(CorePing.OS_VERSION, Integer.toString(Build.VERSION.SDK_INT)); // A String for cross-platform reasons.
|
||||
ping.put(CorePing.SEQ, seq);
|
||||
if (AppConstants.MOZ_SWITCHBOARD) {
|
||||
ping.putArray(CorePing.EXPERIMENTS, SwitchBoard.getActiveExperiments(context));
|
||||
}
|
||||
ping.putArray(CorePing.EXPERIMENTS, Experiments.getActiveExperiments(context));
|
||||
// TODO (bug 1246816): Remove this "optional" parameter work-around when
|
||||
// GeckoProfile.getAndPersistProfileCreationDateFromFilesystem is implemented. That method returns -1
|
||||
// while it's not implemented so we don't include the parameter in the ping if that's the case.
|
||||
|
|
|
@ -4,8 +4,16 @@
|
|||
|
||||
package org.mozilla.gecko.util;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import android.util.Log;
|
||||
import org.mozilla.gecko.mozglue.ContextUtils.SafeIntent;
|
||||
import android.text.TextUtils;
|
||||
import com.keepsafe.switchboard.SwitchBoard;
|
||||
import org.mozilla.gecko.GeckoSharedPrefs;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class should reflect the experiment names found in the Switchboard experiments config here:
|
||||
|
@ -22,6 +30,8 @@ public class Experiments {
|
|||
public static final String ONBOARDING2_B = "onboarding2-b"; // 4 static Feature slides
|
||||
public static final String ONBOARDING2_C = "onboarding2-c"; // 4 static + 1 clickable (Data saving) Feature slides
|
||||
|
||||
public static final String PREF_ONBOARDING_VERSION = "onboarding_version";
|
||||
|
||||
// Show search mode (instead of home panels) when tapping on urlbar if there is a search term in the urlbar.
|
||||
public static final String SEARCH_TERM = "search-term";
|
||||
|
||||
|
@ -55,4 +65,38 @@ public class Experiments {
|
|||
disabled = false;
|
||||
return disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if a user is in certain local experiment.
|
||||
* @param experiment Name of experiment to look up
|
||||
* @return returns value for experiment or false if experiment does not exist.
|
||||
*/
|
||||
public static boolean isInExperimentLocal(Context context, String experiment) {
|
||||
if (SwitchBoard.isInBucket(context, 0, 33)) {
|
||||
return Experiments.ONBOARDING2_A.equals(experiment);
|
||||
} else if (SwitchBoard.isInBucket(context, 33, 66)) {
|
||||
return Experiments.ONBOARDING2_B.equals(experiment);
|
||||
} else if (SwitchBoard.isInBucket(context, 66, 100)) {
|
||||
return Experiments.ONBOARDING2_C.equals(experiment);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of all active experiments, remote and local.
|
||||
* @return List of experiment names Strings
|
||||
*/
|
||||
public static List<String> getActiveExperiments(Context c) {
|
||||
final List<String> experiments = new LinkedList<>();
|
||||
experiments.addAll(SwitchBoard.getActiveExperiments(c));
|
||||
|
||||
// Add onboarding version.
|
||||
final String onboardingExperiment = GeckoSharedPrefs.forProfile(c).getString(Experiments.PREF_ONBOARDING_VERSION, null);
|
||||
if (!TextUtils.isEmpty(onboardingExperiment)) {
|
||||
experiments.add(onboardingExperiment);
|
||||
}
|
||||
|
||||
return experiments;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -230,6 +230,8 @@
|
|||
<!ENTITY pref_tap_to_load_images_data "Only over Wi-Fi">
|
||||
<!ENTITY pref_tap_to_load_images_disabled2 "Blocked">
|
||||
|
||||
<!ENTITY pref_show_web_fonts "Show web fonts">
|
||||
|
||||
<!ENTITY pref_tracking_protection_title "Tracking protection">
|
||||
<!ENTITY pref_tracking_protection_summary3 "Enabled in Private Browsing">
|
||||
<!ENTITY pref_donottrack_title "Do not track">
|
||||
|
|
|
@ -99,7 +99,6 @@ gujar.sources += ['java/org/mozilla/gecko/' + x for x in [
|
|||
'util/ColorUtils.java',
|
||||
'util/DrawableUtil.java',
|
||||
'util/EventCallback.java',
|
||||
'util/Experiments.java',
|
||||
'util/FileUtils.java',
|
||||
'util/FloatUtils.java',
|
||||
'util/GamepadUtils.java',
|
||||
|
@ -578,6 +577,7 @@ gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
|
|||
'trackingprotection/TrackingProtectionPrompt.java',
|
||||
'updater/UpdateService.java',
|
||||
'updater/UpdateServiceHelper.java',
|
||||
'util/Experiments.java',
|
||||
'Webapp.java',
|
||||
'webapp/Allocator.java',
|
||||
'webapp/ApkResources.java',
|
||||
|
|
|
@ -38,6 +38,9 @@
|
|||
android:entryValues="@array/pref_browser_image_blocking_values"
|
||||
android:persistent="false" />
|
||||
|
||||
<CheckBoxPreference android:key="browser.display.use_document_fonts"
|
||||
android:title="@string/pref_show_web_fonts" />
|
||||
|
||||
<ListPreference android:key="plugin.enable"
|
||||
android:title="@string/pref_plugins"
|
||||
android:entries="@array/pref_plugins_entries"
|
||||
|
|
|
@ -214,6 +214,8 @@
|
|||
<string name="pref_tap_to_load_images_data">&pref_tap_to_load_images_data;</string>
|
||||
<string name="pref_tap_to_load_images_disabled2">&pref_tap_to_load_images_disabled2;</string>
|
||||
|
||||
<string name="pref_show_web_fonts">&pref_show_web_fonts;</string>
|
||||
|
||||
<string name="pref_tracking_protection_title">&pref_tracking_protection_title;</string>
|
||||
<string name="pref_tracking_protection_summary">&pref_tracking_protection_summary3;</string>
|
||||
<string name="pref_donottrack_title">&pref_donottrack_title;</string>
|
||||
|
|
|
@ -30,9 +30,9 @@ interface nsIAlertNotification : nsISupports
|
|||
[optional] in boolean inPrivateBrowsing);
|
||||
|
||||
/**
|
||||
* The name of the notification. This is currently only used on Android and
|
||||
* OS X. On Android, the name is hashed and used as a notification ID.
|
||||
* Notifications will replace previous notifications with the same name.
|
||||
* The name of the notification. On Android, the name is hashed and used as
|
||||
* a notification ID. Notifications will replace previous notifications with
|
||||
* the same name.
|
||||
*/
|
||||
readonly attribute AString name;
|
||||
|
||||
|
@ -118,32 +118,11 @@ interface nsIAlertsService : nsISupports
|
|||
void showAlert(in nsIAlertNotification alert,
|
||||
[optional] in nsIObserver alertListener);
|
||||
/**
|
||||
* Displays a sliding notification window.
|
||||
* Initializes and shows an |nsIAlertNotification| with the given parameters.
|
||||
*
|
||||
* @param imageUrl A URL identifying the image to put in the alert.
|
||||
* The OS X implemenation limits the amount of time it
|
||||
* will wait for an icon to load to six seconds. After
|
||||
* that time the alert will show with no icon.
|
||||
* @param title The title for the alert.
|
||||
* @param text The contents of the alert.
|
||||
* @param textClickable If true, causes the alert text to look like a link
|
||||
* and notifies the listener when user attempts to
|
||||
* click the alert text.
|
||||
* @param cookie A blind cookie the alert will pass back to the
|
||||
* consumer during the alert listener callbacks.
|
||||
* @param alertListener Used for callbacks. May be null if the caller
|
||||
* doesn't care about callbacks.
|
||||
* @param name The name of the notification. This is currently only
|
||||
* used on Android and OS X. On Android the name is
|
||||
* hashed and used as a notification ID. Notifications
|
||||
* will replace previous notifications with the same name.
|
||||
* @param dir Bidi override for the title. Valid values are
|
||||
* "auto", "ltr" or "rtl". Only available on supported
|
||||
* platforms.
|
||||
* @param lang Language of title and text of the alert. Only available
|
||||
* on supported platforms.
|
||||
* @param inPrivateBrowsing If set to true, imageUrl will be loaded in private
|
||||
* browsing mode.
|
||||
* @see nsIAlertNotification for descriptions of all other parameters.
|
||||
* @throws NS_ERROR_NOT_AVAILABLE If the notification cannot be displayed.
|
||||
*
|
||||
* The following arguments will be passed to the alertListener's observe()
|
||||
|
|
|
@ -105,7 +105,7 @@ extensions.registerPrivilegedAPI("alarms", (extension, context) => {
|
|||
alarmsMap.get(extension).add(alarm);
|
||||
},
|
||||
|
||||
get: function(args) {
|
||||
get: function(...args) {
|
||||
let name = "", callback;
|
||||
if (args.length == 1) {
|
||||
callback = args[0];
|
||||
|
@ -127,7 +127,7 @@ extensions.registerPrivilegedAPI("alarms", (extension, context) => {
|
|||
|
||||
getAll: function(callback) {
|
||||
let alarms = alarmsMap.get(extension);
|
||||
let result = alarms.map(alarm => alarm.data);
|
||||
let result = Array.from(alarms, alarm => alarm.data);
|
||||
return context.wrapPromise(Promise.resolve(result), callback);
|
||||
},
|
||||
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче