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();
let tab = item.tabManager.convert(contextData.tab);
let tab = contextData.tab && item.tabManager.convert(contextData.tab);
let info = item.getClickInfo(contextData, wasChecked);
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 => {
let listener = (event, info, tab) => {
context.withPendingBrowser(tab.linkedBrowser,
let {linkedBrowser} = tab || tabTracker.activeTab;
context.withPendingBrowser(linkedBrowser,
() => fire.sync(info, tab));
};

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

@ -36,6 +36,9 @@ let extData = {
id: "clickme-page",
title: "Click me!",
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 item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!");
is(item.length, 1, "contextMenu item for page was found");
item[0].click();
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();
});

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

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

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

@ -39,7 +39,7 @@ const TEST_ADDRESS_4 = {
organization: "World Wide Web Consortium",
};
const TEST_ADDRESS_FOR_UPDATE = {
const TEST_ADDRESS_WITH_EMPTY_FIELD = {
"name": "Tim Berners",
"street-address": "",
};
@ -299,6 +299,12 @@ add_task(async function test_add() {
do_check_eq(addresses[0].timeLastUsed, 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),
/"invalidField" is not a valid field\./);
});
@ -333,7 +339,7 @@ add_task(async function test_update() {
do_check_eq(getSyncChangeCounter(profileStorage.addresses, guid), 1);
// 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 profileStorage._saveImmediately();
@ -348,6 +354,12 @@ add_task(async function test_update() {
do_check_neq(address.timeLastModified, timeLastModified);
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(
() => profileStorage.addresses.update("INVALID_GUID", TEST_ADDRESS_3),
/No matching record\./

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

@ -28,6 +28,12 @@ const TEST_CREDIT_CARD_3 = {
"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 = {
"cc-number": "1234123412341234",
"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].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),
/"invalidField" is not a valid field\./);
});
@ -202,6 +214,12 @@ add_task(async function test_update() {
do_check_neq(creditCard.timeLastModified, timeLastModified);
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(
() => profileStorage.creditCards.update("INVALID_GUID", TEST_CREDIT_CARD_3),
/No matching record\./

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -5,20 +5,20 @@
/* eslint-env browser */
"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 Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties");
module.exports = createClass({
displayName: "AddonsInstallError",
propTypes: {
error: PropTypes.string,
retryInstall: PropTypes.func,
},
class AddonsInstallError extends Component {
static get propTypes() {
return {
error: PropTypes.string,
retryInstall: PropTypes.func,
};
}
render() {
if (!this.props.error) {
@ -36,4 +36,6 @@ module.exports = createClass({
{ className: "addons-install-retry", onClick: this.props.retryInstall },
Strings.GetStringFromName("retryTemporaryInstall")));
}
});
}
module.exports = AddonsInstallError;

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

@ -6,7 +6,7 @@
const { AddonManager } = require("resource://gre/modules/AddonManager.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");
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" +
"/WebExtensions/Getting_started_with_web-ext";
module.exports = createClass({
displayName: "AddonsPanel",
propTypes: {
client: PropTypes.instanceOf(DebuggerClient).isRequired,
id: PropTypes.string.isRequired
},
getInitialState() {
class AddonsPanel extends Component {
static get propTypes() {
return {
client: PropTypes.instanceOf(DebuggerClient).isRequired,
id: PropTypes.string.isRequired
};
}
constructor(props) {
super(props);
this.state = {
extensions: [],
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() {
AddonManager.addAddonListener(this);
@ -55,7 +64,7 @@ module.exports = createClass({
this.updateDebugStatus();
this.updateAddonsList();
},
}
componentWillUnmount() {
AddonManager.removeAddonListener(this);
@ -65,7 +74,7 @@ module.exports = createClass({
this.updateDebugStatus);
Services.prefs.removeObserver(REMOTE_ENABLED_PREF,
this.updateDebugStatus);
},
}
updateDebugStatus() {
let debugDisabled =
@ -73,7 +82,7 @@ module.exports = createClass({
!Services.prefs.getBoolPref(REMOTE_ENABLED_PREF);
this.setState({ debugDisabled });
},
}
updateAddonsList() {
this.props.client.listAddons()
@ -95,35 +104,35 @@ module.exports = createClass({
}, error => {
throw new Error("Client error while listing addons: " + error);
});
},
}
/**
* Mandatory callback as AddonManager listener.
*/
onInstalled() {
this.updateAddonsList();
},
}
/**
* Mandatory callback as AddonManager listener.
*/
onUninstalled() {
this.updateAddonsList();
},
}
/**
* Mandatory callback as AddonManager listener.
*/
onEnabled() {
this.updateAddonsList();
},
}
/**
* Mandatory callback as AddonManager listener.
*/
onDisabled() {
this.updateAddonsList();
},
}
render() {
let { client, id } = this.props;
@ -177,4 +186,6 @@ module.exports = createClass({
})
));
}
});
}
module.exports = AddonsPanel;

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

@ -6,7 +6,7 @@
"use strict";
const { createClass, DOM: dom, PropTypes } =
const { Component, DOM: dom, PropTypes } =
require("devtools/client/shared/vendor/react");
const { debugAddon, isTemporaryID, parseFileUri, uninstallAddon } =
require("../../modules/addon");
@ -122,32 +122,39 @@ function warningMessages(warnings = []) {
});
}
module.exports = createClass({
displayName: "AddonTarget",
class AddonTarget extends Component {
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: {
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
},
constructor(props) {
super(props);
this.debug = this.debug.bind(this);
this.uninstall = this.uninstall.bind(this);
this.reload = this.reload.bind(this);
}
debug() {
let { target } = this.props;
debugAddon(target.addonID);
},
}
uninstall() {
let { target } = this.props;
uninstallAddon(target.addonID);
},
}
reload() {
let { client, target } = this.props;
@ -160,7 +167,7 @@ module.exports = createClass({
throw new Error(
"Error reloading addon " + target.addonID + ": " + error);
});
},
}
render() {
let { target, debugDisabled } = this.props;
@ -205,4 +212,6 @@ module.exports = createClass({
),
);
}
});
}
module.exports = AddonTarget;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -4,24 +4,24 @@
"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 CensusTreeItem = createFactory(require("./CensusTreeItem"));
const { TREE_ROW_HEIGHT } = require("../constants");
const { censusModel, diffingModel } = require("../models");
module.exports = createClass({
displayName: "Census",
propTypes: {
census: censusModel,
onExpand: PropTypes.func.isRequired,
onCollapse: PropTypes.func.isRequired,
onFocus: PropTypes.func.isRequired,
onViewSourceInDebugger: PropTypes.func.isRequired,
onViewIndividuals: PropTypes.func.isRequired,
diffing: diffingModel,
},
class Census extends Component {
static get propTypes() {
return {
census: censusModel,
onExpand: PropTypes.func.isRequired,
onCollapse: PropTypes.func.isRequired,
onFocus: PropTypes.func.isRequired,
onViewSourceInDebugger: PropTypes.func.isRequired,
onViewIndividuals: PropTypes.func.isRequired,
diffing: diffingModel,
};
}
render() {
let {
@ -77,4 +77,6 @@ module.exports = createClass({
itemHeight: TREE_ROW_HEIGHT,
});
}
});
}
module.exports = Census;

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

@ -4,16 +4,16 @@
"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 models = require("../models");
module.exports = createClass({
displayName: "CensusHeader",
propTypes: {
diffing: models.diffingModel,
},
class CensusHeader extends Component {
static get propTypes() {
return {
diffing: models.diffingModel,
};
}
render() {
let individualsCell;
@ -71,4 +71,6 @@ module.exports = createClass({
)
);
}
});
}
module.exports = CensusHeader;

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

@ -6,7 +6,7 @@
const { isSavedFrame } = require("devtools/shared/DevToolsUtils");
const {
DOM: dom,
createClass,
Component,
createFactory,
PropTypes
} = 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 models = require("../models");
module.exports = createClass({
displayName: "CensusTreeItem",
class CensusTreeItem extends Component {
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: {
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,
},
constructor(props) {
super(props);
this.toLabel = this.toLabel.bind(this);
}
shouldComponentUpdate(nextProps, nextState) {
return this.props.item != nextProps.item
@ -38,7 +43,7 @@ module.exports = createClass({
|| this.props.expanded != nextProps.expanded
|| this.props.focused != nextProps.focused
|| this.props.diffing != nextProps.diffing;
},
}
toLabel(name, linkToDebugger) {
if (isSavedFrame(name)) {
@ -63,7 +68,7 @@ module.exports = createClass({
}
return String(name);
},
}
render() {
let {
@ -150,5 +155,7 @@ module.exports = createClass({
this.toLabel(item.name, onViewSourceInDebugger)
)
);
},
});
}
}
module.exports = CensusTreeItem;

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

@ -4,7 +4,7 @@
"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 { createParentMap } = require("devtools/shared/heapsnapshot/CensusUtils");
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
* being incrementally loaded and fetched from the `HeapAnalysesWorker`.
*/
const DominatorTreeSubtreeFetching = createFactory(createClass({
displayName: "DominatorTreeSubtreeFetching",
propTypes: {
depth: PropTypes.number.isRequired,
focused: PropTypes.bool.isRequired,
},
class DominatorTreeSubtreeFetchingClass extends Component {
static get propTypes() {
return {
depth: PropTypes.number.isRequired,
focused: PropTypes.bool.isRequired,
};
}
shouldComponentUpdate(nextProps, nextState) {
return this.props.depth !== nextProps.depth
|| this.props.focused !== nextProps.focused;
},
}
render() {
let {
@ -51,26 +51,26 @@ const DominatorTreeSubtreeFetching = createFactory(createClass({
})
);
}
}));
}
/**
* A link to fetch and load more siblings in the dominator tree, when there are
* already many loaded above.
*/
const DominatorTreeSiblingLink = createFactory(createClass({
displayName: "DominatorTreeSiblingLink",
propTypes: {
depth: PropTypes.number.isRequired,
focused: PropTypes.bool.isRequired,
item: PropTypes.instanceOf(DominatorTreeLazyChildren).isRequired,
onLoadMoreSiblings: PropTypes.func.isRequired,
},
class DominatorTreeSiblingLinkClass extends Component {
static get propTypes() {
return {
depth: PropTypes.number.isRequired,
focused: PropTypes.bool.isRequired,
item: PropTypes.instanceOf(DominatorTreeLazyChildren).isRequired,
onLoadMoreSiblings: PropTypes.func.isRequired,
};
}
shouldComponentUpdate(nextProps, nextState) {
return this.props.depth !== nextProps.depth
|| this.props.focused !== nextProps.focused;
},
}
render() {
let {
@ -100,22 +100,19 @@ const DominatorTreeSiblingLink = createFactory(createClass({
)
);
}
}));
}
/**
* The actual dominator tree rendered as an expandable and collapsible tree.
*/
module.exports = createClass({
displayName: "DominatorTree",
propTypes: {
dominatorTree: dominatorTreeModel.isRequired,
onLoadMoreSiblings: PropTypes.func.isRequired,
onViewSourceInDebugger: PropTypes.func.isRequired,
onExpand: PropTypes.func.isRequired,
onCollapse: PropTypes.func.isRequired,
onFocus: PropTypes.func.isRequired,
},
class DominatorTree extends Component {
static get propTypes() {
return {
dominatorTree: dominatorTreeModel.isRequired,
onLoadMoreSiblings: PropTypes.func.isRequired,
onViewSourceInDebugger: PropTypes.func.isRequired,
onExpand: PropTypes.func.isRequired,
onCollapse: PropTypes.func.isRequired,
onFocus: PropTypes.func.isRequired,
};
}
shouldComponentUpdate(nextProps, nextState) {
// 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
// can continue using referential equality here.
return this.props.dominatorTree !== nextProps.dominatorTree;
},
}
render() {
const { dominatorTree, onViewSourceInDebugger, onLoadMoreSiblings } = this.props;
@ -216,4 +213,9 @@ module.exports = createClass({
itemHeight: TREE_ROW_HEIGHT,
});
}
});
}
const DominatorTreeSubtreeFetching = createFactory(DominatorTreeSubtreeFetchingClass);
const DominatorTreeSiblingLink = createFactory(DominatorTreeSiblingLinkClass);
module.exports = DominatorTree;

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

@ -4,13 +4,13 @@
"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");
module.exports = createClass({
displayName: "DominatorTreeHeader",
propTypes: { },
class DominatorTreeHeader extends Component {
static get propTypes() {
return { };
}
render() {
return dom.div(
@ -43,4 +43,6 @@ module.exports = createClass({
)
);
}
});
}
module.exports = DominatorTreeHeader;

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

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

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

@ -4,7 +4,7 @@
"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 Census = createFactory(require("./Census"));
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
* tree, the dominator tree, etc.
*/
module.exports = createClass({
displayName: "Heap",
class Heap extends Component {
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: {
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,
},
constructor(props) {
super(props);
this._renderHeapView = this._renderHeapView.bind(this);
this._renderInitial = this._renderInitial.bind(this);
this._renderStatus = this._renderStatus.bind(this);
this._renderError = this._renderError.bind(this);
this._renderCensus = this._renderCensus.bind(this);
this._renderTreeMap = this._renderTreeMap.bind(this);
this._renderIndividuals = this._renderIndividuals.bind(this);
this._renderDominatorTree = this._renderDominatorTree.bind(this);
}
/**
* Render the heap view's container panel with the given contents inside of
@ -225,7 +237,7 @@ module.exports = createClass({
...contents
)
);
},
}
_renderInitial(onSnapshotClick) {
return this._renderHeapView("initial", dom.button(
@ -236,7 +248,7 @@ module.exports = createClass({
},
L10N.getStr("take-snapshot")
));
},
}
_renderStatus(state, statusText, diffing) {
let throbber = "";
@ -250,7 +262,7 @@ module.exports = createClass({
},
statusText
));
},
}
_renderError(state, statusText, error) {
return this._renderHeapView(
@ -258,7 +270,7 @@ module.exports = createClass({
dom.span({ className: "snapshot-status error" }, statusText),
dom.pre({}, safeErrorString(error))
);
},
}
_renderCensus(state, census, diffing, onViewSourceInDebugger, onViewIndividuals) {
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);
},
}
_renderTreeMap(state, treeMap) {
return this._renderHeapView(
state,
TreeMap({ treeMap })
);
},
}
_renderIndividuals(state, individuals, dominatorTree, onViewSourceInDebugger) {
assert(individuals.state === individualsState.FETCHED,
@ -355,7 +367,7 @@ module.exports = createClass({
onResize: this.props.onShortestPathsResize,
})
);
},
}
_renderDominatorTree(state, onViewSourceInDebugger, dominatorTree, onLoadMoreSiblings) {
const tree = dom.div(
@ -391,7 +403,7 @@ module.exports = createClass({
onResize: this.props.onShortestPathsResize,
})
);
},
}
render() {
let {
@ -454,5 +466,7 @@ module.exports = createClass({
return this._renderDominatorTree(state, onViewSourceInDebugger,
snapshot.dominatorTree,
onLoadMoreSiblings);
},
});
}
}
module.exports = Heap;

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

@ -4,7 +4,7 @@
"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 DominatorTreeItem = createFactory(require("./DominatorTreeItem"));
const { TREE_ROW_HEIGHT } = require("../constants");
@ -13,15 +13,15 @@ const models = require("../models");
/**
* The list of individuals in a census group.
*/
module.exports = createClass({
displayName: "Individuals",
propTypes: {
onViewSourceInDebugger: PropTypes.func.isRequired,
onFocus: PropTypes.func.isRequired,
individuals: models.individuals,
dominatorTree: models.dominatorTreeModel,
},
class Individuals extends Component {
static get propTypes() {
return {
onViewSourceInDebugger: PropTypes.func.isRequired,
onFocus: PropTypes.func.isRequired,
individuals: models.individuals,
dominatorTree: models.dominatorTreeModel,
};
}
render() {
const {
@ -57,4 +57,6 @@ module.exports = createClass({
itemHeight: TREE_ROW_HEIGHT,
});
}
});
}
module.exports = Individuals;

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

@ -4,13 +4,13 @@
"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");
module.exports = createClass({
displayName: "IndividualsHeader",
propTypes: { },
class IndividualsHeader extends Component {
static get propTypes() {
return { };
}
render() {
return dom.div(
@ -43,4 +43,6 @@ module.exports = createClass({
)
);
}
});
}
module.exports = IndividualsHeader;

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

@ -4,21 +4,21 @@
"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
* the children nodes as `itemComponent`, and a list of items to render
* as that component with a click handler.
*/
module.exports = createClass({
displayName: "List",
propTypes: {
itemComponent: PropTypes.any.isRequired,
onClick: PropTypes.func,
items: PropTypes.array.isRequired,
},
class List extends Component {
static get propTypes() {
return {
itemComponent: PropTypes.any.isRequired,
onClick: PropTypes.func,
items: PropTypes.array.isRequired,
};
}
render() {
let { items, onClick, itemComponent: Item } = this.props;
@ -34,4 +34,6 @@ module.exports = createClass({
}))
);
}
});
}
module.exports = List;

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

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

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

@ -4,7 +4,7 @@
"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 {
L10N,
getSnapshotTitle,
@ -16,17 +16,17 @@ const {
const { diffingState } = require("../constants");
const { snapshot: snapshotModel, app: appModel } = require("../models");
module.exports = createClass({
displayName: "SnapshotListItem",
propTypes: {
onClick: PropTypes.func.isRequired,
onSave: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
item: snapshotModel.isRequired,
index: PropTypes.number.isRequired,
diffing: appModel.diffing,
},
class SnapshotListItem extends Component {
static get propTypes() {
return {
onClick: PropTypes.func.isRequired,
onSave: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
item: snapshotModel.isRequired,
index: PropTypes.number.isRequired,
diffing: appModel.diffing,
};
}
render() {
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";
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 models = require("../models");
const { viewState } = require("../constants");
module.exports = createClass({
displayName: "Toolbar",
propTypes: {
censusDisplays: PropTypes.arrayOf(PropTypes.shape({
displayName: PropTypes.string.isRequired,
})).isRequired,
censusDisplay: PropTypes.shape({
displayName: PropTypes.string.isRequired,
}).isRequired,
onTakeSnapshotClick: PropTypes.func.isRequired,
onImportClick: PropTypes.func.isRequired,
onClearSnapshotsClick: PropTypes.func.isRequired,
onCensusDisplayChange: PropTypes.func.isRequired,
onToggleRecordAllocationStacks: PropTypes.func.isRequired,
allocations: models.allocations,
filterString: PropTypes.string,
setFilterString: PropTypes.func.isRequired,
diffing: models.diffingModel,
onToggleDiffing: PropTypes.func.isRequired,
view: models.view.isRequired,
onViewChange: PropTypes.func.isRequired,
labelDisplays: PropTypes.arrayOf(PropTypes.shape({
displayName: PropTypes.string.isRequired,
})).isRequired,
labelDisplay: PropTypes.shape({
displayName: PropTypes.string.isRequired,
}).isRequired,
onLabelDisplayChange: PropTypes.func.isRequired,
treeMapDisplays: PropTypes.arrayOf(PropTypes.shape({
displayName: PropTypes.string.isRequired,
})).isRequired,
onTreeMapDisplayChange: PropTypes.func.isRequired,
snapshots: PropTypes.arrayOf(models.snapshot).isRequired,
},
class Toolbar extends Component {
static get propTypes() {
return {
censusDisplays: PropTypes.arrayOf(PropTypes.shape({
displayName: PropTypes.string.isRequired,
})).isRequired,
censusDisplay: PropTypes.shape({
displayName: PropTypes.string.isRequired,
}).isRequired,
onTakeSnapshotClick: PropTypes.func.isRequired,
onImportClick: PropTypes.func.isRequired,
onClearSnapshotsClick: PropTypes.func.isRequired,
onCensusDisplayChange: PropTypes.func.isRequired,
onToggleRecordAllocationStacks: PropTypes.func.isRequired,
allocations: models.allocations,
filterString: PropTypes.string,
setFilterString: PropTypes.func.isRequired,
diffing: models.diffingModel,
onToggleDiffing: PropTypes.func.isRequired,
view: models.view.isRequired,
onViewChange: PropTypes.func.isRequired,
labelDisplays: PropTypes.arrayOf(PropTypes.shape({
displayName: PropTypes.string.isRequired,
})).isRequired,
labelDisplay: PropTypes.shape({
displayName: PropTypes.string.isRequired,
}).isRequired,
onLabelDisplayChange: PropTypes.func.isRequired,
treeMapDisplays: PropTypes.arrayOf(PropTypes.shape({
displayName: PropTypes.string.isRequired,
})).isRequired,
onTreeMapDisplayChange: PropTypes.func.isRequired,
snapshots: PropTypes.arrayOf(models.snapshot).isRequired,
};
}
render() {
let {
@ -298,4 +298,6 @@ module.exports = createClass({
)
);
}
});
}
module.exports = Toolbar;

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

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

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

@ -4,48 +4,55 @@
"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({
displayName: "AutocompletePopup",
class AutocompletePopup extends Component {
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: {
/**
* 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,
},
getInitialState() {
return this.computeState(this.props);
},
constructor(props, context) {
super(props, context);
this.state = this.computeState(props);
this.computeState = this.computeState.bind(this);
this.jumpToTop = this.jumpToTop.bind(this);
this.jumpToBottom = this.jumpToBottom.bind(this);
this.jumpBy = this.jumpBy.bind(this);
this.select = this.select.bind(this);
this.onMouseDown = this.onMouseDown.bind(this);
}
componentWillReceiveProps(nextProps) {
if (this.props.filter === nextProps.filter) {
return;
}
this.setState(this.computeState(nextProps));
},
}
componentDidUpdate() {
if (this.refs.selected) {
this.refs.selected.scrollIntoView(false);
}
},
}
computeState({ autocompleteProvider, filter }) {
let list = autocompleteProvider(filter);
let selectedIndex = list.length == 1 ? 0 : -1;
return { list, selectedIndex };
},
}
/**
* Use this method to select the top-most item
@ -53,7 +60,7 @@ module.exports = createClass({
*/
jumpToTop() {
this.setState({ selectedIndex: 0 });
},
}
/**
* Use this method to select the bottom-most item
@ -61,7 +68,7 @@ module.exports = createClass({
*/
jumpToBottom() {
this.setState({ selectedIndex: this.state.list.length - 1 });
},
}
/**
* 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;
}
this.setState({selectedIndex: nextIndex});
},
}
/**
* Submit the currently selected item to the onItemSelected callback
@ -91,12 +98,12 @@ module.exports = createClass({
if (this.refs.selected) {
this.props.onItemSelected(this.refs.selected.dataset.value);
}
},
}
onMouseDown(e) {
e.preventDefault();
this.setState({ selectedIndex: Number(e.target.dataset.index) }, this.select);
},
}
render() {
let { list } = this.state;
@ -124,4 +131,6 @@ module.exports = createClass({
)
);
}
});
}
module.exports = AutocompletePopup;

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

@ -4,7 +4,7 @@
"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,
isScratchpadScheme, getSourceMappedFile } = require("devtools/client/shared/source-utils");
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 webl10n = new LocalizationHelper("devtools/client/locales/webconsole.properties");
module.exports = createClass({
displayName: "Frame",
class Frame extends Component {
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: {
// 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() {
static get defaultProps() {
return {
showFunctionName: false,
showAnonymousFunctionName: false,
@ -47,7 +47,13 @@ module.exports = createClass({
showEmptyPathAsHost: false,
showFullSourceUrl: false,
};
},
}
constructor(props) {
super(props);
this._locationChanged = this._locationChanged.bind(this);
this.getSourceForClick = this.getSourceForClick.bind(this);
}
componentWillMount() {
if (this.props.sourceMapService) {
@ -55,7 +61,7 @@ module.exports = createClass({
this.props.sourceMapService.subscribe(source, line, column,
this._locationChanged);
}
},
}
componentWillUnmount() {
if (this.props.sourceMapService) {
@ -63,7 +69,7 @@ module.exports = createClass({
this.props.sourceMapService.unsubscribe(source, line, column,
this._locationChanged);
}
},
}
_locationChanged(isSourceMapped, url, line, column) {
let newState = {
@ -79,7 +85,7 @@ module.exports = createClass({
}
this.setState(newState);
},
}
/**
* Utility method to convert the Frame object model to the
@ -95,7 +101,7 @@ module.exports = createClass({
column,
functionDisplayName: this.props.frame.functionDisplayName,
};
},
}
render() {
let frame, isSourceMapped;
@ -235,4 +241,6 @@ module.exports = createClass({
return dom.span(attributes, ...elements);
}
});
}
module.exports = Frame;

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

@ -25,61 +25,67 @@
const {
DOM: dom,
createClass,
Component,
PropTypes,
} = require("devtools/client/shared/vendor/react");
const { assert } = require("devtools/shared/DevToolsUtils");
module.exports = createClass({
displayName: "HSplitBox",
class HSplitBox extends Component {
static get propTypes() {
return {
// The contents of the start pane.
start: PropTypes.any.isRequired,
propTypes: {
// The contents of the start pane.
start: PropTypes.any.isRequired,
// The contents of the end pane.
end: PropTypes.any.isRequired,
// The contents of the end pane.
end: PropTypes.any.isRequired,
// The relative width of the start pane, expressed as a number between 0 and
// 1. The relative width of the end pane is 1 - startWidth. For example,
// with startWidth = .5, both panes are of equal width; with startWidth =
// .25, the start panel will take up 1/4 width and the end panel will take
// up 3/4 width.
startWidth: PropTypes.number,
// The relative width of the start pane, expressed as a number between 0 and
// 1. The relative width of the end pane is 1 - startWidth. For example,
// with startWidth = .5, both panes are of equal width; with startWidth =
// .25, the start panel will take up 1/4 width and the end panel will take
// up 3/4 width.
startWidth: PropTypes.number,
// A minimum css width value for the start and end panes.
minStartWidth: PropTypes.any,
minEndWidth: PropTypes.any,
// A minimum css width value for the start and end panes.
minStartWidth: PropTypes.any,
minEndWidth: PropTypes.any,
// A callback fired when the user drags the splitter to resize the relative
// pane widths. The function is passed the startWidth value that would put
// the splitter underneath the users mouse.
onResize: PropTypes.func.isRequired,
};
}
// A callback fired when the user drags the splitter to resize the relative
// pane widths. The function is passed the startWidth value that would put
// the splitter underneath the users mouse.
onResize: PropTypes.func.isRequired,
},
getDefaultProps() {
static get defaultProps() {
return {
startWidth: 0.5,
minStartWidth: "20px",
minEndWidth: "20px",
};
},
}
getInitialState() {
return {
constructor(props) {
super(props);
this.state = {
mouseDown: false
};
},
this._onMouseDown = this._onMouseDown.bind(this);
this._onMouseUp = this._onMouseUp.bind(this);
this._onMouseMove = this._onMouseMove.bind(this);
}
componentDidMount() {
document.defaultView.top.addEventListener("mouseup", this._onMouseUp);
document.defaultView.top.addEventListener("mousemove", this._onMouseMove);
},
}
componentWillUnmount() {
document.defaultView.top.removeEventListener("mouseup", this._onMouseUp);
document.defaultView.top.removeEventListener("mousemove", this._onMouseMove);
},
}
_onMouseDown(event) {
if (event.button !== 0) {
@ -88,7 +94,7 @@ module.exports = createClass({
this.setState({ mouseDown: true });
event.preventDefault();
},
}
_onMouseUp(event) {
if (event.button !== 0 || !this.state.mouseDown) {
@ -97,7 +103,7 @@ module.exports = createClass({
this.setState({ mouseDown: false });
event.preventDefault();
},
}
_onMouseMove(event) {
if (!this.state.mouseDown) {
@ -113,7 +119,7 @@ module.exports = createClass({
this.props.onResize(relative / width);
event.preventDefault();
},
}
render() {
/* 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");
// Shortcuts
const { PropTypes, createClass, DOM } = React;
const { PropTypes, Component, DOM } = React;
const { div, span, button } = DOM;
// Priority Levels
@ -34,72 +34,81 @@ const PriorityLevels = {
* See also MDN for more info about <xul:notificationbox>:
* https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/notificationbox
*/
var NotificationBox = createClass({
displayName: "NotificationBox",
propTypes: {
// List of notifications appended into the box.
notifications: PropTypes.arrayOf(PropTypes.shape({
// 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.
class NotificationBox extends Component {
static get propTypes() {
return {
// List of notifications appended into the box.
notifications: PropTypes.arrayOf(PropTypes.shape({
// label to appear on the notification.
label: PropTypes.string.isRequired,
// The accesskey attribute set on the <button> element.
accesskey: PropTypes.string,
// 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,
// 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
// with the notification box.
eventCallback: PropTypes.func,
})),
// Message that should be shown when hovering over the close button
closeButtonTooltip: PropTypes.string
};
}
// Message that should be shown when hovering over the close button
closeButtonTooltip: PropTypes.string
},
getDefaultProps() {
static get defaultProps() {
return {
closeButtonTooltip: l10n.getStr("notificationBox.closeTooltip")
};
},
}
getInitialState() {
return {
constructor(props) {
super(props);
this.state = {
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
* already present with a higher priority, the new notification will be
* added behind it. See `propTypes` for arguments description.
*/
appendNotification(label, value, image, priority, buttons = [],
eventCallback) {
appendNotification(label, value, image, priority, buttons = [], eventCallback) {
// Priority level must be within expected interval
// (see priority levels at the top of this file).
if (priority < PriorityLevels.PRIORITY_INFO_LOW ||
@ -137,14 +146,14 @@ var NotificationBox = createClass({
this.setState({
notifications: notifications
});
},
}
/**
* Remove specific notification from the list.
*/
removeNotification(notification) {
this.close(this.state.notifications.get(notification.value));
},
}
/**
* Returns an object that represents a notification. It can be
@ -163,11 +172,11 @@ var NotificationBox = createClass({
this.close(notification);
}
});
},
}
getCurrentNotification() {
return this.state.notifications.first();
},
}
/**
* Close specified notification.
@ -184,7 +193,7 @@ var NotificationBox = createClass({
this.setState({
notifications: this.state.notifications.remove(notification.value)
});
},
}
/**
* Render a button. A notification can have a set of custom buttons.
@ -210,7 +219,7 @@ var NotificationBox = createClass({
props.label
)
);
},
}
/**
* Render a notification.
@ -241,7 +250,7 @@ var NotificationBox = createClass({
)
)
);
},
}
/**
* Render the top (highest priority) notification. Only one
@ -256,8 +265,8 @@ var NotificationBox = createClass({
return div({className: "notificationbox"},
content
);
},
});
}
}
module.exports.NotificationBox = NotificationBox;
module.exports.PriorityLevels = PriorityLevels;

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

@ -6,31 +6,36 @@
"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 AutocompletePopup = createFactory(require("devtools/client/shared/components/AutoCompletePopup"));
/**
* A generic search box component for use across devtools
*/
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() {
class SearchBox extends Component {
static get propTypes() {
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: "",
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() {
if (!this.props.keyShortcut) {
@ -44,7 +49,7 @@ module.exports = createClass({
event.preventDefault();
this.refs.input.focus();
});
},
}
componentWillUnmount() {
if (this.shortcuts) {
@ -55,7 +60,7 @@ module.exports = createClass({
if (this.searchTimeout) {
clearTimeout(this.searchTimeout);
}
},
}
onChange() {
if (this.state.value !== this.refs.input.value) {
@ -81,20 +86,20 @@ module.exports = createClass({
this.searchTimeout = null;
this.props.onChange(this.state.value);
}, this.props.delay);
},
}
onClearButtonClick() {
this.refs.input.value = "";
this.onChange();
},
}
onFocus() {
this.setState({ focused: true });
},
}
onBlur() {
this.setState({ focused: false });
},
}
onKeyDown(e) {
let { autocomplete } = this.refs;
@ -131,7 +136,7 @@ module.exports = createClass({
autocomplete.jumpToBottom();
break;
}
},
}
render() {
let {
@ -175,4 +180,6 @@ module.exports = createClass({
})
);
}
});
}
module.exports = SearchBox;

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

@ -6,7 +6,7 @@
"use strict";
const { DOM, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
const { DOM, Component, PropTypes } = require("devtools/client/shared/vendor/react");
// Shortcuts
const { button } = DOM;
@ -15,35 +15,39 @@ const { button } = DOM;
* Sidebar toggle button. This button is used to exapand
* and collapse Sidebar.
*/
var SidebarToggle = createClass({
displayName: "SidebarToggle",
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 () {
class SidebarToggle extends Component {
static get propTypes() {
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
onClick: function (event) {
onClick(event) {
this.props.onClick(event);
},
}
// Rendering
render: function () {
render() {
let title = this.state.collapsed ?
this.props.expandPaneTitle :
this.props.collapsePaneTitle;
@ -61,6 +65,6 @@ var SidebarToggle = createClass({
})
);
}
});
}
module.exports = SidebarToggle;

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

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

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

@ -5,7 +5,7 @@
"use strict";
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 NUMBER_OF_OFFSCREEN_ITEMS = 1;
@ -97,158 +97,176 @@ const NUMBER_OF_OFFSCREEN_ITEMS = 1;
* }
* });
*/
module.exports = createClass({
displayName: "Tree",
class Tree extends Component {
static get propTypes() {
return {
// Required props
propTypes: {
// Required props
// A function to get an item's parent, or null if it is a root.
//
// 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.
//
// 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 children.
//
// Type: getChildren(item: Item) -> [Item]
//
// Example:
//
// // This item's children are stored in its `children` property.
// getChildren: item => item.children
getChildren: PropTypes.func.isRequired,
// A function to get an item's children.
//
// Type: getChildren(item: Item) -> [Item]
//
// Example:
//
// // This item's children are stored in its `children` property.
// getChildren: item => item.children
getChildren: PropTypes.func.isRequired,
// A function which takes an item and ArrowExpander component instance and
// returns a component, or text, or anything else that React considers
// renderable.
//
// Type: renderItem(item: Item,
// depth: Number,
// isFocused: Boolean,
// arrow: ReactComponent,
// 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
// returns a component, or text, or anything else that React considers
// renderable.
//
// Type: renderItem(item: Item,
// depth: Number,
// isFocused: Boolean,
// arrow: ReactComponent,
// 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 returns the roots of the tree (forest).
//
// Type: getRoots() -> [Item]
//
// 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
// // tree.
// getRoots: () => [this.props.rootOfMyTree]
getRoots: PropTypes.func.isRequired,
// A function which returns the roots of the tree (forest).
//
// Type: getRoots() -> [Item]
//
// 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
// // tree.
// getRoots: () => [this.props.rootOfMyTree]
getRoots: PropTypes.func.isRequired,
// A function to get a unique key for the given item. This helps speed up
// React's rendering a *TON*.
//
// Type: getKey(item: Item) -> String
//
// Example:
//
// getKey: item => `my-tree-item-${item.uniqueId}`
getKey: PropTypes.func.isRequired,
// A function to get a unique key for the given item. This helps speed up
// React's rendering a *TON*.
//
// Type: getKey(item: Item) -> String
//
// Example:
//
// getKey: item => `my-tree-item-${item.uniqueId}`
getKey: PropTypes.func.isRequired,
// A function to get whether an item is expanded or not. If an item is not
// expanded, then it must be collapsed.
//
// Type: isExpanded(item: Item) -> Boolean
//
// Example:
//
// isExpanded: item => item.expanded,
isExpanded: PropTypes.func.isRequired,
// A function to get whether an item is expanded or not. If an item is not
// expanded, then it must be collapsed.
//
// 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
// pixels.
itemHeight: PropTypes.number.isRequired,
// The height of an item in the tree including margin and padding, in
// pixels.
itemHeight: PropTypes.number.isRequired,
// Optional props
// Optional props
// The currently focused item, if any such item exists.
focused: PropTypes.any,
// The currently focused item, if any such item exists.
focused: PropTypes.any,
// Handle when a new item is focused.
onFocus: PropTypes.func,
// Handle when a new item is focused.
onFocus: PropTypes.func,
// The depth to which we should automatically expand new items.
autoExpandDepth: PropTypes.number,
// The depth to which we should automatically expand new items.
autoExpandDepth: PropTypes.number,
// Note: the two properties below are mutually exclusive. Only one of the
// 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
// 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,
// Optional event handlers for when items are expanded or collapsed. Useful
// 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,
};
}
// Optional event handlers for when items are expanded or collapsed. Useful
// 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() {
static get defaultProps() {
return {
autoExpandDepth: AUTO_EXPAND_DEPTH,
};
},
}
getInitialState() {
return {
constructor(props) {
super(props);
this.state = {
scroll: 0,
height: window.innerHeight,
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() {
window.addEventListener("resize", this._updateHeight);
this._autoExpand();
this._updateHeight();
},
}
componentWillReceiveProps(nextProps) {
this._autoExpand();
this._updateHeight();
},
}
componentWillUnmount() {
window.removeEventListener("resize", this._updateHeight);
},
}
_autoExpand() {
if (!this.props.autoExpandDepth) {
@ -279,7 +297,7 @@ module.exports = createClass({
for (let i = 0; i < length; i++) {
autoExpand(roots[i], 0);
}
},
}
_preventArrowKeyScrolling(e) {
switch (e.key) {
@ -298,7 +316,7 @@ module.exports = createClass({
}
}
}
},
}
/**
* Updates the state's height based on clientHeight.
@ -307,7 +325,7 @@ module.exports = createClass({
this.setState({
height: this.refs.tree.clientHeight
});
},
}
/**
* Perform a pre-order depth-first search from item.
@ -332,7 +350,7 @@ module.exports = createClass({
}
return traversal;
},
}
/**
* Perform a pre-order depth-first search over the whole forest.
@ -347,7 +365,7 @@ module.exports = createClass({
}
return traversal;
},
}
/**
* Expands current row.
@ -355,7 +373,7 @@ module.exports = createClass({
* @param {Object} item
* @param {Boolean} expandAllChildren
*/
_onExpand: oncePerAnimationFrame(function (item, expandAllChildren) {
_onExpand(item, expandAllChildren) {
if (this.props.onExpand) {
this.props.onExpand(item);
@ -367,18 +385,18 @@ module.exports = createClass({
}
}
}
}),
}
/**
* Collapses current row.
*
* @param {Object} item
*/
_onCollapse: oncePerAnimationFrame(function (item) {
_onCollapse(item) {
if (this.props.onCollapse) {
this.props.onCollapse(item);
}
}),
}
/**
* Sets the passed in item to be the focused item.
@ -411,14 +429,14 @@ module.exports = createClass({
if (this.props.onFocus) {
this.props.onFocus(item);
}
},
}
/**
* Sets the state to have no focused item.
*/
_onBlur() {
this._focus(0, undefined);
},
}
/**
* Fired on a scroll within the tree's container, updates
@ -426,12 +444,12 @@ module.exports = createClass({
*
* @param {Event} e
*/
_onScroll: oncePerAnimationFrame(function (e) {
_onScroll(e) {
this.setState({
scroll: Math.max(this.refs.tree.scrollTop, 0),
height: this.refs.tree.clientHeight
});
}),
}
/**
* Handles key down events in the tree's container.
@ -476,12 +494,12 @@ module.exports = createClass({
}
break;
}
},
}
/**
* 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
// focused node. Focus the previous node in the DFS, if it exists. If it
// doesn't exist, we're at the first node already.
@ -505,13 +523,13 @@ module.exports = createClass({
}
this._focus(prevIndex, prev);
}),
}
/**
* Handles the down arrow key which will focus either the next child
* or sibling row.
*/
_focusNextNode: oncePerAnimationFrame(function () {
_focusNextNode() {
// 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
// doesn't exist, we're at the last node already.
@ -530,13 +548,13 @@ module.exports = createClass({
if (i + 1 < traversal.length) {
this._focus(i + 1, traversal[i + 1].item);
}
}),
}
/**
* Handles the left arrow key, going back up to the current rows'
* parent row.
*/
_focusParentNode: oncePerAnimationFrame(function () {
_focusParentNode() {
const parent = this.props.getParent(this.props.focused);
if (!parent) {
return;
@ -552,7 +570,7 @@ module.exports = createClass({
}
this._focus(parentIndex, parent);
}),
}
render() {
const traversal = this._dfsFromRoots();
@ -656,28 +674,28 @@ module.exports = createClass({
nodes
);
}
});
}
/**
* An arrow that displays whether its node is expanded () or collapsed
* (). When its node has no children, it is hidden.
*/
const ArrowExpander = createFactory(createClass({
displayName: "ArrowExpander",
propTypes: {
item: PropTypes.any.isRequired,
visible: PropTypes.bool.isRequired,
expanded: PropTypes.bool.isRequired,
onCollapse: PropTypes.func.isRequired,
onExpand: PropTypes.func.isRequired,
},
class ArrowExpanderClass extends Component {
static get propTypes() {
return {
item: PropTypes.any.isRequired,
visible: PropTypes.bool.isRequired,
expanded: PropTypes.bool.isRequired,
onCollapse: PropTypes.func.isRequired,
onExpand: PropTypes.func.isRequired,
};
}
shouldComponentUpdate(nextProps, nextState) {
return this.props.item !== nextProps.item
|| this.props.visible !== nextProps.visible
|| this.props.expanded !== nextProps.expanded;
},
}
render() {
const attrs = {
@ -699,24 +717,26 @@ const ArrowExpander = createFactory(createClass({
return dom.div(attrs);
}
}));
}
const TreeNode = createFactory(createClass({
propTypes: {
id: PropTypes.any.isRequired,
focused: PropTypes.bool.isRequired,
item: PropTypes.any.isRequired,
expanded: PropTypes.bool.isRequired,
hasChildren: PropTypes.bool.isRequired,
onExpand: PropTypes.func.isRequired,
index: PropTypes.number.isRequired,
first: PropTypes.bool,
last: PropTypes.bool,
onClick: PropTypes.func,
onCollapse: PropTypes.func.isRequired,
depth: PropTypes.number.isRequired,
renderItem: PropTypes.func.isRequired,
},
class TreeNodeClass extends Component {
static get propTypes() {
return {
id: PropTypes.any.isRequired,
focused: PropTypes.bool.isRequired,
item: PropTypes.any.isRequired,
expanded: PropTypes.bool.isRequired,
hasChildren: PropTypes.bool.isRequired,
onExpand: PropTypes.func.isRequired,
index: PropTypes.number.isRequired,
first: PropTypes.bool,
last: PropTypes.bool,
onClick: PropTypes.func,
onCollapse: PropTypes.func.isRequired,
depth: PropTypes.number.isRequired,
renderItem: PropTypes.func.isRequired,
};
}
render() {
const arrow = ArrowExpander({
@ -769,7 +789,10 @@ const TreeNode = createFactory(createClass({
this.props.expanded),
);
}
}));
}
const ArrowExpander = createFactory(ArrowExpanderClass);
const TreeNode = createFactory(TreeNodeClass);
/**
* 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 ReactDOM = require("devtools/client/shared/vendor/react-dom");
const { DOM: dom, PropTypes } = React;
const { Component, DOM: dom, PropTypes } = React;
const Draggable = React.createClass({
displayName: "Draggable",
class Draggable extends Component {
static get propTypes() {
return {
onMove: PropTypes.func.isRequired,
onStart: PropTypes.func,
onStop: PropTypes.func,
style: PropTypes.object,
className: PropTypes.string
};
}
propTypes: {
onMove: PropTypes.func.isRequired,
onStart: PropTypes.func,
onStop: PropTypes.func,
style: PropTypes.object,
className: PropTypes.string
},
constructor(props) {
super(props);
this.startDragging = this.startDragging.bind(this);
this.onMove = this.onMove.bind(this);
this.onUp = this.onUp.bind(this);
}
startDragging(ev) {
ev.preventDefault();
@ -25,14 +32,14 @@ const Draggable = React.createClass({
doc.addEventListener("mousemove", this.onMove);
doc.addEventListener("mouseup", this.onUp);
this.props.onStart && this.props.onStart();
},
}
onMove(ev) {
ev.preventDefault();
// Use viewport coordinates so, moving mouse over iframes
// doesn't mangle (relative) coordinates.
this.props.onMove(ev.clientX, ev.clientY);
},
}
onUp(ev) {
ev.preventDefault();
@ -40,7 +47,7 @@ const Draggable = React.createClass({
doc.removeEventListener("mousemove", this.onMove);
doc.removeEventListener("mouseup", this.onUp);
this.props.onStop && this.props.onStop();
},
}
render() {
return dom.div({
@ -49,6 +56,6 @@ const Draggable = React.createClass({
onMouseDown: this.startDragging
});
}
});
}
module.exports = Draggable;

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

@ -7,62 +7,68 @@
const React = require("devtools/client/shared/vendor/react");
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
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
* as well as horizontal mode.
*/
const SplitBox = React.createClass({
displayName: "SplitBox",
class SplitBox extends Component {
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: {
// 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() {
static get defaultProps() {
return {
splitterSize: 5,
vert: true,
endPanelControl: false
};
},
}
/**
* The state stores the current orientation (vertical or horizontal)
* and the current size (width/height). All these values can change
* during the component's life time.
*/
getInitialState() {
return {
vert: this.props.vert,
width: this.props.initialWidth || this.props.initialSize,
height: this.props.initialHeight || this.props.initialSize
constructor(props) {
super(props);
/**
* The state stores the current orientation (vertical or horizontal)
* and the current size (width/height). All these values can change
* during the component's life time.
*/
this.state = {
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) {
let { vert } = nextProps;
@ -70,7 +76,7 @@ const SplitBox = React.createClass({
if (vert !== this.props.vert) {
this.setState({ vert });
}
},
}
shouldComponentUpdate(nextProps, nextState) {
return nextState.width != this.state.width ||
@ -82,7 +88,7 @@ const SplitBox = React.createClass({
nextProps.minSize != this.props.minSize ||
nextProps.maxSize != this.props.maxSize ||
nextProps.splitterSize != this.props.splitterSize;
},
}
// Dragging Events
@ -102,7 +108,7 @@ const SplitBox = React.createClass({
this.setState({
defaultCursor: defaultCursor
});
},
}
onStopMove() {
const splitBox = ReactDOM.findDOMNode(this);
@ -110,7 +116,7 @@ const SplitBox = React.createClass({
doc.documentElement.style.cursor = this.state.defaultCursor;
splitBox.classList.remove("dragging");
},
}
/**
* Adjust size of the controlled panel. Depending on the current
@ -149,7 +155,7 @@ const SplitBox = React.createClass({
height: size
});
}
},
}
// Rendering
@ -226,6 +232,6 @@ const SplitBox = React.createClass({
)
);
}
});
}
module.exports = SplitBox;

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

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

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

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

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

@ -26,8 +26,8 @@ Test all-tabs menu.
window.onload = Task.async(function* () {
try {
const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
const React = browserRequire("devtools/client/shared/vendor/react");
const Tabbar = React.createFactory(browserRequire("devtools/client/shared/components/tabs/TabBar"));
const { Component, createFactory, DOM } = browserRequire("devtools/client/shared/vendor/react");
const Tabbar = createFactory(browserRequire("devtools/client/shared/components/tabs/TabBar"));
// Create container for the TabBar. Set smaller width
// 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);
// Test panel.
let TabPanel = React.createFactory(React.createClass({
render: function () {
return React.DOM.div({}, "content");
class TabPanelClass extends Component {
render() {
return DOM.div({}, "content");
}
}));
}
// Test panel.
let TabPanel = createFactory(TabPanelClass);
// Create a few panels.
yield addTabWithPanel(1);

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

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

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

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

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

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

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

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

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

@ -7,17 +7,22 @@
// Make this available to both AMD and CJS environments
define(function (require, exports, module) {
// ReactJS
const React = require("devtools/client/shared/vendor/react");
const { cloneElement, Component, createFactory, DOM: dom, PropTypes } =
require("devtools/client/shared/vendor/react");
// Reps
const { ObjectProvider } = require("./ObjectProvider");
const TreeRow = React.createFactory(require("./TreeRow"));
const TreeHeader = React.createFactory(require("./TreeHeader"));
const TreeRow = createFactory(require("./TreeRow"));
const TreeHeader = createFactory(require("./TreeHeader"));
// Shortcuts
const DOM = React.DOM;
const PropTypes = React.PropTypes;
const defaultProps = {
object: null,
renderRow: null,
provider: ObjectProvider,
expandedNodes: new Set(),
expandableStrings: true,
columns: []
};
/**
* This component represents a tree view with expandable/collapsible nodes.
@ -53,88 +58,96 @@ define(function (require, exports, module) {
* renderLabelCell: function(object);
* }
*/
let TreeView = React.createClass({
displayName: "TreeView",
class TreeView extends Component {
// The only required property (not set by default) is the input data
// object that is used to puputate the tree.
propTypes: {
// The input data object.
object: PropTypes.any,
className: PropTypes.string,
label: PropTypes.string,
// Data provider (see also the interface above)
provider: PropTypes.shape({
getChildren: PropTypes.func,
hasChildren: PropTypes.func,
getLabel: PropTypes.func,
getValue: PropTypes.func,
getKey: PropTypes.func,
getType: PropTypes.func,
}).isRequired,
// Tree decorator (see also the interface above)
decorator: PropTypes.shape({
getRowClass: PropTypes.func,
getCellClass: PropTypes.func,
getHeaderClass: PropTypes.func,
renderValue: PropTypes.func,
static get propTypes() {
return {
// The input data object.
object: PropTypes.any,
className: PropTypes.string,
label: PropTypes.string,
// Data provider (see also the interface above)
provider: PropTypes.shape({
getChildren: PropTypes.func,
hasChildren: PropTypes.func,
getLabel: PropTypes.func,
getValue: PropTypes.func,
getKey: PropTypes.func,
getType: PropTypes.func,
}).isRequired,
// Tree decorator (see also the interface above)
decorator: PropTypes.shape({
getRowClass: PropTypes.func,
getCellClass: 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,
// Custom cell renderer
renderCell: PropTypes.func,
// Custom value renderef
renderValue: PropTypes.func,
// Custom tree label (including a toggle button) renderer
renderLabelCell: PropTypes.func,
}),
// Custom tree row (node) renderer
renderRow: PropTypes.func,
// Custom cell renderer
renderCell: PropTypes.func,
// Custom value renderef
renderValue: PropTypes.func,
// Custom tree label (including a toggle button) renderer
renderLabelCell: PropTypes.func,
// Set of expanded nodes
expandedNodes: PropTypes.object,
// Custom filtering callback
onFilter: PropTypes.func,
// Custom sorting callback
onSort: PropTypes.func,
// 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: []
// Set of expanded nodes
expandedNodes: PropTypes.object,
// Custom filtering callback
onFilter: PropTypes.func,
// Custom sorting callback
onSort: PropTypes.func,
// 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
}))
};
},
}
getInitialState: function () {
return {
expandedNodes: this.props.expandedNodes,
columns: ensureDefaultColumn(this.props.columns),
static get defaultProps() {
return defaultProps;
}
constructor(props) {
super(props);
this.state = {
expandedNodes: props.expandedNodes,
columns: ensureDefaultColumn(props.columns),
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;
this.setState(Object.assign({}, this.state, {
expandedNodes,
}));
},
}
componentDidUpdate: function () {
componentDidUpdate() {
let selected = this.getSelectedRow(this.rows);
if (!selected && this.rows.length > 0) {
// 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.
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
toggle: function (nodePath) {
toggle(nodePath) {
let nodes = this.state.expandedNodes;
if (this.isExpanded(nodePath)) {
nodes.delete(nodePath);
@ -158,22 +218,22 @@ define(function (require, exports, module) {
this.setState(Object.assign({}, this.state, {
expandedNodes: nodes
}));
},
}
isExpanded: function (nodePath) {
isExpanded(nodePath) {
return this.state.expandedNodes.has(nodePath);
},
}
// Event Handlers
onKeyDown: function (event) {
onKeyDown(event) {
if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(
event.key)) {
event.preventDefault();
}
},
}
onKeyUp: function (event) {
onKeyUp(event) {
let row = this.getSelectedRow(this.rows);
if (!row) {
return;
@ -209,33 +269,33 @@ define(function (require, exports, module) {
}
event.preventDefault();
},
}
onClickRow: function (nodePath, event) {
onClickRow(nodePath, event) {
event.stopPropagation();
let cell = event.target.closest("td");
if (cell && cell.classList.contains("treeLabelCell")) {
this.toggle(nodePath);
}
this.selectRow(nodePath);
},
}
getSelectedRow: function (rows) {
getSelectedRow(rows) {
if (!this.state.selected || rows.length === 0) {
return null;
}
return rows.find(row => this.isSelected(row.props.member.path));
},
}
selectRow: function (nodePath) {
selectRow(nodePath) {
this.setState(Object.assign({}, this.state, {
selected: nodePath
}));
},
}
isSelected: function (nodePath) {
isSelected(nodePath) {
return nodePath === this.state.selected;
},
}
// Filtering & Sorting
@ -243,15 +303,15 @@ define(function (require, exports, module) {
* Filter out nodes that don't correspond to the current filter.
* @return {Boolean} true if the node should be visible otherwise false.
*/
onFilter: function (object) {
onFilter(object) {
let onFilter = this.props.onFilter;
return onFilter ? onFilter(object) : true;
},
}
onSort: function (parent, children) {
onSort(parent, children) {
let onSort = this.props.onSort;
return onSort ? onSort(parent, children) : children;
},
}
// Members
@ -259,7 +319,7 @@ define(function (require, exports, module) {
* Return children node objects (so called 'members') for given
* parent object.
*/
getMembers: function (parent, level, path) {
getMembers(parent, level, path) {
// Strings don't have children. Note that 'long' strings are using
// the expander icon (+/-) to display the entire original value,
// but there are no child items.
@ -320,12 +380,12 @@ define(function (require, exports, module) {
selected: this.isSelected(nodePath)
};
});
},
}
/**
* Render tree rows/nodes.
*/
renderRows: function (parent, level = 0, path = "") {
renderRows(parent, level = 0, path = "") {
let rows = [];
let decorator = this.props.decorator;
let renderRow = this.props.renderRow || TreeRow;
@ -367,7 +427,7 @@ define(function (require, exports, module) {
if (!Array.isArray(childRows)) {
let lastIndex = rows.length - 1;
props.member.loading = true;
rows[lastIndex] = React.cloneElement(rows[lastIndex], props);
rows[lastIndex] = cloneElement(rows[lastIndex], props);
} else {
rows = rows.concat(childRows);
}
@ -375,9 +435,9 @@ define(function (require, exports, module) {
});
return rows;
},
}
render: function () {
render() {
let root = this.props.object;
let classNames = ["treeTable"];
this.rows = [];
@ -403,7 +463,7 @@ define(function (require, exports, module) {
});
return (
DOM.table({
dom.table({
className: classNames.join(" "),
role: "tree",
tabIndex: 0,
@ -414,62 +474,13 @@ define(function (require, exports, module) {
cellPadding: 0,
cellSpacing: 0},
TreeHeader(props),
DOM.tbody({
dom.tbody({
role: "presentation"
}, 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

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

@ -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:
~StreamListener() { }

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

@ -10,6 +10,7 @@
#include "AudioStreamTrack.h"
#include "Layers.h"
#include "MediaStreamGraph.h"
#include "MediaStreamGraphImpl.h"
#include "MediaStreamListener.h"
#include "VideoStreamTrack.h"
#include "mozilla/dom/AudioNode.h"
@ -20,6 +21,7 @@
#include "mozilla/dom/LocalMediaStreamBinding.h"
#include "mozilla/dom/MediaStreamBinding.h"
#include "mozilla/dom/MediaStreamTrackEvent.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/VideoTrack.h"
#include "mozilla/dom/VideoTrackList.h"
#include "mozilla/media/MediaUtils.h"
@ -27,6 +29,7 @@
#include "nsIScriptError.h"
#include "nsIUUIDGenerator.h"
#include "nsPIDOMWindow.h"
#include "nsProxyRelease.h"
#include "nsRFPService.h"
#include "nsServiceManagerUtils.h"
@ -338,9 +341,8 @@ public:
explicit PlaybackTrackListener(DOMMediaStream* aStream) :
mStream(aStream) {}
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PlaybackTrackListener,
MediaStreamTrackConsumer)
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(PlaybackTrackListener)
NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(PlaybackTrackListener)
void NotifyEnded(MediaStreamTrack* aTrack) override
{
@ -364,15 +366,9 @@ protected:
RefPtr<DOMMediaStream> mStream;
};
NS_IMPL_ADDREF_INHERITED(DOMMediaStream::PlaybackTrackListener,
MediaStreamTrackConsumer)
NS_IMPL_RELEASE_INHERITED(DOMMediaStream::PlaybackTrackListener,
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_ROOT_NATIVE(DOMMediaStream::PlaybackTrackListener, AddRef)
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(DOMMediaStream::PlaybackTrackListener, Release)
NS_IMPL_CYCLE_COLLECTION(DOMMediaStream::PlaybackTrackListener, mStream)
NS_IMPL_CYCLE_COLLECTION_CLASS(DOMMediaStream)
@ -580,6 +576,68 @@ DOMMediaStream::CurrentTime()
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
DOMMediaStream::GetId(nsAString& aID) const
{

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

@ -360,6 +360,9 @@ public:
double CurrentTime();
static already_AddRefed<dom::Promise>
CountUnderlyingStreams(const dom::GlobalObject& aGlobal, ErrorResult& aRv);
void GetId(nsAString& aID) 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.
*
* The key is a hash of nsPIDOMWindowInner, see `WindowToHash`.
*/
static nsDataHashtable<nsUint32HashKey, MediaStreamGraphImpl*> gGraphs;
@ -1589,6 +1591,19 @@ public:
// teardown and just leak, for safety.
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;
// We can't block past the final LIFECYCLE_WAITING_FOR_STREAM_DESTRUCTION
@ -1601,21 +1616,11 @@ public:
mGraph->Destroy();
} else {
// The graph is not empty. We must be in a forced shutdown, or a
// non-realtime graph that has finished processing. Some later
// AppendMessage will detect that the manager has been emptied, and
// non-realtime graph that has finished processing. Some later
// AppendMessage will detect that the graph has been emptied, and
// delete it.
NS_ASSERTION(mGraph->mForceShutDown || !mGraph->mRealtime,
"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 =
MediaStreamGraphImpl::LIFECYCLE_WAITING_FOR_STREAM_DESTRUCTION;
}
@ -2074,11 +2079,23 @@ MediaStream::EnsureTrack(TrackID aTrackId)
void
MediaStream::RemoveAllListenersImpl()
{
for (int32_t i = mListeners.Length() - 1; i >= 0; --i) {
RefPtr<MediaStreamListener> listener = mListeners[i].forget();
listener->NotifyEvent(GraphImpl(), MediaStreamGraphEvent::EVENT_REMOVED);
GraphImpl()->AssertOnGraphThreadOrNotRunning();
auto streamListeners(mListeners);
for (auto& l : streamListeners) {
l->NotifyEvent(GraphImpl(), MediaStreamGraphEvent::EVENT_REMOVED);
}
mListeners.Clear();
auto trackListeners(mTrackListeners);
for (auto& l : trackListeners) {
l.mListener->NotifyRemoved();
}
mTrackListeners.Clear();
if (SourceMediaStream* source = AsSourceStream()) {
source->RemoveAllDirectListeners();
}
}
void
@ -3119,6 +3136,18 @@ SourceMediaStream::EndAllTrackAndFinish()
// 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()
{
}
@ -3494,21 +3523,28 @@ uint32_t WindowToHash(nsPIDOMWindowInner* aWindow)
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::GetInstance(MediaStreamGraph::GraphDriverType aGraphDriverRequested,
nsPIDOMWindowInner* aWindow)
{
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
// MediaStreamGraph hashtable. Effectively, this means there is a graph per
// document.
uint32_t hashkey = WindowToHash(aWindow);
if (!gGraphs.Get(hashkey, &graph)) {
if (!graph) {
if (!gMediaStreamGraphShutdownBlocker) {
class Blocker : public media::ShutdownBlocker
@ -3556,6 +3592,7 @@ MediaStreamGraph::GetInstance(MediaStreamGraph::GraphDriverType aGraphDriverRequ
CubebUtils::PreferredSampleRate(),
mainThread);
uint32_t hashkey = WindowToHash(aWindow);
gGraphs.Put(hashkey, graph);
LOG(LogLevel::Debug,

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

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

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

@ -57,14 +57,6 @@ MediaStreamTrackSource::ApplyConstraints(
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
* through the MediaStreamGraph.
@ -187,7 +179,6 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(MediaStreamTrack)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaStreamTrack,
DOMEventTargetHelper)
tmp->Destroy();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsumers)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwningStream)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSource)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginalTrack)
@ -197,7 +188,6 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaStreamTrack,
DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsumers)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwningStream)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSource)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalTrack)
@ -379,10 +369,14 @@ MediaStreamTrack::NotifyEnded()
{
MOZ_ASSERT(mReadyState == MediaStreamTrackState::Ended);
for (int32_t i = mConsumers.Length() - 1; i >= 0; --i) {
// Loop backwards by index in case the consumer removes itself in the
// callback.
mConsumers[i]->NotifyEnded(this);
auto consumers(mConsumers);
for (const auto& consumer : consumers) {
if (consumer) {
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));
mConsumers.AppendElement(aConsumer);
// Remove destroyed consumers for cleanliness
while (mConsumers.RemoveElement(nullptr)) {
MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
}
}
void
MediaStreamTrack::RemoveConsumer(MediaStreamTrackConsumer* 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>

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

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

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

@ -632,7 +632,11 @@ MediaEncoder::CreateEncoder(TaskQueue* aEncoderThread,
audioEncoder = MakeAndAddRef<OpusTrackEncoder>(aTrackRate);
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);
NS_ENSURE_TRUE(writer, nullptr);
NS_ENSURE_TRUE(videoEncoder, nullptr);

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

@ -385,10 +385,15 @@ protected:
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
{
public:
explicit VideoTrackEncoder(TrackRate aTrackRate)
VideoTrackEncoder(TrackRate aTrackRate, FrameDroppingMode aFrameDroppingMode)
: TrackEncoder(aTrackRate)
, mFrameWidth(0)
, mFrameHeight(0)
@ -396,6 +401,7 @@ public:
, mDisplayHeight(0)
, mEncodedTicks(0)
, mVideoBitrate(0)
, mFrameDroppingMode(aFrameDroppingMode)
{
mLastChunk.mDuration = 0;
}
@ -551,6 +557,12 @@ protected:
TimeStamp mSuspendTime;
uint32_t mVideoBitrate;
/**
* ALLOW to drop frames under load.
* DISALLOW to encode all frames, mainly for testing.
*/
FrameDroppingMode mFrameDroppingMode;
};
} // namespace mozilla

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

@ -56,8 +56,9 @@ GetSourceSurface(already_AddRefed<Image> aImg)
return surf.forget();
}
VP8TrackEncoder::VP8TrackEncoder(TrackRate aTrackRate)
: VideoTrackEncoder(aTrackRate)
VP8TrackEncoder::VP8TrackEncoder(TrackRate aTrackRate,
FrameDroppingMode aFrameDroppingMode)
: VideoTrackEncoder(aTrackRate, aFrameDroppingMode)
, mEncodedTimestamp(0)
, mVPXContext(new vpx_codec_ctx_t())
, mVPXImageWrapper(new vpx_image_t())
@ -576,6 +577,10 @@ VP8TrackEncoder::EncodeOperation
VP8TrackEncoder::GetNextEncodeOperation(TimeDuration aTimeElapsed,
StreamTime aProcessedDuration)
{
if (mFrameDroppingMode == FrameDroppingMode::DISALLOW) {
return ENCODE_NORMAL_FRAME;
}
int64_t durationInUsec =
FramesToUsecs(aProcessedDuration, mTrackRate).value();
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.
SKIP_FRAME, // Skip the next frame.
};
public:
explicit VP8TrackEncoder(TrackRate aTrackRate);
VP8TrackEncoder(TrackRate aTrackRate, FrameDroppingMode aFrameDroppingMode);
virtual ~VP8TrackEncoder();
already_AddRefed<TrackMetadataBase> GetMetadata() final override;

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

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

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

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

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

@ -63,6 +63,7 @@ skip-if = toolkit == 'android' # no windowshare on android
[test_getUserMedia_bug1223696.html]
[test_getUserMedia_constraints.html]
[test_getUserMedia_callbacks.html]
[test_getUserMedia_GC_MediaStream.html]
[test_getUserMedia_getTrackById.html]
[test_getUserMedia_gumWithinGum.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 onremovetrack;
readonly attribute double currentTime;
[ChromeOnly, Throws]
static Promise<long> countUnderlyingStreams();
};

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

@ -44,7 +44,7 @@ html|input.empty {
color: graytext;
}
@media (-moz-windows-default-theme) {
@media (-moz-windows-default-theme) and (-moz-os-version: windows-win7) {
:root:not(.winxp) html|input.empty {
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
# 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.

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

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

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

@ -18,7 +18,10 @@ public class BookmarkUtils {
* full bookmark management features(full-page dialog, bookmark/folder modification, etc.)
*/
public static boolean isEnabled(Context context) {
return AppConstants.NIGHTLY_BUILD &&
SwitchBoard.isInExperiment(context, Experiments.FULL_BOOKMARK_MANAGEMENT);
final boolean initialized = SwitchBoard.hasExperimentValues(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.peerconnection.video.h264_enabled", false);
pref("media.peerconnection.video.vp9_enabled", true);
pref("media.peerconnection.video.vp9_preferred", false);
pref("media.getusermedia.aec", 1);
pref("media.getusermedia.browser.enabled", false);
pref("media.getusermedia.channels", 0);
@ -616,6 +617,13 @@ pref("media.webspeech.synth.enabled", false);
pref("media.encoder.webm.enabled", true);
#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
pref("media.autoplay.enabled", true);

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

@ -10,15 +10,17 @@
#include "CryptoTask.h"
#include "NSSCertDBTrustDomain.h"
#include "ScopedNSSTypes.h"
#include "SharedCertVerifier.h"
#include "certdb.h"
#include "cms.h"
#include "mozilla/Base64.h"
#include "mozilla/Casting.h"
#include "mozilla/Logging.h"
#include "mozilla/RefPtr.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "nsCOMPtr.h"
#include "nsComponentManagerUtils.h"
#include "nsDataSignatureVerifier.h"
#include "nsDependentString.h"
#include "nsHashKeys.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
// search pattern, and then load it. Fails if there are no matches or if
// there is more than one match. If bugDigest is not null then on success
// search pattern, and then loads it. Fails if there are no matches or if
// 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
// algorithm.
nsresult
@ -465,10 +467,10 @@ CheckManifestVersion(const char* & nextLineStart,
// Parses a signature file (SF) based on the JDK 8 JAR Specification.
//
// The SF file must contain a SHA1-Digest-Manifest (or, preferrably,
// SHA256-Digest-Manifest) attribute in the main section. All other sections are
// ignored. This means that this will NOT parse old-style signature files that
// have separate digests per entry.
// The SF file must contain a SHA*-Digest-Manifest attribute in the main
// section (where the * is either 1 or 256, depending on the given digest
// algorithm). All other sections are ignored. This means that this will NOT
// parse old-style signature files that have separate digests per entry.
// The JDK8 x-Digest-Manifest variant is better because:
//
// (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.
//
// filebuf must be null-terminated. On output, mfDigest will contain the
// decoded value of SHA1-Digest-Manifest or SHA256-Digest-Manifest, if found,
// as well as an identifier indicating which algorithm was found.
// decoded value of the appropriate SHA*-DigestManifest, if found.
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;
nsresult rv = CheckManifestVersion(nextLineStart,
NS_LITERAL_CSTRING(JAR_SF_HEADER));
@ -492,9 +507,6 @@ ParseSF(const char* filebuf, /*out*/ DigestWithAlgorithm& mfDigest)
return rv;
}
nsAutoCString savedSHA1Digest;
// Search for SHA256-Digest-Manifest and SHA1-Digest-Manifest. Prefer the
// former.
for (;;) {
nsAutoCString curLine;
rv = ReadLine(nextLineStart, curLine);
@ -503,13 +515,8 @@ ParseSF(const char* filebuf, /*out*/ DigestWithAlgorithm& mfDigest)
}
if (curLine.Length() == 0) {
// End of main section (blank line or end-of-file). We didn't find
// SHA256-Digest-Manifest, but maybe we found SHA1-Digest-Manifest.
if (!savedSHA1Digest.IsEmpty()) {
mfDigest.mDigest.Assign(savedSHA1Digest);
mfDigest.mAlgorithm = SEC_OID_SHA1;
return NS_OK;
}
// End of main section (blank line or end-of-file). We didn't find the
// SHA*-Digest-Manifest we were looking for.
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
}
@ -520,12 +527,11 @@ ParseSF(const char* filebuf, /*out*/ DigestWithAlgorithm& mfDigest)
return rv;
}
if (attrName.LowerCaseEqualsLiteral("sha256-digest-manifest")) {
rv = Base64Decode(attrValue, mfDigest.mDigest);
if (attrName.EqualsIgnoreCase(digestNameToFind)) {
rv = Base64Decode(attrValue, mfDigest);
if (NS_FAILED(rv)) {
return rv;
}
mfDigest.mAlgorithm = SEC_OID_SHA256;
// There could be multiple SHA*-Digest-Manifest attributes, which
// 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;
}
if (attrName.LowerCaseEqualsLiteral("sha1-digest-manifest") &&
savedSHA1Digest.IsEmpty()) {
rv = Base64Decode(attrValue, savedSHA1Digest);
if (NS_FAILED(rv)) {
return rv;
}
}
// 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
@ -702,23 +701,16 @@ ParseMF(const char* filebuf, nsIZipReader* zip, SECOidTag digestAlgorithm,
return NS_OK;
}
struct VerifyCertificateContext {
AppTrustedRoot trustedRoot;
UniqueCERTCertList& builtChain;
};
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) || NS_WARN_IF(!voidContext)) {
if (NS_WARN_IF(!signerCert)) {
return NS_ERROR_INVALID_ARG;
}
const VerifyCertificateContext& context =
*static_cast<const VerifyCertificateContext*>(voidContext);
AppTrustDomain trustDomain(context.builtChain, pinArg);
nsresult rv = trustDomain.SetTrustedRoot(context.trustedRoot);
// TODO: pinArg is null.
AppTrustDomain trustDomain(builtChain, nullptr);
nsresult rv = trustDomain.SetTrustedRoot(trustedRoot);
if (NS_FAILED(rv)) {
return rv;
}
@ -763,23 +755,151 @@ VerifyCertificate(CERTCertificate* signerCert, void* voidContext, void* pinArg)
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
VerifySignature(AppTrustedRoot trustedRoot, const SECItem& buffer,
const SECItem& detachedDigest,
const SECItem& detachedSHA1Digest,
const SECItem& detachedSHA256Digest,
/*out*/ SECOidTag& digestAlgorithm,
/*out*/ UniqueCERTCertList& builtChain)
{
// 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
// CryptoTask implementation should already hold a nsNSSShutDownPreventionLock.
// We acquire a nsNSSShutDownPreventionLock here solely to prove we did to
// VerifyCMSDetachedSignatureIncludingCertificate().
nsNSSShutDownPreventionLock locker;
VerifyCertificateContext context = { trustedRoot, builtChain };
// XXX: missing pinArg
return VerifyCMSDetachedSignatureIncludingCertificate(buffer, detachedDigest,
VerifyCertificate,
&context, nullptr,
locker);
if (NS_WARN_IF(!buffer.data || buffer.len == 0 || !detachedSHA1Digest.data ||
detachedSHA1Digest.len == 0 || !detachedSHA256Digest.data ||
detachedSHA256Digest.len == 0)) {
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_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
@ -818,24 +938,39 @@ OpenSignedAppFile(AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
// Signature (SF) file
nsAutoCString sfFilename;
ScopedAutoSECItem sfBuffer;
Digest sfCalculatedDigest;
rv = FindAndLoadOneEntry(zip, NS_LITERAL_CSTRING(JAR_SF_SEARCH_STRING),
sfFilename, sfBuffer, SEC_OID_SHA1,
&sfCalculatedDigest);
sfFilename, sfBuffer);
if (NS_FAILED(rv)) {
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
}
sigBuffer.type = siBuffer;
UniqueCERTCertList builtChain;
rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedDigest.get(),
builtChain);
// 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;
}
DigestWithAlgorithm mfDigest;
rv = ParseSF(BitwiseCast<char*, unsigned char*>(sfBuffer.data), mfDigest);
sigBuffer.type = siBuffer;
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)) {
return rv;
}
@ -845,7 +980,7 @@ OpenSignedAppFile(AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
ScopedAutoSECItem manifestBuffer;
Digest mfCalculatedDigest;
rv = FindAndLoadOneEntry(zip, NS_LITERAL_CSTRING(JAR_MF_SEARCH_STRING),
mfFilename, manifestBuffer, mfDigest.mAlgorithm,
mfFilename, manifestBuffer, digestToUse,
&mfCalculatedDigest);
if (NS_FAILED(rv)) {
return rv;
@ -853,7 +988,7 @@ OpenSignedAppFile(AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
nsDependentCSubstring calculatedDigest(
DigestToDependentString(mfCalculatedDigest));
if (!mfDigest.mDigest.Equals(calculatedDigest)) {
if (!mfDigest.Equals(calculatedDigest)) {
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
}
@ -865,7 +1000,7 @@ OpenSignedAppFile(AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
nsTHashtable<nsCStringHashKey> items;
rv = ParseMF(BitwiseCast<char*, unsigned char*>(manifestBuffer.data), zip,
mfDigest.mAlgorithm, items, buf);
digestToUse, items, buf);
if (NS_FAILED(rv)) {
return rv;
}
@ -1393,25 +1528,40 @@ VerifySignedDirectory(AppTrustedRoot aTrustedRoot,
+ NS_LITERAL_STRING("sf"));
ScopedAutoSECItem sfBuffer;
Digest sfCalculatedDigest;
rv = LoadOneMetafile(metaDir, sfFilename, sfBuffer, SEC_OID_SHA1,
&sfCalculatedDigest);
rv = LoadOneMetafile(metaDir, sfFilename, sfBuffer);
if (NS_FAILED(rv)) {
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;
UniqueCERTCertList builtChain;
rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedDigest.get(),
builtChain);
SECOidTag digestToUse;
rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedSHA1Digest.get(),
sfCalculatedSHA256Digest.get(), digestToUse, builtChain);
if (NS_FAILED(rv)) {
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
}
// Get the expected manifest hash from the signed .sf file
DigestWithAlgorithm mfDigest;
rv = ParseSF(BitwiseCast<char*, unsigned char*>(sfBuffer.data), mfDigest);
nsAutoCString mfDigest;
rv = ParseSF(BitwiseCast<char*, unsigned char*>(sfBuffer.data), digestToUse,
mfDigest);
if (NS_FAILED(rv)) {
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
}
@ -1421,7 +1571,7 @@ VerifySignedDirectory(AppTrustedRoot aTrustedRoot,
nsAutoString mfFilename(NS_LITERAL_STRING("manifest.mf"));
ScopedAutoSECItem manifestBuffer;
Digest mfCalculatedDigest;
rv = LoadOneMetafile(metaDir, mfFilename, manifestBuffer, mfDigest.mAlgorithm,
rv = LoadOneMetafile(metaDir, mfFilename, manifestBuffer, digestToUse,
&mfCalculatedDigest);
if (NS_FAILED(rv)) {
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
@ -1429,7 +1579,7 @@ VerifySignedDirectory(AppTrustedRoot aTrustedRoot,
nsDependentCSubstring calculatedDigest(
DigestToDependentString(mfCalculatedDigest));
if (!mfDigest.mDigest.Equals(calculatedDigest)) {
if (!mfDigest.Equals(calculatedDigest)) {
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
}
@ -1442,7 +1592,7 @@ VerifySignedDirectory(AppTrustedRoot aTrustedRoot,
nsTHashtable<nsStringHashKey> items;
rv = ParseMFUnpacked(BitwiseCast<char*, unsigned char*>(manifestBuffer.data),
aDirectory, mfDigest.mAlgorithm, items, buf);
aDirectory, digestToUse, items, buf);
if (NS_FAILED(rv)){
return rv;
}

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

@ -5,22 +5,12 @@
#include "nsDataSignatureVerifier.h"
#include "ScopedNSSTypes.h"
#include "SharedCertVerifier.h"
#include "cms.h"
#include "cryptohi.h"
#include "keyhi.h"
#include "mozilla/Base64.h"
#include "mozilla/Casting.h"
#include "mozilla/Unused.h"
#include "nsCOMPtr.h"
#include "nsNSSComponent.h"
#include "nsString.h"
#include "pkix/pkixnss.h"
#include "pkix/pkixtypes.h"
#include "secerr.h"
using namespace mozilla;
using namespace mozilla::pkix;
using namespace mozilla::psm;
SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate)
@ -134,117 +124,3 @@ nsDataSignatureVerifier::VerifyData(const nsACString& aData,
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
#define nsDataSignatureVerifier_h
#include "certt.h"
#include "nsIDataSignatureVerifier.h"
#include "nsNSSShutDown.h"
@ -33,15 +32,4 @@ private:
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

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

@ -35,6 +35,7 @@
#include "sslproto.h"
using namespace mozilla;
using namespace mozilla::pkix;
using namespace mozilla::psm;
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_CA_CERT_INVALID = SEC_ERROR_BASE + 36;
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_CERT_TYPE = SEC_ERROR_BASE + 91;
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:
hash:<hex string>
sha1:<hex string>
sha256:<hex string>
signer:
<pycert specification>
hash is the value that will be put in the messageDigest attribute in
each SignerInfo of the signerInfos field of the SignedData.
Eith or both of sha1 and sha256 may be specified. The value of
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.
Currently only SHA-1 is supported.
"""
from pyasn1.codec.der import decoder
@ -52,7 +58,8 @@ class CMS(object):
generating a CMS message"""
def __init__(self, paramStream):
self.hash = ''
self.sha1 = ''
self.sha256 = ''
signerSpecification = StringIO.StringIO()
readingSignerSpecification = False
for line in paramStream.readlines():
@ -60,8 +67,10 @@ class CMS(object):
print >>signerSpecification, line.strip()
elif line.strip() == 'signer:':
readingSignerSpecification = True
elif line.startswith('hash:'):
self.hash = line.strip()[len('hash:'):]
elif line.startswith('sha1:'):
self.sha1 = line.strip()[len('sha1:'):]
elif line.startswith('sha256:'):
self.sha256 = line.strip()[len('sha256:'):]
else:
raise UnknownDirectiveError(line.strip())
signerSpecification.seek(0)
@ -97,11 +106,15 @@ class CMS(object):
"""Given a pykey hash algorithm identifier, builds an
AlgorithmIdentifier for use with pyasn1."""
if pykeyHash == pykey.HASH_SHA1:
algorithmIdentifier = rfc2459.AlgorithmIdentifier()
algorithmIdentifier['algorithm'] = univ.ObjectIdentifier('1.3.14.3.2.26')
algorithmIdentifier['parameters'] = univ.Null()
return algorithmIdentifier
raise pykey.UnknownHashAlgorithmError(pykeyHash)
oidString = '1.3.14.3.2.26'
elif pykeyHash == pykey.HASH_SHA256:
oidString = '2.16.840.1.101.3.4.2.1'
else:
raise pykey.UnknownHashAlgorithmError(pykeyHash)
algorithmIdentifier = rfc2459.AlgorithmIdentifier()
algorithmIdentifier['algorithm'] = univ.ObjectIdentifier(oidString)
algorithmIdentifier['parameters'] = univ.Null()
return algorithmIdentifier
def buildSignerInfo(self, certificate, pykeyHash, digestValue):
"""Given a pyasn1 certificate, a pykey hash identifier
@ -157,7 +170,12 @@ class CMS(object):
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
encoded = encoder.encode(signedData)

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

@ -33,14 +33,15 @@ def walkDirectory(directory):
return paths
def signZip(appDirectory, outputFile, issuerName, manifestHashes,
signatureHashes, doSign):
signatureHashes, pkcs7Hashes, doSign):
"""Given a directory containing the files to package up,
an output filename to write to, the name of the issuer of
the signing certificate, a list of hash algorithms to use in
the manifest file, a similar list for the signature file,
and whether or not to actually sign the resulting package,
packages up the files in the directory and creates the
output as appropriate."""
a similar list for the pkcs#7 signature, and whether or not
to actually sign the resulting package, packages up the
files in the directory and creates the output as
appropriate."""
mfEntries = []
with zipfile.ZipFile(outputFile, 'w') as outZip:
@ -66,7 +67,12 @@ def signZip(appDirectory, outputFile, issuerName, manifestHashes,
base64hash = b64encode(hashFunc(mfContents).digest())
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 + \
'subject:xpcshell signed app test signer\n' + \
'extension:keyUsage:digitalSignature'
@ -118,12 +124,20 @@ def main(outputFile, appPath, *args):
parser.add_argument('-s', '--signature-hash', action='append',
help='Hash algorithms to use in signature file',
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)
if len(parsed.manifest_hash) == 0:
parsed.manifest_hash.append('sha256')
if len(parsed.signature_hash) == 0:
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,
map(hashNameToFunctionAndIdentifier, parsed.manifest_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
// 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) {
return new Promise((resolve, reject) => {
@ -28,7 +28,7 @@ add_task(async function() {
.QueryInterface(Ci.nsIObserver);
let certdb = Cc["@mozilla.org/security/x509certdb;1"]
.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 = [];
for (let i = 0; i < 25; i++) {

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

@ -1,27 +1,18 @@
"use strict";
/* To regenerate the certificates and apps for this test:
cd security/manager/ssl/tests/unit/test_signed_apps
PATH=$NSS/bin:$NSS/lib:$PATH ./generate.sh
cd ../../../../../..
make -C $OBJDIR/security/manager/ssl/tests
// Tests the API nsIX509CertDB.openSignedAppFileAsync, which backs add-on
// signature verification. Testcases include various ways of tampering with
// add-ons as well as different hash algorithms used in the various
// signature/metadata files.
$NSS is the path to NSS binaries and libraries built for the host platform.
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
// from prio.h
const PR_RDWR = 0x04;
const PR_CREATE_FILE = 0x08;
const PR_TRUNCATE = 0x20;
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
// 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"]);
}
add_test(function () {
certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("signed_app"),
check_open_result("valid", Cr.NS_OK));
});
var hashTestcases = [
// SHA-256 in PKCS#7 + SHA-256 present elsewhere => OK
{ name: "app_mf-1-256_sf-1-256_p7-1-256",
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 () {
certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("signed_app_sha256"),
check_open_result("valid with sha256 hashes in manifest", Cr.NS_OK));
});
// SHA-1 in PKCS#7 + SHA-1 present elsewhere => OK
{ name: "app_mf-1-256_sf-1-256_p7-1",
expectedResult: 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 () {
certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot,
original_app_path("signed_app_sha1_and_sha256"),
check_open_result("valid with sha1 and sha256 hashes in manifest", Cr.NS_OK));
});
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));
original_app_path("empty_signerInfos"),
check_open_result("the signerInfos in the PKCS#7 signature is empty",
Cr.NS_ERROR_CMS_VERIFY_NOT_SIGNED));
});
add_test(function () {
@ -217,15 +222,16 @@ add_test(function () {
// Sanity check to ensure a no-op tampering gives a valid result
add_test(function () {
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(
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));
});
add_test(function () {
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(
Ci.nsIX509CertDB.AppXPCShellRoot, tampered,
check_open_result("missing_rsa", Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED));
@ -233,7 +239,8 @@ add_test(function () {
add_test(function () {
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(
Ci.nsIX509CertDB.AppXPCShellRoot, tampered,
check_open_result("missing_sf", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID));
@ -241,7 +248,8 @@ add_test(function () {
add_test(function () {
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(
Ci.nsIX509CertDB.AppXPCShellRoot, tampered,
check_open_result("missing_manifest_mf",
@ -250,7 +258,8 @@ add_test(function () {
add_test(function () {
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(
Ci.nsIX509CertDB.AppXPCShellRoot, tampered,
check_open_result("missing_entry", Cr.NS_ERROR_SIGNED_JAR_ENTRY_MISSING));
@ -258,15 +267,47 @@ add_test(function () {
add_test(function () {
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(
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 () {
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!" } ]);
certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot, tampered,
@ -275,7 +316,7 @@ add_test(function () {
add_test(function () {
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!" } ]);
certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot, tampered,

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

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

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

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

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

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

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

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

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

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

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

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