merge autoland to mozilla-central. r=merge a=merge

MozReview-Commit-ID: 60XtziNG2CK
This commit is contained in:
Sebastian Hengst 2017-11-02 22:57:14 +01:00
Родитель 299b665375 e4007fdbe7
Коммит 8da0763166
146 изменённых файлов: 2694 добавлений и 2140 удалений

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

@ -238,7 +238,7 @@ var gMenuBuilder = {
item.tabManager.addActiveTabPermission(); item.tabManager.addActiveTabPermission();
let tab = item.tabManager.convert(contextData.tab); let tab = contextData.tab && item.tabManager.convert(contextData.tab);
let info = item.getClickInfo(contextData, wasChecked); let info = item.getClickInfo(contextData, wasChecked);
const map = {shiftKey: "Shift", altKey: "Alt", metaKey: "Command", ctrlKey: "Ctrl"}; const map = {shiftKey: "Shift", altKey: "Alt", metaKey: "Command", ctrlKey: "Ctrl"};
@ -673,7 +673,8 @@ this.menusInternal = class extends ExtensionAPI {
onClicked: new EventManager(context, "menusInternal.onClicked", fire => { onClicked: new EventManager(context, "menusInternal.onClicked", fire => {
let listener = (event, info, tab) => { let listener = (event, info, tab) => {
context.withPendingBrowser(tab.linkedBrowser, let {linkedBrowser} = tab || tabTracker.activeTab;
context.withPendingBrowser(linkedBrowser,
() => fire.sync(info, tab)); () => fire.sync(info, tab));
}; };

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

@ -36,6 +36,9 @@ let extData = {
id: "clickme-page", id: "clickme-page",
title: "Click me!", title: "Click me!",
contexts: ["all"], contexts: ["all"],
onclick(info, tab) {
browser.test.sendMessage("menu-click", tab);
},
}); });
}, },
}; };
@ -59,7 +62,11 @@ add_task(async function sidebar_contextmenu() {
let contentAreaContextMenu = await openContextMenuInSidebar(); let contentAreaContextMenu = await openContextMenuInSidebar();
let item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!"); let item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!");
is(item.length, 1, "contextMenu item for page was found"); is(item.length, 1, "contextMenu item for page was found");
item[0].click();
await closeContextMenu(contentAreaContextMenu); await closeContextMenu(contentAreaContextMenu);
let tab = await extension.awaitMessage("menu-click");
is(tab, null, "tab argument is optional, and missing in clicks from sidebars");
await extension.unload(); await extension.unload();
}); });

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

@ -313,10 +313,12 @@ class AutofillRecords {
add(record, {sourceSync = false} = {}) { add(record, {sourceSync = false} = {}) {
this.log.debug("add:", record); this.log.debug("add:", record);
let recordToSave = this._cloneAndCleanUp(record);
if (sourceSync) { if (sourceSync) {
// Remove tombstones for incoming items that were changed on another // Remove tombstones for incoming items that were changed on another
// device. Local deletions always lose to avoid data loss. // device. Local deletions always lose to avoid data loss.
let index = this._findIndexByGUID(record.guid, { let index = this._findIndexByGUID(recordToSave.guid, {
includeDeleted: true, includeDeleted: true,
}); });
if (index > -1) { if (index > -1) {
@ -324,31 +326,24 @@ class AutofillRecords {
if (existing.deleted) { if (existing.deleted) {
this.data.splice(index, 1); this.data.splice(index, 1);
} else { } else {
throw new Error(`Record ${record.guid} already exists`); throw new Error(`Record ${recordToSave.guid} already exists`);
} }
} }
let recordToSave = this._clone(record); } else if (!recordToSave.deleted) {
return this._saveRecord(recordToSave, {sourceSync}); this._normalizeRecord(recordToSave);
recordToSave.guid = this._generateGUID();
recordToSave.version = this.version;
// Metadata
let now = Date.now();
recordToSave.timeCreated = now;
recordToSave.timeLastModified = now;
recordToSave.timeLastUsed = 0;
recordToSave.timesUsed = 0;
} }
if (record.deleted) { return this._saveRecord(recordToSave, {sourceSync});
return this._saveRecord(record);
}
let recordToSave = this._clone(record);
this._normalizeRecord(recordToSave);
recordToSave.guid = this._generateGUID();
recordToSave.version = this.version;
// Metadata
let now = Date.now();
recordToSave.timeCreated = now;
recordToSave.timeLastModified = now;
recordToSave.timeLastUsed = 0;
recordToSave.timesUsed = 0;
return this._saveRecord(recordToSave);
} }
_saveRecord(record, {sourceSync = false} = {}) { _saveRecord(record, {sourceSync = false} = {}) {
@ -428,7 +423,7 @@ class AutofillRecords {
newValue = oldValue; newValue = oldValue;
} }
if (!newValue) { if (newValue === undefined || newValue === "") {
delete recordFound[field]; delete recordFound[field];
} else { } else {
recordFound[field] = newValue; recordFound[field] = newValue;

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

@ -39,7 +39,7 @@ const TEST_ADDRESS_4 = {
organization: "World Wide Web Consortium", organization: "World Wide Web Consortium",
}; };
const TEST_ADDRESS_FOR_UPDATE = { const TEST_ADDRESS_WITH_EMPTY_FIELD = {
"name": "Tim Berners", "name": "Tim Berners",
"street-address": "", "street-address": "",
}; };
@ -299,6 +299,12 @@ add_task(async function test_add() {
do_check_eq(addresses[0].timeLastUsed, 0); do_check_eq(addresses[0].timeLastUsed, 0);
do_check_eq(addresses[0].timesUsed, 0); do_check_eq(addresses[0].timesUsed, 0);
// Empty string should be deleted before saving.
profileStorage.addresses.add(TEST_ADDRESS_WITH_EMPTY_FIELD);
let address = profileStorage.addresses.data[2];
do_check_eq(address.name, TEST_ADDRESS_WITH_EMPTY_FIELD.name);
do_check_eq(address["street-address"], undefined);
Assert.throws(() => profileStorage.addresses.add(TEST_ADDRESS_WITH_INVALID_FIELD), Assert.throws(() => profileStorage.addresses.add(TEST_ADDRESS_WITH_INVALID_FIELD),
/"invalidField" is not a valid field\./); /"invalidField" is not a valid field\./);
}); });
@ -333,7 +339,7 @@ add_task(async function test_update() {
do_check_eq(getSyncChangeCounter(profileStorage.addresses, guid), 1); do_check_eq(getSyncChangeCounter(profileStorage.addresses, guid), 1);
// Test preserveOldProperties parameter and field with empty string. // Test preserveOldProperties parameter and field with empty string.
profileStorage.addresses.update(guid, TEST_ADDRESS_FOR_UPDATE, true); profileStorage.addresses.update(guid, TEST_ADDRESS_WITH_EMPTY_FIELD, true);
await onChanged; await onChanged;
await profileStorage._saveImmediately(); await profileStorage._saveImmediately();
@ -348,6 +354,12 @@ add_task(async function test_update() {
do_check_neq(address.timeLastModified, timeLastModified); do_check_neq(address.timeLastModified, timeLastModified);
do_check_eq(getSyncChangeCounter(profileStorage.addresses, guid), 2); do_check_eq(getSyncChangeCounter(profileStorage.addresses, guid), 2);
// Empty string should be deleted while updating.
profileStorage.addresses.update(profileStorage.addresses.data[0].guid, TEST_ADDRESS_WITH_EMPTY_FIELD);
address = profileStorage.addresses.data[0];
do_check_eq(address.name, TEST_ADDRESS_WITH_EMPTY_FIELD.name);
do_check_eq(address["street-address"], undefined);
Assert.throws( Assert.throws(
() => profileStorage.addresses.update("INVALID_GUID", TEST_ADDRESS_3), () => profileStorage.addresses.update("INVALID_GUID", TEST_ADDRESS_3),
/No matching record\./ /No matching record\./

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

@ -28,6 +28,12 @@ const TEST_CREDIT_CARD_3 = {
"cc-exp-year": 2000, "cc-exp-year": 2000,
}; };
const TEST_CREDIT_CARD_WITH_EMPTY_FIELD = {
"cc-name": "",
"cc-number": "1234123412341234",
"cc-exp-month": 1,
};
const TEST_CREDIT_CARD_WITH_2_DIGITS_YEAR = { const TEST_CREDIT_CARD_WITH_2_DIGITS_YEAR = {
"cc-number": "1234123412341234", "cc-number": "1234123412341234",
"cc-exp-month": 1, "cc-exp-month": 1,
@ -170,6 +176,12 @@ add_task(async function test_add() {
do_check_eq(creditCards[0].timeLastUsed, 0); do_check_eq(creditCards[0].timeLastUsed, 0);
do_check_eq(creditCards[0].timesUsed, 0); do_check_eq(creditCards[0].timesUsed, 0);
// Empty string should be deleted before saving.
profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_EMPTY_FIELD);
let creditCard = profileStorage.creditCards.data[2];
do_check_eq(creditCard["cc-exp-month"], TEST_CREDIT_CARD_WITH_EMPTY_FIELD["cc-exp-month"]);
do_check_eq(creditCard["cc-name"], undefined);
Assert.throws(() => profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_INVALID_FIELD), Assert.throws(() => profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_INVALID_FIELD),
/"invalidField" is not a valid field\./); /"invalidField" is not a valid field\./);
}); });
@ -202,6 +214,12 @@ add_task(async function test_update() {
do_check_neq(creditCard.timeLastModified, timeLastModified); do_check_neq(creditCard.timeLastModified, timeLastModified);
do_check_credit_card_matches(creditCard, TEST_CREDIT_CARD_3); do_check_credit_card_matches(creditCard, TEST_CREDIT_CARD_3);
// Empty string should be deleted while updating.
profileStorage.creditCards.update(profileStorage.creditCards.data[0].guid, TEST_CREDIT_CARD_WITH_EMPTY_FIELD);
creditCard = profileStorage.creditCards.data[0];
do_check_eq(creditCard["cc-exp-month"], TEST_CREDIT_CARD_WITH_EMPTY_FIELD["cc-exp-month"]);
do_check_eq(creditCard["cc-name"], undefined);
Assert.throws( Assert.throws(
() => profileStorage.creditCards.update("INVALID_GUID", TEST_CREDIT_CARD_3), () => profileStorage.creditCards.update("INVALID_GUID", TEST_CREDIT_CARD_3),
/No matching record\./ /No matching record\./

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

@ -6,7 +6,7 @@
"use strict"; "use strict";
const { createFactory, createClass, DOM: dom, PropTypes } = const { createFactory, Component, DOM: dom, PropTypes } =
require("devtools/client/shared/vendor/react"); require("devtools/client/shared/vendor/react");
const Services = require("Services"); const Services = require("Services");
@ -46,41 +46,46 @@ const panels = [{
const defaultPanelId = "addons"; const defaultPanelId = "addons";
module.exports = createClass({ class AboutDebuggingApp extends Component {
displayName: "AboutDebuggingApp", static get propTypes() {
propTypes: {
client: PropTypes.instanceOf(DebuggerClient).isRequired,
telemetry: PropTypes.instanceOf(Telemetry).isRequired
},
getInitialState() {
return { return {
client: PropTypes.instanceOf(DebuggerClient).isRequired,
telemetry: PropTypes.instanceOf(Telemetry).isRequired
};
}
constructor(props) {
super(props);
this.state = {
selectedPanelId: defaultPanelId selectedPanelId: defaultPanelId
}; };
},
this.onHashChange = this.onHashChange.bind(this);
this.selectPanel = this.selectPanel.bind(this);
}
componentDidMount() { componentDidMount() {
window.addEventListener("hashchange", this.onHashChange); window.addEventListener("hashchange", this.onHashChange);
this.onHashChange(); this.onHashChange();
this.props.telemetry.toolOpened("aboutdebugging"); this.props.telemetry.toolOpened("aboutdebugging");
}, }
componentWillUnmount() { componentWillUnmount() {
window.removeEventListener("hashchange", this.onHashChange); window.removeEventListener("hashchange", this.onHashChange);
this.props.telemetry.toolClosed("aboutdebugging"); this.props.telemetry.toolClosed("aboutdebugging");
this.props.telemetry.destroy(); this.props.telemetry.destroy();
}, }
onHashChange() { onHashChange() {
this.setState({ this.setState({
selectedPanelId: window.location.hash.substr(1) || defaultPanelId selectedPanelId: window.location.hash.substr(1) || defaultPanelId
}); });
}, }
selectPanel(panelId) { selectPanel(panelId) {
window.location.hash = "#" + panelId; window.location.hash = "#" + panelId;
}, }
render() { render() {
let { client } = this.props; let { client } = this.props;
@ -108,4 +113,6 @@ module.exports = createClass({
dom.div({ className: "main-content" }, panel) dom.div({ className: "main-content" }, panel)
); );
} }
}); }
module.exports = AboutDebuggingApp;

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

@ -4,21 +4,23 @@
"use strict"; "use strict";
const { createClass, DOM: dom, PropTypes } = const { Component, DOM: dom, PropTypes } =
require("devtools/client/shared/vendor/react"); require("devtools/client/shared/vendor/react");
module.exports = createClass({ class PanelHeader extends Component {
displayName: "PanelHeader", static get propTypes() {
return {
propTypes: { id: PropTypes.string.isRequired,
id: PropTypes.string.isRequired, name: PropTypes.string.isRequired
name: PropTypes.string.isRequired };
}, }
render() { render() {
let { name, id } = this.props; let { name, id } = this.props;
return dom.div({ className: "header" }, return dom.div({ className: "header" },
dom.h1({ id, className: "header-name" }, name)); dom.h1({ id, className: "header-name" }, name));
}, }
}); }
module.exports = PanelHeader;

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

@ -4,23 +4,23 @@
"use strict"; "use strict";
const { createClass, createFactory, DOM: dom, PropTypes } = const { Component, createFactory, DOM: dom, PropTypes } =
require("devtools/client/shared/vendor/react"); require("devtools/client/shared/vendor/react");
const PanelMenuEntry = createFactory(require("./PanelMenuEntry")); const PanelMenuEntry = createFactory(require("./PanelMenuEntry"));
module.exports = createClass({ class PanelMenu extends Component {
displayName: "PanelMenu", static get propTypes() {
return {
propTypes: { panels: PropTypes.arrayOf(PropTypes.shape({
panels: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.string.isRequired,
id: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
name: PropTypes.string.isRequired, icon: PropTypes.string.isRequired,
icon: PropTypes.string.isRequired, component: PropTypes.func.isRequired
component: PropTypes.func.isRequired })).isRequired,
})).isRequired, selectPanel: PropTypes.func.isRequired,
selectPanel: PropTypes.func.isRequired, selectedPanelId: PropTypes.string
selectedPanelId: PropTypes.string };
}, }
render() { render() {
let { panels, selectedPanelId, selectPanel } = this.props; let { panels, selectedPanelId, selectPanel } = this.props;
@ -37,5 +37,7 @@ module.exports = createClass({
// "categories" id used for styling purposes // "categories" id used for styling purposes
return dom.div({ id: "categories", role: "tablist" }, panelLinks); return dom.div({ id: "categories", role: "tablist" }, panelLinks);
}, }
}); }
module.exports = PanelMenu;

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

@ -4,23 +4,28 @@
"use strict"; "use strict";
const { createClass, DOM: dom, PropTypes } = const { Component, DOM: dom, PropTypes } =
require("devtools/client/shared/vendor/react"); require("devtools/client/shared/vendor/react");
module.exports = createClass({ class PanelMenuEntry extends Component {
displayName: "PanelMenuEntry", static get propTypes() {
return {
icon: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
selected: PropTypes.bool,
selectPanel: PropTypes.func.isRequired
};
}
propTypes: { constructor(props) {
icon: PropTypes.string.isRequired, super(props);
id: PropTypes.string.isRequired, this.onClick = this.onClick.bind(this);
name: PropTypes.string.isRequired, }
selected: PropTypes.bool,
selectPanel: PropTypes.func.isRequired
},
onClick() { onClick() {
this.props.selectPanel(this.props.id); this.props.selectPanel(this.props.id);
}, }
render() { render() {
let { id, name, icon, selected } = this.props; let { id, name, icon, selected } = this.props;
@ -38,4 +43,6 @@ module.exports = createClass({
dom.img({ className: "category-icon", src: icon, role: "presentation" }), dom.img({ className: "category-icon", src: icon, role: "presentation" }),
dom.div({ className: "category-name" }, name)); dom.div({ className: "category-name" }, name));
} }
}); }
module.exports = PanelMenuEntry;

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

@ -4,7 +4,7 @@
"use strict"; "use strict";
const { createClass, DOM: dom, PropTypes } = const { Component, DOM: dom, PropTypes } =
require("devtools/client/shared/vendor/react"); require("devtools/client/shared/vendor/react");
const Services = require("Services"); const Services = require("Services");
@ -18,19 +18,19 @@ const LocaleCompare = (a, b) => {
return a.name.toLowerCase().localeCompare(b.name.toLowerCase()); return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
}; };
module.exports = createClass({ class TargetList extends Component {
displayName: "TargetList", static get propTypes() {
return {
propTypes: { client: PropTypes.instanceOf(DebuggerClient).isRequired,
client: PropTypes.instanceOf(DebuggerClient).isRequired, debugDisabled: PropTypes.bool,
debugDisabled: PropTypes.bool, error: PropTypes.node,
error: PropTypes.node, id: PropTypes.string.isRequired,
id: PropTypes.string.isRequired, name: PropTypes.string,
name: PropTypes.string, sort: PropTypes.bool,
sort: PropTypes.bool, targetClass: PropTypes.func.isRequired,
targetClass: PropTypes.func.isRequired, targets: PropTypes.arrayOf(PropTypes.object).isRequired
targets: PropTypes.arrayOf(PropTypes.object).isRequired };
}, }
render() { render() {
let { client, debugDisabled, error, targetClass, targets, sort } = this.props; let { client, debugDisabled, error, targetClass, targets, sort } = this.props;
@ -52,5 +52,7 @@ module.exports = createClass({
return dom.div({ id: this.props.id, className: "targets" }, return dom.div({ id: this.props.id, className: "targets" },
dom.h2(null, this.props.name), content); dom.h2(null, this.props.name), content);
}, }
}); }
module.exports = TargetList;

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

@ -11,7 +11,7 @@ loader.lazyImporter(this, "AddonManager",
"resource://gre/modules/AddonManager.jsm"); "resource://gre/modules/AddonManager.jsm");
const { Cc, Ci } = require("chrome"); const { Cc, Ci } = require("chrome");
const { createFactory, createClass, DOM: dom, PropTypes } = const { createFactory, Component, DOM: dom, PropTypes } =
require("devtools/client/shared/vendor/react"); require("devtools/client/shared/vendor/react");
const Services = require("Services"); const Services = require("Services");
const AddonsInstallError = createFactory(require("./InstallError")); const AddonsInstallError = createFactory(require("./InstallError"));
@ -22,24 +22,31 @@ const Strings = Services.strings.createBundle(
const MORE_INFO_URL = "https://developer.mozilla.org/docs/Tools" + const MORE_INFO_URL = "https://developer.mozilla.org/docs/Tools" +
"/about:debugging#Enabling_add-on_debugging"; "/about:debugging#Enabling_add-on_debugging";
module.exports = createClass({ class AddonsControls extends Component {
displayName: "AddonsControls", static get propTypes() {
propTypes: {
debugDisabled: PropTypes.bool
},
getInitialState() {
return { return {
debugDisabled: PropTypes.bool
};
}
constructor(props) {
super(props);
this.state = {
installError: null, installError: null,
}; };
},
this.onEnableAddonDebuggingChange = this.onEnableAddonDebuggingChange.bind(this);
this.loadAddonFromFile = this.loadAddonFromFile.bind(this);
this.retryInstall = this.retryInstall.bind(this);
this.installAddon = this.installAddon.bind(this);
}
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); Services.prefs.setBoolPref("devtools.debugger.remote-enabled", enabled);
}, }
loadAddonFromFile() { loadAddonFromFile() {
this.setState({ installError: null }); this.setState({ installError: null });
@ -60,12 +67,12 @@ module.exports = createClass({
this.installAddon(file); this.installAddon(file);
}); });
}, }
retryInstall() { retryInstall() {
this.setState({ installError: null }); this.setState({ installError: null });
this.installAddon(this.state.lastInstallErrorFile); this.installAddon(this.state.lastInstallErrorFile);
}, }
installAddon(file) { installAddon(file) {
AddonManager.installTemporaryAddon(file) AddonManager.installTemporaryAddon(file)
@ -76,7 +83,7 @@ module.exports = createClass({
console.error(e); console.error(e);
this.setState({ installError: e.message, lastInstallErrorFile: file }); this.setState({ installError: e.message, lastInstallErrorFile: file });
}); });
}, }
render() { render() {
let { debugDisabled } = this.props; let { debugDisabled } = this.props;
@ -110,4 +117,6 @@ module.exports = createClass({
retryInstall: this.retryInstall, retryInstall: this.retryInstall,
})); }));
} }
}); }
module.exports = AddonsControls;

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

@ -5,20 +5,20 @@
/* eslint-env browser */ /* eslint-env browser */
"use strict"; "use strict";
const { createClass, DOM: dom, PropTypes } = require("devtools/client/shared/vendor/react"); const { Component, DOM: dom, PropTypes } = require("devtools/client/shared/vendor/react");
const Services = require("Services"); const Services = require("Services");
const Strings = Services.strings.createBundle( const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties"); "chrome://devtools/locale/aboutdebugging.properties");
module.exports = createClass({ class AddonsInstallError extends Component {
displayName: "AddonsInstallError", static get propTypes() {
return {
propTypes: { error: PropTypes.string,
error: PropTypes.string, retryInstall: PropTypes.func,
retryInstall: PropTypes.func, };
}, }
render() { render() {
if (!this.props.error) { if (!this.props.error) {
@ -36,4 +36,6 @@ module.exports = createClass({
{ className: "addons-install-retry", onClick: this.props.retryInstall }, { className: "addons-install-retry", onClick: this.props.retryInstall },
Strings.GetStringFromName("retryTemporaryInstall"))); Strings.GetStringFromName("retryTemporaryInstall")));
} }
}); }
module.exports = AddonsInstallError;

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

@ -6,7 +6,7 @@
const { AddonManager } = require("resource://gre/modules/AddonManager.jsm"); const { AddonManager } = require("resource://gre/modules/AddonManager.jsm");
const { Management } = require("resource://gre/modules/Extension.jsm"); const { Management } = require("resource://gre/modules/Extension.jsm");
const { createFactory, createClass, DOM: dom, PropTypes } = const { createFactory, Component, DOM: dom, PropTypes } =
require("devtools/client/shared/vendor/react"); require("devtools/client/shared/vendor/react");
const Services = require("Services"); const Services = require("Services");
@ -27,20 +27,29 @@ const REMOTE_ENABLED_PREF = "devtools.debugger.remote-enabled";
const WEB_EXT_URL = "https://developer.mozilla.org/Add-ons" + const WEB_EXT_URL = "https://developer.mozilla.org/Add-ons" +
"/WebExtensions/Getting_started_with_web-ext"; "/WebExtensions/Getting_started_with_web-ext";
module.exports = createClass({ class AddonsPanel extends Component {
displayName: "AddonsPanel", static get propTypes() {
propTypes: {
client: PropTypes.instanceOf(DebuggerClient).isRequired,
id: PropTypes.string.isRequired
},
getInitialState() {
return { return {
client: PropTypes.instanceOf(DebuggerClient).isRequired,
id: PropTypes.string.isRequired
};
}
constructor(props) {
super(props);
this.state = {
extensions: [], extensions: [],
debugDisabled: false, debugDisabled: false,
}; };
},
this.updateDebugStatus = this.updateDebugStatus.bind(this);
this.updateAddonsList = this.updateAddonsList.bind(this);
this.onInstalled = this.onInstalled.bind(this);
this.onUninstalled = this.onUninstalled.bind(this);
this.onEnabled = this.onEnabled.bind(this);
this.onDisabled = this.onDisabled.bind(this);
}
componentDidMount() { componentDidMount() {
AddonManager.addAddonListener(this); AddonManager.addAddonListener(this);
@ -55,7 +64,7 @@ module.exports = createClass({
this.updateDebugStatus(); this.updateDebugStatus();
this.updateAddonsList(); this.updateAddonsList();
}, }
componentWillUnmount() { componentWillUnmount() {
AddonManager.removeAddonListener(this); AddonManager.removeAddonListener(this);
@ -65,7 +74,7 @@ module.exports = createClass({
this.updateDebugStatus); this.updateDebugStatus);
Services.prefs.removeObserver(REMOTE_ENABLED_PREF, Services.prefs.removeObserver(REMOTE_ENABLED_PREF,
this.updateDebugStatus); this.updateDebugStatus);
}, }
updateDebugStatus() { updateDebugStatus() {
let debugDisabled = let debugDisabled =
@ -73,7 +82,7 @@ module.exports = createClass({
!Services.prefs.getBoolPref(REMOTE_ENABLED_PREF); !Services.prefs.getBoolPref(REMOTE_ENABLED_PREF);
this.setState({ debugDisabled }); this.setState({ debugDisabled });
}, }
updateAddonsList() { updateAddonsList() {
this.props.client.listAddons() this.props.client.listAddons()
@ -95,35 +104,35 @@ module.exports = createClass({
}, error => { }, error => {
throw new Error("Client error while listing addons: " + error); throw new Error("Client error while listing addons: " + error);
}); });
}, }
/** /**
* Mandatory callback as AddonManager listener. * Mandatory callback as AddonManager listener.
*/ */
onInstalled() { onInstalled() {
this.updateAddonsList(); this.updateAddonsList();
}, }
/** /**
* Mandatory callback as AddonManager listener. * Mandatory callback as AddonManager listener.
*/ */
onUninstalled() { onUninstalled() {
this.updateAddonsList(); this.updateAddonsList();
}, }
/** /**
* Mandatory callback as AddonManager listener. * Mandatory callback as AddonManager listener.
*/ */
onEnabled() { onEnabled() {
this.updateAddonsList(); this.updateAddonsList();
}, }
/** /**
* Mandatory callback as AddonManager listener. * Mandatory callback as AddonManager listener.
*/ */
onDisabled() { onDisabled() {
this.updateAddonsList(); this.updateAddonsList();
}, }
render() { render() {
let { client, id } = this.props; let { client, id } = this.props;
@ -177,4 +186,6 @@ module.exports = createClass({
}) })
)); ));
} }
}); }
module.exports = AddonsPanel;

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

@ -6,7 +6,7 @@
"use strict"; "use strict";
const { createClass, DOM: dom, PropTypes } = const { Component, DOM: dom, PropTypes } =
require("devtools/client/shared/vendor/react"); require("devtools/client/shared/vendor/react");
const { debugAddon, isTemporaryID, parseFileUri, uninstallAddon } = const { debugAddon, isTemporaryID, parseFileUri, uninstallAddon } =
require("../../modules/addon"); require("../../modules/addon");
@ -122,32 +122,39 @@ function warningMessages(warnings = []) {
}); });
} }
module.exports = createClass({ class AddonTarget extends Component {
displayName: "AddonTarget", static get propTypes() {
return {
client: PropTypes.instanceOf(DebuggerClient).isRequired,
debugDisabled: PropTypes.bool,
target: PropTypes.shape({
addonActor: PropTypes.string.isRequired,
addonID: PropTypes.string.isRequired,
icon: PropTypes.string,
name: PropTypes.string.isRequired,
temporarilyInstalled: PropTypes.bool,
url: PropTypes.string,
warnings: PropTypes.array,
}).isRequired
};
}
propTypes: { constructor(props) {
client: PropTypes.instanceOf(DebuggerClient).isRequired, super(props);
debugDisabled: PropTypes.bool, this.debug = this.debug.bind(this);
target: PropTypes.shape({ this.uninstall = this.uninstall.bind(this);
addonActor: PropTypes.string.isRequired, this.reload = this.reload.bind(this);
addonID: PropTypes.string.isRequired, }
icon: PropTypes.string,
name: PropTypes.string.isRequired,
temporarilyInstalled: PropTypes.bool,
url: PropTypes.string,
warnings: PropTypes.array,
}).isRequired
},
debug() { debug() {
let { target } = this.props; let { target } = this.props;
debugAddon(target.addonID); debugAddon(target.addonID);
}, }
uninstall() { uninstall() {
let { target } = this.props; let { target } = this.props;
uninstallAddon(target.addonID); uninstallAddon(target.addonID);
}, }
reload() { reload() {
let { client, target } = this.props; let { client, target } = this.props;
@ -160,7 +167,7 @@ module.exports = createClass({
throw new Error( throw new Error(
"Error reloading addon " + target.addonID + ": " + error); "Error reloading addon " + target.addonID + ": " + error);
}); });
}, }
render() { render() {
let { target, debugDisabled } = this.props; let { target, debugDisabled } = this.props;
@ -205,4 +212,6 @@ module.exports = createClass({
), ),
); );
} }
}); }
module.exports = AddonTarget;

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

@ -6,7 +6,7 @@
"use strict"; "use strict";
const { createClass, createFactory, DOM: dom, PropTypes } = const { Component, createFactory, DOM: dom, PropTypes } =
require("devtools/client/shared/vendor/react"); require("devtools/client/shared/vendor/react");
const Services = require("Services"); const Services = require("Services");
@ -20,30 +20,34 @@ loader.lazyRequireGetter(this, "DebuggerClient",
const Strings = Services.strings.createBundle( const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties"); "chrome://devtools/locale/aboutdebugging.properties");
module.exports = createClass({ class TabsPanel extends Component {
displayName: "TabsPanel", static get propTypes() {
propTypes: {
client: PropTypes.instanceOf(DebuggerClient).isRequired,
id: PropTypes.string.isRequired
},
getInitialState() {
return { return {
client: PropTypes.instanceOf(DebuggerClient).isRequired,
id: PropTypes.string.isRequired
};
}
constructor(props) {
super(props);
this.state = {
tabs: [] tabs: []
}; };
},
this.update = this.update.bind(this);
}
componentDidMount() { componentDidMount() {
let { client } = this.props; let { client } = this.props;
client.addListener("tabListChanged", this.update); client.addListener("tabListChanged", this.update);
this.update(); this.update();
}, }
componentWillUnmount() { componentWillUnmount() {
let { client } = this.props; let { client } = this.props;
client.removeListener("tabListChanged", this.update); client.removeListener("tabListChanged", this.update);
}, }
update() { update() {
this.props.client.mainRoot.listTabs().then(({ tabs }) => { this.props.client.mainRoot.listTabs().then(({ tabs }) => {
@ -68,7 +72,7 @@ module.exports = createClass({
}); });
this.setState({ tabs }); this.setState({ tabs });
}); });
}, }
render() { render() {
let { client, id } = this.props; let { client, id } = this.props;
@ -95,4 +99,6 @@ module.exports = createClass({
}) })
)); ));
} }
}); }
module.exports = TabsPanel;

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

@ -6,29 +6,34 @@
"use strict"; "use strict";
const { createClass, DOM: dom, PropTypes } = const { Component, DOM: dom, PropTypes } =
require("devtools/client/shared/vendor/react"); require("devtools/client/shared/vendor/react");
const Services = require("Services"); const Services = require("Services");
const Strings = Services.strings.createBundle( const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties"); "chrome://devtools/locale/aboutdebugging.properties");
module.exports = createClass({ class TabTarget extends Component {
displayName: "TabTarget", static get propTypes() {
return {
target: PropTypes.shape({
icon: PropTypes.string,
outerWindowID: PropTypes.number.isRequired,
title: PropTypes.string,
url: PropTypes.string.isRequired
}).isRequired
};
}
propTypes: { constructor(props) {
target: PropTypes.shape({ super(props);
icon: PropTypes.string, this.debug = this.debug.bind(this);
outerWindowID: PropTypes.number.isRequired, }
title: PropTypes.string,
url: PropTypes.string.isRequired
}).isRequired
},
debug() { debug() {
let { target } = this.props; let { target } = this.props;
window.open("about:devtools-toolbox?type=tab&id=" + target.outerWindowID); window.open("about:devtools-toolbox?type=tab&id=" + target.outerWindowID);
}, }
render() { render() {
let { target } = this.props; let { target } = this.props;
@ -50,4 +55,6 @@ module.exports = createClass({
}, Strings.GetStringFromName("debug")) }, Strings.GetStringFromName("debug"))
); );
} }
}); }
module.exports = TabTarget;

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

@ -8,7 +8,7 @@
loader.lazyImporter(this, "PrivateBrowsingUtils", loader.lazyImporter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm"); "resource://gre/modules/PrivateBrowsingUtils.jsm");
const { createClass, DOM: dom } = const { Component, DOM: dom } =
require("devtools/client/shared/vendor/react"); require("devtools/client/shared/vendor/react");
const Services = require("Services"); const Services = require("Services");
const { Ci } = require("chrome"); const { Ci } = require("chrome");
@ -22,8 +22,11 @@ loader.lazyRequireGetter(this, "DebuggerClient",
const Strings = Services.strings.createBundle("chrome://devtools/locale/aboutdebugging.properties"); const Strings = Services.strings.createBundle("chrome://devtools/locale/aboutdebugging.properties");
const MULTI_OPT_OUT_PREF = "dom.ipc.multiOptOut"; const MULTI_OPT_OUT_PREF = "dom.ipc.multiOptOut";
module.exports = createClass({ class multiE10SWarning extends Component {
displayName: "multiE10SWarning", constructor(props) {
super(props);
this.onUpdatePreferenceClick = this.onUpdatePreferenceClick.bind(this);
}
onUpdatePreferenceClick() { onUpdatePreferenceClick() {
let message = Strings.GetStringFromName("multiProcessWarningConfirmUpdate2"); let message = Strings.GetStringFromName("multiProcessWarningConfirmUpdate2");
@ -34,7 +37,7 @@ module.exports = createClass({
// Restart the browser. // Restart the browser.
Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart); Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
} }
}, }
render() { render() {
return dom.div( return dom.div(
@ -58,5 +61,7 @@ module.exports = createClass({
Strings.GetStringFromName("multiProcessWarningUpdateLink2") Strings.GetStringFromName("multiProcessWarningUpdateLink2")
) )
); );
}, }
}); }
module.exports = multiE10SWarning;

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

@ -8,7 +8,7 @@ loader.lazyImporter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm"); "resource://gre/modules/PrivateBrowsingUtils.jsm");
const { Ci } = require("chrome"); const { Ci } = require("chrome");
const { createClass, createFactory, DOM: dom, PropTypes } = const { Component, createFactory, DOM: dom, PropTypes } =
require("devtools/client/shared/vendor/react"); require("devtools/client/shared/vendor/react");
const { getWorkerForms } = require("../../modules/worker"); const { getWorkerForms } = require("../../modules/worker");
const Services = require("Services"); const Services = require("Services");
@ -34,24 +34,25 @@ const MORE_INFO_URL = "https://developer.mozilla.org/en-US/docs/Tools/about%3Ade
const PROCESS_COUNT_PREF = "dom.ipc.processCount"; const PROCESS_COUNT_PREF = "dom.ipc.processCount";
const MULTI_OPTOUT_PREF = "dom.ipc.multiOptOut"; const MULTI_OPTOUT_PREF = "dom.ipc.multiOptOut";
module.exports = createClass({ class WorkersPanel extends Component {
displayName: "WorkersPanel", static get propTypes() {
propTypes: {
client: PropTypes.instanceOf(DebuggerClient).isRequired,
id: PropTypes.string.isRequired
},
getInitialState() {
return { return {
workers: { client: PropTypes.instanceOf(DebuggerClient).isRequired,
service: [], id: PropTypes.string.isRequired
shared: [],
other: []
},
processCount: 1,
}; };
}, }
constructor(props) {
super(props);
this.updateMultiE10S = this.updateMultiE10S.bind(this);
this.updateWorkers = this.updateWorkers.bind(this);
this.getRegistrationForWorker = this.getRegistrationForWorker.bind(this);
this.isE10S = this.isE10S.bind(this);
this.renderServiceWorkersError = this.renderServiceWorkersError.bind(this);
this.state = this.initialState;
}
componentDidMount() { componentDidMount() {
let client = this.props.client; let client = this.props.client;
@ -77,7 +78,7 @@ module.exports = createClass({
this.updateMultiE10S(); this.updateMultiE10S();
this.updateWorkers(); this.updateWorkers();
}, }
componentWillUnmount() { componentWillUnmount() {
let client = this.props.client; let client = this.props.client;
@ -88,17 +89,28 @@ module.exports = createClass({
Services.prefs.removeObserver(PROCESS_COUNT_PREF, this.updateMultiE10S); Services.prefs.removeObserver(PROCESS_COUNT_PREF, this.updateMultiE10S);
Services.prefs.removeObserver(MULTI_OPTOUT_PREF, this.updateMultiE10S); Services.prefs.removeObserver(MULTI_OPTOUT_PREF, this.updateMultiE10S);
}, }
get initialState() {
return {
workers: {
service: [],
shared: [],
other: []
},
processCount: 1,
};
}
updateMultiE10S() { updateMultiE10S() {
// We watch the pref but set the state based on // We watch the pref but set the state based on
// nsIXULRuntime.maxWebProcessCount. // nsIXULRuntime.maxWebProcessCount.
let processCount = Services.appinfo.maxWebProcessCount; let processCount = Services.appinfo.maxWebProcessCount;
this.setState({ processCount }); this.setState({ processCount });
}, }
updateWorkers() { updateWorkers() {
let workers = this.getInitialState().workers; let workers = this.initialState.workers;
getWorkerForms(this.props.client).then(forms => { getWorkerForms(this.props.client).then(forms => {
forms.registrations.forEach(form => { forms.registrations.forEach(form => {
@ -156,7 +168,7 @@ module.exports = createClass({
this.setState({ workers }); this.setState({ workers });
}); });
}, }
getRegistrationForWorker(form, registrations) { getRegistrationForWorker(form, registrations) {
for (let registration of registrations) { for (let registration of registrations) {
@ -165,11 +177,11 @@ module.exports = createClass({
} }
} }
return null; return null;
}, }
isE10S() { isE10S() {
return Services.appinfo.browserTabsRemoteAutostart; return Services.appinfo.browserTabsRemoteAutostart;
}, }
renderServiceWorkersError() { renderServiceWorkersError() {
let isWindowPrivate = PrivateBrowsingUtils.isContentWindowPrivate(window); let isWindowPrivate = PrivateBrowsingUtils.isContentWindowPrivate(window);
@ -200,7 +212,7 @@ module.exports = createClass({
Strings.GetStringFromName("configurationIsNotCompatible.learnMore") Strings.GetStringFromName("configurationIsNotCompatible.learnMore")
), ),
); );
}, }
render() { render() {
let { client, id } = this.props; let { client, id } = this.props;
@ -255,4 +267,6 @@ module.exports = createClass({
) )
); );
} }
}); }
module.exports = WorkersPanel;

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

@ -6,7 +6,7 @@
"use strict"; "use strict";
const { createClass, DOM: dom, PropTypes } = const { Component, DOM: dom, PropTypes } =
require("devtools/client/shared/vendor/react"); require("devtools/client/shared/vendor/react");
const { debugWorker } = require("../../modules/worker"); const { debugWorker } = require("../../modules/worker");
const Services = require("Services"); const Services = require("Services");
@ -17,36 +17,50 @@ loader.lazyRequireGetter(this, "DebuggerClient",
const Strings = Services.strings.createBundle( const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties"); "chrome://devtools/locale/aboutdebugging.properties");
module.exports = createClass({ class ServiceWorkerTarget extends Component {
displayName: "ServiceWorkerTarget", static get propTypes() {
propTypes: {
client: PropTypes.instanceOf(DebuggerClient).isRequired,
debugDisabled: PropTypes.bool,
target: PropTypes.shape({
active: PropTypes.bool,
fetch: PropTypes.bool.isRequired,
icon: PropTypes.string,
name: PropTypes.string.isRequired,
url: PropTypes.string,
scope: PropTypes.string.isRequired,
// registrationActor can be missing in e10s.
registrationActor: PropTypes.string,
workerActor: PropTypes.string
}).isRequired
},
getInitialState() {
return { return {
client: PropTypes.instanceOf(DebuggerClient).isRequired,
debugDisabled: PropTypes.bool,
target: PropTypes.shape({
active: PropTypes.bool,
fetch: PropTypes.bool.isRequired,
icon: PropTypes.string,
name: PropTypes.string.isRequired,
url: PropTypes.string,
scope: PropTypes.string.isRequired,
// registrationActor can be missing in e10s.
registrationActor: PropTypes.string,
workerActor: PropTypes.string
}).isRequired
};
}
constructor(props) {
super(props);
this.state = {
pushSubscription: null pushSubscription: null
}; };
},
this.debug = this.debug.bind(this);
this.push = this.push.bind(this);
this.start = this.start.bind(this);
this.unregister = this.unregister.bind(this);
this.onPushSubscriptionModified = this.onPushSubscriptionModified.bind(this);
this.updatePushSubscription = this.updatePushSubscription.bind(this);
this.isRunning = this.isRunning.bind(this);
this.isActive = this.isActive.bind(this);
this.getServiceWorkerStatus = this.getServiceWorkerStatus.bind(this);
this.renderButtons = this.renderButtons.bind(this);
this.renderUnregisterLink = this.renderUnregisterLink.bind(this);
}
componentDidMount() { componentDidMount() {
let { client } = this.props; let { client } = this.props;
client.addListener("push-subscription-modified", this.onPushSubscriptionModified); client.addListener("push-subscription-modified", this.onPushSubscriptionModified);
this.updatePushSubscription(); this.updatePushSubscription();
}, }
componentDidUpdate(oldProps, oldState) { componentDidUpdate(oldProps, oldState) {
let wasActive = oldProps.target.active; let wasActive = oldProps.target.active;
@ -56,12 +70,12 @@ module.exports = createClass({
// subscription change by updating it now. // subscription change by updating it now.
this.updatePushSubscription(); this.updatePushSubscription();
} }
}, }
componentWillUnmount() { componentWillUnmount() {
let { client } = this.props; let { client } = this.props;
client.removeListener("push-subscription-modified", this.onPushSubscriptionModified); client.removeListener("push-subscription-modified", this.onPushSubscriptionModified);
}, }
debug() { debug() {
if (!this.isRunning()) { if (!this.isRunning()) {
@ -71,7 +85,7 @@ module.exports = createClass({
let { client, target } = this.props; let { client, target } = this.props;
debugWorker(client, target.workerActor); debugWorker(client, target.workerActor);
}, }
push() { push() {
if (!this.isActive() || !this.isRunning()) { if (!this.isActive() || !this.isRunning()) {
@ -86,7 +100,7 @@ module.exports = createClass({
to: target.workerActor, to: target.workerActor,
type: "push" type: "push"
}); });
}, }
start() { start() {
if (!this.isActive() || this.isRunning()) { if (!this.isActive() || this.isRunning()) {
@ -99,7 +113,7 @@ module.exports = createClass({
to: target.registrationActor, to: target.registrationActor,
type: "start" type: "start"
}); });
}, }
unregister() { unregister() {
let { client, target } = this.props; let { client, target } = this.props;
@ -107,14 +121,14 @@ module.exports = createClass({
to: target.registrationActor, to: target.registrationActor,
type: "unregister" type: "unregister"
}); });
}, }
onPushSubscriptionModified(type, data) { onPushSubscriptionModified(type, data) {
let { target } = this.props; let { target } = this.props;
if (data.from === target.registrationActor) { if (data.from === target.registrationActor) {
this.updatePushSubscription(); this.updatePushSubscription();
} }
}, }
updatePushSubscription() { updatePushSubscription() {
if (!this.props.target.registrationActor) { if (!this.props.target.registrationActor) {
@ -129,16 +143,16 @@ module.exports = createClass({
}, ({ subscription }) => { }, ({ subscription }) => {
this.setState({ pushSubscription: subscription }); this.setState({ pushSubscription: subscription });
}); });
}, }
isRunning() { isRunning() {
// We know the target is running if it has a worker actor. // We know the target is running if it has a worker actor.
return !!this.props.target.workerActor; return !!this.props.target.workerActor;
}, }
isActive() { isActive() {
return this.props.target.active; return this.props.target.active;
}, }
getServiceWorkerStatus() { getServiceWorkerStatus() {
if (this.isActive() && this.isRunning()) { if (this.isActive() && this.isRunning()) {
@ -150,7 +164,7 @@ module.exports = createClass({
// ACTIVE state. Unable to know the actual state ("installing", "waiting"), we // ACTIVE state. Unable to know the actual state ("installing", "waiting"), we
// display a custom state "registering" for now. See Bug 1153292. // display a custom state "registering" for now. See Bug 1153292.
return "registering"; return "registering";
}, }
renderButtons() { renderButtons() {
let pushButton = dom.button({ let pushButton = dom.button({
@ -179,7 +193,7 @@ module.exports = createClass({
return debugButton; return debugButton;
} }
return startButton; return startButton;
}, }
renderUnregisterLink() { renderUnregisterLink() {
if (!this.isActive()) { if (!this.isActive()) {
@ -191,7 +205,7 @@ module.exports = createClass({
onClick: this.unregister, onClick: this.unregister,
className: "unregister-link", className: "unregister-link",
}, Strings.GetStringFromName("unregister")); }, Strings.GetStringFromName("unregister"));
}, }
render() { render() {
let { target } = this.props; let { target } = this.props;
@ -240,4 +254,6 @@ module.exports = createClass({
this.renderButtons() this.renderButtons()
); );
} }
}); }
module.exports = ServiceWorkerTarget;

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

@ -6,7 +6,7 @@
"use strict"; "use strict";
const { createClass, DOM: dom, PropTypes } = const { Component, DOM: dom, PropTypes } =
require("devtools/client/shared/vendor/react"); require("devtools/client/shared/vendor/react");
const { debugWorker } = require("../../modules/worker"); const { debugWorker } = require("../../modules/worker");
const Services = require("Services"); const Services = require("Services");
@ -17,23 +17,28 @@ loader.lazyRequireGetter(this, "DebuggerClient",
const Strings = Services.strings.createBundle( const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties"); "chrome://devtools/locale/aboutdebugging.properties");
module.exports = createClass({ class WorkerTarget extends Component {
displayName: "WorkerTarget", static get propTypes() {
return {
client: PropTypes.instanceOf(DebuggerClient).isRequired,
debugDisabled: PropTypes.bool,
target: PropTypes.shape({
icon: PropTypes.string,
name: PropTypes.string.isRequired,
workerActor: PropTypes.string
}).isRequired
};
}
propTypes: { constructor(props) {
client: PropTypes.instanceOf(DebuggerClient).isRequired, super(props);
debugDisabled: PropTypes.bool, this.debug = this.debug.bind(this);
target: PropTypes.shape({ }
icon: PropTypes.string,
name: PropTypes.string.isRequired,
workerActor: PropTypes.string
}).isRequired
},
debug() { debug() {
let { client, target } = this.props; let { client, target } = this.props;
debugWorker(client, target.workerActor); debugWorker(client, target.workerActor);
}, }
render() { render() {
let { target, debugDisabled } = this.props; let { target, debugDisabled } = this.props;
@ -54,4 +59,6 @@ module.exports = createClass({
}, Strings.GetStringFromName("debug")) }, Strings.GetStringFromName("debug"))
); );
} }
}); }
module.exports = WorkerTarget;

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

@ -6,7 +6,7 @@
const { assert } = require("devtools/shared/DevToolsUtils"); const { assert } = require("devtools/shared/DevToolsUtils");
const { appinfo } = require("Services"); const { appinfo } = require("Services");
const { DOM: dom, createClass, createFactory, PropTypes } = require("devtools/client/shared/vendor/react"); const { DOM: dom, Component, createFactory, PropTypes } = require("devtools/client/shared/vendor/react");
const { connect } = require("devtools/client/shared/vendor/react-redux"); const { connect } = require("devtools/client/shared/vendor/react-redux");
const { censusDisplays, labelDisplays, treeMapDisplays, diffingState, viewState } = require("./constants"); const { censusDisplays, labelDisplays, treeMapDisplays, diffingState, viewState } = require("./constants");
const { toggleRecordingAllocationStacks } = require("./actions/allocations"); const { toggleRecordingAllocationStacks } = require("./actions/allocations");
@ -51,20 +51,30 @@ const SnapshotListItem = createFactory(require("./components/SnapshotListItem"))
const Heap = createFactory(require("./components/Heap")); const Heap = createFactory(require("./components/Heap"));
const { app: appModel } = require("./models"); const { app: appModel } = require("./models");
const MemoryApp = createClass({ class MemoryApp extends Component {
displayName: "MemoryApp", static get propTypes() {
return appModel;
}
propTypes: appModel, static get childContextTypes() {
return {
front: PropTypes.any,
heapWorker: PropTypes.any,
toolbox: PropTypes.any,
};
}
childContextTypes: { static get defaultProps() {
front: PropTypes.any,
heapWorker: PropTypes.any,
toolbox: PropTypes.any,
},
getDefaultProps() {
return {}; return {};
}, }
constructor(props) {
super(props);
this.onKeyDown = this.onKeyDown.bind(this);
this._getCensusDisplays = this._getCensusDisplays.bind(this);
this._getLabelDisplays = this._getLabelDisplays.bind(this);
this._getTreeMapDisplays = this._getTreeMapDisplays.bind(this);
}
getChildContext() { getChildContext() {
return { return {
@ -72,18 +82,18 @@ const MemoryApp = createClass({
heapWorker: this.props.heapWorker, heapWorker: this.props.heapWorker,
toolbox: this.props.toolbox, toolbox: this.props.toolbox,
}; };
}, }
componentDidMount() { componentDidMount() {
// Attach the keydown listener directly to the window. When an element that // Attach the keydown listener directly to the window. When an element that
// has the focus (such as a tree node) is removed from the DOM, the focus // has the focus (such as a tree node) is removed from the DOM, the focus
// falls back to the body. // falls back to the body.
window.addEventListener("keydown", this.onKeyDown); window.addEventListener("keydown", this.onKeyDown);
}, }
componentWillUnmount() { componentWillUnmount() {
window.removeEventListener("keydown", this.onKeyDown); window.removeEventListener("keydown", this.onKeyDown);
}, }
onKeyDown(e) { onKeyDown(e) {
let { snapshots, dispatch, heapWorker } = this.props; let { snapshots, dispatch, heapWorker } = this.props;
@ -106,7 +116,7 @@ const MemoryApp = createClass({
let nextSnapshotId = snapshots[nextIndex].id; let nextSnapshotId = snapshots[nextIndex].id;
dispatch(selectSnapshotAndRefresh(heapWorker, nextSnapshotId)); dispatch(selectSnapshotAndRefresh(heapWorker, nextSnapshotId));
} }
}, }
_getCensusDisplays() { _getCensusDisplays() {
const customDisplays = getCustomCensusDisplays(); const customDisplays = getCustomCensusDisplays();
@ -120,7 +130,7 @@ const MemoryApp = createClass({
censusDisplays.allocationStack, censusDisplays.allocationStack,
censusDisplays.invertedAllocationStack, censusDisplays.invertedAllocationStack,
].concat(custom); ].concat(custom);
}, }
_getLabelDisplays() { _getLabelDisplays() {
const customDisplays = getCustomLabelDisplays(); const customDisplays = getCustomLabelDisplays();
@ -133,7 +143,7 @@ const MemoryApp = createClass({
labelDisplays.coarseType, labelDisplays.coarseType,
labelDisplays.allocationStack, labelDisplays.allocationStack,
].concat(custom); ].concat(custom);
}, }
_getTreeMapDisplays() { _getTreeMapDisplays() {
const customDisplays = getCustomTreeMapDisplays(); const customDisplays = getCustomTreeMapDisplays();
@ -145,7 +155,7 @@ const MemoryApp = createClass({
return [ return [
treeMapDisplays.coarseType treeMapDisplays.coarseType
].concat(custom); ].concat(custom);
}, }
render() { render() {
let { let {
@ -317,8 +327,8 @@ const MemoryApp = createClass({
) )
) )
); );
}, }
}); }
/** /**
* Passed into react-redux's `connect` method that is called on store change * Passed into react-redux's `connect` method that is called on store change

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

@ -4,24 +4,24 @@
"use strict"; "use strict";
const { createClass, PropTypes, createFactory } = require("devtools/client/shared/vendor/react"); const { Component, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
const Tree = createFactory(require("devtools/client/shared/components/Tree")); const Tree = createFactory(require("devtools/client/shared/components/Tree"));
const CensusTreeItem = createFactory(require("./CensusTreeItem")); const CensusTreeItem = createFactory(require("./CensusTreeItem"));
const { TREE_ROW_HEIGHT } = require("../constants"); const { TREE_ROW_HEIGHT } = require("../constants");
const { censusModel, diffingModel } = require("../models"); const { censusModel, diffingModel } = require("../models");
module.exports = createClass({ class Census extends Component {
displayName: "Census", static get propTypes() {
return {
propTypes: { census: censusModel,
census: censusModel, onExpand: PropTypes.func.isRequired,
onExpand: PropTypes.func.isRequired, onCollapse: PropTypes.func.isRequired,
onCollapse: PropTypes.func.isRequired, onFocus: PropTypes.func.isRequired,
onFocus: PropTypes.func.isRequired, onViewSourceInDebugger: PropTypes.func.isRequired,
onViewSourceInDebugger: PropTypes.func.isRequired, onViewIndividuals: PropTypes.func.isRequired,
onViewIndividuals: PropTypes.func.isRequired, diffing: diffingModel,
diffing: diffingModel, };
}, }
render() { render() {
let { let {
@ -77,4 +77,6 @@ module.exports = createClass({
itemHeight: TREE_ROW_HEIGHT, itemHeight: TREE_ROW_HEIGHT,
}); });
} }
}); }
module.exports = Census;

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

@ -4,16 +4,16 @@
"use strict"; "use strict";
const { DOM: dom, createClass } = require("devtools/client/shared/vendor/react"); const { DOM: dom, Component } = require("devtools/client/shared/vendor/react");
const { L10N } = require("../utils"); const { L10N } = require("../utils");
const models = require("../models"); const models = require("../models");
module.exports = createClass({ class CensusHeader extends Component {
displayName: "CensusHeader", static get propTypes() {
return {
propTypes: { diffing: models.diffingModel,
diffing: models.diffingModel, };
}, }
render() { render() {
let individualsCell; let individualsCell;
@ -71,4 +71,6 @@ module.exports = createClass({
) )
); );
} }
}); }
module.exports = CensusHeader;

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

@ -6,7 +6,7 @@
const { isSavedFrame } = require("devtools/shared/DevToolsUtils"); const { isSavedFrame } = require("devtools/shared/DevToolsUtils");
const { const {
DOM: dom, DOM: dom,
createClass, Component,
createFactory, createFactory,
PropTypes PropTypes
} = require("devtools/client/shared/vendor/react"); } = require("devtools/client/shared/vendor/react");
@ -15,22 +15,27 @@ const Frame = createFactory(require("devtools/client/shared/components/Frame"));
const { TREE_ROW_HEIGHT } = require("../constants"); const { TREE_ROW_HEIGHT } = require("../constants");
const models = require("../models"); const models = require("../models");
module.exports = createClass({ class CensusTreeItem extends Component {
displayName: "CensusTreeItem", static get propTypes() {
return {
arrow: PropTypes.any,
depth: PropTypes.number.isRequired,
diffing: models.app.diffing,
expanded: PropTypes.bool.isRequired,
focused: PropTypes.bool.isRequired,
getPercentBytes: PropTypes.func.isRequired,
getPercentCount: PropTypes.func.isRequired,
inverted: PropTypes.bool,
item: PropTypes.object.isRequired,
onViewIndividuals: PropTypes.func.isRequired,
onViewSourceInDebugger: PropTypes.func.isRequired,
};
}
propTypes: { constructor(props) {
arrow: PropTypes.any, super(props);
depth: PropTypes.number.isRequired, this.toLabel = this.toLabel.bind(this);
diffing: models.app.diffing, }
expanded: PropTypes.bool.isRequired,
focused: PropTypes.bool.isRequired,
getPercentBytes: PropTypes.func.isRequired,
getPercentCount: PropTypes.func.isRequired,
inverted: PropTypes.bool,
item: PropTypes.object.isRequired,
onViewIndividuals: PropTypes.func.isRequired,
onViewSourceInDebugger: PropTypes.func.isRequired,
},
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
return this.props.item != nextProps.item return this.props.item != nextProps.item
@ -38,7 +43,7 @@ module.exports = createClass({
|| this.props.expanded != nextProps.expanded || this.props.expanded != nextProps.expanded
|| this.props.focused != nextProps.focused || this.props.focused != nextProps.focused
|| this.props.diffing != nextProps.diffing; || this.props.diffing != nextProps.diffing;
}, }
toLabel(name, linkToDebugger) { toLabel(name, linkToDebugger) {
if (isSavedFrame(name)) { if (isSavedFrame(name)) {
@ -63,7 +68,7 @@ module.exports = createClass({
} }
return String(name); return String(name);
}, }
render() { render() {
let { let {
@ -150,5 +155,7 @@ module.exports = createClass({
this.toLabel(item.name, onViewSourceInDebugger) this.toLabel(item.name, onViewSourceInDebugger)
) )
); );
}, }
}); }
module.exports = CensusTreeItem;

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

@ -4,7 +4,7 @@
"use strict"; "use strict";
const { DOM: dom, createClass, PropTypes, createFactory } = require("devtools/client/shared/vendor/react"); const { DOM: dom, Component, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
const { assert } = require("devtools/shared/DevToolsUtils"); const { assert } = require("devtools/shared/DevToolsUtils");
const { createParentMap } = require("devtools/shared/heapsnapshot/CensusUtils"); const { createParentMap } = require("devtools/shared/heapsnapshot/CensusUtils");
const Tree = createFactory(require("devtools/client/shared/components/Tree")); const Tree = createFactory(require("devtools/client/shared/components/Tree"));
@ -20,18 +20,18 @@ const DOMINATOR_TREE_AUTO_EXPAND_DEPTH = 3;
* A throbber that represents a subtree in the dominator tree that is actively * A throbber that represents a subtree in the dominator tree that is actively
* being incrementally loaded and fetched from the `HeapAnalysesWorker`. * being incrementally loaded and fetched from the `HeapAnalysesWorker`.
*/ */
const DominatorTreeSubtreeFetching = createFactory(createClass({ class DominatorTreeSubtreeFetchingClass extends Component {
displayName: "DominatorTreeSubtreeFetching", static get propTypes() {
return {
propTypes: { depth: PropTypes.number.isRequired,
depth: PropTypes.number.isRequired, focused: PropTypes.bool.isRequired,
focused: PropTypes.bool.isRequired, };
}, }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
return this.props.depth !== nextProps.depth return this.props.depth !== nextProps.depth
|| this.props.focused !== nextProps.focused; || this.props.focused !== nextProps.focused;
}, }
render() { render() {
let { let {
@ -51,26 +51,26 @@ const DominatorTreeSubtreeFetching = createFactory(createClass({
}) })
); );
} }
})); }
/** /**
* A link to fetch and load more siblings in the dominator tree, when there are * A link to fetch and load more siblings in the dominator tree, when there are
* already many loaded above. * already many loaded above.
*/ */
const DominatorTreeSiblingLink = createFactory(createClass({ class DominatorTreeSiblingLinkClass extends Component {
displayName: "DominatorTreeSiblingLink", static get propTypes() {
return {
propTypes: { depth: PropTypes.number.isRequired,
depth: PropTypes.number.isRequired, focused: PropTypes.bool.isRequired,
focused: PropTypes.bool.isRequired, item: PropTypes.instanceOf(DominatorTreeLazyChildren).isRequired,
item: PropTypes.instanceOf(DominatorTreeLazyChildren).isRequired, onLoadMoreSiblings: PropTypes.func.isRequired,
onLoadMoreSiblings: PropTypes.func.isRequired, };
}, }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
return this.props.depth !== nextProps.depth return this.props.depth !== nextProps.depth
|| this.props.focused !== nextProps.focused; || this.props.focused !== nextProps.focused;
}, }
render() { render() {
let { let {
@ -100,22 +100,19 @@ const DominatorTreeSiblingLink = createFactory(createClass({
) )
); );
} }
})); }
/** class DominatorTree extends Component {
* The actual dominator tree rendered as an expandable and collapsible tree. static get propTypes() {
*/ return {
module.exports = createClass({ dominatorTree: dominatorTreeModel.isRequired,
displayName: "DominatorTree", onLoadMoreSiblings: PropTypes.func.isRequired,
onViewSourceInDebugger: PropTypes.func.isRequired,
propTypes: { onExpand: PropTypes.func.isRequired,
dominatorTree: dominatorTreeModel.isRequired, onCollapse: PropTypes.func.isRequired,
onLoadMoreSiblings: PropTypes.func.isRequired, onFocus: PropTypes.func.isRequired,
onViewSourceInDebugger: PropTypes.func.isRequired, };
onExpand: PropTypes.func.isRequired, }
onCollapse: PropTypes.func.isRequired,
onFocus: PropTypes.func.isRequired,
},
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
// Safe to use referential equality here because all of our mutations on // Safe to use referential equality here because all of our mutations on
@ -125,7 +122,7 @@ module.exports = createClass({
// mutations to the expanded set occur. Because of the re-allocations, we // mutations to the expanded set occur. Because of the re-allocations, we
// can continue using referential equality here. // can continue using referential equality here.
return this.props.dominatorTree !== nextProps.dominatorTree; return this.props.dominatorTree !== nextProps.dominatorTree;
}, }
render() { render() {
const { dominatorTree, onViewSourceInDebugger, onLoadMoreSiblings } = this.props; const { dominatorTree, onViewSourceInDebugger, onLoadMoreSiblings } = this.props;
@ -216,4 +213,9 @@ module.exports = createClass({
itemHeight: TREE_ROW_HEIGHT, itemHeight: TREE_ROW_HEIGHT,
}); });
} }
}); }
const DominatorTreeSubtreeFetching = createFactory(DominatorTreeSubtreeFetchingClass);
const DominatorTreeSiblingLink = createFactory(DominatorTreeSiblingLinkClass);
module.exports = DominatorTree;

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

@ -4,13 +4,13 @@
"use strict"; "use strict";
const { DOM: dom, createClass } = require("devtools/client/shared/vendor/react"); const { DOM: dom, Component } = require("devtools/client/shared/vendor/react");
const { L10N } = require("../utils"); const { L10N } = require("../utils");
module.exports = createClass({ class DominatorTreeHeader extends Component {
displayName: "DominatorTreeHeader", static get propTypes() {
return { };
propTypes: { }, }
render() { render() {
return dom.div( return dom.div(
@ -43,4 +43,6 @@ module.exports = createClass({
) )
); );
} }
}); }
module.exports = DominatorTreeHeader;

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

@ -5,38 +5,38 @@
"use strict"; "use strict";
const { assert, isSavedFrame } = require("devtools/shared/DevToolsUtils"); const { assert, isSavedFrame } = require("devtools/shared/DevToolsUtils");
const { DOM: dom, createClass, createFactory, PropTypes } = require("devtools/client/shared/vendor/react"); const { DOM: dom, Component, createFactory, PropTypes } = require("devtools/client/shared/vendor/react");
const { L10N, formatNumber, formatPercent } = require("../utils"); const { L10N, formatNumber, formatPercent } = require("../utils");
const Frame = createFactory(require("devtools/client/shared/components/Frame")); const Frame = createFactory(require("devtools/client/shared/components/Frame"));
const { TREE_ROW_HEIGHT } = require("../constants"); const { TREE_ROW_HEIGHT } = require("../constants");
const Separator = createFactory(createClass({ class SeparatorClass extends Component {
displayName: "Separator",
render() { render() {
return dom.span({ className: "separator" }, ""); return dom.span({ className: "separator" }, "");
} }
})); }
module.exports = createClass({ const Separator = createFactory(SeparatorClass);
displayName: "DominatorTreeItem",
propTypes: { class DominatorTreeItem extends Component {
item: PropTypes.object.isRequired, static get propTypes() {
depth: PropTypes.number.isRequired, return {
arrow: PropTypes.object, item: PropTypes.object.isRequired,
expanded: PropTypes.bool.isRequired, depth: PropTypes.number.isRequired,
focused: PropTypes.bool.isRequired, arrow: PropTypes.object,
getPercentSize: PropTypes.func.isRequired, expanded: PropTypes.bool.isRequired,
onViewSourceInDebugger: PropTypes.func.isRequired, focused: PropTypes.bool.isRequired,
}, getPercentSize: PropTypes.func.isRequired,
onViewSourceInDebugger: PropTypes.func.isRequired,
};
}
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
return this.props.item != nextProps.item return this.props.item != nextProps.item
|| this.props.depth != nextProps.depth || this.props.depth != nextProps.depth
|| this.props.expanded != nextProps.expanded || this.props.expanded != nextProps.expanded
|| this.props.focused != nextProps.focused; || this.props.focused != nextProps.focused;
}, }
render() { render() {
let { let {
@ -141,5 +141,7 @@ module.exports = createClass({
`@ 0x${item.nodeId.toString(16)}`) `@ 0x${item.nodeId.toString(16)}`)
) )
); );
}, }
}); }
module.exports = DominatorTreeItem;

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

@ -4,7 +4,7 @@
"use strict"; "use strict";
const { DOM: dom, createClass, PropTypes, createFactory } = require("devtools/client/shared/vendor/react"); const { DOM: dom, Component, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
const { assert, safeErrorString } = require("devtools/shared/DevToolsUtils"); const { assert, safeErrorString } = require("devtools/shared/DevToolsUtils");
const Census = createFactory(require("./Census")); const Census = createFactory(require("./Census"));
const CensusHeader = createFactory(require("./CensusHeader")); const CensusHeader = createFactory(require("./CensusHeader"));
@ -180,29 +180,41 @@ function getError(snapshot, diffing, individuals) {
* state of only a button to take a snapshot, loading states, the census view * state of only a button to take a snapshot, loading states, the census view
* tree, the dominator tree, etc. * tree, the dominator tree, etc.
*/ */
module.exports = createClass({ class Heap extends Component {
displayName: "Heap", static get propTypes() {
return {
onSnapshotClick: PropTypes.func.isRequired,
onLoadMoreSiblings: PropTypes.func.isRequired,
onCensusExpand: PropTypes.func.isRequired,
onCensusCollapse: PropTypes.func.isRequired,
onDominatorTreeExpand: PropTypes.func.isRequired,
onDominatorTreeCollapse: PropTypes.func.isRequired,
onCensusFocus: PropTypes.func.isRequired,
onDominatorTreeFocus: PropTypes.func.isRequired,
onShortestPathsResize: PropTypes.func.isRequired,
snapshot: snapshotModel,
onViewSourceInDebugger: PropTypes.func.isRequired,
onPopView: PropTypes.func.isRequired,
individuals: models.individuals,
onViewIndividuals: PropTypes.func.isRequired,
onFocusIndividual: PropTypes.func.isRequired,
diffing: diffingModel,
view: models.view.isRequired,
sizes: PropTypes.object.isRequired,
};
}
propTypes: { constructor(props) {
onSnapshotClick: PropTypes.func.isRequired, super(props);
onLoadMoreSiblings: PropTypes.func.isRequired, this._renderHeapView = this._renderHeapView.bind(this);
onCensusExpand: PropTypes.func.isRequired, this._renderInitial = this._renderInitial.bind(this);
onCensusCollapse: PropTypes.func.isRequired, this._renderStatus = this._renderStatus.bind(this);
onDominatorTreeExpand: PropTypes.func.isRequired, this._renderError = this._renderError.bind(this);
onDominatorTreeCollapse: PropTypes.func.isRequired, this._renderCensus = this._renderCensus.bind(this);
onCensusFocus: PropTypes.func.isRequired, this._renderTreeMap = this._renderTreeMap.bind(this);
onDominatorTreeFocus: PropTypes.func.isRequired, this._renderIndividuals = this._renderIndividuals.bind(this);
onShortestPathsResize: PropTypes.func.isRequired, this._renderDominatorTree = this._renderDominatorTree.bind(this);
snapshot: snapshotModel, }
onViewSourceInDebugger: PropTypes.func.isRequired,
onPopView: PropTypes.func.isRequired,
individuals: models.individuals,
onViewIndividuals: PropTypes.func.isRequired,
onFocusIndividual: PropTypes.func.isRequired,
diffing: diffingModel,
view: models.view.isRequired,
sizes: PropTypes.object.isRequired,
},
/** /**
* Render the heap view's container panel with the given contents inside of * Render the heap view's container panel with the given contents inside of
@ -225,7 +237,7 @@ module.exports = createClass({
...contents ...contents
) )
); );
}, }
_renderInitial(onSnapshotClick) { _renderInitial(onSnapshotClick) {
return this._renderHeapView("initial", dom.button( return this._renderHeapView("initial", dom.button(
@ -236,7 +248,7 @@ module.exports = createClass({
}, },
L10N.getStr("take-snapshot") L10N.getStr("take-snapshot")
)); ));
}, }
_renderStatus(state, statusText, diffing) { _renderStatus(state, statusText, diffing) {
let throbber = ""; let throbber = "";
@ -250,7 +262,7 @@ module.exports = createClass({
}, },
statusText statusText
)); ));
}, }
_renderError(state, statusText, error) { _renderError(state, statusText, error) {
return this._renderHeapView( return this._renderHeapView(
@ -258,7 +270,7 @@ module.exports = createClass({
dom.span({ className: "snapshot-status error" }, statusText), dom.span({ className: "snapshot-status error" }, statusText),
dom.pre({}, safeErrorString(error)) dom.pre({}, safeErrorString(error))
); );
}, }
_renderCensus(state, census, diffing, onViewSourceInDebugger, onViewIndividuals) { _renderCensus(state, census, diffing, onViewSourceInDebugger, onViewIndividuals) {
assert(census.report, "Should not render census that does not have a report"); assert(census.report, "Should not render census that does not have a report");
@ -293,14 +305,14 @@ module.exports = createClass({
})); }));
return this._renderHeapView(state, ...contents); return this._renderHeapView(state, ...contents);
}, }
_renderTreeMap(state, treeMap) { _renderTreeMap(state, treeMap) {
return this._renderHeapView( return this._renderHeapView(
state, state,
TreeMap({ treeMap }) TreeMap({ treeMap })
); );
}, }
_renderIndividuals(state, individuals, dominatorTree, onViewSourceInDebugger) { _renderIndividuals(state, individuals, dominatorTree, onViewSourceInDebugger) {
assert(individuals.state === individualsState.FETCHED, assert(individuals.state === individualsState.FETCHED,
@ -355,7 +367,7 @@ module.exports = createClass({
onResize: this.props.onShortestPathsResize, onResize: this.props.onShortestPathsResize,
}) })
); );
}, }
_renderDominatorTree(state, onViewSourceInDebugger, dominatorTree, onLoadMoreSiblings) { _renderDominatorTree(state, onViewSourceInDebugger, dominatorTree, onLoadMoreSiblings) {
const tree = dom.div( const tree = dom.div(
@ -391,7 +403,7 @@ module.exports = createClass({
onResize: this.props.onShortestPathsResize, onResize: this.props.onShortestPathsResize,
}) })
); );
}, }
render() { render() {
let { let {
@ -454,5 +466,7 @@ module.exports = createClass({
return this._renderDominatorTree(state, onViewSourceInDebugger, return this._renderDominatorTree(state, onViewSourceInDebugger,
snapshot.dominatorTree, snapshot.dominatorTree,
onLoadMoreSiblings); onLoadMoreSiblings);
}, }
}); }
module.exports = Heap;

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

@ -4,7 +4,7 @@
"use strict"; "use strict";
const { createClass, PropTypes, createFactory } = require("devtools/client/shared/vendor/react"); const { Component, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
const Tree = createFactory(require("devtools/client/shared/components/Tree")); const Tree = createFactory(require("devtools/client/shared/components/Tree"));
const DominatorTreeItem = createFactory(require("./DominatorTreeItem")); const DominatorTreeItem = createFactory(require("./DominatorTreeItem"));
const { TREE_ROW_HEIGHT } = require("../constants"); const { TREE_ROW_HEIGHT } = require("../constants");
@ -13,15 +13,15 @@ const models = require("../models");
/** /**
* The list of individuals in a census group. * The list of individuals in a census group.
*/ */
module.exports = createClass({ class Individuals extends Component {
displayName: "Individuals", static get propTypes() {
return {
propTypes: { onViewSourceInDebugger: PropTypes.func.isRequired,
onViewSourceInDebugger: PropTypes.func.isRequired, onFocus: PropTypes.func.isRequired,
onFocus: PropTypes.func.isRequired, individuals: models.individuals,
individuals: models.individuals, dominatorTree: models.dominatorTreeModel,
dominatorTree: models.dominatorTreeModel, };
}, }
render() { render() {
const { const {
@ -57,4 +57,6 @@ module.exports = createClass({
itemHeight: TREE_ROW_HEIGHT, itemHeight: TREE_ROW_HEIGHT,
}); });
} }
}); }
module.exports = Individuals;

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

@ -4,13 +4,13 @@
"use strict"; "use strict";
const { DOM: dom, createClass } = require("devtools/client/shared/vendor/react"); const { DOM: dom, Component } = require("devtools/client/shared/vendor/react");
const { L10N } = require("../utils"); const { L10N } = require("../utils");
module.exports = createClass({ class IndividualsHeader extends Component {
displayName: "IndividualsHeader", static get propTypes() {
return { };
propTypes: { }, }
render() { render() {
return dom.div( return dom.div(
@ -43,4 +43,6 @@ module.exports = createClass({
) )
); );
} }
}); }
module.exports = IndividualsHeader;

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

@ -4,21 +4,21 @@
"use strict"; "use strict";
const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react"); const { DOM: dom, Component, PropTypes } = require("devtools/client/shared/vendor/react");
/** /**
* Generic list component that takes another react component to represent * Generic list component that takes another react component to represent
* the children nodes as `itemComponent`, and a list of items to render * the children nodes as `itemComponent`, and a list of items to render
* as that component with a click handler. * as that component with a click handler.
*/ */
module.exports = createClass({ class List extends Component {
displayName: "List", static get propTypes() {
return {
propTypes: { itemComponent: PropTypes.any.isRequired,
itemComponent: PropTypes.any.isRequired, onClick: PropTypes.func,
onClick: PropTypes.func, items: PropTypes.array.isRequired,
items: PropTypes.array.isRequired, };
}, }
render() { render() {
let { items, onClick, itemComponent: Item } = this.props; let { items, onClick, itemComponent: Item } = this.props;
@ -34,4 +34,6 @@ module.exports = createClass({
})) }))
); );
} }
}); }
module.exports = List;

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

@ -6,7 +6,7 @@
const { const {
DOM: dom, DOM: dom,
createClass, Component,
PropTypes, PropTypes,
} = require("devtools/client/shared/vendor/react"); } = require("devtools/client/shared/vendor/react");
const { isSavedFrame } = require("devtools/shared/DevToolsUtils"); const { isSavedFrame } = require("devtools/shared/DevToolsUtils");
@ -50,41 +50,43 @@ function stringifyLabel(label, id) {
return `${sanitized.join(" ")} @ 0x${id.toString(16)}`; return `${sanitized.join(" ")} @ 0x${id.toString(16)}`;
} }
module.exports = createClass({ class ShortestPaths extends Component {
displayName: "ShortestPaths", static get propTypes() {
return {
graph: PropTypes.shape({
nodes: PropTypes.arrayOf(PropTypes.object),
edges: PropTypes.arrayOf(PropTypes.object),
}),
};
}
propTypes: { constructor(props) {
graph: PropTypes.shape({ super(props);
nodes: PropTypes.arrayOf(PropTypes.object), this.state = { zoom: null };
edges: PropTypes.arrayOf(PropTypes.object), this._renderGraph = this._renderGraph.bind(this);
}), }
},
getInitialState() {
return { zoom: null };
},
componentDidMount() { componentDidMount() {
if (this.props.graph) { if (this.props.graph) {
this._renderGraph(this.refs.container, this.props.graph); this._renderGraph(this.refs.container, this.props.graph);
} }
}, }
shouldComponentUpdate(nextProps) { shouldComponentUpdate(nextProps) {
return this.props.graph != nextProps.graph; return this.props.graph != nextProps.graph;
}, }
componentDidUpdate() { componentDidUpdate() {
if (this.props.graph) { if (this.props.graph) {
this._renderGraph(this.refs.container, this.props.graph); this._renderGraph(this.refs.container, this.props.graph);
} }
}, }
componentWillUnmount() { componentWillUnmount() {
if (this.state.zoom) { if (this.state.zoom) {
this.state.zoom.on("zoom", null); this.state.zoom.on("zoom", null);
} }
}, }
_renderGraph(container, { nodes, edges }) { _renderGraph(container, { nodes, edges }) {
if (!container.firstChild) { if (!container.firstChild) {
@ -144,7 +146,7 @@ module.exports = createClass({
const layout = dagreD3.layout(); const layout = dagreD3.layout();
renderer.layout(layout).run(graph, target); renderer.layout(layout).run(graph, target);
}, }
render() { render() {
let contents; let contents;
@ -182,5 +184,7 @@ module.exports = createClass({
), ),
contents contents
); );
}, }
}); }
module.exports = ShortestPaths;

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

@ -4,7 +4,7 @@
"use strict"; "use strict";
const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react"); const { DOM: dom, Component, PropTypes } = require("devtools/client/shared/vendor/react");
const { const {
L10N, L10N,
getSnapshotTitle, getSnapshotTitle,
@ -16,17 +16,17 @@ const {
const { diffingState } = require("../constants"); const { diffingState } = require("../constants");
const { snapshot: snapshotModel, app: appModel } = require("../models"); const { snapshot: snapshotModel, app: appModel } = require("../models");
module.exports = createClass({ class SnapshotListItem extends Component {
displayName: "SnapshotListItem", static get propTypes() {
return {
propTypes: { onClick: PropTypes.func.isRequired,
onClick: PropTypes.func.isRequired, onSave: PropTypes.func.isRequired,
onSave: PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired, item: snapshotModel.isRequired,
item: snapshotModel.isRequired, index: PropTypes.number.isRequired,
index: PropTypes.number.isRequired, diffing: appModel.diffing,
diffing: appModel.diffing, };
}, }
render() { render() {
let { item: snapshot, onClick, onSave, onDelete, diffing } = this.props; let { item: snapshot, onClick, onSave, onDelete, diffing } = this.props;
@ -112,4 +112,6 @@ module.exports = createClass({
) )
); );
} }
}); }
module.exports = SnapshotListItem;

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

@ -4,46 +4,46 @@
"use strict"; "use strict";
const { assert } = require("devtools/shared/DevToolsUtils"); const { assert } = require("devtools/shared/DevToolsUtils");
const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react"); const { DOM: dom, Component, PropTypes } = require("devtools/client/shared/vendor/react");
const { L10N } = require("../utils"); const { L10N } = require("../utils");
const models = require("../models"); const models = require("../models");
const { viewState } = require("../constants"); const { viewState } = require("../constants");
module.exports = createClass({ class Toolbar extends Component {
displayName: "Toolbar", static get propTypes() {
return {
propTypes: { censusDisplays: PropTypes.arrayOf(PropTypes.shape({
censusDisplays: PropTypes.arrayOf(PropTypes.shape({ displayName: PropTypes.string.isRequired,
displayName: PropTypes.string.isRequired, })).isRequired,
})).isRequired, censusDisplay: PropTypes.shape({
censusDisplay: PropTypes.shape({ displayName: PropTypes.string.isRequired,
displayName: PropTypes.string.isRequired, }).isRequired,
}).isRequired, onTakeSnapshotClick: PropTypes.func.isRequired,
onTakeSnapshotClick: PropTypes.func.isRequired, onImportClick: PropTypes.func.isRequired,
onImportClick: PropTypes.func.isRequired, onClearSnapshotsClick: PropTypes.func.isRequired,
onClearSnapshotsClick: PropTypes.func.isRequired, onCensusDisplayChange: PropTypes.func.isRequired,
onCensusDisplayChange: PropTypes.func.isRequired, onToggleRecordAllocationStacks: PropTypes.func.isRequired,
onToggleRecordAllocationStacks: PropTypes.func.isRequired, allocations: models.allocations,
allocations: models.allocations, filterString: PropTypes.string,
filterString: PropTypes.string, setFilterString: PropTypes.func.isRequired,
setFilterString: PropTypes.func.isRequired, diffing: models.diffingModel,
diffing: models.diffingModel, onToggleDiffing: PropTypes.func.isRequired,
onToggleDiffing: PropTypes.func.isRequired, view: models.view.isRequired,
view: models.view.isRequired, onViewChange: PropTypes.func.isRequired,
onViewChange: PropTypes.func.isRequired, labelDisplays: PropTypes.arrayOf(PropTypes.shape({
labelDisplays: PropTypes.arrayOf(PropTypes.shape({ displayName: PropTypes.string.isRequired,
displayName: PropTypes.string.isRequired, })).isRequired,
})).isRequired, labelDisplay: PropTypes.shape({
labelDisplay: PropTypes.shape({ displayName: PropTypes.string.isRequired,
displayName: PropTypes.string.isRequired, }).isRequired,
}).isRequired, onLabelDisplayChange: PropTypes.func.isRequired,
onLabelDisplayChange: PropTypes.func.isRequired, treeMapDisplays: PropTypes.arrayOf(PropTypes.shape({
treeMapDisplays: PropTypes.arrayOf(PropTypes.shape({ displayName: PropTypes.string.isRequired,
displayName: PropTypes.string.isRequired, })).isRequired,
})).isRequired, onTreeMapDisplayChange: PropTypes.func.isRequired,
onTreeMapDisplayChange: PropTypes.func.isRequired, snapshots: PropTypes.arrayOf(models.snapshot).isRequired,
snapshots: PropTypes.arrayOf(models.snapshot).isRequired, };
}, }
render() { render() {
let { let {
@ -298,4 +298,6 @@ module.exports = createClass({
) )
); );
} }
}); }
module.exports = Toolbar;

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

@ -4,33 +4,36 @@
"use strict"; "use strict";
const { DOM: dom, createClass } = require("devtools/client/shared/vendor/react"); const { DOM: dom, Component } = require("devtools/client/shared/vendor/react");
const { treeMapModel } = require("../models"); const { treeMapModel } = require("../models");
const startVisualization = require("./tree-map/start"); const startVisualization = require("./tree-map/start");
module.exports = createClass({ class TreeMap extends Component {
displayName: "TreeMap", static get propTypes() {
return {
treeMap: treeMapModel
};
}
propTypes: { constructor(props) {
treeMap: treeMapModel super(props);
}, this.state = {};
this._stopVisualization = this._stopVisualization.bind(this);
getInitialState() { this._startVisualization = this._startVisualization.bind(this);
return {}; }
},
componentDidMount() { componentDidMount() {
const { treeMap } = this.props; const { treeMap } = this.props;
if (treeMap && treeMap.report) { if (treeMap && treeMap.report) {
this._startVisualization(); this._startVisualization();
} }
}, }
shouldComponentUpdate(nextProps) { shouldComponentUpdate(nextProps) {
const oldTreeMap = this.props.treeMap; const oldTreeMap = this.props.treeMap;
const newTreeMap = nextProps.treeMap; const newTreeMap = nextProps.treeMap;
return oldTreeMap !== newTreeMap; return oldTreeMap !== newTreeMap;
}, }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
this._stopVisualization(); this._stopVisualization();
@ -38,27 +41,27 @@ module.exports = createClass({
if (this.props.treeMap && this.props.treeMap.report) { if (this.props.treeMap && this.props.treeMap.report) {
this._startVisualization(); this._startVisualization();
} }
}, }
componentWillUnmount() { componentWillUnmount() {
if (this.state.stopVisualization) { if (this.state.stopVisualization) {
this.state.stopVisualization(); this.state.stopVisualization();
} }
}, }
_stopVisualization() { _stopVisualization() {
if (this.state.stopVisualization) { if (this.state.stopVisualization) {
this.state.stopVisualization(); this.state.stopVisualization();
this.setState({ stopVisualization: null }); this.setState({ stopVisualization: null });
} }
}, }
_startVisualization() { _startVisualization() {
const { container } = this.refs; const { container } = this.refs;
const { report } = this.props.treeMap; const { report } = this.props.treeMap;
const stopVisualization = startVisualization(container, report); const stopVisualization = startVisualization(container, report);
this.setState({ stopVisualization }); this.setState({ stopVisualization });
}, }
render() { render() {
return dom.div( return dom.div(
@ -68,4 +71,6 @@ module.exports = createClass({
} }
); );
} }
}); }
module.exports = TreeMap;

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

@ -4,48 +4,55 @@
"use strict"; "use strict";
const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react"); const { DOM: dom, Component, PropTypes } = require("devtools/client/shared/vendor/react");
module.exports = createClass({ class AutocompletePopup extends Component {
displayName: "AutocompletePopup", static get propTypes() {
return {
/**
* autocompleteProvider takes search-box's entire input text as `filter` argument
* ie. "is:cached pr"
* returned value is array of objects like below
* [{value: "is:cached protocol", displayValue: "protocol"}[, ...]]
* `value` is used to update the search-box input box for given item
* `displayValue` is used to render the autocomplete list
*/
autocompleteProvider: PropTypes.func.isRequired,
filter: PropTypes.string.isRequired,
onItemSelected: PropTypes.func.isRequired,
};
}
propTypes: { constructor(props, context) {
/** super(props, context);
* autocompleteProvider takes search-box's entire input text as `filter` argument this.state = this.computeState(props);
* ie. "is:cached pr" this.computeState = this.computeState.bind(this);
* returned value is array of objects like below this.jumpToTop = this.jumpToTop.bind(this);
* [{value: "is:cached protocol", displayValue: "protocol"}[, ...]] this.jumpToBottom = this.jumpToBottom.bind(this);
* `value` is used to update the search-box input box for given item this.jumpBy = this.jumpBy.bind(this);
* `displayValue` is used to render the autocomplete list this.select = this.select.bind(this);
*/ this.onMouseDown = this.onMouseDown.bind(this);
autocompleteProvider: PropTypes.func.isRequired, }
filter: PropTypes.string.isRequired,
onItemSelected: PropTypes.func.isRequired,
},
getInitialState() {
return this.computeState(this.props);
},
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
if (this.props.filter === nextProps.filter) { if (this.props.filter === nextProps.filter) {
return; return;
} }
this.setState(this.computeState(nextProps)); this.setState(this.computeState(nextProps));
}, }
componentDidUpdate() { componentDidUpdate() {
if (this.refs.selected) { if (this.refs.selected) {
this.refs.selected.scrollIntoView(false); this.refs.selected.scrollIntoView(false);
} }
}, }
computeState({ autocompleteProvider, filter }) { computeState({ autocompleteProvider, filter }) {
let list = autocompleteProvider(filter); let list = autocompleteProvider(filter);
let selectedIndex = list.length == 1 ? 0 : -1; let selectedIndex = list.length == 1 ? 0 : -1;
return { list, selectedIndex }; return { list, selectedIndex };
}, }
/** /**
* Use this method to select the top-most item * Use this method to select the top-most item
@ -53,7 +60,7 @@ module.exports = createClass({
*/ */
jumpToTop() { jumpToTop() {
this.setState({ selectedIndex: 0 }); this.setState({ selectedIndex: 0 });
}, }
/** /**
* Use this method to select the bottom-most item * Use this method to select the bottom-most item
@ -61,7 +68,7 @@ module.exports = createClass({
*/ */
jumpToBottom() { jumpToBottom() {
this.setState({ selectedIndex: this.state.list.length - 1 }); this.setState({ selectedIndex: this.state.list.length - 1 });
}, }
/** /**
* Increment the selected index with the provided increment value. Will cycle to the * Increment the selected index with the provided increment value. Will cycle to the
@ -81,7 +88,7 @@ module.exports = createClass({
nextIndex = nextIndex < 0 ? list.length - 1 : nextIndex; nextIndex = nextIndex < 0 ? list.length - 1 : nextIndex;
} }
this.setState({selectedIndex: nextIndex}); this.setState({selectedIndex: nextIndex});
}, }
/** /**
* Submit the currently selected item to the onItemSelected callback * Submit the currently selected item to the onItemSelected callback
@ -91,12 +98,12 @@ module.exports = createClass({
if (this.refs.selected) { if (this.refs.selected) {
this.props.onItemSelected(this.refs.selected.dataset.value); this.props.onItemSelected(this.refs.selected.dataset.value);
} }
}, }
onMouseDown(e) { onMouseDown(e) {
e.preventDefault(); e.preventDefault();
this.setState({ selectedIndex: Number(e.target.dataset.index) }, this.select); this.setState({ selectedIndex: Number(e.target.dataset.index) }, this.select);
}, }
render() { render() {
let { list } = this.state; let { list } = this.state;
@ -124,4 +131,6 @@ module.exports = createClass({
) )
); );
} }
}); }
module.exports = AutocompletePopup;

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

@ -4,7 +4,7 @@
"use strict"; "use strict";
const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react"); const { DOM: dom, Component, PropTypes } = require("devtools/client/shared/vendor/react");
const { getSourceNames, parseURL, const { getSourceNames, parseURL,
isScratchpadScheme, getSourceMappedFile } = require("devtools/client/shared/source-utils"); isScratchpadScheme, getSourceMappedFile } = require("devtools/client/shared/source-utils");
const { LocalizationHelper } = require("devtools/shared/l10n"); const { LocalizationHelper } = require("devtools/shared/l10n");
@ -12,34 +12,34 @@ const { LocalizationHelper } = require("devtools/shared/l10n");
const l10n = new LocalizationHelper("devtools/client/locales/components.properties"); const l10n = new LocalizationHelper("devtools/client/locales/components.properties");
const webl10n = new LocalizationHelper("devtools/client/locales/webconsole.properties"); const webl10n = new LocalizationHelper("devtools/client/locales/webconsole.properties");
module.exports = createClass({ class Frame extends Component {
displayName: "Frame", static get propTypes() {
return {
// SavedFrame, or an object containing all the required properties.
frame: PropTypes.shape({
functionDisplayName: PropTypes.string,
source: PropTypes.string.isRequired,
line: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
column: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
}).isRequired,
// Clicking on the frame link -- probably should link to the debugger.
onClick: PropTypes.func.isRequired,
// Option to display a function name before the source link.
showFunctionName: PropTypes.bool,
// Option to display a function name even if it's anonymous.
showAnonymousFunctionName: PropTypes.bool,
// Option to display a host name after the source link.
showHost: PropTypes.bool,
// Option to display a host name if the filename is empty or just '/'
showEmptyPathAsHost: PropTypes.bool,
// Option to display a full source instead of just the filename.
showFullSourceUrl: PropTypes.bool,
// Service to enable the source map feature for console.
sourceMapService: PropTypes.object,
};
}
propTypes: { static get defaultProps() {
// SavedFrame, or an object containing all the required properties.
frame: PropTypes.shape({
functionDisplayName: PropTypes.string,
source: PropTypes.string.isRequired,
line: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
column: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
}).isRequired,
// Clicking on the frame link -- probably should link to the debugger.
onClick: PropTypes.func.isRequired,
// Option to display a function name before the source link.
showFunctionName: PropTypes.bool,
// Option to display a function name even if it's anonymous.
showAnonymousFunctionName: PropTypes.bool,
// Option to display a host name after the source link.
showHost: PropTypes.bool,
// Option to display a host name if the filename is empty or just '/'
showEmptyPathAsHost: PropTypes.bool,
// Option to display a full source instead of just the filename.
showFullSourceUrl: PropTypes.bool,
// Service to enable the source map feature for console.
sourceMapService: PropTypes.object,
},
getDefaultProps() {
return { return {
showFunctionName: false, showFunctionName: false,
showAnonymousFunctionName: false, showAnonymousFunctionName: false,
@ -47,7 +47,13 @@ module.exports = createClass({
showEmptyPathAsHost: false, showEmptyPathAsHost: false,
showFullSourceUrl: false, showFullSourceUrl: false,
}; };
}, }
constructor(props) {
super(props);
this._locationChanged = this._locationChanged.bind(this);
this.getSourceForClick = this.getSourceForClick.bind(this);
}
componentWillMount() { componentWillMount() {
if (this.props.sourceMapService) { if (this.props.sourceMapService) {
@ -55,7 +61,7 @@ module.exports = createClass({
this.props.sourceMapService.subscribe(source, line, column, this.props.sourceMapService.subscribe(source, line, column,
this._locationChanged); this._locationChanged);
} }
}, }
componentWillUnmount() { componentWillUnmount() {
if (this.props.sourceMapService) { if (this.props.sourceMapService) {
@ -63,7 +69,7 @@ module.exports = createClass({
this.props.sourceMapService.unsubscribe(source, line, column, this.props.sourceMapService.unsubscribe(source, line, column,
this._locationChanged); this._locationChanged);
} }
}, }
_locationChanged(isSourceMapped, url, line, column) { _locationChanged(isSourceMapped, url, line, column) {
let newState = { let newState = {
@ -79,7 +85,7 @@ module.exports = createClass({
} }
this.setState(newState); this.setState(newState);
}, }
/** /**
* Utility method to convert the Frame object model to the * Utility method to convert the Frame object model to the
@ -95,7 +101,7 @@ module.exports = createClass({
column, column,
functionDisplayName: this.props.frame.functionDisplayName, functionDisplayName: this.props.frame.functionDisplayName,
}; };
}, }
render() { render() {
let frame, isSourceMapped; let frame, isSourceMapped;
@ -235,4 +241,6 @@ module.exports = createClass({
return dom.span(attributes, ...elements); return dom.span(attributes, ...elements);
} }
}); }
module.exports = Frame;

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

@ -25,61 +25,67 @@
const { const {
DOM: dom, DOM: dom,
createClass, Component,
PropTypes, PropTypes,
} = require("devtools/client/shared/vendor/react"); } = require("devtools/client/shared/vendor/react");
const { assert } = require("devtools/shared/DevToolsUtils"); const { assert } = require("devtools/shared/DevToolsUtils");
module.exports = createClass({ class HSplitBox extends Component {
displayName: "HSplitBox", static get propTypes() {
return {
// The contents of the start pane.
start: PropTypes.any.isRequired,
propTypes: { // The contents of the end pane.
// The contents of the start pane. end: PropTypes.any.isRequired,
start: PropTypes.any.isRequired,
// The contents of the end pane. // The relative width of the start pane, expressed as a number between 0 and
end: PropTypes.any.isRequired, // 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,
// The relative width of the start pane, expressed as a number between 0 and // A minimum css width value for the start and end panes.
// 1. The relative width of the end pane is 1 - startWidth. For example, minStartWidth: PropTypes.any,
// with startWidth = .5, both panes are of equal width; with startWidth = minEndWidth: PropTypes.any,
// .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. // A callback fired when the user drags the splitter to resize the relative
minStartWidth: PropTypes.any, // pane widths. The function is passed the startWidth value that would put
minEndWidth: PropTypes.any, // the splitter underneath the users mouse.
onResize: PropTypes.func.isRequired,
};
}
// A callback fired when the user drags the splitter to resize the relative static get defaultProps() {
// pane widths. The function is passed the startWidth value that would put
// the splitter underneath the users mouse.
onResize: PropTypes.func.isRequired,
},
getDefaultProps() {
return { return {
startWidth: 0.5, startWidth: 0.5,
minStartWidth: "20px", minStartWidth: "20px",
minEndWidth: "20px", minEndWidth: "20px",
}; };
}, }
getInitialState() { constructor(props) {
return { super(props);
this.state = {
mouseDown: false mouseDown: false
}; };
},
this._onMouseDown = this._onMouseDown.bind(this);
this._onMouseUp = this._onMouseUp.bind(this);
this._onMouseMove = this._onMouseMove.bind(this);
}
componentDidMount() { componentDidMount() {
document.defaultView.top.addEventListener("mouseup", this._onMouseUp); document.defaultView.top.addEventListener("mouseup", this._onMouseUp);
document.defaultView.top.addEventListener("mousemove", this._onMouseMove); document.defaultView.top.addEventListener("mousemove", this._onMouseMove);
}, }
componentWillUnmount() { componentWillUnmount() {
document.defaultView.top.removeEventListener("mouseup", this._onMouseUp); document.defaultView.top.removeEventListener("mouseup", this._onMouseUp);
document.defaultView.top.removeEventListener("mousemove", this._onMouseMove); document.defaultView.top.removeEventListener("mousemove", this._onMouseMove);
}, }
_onMouseDown(event) { _onMouseDown(event) {
if (event.button !== 0) { if (event.button !== 0) {
@ -88,7 +94,7 @@ module.exports = createClass({
this.setState({ mouseDown: true }); this.setState({ mouseDown: true });
event.preventDefault(); event.preventDefault();
}, }
_onMouseUp(event) { _onMouseUp(event) {
if (event.button !== 0 || !this.state.mouseDown) { if (event.button !== 0 || !this.state.mouseDown) {
@ -97,7 +103,7 @@ module.exports = createClass({
this.setState({ mouseDown: false }); this.setState({ mouseDown: false });
event.preventDefault(); event.preventDefault();
}, }
_onMouseMove(event) { _onMouseMove(event) {
if (!this.state.mouseDown) { if (!this.state.mouseDown) {
@ -113,7 +119,7 @@ module.exports = createClass({
this.props.onResize(relative / width); this.props.onResize(relative / width);
event.preventDefault(); event.preventDefault();
}, }
render() { render() {
/* eslint-disable no-shadow */ /* eslint-disable no-shadow */
@ -149,4 +155,6 @@ module.exports = createClass({
) )
); );
} }
}); }
module.exports = HSplitBox;

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

@ -10,7 +10,7 @@ const { LocalizationHelper } = require("devtools/shared/l10n");
const l10n = new LocalizationHelper("devtools/client/locales/components.properties"); const l10n = new LocalizationHelper("devtools/client/locales/components.properties");
// Shortcuts // Shortcuts
const { PropTypes, createClass, DOM } = React; const { PropTypes, Component, DOM } = React;
const { div, span, button } = DOM; const { div, span, button } = DOM;
// Priority Levels // Priority Levels
@ -34,72 +34,81 @@ const PriorityLevels = {
* See also MDN for more info about <xul:notificationbox>: * See also MDN for more info about <xul:notificationbox>:
* https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/notificationbox * https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/notificationbox
*/ */
var NotificationBox = createClass({ class NotificationBox extends Component {
displayName: "NotificationBox", static get propTypes() {
return {
propTypes: { // List of notifications appended into the box.
// List of notifications appended into the box. notifications: PropTypes.arrayOf(PropTypes.shape({
notifications: PropTypes.arrayOf(PropTypes.shape({ // label to appear on the notification.
// label to appear on the notification.
label: PropTypes.string.isRequired,
// Value used to identify the notification
value: PropTypes.string.isRequired,
// URL of image to appear on the notification. If "" then an icon
// appropriate for the priority level is used.
image: PropTypes.string.isRequired,
// Notification priority; see Priority Levels.
priority: PropTypes.number.isRequired,
// Array of button descriptions to appear on the notification.
buttons: PropTypes.arrayOf(PropTypes.shape({
// Function to be called when the button is activated.
// This function is passed three arguments:
// 1) the NotificationBox component the button is associated with
// 2) the button description as passed to appendNotification.
// 3) the element which was the target of the button press event.
// If the return value from this function is not True, then the
// notification is closed. The notification is also not closed
// if an error is thrown.
callback: PropTypes.func.isRequired,
// The label to appear on the button.
label: PropTypes.string.isRequired, label: PropTypes.string.isRequired,
// The accesskey attribute set on the <button> element. // Value used to identify the notification
accesskey: PropTypes.string, value: PropTypes.string.isRequired,
// URL of image to appear on the notification. If "" then an icon
// appropriate for the priority level is used.
image: PropTypes.string.isRequired,
// Notification priority; see Priority Levels.
priority: PropTypes.number.isRequired,
// Array of button descriptions to appear on the notification.
buttons: PropTypes.arrayOf(PropTypes.shape({
// Function to be called when the button is activated.
// This function is passed three arguments:
// 1) the NotificationBox component the button is associated with
// 2) the button description as passed to appendNotification.
// 3) the element which was the target of the button press event.
// If the return value from this function is not True, then the
// notification is closed. The notification is also not closed
// if an error is thrown.
callback: PropTypes.func.isRequired,
// The label to appear on the button.
label: PropTypes.string.isRequired,
// The accesskey attribute set on the <button> element.
accesskey: PropTypes.string,
})),
// A function to call to notify you of interesting things that happen
// with the notification box.
eventCallback: PropTypes.func,
})), })),
// A function to call to notify you of interesting things that happen // Message that should be shown when hovering over the close button
// with the notification box. closeButtonTooltip: PropTypes.string
eventCallback: PropTypes.func, };
})), }
// Message that should be shown when hovering over the close button static get defaultProps() {
closeButtonTooltip: PropTypes.string
},
getDefaultProps() {
return { return {
closeButtonTooltip: l10n.getStr("notificationBox.closeTooltip") closeButtonTooltip: l10n.getStr("notificationBox.closeTooltip")
}; };
}, }
getInitialState() { constructor(props) {
return { super(props);
this.state = {
notifications: new Immutable.OrderedMap() notifications: new Immutable.OrderedMap()
}; };
},
this.appendNotification = this.appendNotification.bind(this);
this.removeNotification = this.removeNotification.bind(this);
this.getNotificationWithValue = this.getNotificationWithValue.bind(this);
this.getCurrentNotification = this.getCurrentNotification.bind(this);
this.close = this.close.bind(this);
this.renderButton = this.renderButton.bind(this);
this.renderNotification = this.renderNotification.bind(this);
}
/** /**
* Create a new notification and display it. If another notification is * Create a new notification and display it. If another notification is
* already present with a higher priority, the new notification will be * already present with a higher priority, the new notification will be
* added behind it. See `propTypes` for arguments description. * added behind it. See `propTypes` for arguments description.
*/ */
appendNotification(label, value, image, priority, buttons = [], appendNotification(label, value, image, priority, buttons = [], eventCallback) {
eventCallback) {
// Priority level must be within expected interval // Priority level must be within expected interval
// (see priority levels at the top of this file). // (see priority levels at the top of this file).
if (priority < PriorityLevels.PRIORITY_INFO_LOW || if (priority < PriorityLevels.PRIORITY_INFO_LOW ||
@ -137,14 +146,14 @@ var NotificationBox = createClass({
this.setState({ this.setState({
notifications: notifications notifications: notifications
}); });
}, }
/** /**
* Remove specific notification from the list. * Remove specific notification from the list.
*/ */
removeNotification(notification) { removeNotification(notification) {
this.close(this.state.notifications.get(notification.value)); this.close(this.state.notifications.get(notification.value));
}, }
/** /**
* Returns an object that represents a notification. It can be * Returns an object that represents a notification. It can be
@ -163,11 +172,11 @@ var NotificationBox = createClass({
this.close(notification); this.close(notification);
} }
}); });
}, }
getCurrentNotification() { getCurrentNotification() {
return this.state.notifications.first(); return this.state.notifications.first();
}, }
/** /**
* Close specified notification. * Close specified notification.
@ -184,7 +193,7 @@ var NotificationBox = createClass({
this.setState({ this.setState({
notifications: this.state.notifications.remove(notification.value) notifications: this.state.notifications.remove(notification.value)
}); });
}, }
/** /**
* Render a button. A notification can have a set of custom buttons. * Render a button. A notification can have a set of custom buttons.
@ -210,7 +219,7 @@ var NotificationBox = createClass({
props.label props.label
) )
); );
}, }
/** /**
* Render a notification. * Render a notification.
@ -241,7 +250,7 @@ var NotificationBox = createClass({
) )
) )
); );
}, }
/** /**
* Render the top (highest priority) notification. Only one * Render the top (highest priority) notification. Only one
@ -256,8 +265,8 @@ var NotificationBox = createClass({
return div({className: "notificationbox"}, return div({className: "notificationbox"},
content content
); );
}, }
}); }
module.exports.NotificationBox = NotificationBox; module.exports.NotificationBox = NotificationBox;
module.exports.PriorityLevels = PriorityLevels; module.exports.PriorityLevels = PriorityLevels;

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

@ -6,31 +6,36 @@
"use strict"; "use strict";
const { DOM: dom, createClass, PropTypes, createFactory } = require("devtools/client/shared/vendor/react"); const { DOM: dom, Component, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
const KeyShortcuts = require("devtools/client/shared/key-shortcuts"); const KeyShortcuts = require("devtools/client/shared/key-shortcuts");
const AutocompletePopup = createFactory(require("devtools/client/shared/components/AutoCompletePopup")); const AutocompletePopup = createFactory(require("devtools/client/shared/components/AutoCompletePopup"));
/** class SearchBox extends Component {
* A generic search box component for use across devtools static get propTypes() {
*/
module.exports = createClass({
displayName: "SearchBox",
propTypes: {
delay: PropTypes.number,
keyShortcut: PropTypes.string,
onChange: PropTypes.func,
placeholder: PropTypes.string,
type: PropTypes.string,
autocompleteProvider: PropTypes.func,
},
getInitialState() {
return { return {
delay: PropTypes.number,
keyShortcut: PropTypes.string,
onChange: PropTypes.func,
placeholder: PropTypes.string,
type: PropTypes.string,
autocompleteProvider: PropTypes.func,
};
}
constructor(props) {
super(props);
this.state = {
value: "", value: "",
focused: false, focused: false,
}; };
},
this.onChange = this.onChange.bind(this);
this.onClearButtonClick = this.onClearButtonClick.bind(this);
this.onFocus = this.onFocus.bind(this);
this.onBlur = this.onBlur.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);
}
componentDidMount() { componentDidMount() {
if (!this.props.keyShortcut) { if (!this.props.keyShortcut) {
@ -44,7 +49,7 @@ module.exports = createClass({
event.preventDefault(); event.preventDefault();
this.refs.input.focus(); this.refs.input.focus();
}); });
}, }
componentWillUnmount() { componentWillUnmount() {
if (this.shortcuts) { if (this.shortcuts) {
@ -55,7 +60,7 @@ module.exports = createClass({
if (this.searchTimeout) { if (this.searchTimeout) {
clearTimeout(this.searchTimeout); clearTimeout(this.searchTimeout);
} }
}, }
onChange() { onChange() {
if (this.state.value !== this.refs.input.value) { if (this.state.value !== this.refs.input.value) {
@ -81,20 +86,20 @@ module.exports = createClass({
this.searchTimeout = null; this.searchTimeout = null;
this.props.onChange(this.state.value); this.props.onChange(this.state.value);
}, this.props.delay); }, this.props.delay);
}, }
onClearButtonClick() { onClearButtonClick() {
this.refs.input.value = ""; this.refs.input.value = "";
this.onChange(); this.onChange();
}, }
onFocus() { onFocus() {
this.setState({ focused: true }); this.setState({ focused: true });
}, }
onBlur() { onBlur() {
this.setState({ focused: false }); this.setState({ focused: false });
}, }
onKeyDown(e) { onKeyDown(e) {
let { autocomplete } = this.refs; let { autocomplete } = this.refs;
@ -131,7 +136,7 @@ module.exports = createClass({
autocomplete.jumpToBottom(); autocomplete.jumpToBottom();
break; break;
} }
}, }
render() { render() {
let { let {
@ -175,4 +180,6 @@ module.exports = createClass({
}) })
); );
} }
}); }
module.exports = SearchBox;

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

@ -6,7 +6,7 @@
"use strict"; "use strict";
const { DOM, createClass, PropTypes } = require("devtools/client/shared/vendor/react"); const { DOM, Component, PropTypes } = require("devtools/client/shared/vendor/react");
// Shortcuts // Shortcuts
const { button } = DOM; const { button } = DOM;
@ -15,35 +15,39 @@ const { button } = DOM;
* Sidebar toggle button. This button is used to exapand * Sidebar toggle button. This button is used to exapand
* and collapse Sidebar. * and collapse Sidebar.
*/ */
var SidebarToggle = createClass({ class SidebarToggle extends Component {
displayName: "SidebarToggle", static get propTypes() {
propTypes: {
// Set to true if collapsed.
collapsed: PropTypes.bool.isRequired,
// Tooltip text used when the button indicates expanded state.
collapsePaneTitle: PropTypes.string.isRequired,
// Tooltip text used when the button indicates collapsed state.
expandPaneTitle: PropTypes.string.isRequired,
// Click callback
onClick: PropTypes.func.isRequired,
},
getInitialState: function () {
return { return {
collapsed: this.props.collapsed, // Set to true if collapsed.
collapsed: PropTypes.bool.isRequired,
// Tooltip text used when the button indicates expanded state.
collapsePaneTitle: PropTypes.string.isRequired,
// Tooltip text used when the button indicates collapsed state.
expandPaneTitle: PropTypes.string.isRequired,
// Click callback
onClick: PropTypes.func.isRequired,
}; };
}, }
constructor(props) {
super(props);
this.state = {
collapsed: props.collapsed,
};
this.onClick = this.onClick.bind(this);
}
// Events // Events
onClick: function (event) { onClick(event) {
this.props.onClick(event); this.props.onClick(event);
}, }
// Rendering // Rendering
render: function () { render() {
let title = this.state.collapsed ? let title = this.state.collapsed ?
this.props.expandPaneTitle : this.props.expandPaneTitle :
this.props.collapsePaneTitle; this.props.collapsePaneTitle;
@ -61,6 +65,6 @@ var SidebarToggle = createClass({
}) })
); );
} }
}); }
module.exports = SidebarToggle; module.exports = SidebarToggle;

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

@ -5,18 +5,18 @@
"use strict"; "use strict";
const React = require("devtools/client/shared/vendor/react"); const React = require("devtools/client/shared/vendor/react");
const { DOM: dom, createClass, createFactory, PropTypes } = React; const { DOM: dom, Component, createFactory, PropTypes } = React;
const { LocalizationHelper } = require("devtools/shared/l10n"); const { LocalizationHelper } = require("devtools/shared/l10n");
const Frame = createFactory(require("./Frame")); const Frame = createFactory(require("./Frame"));
const l10n = new LocalizationHelper("devtools/client/locales/webconsole.properties"); const l10n = new LocalizationHelper("devtools/client/locales/webconsole.properties");
const AsyncFrame = createFactory(createClass({ class AsyncFrameClass extends Component {
displayName: "AsyncFrame", static get propTypes() {
return {
propTypes: { asyncCause: PropTypes.string.isRequired
asyncCause: PropTypes.string.isRequired };
}, }
render() { render() {
let { asyncCause } = this.props; let { asyncCause } = this.props;
@ -26,18 +26,18 @@ const AsyncFrame = createFactory(createClass({
l10n.getFormatStr("stacktrace.asyncStack", asyncCause) l10n.getFormatStr("stacktrace.asyncStack", asyncCause)
); );
} }
})); }
const StackTrace = createClass({ class StackTrace extends Component {
displayName: "StackTrace", static get propTypes() {
return {
propTypes: { stacktrace: PropTypes.array.isRequired,
stacktrace: PropTypes.array.isRequired, onViewSourceInDebugger: PropTypes.func.isRequired,
onViewSourceInDebugger: PropTypes.func.isRequired, onViewSourceInScratchpad: PropTypes.func,
onViewSourceInScratchpad: PropTypes.func, // Service to enable the source map feature.
// Service to enable the source map feature. sourceMapService: PropTypes.object,
sourceMapService: PropTypes.object, };
}, }
render() { render() {
let { let {
@ -77,6 +77,8 @@ const StackTrace = createClass({
return dom.div({ className: "stack-trace" }, frames); return dom.div({ className: "stack-trace" }, frames);
} }
}); }
const AsyncFrame = createFactory(AsyncFrameClass);
module.exports = StackTrace; module.exports = StackTrace;

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

@ -5,7 +5,7 @@
"use strict"; "use strict";
const React = require("devtools/client/shared/vendor/react"); const React = require("devtools/client/shared/vendor/react");
const { DOM: dom, createClass, createFactory, PropTypes } = React; const { DOM: dom, Component, createFactory, PropTypes } = React;
const AUTO_EXPAND_DEPTH = 0; const AUTO_EXPAND_DEPTH = 0;
const NUMBER_OF_OFFSCREEN_ITEMS = 1; const NUMBER_OF_OFFSCREEN_ITEMS = 1;
@ -97,158 +97,176 @@ const NUMBER_OF_OFFSCREEN_ITEMS = 1;
* } * }
* }); * });
*/ */
module.exports = createClass({ class Tree extends Component {
displayName: "Tree", static get propTypes() {
return {
// Required props
propTypes: { // A function to get an item's parent, or null if it is a root.
// Required props //
// Type: getParent(item: Item) -> Maybe<Item>
//
// Example:
//
// // The parent of this item is stored in its `parent` property.
// getParent: item => item.parent
getParent: PropTypes.func.isRequired,
// A function to get an item's parent, or null if it is a root. // A function to get an item's children.
// //
// Type: getParent(item: Item) -> Maybe<Item> // Type: getChildren(item: Item) -> [Item]
// //
// Example: // Example:
// //
// // The parent of this item is stored in its `parent` property. // // This item's children are stored in its `children` property.
// getParent: item => item.parent // getChildren: item => item.children
getParent: PropTypes.func.isRequired, getChildren: PropTypes.func.isRequired,
// A function to get an item's children. // A function which takes an item and ArrowExpander component instance and
// // returns a component, or text, or anything else that React considers
// Type: getChildren(item: Item) -> [Item] // renderable.
// //
// Example: // Type: renderItem(item: Item,
// // depth: Number,
// // This item's children are stored in its `children` property. // isFocused: Boolean,
// getChildren: item => item.children // arrow: ReactComponent,
getChildren: PropTypes.func.isRequired, // isExpanded: Boolean) -> ReactRenderable
//
// Example:
//
// renderItem: (item, depth, isFocused, arrow, isExpanded) => {
// let className = "my-tree-item";
// if (isFocused) {
// className += " focused";
// }
// return dom.div(
// {
// className,
// style: { marginLeft: depth * 10 + "px" }
// },
// arrow,
// dom.span({ className: "my-tree-item-label" }, item.label)
// );
// },
renderItem: PropTypes.func.isRequired,
// A function which takes an item and ArrowExpander component instance and // A function which returns the roots of the tree (forest).
// returns a component, or text, or anything else that React considers //
// renderable. // Type: getRoots() -> [Item]
// //
// Type: renderItem(item: Item, // Example:
// depth: Number, //
// isFocused: Boolean, // // In this case, we only have one top level, root item. You could
// arrow: ReactComponent, // // return multiple items if you have many top level items in your
// isExpanded: Boolean) -> ReactRenderable // // tree.
// // getRoots: () => [this.props.rootOfMyTree]
// Example: getRoots: PropTypes.func.isRequired,
//
// renderItem: (item, depth, isFocused, arrow, isExpanded) => {
// let className = "my-tree-item";
// if (isFocused) {
// className += " focused";
// }
// return dom.div(
// {
// className,
// style: { marginLeft: depth * 10 + "px" }
// },
// arrow,
// dom.span({ className: "my-tree-item-label" }, item.label)
// );
// },
renderItem: PropTypes.func.isRequired,
// A function which returns the roots of the tree (forest). // A function to get a unique key for the given item. This helps speed up
// // React's rendering a *TON*.
// Type: getRoots() -> [Item] //
// // Type: getKey(item: Item) -> String
// Example: //
// // Example:
// // In this case, we only have one top level, root item. You could //
// // return multiple items if you have many top level items in your // getKey: item => `my-tree-item-${item.uniqueId}`
// // tree. getKey: PropTypes.func.isRequired,
// getRoots: () => [this.props.rootOfMyTree]
getRoots: PropTypes.func.isRequired,
// A function to get a unique key for the given item. This helps speed up // A function to get whether an item is expanded or not. If an item is not
// React's rendering a *TON*. // expanded, then it must be collapsed.
// //
// Type: getKey(item: Item) -> String // Type: isExpanded(item: Item) -> Boolean
// //
// Example: // Example:
// //
// getKey: item => `my-tree-item-${item.uniqueId}` // isExpanded: item => item.expanded,
getKey: PropTypes.func.isRequired, isExpanded: PropTypes.func.isRequired,
// A function to get whether an item is expanded or not. If an item is not // The height of an item in the tree including margin and padding, in
// expanded, then it must be collapsed. // pixels.
// itemHeight: PropTypes.number.isRequired,
// Type: isExpanded(item: Item) -> Boolean
//
// Example:
//
// isExpanded: item => item.expanded,
isExpanded: PropTypes.func.isRequired,
// The height of an item in the tree including margin and padding, in // Optional props
// pixels.
itemHeight: PropTypes.number.isRequired,
// Optional props // The currently focused item, if any such item exists.
focused: PropTypes.any,
// The currently focused item, if any such item exists. // Handle when a new item is focused.
focused: PropTypes.any, onFocus: PropTypes.func,
// Handle when a new item is focused. // The depth to which we should automatically expand new items.
onFocus: PropTypes.func, autoExpandDepth: PropTypes.number,
// The depth to which we should automatically expand new items. // Note: the two properties below are mutually exclusive. Only one of the
autoExpandDepth: PropTypes.number, // label properties is necessary.
// ID of an element whose textual content serves as an accessible label for
// a tree.
labelledby: PropTypes.string,
// Accessibility label for a tree widget.
label: PropTypes.string,
// Note: the two properties below are mutually exclusive. Only one of the // Optional event handlers for when items are expanded or collapsed. Useful
// label properties is necessary. // for dispatching redux events and updating application state, maybe lazily
// ID of an element whose textual content serves as an accessible label for // loading subtrees from a worker, etc.
// a tree. //
labelledby: PropTypes.string, // Type:
// Accessibility label for a tree widget. // onExpand(item: Item)
label: PropTypes.string, // onCollapse(item: Item)
//
// Example:
//
// onExpand: item => dispatchExpandActionToRedux(item)
onExpand: PropTypes.func,
onCollapse: PropTypes.func,
};
}
// Optional event handlers for when items are expanded or collapsed. Useful static get defaultProps() {
// for dispatching redux events and updating application state, maybe lazily
// loading subtrees from a worker, etc.
//
// Type:
// onExpand(item: Item)
// onCollapse(item: Item)
//
// Example:
//
// onExpand: item => dispatchExpandActionToRedux(item)
onExpand: PropTypes.func,
onCollapse: PropTypes.func,
},
getDefaultProps() {
return { return {
autoExpandDepth: AUTO_EXPAND_DEPTH, autoExpandDepth: AUTO_EXPAND_DEPTH,
}; };
}, }
getInitialState() { constructor(props) {
return { super(props);
this.state = {
scroll: 0, scroll: 0,
height: window.innerHeight, height: window.innerHeight,
seen: new Set(), seen: new Set(),
}; };
},
this._onExpand = oncePerAnimationFrame(this._onExpand).bind(this);
this._onCollapse = oncePerAnimationFrame(this._onCollapse).bind(this);
this._onScroll = oncePerAnimationFrame(this._onScroll).bind(this);
this._focusPrevNode = oncePerAnimationFrame(this._focusPrevNode).bind(this);
this._focusNextNode = oncePerAnimationFrame(this._focusNextNode).bind(this);
this._focusParentNode = oncePerAnimationFrame(this._focusParentNode).bind(this);
this._autoExpand = this._autoExpand.bind(this);
this._preventArrowKeyScrolling = this._preventArrowKeyScrolling.bind(this);
this._updateHeight = this._updateHeight.bind(this);
this._dfs = this._dfs.bind(this);
this._dfsFromRoots = this._dfsFromRoots.bind(this);
this._focus = this._focus.bind(this);
this._onBlur = this._onBlur.bind(this);
this._onKeyDown = this._onKeyDown.bind(this);
}
componentDidMount() { componentDidMount() {
window.addEventListener("resize", this._updateHeight); window.addEventListener("resize", this._updateHeight);
this._autoExpand(); this._autoExpand();
this._updateHeight(); this._updateHeight();
}, }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
this._autoExpand(); this._autoExpand();
this._updateHeight(); this._updateHeight();
}, }
componentWillUnmount() { componentWillUnmount() {
window.removeEventListener("resize", this._updateHeight); window.removeEventListener("resize", this._updateHeight);
}, }
_autoExpand() { _autoExpand() {
if (!this.props.autoExpandDepth) { if (!this.props.autoExpandDepth) {
@ -279,7 +297,7 @@ module.exports = createClass({
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
autoExpand(roots[i], 0); autoExpand(roots[i], 0);
} }
}, }
_preventArrowKeyScrolling(e) { _preventArrowKeyScrolling(e) {
switch (e.key) { switch (e.key) {
@ -298,7 +316,7 @@ module.exports = createClass({
} }
} }
} }
}, }
/** /**
* Updates the state's height based on clientHeight. * Updates the state's height based on clientHeight.
@ -307,7 +325,7 @@ module.exports = createClass({
this.setState({ this.setState({
height: this.refs.tree.clientHeight height: this.refs.tree.clientHeight
}); });
}, }
/** /**
* Perform a pre-order depth-first search from item. * Perform a pre-order depth-first search from item.
@ -332,7 +350,7 @@ module.exports = createClass({
} }
return traversal; return traversal;
}, }
/** /**
* Perform a pre-order depth-first search over the whole forest. * Perform a pre-order depth-first search over the whole forest.
@ -347,7 +365,7 @@ module.exports = createClass({
} }
return traversal; return traversal;
}, }
/** /**
* Expands current row. * Expands current row.
@ -355,7 +373,7 @@ module.exports = createClass({
* @param {Object} item * @param {Object} item
* @param {Boolean} expandAllChildren * @param {Boolean} expandAllChildren
*/ */
_onExpand: oncePerAnimationFrame(function (item, expandAllChildren) { _onExpand(item, expandAllChildren) {
if (this.props.onExpand) { if (this.props.onExpand) {
this.props.onExpand(item); this.props.onExpand(item);
@ -367,18 +385,18 @@ module.exports = createClass({
} }
} }
} }
}), }
/** /**
* Collapses current row. * Collapses current row.
* *
* @param {Object} item * @param {Object} item
*/ */
_onCollapse: oncePerAnimationFrame(function (item) { _onCollapse(item) {
if (this.props.onCollapse) { if (this.props.onCollapse) {
this.props.onCollapse(item); this.props.onCollapse(item);
} }
}), }
/** /**
* Sets the passed in item to be the focused item. * Sets the passed in item to be the focused item.
@ -411,14 +429,14 @@ module.exports = createClass({
if (this.props.onFocus) { if (this.props.onFocus) {
this.props.onFocus(item); this.props.onFocus(item);
} }
}, }
/** /**
* Sets the state to have no focused item. * Sets the state to have no focused item.
*/ */
_onBlur() { _onBlur() {
this._focus(0, undefined); this._focus(0, undefined);
}, }
/** /**
* Fired on a scroll within the tree's container, updates * Fired on a scroll within the tree's container, updates
@ -426,12 +444,12 @@ module.exports = createClass({
* *
* @param {Event} e * @param {Event} e
*/ */
_onScroll: oncePerAnimationFrame(function (e) { _onScroll(e) {
this.setState({ this.setState({
scroll: Math.max(this.refs.tree.scrollTop, 0), scroll: Math.max(this.refs.tree.scrollTop, 0),
height: this.refs.tree.clientHeight height: this.refs.tree.clientHeight
}); });
}), }
/** /**
* Handles key down events in the tree's container. * Handles key down events in the tree's container.
@ -476,12 +494,12 @@ module.exports = createClass({
} }
break; break;
} }
}, }
/** /**
* Sets the previous node relative to the currently focused item, to focused. * Sets the previous node relative to the currently focused item, to focused.
*/ */
_focusPrevNode: oncePerAnimationFrame(function () { _focusPrevNode() {
// Start a depth first search and keep going until we reach the currently // Start a depth first search and keep going until we reach the currently
// focused node. Focus the previous node in the DFS, if it exists. If it // focused node. Focus the previous node in the DFS, if it exists. If it
// doesn't exist, we're at the first node already. // doesn't exist, we're at the first node already.
@ -505,13 +523,13 @@ module.exports = createClass({
} }
this._focus(prevIndex, prev); this._focus(prevIndex, prev);
}), }
/** /**
* Handles the down arrow key which will focus either the next child * Handles the down arrow key which will focus either the next child
* or sibling row. * or sibling row.
*/ */
_focusNextNode: oncePerAnimationFrame(function () { _focusNextNode() {
// Start a depth first search and keep going until we reach the currently // Start a depth first search and keep going until we reach the currently
// focused node. Focus the next node in the DFS, if it exists. If it // focused node. Focus the next node in the DFS, if it exists. If it
// doesn't exist, we're at the last node already. // doesn't exist, we're at the last node already.
@ -530,13 +548,13 @@ module.exports = createClass({
if (i + 1 < traversal.length) { if (i + 1 < traversal.length) {
this._focus(i + 1, traversal[i + 1].item); this._focus(i + 1, traversal[i + 1].item);
} }
}), }
/** /**
* Handles the left arrow key, going back up to the current rows' * Handles the left arrow key, going back up to the current rows'
* parent row. * parent row.
*/ */
_focusParentNode: oncePerAnimationFrame(function () { _focusParentNode() {
const parent = this.props.getParent(this.props.focused); const parent = this.props.getParent(this.props.focused);
if (!parent) { if (!parent) {
return; return;
@ -552,7 +570,7 @@ module.exports = createClass({
} }
this._focus(parentIndex, parent); this._focus(parentIndex, parent);
}), }
render() { render() {
const traversal = this._dfsFromRoots(); const traversal = this._dfsFromRoots();
@ -656,28 +674,28 @@ module.exports = createClass({
nodes nodes
); );
} }
}); }
/** /**
* An arrow that displays whether its node is expanded () or collapsed * An arrow that displays whether its node is expanded () or collapsed
* (). When its node has no children, it is hidden. * (). When its node has no children, it is hidden.
*/ */
const ArrowExpander = createFactory(createClass({ class ArrowExpanderClass extends Component {
displayName: "ArrowExpander", static get propTypes() {
return {
propTypes: { item: PropTypes.any.isRequired,
item: PropTypes.any.isRequired, visible: PropTypes.bool.isRequired,
visible: PropTypes.bool.isRequired, expanded: PropTypes.bool.isRequired,
expanded: PropTypes.bool.isRequired, onCollapse: PropTypes.func.isRequired,
onCollapse: PropTypes.func.isRequired, onExpand: PropTypes.func.isRequired,
onExpand: PropTypes.func.isRequired, };
}, }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
return this.props.item !== nextProps.item return this.props.item !== nextProps.item
|| this.props.visible !== nextProps.visible || this.props.visible !== nextProps.visible
|| this.props.expanded !== nextProps.expanded; || this.props.expanded !== nextProps.expanded;
}, }
render() { render() {
const attrs = { const attrs = {
@ -699,24 +717,26 @@ const ArrowExpander = createFactory(createClass({
return dom.div(attrs); return dom.div(attrs);
} }
})); }
const TreeNode = createFactory(createClass({ class TreeNodeClass extends Component {
propTypes: { static get propTypes() {
id: PropTypes.any.isRequired, return {
focused: PropTypes.bool.isRequired, id: PropTypes.any.isRequired,
item: PropTypes.any.isRequired, focused: PropTypes.bool.isRequired,
expanded: PropTypes.bool.isRequired, item: PropTypes.any.isRequired,
hasChildren: PropTypes.bool.isRequired, expanded: PropTypes.bool.isRequired,
onExpand: PropTypes.func.isRequired, hasChildren: PropTypes.bool.isRequired,
index: PropTypes.number.isRequired, onExpand: PropTypes.func.isRequired,
first: PropTypes.bool, index: PropTypes.number.isRequired,
last: PropTypes.bool, first: PropTypes.bool,
onClick: PropTypes.func, last: PropTypes.bool,
onCollapse: PropTypes.func.isRequired, onClick: PropTypes.func,
depth: PropTypes.number.isRequired, onCollapse: PropTypes.func.isRequired,
renderItem: PropTypes.func.isRequired, depth: PropTypes.number.isRequired,
}, renderItem: PropTypes.func.isRequired,
};
}
render() { render() {
const arrow = ArrowExpander({ const arrow = ArrowExpander({
@ -769,7 +789,10 @@ const TreeNode = createFactory(createClass({
this.props.expanded), this.props.expanded),
); );
} }
})); }
const ArrowExpander = createFactory(ArrowExpanderClass);
const TreeNode = createFactory(TreeNodeClass);
/** /**
* Create a function that calls the given function `fn` only once per animation * Create a function that calls the given function `fn` only once per animation
@ -794,3 +817,5 @@ function oncePerAnimationFrame(fn) {
}); });
}; };
} }
module.exports = Tree;

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

@ -6,18 +6,25 @@
const React = require("devtools/client/shared/vendor/react"); const React = require("devtools/client/shared/vendor/react");
const ReactDOM = require("devtools/client/shared/vendor/react-dom"); const ReactDOM = require("devtools/client/shared/vendor/react-dom");
const { DOM: dom, PropTypes } = React; const { Component, DOM: dom, PropTypes } = React;
const Draggable = React.createClass({ class Draggable extends Component {
displayName: "Draggable", static get propTypes() {
return {
onMove: PropTypes.func.isRequired,
onStart: PropTypes.func,
onStop: PropTypes.func,
style: PropTypes.object,
className: PropTypes.string
};
}
propTypes: { constructor(props) {
onMove: PropTypes.func.isRequired, super(props);
onStart: PropTypes.func, this.startDragging = this.startDragging.bind(this);
onStop: PropTypes.func, this.onMove = this.onMove.bind(this);
style: PropTypes.object, this.onUp = this.onUp.bind(this);
className: PropTypes.string }
},
startDragging(ev) { startDragging(ev) {
ev.preventDefault(); ev.preventDefault();
@ -25,14 +32,14 @@ const Draggable = React.createClass({
doc.addEventListener("mousemove", this.onMove); doc.addEventListener("mousemove", this.onMove);
doc.addEventListener("mouseup", this.onUp); doc.addEventListener("mouseup", this.onUp);
this.props.onStart && this.props.onStart(); this.props.onStart && this.props.onStart();
}, }
onMove(ev) { onMove(ev) {
ev.preventDefault(); ev.preventDefault();
// Use viewport coordinates so, moving mouse over iframes // Use viewport coordinates so, moving mouse over iframes
// doesn't mangle (relative) coordinates. // doesn't mangle (relative) coordinates.
this.props.onMove(ev.clientX, ev.clientY); this.props.onMove(ev.clientX, ev.clientY);
}, }
onUp(ev) { onUp(ev) {
ev.preventDefault(); ev.preventDefault();
@ -40,7 +47,7 @@ const Draggable = React.createClass({
doc.removeEventListener("mousemove", this.onMove); doc.removeEventListener("mousemove", this.onMove);
doc.removeEventListener("mouseup", this.onUp); doc.removeEventListener("mouseup", this.onUp);
this.props.onStop && this.props.onStop(); this.props.onStop && this.props.onStop();
}, }
render() { render() {
return dom.div({ return dom.div({
@ -49,6 +56,6 @@ const Draggable = React.createClass({
onMouseDown: this.startDragging onMouseDown: this.startDragging
}); });
} }
}); }
module.exports = Draggable; module.exports = Draggable;

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

@ -7,62 +7,68 @@
const React = require("devtools/client/shared/vendor/react"); const React = require("devtools/client/shared/vendor/react");
const ReactDOM = require("devtools/client/shared/vendor/react-dom"); const ReactDOM = require("devtools/client/shared/vendor/react-dom");
const Draggable = React.createFactory(require("devtools/client/shared/components/splitter/Draggable")); const Draggable = React.createFactory(require("devtools/client/shared/components/splitter/Draggable"));
const { DOM: dom, PropTypes } = React; const { Component, DOM: dom, PropTypes } = React;
/** /**
* This component represents a Splitter. The splitter supports vertical * This component represents a Splitter. The splitter supports vertical
* as well as horizontal mode. * as well as horizontal mode.
*/ */
const SplitBox = React.createClass({ class SplitBox extends Component {
displayName: "SplitBox", static get propTypes() {
return {
// Custom class name. You can use more names separated by a space.
className: PropTypes.string,
// Initial size of controlled panel.
initialSize: PropTypes.string,
// Initial width of controlled panel.
initialWidth: PropTypes.string,
// Initial height of controlled panel.
initialHeight: PropTypes.string,
// Left/top panel
startPanel: PropTypes.any,
// Min panel size.
minSize: PropTypes.string,
// Max panel size.
maxSize: PropTypes.string,
// Right/bottom panel
endPanel: PropTypes.any,
// True if the right/bottom panel should be controlled.
endPanelControl: PropTypes.bool,
// Size of the splitter handle bar.
splitterSize: PropTypes.string,
// True if the splitter bar is vertical (default is vertical).
vert: PropTypes.bool,
// Style object.
style: PropTypes.object,
};
}
propTypes: { static get defaultProps() {
// Custom class name. You can use more names separated by a space.
className: PropTypes.string,
// Initial size of controlled panel.
initialSize: PropTypes.string,
// Initial width of controlled panel.
initialWidth: PropTypes.string,
// Initial height of controlled panel.
initialHeight: PropTypes.string,
// Left/top panel
startPanel: PropTypes.any,
// Min panel size.
minSize: PropTypes.string,
// Max panel size.
maxSize: PropTypes.string,
// Right/bottom panel
endPanel: PropTypes.any,
// True if the right/bottom panel should be controlled.
endPanelControl: PropTypes.bool,
// Size of the splitter handle bar.
splitterSize: PropTypes.string,
// True if the splitter bar is vertical (default is vertical).
vert: PropTypes.bool,
// Style object.
style: PropTypes.object,
},
getDefaultProps() {
return { return {
splitterSize: 5, splitterSize: 5,
vert: true, vert: true,
endPanelControl: false endPanelControl: false
}; };
}, }
/** constructor(props) {
* The state stores the current orientation (vertical or horizontal) super(props);
* and the current size (width/height). All these values can change
* during the component's life time. /**
*/ * The state stores the current orientation (vertical or horizontal)
getInitialState() { * and the current size (width/height). All these values can change
return { * during the component's life time.
vert: this.props.vert, */
width: this.props.initialWidth || this.props.initialSize, this.state = {
height: this.props.initialHeight || this.props.initialSize vert: props.vert,
width: props.initialWidth || props.initialSize,
height: props.initialHeight || props.initialSize
}; };
},
this.onStartMove = this.onStartMove.bind(this);
this.onStopMove = this.onStopMove.bind(this);
this.onMove = this.onMove.bind(this);
}
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
let { vert } = nextProps; let { vert } = nextProps;
@ -70,7 +76,7 @@ const SplitBox = React.createClass({
if (vert !== this.props.vert) { if (vert !== this.props.vert) {
this.setState({ vert }); this.setState({ vert });
} }
}, }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
return nextState.width != this.state.width || return nextState.width != this.state.width ||
@ -82,7 +88,7 @@ const SplitBox = React.createClass({
nextProps.minSize != this.props.minSize || nextProps.minSize != this.props.minSize ||
nextProps.maxSize != this.props.maxSize || nextProps.maxSize != this.props.maxSize ||
nextProps.splitterSize != this.props.splitterSize; nextProps.splitterSize != this.props.splitterSize;
}, }
// Dragging Events // Dragging Events
@ -102,7 +108,7 @@ const SplitBox = React.createClass({
this.setState({ this.setState({
defaultCursor: defaultCursor defaultCursor: defaultCursor
}); });
}, }
onStopMove() { onStopMove() {
const splitBox = ReactDOM.findDOMNode(this); const splitBox = ReactDOM.findDOMNode(this);
@ -110,7 +116,7 @@ const SplitBox = React.createClass({
doc.documentElement.style.cursor = this.state.defaultCursor; doc.documentElement.style.cursor = this.state.defaultCursor;
splitBox.classList.remove("dragging"); splitBox.classList.remove("dragging");
}, }
/** /**
* Adjust size of the controlled panel. Depending on the current * Adjust size of the controlled panel. Depending on the current
@ -149,7 +155,7 @@ const SplitBox = React.createClass({
height: size height: size
}); });
} }
}, }
// Rendering // Rendering
@ -226,6 +232,6 @@ const SplitBox = React.createClass({
) )
); );
} }
}); }
module.exports = SplitBox; module.exports = SplitBox;

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

@ -8,7 +8,7 @@
"use strict"; "use strict";
const { DOM, createClass, PropTypes, createFactory } = require("devtools/client/shared/vendor/react"); const { DOM, Component, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
const Tabs = createFactory(require("devtools/client/shared/components/tabs/Tabs").Tabs); const Tabs = createFactory(require("devtools/client/shared/components/tabs/Tabs").Tabs);
const Menu = require("devtools/client/framework/menu"); const Menu = require("devtools/client/framework/menu");
@ -20,37 +20,50 @@ const { div } = DOM;
/** /**
* Renders Tabbar component. * Renders Tabbar component.
*/ */
let Tabbar = createClass({ class Tabbar extends Component {
displayName: "Tabbar", static get propTypes() {
return {
children: PropTypes.array,
menuDocument: PropTypes.object,
onSelect: PropTypes.func,
showAllTabsMenu: PropTypes.bool,
activeTabId: PropTypes.string,
renderOnlySelected: PropTypes.bool,
};
}
propTypes: { static get defaultProps() {
children: PropTypes.array,
menuDocument: PropTypes.object,
onSelect: PropTypes.func,
showAllTabsMenu: PropTypes.bool,
activeTabId: PropTypes.string,
renderOnlySelected: PropTypes.bool,
},
getDefaultProps: function () {
return { return {
menuDocument: window.parent.document, menuDocument: window.parent.document,
showAllTabsMenu: false, showAllTabsMenu: false,
}; };
}, }
getInitialState: function () { constructor(props, context) {
let { activeTabId, children = [] } = this.props; super(props, context);
let { activeTabId, children = [] } = props;
let tabs = this.createTabs(children); let tabs = this.createTabs(children);
let activeTab = tabs.findIndex((tab, index) => tab.id === activeTabId); let activeTab = tabs.findIndex((tab, index) => tab.id === activeTabId);
return { this.state = {
activeTab: activeTab === -1 ? 0 : activeTab, activeTab: activeTab === -1 ? 0 : activeTab,
tabs, tabs,
}; };
},
componentWillReceiveProps: function (nextProps) { this.createTabs = this.createTabs.bind(this);
this.addTab = this.addTab.bind(this);
this.toggleTab = this.toggleTab.bind(this);
this.removeTab = this.removeTab.bind(this);
this.select = this.select.bind(this);
this.getTabIndex = this.getTabIndex.bind(this);
this.getTabId = this.getTabId.bind(this);
this.getCurrentTabId = this.getCurrentTabId.bind(this);
this.onTabChanged = this.onTabChanged.bind(this);
this.onAllTabsMenuClick = this.onAllTabsMenuClick.bind(this);
this.renderTab = this.renderTab.bind(this);
}
componentWillReceiveProps(nextProps) {
let { activeTabId, children = [] } = nextProps; let { activeTabId, children = [] } = nextProps;
let tabs = this.createTabs(children); let tabs = this.createTabs(children);
let activeTab = tabs.findIndex((tab, index) => tab.id === activeTabId); let activeTab = tabs.findIndex((tab, index) => tab.id === activeTabId);
@ -62,9 +75,9 @@ let Tabbar = createClass({
tabs, tabs,
}); });
} }
}, }
createTabs: function (children) { createTabs(children) {
return children return children
.filter((panel) => panel) .filter((panel) => panel)
.map((panel, index) => .map((panel, index) =>
@ -74,11 +87,11 @@ let Tabbar = createClass({
title: panel.props.title, title: panel.props.title,
}) })
); );
}, }
// Public API // Public API
addTab: function (id, title, selected = false, panel, url, index = -1) { addTab(id, title, selected = false, panel, url, index = -1) {
let tabs = this.state.tabs.slice(); let tabs = this.state.tabs.slice();
if (index >= 0) { if (index >= 0) {
@ -100,9 +113,9 @@ let Tabbar = createClass({
this.props.onSelect(id); this.props.onSelect(id);
} }
}); });
}, }
toggleTab: function (tabId, isVisible) { toggleTab(tabId, isVisible) {
let index = this.getTabIndex(tabId); let index = this.getTabIndex(tabId);
if (index < 0) { if (index < 0) {
return; return;
@ -116,9 +129,9 @@ let Tabbar = createClass({
this.setState(Object.assign({}, this.state, { this.setState(Object.assign({}, this.state, {
tabs: tabs, tabs: tabs,
})); }));
}, }
removeTab: function (tabId) { removeTab(tabId) {
let index = this.getTabIndex(tabId); let index = this.getTabIndex(tabId);
if (index < 0) { if (index < 0) {
return; return;
@ -137,9 +150,9 @@ let Tabbar = createClass({
tabs, tabs,
activeTab, activeTab,
})); }));
}, }
select: function (tabId) { select(tabId) {
let index = this.getTabIndex(tabId); let index = this.getTabIndex(tabId);
if (index < 0) { if (index < 0) {
return; return;
@ -154,11 +167,11 @@ let Tabbar = createClass({
this.props.onSelect(tabId); this.props.onSelect(tabId);
} }
}); });
}, }
// Helpers // Helpers
getTabIndex: function (tabId) { getTabIndex(tabId) {
let tabIndex = -1; let tabIndex = -1;
this.state.tabs.forEach((tab, index) => { this.state.tabs.forEach((tab, index) => {
if (tab.id === tabId) { if (tab.id === tabId) {
@ -166,19 +179,19 @@ let Tabbar = createClass({
} }
}); });
return tabIndex; return tabIndex;
}, }
getTabId: function (index) { getTabId(index) {
return this.state.tabs[index].id; return this.state.tabs[index].id;
}, }
getCurrentTabId: function () { getCurrentTabId() {
return this.state.tabs[this.state.activeTab].id; return this.state.tabs[this.state.activeTab].id;
}, }
// Event Handlers // Event Handlers
onTabChanged: function (index) { onTabChanged(index) {
this.setState({ this.setState({
activeTab: index activeTab: index
}); });
@ -186,9 +199,9 @@ let Tabbar = createClass({
if (this.props.onSelect) { if (this.props.onSelect) {
this.props.onSelect(this.state.tabs[index].id); this.props.onSelect(this.state.tabs[index].id);
} }
}, }
onAllTabsMenuClick: function (event) { onAllTabsMenuClick(event) {
let menu = new Menu(); let menu = new Menu();
let target = event.target; let target = event.target;
@ -214,11 +227,11 @@ let Tabbar = createClass({
{ doc: this.props.menuDocument }); { doc: this.props.menuDocument });
return menu; return menu;
}, }
// Rendering // Rendering
renderTab: function (tab) { renderTab(tab) {
if (typeof tab.panel === "function") { if (typeof tab.panel === "function") {
return tab.panel({ return tab.panel({
key: tab.id, key: tab.id,
@ -229,9 +242,9 @@ let Tabbar = createClass({
} }
return tab.panel; return tab.panel;
}, }
render: function () { render() {
let tabs = this.state.tabs.map((tab) => this.renderTab(tab)); let tabs = this.state.tabs.map((tab) => this.renderTab(tab));
return ( return (
@ -247,7 +260,7 @@ let Tabbar = createClass({
) )
) )
); );
}, }
}); }
module.exports = Tabbar; module.exports = Tabbar;

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

@ -8,7 +8,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 { DOM } = React; const { Component, DOM } = React;
const { findDOMNode } = require("devtools/client/shared/vendor/react-dom"); const { findDOMNode } = require("devtools/client/shared/vendor/react-dom");
/** /**
@ -31,43 +31,45 @@ define(function (require, exports, module) {
* </div> * </div>
* <div> * <div>
*/ */
let Tabs = React.createClass({ class Tabs extends Component {
displayName: "Tabs", static get propTypes() {
return {
className: React.PropTypes.oneOfType([
React.PropTypes.array,
React.PropTypes.string,
React.PropTypes.object
]),
tabActive: React.PropTypes.number,
onMount: React.PropTypes.func,
onBeforeChange: React.PropTypes.func,
onAfterChange: React.PropTypes.func,
children: React.PropTypes.oneOfType([
React.PropTypes.array,
React.PropTypes.element
]).isRequired,
showAllTabsMenu: React.PropTypes.bool,
onAllTabsMenuClick: React.PropTypes.func,
propTypes: { // Set true will only render selected panel on DOM. It's complete
className: React.PropTypes.oneOfType([ // opposite of the created array, and it's useful if panels content
React.PropTypes.array, // is unpredictable and update frequently.
React.PropTypes.string, renderOnlySelected: React.PropTypes.bool,
React.PropTypes.object };
]), }
tabActive: React.PropTypes.number,
onMount: React.PropTypes.func,
onBeforeChange: React.PropTypes.func,
onAfterChange: React.PropTypes.func,
children: React.PropTypes.oneOfType([
React.PropTypes.array,
React.PropTypes.element
]).isRequired,
showAllTabsMenu: React.PropTypes.bool,
onAllTabsMenuClick: React.PropTypes.func,
// Set true will only render selected panel on DOM. It's complete static get defaultProps() {
// opposite of the created array, and it's useful if panels content
// is unpredictable and update frequently.
renderOnlySelected: React.PropTypes.bool,
},
getDefaultProps: function () {
return { return {
tabActive: 0, tabActive: 0,
showAllTabsMenu: false, showAllTabsMenu: false,
renderOnlySelected: false, renderOnlySelected: false,
}; };
}, }
getInitialState: function () { constructor(props) {
return { super(props);
tabActive: this.props.tabActive,
this.state = {
tabActive: props.tabActive,
// This array is used to store an information whether a tab // This array is used to store an information whether a tab
// at specific index has already been created (e.g. selected // at specific index has already been created (e.g. selected
@ -82,9 +84,17 @@ define(function (require, exports, module) {
// True if tabs can't fit into available horizontal space. // True if tabs can't fit into available horizontal space.
overflow: false, overflow: false,
}; };
},
componentDidMount: function () { this.onOverflow = this.onOverflow.bind(this);
this.onUnderflow = this.onUnderflow.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);
this.onClickTab = this.onClickTab.bind(this);
this.setActive = this.setActive.bind(this);
this.renderMenuItems = this.renderMenuItems.bind(this);
this.renderPanels = this.renderPanels.bind(this);
}
componentDidMount() {
let node = findDOMNode(this); let node = findDOMNode(this);
node.addEventListener("keydown", this.onKeyDown); node.addEventListener("keydown", this.onKeyDown);
@ -101,9 +111,9 @@ define(function (require, exports, module) {
if (this.props.onMount) { if (this.props.onMount) {
this.props.onMount(index); this.props.onMount(index);
} }
}, }
componentWillReceiveProps: function (nextProps) { componentWillReceiveProps(nextProps) {
let { children, tabActive } = nextProps; let { children, tabActive } = nextProps;
// Check type of 'tabActive' props to see if it's valid // Check type of 'tabActive' props to see if it's valid
@ -123,9 +133,9 @@ define(function (require, exports, module) {
tabActive, tabActive,
}); });
} }
}, }
componentWillUnmount: function () { componentWillUnmount() {
let node = findDOMNode(this); let node = findDOMNode(this);
node.removeEventListener("keydown", this.onKeyDown); node.removeEventListener("keydown", this.onKeyDown);
@ -133,27 +143,27 @@ define(function (require, exports, module) {
node.removeEventListener("overflow", this.onOverflow); node.removeEventListener("overflow", this.onOverflow);
node.removeEventListener("underflow", this.onUnderflow); node.removeEventListener("underflow", this.onUnderflow);
} }
}, }
// DOM Events // DOM Events
onOverflow: function (event) { onOverflow(event) {
if (event.target.classList.contains("tabs-menu")) { if (event.target.classList.contains("tabs-menu")) {
this.setState({ this.setState({
overflow: true overflow: true
}); });
} }
}, }
onUnderflow: function (event) { onUnderflow(event) {
if (event.target.classList.contains("tabs-menu")) { if (event.target.classList.contains("tabs-menu")) {
this.setState({ this.setState({
overflow: false overflow: false
}); });
} }
}, }
onKeyDown: function (event) { onKeyDown(event) {
// Bail out if the focus isn't on a tab. // Bail out if the focus isn't on a tab.
if (!event.target.closest(".tabs-menu-item")) { if (!event.target.closest(".tabs-menu-item")) {
return; return;
@ -174,19 +184,19 @@ define(function (require, exports, module) {
if (this.state.tabActive != tabActive) { if (this.state.tabActive != tabActive) {
this.setActive(tabActive); this.setActive(tabActive);
} }
}, }
onClickTab: function (index, event) { onClickTab(index, event) {
this.setActive(index); this.setActive(index);
if (event) { if (event) {
event.preventDefault(); event.preventDefault();
} }
}, }
// API // API
setActive: function (index) { setActive(index) {
let onAfterChange = this.props.onAfterChange; let onAfterChange = this.props.onAfterChange;
let onBeforeChange = this.props.onBeforeChange; let onBeforeChange = this.props.onBeforeChange;
@ -217,11 +227,11 @@ define(function (require, exports, module) {
onAfterChange(index); onAfterChange(index);
} }
}); });
}, }
// Rendering // Rendering
renderMenuItems: function () { renderMenuItems() {
if (!this.props.children) { if (!this.props.children) {
throw new Error("There must be at least one Tab"); throw new Error("There must be at least one Tab");
} }
@ -299,9 +309,9 @@ define(function (require, exports, module) {
allTabsMenu allTabsMenu
) )
); );
}, }
renderPanels: function () { renderPanels() {
let { children, renderOnlySelected } = this.props; let { children, renderOnlySelected } = this.props;
if (!children) { if (!children) {
@ -359,38 +369,38 @@ define(function (require, exports, module) {
panels panels
) )
); );
}, }
render: function () { render() {
return ( return (
DOM.div({ className: ["tabs", this.props.className].join(" ") }, DOM.div({ className: ["tabs", this.props.className].join(" ") },
this.renderMenuItems(), this.renderMenuItems(),
this.renderPanels() this.renderPanels()
) )
); );
}, }
}); }
/** /**
* Renders simple tab 'panel'. * Renders simple tab 'panel'.
*/ */
let Panel = React.createClass({ class Panel extends Component {
displayName: "Panel", static get propTypes() {
return {
title: React.PropTypes.string.isRequired,
children: React.PropTypes.oneOfType([
React.PropTypes.array,
React.PropTypes.element
]).isRequired
};
}
propTypes: { render() {
title: React.PropTypes.string.isRequired,
children: React.PropTypes.oneOfType([
React.PropTypes.array,
React.PropTypes.element
]).isRequired
},
render: function () {
return DOM.div({className: "tab-panel"}, return DOM.div({className: "tab-panel"},
this.props.children this.props.children
); );
} }
}); }
// Exports from this module // Exports from this module
exports.TabPanel = Panel; exports.TabPanel = Panel;

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

@ -26,8 +26,8 @@ Test all-tabs menu.
window.onload = Task.async(function* () { window.onload = Task.async(function* () {
try { try {
const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom"); const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
const React = browserRequire("devtools/client/shared/vendor/react"); const { Component, createFactory, DOM } = browserRequire("devtools/client/shared/vendor/react");
const Tabbar = React.createFactory(browserRequire("devtools/client/shared/components/tabs/TabBar")); const Tabbar = createFactory(browserRequire("devtools/client/shared/components/tabs/TabBar"));
// Create container for the TabBar. Set smaller width // Create container for the TabBar. Set smaller width
// to ensure that tabs won't fit and the all-tabs menu // to ensure that tabs won't fit and the all-tabs menu
@ -45,12 +45,14 @@ window.onload = Task.async(function* () {
const tabbarReact = ReactDOM.render(tabbar, tabBarBox); const tabbarReact = ReactDOM.render(tabbar, tabBarBox);
// Test panel. class TabPanelClass extends Component {
let TabPanel = React.createFactory(React.createClass({ render() {
render: function () { return DOM.div({}, "content");
return React.DOM.div({}, "content");
} }
})); }
// Test panel.
let TabPanel = createFactory(TabPanelClass);
// Create a few panels. // Create a few panels.
yield addTabWithPanel(1); yield addTabWithPanel(1);

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

@ -8,26 +8,23 @@
// Make this available to both AMD and CJS environments // Make this available to both AMD and CJS environments
define(function (require, exports, module) { define(function (require, exports, module) {
// ReactJS // ReactJS
const React = require("devtools/client/shared/vendor/react"); const { Component, DOM: dom, PropTypes } =
require("devtools/client/shared/vendor/react");
// Shortcuts
const { td, span } = React.DOM;
const PropTypes = React.PropTypes;
/** /**
* Render the default cell used for toggle buttons * Render the default cell used for toggle buttons
*/ */
let LabelCell = React.createClass({ class LabelCell extends Component {
displayName: "LabelCell",
// See the TreeView component for details related // See the TreeView component for details related
// to the 'member' object. // to the 'member' object.
propTypes: { static get propTypes() {
id: PropTypes.string.isRequired, return {
member: PropTypes.object.isRequired id: PropTypes.string.isRequired,
}, member: PropTypes.object.isRequired
};
}
render: function () { render() {
let id = this.props.id; let id = this.props.id;
let member = this.props.member; let member = this.props.member;
let level = member.level || 0; let level = member.level || 0;
@ -49,16 +46,16 @@ define(function (require, exports, module) {
} }
return ( return (
td({ dom.td({
className: "treeLabelCell", className: "treeLabelCell",
key: "default", key: "default",
style: rowStyle, style: rowStyle,
role: "presentation"}, role: "presentation"},
span({ dom.span({
className: iconClassList.join(" "), className: iconClassList.join(" "),
role: "presentation" role: "presentation"
}), }),
span({ dom.span({
className: "treeLabel " + member.type + "Label", className: "treeLabel " + member.type + "Label",
"aria-labelledby": id, "aria-labelledby": id,
"data-level": level "data-level": level
@ -66,7 +63,7 @@ define(function (require, exports, module) {
) )
); );
} }
}); }
// Exports from this module // Exports from this module
module.exports = LabelCell; module.exports = LabelCell;

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

@ -8,45 +8,48 @@
// Make this available to both AMD and CJS environments // Make this available to both AMD and CJS environments
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 { Component, PropTypes } = React;
// Shortcuts
const { input, span, td } = React.DOM; const { input, span, td } = React.DOM;
const PropTypes = React.PropTypes;
/** /**
* This template represents a cell in TreeView row. It's rendered * This template represents a cell in TreeView row. It's rendered
* using <td> element (the row is <tr> and the entire tree is <table>). * using <td> element (the row is <tr> and the entire tree is <table>).
*/ */
let TreeCell = React.createClass({ class TreeCell extends Component {
displayName: "TreeCell",
// See TreeView component for detailed property explanation. // See TreeView component for detailed property explanation.
propTypes: { static get propTypes() {
value: PropTypes.any,
decorator: PropTypes.object,
id: PropTypes.string.isRequired,
member: PropTypes.object.isRequired,
renderValue: PropTypes.func.isRequired,
enableInput: PropTypes.bool,
},
getInitialState: function () {
return { return {
value: PropTypes.any,
decorator: PropTypes.object,
id: PropTypes.string.isRequired,
member: PropTypes.object.isRequired,
renderValue: PropTypes.func.isRequired,
enableInput: PropTypes.bool,
};
}
constructor(props) {
super(props);
this.state = {
inputEnabled: false, inputEnabled: false,
}; };
},
this.getCellClass = this.getCellClass.bind(this);
this.updateInputEnabled = this.updateInputEnabled.bind(this);
}
/** /**
* Optimize cell rendering. Rerender cell content only if * Optimize cell rendering. Rerender cell content only if
* the value or expanded state changes. * the value or expanded state changes.
*/ */
shouldComponentUpdate: function (nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
return (this.props.value != nextProps.value) || return (this.props.value != nextProps.value) ||
(this.state !== nextState) || (this.state !== nextState) ||
(this.props.member.open != nextProps.member.open); (this.props.member.open != nextProps.member.open);
}, }
getCellClass: function (object, id) { getCellClass(object, id) {
let decorator = this.props.decorator; let decorator = this.props.decorator;
if (!decorator || !decorator.getCellClass) { if (!decorator || !decorator.getCellClass) {
return []; return [];
@ -63,15 +66,15 @@ define(function (require, exports, module) {
} }
return classNames; return classNames;
}, }
updateInputEnabled: function (evt) { updateInputEnabled(evt) {
this.setState(Object.assign({}, this.state, { this.setState(Object.assign({}, this.state, {
inputEnabled: evt.target.nodeName.toLowerCase() !== "input", inputEnabled: evt.target.nodeName.toLowerCase() !== "input",
})); }));
}, }
render: function () { render() {
let { let {
member, member,
id, id,
@ -127,7 +130,7 @@ define(function (require, exports, module) {
) )
); );
} }
}); }
// Default value rendering. // Default value rendering.
let defaultRenderValue = props => { let defaultRenderValue = props => {

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

@ -7,39 +7,41 @@
// Make this available to both AMD and CJS environments // Make this available to both AMD and CJS environments
define(function (require, exports, module) { define(function (require, exports, module) {
// ReactJS
const React = require("devtools/client/shared/vendor/react"); const React = require("devtools/client/shared/vendor/react");
const { Component, PropTypes } = React;
// Shortcuts
const { thead, tr, td, div } = React.DOM; const { thead, tr, td, div } = React.DOM;
const PropTypes = React.PropTypes;
/** /**
* This component is responsible for rendering tree header. * This component is responsible for rendering tree header.
* It's based on <thead> element. * It's based on <thead> element.
*/ */
let TreeHeader = React.createClass({ class TreeHeader extends Component {
displayName: "TreeHeader",
// See also TreeView component for detailed info about properties. // See also TreeView component for detailed info about properties.
propTypes: { static get propTypes() {
// Custom tree decorator return {
decorator: PropTypes.object, // Custom tree decorator
// True if the header should be visible decorator: PropTypes.object,
header: PropTypes.bool, // True if the header should be visible
// Array with column definition header: PropTypes.bool,
columns: PropTypes.array // Array with column definition
}, columns: PropTypes.array
};
}
getDefaultProps: function () { static get defaultProps() {
return { return {
columns: [{ columns: [{
id: "default" id: "default"
}] }]
}; };
}, }
getHeaderClass: function (colId) { constructor(props) {
super(props);
this.getHeaderClass = this.getHeaderClass.bind(this);
}
getHeaderClass(colId) {
let decorator = this.props.decorator; let decorator = this.props.decorator;
if (!decorator || !decorator.getHeaderClass) { if (!decorator || !decorator.getHeaderClass) {
return []; return [];
@ -56,9 +58,9 @@ define(function (require, exports, module) {
} }
return classNames; return classNames;
}, }
render: function () { render() {
let cells = []; let cells = [];
let visible = this.props.header; let visible = this.props.header;
@ -97,7 +99,7 @@ define(function (require, exports, module) {
}, cells)) }, cells))
); );
} }
}); }
// Exports from this module // Exports from this module
module.exports = TreeHeader; module.exports = TreeHeader;

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

@ -7,54 +7,56 @@
// Make this available to both AMD and CJS environments // Make this available to both AMD and CJS environments
define(function (require, exports, module) { define(function (require, exports, module) {
// ReactJS
const React = require("devtools/client/shared/vendor/react"); const React = require("devtools/client/shared/vendor/react");
const ReactDOM = require("devtools/client/shared/vendor/react-dom"); const { Component, createFactory, PropTypes } = React;
const { findDOMNode } = require("devtools/client/shared/vendor/react-dom");
const { tr } = React.DOM;
// Tree // Tree
const TreeCell = React.createFactory(require("./TreeCell")); const TreeCell = createFactory(require("./TreeCell"));
const LabelCell = React.createFactory(require("./LabelCell")); const LabelCell = createFactory(require("./LabelCell"));
// Scroll // Scroll
const { scrollIntoViewIfNeeded } = require("devtools/client/shared/scroll"); const { scrollIntoViewIfNeeded } = require("devtools/client/shared/scroll");
// Shortcuts
const { tr } = React.DOM;
const PropTypes = React.PropTypes;
/** /**
* This template represents a node in TreeView component. It's rendered * This template represents a node in TreeView component. It's rendered
* using <tr> element (the entire tree is one big <table>). * using <tr> element (the entire tree is one big <table>).
*/ */
let TreeRow = React.createClass({ class TreeRow extends Component {
displayName: "TreeRow",
// See TreeView component for more details about the props and // See TreeView component for more details about the props and
// the 'member' object. // the 'member' object.
propTypes: { static get propTypes() {
member: PropTypes.shape({ return {
object: PropTypes.obSject, member: PropTypes.shape({
name: PropTypes.sring, object: PropTypes.obSject,
type: PropTypes.string.isRequired, name: PropTypes.sring,
rowClass: PropTypes.string.isRequired, type: PropTypes.string.isRequired,
level: PropTypes.number.isRequired, rowClass: PropTypes.string.isRequired,
hasChildren: PropTypes.bool, level: PropTypes.number.isRequired,
value: PropTypes.any, hasChildren: PropTypes.bool,
open: PropTypes.bool.isRequired, value: PropTypes.any,
path: PropTypes.string.isRequired, open: PropTypes.bool.isRequired,
hidden: PropTypes.bool, path: PropTypes.string.isRequired,
selected: PropTypes.bool, hidden: PropTypes.bool,
}), selected: PropTypes.bool,
decorator: PropTypes.object, }),
renderCell: PropTypes.object, decorator: PropTypes.object,
renderLabelCell: PropTypes.object, renderCell: PropTypes.object,
columns: PropTypes.array.isRequired, renderLabelCell: PropTypes.object,
id: PropTypes.string.isRequired, columns: PropTypes.array.isRequired,
provider: PropTypes.object.isRequired, id: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired, provider: PropTypes.object.isRequired,
onMouseOver: PropTypes.func, onClick: PropTypes.func.isRequired,
onMouseOut: PropTypes.func onMouseOver: PropTypes.func,
}, onMouseOut: PropTypes.func
};
}
constructor(props) {
super(props);
this.getRowClass = this.getRowClass.bind(this);
}
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
// I don't like accessing the underlying DOM elements directly, // I don't like accessing the underlying DOM elements directly,
@ -64,16 +66,16 @@ define(function (require, exports, module) {
// The important part is that DOM elements don't need to be // The important part is that DOM elements don't need to be
// re-created when they should appear again. // re-created when they should appear again.
if (nextProps.member.hidden != this.props.member.hidden) { if (nextProps.member.hidden != this.props.member.hidden) {
let row = ReactDOM.findDOMNode(this); let row = findDOMNode(this);
row.classList.toggle("hidden"); row.classList.toggle("hidden");
} }
}, }
/** /**
* Optimize row rendering. If props are the same do not render. * Optimize row rendering. If props are the same do not render.
* This makes the rendering a lot faster! * This makes the rendering a lot faster!
*/ */
shouldComponentUpdate: function (nextProps) { shouldComponentUpdate(nextProps) {
let props = ["name", "open", "value", "loading", "selected", "hasChildren"]; let props = ["name", "open", "value", "loading", "selected", "hasChildren"];
for (let p in props) { for (let p in props) {
if (nextProps.member[props[p]] != this.props.member[props[p]]) { if (nextProps.member[props[p]] != this.props.member[props[p]]) {
@ -82,20 +84,20 @@ define(function (require, exports, module) {
} }
return false; return false;
}, }
componentDidUpdate: function () { componentDidUpdate() {
if (this.props.member.selected) { if (this.props.member.selected) {
let row = ReactDOM.findDOMNode(this); let row = findDOMNode(this);
// Because this is called asynchronously, context window might be // Because this is called asynchronously, context window might be
// already gone. // already gone.
if (row.ownerDocument.defaultView) { if (row.ownerDocument.defaultView) {
scrollIntoViewIfNeeded(row); scrollIntoViewIfNeeded(row);
} }
} }
}, }
getRowClass: function (object) { getRowClass(object) {
let decorator = this.props.decorator; let decorator = this.props.decorator;
if (!decorator || !decorator.getRowClass) { if (!decorator || !decorator.getRowClass) {
return []; return [];
@ -112,9 +114,9 @@ define(function (require, exports, module) {
} }
return classNames; return classNames;
}, }
render: function () { render() {
let member = this.props.member; let member = this.props.member;
let decorator = this.props.decorator; let decorator = this.props.decorator;
let props = { let props = {
@ -198,7 +200,7 @@ define(function (require, exports, module) {
tr(props, cells) tr(props, cells)
); );
} }
}); }
// Helpers // Helpers

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

@ -7,17 +7,22 @@
// Make this available to both AMD and CJS environments // Make this available to both AMD and CJS environments
define(function (require, exports, module) { define(function (require, exports, module) {
// ReactJS const { cloneElement, Component, createFactory, DOM: dom, PropTypes } =
const React = require("devtools/client/shared/vendor/react"); require("devtools/client/shared/vendor/react");
// Reps // Reps
const { ObjectProvider } = require("./ObjectProvider"); const { ObjectProvider } = require("./ObjectProvider");
const TreeRow = React.createFactory(require("./TreeRow")); const TreeRow = createFactory(require("./TreeRow"));
const TreeHeader = React.createFactory(require("./TreeHeader")); const TreeHeader = createFactory(require("./TreeHeader"));
// Shortcuts const defaultProps = {
const DOM = React.DOM; object: null,
const PropTypes = React.PropTypes; renderRow: null,
provider: ObjectProvider,
expandedNodes: new Set(),
expandableStrings: true,
columns: []
};
/** /**
* This component represents a tree view with expandable/collapsible nodes. * This component represents a tree view with expandable/collapsible nodes.
@ -53,88 +58,96 @@ define(function (require, exports, module) {
* renderLabelCell: function(object); * renderLabelCell: function(object);
* } * }
*/ */
let TreeView = React.createClass({ class TreeView extends Component {
displayName: "TreeView",
// The only required property (not set by default) is the input data // The only required property (not set by default) is the input data
// object that is used to puputate the tree. // object that is used to puputate the tree.
propTypes: { static get propTypes() {
// The input data object. return {
object: PropTypes.any, // The input data object.
className: PropTypes.string, object: PropTypes.any,
label: PropTypes.string, className: PropTypes.string,
// Data provider (see also the interface above) label: PropTypes.string,
provider: PropTypes.shape({ // Data provider (see also the interface above)
getChildren: PropTypes.func, provider: PropTypes.shape({
hasChildren: PropTypes.func, getChildren: PropTypes.func,
getLabel: PropTypes.func, hasChildren: PropTypes.func,
getValue: PropTypes.func, getLabel: PropTypes.func,
getKey: PropTypes.func, getValue: PropTypes.func,
getType: PropTypes.func, getKey: PropTypes.func,
}).isRequired, getType: PropTypes.func,
// Tree decorator (see also the interface above) }).isRequired,
decorator: PropTypes.shape({ // Tree decorator (see also the interface above)
getRowClass: PropTypes.func, decorator: PropTypes.shape({
getCellClass: PropTypes.func, getRowClass: PropTypes.func,
getHeaderClass: PropTypes.func, getCellClass: PropTypes.func,
renderValue: PropTypes.func, getHeaderClass: PropTypes.func,
renderValue: PropTypes.func,
renderRow: PropTypes.func,
renderCell: PropTypes.func,
renderLabelCell: PropTypes.func,
}),
// Custom tree row (node) renderer
renderRow: PropTypes.func, renderRow: PropTypes.func,
// Custom cell renderer
renderCell: PropTypes.func, renderCell: PropTypes.func,
// Custom value renderef
renderValue: PropTypes.func,
// Custom tree label (including a toggle button) renderer
renderLabelCell: PropTypes.func, renderLabelCell: PropTypes.func,
}), // Set of expanded nodes
// Custom tree row (node) renderer expandedNodes: PropTypes.object,
renderRow: PropTypes.func, // Custom filtering callback
// Custom cell renderer onFilter: PropTypes.func,
renderCell: PropTypes.func, // Custom sorting callback
// Custom value renderef onSort: PropTypes.func,
renderValue: PropTypes.func, // A header is displayed if set to true
// Custom tree label (including a toggle button) renderer header: PropTypes.bool,
renderLabelCell: PropTypes.func, // Long string is expandable by a toggle button
// Set of expanded nodes expandableStrings: PropTypes.bool,
expandedNodes: PropTypes.object, // Array of columns
// Custom filtering callback columns: PropTypes.arrayOf(PropTypes.shape({
onFilter: PropTypes.func, id: PropTypes.string.isRequired,
// Custom sorting callback title: PropTypes.string,
onSort: PropTypes.func, width: PropTypes.string
// A header is displayed if set to true }))
header: PropTypes.bool,
// Long string is expandable by a toggle button
expandableStrings: PropTypes.bool,
// Array of columns
columns: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.string.isRequired,
title: PropTypes.string,
width: PropTypes.string
}))
},
getDefaultProps: function () {
return {
object: null,
renderRow: null,
provider: ObjectProvider,
expandedNodes: new Set(),
expandableStrings: true,
columns: []
}; };
}, }
getInitialState: function () { static get defaultProps() {
return { return defaultProps;
expandedNodes: this.props.expandedNodes, }
columns: ensureDefaultColumn(this.props.columns),
constructor(props) {
super(props);
this.state = {
expandedNodes: props.expandedNodes,
columns: ensureDefaultColumn(props.columns),
selected: null selected: null
}; };
},
componentWillReceiveProps: function (nextProps) { this.toggle = this.toggle.bind(this);
this.isExpanded = this.isExpanded.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);
this.onKeyUp = this.onKeyUp.bind(this);
this.onClickRow = this.onClickRow.bind(this);
this.getSelectedRow = this.getSelectedRow.bind(this);
this.selectRow = this.selectRow.bind(this);
this.isSelected = this.isSelected.bind(this);
this.onFilter = this.onFilter.bind(this);
this.onSort = this.onSort.bind(this);
this.getMembers = this.getMembers.bind(this);
this.renderRows = this.renderRows.bind(this);
}
componentWillReceiveProps(nextProps) {
let { expandedNodes } = nextProps; let { expandedNodes } = nextProps;
this.setState(Object.assign({}, this.state, { this.setState(Object.assign({}, this.state, {
expandedNodes, expandedNodes,
})); }));
}, }
componentDidUpdate: function () { componentDidUpdate() {
let selected = this.getSelectedRow(this.rows); let selected = this.getSelectedRow(this.rows);
if (!selected && this.rows.length > 0) { if (!selected && this.rows.length > 0) {
// TODO: Do better than just selecting the first row again. We want to // TODO: Do better than just selecting the first row again. We want to
@ -142,11 +155,58 @@ define(function (require, exports, module) {
// row is removed. // row is removed.
this.selectRow(this.rows[0].props.member.path); this.selectRow(this.rows[0].props.member.path);
} }
}, }
static subPath(path, subKey) {
return path + "/" + String(subKey).replace(/[\\/]/g, "\\$&");
}
/**
* Creates a set with the paths of the nodes that should be expanded by default
* according to the passed options.
* @param {Object} The root node of the tree.
* @param {Object} [optional] An object with the following optional parameters:
* - maxLevel: nodes nested deeper than this level won't be expanded.
* - maxNodes: maximum number of nodes that can be expanded. The traversal is
breadth-first, so expanding nodes nearer to the root will be preferred.
Sibling nodes will either be all expanded or none expanded.
* }
*/
static getExpandedNodes(rootObj, { maxLevel = Infinity, maxNodes = Infinity } = {}) {
let expandedNodes = new Set();
let queue = [{
object: rootObj,
level: 1,
path: ""
}];
while (queue.length) {
let {object, level, path} = queue.shift();
if (Object(object) !== object) {
continue;
}
let keys = Object.keys(object);
if (expandedNodes.size + keys.length > maxNodes) {
// Avoid having children half expanded.
break;
}
for (let key of keys) {
let nodePath = TreeView.subPath(path, key);
expandedNodes.add(nodePath);
if (level < maxLevel) {
queue.push({
object: object[key],
level: level + 1,
path: nodePath
});
}
}
}
return expandedNodes;
}
// Node expand/collapse // Node expand/collapse
toggle: function (nodePath) { toggle(nodePath) {
let nodes = this.state.expandedNodes; let nodes = this.state.expandedNodes;
if (this.isExpanded(nodePath)) { if (this.isExpanded(nodePath)) {
nodes.delete(nodePath); nodes.delete(nodePath);
@ -158,22 +218,22 @@ define(function (require, exports, module) {
this.setState(Object.assign({}, this.state, { this.setState(Object.assign({}, this.state, {
expandedNodes: nodes expandedNodes: nodes
})); }));
}, }
isExpanded: function (nodePath) { isExpanded(nodePath) {
return this.state.expandedNodes.has(nodePath); return this.state.expandedNodes.has(nodePath);
}, }
// Event Handlers // Event Handlers
onKeyDown: function (event) { onKeyDown(event) {
if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes( if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(
event.key)) { event.key)) {
event.preventDefault(); event.preventDefault();
} }
}, }
onKeyUp: function (event) { onKeyUp(event) {
let row = this.getSelectedRow(this.rows); let row = this.getSelectedRow(this.rows);
if (!row) { if (!row) {
return; return;
@ -209,33 +269,33 @@ define(function (require, exports, module) {
} }
event.preventDefault(); event.preventDefault();
}, }
onClickRow: function (nodePath, event) { onClickRow(nodePath, event) {
event.stopPropagation(); event.stopPropagation();
let cell = event.target.closest("td"); let cell = event.target.closest("td");
if (cell && cell.classList.contains("treeLabelCell")) { if (cell && cell.classList.contains("treeLabelCell")) {
this.toggle(nodePath); this.toggle(nodePath);
} }
this.selectRow(nodePath); this.selectRow(nodePath);
}, }
getSelectedRow: function (rows) { getSelectedRow(rows) {
if (!this.state.selected || rows.length === 0) { if (!this.state.selected || rows.length === 0) {
return null; return null;
} }
return rows.find(row => this.isSelected(row.props.member.path)); return rows.find(row => this.isSelected(row.props.member.path));
}, }
selectRow: function (nodePath) { selectRow(nodePath) {
this.setState(Object.assign({}, this.state, { this.setState(Object.assign({}, this.state, {
selected: nodePath selected: nodePath
})); }));
}, }
isSelected: function (nodePath) { isSelected(nodePath) {
return nodePath === this.state.selected; return nodePath === this.state.selected;
}, }
// Filtering & Sorting // Filtering & Sorting
@ -243,15 +303,15 @@ define(function (require, exports, module) {
* Filter out nodes that don't correspond to the current filter. * Filter out nodes that don't correspond to the current filter.
* @return {Boolean} true if the node should be visible otherwise false. * @return {Boolean} true if the node should be visible otherwise false.
*/ */
onFilter: function (object) { onFilter(object) {
let onFilter = this.props.onFilter; let onFilter = this.props.onFilter;
return onFilter ? onFilter(object) : true; return onFilter ? onFilter(object) : true;
}, }
onSort: function (parent, children) { onSort(parent, children) {
let onSort = this.props.onSort; let onSort = this.props.onSort;
return onSort ? onSort(parent, children) : children; return onSort ? onSort(parent, children) : children;
}, }
// Members // Members
@ -259,7 +319,7 @@ define(function (require, exports, module) {
* Return children node objects (so called 'members') for given * Return children node objects (so called 'members') for given
* parent object. * parent object.
*/ */
getMembers: function (parent, level, path) { getMembers(parent, level, path) {
// Strings don't have children. Note that 'long' strings are using // Strings don't have children. Note that 'long' strings are using
// the expander icon (+/-) to display the entire original value, // the expander icon (+/-) to display the entire original value,
// but there are no child items. // but there are no child items.
@ -320,12 +380,12 @@ define(function (require, exports, module) {
selected: this.isSelected(nodePath) selected: this.isSelected(nodePath)
}; };
}); });
}, }
/** /**
* Render tree rows/nodes. * Render tree rows/nodes.
*/ */
renderRows: function (parent, level = 0, path = "") { renderRows(parent, level = 0, path = "") {
let rows = []; let rows = [];
let decorator = this.props.decorator; let decorator = this.props.decorator;
let renderRow = this.props.renderRow || TreeRow; let renderRow = this.props.renderRow || TreeRow;
@ -367,7 +427,7 @@ define(function (require, exports, module) {
if (!Array.isArray(childRows)) { if (!Array.isArray(childRows)) {
let lastIndex = rows.length - 1; let lastIndex = rows.length - 1;
props.member.loading = true; props.member.loading = true;
rows[lastIndex] = React.cloneElement(rows[lastIndex], props); rows[lastIndex] = cloneElement(rows[lastIndex], props);
} else { } else {
rows = rows.concat(childRows); rows = rows.concat(childRows);
} }
@ -375,9 +435,9 @@ define(function (require, exports, module) {
}); });
return rows; return rows;
}, }
render: function () { render() {
let root = this.props.object; let root = this.props.object;
let classNames = ["treeTable"]; let classNames = ["treeTable"];
this.rows = []; this.rows = [];
@ -403,7 +463,7 @@ define(function (require, exports, module) {
}); });
return ( return (
DOM.table({ dom.table({
className: classNames.join(" "), className: classNames.join(" "),
role: "tree", role: "tree",
tabIndex: 0, tabIndex: 0,
@ -414,62 +474,13 @@ define(function (require, exports, module) {
cellPadding: 0, cellPadding: 0,
cellSpacing: 0}, cellSpacing: 0},
TreeHeader(props), TreeHeader(props),
DOM.tbody({ dom.tbody({
role: "presentation" role: "presentation"
}, rows) }, rows)
) )
); );
} }
}); }
TreeView.subPath = function (path, subKey) {
return path + "/" + String(subKey).replace(/[\\/]/g, "\\$&");
};
/**
* Creates a set with the paths of the nodes that should be expanded by default
* according to the passed options.
* @param {Object} The root node of the tree.
* @param {Object} [optional] An object with the following optional parameters:
* - maxLevel: nodes nested deeper than this level won't be expanded.
* - maxNodes: maximum number of nodes that can be expanded. The traversal is
breadth-first, so expanding nodes nearer to the root will be preferred.
Sibling nodes will either be all expanded or none expanded.
* }
*/
TreeView.getExpandedNodes = function (rootObj,
{ maxLevel = Infinity, maxNodes = Infinity } = {}
) {
let expandedNodes = new Set();
let queue = [{
object: rootObj,
level: 1,
path: ""
}];
while (queue.length) {
let {object, level, path} = queue.shift();
if (Object(object) !== object) {
continue;
}
let keys = Object.keys(object);
if (expandedNodes.size + keys.length > maxNodes) {
// Avoid having children half expanded.
break;
}
for (let key of keys) {
let nodePath = TreeView.subPath(path, key);
expandedNodes.add(nodePath);
if (level < maxLevel) {
queue.push({
object: object[key],
level: level + 1,
path: nodePath
});
}
}
}
return expandedNodes;
};
// Helpers // Helpers

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

@ -70,6 +70,17 @@ public:
} }
} }
void NotifyEvent(MediaStreamGraph* aGraph, MediaStreamGraphEvent aEvent) override
{
if (aEvent == MediaStreamGraphEvent::EVENT_REMOVED) {
EndStream();
mSourceStream->EndAllTrackAndFinish();
MutexAutoLock lock(mMutex);
mImage = nullptr;
}
}
protected: protected:
~StreamListener() { } ~StreamListener() { }

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

@ -10,6 +10,7 @@
#include "AudioStreamTrack.h" #include "AudioStreamTrack.h"
#include "Layers.h" #include "Layers.h"
#include "MediaStreamGraph.h" #include "MediaStreamGraph.h"
#include "MediaStreamGraphImpl.h"
#include "MediaStreamListener.h" #include "MediaStreamListener.h"
#include "VideoStreamTrack.h" #include "VideoStreamTrack.h"
#include "mozilla/dom/AudioNode.h" #include "mozilla/dom/AudioNode.h"
@ -20,6 +21,7 @@
#include "mozilla/dom/LocalMediaStreamBinding.h" #include "mozilla/dom/LocalMediaStreamBinding.h"
#include "mozilla/dom/MediaStreamBinding.h" #include "mozilla/dom/MediaStreamBinding.h"
#include "mozilla/dom/MediaStreamTrackEvent.h" #include "mozilla/dom/MediaStreamTrackEvent.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/VideoTrack.h" #include "mozilla/dom/VideoTrack.h"
#include "mozilla/dom/VideoTrackList.h" #include "mozilla/dom/VideoTrackList.h"
#include "mozilla/media/MediaUtils.h" #include "mozilla/media/MediaUtils.h"
@ -27,6 +29,7 @@
#include "nsIScriptError.h" #include "nsIScriptError.h"
#include "nsIUUIDGenerator.h" #include "nsIUUIDGenerator.h"
#include "nsPIDOMWindow.h" #include "nsPIDOMWindow.h"
#include "nsProxyRelease.h"
#include "nsRFPService.h" #include "nsRFPService.h"
#include "nsServiceManagerUtils.h" #include "nsServiceManagerUtils.h"
@ -338,9 +341,8 @@ public:
explicit PlaybackTrackListener(DOMMediaStream* aStream) : explicit PlaybackTrackListener(DOMMediaStream* aStream) :
mStream(aStream) {} mStream(aStream) {}
NS_DECL_ISUPPORTS_INHERITED NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(PlaybackTrackListener)
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PlaybackTrackListener, NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(PlaybackTrackListener)
MediaStreamTrackConsumer)
void NotifyEnded(MediaStreamTrack* aTrack) override void NotifyEnded(MediaStreamTrack* aTrack) override
{ {
@ -364,15 +366,9 @@ protected:
RefPtr<DOMMediaStream> mStream; RefPtr<DOMMediaStream> mStream;
}; };
NS_IMPL_ADDREF_INHERITED(DOMMediaStream::PlaybackTrackListener, NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(DOMMediaStream::PlaybackTrackListener, AddRef)
MediaStreamTrackConsumer) NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(DOMMediaStream::PlaybackTrackListener, Release)
NS_IMPL_RELEASE_INHERITED(DOMMediaStream::PlaybackTrackListener, NS_IMPL_CYCLE_COLLECTION(DOMMediaStream::PlaybackTrackListener, mStream)
MediaStreamTrackConsumer)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMMediaStream::PlaybackTrackListener)
NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackConsumer)
NS_IMPL_CYCLE_COLLECTION_INHERITED(DOMMediaStream::PlaybackTrackListener,
MediaStreamTrackConsumer,
mStream)
NS_IMPL_CYCLE_COLLECTION_CLASS(DOMMediaStream) NS_IMPL_CYCLE_COLLECTION_CLASS(DOMMediaStream)
@ -580,6 +576,68 @@ DOMMediaStream::CurrentTime()
StreamTimeToSeconds(mPlaybackStream->GetCurrentTime() - mLogicalStreamStartTime)); StreamTimeToSeconds(mPlaybackStream->GetCurrentTime() - mLogicalStreamStartTime));
} }
already_AddRefed<Promise>
DOMMediaStream::CountUnderlyingStreams(const GlobalObject& aGlobal, ErrorResult& aRv)
{
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports());
if (!window) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(aGlobal.GetAsSupports());
if (!go) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
RefPtr<Promise> p = Promise::Create(go, aRv);
if (aRv.Failed()) {
return nullptr;
}
MediaStreamGraph* graph = MediaStreamGraph::GetInstanceIfExists(window);
if (!graph) {
p->MaybeResolve(0);
return p.forget();
}
auto* graphImpl = static_cast<MediaStreamGraphImpl*>(graph);
class Counter : public ControlMessage
{
public:
Counter(MediaStreamGraphImpl* aGraph,
const RefPtr<Promise>& aPromise)
: ControlMessage(nullptr)
, mGraph(aGraph)
, mPromise(MakeAndAddRef<nsMainThreadPtrHolder<Promise>>("DOMMediaStream::Counter::mPromise", aPromise))
{
MOZ_ASSERT(NS_IsMainThread());
}
void Run() override
{
nsMainThreadPtrHandle<Promise>& promise = mPromise;
uint32_t streams = mGraph->mStreams.Length() +
mGraph->mSuspendedStreams.Length();
mGraph->DispatchToMainThreadAfterStreamStateUpdate(
NewRunnableFrom([promise, streams]() mutable {
promise->MaybeResolve(streams);
return NS_OK;
}));
}
private:
// mGraph owns this Counter instance and decides its lifetime.
MediaStreamGraphImpl* mGraph;
nsMainThreadPtrHandle<Promise> mPromise;
};
graphImpl->AppendMessage(MakeUnique<Counter>(graphImpl, p));
return p.forget();
}
void void
DOMMediaStream::GetId(nsAString& aID) const DOMMediaStream::GetId(nsAString& aID) const
{ {

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

@ -360,6 +360,9 @@ public:
double CurrentTime(); double CurrentTime();
static already_AddRefed<dom::Promise>
CountUnderlyingStreams(const dom::GlobalObject& aGlobal, ErrorResult& aRv);
void GetId(nsAString& aID) const; void GetId(nsAString& aID) const;
void GetAudioTracks(nsTArray<RefPtr<AudioStreamTrack> >& aTracks) const; void GetAudioTracks(nsTArray<RefPtr<AudioStreamTrack> >& aTracks) const;

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

@ -59,6 +59,8 @@ enum SourceMediaStream::TrackCommands : uint32_t {
/** /**
* A hash table containing the graph instances, one per document. * A hash table containing the graph instances, one per document.
*
* The key is a hash of nsPIDOMWindowInner, see `WindowToHash`.
*/ */
static nsDataHashtable<nsUint32HashKey, MediaStreamGraphImpl*> gGraphs; static nsDataHashtable<nsUint32HashKey, MediaStreamGraphImpl*> gGraphs;
@ -1589,6 +1591,19 @@ public:
// teardown and just leak, for safety. // teardown and just leak, for safety.
return NS_OK; return NS_OK;
} }
for (MediaStream* stream : mGraph->AllStreams()) {
// Clean up all MediaSegments since we cannot release Images too
// late during shutdown. Also notify listeners that they were removed
// so they can clean up any gfx resources.
if (SourceMediaStream* source = stream->AsSourceStream()) {
// Finishing a SourceStream prevents new data from being appended.
source->Finish();
}
stream->GetStreamTracks().Clear();
stream->RemoveAllListenersImpl();
}
mGraph->mForceShutdownTicket = nullptr; mGraph->mForceShutdownTicket = nullptr;
// We can't block past the final LIFECYCLE_WAITING_FOR_STREAM_DESTRUCTION // We can't block past the final LIFECYCLE_WAITING_FOR_STREAM_DESTRUCTION
@ -1601,21 +1616,11 @@ public:
mGraph->Destroy(); mGraph->Destroy();
} else { } else {
// The graph is not empty. We must be in a forced shutdown, or a // The graph is not empty. We must be in a forced shutdown, or a
// non-realtime graph that has finished processing. Some later // non-realtime graph that has finished processing. Some later
// AppendMessage will detect that the manager has been emptied, and // AppendMessage will detect that the graph has been emptied, and
// delete it. // delete it.
NS_ASSERTION(mGraph->mForceShutDown || !mGraph->mRealtime, NS_ASSERTION(mGraph->mForceShutDown || !mGraph->mRealtime,
"Not in forced shutdown?"); "Not in forced shutdown?");
for (MediaStream* stream : mGraph->AllStreams()) {
// Clean up all MediaSegments since we cannot release Images too
// late during shutdown.
if (SourceMediaStream* source = stream->AsSourceStream()) {
// Finishing a SourceStream prevents new data from being appended.
source->Finish();
}
stream->GetStreamTracks().Clear();
}
mGraph->mLifecycleState = mGraph->mLifecycleState =
MediaStreamGraphImpl::LIFECYCLE_WAITING_FOR_STREAM_DESTRUCTION; MediaStreamGraphImpl::LIFECYCLE_WAITING_FOR_STREAM_DESTRUCTION;
} }
@ -2074,11 +2079,23 @@ MediaStream::EnsureTrack(TrackID aTrackId)
void void
MediaStream::RemoveAllListenersImpl() MediaStream::RemoveAllListenersImpl()
{ {
for (int32_t i = mListeners.Length() - 1; i >= 0; --i) { GraphImpl()->AssertOnGraphThreadOrNotRunning();
RefPtr<MediaStreamListener> listener = mListeners[i].forget();
listener->NotifyEvent(GraphImpl(), MediaStreamGraphEvent::EVENT_REMOVED); auto streamListeners(mListeners);
for (auto& l : streamListeners) {
l->NotifyEvent(GraphImpl(), MediaStreamGraphEvent::EVENT_REMOVED);
} }
mListeners.Clear(); mListeners.Clear();
auto trackListeners(mTrackListeners);
for (auto& l : trackListeners) {
l.mListener->NotifyRemoved();
}
mTrackListeners.Clear();
if (SourceMediaStream* source = AsSourceStream()) {
source->RemoveAllDirectListeners();
}
} }
void void
@ -3119,6 +3136,18 @@ SourceMediaStream::EndAllTrackAndFinish()
// we will call NotifyEvent() to let GetUserMedia know // we will call NotifyEvent() to let GetUserMedia know
} }
void
SourceMediaStream::RemoveAllDirectListeners()
{
GraphImpl()->AssertOnGraphThreadOrNotRunning();
auto directListeners(mDirectTrackListeners);
for (auto& l : directListeners) {
l.mListener->NotifyDirectListenerUninstalled();
}
mDirectTrackListeners.Clear();
}
SourceMediaStream::~SourceMediaStream() SourceMediaStream::~SourceMediaStream()
{ {
} }
@ -3494,21 +3523,28 @@ uint32_t WindowToHash(nsPIDOMWindowInner* aWindow)
return hashkey; return hashkey;
} }
MediaStreamGraph*
MediaStreamGraph::GetInstanceIfExists(nsPIDOMWindowInner* aWindow)
{
NS_ASSERTION(NS_IsMainThread(), "Main thread only");
uint32_t hashkey = WindowToHash(aWindow);
MediaStreamGraphImpl* graph = nullptr;
gGraphs.Get(hashkey, &graph);
return graph;
}
MediaStreamGraph* MediaStreamGraph*
MediaStreamGraph::GetInstance(MediaStreamGraph::GraphDriverType aGraphDriverRequested, MediaStreamGraph::GetInstance(MediaStreamGraph::GraphDriverType aGraphDriverRequested,
nsPIDOMWindowInner* aWindow) nsPIDOMWindowInner* aWindow)
{ {
NS_ASSERTION(NS_IsMainThread(), "Main thread only"); NS_ASSERTION(NS_IsMainThread(), "Main thread only");
MediaStreamGraphImpl* graph = nullptr; MediaStreamGraphImpl* graph =
static_cast<MediaStreamGraphImpl*>(GetInstanceIfExists(aWindow));
// We hash the nsPIDOMWindowInner to form a key to the gloabl if (!graph) {
// MediaStreamGraph hashtable. Effectively, this means there is a graph per
// document.
uint32_t hashkey = WindowToHash(aWindow);
if (!gGraphs.Get(hashkey, &graph)) {
if (!gMediaStreamGraphShutdownBlocker) { if (!gMediaStreamGraphShutdownBlocker) {
class Blocker : public media::ShutdownBlocker class Blocker : public media::ShutdownBlocker
@ -3556,6 +3592,7 @@ MediaStreamGraph::GetInstance(MediaStreamGraph::GraphDriverType aGraphDriverRequ
CubebUtils::PreferredSampleRate(), CubebUtils::PreferredSampleRate(),
mainThread); mainThread);
uint32_t hashkey = WindowToHash(aWindow);
gGraphs.Put(hashkey, graph); gGraphs.Put(hashkey, graph);
LOG(LogLevel::Debug, LOG(LogLevel::Debug,

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

@ -784,6 +784,12 @@ public:
*/ */
void EndAllTrackAndFinish(); void EndAllTrackAndFinish();
/**
* Removes all direct listeners and signals to them that they have been
* uninstalled.
*/
void RemoveAllDirectListeners();
void RegisterForAudioMixing(); void RegisterForAudioMixing();
/** /**
@ -1264,6 +1270,7 @@ public:
static const uint32_t AUDIO_CALLBACK_DRIVER_SHUTDOWN_TIMEOUT = 20*1000; static const uint32_t AUDIO_CALLBACK_DRIVER_SHUTDOWN_TIMEOUT = 20*1000;
// Main thread only // Main thread only
static MediaStreamGraph* GetInstanceIfExists(nsPIDOMWindowInner* aWindow);
static MediaStreamGraph* GetInstance(GraphDriverType aGraphDriverRequested, static MediaStreamGraph* GetInstance(GraphDriverType aGraphDriverRequested,
nsPIDOMWindowInner* aWindow); nsPIDOMWindowInner* aWindow);
static MediaStreamGraph* CreateNonRealtimeInstance( static MediaStreamGraph* CreateNonRealtimeInstance(

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

@ -57,14 +57,6 @@ MediaStreamTrackSource::ApplyConstraints(
return p.forget(); return p.forget();
} }
NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaStreamTrackConsumer)
NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaStreamTrackConsumer)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamTrackConsumer)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTION_0(MediaStreamTrackConsumer)
/** /**
* PrincipalHandleListener monitors changes in PrincipalHandle of the media flowing * PrincipalHandleListener monitors changes in PrincipalHandle of the media flowing
* through the MediaStreamGraph. * through the MediaStreamGraph.
@ -187,7 +179,6 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(MediaStreamTrack)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaStreamTrack, NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaStreamTrack,
DOMEventTargetHelper) DOMEventTargetHelper)
tmp->Destroy(); tmp->Destroy();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsumers)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwningStream) NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwningStream)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSource) NS_IMPL_CYCLE_COLLECTION_UNLINK(mSource)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginalTrack) NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginalTrack)
@ -197,7 +188,6 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaStreamTrack, NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaStreamTrack,
DOMEventTargetHelper) DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsumers)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwningStream) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwningStream)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSource) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSource)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalTrack) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalTrack)
@ -379,10 +369,14 @@ MediaStreamTrack::NotifyEnded()
{ {
MOZ_ASSERT(mReadyState == MediaStreamTrackState::Ended); MOZ_ASSERT(mReadyState == MediaStreamTrackState::Ended);
for (int32_t i = mConsumers.Length() - 1; i >= 0; --i) { auto consumers(mConsumers);
// Loop backwards by index in case the consumer removes itself in the for (const auto& consumer : consumers) {
// callback. if (consumer) {
mConsumers[i]->NotifyEnded(this); consumer->NotifyEnded(this);
} else {
MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
mConsumers.RemoveElement(consumer);
}
} }
} }
@ -405,12 +399,22 @@ MediaStreamTrack::AddConsumer(MediaStreamTrackConsumer* aConsumer)
{ {
MOZ_ASSERT(!mConsumers.Contains(aConsumer)); MOZ_ASSERT(!mConsumers.Contains(aConsumer));
mConsumers.AppendElement(aConsumer); mConsumers.AppendElement(aConsumer);
// Remove destroyed consumers for cleanliness
while (mConsumers.RemoveElement(nullptr)) {
MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
}
} }
void void
MediaStreamTrack::RemoveConsumer(MediaStreamTrackConsumer* aConsumer) MediaStreamTrack::RemoveConsumer(MediaStreamTrackConsumer* aConsumer)
{ {
mConsumers.RemoveElement(aConsumer); mConsumers.RemoveElement(aConsumer);
// Remove destroyed consumers for cleanliness
while (mConsumers.RemoveElement(nullptr)) {
MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
}
} }
already_AddRefed<MediaStreamTrack> already_AddRefed<MediaStreamTrack>

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

@ -14,6 +14,7 @@
#include "mozilla/dom/MediaStreamTrackBinding.h" #include "mozilla/dom/MediaStreamTrackBinding.h"
#include "mozilla/dom/MediaTrackSettingsBinding.h" #include "mozilla/dom/MediaTrackSettingsBinding.h"
#include "mozilla/media/MediaUtils.h" #include "mozilla/media/MediaUtils.h"
#include "mozilla/WeakPtr.h"
#include "nsError.h" #include "nsError.h"
#include "nsID.h" #include "nsID.h"
#include "nsIPrincipal.h" #include "nsIPrincipal.h"
@ -221,11 +222,11 @@ protected:
* Base class that consumers of a MediaStreamTrack can use to get notifications * Base class that consumers of a MediaStreamTrack can use to get notifications
* about state changes in the track. * about state changes in the track.
*/ */
class MediaStreamTrackConsumer : public nsISupports class MediaStreamTrackConsumer
: public SupportsWeakPtr<MediaStreamTrackConsumer>
{ {
public: public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS MOZ_DECLARE_WEAKREFERENCE_TYPENAME(MediaStreamTrackConsumer)
NS_DECL_CYCLE_COLLECTION_CLASS(MediaStreamTrackConsumer)
/** /**
* Called when the track's readyState transitions to "ended". * Called when the track's readyState transitions to "ended".
@ -233,9 +234,6 @@ public:
* including MediaStreamTrack::Stop(). * including MediaStreamTrack::Stop().
*/ */
virtual void NotifyEnded(MediaStreamTrack* aTrack) {}; virtual void NotifyEnded(MediaStreamTrack* aTrack) {};
protected:
virtual ~MediaStreamTrackConsumer() {}
}; };
/** /**
@ -456,7 +454,7 @@ protected:
nsTArray<PrincipalChangeObserver<MediaStreamTrack>*> mPrincipalChangeObservers; nsTArray<PrincipalChangeObserver<MediaStreamTrack>*> mPrincipalChangeObservers;
nsTArray<RefPtr<MediaStreamTrackConsumer>> mConsumers; nsTArray<WeakPtr<MediaStreamTrackConsumer>> mConsumers;
RefPtr<DOMMediaStream> mOwningStream; RefPtr<DOMMediaStream> mOwningStream;
TrackID mTrackID; TrackID mTrackID;

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

@ -632,7 +632,11 @@ MediaEncoder::CreateEncoder(TaskQueue* aEncoderThread,
audioEncoder = MakeAndAddRef<OpusTrackEncoder>(aTrackRate); audioEncoder = MakeAndAddRef<OpusTrackEncoder>(aTrackRate);
NS_ENSURE_TRUE(audioEncoder, nullptr); NS_ENSURE_TRUE(audioEncoder, nullptr);
} }
videoEncoder = MakeAndAddRef<VP8TrackEncoder>(aTrackRate); if (Preferences::GetBool("media.recorder.video.frame_drops", true)) {
videoEncoder = MakeAndAddRef<VP8TrackEncoder>(aTrackRate, FrameDroppingMode::ALLOW);
} else {
videoEncoder = MakeAndAddRef<VP8TrackEncoder>(aTrackRate, FrameDroppingMode::DISALLOW);
}
writer = MakeUnique<WebMWriter>(aTrackTypes); writer = MakeUnique<WebMWriter>(aTrackTypes);
NS_ENSURE_TRUE(writer, nullptr); NS_ENSURE_TRUE(writer, nullptr);
NS_ENSURE_TRUE(videoEncoder, nullptr); NS_ENSURE_TRUE(videoEncoder, nullptr);

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

@ -385,10 +385,15 @@ protected:
bool mDirectConnected; bool mDirectConnected;
}; };
enum class FrameDroppingMode {
ALLOW, // Allowed to drop frames to keep up under load
DISALLOW, // Must not drop any frames, even if it means we will OOM
};
class VideoTrackEncoder : public TrackEncoder class VideoTrackEncoder : public TrackEncoder
{ {
public: public:
explicit VideoTrackEncoder(TrackRate aTrackRate) VideoTrackEncoder(TrackRate aTrackRate, FrameDroppingMode aFrameDroppingMode)
: TrackEncoder(aTrackRate) : TrackEncoder(aTrackRate)
, mFrameWidth(0) , mFrameWidth(0)
, mFrameHeight(0) , mFrameHeight(0)
@ -396,6 +401,7 @@ public:
, mDisplayHeight(0) , mDisplayHeight(0)
, mEncodedTicks(0) , mEncodedTicks(0)
, mVideoBitrate(0) , mVideoBitrate(0)
, mFrameDroppingMode(aFrameDroppingMode)
{ {
mLastChunk.mDuration = 0; mLastChunk.mDuration = 0;
} }
@ -551,6 +557,12 @@ protected:
TimeStamp mSuspendTime; TimeStamp mSuspendTime;
uint32_t mVideoBitrate; uint32_t mVideoBitrate;
/**
* ALLOW to drop frames under load.
* DISALLOW to encode all frames, mainly for testing.
*/
FrameDroppingMode mFrameDroppingMode;
}; };
} // namespace mozilla } // namespace mozilla

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

@ -56,8 +56,9 @@ GetSourceSurface(already_AddRefed<Image> aImg)
return surf.forget(); return surf.forget();
} }
VP8TrackEncoder::VP8TrackEncoder(TrackRate aTrackRate) VP8TrackEncoder::VP8TrackEncoder(TrackRate aTrackRate,
: VideoTrackEncoder(aTrackRate) FrameDroppingMode aFrameDroppingMode)
: VideoTrackEncoder(aTrackRate, aFrameDroppingMode)
, mEncodedTimestamp(0) , mEncodedTimestamp(0)
, mVPXContext(new vpx_codec_ctx_t()) , mVPXContext(new vpx_codec_ctx_t())
, mVPXImageWrapper(new vpx_image_t()) , mVPXImageWrapper(new vpx_image_t())
@ -576,6 +577,10 @@ VP8TrackEncoder::EncodeOperation
VP8TrackEncoder::GetNextEncodeOperation(TimeDuration aTimeElapsed, VP8TrackEncoder::GetNextEncodeOperation(TimeDuration aTimeElapsed,
StreamTime aProcessedDuration) StreamTime aProcessedDuration)
{ {
if (mFrameDroppingMode == FrameDroppingMode::DISALLOW) {
return ENCODE_NORMAL_FRAME;
}
int64_t durationInUsec = int64_t durationInUsec =
FramesToUsecs(aProcessedDuration, mTrackRate).value(); FramesToUsecs(aProcessedDuration, mTrackRate).value();
if (aTimeElapsed.ToMicroseconds() > (durationInUsec * SKIP_FRAME_RATIO)) { if (aTimeElapsed.ToMicroseconds() > (durationInUsec * SKIP_FRAME_RATIO)) {

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

@ -27,8 +27,9 @@ class VP8TrackEncoder : public VideoTrackEncoder
ENCODE_I_FRAME, // The next frame will be encoded as I-Frame. ENCODE_I_FRAME, // The next frame will be encoded as I-Frame.
SKIP_FRAME, // Skip the next frame. SKIP_FRAME, // Skip the next frame.
}; };
public: public:
explicit VP8TrackEncoder(TrackRate aTrackRate); VP8TrackEncoder(TrackRate aTrackRate, FrameDroppingMode aFrameDroppingMode);
virtual ~VP8TrackEncoder(); virtual ~VP8TrackEncoder();
already_AddRefed<TrackMetadataBase> GetMetadata() final override; already_AddRefed<TrackMetadataBase> GetMetadata() final override;

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

@ -192,7 +192,7 @@ class TestVP8TrackEncoder: public VP8TrackEncoder
{ {
public: public:
explicit TestVP8TrackEncoder(TrackRate aTrackRate = VIDEO_TRACK_RATE) explicit TestVP8TrackEncoder(TrackRate aTrackRate = VIDEO_TRACK_RATE)
: VP8TrackEncoder(aTrackRate) {} : VP8TrackEncoder(aTrackRate, FrameDroppingMode::DISALLOW) {}
::testing::AssertionResult TestInit(const InitParam &aParam) ::testing::AssertionResult TestInit(const InitParam &aParam)
{ {

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

@ -31,7 +31,7 @@ class WebMVP8TrackEncoder: public VP8TrackEncoder
{ {
public: public:
explicit WebMVP8TrackEncoder(TrackRate aTrackRate = 90000) explicit WebMVP8TrackEncoder(TrackRate aTrackRate = 90000)
: VP8TrackEncoder(aTrackRate) {} : VP8TrackEncoder(aTrackRate, FrameDroppingMode::DISALLOW) {}
bool TestVP8Creation(int32_t aWidth, int32_t aHeight, int32_t aDisplayWidth, bool TestVP8Creation(int32_t aWidth, int32_t aHeight, int32_t aDisplayWidth,
int32_t aDisplayHeight) int32_t aDisplayHeight)

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

@ -63,6 +63,7 @@ skip-if = toolkit == 'android' # no windowshare on android
[test_getUserMedia_bug1223696.html] [test_getUserMedia_bug1223696.html]
[test_getUserMedia_constraints.html] [test_getUserMedia_constraints.html]
[test_getUserMedia_callbacks.html] [test_getUserMedia_callbacks.html]
[test_getUserMedia_GC_MediaStream.html]
[test_getUserMedia_getTrackById.html] [test_getUserMedia_getTrackById.html]
[test_getUserMedia_gumWithinGum.html] [test_getUserMedia_gumWithinGum.html]
[test_getUserMedia_loadedmetadata.html] [test_getUserMedia_loadedmetadata.html]

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

@ -0,0 +1,68 @@
<!DOCTYPE HTML>
<html>
<head>
<script type="application/javascript" src="mediaStreamPlayback.js"></script>
</head>
<body>
<pre id="test">
<script type="application/javascript">
"use strict";
createHTML({
title: "MediaStreams can be garbage collected",
bug: "1407542"
});
let SpecialStream = SpecialPowers.wrap(MediaStream);
async function testGC(stream, numCopies, copy) {
let startStreams = await SpecialStream.countUnderlyingStreams();
let copies = new Array(numCopies).fill(0).map(() => copy(stream));
ok(await SpecialStream.countUnderlyingStreams() > startStreams,
"MediaStream constructor creates more underlying streams");
copies = [];
await new Promise(r => SpecialPowers.exactGC(r));
is(await SpecialStream.countUnderlyingStreams(), startStreams,
"MediaStreams should have been collected");
}
runTest(async () => {
let gUMStream = await getUserMedia({video: true});
info("Testing GC of copy constructor");
await testGC(gUMStream, 10, s => new MediaStream(s));
info("Testing GC of track-array constructor");
await testGC(gUMStream, 10, s => new MediaStream(s.getTracks()));
info("Testing GC of empty constructor plus addTrack");
await testGC(gUMStream, 10, s => {
let s2 = new MediaStream();
s.getTracks().forEach(t => s2.addTrack(t));
return s2;
});
info("Testing GC of track-array constructor with cloned tracks");
await testGC(gUMStream, 10, s => new MediaStream(s.getTracks().map(t => t.clone())));
info("Testing GC of empty constructor plus addTrack with cloned tracks");
await testGC(gUMStream, 10, s => {
let s2 = new MediaStream();
s.getTracks().forEach(t => s2.addTrack(t.clone()));
return s2;
});
info("Testing GC of cloned stream");
await testGC(gUMStream, 10, s => s.clone());
info("Testing GC of gUM stream");
gUMStream = null;
await new Promise(r => SpecialPowers.exactGC(r));
is(await SpecialStream.countUnderlyingStreams(), 0,
"Original gUM stream should be collectable");
});
</script>
</pre>
</body>
</html>

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

@ -40,4 +40,7 @@ interface MediaStream : EventTarget {
attribute EventHandler onaddtrack; attribute EventHandler onaddtrack;
// attribute EventHandler onremovetrack; // attribute EventHandler onremovetrack;
readonly attribute double currentTime; readonly attribute double currentTime;
[ChromeOnly, Throws]
static Promise<long> countUnderlyingStreams();
}; };

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

@ -44,7 +44,7 @@ html|input.empty {
color: graytext; color: graytext;
} }
@media (-moz-windows-default-theme) { @media (-moz-windows-default-theme) and (-moz-os-version: windows-win7) {
:root:not(.winxp) html|input.empty { :root:not(.winxp) html|input.empty {
font-style: italic; font-style: italic;
} }

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

@ -1,4 +1,4 @@
fails-if(Android) fails-if(stylo&&winWidget) skip-if(browserIsRemote&&winWidget) == empty-1.xul empty-ref.xul # Windows: bug 1239170, stylo: bug 1411460 fails-if(Android) skip-if(browserIsRemote&&winWidget) == empty-1.xul empty-ref.xul # Windows: bug 1239170
!= empty-2.xul empty-ref.xul != empty-2.xul empty-ref.xul
# There is no way to simulate an autocomplete textbox in windows XP/Vista/7/8/10 default theme using CSS. # There is no way to simulate an autocomplete textbox in windows XP/Vista/7/8/10 default theme using CSS.
# Therefore, the equlity tests below should be marked as failing. # Therefore, the equlity tests below should be marked as failing.

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

@ -203,6 +203,7 @@ XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_WRONG_SIGNATURE , "The JAR's signature is wr
XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE , "An entry in the JAR is too large.") XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE , "An entry in the JAR is too large.")
XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_ENTRY_INVALID , "An entry in the JAR is invalid.") XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_ENTRY_INVALID , "An entry in the JAR is invalid.")
XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_MANIFEST_INVALID , "The JAR's manifest or signature file is invalid.") XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_MANIFEST_INVALID , "The JAR's manifest or signature file is invalid.")
XPC_MSG_DEF(NS_ERROR_CMS_VERIFY_NOT_SIGNED , "The PKCS#7 information is not signed.")
/* Codes related to signed manifests */ /* Codes related to signed manifests */
XPC_MSG_DEF(NS_ERROR_SIGNED_APP_MANIFEST_INVALID , "The signed app manifest or signature file is invalid.") XPC_MSG_DEF(NS_ERROR_SIGNED_APP_MANIFEST_INVALID , "The signed app manifest or signature file is invalid.")

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

@ -833,7 +833,8 @@ class ConfigureCodec {
mHardwareH264Supported(false), mHardwareH264Supported(false),
mSoftwareH264Enabled(false), mSoftwareH264Enabled(false),
mH264Enabled(false), mH264Enabled(false),
mVP9Enabled(false), mVP9Enabled(true),
mVP9Preferred(false),
mH264Level(13), // minimum suggested for WebRTC spec mH264Level(13), // minimum suggested for WebRTC spec
mH264MaxBr(0), // Unlimited mH264MaxBr(0), // Unlimited
mH264MaxMbps(0), // Unlimited mH264MaxMbps(0), // Unlimited
@ -859,6 +860,9 @@ class ConfigureCodec {
branch->GetBoolPref("media.peerconnection.video.vp9_enabled", branch->GetBoolPref("media.peerconnection.video.vp9_enabled",
&mVP9Enabled); &mVP9Enabled);
branch->GetBoolPref("media.peerconnection.video.vp9_preferred",
&mVP9Preferred);
branch->GetIntPref("media.navigator.video.max_fs", &mVP8MaxFs); branch->GetIntPref("media.navigator.video.max_fs", &mVP8MaxFs);
if (mVP8MaxFs <= 0) { if (mVP8MaxFs <= 0) {
mVP8MaxFs = 12288; // We must specify something other than 0 mVP8MaxFs = 12288; // We must specify something other than 0
@ -930,9 +934,14 @@ class ConfigureCodec {
} else if (videoCodec.mName == "ulpfec") { } else if (videoCodec.mName == "ulpfec") {
videoCodec.mEnabled = mRedUlpfecEnabled; videoCodec.mEnabled = mRedUlpfecEnabled;
} else if (videoCodec.mName == "VP8" || videoCodec.mName == "VP9") { } else if (videoCodec.mName == "VP8" || videoCodec.mName == "VP9") {
if (videoCodec.mName == "VP9" && !mVP9Enabled) { if (videoCodec.mName == "VP9") {
videoCodec.mEnabled = false; if (!mVP9Enabled) {
break; videoCodec.mEnabled = false;
break;
}
if (mVP9Preferred) {
videoCodec.mStronglyPreferred = true;
}
} }
videoCodec.mConstraints.maxFs = mVP8MaxFs; videoCodec.mConstraints.maxFs = mVP8MaxFs;
videoCodec.mConstraints.maxFps = mVP8MaxFr; videoCodec.mConstraints.maxFps = mVP8MaxFr;
@ -959,6 +968,7 @@ class ConfigureCodec {
bool mSoftwareH264Enabled; bool mSoftwareH264Enabled;
bool mH264Enabled; bool mH264Enabled;
bool mVP9Enabled; bool mVP9Enabled;
bool mVP9Preferred;
int32_t mH264Level; int32_t mH264Level;
int32_t mH264MaxBr; int32_t mH264MaxBr;
int32_t mH264MaxMbps; int32_t mH264MaxMbps;

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

@ -18,7 +18,10 @@ public class BookmarkUtils {
* full bookmark management features(full-page dialog, bookmark/folder modification, etc.) * full bookmark management features(full-page dialog, bookmark/folder modification, etc.)
*/ */
public static boolean isEnabled(Context context) { public static boolean isEnabled(Context context) {
return AppConstants.NIGHTLY_BUILD && final boolean initialized = SwitchBoard.hasExperimentValues(context, Experiments.FULL_BOOKMARK_MANAGEMENT);
SwitchBoard.isInExperiment(context, Experiments.FULL_BOOKMARK_MANAGEMENT); if (!initialized) {
return true;
}
return SwitchBoard.isInExperiment(context, Experiments.FULL_BOOKMARK_MANAGEMENT);
} }
} }

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

@ -485,6 +485,7 @@ pref("media.navigator.video.h264.max_br", 0);
pref("media.navigator.video.h264.max_mbps", 0); pref("media.navigator.video.h264.max_mbps", 0);
pref("media.peerconnection.video.h264_enabled", false); pref("media.peerconnection.video.h264_enabled", false);
pref("media.peerconnection.video.vp9_enabled", true); pref("media.peerconnection.video.vp9_enabled", true);
pref("media.peerconnection.video.vp9_preferred", false);
pref("media.getusermedia.aec", 1); pref("media.getusermedia.aec", 1);
pref("media.getusermedia.browser.enabled", false); pref("media.getusermedia.browser.enabled", false);
pref("media.getusermedia.channels", 0); pref("media.getusermedia.channels", 0);
@ -616,6 +617,13 @@ pref("media.webspeech.synth.enabled", false);
pref("media.encoder.webm.enabled", true); pref("media.encoder.webm.enabled", true);
#endif #endif
// Whether to allow recording of AudioNodes with MediaRecorder
pref("media.recorder.audio_node.enabled", false);
// Whether MediaRecorder's video encoder should allow dropping frames in order
// to keep up under load. Useful for tests but beware of memory consumption!
pref("media.recorder.video.frame_drops", true);
// Whether to autostart a media element with an |autoplay| attribute // Whether to autostart a media element with an |autoplay| attribute
pref("media.autoplay.enabled", true); pref("media.autoplay.enabled", true);

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

@ -10,15 +10,17 @@
#include "CryptoTask.h" #include "CryptoTask.h"
#include "NSSCertDBTrustDomain.h" #include "NSSCertDBTrustDomain.h"
#include "ScopedNSSTypes.h" #include "ScopedNSSTypes.h"
#include "SharedCertVerifier.h"
#include "certdb.h" #include "certdb.h"
#include "cms.h"
#include "mozilla/Base64.h" #include "mozilla/Base64.h"
#include "mozilla/Casting.h" #include "mozilla/Casting.h"
#include "mozilla/Logging.h" #include "mozilla/Logging.h"
#include "mozilla/RefPtr.h" #include "mozilla/RefPtr.h"
#include "mozilla/UniquePtr.h" #include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "nsCOMPtr.h" #include "nsCOMPtr.h"
#include "nsComponentManagerUtils.h" #include "nsComponentManagerUtils.h"
#include "nsDataSignatureVerifier.h"
#include "nsDependentString.h" #include "nsDependentString.h"
#include "nsHashKeys.h" #include "nsHashKeys.h"
#include "nsIDirectoryEnumerator.h" #include "nsIDirectoryEnumerator.h"
@ -139,8 +141,8 @@ ReadStream(const nsCOMPtr<nsIInputStream>& stream, /*out*/ SECItem& buf)
} }
// Finds exactly one (signature metadata) JAR entry that matches the given // Finds exactly one (signature metadata) JAR entry that matches the given
// search pattern, and then load it. Fails if there are no matches or if // search pattern, and then loads it. Fails if there are no matches or if
// there is more than one match. If bugDigest is not null then on success // there is more than one match. If bufDigest is not null then on success
// bufDigest will contain the digeset of the entry using the given digest // bufDigest will contain the digeset of the entry using the given digest
// algorithm. // algorithm.
nsresult nsresult
@ -465,10 +467,10 @@ CheckManifestVersion(const char* & nextLineStart,
// Parses a signature file (SF) based on the JDK 8 JAR Specification. // Parses a signature file (SF) based on the JDK 8 JAR Specification.
// //
// The SF file must contain a SHA1-Digest-Manifest (or, preferrably, // The SF file must contain a SHA*-Digest-Manifest attribute in the main
// SHA256-Digest-Manifest) attribute in the main section. All other sections are // section (where the * is either 1 or 256, depending on the given digest
// ignored. This means that this will NOT parse old-style signature files that // algorithm). All other sections are ignored. This means that this will NOT
// have separate digests per entry. // parse old-style signature files that have separate digests per entry.
// The JDK8 x-Digest-Manifest variant is better because: // The JDK8 x-Digest-Manifest variant is better because:
// //
// (1) It allows us to follow the principle that we should minimize the // (1) It allows us to follow the principle that we should minimize the
@ -480,11 +482,24 @@ CheckManifestVersion(const char* & nextLineStart,
// x-Digest-Manifest instead of multiple x-Digest values. // x-Digest-Manifest instead of multiple x-Digest values.
// //
// filebuf must be null-terminated. On output, mfDigest will contain the // filebuf must be null-terminated. On output, mfDigest will contain the
// decoded value of SHA1-Digest-Manifest or SHA256-Digest-Manifest, if found, // decoded value of the appropriate SHA*-DigestManifest, if found.
// as well as an identifier indicating which algorithm was found.
nsresult nsresult
ParseSF(const char* filebuf, /*out*/ DigestWithAlgorithm& mfDigest) ParseSF(const char* filebuf, SECOidTag digestAlgorithm,
/*out*/ nsAutoCString& mfDigest)
{ {
const char* digestNameToFind = nullptr;
switch (digestAlgorithm) {
case SEC_OID_SHA256:
digestNameToFind = "sha256-digest-manifest";
break;
case SEC_OID_SHA1:
digestNameToFind = "sha1-digest-manifest";
break;
default:
MOZ_ASSERT_UNREACHABLE("bad argument to ParseSF");
return NS_ERROR_FAILURE;
}
const char* nextLineStart = filebuf; const char* nextLineStart = filebuf;
nsresult rv = CheckManifestVersion(nextLineStart, nsresult rv = CheckManifestVersion(nextLineStart,
NS_LITERAL_CSTRING(JAR_SF_HEADER)); NS_LITERAL_CSTRING(JAR_SF_HEADER));
@ -492,9 +507,6 @@ ParseSF(const char* filebuf, /*out*/ DigestWithAlgorithm& mfDigest)
return rv; return rv;
} }
nsAutoCString savedSHA1Digest;
// Search for SHA256-Digest-Manifest and SHA1-Digest-Manifest. Prefer the
// former.
for (;;) { for (;;) {
nsAutoCString curLine; nsAutoCString curLine;
rv = ReadLine(nextLineStart, curLine); rv = ReadLine(nextLineStart, curLine);
@ -503,13 +515,8 @@ ParseSF(const char* filebuf, /*out*/ DigestWithAlgorithm& mfDigest)
} }
if (curLine.Length() == 0) { if (curLine.Length() == 0) {
// End of main section (blank line or end-of-file). We didn't find // End of main section (blank line or end-of-file). We didn't find the
// SHA256-Digest-Manifest, but maybe we found SHA1-Digest-Manifest. // SHA*-Digest-Manifest we were looking for.
if (!savedSHA1Digest.IsEmpty()) {
mfDigest.mDigest.Assign(savedSHA1Digest);
mfDigest.mAlgorithm = SEC_OID_SHA1;
return NS_OK;
}
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
} }
@ -520,12 +527,11 @@ ParseSF(const char* filebuf, /*out*/ DigestWithAlgorithm& mfDigest)
return rv; return rv;
} }
if (attrName.LowerCaseEqualsLiteral("sha256-digest-manifest")) { if (attrName.EqualsIgnoreCase(digestNameToFind)) {
rv = Base64Decode(attrValue, mfDigest.mDigest); rv = Base64Decode(attrValue, mfDigest);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
return rv; return rv;
} }
mfDigest.mAlgorithm = SEC_OID_SHA256;
// There could be multiple SHA*-Digest-Manifest attributes, which // There could be multiple SHA*-Digest-Manifest attributes, which
// would be an error, but it's better to just skip any erroneous // would be an error, but it's better to just skip any erroneous
@ -538,18 +544,11 @@ ParseSF(const char* filebuf, /*out*/ DigestWithAlgorithm& mfDigest)
return NS_OK; return NS_OK;
} }
if (attrName.LowerCaseEqualsLiteral("sha1-digest-manifest") &&
savedSHA1Digest.IsEmpty()) {
rv = Base64Decode(attrValue, savedSHA1Digest);
if (NS_FAILED(rv)) {
return rv;
}
}
// ignore unrecognized attributes // ignore unrecognized attributes
} }
return NS_OK; MOZ_ASSERT_UNREACHABLE("somehow exited loop in ParseSF without returning");
return NS_ERROR_FAILURE;
} }
// Parses MANIFEST.MF. The filenames of all entries will be returned in // Parses MANIFEST.MF. The filenames of all entries will be returned in
@ -702,23 +701,16 @@ ParseMF(const char* filebuf, nsIZipReader* zip, SECOidTag digestAlgorithm,
return NS_OK; return NS_OK;
} }
struct VerifyCertificateContext {
AppTrustedRoot trustedRoot;
UniqueCERTCertList& builtChain;
};
nsresult nsresult
VerifyCertificate(CERTCertificate* signerCert, void* voidContext, void* pinArg) VerifyCertificate(CERTCertificate* signerCert, AppTrustedRoot trustedRoot,
/*out*/ UniqueCERTCertList& builtChain)
{ {
// TODO: null pinArg is tolerated. if (NS_WARN_IF(!signerCert)) {
if (NS_WARN_IF(!signerCert) || NS_WARN_IF(!voidContext)) {
return NS_ERROR_INVALID_ARG; return NS_ERROR_INVALID_ARG;
} }
const VerifyCertificateContext& context = // TODO: pinArg is null.
*static_cast<const VerifyCertificateContext*>(voidContext); AppTrustDomain trustDomain(builtChain, nullptr);
nsresult rv = trustDomain.SetTrustedRoot(trustedRoot);
AppTrustDomain trustDomain(context.builtChain, pinArg);
nsresult rv = trustDomain.SetTrustedRoot(context.trustedRoot);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
return rv; return rv;
} }
@ -763,23 +755,151 @@ VerifyCertificate(CERTCertificate* signerCert, void* voidContext, void* pinArg)
return NS_OK; return NS_OK;
} }
// Given a SECOidTag representing a digest algorithm (either SEC_OID_SHA1 or
// SEC_OID_SHA256), returns the first signerInfo in the given signedData that
// purports to have been created using that digest algorithm, or nullptr if
// there is none.
// The returned signerInfo is owned by signedData, so the caller must ensure
// that the lifetime of the signerInfo is contained by the lifetime of the
// signedData.
NSSCMSSignerInfo*
GetSignerInfoForDigestAlgorithm(NSSCMSSignedData* signedData,
SECOidTag digestAlgorithm)
{
MOZ_ASSERT(digestAlgorithm == SEC_OID_SHA1 ||
digestAlgorithm == SEC_OID_SHA256);
if (digestAlgorithm != SEC_OID_SHA1 && digestAlgorithm != SEC_OID_SHA256) {
return nullptr;
}
int numSigners = NSS_CMSSignedData_SignerInfoCount(signedData);
if (numSigners < 1) {
return nullptr;
}
for (int i = 0; i < numSigners; i++) {
NSSCMSSignerInfo* signerInfo =
NSS_CMSSignedData_GetSignerInfo(signedData, i);
// NSS_CMSSignerInfo_GetDigestAlgTag isn't exported from NSS.
SECOidData* digestAlgOID = SECOID_FindOID(&signerInfo->digestAlg.algorithm);
if (!digestAlgOID) {
continue;
}
if (digestAlgorithm == digestAlgOID->offset) {
return signerInfo;
}
}
return nullptr;
}
nsresult nsresult
VerifySignature(AppTrustedRoot trustedRoot, const SECItem& buffer, VerifySignature(AppTrustedRoot trustedRoot, const SECItem& buffer,
const SECItem& detachedDigest, const SECItem& detachedSHA1Digest,
const SECItem& detachedSHA256Digest,
/*out*/ SECOidTag& digestAlgorithm,
/*out*/ UniqueCERTCertList& builtChain) /*out*/ UniqueCERTCertList& builtChain)
{ {
// Currently, this function is only called within the CalculateResult() method // Currently, this function is only called within the CalculateResult() method
// of CryptoTasks. As such, NSS should not be shut down at this point and the // of CryptoTasks. As such, NSS should not be shut down at this point and the
// CryptoTask implementation should already hold a nsNSSShutDownPreventionLock. // CryptoTask implementation should already hold a nsNSSShutDownPreventionLock.
// We acquire a nsNSSShutDownPreventionLock here solely to prove we did to
// VerifyCMSDetachedSignatureIncludingCertificate(). if (NS_WARN_IF(!buffer.data || buffer.len == 0 || !detachedSHA1Digest.data ||
nsNSSShutDownPreventionLock locker; detachedSHA1Digest.len == 0 || !detachedSHA256Digest.data ||
VerifyCertificateContext context = { trustedRoot, builtChain }; detachedSHA256Digest.len == 0)) {
// XXX: missing pinArg return NS_ERROR_INVALID_ARG;
return VerifyCMSDetachedSignatureIncludingCertificate(buffer, detachedDigest, }
VerifyCertificate,
&context, nullptr, UniqueNSSCMSMessage
locker); cmsMsg(NSS_CMSMessage_CreateFromDER(const_cast<SECItem*>(&buffer), nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr));
if (!cmsMsg) {
return NS_ERROR_CMS_VERIFY_NOT_SIGNED;
}
if (!NSS_CMSMessage_IsSigned(cmsMsg.get())) {
return NS_ERROR_CMS_VERIFY_NOT_SIGNED;
}
NSSCMSContentInfo* cinfo = NSS_CMSMessage_ContentLevel(cmsMsg.get(), 0);
if (!cinfo) {
return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO;
}
// We're expecting this to be a PKCS#7 signedData content info.
if (NSS_CMSContentInfo_GetContentTypeTag(cinfo)
!= SEC_OID_PKCS7_SIGNED_DATA) {
return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO;
}
// signedData is non-owning
NSSCMSSignedData* signedData =
static_cast<NSSCMSSignedData*>(NSS_CMSContentInfo_GetContent(cinfo));
if (!signedData) {
return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO;
}
// Parse the certificates into CERTCertificate objects held in memory so
// verifyCertificate will be able to find them during path building.
UniqueCERTCertList certs(CERT_NewCertList());
if (!certs) {
return NS_ERROR_OUT_OF_MEMORY;
}
if (signedData->rawCerts) {
for (size_t i = 0; signedData->rawCerts[i]; ++i) {
UniqueCERTCertificate
cert(CERT_NewTempCertificate(CERT_GetDefaultCertDB(),
signedData->rawCerts[i], nullptr, false,
true));
// Skip certificates that fail to parse
if (!cert) {
continue;
}
if (CERT_AddCertToListTail(certs.get(), cert.get()) != SECSuccess) {
return NS_ERROR_OUT_OF_MEMORY;
}
Unused << cert.release(); // Ownership transferred to the cert list.
}
}
NSSCMSSignerInfo* signerInfo =
GetSignerInfoForDigestAlgorithm(signedData, SEC_OID_SHA256);
const SECItem* detachedDigest = &detachedSHA256Digest;
digestAlgorithm = SEC_OID_SHA256;
if (!signerInfo) {
signerInfo = GetSignerInfoForDigestAlgorithm(signedData, SEC_OID_SHA1);
if (!signerInfo) {
return NS_ERROR_CMS_VERIFY_NOT_SIGNED;
}
detachedDigest = &detachedSHA1Digest;
digestAlgorithm = SEC_OID_SHA1;
}
// Get the end-entity certificate.
CERTCertificate* signerCert =
NSS_CMSSignerInfo_GetSigningCertificate(signerInfo,
CERT_GetDefaultCertDB());
if (!signerCert) {
return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
}
nsresult rv = VerifyCertificate(signerCert, trustedRoot, builtChain);
if (NS_FAILED(rv)) {
return rv;
}
// Ensure that the PKCS#7 data OID is present as the PKCS#9 contentType.
const char* pkcs7DataOidString = "1.2.840.113549.1.7.1";
ScopedAutoSECItem pkcs7DataOid;
if (SEC_StringToOID(nullptr, &pkcs7DataOid, pkcs7DataOidString, 0)
!= SECSuccess) {
return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
}
return MapSECStatus(
NSS_CMSSignerInfo_Verify(signerInfo, const_cast<SECItem*>(detachedDigest),
&pkcs7DataOid));
} }
nsresult nsresult
@ -818,24 +938,39 @@ OpenSignedAppFile(AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
// Signature (SF) file // Signature (SF) file
nsAutoCString sfFilename; nsAutoCString sfFilename;
ScopedAutoSECItem sfBuffer; ScopedAutoSECItem sfBuffer;
Digest sfCalculatedDigest;
rv = FindAndLoadOneEntry(zip, NS_LITERAL_CSTRING(JAR_SF_SEARCH_STRING), rv = FindAndLoadOneEntry(zip, NS_LITERAL_CSTRING(JAR_SF_SEARCH_STRING),
sfFilename, sfBuffer, SEC_OID_SHA1, sfFilename, sfBuffer);
&sfCalculatedDigest);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
} }
sigBuffer.type = siBuffer; // Calculate both the SHA-1 and SHA-256 hashes of the signature file - we
UniqueCERTCertList builtChain; // don't know what algorithm the PKCS#7 signature used.
rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedDigest.get(), Digest sfCalculatedSHA1Digest;
builtChain); rv = sfCalculatedSHA1Digest.DigestBuf(SEC_OID_SHA1, sfBuffer.data,
sfBuffer.len - 1);
if (NS_FAILED(rv)) {
return rv;
}
Digest sfCalculatedSHA256Digest;
rv = sfCalculatedSHA256Digest.DigestBuf(SEC_OID_SHA256, sfBuffer.data,
sfBuffer.len - 1);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
return rv; return rv;
} }
DigestWithAlgorithm mfDigest; sigBuffer.type = siBuffer;
rv = ParseSF(BitwiseCast<char*, unsigned char*>(sfBuffer.data), mfDigest); UniqueCERTCertList builtChain;
SECOidTag digestToUse;
rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedSHA1Digest.get(),
sfCalculatedSHA256Digest.get(), digestToUse, builtChain);
if (NS_FAILED(rv)) {
return rv;
}
nsAutoCString mfDigest;
rv = ParseSF(BitwiseCast<char*, unsigned char*>(sfBuffer.data), digestToUse,
mfDigest);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
return rv; return rv;
} }
@ -845,7 +980,7 @@ OpenSignedAppFile(AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
ScopedAutoSECItem manifestBuffer; ScopedAutoSECItem manifestBuffer;
Digest mfCalculatedDigest; Digest mfCalculatedDigest;
rv = FindAndLoadOneEntry(zip, NS_LITERAL_CSTRING(JAR_MF_SEARCH_STRING), rv = FindAndLoadOneEntry(zip, NS_LITERAL_CSTRING(JAR_MF_SEARCH_STRING),
mfFilename, manifestBuffer, mfDigest.mAlgorithm, mfFilename, manifestBuffer, digestToUse,
&mfCalculatedDigest); &mfCalculatedDigest);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
return rv; return rv;
@ -853,7 +988,7 @@ OpenSignedAppFile(AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
nsDependentCSubstring calculatedDigest( nsDependentCSubstring calculatedDigest(
DigestToDependentString(mfCalculatedDigest)); DigestToDependentString(mfCalculatedDigest));
if (!mfDigest.mDigest.Equals(calculatedDigest)) { if (!mfDigest.Equals(calculatedDigest)) {
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
} }
@ -865,7 +1000,7 @@ OpenSignedAppFile(AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
nsTHashtable<nsCStringHashKey> items; nsTHashtable<nsCStringHashKey> items;
rv = ParseMF(BitwiseCast<char*, unsigned char*>(manifestBuffer.data), zip, rv = ParseMF(BitwiseCast<char*, unsigned char*>(manifestBuffer.data), zip,
mfDigest.mAlgorithm, items, buf); digestToUse, items, buf);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
return rv; return rv;
} }
@ -1393,25 +1528,40 @@ VerifySignedDirectory(AppTrustedRoot aTrustedRoot,
+ NS_LITERAL_STRING("sf")); + NS_LITERAL_STRING("sf"));
ScopedAutoSECItem sfBuffer; ScopedAutoSECItem sfBuffer;
Digest sfCalculatedDigest; rv = LoadOneMetafile(metaDir, sfFilename, sfBuffer);
rv = LoadOneMetafile(metaDir, sfFilename, sfBuffer, SEC_OID_SHA1,
&sfCalculatedDigest);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
} }
// Calculate both the SHA-1 and SHA-256 hashes of the signature file - we
// don't know what algorithm the PKCS#7 signature used.
Digest sfCalculatedSHA1Digest;
rv = sfCalculatedSHA1Digest.DigestBuf(SEC_OID_SHA1, sfBuffer.data,
sfBuffer.len - 1);
if (NS_FAILED(rv)) {
return rv;
}
Digest sfCalculatedSHA256Digest;
rv = sfCalculatedSHA256Digest.DigestBuf(SEC_OID_SHA256, sfBuffer.data,
sfBuffer.len - 1);
if (NS_FAILED(rv)) {
return rv;
}
sigBuffer.type = siBuffer; sigBuffer.type = siBuffer;
UniqueCERTCertList builtChain; UniqueCERTCertList builtChain;
rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedDigest.get(), SECOidTag digestToUse;
builtChain); rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedSHA1Digest.get(),
sfCalculatedSHA256Digest.get(), digestToUse, builtChain);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
} }
// Get the expected manifest hash from the signed .sf file // Get the expected manifest hash from the signed .sf file
DigestWithAlgorithm mfDigest; nsAutoCString mfDigest;
rv = ParseSF(BitwiseCast<char*, unsigned char*>(sfBuffer.data), mfDigest); rv = ParseSF(BitwiseCast<char*, unsigned char*>(sfBuffer.data), digestToUse,
mfDigest);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
} }
@ -1421,7 +1571,7 @@ VerifySignedDirectory(AppTrustedRoot aTrustedRoot,
nsAutoString mfFilename(NS_LITERAL_STRING("manifest.mf")); nsAutoString mfFilename(NS_LITERAL_STRING("manifest.mf"));
ScopedAutoSECItem manifestBuffer; ScopedAutoSECItem manifestBuffer;
Digest mfCalculatedDigest; Digest mfCalculatedDigest;
rv = LoadOneMetafile(metaDir, mfFilename, manifestBuffer, mfDigest.mAlgorithm, rv = LoadOneMetafile(metaDir, mfFilename, manifestBuffer, digestToUse,
&mfCalculatedDigest); &mfCalculatedDigest);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
@ -1429,7 +1579,7 @@ VerifySignedDirectory(AppTrustedRoot aTrustedRoot,
nsDependentCSubstring calculatedDigest( nsDependentCSubstring calculatedDigest(
DigestToDependentString(mfCalculatedDigest)); DigestToDependentString(mfCalculatedDigest));
if (!mfDigest.mDigest.Equals(calculatedDigest)) { if (!mfDigest.Equals(calculatedDigest)) {
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID; return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
} }
@ -1442,7 +1592,7 @@ VerifySignedDirectory(AppTrustedRoot aTrustedRoot,
nsTHashtable<nsStringHashKey> items; nsTHashtable<nsStringHashKey> items;
rv = ParseMFUnpacked(BitwiseCast<char*, unsigned char*>(manifestBuffer.data), rv = ParseMFUnpacked(BitwiseCast<char*, unsigned char*>(manifestBuffer.data),
aDirectory, mfDigest.mAlgorithm, items, buf); aDirectory, digestToUse, items, buf);
if (NS_FAILED(rv)){ if (NS_FAILED(rv)){
return rv; return rv;
} }

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

@ -5,22 +5,12 @@
#include "nsDataSignatureVerifier.h" #include "nsDataSignatureVerifier.h"
#include "ScopedNSSTypes.h" #include "ScopedNSSTypes.h"
#include "SharedCertVerifier.h"
#include "cms.h"
#include "cryptohi.h"
#include "keyhi.h"
#include "mozilla/Base64.h" #include "mozilla/Base64.h"
#include "mozilla/Casting.h"
#include "mozilla/Unused.h"
#include "nsCOMPtr.h" #include "nsCOMPtr.h"
#include "nsNSSComponent.h"
#include "nsString.h" #include "nsString.h"
#include "pkix/pkixnss.h"
#include "pkix/pkixtypes.h"
#include "secerr.h" #include "secerr.h"
using namespace mozilla; using namespace mozilla;
using namespace mozilla::pkix;
using namespace mozilla::psm; using namespace mozilla::psm;
SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate) SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate)
@ -134,117 +124,3 @@ nsDataSignatureVerifier::VerifyData(const nsACString& aData,
return NS_OK; return NS_OK;
} }
namespace mozilla {
nsresult
VerifyCMSDetachedSignatureIncludingCertificate(
const SECItem& buffer, const SECItem& detachedDigest,
nsresult (*verifyCertificate)(CERTCertificate* cert, void* context,
void* pinArg),
void* verifyCertificateContext, void* pinArg,
const nsNSSShutDownPreventionLock& /*proofOfLock*/)
{
// XXX: missing pinArg is tolerated.
if (NS_WARN_IF(!buffer.data && buffer.len > 0) ||
NS_WARN_IF(!detachedDigest.data && detachedDigest.len > 0) ||
(!verifyCertificate) ||
NS_WARN_IF(!verifyCertificateContext)) {
return NS_ERROR_INVALID_ARG;
}
UniqueNSSCMSMessage
cmsMsg(NSS_CMSMessage_CreateFromDER(const_cast<SECItem*>(&buffer), nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr));
if (!cmsMsg) {
return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
}
if (!NSS_CMSMessage_IsSigned(cmsMsg.get())) {
return NS_ERROR_CMS_VERIFY_NOT_SIGNED;
}
NSSCMSContentInfo* cinfo = NSS_CMSMessage_ContentLevel(cmsMsg.get(), 0);
if (!cinfo) {
return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO;
}
// We're expecting this to be a PKCS#7 signedData content info.
if (NSS_CMSContentInfo_GetContentTypeTag(cinfo)
!= SEC_OID_PKCS7_SIGNED_DATA) {
return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO;
}
// signedData is non-owning
NSSCMSSignedData* signedData =
static_cast<NSSCMSSignedData*>(NSS_CMSContentInfo_GetContent(cinfo));
if (!signedData) {
return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO;
}
// Set digest value.
if (NSS_CMSSignedData_SetDigestValue(signedData, SEC_OID_SHA1,
const_cast<SECItem*>(&detachedDigest))) {
return NS_ERROR_CMS_VERIFY_BAD_DIGEST;
}
// Parse the certificates into CERTCertificate objects held in memory so
// verifyCertificate will be able to find them during path building.
UniqueCERTCertList certs(CERT_NewCertList());
if (!certs) {
return NS_ERROR_OUT_OF_MEMORY;
}
if (signedData->rawCerts) {
for (size_t i = 0; signedData->rawCerts[i]; ++i) {
UniqueCERTCertificate
cert(CERT_NewTempCertificate(CERT_GetDefaultCertDB(),
signedData->rawCerts[i], nullptr, false,
true));
// Skip certificates that fail to parse
if (!cert) {
continue;
}
if (CERT_AddCertToListTail(certs.get(), cert.get()) != SECSuccess) {
return NS_ERROR_OUT_OF_MEMORY;
}
Unused << cert.release(); // Ownership transferred to the cert list.
}
}
// Get the end-entity certificate.
int numSigners = NSS_CMSSignedData_SignerInfoCount(signedData);
if (NS_WARN_IF(numSigners != 1)) {
return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
}
// signer is non-owning.
NSSCMSSignerInfo* signer = NSS_CMSSignedData_GetSignerInfo(signedData, 0);
if (NS_WARN_IF(!signer)) {
return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
}
CERTCertificate* signerCert =
NSS_CMSSignerInfo_GetSigningCertificate(signer, CERT_GetDefaultCertDB());
if (!signerCert) {
return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
}
nsresult rv = verifyCertificate(signerCert, verifyCertificateContext, pinArg);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// See NSS_CMSContentInfo_GetContentTypeOID, which isn't exported from NSS.
SECOidData* contentTypeOidData =
SECOID_FindOID(&signedData->contentInfo.contentType);
if (!contentTypeOidData) {
return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
}
return MapSECStatus(NSS_CMSSignerInfo_Verify(signer,
const_cast<SECItem*>(&detachedDigest),
&contentTypeOidData->oid));
}
} // namespace mozilla

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

@ -5,7 +5,6 @@
#ifndef nsDataSignatureVerifier_h #ifndef nsDataSignatureVerifier_h
#define nsDataSignatureVerifier_h #define nsDataSignatureVerifier_h
#include "certt.h"
#include "nsIDataSignatureVerifier.h" #include "nsIDataSignatureVerifier.h"
#include "nsNSSShutDown.h" #include "nsNSSShutDown.h"
@ -33,15 +32,4 @@ private:
virtual void virtualDestroyNSSReference() override {} virtual void virtualDestroyNSSReference() override {}
}; };
namespace mozilla {
nsresult VerifyCMSDetachedSignatureIncludingCertificate(
const SECItem& buffer, const SECItem& detachedDigest,
nsresult (*verifyCertificate)(CERTCertificate* cert, void* context,
void* pinArg),
void* verifyCertificateContext, void* pinArg,
const nsNSSShutDownPreventionLock& proofOfLock);
} // namespace mozilla
#endif // nsDataSignatureVerifier_h #endif // nsDataSignatureVerifier_h

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

@ -35,6 +35,7 @@
#include "sslproto.h" #include "sslproto.h"
using namespace mozilla; using namespace mozilla;
using namespace mozilla::pkix;
using namespace mozilla::psm; using namespace mozilla::psm;
extern LazyLogModule gPIPNSSLog; extern LazyLogModule gPIPNSSLog;

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

@ -50,6 +50,7 @@ const SEC_ERROR_UNTRUSTED_CERT = SEC_ERROR_BASE + 21;
const SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE = SEC_ERROR_BASE + 30; const SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE = SEC_ERROR_BASE + 30;
const SEC_ERROR_CA_CERT_INVALID = SEC_ERROR_BASE + 36; const SEC_ERROR_CA_CERT_INVALID = SEC_ERROR_BASE + 36;
const SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION = SEC_ERROR_BASE + 41; const SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION = SEC_ERROR_BASE + 41;
const SEC_ERROR_PKCS7_BAD_SIGNATURE = SEC_ERROR_BASE + 47;
const SEC_ERROR_INADEQUATE_KEY_USAGE = SEC_ERROR_BASE + 90; const SEC_ERROR_INADEQUATE_KEY_USAGE = SEC_ERROR_BASE + 90;
const SEC_ERROR_INADEQUATE_CERT_TYPE = SEC_ERROR_BASE + 91; const SEC_ERROR_INADEQUATE_CERT_TYPE = SEC_ERROR_BASE + 91;
const SEC_ERROR_CERT_NOT_IN_NAME_SPACE = SEC_ERROR_BASE + 112; const SEC_ERROR_CERT_NOT_IN_NAME_SPACE = SEC_ERROR_BASE + 112;

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

@ -10,14 +10,20 @@ the desired properties.
The specification format is as follows: The specification format is as follows:
hash:<hex string> sha1:<hex string>
sha256:<hex string>
signer: signer:
<pycert specification> <pycert specification>
hash is the value that will be put in the messageDigest attribute in Eith or both of sha1 and sha256 may be specified. The value of
each SignerInfo of the signerInfos field of the SignedData. each hash directive is what will be put in the messageDigest
attribute of the SignerInfo that corresponds to the signature
algorithm defined by the hash algorithm and key type of the
default key. Together, these comprise the signerInfos field of
the SignedData. If neither hash is specified, the signerInfos
will be an empty SET (i.e. there will be no actual signature
information).
The certificate specification must come last. The certificate specification must come last.
Currently only SHA-1 is supported.
""" """
from pyasn1.codec.der import decoder from pyasn1.codec.der import decoder
@ -52,7 +58,8 @@ class CMS(object):
generating a CMS message""" generating a CMS message"""
def __init__(self, paramStream): def __init__(self, paramStream):
self.hash = '' self.sha1 = ''
self.sha256 = ''
signerSpecification = StringIO.StringIO() signerSpecification = StringIO.StringIO()
readingSignerSpecification = False readingSignerSpecification = False
for line in paramStream.readlines(): for line in paramStream.readlines():
@ -60,8 +67,10 @@ class CMS(object):
print >>signerSpecification, line.strip() print >>signerSpecification, line.strip()
elif line.strip() == 'signer:': elif line.strip() == 'signer:':
readingSignerSpecification = True readingSignerSpecification = True
elif line.startswith('hash:'): elif line.startswith('sha1:'):
self.hash = line.strip()[len('hash:'):] self.sha1 = line.strip()[len('sha1:'):]
elif line.startswith('sha256:'):
self.sha256 = line.strip()[len('sha256:'):]
else: else:
raise UnknownDirectiveError(line.strip()) raise UnknownDirectiveError(line.strip())
signerSpecification.seek(0) signerSpecification.seek(0)
@ -97,11 +106,15 @@ class CMS(object):
"""Given a pykey hash algorithm identifier, builds an """Given a pykey hash algorithm identifier, builds an
AlgorithmIdentifier for use with pyasn1.""" AlgorithmIdentifier for use with pyasn1."""
if pykeyHash == pykey.HASH_SHA1: if pykeyHash == pykey.HASH_SHA1:
algorithmIdentifier = rfc2459.AlgorithmIdentifier() oidString = '1.3.14.3.2.26'
algorithmIdentifier['algorithm'] = univ.ObjectIdentifier('1.3.14.3.2.26') elif pykeyHash == pykey.HASH_SHA256:
algorithmIdentifier['parameters'] = univ.Null() oidString = '2.16.840.1.101.3.4.2.1'
return algorithmIdentifier else:
raise pykey.UnknownHashAlgorithmError(pykeyHash) raise pykey.UnknownHashAlgorithmError(pykeyHash)
algorithmIdentifier = rfc2459.AlgorithmIdentifier()
algorithmIdentifier['algorithm'] = univ.ObjectIdentifier(oidString)
algorithmIdentifier['parameters'] = univ.Null()
return algorithmIdentifier
def buildSignerInfo(self, certificate, pykeyHash, digestValue): def buildSignerInfo(self, certificate, pykeyHash, digestValue):
"""Given a pyasn1 certificate, a pykey hash identifier """Given a pyasn1 certificate, a pykey hash identifier
@ -157,7 +170,12 @@ class CMS(object):
signerInfos = rfc2315.SignerInfos() signerInfos = rfc2315.SignerInfos()
signerInfos[0] = self.buildSignerInfo(certificate, pykey.HASH_SHA1, self.hash) if len(self.sha1) > 0:
signerInfos[len(signerInfos)] = self.buildSignerInfo(certificate,
pykey.HASH_SHA1, self.sha1)
if len(self.sha256) > 0:
signerInfos[len(signerInfos)] = self.buildSignerInfo(certificate,
pykey.HASH_SHA256, self.sha256)
signedData['signerInfos'] = signerInfos signedData['signerInfos'] = signerInfos
encoded = encoder.encode(signedData) encoded = encoder.encode(signedData)

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

@ -33,14 +33,15 @@ def walkDirectory(directory):
return paths return paths
def signZip(appDirectory, outputFile, issuerName, manifestHashes, def signZip(appDirectory, outputFile, issuerName, manifestHashes,
signatureHashes, doSign): signatureHashes, pkcs7Hashes, doSign):
"""Given a directory containing the files to package up, """Given a directory containing the files to package up,
an output filename to write to, the name of the issuer of an output filename to write to, the name of the issuer of
the signing certificate, a list of hash algorithms to use in the signing certificate, a list of hash algorithms to use in
the manifest file, a similar list for the signature file, the manifest file, a similar list for the signature file,
and whether or not to actually sign the resulting package, a similar list for the pkcs#7 signature, and whether or not
packages up the files in the directory and creates the to actually sign the resulting package, packages up the
output as appropriate.""" files in the directory and creates the output as
appropriate."""
mfEntries = [] mfEntries = []
with zipfile.ZipFile(outputFile, 'w') as outZip: with zipfile.ZipFile(outputFile, 'w') as outZip:
@ -66,7 +67,12 @@ def signZip(appDirectory, outputFile, issuerName, manifestHashes,
base64hash = b64encode(hashFunc(mfContents).digest()) base64hash = b64encode(hashFunc(mfContents).digest())
sfContents += '%s-Digest-Manifest: %s\n' % (name, base64hash) sfContents += '%s-Digest-Manifest: %s\n' % (name, base64hash)
cmsSpecification = 'hash:%s\nsigner:\n' % sha1(sfContents).hexdigest() + \ cmsSpecification = ''
for name in pkcs7Hashes:
hashFunc, _ = hashNameToFunctionAndIdentifier(name)
cmsSpecification += '%s:%s\n' % (name,
hashFunc(sfContents).hexdigest())
cmsSpecification += 'signer:\n' + \
'issuer:%s\n' % issuerName + \ 'issuer:%s\n' % issuerName + \
'subject:xpcshell signed app test signer\n' + \ 'subject:xpcshell signed app test signer\n' + \
'extension:keyUsage:digitalSignature' 'extension:keyUsage:digitalSignature'
@ -118,12 +124,20 @@ def main(outputFile, appPath, *args):
parser.add_argument('-s', '--signature-hash', action='append', parser.add_argument('-s', '--signature-hash', action='append',
help='Hash algorithms to use in signature file', help='Hash algorithms to use in signature file',
default=[]) default=[])
group = parser.add_mutually_exclusive_group()
group.add_argument('-p', '--pkcs7-hash', action='append',
help='Hash algorithms to use in PKCS#7 signature',
default=[])
group.add_argument('-e', '--empty-signerInfos', action='store_true',
help='Emit pkcs#7 SignedData with empty signerInfos')
parsed = parser.parse_args(args) parsed = parser.parse_args(args)
if len(parsed.manifest_hash) == 0: if len(parsed.manifest_hash) == 0:
parsed.manifest_hash.append('sha256') parsed.manifest_hash.append('sha256')
if len(parsed.signature_hash) == 0: if len(parsed.signature_hash) == 0:
parsed.signature_hash.append('sha256') parsed.signature_hash.append('sha256')
if len(parsed.pkcs7_hash) == 0 and not parsed.empty_signerInfos:
parsed.pkcs7_hash.append('sha256')
signZip(appPath, outputFile, parsed.issuer, signZip(appPath, outputFile, parsed.issuer,
map(hashNameToFunctionAndIdentifier, parsed.manifest_hash), map(hashNameToFunctionAndIdentifier, parsed.manifest_hash),
map(hashNameToFunctionAndIdentifier, parsed.signature_hash), map(hashNameToFunctionAndIdentifier, parsed.signature_hash),
not parsed.no_sign) parsed.pkcs7_hash, not parsed.no_sign)

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

@ -6,7 +6,7 @@
// This test attempts to ensure that PSM doesn't deadlock or crash when shutting // This test attempts to ensure that PSM doesn't deadlock or crash when shutting
// down NSS while a background thread is attempting to use NSS. // down NSS while a background thread is attempting to use NSS.
// Uses test_signed_apps/signed_app.zip from test_signed_apps.js. // Uses test_signed_apps/app_mf-1_sf-1_p7-1.zip from test_signed_apps.js.
function startAsyncNSSOperation(certdb, appFile) { function startAsyncNSSOperation(certdb, appFile) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -28,7 +28,7 @@ add_task(async function() {
.QueryInterface(Ci.nsIObserver); .QueryInterface(Ci.nsIObserver);
let certdb = Cc["@mozilla.org/security/x509certdb;1"] let certdb = Cc["@mozilla.org/security/x509certdb;1"]
.getService(Ci.nsIX509CertDB); .getService(Ci.nsIX509CertDB);
let appFile = do_get_file("test_signed_apps/signed_app.zip"); let appFile = do_get_file("test_signed_apps/app_mf-1_sf-1_p7-1.zip");
let promises = []; let promises = [];
for (let i = 0; i < 25; i++) { for (let i = 0; i < 25; i++) {

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

@ -1,27 +1,18 @@
"use strict"; "use strict";
/* To regenerate the certificates and apps for this test:
cd security/manager/ssl/tests/unit/test_signed_apps // Tests the API nsIX509CertDB.openSignedAppFileAsync, which backs add-on
PATH=$NSS/bin:$NSS/lib:$PATH ./generate.sh // signature verification. Testcases include various ways of tampering with
cd ../../../../../.. // add-ons as well as different hash algorithms used in the various
make -C $OBJDIR/security/manager/ssl/tests // signature/metadata files.
$NSS is the path to NSS binaries and libraries built for the host platform. // from prio.h
If you get error messages about "CertUtil" on Windows, then it means that
the Windows CertUtil.exe is ahead of the NSS certutil.exe in $PATH.
Check in the generated files. These steps are not done as part of the build
because we do not want to add a build-time dependency on the OpenSSL or NSS
tools or libraries built for the host platform.
*/
// XXX from prio.h
const PR_RDWR = 0x04; const PR_RDWR = 0x04;
const PR_CREATE_FILE = 0x08; const PR_CREATE_FILE = 0x08;
const PR_TRUNCATE = 0x20; const PR_TRUNCATE = 0x20;
do_get_profile(); // must be called before getting nsIX509CertDB do_get_profile(); // must be called before getting nsIX509CertDB
const certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(Ci.nsIX509CertDB); const certdb = Cc["@mozilla.org/security/x509certdb;1"]
.getService(Ci.nsIX509CertDB);
// Creates a new app package based in the inFilePath package, with a set of // Creates a new app package based in the inFilePath package, with a set of
// modifications (including possibly deletions) applied to the existing entries, // modifications (including possibly deletions) applied to the existing entries,
@ -134,71 +125,85 @@ function tampered_app_path(test_name) {
return FileUtils.getFile("TmpD", ["test_signed_app-" + test_name + ".zip"]); return FileUtils.getFile("TmpD", ["test_signed_app-" + test_name + ".zip"]);
} }
add_test(function () { var hashTestcases = [
certdb.openSignedAppFileAsync( // SHA-256 in PKCS#7 + SHA-256 present elsewhere => OK
Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("signed_app"), { name: "app_mf-1-256_sf-1-256_p7-1-256",
check_open_result("valid", Cr.NS_OK)); expectedResult: Cr.NS_OK },
}); { name: "app_mf-1-256_sf-1-256_p7-256",
expectedResult: Cr.NS_OK },
{ name: "app_mf-1-256_sf-256_p7-1-256",
expectedResult: Cr.NS_OK },
{ name: "app_mf-1-256_sf-256_p7-256",
expectedResult: Cr.NS_OK },
{ name: "app_mf-256_sf-1-256_p7-1-256",
expectedResult: Cr.NS_OK },
{ name: "app_mf-256_sf-1-256_p7-256",
expectedResult: Cr.NS_OK },
{ name: "app_mf-256_sf-256_p7-1-256",
expectedResult: Cr.NS_OK },
{ name: "app_mf-256_sf-256_p7-256",
expectedResult: Cr.NS_OK },
add_test(function () { // SHA-1 in PKCS#7 + SHA-1 present elsewhere => OK
certdb.openSignedAppFileAsync( { name: "app_mf-1-256_sf-1-256_p7-1",
Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("signed_app_sha256"), expectedResult: Cr.NS_OK },
check_open_result("valid with sha256 hashes in manifest", Cr.NS_OK)); { name: "app_mf-1-256_sf-1_p7-1",
}); expectedResult: Cr.NS_OK },
{ name: "app_mf-1_sf-1-256_p7-1",
expectedResult: Cr.NS_OK },
{ name: "app_mf-1_sf-1_p7-1",
expectedResult: Cr.NS_OK },
// SHA-256 in PKCS#7 + SHA-256 not present elsewhere => INVALID
{ name: "app_mf-1-256_sf-1_p7-1-256",
expectedResult: Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID },
{ name: "app_mf-1-256_sf-1_p7-256",
expectedResult: Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID },
{ name: "app_mf-1_sf-1-256_p7-1-256",
expectedResult: Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID },
{ name: "app_mf-1_sf-1-256_p7-256",
expectedResult: Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID },
{ name: "app_mf-1_sf-1_p7-1-256",
expectedResult: Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID },
{ name: "app_mf-1_sf-1_p7-256",
expectedResult: Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID },
{ name: "app_mf-1_sf-256_p7-1-256",
expectedResult: Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID },
{ name: "app_mf-1_sf-256_p7-256",
expectedResult: Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID },
{ name: "app_mf-256_sf-1_p7-1-256",
expectedResult: Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID },
{ name: "app_mf-256_sf-1_p7-256",
expectedResult: Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID },
// SHA-1 in PKCS#7 + SHA-1 not present elsewhere => INVALID
{ name: "app_mf-1-256_sf-256_p7-1",
expectedResult: Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID },
{ name: "app_mf-1_sf-256_p7-1",
expectedResult: Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID },
{ name: "app_mf-256_sf-1-256_p7-1",
expectedResult: Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID },
{ name: "app_mf-256_sf-1_p7-1",
expectedResult: Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID },
{ name: "app_mf-256_sf-256_p7-1",
expectedResult: Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID },
];
for (let testcase of hashTestcases) {
add_test(function () {
certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot,
original_app_path(testcase.name),
check_open_result(testcase.name, testcase.expectedResult));
});
}
add_test(function () { add_test(function () {
certdb.openSignedAppFileAsync( certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot, Ci.nsIX509CertDB.AppXPCShellRoot,
original_app_path("signed_app_sha1_and_sha256"), original_app_path("empty_signerInfos"),
check_open_result("valid with sha1 and sha256 hashes in manifest", Cr.NS_OK)); check_open_result("the signerInfos in the PKCS#7 signature is empty",
}); Cr.NS_ERROR_CMS_VERIFY_NOT_SIGNED));
add_test(function () {
certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot,
original_app_path("signed_app_sha256_manifest"),
check_open_result("sha256 hashes in manifest, but only a sha1 hash in the signature file",
Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID));
});
add_test(function () {
certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot,
original_app_path("signed_app_sha256_signature_file"),
check_open_result("sha256 hash in the signature file, but only sha1 hashes in the manifest",
Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID));
});
add_test(function () {
certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot,
original_app_path("sha1_and_sha256_manifest_sha1_signature_file"),
check_open_result("sha1 and sha256 hashes in the manifest, but only sha1 hash in the signature file",
Cr.NS_OK));
});
add_test(function () {
certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot,
original_app_path("sha1_and_sha256_manifest_sha256_signature_file"),
check_open_result("sha1 and sha256 hashes in the manifest, but only sha256 hash in the signature file",
Cr.NS_OK));
});
add_test(function () {
certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot,
original_app_path("sha1_manifest_sha1_and_sha256_signature_file"),
check_open_result("only sha1 in the manifest, sha1 and sha256 hashes in the signature file",
Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID));
});
add_test(function () {
certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot,
original_app_path("sha256_manifest_sha1_and_sha256_signature_file"),
check_open_result("only sha256 in the manifest, sha1 and sha256 hashes in the signature file",
Cr.NS_OK));
}); });
add_test(function () { add_test(function () {
@ -217,15 +222,16 @@ add_test(function () {
// Sanity check to ensure a no-op tampering gives a valid result // Sanity check to ensure a no-op tampering gives a valid result
add_test(function () { add_test(function () {
let tampered = tampered_app_path("identity_tampering"); let tampered = tampered_app_path("identity_tampering");
tamper(original_app_path("signed_app"), tampered, { }, []); tamper(original_app_path("app_mf-1_sf-1_p7-1"), tampered, { }, []);
certdb.openSignedAppFileAsync( certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("signed_app"), Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("app_mf-1_sf-1_p7-1"),
check_open_result("identity_tampering", Cr.NS_OK)); check_open_result("identity_tampering", Cr.NS_OK));
}); });
add_test(function () { add_test(function () {
let tampered = tampered_app_path("missing_rsa"); let tampered = tampered_app_path("missing_rsa");
tamper(original_app_path("signed_app"), tampered, { "META-INF/A.RSA": removeEntry }, []); tamper(original_app_path("app_mf-1_sf-1_p7-1"), tampered,
{ "META-INF/A.RSA": removeEntry }, []);
certdb.openSignedAppFileAsync( certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot, tampered, Ci.nsIX509CertDB.AppXPCShellRoot, tampered,
check_open_result("missing_rsa", Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED)); check_open_result("missing_rsa", Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED));
@ -233,7 +239,8 @@ add_test(function () {
add_test(function () { add_test(function () {
let tampered = tampered_app_path("missing_sf"); let tampered = tampered_app_path("missing_sf");
tamper(original_app_path("signed_app"), tampered, { "META-INF/A.SF": removeEntry }, []); tamper(original_app_path("app_mf-1_sf-1_p7-1"), tampered,
{ "META-INF/A.SF": removeEntry }, []);
certdb.openSignedAppFileAsync( certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot, tampered, Ci.nsIX509CertDB.AppXPCShellRoot, tampered,
check_open_result("missing_sf", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID)); check_open_result("missing_sf", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID));
@ -241,7 +248,8 @@ add_test(function () {
add_test(function () { add_test(function () {
let tampered = tampered_app_path("missing_manifest_mf"); let tampered = tampered_app_path("missing_manifest_mf");
tamper(original_app_path("signed_app"), tampered, { "META-INF/MANIFEST.MF": removeEntry }, []); tamper(original_app_path("app_mf-1_sf-1_p7-1"), tampered,
{ "META-INF/MANIFEST.MF": removeEntry }, []);
certdb.openSignedAppFileAsync( certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot, tampered, Ci.nsIX509CertDB.AppXPCShellRoot, tampered,
check_open_result("missing_manifest_mf", check_open_result("missing_manifest_mf",
@ -250,7 +258,8 @@ add_test(function () {
add_test(function () { add_test(function () {
let tampered = tampered_app_path("missing_entry"); let tampered = tampered_app_path("missing_entry");
tamper(original_app_path("signed_app"), tampered, { "manifest.json": removeEntry }, []); tamper(original_app_path("app_mf-1_sf-1_p7-1"), tampered,
{ "manifest.json": removeEntry }, []);
certdb.openSignedAppFileAsync( certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot, tampered, Ci.nsIX509CertDB.AppXPCShellRoot, tampered,
check_open_result("missing_entry", Cr.NS_ERROR_SIGNED_JAR_ENTRY_MISSING)); check_open_result("missing_entry", Cr.NS_ERROR_SIGNED_JAR_ENTRY_MISSING));
@ -258,15 +267,47 @@ add_test(function () {
add_test(function () { add_test(function () {
let tampered = tampered_app_path("truncated_entry"); let tampered = tampered_app_path("truncated_entry");
tamper(original_app_path("signed_app"), tampered, { "manifest.json": truncateEntry }, []); tamper(original_app_path("app_mf-1_sf-1_p7-1"), tampered,
{ "manifest.json": truncateEntry }, []);
certdb.openSignedAppFileAsync( certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot, tampered, Ci.nsIX509CertDB.AppXPCShellRoot, tampered,
check_open_result("truncated_entry", Cr.NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY)); check_open_result("truncated_entry",
Cr.NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY));
});
add_test(function () {
let tampered = tampered_app_path("truncated_manifestFile");
tamper(original_app_path("app_mf-1_sf-1_p7-1"), tampered,
{ "META-INF/MANIFEST.MF": truncateEntry }, []);
certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot, tampered,
check_open_result("truncated_manifestFile",
Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID));
});
add_test(function () {
let tampered = tampered_app_path("truncated_signatureFile");
tamper(original_app_path("app_mf-1_sf-1_p7-1"), tampered,
{ "META-INF/A.SF": truncateEntry }, []);
certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot, tampered,
check_open_result("truncated_signatureFile",
getXPCOMStatusFromNSS(SEC_ERROR_PKCS7_BAD_SIGNATURE)));
});
add_test(function () {
let tampered = tampered_app_path("truncated_pkcs7File");
tamper(original_app_path("app_mf-1_sf-1_p7-1"), tampered,
{ "META-INF/A.RSA": truncateEntry }, []);
certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot, tampered,
check_open_result("truncated_pkcs7File",
Cr.NS_ERROR_CMS_VERIFY_NOT_SIGNED));
}); });
add_test(function () { add_test(function () {
let tampered = tampered_app_path("unsigned_entry"); let tampered = tampered_app_path("unsigned_entry");
tamper(original_app_path("signed_app"), tampered, {}, tamper(original_app_path("app_mf-1_sf-1_p7-1"), tampered, {},
[ { "name": "unsigned.txt", "content": "unsigned content!" } ]); [ { "name": "unsigned.txt", "content": "unsigned content!" } ]);
certdb.openSignedAppFileAsync( certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot, tampered, Ci.nsIX509CertDB.AppXPCShellRoot, tampered,
@ -275,7 +316,7 @@ add_test(function () {
add_test(function () { add_test(function () {
let tampered = tampered_app_path("unsigned_metainf_entry"); let tampered = tampered_app_path("unsigned_metainf_entry");
tamper(original_app_path("signed_app"), tampered, {}, tamper(original_app_path("app_mf-1_sf-1_p7-1"), tampered, {},
[ { name: "META-INF/unsigned.txt", content: "unsigned content!" } ]); [ { name: "META-INF/unsigned.txt", content: "unsigned content!" } ]);
certdb.openSignedAppFileAsync( certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot, tampered, Ci.nsIX509CertDB.AppXPCShellRoot, tampered,

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

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