зеркало из 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)) {
|
checkEmptyPageOrigin(gBrowser.selectedBrowser, uri)) {
|
||||||
value = "";
|
value = "";
|
||||||
} else {
|
} 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);
|
valid = !isBlankPageURL(uri.spec);
|
||||||
|
|
|
@ -8,5 +8,6 @@ support-files =
|
||||||
[browser_notification_open_settings.js]
|
[browser_notification_open_settings.js]
|
||||||
[browser_notification_remove_permission.js]
|
[browser_notification_remove_permission.js]
|
||||||
[browser_notification_permission_migration.js]
|
[browser_notification_permission_migration.js]
|
||||||
|
[browser_notification_replace.js]
|
||||||
[browser_notification_tab_switching.js]
|
[browser_notification_tab_switching.js]
|
||||||
skip-if = buildapp == 'mulet'
|
skip-if = buildapp == 'mulet'
|
||||||
|
|
|
@ -28,7 +28,7 @@ add_task(function* test_notificationClose() {
|
||||||
let alertTitleLabel = alertWindow.document.getElementById("alertTitleLabel");
|
let alertTitleLabel = alertWindow.document.getElementById("alertTitleLabel");
|
||||||
is(alertTitleLabel.value, "Test title", "Title text of notification should be present");
|
is(alertTitleLabel.value, "Test title", "Title text of notification should be present");
|
||||||
let alertTextLabel = alertWindow.document.getElementById("alertTextLabel");
|
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");
|
let alertCloseButton = alertWindow.document.querySelector(".alertCloseButton");
|
||||||
is(alertCloseButton.localName, "toolbarbutton", "close button found");
|
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 = {
|
var options = {
|
||||||
dir: undefined,
|
dir: undefined,
|
||||||
lang: undefined,
|
lang: undefined,
|
||||||
body: "Test body",
|
body: "Test body 1",
|
||||||
tag: "Test tag",
|
tag: "Test tag",
|
||||||
icon: undefined,
|
icon: undefined,
|
||||||
};
|
};
|
||||||
|
@ -23,7 +23,7 @@ function showNotification2() {
|
||||||
var options = {
|
var options = {
|
||||||
dir: undefined,
|
dir: undefined,
|
||||||
lang: undefined,
|
lang: undefined,
|
||||||
body: "Test body",
|
body: "Test body 2",
|
||||||
tag: "Test tag",
|
tag: "Test tag",
|
||||||
icon: undefined,
|
icon: undefined,
|
||||||
};
|
};
|
||||||
|
|
|
@ -60,6 +60,9 @@ function test() {
|
||||||
testVal("<user:pass@sub1.sub2.sub3.>mozilla.org");
|
testVal("<user:pass@sub1.sub2.sub3.>mozilla.org");
|
||||||
testVal("<user:pass@>mozilla.org");
|
testVal("<user:pass@>mozilla.org");
|
||||||
|
|
||||||
|
testVal("<https://>mozilla.org< >");
|
||||||
|
testVal("mozilla.org< >");
|
||||||
|
|
||||||
testVal("<https://>mozilla.org</file.ext>");
|
testVal("<https://>mozilla.org</file.ext>");
|
||||||
testVal("<https://>mozilla.org</sub/file.ext>");
|
testVal("<https://>mozilla.org</sub/file.ext>");
|
||||||
testVal("<https://>mozilla.org</sub/file.ext?foo>");
|
testVal("<https://>mozilla.org</sub/file.ext?foo>");
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
[browser_moz_action_link.js]
|
||||||
[browser_urlbar_blanking.js]
|
[browser_urlbar_blanking.js]
|
||||||
support-files =
|
support-files =
|
||||||
file_blank_but_not_blank.html
|
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;
|
trimmedLength = "http://".length;
|
||||||
}
|
}
|
||||||
|
|
||||||
let matchedURL = value.match(/^((?:[a-z]+:\/\/)(?:[^\/#?]+@)?)(.+?)(?::\d+)?(?:[\/#?]|$)/);
|
let matchedURL = value.match(/^((?:[a-z]+:\/\/)(?:[^\/#?]+@)?)(\S+?)(?::\d+)?\s*(?:[\/#?]|$)/);
|
||||||
if (!matchedURL)
|
if (!matchedURL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -839,12 +839,13 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
<method name="_parseActionUrl">
|
<method name="_parseActionUrl">
|
||||||
<parameter name="aUrl"/>
|
<parameter name="aUrl"/>
|
||||||
<body><![CDATA[
|
<body><![CDATA[
|
||||||
if (!aUrl.startsWith("moz-action:"))
|
const MOZ_ACTION_REGEX = /^moz-action:([^,]+),(.*)$/;
|
||||||
|
if (!MOZ_ACTION_REGEX.test(aUrl))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
// URL is in the format moz-action:ACTION,PARAMS
|
// URL is in the format moz-action:ACTION,PARAMS
|
||||||
// Where PARAMS is a JSON encoded object.
|
// Where PARAMS is a JSON encoded object.
|
||||||
let [, type, params] = aUrl.match(/^moz-action:([^,]+),(.*)$/);
|
let [, type, params] = aUrl.match(MOZ_ACTION_REGEX);
|
||||||
|
|
||||||
let action = {
|
let action = {
|
||||||
type: type,
|
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() {
|
Mozilla.UITour.resetFirefox = function() {
|
||||||
|
|
|
@ -610,8 +610,15 @@ this.UITour = {
|
||||||
case "showFirefoxAccounts": {
|
case "showFirefoxAccounts": {
|
||||||
// 'signup' is the only action that makes sense currently, so we don't
|
// 'signup' is the only action that makes sense currently, so we don't
|
||||||
// accept arbitrary actions just to be safe...
|
// 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.
|
// We want to replace the current tab.
|
||||||
browser.loadURI("about:accounts?action=signup&entrypoint=uitour");
|
browser.loadURI("about:accounts?" + p.toString());
|
||||||
break;
|
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) {
|
setTelemetryBucket: function(aPageID) {
|
||||||
let bucket = BUCKET_NAME + BrowserUITelemetry.BUCKET_SEPARATOR + aPageID;
|
let bucket = BUCKET_NAME + BrowserUITelemetry.BUCKET_SEPARATOR + aPageID;
|
||||||
BrowserUITelemetry.setBucket(bucket);
|
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...
|
// 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 gContentAPI.showFirefoxAccounts();
|
||||||
yield BrowserTestUtils.browserLoaded(gTestTab.linkedBrowser, false,
|
yield BrowserTestUtils.browserLoaded(gTestTab.linkedBrowser, false,
|
||||||
"about:accounts?action=signup&entrypoint=uitour");
|
"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;
|
max-width: 800px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Prefs */
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Targets */
|
/* Targets */
|
||||||
|
|
||||||
.targets {
|
.targets {
|
||||||
|
@ -88,3 +81,8 @@ label {
|
||||||
.addons-options {
|
.addons-options {
|
||||||
flex: 1;
|
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(
|
const Strings = Services.strings.createBundle(
|
||||||
"chrome://devtools/locale/aboutdebugging.properties");
|
"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({
|
exports.AddonsControls = React.createClass({
|
||||||
displayName: "AddonsControls",
|
displayName: "AddonsControls",
|
||||||
|
|
||||||
|
@ -33,9 +36,14 @@ exports.AddonsControls = React.createClass({
|
||||||
onChange: this.onEnableAddonDebuggingChange,
|
onChange: this.onEnableAddonDebuggingChange,
|
||||||
}),
|
}),
|
||||||
React.createElement("label", {
|
React.createElement("label", {
|
||||||
|
className: "addons-debugging-label",
|
||||||
htmlFor: "enable-addon-debugging",
|
htmlFor: "enable-addon-debugging",
|
||||||
title: Strings.GetStringFromName("addonDebugging.tooltip")
|
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", {
|
React.createElement("button", {
|
||||||
id: "load-addon-from-file",
|
id: "load-addon-from-file",
|
||||||
|
@ -47,6 +55,7 @@ exports.AddonsControls = React.createClass({
|
||||||
onEnableAddonDebuggingChange(event) {
|
onEnableAddonDebuggingChange(event) {
|
||||||
let enabled = event.target.checked;
|
let enabled = event.target.checked;
|
||||||
Services.prefs.setBoolPref("devtools.chrome.enabled", enabled);
|
Services.prefs.setBoolPref("devtools.chrome.enabled", enabled);
|
||||||
|
Services.prefs.setBoolPref("devtools.debugger.remote-enabled", enabled);
|
||||||
},
|
},
|
||||||
|
|
||||||
loadAddonFromFile(event) {
|
loadAddonFromFile(event) {
|
||||||
|
|
|
@ -23,6 +23,9 @@ const ExtensionIcon = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
|
||||||
const Strings = Services.strings.createBundle(
|
const Strings = Services.strings.createBundle(
|
||||||
"chrome://devtools/locale/aboutdebugging.properties");
|
"chrome://devtools/locale/aboutdebugging.properties");
|
||||||
|
|
||||||
|
const CHROME_ENABLED_PREF = "devtools.chrome.enabled";
|
||||||
|
const REMOTE_ENABLED_PREF = "devtools.debugger.remote-enabled";
|
||||||
|
|
||||||
exports.AddonsTab = React.createClass({
|
exports.AddonsTab = React.createClass({
|
||||||
displayName: "AddonsTab",
|
displayName: "AddonsTab",
|
||||||
|
|
||||||
|
@ -35,15 +38,21 @@ exports.AddonsTab = React.createClass({
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
AddonManager.addAddonListener(this);
|
AddonManager.addAddonListener(this);
|
||||||
Services.prefs.addObserver("devtools.chrome.enabled",
|
|
||||||
|
Services.prefs.addObserver(CHROME_ENABLED_PREF,
|
||||||
this.updateDebugStatus, false);
|
this.updateDebugStatus, false);
|
||||||
|
Services.prefs.addObserver(REMOTE_ENABLED_PREF,
|
||||||
|
this.updateDebugStatus, false);
|
||||||
|
|
||||||
this.updateDebugStatus();
|
this.updateDebugStatus();
|
||||||
this.updateAddonsList();
|
this.updateAddonsList();
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
AddonManager.removeAddonListener(this);
|
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);
|
this.updateDebugStatus);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -68,9 +77,11 @@ exports.AddonsTab = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
updateDebugStatus() {
|
updateDebugStatus() {
|
||||||
this.setState({
|
let debugDisabled =
|
||||||
debugDisabled: !Services.prefs.getBoolPref("devtools.chrome.enabled")
|
!Services.prefs.getBoolPref(CHROME_ENABLED_PREF) ||
|
||||||
});
|
!Services.prefs.getBoolPref(REMOTE_ENABLED_PREF);
|
||||||
|
|
||||||
|
this.setState({ debugDisabled });
|
||||||
},
|
},
|
||||||
|
|
||||||
updateAddonsList() {
|
updateAddonsList() {
|
||||||
|
|
|
@ -8,6 +8,7 @@ support-files =
|
||||||
service-workers/empty-sw.html
|
service-workers/empty-sw.html
|
||||||
service-workers/empty-sw.js
|
service-workers/empty-sw.js
|
||||||
|
|
||||||
|
[browser_addons_debugging_initial_state.js]
|
||||||
[browser_addons_install.js]
|
[browser_addons_install.js]
|
||||||
[browser_addons_toggle_debug.js]
|
[browser_addons_toggle_debug.js]
|
||||||
[browser_service_workers.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 => {
|
yield new Promise(resolve => {
|
||||||
let options = {"set": [
|
let options = {"set": [
|
||||||
["devtools.chrome.enabled", false],
|
["devtools.chrome.enabled", false],
|
||||||
|
["devtools.debugger.remote-enabled", false],
|
||||||
]};
|
]};
|
||||||
SpecialPowers.pushPrefEnv(options, resolve);
|
SpecialPowers.pushPrefEnv(options, resolve);
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,6 +9,11 @@ Services.scriptloader.loadSubScript(
|
||||||
"chrome://mochitests/content/browser/devtools/client/inspector/test/head.js",
|
"chrome://mochitests/content/browser/devtools/client/inspector/test/head.js",
|
||||||
this);
|
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
|
* Adds a new tab with the given URL, opens the inspector and selects the
|
||||||
* font-inspector tab.
|
* font-inspector tab.
|
||||||
|
|
|
@ -216,6 +216,7 @@ devtools.jar:
|
||||||
skin/styleeditor.css (themes/styleeditor.css)
|
skin/styleeditor.css (themes/styleeditor.css)
|
||||||
skin/webaudioeditor.css (themes/webaudioeditor.css)
|
skin/webaudioeditor.css (themes/webaudioeditor.css)
|
||||||
skin/components-frame.css (themes/components-frame.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/jit-optimizations.css (themes/jit-optimizations.css)
|
||||||
skin/images/magnifying-glass.png (themes/images/magnifying-glass.png)
|
skin/images/magnifying-glass.png (themes/images/magnifying-glass.png)
|
||||||
skin/images/magnifying-glass@2x.png (themes/images/magnifying-glass@2x.png)
|
skin/images/magnifying-glass@2x.png (themes/images/magnifying-glass@2x.png)
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
define(function(require, exports, module) {
|
define(function(require, exports, module) {
|
||||||
|
|
||||||
const React = require("devtools/client/shared/vendor/react");
|
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 { Headers } = createFactories(require("./headers"));
|
||||||
const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
|
const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
define(function(require, exports, module) {
|
define(function(require, exports, module) {
|
||||||
|
|
||||||
const React = require("devtools/client/shared/vendor/react");
|
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 { TreeView } = createFactories(require("./reps/tree-view"));
|
||||||
const { SearchBox } = createFactories(require("./search-box"));
|
const { SearchBox } = createFactories(require("./search-box"));
|
||||||
const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
|
const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
define(function(require, exports, module) {
|
define(function(require, exports, module) {
|
||||||
|
|
||||||
const React = require("devtools/client/shared/vendor/react");
|
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 { JsonPanel } = createFactories(require("./json-panel"));
|
||||||
const { TextPanel } = createFactories(require("./text-panel"));
|
const { TextPanel } = createFactories(require("./text-panel"));
|
||||||
const { HeadersPanel } = createFactories(require("./headers-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/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
DevToolsModules(
|
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',
|
'tabs.js',
|
||||||
'toolbar.js',
|
'toolbar.js',
|
||||||
'tree-view.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
|
// Dependencies
|
||||||
const React = require("devtools/client/shared/vendor/react");
|
const React = require("devtools/client/shared/vendor/react");
|
||||||
const { createFactories } = require("./rep-utils");
|
const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
|
||||||
const { Rep } = createFactories(require("./rep"));
|
const { Rep } = createFactories(require("devtools/client/shared/components/reps/rep"));
|
||||||
const { StringRep } = require("./string");
|
const { StringRep } = require("devtools/client/shared/components/reps/string");
|
||||||
const DOM = React.DOM;
|
const DOM = React.DOM;
|
||||||
|
|
||||||
var uid = 0;
|
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) {
|
define(function(require, exports, module) {
|
||||||
|
|
||||||
const React = require("devtools/client/shared/vendor/react");
|
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 { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
|
||||||
const DOM = React.DOM;
|
const DOM = React.DOM;
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,9 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* 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 "general.css";
|
||||||
@import "reps.css";
|
|
||||||
@import "dom-tree.css";
|
@import "dom-tree.css";
|
||||||
@import "search-box.css";
|
@import "search-box.css";
|
||||||
@import "tabs.css";
|
@import "tabs.css";
|
||||||
|
|
|
@ -14,7 +14,6 @@ DevToolsModules(
|
||||||
'json-panel.css',
|
'json-panel.css',
|
||||||
'main.css',
|
'main.css',
|
||||||
'read-only-prop.svg',
|
'read-only-prop.svg',
|
||||||
'reps.css',
|
|
||||||
'search-box.css',
|
'search-box.css',
|
||||||
'search.svg',
|
'search.svg',
|
||||||
'tabs.css',
|
'tabs.css',
|
||||||
|
|
|
@ -8,7 +8,7 @@ define(function(require, exports, module) {
|
||||||
|
|
||||||
// ReactJS
|
// ReactJS
|
||||||
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
|
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 { MainTabbedArea } = createFactories(require("./components/main-tabbed-area"));
|
||||||
|
|
||||||
const json = document.getElementById("json");
|
const json = document.getElementById("json");
|
||||||
|
|
|
@ -7,6 +7,7 @@ debug = Debug
|
||||||
addons = Add-ons
|
addons = Add-ons
|
||||||
addonDebugging.label = Enable add-on debugging
|
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.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
|
loadTemporaryAddon = Load Temporary Add-on
|
||||||
extensions = Extensions
|
extensions = Extensions
|
||||||
selectAddonFromFile = Select Add-on Directory or XPI File
|
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
|
# LOCALIZATION NOTE (heapview.field.name.tooltip): The tooltip for the column
|
||||||
# header in the heap view for name.
|
# header in the heap view for name.
|
||||||
heapview.field.name.tooltip=The name of this group
|
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',
|
'inverted.js',
|
||||||
'io.js',
|
'io.js',
|
||||||
'refresh.js',
|
'refresh.js',
|
||||||
|
'sizes.js',
|
||||||
'snapshot.js',
|
'snapshot.js',
|
||||||
'view.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,
|
focusDominatorTreeNode,
|
||||||
} = require("./actions/snapshot");
|
} = require("./actions/snapshot");
|
||||||
const { changeViewAndRefresh } = require("./actions/view");
|
const { changeViewAndRefresh } = require("./actions/view");
|
||||||
|
const { resizeShortestPaths } = require("./actions/sizes");
|
||||||
const {
|
const {
|
||||||
breakdownNameToSpec,
|
breakdownNameToSpec,
|
||||||
getBreakdownDisplayData,
|
getBreakdownDisplayData,
|
||||||
|
@ -113,7 +114,7 @@ const MemoryApp = createClass({
|
||||||
filter,
|
filter,
|
||||||
diffing,
|
diffing,
|
||||||
view,
|
view,
|
||||||
dominatorTreeBreakdown
|
sizes,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const selectedSnapshot = snapshots.find(s => s.selected);
|
const selectedSnapshot = snapshots.find(s => s.selected);
|
||||||
|
@ -237,6 +238,10 @@ const MemoryApp = createClass({
|
||||||
"...and that snapshot should have a dominator tree");
|
"...and that snapshot should have a dominator tree");
|
||||||
dispatch(focusDominatorTreeNode(selectedSnapshot.id, node));
|
dispatch(focusDominatorTreeNode(selectedSnapshot.id, node));
|
||||||
},
|
},
|
||||||
|
onShortestPathsResize: newSize => {
|
||||||
|
dispatch(resizeShortestPaths(newSize));
|
||||||
|
},
|
||||||
|
sizes,
|
||||||
view,
|
view,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
|
@ -25,11 +25,11 @@ const CensusTreeItem = module.exports = createClass({
|
||||||
depth,
|
depth,
|
||||||
arrow,
|
arrow,
|
||||||
focused,
|
focused,
|
||||||
toolbox,
|
|
||||||
getPercentBytes,
|
getPercentBytes,
|
||||||
getPercentCount,
|
getPercentCount,
|
||||||
showSign,
|
showSign,
|
||||||
onViewSourceInDebugger,
|
onViewSourceInDebugger,
|
||||||
|
inverted,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const bytes = formatNumber(item.bytes, showSign);
|
const bytes = formatNumber(item.bytes, showSign);
|
||||||
|
@ -44,7 +44,14 @@ const CensusTreeItem = module.exports = createClass({
|
||||||
const totalCount = formatNumber(item.totalCount, showSign);
|
const totalCount = formatNumber(item.totalCount, showSign);
|
||||||
const percentTotalCount = formatPercent(getPercentCount(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-item-field heap-tree-item-bytes" },
|
||||||
dom.span({ className: "heap-tree-number" }, bytes),
|
dom.span({ className: "heap-tree-number" }, bytes),
|
||||||
dom.span({ className: "heap-tree-percent" }, percentBytes)),
|
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",
|
dom.span({ className: "heap-tree-item-field heap-tree-item-name",
|
||||||
style: { marginLeft: depth * TREE_ROW_HEIGHT }},
|
style: { marginLeft: depth * TREE_ROW_HEIGHT }},
|
||||||
arrow,
|
arrow,
|
||||||
|
pointer,
|
||||||
this.toLabel(item.name, onViewSourceInDebugger)
|
this.toLabel(item.name, onViewSourceInDebugger)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
@ -66,6 +66,7 @@ const Census = module.exports = createClass({
|
||||||
getPercentBytes,
|
getPercentBytes,
|
||||||
getPercentCount,
|
getPercentCount,
|
||||||
showSign: !!diffing,
|
showSign: !!diffing,
|
||||||
|
inverted: census.inverted,
|
||||||
}),
|
}),
|
||||||
getRoots: () => report.children || [],
|
getRoots: () => report.children || [],
|
||||||
getKey: node => node.id,
|
getKey: node => node.id,
|
||||||
|
|
|
@ -8,6 +8,8 @@ const Census = createFactory(require("./census"));
|
||||||
const CensusHeader = createFactory(require("./census-header"));
|
const CensusHeader = createFactory(require("./census-header"));
|
||||||
const DominatorTree = createFactory(require("./dominator-tree"));
|
const DominatorTree = createFactory(require("./dominator-tree"));
|
||||||
const DominatorTreeHeader = createFactory(require("./dominator-tree-header"));
|
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 { getStatusTextFull, L10N } = require("../utils");
|
||||||
const { snapshotState: states, diffingState, viewState, dominatorTreeState } = require("../constants");
|
const { snapshotState: states, diffingState, viewState, dominatorTreeState } = require("../constants");
|
||||||
const { snapshot: snapshotModel, diffingModel } = require("../models");
|
const { snapshot: snapshotModel, diffingModel } = require("../models");
|
||||||
|
@ -145,10 +147,12 @@ const Heap = module.exports = createClass({
|
||||||
onDominatorTreeCollapse: PropTypes.func.isRequired,
|
onDominatorTreeCollapse: PropTypes.func.isRequired,
|
||||||
onCensusFocus: PropTypes.func.isRequired,
|
onCensusFocus: PropTypes.func.isRequired,
|
||||||
onDominatorTreeFocus: PropTypes.func.isRequired,
|
onDominatorTreeFocus: PropTypes.func.isRequired,
|
||||||
|
onShortestPathsResize: PropTypes.func.isRequired,
|
||||||
snapshot: snapshotModel,
|
snapshot: snapshotModel,
|
||||||
onViewSourceInDebugger: PropTypes.func.isRequired,
|
onViewSourceInDebugger: PropTypes.func.isRequired,
|
||||||
diffing: diffingModel,
|
diffing: diffingModel,
|
||||||
view: PropTypes.string.isRequired,
|
view: PropTypes.string.isRequired,
|
||||||
|
sizes: PropTypes.object.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -277,8 +281,13 @@ const Heap = module.exports = createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_renderDominatorTree(state, onViewSourceInDebugger, dominatorTree, onLoadMoreSiblings) {
|
_renderDominatorTree(state, onViewSourceInDebugger, dominatorTree, onLoadMoreSiblings) {
|
||||||
return this._renderHeapView(
|
const tree = dom.div(
|
||||||
state,
|
{
|
||||||
|
className: "vbox",
|
||||||
|
style: {
|
||||||
|
overflowY: "auto"
|
||||||
|
}
|
||||||
|
},
|
||||||
DominatorTreeHeader(),
|
DominatorTreeHeader(),
|
||||||
DominatorTree({
|
DominatorTree({
|
||||||
onViewSourceInDebugger,
|
onViewSourceInDebugger,
|
||||||
|
@ -289,5 +298,21 @@ const Heap = module.exports = createClass({
|
||||||
onFocus: this.props.onDominatorTreeFocus,
|
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',
|
'dominator-tree.js',
|
||||||
'heap.js',
|
'heap.js',
|
||||||
'list.js',
|
'list.js',
|
||||||
|
'shortest-paths.js',
|
||||||
'snapshot-list-item.js',
|
'snapshot-list-item.js',
|
||||||
'toolbar.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.EXPAND_DOMINATOR_TREE_NODE = "expand-dominator-tree-node";
|
||||||
actions.COLLAPSE_DOMINATOR_TREE_NODE = "collapse-dominator-tree-node";
|
actions.COLLAPSE_DOMINATOR_TREE_NODE = "collapse-dominator-tree-node";
|
||||||
|
|
||||||
|
actions.RESIZE_SHORTEST_PATHS = "resize-shortest-paths";
|
||||||
|
|
||||||
/*** Breakdowns ***************************************************************/
|
/*** Breakdowns ***************************************************************/
|
||||||
|
|
||||||
const COUNT = { by: "count", count: true, bytes: true };
|
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/widgets.css" type="text/css"/>
|
||||||
<link rel="stylesheet" href="chrome://devtools/skin/memory.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"/>
|
<link rel="stylesheet" href="chrome://devtools/skin/components-frame.css" type="text/css"/>
|
||||||
|
<link rel="stylesheet" href="chrome://devtools/skin/components-h-split-box.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>
|
|
||||||
</head>
|
</head>
|
||||||
<body class="theme-body">
|
<body class="theme-body">
|
||||||
<div id="app">
|
<div id="app"></div>
|
||||||
</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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -10,5 +10,6 @@ exports.dominatorTreeBreakdown = require("./reducers/dominatorTreeBreakdown");
|
||||||
exports.errors = require("./reducers/errors");
|
exports.errors = require("./reducers/errors");
|
||||||
exports.filter = require("./reducers/filter");
|
exports.filter = require("./reducers/filter");
|
||||||
exports.inverted = require("./reducers/inverted");
|
exports.inverted = require("./reducers/inverted");
|
||||||
|
exports.sizes = require("./reducers/sizes");
|
||||||
exports.snapshots = require("./reducers/snapshots");
|
exports.snapshots = require("./reducers/snapshots");
|
||||||
exports.view = require("./reducers/view");
|
exports.view = require("./reducers/view");
|
||||||
|
|
|
@ -11,6 +11,7 @@ DevToolsModules(
|
||||||
'errors.js',
|
'errors.js',
|
||||||
'filter.js',
|
'filter.js',
|
||||||
'inverted.js',
|
'inverted.js',
|
||||||
|
'sizes.js',
|
||||||
'snapshots.js',
|
'snapshots.js',
|
||||||
'view.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")];
|
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");
|
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'");
|
for (let el of nameElems) {
|
||||||
ok(nameElems.some(e => e.textContent.trim() === "strings"), "One for coarse type 'strings'");
|
dumpn(`Found ${el.textContent.trim()}`);
|
||||||
ok(nameElems.some(e => e.textContent.trim() === "other"), "One for coarse type 'other'");
|
}
|
||||||
|
|
||||||
|
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) {
|
for (let e of nameElems) {
|
||||||
is(e.style.marginLeft, "0px",
|
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
|
* Returns a promise that will resolve when the provided store matches
|
||||||
* the expected array. expectedStates is an array of dominatorTree states.
|
* the expected array. expectedStates is an array of dominatorTree states.
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
support-files =
|
support-files =
|
||||||
head.js
|
head.js
|
||||||
|
|
||||||
|
[test_CensusTreeItem_01.html]
|
||||||
[test_DominatorTree_01.html]
|
[test_DominatorTree_01.html]
|
||||||
[test_DominatorTree_02.html]
|
[test_DominatorTree_02.html]
|
||||||
[test_DominatorTree_03.html]
|
[test_DominatorTree_03.html]
|
||||||
|
@ -10,5 +11,7 @@ support-files =
|
||||||
[test_Heap_02.html]
|
[test_Heap_02.html]
|
||||||
[test_Heap_03.html]
|
[test_Heap_03.html]
|
||||||
[test_Heap_04.html]
|
[test_Heap_04.html]
|
||||||
|
[test_ShortestPaths_01.html]
|
||||||
|
[test_ShortestPaths_02.html]
|
||||||
[test_Toolbar_01.html]
|
[test_Toolbar_01.html]
|
||||||
[test_Toolbar_02.html]
|
[test_Toolbar_02.html]
|
||||||
|
|
|
@ -28,6 +28,7 @@ var {
|
||||||
const {
|
const {
|
||||||
getBreakdownDisplayData,
|
getBreakdownDisplayData,
|
||||||
getDominatorTreeBreakdownDisplayData,
|
getDominatorTreeBreakdownDisplayData,
|
||||||
|
L10N,
|
||||||
} = require("devtools/client/memory/utils");
|
} = require("devtools/client/memory/utils");
|
||||||
|
|
||||||
var models = require("devtools/client/memory/models");
|
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 React = require("devtools/client/shared/vendor/react");
|
||||||
var ReactDOM = require("devtools/client/shared/vendor/react-dom");
|
var ReactDOM = require("devtools/client/shared/vendor/react-dom");
|
||||||
var Heap = React.createFactory(require("devtools/client/memory/components/heap"));
|
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 DominatorTreeComponent = React.createFactory(require("devtools/client/memory/components/dominator-tree"));
|
||||||
var DominatorTreeItem = React.createFactory(require("devtools/client/memory/components/dominator-tree-item"));
|
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"));
|
var Toolbar = React.createFactory(require("devtools/client/memory/components/toolbar"));
|
||||||
|
|
||||||
// All tests are asynchronous.
|
// All tests are asynchronous.
|
||||||
|
@ -44,6 +47,33 @@ SimpleTest.waitForExplicitFinish();
|
||||||
|
|
||||||
var noop = () => {};
|
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.
|
// Counter for mock DominatorTreeNode ids.
|
||||||
var TEST_NODE_ID_COUNTER = 0;
|
var TEST_NODE_ID_COUNTER = 0;
|
||||||
|
|
||||||
|
@ -105,6 +135,21 @@ var TEST_DOMINATOR_TREE_PROPS = Object.freeze({
|
||||||
onCollapse: noop,
|
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({
|
var TEST_HEAP_PROPS = Object.freeze({
|
||||||
onSnapshotClick: noop,
|
onSnapshotClick: noop,
|
||||||
onLoadMoreSiblings: noop,
|
onLoadMoreSiblings: noop,
|
||||||
|
@ -146,6 +191,8 @@ var TEST_HEAP_PROPS = Object.freeze({
|
||||||
creationTime: 0,
|
creationTime: 0,
|
||||||
state: snapshotState.SAVED_CENSUS,
|
state: snapshotState.SAVED_CENSUS,
|
||||||
}),
|
}),
|
||||||
|
sizes: Object.freeze({ shortestPathsSize: .5 }),
|
||||||
|
onShortestPathsResize: noop,
|
||||||
});
|
});
|
||||||
|
|
||||||
var TEST_TOOLBAR_PROPS = Object.freeze({
|
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.className = "marker-details-stack";
|
||||||
container.appendChild(labelName);
|
container.appendChild(labelName);
|
||||||
|
|
||||||
|
// Workaround for profiles that have looping stack traces. See
|
||||||
|
// bug 1246555.
|
||||||
let wasAsyncParent = false;
|
let wasAsyncParent = false;
|
||||||
|
let seen = new Set();
|
||||||
|
|
||||||
while (frameIndex > 0) {
|
while (frameIndex > 0) {
|
||||||
|
if (seen.has(frameIndex)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
seen.add(frameIndex);
|
||||||
|
|
||||||
let frame = frames[frameIndex];
|
let frame = frames[frameIndex];
|
||||||
let url = frame.source;
|
let url = frame.source;
|
||||||
let displayName = frame.functionDisplayName;
|
let displayName = frame.functionDisplayName;
|
||||||
|
|
|
@ -58,9 +58,21 @@ function* spawnTest() {
|
||||||
return m.start;
|
return m.start;
|
||||||
}, 0);
|
}, 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 = {
|
const tests = {
|
||||||
ConsoleTime: function (marker) {
|
ConsoleTime: function (marker) {
|
||||||
info("Got `ConsoleTime` marker with data: " + JSON.stringify(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($, "startStack", marker);
|
||||||
shouldHaveStack($, "endStack", marker);
|
shouldHaveStack($, "endStack", marker);
|
||||||
shouldHaveLabel($, "Timer Name:", "!!!", marker);
|
shouldHaveLabel($, "Timer Name:", "!!!", marker);
|
||||||
|
|
|
@ -325,7 +325,7 @@ pref("devtools.editor.enableCodeFolding", true);
|
||||||
pref("devtools.editor.autocomplete", true);
|
pref("devtools.editor.autocomplete", true);
|
||||||
|
|
||||||
// Enable the Font Inspector
|
// 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
|
// 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
|
// 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
|
# 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/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
DIRS += [
|
||||||
|
'reps',
|
||||||
|
]
|
||||||
|
|
||||||
DevToolsModules(
|
DevToolsModules(
|
||||||
'frame.js',
|
'frame.js',
|
||||||
|
'h-split-box.js',
|
||||||
'tree.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 =
|
support-files =
|
||||||
head.js
|
head.js
|
||||||
|
|
||||||
|
[test_HSplitBox_01.html]
|
||||||
[test_frame_01.html]
|
[test_frame_01.html]
|
||||||
[test_frame_02.html]
|
[test_frame_02.html]
|
||||||
[test_tree_01.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
|
* 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 {
|
#heap-view {
|
||||||
/**
|
/**
|
||||||
* Flex: contains a .heap-view-panel which needs to fill out all the
|
* 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 > .snapshot-status,
|
||||||
#heap-view > .heap-view-panel > .take-snapshot {
|
#heap-view > .heap-view-panel > .take-snapshot,
|
||||||
|
#shortest-paths-select-node-msg {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
margin-top: 65px;
|
margin-top: 65px;
|
||||||
font-size: 120%;
|
font-size: 120%;
|
||||||
|
@ -297,20 +318,37 @@ html, body, #app, #memory-tool {
|
||||||
color: var(--theme-body-color);
|
color: var(--theme-body-color);
|
||||||
background-color: var(--theme-tab-toolbar-background);
|
background-color: var(--theme-tab-toolbar-background);
|
||||||
border-bottom: 1px solid var(--cell-border-color);
|
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 {
|
.header > span {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
|
||||||
line-height: var(--heap-tree-header-height);
|
|
||||||
justify-content: center;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header > .heap-tree-item-name {
|
.header > .heap-tree-item-name {
|
||||||
justify-content: flex-start;
|
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
|
* Heap tree view body
|
||||||
*/
|
*/
|
||||||
|
@ -329,6 +367,10 @@ html, body, #app, #memory-tool {
|
||||||
line-height: var(--heap-tree-row-height);
|
line-height: var(--heap-tree-row-height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.children-pointer {
|
||||||
|
padding-inline-end: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Heap tree view columns
|
* Heap tree view columns
|
||||||
*/
|
*/
|
||||||
|
@ -484,3 +526,50 @@ html, body, #app, #memory-tool {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 5px;
|
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_09.js]
|
||||||
[test_HeapSnapshot_takeCensus_10.js]
|
[test_HeapSnapshot_takeCensus_10.js]
|
||||||
[test_HeapSnapshot_takeCensus_11.js]
|
[test_HeapSnapshot_takeCensus_11.js]
|
||||||
|
[test_HeapSnapshot_takeCensus_12.js]
|
||||||
[test_ReadHeapSnapshot.js]
|
[test_ReadHeapSnapshot.js]
|
||||||
[test_ReadHeapSnapshot_with_allocations.js]
|
[test_ReadHeapSnapshot_with_allocations.js]
|
||||||
skip-if = os == 'linux' # Bug 1176173
|
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
|
types. When the census cannot find the byte size for a given type, it
|
||||||
returns zero.
|
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>
|
<code>{ by: "allocationStack", then:<i>breakdown</i>, noStack:<i>noStackBreakdown</i> }</code>
|
||||||
: Group items by the full JavaScript stack trace at which they were
|
: Group items by the full JavaScript stack trace at which they were
|
||||||
allocated.
|
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',
|
// A type that categorizes nodes by their JavaScript type -- 'objects',
|
||||||
// 'strings', 'scripts', and 'other' -- and then passes the nodes to child
|
// 'strings', 'scripts', and 'other' -- and then passes the nodes to child
|
||||||
// types.
|
// types.
|
||||||
|
@ -977,6 +1031,9 @@ ParseBreakdown(JSContext* cx, HandleValue breakdownValue)
|
||||||
return simple;
|
return simple;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (StringEqualsAscii(by, "bucket"))
|
||||||
|
return CountTypePtr(js_new<BucketCount>());
|
||||||
|
|
||||||
if (StringEqualsAscii(by, "objectClass")) {
|
if (StringEqualsAscii(by, "objectClass")) {
|
||||||
CountTypePtr thenType(ParseChildBreakdown(cx, breakdown, cx->names().then));
|
CountTypePtr thenType(ParseChildBreakdown(cx, breakdown, cx->names().then));
|
||||||
if (!thenType)
|
if (!thenType)
|
||||||
|
|
|
@ -26,6 +26,7 @@ public final class PrefsHelper {
|
||||||
// Map pref name to ArrayList for multiple observers or PrefHandler for single observer.
|
// 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 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_STRING_PREFS = new HashSet<>(8);
|
||||||
|
private static final HashSet<String> INT_TO_BOOL_PREFS = new HashSet<>(2);
|
||||||
|
|
||||||
static {
|
static {
|
||||||
INT_TO_STRING_PREFS.add("browser.chrome.titlebarMode");
|
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("font.size.inflation.minTwips");
|
||||||
INT_TO_STRING_PREFS.add("home.sync.updateMode");
|
INT_TO_STRING_PREFS.add("home.sync.updateMode");
|
||||||
INT_TO_STRING_PREFS.add("browser.image_blocking");
|
INT_TO_STRING_PREFS.add("browser.image_blocking");
|
||||||
|
INT_TO_BOOL_PREFS.add("browser.display.use_document_fonts");
|
||||||
}
|
}
|
||||||
|
|
||||||
@WrapForJNI
|
@WrapForJNI
|
||||||
|
@ -89,6 +91,9 @@ public final class PrefsHelper {
|
||||||
// actual types so we can store them.
|
// actual types so we can store them.
|
||||||
type = PREF_INT;
|
type = PREF_INT;
|
||||||
intVal = Integer.parseInt(String.valueOf(value));
|
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) {
|
} else if (value instanceof Boolean) {
|
||||||
type = PREF_BOOL;
|
type = PREF_BOOL;
|
||||||
boolVal = (Boolean) value;
|
boolVal = (Boolean) value;
|
||||||
|
@ -214,6 +219,9 @@ public final class PrefsHelper {
|
||||||
if (INT_TO_STRING_PREFS.contains(pref)) {
|
if (INT_TO_STRING_PREFS.contains(pref)) {
|
||||||
type = PREF_STRING;
|
type = PREF_STRING;
|
||||||
strVal = String.valueOf(intVal);
|
strVal = String.valueOf(intVal);
|
||||||
|
} else if (INT_TO_BOOL_PREFS.contains(pref)) {
|
||||||
|
type = PREF_BOOL;
|
||||||
|
boolVal = intVal == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
|
|
@ -8,8 +8,7 @@ package org.mozilla.gecko.firstrun;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import com.keepsafe.switchboard.SwitchBoard;
|
import org.mozilla.gecko.GeckoSharedPrefs;
|
||||||
import org.mozilla.gecko.AppConstants;
|
|
||||||
import org.mozilla.gecko.R;
|
import org.mozilla.gecko.R;
|
||||||
import org.mozilla.gecko.Telemetry;
|
import org.mozilla.gecko.Telemetry;
|
||||||
import org.mozilla.gecko.TelemetryContract;
|
import org.mozilla.gecko.TelemetryContract;
|
||||||
|
@ -28,22 +27,25 @@ public class FirstrunPagerConfig {
|
||||||
public static List<FirstrunPanelConfig> getDefault(Context context) {
|
public static List<FirstrunPanelConfig> getDefault(Context context) {
|
||||||
final List<FirstrunPanelConfig> panels = new LinkedList<>();
|
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));
|
panels.add(new FirstrunPanelConfig(WelcomePanel.class.getName(), WelcomePanel.TITLE_RES));
|
||||||
Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, Experiments.ONBOARDING2_A);
|
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.urlbarPanelConfig);
|
||||||
panels.add(SimplePanelConfigs.bookmarksPanelConfig);
|
panels.add(SimplePanelConfigs.bookmarksPanelConfig);
|
||||||
panels.add(SimplePanelConfigs.syncPanelConfig);
|
panels.add(SimplePanelConfigs.syncPanelConfig);
|
||||||
panels.add(new FirstrunPanelConfig(SyncPanel.class.getName(), SyncPanel.TITLE_RES));
|
panels.add(new FirstrunPanelConfig(SyncPanel.class.getName(), SyncPanel.TITLE_RES));
|
||||||
Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, Experiments.ONBOARDING2_B);
|
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.urlbarPanelConfig);
|
||||||
panels.add(SimplePanelConfigs.bookmarksPanelConfig);
|
panels.add(SimplePanelConfigs.bookmarksPanelConfig);
|
||||||
panels.add(SimplePanelConfigs.dataPanelConfig);
|
panels.add(SimplePanelConfigs.dataPanelConfig);
|
||||||
panels.add(SimplePanelConfigs.syncPanelConfig);
|
panels.add(SimplePanelConfigs.syncPanelConfig);
|
||||||
panels.add(new FirstrunPanelConfig(SyncPanel.class.getName(), SyncPanel.TITLE_RES));
|
panels.add(new FirstrunPanelConfig(SyncPanel.class.getName(), SyncPanel.TITLE_RES));
|
||||||
Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, Experiments.ONBOARDING2_C);
|
Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, Experiments.ONBOARDING2_C);
|
||||||
|
GeckoSharedPrefs.forProfile(context).edit().putString(Experiments.PREF_ONBOARDING_VERSION, Experiments.ONBOARDING2_C).apply();
|
||||||
} else {
|
} else {
|
||||||
Log.d(LOGTAG, "Not in an experiment!");
|
Log.d(LOGTAG, "Not in an experiment!");
|
||||||
panels.add(new FirstrunPanelConfig(WelcomePanel.class.getName(), WelcomePanel.TITLE_RES));
|
panels.add(new FirstrunPanelConfig(WelcomePanel.class.getName(), WelcomePanel.TITLE_RES));
|
||||||
|
@ -52,22 +54,6 @@ public class FirstrunPagerConfig {
|
||||||
return panels;
|
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() {
|
public static List<FirstrunPanelConfig> getRestricted() {
|
||||||
final List<FirstrunPanelConfig> panels = new LinkedList<>();
|
final List<FirstrunPanelConfig> panels = new LinkedList<>();
|
||||||
|
|
|
@ -8,12 +8,11 @@ package org.mozilla.gecko.telemetry;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
import com.keepsafe.switchboard.SwitchBoard;
|
|
||||||
|
|
||||||
import org.mozilla.gecko.AppConstants;
|
import org.mozilla.gecko.AppConstants;
|
||||||
import org.mozilla.gecko.Locales;
|
import org.mozilla.gecko.Locales;
|
||||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||||
import org.mozilla.gecko.telemetry.TelemetryConstants.CorePing;
|
import org.mozilla.gecko.telemetry.TelemetryConstants.CorePing;
|
||||||
|
import org.mozilla.gecko.util.Experiments;
|
||||||
import org.mozilla.gecko.util.StringUtils;
|
import org.mozilla.gecko.util.StringUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -87,9 +86,7 @@ public class TelemetryPingGenerator {
|
||||||
ping.put(CorePing.LOCALE, Locales.getLanguageTag(Locale.getDefault()));
|
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.OS_VERSION, Integer.toString(Build.VERSION.SDK_INT)); // A String for cross-platform reasons.
|
||||||
ping.put(CorePing.SEQ, seq);
|
ping.put(CorePing.SEQ, seq);
|
||||||
if (AppConstants.MOZ_SWITCHBOARD) {
|
ping.putArray(CorePing.EXPERIMENTS, Experiments.getActiveExperiments(context));
|
||||||
ping.putArray(CorePing.EXPERIMENTS, SwitchBoard.getActiveExperiments(context));
|
|
||||||
}
|
|
||||||
// TODO (bug 1246816): Remove this "optional" parameter work-around when
|
// TODO (bug 1246816): Remove this "optional" parameter work-around when
|
||||||
// GeckoProfile.getAndPersistProfileCreationDateFromFilesystem is implemented. That method returns -1
|
// 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.
|
// 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;
|
package org.mozilla.gecko.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import org.mozilla.gecko.mozglue.ContextUtils.SafeIntent;
|
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:
|
* 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_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 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.
|
// 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";
|
public static final String SEARCH_TERM = "search-term";
|
||||||
|
|
||||||
|
@ -55,4 +65,38 @@ public class Experiments {
|
||||||
disabled = false;
|
disabled = false;
|
||||||
return disabled;
|
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_data "Only over Wi-Fi">
|
||||||
<!ENTITY pref_tap_to_load_images_disabled2 "Blocked">
|
<!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_title "Tracking protection">
|
||||||
<!ENTITY pref_tracking_protection_summary3 "Enabled in Private Browsing">
|
<!ENTITY pref_tracking_protection_summary3 "Enabled in Private Browsing">
|
||||||
<!ENTITY pref_donottrack_title "Do not track">
|
<!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/ColorUtils.java',
|
||||||
'util/DrawableUtil.java',
|
'util/DrawableUtil.java',
|
||||||
'util/EventCallback.java',
|
'util/EventCallback.java',
|
||||||
'util/Experiments.java',
|
|
||||||
'util/FileUtils.java',
|
'util/FileUtils.java',
|
||||||
'util/FloatUtils.java',
|
'util/FloatUtils.java',
|
||||||
'util/GamepadUtils.java',
|
'util/GamepadUtils.java',
|
||||||
|
@ -578,6 +577,7 @@ gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
|
||||||
'trackingprotection/TrackingProtectionPrompt.java',
|
'trackingprotection/TrackingProtectionPrompt.java',
|
||||||
'updater/UpdateService.java',
|
'updater/UpdateService.java',
|
||||||
'updater/UpdateServiceHelper.java',
|
'updater/UpdateServiceHelper.java',
|
||||||
|
'util/Experiments.java',
|
||||||
'Webapp.java',
|
'Webapp.java',
|
||||||
'webapp/Allocator.java',
|
'webapp/Allocator.java',
|
||||||
'webapp/ApkResources.java',
|
'webapp/ApkResources.java',
|
||||||
|
|
|
@ -38,6 +38,9 @@
|
||||||
android:entryValues="@array/pref_browser_image_blocking_values"
|
android:entryValues="@array/pref_browser_image_blocking_values"
|
||||||
android:persistent="false" />
|
android:persistent="false" />
|
||||||
|
|
||||||
|
<CheckBoxPreference android:key="browser.display.use_document_fonts"
|
||||||
|
android:title="@string/pref_show_web_fonts" />
|
||||||
|
|
||||||
<ListPreference android:key="plugin.enable"
|
<ListPreference android:key="plugin.enable"
|
||||||
android:title="@string/pref_plugins"
|
android:title="@string/pref_plugins"
|
||||||
android:entries="@array/pref_plugins_entries"
|
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_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_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_title">&pref_tracking_protection_title;</string>
|
||||||
<string name="pref_tracking_protection_summary">&pref_tracking_protection_summary3;</string>
|
<string name="pref_tracking_protection_summary">&pref_tracking_protection_summary3;</string>
|
||||||
<string name="pref_donottrack_title">&pref_donottrack_title;</string>
|
<string name="pref_donottrack_title">&pref_donottrack_title;</string>
|
||||||
|
|
|
@ -30,9 +30,9 @@ interface nsIAlertNotification : nsISupports
|
||||||
[optional] in boolean inPrivateBrowsing);
|
[optional] in boolean inPrivateBrowsing);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the notification. This is currently only used on Android and
|
* The name of the notification. On Android, the name is hashed and used as
|
||||||
* OS X. On Android, the name is hashed and used as a notification ID.
|
* a notification ID. Notifications will replace previous notifications with
|
||||||
* Notifications will replace previous notifications with the same name.
|
* the same name.
|
||||||
*/
|
*/
|
||||||
readonly attribute AString name;
|
readonly attribute AString name;
|
||||||
|
|
||||||
|
@ -118,32 +118,11 @@ interface nsIAlertsService : nsISupports
|
||||||
void showAlert(in nsIAlertNotification alert,
|
void showAlert(in nsIAlertNotification alert,
|
||||||
[optional] in nsIObserver alertListener);
|
[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
|
* @param alertListener Used for callbacks. May be null if the caller
|
||||||
* doesn't care about callbacks.
|
* doesn't care about callbacks.
|
||||||
* @param name The name of the notification. This is currently only
|
* @see nsIAlertNotification for descriptions of all other parameters.
|
||||||
* 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.
|
|
||||||
* @throws NS_ERROR_NOT_AVAILABLE If the notification cannot be displayed.
|
* @throws NS_ERROR_NOT_AVAILABLE If the notification cannot be displayed.
|
||||||
*
|
*
|
||||||
* The following arguments will be passed to the alertListener's observe()
|
* 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);
|
alarmsMap.get(extension).add(alarm);
|
||||||
},
|
},
|
||||||
|
|
||||||
get: function(args) {
|
get: function(...args) {
|
||||||
let name = "", callback;
|
let name = "", callback;
|
||||||
if (args.length == 1) {
|
if (args.length == 1) {
|
||||||
callback = args[0];
|
callback = args[0];
|
||||||
|
@ -127,7 +127,7 @@ extensions.registerPrivilegedAPI("alarms", (extension, context) => {
|
||||||
|
|
||||||
getAll: function(callback) {
|
getAll: function(callback) {
|
||||||
let alarms = alarmsMap.get(extension);
|
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);
|
return context.wrapPromise(Promise.resolve(result), callback);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче