merge fx-team to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2016-02-24 12:04:15 +01:00
Родитель 3d5797401d 042bc9b036
Коммит 20095687cf
107 изменённых файлов: 2733 добавлений и 1033 удалений

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

@ -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);
}, },

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